mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 06:16:37 +00:00
refactor(pricing): derive fast-tier cache rates via buildRates helper
Address review feedback on the per-service-tier pricing: - Extract `buildRates(input, output)` plus named CACHE_5M_WRITE_MULTIPLIER / CACHE_READ_MULTIPLIER constants so fast-tier cache rates are derived from Anthropic's documented multipliers instead of hand-computed numbers. This removes the inconsistency where Opus 4.6 fast-tier lacked the explanatory inline comments that 4.7/4.8 carried (presto suggestion #3). - Document in `applyServiceTier` that no production caller passes `serviceTier` yet (Anthropic's service_tier is not captured on CliproxyRequestDetail), so fast-mode usage is currently billed at the standard rate until the usage pipeline records the tier. - Add a combined date-suffix + fast-tier lookup test to prove the two resolution stages compose. Built [OnSteroids](https://onsteroids.ai)
This commit is contained in:
@@ -50,6 +50,26 @@ export interface PricingLookupOptions extends ModelsDevPricingLookupOptions {
|
||||
serviceTier?: string;
|
||||
}
|
||||
|
||||
// Anthropic prompt-caching multipliers, expressed relative to the base input
|
||||
// rate (see https://platform.claude.com/docs/en/about-claude/pricing#prompt-caching).
|
||||
// Keeping them as named constants avoids drift when deriving per-tier cache rates.
|
||||
const CACHE_5M_WRITE_MULTIPLIER = 1.25;
|
||||
const CACHE_READ_MULTIPLIER = 0.1;
|
||||
|
||||
/**
|
||||
* Build a full rate set from input/output rates, deriving cache rates from the
|
||||
* documented Anthropic multipliers so fast-tier entries stay in sync with the
|
||||
* base-rate math instead of carrying hand-computed cache numbers.
|
||||
*/
|
||||
function buildRates(inputPerMillion: number, outputPerMillion: number): PricingRates {
|
||||
return {
|
||||
inputPerMillion,
|
||||
outputPerMillion,
|
||||
cacheCreationPerMillion: inputPerMillion * CACHE_5M_WRITE_MULTIPLIER,
|
||||
cacheReadPerMillion: inputPerMillion * CACHE_READ_MULTIPLIER,
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// USER-EDITABLE PRICING TABLE
|
||||
// Update rates below (per million tokens in USD)
|
||||
@@ -247,12 +267,7 @@ const PRICING_REGISTRY: Record<string, ModelPricing> = {
|
||||
cacheCreationPerMillion: 6.25,
|
||||
cacheReadPerMillion: 0.5,
|
||||
serviceTiers: {
|
||||
fast: {
|
||||
inputPerMillion: 30.0,
|
||||
outputPerMillion: 150.0,
|
||||
cacheCreationPerMillion: 37.5,
|
||||
cacheReadPerMillion: 3.0,
|
||||
},
|
||||
fast: buildRates(30.0, 150.0),
|
||||
},
|
||||
},
|
||||
'claude-opus-4-6-thinking': {
|
||||
@@ -268,12 +283,7 @@ const PRICING_REGISTRY: Record<string, ModelPricing> = {
|
||||
cacheCreationPerMillion: 6.25,
|
||||
cacheReadPerMillion: 0.5,
|
||||
serviceTiers: {
|
||||
fast: {
|
||||
inputPerMillion: 30.0,
|
||||
outputPerMillion: 150.0,
|
||||
cacheCreationPerMillion: 37.5, // 30 * 1.25 (5m cache write multiplier)
|
||||
cacheReadPerMillion: 3.0, // 30 * 0.1 (cache read multiplier)
|
||||
},
|
||||
fast: buildRates(30.0, 150.0),
|
||||
},
|
||||
},
|
||||
// Claude 4.8 Opus ($5/$25) — fast mode ($10/$50, 2x premium per Anthropic docs)
|
||||
@@ -283,12 +293,7 @@ const PRICING_REGISTRY: Record<string, ModelPricing> = {
|
||||
cacheCreationPerMillion: 6.25,
|
||||
cacheReadPerMillion: 0.5,
|
||||
serviceTiers: {
|
||||
fast: {
|
||||
inputPerMillion: 10.0,
|
||||
outputPerMillion: 50.0,
|
||||
cacheCreationPerMillion: 12.5, // 10 * 1.25 (5m cache write multiplier)
|
||||
cacheReadPerMillion: 1.0, // 10 * 0.1 (cache read multiplier)
|
||||
},
|
||||
fast: buildRates(10.0, 50.0),
|
||||
},
|
||||
},
|
||||
'claude-opus-4-7-thinking': {
|
||||
@@ -944,6 +949,13 @@ function hasProviderContext(model: string, options: PricingLookupOptions): boole
|
||||
* Apply per-service-tier rates if the resolved model declares them and the
|
||||
* caller requested a matching tier. Unknown tiers transparently fall through
|
||||
* to the base rates so existing callers stay unaffected.
|
||||
*
|
||||
* TODO(opus-fast-mode): No production caller currently passes `serviceTier`.
|
||||
* Anthropic's `service_tier` is not yet captured on CliproxyRequestDetail, so
|
||||
* usage transformers (cliproxy-usage-transformer.ts, data-aggregator.ts) bill
|
||||
* fast-mode requests at the standard rate — i.e. fast Opus is under-reported by
|
||||
* the tier premium ($10/$50 vs $5/$25). Wire `serviceTier` through once the
|
||||
* usage pipeline records it. The schema below is ready for that integration.
|
||||
*/
|
||||
function applyServiceTier(pricing: ModelPricing, tier: string | undefined): ModelPricing {
|
||||
if (!tier) return pricing;
|
||||
|
||||
@@ -202,6 +202,16 @@ describe('model-pricing', () => {
|
||||
expect(opus47fast.outputPerMillion).toBe(150.0);
|
||||
});
|
||||
|
||||
it('should apply fast-tier pricing together with date-suffix stripping', () => {
|
||||
// stripDateSuffix (base lookup) and applyServiceTier run independently;
|
||||
// confirm they compose for a date-stamped id requesting the fast tier.
|
||||
const opus48fastDated = getModelPricing('claude-opus-4-8-20260530', { serviceTier: 'fast' });
|
||||
expect(opus48fastDated.inputPerMillion).toBe(10.0);
|
||||
expect(opus48fastDated.outputPerMillion).toBe(50.0);
|
||||
expect(opus48fastDated.cacheCreationPerMillion).toBe(12.5);
|
||||
expect(opus48fastDated.cacheReadPerMillion).toBe(1.0);
|
||||
});
|
||||
|
||||
it('should fall back to standard rates when serviceTier is unknown', () => {
|
||||
// Unknown tier names must not throw; revert to base pricing transparently.
|
||||
const opus48 = getModelPricing('claude-opus-4-8', { serviceTier: 'enterprise-mythos' });
|
||||
|
||||
Reference in New Issue
Block a user