moeru-ai/airi · error · Error
Unknown Kokoro voice: ${body.voice}
Error message
Unknown Kokoro voice: ${body.voice} What it means
Inside the provider's fetch shim, after the kokoro adapter loads, the requested body.voice must be a key of adapter.getVoices(); otherwise the request is rejected before synthesis runs. The available set is defined by the adapter's voice data, so validity depends on which voices that adapter version ships.
Source
Thrown at packages/stage-ui/src/libs/providers/providers/kokoro-local/index.ts:99
},
createProvider() {
const adapterPromise = getKokoroAdapter()
return {
speech: () => ({
baseURL: 'http://kokoro-local/v1/',
model: 'kokoro-82m',
fetch: async (_input: RequestInfo | URL, init?: RequestInit) => {
if (!init?.body || typeof init.body !== 'string')
throw new Error('Invalid request body')
const body = JSON.parse(init.body) as { input?: string, voice?: string }
if (!body.voice)
throw new Error('Voice parameter is required')
try {
const adapter = await adapterPromise
if (!(body.voice in adapter.getVoices()))
throw new Error(`Unknown Kokoro voice: ${body.voice}`)
const buffer = await adapter.generate(body.input ?? '', body.voice as VoiceKey)
return new Response(buffer, {
status: 200,
headers: { 'Content-Type': 'audio/wav' },
})
}
catch (error) {
console.error('Kokoro TTS generation failed:', error)
throw error
}
},
}),
}
},
validationRequiredWhen: () => false,
validators: {
validateConfig: [
({ t }) => ({View on GitHub (pinned to 677329427f)
Solutions
- Use a voice id from adapter.getVoices() — e.g. af_heart, af_bella, am_michael
- Populate the voice picker from the adapter's actual voices, not a static list
- Update the kokoro voice data files to the version matching the requested ids
- Re-select the voice in the UI after upgrading the provider
Example fix
// before
body: JSON.stringify({ input: text, voice: 'alloy' }) // OpenAI voice id → throw
// after
body: JSON.stringify({ input: text, voice: 'af_heart' }) // key of adapter.getVoices() Defensive patterns
Strategy: validation
Validate before calling
const voices = adapter.getVoices() const voice = voiceId in voices ? voiceId : (Object.keys(voices)[0] as string)
Type guard
function isKokoroVoice(voice: string, voices: Record<string, unknown>): voice is VoiceKey {
return voice in voices
} Try / catch
try {
await generate(text, voice)
}
catch (err) {
if (err.message.startsWith('Unknown Kokoro voice:'))
await generate(text, 'af_heart') // fall back to a known voice
} Prevention
- Source voice dropdowns from the loaded adapter's getVoices()
- Map OpenAI voice names to kokoro equivalents in compatibility layers
- Validate persisted voice ids against the adapter after upgrades
When it happens
Trigger: Voice name typo (kokoro uses snake_case ids like af_heart); passing OpenAI TTS voice names (alloy, echo) through a compatibility layer; a persisted voice removed or renamed in a newer voices data file; the adapter loaded with a reduced voice bundle.
Common situations: Client code written against the OpenAI speech API reused for kokoro; voice list from a different kokoro version; user config carrying an outdated voice id.
Related errors
- Invalid model: ${modelId}. Must be one of: ${KOKORO_MODELS.m
- MiMo voice clone requires a base64 audio sample in data URI
- MiMo voice design requires a style prompt in the user messag
- audio voices upstream returned malformed body
- Kokoro TTS generation failed: No model loaded.
AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18).
Data as JSON: /api/errors/b40a7e23f18bb68a.
Report an issue: GitHub.