feat(pricing): support per-service-tier rates; wire Opus fast mode

Extend `ModelPricing` with an optional `serviceTiers` map keyed by
Anthropic's `service_tier` request parameter. `getModelPricing(model,
{ serviceTier })` returns the matching tier rates when present and
transparently falls through to base rates otherwise — existing call
sites keep current behavior.

Registry entries added (per Anthropic Fast mode pricing docs):
- claude-opus-4-8 fast: $10/$50 input/output (2x premium)
- claude-opus-4-7 fast: $30/$150 (6x premium)
- claude-opus-4-6 fast: $30/$150 (6x premium)

Cache rates derived from the documented Prompt caching multipliers
(1.25x for 5-min cache write, 0.1x for cache read).

Note: tier tracking through CliproxyRequestDetail is not wired yet —
that's a follow-up so usage transformers can pass `serviceTier` when
Anthropic returns it on the response. Schema is in place; integration
can land independently.

Also adds a defensive `claude-opus-4-8-20260530` pricing test to exercise
`stripDateSuffix` even though Anthropic no longer issues date-stamped IDs
for the 4.6+ generation (addresses presto-review suggestion #1).

Built [OnSteroids](https://onsteroids.ai)
This commit is contained in:
Sergey Galuza
2026-05-30 22:17:21 +02:00
parent 357ce8b07a
commit 878775f5b6
2 changed files with 113 additions and 5 deletions
+62 -5
View File
@@ -17,13 +17,23 @@ import {
// TYPE DEFINITIONS
// ============================================================================
export interface ModelPricing {
export interface PricingRates {
inputPerMillion: number;
outputPerMillion: number;
cacheCreationPerMillion: number;
cacheReadPerMillion: number;
}
export interface ModelPricing extends PricingRates {
/**
* Optional per-service-tier rate overrides keyed by Anthropic's
* `service_tier` request parameter (e.g. `'fast'`). When the lookup is
* called with a known tier, these rates replace the base ones; otherwise
* the base rates apply.
*/
serviceTiers?: Record<string, PricingRates>;
}
export interface TokenUsage {
inputTokens: number;
outputTokens: number;
@@ -31,7 +41,14 @@ export interface TokenUsage {
cacheReadTokens: number;
}
export type PricingLookupOptions = ModelsDevPricingLookupOptions;
export interface PricingLookupOptions extends ModelsDevPricingLookupOptions {
/**
* Anthropic `service_tier` (e.g. `'fast'`). When set and the resolved model
* has matching `serviceTiers` rates, those are returned instead of the
* base rates. Unknown tiers transparently fall through to base.
*/
serviceTier?: string;
}
// ============================================================================
// USER-EDITABLE PRICING TABLE
@@ -223,12 +240,20 @@ const PRICING_REGISTRY: Record<string, ModelPricing> = {
cacheCreationPerMillion: 6.25,
cacheReadPerMillion: 0.5,
},
// Claude 4.6 Opus ($5/$25)
// Claude 4.6 Opus ($5/$25) — fast mode ($30/$150, 6x premium per Anthropic docs)
'claude-opus-4-6': {
inputPerMillion: 5.0,
outputPerMillion: 25.0,
cacheCreationPerMillion: 6.25,
cacheReadPerMillion: 0.5,
serviceTiers: {
fast: {
inputPerMillion: 30.0,
outputPerMillion: 150.0,
cacheCreationPerMillion: 37.5,
cacheReadPerMillion: 3.0,
},
},
},
'claude-opus-4-6-thinking': {
inputPerMillion: 5.0,
@@ -236,19 +261,35 @@ const PRICING_REGISTRY: Record<string, ModelPricing> = {
cacheCreationPerMillion: 6.25,
cacheReadPerMillion: 0.5,
},
// Claude 4.7 Opus ($5/$25)
// Claude 4.7 Opus ($5/$25) — fast mode ($30/$150, 6x premium per Anthropic docs)
'claude-opus-4-7': {
inputPerMillion: 5.0,
outputPerMillion: 25.0,
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)
},
},
},
// Claude 4.8 Opus ($5/$25)
// Claude 4.8 Opus ($5/$25) — fast mode ($10/$50, 2x premium per Anthropic docs)
'claude-opus-4-8': {
inputPerMillion: 5.0,
outputPerMillion: 25.0,
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)
},
},
},
// ---------------------------------------------------------------------------
@@ -893,12 +934,28 @@ function hasProviderContext(model: string, options: PricingLookupOptions): boole
return Boolean(options.provider || /^[^/]+\//.test(model.trim()));
}
/**
* 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.
*/
function applyServiceTier(pricing: ModelPricing, tier: string | undefined): ModelPricing {
if (!tier) return pricing;
const tierRates = pricing.serviceTiers?.[tier];
if (!tierRates) return pricing;
return { ...tierRates, serviceTiers: pricing.serviceTiers };
}
/**
* Get pricing for a model with narrow fuzzy matching fallback.
* Unknown future model families should fall back instead of inheriting the
* first known family tier that happens to share a prefix.
*/
export function getModelPricing(model: string, options: PricingLookupOptions = {}): ModelPricing {
return applyServiceTier(resolveBasePricing(model, options), options.serviceTier);
}
function resolveBasePricing(model: string, options: PricingLookupOptions): ModelPricing {
if (hasProviderContext(model, options)) {
const ccsOverridePricing = getCcsPolicyOverridePricing(model);
if (ccsOverridePricing !== undefined) {
+51
View File
@@ -176,6 +176,46 @@ describe('model-pricing', () => {
expect(opus48.cacheReadPerMillion).toBe(0.5);
});
it('should match date-stamped Claude Opus 4.8 to correct pricing', () => {
// Anthropic stopped issuing date-stamped Opus IDs starting with the 4.6
// generation, so this is a defensive guard for stripDateSuffix in case
// a third-party emits a synthesized dated alias.
const opus48dated = getModelPricing('claude-opus-4-8-20260530');
expect(opus48dated.inputPerMillion).toBe(5.0);
expect(opus48dated.outputPerMillion).toBe(25.0);
});
it('should return fast-tier pricing for Claude Opus 4.8 when serviceTier=fast', () => {
// Anthropic's "fast mode" charges 2x for Opus 4.8 ($10/$50 vs $5/$25).
// Cache rates scale by the same standard multipliers (1.25x write, 0.1x read).
const opus48fast = getModelPricing('claude-opus-4-8', { serviceTier: 'fast' });
expect(opus48fast.inputPerMillion).toBe(10.0);
expect(opus48fast.outputPerMillion).toBe(50.0);
expect(opus48fast.cacheCreationPerMillion).toBe(12.5);
expect(opus48fast.cacheReadPerMillion).toBe(1.0);
});
it('should return fast-tier pricing for Claude Opus 4.7 when serviceTier=fast', () => {
// Fast mode on 4.7 carries a 6x premium ($30/$150 per Anthropic docs).
const opus47fast = getModelPricing('claude-opus-4-7', { serviceTier: 'fast' });
expect(opus47fast.inputPerMillion).toBe(30.0);
expect(opus47fast.outputPerMillion).toBe(150.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' });
expect(opus48.inputPerMillion).toBe(5.0);
expect(opus48.outputPerMillion).toBe(25.0);
});
it('should preserve standard rates for Opus 4.8 when serviceTier omitted', () => {
// Regression guard: existing callers must keep current behavior.
const opus48 = getModelPricing('claude-opus-4-8');
expect(opus48.inputPerMillion).toBe(5.0);
expect(opus48.outputPerMillion).toBe(25.0);
});
it('should not map unknown future model families onto known family pricing', () => {
const fallback = getModelPricing('unknown-model-xyz');
@@ -285,6 +325,17 @@ describe('model-pricing', () => {
const cost = calculateCost(usage, 'claude-opus-4-8');
expect(cost).toBe(36.75); // 5 + 25 + 6.25 + 0.5
});
it('should calculate fast-tier Claude Opus 4.8 cost (2x premium)', () => {
const usage: TokenUsage = {
inputTokens: 1_000_000,
outputTokens: 1_000_000,
cacheCreationTokens: 1_000_000,
cacheReadTokens: 1_000_000,
};
const cost = calculateCost(usage, 'claude-opus-4-8', { serviceTier: 'fast' });
expect(cost).toBe(73.5); // 10 + 50 + 12.5 + 1.0
});
});
describe('getKnownModels', () => {