feat: add optional 60db text-to-speech provider - #694
uditgoenka wants to merge 1 commit into
Conversation
Expose workspace-scoped synthesis through the existing optional TTS plugin interface with native HTTP and validated PCM output. Constraint: Voice IDs and credentials belong to the user's workspace Confidence: high Scope-risk: narrow Tested: 206 focused tests; final 36 plugin tests; Ruff; mypy; extras; lock; wheel and sdist Not-tested: Live service synthesis and real call playback
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. 🧰 Additional context used📚 Code guidelines (1)📝 WalkthroughWalkthroughThe change adds a SIXTYDB TTS plugin for mono PCM16 audio at 24 kHz. The provider validates credentials, options, and text; sends synthesis requests; and parses supported response formats with audio validation. The change also registers the plugin in the workspace and optional dependencies. It includes local HTTP tests, usage documentation, and an example that writes streamed audio to a WAV file. Priority: ➖ Normal Merge Risk: 🔵 Low · up to The optional provider is mergeable with bounded follow-up: preserve service error details to simplify troubleshooting and align its annotations with repository guidance. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The integration is opt-in and includes redirect rejection, response limits, and stale-audio filtering. Interruptions do not cancel remote synthesis or its local worker. Live service behavior and credential permissions were not validated, so the assessment remains low rather than minimal risk. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
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: 1
🧹 Nitpick comments (1)
plugins/sixtydb/vision_agents/plugins/sixtydb/tts.py (1)
11-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReplace
Anywithobjectin_validateand_record_audio.Both methods take untrusted JSON and check its type at runtime with
isinstance(record, dict). Theobjecttype works the same way here and follows the repository rule. Inside_record_audio, usedict[str, object]once_validatehas run, or narrow the type with anisinstancecheck.As per coding guidelines: "Avoid using
Anytype."Also applies to: 145-145, 174-174
Source: Coding guidelines
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: GetStream/Vision-Agents/.coderabbit.yaml
Review profile: CHILL
Plan: Advanced
Run ID: e242679f-fdda-4022-a2c3-1ed76e9673b9
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (9)
agents-core/pyproject.tomlplugins/sixtydb/README.mdplugins/sixtydb/example/sixtydb_tts_example.pyplugins/sixtydb/pyproject.tomlplugins/sixtydb/tests/test_tts.pyplugins/sixtydb/vision_agents/plugins/sixtydb/__init__.pyplugins/sixtydb/vision_agents/plugins/sixtydb/py.typedplugins/sixtydb/vision_agents/plugins/sixtydb/tts.pypyproject.toml
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.
| except HTTPError as exc: | ||
| exc.close() | ||
| raise RuntimeError(f"60db synthesis failed (HTTP {exc.code})") from None |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
sed -n '1,240p' plugins/sixtydb/vision_agents/plugins/sixtydb/tts.py
sed -n '1,240p' plugins/sixtydb/tests/test_tts.py
cat plugins/sixtydb/README.mdRepository: GetStream/Vision-Agents
Length of output: 18366
🏁 Script executed:
#!/bin/bash
set -u
printf '%s\n' '--- relevant diff ---'
git diff --unified=20 902db86438aab03fb64aa7e6ea3afdcc867a3e80 dfd0cf7772d4c1ad880a5ff1c5602399200b45e8 -- plugins/sixtydb/vision_agents/plugins/sixtydb/tts.py plugins/sixtydb/tests/test_tts.py
printf '%s\n' '--- HTTP/error handling references ---'
rg -n -g '*.py' 'HTTPError|URLError|synthesis failed|connection failed|exc\.read|read\(2048\)|from None' .
printf '%s\n' '--- relevant project guidance and provider files ---'
rg -n -g 'CLAUDE.md' -g 'README.md' -g '*.py' 'error body|response body|redirect|timeout|HTTP error|diagnostic|60db|tts' plugins/sixtydb CLAUDE.md 2>/dev/null | head -240
printf '%s\n' '--- tracked relevant files ---'
git ls-files '*sixtydb*' '*tts*.py' | head -200Repository: GetStream/Vision-Agents
Length of output: 26840
🌐 Web query:
official 60db API documentation tts-synthesize HTTP errors response body error code quota invalid voice_id timeout redirect
💡 Result:
**Official docs:** 60db documents `POST https://api.60db.ai/tts-synthesize` and a general error-body shape:
```json
{
"success": false,
"error": "Error type",
"message": "Detailed error message"
}
```
The general API reference lists HTTP statuses including `402` (insufficient credits), `404`, `413`, `422`, `429`, `500`, and `503`; it says rate-limit headers include `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset`. ([docs.60db.ai](https://docs.60db.ai/api-reference/introduction))
**What’s not established in the docs:** The TTS endpoint page documents the request and successful response, but does not specify endpoint-specific error codes/bodies for quota, invalid `voice_id`, timeout, or redirects. So the general status list alone does not confirm which of those apply to TTS or the exact error code/body for each. It says to fetch valid voice IDs from `GET /voices`. ([docs.60db.ai](https://docs.60db.ai/api-reference/tts/text-to-speech))
Closest official references: [TTS endpoint](https://docs.60db.ai/api-reference/tts/text-to-speech) and [general API reference](https://docs.60db.ai/api-reference/introduction).
Citations:
- 1: https://docs.60db.ai/api-reference/introduction
- 2: https://docs.60db.ai/api-reference/tts/text-to-speech
🤖 get_repo_knowledge executed:
get_repo_knowledge GetStream/Vision-Agents /tmp/coderabbit-repo-knowledge/getstream-vision-agents-11d6373a/architecture /tmp/coderabbit-repo-knowledge/getstream-vision-agents-11d6373a/learnings
Length of output: 47118
🏁 Script executed:
#!/bin/bash
set -u
printf '%s\n' '--- relevant diff ---'
git diff --unified=20 902db86438aab03fb64aa7e6ea3afdcc867a3e80 dfd0cf7772d4c1ad880a5ff1c5602399200b45e8 -- plugins/sixtydb/vision_agents/plugins/sixtydb/tts.py plugins/sixtydb/tests/test_tts.py
printf '%s\n' '--- HTTP/error handling references ---'
rg -n -g '*.py' 'HTTPError|URLError|synthesis failed|connection failed|exc\.read|read\(2048\)|from None' .
printf '%s\n' '--- relevant project guidance and provider files ---'
rg -n -g 'CLAUDE.md' -g 'README.md' -g '*.py' 'error body|response body|redirect|timeout|HTTP error|diagnostic|60db|tts' plugins/sixtydb CLAUDE.md 2>/dev/null | head -240
printf '%s\n' '--- tracked relevant files ---'
git ls-files '*sixtydb*' '*tts*.py' | head -200Repository: GetStream/Vision-Agents
Length of output: 26840
🏁 Script executed:
git diff --unified=20 902db86438aab03fb64aa7e6ea3afdcc867a3e80 dfd0cf7772d4c1ad880a5ff1c5602399200b45e8 -- plugins/sixtydb/vision_agents/plugins/sixtydb/tts.py plugins/sixtydb/tests/test_tts.py
printf '\n--- references ---\n'
rg -n -g '*.py' 'HTTPError|URLError|synthesis failed|connection failed|exc\.read|from None' .
printf '\n--- provider guidance ---\n'
rg -n -g 'CLAUDE.md' -g 'README.md' -g '*.py' 'error body|response body|redirect|timeout|HTTP error|diagnostic|60db|tts' plugins/sixtydb CLAUDE.md 2>/dev/null | head -240Repository: GetStream/Vision-Agents
Length of output: 24968
Preserve 60db error details for non-redirect HTTP failures.
The 60db API documents error and detailed message fields in its error response. The current urllib.error.HTTPError handler discards that body and exposes only the status code. Keep redirects status-only and continue rejecting them.
Suggested fix
except HTTPError as exc:
- exc.close()
- raise RuntimeError(f"60db synthesis failed (HTTP {exc.code})") from None
+ detail = ""
+ try:
+ if not 300 <= exc.code < 400:
+ detail = (
+ exc.read(2048).decode("utf-8", errors="replace").strip()
+ )
+ finally:
+ exc.close()
+ message = f"60db synthesis failed (HTTP {exc.code})"
+ if detail:
+ message += f": {detail}"
+ raise RuntimeError(message) from None📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| except HTTPError as exc: | |
| exc.close() | |
| raise RuntimeError(f"60db synthesis failed (HTTP {exc.code})") from None | |
| except HTTPError as exc: | |
| detail = "" | |
| try: | |
| if not 300 <= exc.code < 400: | |
| detail = ( | |
| exc.read(2048).decode("utf-8", errors="replace").strip() | |
| ) | |
| finally: | |
| exc.close() | |
| message = f"60db synthesis failed (HTTP {exc.code})" | |
| if detail: | |
| message += f": {detail}" | |
| raise RuntimeError(message) from None |
Why
Applications using 60db workspace voices currently need a custom TTS adapter. 60db exposes an authenticated synthesis API with workspace-scoped voice IDs. This adds it as an optional provider through the existing Vision Agents TTS interface.
Changes
sixtydb.TTS()and thevision-agents[sixtydb]extra, using standard-library HTTP without an additional SDK dependency.SIXTYDB_API_KEY/SIXTYDB_VOICE_ID; accept optional model and speed settings.PcmData, and reject failed or incompatible responses.send_iter()path, interruptions, redirects, timeouts, and response limits.This provider synthesizes complete utterances and buffers the response. Interruptions discard stale output but do not cancel requests at the service.
Validation: 206 focused plugin, core TTS, and workspace source tests passed; all 36 plugin tests passed again after the final WAV-envelope fix. Ruff, provider mypy, extras validation, lockfile consistency, and wheel/sdist builds passed. Live synthesis and call playback were not tested because service credentials were unavailable.