moeru-ai/airi · error · Error
OpenRouter audio response has no body
Error message
OpenRouter audio response has no body
What it means
Thrown by the OpenRouter audio fetch wrapper when response.ok is true but response.body is null/undefined, preventing SSE chunk collection. This is a defensive guard because the rest of the pipeline (collectAudioChunks) requires a ReadableStream to iterate. It indicates the transport returned a success status without a streamable body, which the audio codec cannot handle.
Source
Thrown at packages/stage-ui/src/libs/providers/providers/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>({
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 27111382b4)
Solutions
- Verify the runtime provides a standards-compliant fetch where successful streaming responses expose response.body (browsers and Node 18+ do).
- Check for proxies or service workers between the app and openrouter.ai that may synthesize or buffer responses; bypass them for this endpoint.
- If intermittently hitting this during OpenRouter incidents, retry once and report upstream unavailability if it persists.
- As a fallback, treat a null body on a 2xx as an upstream protocol error and surface it distinctly from a transport error so ops can distinguish.
Example fix
// before
if (!response.body)
throw new Error('OpenRouter audio response has no body')
// after: distinguish missing-body from environment limitation
if (!response.body) {
throw new Error(
'OpenRouter returned 2xx but no streaming body; ' +
'verify fetch implementation supports ReadableStream responses ' +
'(status=' + response.status + ')',
)
} Defensive patterns
Strategy: try-catch
Validate before calling
// Detect environments where fetch streaming bodies are unsupported
function supportsStreamingResponseBody(): boolean {
return typeof ReadableStream !== 'undefined' && typeof globalThis.fetch === 'function'
&& typeof new Response(new ReadableStream<Uint8Array>()).body !== 'undefined'
} Type guard
function hasReadableBody(response: Response): response is Response & { body: ReadableStream<Uint8Array> } {
return response.ok && response.body instanceof ReadableStream
} Try / catch
try {
const response = await audioFetch(input, init)
if (!response.ok)
throw new Error(`OpenRouter audio request failed: ${response.status}`)
if (!hasReadableBody(response))
throw new Error('OpenRouter audio response has no body; streaming unsupported in this environment')
// proceed with collectAudioChunks(response.body)
}
catch (error) {
// Distinguish transport incompatibility from upstream failure for ops
reportTransportIssue(error)
throw error
} Prevention
- Confirm the runtime exposes response.body on streaming fetches before using audio TTS.
- Avoid proxies/service workers that buffer SSE responses for this endpoint.
- Treat a null body on 2xx as a transport defect, not an upstream error, when reporting.
When it happens
Trigger: A 2xx response from OpenRouter whose body was already consumed or stripped, or a fetch implementation / intermediary (proxy, service worker, HTTP/2 shim) that does not expose streaming bodies. Also possible if a misconfigured proxy buffers and detaches the body, or if the runtime lacks ReadableStream support on Response.
Common situations: Running in an environment where fetch does not return a streaming body (older Node without web streams polyfill); a corporate proxy or CDN that strips Transfer-Encoding: chunked; an OpenRouter edge response that returned 200 with empty body on partial outages; service workers intercepting the request and returning a synthesized Response without a body.
Related errors
- OpenRouter audio request failed: ${response.status} ${await
- Streaming transcription response is missing a readable body.
- Failed to fetch image: ${response.statusText}
- Failed to fetch MMD model: ${response.status} ${response.sta
- Gemini TTS request failed: ${response.status} ${await respon
AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12).
Data as JSON: /api/errors/12d1e6184b3863fc.
Report an issue: GitHub.