mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-13 14:15:56 +00:00
feat(models): add Claude Fable 5.1 and correct Sonnet 5 pricing
Register `claude-fable-5-1` in the CLIProxy model catalog, the dashboard
catalog, and the usage pricing registry.
Pricing is taken from Anthropic's official pricing page:
- Fable 5.1 base rates are $10/$50 per MTok with a $12.50 5m cache write,
matching Fable 5.
- Cache hits bill at 0.025x base input ($0.25/MTok) rather than the
standard 0.1x multiplier. Anthropic applies that reduced rate only to
Fable 5.1 and Mythos 5.1, so this entry cannot derive its cache rates
from CACHE_READ_MULTIPLIER.
This also corrects Claude Sonnet 5 from $3/$15 to $2/$10 (cache write
$2.50, cache read $0.20). The launch introductory rate became the
standard price and the increase scheduled for 2026-09-01 was cancelled,
so the previous entry over-reported Sonnet 5 usage cost by 50%.
Thinking on Fable 5.1 is always on and can only be steered through
effort levels, so the catalog entry exposes the same `low`..`max` level
surface as Fable 5 and Opus 5 instead of a manual token budget.
The GitHub Copilot catalog is deliberately left unchanged, since Copilot
availability for Fable 5.1 is not verified; surfacing it there would
offer a model the backend may reject.
Regenerates docs/reports/hardening-inventory.{json,md} so the ci-parity
gate matches the source tree.
This commit is contained in:
1 parent
485c436958
commit
3583b544d0
8 files changed
+83
-22
No files matched your search
@@ -739,7 +739,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "src/web-server/model-pricing.ts",
|
"file": "src/web-server/model-pricing.ts",
|
||||||
"loc": 1127
|
"loc": 1138
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "src/cliproxy/config/generator.ts",
|
"file": "src/cliproxy/config/generator.ts",
|
||||||
@@ -778,8 +778,8 @@
|
|||||||
"loc": 939
|
"loc": 939
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "src/cliproxy/accounts/registry.ts",
|
"file": "src/cliproxy/model-catalog.ts",
|
||||||
"loc": 871
|
"loc": 884
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ Scope: `src/**/*.{ts,tsx,js,jsx,mjs,cjs}`
|
|||||||
| `src/web-server/routes/cliproxy-auth-routes.ts` | 1531 |
|
| `src/web-server/routes/cliproxy-auth-routes.ts` | 1531 |
|
||||||
| `src/cliproxy/auth/oauth-handler.ts` | 1510 |
|
| `src/cliproxy/auth/oauth-handler.ts` | 1510 |
|
||||||
| `src/cursor/cursor-executor.ts` | 1234 |
|
| `src/cursor/cursor-executor.ts` | 1234 |
|
||||||
| `src/web-server/model-pricing.ts` | 1127 |
|
| `src/web-server/model-pricing.ts` | 1138 |
|
||||||
| `src/cliproxy/config/generator.ts` | 1109 |
|
| `src/cliproxy/config/generator.ts` | 1109 |
|
||||||
| `src/cliproxy/auth/oauth-process.ts` | 1048 |
|
| `src/cliproxy/auth/oauth-process.ts` | 1048 |
|
||||||
| `src/cliproxy/config/env-builder.ts` | 1045 |
|
| `src/cliproxy/config/env-builder.ts` | 1045 |
|
||||||
@@ -103,5 +103,5 @@ Scope: `src/**/*.{ts,tsx,js,jsx,mjs,cjs}`
|
|||||||
| `src/cliproxy/quota/quota-manager.ts` | 954 |
|
| `src/cliproxy/quota/quota-manager.ts` | 954 |
|
||||||
| `src/web-server/services/codex-dashboard-service.ts` | 940 |
|
| `src/web-server/services/codex-dashboard-service.ts` | 940 |
|
||||||
| `src/glmt/glmt-proxy.ts` | 939 |
|
| `src/glmt/glmt-proxy.ts` | 939 |
|
||||||
| `src/cliproxy/accounts/registry.ts` | 871 |
|
| `src/cliproxy/model-catalog.ts` | 884 |
|
||||||
|
|
||||||
@@ -60,6 +60,14 @@ describe('Thinking Validator', () => {
|
|||||||
expect(result.warning).toBeUndefined();
|
expect(result.warning).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should treat max as a distinct top tier on Claude Fable 5.1', () => {
|
||||||
|
// Fable 5.1 has always-on adaptive thinking steered only by effort level.
|
||||||
|
const result = validateThinking('claude', 'claude-fable-5-1', 'max');
|
||||||
|
expect(result.valid).toBe(true);
|
||||||
|
expect(result.value).toBe('max');
|
||||||
|
expect(result.warning).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
it('should treat max as a distinct top tier on Claude Fable 5', () => {
|
it('should treat max as a distinct top tier on Claude Fable 5', () => {
|
||||||
// Fable 5 shares Opus 4.8's adaptive thinking surface; max must remain
|
// Fable 5 shares Opus 4.8's adaptive thinking surface; max must remain
|
||||||
// distinct from xhigh.
|
// distinct from xhigh.
|
||||||
|
|||||||
@@ -531,10 +531,27 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
|||||||
},
|
},
|
||||||
extendedContext: true,
|
extendedContext: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'claude-fable-5-1',
|
||||||
|
name: 'Claude Fable 5.1',
|
||||||
|
description: 'Most powerful model (1M context, 128K output)',
|
||||||
|
contextWindow: 1000000,
|
||||||
|
nativeImageInput: true,
|
||||||
|
// Thinking is always on and cannot be disabled: Anthropic rejects both
|
||||||
|
// `thinking.type: "disabled"` and manual budget_tokens with 400. Depth is
|
||||||
|
// steered only through effort levels (default `high`).
|
||||||
|
thinking: {
|
||||||
|
type: 'levels',
|
||||||
|
levels: ['low', 'medium', 'high', 'xhigh', 'max'],
|
||||||
|
maxLevel: 'max',
|
||||||
|
dynamicAllowed: true,
|
||||||
|
},
|
||||||
|
extendedContext: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'claude-fable-5',
|
id: 'claude-fable-5',
|
||||||
name: 'Claude Fable 5',
|
name: 'Claude Fable 5',
|
||||||
description: 'Most powerful model',
|
description: 'Previous most powerful model',
|
||||||
contextWindow: 1000000,
|
contextWindow: 1000000,
|
||||||
nativeImageInput: true,
|
nativeImageInput: true,
|
||||||
// New tier above Opus. Same adaptive-thinking surface as Opus 4.8:
|
// New tier above Opus. Same adaptive-thinking surface as Opus 4.8:
|
||||||
|
|||||||
@@ -216,20 +216,21 @@ const PRICING_REGISTRY: Record<string, ModelPricing> = {
|
|||||||
cacheCreationPerMillion: 3.75,
|
cacheCreationPerMillion: 3.75,
|
||||||
cacheReadPerMillion: 0.3,
|
cacheReadPerMillion: 0.3,
|
||||||
},
|
},
|
||||||
// Claude Sonnet 5 ($3/$15) — latest Sonnet; shares the standard Sonnet rates.
|
// Claude Sonnet 5 ($2/$10) — the launch introductory rate is now the standard
|
||||||
|
// price; Anthropic cancelled the scheduled 2026-09-01 increase to $3/$15.
|
||||||
// Registered explicitly so it is a known model rather than relying on the
|
// Registered explicitly so it is a known model rather than relying on the
|
||||||
// unknown-model fallback that coincidentally matches these rates.
|
// unknown-model fallback.
|
||||||
'claude-sonnet-5': {
|
'claude-sonnet-5': {
|
||||||
inputPerMillion: 3.0,
|
inputPerMillion: 2.0,
|
||||||
outputPerMillion: 15.0,
|
outputPerMillion: 10.0,
|
||||||
cacheCreationPerMillion: 3.75,
|
cacheCreationPerMillion: 2.5,
|
||||||
cacheReadPerMillion: 0.3,
|
cacheReadPerMillion: 0.2,
|
||||||
},
|
},
|
||||||
'claude-sonnet-5-thinking': {
|
'claude-sonnet-5-thinking': {
|
||||||
inputPerMillion: 3.0,
|
inputPerMillion: 2.0,
|
||||||
outputPerMillion: 15.0,
|
outputPerMillion: 10.0,
|
||||||
cacheCreationPerMillion: 3.75,
|
cacheCreationPerMillion: 2.5,
|
||||||
cacheReadPerMillion: 0.3,
|
cacheReadPerMillion: 0.2,
|
||||||
},
|
},
|
||||||
// Claude 4 Opus ($15/$75)
|
// Claude 4 Opus ($15/$75)
|
||||||
'claude-4-opus-20250514': {
|
'claude-4-opus-20250514': {
|
||||||
@@ -360,6 +361,16 @@ const PRICING_REGISTRY: Record<string, ModelPricing> = {
|
|||||||
cacheCreationPerMillion: 12.5,
|
cacheCreationPerMillion: 12.5,
|
||||||
cacheReadPerMillion: 1.0,
|
cacheReadPerMillion: 1.0,
|
||||||
},
|
},
|
||||||
|
// Claude Fable 5.1 ($10/$50) — same tier and base rates as Fable 5, but cache
|
||||||
|
// hits are billed at 0.025x base input ($0.25/MTok) instead of the usual 0.1x.
|
||||||
|
// Anthropic applies that reduced multiplier only to Fable 5.1 and Mythos 5.1,
|
||||||
|
// so this entry cannot use CACHE_READ_MULTIPLIER-derived rates.
|
||||||
|
'claude-fable-5-1': {
|
||||||
|
inputPerMillion: 10.0,
|
||||||
|
outputPerMillion: 50.0,
|
||||||
|
cacheCreationPerMillion: 12.5,
|
||||||
|
cacheReadPerMillion: 0.25,
|
||||||
|
},
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// OpenAI Models - Source: better-ccusage
|
// OpenAI Models - Source: better-ccusage
|
||||||
|
|||||||
@@ -277,12 +277,24 @@ describe('model-pricing', () => {
|
|||||||
expect(fable5.cacheReadPerMillion).toBe(1.0);
|
expect(fable5.cacheReadPerMillion).toBe(1.0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return correct pricing for Claude Fable 5.1', () => {
|
||||||
|
// Same base rates as Fable 5, but cache hits bill at 0.025x base input
|
||||||
|
// ($0.25/MTok) rather than the standard 0.1x multiplier.
|
||||||
|
const fable51 = getModelPricing('claude-fable-5-1');
|
||||||
|
expect(fable51.inputPerMillion).toBe(10.0);
|
||||||
|
expect(fable51.outputPerMillion).toBe(50.0);
|
||||||
|
expect(fable51.cacheCreationPerMillion).toBe(12.5);
|
||||||
|
expect(fable51.cacheReadPerMillion).toBe(0.25);
|
||||||
|
});
|
||||||
|
|
||||||
it('should return correct pricing for Claude Sonnet 5', () => {
|
it('should return correct pricing for Claude Sonnet 5', () => {
|
||||||
|
// The $2/$10 launch introductory rate became the standard price; the
|
||||||
|
// scheduled 2026-09-01 increase to $3/$15 was cancelled.
|
||||||
const sonnet5 = getModelPricing('claude-sonnet-5');
|
const sonnet5 = getModelPricing('claude-sonnet-5');
|
||||||
expect(sonnet5.inputPerMillion).toBe(3.0);
|
expect(sonnet5.inputPerMillion).toBe(2.0);
|
||||||
expect(sonnet5.outputPerMillion).toBe(15.0);
|
expect(sonnet5.outputPerMillion).toBe(10.0);
|
||||||
expect(sonnet5.cacheCreationPerMillion).toBe(3.75);
|
expect(sonnet5.cacheCreationPerMillion).toBe(2.5);
|
||||||
expect(sonnet5.cacheReadPerMillion).toBe(0.3);
|
expect(sonnet5.cacheReadPerMillion).toBe(0.2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return Opus-tier pricing for Claude Opus 5', () => {
|
it('should return Opus-tier pricing for Claude Opus 5', () => {
|
||||||
@@ -430,7 +442,7 @@ describe('model-pricing', () => {
|
|||||||
cacheReadTokens: 1_000_000,
|
cacheReadTokens: 1_000_000,
|
||||||
};
|
};
|
||||||
const cost = calculateCost(usage, 'claude-sonnet-5');
|
const cost = calculateCost(usage, 'claude-sonnet-5');
|
||||||
expect(cost).toBe(22.05); // 3 + 15 + 3.75 + 0.3
|
expect(cost).toBe(14.7); // 2 + 10 + 2.5 + 0.2
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should calculate fast-tier Claude Opus 4.8 cost (2x premium)', () => {
|
it('should calculate fast-tier Claude Opus 4.8 cost (2x premium)', () => {
|
||||||
|
|||||||
@@ -977,10 +977,22 @@ export const MODEL_CATALOGS: Record<string, ProviderCatalog> = {
|
|||||||
haiku: 'claude-haiku-4-5-20251001',
|
haiku: 'claude-haiku-4-5-20251001',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'claude-fable-5-1',
|
||||||
|
name: 'Claude Fable 5.1',
|
||||||
|
description: 'Most powerful model',
|
||||||
|
extendedContext: true,
|
||||||
|
presetMapping: {
|
||||||
|
default: 'claude-fable-5-1',
|
||||||
|
opus: 'claude-fable-5-1',
|
||||||
|
sonnet: 'claude-sonnet-5',
|
||||||
|
haiku: 'claude-haiku-4-5-20251001',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'claude-fable-5',
|
id: 'claude-fable-5',
|
||||||
name: 'Claude Fable 5',
|
name: 'Claude Fable 5',
|
||||||
description: 'Most powerful model',
|
description: 'Previous most powerful model',
|
||||||
extendedContext: true,
|
extendedContext: true,
|
||||||
presetMapping: {
|
presetMapping: {
|
||||||
default: 'claude-fable-5',
|
default: 'claude-fable-5',
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ describe('claude preset utils', () => {
|
|||||||
|
|
||||||
expect(claudeCatalog.defaultModel).toBe('claude-sonnet-5');
|
expect(claudeCatalog.defaultModel).toBe('claude-sonnet-5');
|
||||||
expect(ids).toContain('claude-sonnet-5');
|
expect(ids).toContain('claude-sonnet-5');
|
||||||
|
expect(ids).toContain('claude-fable-5-1');
|
||||||
expect(ids).toContain('claude-fable-5');
|
expect(ids).toContain('claude-fable-5');
|
||||||
expect(ids).toContain('claude-opus-5');
|
expect(ids).toContain('claude-opus-5');
|
||||||
expect(ids).toContain('claude-opus-4-8');
|
expect(ids).toContain('claude-opus-4-8');
|
||||||
|
|||||||
Reference in new issue
Block a user