moeru-ai/airi · error · Error
Invalid speech request body
Error message
Invalid speech request body
What it means
readSpeechRequest extracts { input, voice } from a fetch-style RequestInit body routed to the VOICEVOX provider adapter. It throws this error when the request has no body or the body is not a string, because the adapter expects an init object (as produced by $useProvider speech requests) rather than a live Request. Only input and voice are read; other synthesis parameters come from provider configuration.
Source
Thrown at packages/stage-ui/src/libs/providers/providers/voicevox/define.ts:207
function createVoicevoxConfigSchema(defaultBaseUrl: string) {
return z.object({
baseUrl: z.string().default(defaultBaseUrl),
voiceSettings: voicevoxVoiceSettingsSchema.default({ intonation: 1, pitch: 0, speed: 1, volume: 1 }),
})
}
/**
* Reads the segment text and the style id out of the OpenAI-shaped body that
* `generateSpeech` builds.
*
* `requestBody` in `@xsai/shared` passes that object through `objCamelToSnake`.
* Only single-word keys survive unchanged, and `input` and `voice` are two of
* them. A key of more than one word arrives renamed. The synthesis parameters
* therefore come from the provider configuration, not from this body.
*/
function readSpeechRequest(init: RequestInit | undefined): { input: string, voice: string } {
if (!init?.body || typeof init.body !== 'string')
throw new Error('Invalid speech request body')
const body = JSON.parse(init.body) as { input?: string, voice?: string }
if (!body.voice)
throw new Error('No voice selected. Pick a character in the speech settings.')
return { input: body.input ?? '', voice: body.voice }
}
function toVoiceInfo(providerId: string, speakerName: string, style: { id: number, name: string }): VoiceInfo {
return {
id: String(style.id),
languages: [{ code: 'ja', title: 'Japanese' }],
name: `${speakerName} / ${style.name}`,
provider: providerId,
}
}
View on GitHub (pinned to 9c213115f8)
Solutions
- Pass a RequestInit whose body is a JSON string containing at least { voice } and optionally { input }
- Ensure the calling hook passes the fetch init object, not a Request instance
- Use method POST with the serialized body instead of GET
- Stringify the payload: body: JSON.stringify({ input, voice })
Example fix
// before
provider.speech(new Request('/speech', { method: 'POST', body: JSON.stringify({ input, voice }) }))
// after
provider.speech({ method: 'POST', body: JSON.stringify({ input, voice }) }) Defensive patterns
Strategy: validation
Validate before calling
function hasJsonBody(init) {
return typeof init?.body === 'string' && init.body.length > 0
}
if (!hasJsonBody(init))
init = { method: 'POST', body: JSON.stringify({ input, voice }) } Type guard
const isStringBodyInit = (init) => typeof init?.body === 'string'
Try / catch
try {
const request = readSpeechRequest(init)
}
catch (error) {
if (error.message === 'Invalid speech request body')
console.error('Expected a RequestInit with a JSON string body containing { input, voice }, got:', typeof init?.body)
else if (error.message.startsWith('No voice selected'))
openSpeechSettings()
else throw error
} Prevention
- Pass the fetch init object, never a Request instance, to provider adapters
- Always serialize the body with JSON.stringify before calling
- Ensure the voice is selected in speech settings before synthesis
- Use POST so the request carries a body
When it happens
Trigger: Invoking the VOICEVOX speech adapter with undefined RequestInit, a Request object (whose body is a ReadableStream, not a string), a FormData/Blob body, or a fetch built with a GET method that has no body.
Common situations: Wiring the provider adapter to standard fetch onSend hooks that pass a Request instead of init; calling the adapter directly without a body; middleware that consumed or replaced the body stream.
Related errors
- Invalid request body
- Invalid request body
- No voice selected. Pick a character in the speech settings.
- The Base URL is not an absolute http:// or https:// address.
- Voice clone model requires a base64 audio sample in data URI
AI-assisted analysis of moeru-ai/airi@9c213115f8 (2026-09-02).
Data as JSON: /api/errors/0aa37f714c09d11d.
Report an issue: GitHub.