moeru-ai/airi · error · Error

Missing model for Gemini TTS

Error message

Missing model for Gemini TTS

What it means

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.

Source

Thrown at packages/stage-ui/src/libs/providers/providers/google-gemini-audio-speech/index.ts:82

    bytes[index] = binary.charCodeAt(index)
  return bytes
}

function createAudioFetch(apiKey: string, baseUrl: string) {
  return async (_input: RequestInfo | URL, init?: RequestInit) => {
    if (!init?.body || typeof init.body !== 'string')
      throw new Error('Invalid request body')

    const body = JSON.parse(init.body) as {
      input?: string
      model?: string
      voice?: string
      temperature?: number
    }
    if (!body.input)
      throw new Error('Missing input text for Gemini TTS')
    if (!body.model)
      throw new Error('Missing model for Gemini TTS')

    const response = await globalThis.fetch(new URL(`models/${body.model}:generateContent`, baseUrl), {
      method: 'POST',
      headers: { 'x-goog-api-key': apiKey, 'Content-Type': 'application/json' },
      body: JSON.stringify({
        contents: [{ parts: [{ text: body.input }] }],
        generationConfig: {
          responseModalities: ['AUDIO'],
          speechConfig: {
            voiceConfig: { prebuiltVoiceConfig: { voiceName: body.voice || 'Kore' } },
          },
          ...(body.temperature !== undefined ? { temperature: body.temperature } : {}),
        },
      }),
    })
    if (!response.ok)
      throw new Error(`Gemini TTS request failed: ${response.status} ${await response.text().catch(() => '')}`)

View on GitHub (pinned to 27111382b4)

Solutions

  1. Select a Gemini TTS model in the provider settings before invoking synthesis.
  2. Pass a non-empty `model` in the request body JSON.
  3. Default the model in the caller if the user left it blank, before calling fetch.

Example fix

// before
fetch(url, { body: JSON.stringify({ input: 'Hello', voice: 'Kore' }) })
// after
fetch(url, { body: JSON.stringify({ input: 'Hello', model: 'gemini-2.5-flash-preview-tts', voice: 'Kore' }) })
Defensive patterns

Strategy: validation

Validate before calling

function hasGeminiModel(body: unknown): boolean {
  return !!body && typeof body === 'object'
    && typeof (body as { model?: unknown }).model === 'string'
    && (body as { model: string }).model.length > 0
}

Type guard

function isGeminiTtsBody(body: unknown): body is { input: string, model: string, voice?: string } {
  return !!body && typeof body === 'object'
    && typeof (body as { model?: unknown }).model === 'string'
    && (body as { model: string }).model.length > 0
}

Try / catch

try {
  await provider.speech(model).fetch(url, { body: JSON.stringify(body) })
}
catch (err) {
  if (err instanceof Error && err.message === 'Missing model for Gemini TTS') {
    // caller must set a Gemini TTS model id in the body
  }
  else throw err
}

Prevention

When it happens

Trigger: 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.

Common situations: Provider settings have no model selected. Caller bug omitting the model field. Config reset cleared the model selection.

Related errors


AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12). Data as JSON: /api/errors/2a16e33d5a8445af. Report an issue: GitHub.