Added 60dB integration#218
Conversation
📝 WalkthroughWalkthroughthis pr adds 60db as a speech provider across the sdk. you get tts/stt REST calls wrapped in a provider class, ai sdk v3 integration with optional alignment recovery, model definitions for billing, provider selection in voice actions, and full test/doc coverage. Changes60db Text-to-Speech Provider Integration
Possibly related PRs
Poem
🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
src/ai-sdk/providers/60db.ts (1)
174-176: ⚡ Quick winunnecessary json round-trip
you're doing
JSON.parse(JSON.stringify({ sixtydb: { alignment } }))but can just assign the object directly. the alignment is already a plain object with plain arrays, so it's already json-serializable.skip the round-trip meow
if (words.length > 0) { const alignment = wordsToCharacterAlignment(words); - providerMetadata = JSON.parse( - JSON.stringify({ sixtydb: { alignment } }), - ); + providerMetadata = { sixtydb: { alignment } }; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/ai-sdk/providers/60db.ts` around lines 174 - 176, The assignment to providerMetadata uses an unnecessary JSON round-trip; replace providerMetadata = JSON.parse(JSON.stringify({ sixtydb: { alignment } })) with a direct object assignment like providerMetadata = { sixtydb: { alignment } } so the plain JS object is used without serializing/deserializing; update the code path that sets providerMetadata (the variable named providerMetadata in the sixtydb provider code where alignment is referenced) accordingly.src/providers/60db.ts (1)
100-100: 💤 Low valuejob id prefix doesn't match provider name
you're using
db60_but the provider name is60db. might wanna keep them consistent for easier debugging later.tiny consistency fix
- const jobId = `db60_${Date.now()}_${Math.random().toString(36).slice(2)}`; + const jobId = `60db_${Date.now()}_${Math.random().toString(36).slice(2)}`;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/providers/60db.ts` at line 100, The jobId prefix is inconsistent with the provider name: update the job id construction (the const jobId assignment that currently uses `db60_`) to use the provider name prefix `60db_` instead so identifiers match the provider; locate the `jobId` variable in this file (the template string using Date.now() and Math.random()) and replace the `db60_` prefix with `60db_` (and update any related places that rely on that exact prefix if present).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/definitions/models/60db.ts`:
- Line 14: The schema's text field documents a 5000-char cap but doesn't enforce
it—update the zod schema for the text property (the line defining text:
z.string().describe(...)) to include .max(5000) (and an appropriate error
message); separately, clamp the computed pricing to the declared bounds by
ensuring any function that returns minUsd/maxUsd (the pricing computation in
this module) enforces minUsd <= price <= maxUsd before returning (or returns the
clamped minUsd/maxUsd values), and apply the same fix to the related pricing
code referenced around the minUsd/maxUsd fields.
In `@src/providers/60db.ts`:
- Line 13: The code imports writeFileSync from "node:fs" and uses it to write
the TTS output; replace that synchronous node fs usage with Bun.file to make
textToSpeech fully async: remove the writeFileSync import, find the textToSpeech
function (and the line where writeFileSync is called) and use
Bun.file(...).write or .writeSync as appropriate (prefer .write which returns a
Promise) to write the speech buffer/bytes, ensuring you await the write so
textToSpeech remains async and returns only after the file is persisted.
---
Nitpick comments:
In `@src/ai-sdk/providers/60db.ts`:
- Around line 174-176: The assignment to providerMetadata uses an unnecessary
JSON round-trip; replace providerMetadata = JSON.parse(JSON.stringify({ sixtydb:
{ alignment } })) with a direct object assignment like providerMetadata = {
sixtydb: { alignment } } so the plain JS object is used without
serializing/deserializing; update the code path that sets providerMetadata (the
variable named providerMetadata in the sixtydb provider code where alignment is
referenced) accordingly.
In `@src/providers/60db.ts`:
- Line 100: The jobId prefix is inconsistent with the provider name: update the
job id construction (the const jobId assignment that currently uses `db60_`) to
use the provider name prefix `60db_` instead so identifiers match the provider;
locate the `jobId` variable in this file (the template string using Date.now()
and Math.random()) and replace the `db60_` prefix with `60db_` (and update any
related places that rely on that exact prefix if present).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 9b84d15f-373c-425b-9c9f-7117730e79e5
📒 Files selected for processing (18)
.claude/rules/video-generation.md.claude/settings.local.json.claude/skills/ffmpeg-mixing/SKILL.md.claude/skills/varg-video-generation/GUIDE.md.claude/skills/varg-video-generation/SKILL.md.env.exampleREADME.mdsrc/ai-sdk/index.tssrc/ai-sdk/providers/60db.tssrc/core/schema/shared.tssrc/definitions/actions/voice.tssrc/definitions/models/60db.tssrc/definitions/models/index.tssrc/providers/60db.tssrc/providers/index.tssrc/react/resolve.tssrc/tests/all.test.tssrc/tests/unit.test.ts
💤 Files with no reviewable changes (5)
- .claude/skills/varg-video-generation/SKILL.md
- .claude/rules/video-generation.md
- .claude/settings.local.json
- .claude/skills/varg-video-generation/GUIDE.md
- .claude/skills/ffmpeg-mixing/SKILL.md
|
|
||
| // Input schema with Zod | ||
| const sixtydbInputSchema = z.object({ | ||
| text: z.string().describe("Text to convert to speech (max 5000 characters)"), |
There was a problem hiding this comment.
enforce the declared 5k text cap and pricing bounds in code
line 14 documents a 5000-char cap but doesn’t validate it, and line 64 can return values outside the declared minUsd/maxUsd range. that makes pricing/bounds inconsistent for billing consumers.
suggested patch
const sixtydbInputSchema = z.object({
- text: z.string().describe("Text to convert to speech (max 5000 characters)"),
+ text: z
+ .string()
+ .min(1)
+ .max(5000)
+ .describe("Text to convert to speech (max 5000 characters)"),
voice_id: z.string().optional().describe("60db voice_id (UUID) to use"),
@@
pricing: {
"60db": {
description: "$0.00002 per character via 60db ($0.01 minimum).",
- calculate: ({ characters = 500 }) => 0.00002 * characters,
+ calculate: ({ characters = 500 }) => {
+ const boundedCharacters = Math.min(Math.max(characters, 0), 5000);
+ const raw = 0.00002 * boundedCharacters;
+ return Math.min(0.1, Math.max(0.01, raw));
+ },
minUsd: 0.01,
maxUsd: 0.1, // ~5,000 char max input
},
},Also applies to: 63-66
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/definitions/models/60db.ts` at line 14, The schema's text field documents
a 5000-char cap but doesn't enforce it—update the zod schema for the text
property (the line defining text: z.string().describe(...)) to include
.max(5000) (and an appropriate error message); separately, clamp the computed
pricing to the declared bounds by ensuring any function that returns
minUsd/maxUsd (the pricing computation in this module) enforces minUsd <= price
<= maxUsd before returning (or returns the clamped minUsd/maxUsd values), and
apply the same fix to the related pricing code referenced around the
minUsd/maxUsd fields.
| * speech providers are interchangeable behind the `voice` action. | ||
| */ | ||
|
|
||
| import { writeFileSync } from "node:fs"; |
There was a problem hiding this comment.
use bun.file instead of node:fs
the coding guidelines say to prefer Bun.file over node:fs readFile/writeFile. you're using this on line 200 for writing the output file.
✨ switch to bun.file api
-import { writeFileSync } from "node:fs";then on line 200:
if (outputPath) {
- writeFileSync(outputPath, buffer);
+ await Bun.write(outputPath, buffer);
console.log(`[60db] saved to ${outputPath}`);
}note: this makes textToSpeech fully async, which it already is meow
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/providers/60db.ts` at line 13, The code imports writeFileSync from
"node:fs" and uses it to write the TTS output; replace that synchronous node fs
usage with Bun.file to make textToSpeech fully async: remove the writeFileSync
import, find the textToSpeech function (and the line where writeFileSync is
called) and use Bun.file(...).write or .writeSync as appropriate (prefer .write
which returns a Promise) to write the speech buffer/bytes, ensuring you await
the write so textToSpeech remains async and returns only after the file is
persisted.
Source: Coding guidelines
What
Brief description of what this PR does.
Why
Why is this change needed?
How to test
Steps to verify the changes work correctly.
Checklist
bun run check