mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-05-16 19:44:14 +00:00
Cursor emits model names in a `claude-<dot-version>-<tier>` shape (`claude-4.6-sonnet`, `claude-4.5-opus`, `claude-4.5-opus-high-thinking`, etc.) plus its own `composer-1` house model. None of these match the canonical LiteLLM pricing keys (`claude-sonnet-4-6`, `claude-opus-4-5`). The alias map in `src/models.ts` filled some of these in v0.9.4 but missed: - plain no-suffix forms: `claude-4.5-opus`, `claude-4.5-sonnet`, `claude-4.6-opus` - haiku tier: `claude-4.5-haiku`, `claude-4.6-haiku` - forward-looking: `claude-4.7-opus` - Cursor's house model: `composer-1` The dashboard rendered $0 for sessions that used any unaliased model — visible in the screenshots posted in #159 even after the v0.9.4 fix that added the `-thinking` variants. This PR fills the gaps and adds 16 regression tests under `Cursor model variants resolve to pricing` that assert every model name in `src/providers/cursor.ts:modelDisplayNames` plus the additional plain forms resolves to a non-null pricing entry with `inputCostPerToken > 0` and `outputCostPerToken > 0`. So a future LiteLLM snapshot bump or a typo in the alias map will fail the test before users see $0. Direct hits in the snapshot (no alias needed): `gpt-5`, `gpt-5.2`, `grok-code-fast-1`, `gemini-3-pro` (already aliased). These are covered in the test suite as well so a snapshot that drops them would also be caught. Tests: 45 files, 617 passing locally (16 new). Closes #159.
This commit is contained in:
parent
7a878f4d19
commit
cdf7169a89
3 changed files with 123 additions and 4 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
|
@ -41,6 +41,19 @@
|
|||
reconcile. Closes #279.
|
||||
|
||||
### Fixed (CLI)
|
||||
- **Cursor cost shown for every model, not just Auto.** Cursor emits model
|
||||
names in a `claude-<dot-version>-<tier>` shape (`claude-4.6-sonnet`,
|
||||
`claude-4.5-opus`, `claude-4.5-opus-high-thinking`, etc.) plus its own
|
||||
`composer-1` house model, none of which match the canonical LiteLLM
|
||||
pricing keys (`claude-sonnet-4-6`, `claude-opus-4-5`). The alias map in
|
||||
`src/models.ts` filled some of these in v0.9.4 but missed the plain
|
||||
no-suffix forms (`claude-4.5-opus`, `claude-4.5-sonnet`,
|
||||
`claude-4.6-opus`), the haiku tier, the forward-looking 4.7 variant,
|
||||
and `composer-1`. The dashboard rendered $0 for sessions that used any
|
||||
unaliased model. Visible to users in #159 even after the v0.9.4 fix.
|
||||
Every Cursor variant in `src/providers/cursor.ts:modelDisplayNames`
|
||||
now has an alias and a regression test asserting non-zero pricing
|
||||
resolution. Closes #159.
|
||||
- **Activity classifier no longer mislabels feature work as debugging.**
|
||||
Messages like "add error handling", "create an issue tracker", or
|
||||
"implement the 404 page" used to land in the Debugging bucket because
|
||||
|
|
|
|||
|
|
@ -170,12 +170,49 @@ const BUILTIN_ALIASES: Record<string, string> = {
|
|||
'cline-auto': 'claude-sonnet-4-5',
|
||||
'openclaw-auto': 'claude-sonnet-4-5',
|
||||
'qwen-auto': 'claude-sonnet-4-5',
|
||||
// Cursor emits dot-version tier-last names
|
||||
'claude-4.6-sonnet': 'claude-sonnet-4-6',
|
||||
'claude-4.5-sonnet-thinking': 'claude-sonnet-4-5',
|
||||
// Cursor emits dot-version tier-last names plus tier/reasoning suffixes
|
||||
// that LiteLLM does not index (`-high`, `-low`, `-medium`, `-thinking`,
|
||||
// `-high-thinking`, `-fast-mode`). Missing aliases here surface as $0 in
|
||||
// the dashboard for users on non-Auto models (issue #159). Sources: the
|
||||
// display map at `src/providers/cursor.ts:modelDisplayNames`, Cursor's
|
||||
// public model docs at https://cursor.com/docs/models, and forum bug
|
||||
// reports that quote literal slugs (e.g. forum.cursor.com/t/154933).
|
||||
'claude-4-sonnet': 'claude-sonnet-4',
|
||||
'claude-4-sonnet-1m': 'claude-sonnet-4',
|
||||
'claude-4-sonnet-thinking': 'claude-sonnet-4-5',
|
||||
'claude-4-opus': 'claude-opus-4-5',
|
||||
'claude-4.5-sonnet': 'claude-sonnet-4-5',
|
||||
'claude-4.5-sonnet-thinking': 'claude-sonnet-4-5',
|
||||
'claude-4.6-sonnet': 'claude-sonnet-4-6',
|
||||
'claude-4.6-sonnet-high': 'claude-sonnet-4-6',
|
||||
'claude-4.6-sonnet-low': 'claude-sonnet-4-6',
|
||||
'claude-4.6-sonnet-thinking': 'claude-sonnet-4-6',
|
||||
'claude-4.6-sonnet-high-thinking':'claude-sonnet-4-6',
|
||||
'claude-4-opus': 'claude-opus-4',
|
||||
'claude-4.5-opus': 'claude-opus-4-5',
|
||||
'claude-4.5-opus-high': 'claude-opus-4-5',
|
||||
'claude-4.5-opus-low': 'claude-opus-4-5',
|
||||
'claude-4.5-opus-medium': 'claude-opus-4-5',
|
||||
'claude-4.5-opus-high-thinking': 'claude-opus-4-5',
|
||||
'claude-4.6-opus': 'claude-opus-4-6',
|
||||
'claude-4.6-opus-fast-mode': 'claude-opus-4-6',
|
||||
'claude-4.6-opus-high': 'claude-opus-4-6',
|
||||
'claude-4.6-opus-low': 'claude-opus-4-6',
|
||||
'claude-4.6-opus-medium': 'claude-opus-4-6',
|
||||
'claude-4.6-opus-high-thinking': 'claude-opus-4-6',
|
||||
'claude-4.7-opus': 'claude-opus-4-7',
|
||||
// Dash form (NOT dot) seen in forum.cursor.com/t/158597.
|
||||
'claude-opus-4-7-thinking-high': 'claude-opus-4-7',
|
||||
'claude-4.5-haiku': 'claude-haiku-4-5',
|
||||
'claude-4.6-haiku': 'claude-haiku-4-5',
|
||||
// Cursor's house models have no LiteLLM pricing entry. composer-1 is
|
||||
// sonnet-4.5-class per Cursor docs; composer-2 is built on Sonnet 4.6
|
||||
// per cursor.com/blog/composer-2.
|
||||
'composer-1': 'claude-sonnet-4-5',
|
||||
'composer-1.5': 'claude-sonnet-4-5',
|
||||
'composer-2': 'claude-sonnet-4-6',
|
||||
// Cursor's "fast" routing variant of GPT-5 is the same model behind a
|
||||
// lower-latency endpoint; price as base GPT-5 until LiteLLM tracks it.
|
||||
'gpt-5-fast': 'gpt-5',
|
||||
'gpt-4.1': 'gpt-4.1',
|
||||
'gpt-5.2-low': 'gpt-5',
|
||||
'gpt-5.1-codex-high': 'gpt-5.3-codex',
|
||||
|
|
|
|||
|
|
@ -179,3 +179,72 @@ describe('existing model names still resolve', () => {
|
|||
expect(getModelCosts('anthropic/claude-opus-4-6')).not.toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
// Issue #159: every model name Cursor emits in its SQLite database must
|
||||
// resolve to a non-zero pricing entry, otherwise the dashboard shows $0 for
|
||||
// that model. Each case asserts the resolved pricing identity matches the
|
||||
// pricing of the expected canonical key, so an accidental alias swap (e.g.
|
||||
// `claude-4.6-opus` aliased to a haiku entry) fails the test even though
|
||||
// haiku also has positive pricing.
|
||||
describe('Cursor model variants resolve to pricing', () => {
|
||||
const cases: Array<[string, string]> = [
|
||||
// Sonnet family
|
||||
['claude-4-sonnet', 'claude-sonnet-4'],
|
||||
['claude-4-sonnet-1m', 'claude-sonnet-4'],
|
||||
['claude-4-sonnet-thinking', 'claude-sonnet-4-5'],
|
||||
['claude-4.5-sonnet', 'claude-sonnet-4-5'],
|
||||
['claude-4.5-sonnet-thinking', 'claude-sonnet-4-5'],
|
||||
['claude-4.6-sonnet', 'claude-sonnet-4-6'],
|
||||
['claude-4.6-sonnet-high', 'claude-sonnet-4-6'],
|
||||
['claude-4.6-sonnet-low', 'claude-sonnet-4-6'],
|
||||
['claude-4.6-sonnet-thinking', 'claude-sonnet-4-6'],
|
||||
['claude-4.6-sonnet-high-thinking', 'claude-sonnet-4-6'],
|
||||
// Opus family
|
||||
['claude-4-opus', 'claude-opus-4'],
|
||||
['claude-4.5-opus', 'claude-opus-4-5'],
|
||||
['claude-4.5-opus-high', 'claude-opus-4-5'],
|
||||
['claude-4.5-opus-low', 'claude-opus-4-5'],
|
||||
['claude-4.5-opus-medium', 'claude-opus-4-5'],
|
||||
['claude-4.5-opus-high-thinking', 'claude-opus-4-5'],
|
||||
['claude-4.6-opus', 'claude-opus-4-6'],
|
||||
['claude-4.6-opus-fast-mode', 'claude-opus-4-6'],
|
||||
['claude-4.6-opus-high', 'claude-opus-4-6'],
|
||||
['claude-4.6-opus-low', 'claude-opus-4-6'],
|
||||
['claude-4.6-opus-medium', 'claude-opus-4-6'],
|
||||
['claude-4.6-opus-high-thinking', 'claude-opus-4-6'],
|
||||
['claude-4.7-opus', 'claude-opus-4-7'],
|
||||
['claude-opus-4-7-thinking-high', 'claude-opus-4-7'],
|
||||
// Haiku family
|
||||
['claude-4.5-haiku', 'claude-haiku-4-5'],
|
||||
['claude-4.6-haiku', 'claude-haiku-4-5'],
|
||||
// Cursor house models
|
||||
['composer-1', 'claude-sonnet-4-5'],
|
||||
['composer-1.5', 'claude-sonnet-4-5'],
|
||||
['composer-2', 'claude-sonnet-4-6'],
|
||||
['cursor-auto', 'claude-sonnet-4-5'],
|
||||
// OpenAI variants Cursor emits
|
||||
['gpt-5', 'gpt-5'],
|
||||
['gpt-5-fast', 'gpt-5'],
|
||||
['gpt-5.2', 'gpt-5.2'],
|
||||
['gpt-5.2-low', 'gpt-5'],
|
||||
// Direct LiteLLM hits where no alias is required
|
||||
['grok-code-fast-1', 'grok-code-fast-1'],
|
||||
['gemini-3-pro', 'gemini-3-pro-preview'],
|
||||
]
|
||||
|
||||
for (const [input, expectedAlias] of cases) {
|
||||
it(`${input} resolves to ${expectedAlias} pricing`, () => {
|
||||
const costs = getModelCosts(input)
|
||||
expect(costs, `${input} should resolve to pricing (and not produce $0 in the dashboard)`).not.toBeNull()
|
||||
expect(costs!.inputCostPerToken).toBeGreaterThan(0)
|
||||
expect(costs!.outputCostPerToken).toBeGreaterThan(0)
|
||||
const expected = getModelCosts(expectedAlias)
|
||||
expect(expected, `expected target ${expectedAlias} should itself resolve`).not.toBeNull()
|
||||
// Identity check: the alias must produce the SAME pricing object as
|
||||
// the canonical key, not just any non-zero pricing. Catches drift
|
||||
// where a future edit re-points an alias at a wrong-but-positive entry.
|
||||
expect(costs!.inputCostPerToken).toBe(expected!.inputCostPerToken)
|
||||
expect(costs!.outputCostPerToken).toBe(expected!.outputCostPerToken)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue