{"record":{"id":"bbf050b8028db98d","repo":"moeru-ai/airi","slug":"gemini-tts-request-failed-response-status-aw","errorCode":null,"errorMessage":"Gemini TTS request failed: ${response.status} ${await response.text().catch(() => '')}","messagePattern":"Gemini TTS request failed: (.+?) (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/libs/providers/providers/google-gemini-audio-speech/index.ts","lineNumber":99,"sourceCode":"    if (!body.model)\n      throw new Error('Missing model for Gemini TTS')\n\n    const response = await globalThis.fetch(new URL(`models/${body.model}:generateContent`, baseUrl), {\n      method: 'POST',\n      headers: { 'x-goog-api-key': apiKey, 'Content-Type': 'application/json' },\n      body: JSON.stringify({\n        contents: [{ parts: [{ text: body.input }] }],\n        generationConfig: {\n          responseModalities: ['AUDIO'],\n          speechConfig: {\n            voiceConfig: { prebuiltVoiceConfig: { voiceName: body.voice || 'Kore' } },\n          },\n          ...(body.temperature !== undefined ? { temperature: body.temperature } : {}),\n        },\n      }),\n    })\n    if (!response.ok)\n      throw new Error(`Gemini TTS request failed: ${response.status} ${await response.text().catch(() => '')}`)\n\n    const data = await response.json() as {\n      candidates?: Array<{ content?: { parts?: Array<{ inlineData?: { data?: string } }> } }>\n    }\n    const audio = data.candidates?.[0]?.content?.parts?.find(part => part.inlineData)?.inlineData?.data\n    if (!audio)\n      throw new Error('Gemini TTS response missing audio data')\n\n    return new Response(toWavFromPCM16(decodeBase64(audio), 24000), {\n      status: 200,\n      headers: { 'Content-Type': 'audio/wav' },\n    })\n  }\n}\n\nexport const providerGoogleGeminiAudioSpeech = defineProvider<GoogleGeminiSpeechConfig>({\n  id: 'google-gemini-audio-speech',\n  name: 'Google Gemini',","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-ui/src/libs/providers/providers/google-gemini-audio-speech/index.ts#L81-L117","documentation":"The fetch to the Gemini TTS endpoint returned a non-OK status; the message embeds the status code and the response body text (empty string if reading the body throws). This is the provider's only HTTP-error surface for speech synthesis, and the status carries the diagnosis: 400 for bad voice/model/temperature, 401/403 for key problems, 429 for quota.","triggerScenarios":"Invalid or revoked API key (401/403); project or free-tier quota exhausted (429); voiceName not supported by the model (400); wrong model id for audio output; billing disabled on the project.","commonSituations":"Key rotated but provider settings still hold the old one; RPM exceeded on the free tier; passing an OpenAI-style voice name where Gemini expects names like Kore; region-restricted key.","solutions":["Map the embedded status: 401/403 re-enter the API key in provider settings; 429 wait or raise quota; 400 check voice and model values","Log the body text included in the message for the exact Gemini error detail","Verify billing and quota in Google AI Studio or Cloud Console","Retry with the default voice 'Kore' and no temperature override to isolate parameter issues"],"exampleFix":"// before\nvoice: 'alloy' // OpenAI voice name → 400 Bad Request\n\n// after\nvoice: 'Kore' // Gemini prebuilt voice, e.g. Kore, Puck, Aoede","handlingStrategy":"try-catch","validationCode":"if (!config.apiKey?.trim())\n  throw new Error('Gemini API key is required') // fail fast before the fetch","typeGuard":null,"tryCatchPattern":"try {\n  const audio = await geminiSpeech(body)\n}\ncatch (err) {\n  const [, status] = err.message.match(/Gemini TTS request failed: (\\d+)/) ?? []\n  if (status === '401' || status === '403') promptForApiKey()\n  else if (status === '429') scheduleRetry(backoff)\n}","preventionTips":["Validate the API key before invoking synthesis","Pin voice names to the documented Gemini set","Cache quota state and back off on 429","Surface response body text in logs — it names the exact field at fault"],"tags":["gemini","tts","api","http","quota"],"backgroundTag":"api-request-failed","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}