{"record":{"id":"0aa37f714c09d11d","repo":"moeru-ai/airi","slug":"invalid-speech-request-body","errorCode":null,"errorMessage":"Invalid speech request body","messagePattern":"Invalid speech request body","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/libs/providers/providers/voicevox/define.ts","lineNumber":207,"sourceCode":"function createVoicevoxConfigSchema(defaultBaseUrl: string) {\n  return z.object({\n    baseUrl: z.string().default(defaultBaseUrl),\n    voiceSettings: voicevoxVoiceSettingsSchema.default({ intonation: 1, pitch: 0, speed: 1, volume: 1 }),\n  })\n}\n\n/**\n * Reads the segment text and the style id out of the OpenAI-shaped body that\n * `generateSpeech` builds.\n *\n * `requestBody` in `@xsai/shared` passes that object through `objCamelToSnake`.\n * Only single-word keys survive unchanged, and `input` and `voice` are two of\n * them. A key of more than one word arrives renamed. The synthesis parameters\n * therefore come from the provider configuration, not from this body.\n */\nfunction readSpeechRequest(init: RequestInit | undefined): { input: string, voice: string } {\n  if (!init?.body || typeof init.body !== 'string')\n    throw new Error('Invalid speech request body')\n\n  const body = JSON.parse(init.body) as { input?: string, voice?: string }\n  if (!body.voice)\n    throw new Error('No voice selected. Pick a character in the speech settings.')\n\n  return { input: body.input ?? '', voice: body.voice }\n}\n\nfunction toVoiceInfo(providerId: string, speakerName: string, style: { id: number, name: string }): VoiceInfo {\n  return {\n    id: String(style.id),\n    languages: [{ code: 'ja', title: 'Japanese' }],\n    name: `${speakerName} / ${style.name}`,\n    provider: providerId,\n  }\n}\n","sourceCodeStart":189,"sourceCodeEnd":224,"githubUrl":"https://github.com/moeru-ai/airi/blob/9c213115f8bd0fff9e6eabab02b077ac32da21be/packages/stage-ui/src/libs/providers/providers/voicevox/define.ts#L189-L224","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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 })"],"exampleFix":"// before\nprovider.speech(new Request('/speech', { method: 'POST', body: JSON.stringify({ input, voice }) }))\n// after\nprovider.speech({ method: 'POST', body: JSON.stringify({ input, voice }) })","handlingStrategy":"validation","validationCode":"function hasJsonBody(init) {\n  return typeof init?.body === 'string' && init.body.length > 0\n}\nif (!hasJsonBody(init))\n  init = { method: 'POST', body: JSON.stringify({ input, voice }) }","typeGuard":"const isStringBodyInit = (init) => typeof init?.body === 'string'","tryCatchPattern":"try {\n  const request = readSpeechRequest(init)\n}\ncatch (error) {\n  if (error.message === 'Invalid speech request body')\n    console.error('Expected a RequestInit with a JSON string body containing { input, voice }, got:', typeof init?.body)\n  else if (error.message.startsWith('No voice selected'))\n    openSpeechSettings()\n  else throw error\n}","preventionTips":["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"],"tags":["validation","voicevox","speech","request-body"],"backgroundTag":"invalid-request-body","analyzedSha":"9c213115f8bd0fff9e6eabab02b077ac32da21be","analyzedAt":"2026-09-02T04:27:24.639Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}