moeru-ai/airi · error
OpenRouter audio response has no body
Error message
OpenRouter audio response has no body
What it means
After a successful (ok) response, the OpenRouter adapter still requires a streaming body to collect base64 PCM audio chunks. If response.body is null/undefined despite status ok, this error is thrown — the response cannot be decoded into audio.
Source
Thrown at packages/provider-inference/src/providers/cloud/openrouter-audio-speech/index.ts:116
const response = await globalThis.fetch(new URL('chat/completions', baseUrl), {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
...OPENROUTER_ATTRIBUTION_HEADERS,
},
body: JSON.stringify({
model,
messages: [{ role: 'user', content: ttsPrompt(body.input ?? '') }],
modalities: ['text', 'audio'],
audio: { voice: body.voice, format: 'pcm16' },
stream: true,
}),
})
if (!response.ok)
throw new Error(`OpenRouter audio request failed: ${response.status} ${await response.text()}`)
if (!response.body)
throw new Error('OpenRouter audio response has no body')
const wav = toWavFromPCM16(decodeBase64Pcm(await collectAudioChunks(response.body)), 24000)
return new Response(new Blob([wav], { type: 'audio/wav' }), {
status: 200,
headers: { 'Content-Type': 'audio/wav' },
})
}
}
export const providerOpenRouterAudioSpeech = defineProvider<OpenRouterAudioConfig, 'openrouter-audio-speech'>({
id: 'openrouter-audio-speech',
name: 'OpenRouter',
nameLocalize: ({ t }) => t('settings.pages.providers.provider.openrouter-audio-speech.title'),
description: 'openrouter.ai',
descriptionLocalize: ({ t }) => t('settings.pages.providers.provider.openrouter-audio-speech.description'),
tasks: ['text-to-speech'],
icon: 'i-lobe-icons:openrouter',
createProviderConfig: () => openRouterAudioConfigSchema,View on GitHub (pinned to f679616c34)
Solutions
- Ensure fetch is not mocked/intercepted in a way that drops the response body.
- Test outside service workers/proxies that may consume the stream.
- Confirm the runtime supports streaming response bodies (ReadabilityStream on Response); upgrade Node/browser if needed.
- Check that no middleware calls response.text()/arrayBuffer() before the adapter.
Example fix
// before (test mock)
fetch.mockResolvedValue(new Response(null, { status: 200 }))
// after
fetch.mockResolvedValue(new Response(sseStream, { status: 200 })) Defensive patterns
Strategy: fallback
Validate before calling
const res = await fetchOnce()
if (res.ok && !res.body) throw new Error('Runtime does not support streaming response bodies') Type guard
const hasBody = (res: Response): res is Response & { body: ReadableStream } => res.body != null Try / catch
try { await speak(text) } catch (e) { if (e.message.includes('no body')) { /* disable stream interception/mocks or switch runtime */ } else throw e } Prevention
- Don't mock fetch with body-less Responses in tests
- Avoid service workers that consume response streams
- Verify runtime supports ReadableStream response bodies
- Don't pre-consume response.body in middleware
When it happens
Trigger: A runtime or polyfilled fetch returning a body-less Response (e.g. some service-worker intercepts, mocks, or HTTP/1 environments without streaming support), or an interceptor that consumed the body before the adapter reads it.
Common situations: Testing with a fetch mock that returns Response without a body; service workers or proxies consuming the stream; older runtimes lacking ReadableStream response bodies.
Related errors
- MiMo transcription failed: ${response.status} ${response.sta
- OpenRouter audio response has no body
- Skipping malformed SSE chunk from OpenRouter audio stream:
- Streaming transcription request failed with status ${respons
- Streaming transcription response is missing a readable body.
AI-assisted analysis of moeru-ai/airi@f679616c34 (2026-09-08).
Data as JSON: /api/errors/9f8b11d65b10af51.
Report an issue: GitHub.