From fc8a512a9ad054056a18dd74215476ec75552db8 Mon Sep 17 00:00:00 2001 From: Giorgos Giakoumettis Date: Tue, 9 Jun 2026 22:39:02 +0300 Subject: [PATCH 1/4] feat(models): add Claude Fable 5 support Wire claude-fable-5 across the full model pipeline: provider registry (cc + kiro), pricing constants, model specs, fast mode, 1M context support, fallback chain, and cost data. Adds a catalog test confirming the model is exposed with correct context/output limits and pricing. --- open-sse/config/providerRegistry.ts | 12 ++++++++++++ open-sse/services/claudeCodeCompatible.ts | 2 +- open-sse/services/modelFamilyFallback.ts | 1 + open-sse/services/providerCostData.ts | 1 + src/lib/db/settings.ts | 2 +- src/lib/providers/claudeFastMode.ts | 1 + src/lib/providers/staticModels.ts | 1 + src/shared/constants/modelSpecs.ts | 12 ++++++++++++ src/shared/constants/pricing.ts | 23 +++++++++++++++++++++++ tests/unit/catalog-updates-v3x.test.ts | 19 +++++++++++++++++++ 10 files changed, 72 insertions(+), 2 deletions(-) diff --git a/open-sse/config/providerRegistry.ts b/open-sse/config/providerRegistry.ts index f76d0c293724..ea31fc01ded8 100644 --- a/open-sse/config/providerRegistry.ts +++ b/open-sse/config/providerRegistry.ts @@ -660,6 +660,12 @@ const _REGISTRY_EAGER: Record = { tokenUrl: "https://api.anthropic.com/v1/oauth/token", }, models: [ + { + id: "claude-fable-5", + name: "Claude Fable 5", + contextLength: 1000000, + maxOutputTokens: 128000, + }, { id: "claude-opus-4-8", name: "Claude Opus 4.8", @@ -1142,6 +1148,12 @@ const _REGISTRY_EAGER: Record = { }, models: [ { id: "auto-kiro", name: "Auto (Kiro picks best model)" }, + { + id: "claude-fable-5", + name: "Claude Fable 5", + contextLength: 1000000, + maxOutputTokens: 128000, + }, { id: "claude-opus-4.8", name: "Claude Opus 4.8", diff --git a/open-sse/services/claudeCodeCompatible.ts b/open-sse/services/claudeCodeCompatible.ts index 1eb33cca15e7..9350f9ab0b49 100644 --- a/open-sse/services/claudeCodeCompatible.ts +++ b/open-sse/services/claudeCodeCompatible.ts @@ -52,7 +52,7 @@ const CLAUDE_CODE_COMPATIBLE_DEFAULT_SYSTEM_BLOCKS = [ text: "You are a Claude agent, built on Anthropic's Claude Agent SDK.", }, ]; -const CONTEXT_1M_SUPPORTED_MODELS = ["claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6"]; +const CONTEXT_1M_SUPPORTED_MODELS = ["claude-fable-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6"]; export const CLAUDE_CODE_COMPATIBLE_STAINLESS_TIMEOUT_SECONDS = getStainlessTimeoutSeconds( process.env ); diff --git a/open-sse/services/modelFamilyFallback.ts b/open-sse/services/modelFamilyFallback.ts index 06f2d4d05a94..5bf028f26cb4 100644 --- a/open-sse/services/modelFamilyFallback.ts +++ b/open-sse/services/modelFamilyFallback.ts @@ -73,6 +73,7 @@ const MODEL_FAMILIES: Record = { "gemini-2.5-pro-preview-06-05": ["gemini-2.5-pro", "gemini-2.5-pro-exp-03-25"], // Claude Opus family + "claude-fable-5": ["claude-opus-4-8", "claude-opus-4-7", "claude-sonnet-4-6"], "claude-opus-4-8": ["claude-opus-4-7", "claude-opus-4-6", "claude-sonnet-4-6"], "claude-opus-4-7": ["claude-opus-4-6", "claude-opus-4-5-20251101", "claude-sonnet-4-6"], "claude-opus-4-6": ["claude-opus-4-6-thinking", "claude-opus-4-5-20251101", "claude-sonnet-4-6"], diff --git a/open-sse/services/providerCostData.ts b/open-sse/services/providerCostData.ts index 7397b4641f5d..461caf004669 100644 --- a/open-sse/services/providerCostData.ts +++ b/open-sse/services/providerCostData.ts @@ -11,6 +11,7 @@ export interface ModelPricing { export const KNOWN_MODEL_PRICING: Record = { "gpt-4o": { inputCostPer1M: 2.5, outputCostPer1M: 10.0, isFree: false }, "gpt-4o-mini": { inputCostPer1M: 0.15, outputCostPer1M: 0.6, isFree: false }, + "claude-fable-5": { inputCostPer1M: 15.0, outputCostPer1M: 75.0, isFree: false }, "claude-opus-4-8": { inputCostPer1M: 15.0, outputCostPer1M: 75.0, isFree: false }, "claude-opus-4-7": { inputCostPer1M: 15.0, outputCostPer1M: 75.0, isFree: false }, "claude-sonnet-4-6": { inputCostPer1M: 3.0, outputCostPer1M: 15.0, isFree: false }, diff --git a/src/lib/db/settings.ts b/src/lib/db/settings.ts index 42246413ca19..c9ec88c002c2 100644 --- a/src/lib/db/settings.ts +++ b/src/lib/db/settings.ts @@ -106,7 +106,7 @@ export async function getSettings() { codexServiceTier: { enabled: false }, claudeFastMode: { enabled: false, - supportedModels: ["claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6"], + supportedModels: ["claude-fable-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6"], }, codexSessionAffinityTtlMs: 0, responsesPreviousResponseIdMode: DEFAULT_RESPONSES_PREVIOUS_RESPONSE_ID_MODE, diff --git a/src/lib/providers/claudeFastMode.ts b/src/lib/providers/claudeFastMode.ts index 241842619863..8122fc53f301 100644 --- a/src/lib/providers/claudeFastMode.ts +++ b/src/lib/providers/claudeFastMode.ts @@ -7,6 +7,7 @@ type JsonRecord = Record; * only the latest Opus tiers can request the priority service path. */ export const CLAUDE_FAST_MODE_DEFAULT_MODELS = [ + "claude-fable-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6", diff --git a/src/lib/providers/staticModels.ts b/src/lib/providers/staticModels.ts index 0cf74fe5fe2d..ab65ec66dd71 100644 --- a/src/lib/providers/staticModels.ts +++ b/src/lib/providers/staticModels.ts @@ -33,6 +33,7 @@ const STATIC_MODEL_PROVIDERS: Record Array<{ id: string; name: str ], antigravity: () => ANTIGRAVITY_PUBLIC_MODELS.map((model) => ({ ...model })), claude: () => [ + { id: "claude-fable-5", name: "Claude Fable 5" }, { id: "claude-opus-4-8", name: "Claude Opus 4.8" }, { id: "claude-opus-4-7", name: "Claude Opus 4.7" }, { id: "claude-opus-4-6", name: "Claude Opus 4.6" }, diff --git a/src/shared/constants/modelSpecs.ts b/src/shared/constants/modelSpecs.ts index ea36a217a2cb..a478d620388f 100644 --- a/src/shared/constants/modelSpecs.ts +++ b/src/shared/constants/modelSpecs.ts @@ -212,6 +212,18 @@ export const MODEL_SPECS: Record = { aliases: BEDROCK_CLAUDE_ALIASES("claude-opus-4-7", "claude-opus-4.7"), }, + // ── Claude Fable 5 ────────────────────────────────────────────── + "claude-fable-5": { + maxOutputTokens: 128000, + contextWindow: 1000000, + defaultThinkingBudget: 32000, + thinkingBudgetCap: 120000, + supportsThinking: true, + supportsTools: true, + supportsVision: true, + aliases: [], + }, + // ── Claude Opus 4.8 ───────────────────────────────────────────── "claude-opus-4-8": { maxOutputTokens: 128000, diff --git a/src/shared/constants/pricing.ts b/src/shared/constants/pricing.ts index b5c29cf8d11f..7fa507317adf 100644 --- a/src/shared/constants/pricing.ts +++ b/src/shared/constants/pricing.ts @@ -19,6 +19,14 @@ const GPT_5_5_PRICING = { cache_creation: 5.0, }; +const CLAUDE_FABLE_5_PRICING = { + input: 15.0, + output: 75.0, + cached: 7.5, + reasoning: 112.5, + cache_creation: 15.0, +}; + const CLAUDE_OPUS_4_PRICING = { input: 15.0, output: 75.0, @@ -129,6 +137,13 @@ export const DEFAULT_PRICING = { // Claude Code (cc) cc: { + "claude-fable-5": { + input: 5.0, + output: 25.0, + cached: 2.5, + reasoning: 37.5, + cache_creation: 5.0, + }, "claude-opus-4-8": { input: 5.0, output: 25.0, @@ -717,6 +732,7 @@ export const DEFAULT_PRICING = { // 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": CLAUDE_FABLE_5_PRICING, "claude-opus-4.8": CLAUDE_OPUS_4_PRICING, "claude-opus-4-8": CLAUDE_OPUS_4_PRICING, "claude-opus-4-7": CLAUDE_OPUS_4_PRICING, @@ -1282,6 +1298,13 @@ export const DEFAULT_PRICING = { }, kiro: { + "claude-fable-5": { + input: 15.0, + output: 75.0, + cached: 7.5, + reasoning: 112.5, + cache_creation: 15.0, + }, "claude-sonnet-4.5": { input: 3.0, output: 15.0, diff --git a/tests/unit/catalog-updates-v3x.test.ts b/tests/unit/catalog-updates-v3x.test.ts index 4b9777334425..53623a5e864e 100644 --- a/tests/unit/catalog-updates-v3x.test.ts +++ b/tests/unit/catalog-updates-v3x.test.ts @@ -47,6 +47,25 @@ test("NVIDIA catalog includes the verified 2026 additions and GPT OSS 20B alias }); }); +test("Fable 5 catalog exposes claude-fable-5 in cc and kiro providers with matching pricing", () => { + const ccIds = new Set(getModelsByProviderId("cc").map((m) => m.id)); + assert.ok(ccIds.has("claude-fable-5"), "cc must expose claude-fable-5"); + + const kiroModels = getModelsByProviderId("kiro"); + const kiroIds = new Set(kiroModels.map((m) => m.id)); + assert.ok(kiroIds.has("claude-fable-5"), "kiro must expose claude-fable-5"); + + const fable = kiroModels.find((m) => m.id === "claude-fable-5"); + assert.equal(fable?.contextLength, 1000000); + assert.equal(fable?.maxOutputTokens, 128000); + + const ccPricing = (DEFAULT_PRICING as Record>).cc; + assert.ok(ccPricing["claude-fable-5"], "cc pricing must include claude-fable-5"); + + const kiroPricing = (DEFAULT_PRICING as Record>).kiro; + assert.ok(kiroPricing["claude-fable-5"], "kiro pricing must include claude-fable-5"); +}); + test("Kiro catalog exposes Claude Opus 4.8 alongside 4.7 with matching pricing", () => { const models = getModelsByProviderId("kiro"); const ids = new Set(models.map((model) => model.id)); From 6a9b27cc0afcefa5f437527e4c5c8df4da6b8448 Mon Sep 17 00:00:00 2001 From: Giorgos Giakoumettis Date: Tue, 9 Jun 2026 22:42:41 +0300 Subject: [PATCH 2/4] chore(changelog): add Fable 5 entry to Unreleased --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9a7e53ad070..41a2c570674a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### ✨ New Features + +- **feat(providers):** add Claude Fable 5 (`claude-fable-5`) — wires the new flagship model across the full pipeline: `cc` and `kiro` provider registries (1M context, 128k output), pricing constants, model spec (adaptive thinking, vision, tool use), fast mode, 1M-context beta header, fallback chain (`claude-fable-5 → claude-opus-4-8 → claude-opus-4-7 → claude-sonnet-4-6`), and cost data. + --- ## [3.8.18] — Unreleased From 50af49732030f13795073df1e35dc8548c910a64 Mon Sep 17 00:00:00 2001 From: Giorgos Giakoumettis Date: Tue, 9 Jun 2026 23:06:51 +0300 Subject: [PATCH 3/4] fix(providers): address Fable 5 review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move claude-fable-5 to its own Mythos family in MODEL_FAMILIES (was incorrectly grouped under Claude Opus) - Fix notation mismatch in getNextFamilyFallback: normalize dot→hyphen for MODEL_FAMILIES lookup and resolve candidates against the provider's registered model IDs (dot-notation for kiro/github, hyphen for cc/vertex) - Add BEDROCK_CLAUDE_ALIASES to claude-fable-5 spec so Bedrock-routed requests resolve 128k output limit instead of falling back to 8k default - Import getRegistryEntry from providerRegistry for notation resolution --- open-sse/services/modelFamilyFallback.ts | 27 +++++++++++++++++++----- src/shared/constants/modelSpecs.ts | 2 +- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/open-sse/services/modelFamilyFallback.ts b/open-sse/services/modelFamilyFallback.ts index 5bf028f26cb4..22e4e13fce62 100644 --- a/open-sse/services/modelFamilyFallback.ts +++ b/open-sse/services/modelFamilyFallback.ts @@ -14,6 +14,7 @@ import { getModelContextLimit } from "../../src/lib/modelCapabilities"; import { parseModel } from "./model.ts"; import { CONTEXT_OVERFLOW_REGEX } from "./errorClassifier.ts"; +import { getRegistryEntry } from "../config/providerRegistry.ts"; // ── Model Family Definitions ───────────────────────────────────────────────── @@ -72,8 +73,10 @@ const MODEL_FAMILIES: Record = { "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) + "claude-fable-5": ["claude-sonnet-4-6", "claude-opus-4-8"], + // Claude Opus family - "claude-fable-5": ["claude-opus-4-8", "claude-opus-4-7", "claude-sonnet-4-6"], "claude-opus-4-8": ["claude-opus-4-7", "claude-opus-4-6", "claude-sonnet-4-6"], "claude-opus-4-7": ["claude-opus-4-6", "claude-opus-4-5-20251101", "claude-sonnet-4-6"], "claude-opus-4-6": ["claude-opus-4-6-thinking", "claude-opus-4-5-20251101", "claude-sonnet-4-6"], @@ -143,14 +146,28 @@ export function getNextFamilyFallback( ): string | null { const parsed = parseModel(currentModel); const bareModel = parsed.model || currentModel; - const prefix = - parsed.provider || parsed.providerAlias ? `${parsed.provider || parsed.providerAlias}/` : ""; + const provider = parsed.provider || parsed.providerAlias || ""; + const prefix = provider ? `${provider}/` : ""; - const family = MODEL_FAMILIES[bareModel]; + // Normalize dots to hyphens for the lookup so kiro/claude-opus-4.8 finds the right entry + const lookupKey = bareModel.replace(/\./g, "-"); + const family = MODEL_FAMILIES[lookupKey]; if (!family) return null; + // Resolve the provider's supported model IDs so we can match notation (dot vs hyphen) + const registryEntry = provider ? getRegistryEntry(provider) : null; + const supportedIds = registryEntry ? new Set(registryEntry.models.map((m) => m.id)) : null; + for (const candidate of family) { - const fullCandidate = `${prefix}${candidate}`; + let resolvedCandidate = candidate; + if (supportedIds && !supportedIds.has(candidate)) { + // Try dot-notation variants: claude-opus-4-8 → claude-opus-4.8 + const dotVariant = candidate.replace(/-(\d+)-(\d+)$/, "-$1.$2"); + const dotVariant2 = candidate.replace(/-(\d+)-(\d+)-/, "-$1.$2-"); + if (supportedIds.has(dotVariant)) resolvedCandidate = dotVariant; + else if (supportedIds.has(dotVariant2)) resolvedCandidate = dotVariant2; + } + const fullCandidate = `${prefix}${resolvedCandidate}`; if (!triedModels.has(fullCandidate)) { return fullCandidate; } diff --git a/src/shared/constants/modelSpecs.ts b/src/shared/constants/modelSpecs.ts index a478d620388f..0e966f842cff 100644 --- a/src/shared/constants/modelSpecs.ts +++ b/src/shared/constants/modelSpecs.ts @@ -221,7 +221,7 @@ export const MODEL_SPECS: Record = { supportsThinking: true, supportsTools: true, supportsVision: true, - aliases: [], + aliases: BEDROCK_CLAUDE_ALIASES("claude-fable-5"), }, // ── Claude Opus 4.8 ───────────────────────────────────────────── From 07129f1b7352a88ad2254bbc101188fa9515f400 Mon Sep 17 00:00:00 2001 From: Giorgos Giakoumettis Date: Wed, 10 Jun 2026 09:14:39 -0300 Subject: [PATCH 4/4] fix(providers): Fable 5 fallback prefers Opus + test dot-notation resolver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fallback chain was [sonnet-4-6, opus-4-8] (flagship dropping to the cheaper Sonnet before Opus, contradicting the changelog). Reorder to [opus-4-8, opus-4-7, sonnet-4-6] to match the Opus family. Add tests for getNextFamilyFallback covering the new dot-vs-hyphen notation resolution (kiro/claude-fable-5 → kiro/claude-opus-4.8, cc→claude normalization). Co-authored-by: diegosouzapw --- open-sse/services/modelFamilyFallback.ts | 5 ++- .../model-family-fallback-notation.test.ts | 39 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 tests/unit/model-family-fallback-notation.test.ts diff --git a/open-sse/services/modelFamilyFallback.ts b/open-sse/services/modelFamilyFallback.ts index 22e4e13fce62..03d4c34e57a4 100644 --- a/open-sse/services/modelFamilyFallback.ts +++ b/open-sse/services/modelFamilyFallback.ts @@ -73,8 +73,9 @@ const MODEL_FAMILIES: Record = { "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) - "claude-fable-5": ["claude-sonnet-4-6", "claude-opus-4-8"], + // Claude Mythos family (Fable 5) — flagship falls to the next-best Opus + // tiers before the cheaper Sonnet, matching the Opus family ordering. + "claude-fable-5": ["claude-opus-4-8", "claude-opus-4-7", "claude-sonnet-4-6"], // Claude Opus family "claude-opus-4-8": ["claude-opus-4-7", "claude-opus-4-6", "claude-sonnet-4-6"], diff --git a/tests/unit/model-family-fallback-notation.test.ts b/tests/unit/model-family-fallback-notation.test.ts new file mode 100644 index 000000000000..bda527d98df1 --- /dev/null +++ b/tests/unit/model-family-fallback-notation.test.ts @@ -0,0 +1,39 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +// Covers getNextFamilyFallback's dot-vs-hyphen notation resolution (the hunk +// added alongside Claude Fable 5 in #3524 that affects ALL families): the +// lookup normalizes dots→hyphens, and each candidate is resolved to the +// notation the provider's registry actually exposes (kiro uses dot notation +// `claude-opus-4.8`, cc uses hyphen `claude-opus-4-8`). +const { getNextFamilyFallback } = await import("../../open-sse/services/modelFamilyFallback.ts"); + +test("Fable 5 falls back to the next-best Opus tier first (not Sonnet) — cc→claude", () => { + // `cc` is an alias parseModel normalizes to the `claude` provider. + const next = getNextFamilyFallback("cc/claude-fable-5", new Set(["cc/claude-fable-5"])); + assert.equal(next, "claude/claude-opus-4-8"); +}); + +test("Fable 5 fallback resolves to kiro's dot-notation model id", () => { + // kiro registry exposes `claude-opus-4.8` (dot), not `claude-opus-4-8`. + const next = getNextFamilyFallback("kiro/claude-fable-5", new Set(["kiro/claude-fable-5"])); + assert.equal(next, "kiro/claude-opus-4.8"); +}); + +test("dot-notation current model is normalized for the family lookup", () => { + // kiro/claude-opus-4.8 must find the claude-opus-4-8 family entry. + const next = getNextFamilyFallback("kiro/claude-opus-4.8", new Set(["kiro/claude-opus-4.8"])); + assert.equal(next, "kiro/claude-opus-4.7"); +}); + +test("skips already-tried candidates and advances down the Fable chain", () => { + const next = getNextFamilyFallback( + "cc/claude-fable-5", + new Set(["cc/claude-fable-5", "claude/claude-opus-4-8"]) + ); + assert.equal(next, "claude/claude-opus-4-7"); +}); + +test("returns null for an unknown family", () => { + assert.equal(getNextFamilyFallback("cc/not-a-real-model", new Set()), null); +});