feat(voice): Munsit and Fish Audio streaming TTS behind a resolve_tts factory - #123
Merged
Conversation
Extract the inline TTS dispatch in build_voice_session into resolve_tts (mirrors resolve_stt: aliases, ValueError on unknown, explicit fallback) with a provider_id class attr for client/log identity. Munsit now rejects out-of-range sample rates at connect instead of silently synthesizing at 24 kHz while playback clocks the session rate. Fish sends condition_on_previous_chunks explicitly so the voice-consistency claim is on the wire, not just in the docstring. Co-authored-by: Cursor <cursoragent@cursor.com>
…only The live voice pipeline needs only the VoiceSession providers; batch TTS / voice-discovery agent tools for Munsit (and eventually Fish Audio) can land together in a follow-up. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit ac66f1f. Configure here.
…S fallback An unknown tts_provider swapped in ElevenLabs but kept the requested tts_model and voice, so the session reported elevenlabs while putting a Munsit/Fish model id — and voice id, which is a path segment — on the stream-input URL. TTS died on a config that read as correct in the logs. The STT fallback directly above already clears its requested model for exactly this reason; TTS never got the same treatment. Clear the model on fallback and substitute the default voice rather than clearing it: connect() rejects an empty voice, so None would turn a broken socket into a hard session failure. Also give ElevenLabs the foreign-id guard Munsit and Fish already have. It was the only provider taking cfg.model unchecked, which made the same failure reachable without any fallback — a stale server-wide tts_model alongside tts_provider="elevenlabs". Prefix-matched rather than an allowlist, since every ElevenLabs model carries the eleven prefix and new ones ship regularly.
pedrolivaresanchez
approved these changes
Aug 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
MUNSIT_API_KEY) and Fish Audio (FISH_API_KEY, msgpack framing — hence theormsgpackserver extra).resolve_tts()invoice/providers.pyas the counterpart to the existingresolve_stt(). Provider ids are case-insensitive with aliases (el/11labs,faseeh,fish/fish-audio), and unknown ids raiseValueErrorso callers fall back explicitly instead of silently defaulting. Provider modules import lazily, so selecting Munsit never pulls in the ElevenLabs or Fish WebSocket stacks.VoiceConfiggainstts_provider(defaultelevenlabs), andTextToSpeechgains aprovider_idso a provider reports the config-style id the playground uses rather than its class name.voice.html/voice.py) exposes the TTS provider selector.Default behaviour is unchanged: with no
tts_providerset, ElevenLabs is still selected.Test plan
main: 3710 passed, 6 skipped, 7 xfailedtest_fish_audio.py(230 lines),test_munsit.py(182), plusresolve_ttscases intest_providers.pyNotes for review
mainis merged into the branch (ac66f1fc) — it was 5 commits behind, including the guardrails work from #122. The merge was conflict-free anduv.lockcarries only theormsgpackaddition.Made with Cursor