{"record":{"id":"9f8b11d65b10af51","repo":"moeru-ai/airi","slug":"openrouter-audio-response-has-no-body-9f8b11","errorCode":null,"errorMessage":"OpenRouter audio response has no body","messagePattern":"OpenRouter audio response has no body","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/provider-inference/src/providers/cloud/openrouter-audio-speech/index.ts","lineNumber":116,"sourceCode":"    const response = await globalThis.fetch(new URL('chat/completions', baseUrl), {\n      method: 'POST',\n      headers: {\n        'Authorization': `Bearer ${apiKey}`,\n        'Content-Type': 'application/json',\n        ...OPENROUTER_ATTRIBUTION_HEADERS,\n      },\n      body: JSON.stringify({\n        model,\n        messages: [{ role: 'user', content: ttsPrompt(body.input ?? '') }],\n        modalities: ['text', 'audio'],\n        audio: { voice: body.voice, format: 'pcm16' },\n        stream: true,\n      }),\n    })\n    if (!response.ok)\n      throw new Error(`OpenRouter audio request failed: ${response.status} ${await response.text()}`)\n    if (!response.body)\n      throw new Error('OpenRouter audio response has no body')\n\n    const wav = toWavFromPCM16(decodeBase64Pcm(await collectAudioChunks(response.body)), 24000)\n    return new Response(new Blob([wav], { type: 'audio/wav' }), {\n      status: 200,\n      headers: { 'Content-Type': 'audio/wav' },\n    })\n  }\n}\n\nexport const providerOpenRouterAudioSpeech = defineProvider<OpenRouterAudioConfig, 'openrouter-audio-speech'>({\n  id: 'openrouter-audio-speech',\n  name: 'OpenRouter',\n  nameLocalize: ({ t }) => t('settings.pages.providers.provider.openrouter-audio-speech.title'),\n  description: 'openrouter.ai',\n  descriptionLocalize: ({ t }) => t('settings.pages.providers.provider.openrouter-audio-speech.description'),\n  tasks: ['text-to-speech'],\n  icon: 'i-lobe-icons:openrouter',\n  createProviderConfig: () => openRouterAudioConfigSchema,","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/moeru-ai/airi/blob/f679616c34f1cf6d282c8d64264242af944b3fed/packages/provider-inference/src/providers/cloud/openrouter-audio-speech/index.ts#L98-L134","documentation":"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.","triggerScenarios":"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.","commonSituations":"Testing with a fetch mock that returns Response without a body; service workers or proxies consuming the stream; older runtimes lacking ReadableStream response bodies.","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."],"exampleFix":"// before (test mock)\nfetch.mockResolvedValue(new Response(null, { status: 200 }))\n// after\nfetch.mockResolvedValue(new Response(sseStream, { status: 200 }))","handlingStrategy":"fallback","validationCode":"const res = await fetchOnce()\nif (res.ok && !res.body) throw new Error('Runtime does not support streaming response bodies')","typeGuard":"const hasBody = (res: Response): res is Response & { body: ReadableStream } => res.body != null","tryCatchPattern":"try { await speak(text) } catch (e) { if (e.message.includes('no body')) { /* disable stream interception/mocks or switch runtime */ } else throw e }","preventionTips":["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"],"tags":["network","streaming","audio","response-body"],"backgroundTag":"empty-response-body","analyzedSha":"f679616c34f1cf6d282c8d64264242af944b3fed","analyzedAt":"2026-09-08T13:18:15.005Z","contentChangedAt":"2026-09-08T13:18:15.005Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}