decolua/9router · error · Error

OpenRouter TTS returned no audio data

Error message

OpenRouter TTS returned no audio data

What it means

After a successful HTTP response, the OpenRouter TTS handler parses the SSE stream and collects base64 audio chunks from choices[0].delta.audio.data. If zero chunks were collected, it throws this error. This can happen when the stream contains only text deltas, errors, or an immediate [DONE].

Source

Thrown at open-sse/handlers/ttsProviders/openrouter.js:70

    let buffer = "";

    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      buffer += decoder.decode(value, { stream: true });
      const lines = buffer.split("\n");
      buffer = lines.pop();
      for (const line of lines) {
        if (!line.startsWith("data: ") || line === "data: [DONE]") continue;
        try {
          const json = JSON.parse(line.slice(6));
          const audioData = json.choices?.[0]?.delta?.audio?.data;
          if (audioData) chunks.push(audioData);
        } catch {}
      }
    }

    if (chunks.length === 0) throw new Error("OpenRouter TTS returned no audio data");
    return { base64: chunks.join(""), format: "wav" };
  },
};

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Verify the chosen model supports audio output modality on OpenRouter and include modality/audio params correctly
  2. Log the raw SSE lines to inspect the actual delta shape
  3. Confirm your OpenRouter account has access to audio-capable models
  4. Try a different TTS model/voice to isolate model-side issues
  5. Check OpenRouter status page for provider outages
Defensive patterns

Strategy: validation

Validate before calling

const res = await fetch("https://openrouter.ai/api/v1/models");
const models = (await res.json()).data.map(m => m.id);
if (!models.includes(ttsModel)) throw new Error(`Model ${ttsModel} not available on OpenRouter`);

Type guard

function sseHasAudioChunk(line) {
  try {
    const j = JSON.parse(line.replace(/^data: /, ""));
    return typeof j?.choices?.[0]?.delta?.audio?.data === "string" && j.choices[0].delta.audio.data.length > 0;
  } catch { return false; }
}

Try / catch

try {
  return await openrouterTts.synthesize(text, model, creds);
} catch (e) {
  if (e.message === "OpenRouter TTS returned no audio data") {
    logger.warn("openrouter tts: empty audio stream, falling back", { model });
    return fallbackTts(text);
  }
  throw e;
}

Prevention

When it happens

Trigger: OpenRouter returns 200 with an SSE stream that never includes delta.audio.data: the model produced no audio (modality not enabled for the model, 'audio' param rejected silently), stream closed early, or chunks arrive in a different JSON shape after an API change.

Common situations: Selecting an OpenRouter model that doesn't actually support audio output, account without audio modality access, upstream provider outage mid-stream, or OpenRouter changing the delta payload shape.

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/37d90ffd6b600dcb. Report an issue: GitHub.