{"record":{"id":"83a1ab8024265bca","repo":"decolua/9router","slug":"gemini-tts-failed-res-status","errorCode":null,"errorMessage":"Gemini TTS failed: ${res.status}","messagePattern":"Gemini TTS failed: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"open-sse/handlers/ttsProviders/gemini.js","lineNumber":79,"sourceCode":"export default {\n  async synthesize(text, model, credentials, _responseFormat, opts = {}) {\n    if (!credentials?.apiKey) throw new Error(\"No Gemini API key configured\");\n    const { modelId, voiceId } = parseGeminiModelVoice(model);\n    const url = `${TTS_BASE}/${modelId}:generateContent?key=${credentials.apiKey}`;\n    const res = await fetch(url, {\n      method: \"POST\",\n      headers: { \"Content-Type\": \"application/json\" },\n      body: JSON.stringify({\n        contents: [{ parts: [{ text: buildPrompt(text, opts.language) }] }],\n        generationConfig: {\n          responseModalities: [\"AUDIO\"],\n          speechConfig: { voiceConfig: { prebuiltVoiceConfig: { voiceName: voiceId } } },\n        },\n      }),\n    });\n    if (!res.ok) {\n      const err = await res.json().catch(() => ({}));\n      throw new Error(err?.error?.message || `Gemini TTS failed: ${res.status}`);\n    }\n    const data = await res.json();\n    const b64 = data?.candidates?.[0]?.content?.parts?.find((p) => p.inlineData?.data)?.inlineData?.data;\n    if (!b64) {\n      const reason = data?.candidates?.[0]?.finishReason || data?.promptFeedback?.blockReason || \"unknown\";\n      throw new Error(`Gemini TTS returned no audio (finishReason: ${reason}, voice: ${voiceId}, model: ${modelId})`);\n    }\n    const wav = pcmToWav(Buffer.from(b64, \"base64\"));\n    return { base64: wav.toString(\"base64\"), format: \"wav\" };\n  },\n};\n\n// Voice fetcher — return prebuilt voices (Gemini has no list API)\nconst PREBUILT_VOICES = [\n  { id: \"Zephyr\", lang: \"en\", gender: \"Female\" },\n  { id: \"Puck\", lang: \"en\", gender: \"Male\" },\n  { id: \"Charon\", lang: \"en\", gender: \"Male\" },\n  { id: \"Kore\", lang: \"en\", gender: \"Female\" },","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/open-sse/handlers/ttsProviders/gemini.js#L61-L97","documentation":"When the Gemini generateContent call (with responseModalities AUDIO) returns a non-OK HTTP status, gemini.js surfaces Google's `error.message` if present, else this generic `Gemini TTS failed: <status>`. It means Google rejected the synthesis request or the call failed upstream; the status code distinguishes auth (401/403), bad model/voice (400/404), quota (429), and outages (5xx).","triggerScenarios":"Non-OK response from POST {TTS_BASE}/{modelId}:generateContent?key=... — invalid/expired API key (401/403), unknown modelId (404/400, e.g. a preview TTS model that was retired), unsupported voiceName or malformed request body (400), quota/rate limits (429), or Gemini outage (500/503).","commonSituations":"Preview TTS model names (like gemini-2.5/3.1-flash-tts-preview) rotating out of availability so the configured model id 404s; API key from a project without the Generative Language API enabled; free-tier quota exhausted; voice name typo (voice ids are case-sensitive, e.g. \"Kore\" not \"kore\").","solutions":["Read the status: 400/404 → check that modelId is a currently available Gemini TTS model and voiceName matches a prebuilt voice exactly (case-sensitive); 401/403 → fix the API key and enable the Generative Language API; 429 → wait or raise quota.","Update the configured TTS model to a currently supported one (the module's KNOWN_MODELS list comes from config/providerModels.js — keep it current).","Test the key and model directly with curl against the generateContent endpoint to see Google's full error body.","Retry on 429/5xx with backoff.","Fall back to a different TTS provider if the preview model is deprecated in your region/account."],"exampleFix":"// before: hardcoded retired preview model\nawait gemini.synthesize(text, \"gemini-3.1-flash-tts-preview/kore\", creds);\n// after: use a live model + exact-case voice\nconst model = \"gemini-2.5-flash-preview-tts\"; // verify availability first\nawait gemini.synthesize(text, `${model}/Kore`, creds);","handlingStrategy":"retry","validationCode":"const VALID_VOICES = [\"Zephyr\",\"Puck\",\"Charon\",\"Kore\",\"Fenrir\",\"Leda\",\"Orus\",\"Aoede\"];\nif (voiceId && !VALID_VOICES.includes(voiceId)) throw new Error(`Unknown Gemini voice: ${voiceId} (case-sensitive)`);\nif (modelId && !/tts/.test(modelId)) throw new Error(`${modelId} is not a Gemini TTS model`);","typeGuard":null,"tryCatchPattern":"try {\n  const audio = await gemini.synthesize(text, `${modelId}/${voiceId}`, creds);\n} catch (e) {\n  if (/failed: 429/.test(e.message)) {\n    await sleep(10000);\n    return gemini.synthesize(text, `${modelId}/${voiceId}`, creds);\n  }\n  if (/failed: (400|404)/.test(e.message)) throw new Error(`Gemini model/voice invalid: ${modelId}/${voiceId}`);\n  if (/failed: 40[13]/.test(e.message)) throw new Error(\"Gemini API key invalid or API not enabled\");\n  throw e;\n}","preventionTips":["Keep KNOWN_MODELS/config providerModels.js updated as Google rotates preview TTS models.","Use exact-case prebuilt voice names (Kore, Puck, ...).","Enable the Generative Language API on the key's Google Cloud project.","Back off and retry on 429/5xx; fail fast with a clear message on 400/404."],"tags":["network","tts","upstream-provider","http-status","google-gemini"],"backgroundTag":"upstream-http-error","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}