fix(anthropic): add Fable 5 to Claude CLI catalog (#101453)

* fix(anthropic): add claude-fable-5 to CLI allowlist, aliases, labels, and context window

CLI path was missing claude-fable-5 across all four metadata layers while
the direct Anthropic provider path in register.runtime.ts already had full
support via isAnthropicFable5Model() / resolveAnthropicFixedContextWindow().

- CLAUDE_CLI_DEFAULT_ALLOWLIST_REFS: add claude-cli/claude-fable-5
- CLAUDE_CLI_MODEL_ALIASES: add fable/fable-5/claude-fable-5 mappings
- CLAUDE_CLI_CONTEXT_WINDOWS: add claude-fable-5 -> 1_000_000 (was falling back to 200K)
- CLAUDE_CLI_MODEL_LABELS: add Claude Fable 5 (Claude CLI)
- resolveClaudeCliImageMediaInput: add fable-5 to 2576 max-side tier

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(anthropic): route fable alias through context/model-ref canonicalization paths

* fix(agent): include claude-cli in fable-5/mythos-5 fixed context window resolution

* fix(anthropic): add claude-fable-5 to static CLI manifest catalog

* test(anthropic): cover fable-5 alias canonicalization in bare and provider-qualified forms

* style(anthropic): fix indentation in plugin manifest

* fix(anthropic): publish Fable CLI output limit

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Peter Lee
2026-07-18 00:36:45 -05:00
committed by GitHub
parent e6534018e1
commit 893196483d
9 changed files with 70 additions and 9 deletions

View File

@@ -8,6 +8,7 @@ import { CLAUDE_CLI_BACKEND_ID, CLAUDE_CLI_MODEL_ALIASES } from "./cli-constants
const DEFAULT_CLAUDE_MODEL_BY_FAMILY: Record<string, string> = {
opus: "claude-opus-4-8",
sonnet: "claude-sonnet-5",
fable: "claude-fable-5",
haiku: "claude-haiku-4-5",
};

View File

@@ -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<string, number> = {
"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<string, number> = {
@@ -16,6 +17,7 @@ const CLAUDE_CLI_MAX_OUTPUT_TOKENS: Record<string, number> = {
"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<string, string> = {
"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,

View File

@@ -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<string, string> = {
"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",
};

View File

@@ -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" } },
},

View File

@@ -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 [

View File

@@ -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)",

View File

@@ -10,6 +10,8 @@ export const CLAUDE_CLI_CONTEXT_MODEL_ALIASES: Record<string, string> = {
"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: {

View File

@@ -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;

View File

@@ -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],