From 90e2451ad1cef0a78c7ef2a41f12c5c18b0795c3 Mon Sep 17 00:00:00 2001 From: Peter Dave Hello <3691490+PeterDaveHello@users.noreply.github.com> Date: Wed, 18 Feb 2026 03:28:18 +0800 Subject: [PATCH] Add Anthropic Claude Sonnet 4.6 support across providers (#11509) * Add Anthropic Claude Sonnet 4.6 support across providers Add model definitions and capability flags for Anthropic, Bedrock, Vertex, OpenRouter, and Vercel AI Gateway. Update Anthropic handler and UI model selection logic to support Claude Sonnet 4.6 1M context behavior and tier pricing. Add focused tests for provider handlers, fetchers, and selected-model hooks. Keep Bedrock UI tier pricing parity as-is because this is a pre-existing issue for Opus 4.6 and will be handled separately. Reference: - https://www.anthropic.com/news/claude-sonnet-4-6 - https://platform.claude.com/docs/en/about-claude/models/overview#latest-models-comparison * Delete .changeset/soft-carpets-hunt.md --------- Co-authored-by: Hannes Rudolph --- packages/types/src/providers/anthropic.ts | 21 +++++++++ packages/types/src/providers/bedrock.ts | 27 +++++++++++ packages/types/src/providers/openrouter.ts | 2 + .../types/src/providers/vercel-ai-gateway.ts | 2 + packages/types/src/providers/vertex.ts | 22 +++++++++ .../__tests__/anthropic-vertex.spec.ts | 15 ++++++ src/api/providers/__tests__/anthropic.spec.ts | 46 +++++++++++++++++++ src/api/providers/__tests__/bedrock.spec.ts | 15 ++++++ src/api/providers/anthropic.ts | 10 +++- .../fetchers/__tests__/openrouter.spec.ts | 24 ++++++++++ src/api/providers/fetchers/openrouter.ts | 5 ++ .../settings/providers/Anthropic.tsx | 1 + .../hooks/__tests__/useSelectedModel.spec.ts | 32 +++++++++++++ .../components/ui/hooks/useSelectedModel.ts | 7 ++- 14 files changed, 225 insertions(+), 4 deletions(-) diff --git a/packages/types/src/providers/anthropic.ts b/packages/types/src/providers/anthropic.ts index 62e377c7e5..40a3d885d8 100644 --- a/packages/types/src/providers/anthropic.ts +++ b/packages/types/src/providers/anthropic.ts @@ -7,6 +7,27 @@ export type AnthropicModelId = keyof typeof anthropicModels export const anthropicDefaultModelId: AnthropicModelId = "claude-sonnet-4-5" export const anthropicModels = { + "claude-sonnet-4-6": { + 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' + supportsImages: true, + supportsPromptCache: true, + inputPrice: 3.0, // $3 per million input tokens (≤200K context) + outputPrice: 15.0, // $15 per million output tokens (≤200K context) + cacheWritesPrice: 3.75, // $3.75 per million tokens + cacheReadsPrice: 0.3, // $0.30 per million tokens + supportsReasoningBudget: true, + // Tiered pricing for extended context (requires beta flag 'context-1m-2025-08-07') + tiers: [ + { + contextWindow: 1_000_000, // 1M tokens with beta flag + inputPrice: 6.0, // $6 per million input tokens (>200K context) + outputPrice: 22.5, // $22.50 per million output tokens (>200K context) + cacheWritesPrice: 7.5, // $7.50 per million tokens (>200K context) + cacheReadsPrice: 0.6, // $0.60 per million tokens (>200K context) + }, + ], + }, "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 008961b301..575db6984a 100644 --- a/packages/types/src/providers/bedrock.ts +++ b/packages/types/src/providers/bedrock.ts @@ -27,6 +27,30 @@ export const bedrockModels = { maxCachePoints: 4, cachableFields: ["system", "messages", "tools"], }, + "anthropic.claude-sonnet-4-6-20260114-v1:0": { + maxTokens: 8192, + contextWindow: 200_000, // Default 200K, extendable to 1M with beta flag 'context-1m-2025-08-07' + supportsImages: true, + supportsPromptCache: true, + supportsReasoningBudget: true, + inputPrice: 3.0, // $3 per million input tokens (≤200K context) + outputPrice: 15.0, // $15 per million output tokens (≤200K context) + cacheWritesPrice: 3.75, // $3.75 per million tokens + cacheReadsPrice: 0.3, // $0.30 per million tokens + minTokensPerCachePoint: 1024, + maxCachePoints: 4, + cachableFields: ["system", "messages", "tools"], + // Tiered pricing for extended context (requires beta flag 'context-1m-2025-08-07') + tiers: [ + { + contextWindow: 1_000_000, // 1M tokens with beta flag + inputPrice: 6.0, // $6 per million input tokens (>200K context) + outputPrice: 22.5, // $22.50 per million output tokens (>200K context) + cacheWritesPrice: 7.5, // $7.50 per million tokens (>200K context) + cacheReadsPrice: 0.6, // $0.60 per million tokens (>200K context) + }, + ], + }, "amazon.nova-pro-v1:0": { maxTokens: 5000, contextWindow: 300_000, @@ -499,6 +523,7 @@ export const BEDROCK_REGIONS = [ export const BEDROCK_1M_CONTEXT_MODEL_IDS = [ "anthropic.claude-sonnet-4-20250514-v1:0", "anthropic.claude-sonnet-4-5-20250929-v1:0", + "anthropic.claude-sonnet-4-6-20260114-v1:0", "anthropic.claude-opus-4-6-v1", ] as const @@ -506,12 +531,14 @@ export const BEDROCK_1M_CONTEXT_MODEL_IDS = [ // As of Nov 2025, AWS supports Global Inference for: // - Claude Sonnet 4 // - Claude Sonnet 4.5 +// - Claude Sonnet 4.6 // - Claude Haiku 4.5 // - Claude Opus 4.5 // - Claude Opus 4.6 export const BEDROCK_GLOBAL_INFERENCE_MODEL_IDS = [ "anthropic.claude-sonnet-4-20250514-v1:0", "anthropic.claude-sonnet-4-5-20250929-v1:0", + "anthropic.claude-sonnet-4-6-20260114-v1:0", "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 c8168e6024..834f40528e 100644 --- a/packages/types/src/providers/openrouter.ts +++ b/packages/types/src/providers/openrouter.ts @@ -38,6 +38,7 @@ export const OPEN_ROUTER_PROMPT_CACHING_MODELS = new Set([ "anthropic/claude-3.7-sonnet:thinking", "anthropic/claude-sonnet-4", "anthropic/claude-sonnet-4.5", + "anthropic/claude-sonnet-4.6", "anthropic/claude-opus-4", "anthropic/claude-opus-4.1", "anthropic/claude-opus-4.5", @@ -75,6 +76,7 @@ export const OPEN_ROUTER_REASONING_BUDGET_MODELS = new Set([ "anthropic/claude-opus-4.6", "anthropic/claude-sonnet-4", "anthropic/claude-sonnet-4.5", + "anthropic/claude-sonnet-4.6", "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 43a94a0697..ac633747ba 100644 --- a/packages/types/src/providers/vercel-ai-gateway.ts +++ b/packages/types/src/providers/vercel-ai-gateway.ts @@ -14,6 +14,7 @@ export const VERCEL_AI_GATEWAY_PROMPT_CACHING_MODELS = new Set([ "anthropic/claude-opus-4.5", "anthropic/claude-opus-4.6", "anthropic/claude-sonnet-4", + "anthropic/claude-sonnet-4.6", "openai/gpt-4.1", "openai/gpt-4.1-mini", "openai/gpt-4.1-nano", @@ -55,6 +56,7 @@ export const VERCEL_AI_GATEWAY_VISION_AND_TOOLS_MODELS = new Set([ "anthropic/claude-opus-4.5", "anthropic/claude-opus-4.6", "anthropic/claude-sonnet-4", + "anthropic/claude-sonnet-4.6", "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 55e5648011..2f8a05602a 100644 --- a/packages/types/src/providers/vertex.ts +++ b/packages/types/src/providers/vertex.ts @@ -263,6 +263,27 @@ export const vertexModels = { }, ], }, + "claude-sonnet-4-6@20260114": { + maxTokens: 8192, + contextWindow: 200_000, // Default 200K, extendable to 1M with beta flag 'context-1m-2025-08-07' + supportsImages: true, + supportsPromptCache: true, + inputPrice: 3.0, // $3 per million input tokens (≤200K context) + outputPrice: 15.0, // $15 per million output tokens (≤200K context) + cacheWritesPrice: 3.75, // $3.75 per million tokens + cacheReadsPrice: 0.3, // $0.30 per million tokens + supportsReasoningBudget: true, + // Tiered pricing for extended context (requires beta flag 'context-1m-2025-08-07') + tiers: [ + { + contextWindow: 1_000_000, // 1M tokens with beta flag + inputPrice: 6.0, // $6 per million input tokens (>200K context) + outputPrice: 22.5, // $22.50 per million output tokens (>200K context) + cacheWritesPrice: 7.5, // $7.50 per million tokens (>200K context) + cacheReadsPrice: 0.6, // $0.60 per million tokens (>200K context) + }, + ], + }, "claude-haiku-4-5@20251001": { maxTokens: 8192, contextWindow: 200_000, @@ -491,6 +512,7 @@ export const vertexModels = { export const VERTEX_1M_CONTEXT_MODEL_IDS = [ "claude-sonnet-4@20250514", "claude-sonnet-4-5@20250929", + "claude-sonnet-4-6@20260114", "claude-opus-4-6", ] as const diff --git a/src/api/providers/__tests__/anthropic-vertex.spec.ts b/src/api/providers/__tests__/anthropic-vertex.spec.ts index 3d9798fde9..dc284ab754 100644 --- a/src/api/providers/__tests__/anthropic-vertex.spec.ts +++ b/src/api/providers/__tests__/anthropic-vertex.spec.ts @@ -899,6 +899,21 @@ describe("VertexHandler", () => { expect(model.betas).toContain("context-1m-2025-08-07") }) + it("should enable 1M context for Claude Sonnet 4.6 when beta flag is set", () => { + const handler = new AnthropicVertexHandler({ + apiModelId: "claude-sonnet-4-6@20260114", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + vertex1MContext: true, + }) + + const model = handler.getModel() + expect(model.info.contextWindow).toBe(1_000_000) + expect(model.info.inputPrice).toBe(6.0) + expect(model.info.outputPrice).toBe(22.5) + expect(model.betas).toContain("context-1m-2025-08-07") + }) + it("should not enable 1M context when flag is disabled", () => { const handler = new AnthropicVertexHandler({ apiModelId: VERTEX_1M_CONTEXT_MODEL_IDS[0], diff --git a/src/api/providers/__tests__/anthropic.spec.ts b/src/api/providers/__tests__/anthropic.spec.ts index 7a107edbc8..3731f3a068 100644 --- a/src/api/providers/__tests__/anthropic.spec.ts +++ b/src/api/providers/__tests__/anthropic.spec.ts @@ -187,6 +187,28 @@ describe("AnthropicHandler", () => { // Verify API expect(mockCreate).toHaveBeenCalled() }) + + it("should include 1M context beta header for Claude Sonnet 4.6 when enabled", async () => { + const sonnet46Handler = new AnthropicHandler({ + apiKey: "test-api-key", + apiModelId: "claude-sonnet-4-6", + anthropicBeta1MContext: true, + }) + + const stream = sonnet46Handler.createMessage(systemPrompt, [ + { + role: "user", + content: [{ type: "text" as const, text: "Hello" }], + }, + ]) + + for await (const _chunk of stream) { + // Consume stream + } + + const requestOptions = mockCreate.mock.calls[mockCreate.mock.calls.length - 1]?.[1] + expect(requestOptions?.headers?.["anthropic-beta"]).toContain("context-1m-2025-08-07") + }) }) describe("completePrompt", () => { @@ -286,6 +308,18 @@ describe("AnthropicHandler", () => { expect(model.info.supportsReasoningBudget).toBe(true) }) + it("should handle Claude 4.6 Sonnet model correctly", () => { + const handler = new AnthropicHandler({ + apiKey: "test-api-key", + apiModelId: "claude-sonnet-4-6", + }) + const model = handler.getModel() + expect(model.id).toBe("claude-sonnet-4-6") + expect(model.info.maxTokens).toBe(64000) + expect(model.info.contextWindow).toBe(200000) + expect(model.info.supportsReasoningBudget).toBe(true) + }) + it("should enable 1M context for Claude 4.5 Sonnet when beta flag is set", () => { const handler = new AnthropicHandler({ apiKey: "test-api-key", @@ -297,6 +331,18 @@ describe("AnthropicHandler", () => { expect(model.info.inputPrice).toBe(6.0) expect(model.info.outputPrice).toBe(22.5) }) + + it("should enable 1M context for Claude 4.6 Sonnet when beta flag is set", () => { + const handler = new AnthropicHandler({ + apiKey: "test-api-key", + apiModelId: "claude-sonnet-4-6", + anthropicBeta1MContext: true, + }) + const model = handler.getModel() + expect(model.info.contextWindow).toBe(1000000) + expect(model.info.inputPrice).toBe(6.0) + expect(model.info.outputPrice).toBe(22.5) + }) }) describe("reasoning block filtering", () => { diff --git a/src/api/providers/__tests__/bedrock.spec.ts b/src/api/providers/__tests__/bedrock.spec.ts index 115cb9fb40..4c45a62325 100644 --- a/src/api/providers/__tests__/bedrock.spec.ts +++ b/src/api/providers/__tests__/bedrock.spec.ts @@ -701,6 +701,21 @@ describe("AwsBedrockHandler", () => { expect(model.info.contextWindow).toBe(1_000_000) }) + it("should apply 1M tier pricing when awsBedrock1MContext is true for Claude Sonnet 4.6", () => { + const handler = new AwsBedrockHandler({ + apiModelId: "anthropic.claude-sonnet-4-6-20260114-v1:0", + awsAccessKey: "test", + awsSecretKey: "test", + awsRegion: "us-east-1", + awsBedrock1MContext: true, + }) + + const model = handler.getModel() + expect(model.info.contextWindow).toBe(1_000_000) + expect(model.info.inputPrice).toBe(6.0) + expect(model.info.outputPrice).toBe(22.5) + }) + it("should use default context window when awsBedrock1MContext is false for Claude Sonnet 4", () => { const handler = new AwsBedrockHandler({ apiModelId: BEDROCK_1M_CONTEXT_MODEL_IDS[0], diff --git a/src/api/providers/anthropic.ts b/src/api/providers/anthropic.ts index b2b158f095..1786a105a5 100644 --- a/src/api/providers/anthropic.ts +++ b/src/api/providers/anthropic.ts @@ -64,10 +64,11 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa // Filter out non-Anthropic blocks (reasoning, thoughtSignature, etc.) before sending to the API const sanitizedMessages = filterNonAnthropicBlocks(messages) - // Add 1M context beta flag if enabled for supported models (Claude Sonnet 4/4.5, Opus 4.6) + // Add 1M context beta flag if enabled for supported models (Claude Sonnet 4/4.5/4.6, Opus 4.6) if ( (modelId === "claude-sonnet-4-20250514" || modelId === "claude-sonnet-4-5" || + modelId === "claude-sonnet-4-6" || modelId === "claude-opus-4-6") && this.options.anthropicBeta1MContext ) { @@ -80,6 +81,7 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa } switch (modelId) { + case "claude-sonnet-4-6": case "claude-sonnet-4-5": case "claude-sonnet-4-20250514": case "claude-opus-4-6": @@ -145,6 +147,7 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa // Then check for models that support prompt caching switch (modelId) { + case "claude-sonnet-4-6": case "claude-sonnet-4-5": case "claude-sonnet-4-20250514": case "claude-opus-4-6": @@ -336,7 +339,10 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa // If 1M context beta is enabled for supported models, update the model info if ( - (id === "claude-sonnet-4-20250514" || id === "claude-sonnet-4-5" || id === "claude-opus-4-6") && + (id === "claude-sonnet-4-20250514" || + id === "claude-sonnet-4-5" || + id === "claude-sonnet-4-6" || + id === "claude-opus-4-6") && this.options.anthropicBeta1MContext ) { // Use the tier pricing for 1M context diff --git a/src/api/providers/fetchers/__tests__/openrouter.spec.ts b/src/api/providers/fetchers/__tests__/openrouter.spec.ts index 3bcd27716f..bca54b3078 100644 --- a/src/api/providers/fetchers/__tests__/openrouter.spec.ts +++ b/src/api/providers/fetchers/__tests__/openrouter.spec.ts @@ -266,6 +266,30 @@ describe("OpenRouter API", () => { }) describe("parseOpenRouterModel", () => { + it("sets claude-sonnet-4.6 model to Anthropic max tokens", () => { + const mockModel = { + name: "Claude Sonnet 4.6", + description: "Test model", + context_length: 200000, + max_completion_tokens: 8192, + pricing: { + prompt: "0.000003", + completion: "0.000015", + }, + } + + const result = parseOpenRouterModel({ + id: "anthropic/claude-sonnet-4.6", + model: mockModel, + inputModality: ["text"], + outputModality: ["text"], + maxTokens: 8192, + }) + + expect(result.maxTokens).toBe(64000) + expect(result.contextWindow).toBe(200000) + }) + it("sets horizon-alpha model to 32k max tokens", () => { const mockModel = { name: "Horizon Alpha", diff --git a/src/api/providers/fetchers/openrouter.ts b/src/api/providers/fetchers/openrouter.ts index 9fcf3d49cb..0cf65fb09c 100644 --- a/src/api/providers/fetchers/openrouter.ts +++ b/src/api/providers/fetchers/openrouter.ts @@ -243,6 +243,11 @@ export const parseOpenRouterModel = ({ modelInfo.maxTokens = anthropicModels["claude-3-7-sonnet-20250219:thinking"].maxTokens } + // Set claude-sonnet-4.6 model to use the correct configuration + if (id === "anthropic/claude-sonnet-4.6") { + modelInfo.maxTokens = anthropicModels["claude-sonnet-4-6"].maxTokens + } + // Set claude-opus-4.1 model to use the correct configuration if (id === "anthropic/claude-opus-4.1") { modelInfo.maxTokens = anthropicModels["claude-opus-4-1-20250805"].maxTokens diff --git a/webview-ui/src/components/settings/providers/Anthropic.tsx b/webview-ui/src/components/settings/providers/Anthropic.tsx index 58fa81d6bc..6417e34b8b 100644 --- a/webview-ui/src/components/settings/providers/Anthropic.tsx +++ b/webview-ui/src/components/settings/providers/Anthropic.tsx @@ -26,6 +26,7 @@ export const Anthropic = ({ apiConfiguration, setApiConfigurationField }: Anthro const supports1MContextBeta = selectedModel?.id === "claude-sonnet-4-20250514" || selectedModel?.id === "claude-sonnet-4-5" || + selectedModel?.id === "claude-sonnet-4-6" || selectedModel?.id === "claude-opus-4-6" const handleInputChange = useCallback( diff --git a/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts b/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts index 8925adf5fd..a8dead311f 100644 --- a/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts +++ b/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts @@ -402,6 +402,38 @@ describe("useSelectedModel", () => { }) }) + describe("anthropic provider with 1M context", () => { + beforeEach(() => { + mockUseRouterModels.mockReturnValue({ + data: undefined, + isLoading: false, + isError: false, + } as any) + + mockUseOpenRouterModelProviders.mockReturnValue({ + data: undefined, + isLoading: false, + isError: false, + } as any) + }) + + it("should apply 1M pricing tier for Claude Sonnet 4.6 when enabled", () => { + const apiConfiguration: ProviderSettings = { + apiProvider: "anthropic", + apiModelId: "claude-sonnet-4-6", + anthropicBeta1MContext: true, + } + + const wrapper = createWrapper() + const { result } = renderHook(() => useSelectedModel(apiConfiguration), { wrapper }) + + expect(result.current.id).toBe("claude-sonnet-4-6") + expect(result.current.info?.contextWindow).toBe(1_000_000) + expect(result.current.info?.inputPrice).toBe(6.0) + expect(result.current.info?.outputPrice).toBe(22.5) + }) + }) + describe("bedrock provider with 1M context", () => { beforeEach(() => { mockUseRouterModels.mockReturnValue({ diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index 0ac82b5062..8a6e49e212 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -341,11 +341,14 @@ function getSelectedModel({ // Apply 1M context beta tier pricing for supported Claude 4 models if ( provider === "anthropic" && - (id === "claude-sonnet-4-20250514" || id === "claude-sonnet-4-5" || id === "claude-opus-4-6") && + (id === "claude-sonnet-4-20250514" || + id === "claude-sonnet-4-5" || + id === "claude-sonnet-4-6" || + id === "claude-opus-4-6") && apiConfiguration.anthropicBeta1MContext && baseInfo ) { - // Type assertion since we know claude-sonnet-4-20250514 and claude-sonnet-4-5 have tiers + // Type assertion since supported Claude 4 models include 1M context pricing tiers. const modelWithTiers = baseInfo as typeof baseInfo & { tiers?: Array<{ contextWindow: number