Skip to content

Commit dd2f18a

Browse files
author
CodeKing
committed
feat: implement Claude Sonnet 5 support (Zoo-Code-Org#778) [imported from upstream f845f2a]
1 parent 5ef31ba commit dd2f18a

19 files changed

Lines changed: 430 additions & 8 deletions

packages/types/src/providers/anthropic.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,25 @@ export const anthropicModels = {
2828
},
2929
],
3030
},
31+
"claude-sonnet-5": {
32+
maxTokens: 128_000, // Overridden to 8k if `enableReasoningEffort` is false.
33+
contextWindow: 1_000_000, // 1M context window native (no beta header required)
34+
supportsImages: true,
35+
supportsPromptCache: true,
36+
inputPrice: 2.0, // $2 per million input tokens (introductory pricing through Aug 31, 2026)
37+
outputPrice: 10.0, // $10 per million output tokens (introductory pricing through Aug 31, 2026)
38+
cacheWritesPrice: 2.5, // $2.50 per million tokens (introductory pricing through Aug 31, 2026)
39+
cacheReadsPrice: 0.2, // $0.20 per million tokens (introductory pricing through Aug 31, 2026)
40+
// Sonnet 5 uses the same adaptive-thinking / binary-toggle convention as
41+
// Opus 4.7+ and Fable 5 on the direct Anthropic provider path. Manual
42+
// extended thinking (budget_tokens) is removed and returns a 400, and
43+
// setting sampling parameters (temperature/top_p/top_k) returns a 400.
44+
supportsReasoningBudget: true,
45+
supportsReasoningBinary: true,
46+
supportsTemperature: false,
47+
description:
48+
"Claude Sonnet 5 is the best combination of speed and intelligence, optimized for coding, tool use, and agentic workflows.",
49+
},
3150
"claude-sonnet-4-5": {
3251
maxTokens: 64_000, // Overridden to 8k if `enableReasoningEffort` is false.
3352
contextWindow: 200_000, // Default 200K, extendable to 1M with beta flag 'context-1m-2025-08-07'

packages/types/src/providers/bedrock.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,24 @@ export const bedrockModels = {
5151
},
5252
],
5353
},
54+
"anthropic.claude-sonnet-5": {
55+
maxTokens: 8192,
56+
contextWindow: 1_000_000, // 1M context window native (no beta header required)
57+
supportsImages: true,
58+
supportsPromptCache: true,
59+
supportsReasoningBudget: true,
60+
supportsReasoningBinary: true,
61+
supportsTemperature: false,
62+
inputPrice: 2.0, // $2 per million input tokens (introductory pricing through Aug 31, 2026)
63+
outputPrice: 10.0, // $10 per million output tokens (introductory pricing through Aug 31, 2026)
64+
cacheWritesPrice: 2.5, // $2.50 per million tokens (introductory pricing through Aug 31, 2026)
65+
cacheReadsPrice: 0.2, // $0.20 per million tokens (introductory pricing through Aug 31, 2026)
66+
minTokensPerCachePoint: 1024,
67+
maxCachePoints: 4,
68+
cachableFields: ["system", "messages", "tools"],
69+
description:
70+
"Claude Sonnet 5 is the best combination of speed and intelligence, optimized for coding, tool use, and agentic workflows.",
71+
},
5472
"amazon.nova-pro-v1:0": {
5573
maxTokens: 5000,
5674
contextWindow: 300_000,
@@ -602,6 +620,7 @@ export const BEDROCK_1M_CONTEXT_MODEL_IDS = [
602620
// - Claude Sonnet 4
603621
// - Claude Sonnet 4.5
604622
// - Claude Sonnet 4.6
623+
// - Claude Sonnet 5
605624
// - Claude Haiku 4.5
606625
// - Claude Opus 4.5
607626
// - Claude Opus 4.6
@@ -611,6 +630,7 @@ export const BEDROCK_GLOBAL_INFERENCE_MODEL_IDS = [
611630
"anthropic.claude-sonnet-4-20250514-v1:0",
612631
"anthropic.claude-sonnet-4-5-20250929-v1:0",
613632
"anthropic.claude-sonnet-4-6",
633+
"anthropic.claude-sonnet-5",
614634
"anthropic.claude-haiku-4-5-20251001-v1:0",
615635
"anthropic.claude-opus-4-5-20251101-v1:0",
616636
"anthropic.claude-opus-4-6-v1",

packages/types/src/providers/openrouter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export const OPEN_ROUTER_PROMPT_CACHING_MODELS = new Set([
3939
"anthropic/claude-sonnet-4",
4040
"anthropic/claude-sonnet-4.5",
4141
"anthropic/claude-sonnet-4.6",
42+
"anthropic/claude-sonnet-5",
4243
"anthropic/claude-opus-4",
4344
"anthropic/claude-opus-4.1",
4445
"anthropic/claude-opus-4.5",
@@ -79,6 +80,7 @@ export const OPEN_ROUTER_REASONING_BUDGET_MODELS = new Set([
7980
"anthropic/claude-sonnet-4",
8081
"anthropic/claude-sonnet-4.5",
8182
"anthropic/claude-sonnet-4.6",
83+
"anthropic/claude-sonnet-5",
8284
"anthropic/claude-haiku-4.5",
8385
"google/gemini-2.5-pro-preview",
8486
"google/gemini-2.5-pro",

packages/types/src/providers/vercel-ai-gateway.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const VERCEL_AI_GATEWAY_PROMPT_CACHING_MODELS = new Set([
1616
"anthropic/claude-fable-5",
1717
"anthropic/claude-sonnet-4",
1818
"anthropic/claude-sonnet-4.6",
19+
"anthropic/claude-sonnet-5",
1920
"openai/gpt-4.1",
2021
"openai/gpt-4.1-mini",
2122
"openai/gpt-4.1-nano",
@@ -59,6 +60,7 @@ export const VERCEL_AI_GATEWAY_VISION_AND_TOOLS_MODELS = new Set([
5960
"anthropic/claude-fable-5",
6061
"anthropic/claude-sonnet-4",
6162
"anthropic/claude-sonnet-4.6",
63+
"anthropic/claude-sonnet-5",
6264
"google/gemini-1.5-flash",
6365
"google/gemini-1.5-pro",
6466
"google/gemini-2.0-flash",

packages/types/src/providers/vertex.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,21 @@ export const vertexModels = {
355355
},
356356
],
357357
},
358+
"claude-sonnet-5": {
359+
maxTokens: 8192,
360+
contextWindow: 1_000_000, // 1M context window native (no beta header required)
361+
supportsImages: true,
362+
supportsPromptCache: true,
363+
inputPrice: 2.0, // $2 per million input tokens (introductory pricing through Aug 31, 2026)
364+
outputPrice: 10.0, // $10 per million output tokens (introductory pricing through Aug 31, 2026)
365+
cacheWritesPrice: 2.5, // $2.50 per million tokens (introductory pricing through Aug 31, 2026)
366+
cacheReadsPrice: 0.2, // $0.20 per million tokens (introductory pricing through Aug 31, 2026)
367+
supportsReasoningBudget: true,
368+
supportsReasoningBinary: true,
369+
supportsTemperature: false,
370+
description:
371+
"Claude Sonnet 5 is the best combination of speed and intelligence, optimized for coding, tool use, and agentic workflows.",
372+
},
358373
"claude-haiku-4-5@20251001": {
359374
maxTokens: 8192,
360375
contextWindow: 200_000,

src/api/providers/__tests__/anthropic-vertex.spec.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,6 +1033,23 @@ describe("VertexHandler", () => {
10331033
expect(model.info.supportsTemperature).toBe(false)
10341034
})
10351035

1036+
it("should return Claude Sonnet 5 model info", () => {
1037+
const handler = new AnthropicVertexHandler({
1038+
apiModelId: "claude-sonnet-5",
1039+
vertexProjectId: "test-project",
1040+
vertexRegion: "us-central1",
1041+
})
1042+
1043+
const model = handler.getModel()
1044+
expect(model.id).toBe("claude-sonnet-5")
1045+
expect(model.info.maxTokens).toBe(8192)
1046+
expect(model.info.contextWindow).toBe(1_000_000)
1047+
expect(model.info.supportsReasoningBinary).toBe(true)
1048+
expect(model.info.supportsReasoningBudget).toBe(true)
1049+
expect(model.info.supportsPromptCache).toBe(true)
1050+
expect(model.info.supportsTemperature).toBe(false)
1051+
})
1052+
10361053
it("should not enable 1M context when flag is disabled", () => {
10371054
const handler = new AnthropicVertexHandler({
10381055
apiModelId: VERTEX_1M_CONTEXT_MODEL_IDS[0],
@@ -1309,6 +1326,35 @@ describe("VertexHandler", () => {
13091326
expect(request.thinking).not.toHaveProperty("budget_tokens")
13101327
expect(request.temperature).toBeUndefined()
13111328
})
1329+
1330+
it("should use adaptive thinking for Claude Sonnet 5", async () => {
1331+
const sonnetHandler = new AnthropicVertexHandler({
1332+
apiModelId: "claude-sonnet-5",
1333+
vertexProjectId: "test-project",
1334+
vertexRegion: "us-central1",
1335+
enableReasoningEffort: true,
1336+
})
1337+
1338+
const mockCreate = vitest.fn().mockImplementation(async () => ({
1339+
async *[Symbol.asyncIterator]() {
1340+
yield { type: "message_start", message: { usage: { input_tokens: 10, output_tokens: 5 } } }
1341+
},
1342+
}))
1343+
;(sonnetHandler["client"].messages as any).create = mockCreate
1344+
1345+
await sonnetHandler.createMessage("You are a helpful assistant", [{ role: "user", content: "Hello" }]).next()
1346+
1347+
expect(mockCreate).toHaveBeenCalledWith(
1348+
expect.objectContaining({
1349+
thinking: { type: "adaptive" },
1350+
}),
1351+
undefined,
1352+
)
1353+
1354+
const request = mockCreate.mock.calls[0][0]
1355+
expect(request.thinking).not.toHaveProperty("budget_tokens")
1356+
expect(request.temperature).toBeUndefined()
1357+
})
13121358
})
13131359

13141360
describe("native tool calling", () => {

src/api/providers/__tests__/anthropic.spec.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,33 @@ describe("AnthropicHandler", () => {
428428
expect(requestBody?.max_tokens).toBe(32768)
429429
expect(requestOptions?.headers?.["anthropic-beta"]).toContain("prompt-caching-2024-07-31")
430430
})
431+
432+
it("should use adaptive thinking for Claude Sonnet 5 when reasoning is enabled", async () => {
433+
const sonnetHandler = new AnthropicHandler({
434+
apiKey: "test-api-key",
435+
apiModelId: "claude-sonnet-5",
436+
enableReasoningEffort: true,
437+
modelMaxTokens: 32768,
438+
})
439+
440+
const stream = sonnetHandler.createMessage(systemPrompt, [
441+
{
442+
role: "user",
443+
content: [{ type: "text" as const, text: "Hello" }],
444+
},
445+
])
446+
447+
for await (const _chunk of stream) {
448+
// Consume stream
449+
}
450+
451+
const requestBody = mockCreate.mock.calls[mockCreate.mock.calls.length - 1]?.[0]
452+
const requestOptions = mockCreate.mock.calls[mockCreate.mock.calls.length - 1]?.[1]
453+
expect(requestBody?.thinking).toEqual({ type: "adaptive" })
454+
expect(requestBody?.temperature).toBeUndefined()
455+
expect(requestBody?.max_tokens).toBe(32768)
456+
expect(requestOptions?.headers?.["anthropic-beta"]).toContain("prompt-caching-2024-07-31")
457+
})
431458
})
432459

433460
describe("completePrompt", () => {
@@ -589,6 +616,23 @@ describe("AnthropicHandler", () => {
589616
expect(model.reasoningBudget).toBeUndefined()
590617
})
591618

619+
it("should handle Claude Sonnet 5 model correctly", () => {
620+
const handler = new AnthropicHandler({
621+
apiKey: "test-api-key",
622+
apiModelId: "claude-sonnet-5",
623+
})
624+
const model = handler.getModel()
625+
expect(model.id).toBe("claude-sonnet-5")
626+
expect(model.info.maxTokens).toBe(128000)
627+
expect(model.info.contextWindow).toBe(1000000)
628+
expect(model.maxTokens).toBe(8192)
629+
expect(model.info.supportsReasoningBinary).toBe(true)
630+
expect(model.info.supportsReasoningBudget).toBe(true)
631+
expect(model.info.supportsPromptCache).toBe(true)
632+
expect(model.info.supportsTemperature).toBe(false)
633+
expect(model.reasoningBudget).toBeUndefined()
634+
})
635+
592636
it("should enable 1M context for Claude 4.5 Sonnet when beta flag is set", () => {
593637
const handler = new AnthropicHandler({
594638
apiKey: "test-api-key",

src/api/providers/__tests__/bedrock.spec.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,37 @@ describe("AwsBedrockHandler", () => {
724724
const model = handler.getModel()
725725
expect(model.id).toBe("global.anthropic.claude-fable-5")
726726
})
727+
728+
it("should return Claude Sonnet 5 model info", () => {
729+
const handler = new AwsBedrockHandler({
730+
apiModelId: "anthropic.claude-sonnet-5",
731+
awsAccessKey: "test",
732+
awsSecretKey: "test",
733+
awsRegion: "us-east-1",
734+
})
735+
736+
const model = handler.getModel()
737+
expect(model.id).toBe("anthropic.claude-sonnet-5")
738+
expect(model.info.contextWindow).toBe(1_000_000)
739+
expect(model.info.supportsReasoningBinary).toBe(true)
740+
expect(model.info.supportsReasoningBudget).toBe(true)
741+
expect(model.info.supportsPromptCache).toBe(true)
742+
expect(model.info.supportsTemperature).toBe(false)
743+
expect(model.maxTokens).toBe(8192)
744+
})
745+
746+
it("should apply global inference prefix for Claude Sonnet 5 when awsUseGlobalInference is true", () => {
747+
const handler = new AwsBedrockHandler({
748+
apiModelId: "anthropic.claude-sonnet-5",
749+
awsAccessKey: "test",
750+
awsSecretKey: "test",
751+
awsRegion: "us-east-1",
752+
awsUseGlobalInference: true,
753+
})
754+
755+
const model = handler.getModel()
756+
expect(model.id).toBe("global.anthropic.claude-sonnet-5")
757+
})
727758
})
728759

729760
describe("1M context beta feature", () => {
@@ -1426,6 +1457,36 @@ describe("AwsBedrockHandler", () => {
14261457
expect(commandArg.inferenceConfig?.temperature).toBeUndefined()
14271458
})
14281459

1460+
it("should send adaptive thinking with effort xhigh for Claude Sonnet 5 when reasoning is enabled", async () => {
1461+
// End-to-end regression guard for the Sonnet 5 handler branch. The
1462+
// isAdaptiveThinkingModel predicate is unit-covered, but a regression in
1463+
// the createMessage adaptive-thinking branch for this specific model
1464+
// wouldn't be caught without a request-level test (see review feedback).
1465+
const sonnet5Handler = new AwsBedrockHandler({
1466+
apiModelId: "anthropic.claude-sonnet-5",
1467+
awsAccessKey: "test-access-key",
1468+
awsSecretKey: "test-secret-key",
1469+
awsRegion: "us-east-1",
1470+
enableReasoningEffort: true,
1471+
})
1472+
1473+
const generator = sonnet5Handler.createMessage("System prompt", messages)
1474+
await generator.next()
1475+
1476+
expect(mockConverseStreamCommand).toHaveBeenCalled()
1477+
const commandArg = mockConverseStreamCommand.mock.calls[0][0] as any
1478+
1479+
// Sonnet 5 uses the same adaptive-thinking contract as Opus 4.7/4.8 —
1480+
// budget_tokens causes a 400, so thinking.type is "adaptive" with effort.
1481+
expect(commandArg.additionalModelRequestFields?.thinking).toEqual({
1482+
type: "adaptive",
1483+
display: "summarized",
1484+
})
1485+
expect(commandArg.additionalModelRequestFields?.output_config).toEqual({ effort: "xhigh" })
1486+
// Sonnet 5 rejects sampling parameters: temperature must be omitted entirely.
1487+
expect(commandArg.inferenceConfig?.temperature).toBeUndefined()
1488+
})
1489+
14291490
it("should omit thinking and temperature for Claude Opus 4.8 when reasoning is disabled", async () => {
14301491
const opus48Handler = new AwsBedrockHandler({
14311492
apiModelId: "anthropic.claude-opus-4-8",
@@ -1558,6 +1619,7 @@ describe("AwsBedrockHandler", () => {
15581619
expect(isAdaptiveThinkingModel("anthropic.claude-opus-4-7")).toBe(true)
15591620
expect(isAdaptiveThinkingModel("anthropic.claude-opus-4-8")).toBe(true)
15601621
expect(isAdaptiveThinkingModel("anthropic.claude-fable-5")).toBe(true)
1622+
expect(isAdaptiveThinkingModel("anthropic.claude-sonnet-5")).toBe(true)
15611623
// Future-proof Sonnet patterns — guarded even before a registry entry exists.
15621624
expect(isAdaptiveThinkingModel("anthropic.claude-sonnet-4-7")).toBe(true)
15631625
expect(isAdaptiveThinkingModel("anthropic.claude-sonnet-4-8")).toBe(true)
@@ -1566,6 +1628,7 @@ describe("AwsBedrockHandler", () => {
15661628
it("returns true when the id carries a cross-region or global prefix", () => {
15671629
expect(isAdaptiveThinkingModel("us.anthropic.claude-opus-4-8")).toBe(true)
15681630
expect(isAdaptiveThinkingModel("global.anthropic.claude-fable-5")).toBe(true)
1631+
expect(isAdaptiveThinkingModel("global.anthropic.claude-sonnet-5")).toBe(true)
15691632
expect(isAdaptiveThinkingModel("eu.anthropic.claude-sonnet-4-7")).toBe(true)
15701633
expect(isAdaptiveThinkingModel("global.anthropic.claude-opus-4-8")).toBe(true)
15711634
})

0 commit comments

Comments
 (0)