{"record":{"id":"cf89933953e61caa","repo":"moeru-ai/airi","slug":"skipping-malformed-sse-chunk-from-openrouter-audio","errorCode":null,"errorMessage":"Skipping malformed SSE chunk from OpenRouter audio stream:","messagePattern":"Skipping malformed SSE chunk from OpenRouter audio stream:","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/stage-ui/src/libs/providers/providers/openrouter-audio-speech/index.ts","lineNumber":76,"sourceCode":"      if (!line.startsWith('data: '))\n        continue\n\n      const data = line.slice('data: '.length).trim()\n      if (data === '[DONE]') {\n        done = true\n        break\n      }\n\n      try {\n        const event = JSON.parse(data) as {\n          choices?: Array<{ delta?: { audio?: { data?: string } } }>\n        }\n        const audio = event.choices?.[0]?.delta?.audio?.data\n        if (audio)\n          chunks.push(audio)\n      }\n      catch (error) {\n        console.warn('Skipping malformed SSE chunk from OpenRouter audio stream:', data, error)\n      }\n    }\n  }\n\n  return chunks\n}\n\nfunction decodeBase64Pcm(chunks: string[]) {\n  const binary = atob(chunks.join(''))\n  const bytes = new Uint8Array(binary.length)\n  for (let index = 0; index < binary.length; index++)\n    bytes[index] = binary.charCodeAt(index)\n  return bytes\n}\n\nfunction createAudioFetch(apiKey: string, baseUrl: string, model: string) {\n  return async (_input: RequestInfo | URL, init?: RequestInit) => {\n    if (!init?.body || typeof init.body !== 'string')","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-ui/src/libs/providers/providers/openrouter-audio-speech/index.ts#L58-L94","documentation":"The OpenRouter audio speech provider parses a streamed response as server-sent events and JSON.parse()s each data line to extract base64 PCM audio chunks. When a line is not valid JSON (or does not match the expected shape it is skipped), the parse error is caught per-chunk and logged with this warning; the rest of the stream continues. This is loss-tolerant by design but can hide truncated output.","triggerScenarios":"An SSE data line contains the '[DONE]' sentinel or a keep-alive/comment payload, the proxy mangles chunk boundaries (split multi-byte/bracketed JSON across lines), or the endpoint emits a non-JSON error frame inside the SSE stream.","commonSituations":"Streaming audio from an OpenRouter-compatible model through nginx/Cloudflare with buffering or fragmenting; model emits usage/final chunks with a different schema; trailing '[DONE]' line reaching the parser; upstream returning an HTML error page mid-stream.","solutions":["Log the skipped line (already included) and check whether it is '[DONE]' — if so, filter it explicitly before JSON.parse.","Disable proxy buffering for the SSE route (proxy_buffering off; flush immediately) so chunks arrive intact.","If many chunks are skipped, compare the raw stream (devtools) with what the client sees to find the mangling hop.","Verify the model actually supports audio output via OpenRouter's /api/v1 chat completions with modalities: ['audio']."],"exampleFix":"// before\nconst event = JSON.parse(data) as { choices?: ... }\n\n// after — skip sentinels before parsing\nif (data === '[DONE]')\n  break\nconst event = JSON.parse(data) as { choices?: ... }","handlingStrategy":"try-catch","validationCode":"function isSseDataLine(line: string): boolean {\n  return line.startsWith('data:') && !line.includes('[DONE]')\n}","typeGuard":"function isOpenRouterAudioChunk(event: unknown): event is { choices: Array<{ delta?: { audio?: { data?: string } } }> } {\n  return typeof event === 'object'\n    && event !== null\n    && Array.isArray((event as any).choices)\n}","tryCatchPattern":"for (const data of dataLines) {\n  if (data === '[DONE]') break\n  try {\n    const event = JSON.parse(data)\n    if (isOpenRouterAudioChunk(event) && event.choices[0]?.delta?.audio?.data)\n      chunks.push(event.choices[0].delta.audio.data)\n  }\n  catch {\n    // skip malformed chunk; count skips and warn if ratio is high\n  }\n}","preventionTips":["Filter [DONE] and comment/keep-alive lines before JSON.parse.","Run SSE routes through buffering-disabled proxies so chunks stay intact.","Track a skipped-chunk counter to detect stream corruption early."],"tags":["sse","streaming","json-parse","openrouter","audio"],"backgroundTag":"sse-stream-parse-error","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}