From dc25fb5b8a823e42b59d52ce13dec0de03dc0abb Mon Sep 17 00:00:00 2001 From: kousw Date: Sun, 26 Jul 2026 15:26:43 +0900 Subject: [PATCH] feat(anthropic): add Claude Opus 5 --- AGENTS.md | 1 + packages/core/src/models/anthropic.ts | 8 ++++++++ packages/core/tests/model-registry.test.ts | 14 ++++++++++++++ packages/runtime/src/model-reasoning.ts | 12 ++++++++++++ packages/runtime/tests/model-fast.test.ts | 7 +++++++ packages/runtime/tests/model-list-static.test.ts | 16 ++++++++++++++++ packages/runtime/tests/model-reasoning.test.ts | 15 +++++++++++++++ 7 files changed, 73 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 13ec149..f288697 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -42,6 +42,7 @@ Future built-in tool candidates (`apply_patch`, `request_user_input`, `webfetch` Native Z.ai GLM-5.2 provider behavior is specified in `dev-docs/specs/zai-provider.md` and implemented through core `ChatZai` plus runtime `model.provider=zai`. Native Moonshot Kimi K3 provider behavior is specified in `dev-docs/specs/moonshot-provider.md` and implemented through core `ChatMoonshot` plus runtime `model.provider=moonshot`. Native xAI Grok 4.5 provider behavior is specified in `dev-docs/specs/xai-provider.md` and implemented through core `ChatXai` plus runtime `model.provider=xai`. +Anthropic Claude Opus 5 uses the existing native `ChatAnthropic` path with model ID `claude-opus-5`, adaptive thinking across `low|medium|high|xhigh|max`, and Claude API fast mode; its static model contract lives in `packages/core/src/models/anthropic.ts`. ## Implementation plan diff --git a/packages/core/src/models/anthropic.ts b/packages/core/src/models/anthropic.ts index 4f5a79d..db825b2 100644 --- a/packages/core/src/models/anthropic.ts +++ b/packages/core/src/models/anthropic.ts @@ -15,6 +15,14 @@ export const ANTHROPIC_MODELS: ModelSpec[] = [ maxInputTokens: 1_000_000, maxOutputTokens: 128_000, }, + { + id: "claude-opus-5", + provider: "anthropic", + contextWindow: 1_000_000, + maxInputTokens: 1_000_000, + maxOutputTokens: 128_000, + supportsFast: true, + }, { id: "claude-opus-4-8", provider: "anthropic", diff --git a/packages/core/tests/model-registry.test.ts b/packages/core/tests/model-registry.test.ts index 78de072..4b76b1b 100644 --- a/packages/core/tests/model-registry.test.ts +++ b/packages/core/tests/model-registry.test.ts @@ -55,6 +55,20 @@ describe("resolveProviderModelId", () => { }); }); + test("registers Claude Opus 5 with published limits and fast mode", () => { + const registry = createModelRegistry(ANTHROPIC_MODELS); + + expect(resolveModel(registry, "claude-opus-5", "anthropic")).toEqual({ + id: "claude-opus-5", + provider: "anthropic", + contextWindow: 1_000_000, + maxInputTokens: 1_000_000, + maxOutputTokens: 128_000, + supportsFast: true, + }); + expect(supportsFastMode(registry, "claude-opus-5", "anthropic")).toBe(true); + }); + test("uses GPT-5.6 as the OpenAI default without a registry alias", () => { const registry = createModelRegistry(OPENAI_MODELS); diff --git a/packages/runtime/src/model-reasoning.ts b/packages/runtime/src/model-reasoning.ts index 03c9aa7..33097eb 100644 --- a/packages/runtime/src/model-reasoning.ts +++ b/packages/runtime/src/model-reasoning.ts @@ -140,6 +140,17 @@ const ANTHROPIC_REASONING_MODEL_TABLE: Readonly< }, outputEffortByLevel: ANTHROPIC_OUTPUT_EFFORT_ALL, }, + "claude-opus-5": { + supportedLevels: ["low", "medium", "high", "xhigh", "max"], + budgetPresetByLevel: { + low: "reasoning_low", + medium: "reasoning_medium", + high: "reasoning_high", + xhigh: "reasoning_xhigh", + max: "reasoning_xhigh", + }, + outputEffortByLevel: ANTHROPIC_OUTPUT_EFFORT_ALL, + }, "claude-opus-4-8": { supportedLevels: ["low", "medium", "high", "xhigh", "max"], budgetPresetByLevel: { @@ -238,6 +249,7 @@ const ANTHROPIC_REASONING_MODEL_TABLE: Readonly< const ANTHROPIC_ADAPTIVE_THINKING_MODELS = new Set([ "claude-fable-5", + "claude-opus-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6", diff --git a/packages/runtime/tests/model-fast.test.ts b/packages/runtime/tests/model-fast.test.ts index 5530545..781fe73 100644 --- a/packages/runtime/tests/model-fast.test.ts +++ b/packages/runtime/tests/model-fast.test.ts @@ -41,6 +41,13 @@ describe("model fast mode resolution", () => { }); test("enables Anthropic fast mode for supported models only", () => { + expect( + resolveFastMode({ + provider: "anthropic", + model: "claude-opus-5", + requested: true, + }), + ).toEqual({ enabled: true, provider: "anthropic", fastMode: true }); expect( resolveFastMode({ provider: "anthropic", diff --git a/packages/runtime/tests/model-list-static.test.ts b/packages/runtime/tests/model-list-static.test.ts index 5848088..46c8381 100644 --- a/packages/runtime/tests/model-list-static.test.ts +++ b/packages/runtime/tests/model-list-static.test.ts @@ -19,6 +19,22 @@ describe("model.list static providers", () => { }); }); + test("lists Claude Opus 5 from static metadata", async () => { + const result = await buildProviderModelList({ + provider: "anthropic", + includeDetails: true, + log: () => {}, + providerEntriesOverride: {}, + }); + + expect(result.models).toContain("claude-opus-5"); + expect(result.details?.["claude-opus-5"]).toEqual({ + context_window: 1_000_000, + max_input_tokens: 1_000_000, + max_output_tokens: 128_000, + }); + }); + test("details follow merged runtime registry for static providers", async () => { const providerEntries: Record = { "gpt-5.6": { diff --git a/packages/runtime/tests/model-reasoning.test.ts b/packages/runtime/tests/model-reasoning.test.ts index f375538..db5eacd 100644 --- a/packages/runtime/tests/model-reasoning.test.ts +++ b/packages/runtime/tests/model-reasoning.test.ts @@ -248,6 +248,21 @@ describe("model reasoning mapping", () => { expect(mapped.usedFallbackModelProfile).toBe(false); }); + test.each(["low", "medium", "high", "xhigh", "max"] as const)( + "maps Claude Opus 5 %s to native adaptive effort", + (requested) => { + const mapped = resolveAnthropicReasoning({ + model: "claude-opus-5", + requested, + }); + expect(mapped.applied).toBe(requested); + expect(mapped.thinking).toEqual({ type: "adaptive" }); + expect(mapped.outputConfig).toEqual({ effort: requested }); + expect(mapped.fallbackApplied).toBe(false); + expect(mapped.usedFallbackModelProfile).toBe(false); + }, + ); + test("uses conservative fallback profile for unknown anthropic model", () => { const missing: string[] = []; const mapped = resolveAnthropicReasoning({