Skip to content

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

Description

@xyzs996

Before submitting your bug report

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions