continuedev / continuedev/continue
[Bug] gpt-4.1 / -mini / -nano are priced as legacy gpt-4 — 15x to 300x over list
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 36k
- Forks
- 5.4k
- PR merge metrics
- No merged PRs in 30d
Description
Before submitting your bug report
- I've searched open issues — the closest is #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 price
const 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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in core/llm/utils/calculateRequestCost.ts:159-185 and inspect the pricing table and longest-prefix matcher. Add pricing entries for gpt-4.1, gpt-4.1-mini, and gpt-4.1-nano, including date-stamped IDs through prefix matching. Done means the listed models use OpenAI's rates instead of the legacy gpt-4 rates in CLI cost display, telemetry, and the GUI usage panel.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 86/100