{"record":{"id":"f5a64b607336806d","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":null,"httpStatus":null,"severity":"error","filePath":"packages/provider-inference/src/providers/cloud/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/438a067dde47aa0bdb46c2323d1fe293dc805218/packages/provider-inference/src/providers/cloud/google-gemini-audio-speech/index.ts#L64-L100","documentation":"The wrapper validates that the parsed request body includes a model identifier before forwarding to models/{model}:generateContent. Without a model the URL would be malformed and the Gemini request would fail; the library throws locally with a clear message instead.","triggerScenarios":"Calling the speech API without specifying the TTS model, or with a model value of empty string/undefined, so body.model is falsy after JSON.parse.","commonSituations":"Provider configured without a default model, a settings migration dropped the stored model, or the caller passed the model under a wrong option key.","solutions":["Explicitly pass the TTS model (e.g. 'gemini-2.5-flash-preview-tts') in the speech request","Set a default model in the provider configuration so callers need not pass one each time","Check stored settings/config for an empty model value and restore a valid one","Confirm the option key for model matches what the provider expects"],"exampleFix":"// before\nawait speech({ input: 'hello', model: undefined })\n// after\nawait speech({ input: 'hello', model: 'gemini-2.5-flash-preview-tts' })","handlingStrategy":"validation","validationCode":"const GEMINI_TTS_MODELS = ['gemini-2.5-flash-preview-tts', 'gemini-2.5-pro-preview-tts']\nif (!model || !GEMINI_TTS_MODELS.includes(model)) throw new Error(`Invalid or missing Gemini TTS model: ${model}`)","typeGuard":"function hasModel(opts: { model?: string }): opts is { model: string } {\n  return typeof opts.model === 'string' && opts.model.length > 0\n}","tryCatchPattern":"try {\n  const audio = await speech({ input, model })\n} catch (e) {\n  if (e instanceof Error && e.message.includes('Missing model')) {\n    // set a default model and retry\n  }\n}","preventionTips":["Configure a default TTS model at the provider level so it is never undefined","Validate model ids against a known list before calling","Watch for settings migrations that may drop the stored model value"],"tags":["tts","validation","missing-model"],"backgroundTag":"missing-required-argument","analyzedSha":"438a067dde47aa0bdb46c2323d1fe293dc805218","analyzedAt":"2026-09-08T13:18:15.005Z","contentChangedAt":"2026-09-08T13:18:15.005Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}