Skip to content

Commit d5a8c4a

Browse files
feat: implement Claude Opus 5 support across all providers (#1010)
Add full Claude Opus 5 support across all provider paths with correct model definitions, adaptive-thinking handling, fetcher overrides, and accurate pricing from Anthropic's published docs. Model definitions (anthropic.ts, bedrock.ts, vertex.ts): - 1M native context window (no beta header required) - Adaptive-thinking binary toggle (supportsReasoningBudget/Binary) - supportsTemperature: false (sampling params return a 400) - Pricing: input $5.0, output $25.0, cacheWrites $6.25, cacheReads $0.50 per million tokens Provider handling: - Add opus-5 to the Bedrock adaptive-thinking guard - Add claude-opus-5 to OpenRouter and Vercel AI Gateway allow-lists - Apply Opus 5 overrides (maxTokens, reasoning budget, temperature) in the openrouter, requesty, and vercel-ai-gateway fetchers Tests: - Add Opus 5 coverage across provider and fetcher test suites Co-authored-by: Naved Merchant <14171946+navedmerchant@users.noreply.github.com>
1 parent 2634d5c commit d5a8c4a

19 files changed

Lines changed: 423 additions & 4 deletions

packages/types/src/providers/anthropic.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,24 @@ export const anthropicModels = {
145145
supportsReasoningBinary: true,
146146
supportsTemperature: false,
147147
},
148+
"claude-opus-5": {
149+
maxTokens: 128_000, // Overridden to 8k if `enableReasoningEffort` is false.
150+
contextWindow: 1_000_000, // 1M context window native (no beta header required)
151+
supportsImages: true,
152+
supportsPromptCache: true,
153+
inputPrice: 5.0, // $5 per million input tokens
154+
outputPrice: 25.0, // $25 per million output tokens
155+
cacheWritesPrice: 6.25, // $6.25 per million tokens
156+
cacheReadsPrice: 0.5, // $0.50 per million tokens
157+
// Opus 5 uses the same adaptive-thinking / binary-toggle convention as
158+
// Opus 4.7+ and Sonnet 5 on the direct Anthropic provider path. Manual
159+
// extended thinking (budget_tokens) is removed and returns a 400, and
160+
// setting sampling parameters (temperature/top_p/top_k) returns a 400.
161+
supportsReasoningBudget: true,
162+
supportsReasoningBinary: true,
163+
supportsTemperature: false,
164+
description: "Claude Opus 5 is Anthropic's most capable model for complex agentic coding and enterprise work.",
165+
},
148166
"claude-fable-5": {
149167
maxTokens: 128_000, // Overridden to 8k if `enableReasoningEffort` is false.
150168
contextWindow: 1_000_000,

packages/types/src/providers/bedrock.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,23 @@ export const bedrockModels = {
235235
},
236236
],
237237
},
238+
"anthropic.claude-opus-5": {
239+
maxTokens: 8192,
240+
contextWindow: 1_000_000, // 1M context window native (no beta header required)
241+
supportsImages: true,
242+
supportsPromptCache: true,
243+
supportsReasoningBudget: true,
244+
supportsReasoningBinary: true,
245+
supportsTemperature: false,
246+
inputPrice: 5.0, // $5 per million input tokens
247+
outputPrice: 25.0, // $25 per million output tokens
248+
cacheWritesPrice: 6.25, // $6.25 per million tokens
249+
cacheReadsPrice: 0.5, // $0.50 per million tokens
250+
minTokensPerCachePoint: 1024,
251+
maxCachePoints: 4,
252+
cachableFields: ["system", "messages", "tools"],
253+
description: "Claude Opus 5 is Anthropic's most capable model for complex agentic coding and enterprise work.",
254+
},
238255
"anthropic.claude-fable-5": {
239256
maxTokens: 8192,
240257
contextWindow: 1_000_000,
@@ -625,6 +642,7 @@ export const BEDROCK_1M_CONTEXT_MODEL_IDS = [
625642
// - Claude Opus 4.5
626643
// - Claude Opus 4.6
627644
// - Claude Opus 4.7
645+
// - Claude Opus 5
628646
// - Claude Fable 5 (cross-region inference only — can only be used through an inference profile)
629647
export const BEDROCK_GLOBAL_INFERENCE_MODEL_IDS = [
630648
"anthropic.claude-sonnet-4-20250514-v1:0",
@@ -636,6 +654,7 @@ export const BEDROCK_GLOBAL_INFERENCE_MODEL_IDS = [
636654
"anthropic.claude-opus-4-6-v1",
637655
"anthropic.claude-opus-4-7",
638656
"anthropic.claude-opus-4-8",
657+
"anthropic.claude-opus-5",
639658
"anthropic.claude-fable-5",
640659
] as const
641660

packages/types/src/providers/openrouter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export const OPEN_ROUTER_PROMPT_CACHING_MODELS = new Set([
4444
"anthropic/claude-opus-4.1",
4545
"anthropic/claude-opus-4.5",
4646
"anthropic/claude-opus-4.6",
47+
"anthropic/claude-opus-5",
4748
"anthropic/claude-fable-5",
4849
"anthropic/claude-haiku-4.5",
4950
"google/gemini-2.5-flash-preview",
@@ -76,6 +77,7 @@ export const OPEN_ROUTER_REASONING_BUDGET_MODELS = new Set([
7677
"anthropic/claude-opus-4.1",
7778
"anthropic/claude-opus-4.5",
7879
"anthropic/claude-opus-4.6",
80+
"anthropic/claude-opus-5",
7981
"anthropic/claude-fable-5",
8082
"anthropic/claude-sonnet-4",
8183
"anthropic/claude-sonnet-4.5",

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const VERCEL_AI_GATEWAY_PROMPT_CACHING_MODELS = new Set([
1313
"anthropic/claude-opus-4.1",
1414
"anthropic/claude-opus-4.5",
1515
"anthropic/claude-opus-4.6",
16+
"anthropic/claude-opus-5",
1617
"anthropic/claude-fable-5",
1718
"anthropic/claude-sonnet-4",
1819
"anthropic/claude-sonnet-4.6",
@@ -58,6 +59,7 @@ export const VERCEL_AI_GATEWAY_VISION_AND_TOOLS_MODELS = new Set([
5859
"anthropic/claude-opus-4.1",
5960
"anthropic/claude-opus-4.5",
6061
"anthropic/claude-opus-4.6",
62+
"anthropic/claude-opus-5",
6163
"anthropic/claude-fable-5",
6264
"anthropic/claude-sonnet-4",
6365
"anthropic/claude-sonnet-4.5",

packages/types/src/providers/vertex.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,20 @@ export const vertexModels = {
462462
},
463463
],
464464
},
465+
"claude-opus-5": {
466+
maxTokens: 8192,
467+
contextWindow: 1_000_000, // 1M context window native (no beta header required)
468+
supportsImages: true,
469+
supportsPromptCache: true,
470+
inputPrice: 5.0, // $5 per million input tokens
471+
outputPrice: 25.0, // $25 per million output tokens
472+
cacheWritesPrice: 6.25, // $6.25 per million tokens
473+
cacheReadsPrice: 0.5, // $0.50 per million tokens
474+
supportsReasoningBudget: true,
475+
supportsReasoningBinary: true,
476+
supportsTemperature: false,
477+
description: "Claude Opus 5 is Anthropic's most capable model for complex agentic coding and enterprise work.",
478+
},
465479
"claude-fable-5": {
466480
maxTokens: 8192,
467481
contextWindow: 1_000_000,

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

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

1088+
it("should return Claude Opus 5 model info", () => {
1089+
const handler = new AnthropicVertexHandler({
1090+
apiModelId: "claude-opus-5",
1091+
vertexProjectId: "test-project",
1092+
vertexRegion: "us-central1",
1093+
})
1094+
1095+
const model = handler.getModel()
1096+
expect(model.id).toBe("claude-opus-5")
1097+
expect(model.info.maxTokens).toBe(8192)
1098+
expect(model.info.contextWindow).toBe(1_000_000)
1099+
expect(model.info.supportsReasoningBinary).toBe(true)
1100+
expect(model.info.supportsReasoningBudget).toBe(true)
1101+
expect(model.info.supportsPromptCache).toBe(true)
1102+
expect(model.info.supportsTemperature).toBe(false)
1103+
})
1104+
10881105
it("should not enable 1M context when flag is disabled", () => {
10891106
const handler = new AnthropicVertexHandler({
10901107
apiModelId: VERTEX_1M_CONTEXT_MODEL_IDS[0],
@@ -1392,6 +1409,35 @@ describe("VertexHandler", () => {
13921409
expect(request.thinking).not.toHaveProperty("budget_tokens")
13931410
expect(request.temperature).toBeUndefined()
13941411
})
1412+
1413+
it("should use adaptive thinking for Claude Opus 5", async () => {
1414+
const opusHandler = new AnthropicVertexHandler({
1415+
apiModelId: "claude-opus-5",
1416+
vertexProjectId: "test-project",
1417+
vertexRegion: "us-central1",
1418+
enableReasoningEffort: true,
1419+
})
1420+
1421+
const mockCreate = vitest.fn().mockImplementation(async () => ({
1422+
async *[Symbol.asyncIterator]() {
1423+
yield { type: "message_start", message: { usage: { input_tokens: 10, output_tokens: 5 } } }
1424+
},
1425+
}))
1426+
;(opusHandler["client"].messages as any).create = mockCreate
1427+
1428+
await opusHandler.createMessage("You are a helpful assistant", [{ role: "user", content: "Hello" }]).next()
1429+
1430+
expect(mockCreate).toHaveBeenCalledWith(
1431+
expect.objectContaining({
1432+
thinking: { type: "adaptive" },
1433+
}),
1434+
undefined,
1435+
)
1436+
1437+
const request = mockCreate.mock.calls[0][0]
1438+
expect(request.thinking).not.toHaveProperty("budget_tokens")
1439+
expect(request.temperature).toBeUndefined()
1440+
})
13951441
})
13961442

13971443
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
@@ -456,6 +456,33 @@ describe("AnthropicHandler", () => {
456456
expect(requestOptions?.headers?.["anthropic-beta"]).toContain("prompt-caching-2024-07-31")
457457
})
458458

459+
it("should use adaptive thinking for Claude Opus 5 when reasoning is enabled", async () => {
460+
const opusHandler = new AnthropicHandler({
461+
apiKey: "test-api-key",
462+
apiModelId: "claude-opus-5",
463+
enableReasoningEffort: true,
464+
modelMaxTokens: 32768,
465+
})
466+
467+
const stream = opusHandler.createMessage(systemPrompt, [
468+
{
469+
role: "user",
470+
content: [{ type: "text" as const, text: "Hello" }],
471+
},
472+
])
473+
474+
for await (const _chunk of stream) {
475+
// Consume stream
476+
}
477+
478+
const requestBody = mockCreate.mock.calls[mockCreate.mock.calls.length - 1]?.[0]
479+
const requestOptions = mockCreate.mock.calls[mockCreate.mock.calls.length - 1]?.[1]
480+
expect(requestBody?.thinking).toEqual({ type: "adaptive" })
481+
expect(requestBody?.temperature).toBeUndefined()
482+
expect(requestBody?.max_tokens).toBe(32768)
483+
expect(requestOptions?.headers?.["anthropic-beta"]).toContain("prompt-caching-2024-07-31")
484+
})
485+
459486
it("should send the custom model ID as-is and use adaptive thinking for a custom Sonnet-5-family model", async () => {
460487
const customHandler = new AnthropicHandler({
461488
apiKey: "test-api-key",
@@ -656,6 +683,23 @@ describe("AnthropicHandler", () => {
656683
expect(model.reasoningBudget).toBeUndefined()
657684
})
658685

686+
it("should handle Claude Opus 5 model correctly", () => {
687+
const handler = new AnthropicHandler({
688+
apiKey: "test-api-key",
689+
apiModelId: "claude-opus-5",
690+
})
691+
const model = handler.getModel()
692+
expect(model.id).toBe("claude-opus-5")
693+
expect(model.info.maxTokens).toBe(128000)
694+
expect(model.info.contextWindow).toBe(1000000)
695+
expect(model.maxTokens).toBe(8192)
696+
expect(model.info.supportsReasoningBinary).toBe(true)
697+
expect(model.info.supportsReasoningBudget).toBe(true)
698+
expect(model.info.supportsPromptCache).toBe(true)
699+
expect(model.info.supportsTemperature).toBe(false)
700+
expect(model.reasoningBudget).toBeUndefined()
701+
})
702+
659703
it("should enable 1M context for Claude 4.5 Sonnet when beta flag is set", () => {
660704
const handler = new AnthropicHandler({
661705
apiKey: "test-api-key",

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

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,37 @@ describe("AwsBedrockHandler", () => {
755755
const model = handler.getModel()
756756
expect(model.id).toBe("global.anthropic.claude-sonnet-5")
757757
})
758+
759+
it("should return Claude Opus 5 model info", () => {
760+
const handler = new AwsBedrockHandler({
761+
apiModelId: "anthropic.claude-opus-5",
762+
awsAccessKey: "test",
763+
awsSecretKey: "test",
764+
awsRegion: "us-east-1",
765+
})
766+
767+
const model = handler.getModel()
768+
expect(model.id).toBe("anthropic.claude-opus-5")
769+
expect(model.info.contextWindow).toBe(1_000_000)
770+
expect(model.info.supportsReasoningBinary).toBe(true)
771+
expect(model.info.supportsReasoningBudget).toBe(true)
772+
expect(model.info.supportsPromptCache).toBe(true)
773+
expect(model.info.supportsTemperature).toBe(false)
774+
expect(model.maxTokens).toBe(8192)
775+
})
776+
777+
it("should apply global inference prefix for Claude Opus 5 when awsUseGlobalInference is true", () => {
778+
const handler = new AwsBedrockHandler({
779+
apiModelId: "anthropic.claude-opus-5",
780+
awsAccessKey: "test",
781+
awsSecretKey: "test",
782+
awsRegion: "us-east-1",
783+
awsUseGlobalInference: true,
784+
})
785+
786+
const model = handler.getModel()
787+
expect(model.id).toBe("global.anthropic.claude-opus-5")
788+
})
758789
})
759790

760791
describe("1M context beta feature", () => {
@@ -1487,6 +1518,36 @@ describe("AwsBedrockHandler", () => {
14871518
expect(commandArg.inferenceConfig?.temperature).toBeUndefined()
14881519
})
14891520

1521+
it("should send adaptive thinking with effort xhigh for Claude Opus 5 when reasoning is enabled", async () => {
1522+
// End-to-end regression guard for the Opus 5 handler branch. The
1523+
// isAdaptiveThinkingModel predicate is unit-covered, but a regression in
1524+
// the createMessage adaptive-thinking branch for this specific model
1525+
// wouldn't be caught without a request-level test.
1526+
const opus5Handler = new AwsBedrockHandler({
1527+
apiModelId: "anthropic.claude-opus-5",
1528+
awsAccessKey: "test-access-key",
1529+
awsSecretKey: "test-secret-key",
1530+
awsRegion: "us-east-1",
1531+
enableReasoningEffort: true,
1532+
})
1533+
1534+
const generator = opus5Handler.createMessage("System prompt", messages)
1535+
await generator.next()
1536+
1537+
expect(mockConverseStreamCommand).toHaveBeenCalled()
1538+
const commandArg = mockConverseStreamCommand.mock.calls[0][0] as any
1539+
1540+
// Opus 5 uses the same adaptive-thinking contract as Opus 4.7/4.8 —
1541+
// budget_tokens causes a 400, so thinking.type is "adaptive" with effort.
1542+
expect(commandArg.additionalModelRequestFields?.thinking).toEqual({
1543+
type: "adaptive",
1544+
display: "summarized",
1545+
})
1546+
expect(commandArg.additionalModelRequestFields?.output_config).toEqual({ effort: "xhigh" })
1547+
// Opus 5 rejects sampling parameters: temperature must be omitted entirely.
1548+
expect(commandArg.inferenceConfig?.temperature).toBeUndefined()
1549+
})
1550+
14901551
it("should omit thinking and temperature for Claude Opus 4.8 when reasoning is disabled", async () => {
14911552
const opus48Handler = new AwsBedrockHandler({
14921553
apiModelId: "anthropic.claude-opus-4-8",
@@ -1620,6 +1681,7 @@ describe("AwsBedrockHandler", () => {
16201681
expect(isAdaptiveThinkingModel("anthropic.claude-opus-4-8")).toBe(true)
16211682
expect(isAdaptiveThinkingModel("anthropic.claude-fable-5")).toBe(true)
16221683
expect(isAdaptiveThinkingModel("anthropic.claude-sonnet-5")).toBe(true)
1684+
expect(isAdaptiveThinkingModel("anthropic.claude-opus-5")).toBe(true)
16231685
// Future-proof Sonnet patterns — guarded even before a registry entry exists.
16241686
expect(isAdaptiveThinkingModel("anthropic.claude-sonnet-4-7")).toBe(true)
16251687
expect(isAdaptiveThinkingModel("anthropic.claude-sonnet-4-8")).toBe(true)
@@ -1629,12 +1691,14 @@ describe("AwsBedrockHandler", () => {
16291691
expect(isAdaptiveThinkingModel("us.anthropic.claude-opus-4-8")).toBe(true)
16301692
expect(isAdaptiveThinkingModel("global.anthropic.claude-fable-5")).toBe(true)
16311693
expect(isAdaptiveThinkingModel("global.anthropic.claude-sonnet-5")).toBe(true)
1694+
expect(isAdaptiveThinkingModel("global.anthropic.claude-opus-5")).toBe(true)
16321695
expect(isAdaptiveThinkingModel("eu.anthropic.claude-sonnet-4-7")).toBe(true)
16331696
expect(isAdaptiveThinkingModel("global.anthropic.claude-opus-4-8")).toBe(true)
16341697
})
16351698

16361699
it("returns false for older / non-adaptive models", () => {
16371700
expect(isAdaptiveThinkingModel("anthropic.claude-opus-4-6-v1")).toBe(false)
1701+
expect(isAdaptiveThinkingModel("anthropic.claude-opus-4-5-20251101-v1:0")).toBe(false)
16381702
expect(isAdaptiveThinkingModel("anthropic.claude-sonnet-4-6")).toBe(false)
16391703
expect(isAdaptiveThinkingModel("anthropic.claude-3-5-sonnet-20241022-v2:0")).toBe(false)
16401704
expect(isAdaptiveThinkingModel("amazon.nova-lite-v1:0")).toBe(false)

0 commit comments

Comments
 (0)