{"record":{"id":"15aa04ab3e8f578e","repo":"HeyPuter/puter","slug":"internal-error-15aa04","errorCode":"internal_error","errorMessage":"No cost data for model: ${model}","messagePattern":"No cost data for model: (.+?)","errorType":"http","errorClass":"HttpError","httpStatus":500,"severity":"error","filePath":"src/backend/drivers/ai-tts/providers/gemini/GeminiTTSProvider.ts","lineNumber":195,"sourceCode":"                400,\n                `Invalid voice: ${voice}. Expected: ${GEMINI_TTS_VOICES.map(({ id }) => id).join(', ')}`,\n                {\n                    legacyCode: 'field_invalid',\n                    fields: {\n                        key: 'voice',\n                        expected: GEMINI_TTS_VOICES.map(({ id }) => id).join(\n                            ', ',\n                        ),\n                        got: voice,\n                    },\n                },\n            );\n        }\n\n        const actor = Context.get('actor')!;\n        const costs = GEMINI_TTS_COSTS[model];\n        if (!costs) {\n            throw new HttpError(500, `No cost data for model: ${model}`, {\n                legacyCode: 'internal_error',\n            });\n        }\n\n        // Estimate input tokens (~4 chars per token) and a rough output\n        // audio duration (~150 words/min, 25 tokens/sec).\n        const estimatedInputTokens = Math.max(1, Math.ceil(text.length / 4));\n        const wordCount = text.split(/\\s+/).length;\n        const estimatedDurationSec = Math.max(1, (wordCount / 150) * 60);\n        const estimatedOutputTokens = Math.ceil(estimatedDurationSec * 25);\n\n        const estimatedInputCostCents =\n            (estimatedInputTokens / 1_000_000) * costs.input;\n        const estimatedOutputCostCents =\n            (estimatedOutputTokens / 1_000_000) * costs.output_audio;\n        const estimatedTotalMicroCents = this.#toMicroCents(\n            estimatedInputCostCents + estimatedOutputCostCents,\n        );","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/drivers/ai-tts/providers/gemini/GeminiTTSProvider.ts#L177-L213","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Add the missing model to GEMINI_TTS_COSTS in src/backend/drivers/ai-tts/providers/gemini/costs.ts with input and output_audio rates.","Ensure GEMINI_TTS_MODELS and GEMINI_TTS_COSTS stay in sync whenever a model is added or renamed (treat them as one unit).","As a caller workaround until the bug is fixed, select a different model that has a cost entry.","Add a unit test asserting every GEMINI_TTS_MODELS id has a GEMINI_TTS_COSTS entry to prevent regression."],"exampleFix":"// costs.ts — before\nexport const GEMINI_TTS_COSTS = {\n  'gemini-2.5-flash-preview-tts': { input: 0.5, output_audio: 3 },\n};\n// after — add the missing model that GEMINI_TTS_MODELS advertises\nexport const GEMINI_TTS_COSTS = {\n  'gemini-2.5-flash-preview-tts': { input: 0.5, output_audio: 3 },\n  'gemini-3.1-flash-tts-preview': { input: 0.5, output_audio: 3 },\n};","handlingStrategy":"try-catch","validationCode":"// Caller-side: avoid models the server cannot price by using a known-cost model.\nconst KNOWN_COST_GEMINI_MODELS = ['gemini-2.5-flash-preview-tts', 'gemini-2.5-pro-preview-tts'];\nfunction synthesizeGemini(text, model = 'gemini-2.5-flash-preview-tts') {\n  if (!KNOWN_COST_GEMINI_MODELS.includes(model)) {\n    // pick a known-cost default rather than risk a 500\n    model = 'gemini-2.5-flash-preview-tts';\n  }\n  return driver.synthesize({ text, provider: 'gemini', model });\n}\n// Server-side fix: add the missing entry to GEMINI_TTS_COSTS in costs.ts.","typeGuard":null,"tryCatchPattern":"try {\n  await driver.synthesize({ text, provider: 'gemini', model });\n} catch (e) {\n  if (e?.status === 500 && /No cost data for model/.test(e.message)) {\n    // internal inconsistency — file a bug, fall back to a known-cost model\n    await driver.synthesize({ text, provider: 'gemini', model: 'gemini-2.5-flash-preview-tts' });\n  } else throw e;\n}","preventionTips":["Treat GEMINI_TTS_MODELS and GEMINI_TTS_COSTS as one unit: add/remove in lockstep.","Add a unit test asserting every advertised model has a cost entry.","When selecting a model programmatically, prefer ones with a known cost row."],"tags":["gemini","tts","internal-error","config","cost-table","bug"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}