fix(usage): clarify static pricing fallback

This commit is contained in:
Tam Nhu Tran
2026-04-28 16:26:27 -04:00
parent c7141b3d3a
commit 42fc5281a1
2 changed files with 45 additions and 17 deletions
+35 -17
View File
@@ -758,16 +758,25 @@ const UNKNOWN_MODEL_PRICING: ModelPricing = {
// ============================================================================
/**
* Normalize model name for matching
* Handles variations like provider prefixes and case differences
* Strip provider prefixes used by routing/catalog metadata.
* The CCS static table remains model-keyed, so static fallback normalizes
* provider-qualified model IDs before checking aliases.
*/
function stripProviderPrefix(model: string): string {
const trimmed = model.trim();
const slashIndex = trimmed.indexOf('/');
if (slashIndex <= 0) {
return trimmed;
}
return trimmed.slice(slashIndex + 1);
}
/**
* Normalize model name for matching.
* Handles variations like provider prefixes and case differences.
*/
function normalizeModelName(model: string): string {
// Remove provider prefixes (e.g., "anthropic/claude-..." -> "claude-...")
const normalized = model
.trim()
.toLowerCase()
.replace(/^[^/]+\//, '');
return normalized;
return stripProviderPrefix(model).toLowerCase();
}
/**
@@ -835,6 +844,20 @@ function getDirectOrAliasPricing(model: string): ModelPricing | undefined {
return undefined;
}
function getCcsStaticPricing(model: string): ModelPricing | undefined {
const staticPricing = getDirectOrAliasPricing(model);
if (staticPricing !== undefined) {
return staticPricing;
}
const providerlessModel = stripProviderPrefix(model);
if (providerlessModel !== model.trim()) {
return getDirectOrAliasPricing(providerlessModel);
}
return undefined;
}
function hasProviderContext(model: string, options: PricingLookupOptions): boolean {
return Boolean(options.provider || /^[^/]+\//.test(model.trim()));
}
@@ -852,14 +875,9 @@ export function getModelPricing(model: string, options: PricingLookupOptions = {
}
}
const exactPricing = PRICING_REGISTRY[model];
if (exactPricing !== undefined) {
return exactPricing;
}
const directOrAliasPricing = getDirectOrAliasPricing(model);
if (directOrAliasPricing !== undefined) {
return directOrAliasPricing;
const ccsStaticPricing = getCcsStaticPricing(model);
if (ccsStaticPricing !== undefined) {
return ccsStaticPricing;
}
const modelsDevPricing = resolveModelsDevPricing(model, options);
@@ -914,7 +932,7 @@ export function getKnownModels(): string[] {
*/
export function hasCustomPricing(model: string, options: PricingLookupOptions = {}): boolean {
return (
getDirectOrAliasPricing(model) !== undefined ||
getCcsStaticPricing(model) !== undefined ||
resolveModelsDevPricing(model, options) !== undefined
);
}
+10
View File
@@ -388,6 +388,16 @@ describe('model-pricing', () => {
expect(getModelPricing('gpt-4o').inputPerMillion).toBe(2.5);
});
it('falls back to CCS static pricing when provider-aware models.dev lookup misses a known model', () => {
const staticPricing = getModelPricing('claude-sonnet-4-5');
expect(getModelPricing('anthropic/claude-sonnet-4-5')).toEqual(staticPricing);
expect(getModelPricing('claude-sonnet-4-5', { provider: 'anthropic' })).toEqual(
staticPricing
);
expect(hasCustomPricing('anthropic/claude-sonnet-4-5')).toBe(true);
});
it('does not use ambiguous model-only models.dev matches', () => {
const pricing = getModelPricing('gpt-5.5');
expect(pricing).toEqual(getModelPricing('unknown-model-xyz'));