mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-05-17 03:56:45 +00:00
132 lines
4.3 KiB
TypeScript
132 lines
4.3 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import { calculateCost, getModelCosts, getShortModelName } from '../src/models.js'
|
|
|
|
// Lock down the post-hoist refactor: every model name a real user has
|
|
// emitted in the last year should resolve to the same display name and
|
|
// the same costs as before. If this list grows or shrinks, the refactor
|
|
// is fine — it's the per-name resolution that must stay stable.
|
|
const KNOWN_NAMES = [
|
|
'claude-opus-4-7',
|
|
'claude-opus-4-6',
|
|
'claude-opus-4-5',
|
|
'claude-sonnet-4-6',
|
|
'claude-sonnet-4-5',
|
|
'claude-haiku-4-5',
|
|
'claude-3-5-sonnet',
|
|
'claude-3-5-haiku',
|
|
'claude-opus-4-7-20250101',
|
|
'claude-sonnet-4-6-20250929',
|
|
'anthropic/claude-opus-4-7',
|
|
'anthropic--claude-4.6-opus',
|
|
'anthropic--claude-4.6-sonnet',
|
|
'claude-4.6-sonnet',
|
|
'gpt-5',
|
|
'gpt-5-mini',
|
|
'gpt-5-nano',
|
|
'gpt-5-pro',
|
|
'gpt-5.1',
|
|
'gpt-5.1-codex',
|
|
'gpt-5.1-codex-mini',
|
|
'gpt-5.2',
|
|
'gpt-5.2-low',
|
|
'gpt-5.3-codex',
|
|
'gpt-5.4',
|
|
'gpt-5.4-mini',
|
|
'gpt-4o',
|
|
'gpt-4o-mini',
|
|
'gpt-4.1',
|
|
'gpt-4.1-mini',
|
|
'gpt-4.1-nano',
|
|
'gemini-2.5-pro',
|
|
'gemini-2.5-flash',
|
|
'gemini-3.1-pro-preview',
|
|
'gemini-3-flash-preview',
|
|
'gemini-3.1-pro',
|
|
'gemini-3-flash',
|
|
'cursor-auto',
|
|
'cursor-agent-auto',
|
|
'copilot-auto',
|
|
'copilot-openai-auto',
|
|
'kiro-auto',
|
|
'cline-auto',
|
|
'qwen-auto',
|
|
'kimi-auto',
|
|
'kimi-for-coding',
|
|
'kimi-k2-thinking-turbo',
|
|
'kimi-k2.6',
|
|
'o3',
|
|
'o4-mini',
|
|
'deepseek-coder',
|
|
'deepseek-coder-max',
|
|
'deepseek-r1',
|
|
'MiniMax-M2.7',
|
|
'MiniMax-M2.7-highspeed',
|
|
]
|
|
|
|
describe('post-hoist resolution stability', () => {
|
|
it('every known model resolves to a non-empty short name', () => {
|
|
for (const name of KNOWN_NAMES) {
|
|
const short = getShortModelName(name)
|
|
expect(short, `short name for ${name}`).toBeTruthy()
|
|
expect(typeof short, `short name for ${name}`).toBe('string')
|
|
}
|
|
})
|
|
|
|
it('gpt-5-mini does NOT collide with gpt-5 (longest-prefix wins)', () => {
|
|
expect(getShortModelName('gpt-5-mini')).toBe('GPT-5 Mini')
|
|
expect(getShortModelName('gpt-5')).toBe('GPT-5')
|
|
expect(getShortModelName('gpt-5-nano')).toBe('GPT-5 Nano')
|
|
expect(getShortModelName('gpt-5-pro')).toBe('GPT-5 Pro')
|
|
})
|
|
|
|
it('gpt-5.1-codex-mini does NOT collapse to gpt-5.1-codex or gpt-5', () => {
|
|
expect(getShortModelName('gpt-5.1-codex-mini')).toBe('GPT-5.1 Codex Mini')
|
|
expect(getShortModelName('gpt-5.1-codex')).toBe('GPT-5.1 Codex')
|
|
expect(getShortModelName('gpt-5.1')).toBe('GPT-5.1')
|
|
})
|
|
|
|
it('claude-haiku-4-5 does NOT collapse to claude-haiku-4 or claude-3-5-haiku', () => {
|
|
expect(getShortModelName('claude-haiku-4-5')).toBe('Haiku 4.5')
|
|
expect(getShortModelName('claude-3-5-haiku')).toBe('Haiku 3.5')
|
|
})
|
|
|
|
it('kimi managed aliases resolve to priced Kimi models', () => {
|
|
expect(getShortModelName('kimi-auto')).toBe('Kimi (auto)')
|
|
expect(getShortModelName('kimi-for-coding')).toBe('Kimi K2 Thinking')
|
|
expect(getShortModelName('kimi-k2-thinking-turbo')).toBe('Kimi K2 Thinking Turbo')
|
|
expect(getShortModelName('kimi-k2.6')).toBe('Kimi K2.6')
|
|
expect(getModelCosts('kimi-auto')?.inputCostPerToken).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('getModelCosts returns positive token costs for every known name', () => {
|
|
for (const name of KNOWN_NAMES) {
|
|
const c = getModelCosts(name)
|
|
expect(c, `costs for ${name}`).not.toBeNull()
|
|
expect(c!.inputCostPerToken).toBeGreaterThan(0)
|
|
expect(c!.outputCostPerToken).toBeGreaterThan(0)
|
|
}
|
|
})
|
|
|
|
it('calculateCost is stable for a typical Sonnet 4.6 turn', () => {
|
|
// 1k input, 2k output, 50k cache read — common Claude Code shape.
|
|
const cost = calculateCost('claude-sonnet-4-6', 1000, 2000, 0, 50_000, 0)
|
|
expect(cost).toBeGreaterThan(0)
|
|
expect(Number.isFinite(cost)).toBe(true)
|
|
})
|
|
|
|
it('calculateCost clamps NaN/negative inputs to 0', () => {
|
|
const c1 = calculateCost('claude-sonnet-4-6', NaN, 1000, 0, 0, 0)
|
|
const c2 = calculateCost('claude-sonnet-4-6', 0, 1000, 0, 0, 0)
|
|
expect(c1).toBe(c2)
|
|
const c3 = calculateCost('claude-sonnet-4-6', -1000, 1000, 0, 0, 0)
|
|
expect(c3).toBe(c2)
|
|
})
|
|
|
|
it('repeated calls return the same cost (memoized sort cache is consistent)', () => {
|
|
const a = getModelCosts('gpt-5-mini')
|
|
const b = getModelCosts('gpt-5-mini')
|
|
const c = getModelCosts('gpt-5-mini')
|
|
expect(a).toEqual(b)
|
|
expect(b).toEqual(c)
|
|
})
|
|
})
|