From 9fcae41c98802b8c4e432fd2f62439fe0b91ce22 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Wed, 7 Oct 2026 21:00:11 +0100 Subject: [PATCH] feat(claude): add Haiku 5.5 with its model capabilities (#6332) --- packages/cli/tests/15-provider.test.ts | 11 +++- .../agent/providers/claude/agent.test.ts | 12 +++- .../agent/providers/claude/model-manifest.ts | 12 +++- .../agent/providers/claude/models.test.ts | 57 ++++++++++++++++++- 4 files changed, 86 insertions(+), 6 deletions(-) diff --git a/packages/cli/tests/15-provider.test.ts b/packages/cli/tests/15-provider.test.ts index bc1183de81..3081ee3584 100644 --- a/packages/cli/tests/15-provider.test.ts +++ b/packages/cli/tests/15-provider.test.ts @@ -122,10 +122,15 @@ const EXPECTED_CLAUDE_MODELS = [ model: "Opus 4.6", descriptionFragment: "Most capable", }, + { + id: "claude-haiku-5-5", + model: "Haiku 5.5", + descriptionFragment: "Fastest", + }, { id: "claude-haiku-4-5", model: "Haiku 4.5", - descriptionFragment: "Fastest", + descriptionFragment: "Previous release", }, ] as const; @@ -435,8 +440,8 @@ try { "--quiet should print the same ordered model IDs returned by --json", ); assert( - claudeModelsFromJson.some((m) => m.id === "claude-sonnet-5-5"), - "captured --json output should include the current Claude everyday model id", + claudeModelsFromJson.some((m) => m.id === "claude-haiku-5-5"), + "captured --json output should include the current Claude quick-answer model id", ); console.log("✓ provider models --quiet outputs model IDs only\n"); } 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 2d970c49de..9f7430b103 100644 --- a/packages/server/src/server/agent/providers/claude/agent.test.ts +++ b/packages/server/src/server/agent/providers/claude/agent.test.ts @@ -496,7 +496,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => { const client = new ClaudeAgentClient({ logger, resolveBinary: async () => "/test/claude/bin", - resolveVersion: async () => "2.1.284", + resolveVersion: async () => "2.1.293", runtimeSettings: { env: { CLAUDE_CONFIG_DIR: emptyConfigDir } }, }); const { models } = await client.fetchCatalog({ @@ -515,6 +515,15 @@ describe("ClaudeAgentClient.fetchCatalog", () => { expect(getThinkingIds("claude-opus-4-8")).toContain("ultracode"); expect(getThinkingIds("claude-sonnet-5")).toContain("xhigh"); expect(getThinkingIds("claude-sonnet-5")).toContain("ultracode"); + expect(getThinkingIds("claude-haiku-5-5")).toEqual([ + "off", + "low", + "medium", + "high", + "xhigh", + "max", + "ultracode", + ]); expect(getThinkingIds("claude-sonnet-5-5")).toContain("xhigh"); expect(getThinkingIds("claude-sonnet-5-5")).not.toContain("off"); expect(getThinkingIds("claude-opus-4-7[1m]")).toContain("ultracode"); @@ -1223,6 +1232,7 @@ describe("ClaudeAgentSession features", () => { test.each([ ["supported model", "claude-opus-4-8", { type: "disabled" }, undefined], + ["Haiku 5.5", "claude-haiku-5-5", { type: "disabled" }, undefined], ["unsupported model", "claude-fable-5", { type: "adaptive", display: "summarized" }, "high"], ["custom model", "openrouter/anthropic/claude-opus-4-8", undefined, undefined], ["provider default", null, undefined, undefined], 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 e2e64f28fd..398fcb1ccd 100644 --- a/packages/server/src/server/agent/providers/claude/model-manifest.ts +++ b/packages/server/src/server/agent/providers/claude/model-manifest.ts @@ -170,10 +170,20 @@ export const CLAUDE_MODEL_MANIFEST = [ effortLevels: CLAUDE_EFFORT_LEVELS.standard, supportsThinkingDisabled: true, }, + { + id: "claude-haiku-5-5", + label: "Haiku 5.5", + description: "Haiku 5.5 · Fastest for quick answers", + minimumClaudeCodeVersion: "2.1.293", + defaultThinkingOptionId: "medium", + contextWindowMaxTokens: 1_000_000, + effortLevels: CLAUDE_EFFORT_LEVELS.xhigh, + supportsThinkingDisabled: true, + }, { id: "claude-haiku-4-5", label: "Haiku 4.5", - description: "Haiku 4.5 · Fastest for quick answers", + description: "Haiku 4.5 · Previous release", contextWindowMaxTokens: 200_000, }, ] as const satisfies readonly ClaudeModelManifestEntry[]; 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 0e187734b8..ed7db8841c 100644 --- a/packages/server/src/server/agent/providers/claude/models.test.ts +++ b/packages/server/src/server/agent/providers/claude/models.test.ts @@ -39,7 +39,7 @@ async function createClaudeConfigDirWithRawSettings(settings: string): Promise claudeCodeVersion, @@ -66,6 +66,7 @@ describe("getClaudeModels", () => { "claude-opus-4-6", "claude-sonnet-4-6[1m]", "claude-sonnet-4-6", + "claude-haiku-5-5", "claude-haiku-4-5", ]); }); @@ -100,6 +101,7 @@ describe("getClaudeModels", () => { ["claude-opus-4-6", 200_000], ["claude-sonnet-4-6[1m]", 1_000_000], ["claude-sonnet-4-6", 200_000], + ["claude-haiku-5-5", 1_000_000], ["claude-haiku-4-5", 200_000], ]), ); @@ -123,6 +125,12 @@ describe("getClaudeModels", () => { expect(getClaudeModels("2.1.284").map((model) => model.id)).toContain("claude-sonnet-5-5"); }); + it("gates Haiku 5.5 on Claude Code 2.1.293 without changing the default", () => { + expect(getClaudeModels("2.1.292").map((model) => model.id)).not.toContain("claude-haiku-5-5"); + expect(getClaudeModels("2.1.293").map((model) => model.id)).toContain("claude-haiku-5-5"); + expect(getClaudeModels("2.1.293").find((model) => model.isDefault)?.id).toBe("claude-opus-5-5"); + }); + it("derives thinking options from model effort capabilities", () => { const models = new Map(getClaudeModels().map((model) => [model.id, model])); @@ -192,6 +200,8 @@ describe("getClaudeModels", () => { ["claude-sonnet-5-20260101", true, "high"], ["claude-fable-5", false, "high"], ["claude-fable-5-1", false, "high"], + ["claude-haiku-5-5", true, "medium"], + ["claude-haiku-5-5-20261007", true, "medium"], ["claude-haiku-4-5", false, undefined], ["openrouter/anthropic/claude-opus-4-8", false, undefined], [null, false, undefined], @@ -653,6 +663,51 @@ describe("Claude Sonnet 5.5 catalog", () => { }); }); +describe("Claude Haiku 5.5 catalog", () => { + it("offers one Haiku 5.5 entry with a 1M context window", () => { + const haiku55Models = getClaudeModels() + .filter((model) => model.id.startsWith("claude-haiku-5-5")) + .map(({ id, label, contextWindowMaxTokens }) => ({ id, label, contextWindowMaxTokens })); + + expect(haiku55Models).toEqual([ + { id: "claude-haiku-5-5", label: "Haiku 5.5", contextWindowMaxTokens: 1_000_000 }, + ]); + }); + + it("offers Off and every effort level with medium as the default", () => { + const haiku55 = getClaudeModels().find((model) => model.id === "claude-haiku-5-5"); + + expect(haiku55?.thinkingOptions?.map((option) => option.id)).toEqual([ + CLAUDE_DISABLED_THINKING_OPTION_ID, + "low", + "medium", + "high", + "xhigh", + "max", + CLAUDE_ULTRACODE_THINKING_OPTION_ID, + ]); + expect(haiku55?.defaultThinkingOptionId).toBe("medium"); + expect( + haiku55?.thinkingOptions?.filter((option) => option.isDefault).map((option) => option.id), + ).toEqual(["medium"]); + }); + + it("normalizes provider-prefixed runtime IDs", () => { + expect(normalizeClaudeRuntimeModelId("anthropic/claude-haiku-5-5")).toBe("claude-haiku-5-5"); + expect(normalizeClaudeRuntimeModelId("us.anthropic.claude-haiku-5-5-v1:0")).toBe( + "claude-haiku-5-5", + ); + expect(claudeManifestModelSupportsFastMode("claude-haiku-5-5")).toBe(false); + }); + + it("resolves suffixed and dated Haiku 5.5 IDs to the single catalog entry", () => { + expect(findClaudeModel("claude-haiku-5-5[1m]")?.id).toBe("claude-haiku-5-5"); + expect(findClaudeModel("claude-haiku-5-5-20261007")?.id).toBe("claude-haiku-5-5"); + expect(findClaudeModel("claude-haiku-5-5-20261007[1m]")?.id).toBe("claude-haiku-5-5"); + expect(findClaudeModel("claude-haiku-5-5[1m]")?.contextWindowMaxTokens).toBe(1_000_000); + }); +}); + describe("claudeManifestModelSupportsFastMode", () => { it("keeps fast mode strict to first-party manifest model IDs", () => { expect(normalizeClaudeManifestModelId("openrouter/anthropic/claude-opus-4-8")).toBeNull();