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
This commit is contained in:
AgentSeal 2026-07-09 21:22:23 +02:00
parent e6162f6b50
commit bf6eee00f3
2 changed files with 16 additions and 8 deletions

View file

@ -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)

View file

@ -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 })