Repository navigation
feat(alerts): calls speak in an agent's own Kokoro voice, not a vendor's - #136
Merged
Merged
Conversation
Alert calls read the line with a generic Telnyx "female" voice. Our agents have voices of their own — riotcoder is kokoro/am_michael in its OpenProfile — and as of c0mpute 0.2.58 every c0mpute node provisions Kokoro itself, so the network can speak for us. Three tiers, best first: 1. Our own network. POST /v1/tts on the c0mpute speech gateway auctions a tts.synthesize job to a node running Kokoro and hands back a WAV; the call plays it with playback_start. No per-character bill, and the voice is the agent's. 2. Telnyx's copy of the same model (Telnyx.KokoroTTS.<voice>) when we have no gateway in reach — same voice, vendor round trip. 3. The old generic voice, only if Telnyx ever rejects a Kokoro voice id. An alert is worth more than the voice it arrives in, so every step fails soft: no gateway configured, a node that cannot be reached, a 200 that is not audio — each returns null and the call speaks the next way down. The rendered WAV waits in a disk cache under an HMAC-named path and /api/v1/alerts/audio/<token> serves it unauthenticated, because the only fetcher is Telnyx's media loader: the token is an HMAC of the call id under a server secret, the URL stops working after ten minutes, and the sweep runs on each write. Not /tmp — that is quota-bound on dev2. call.playback.ended now hangs up on the same h = 1 rule as call.speak.ended, so a played line ends the call exactly like a spoken one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ThreatCrush Security Scan14 finding(s) HIGH/CRITICAL: 2 | MEDIUM: 4 | LOW: 8
Snippets are redacted; ThreatCrush never prints matched credential material. |
| const token = audioToken(options.id, env); | ||
| try { | ||
| await mkdir(dir, { recursive: true }); | ||
| await writeFile(join(dir, `${token}.wav`), wav); |
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.
Anthony: "give my agents a voice using this instead of elevenlabs" →
"can't we use c0mpute.com?" → "use smshub.dev".
Alert calls read the line in a generic Telnyx
femalevoice. Our agents havevoices of their own (riotcoder is
kokoro/am_michaelin its OpenProfile),and since c0mpute 0.2.58 every c0mpute node provisions Kokoro itself — so the
network can speak for us, with no per-character bill.
Three tiers, best first
POST /v1/ttson the c0mpute speech gateway auctions atts.synthesizejob to a node running Kokoro and hands back a WAV; the callplays it with
playback_start.Telnyx.KokoroTTS.<voice>) when nogateway is in reach — same voice, vendor round trip, no hosting.
Every step fails soft — no gateway configured, a node that cannot be reached, a
200 that is not audio — because an alert is worth more than the voice it
arrives in.
The audio URL
The rendered WAV waits in a disk cache under an HMAC-named path, and
/api/v1/alerts/audio/<token>serves it unauthenticated, because the onlyfetcher is Telnyx's media loader. The token is an HMAC of the call id under a
server secret, so a path cannot be guessed or walked; the URL stops working
after ten minutes, and the sweep runs on each write. Not
/tmp— that isquota-bound on dev2.
call.playback.endednow hangs up on the sameh = 1rule ascall.speak.ended, so a played line ends a call exactly like a spoken one.Config
C0MPUTE_SPEECH_URL,C0MPUTE_SPEECH_TOKENALERT_KOKORO_VOICEaf_sarah,af_bella,am_onyx,am_michael(default)ALERT_AUDIO_DIR.cache/alert-audio)ALERT_AUDIO_SECRETTesting
and lint clean. 8 of those are new, covering the gateway being off, an empty
line, a non-absolute base URL, a refusing gateway, a throwing fetch, a 200
that is not audio, token path-walking (
../../etc/passwd), tokens being tiedto the secret, and the TTL sweep.
Telnyx.KokoroTTS.am_michaelrather than a generic voice.this: 15.56 s of 24 kHz audio in
am_michael, auctioned to a Kokoro node.🤖 Generated with Claude Code