feat(pricing): register Claude Sonnet 5 in the pricing registry

Add explicit `claude-sonnet-5` and `claude-sonnet-5-thinking` entries to the
web-server PRICING_REGISTRY at the standard Sonnet rates ($3/$15 in/out,
$3.75/$0.30 cache).

The model catalog and dashboard already ship Sonnet 5 (it is the Claude
default), but pricing was missing: getModelPricing() fell through to
UNKNOWN_MODEL_PRICING, which only coincidentally matches Sonnet rates. As a
result hasCustomPricing('claude-sonnet-5') returned false and getKnownModels()
omitted it, so Sonnet 5 was treated as an unknown model in cost accounting and
known-model listings.

Registering it explicitly makes the pricing intentional and consistent with
every other Claude model, without changing any resolved values.

Built [OnSteroids](https://onsteroids.ai)
This commit is contained in:
Sergey Galuza committed 2026-07-07 14:51:43 +02:00
1 parent f1eee3029e
commit 1e63c37baf
2 files changed
+49

No files matched your search

+15
View File
@@ -216,6 +216,21 @@ const PRICING_REGISTRY: Record<string, ModelPricing> = {
cacheCreationPerMillion: 3.75,
cacheReadPerMillion: 0.3,
},
// Claude Sonnet 5 ($3/$15) — latest Sonnet; shares the standard Sonnet rates.
// Registered explicitly so it is a known model rather than relying on the
// unknown-model fallback that coincidentally matches these rates.
'claude-sonnet-5': {
inputPerMillion: 3.0,
outputPerMillion: 15.0,
cacheCreationPerMillion: 3.75,
cacheReadPerMillion: 0.3,
},
'claude-sonnet-5-thinking': {
inputPerMillion: 3.0,
outputPerMillion: 15.0,
cacheCreationPerMillion: 3.75,
cacheReadPerMillion: 0.3,
},
// Claude 4 Opus ($15/$75)
'claude-4-opus-20250514': {
inputPerMillion: 15.0,
+34
View File
@@ -277,6 +277,14 @@ describe('model-pricing', () => {
expect(fable5.cacheReadPerMillion).toBe(1.0);
});
it('should return correct pricing for Claude Sonnet 5', () => {
const sonnet5 = getModelPricing('claude-sonnet-5');
expect(sonnet5.inputPerMillion).toBe(3.0);
expect(sonnet5.outputPerMillion).toBe(15.0);
expect(sonnet5.cacheCreationPerMillion).toBe(3.75);
expect(sonnet5.cacheReadPerMillion).toBe(0.3);
});
it('should not map unknown future model families onto known family pricing', () => {
const fallback = getModelPricing('unknown-model-xyz');
@@ -398,6 +406,17 @@ describe('model-pricing', () => {
expect(cost).toBe(73.5); // 10 + 50 + 12.5 + 1.0
});
it('should calculate Claude Sonnet 5 cost including cache token rates', () => {
const usage: TokenUsage = {
inputTokens: 1_000_000,
outputTokens: 1_000_000,
cacheCreationTokens: 1_000_000,
cacheReadTokens: 1_000_000,
};
const cost = calculateCost(usage, 'claude-sonnet-5');
expect(cost).toBe(22.05); // 3 + 15 + 3.75 + 0.3
});
it('should calculate fast-tier Claude Opus 4.8 cost (2x premium)', () => {
const usage: TokenUsage = {
inputTokens: 1_000_000,
@@ -426,6 +445,13 @@ describe('model-pricing', () => {
const models = getKnownModels();
expect(models.some((m) => m.startsWith('glm-'))).toBe(true);
});
it('should include Claude Sonnet 5 as an explicitly registered model', () => {
// Sonnet 5 must be a first-class known model rather than relying on the
// unknown-model fallback that happens to share Sonnet's $3/$15 rates.
const models = getKnownModels();
expect(models).toContain('claude-sonnet-5');
});
});
describe('hasCustomPricing', () => {
@@ -434,6 +460,14 @@ describe('model-pricing', () => {
expect(hasCustomPricing('glm-4.6')).toBe(true);
});
it('should treat Claude Sonnet 5 as a known model with explicit pricing', () => {
// Without an explicit registry entry Sonnet 5 falls through to
// UNKNOWN_MODEL_PRICING (which coincidentally matches Sonnet rates);
// register it so cost accounting is intentional, not accidental.
expect(hasCustomPricing('claude-sonnet-5')).toBe(true);
expect(hasCustomPricing('claude-sonnet-5-thinking')).toBe(true);
});
it('should return true for deterministic qwen3-coder alias', () => {
expect(hasCustomPricing('qwen3-coder')).toBe(true);
});