continuedev / continuedev/continue

[Bug] gpt-4.1 / -mini / -nano are priced as legacy gpt-4 — 15x to 300x over list

Open Beginner friendly
#13,184 0 comments 0 reactions 0 assignees View on GitHub

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 (any gpt-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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.