mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 16:19:27 +00:00
fix(usage): clarify static pricing fallback
This commit is contained in:
@@ -758,16 +758,25 @@ const UNKNOWN_MODEL_PRICING: ModelPricing = {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize model name for matching
|
* Strip provider prefixes used by routing/catalog metadata.
|
||||||
* Handles variations like provider prefixes and case differences
|
* 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 {
|
function normalizeModelName(model: string): string {
|
||||||
// Remove provider prefixes (e.g., "anthropic/claude-..." -> "claude-...")
|
return stripProviderPrefix(model).toLowerCase();
|
||||||
const normalized = model
|
|
||||||
.trim()
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(/^[^/]+\//, '');
|
|
||||||
return normalized;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -835,6 +844,20 @@ function getDirectOrAliasPricing(model: string): ModelPricing | undefined {
|
|||||||
return 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 {
|
function hasProviderContext(model: string, options: PricingLookupOptions): boolean {
|
||||||
return Boolean(options.provider || /^[^/]+\//.test(model.trim()));
|
return Boolean(options.provider || /^[^/]+\//.test(model.trim()));
|
||||||
}
|
}
|
||||||
@@ -852,14 +875,9 @@ export function getModelPricing(model: string, options: PricingLookupOptions = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const exactPricing = PRICING_REGISTRY[model];
|
const ccsStaticPricing = getCcsStaticPricing(model);
|
||||||
if (exactPricing !== undefined) {
|
if (ccsStaticPricing !== undefined) {
|
||||||
return exactPricing;
|
return ccsStaticPricing;
|
||||||
}
|
|
||||||
|
|
||||||
const directOrAliasPricing = getDirectOrAliasPricing(model);
|
|
||||||
if (directOrAliasPricing !== undefined) {
|
|
||||||
return directOrAliasPricing;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const modelsDevPricing = resolveModelsDevPricing(model, options);
|
const modelsDevPricing = resolveModelsDevPricing(model, options);
|
||||||
@@ -914,7 +932,7 @@ export function getKnownModels(): string[] {
|
|||||||
*/
|
*/
|
||||||
export function hasCustomPricing(model: string, options: PricingLookupOptions = {}): boolean {
|
export function hasCustomPricing(model: string, options: PricingLookupOptions = {}): boolean {
|
||||||
return (
|
return (
|
||||||
getDirectOrAliasPricing(model) !== undefined ||
|
getCcsStaticPricing(model) !== undefined ||
|
||||||
resolveModelsDevPricing(model, options) !== undefined
|
resolveModelsDevPricing(model, options) !== undefined
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -388,6 +388,16 @@ describe('model-pricing', () => {
|
|||||||
expect(getModelPricing('gpt-4o').inputPerMillion).toBe(2.5);
|
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', () => {
|
it('does not use ambiguous model-only models.dev matches', () => {
|
||||||
const pricing = getModelPricing('gpt-5.5');
|
const pricing = getModelPricing('gpt-5.5');
|
||||||
expect(pricing).toEqual(getModelPricing('unknown-model-xyz'));
|
expect(pricing).toEqual(getModelPricing('unknown-model-xyz'));
|
||||||
|
|||||||
Reference in New Issue
Block a user