diff --git a/extensions/anthropic/claude-model-refs.ts b/extensions/anthropic/claude-model-refs.ts index 44d21b80e592..f13d58c212d7 100644 --- a/extensions/anthropic/claude-model-refs.ts +++ b/extensions/anthropic/claude-model-refs.ts @@ -8,6 +8,7 @@ import { CLAUDE_CLI_BACKEND_ID, CLAUDE_CLI_MODEL_ALIASES } from "./cli-constants const DEFAULT_CLAUDE_MODEL_BY_FAMILY: Record = { opus: "claude-opus-4-8", sonnet: "claude-sonnet-5", + fable: "claude-fable-5", haiku: "claude-haiku-4-5", }; diff --git a/extensions/anthropic/cli-catalog.ts b/extensions/anthropic/cli-catalog.ts index 0a9a42934c06..d9ecb8aa87cf 100644 --- a/extensions/anthropic/cli-catalog.ts +++ b/extensions/anthropic/cli-catalog.ts @@ -9,6 +9,7 @@ import { CLAUDE_CLI_BACKEND_ID, CLAUDE_CLI_DEFAULT_ALLOWLIST_REFS } from "./cli- const CLAUDE_CLI_DEFAULT_CONTEXT_WINDOW = 200_000; const CLAUDE_CLI_CONTEXT_WINDOWS: Record = { "claude-sonnet-5": 1_000_000, + "claude-fable-5": 1_000_000, }; const CLAUDE_CLI_DEFAULT_MAX_OUTPUT_TOKENS = 64_000; const CLAUDE_CLI_MAX_OUTPUT_TOKENS: Record = { @@ -16,6 +17,7 @@ const CLAUDE_CLI_MAX_OUTPUT_TOKENS: Record = { "claude-opus-4-7": 128_000, "claude-opus-4-6": 128_000, "claude-sonnet-5": 128_000, + "claude-fable-5": 128_000, "claude-sonnet-4-6": 128_000, }; @@ -24,12 +26,18 @@ const CLAUDE_CLI_MODEL_LABELS: Record = { "claude-opus-4-7": "Claude Opus 4.7 (Claude CLI)", "claude-opus-4-6": "Claude Opus 4.6 (Claude CLI)", "claude-sonnet-5": "Claude Sonnet 5 (Claude CLI)", + "claude-fable-5": "Claude Fable 5 (Claude CLI)", "claude-sonnet-4-6": "Claude Sonnet 4.6 (Claude CLI)", }; function resolveClaudeCliImageMediaInput(id: string): ModelCatalogEntry["mediaInput"] { const maxSidePx = - id === "claude-opus-4-8" || id === "claude-opus-4-7" || id === "claude-sonnet-5" ? 2576 : 1568; + id === "claude-opus-4-8" || + id === "claude-opus-4-7" || + id === "claude-sonnet-5" || + id === "claude-fable-5" + ? 2576 + : 1568; return { image: { maxSidePx, diff --git a/extensions/anthropic/cli-constants.ts b/extensions/anthropic/cli-constants.ts index 164a2f5515f6..55cdfd73d945 100644 --- a/extensions/anthropic/cli-constants.ts +++ b/extensions/anthropic/cli-constants.ts @@ -20,6 +20,7 @@ export const CLAUDE_CLI_CANONICAL_DEFAULT_MODEL_REF = `anthropic/${CLAUDE_CLI_CA export const CLAUDE_CLI_DEFAULT_ALLOWLIST_REFS = [ CLAUDE_CLI_DEFAULT_MODEL_REF, `${CLAUDE_CLI_BACKEND_ID}/claude-sonnet-5`, + `${CLAUDE_CLI_BACKEND_ID}/claude-fable-5`, `${CLAUDE_CLI_BACKEND_ID}/claude-opus-4-7`, `${CLAUDE_CLI_BACKEND_ID}/claude-sonnet-4-6`, `${CLAUDE_CLI_BACKEND_ID}/claude-opus-4-6`, @@ -39,6 +40,9 @@ export const CLAUDE_CLI_MODEL_ALIASES: Record = { "claude-sonnet-5": "claude-sonnet-5", "sonnet-4.6": "claude-sonnet-4-6", "claude-sonnet-4-6": "claude-sonnet-4-6", + fable: "fable", + "fable-5": "claude-fable-5", + "claude-fable-5": "claude-fable-5", haiku: "haiku", }; diff --git a/extensions/anthropic/cli-migration.test.ts b/extensions/anthropic/cli-migration.test.ts index b7ef548aaae9..12bcbf1fc5ea 100644 --- a/extensions/anthropic/cli-migration.test.ts +++ b/extensions/anthropic/cli-migration.test.ts @@ -61,6 +61,20 @@ describe("anthropic Claude model refs", () => { ); }); + it("canonicalizes fable family aliases in bare and provider-qualified forms", () => { + // "fable-5" must map to the full model id, not the family name: the + // canonicalizer only accepts alias values that already start with + // "claude-", so a family-name value would leave the shorthand unresolved. + expect(resolveKnownAnthropicModelRef("fable")).toBe("anthropic/claude-fable-5"); + expect(resolveKnownAnthropicModelRef("fable-5")).toBe("anthropic/claude-fable-5"); + expect(resolveKnownAnthropicModelRef("claude-fable-5")).toBe("anthropic/claude-fable-5"); + expect(resolveKnownAnthropicModelRef("claude-cli/fable")).toBe("anthropic/claude-fable-5"); + expect(resolveKnownAnthropicModelRef("claude-cli/fable-5")).toBe("anthropic/claude-fable-5"); + expect(resolveKnownAnthropicModelRef("claude-cli/claude-fable-5")).toBe( + "anthropic/claude-fable-5", + ); + }); + it("preserves the current claude-haiku-4-5 model and its bare alias", () => { // claude-haiku-4-5 is a current production model (not retired), so neither // its full ref, its dotted variant, nor the bare "haiku" family alias must @@ -166,14 +180,15 @@ describe("anthropic cli migration", () => { alias: "Opus", agentRuntime: { id: "claude-cli" }, }, - "anthropic/claude-opus-4-8": { agentRuntime: { id: "claude-cli" } }, - "anthropic/claude-sonnet-5": { agentRuntime: { id: "claude-cli" } }, - "anthropic/claude-sonnet-4-6": { agentRuntime: { id: "claude-cli" } }, "anthropic/claude-opus-4-6": { alias: "Opus", agentRuntime: { id: "claude-cli" }, }, "openai/gpt-5.2": {}, + "anthropic/claude-opus-4-8": { agentRuntime: { id: "claude-cli" } }, + "anthropic/claude-sonnet-5": { agentRuntime: { id: "claude-cli" } }, + "anthropic/claude-fable-5": { agentRuntime: { id: "claude-cli" } }, + "anthropic/claude-sonnet-4-6": { agentRuntime: { id: "claude-cli" } }, }, }, }, @@ -259,9 +274,10 @@ describe("anthropic cli migration", () => { defaults: { models: { "openai/gpt-5.2": {}, + "anthropic/claude-opus-4-7": { agentRuntime: { id: "claude-cli" } }, "anthropic/claude-opus-4-8": { agentRuntime: { id: "claude-cli" } }, "anthropic/claude-sonnet-5": { agentRuntime: { id: "claude-cli" } }, - "anthropic/claude-opus-4-7": { agentRuntime: { id: "claude-cli" } }, + "anthropic/claude-fable-5": { agentRuntime: { id: "claude-cli" } }, "anthropic/claude-sonnet-4-6": { agentRuntime: { id: "claude-cli" } }, "anthropic/claude-opus-4-6": { agentRuntime: { id: "claude-cli" } }, }, @@ -304,9 +320,10 @@ describe("anthropic cli migration", () => { defaults: { model: { primary: "anthropic/claude-opus-4-7" }, models: { + "anthropic/claude-opus-4-7": { agentRuntime: { id: "claude-cli" } }, "anthropic/claude-opus-4-8": { agentRuntime: { id: "claude-cli" } }, "anthropic/claude-sonnet-5": { agentRuntime: { id: "claude-cli" } }, - "anthropic/claude-opus-4-7": { agentRuntime: { id: "claude-cli" } }, + "anthropic/claude-fable-5": { agentRuntime: { id: "claude-cli" } }, "anthropic/claude-sonnet-4-6": { agentRuntime: { id: "claude-cli" } }, "anthropic/claude-opus-4-6": { agentRuntime: { id: "claude-cli" } }, }, diff --git a/extensions/anthropic/index.test.ts b/extensions/anthropic/index.test.ts index 053fb98df87e..e07a70e918e6 100644 --- a/extensions/anthropic/index.test.ts +++ b/extensions/anthropic/index.test.ts @@ -134,6 +134,20 @@ describe("anthropic provider replay hooks", () => { }); }); + it("publishes Claude Fable 5 CLI metadata without downgrading its API contract", () => { + expect( + buildClaudeCliCatalogEntries().find((model) => model.id === "claude-fable-5"), + ).toMatchObject({ + id: "claude-fable-5", + name: "Claude Fable 5 (Claude CLI)", + contextWindow: 1_000_000, + maxTokens: 128_000, + mediaInput: { + image: { maxSidePx: 2576, preferredSidePx: 2576, tokenMode: "provider" }, + }, + }); + }); + it("keeps bare Claude CLI context plan-safe while publishing output limits", () => { const models = buildClaudeCliCatalogEntries(); for (const id of [ diff --git a/extensions/anthropic/openclaw.plugin.json b/extensions/anthropic/openclaw.plugin.json index cfab8b9478cf..e674107f536f 100644 --- a/extensions/anthropic/openclaw.plugin.json +++ b/extensions/anthropic/openclaw.plugin.json @@ -14,7 +14,7 @@ "providerCatalogEntry": "./provider-discovery.ts", "modelCatalog": { "runtimeAugment": true, - "providers": { + "providers": { "claude-cli": { "models": [ { @@ -28,6 +28,17 @@ "contextWindow": 1000000, "maxTokens": 128000 }, + { + "id": "claude-fable-5", + "name": "Claude Fable 5 (Claude CLI)", + "reasoning": true, + "input": ["text", "image"], + "mediaInput": { + "image": { "maxSidePx": 2576, "preferredSidePx": 2576, "tokenMode": "provider" } + }, + "contextWindow": 1000000, + "maxTokens": 128000 + }, { "id": "claude-opus-4-8", "name": "Claude Opus 4.8 (Claude CLI)", diff --git a/src/agents/cli-runner/prepare-claude.ts b/src/agents/cli-runner/prepare-claude.ts index 2350e7e515b0..fa242583cb71 100644 --- a/src/agents/cli-runner/prepare-claude.ts +++ b/src/agents/cli-runner/prepare-claude.ts @@ -10,6 +10,8 @@ export const CLAUDE_CLI_CONTEXT_MODEL_ALIASES: Record = { "sonnet-5": "claude-sonnet-5", "sonnet-4.6": "claude-sonnet-4-6", "sonnet-4-6": "claude-sonnet-4-6", + fable: "claude-fable-5", + "fable-5": "claude-fable-5", }; export function resolveNodeClaudePlacement(params: { diff --git a/src/agents/context-resolution.ts b/src/agents/context-resolution.ts index 809fdf2907a2..0f7ca5b1ca49 100644 --- a/src/agents/context-resolution.ts +++ b/src/agents/context-resolution.ts @@ -197,13 +197,13 @@ export function resolveAnthropicFixedContextWindow( return undefined; } if ( - (provider === "anthropic" || provider === "anthropic-vertex") && + (provider === "anthropic" || provider === "anthropic-vertex" || provider === "claude-cli") && /^claude-fable-5(?=$|[^a-z0-9])/.test(modelId) ) { return ANTHROPIC_FABLE_CONTEXT_TOKENS; } if ( - (provider === "anthropic" || provider === "anthropic-vertex") && + (provider === "anthropic" || provider === "anthropic-vertex" || provider === "claude-cli") && /^claude-mythos-5(?=$|[^a-z0-9])/.test(modelId) ) { return ANTHROPIC_MYTHOS_5_CONTEXT_TOKENS; diff --git a/src/agents/context.test.ts b/src/agents/context.test.ts index d645a9263c7b..e50c17dc25f5 100644 --- a/src/agents/context.test.ts +++ b/src/agents/context.test.ts @@ -558,8 +558,10 @@ describe("resolveContextTokensForModel", () => { it.each([ ["anthropic", "claude-fable-5", ANTHROPIC_FABLE_CONTEXT_TOKENS], ["anthropic-vertex", "claude-fable-5", ANTHROPIC_FABLE_CONTEXT_TOKENS], + ["claude-cli", "claude-fable-5", ANTHROPIC_FABLE_CONTEXT_TOKENS], ["anthropic", "claude-mythos-5", ANTHROPIC_MYTHOS_5_CONTEXT_TOKENS], ["anthropic-vertex", "claude-mythos-5", ANTHROPIC_MYTHOS_5_CONTEXT_TOKENS], + ["claude-cli", "claude-mythos-5", ANTHROPIC_MYTHOS_5_CONTEXT_TOKENS], ["anthropic", "claude-sonnet-5", ANTHROPIC_SONNET_5_CONTEXT_TOKENS], ["anthropic-vertex", "claude-sonnet-5", ANTHROPIC_SONNET_5_CONTEXT_TOKENS], ["claude-cli", "claude-sonnet-5", ANTHROPIC_SONNET_5_CONTEXT_TOKENS], @@ -697,8 +699,10 @@ describe("resolveContextTokensForModel", () => { it.each([ ["anthropic", "claude-fable-5", ANTHROPIC_FABLE_CONTEXT_TOKENS], ["anthropic-vertex", "claude-fable-5", ANTHROPIC_FABLE_CONTEXT_TOKENS], + ["claude-cli", "claude-fable-5", ANTHROPIC_FABLE_CONTEXT_TOKENS], ["anthropic", "claude-mythos-5", ANTHROPIC_MYTHOS_5_CONTEXT_TOKENS], ["anthropic-vertex", "claude-mythos-5", ANTHROPIC_MYTHOS_5_CONTEXT_TOKENS], + ["claude-cli", "claude-mythos-5", ANTHROPIC_MYTHOS_5_CONTEXT_TOKENS], ["anthropic", "claude-sonnet-5", ANTHROPIC_SONNET_5_CONTEXT_TOKENS], ["anthropic-vertex", "claude-sonnet-5", ANTHROPIC_SONNET_5_CONTEXT_TOKENS], ["claude-cli", "claude-sonnet-5", ANTHROPIC_SONNET_5_CONTEXT_TOKENS],