diff --git a/.changeset/add-claude-sonnet-5-5-support.md b/.changeset/add-claude-sonnet-5-5-support.md new file mode 100644 index 0000000000..bf72f62371 --- /dev/null +++ b/.changeset/add-claude-sonnet-5-5-support.md @@ -0,0 +1,5 @@ +--- +"zoo-code": minor +--- + +Add Claude Sonnet 5.5 (`claude-sonnet-5-5`) support across the Anthropic, Amazon Bedrock, Vertex AI, OpenRouter, Requesty, and Vercel AI Gateway providers. Sonnet 5.5 has a native 1M-token context window, 128K max output tokens, $2/$10 per-million-token input/output pricing, and keeps the Sonnet 5 adaptive-thinking contract (no sampling parameters, effort-based reasoning). diff --git a/packages/types/src/providers/anthropic.ts b/packages/types/src/providers/anthropic.ts index 8103690da0..99579ce6ea 100644 --- a/packages/types/src/providers/anthropic.ts +++ b/packages/types/src/providers/anthropic.ts @@ -48,6 +48,24 @@ export const anthropicModels = { description: "Claude Sonnet 5 is the best combination of speed and intelligence, optimized for coding, tool use, and agentic workflows.", }, + "claude-sonnet-5-5": { + maxTokens: 128_000, // Overridden to 8k if `enableReasoningEffort` is false. + contextWindow: 1_000_000, // 1M context window native (no beta header required) + supportsImages: true, + supportsPromptCache: true, + inputPrice: 2.0, // $2 per million input tokens + outputPrice: 10.0, // $10 per million output tokens + cacheWritesPrice: 2.5, // $2.50 per million tokens (5m cache write) + cacheReadsPrice: 0.2, // $0.20 per million tokens + // Sonnet 5.5 keeps the Sonnet 5 adaptive-thinking / binary-toggle + // convention on the direct Anthropic provider path: manual budget_tokens + // and non-default sampling parameters return a 400. + supportsReasoningBudget: true, + supportsReasoningBinary: true, + supportsTemperature: false, + description: + "Claude Sonnet 5.5 is the best combination of speed and intelligence, optimized for coding, tool use, and agentic workflows.", + }, "claude-sonnet-4-5": { maxTokens: 64_000, // Overridden to 8k if `enableReasoningEffort` is false. contextWindow: 200_000, // Default 200K, extendable to 1M with beta flag 'context-1m-2025-08-07' diff --git a/packages/types/src/providers/bedrock.ts b/packages/types/src/providers/bedrock.ts index 492089be5b..0d75383205 100644 --- a/packages/types/src/providers/bedrock.ts +++ b/packages/types/src/providers/bedrock.ts @@ -70,6 +70,27 @@ export const bedrockModels = { description: "Claude Sonnet 5 is the best combination of speed and intelligence, optimized for coding, tool use, and agentic workflows.", }, + "anthropic.claude-sonnet-5-5": { + // Undated model ID exactly as documented for Bedrock; no date-suffix + // variant has been published yet. + maxTokens: 128_000, + supportsMaxTokens: true, + contextWindow: 1_000_000, // 1M context window native (no beta header required) + supportsImages: true, + supportsPromptCache: true, + supportsReasoningBudget: true, + supportsReasoningBinary: true, + supportsTemperature: false, + inputPrice: 2.0, // $2 per million input tokens + outputPrice: 10.0, // $10 per million output tokens + cacheWritesPrice: 2.5, // $2.50 per million tokens (5m cache write) + cacheReadsPrice: 0.2, // $0.20 per million tokens + minTokensPerCachePoint: 1024, + maxCachePoints: 4, + cachableFields: ["system", "messages", "tools"], + description: + "Claude Sonnet 5.5 is the best combination of speed and intelligence, optimized for coding, tool use, and agentic workflows.", + }, "amazon.nova-pro-v1:0": { maxTokens: 5000, contextWindow: 300_000, @@ -680,7 +701,11 @@ export const BEDROCK_1M_CONTEXT_MODEL_IDS = [ // intentionally absent. // https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-sonnet-5.html // https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-opus-5.html -export const BEDROCK_THINKING_DISABLE_MODEL_IDS = ["anthropic.claude-sonnet-5", "anthropic.claude-opus-5"] as const +export const BEDROCK_THINKING_DISABLE_MODEL_IDS = [ + "anthropic.claude-sonnet-5", + "anthropic.claude-sonnet-5-5", + "anthropic.claude-opus-5", +] as const // Amazon Bedrock models that support Global Inference profiles // As of Nov 2025, AWS supports Global Inference for: @@ -688,6 +713,7 @@ export const BEDROCK_THINKING_DISABLE_MODEL_IDS = ["anthropic.claude-sonnet-5", // - Claude Sonnet 4.5 // - Claude Sonnet 4.6 // - Claude Sonnet 5 +// - Claude Sonnet 5.5 // - Claude Haiku 4.5 // - Claude Opus 4.5 // - Claude Opus 4.6 @@ -700,6 +726,7 @@ export const BEDROCK_GLOBAL_INFERENCE_MODEL_IDS = [ "anthropic.claude-sonnet-4-5-20250929-v1:0", "anthropic.claude-sonnet-4-6", "anthropic.claude-sonnet-5", + "anthropic.claude-sonnet-5-5", "anthropic.claude-haiku-4-5-20251001-v1:0", "anthropic.claude-opus-4-5-20251101-v1:0", "anthropic.claude-opus-4-6-v1", diff --git a/packages/types/src/providers/openrouter.ts b/packages/types/src/providers/openrouter.ts index e54810c06a..c190a0e69c 100644 --- a/packages/types/src/providers/openrouter.ts +++ b/packages/types/src/providers/openrouter.ts @@ -40,6 +40,7 @@ export const OPEN_ROUTER_PROMPT_CACHING_MODELS = new Set([ "anthropic/claude-sonnet-4.5", "anthropic/claude-sonnet-4.6", "anthropic/claude-sonnet-5", + "anthropic/claude-sonnet-5-5", "anthropic/claude-opus-4", "anthropic/claude-opus-4.1", "anthropic/claude-opus-4.5", @@ -87,6 +88,7 @@ export const OPEN_ROUTER_REASONING_BUDGET_MODELS = new Set([ "anthropic/claude-sonnet-4.5", "anthropic/claude-sonnet-4.6", "anthropic/claude-sonnet-5", + "anthropic/claude-sonnet-5-5", "anthropic/claude-haiku-4.5", "google/gemini-2.5-pro-preview", "google/gemini-2.5-pro", diff --git a/packages/types/src/providers/vercel-ai-gateway.ts b/packages/types/src/providers/vercel-ai-gateway.ts index 5fc6a360ba..cb300398da 100644 --- a/packages/types/src/providers/vercel-ai-gateway.ts +++ b/packages/types/src/providers/vercel-ai-gateway.ts @@ -20,6 +20,7 @@ export const VERCEL_AI_GATEWAY_PROMPT_CACHING_MODELS = new Set([ "anthropic/claude-sonnet-4", "anthropic/claude-sonnet-4.6", "anthropic/claude-sonnet-5", + "anthropic/claude-sonnet-5-5", "openai/gpt-4.1", "openai/gpt-4.1-mini", "openai/gpt-4.1-nano", @@ -69,6 +70,7 @@ export const VERCEL_AI_GATEWAY_VISION_AND_TOOLS_MODELS = new Set([ "anthropic/claude-sonnet-4.5", "anthropic/claude-sonnet-4.6", "anthropic/claude-sonnet-5", + "anthropic/claude-sonnet-5-5", "google/gemini-1.5-flash", "google/gemini-1.5-pro", "google/gemini-2.0-flash", diff --git a/packages/types/src/providers/vertex.ts b/packages/types/src/providers/vertex.ts index 68a063986a..4d1f9dcf56 100644 --- a/packages/types/src/providers/vertex.ts +++ b/packages/types/src/providers/vertex.ts @@ -396,6 +396,21 @@ export const vertexModels = { description: "Claude Sonnet 5 is the best combination of speed and intelligence, optimized for coding, tool use, and agentic workflows.", }, + "claude-sonnet-5-5": { + maxTokens: 128_000, // 128K max output tokens per the Anthropic model card (same across all platforms). + contextWindow: 1_000_000, // 1M context window native (no beta header required) + supportsImages: true, + supportsPromptCache: true, + inputPrice: 2.0, // $2 per million input tokens + outputPrice: 10.0, // $10 per million output tokens + cacheWritesPrice: 2.5, // $2.50 per million tokens (5m cache write) + cacheReadsPrice: 0.2, // $0.20 per million tokens + supportsReasoningBudget: true, + supportsReasoningBinary: true, + supportsTemperature: false, + description: + "Claude Sonnet 5.5 is the best combination of speed and intelligence, optimized for coding, tool use, and agentic workflows.", + }, "claude-haiku-4-5@20251001": { maxTokens: 8192, contextWindow: 200_000, diff --git a/src/api/providers/__tests__/anthropic-vertex.spec.ts b/src/api/providers/__tests__/anthropic-vertex.spec.ts index 89f352cfc5..6a7c7d30c2 100644 --- a/src/api/providers/__tests__/anthropic-vertex.spec.ts +++ b/src/api/providers/__tests__/anthropic-vertex.spec.ts @@ -1027,6 +1027,25 @@ describe("VertexHandler", () => { expect(model.info.supportsTemperature).toBe(false) }) + it("should return Claude Sonnet 5.5 model info", () => { + const handler = new AnthropicVertexHandler({ + apiModelId: "claude-sonnet-5-5", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + + const model = handler.getModel() + expect(model.id).toBe("claude-sonnet-5-5") + expect(model.info.maxTokens).toBe(128_000) + expect(model.info.contextWindow).toBe(1_000_000) + expect(model.info.inputPrice).toBe(2.0) + expect(model.info.outputPrice).toBe(10.0) + 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 return Claude Opus 5 model info", () => { const handler = new AnthropicVertexHandler({ apiModelId: "claude-opus-5", @@ -1390,6 +1409,41 @@ describe("VertexHandler", () => { expect(request.temperature).toBeUndefined() }) + it("should use adaptive thinking for Claude Sonnet 5.5", async () => { + const sonnetHandler = new AnthropicVertexHandler({ + apiModelId: "claude-sonnet-5-5", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + enableReasoningEffort: true, + }) + + const mockCreate = vitest + .fn() + .mockImplementation(async () => + asyncStreamFrom([ + { type: "message_start", message: { usage: { input_tokens: 10, output_tokens: 5 } } }, + ]), + ) + // The SDK client's overloaded `create` signature can't be assigned a + // vitest mock directly, so a structural double assertion is required. + ;(sonnetHandler["client"].messages as unknown as { create: typeof mockCreate }).create = mockCreate + + await sonnetHandler + .createMessage("You are a helpful assistant", [{ role: "user", content: "Hello" }]) + .next() + + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ + thinking: { type: "adaptive" }, + }), + 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", async () => { const opusHandler = new AnthropicVertexHandler({ apiModelId: "claude-opus-5", diff --git a/src/api/providers/__tests__/anthropic.spec.ts b/src/api/providers/__tests__/anthropic.spec.ts index 6a3192798f..6beaa52663 100644 --- a/src/api/providers/__tests__/anthropic.spec.ts +++ b/src/api/providers/__tests__/anthropic.spec.ts @@ -456,6 +456,31 @@ describe("AnthropicHandler", () => { expect(requestOptions?.headers?.["anthropic-beta"]).toContain("prompt-caching-2024-07-31") }) + it("should use adaptive thinking for Claude Sonnet 5.5 when reasoning is enabled", async () => { + const sonnetHandler = new AnthropicHandler({ + apiKey: "test-api-key", + apiModelId: "claude-sonnet-5-5", + enableReasoningEffort: true, + modelMaxTokens: 32768, + }) + + const stream = sonnetHandler.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" }) + expect(requestBody?.temperature).toBeUndefined() + expect(requestBody?.max_tokens).toBe(32768) + expect(requestOptions?.headers?.["anthropic-beta"]).toContain("prompt-caching-2024-07-31") + }) + it("should use adaptive thinking for Claude Opus 5 when reasoning is enabled", async () => { const opusHandler = new AnthropicHandler({ apiKey: "test-api-key", @@ -725,6 +750,27 @@ describe("AnthropicHandler", () => { expect(model.reasoningBudget).toBeUndefined() }) + it("should handle Claude Sonnet 5.5 model correctly", () => { + const handler = new AnthropicHandler({ + apiKey: "test-api-key", + apiModelId: "claude-sonnet-5-5", + }) + const model = handler.getModel() + expect(model.id).toBe("claude-sonnet-5-5") + expect(model.info.maxTokens).toBe(128000) + expect(model.info.contextWindow).toBe(1000000) + expect(model.info.inputPrice).toBe(2.0) + expect(model.info.outputPrice).toBe(10.0) + expect(model.info.cacheWritesPrice).toBe(2.5) + expect(model.info.cacheReadsPrice).toBe(0.2) + 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 handle Claude Opus 5 model correctly", () => { const handler = new AnthropicHandler({ apiKey: "test-api-key", diff --git a/src/api/providers/__tests__/bedrock.spec.ts b/src/api/providers/__tests__/bedrock.spec.ts index fd6da53a9b..e26f02e4e4 100644 --- a/src/api/providers/__tests__/bedrock.spec.ts +++ b/src/api/providers/__tests__/bedrock.spec.ts @@ -138,6 +138,7 @@ describe("AwsBedrockHandler", () => { ["anthropic.claude-sonnet-4-5-20250929-v1:0", 64_000], ["anthropic.claude-sonnet-4-6", 64_000], ["anthropic.claude-sonnet-5", 128_000], + ["anthropic.claude-sonnet-5-5", 128_000], ["anthropic.claude-opus-4-7", 128_000], ["anthropic.claude-opus-4-8", 128_000], ["anthropic.claude-opus-5", 128_000], @@ -977,6 +978,41 @@ describe("AwsBedrockHandler", () => { expect(model.id).toBe("global.anthropic.claude-sonnet-5") }) + it("should return Claude Sonnet 5.5 model info", () => { + const handler = new AwsBedrockHandler({ + apiModelId: "anthropic.claude-sonnet-5-5", + awsAccessKey: "test", + awsSecretKey: "test", + awsRegion: "us-east-1", + }) + + const model = handler.getModel() + expect(model.id).toBe("anthropic.claude-sonnet-5-5") + expect(model.info.maxTokens).toBe(128_000) + expect(model.info.contextWindow).toBe(1_000_000) + expect(model.info.inputPrice).toBe(2.0) + expect(model.info.outputPrice).toBe(10.0) + expect(model.info.minTokensPerCachePoint).toBe(1024) + 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.maxTokens).toBe(8192) + }) + + it("should apply global inference prefix for Claude Sonnet 5.5 when awsUseGlobalInference is true", () => { + const handler = new AwsBedrockHandler({ + apiModelId: "anthropic.claude-sonnet-5-5", + awsAccessKey: "test", + awsSecretKey: "test", + awsRegion: "us-east-1", + awsUseGlobalInference: true, + }) + + const model = handler.getModel() + expect(model.id).toBe("global.anthropic.claude-sonnet-5-5") + }) + it("should return Claude Opus 5 model info", () => { const handler = new AwsBedrockHandler({ apiModelId: "anthropic.claude-opus-5", @@ -1774,6 +1810,36 @@ describe("AwsBedrockHandler", () => { expect(commandArg.inferenceConfig?.temperature).toBeUndefined() }) + it("should send adaptive thinking with effort xhigh for Claude Sonnet 5.5 when reasoning is enabled", async () => { + // Sonnet 5.5 keeps the Sonnet 5 adaptive-thinking contract. + const sonnet55Handler = new AwsBedrockHandler({ + apiModelId: "anthropic.claude-sonnet-5-5", + awsAccessKey: "test-access-key", + awsSecretKey: "test-secret-key", + awsRegion: "us-east-1", + enableReasoningEffort: true, + }) + + const generator = sonnet55Handler.createMessage("System prompt", messages) + await generator.next() + + expect(mockConverseStreamCommand).toHaveBeenCalled() + const commandArg = mockConverseStreamCommand.mock.calls[0][0] + + // additionalModelRequestFields is typed as a recursive DocumentType union; + // narrow to the adaptive-thinking shape the handler sends. + const requestFields = commandArg.additionalModelRequestFields as + | { thinking?: unknown; output_config?: unknown } + | undefined + expect(requestFields?.thinking).toEqual({ + type: "adaptive", + display: "summarized", + }) + expect(requestFields?.output_config).toEqual({ effort: "xhigh" }) + // Sonnet 5.5 rejects sampling parameters: temperature must be omitted entirely. + expect(commandArg.inferenceConfig?.temperature).toBeUndefined() + }) + it("should send adaptive thinking with effort xhigh for Claude Opus 5 when reasoning is enabled", async () => { // End-to-end regression guard for the Opus 5 handler branch. The // isAdaptiveThinkingModel predicate is unit-covered, but a regression in @@ -1816,23 +1882,25 @@ describe("AwsBedrockHandler", () => { expect(commandArg.inferenceConfig?.temperature).toBeUndefined() }) - it.each(["anthropic.claude-sonnet-5", "anthropic.claude-opus-5", "us.anthropic.claude-opus-5"])( - "explicitly disables thinking for %s when reasoning is disabled", - async (apiModelId) => { - const provider = new AwsBedrockHandler({ - apiModelId, - enableReasoningEffort: false, - modelMaxTokens: 32_000, - }) - await collectStream(provider.createMessage("System prompt", messages)) + it.each([ + "anthropic.claude-sonnet-5", + "anthropic.claude-sonnet-5-5", + "anthropic.claude-opus-5", + "us.anthropic.claude-opus-5", + ])("explicitly disables thinking for %s when reasoning is disabled", async (apiModelId) => { + const provider = new AwsBedrockHandler({ + apiModelId, + enableReasoningEffort: false, + modelMaxTokens: 32_000, + }) + await collectStream(provider.createMessage("System prompt", messages)) - const commandArg = mockConverseStreamCommand.mock.calls[0][0] - expect(commandArg.additionalModelRequestFields).toEqual( - expect.objectContaining({ thinking: { type: "disabled" } }), - ) - expect(commandArg.inferenceConfig?.temperature).toBeUndefined() - }, - ) + const commandArg = mockConverseStreamCommand.mock.calls[0][0] + expect(commandArg.additionalModelRequestFields).toEqual( + expect.objectContaining({ thinking: { type: "disabled" } }), + ) + expect(commandArg.inferenceConfig?.temperature).toBeUndefined() + }) it.each([ "anthropic.claude-fable-5", @@ -1965,6 +2033,7 @@ describe("AwsBedrockHandler", () => { expect(isAdaptiveThinkingModel("anthropic.claude-opus-4-8")).toBe(true) expect(isAdaptiveThinkingModel("anthropic.claude-fable-5")).toBe(true) expect(isAdaptiveThinkingModel("anthropic.claude-sonnet-5")).toBe(true) + expect(isAdaptiveThinkingModel("anthropic.claude-sonnet-5-5")).toBe(true) expect(isAdaptiveThinkingModel("anthropic.claude-opus-5")).toBe(true) expect(isAdaptiveThinkingModel("anthropic.claude-opus-5-5")).toBe(true) // Future-proof Sonnet patterns — guarded even before a registry entry exists. @@ -1976,6 +2045,7 @@ describe("AwsBedrockHandler", () => { expect(isAdaptiveThinkingModel("us.anthropic.claude-opus-4-8")).toBe(true) expect(isAdaptiveThinkingModel("global.anthropic.claude-fable-5")).toBe(true) expect(isAdaptiveThinkingModel("global.anthropic.claude-sonnet-5")).toBe(true) + expect(isAdaptiveThinkingModel("global.anthropic.claude-sonnet-5-5")).toBe(true) expect(isAdaptiveThinkingModel("global.anthropic.claude-opus-5")).toBe(true) expect(isAdaptiveThinkingModel("global.anthropic.claude-opus-5-5")).toBe(true) expect(isAdaptiveThinkingModel("eu.anthropic.claude-sonnet-4-7")).toBe(true) diff --git a/src/api/providers/anthropic.ts b/src/api/providers/anthropic.ts index da3559a331..acd124839c 100644 --- a/src/api/providers/anthropic.ts +++ b/src/api/providers/anthropic.ts @@ -115,6 +115,7 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa switch (modelId) { case "claude-sonnet-5": + case "claude-sonnet-5-5": case "claude-sonnet-4-6": case "claude-sonnet-4-5": case "claude-sonnet-4-20250514": @@ -189,6 +190,7 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa // Then check for models that support prompt caching switch (modelId) { case "claude-sonnet-5": + case "claude-sonnet-5-5": case "claude-sonnet-4-6": case "claude-sonnet-4-5": case "claude-sonnet-4-20250514": diff --git a/src/api/providers/fetchers/__tests__/openrouter.spec.ts b/src/api/providers/fetchers/__tests__/openrouter.spec.ts index 99ef4d6119..1acd93f255 100644 --- a/src/api/providers/fetchers/__tests__/openrouter.spec.ts +++ b/src/api/providers/fetchers/__tests__/openrouter.spec.ts @@ -446,6 +446,34 @@ describe("OpenRouter API", () => { expect(result.supportsReasoningBinary).toBe(true) }) + it("sets claude-sonnet-5-5 model to Anthropic max tokens and omits temperature", () => { + const mockModel = { + name: "Claude Sonnet 5.5", + description: "Test model", + context_length: 1000000, + max_completion_tokens: 128000, + pricing: { + prompt: "0.000002", + completion: "0.00001", + }, + } + + const result = parseOpenRouterModel({ + id: "anthropic/claude-sonnet-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 claude-opus-5 model to Anthropic max tokens and omits temperature", () => { const mockModel = { name: "Claude Opus 5", diff --git a/src/api/providers/fetchers/__tests__/requesty.spec.ts b/src/api/providers/fetchers/__tests__/requesty.spec.ts index 0cf63abd52..d76401fad2 100644 --- a/src/api/providers/fetchers/__tests__/requesty.spec.ts +++ b/src/api/providers/fetchers/__tests__/requesty.spec.ts @@ -101,6 +101,31 @@ describe("getRequestyModels", () => { expect(sonnet5.supportsTemperature).toBe(false) }) + it("applies Sonnet 5.5 overrides when parsing anthropic/claude-sonnet-5-5", async () => { + const rawSonnet55 = makeRawModel({ + id: "anthropic/claude-sonnet-5-5", + max_output_tokens: 128000, + context_window: 1000000, + supports_caching: true, + supports_vision: true, + supports_reasoning: true, + input_price: "0.000002", + output_price: "0.00001", + caching_price: "0.0000025", + cached_price: "0.0000002", + }) + + mockAxiosGet.mockResolvedValueOnce({ data: { data: [rawSonnet55] } }) + + const models = await getRequestyModels() + const sonnet55 = models["anthropic/claude-sonnet-5-5"] + + expect(sonnet55).toBeDefined() + expect(sonnet55.supportsReasoningBudget).toBe(true) + expect(sonnet55.supportsReasoningBinary).toBe(true) + expect(sonnet55.supportsTemperature).toBe(false) + }) + it("applies Opus 5 overrides when parsing anthropic/claude-opus-5", async () => { const rawOpus5 = makeRawModel({ id: "anthropic/claude-opus-5", 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 1331ed4049..2556550f32 100644 --- a/src/api/providers/fetchers/__tests__/vercel-ai-gateway.spec.ts +++ b/src/api/providers/fetchers/__tests__/vercel-ai-gateway.spec.ts @@ -318,6 +318,22 @@ describe("Vercel AI Gateway Fetchers", () => { expect(result.supportsTemperature).toBe(false) }) + it("marks Claude Sonnet 5.5 as not supporting temperature", () => { + const result = parseVercelAiGatewayModel({ + id: "anthropic/claude-sonnet-5-5", + model: { + ...baseModel, + id: "anthropic/claude-sonnet-5-5", + context_window: 1000000, + max_tokens: 128000, + }, + }) + + expect(result.maxTokens).toBe(128000) + expect(result.contextWindow).toBe(1000000) + expect(result.supportsTemperature).toBe(false) + }) + it("marks Claude Opus 5 as not supporting temperature", () => { const result = parseVercelAiGatewayModel({ id: "anthropic/claude-opus-5", diff --git a/src/api/providers/fetchers/openrouter.ts b/src/api/providers/fetchers/openrouter.ts index 710d55b890..db38cffcaa 100644 --- a/src/api/providers/fetchers/openrouter.ts +++ b/src/api/providers/fetchers/openrouter.ts @@ -302,6 +302,13 @@ export const parseOpenRouterModel = ({ modelInfo.supportsTemperature = false } + // Set claude-sonnet-5-5 model to use the correct Anthropic configuration + if (id === "anthropic/claude-sonnet-5-5") { + modelInfo.maxTokens = anthropicModels["claude-sonnet-5-5"].maxTokens + modelInfo.supportsReasoningBinary = true + modelInfo.supportsTemperature = false + } + // Set claude-opus-5 model to use the correct Anthropic configuration if (id === "anthropic/claude-opus-5") { modelInfo.maxTokens = anthropicModels["claude-opus-5"].maxTokens diff --git a/src/api/providers/fetchers/requesty.ts b/src/api/providers/fetchers/requesty.ts index 5eebf92b3e..3d09a76461 100644 --- a/src/api/providers/fetchers/requesty.ts +++ b/src/api/providers/fetchers/requesty.ts @@ -63,6 +63,12 @@ export async function getRequestyModels( modelInfo.supportsTemperature = false } + if (rawModel.id === "anthropic/claude-sonnet-5-5") { + modelInfo.supportsReasoningBudget = true + modelInfo.supportsReasoningBinary = true + modelInfo.supportsTemperature = false + } + if (rawModel.id === "anthropic/claude-opus-5") { modelInfo.supportsReasoningBudget = true modelInfo.supportsReasoningBinary = true diff --git a/src/api/providers/fetchers/vercel-ai-gateway.ts b/src/api/providers/fetchers/vercel-ai-gateway.ts index e0bf5861c7..f479069384 100644 --- a/src/api/providers/fetchers/vercel-ai-gateway.ts +++ b/src/api/providers/fetchers/vercel-ai-gateway.ts @@ -140,6 +140,10 @@ export const parseVercelAiGatewayModel = ({ id, model }: { id: string; model: Ve modelInfo.supportsTemperature = false } + if (id === "anthropic/claude-sonnet-5-5") { + modelInfo.supportsTemperature = false + } + if (id === "anthropic/claude-opus-5") { modelInfo.supportsTemperature = false } diff --git a/src/shared/__tests__/api.spec.ts b/src/shared/__tests__/api.spec.ts index 8f405a38e6..73ccd80a80 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 Sonnet 5.5", () => { + const model: ModelInfo = { + contextWindow: 1_000_000, + supportsPromptCache: true, + supportsReasoningBudget: true, + supportsReasoningBinary: true, + supportsTemperature: false, + maxTokens: 128_000, + } + + expect( + getModelMaxOutputTokens({ + modelId: "claude-sonnet-5-5", + model, + settings: { apiProvider: providerIdentifiers.anthropic, enableReasoningEffort: false }, + }), + ).toBe(ANTHROPIC_DEFAULT_MAX_TOKENS) + + expect( + getModelMaxOutputTokens({ + modelId: "claude-sonnet-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 = { diff --git a/webview-ui/src/components/settings/__tests__/ThinkingBudget.spec.tsx b/webview-ui/src/components/settings/__tests__/ThinkingBudget.spec.tsx index 30a7927468..bcba1ef413 100644 --- a/webview-ui/src/components/settings/__tests__/ThinkingBudget.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/ThinkingBudget.spec.tsx @@ -83,6 +83,7 @@ describe("ThinkingBudget", () => { ["anthropic.claude-sonnet-4-5-20250929-v1:0", 64_000], ["anthropic.claude-opus-4-8", 128_000], ["anthropic.claude-sonnet-5", 128_000], + ["anthropic.claude-sonnet-5-5", 128_000], ["anthropic.claude-opus-5", 128_000], ] as const)("allows selecting the documented Bedrock output ceiling for %s", (apiModelId, ceiling) => { render(