From 7ef3376b62837f4051df711232e1a72c9d3e4fde Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 24 Jul 2026 19:37:27 +0200 Subject: [PATCH] feat(claude): add Opus 5 model --- .../server/agent/providers/claude/agent.test.ts | 4 +++- .../agent/providers/claude/model-manifest.ts | 12 ++++++++++-- .../agent/providers/claude/models.test.ts | 17 ++++++++++++++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/packages/server/src/server/agent/providers/claude/agent.test.ts b/packages/server/src/server/agent/providers/claude/agent.test.ts index 50e505011..60c6cc16e 100644 --- a/packages/server/src/server/agent/providers/claude/agent.test.ts +++ b/packages/server/src/server/agent/providers/claude/agent.test.ts @@ -414,6 +414,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => { }); expect(models.map((m) => m.id)).toEqual([ + "claude-opus-5", "claude-fable-5", "claude-opus-4-8[1m]", "claude-opus-4-8", @@ -433,7 +434,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => { } const defaultModel = models.find((m) => m.isDefault); - expect(defaultModel?.id).toBe("claude-opus-4-8"); + expect(defaultModel?.id).toBe("claude-opus-5"); } finally { await fs.rm(emptyConfigDir, { recursive: true, force: true }); } @@ -456,6 +457,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => { return models.find((model) => model.id === modelId)?.thinkingOptions?.map(({ id }) => id); }; + expect(getThinkingIds("claude-opus-5")).toContain("ultracode"); expect(getThinkingIds("claude-fable-5")).toContain("ultracode"); expect(getThinkingIds("claude-opus-4-8[1m]")).toContain("ultracode"); expect(getThinkingIds("claude-opus-4-8")).toContain("ultracode"); diff --git a/packages/server/src/server/agent/providers/claude/model-manifest.ts b/packages/server/src/server/agent/providers/claude/model-manifest.ts index 88c469ad3..b569f0da1 100644 --- a/packages/server/src/server/agent/providers/claude/model-manifest.ts +++ b/packages/server/src/server/agent/providers/claude/model-manifest.ts @@ -30,6 +30,15 @@ export const CLAUDE_DISABLED_THINKING_OPTION_ID = "off"; export const CLAUDE_ULTRACODE_THINKING_OPTION_ID = "ultracode"; export const CLAUDE_MODEL_MANIFEST = [ + { + id: "claude-opus-5", + label: "Opus 5", + description: "Opus 5 · Latest release", + isDefault: true, + contextWindowMaxTokens: 1_000_000, + effortLevels: CLAUDE_EFFORT_LEVELS.xhigh, + supportsThinkingDisabled: true, + }, { id: "claude-fable-5", label: "Fable 5", @@ -49,8 +58,7 @@ export const CLAUDE_MODEL_MANIFEST = [ { id: "claude-opus-4-8", label: "Opus 4.8", - description: "Opus 4.8 · Latest release", - isDefault: true, + description: "Opus 4.8 · Previous release", contextWindowMaxTokens: 200_000, effortLevels: CLAUDE_EFFORT_LEVELS.xhigh, supportsThinkingDisabled: true, diff --git a/packages/server/src/server/agent/providers/claude/models.test.ts b/packages/server/src/server/agent/providers/claude/models.test.ts index a62784494..5b069dce1 100644 --- a/packages/server/src/server/agent/providers/claude/models.test.ts +++ b/packages/server/src/server/agent/providers/claude/models.test.ts @@ -42,6 +42,7 @@ describe("getClaudeModels", () => { it("returns all claude models", () => { const models = getClaudeModels(); expect(models.map((m) => m.id)).toEqual([ + "claude-opus-5", "claude-fable-5", "claude-opus-4-8[1m]", "claude-opus-4-8", @@ -60,7 +61,7 @@ describe("getClaudeModels", () => { const models = getClaudeModels(); const defaults = models.filter((m) => m.isDefault); expect(defaults).toHaveLength(1); - expect(defaults[0].id).toBe("claude-opus-4-8"); + expect(defaults[0].id).toBe("claude-opus-5"); }); it("defines context window sizes in the catalog", () => { @@ -70,6 +71,7 @@ describe("getClaudeModels", () => { expect(contextWindows).toEqual( new Map([ + ["claude-opus-5", 1_000_000], ["claude-fable-5", 1_000_000], ["claude-opus-4-8[1m]", 1_000_000], ["claude-opus-4-8", 200_000], @@ -88,6 +90,15 @@ describe("getClaudeModels", () => { it("derives thinking options from model effort capabilities", () => { const models = new Map(getClaudeModels().map((model) => [model.id, model])); + expect(models.get("claude-opus-5")?.thinkingOptions?.map((option) => option.id)).toEqual([ + CLAUDE_DISABLED_THINKING_OPTION_ID, + "low", + "medium", + "high", + "xhigh", + "max", + CLAUDE_ULTRACODE_THINKING_OPTION_ID, + ]); expect(models.get("claude-sonnet-5")?.thinkingOptions?.map((option) => option.id)).toEqual([ CLAUDE_DISABLED_THINKING_OPTION_ID, "low", @@ -128,6 +139,8 @@ describe("getClaudeModels", () => { }); it.each([ + ["claude-opus-5", true, "low"], + ["claude-opus-5-20260724", true, "low"], ["claude-sonnet-5", true, "low"], ["claude-sonnet-5-20260101", true, "low"], ["claude-fable-5", false, "low"], @@ -286,6 +299,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => { describe("normalizeClaudeRuntimeModelId", () => { it("returns exact match for known model IDs", () => { + expect(normalizeClaudeRuntimeModelId("claude-opus-5")).toBe("claude-opus-5"); expect(normalizeClaudeRuntimeModelId("claude-fable-5")).toBe("claude-fable-5"); expect(normalizeClaudeRuntimeModelId("claude-sonnet-5")).toBe("claude-sonnet-5"); expect(normalizeClaudeRuntimeModelId("claude-opus-4-6")).toBe("claude-opus-4-6"); @@ -295,6 +309,7 @@ describe("normalizeClaudeRuntimeModelId", () => { }); it("normalizes dated model IDs to base model", () => { + expect(normalizeClaudeRuntimeModelId("claude-opus-5-20260724")).toBe("claude-opus-5"); expect(normalizeClaudeRuntimeModelId("claude-fable-5-20260301")).toBe("claude-fable-5"); expect(normalizeClaudeRuntimeModelId("claude-sonnet-5-20260101")).toBe("claude-sonnet-5"); expect(normalizeClaudeRuntimeModelId("claude-opus-4-6-20260101")).toBe("claude-opus-4-6");