{"record":{"id":"2a16e33d5a8445af","repo":"moeru-ai/airi","slug":"missing-model-for-gemini-tts","errorCode":null,"errorMessage":"Missing model for Gemini TTS","messagePattern":"Missing model for Gemini TTS","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/libs/providers/providers/google-gemini-audio-speech/index.ts","lineNumber":82,"sourceCode":"    bytes[index] = binary.charCodeAt(index)\n  return bytes\n}\n\nfunction createAudioFetch(apiKey: string, baseUrl: string) {\n  return async (_input: RequestInfo | URL, init?: RequestInit) => {\n    if (!init?.body || typeof init.body !== 'string')\n      throw new Error('Invalid request body')\n\n    const body = JSON.parse(init.body) as {\n      input?: string\n      model?: string\n      voice?: string\n      temperature?: number\n    }\n    if (!body.input)\n      throw new Error('Missing input text for Gemini TTS')\n    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","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/packages/stage-ui/src/libs/providers/providers/google-gemini-audio-speech/index.ts#L64-L100","documentation":"Thrown inside the Gemini TTS custom fetch wrapper when the parsed request body has no `model` field. The wrapper interpolates body.model into the Gemini models/${model}:generateContent path, so an absent model would produce a malformed URL. The guard fails fast before the request is built.","triggerScenarios":"Calling the provider's speech fetch with a JSON string body whose `model` key is missing or undefined. The caller did not select a Gemini TTS model (e.g. gemini-2.5-flash-preview-tts) before invoking synthesis.","commonSituations":"Provider settings have no model selected. Caller bug omitting the model field. Config reset cleared the model selection.","solutions":["Select a Gemini TTS model in the provider settings before invoking synthesis.","Pass a non-empty `model` in the request body JSON.","Default the model in the caller if the user left it blank, before calling fetch."],"exampleFix":"// before\nfetch(url, { body: JSON.stringify({ input: 'Hello', voice: 'Kore' }) })\n// after\nfetch(url, { body: JSON.stringify({ input: 'Hello', model: 'gemini-2.5-flash-preview-tts', voice: 'Kore' }) })","handlingStrategy":"validation","validationCode":"function hasGeminiModel(body: unknown): boolean {\n  return !!body && typeof body === 'object'\n    && typeof (body as { model?: unknown }).model === 'string'\n    && (body as { model: string }).model.length > 0\n}","typeGuard":"function isGeminiTtsBody(body: unknown): body is { input: string, model: string, voice?: string } {\n  return !!body && typeof body === 'object'\n    && typeof (body as { model?: unknown }).model === 'string'\n    && (body as { model: string }).model.length > 0\n}","tryCatchPattern":"try {\n  await provider.speech(model).fetch(url, { body: JSON.stringify(body) })\n}\ncatch (err) {\n  if (err instanceof Error && err.message === 'Missing model for Gemini TTS') {\n    // caller must set a Gemini TTS model id in the body\n  }\n  else throw err\n}","preventionTips":["Select a Gemini TTS model in provider settings before invoking synthesis.","Default the model in the caller if the user left it blank.","Use the exact `model` key in the request body."],"tags":["validation","gemini","tts","precondition","request-body","configuration"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}