mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
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 <hrudolph@gmail.com>
This commit is contained in:
parent
dc243e4cf9
commit
90e2451ad1
14 changed files with 225 additions and 4 deletions
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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", () => {
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue