moeru-ai/airi · error · Error

Missing input text for Gemini TTS

Error message

Missing input text for Gemini TTS

What it means

Thrown inside the Gemini TTS custom fetch wrapper when the request body parses to an object with no `input` field. The wrapper builds the Gemini generateContent request from a JSON body contract (input/model/voice/temperature); missing input text means the caller never supplied the text to synthesize, so the request is aborted before hitting the network.

Source

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

  const bytes = new Uint8Array(binary.length)
  for (let index = 0; index < binary.length; index++)
    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)

View on GitHub (pinned to 27111382b4)

Solutions

  1. Ensure the caller passes a non-empty `input` field in the request body JSON.
  2. Guard upstream: skip TTS when the text is empty or whitespace.
  3. Verify the body key name matches `input` exactly (not `text`/`prompt`).

Example fix

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

Strategy: validation

Validate before calling

function hasGeminiInput(body: unknown): boolean {
  return !!body && typeof body === 'object'
    && typeof (body as { input?: unknown }).input === 'string'
    && (body as { input: string }).input.length > 0
}
// before fetch:
if (!hasGeminiInput(parsedBody)) {
  // do not call fetch; skip TTS for empty text
}

Type guard

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

Try / catch

try {
  await provider.speech(model).fetch(url, { body: JSON.stringify(body) })
}
catch (err) {
  if (err instanceof Error && err.message === 'Missing input text for Gemini TTS') {
    // caller bug: ensure `input` is set; skip empty text
  }
  else throw err
}

Prevention

When it happens

Trigger: Calling the provider's speech fetch with a JSON string body where the `input` key is missing, empty-string, or undefined. The upstream caller (TTS module) constructed the body without a text field, or passed a differently-shaped object.

Common situations: TTS invoked with empty text (e.g. an empty assistant message). Caller bug building the body with a wrong key name (e.g. `text` instead of `input`). Template/coder error stripping the field.

Related errors


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