Skip to content

feat(voice): Munsit and Fish Audio streaming TTS behind a resolve_tts factory - #123

Merged
berges99 merged 5 commits into
mainfrom
voice/new-providers
Aug 10, 2026
Merged

feat(voice): Munsit and Fish Audio streaming TTS behind a resolve_tts factory#123
berges99 merged 5 commits into
mainfrom
voice/new-providers

Conversation

@berges99

@berges99 berges99 commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds two streaming TTS providers alongside ElevenLabs: Munsit (Arabic, MUNSIT_API_KEY) and Fish Audio (FISH_API_KEY, msgpack framing — hence the ormsgpack server extra).
  • Introduces resolve_tts() in voice/providers.py as the counterpart to the existing resolve_stt(). Provider ids are case-insensitive with aliases (el/11labs, faseeh, fish/fish-audio), and unknown ids raise ValueError so callers fall back explicitly instead of silently defaulting. Provider modules import lazily, so selecting Munsit never pulls in the ElevenLabs or Fish WebSocket stacks.
  • VoiceConfig gains tts_provider (default elevenlabs), and TextToSpeech gains a provider_id so a provider reports the config-style id the playground uses rather than its class name.
  • Playground (voice.html / voice.py) exposes the TTS provider selector.
  • Drops the Munsit agent tools that were in the first commit — the branch scope is voice TTS only.

Default behaviour is unchanged: with no tts_provider set, ElevenLabs is still selected.

Test plan

  • Full suite green after merging main: 3710 passed, 6 skipped, 7 xfailed
  • New coverage: test_fish_audio.py (230 lines), test_munsit.py (182), plus resolve_tts cases in test_providers.py
  • Manual smoke in the playground against live Munsit and Fish Audio keys (needs real credentials)

Notes for review

main is merged into the branch (ac66f1fc) — it was 5 commits behind, including the guardrails work from #122. The merge was conflict-free and uv.lock carries only the ormsgpack addition.

Made with Cursor

berges99 and others added 4 commits August 8, 2026 18:50
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>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread python/timbal/server/voice.py
…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.
@berges99
berges99 merged commit 12a75bb into main Aug 10, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants