mirror of
https://github.com/stablyai/orca.git
synced 2026-10-19 16:03:26 +00:00
fix(usage): price the bare gpt-5.6 alias and assert Opus 4.5 separately
OpenAI routes the bare `gpt-5.6` alias to Sol, but only the explicit `-sol` / `-terra` / `-luna` IDs resolved, so alias-recorded sessions still reported no cost. Match it exactly rather than by prefix so it cannot swallow the tier IDs or a future cheaper variant. Also split the Claude 5 shadowing guard into per-model breakdown assertions and add the missing Opus 4.5 fixture the test name claimed.
This commit is contained in:
@@ -343,27 +343,34 @@ describe('ClaudeUsageStore', () => {
|
||||
|
||||
it('does not collapse Opus 4.5 or Sonnet 4.5 usage into Claude 5 pricing', async () => {
|
||||
const store = createStoreWithState({
|
||||
dailyAggregates: [
|
||||
{
|
||||
day: '2026-04-09',
|
||||
model: 'claude-sonnet-4-5-20250929',
|
||||
projectKey: 'worktree:repo-1::/workspace/repo-a',
|
||||
projectLabel: 'Repo A',
|
||||
repoId: 'repo-1',
|
||||
worktreeId: 'repo-1::/workspace/repo-a',
|
||||
turnCount: 1,
|
||||
zeroCacheReadTurnCount: 0,
|
||||
inputTokens: 300_000,
|
||||
outputTokens: 300_000,
|
||||
cacheReadTokens: 300_000,
|
||||
cacheWriteTokens: 300_000
|
||||
}
|
||||
]
|
||||
dailyAggregates: ['claude-sonnet-4-5-20250929', 'claude-opus-4-5-20251101'].map((model) => ({
|
||||
day: '2026-04-09',
|
||||
model,
|
||||
projectKey: 'worktree:repo-1::/workspace/repo-a',
|
||||
projectLabel: 'Repo A',
|
||||
repoId: 'repo-1',
|
||||
worktreeId: 'repo-1::/workspace/repo-a',
|
||||
turnCount: 1,
|
||||
zeroCacheReadTurnCount: 0,
|
||||
inputTokens: 300_000,
|
||||
outputTokens: 300_000,
|
||||
cacheReadTokens: 300_000,
|
||||
cacheWriteTokens: 300_000
|
||||
}))
|
||||
})
|
||||
|
||||
const summary = await store.getSummary('orca', '30d')
|
||||
const breakdown = await store.getBreakdown('orca', '30d', 'model')
|
||||
|
||||
expect(summary.estimatedCostUsd).toBeCloseTo(8.07)
|
||||
// Why: the 4.5 tier premium only survives if `-4-5-` never matches the `-5`
|
||||
// family regex, so this doubles as the digit-boundary proof for both families.
|
||||
expect(
|
||||
breakdown.find((row) => row.key === 'claude-sonnet-4-5-20250929')?.estimatedCostUsd
|
||||
).toBeCloseTo(8.07)
|
||||
// Why: Opus 4.5 and Opus 5 share rates today, so this pins the rate rather
|
||||
// than the routing — it fails only if the two ever diverge.
|
||||
expect(
|
||||
breakdown.find((row) => row.key === 'claude-opus-4-5-20251101')?.estimatedCostUsd
|
||||
).toBeCloseTo(11.025)
|
||||
})
|
||||
|
||||
it('prices unknown newer Opus 4 point releases with current Opus rates', async () => {
|
||||
|
||||
@@ -425,6 +425,31 @@ describe('CodexUsageStore', () => {
|
||||
).toBeCloseTo(0.205)
|
||||
})
|
||||
|
||||
it('prices the bare gpt-5.6 alias at Sol rates without shadowing the tier IDs', async () => {
|
||||
const store = createStoreWithState({
|
||||
dailyAggregates: ['gpt-5.6', 'gpt-5.6-luna'].map((model) => ({
|
||||
day: '2026-04-09',
|
||||
model,
|
||||
projectKey: 'worktree:repo-1::/workspace/repo',
|
||||
projectLabel: 'Repo',
|
||||
repoId: 'repo-1',
|
||||
worktreeId: 'repo-1::/workspace/repo',
|
||||
eventCount: 1,
|
||||
inputTokens: 100_000,
|
||||
cachedInputTokens: 50_000,
|
||||
outputTokens: 25_000,
|
||||
reasoningOutputTokens: 5_000,
|
||||
totalTokens: 125_000,
|
||||
hasInferredPricing: false
|
||||
}))
|
||||
})
|
||||
|
||||
const breakdown = await store.getBreakdown('orca', '30d', 'model')
|
||||
|
||||
expect(breakdown.find((row) => row.key === 'gpt-5.6')?.estimatedCostUsd).toBeCloseTo(1.025)
|
||||
expect(breakdown.find((row) => row.key === 'gpt-5.6-luna')?.estimatedCostUsd).toBeCloseTo(0.205)
|
||||
})
|
||||
|
||||
it('normalizes Codex model variants and reasoning suffixes before pricing', async () => {
|
||||
const store = createStoreWithState({
|
||||
dailyAggregates: [
|
||||
|
||||
@@ -261,6 +261,12 @@ function normalizeModelForPricing(model: string | null): string | null {
|
||||
if (normalized === 'gpt-5.6-luna' || normalized.startsWith('gpt-5.6-luna-')) {
|
||||
return 'gpt-5.6-luna'
|
||||
}
|
||||
// Why: OpenAI routes the bare `gpt-5.6` alias to Sol. Match it exactly — a
|
||||
// `gpt-5.6-` prefix match would swallow the tier IDs above and any future
|
||||
// cheaper variant.
|
||||
if (normalized === 'gpt-5.6') {
|
||||
return 'gpt-5.6-sol'
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user