Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelog.d/features/12366-claude-fable-5-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- **feat(models):** add Claude Fable 5.1 (`claude-fable-5-1`) to the anthropic, claude, claude-web, and vertex registries with capability specs, Bedrock aliases, 1M-context beta eligibility, family fallback, and pricing — including its lower $0.25/MTok cache-read price ([#12366](https://github.com/diegosouzapw/OmniRoute/pull/12366)) — thanks @davidebaraldo
- **fix(claude):** bump spoofed Claude Code client version to 2.1.258 so Anthropic accepts Fable 5.1 on OAuth connections ([#12366](https://github.com/diegosouzapw/OmniRoute/pull/12366)) — thanks @davidebaraldo
3 changes: 2 additions & 1 deletion open-sse/config/context1m.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* without importing each other.
*/
export const CONTEXT_1M_SUPPORTED_MODELS = [
"claude-fable-5-1",
"claude-fable-5",
"claude-sonnet-5",
"claude-sonnet-4-6",
Expand All @@ -36,4 +37,4 @@ export function modelSupportsContext1mBeta(model: string | null | undefined): bo
return CONTEXT_1M_SUPPORTED_MODELS.some(
(supported) => normalizedModel === supported || normalizedModel.startsWith(`${supported}-`)
);
}
}
9 changes: 9 additions & 0 deletions open-sse/config/providers/registry/anthropic/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ export const anthropicProvider: RegistryEntry = {
"Anthropic-Beta": ANTHROPIC_BETA_API_KEY,
},
models: [
{
id: "claude-fable-5-1",
name: "Claude Fable 5.1",
contextLength: 1048576,
maxOutputTokens: 128000,
// Fable 5.1 (2026-09-01) keeps Fable 5's request surface: adaptive-only thinking,
// non-default temperature/top_p/top_k rejected with a 400.
unsupportedParams: ["temperature", "top_p", "top_k"],
},
{
id: "claude-fable-5",
name: "Claude Fable 5",
Expand Down
7 changes: 7 additions & 0 deletions open-sse/config/providers/registry/claude/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ export const claudeProvider: RegistryEntry = {
tokenUrl: "https://api.anthropic.com/v1/oauth/token",
},
models: [
{
id: "claude-fable-5-1",
name: "Claude Fable 5.1",
contextLength: 1000000,
maxOutputTokens: 128000,
unsupportedParams: ["temperature", "top_p", "top_k"],
},
{
id: "claude-fable-5",
name: "Claude Fable 5",
Expand Down
1 change: 1 addition & 0 deletions open-sse/config/providers/registry/claude/web/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const claude_webProvider: RegistryEntry = {
authType: "apikey",
authHeader: "cookie",
models: [
{ id: "claude-fable-5-1", name: "Claude Fable 5.1 (web)", toolCalling: false },
{ id: "claude-fable-5", name: "Claude Fable 5 (web)", toolCalling: false },
{
id: "claude-opus-5",
Expand Down
1 change: 1 addition & 0 deletions open-sse/config/providers/registry/vertex/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const vertexProvider: RegistryEntry = {
{ id: "DeepSeek-V4-Pro", name: "DeepSeek V4 Pro (Vertex Partner)" },
{ id: "Qwen3.6-35B-A3B", name: "Qwen3.6 35B A3B (Vertex Partner)" },
{ id: "GLM-5.1-FP8", name: "GLM-5.1 (Vertex Partner)" },
{ id: "claude-fable-5-1", name: "Claude Fable 5.1 (Vertex)", targetFormat: "claude" },
{ id: "claude-fable-5", name: "Claude Fable 5 (Vertex)", targetFormat: "claude" },
{ id: "claude-opus-5", name: "Claude Opus 5 (Vertex)", targetFormat: "claude" },
{ id: "claude-sonnet-5", name: "Claude Sonnet 5 (Vertex)", targetFormat: "claude" },
Expand Down
3 changes: 2 additions & 1 deletion open-sse/services/modelFamilyFallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ const FAMILY_FALLBACK_TEMPLATES: Record<string, readonly string[]> = {
"gemini-2.5-pro": ["gemini-2.5-pro-preview-06-05", "gemini-2.5-pro-exp-03-25"],
"gemini-2.5-pro-preview-06-05": ["gemini-2.5-pro", "gemini-2.5-pro-exp-03-25"],

// Claude Mythos family (Fable 5) — flagship falls to the next-best Opus
// Claude Mythos family (Fable 5.1/5) — flagship falls to the next-best Opus
// tiers before the cheaper Sonnet, matching the Opus family ordering.
"claude-fable-5-1": ["claude-fable-5", "claude-opus-5", "claude-sonnet-5"],
"claude-fable-5": ["claude-opus-4-8", "claude-opus-4-7", "claude-sonnet-5"],

// Claude Opus family
Expand Down
1 change: 1 addition & 0 deletions open-sse/services/providerCostData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface ModelPricing {
export const KNOWN_MODEL_PRICING: Record<string, ModelPricing> = {
"gpt-4o": { inputCostPer1M: 2.5, outputCostPer1M: 10.0, isFree: false },
"gpt-4o-mini": { inputCostPer1M: 0.15, outputCostPer1M: 0.6, isFree: false },
"claude-fable-5-1": { inputCostPer1M: 10.0, outputCostPer1M: 50.0, isFree: false },
"claude-fable-5": { inputCostPer1M: 15.0, outputCostPer1M: 75.0, isFree: false },
"claude-opus-5": { inputCostPer1M: 5.0, outputCostPer1M: 25.0, isFree: false },
"claude-opus-4-8": { inputCostPer1M: 15.0, outputCostPer1M: 75.0, isFree: false },
Expand Down
1 change: 1 addition & 0 deletions src/lib/providers/staticModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const STATIC_MODEL_PROVIDERS: Record<string, () => Array<{ id: string; name: str
],
antigravity: () => ANTIGRAVITY_PUBLIC_MODELS.map((model) => ({ ...model })),
claude: () => [
{ id: "claude-fable-5-1", name: "Claude Fable 5.1" },
{ id: "claude-fable-5", name: "Claude Fable 5" },
{ id: "claude-opus-5", name: "Claude Opus 5" },
{ id: "claude-opus-4-8", name: "Claude Opus 4.8" },
Expand Down
2 changes: 1 addition & 1 deletion src/shared/constants/claudeCodeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Keep this leaf dependency-free so server executors, compatibility bridges,
* and client-facing identity presets can share one source of truth.
*/
export const CLAUDE_CODE_CLIENT_VERSION = "2.1.220";
export const CLAUDE_CODE_CLIENT_VERSION = "2.1.258";
export const CLAUDE_CODE_CLIENT_BUILD_REVISION = "1f2";
export const CLAUDE_CODE_CLIENT_BILLING_VERSION = `${CLAUDE_CODE_CLIENT_VERSION}.${CLAUDE_CODE_CLIENT_BUILD_REVISION}`;
export const CLAUDE_CODE_SDK_PACKAGE_VERSION = "0.94.0";
Expand Down
16 changes: 16 additions & 0 deletions src/shared/constants/modelSpecs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,22 @@ export const MODEL_SPECS: Record<string, ModelSpec> = {
aliases: BEDROCK_CLAUDE_ALIASES("claude-opus-4-7", "claude-opus-4.7"),
},

// ── Claude Fable 5.1 ────────────────────────────────────────────
// Same request surface as Claude Fable 5 (adaptive-only, thinking cannot be
// disabled); released 2026-09-01 with cache reads at a quarter of Fable 5's price.
"claude-fable-5-1": {
maxOutputTokens: 128000,
contextWindow: 1000000,
defaultThinkingBudget: 32000,
thinkingBudgetCap: 120000,
supportsThinking: true,
supportsTools: true,
supportsVision: true,
rejectsThinkingDisabled: true,
adaptiveThinkingOnly: true,
aliases: BEDROCK_CLAUDE_ALIASES("claude-fable-5-1"),
},

// ── Claude Fable 5 ──────────────────────────────────────────────
"claude-fable-5": {
maxOutputTokens: 128000,
Expand Down
2 changes: 2 additions & 0 deletions src/shared/constants/pricing/frontier-labs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
GPT_5_6_LUNA_PRICING,
GPT_5_6_SOL_PRICING,
GPT_5_6_TERRA_PRICING,
CLAUDE_FABLE_5_1_PRICING,
CLAUDE_FABLE_5_PRICING,
CLAUDE_OPUS_5_PRICING,
CLAUDE_OPUS_4_PRICING,
Expand Down Expand Up @@ -213,6 +214,7 @@ export const DEFAULT_PRICING_FRONTIER = {
// Common model IDs (without dates) used across providers
// Intentional duplicates of dot-notation variants (e.g. claude-opus-4.6)
// to cover hyphen-notation IDs (claude-opus-4-6) used by some clients
"claude-fable-5-1": CLAUDE_FABLE_5_1_PRICING,
"claude-fable-5": CLAUDE_FABLE_5_PRICING,
"claude-opus-5": CLAUDE_OPUS_5_PRICING,
"claude-sonnet-5": CLAUDE_SONNET_5_PRICING,
Expand Down
7 changes: 7 additions & 0 deletions src/shared/constants/pricing/oauth-subscriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ const ANTIGRAVITY_GEMINI_3_7_PRICING = {

export const DEFAULT_PRICING_OAUTH = {
cc: {
"claude-fable-5-1": {
input: 10.0,
output: 50.0,
cached: 0.25,
reasoning: 50.0,
cache_creation: 12.5,
},
"claude-fable-5": {
input: 10.0,
output: 50.0,
Expand Down
10 changes: 10 additions & 0 deletions src/shared/constants/pricing/shared-tiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ export const GEMINI_3_7_FLASH_PROMO_PRICING = {
cache_creation: 0.75,
};

// Claude Fable 5.1 (2026-09-01): same list price as Fable 5's official $10/$50 tier,
// with cache reads dropped to $0.25/MTok (a quarter of Fable 5's read price).
export const CLAUDE_FABLE_5_1_PRICING = {
input: 10.0,
output: 50.0,
cached: 0.25,
reasoning: 50.0,
cache_creation: 12.5,
};

export const CLAUDE_FABLE_5_PRICING = {
input: 15.0,
output: 75.0,
Expand Down
12 changes: 6 additions & 6 deletions tests/snapshots/provider/translate-path.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"apiKey": {
"Accept": "text/event-stream",
"Content-Type": "application/json",
"User-Agent": "claude-cli/2.1.220 (external, sdk-cli)",
"User-Agent": "claude-cli/2.1.258 (external, sdk-cli)",
"X-Stainless-Arch": "<ARCH>",
"X-Stainless-Lang": "js",
"X-Stainless-OS": "MacOS",
Expand All @@ -47,7 +47,7 @@
"nonStream": {
"Accept": "application/json",
"Content-Type": "application/json",
"User-Agent": "claude-cli/2.1.220 (external, sdk-cli)",
"User-Agent": "claude-cli/2.1.258 (external, sdk-cli)",
"X-Stainless-Arch": "<ARCH>",
"X-Stainless-Lang": "js",
"X-Stainless-OS": "MacOS",
Expand All @@ -66,7 +66,7 @@
"oauth": {
"Accept": "text/event-stream",
"Content-Type": "application/json",
"User-Agent": "claude-cli/2.1.220 (external, sdk-cli)",
"User-Agent": "claude-cli/2.1.258 (external, sdk-cli)",
"X-Stainless-Arch": "<ARCH>",
"X-Stainless-Lang": "js",
"X-Stainless-OS": "MacOS",
Expand Down Expand Up @@ -966,7 +966,7 @@
"Anthropic-Dangerous-Direct-Browser-Access": "true",
"Anthropic-Version": "2023-06-01",
"Content-Type": "application/json",
"User-Agent": "claude-cli/2.1.220 (external, cli)",
"User-Agent": "claude-cli/2.1.258 (external, cli)",
"X-App": "cli",
"X-Stainless-Arch": "<ARCH>",
"X-Stainless-Helper-Method": "stream",
Expand All @@ -984,7 +984,7 @@
"Anthropic-Dangerous-Direct-Browser-Access": "true",
"Anthropic-Version": "2023-06-01",
"Content-Type": "application/json",
"User-Agent": "claude-cli/2.1.220 (external, cli)",
"User-Agent": "claude-cli/2.1.258 (external, cli)",
"X-App": "cli",
"X-Stainless-Arch": "<ARCH>",
"X-Stainless-Helper-Method": "stream",
Expand All @@ -1003,7 +1003,7 @@
"Anthropic-Dangerous-Direct-Browser-Access": "true",
"Anthropic-Version": "2023-06-01",
"Content-Type": "application/json",
"User-Agent": "claude-cli/2.1.220 (external, cli)",
"User-Agent": "claude-cli/2.1.258 (external, cli)",
"X-App": "cli",
"X-Stainless-Arch": "<ARCH>",
"X-Stainless-Helper-Method": "stream",
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/anthropic-cache-fingerprint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ import { CLAUDE_CODE_CLIENT_BILLING_VERSION } from "../../src/shared/constants/c

describe("Anthropic billing header fingerprint (#1638)", () => {
it("uses the immutable build revision captured from the signed CLI", () => {
assert.equal(CLAUDE_CODE_CLIENT_BILLING_VERSION, "2.1.220.1f2");
assert.equal(CLAUDE_CODE_CLIENT_BILLING_VERSION, "2.1.258.1f2");
});
});
6 changes: 5 additions & 1 deletion tests/unit/cc-bridge-transforms.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ test("DEFAULT_CC_BRIDGE_PIPELINE places billing header at [0] and identity at [1
DEFAULT_CC_BRIDGE_PIPELINE
);
const blocks = result.body.system as any[];
assert.ok(blocks[0].text.startsWith("x-anthropic-billing-header: cc_version=2.1.220.1f2;"));
assert.ok(
blocks[0].text.startsWith(
`x-anthropic-billing-header: cc_version=${DEFAULT_CLAUDE_CODE_VERSION}.1f2;`
)
);
assert.equal(blocks[1].text, CLAUDE_AGENT_SDK_IDENTITY);
});

Expand Down
6 changes: 3 additions & 3 deletions tests/unit/claude-codex-identity-version-sync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ test("Claude CLI version constants are in lockstep across all 4 sources", () =>
);
});

test("Claude CLI wire versions match the captured 2.1.220 binary", () => {
assert.equal(canonical.CLAUDE_CODE_CLIENT_VERSION, "2.1.220");
test("Claude CLI wire versions match the captured 2.1.258 binary", () => {
assert.equal(canonical.CLAUDE_CODE_CLIENT_VERSION, "2.1.258");
assert.equal(canonical.CLAUDE_CODE_CLIENT_BUILD_REVISION, "1f2");
assert.equal(canonical.CLAUDE_CODE_CLIENT_BILLING_VERSION, "2.1.220.1f2");
assert.equal(canonical.CLAUDE_CODE_CLIENT_BILLING_VERSION, "2.1.258.1f2");
assert.equal(canonical.CLAUDE_CODE_SDK_PACKAGE_VERSION, "0.94.0");
assert.equal(canonical.CLAUDE_CODE_RUNTIME_VERSION, "v26.3.0");
assert.equal(
Expand Down
1 change: 1 addition & 0 deletions tests/unit/claude-web-sonnet5-registry-6209.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ test("claude-web registry matches the current selectable model set", () => {
assert.deepEqual(
ids,
[
"claude-fable-5-1",
"claude-fable-5",
"claude-haiku-4-5-20251001",
"claude-opus-5",
Expand Down
7 changes: 4 additions & 3 deletions tests/unit/client-identity-profiles.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const {
getClientIdentityProfileHeaders,
isClientIdentityProfileId,
} = await import("../../src/shared/constants/clientIdentityProfiles.ts");
const { getClaudeCodeUserAgent } = await import("../../src/shared/constants/claudeCodeClient.ts");
const { isForbiddenCustomHeaderName } =
await import("../../src/shared/constants/upstreamHeaders.ts");
const { DefaultExecutor } = await import("../../open-sse/executors/default.ts");
Expand All @@ -39,7 +40,7 @@ test("getClientIdentityProfileHeaders: unknown profile id falls back to no heade

test("getClientIdentityProfileHeaders: known CLI profiles expose their preset headers", () => {
const claudeCli = getClientIdentityProfileHeaders("claude-cli");
assert.equal(claudeCli["User-Agent"], "claude-cli/2.1.220 (external, cli)");
assert.equal(claudeCli["User-Agent"], getClaudeCodeUserAgent("cli"));
assert.equal(claudeCli["X-App"], "cli");

const codexCli = getClientIdentityProfileHeaders("codex-cli");
Expand All @@ -55,7 +56,7 @@ test("getClientIdentityProfileHeaders: returns a fresh mutable copy (catalog sta
headers["User-Agent"] = "tampered";
assert.equal(
CLIENT_IDENTITY_PROFILES["claude-cli"].headers["User-Agent"],
"claude-cli/2.1.220 (external, cli)"
getClaudeCodeUserAgent("cli")
);
});

Expand Down Expand Up @@ -100,7 +101,7 @@ test("profile headers merged into customHeaders survive applyCustomHeaders sanit
true
) as Record<string, string>;

assert.equal(headers["User-Agent"], "claude-cli/2.1.220 (external, cli)");
assert.equal(headers["User-Agent"], getClaudeCodeUserAgent("cli"));
assert.equal(headers["X-App"], "cli");
assert.equal(headers["Authorization"], "Bearer test-key");
});
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/executor-default-base.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1576,7 +1576,7 @@ test("DefaultExecutor.execute does not produce duplicate anthropic-version heade
const sentBody = JSON.parse(capturedBody) as { system?: Array<{ text?: string }> };
assert.match(
sentBody.system?.[0]?.text ?? "",
/^x-anthropic-billing-header: cc_version=2\.1\.220\.1f2; cc_entrypoint=cli; cch=[0-9a-f]{5};$/
/^x-anthropic-billing-header: cc_version=2\.1\.258\.1f2; cc_entrypoint=cli; cch=[0-9a-f]{5};$/
);
});

Expand Down
12 changes: 8 additions & 4 deletions tests/unit/glm-executor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import assert from "node:assert/strict";

import { getExecutor } from "../../open-sse/executors/index.ts";
import { GlmExecutor } from "../../open-sse/executors/glm.ts";
import { CLAUDE_CODE_CLIENT_VERSION } from "../../src/shared/constants/claudeCodeClient.ts";

function makeSseResponse(lines: string[]): Response {
return new Response(lines.join("\n\n") + "\n\n", {
Expand Down Expand Up @@ -130,9 +131,9 @@ test("GlmExecutor normalizes GLM coding and Anthropic URLs without duplicating e
});

test("GlmExecutor separates OpenAI-compatible coding headers from Anthropic headers", async () => {
assert.equal(await getExecutor("glm") instanceof GlmExecutor, true);
assert.equal(await getExecutor("glm-cn") instanceof GlmExecutor, true);
assert.equal(await getExecutor("glmt") instanceof GlmExecutor, true);
assert.equal((await getExecutor("glm")) instanceof GlmExecutor, true);
assert.equal((await getExecutor("glm-cn")) instanceof GlmExecutor, true);
assert.equal((await getExecutor("glmt")) instanceof GlmExecutor, true);

const executor = new GlmExecutor("glm");
const codingHeaders = executor.buildHeaders(
Expand Down Expand Up @@ -183,7 +184,10 @@ test("GlmExecutor separates OpenAI-compatible coding headers from Anthropic head
assert.equal(anthropicHeaders["anthropic-version"], "2023-06-01");
assert.match(anthropicHeaders["anthropic-beta"], /claude-code-20250219/);
assert.equal(anthropicHeaders["anthropic-dangerous-direct-browser-access"], "true");
assert.match(anthropicHeaders["User-Agent"], /^claude-cli\/2\.1\.220 \(external, sdk-cli\)$/);
assert.equal(
anthropicHeaders["User-Agent"],
`claude-cli/${CLAUDE_CODE_CLIENT_VERSION} (external, sdk-cli)`
);
assert.equal(anthropicHeaders["X-Stainless-Lang"], "js");
assert.equal(anthropicHeaders["X-Stainless-Runtime"], "node");
});
Expand Down
13 changes: 11 additions & 2 deletions tests/unit/provider-models-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,19 @@ test("GitHub Copilot registry reflects the current supported model lineup", () =
});

test("Claude flagship catalogs keep Fable 5 first", () => {
for (const provider of ["anthropic", "cc", "cw", "gh", "ghe-copilot"]) {
// Anthropic-native catalogs list Fable 5.1 first (2026-09-01). Copilot's
// public model list has not shipped 5.1 yet, so gh/ghe still flagship Fable 5.
const expectedFirst = {
anthropic: "claude-fable-5-1",
cc: "claude-fable-5-1",
cw: "claude-fable-5-1",
gh: "claude-fable-5",
"ghe-copilot": "claude-fable-5",
};
for (const [provider, expected] of Object.entries(expectedFirst)) {
assert.equal(
getProviderModels(provider)[0]?.id,
"claude-fable-5",
expected,
`${provider} must list the strongest Claude model first`
);
}
Expand Down