From bf6eee00f3aeb006c3126f209450bf813e46ffec Mon Sep 17 00:00:00 2001 From: AgentSeal Date: Thu, 9 Jul 2026 21:22:23 +0200 Subject: [PATCH] fix(report): only exact zero-rate overrides suppress the unpriced flag getModelCosts resolves exact overrides before any table hit, so an exact zero-rate override provably priced the model and means free. Prefix and case-insensitive overrides resolve after table hits: a zero-rate stub shadowed by one still reports $0 from the stub, so it stays flagged. Refs #638 --- src/models.ts | 18 ++++++++++-------- tests/models.test.ts | 6 ++++++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/models.ts b/src/models.ts index 9b44417..b314ae9 100644 --- a/src/models.ts +++ b/src/models.ts @@ -692,17 +692,19 @@ function hasBillableRate(costs: ModelCosts): boolean { || costs.cacheReadCostPerToken > 0 } -// The user-override lookup from getModelCosts, in isolation. Lets the unpriced -// detector distinguish "explicitly declared free by the user" (a zero-rate -// override) from a zero-rate LiteLLM stub, which means "listed but unknown -// price" and must still be flagged. -function userPriceOverrideFor(model: string): ModelCosts | null { +// Exact-override lookup with the same key derivation getModelCosts uses. Lets +// the unpriced detector distinguish "explicitly declared free by the user" (a +// zero-rate override) from a zero-rate LiteLLM stub, which means "listed but +// unknown price" and must still be flagged. Only the EXACT override form is +// consulted: getModelCosts checks it before any table hit, so when one exists +// it is provably what priced the model. Prefix and case-insensitive overrides +// resolve AFTER table hits and so cannot prove the $0 was intentional; a +// zero-rate stub shadowed by one still gets flagged (the honest direction). +function exactPriceOverrideFor(model: string): ModelCosts | null { const withPrefix = model.replace(/@.*$/, '').replace(/-\d{8}$/, '') const canonicalName = getCanonicalName(model) const canonical = resolveAlias(canonicalName) return getPriceOverrideExact(model, withPrefix, canonicalName, canonical) - ?? getPriceOverridePrefix(canonical) - ?? getPriceOverrideCaseInsensitive(canonical, withPrefix) } // Render-time unpriced detection (#638): flag aggregated model rows that carry @@ -735,7 +737,7 @@ export function findUnpricedModels( if (getLocalSavingsBaseline(model)) continue const costs = getModelCosts(model) if (costs && hasBillableRate(costs)) continue - if (costs && userPriceOverrideFor(model)) continue + if (costs && exactPriceOverrideFor(model)) continue out.push({ model, calls: row.calls, tokens }) } return out.sort((a, b) => (b.tokens - a.tokens) || (b.calls - a.calls) diff --git a/tests/models.test.ts b/tests/models.test.ts index b0eebff..1f70385 100644 --- a/tests/models.test.ts +++ b/tests/models.test.ts @@ -736,6 +736,12 @@ describe('findUnpricedModels', () => { // An explicit user override at zero rates means "this model is free". setPriceOverrides({ 'zz-zero-stub-model': { input: 0, output: 0 } }) expect(findUnpricedModels(rows)).toEqual([]) + + // A prefix override cannot prove intent: getModelCosts resolves table + // hits before prefix overrides, so the $0 came from the stub, not the + // user. Still flagged. + setPriceOverrides({ 'zz-zero-stub': { input: 0, output: 0 } }) + expect(findUnpricedModels(rows)).toHaveLength(1) } finally { delete process.env['CODEBURN_CACHE_DIR'] await rm(cacheRoot, { recursive: true, force: true })