Add Telnyx streaming TTS provider - #74
Conversation
|
thank you @a692570 |
|
Thanks @jason-shen, and for approving the workflow run so quickly. Follow-up on the AGENTS.md lockstep rule: is streamcore.ai/llms-full.txt regenerated from docs/, or should I send the Telnyx section over for a manual update? I'm also looking at Telnyx STT as a follow-up provider. There's one design wrinkle around barge-in, so I'll open an issue with the analysis first. |
|
Hey Abhishek
Cheers for the pr, no that doesn't get sync automatically, i am ok if u just do a pr in that if you good with that
Cheers
Jason
Get Outlook for iOS<https://aka.ms/o0ukef>
…________________________________
From: Abhishek Sharma ***@***.***>
Sent: Friday, 04 September 2026 01:09:34
To: streamcoreai/streamcore-server ***@***.***>
Cc: Jason Shen ***@***.***>; Mention ***@***.***>
Subject: Re: [streamcoreai/streamcore-server] Add Telnyx streaming TTS provider (PR #74)
[https://avatars.githubusercontent.com/u/182830946?s=20&v=4]a692570 left a comment (streamcoreai/streamcore-server#74)<#74 (comment)>
Thanks @jason-shen<https://github.com/jason-shen>, and for approving the workflow run so quickly.
Follow-up on the AGENTS.md lockstep rule: is streamcore.ai/llms-full.txt regenerated from docs/, or should I send the Telnyx section over for a manual update?
I'm also looking at Telnyx STT as a follow-up provider. There's one design wrinkle around barge-in, so I'll open an issue with the analysis first.
—
Reply to this email directly, view it on GitHub<#74?email_source=notifications&email_token=AANWB42OZKHQ3BSTMZPZYBT5NGQU5A5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKNJSHEZTAMRYGA32M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5529302807>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AANWB433HCRBUFX6LIAUDKL5NGQU5AVCNFSNUABGKJSXA33TNF2G64TZHMYTCOJUGUYDQNJYGE5US43TOVSTWNJTGI3DIMJWHEYTBILWAI>.
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
|
Got it, thanks Jason. One question then: where does the site source live? If it's a repo I can reach, I'll PR the Telnyx section there. If it's hand-edited, here's the addition ready to paste into section 3.4, matching the existing block style: [telnyx] # TTS. Telnyx streaming text-to-speech
api_key = "" # or TELNYX_API_KEY env var
voice = "" # defaults to Telnyx.Qwen3TTS.d9348e0d-988a-42cc-a64e-18093fe45c03 ("Delta");
# any id from GET /v2/text-to-speech/voices, availability varies by account
voice_speed = 1.0 # clamped to 0.8-1.2And the TTS provider list line gains provider = "cartesia" # cartesia | deepgram | elevenlabs | speechify | vibevoice | minimax | mimo | telnyxWhile in there I noticed the same list is missing No rush at all on the #75 design question. |
|
Ah you mean on the live site, if thats what you mean then i will have to do it, that site is private its not public
Just send what you need to add then i can add it, yeah will have to do those other providers too
Let me know
Jason
Get Outlook for iOS<https://aka.ms/o0ukef>
…________________________________
From: Abhishek Sharma ***@***.***>
Sent: Friday, 04 September 2026 01:47:04
To: streamcoreai/streamcore-server ***@***.***>
Cc: Jason Shen ***@***.***>; Mention ***@***.***>
Subject: Re: [streamcoreai/streamcore-server] Add Telnyx streaming TTS provider (PR #74)
[https://avatars.githubusercontent.com/u/182830946?s=20&v=4]a692570 left a comment (streamcoreai/streamcore-server#74)<#74 (comment)>
Got it, thanks Jason.
One question then: where does the site source live? If it's a repo I can reach, I'll PR the Telnyx section there. If it's hand-edited, here's the addition ready to paste into section 3.4, matching the existing block style:
[telnyx] # TTS. Telnyx streaming text-to-speech
api_key = "" # or TELNYX_API_KEY env var
voice = "" # defaults to Telnyx.Qwen3TTS.d9348e0d-988a-42cc-a64e-18093fe45c03 ("Delta");
# any id from GET /v2/text-to-speech/voices, availability varies by account
voice_speed = 1.0 # clamped to 0.8-1.2
And the TTS provider list line gains | telnyx:
provider = "cartesia" # cartesia | deepgram | elevenlabs | speechify | vibevoice | minimax | mimo | telnyx
While in there I noticed the same list is missing volcengine and aliyun TTS (both merged around three weeks ago, #65<#65> and #68<#68>). Happy to draft those blocks too so one paste brings the file current.
No rush at all on the #75<#75> design question.
—
Reply to this email directly, view it on GitHub<#74?email_source=notifications&email_token=AANWB46PTAV3SY2FZDI5AKL5NGVBRA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKNJSHE3TMMZTGY22M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5529763365>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AANWB45RUXSDZ4JFC67E3GT5NGVBRAVCNFSNUABGKJSXA33TNF2G64TZHMYTCOJUGUYDQNJYGE5US43TOVSTWNJTGI3DIMJWHEYTBILWAI>.
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
|
Here's the complete paste, all three providers. Four edits to section 3.4: 1. Replace the TTS provider list line: provider = "cartesia" # aliyun | cartesia | deepgram | elevenlabs | mimo | minimax | speechify | telnyx | vibevoice | volcengine(The STT provider list line is already current, no change needed there.) 2. Add after [telnyx] # TTS. Telnyx streaming text-to-speech
api_key = "" # or TELNYX_API_KEY env var
voice = "" # defaults to Telnyx.Qwen3TTS.d9348e0d-988a-42cc-a64e-18093fe45c03 ("Delta");
# any id from GET /v2/text-to-speech/voices, availability varies by account,
# a voice your key is not provisioned for fails the dial with HTTP 403
voice_speed = 1.0 # clamped to 0.8-1.23. Replace [aliyun] # STT and TTS. Alibaba Cloud Model Studio (DashScope)
api_key = ""
model = "" # STT; defaults to paraformer-realtime-v2; fun-asr-realtime is the alternative
language = "" # STT. "zh", "en"; empty auto-detects
vocabulary_id = "" # STT. hotword list created in the console
tts_model = "" # TTS; defaults to cosyvoice-v2
voice = "" # TTS; defaults to longxiaochun_v2. Voices are tied to a model generation:
# a v1 voice on cosyvoice-v2 is rejected, change tts_model and voice together
url = "" # optional; defaults to wss://dashscope.aliyuncs.com/api-ws/v1/inference4. Replace [volcengine] # STT and TTS. Doubao streaming ASR / seed-tts
api_key = "" # console API key, sent as X-Api-Key; the app-id + access-token pair is rejected
resource_id = "" # STT; defaults to volc.seedasr.sauc.duration (hourly billing)
model = "" # STT; defaults to bigmodel
url = "" # optional; defaults to wss://openspeech.bytedance.com/api/v3/sauc/bigmodel
end_window_ms = 0 # STT. silence that settles an utterance; defaults to 800
tts_resource_id = "" # TTS; defaults to seed-tts-2.0. The 1.0 resource is a separate entitlement
# and answers 403 until it is activatedAll fields verified against the current |
What this adds
A
telnyxTTS provider speaking Telnyx's streaming text-to-speech WebSocket API (wss://api.telnyx.com/v2/text-to-speech/speech?voice=...&audio_format=linear16&sample_rate=16000). Selected withtts.provider = "telnyx", configured through a new[telnyx]section (api_key, voice, voice_speed), overridable viaTELNYX_API_KEY, documented in English and Chinese.Why one WebSocket per utterance
This design is verified against the live protocol, not a guess. The server emits its
isFinalframe only after the client sends an empty-text teardown ({"text": ""}). There is no per-utterance completion marker, and plain text frames are buffered without starting synthesis (verified: two sentences sent 12s apart produced zero audio until teardown). Instead of a persistent connection with idle-gap or byte-estimate completion heuristics, the adapter opens a fresh connection per utterance: dial, init ({"text": " ", "voice_settings": {"voice_speed": ...}}), text withflush: true, teardown, stream audio untilisFinal, and the server closes 1000. Measured: 3.9s of audio synthesized in 1.6s wall with first audio 854ms after dial. Synthesis outpaces playback ~2.4x, so the per-utterance dial costs nothing on the live path and every completion heuristic is avoided.Protocol details that shaped the code:
typefield; routing is by keys:error, thenisFinal, thenaudio. Frames withaudio: nullandisFinalfalse (cache-status notifications) are skipped silently.{"force": true}(best-effort, 2s write deadline) and closes the connection; the server stops audio and never emits a final for the interrupted utterance.GET /v2/text-to-speech/voices.voice_speedrides the init frame, so per-utterance speed control works throughSynthesizeStreamWithControls, clamped to 0.8-1.2 like Cartesia's conversational band.[telnyx] voice_speedsets the baseline for untagged sentences.Telnyx.Qwen3TTS.d9348e0d-988a-42cc-a64e-18093fe45c03("Delta", female, Qwen3TTS family — Qwen3 voices use UUID catalog ids); availability varies by account.How I verified it
gofmt -l .clean,go build ./...,go vet ./...,go test -race ./...all pass, including the hard config gatesTestConfigExampleDocumentsEveryFieldandTestEveryCredentialHasAnEnvOverride.internal/tts/telnyx_test.go(no network, no key) cover frame routing (audio decode, final, cache-status skip, error frames, malformed input), the exact client wire-format bytes, speed clamping, and defaults.cmd/telnyx-smoke(removed before committing) streamed "Hello from the Telnyx TTS provider for StreamCore." with a live key using the default voice: 56 chunks, 179,968 PCM bytes, 5.62s at 16 kHz mono. (An earlier run on a Bayan voice measured 22 chunks / 3.72s with first audio 1.3s after dial; both were confirmed by ffprobe.)Maintainer question
AGENTS.md says
config.toml.example, the README provider table, and https://streamcore.ai/llms-full.txt should be updated together. llms-full.txt lives outside this repo: how is it regenerated, and should that happen as part of this PR or separately?One pre-existing gap I did not touch
The
[tts]supported-provider list in docs/configuration.md was already missingaliyunandvolcenginebefore this change (config.toml.example lists both). I addedtelnyxwithout altering that omission to keep the diff scoped; happy to fix it in a follow-up.