@miadi/voice
v0.3.3
Published
Miadi voice layer — Edge-TTS producer, persona/voice map, KV-backed ledger, and the #457 channel adapter (Assembly-Voice absorption).
Readme
@miadi/voice
Let an agent or a server route speak: a persona reads a message aloud with Edge-TTS, the audio is stored, and the message joins a shared ledger that says who spoke and where to answer them.
In the Miadi stack, voice is how agents at work report to people who are not reading a terminal. Every message carries its origin (the user, machine, multiplexer and pane that published it), so a listener can reply to the right agent, and it can be bound to the episode it belongs to. This package is the capability and holds the credentials. Callers usually reach it through a service:
@miadi/voice-client: HTTP access to the voice routes, with the token tiers.@miadi/voice-mcp: an MCP server that lets an agent play, find and publish voice.
Edge-TTS is free and needs no API key.
Install
npm i @miadi/voiceThe package ships TypeScript source (src/index.ts) and a Python script, so it runs inside a
server that compiles TypeScript (in Next.js, add it to transpilePackages). Producing audio
needs Python 3 with the edge-tts package.
Surface
| export | purpose |
|---|---|
| produce(options, deps?) | text → Edge-TTS → audio store → ledger. Refuses a message with no answerable origin before any audio is made. |
| ASSEMBLY_PERSONAS, PERSONA_IDS, getPersona | the personas |
| VOICE_MAP, resolveVoice, isKnownPersona | persona and language → Edge-TTS voice |
| resolveOrigin, checkOriginInput, requireOrigin, originTarget, probeOriginTarget | the return address a message carries, and its validation |
| KvVoiceLedger, getLedger | the shared ledger, on a KV REST store |
| LocalAudioStore, BlobAudioStore, getAudioStore | where audio lives: a local directory by default, Vercel Blob when a token is set |
| AssemblyVoiceAdapter, getAssemblyVoiceAdapter | a channel adapter that emits assembly.voice.ready for each new voice |
| schemas/voice-message.schema.json, schemas/persona.schema.json | JSON Schemas for a message and a persona |
produce takes text, optional persona and lang, a required origin, and optional
episode (a chronicle folder name, stored where the message's own metadata cannot override
it).
Voices
| persona | voice |
|---|---|
| aureon | fr-FR-DeniseNeural |
| salix | fr-FR-HenriNeural |
| nyro | en-US-AriaNeural |
| jamai | en-US-AndrewNeural |
| synth, jerry | en-US-GuyNeural |
| ava | en-US-AvaNeural |
| tushell | en-GB-RyanNeural |
| mia | en-US-EmmaNeural |
| miette | en-US-JennyNeural |
| mino | en-CA-LiamNeural |
| atlas | en-US-ChristopherNeural |
Without a persona, French speaks as aureon and English as nyro. An unknown persona falls
back to the language's default.
Configuration
| variable | default | used for |
|---|---|---|
| KV_REST_API_URL, KV_REST_API_TOKEN | none | the ledger and live broadcast |
| ASSEMBLY_VOICE_AUDIO_DIR | /srv/miadi/voice-audio | local audio store |
| BLOB_READ_WRITE_TOKEN | unset | selects the Vercel Blob store (needs @vercel/blob installed) |
| ASSEMBLY_VOICE_PYTHON | /usr/bin/python3 | the interpreter that runs the Edge-TTS script |
| ASSEMBLY_VOICE_TTS_SCRIPT | the package's scripts/tts-generate.py | the Edge-TTS script |
| ASSEMBLY_VOICE_TTS_TIMEOUT_MS | 120000 | limit on one generation |
| ASSEMBLY_VOICE_MAX_TEXT | 4000 | longest message, in characters |
| ASSEMBLY_VOICE_TMP | the system temp dir | scratch space during generation |
| ASSEMBLY_VOICE_KV_PREFIX | assembly-voice | key prefix in the KV store |
| ASSEMBLY_VOICE_STREAM_KEY | assembly:voice:events | the stream the adapter appends to |
| ASSEMBLY_VOICE_PUBSUB_CHANNEL | assembly-voice-events | the channel the adapter publishes on |
| ASSEMBLY_VOICE_DEFAULT_PWD | unset | the working directory shown on a message when the publisher declared none |
| MIADI_VOICE_TMUX_BIN / TIDE_TMUX_BIN, MIADI_VOICE_HERDR_BIN / TIDE_HERDR_BIN | tmux, herdr on PATH | checking that a declared pane exists |
No paid TTS, and no secret is committed.
