From d8082606724a06751ffda841da14f250f9ed8f26 Mon Sep 17 00:00:00 2001 From: Roomote Date: Tue, 27 Oct 2026 12:49:23 +0000 Subject: [PATCH 1/2] feat: add Claude Haiku 5.5 support across providers --- packages/types/src/providers/anthropic.ts | 24 +++++++++ packages/types/src/providers/openrouter.ts | 2 + .../types/src/providers/vercel-ai-gateway.ts | 1 + packages/types/src/providers/vertex.ts | 17 +++++++ .../__tests__/anthropic-vertex.spec.ts | 51 +++++++++++++++++++ src/api/providers/__tests__/anthropic.spec.ts | 49 ++++++++++++++++++ src/api/providers/anthropic.ts | 1 + .../fetchers/__tests__/openrouter.spec.ts | 28 ++++++++++ .../fetchers/__tests__/requesty.spec.ts | 25 +++++++++ .../__tests__/vercel-ai-gateway.spec.ts | 16 ++++++ src/api/providers/fetchers/openrouter.ts | 7 +++ src/api/providers/fetchers/requesty.ts | 6 +++ .../providers/fetchers/vercel-ai-gateway.ts | 4 ++ src/shared/__tests__/api.spec.ts | 31 +++++++++++ 14 files changed, 262 insertions(+) diff --git a/packages/types/src/providers/anthropic.ts b/packages/types/src/providers/anthropic.ts index 7584225aa0..567f6268be 100644 --- a/packages/types/src/providers/anthropic.ts +++ b/packages/types/src/providers/anthropic.ts @@ -179,6 +179,30 @@ export const anthropicModels = { description: "Claude Opus 5.5 is Anthropic's most capable model for complex agentic coding and enterprise work.", }, + "claude-haiku-5-5": { + maxTokens: 128_000, + contextWindow: 1_000_000, // 1M context window native (no beta header required) + supportsImages: true, + supportsPromptCache: true, + // Standard pricing (prompts of 100,000 tokens or fewer) from Anthropic's + // pricing page. Prompts over 100,000 tokens bill at tiered rates + // ($0.50 in / $2.50 out / $0.625 5m cache write / $0.05 cache read per + // million tokens) that this entry does not model. + // https://platform.claude.com/docs/en/about-claude/pricing + inputPrice: 0.1, // $0.10 per million input tokens + outputPrice: 0.5, // $0.50 per million output tokens + cacheWritesPrice: 0.125, // $0.125 per million tokens (5m cache writes) + cacheReadsPrice: 0.01, // $0.01 per million tokens (cache hits / refreshes) + // Haiku 5.5 uses the same adaptive-thinking / binary-toggle convention as + // Sonnet 5 and Opus 5 on the direct Anthropic provider path. Manual + // extended thinking (budget_tokens) is not accepted, and sampling + // parameters (temperature/top_p/top_k) must be omitted. + supportsReasoningBudget: true, + supportsReasoningBinary: true, + supportsTemperature: false, + description: + "Claude Haiku 5.5 is the fastest, most efficient Claude 5.5 model, built for high-volume, latency-sensitive coding and agent work.", + }, "claude-fable-5-1": { maxTokens: 128_000, // Overridden to 8k if `enableReasoningEffort` is false. contextWindow: 1_000_000, diff --git a/packages/types/src/providers/openrouter.ts b/packages/types/src/providers/openrouter.ts index e54810c06a..abfdafb8ca 100644 --- a/packages/types/src/providers/openrouter.ts +++ b/packages/types/src/providers/openrouter.ts @@ -49,6 +49,7 @@ export const OPEN_ROUTER_PROMPT_CACHING_MODELS = new Set([ "anthropic/claude-fable-5.1", "anthropic/claude-fable-5", "anthropic/claude-haiku-4.5", + "anthropic/claude-haiku-5.5", "google/gemini-2.5-flash-preview", "google/gemini-2.5-flash-preview:thinking", "google/gemini-2.5-flash-preview-05-20", @@ -88,6 +89,7 @@ export const OPEN_ROUTER_REASONING_BUDGET_MODELS = new Set([ "anthropic/claude-sonnet-4.6", "anthropic/claude-sonnet-5", "anthropic/claude-haiku-4.5", + "anthropic/claude-haiku-5.5", "google/gemini-2.5-pro-preview", "google/gemini-2.5-pro", "google/gemini-2.5-flash-preview-05-20", diff --git a/packages/types/src/providers/vercel-ai-gateway.ts b/packages/types/src/providers/vercel-ai-gateway.ts index 5fc6a360ba..51d9ed877c 100644 --- a/packages/types/src/providers/vercel-ai-gateway.ts +++ b/packages/types/src/providers/vercel-ai-gateway.ts @@ -57,6 +57,7 @@ export const VERCEL_AI_GATEWAY_VISION_AND_TOOLS_MODELS = new Set([ "anthropic/claude-3.5-sonnet", "anthropic/claude-3.7-sonnet", "anthropic/claude-haiku-4.5", + "anthropic/claude-haiku-5.5", "anthropic/claude-opus-4", "anthropic/claude-opus-4.1", "anthropic/claude-opus-4.5", diff --git a/packages/types/src/providers/vertex.ts b/packages/types/src/providers/vertex.ts index ba11b3e158..e58aa3e331 100644 --- a/packages/types/src/providers/vertex.ts +++ b/packages/types/src/providers/vertex.ts @@ -526,6 +526,23 @@ export const vertexModels = { description: "Claude Opus 5.5 is Anthropic's most capable model for complex agentic coding and enterprise work.", }, + "claude-haiku-5-5": { + maxTokens: 128_000, + contextWindow: 1_000_000, // 1M context window native (no beta header required) + supportsImages: true, + supportsPromptCache: true, + // Standard pricing (prompts of 100,000 tokens or fewer). Prompts over + // 100,000 tokens bill at higher tiered rates per Anthropic's pricing page. + inputPrice: 0.1, // $0.10 per million input tokens + outputPrice: 0.5, // $0.50 per million output tokens + cacheWritesPrice: 0.125, // $0.125 per million tokens (5m cache writes) + cacheReadsPrice: 0.01, // $0.01 per million tokens (cache hits / refreshes) + supportsReasoningBudget: true, + supportsReasoningBinary: true, + supportsTemperature: false, + description: + "Claude Haiku 5.5 is the fastest, most efficient Claude 5.5 model, built for high-volume, latency-sensitive coding and agent work.", + }, "claude-fable-5-1": { maxTokens: 128_000, contextWindow: 1_000_000, diff --git a/src/api/providers/__tests__/anthropic-vertex.spec.ts b/src/api/providers/__tests__/anthropic-vertex.spec.ts index 31f26f00ce..34788df89b 100644 --- a/src/api/providers/__tests__/anthropic-vertex.spec.ts +++ b/src/api/providers/__tests__/anthropic-vertex.spec.ts @@ -1359,6 +1359,25 @@ describe("VertexHandler", () => { expect(model.info.supportsTemperature).toBe(false) }) + it("should return Claude Haiku 5.5 model info", () => { + const handler = new AnthropicVertexHandler({ + apiModelId: "claude-haiku-5-5", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + + const model = handler.getModel() + expect(model.id).toBe("claude-haiku-5-5") + expect(model.info.maxTokens).toBe(128_000) + expect(model.info.contextWindow).toBe(1_000_000) + expect(model.info.inputPrice).toBe(0.1) + expect(model.info.outputPrice).toBe(0.5) + expect(model.info.supportsReasoningBinary).toBe(true) + expect(model.info.supportsReasoningBudget).toBe(true) + expect(model.info.supportsPromptCache).toBe(true) + expect(model.info.supportsTemperature).toBe(false) + }) + it("should not enable 1M context when flag is disabled", () => { const handler = new AnthropicVertexHandler({ apiModelId: VERTEX_1M_CONTEXT_MODEL_IDS[0], @@ -1717,6 +1736,38 @@ describe("VertexHandler", () => { expect(request.temperature).toBeUndefined() }) + it("should use adaptive thinking for Claude Haiku 5.5", async () => { + const haikuHandler = new AnthropicVertexHandler({ + apiModelId: "claude-haiku-5-5", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + enableReasoningEffort: true, + }) + + const mockCreate = vitest.fn().mockImplementation(async () => + asyncStreamFrom([ + { type: "message_start", message: { usage: { input_tokens: 100, output_tokens: 50 } } }, + { type: "text_delta", delta: { type: "text_delta", text: "Hello" } }, + ]), + ) + // The SDK client's overloaded `create` signature can't be assigned a + // vitest mock directly, so a structural double assertion is required. + ;(haikuHandler["client"].messages as unknown as { create: typeof mockCreate }).create = mockCreate + + await haikuHandler.createMessage("You are a helpful assistant", [{ role: "user", content: "Hello" }]).next() + + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ + thinking: { type: "adaptive", display: "summarized" }, + }), + undefined, + ) + + const request = mockCreate.mock.calls[0][0] + expect(request.thinking).not.toHaveProperty("budget_tokens") + expect(request.temperature).toBeUndefined() + }) + it("should use adaptive thinking for Claude Opus 5.5", async () => { const opusHandler = new AnthropicVertexHandler({ apiModelId: "claude-opus-5-5", diff --git a/src/api/providers/__tests__/anthropic.spec.ts b/src/api/providers/__tests__/anthropic.spec.ts index b80e9887b7..b97f309dcc 100644 --- a/src/api/providers/__tests__/anthropic.spec.ts +++ b/src/api/providers/__tests__/anthropic.spec.ts @@ -508,6 +508,32 @@ describe("AnthropicHandler", () => { expect(requestOptions?.headers?.["anthropic-beta"]).toContain("prompt-caching-2024-07-31") }) + it("should use adaptive thinking for Claude Haiku 5.5 when reasoning is enabled", async () => { + const haikuHandler = new AnthropicHandler({ + apiKey: "test-api-key", + apiModelId: "claude-haiku-5-5", + enableReasoningEffort: true, + modelMaxTokens: 32768, + }) + + const stream = haikuHandler.createMessage(systemPrompt, [ + { + role: "user", + content: [{ type: "text" as const, text: "Hello" }], + }, + ]) + + await collectStream(stream) + + const requestBody = mockCreate.mock.calls[mockCreate.mock.calls.length - 1]?.[0] + const requestOptions = mockCreate.mock.calls[mockCreate.mock.calls.length - 1]?.[1] + expect(requestBody?.thinking).toEqual({ type: "adaptive", display: "summarized" }) + expect(requestBody?.thinking).not.toHaveProperty("budget_tokens") + expect(requestBody?.temperature).toBeUndefined() + expect(requestBody?.max_tokens).toBe(32768) + expect(requestOptions?.headers?.["anthropic-beta"]).toContain("prompt-caching-2024-07-31") + }) + it("should send the custom model ID as-is and use adaptive thinking for a custom Sonnet-5-family model", async () => { const customHandler = new AnthropicHandler({ apiKey: "test-api-key", @@ -1368,6 +1394,29 @@ describe("AnthropicHandler", () => { expect(model.reasoningBudget).toBeUndefined() }) + it("should handle Claude Haiku 5.5 model correctly", () => { + const handler = new AnthropicHandler({ + apiKey: "test-api-key", + apiModelId: "claude-haiku-5-5", + }) + const model = handler.getModel() + expect(model.id).toBe("claude-haiku-5-5") + expect(model.info.maxTokens).toBe(128000) + expect(model.info.contextWindow).toBe(1000000) + expect(model.info.inputPrice).toBe(0.1) + expect(model.info.outputPrice).toBe(0.5) + expect(model.info.cacheWritesPrice).toBe(0.125) + expect(model.info.cacheReadsPrice).toBe(0.01) + // Hybrid reasoning model with the reasoning toggle off: the request + // budget clamps to the Anthropic default, matching Sonnet 5 / Opus 5. + expect(model.maxTokens).toBe(8192) + expect(model.info.supportsReasoningBinary).toBe(true) + expect(model.info.supportsReasoningBudget).toBe(true) + expect(model.info.supportsPromptCache).toBe(true) + expect(model.info.supportsTemperature).toBe(false) + expect(model.reasoningBudget).toBeUndefined() + }) + it("should enable 1M context for Claude 4.5 Sonnet when beta flag is set", () => { const handler = new AnthropicHandler({ apiKey: "test-api-key", diff --git a/src/api/providers/anthropic.ts b/src/api/providers/anthropic.ts index f00b49cd08..c65eb4beba 100644 --- a/src/api/providers/anthropic.ts +++ b/src/api/providers/anthropic.ts @@ -132,6 +132,7 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa case "claude-opus-4-8": case "claude-opus-5": case "claude-opus-5-5": + case "claude-haiku-5-5": case "claude-fable-5-1": case "claude-fable-5": case "claude-opus-4-5-20251101": diff --git a/src/api/providers/fetchers/__tests__/openrouter.spec.ts b/src/api/providers/fetchers/__tests__/openrouter.spec.ts index fb0b80d259..c9bb33c6bc 100644 --- a/src/api/providers/fetchers/__tests__/openrouter.spec.ts +++ b/src/api/providers/fetchers/__tests__/openrouter.spec.ts @@ -509,6 +509,34 @@ describe("OpenRouter API", () => { expect(result.supportsReasoningBinary).toBe(true) }) + it("sets claude-haiku-5-5 model to Anthropic max tokens and omits temperature", () => { + const mockModel = { + name: "Claude Haiku 5.5", + description: "Test model", + context_length: 1000000, + max_completion_tokens: 64000, + pricing: { + prompt: "0.0000001", + completion: "0.0000005", + }, + } + + const result = parseOpenRouterModel({ + id: "anthropic/claude-haiku-5.5", + model: mockModel, + inputModality: ["text", "image"], + outputModality: ["text"], + maxTokens: 128000, + supportedParameters: ["reasoning", "include_reasoning"], + }) + + expect(result.maxTokens).toBe(128000) + expect(result.contextWindow).toBe(1000000) + expect(result.supportsTemperature).toBe(false) + expect(result.supportsReasoningBudget).toBe(true) + expect(result.supportsReasoningBinary).toBe(true) + }) + it("sets horizon-alpha model to 32k max tokens", () => { const mockModel = { name: "Horizon Alpha", diff --git a/src/api/providers/fetchers/__tests__/requesty.spec.ts b/src/api/providers/fetchers/__tests__/requesty.spec.ts index 0cf63abd52..dfc291b9f3 100644 --- a/src/api/providers/fetchers/__tests__/requesty.spec.ts +++ b/src/api/providers/fetchers/__tests__/requesty.spec.ts @@ -151,6 +151,31 @@ describe("getRequestyModels", () => { expect(opus55.supportsTemperature).toBe(false) }) + it("applies Haiku 5.5 overrides when parsing anthropic/claude-haiku-5-5", async () => { + const rawHaiku55 = makeRawModel({ + id: "anthropic/claude-haiku-5-5", + max_output_tokens: 128000, + context_window: 1000000, + supports_caching: true, + supports_vision: true, + supports_reasoning: true, + input_price: "0.0000001", + output_price: "0.0000005", + caching_price: "0.000000125", + cached_price: "0.00000001", + }) + + mockAxiosGet.mockResolvedValueOnce({ data: { data: [rawHaiku55] } }) + + const models = await getRequestyModels() + const haiku55 = models["anthropic/claude-haiku-5-5"] + + expect(haiku55).toBeDefined() + expect(haiku55.supportsReasoningBudget).toBe(true) + expect(haiku55.supportsReasoningBinary).toBe(true) + expect(haiku55.supportsTemperature).toBe(false) + }) + it("does not apply Fable 5 overrides to other models", async () => { const rawSonnet = makeRawModel({ id: "anthropic/claude-sonnet-4.6", diff --git a/src/api/providers/fetchers/__tests__/vercel-ai-gateway.spec.ts b/src/api/providers/fetchers/__tests__/vercel-ai-gateway.spec.ts index 85ce9cf065..305b2d2514 100644 --- a/src/api/providers/fetchers/__tests__/vercel-ai-gateway.spec.ts +++ b/src/api/providers/fetchers/__tests__/vercel-ai-gateway.spec.ts @@ -377,6 +377,22 @@ describe("Vercel AI Gateway Fetchers", () => { expect(result.supportsTemperature).toBe(false) }) + it("marks Claude Haiku 5.5 as not supporting temperature", () => { + const result = parseVercelAiGatewayModel({ + id: "anthropic/claude-haiku-5.5", + model: { + ...baseModel, + id: "anthropic/claude-haiku-5.5", + context_window: 1000000, + max_tokens: 128000, + }, + }) + + expect(result.maxTokens).toBe(128000) + expect(result.contextWindow).toBe(1000000) + expect(result.supportsTemperature).toBe(false) + }) + it("detects vision-only models", () => { // claude 3.5 haiku in VERCEL_AI_GATEWAY_VISION_ONLY_MODELS const visionModel = { diff --git a/src/api/providers/fetchers/openrouter.ts b/src/api/providers/fetchers/openrouter.ts index d68a44155e..758ee4778b 100644 --- a/src/api/providers/fetchers/openrouter.ts +++ b/src/api/providers/fetchers/openrouter.ts @@ -354,6 +354,13 @@ export const parseOpenRouterModel = ({ modelInfo.supportsTemperature = false } + // Set claude-haiku-5-5 model to use the correct Anthropic configuration + if (id === "anthropic/claude-haiku-5.5") { + modelInfo.maxTokens = anthropicModels["claude-haiku-5-5"].maxTokens + modelInfo.supportsReasoningBinary = true + modelInfo.supportsTemperature = false + } + // Ensure correct reasoning handling for Claude Haiku 4.5 on OpenRouter // Use budget control and disable effort-based reasoning fallback if (id === "anthropic/claude-haiku-4.5") { diff --git a/src/api/providers/fetchers/requesty.ts b/src/api/providers/fetchers/requesty.ts index 5eebf92b3e..c167f6212e 100644 --- a/src/api/providers/fetchers/requesty.ts +++ b/src/api/providers/fetchers/requesty.ts @@ -75,6 +75,12 @@ export async function getRequestyModels( modelInfo.supportsTemperature = false } + if (rawModel.id === "anthropic/claude-haiku-5-5") { + modelInfo.supportsReasoningBudget = true + modelInfo.supportsReasoningBinary = true + modelInfo.supportsTemperature = false + } + models[rawModel.id] = modelInfo } } catch (error) { diff --git a/src/api/providers/fetchers/vercel-ai-gateway.ts b/src/api/providers/fetchers/vercel-ai-gateway.ts index 04d785124a..45926c691a 100644 --- a/src/api/providers/fetchers/vercel-ai-gateway.ts +++ b/src/api/providers/fetchers/vercel-ai-gateway.ts @@ -161,5 +161,9 @@ export const parseVercelAiGatewayModel = ({ id, model }: { id: string; model: Ve modelInfo.supportsTemperature = false } + if (id === "anthropic/claude-haiku-5.5") { + modelInfo.supportsTemperature = false + } + return modelInfo } diff --git a/src/shared/__tests__/api.spec.ts b/src/shared/__tests__/api.spec.ts index 7877f49dd1..b6babfca3a 100644 --- a/src/shared/__tests__/api.spec.ts +++ b/src/shared/__tests__/api.spec.ts @@ -261,6 +261,37 @@ describe("getModelMaxOutputTokens", () => { ).toBe(32_768) }) + test("should preserve Anthropic hybrid token handling for Claude Haiku 5.5", () => { + const model: ModelInfo = { + contextWindow: 1_000_000, + supportsPromptCache: true, + supportsReasoningBudget: true, + supportsReasoningBinary: true, + supportsTemperature: false, + maxTokens: 128_000, + } + + expect( + getModelMaxOutputTokens({ + modelId: "claude-haiku-5-5", + model, + settings: { apiProvider: providerIdentifiers.anthropic, enableReasoningEffort: false }, + }), + ).toBe(ANTHROPIC_DEFAULT_MAX_TOKENS) + + expect( + getModelMaxOutputTokens({ + modelId: "claude-haiku-5-5", + model, + settings: { + apiProvider: providerIdentifiers.anthropic, + enableReasoningEffort: true, + modelMaxTokens: 32_768, + }, + }), + ).toBe(32_768) + }) + test("should return model.maxTokens for non-Anthropic models that support reasoning budget but aren't using it", () => { const geminiModelId = "gemini-2.5-flash-preview-04-17" const model: ModelInfo = { From 91cf63185adae738211c01a0e3a1454027ef235c Mon Sep 17 00:00:00 2001 From: Elliott de Launay Date: Wed, 28 Oct 2026 01:18:47 +0000 Subject: [PATCH 2/2] fix: enable Haiku 5.5 prompt caching and strengthen token override test --- packages/types/src/providers/vercel-ai-gateway.ts | 1 + src/api/providers/fetchers/__tests__/openrouter.spec.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/types/src/providers/vercel-ai-gateway.ts b/packages/types/src/providers/vercel-ai-gateway.ts index 51d9ed877c..eddf792b7d 100644 --- a/packages/types/src/providers/vercel-ai-gateway.ts +++ b/packages/types/src/providers/vercel-ai-gateway.ts @@ -9,6 +9,7 @@ export const VERCEL_AI_GATEWAY_PROMPT_CACHING_MODELS = new Set([ "anthropic/claude-3.5-haiku", "anthropic/claude-3.5-sonnet", "anthropic/claude-3.7-sonnet", + "anthropic/claude-haiku-5.5", "anthropic/claude-opus-4", "anthropic/claude-opus-4.1", "anthropic/claude-opus-4.5", diff --git a/src/api/providers/fetchers/__tests__/openrouter.spec.ts b/src/api/providers/fetchers/__tests__/openrouter.spec.ts index c9bb33c6bc..8e33706836 100644 --- a/src/api/providers/fetchers/__tests__/openrouter.spec.ts +++ b/src/api/providers/fetchers/__tests__/openrouter.spec.ts @@ -526,7 +526,7 @@ describe("OpenRouter API", () => { model: mockModel, inputModality: ["text", "image"], outputModality: ["text"], - maxTokens: 128000, + maxTokens: 64000, supportedParameters: ["reasoning", "include_reasoning"], })