From 4df130d0bead47e06e6ced94bcd44f4cf3b2d5fa Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 25 Jul 2026 19:22:19 +0800 Subject: [PATCH] fix(anthropic): honor opus 5 aliases --- extensions/anthropic/stream-wrappers.test.ts | 8 +++++ .../llm-core/src/model-contracts/anthropic.ts | 36 +++++++++++++------ src/plugin-sdk/provider-model-shared.test.ts | 4 +++ 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/extensions/anthropic/stream-wrappers.test.ts b/extensions/anthropic/stream-wrappers.test.ts index f23a628d47c2..517528e88bcc 100644 --- a/extensions/anthropic/stream-wrappers.test.ts +++ b/extensions/anthropic/stream-wrappers.test.ts @@ -280,6 +280,14 @@ describe("anthropic stream wrappers", () => { expect(captured.model?.cost.output).toBe(50); }); + it.each(["opus", "opus-5"])("uses native fast mode for the %s alias", (modelId) => { + const captured = runNativeFastModeWrapper({ modelId }); + + expect(captured.headers?.["anthropic-beta"]).toContain("fast-mode-2026-02-01"); + expect(captured.payload).toEqual({ speed: "fast" }); + expect(captured.model?.cost.output).toBe(50); + }); + it("keeps standard Opus 5 payload and pricing when fast mode is disabled", () => { const captured = runNativeFastModeWrapper({ enabled: false }); diff --git a/packages/llm-core/src/model-contracts/anthropic.ts b/packages/llm-core/src/model-contracts/anthropic.ts index 9554c98fc85a..a54c12d960b1 100644 --- a/packages/llm-core/src/model-contracts/anthropic.ts +++ b/packages/llm-core/src/model-contracts/anthropic.ts @@ -99,9 +99,12 @@ export function resolveClaudeSonnet5ModelIdentity(ref: ClaudeModelRef): string | return normalized.slice((match.index ?? 0) + (match[0].startsWith("-") ? 1 : 0)); } -/** Resolve Claude Opus 5 through direct ids, cloud ids, or deployment metadata. */ +/** Resolve Claude Opus 5 through aliases, direct ids, cloud ids, or deployment metadata. */ export function resolveClaudeOpus5ModelIdentity(ref: ClaudeModelRef): string | undefined { const normalized = resolveClaudeModelIdentity(ref); + if (normalized === "opus" || normalized === "opus-5") { + return "claude-opus-5"; + } const match = /(?:^|-)claude-opus-5(?=$|[^a-z0-9])/.exec(normalized); if (!match) { return undefined; @@ -112,38 +115,51 @@ export function resolveClaudeOpus5ModelIdentity(ref: ClaudeModelRef): string | u /** Return whether a Claude model supports adaptive thinking. */ export function supportsClaudeAdaptiveThinking(ref: ClaudeModelRef): boolean { const modelId = resolveClaudeModelIdentity(ref); - return /(?:^|-)claude-(?:fable-5|mythos-(?:5|preview)|opus-(?:4-(?:6|7|8)|5)|sonnet-(?:5|4-6))(?=$|[^a-z0-9])/.test( - modelId, + return ( + resolveClaudeOpus5ModelIdentity(ref) !== undefined || + /(?:^|-)claude-(?:fable-5|mythos-(?:5|preview)|opus-4-(?:6|7|8)|sonnet-(?:5|4-6))(?=$|[^a-z0-9])/.test( + modelId, + ) ); } /** Return whether a Claude model has a native 1M-token context window. */ export function supportsClaude1MContext(ref: ClaudeModelRef): boolean { const modelId = resolveClaudeModelIdentity(ref); - return /(?:^|-)claude-(?:fable-5|mythos-(?:5|preview)|opus-(?:4-(?:6|7|8)|5)|sonnet-(?:5|4-6))(?=$|[^a-z0-9])/.test( - modelId, + return ( + resolveClaudeOpus5ModelIdentity(ref) !== undefined || + /(?:^|-)claude-(?:fable-5|mythos-(?:5|preview)|opus-4-(?:6|7|8)|sonnet-(?:5|4-6))(?=$|[^a-z0-9])/.test( + modelId, + ) ); } /** Return whether a Claude model supports Anthropic's native fast mode. */ export function supportsClaudeFastMode(ref: ClaudeModelRef): boolean { const modelId = resolveClaudeModelIdentity(ref); - return /(?:^|-)claude-opus-(?:4-8|5)(?=$|[^a-z0-9])/.test(modelId); + return ( + resolveClaudeOpus5ModelIdentity(ref) !== undefined || + /(?:^|-)claude-opus-4-8(?=$|[^a-z0-9])/.test(modelId) + ); } /** Return whether a Claude model supports native max effort. */ export function supportsClaudeNativeMaxEffort(ref: ClaudeModelRef): boolean { const modelId = resolveClaudeModelIdentity(ref); - return /(?:^|-)claude-(?:fable-5|mythos-5|opus-(?:4-(?:6|7|8)|5)|sonnet-(?:5|4-6))(?=$|[^a-z0-9])/.test( - modelId, + return ( + resolveClaudeOpus5ModelIdentity(ref) !== undefined || + /(?:^|-)claude-(?:fable-5|mythos-5|opus-4-(?:6|7|8)|sonnet-(?:5|4-6))(?=$|[^a-z0-9])/.test( + modelId, + ) ); } /** Return whether a Claude model supports native xhigh effort. */ export function supportsClaudeNativeXhighEffort(ref: ClaudeModelRef): boolean { const modelId = resolveClaudeModelIdentity(ref); - return /(?:^|-)claude-(?:fable-5|mythos-5|opus-(?:4-(?:7|8)|5)|sonnet-5)(?=$|[^a-z0-9])/.test( - modelId, + return ( + resolveClaudeOpus5ModelIdentity(ref) !== undefined || + /(?:^|-)claude-(?:fable-5|mythos-5|opus-4-(?:7|8)|sonnet-5)(?=$|[^a-z0-9])/.test(modelId) ); } diff --git a/src/plugin-sdk/provider-model-shared.test.ts b/src/plugin-sdk/provider-model-shared.test.ts index 815f527f99c0..257a28b01c6e 100644 --- a/src/plugin-sdk/provider-model-shared.test.ts +++ b/src/plugin-sdk/provider-model-shared.test.ts @@ -75,6 +75,8 @@ describe("Claude model contracts", () => { it.each([ ["Anthropic API", { id: "claude-opus-5" }, "claude-opus-5"], + ["Anthropic alias", { id: "opus" }, "claude-opus-5"], + ["Anthropic version alias", { id: "opus-5" }, "claude-opus-5"], ["Claude CLI", { id: "claude-opus-5" }, "claude-opus-5"], ["Vertex AI", { id: "claude-opus-5@20260701" }, "claude-opus-5@20260701"], ["Amazon Bedrock", { id: "global.anthropic.claude-opus-5" }, "claude-opus-5"], @@ -108,6 +110,8 @@ describe("Claude model contracts", () => { it("recognizes native fast-mode Claude models", () => { expect(supportsClaudeFastMode({ id: "claude-opus-5" })).toBe(true); + expect(supportsClaudeFastMode({ id: "opus" })).toBe(true); + expect(supportsClaudeFastMode({ id: "opus-5" })).toBe(true); expect(supportsClaudeFastMode({ id: "global.anthropic.claude-opus-5" })).toBe(true); expect(supportsClaudeFastMode({ id: "claude-opus-4.8" })).toBe(true); expect(supportsClaudeFastMode({ id: "claude-opus-4-7" })).toBe(false);