Repository navigation
Description
Before submitting your bug report
- I've searched open issues — the closest is OpenAI cost calculation ignores cached input tokens (over-costs cache hits) #13104 (cached input tokens not discounted). This is a different defect in the same function, and it survives the fix proposed there.
Relevant environment info
- OS: any
- Continue version:
main@5522c6f - Model:
gpt-4.1,gpt-4.1-mini,gpt-4.1-nano(anygpt-4.1*id) - Surface: CLI cost display + telemetry, and the GUI usage panel
Description
calculateOpenAICost picks a pricing row by longest-prefix startsWith. "gpt-4.1" does not start with "gpt-4o", so the loop falls through to the legacy "gpt-4" row — the most expensive row in the table.
core/llm/utils/calculateRequestCost.ts:159-185:
"gpt-4o-mini": { input: 0.15, output: 0.6 },
"gpt-4o": { input: 2.5, output: 10 },
"gpt-4-turbo": { input: 10, output: 30 },
...
"gpt-4": { input: 30, output: 60 }, // <- GPT-4 (2023) list priceconst sortedKeys = Object.keys(pricing).sort((a, b) => b.length - a.length);
for (const prefix of sortedKeys) {
if (normalizedModel.startsWith(prefix)) { modelPricing = pricing[prefix]; break }
}"gpt-4.1".startsWith("gpt-4") is true, and nothing longer matches first. The gpt-4o family is unaffected — "gpt-4o" is checked before "gpt-4" and wins.
To reproduce
The table and matcher below are copied verbatim from calculateOpenAICost; real prices are OpenAI's list rates from https://developers.openai.com/api/docs/pricing, checked today.
const pricing = {
"gpt-4o-mini": { input: 0.15, output: 0.6 },
"gpt-4o": { input: 2.5, output: 10 },
"gpt-4-turbo": { input: 10, output: 30 },
"gpt-3.5-turbo-0125": { input: 0.5, output: 1.5 },
"gpt-3.5-turbo-1106": { input: 1, output: 2 },
"gpt-3.5-turbo": { input: 1.5, output: 2 },
"gpt-4": { input: 30, output: 60 },
};
const sortedKeys = Object.keys(pricing).sort((a, b) => b.length - a.length);
const match = (m) => {
const n = m.toLowerCase();
for (const p of sortedKeys) if (n.startsWith(p)) return [p, pricing[p]];
return [null, null];
};
for (const m of ["gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "gpt-4.1-2025-04-14", "gpt-4o"])
console.log(m, "->", match(m)[0], JSON.stringify(match(m)[1]));gpt-4.1 -> gpt-4 {"input":30,"output":60}
gpt-4.1-mini -> gpt-4 {"input":30,"output":60}
gpt-4.1-nano -> gpt-4 {"input":30,"output":60}
gpt-4.1-2025-04-14 -> gpt-4 {"input":30,"output":60}
gpt-4o -> gpt-4o {"input":2.5,"output":10}
Expected / actual
| model | charged in / out | OpenAI list in / out | overcharge |
|---|---|---|---|
gpt-4.1 |
$30 / $60 | $2.00 / $8.00 | 15x input, 7.5x output |
gpt-4.1-mini |
$30 / $60 | $0.40 / $1.60 | 75x input, 37.5x output |
gpt-4.1-nano |
$30 / $60 | $0.10 / $0.40 | 300x input, 150x output |
A 100k-token gpt-4.1-mini prompt is reported as $3.00 against a real charge of $0.04.
The three date-stamped ids (gpt-4.1-2025-04-14 and friends) hit it too, so pinning a snapshot doesn't avoid it.
Suggested fix
Add the three rows. sortedKeys is longest-first, so "gpt-4.1-mini" (12) and "gpt-4.1-nano" (12) are checked before "gpt-4.1" (7), which is checked before "gpt-4" (5) — no matcher change needed:
"gpt-4.1-mini": { input: 0.4, output: 1.6 },
"gpt-4.1-nano": { input: 0.1, output: 0.4 },
"gpt-4.1": { input: 2.0, output: 8.0 },Worth considering separately: "gpt-4" is a bare-prefix catch-all at the highest price in the table, so any future gpt-4.x inherits $30/$60 silently. Requiring the next character to be - or end-of-string would send unknown variants to the null path (no cost shown / CLI fallback) instead of to the most expensive row. That is a behaviour change for unknown ids, which is why I've kept it out of the fix above.
Unrelated to this, but visible from the same table: gpt-5* ids match nothing and return null, so the GUI shows no cost for them at all.
Note on a PR
I can't sign the CLA, so this is an issue rather than a PR — the three lines above are the whole change, and the prices are linked to source. Happy to add the vitest cases as a patch in a comment if that's useful.
Activity
tanishqbaweja commentedon Oct 25, 2026
Reproduced on
5522c6fusing the productioncalculateRequestCostfunction: 100k input / 50k output tokens yields $6 for all three GPT-4.1 variants and their dated snapshots. I'm taking the three-row pricing fix with regression tests and CLI/UI verification. Cached-token handling in #13315 stays separate.Update: I am no longer working on this issue. The reproduction above is available for anyone picking it up.