HeyPuter/puter · error · HttpError

internal_error

internal_error

Error message

No cost data for model: ${model}

What it means

After the model passes the GEMINI_TTS_MODELS check, GeminiTTSProvider looks up GEMINI_TTS_COSTS[model]; if missing it throws HTTP 500 (legacyCode internal_error). This indicates an internal inconsistency: a model is advertised in GEMINI_TTS_MODELS but has no entry in the GEMINI_TTS_COSTS table. It is a code/config bug in the provider, not a caller error.

Source

Thrown at src/backend/drivers/ai-tts/providers/gemini/GeminiTTSProvider.ts:195

                400,
                `Invalid voice: ${voice}. Expected: ${GEMINI_TTS_VOICES.map(({ id }) => id).join(', ')}`,
                {
                    legacyCode: 'field_invalid',
                    fields: {
                        key: 'voice',
                        expected: GEMINI_TTS_VOICES.map(({ id }) => id).join(
                            ', ',
                        ),
                        got: voice,
                    },
                },
            );
        }

        const actor = Context.get('actor')!;
        const costs = GEMINI_TTS_COSTS[model];
        if (!costs) {
            throw new HttpError(500, `No cost data for model: ${model}`, {
                legacyCode: 'internal_error',
            });
        }

        // Estimate input tokens (~4 chars per token) and a rough output
        // audio duration (~150 words/min, 25 tokens/sec).
        const estimatedInputTokens = Math.max(1, Math.ceil(text.length / 4));
        const wordCount = text.split(/\s+/).length;
        const estimatedDurationSec = Math.max(1, (wordCount / 150) * 60);
        const estimatedOutputTokens = Math.ceil(estimatedDurationSec * 25);

        const estimatedInputCostCents =
            (estimatedInputTokens / 1_000_000) * costs.input;
        const estimatedOutputCostCents =
            (estimatedOutputTokens / 1_000_000) * costs.output_audio;
        const estimatedTotalMicroCents = this.#toMicroCents(
            estimatedInputCostCents + estimatedOutputCostCents,
        );

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Add the missing model to GEMINI_TTS_COSTS in src/backend/drivers/ai-tts/providers/gemini/costs.ts with input and output_audio rates.
  2. Ensure GEMINI_TTS_MODELS and GEMINI_TTS_COSTS stay in sync whenever a model is added or renamed (treat them as one unit).
  3. As a caller workaround until the bug is fixed, select a different model that has a cost entry.
  4. Add a unit test asserting every GEMINI_TTS_MODELS id has a GEMINI_TTS_COSTS entry to prevent regression.

Example fix

// costs.ts — before
export const GEMINI_TTS_COSTS = {
  'gemini-2.5-flash-preview-tts': { input: 0.5, output_audio: 3 },
};
// after — add the missing model that GEMINI_TTS_MODELS advertises
export const GEMINI_TTS_COSTS = {
  'gemini-2.5-flash-preview-tts': { input: 0.5, output_audio: 3 },
  'gemini-3.1-flash-tts-preview': { input: 0.5, output_audio: 3 },
};
Defensive patterns

Strategy: try-catch

Validate before calling

// Caller-side: avoid models the server cannot price by using a known-cost model.
const KNOWN_COST_GEMINI_MODELS = ['gemini-2.5-flash-preview-tts', 'gemini-2.5-pro-preview-tts'];
function synthesizeGemini(text, model = 'gemini-2.5-flash-preview-tts') {
  if (!KNOWN_COST_GEMINI_MODELS.includes(model)) {
    // pick a known-cost default rather than risk a 500
    model = 'gemini-2.5-flash-preview-tts';
  }
  return driver.synthesize({ text, provider: 'gemini', model });
}
// Server-side fix: add the missing entry to GEMINI_TTS_COSTS in costs.ts.

Try / catch

try {
  await driver.synthesize({ text, provider: 'gemini', model });
} catch (e) {
  if (e?.status === 500 && /No cost data for model/.test(e.message)) {
    // internal inconsistency — file a bug, fall back to a known-cost model
    await driver.synthesize({ text, provider: 'gemini', model: 'gemini-2.5-flash-preview-tts' });
  } else throw e;
}

Prevention

When it happens

Trigger: A developer added a new model id to GEMINI_TTS_MODELS but forgot to add its pricing to GEMINI_TTS_COSTS (in costs.js), then a caller (or default) selects that model. The voice/model validation passes; only the cost lookup fails.

Common situations: A new Gemini TTS preview model was added to the model list without its cost row; a rename that updated one table but not the other; refactoring that split the tables.

Related errors


AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12). Data as JSON: /api/errors/15aa04ab3e8f578e. Report an issue: GitHub.