Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions packages/types/src/providers/anthropic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,24 @@ export const anthropicModels = {
supportsReasoningBinary: true,
supportsTemperature: false,
},
"claude-opus-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: 5.0, // $5 per million input tokens
outputPrice: 25.0, // $25 per million output tokens
cacheWritesPrice: 6.25, // $6.25 per million tokens
cacheReadsPrice: 0.5, // $0.50 per million tokens
// Opus 5 uses the same adaptive-thinking / binary-toggle convention as
// Opus 4.7+ and Sonnet 5 on the direct Anthropic provider path. Manual
// extended thinking (budget_tokens) is removed and returns a 400, and
// setting sampling parameters (temperature/top_p/top_k) returns a 400.
supportsReasoningBudget: true,
supportsReasoningBinary: true,
supportsTemperature: false,
description: "Claude Opus 5 is Anthropic's most capable model for complex agentic coding and enterprise work.",
},
"claude-fable-5": {
maxTokens: 128_000, // Overridden to 8k if `enableReasoningEffort` is false.
contextWindow: 1_000_000,
Expand Down
19 changes: 19 additions & 0 deletions packages/types/src/providers/bedrock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,23 @@ export const bedrockModels = {
},
],
},
"anthropic.claude-opus-5": {
maxTokens: 8192,
contextWindow: 1_000_000, // 1M context window native (no beta header required)
supportsImages: true,
supportsPromptCache: true,
supportsReasoningBudget: true,
supportsReasoningBinary: true,
supportsTemperature: false,
inputPrice: 5.0, // $5 per million input tokens
outputPrice: 25.0, // $25 per million output tokens
cacheWritesPrice: 6.25, // $6.25 per million tokens
cacheReadsPrice: 0.5, // $0.50 per million tokens
minTokensPerCachePoint: 1024,
maxCachePoints: 4,
cachableFields: ["system", "messages", "tools"],
description: "Claude Opus 5 is Anthropic's most capable model for complex agentic coding and enterprise work.",
},
"anthropic.claude-fable-5": {
maxTokens: 8192,
contextWindow: 1_000_000,
Expand Down Expand Up @@ -625,6 +642,7 @@ export const BEDROCK_1M_CONTEXT_MODEL_IDS = [
// - Claude Opus 4.5
// - Claude Opus 4.6
// - Claude Opus 4.7
// - Claude Opus 5
// - Claude Fable 5 (cross-region inference only — can only be used through an inference profile)
export const BEDROCK_GLOBAL_INFERENCE_MODEL_IDS = [
"anthropic.claude-sonnet-4-20250514-v1:0",
Expand All @@ -636,6 +654,7 @@ export const BEDROCK_GLOBAL_INFERENCE_MODEL_IDS = [
"anthropic.claude-opus-4-6-v1",
"anthropic.claude-opus-4-7",
"anthropic.claude-opus-4-8",
"anthropic.claude-opus-5",
"anthropic.claude-fable-5",
] as const

Expand Down
2 changes: 2 additions & 0 deletions packages/types/src/providers/openrouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const OPEN_ROUTER_PROMPT_CACHING_MODELS = new Set([
"anthropic/claude-opus-4.1",
"anthropic/claude-opus-4.5",
"anthropic/claude-opus-4.6",
"anthropic/claude-opus-5",
"anthropic/claude-fable-5",
"anthropic/claude-haiku-4.5",
"google/gemini-2.5-flash-preview",
Expand Down Expand Up @@ -76,6 +77,7 @@ export const OPEN_ROUTER_REASONING_BUDGET_MODELS = new Set([
"anthropic/claude-opus-4.1",
"anthropic/claude-opus-4.5",
"anthropic/claude-opus-4.6",
"anthropic/claude-opus-5",
"anthropic/claude-fable-5",
"anthropic/claude-sonnet-4",
"anthropic/claude-sonnet-4.5",
Expand Down
2 changes: 2 additions & 0 deletions packages/types/src/providers/vercel-ai-gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const VERCEL_AI_GATEWAY_PROMPT_CACHING_MODELS = new Set([
"anthropic/claude-opus-4.1",
"anthropic/claude-opus-4.5",
"anthropic/claude-opus-4.6",
"anthropic/claude-opus-5",
"anthropic/claude-fable-5",
"anthropic/claude-sonnet-4",
"anthropic/claude-sonnet-4.6",
Expand Down Expand Up @@ -58,6 +59,7 @@ export const VERCEL_AI_GATEWAY_VISION_AND_TOOLS_MODELS = new Set([
"anthropic/claude-opus-4.1",
"anthropic/claude-opus-4.5",
"anthropic/claude-opus-4.6",
"anthropic/claude-opus-5",
"anthropic/claude-fable-5",
"anthropic/claude-sonnet-4",
"anthropic/claude-sonnet-4.5",
Expand Down
14 changes: 14 additions & 0 deletions packages/types/src/providers/vertex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,20 @@ export const vertexModels = {
},
],
},
"claude-opus-5": {
maxTokens: 8192,
contextWindow: 1_000_000, // 1M context window native (no beta header required)
supportsImages: true,
supportsPromptCache: true,
inputPrice: 5.0, // $5 per million input tokens
outputPrice: 25.0, // $25 per million output tokens
cacheWritesPrice: 6.25, // $6.25 per million tokens
cacheReadsPrice: 0.5, // $0.50 per million tokens
supportsReasoningBudget: true,
supportsReasoningBinary: true,
supportsTemperature: false,
description: "Claude Opus 5 is Anthropic's most capable model for complex agentic coding and enterprise work.",
},
"claude-fable-5": {
maxTokens: 8192,
contextWindow: 1_000_000,
Expand Down
46 changes: 46 additions & 0 deletions src/api/providers/__tests__/anthropic-vertex.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1085,6 +1085,23 @@ describe("VertexHandler", () => {
expect(model.info.supportsTemperature).toBe(false)
})

it("should return Claude Opus 5 model info", () => {
const handler = new AnthropicVertexHandler({
apiModelId: "claude-opus-5",
vertexProjectId: "test-project",
vertexRegion: "us-central1",
})

const model = handler.getModel()
expect(model.id).toBe("claude-opus-5")
expect(model.info.maxTokens).toBe(8192)
expect(model.info.contextWindow).toBe(1_000_000)
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 not enable 1M context when flag is disabled", () => {
const handler = new AnthropicVertexHandler({
apiModelId: VERTEX_1M_CONTEXT_MODEL_IDS[0],
Expand Down Expand Up @@ -1392,6 +1409,35 @@ describe("VertexHandler", () => {
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",
vertexProjectId: "test-project",
vertexRegion: "us-central1",
enableReasoningEffort: true,
})

const mockCreate = vitest.fn().mockImplementation(async () => ({
async *[Symbol.asyncIterator]() {
yield { type: "message_start", message: { usage: { input_tokens: 10, output_tokens: 5 } } }
},
}))
;(opusHandler["client"].messages as any).create = mockCreate

await opusHandler.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()
})
})

describe("native tool calling", () => {
Expand Down
44 changes: 44 additions & 0 deletions src/api/providers/__tests__/anthropic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,33 @@ describe("AnthropicHandler", () => {
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",
apiModelId: "claude-opus-5",
enableReasoningEffort: true,
modelMaxTokens: 32768,
})

const stream = opusHandler.createMessage(systemPrompt, [
{
role: "user",
content: [{ type: "text" as const, text: "Hello" }],
},
])

for await (const _chunk of stream) {
// Consume 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 send the custom model ID as-is and use adaptive thinking for a custom Sonnet-5-family model", async () => {
const customHandler = new AnthropicHandler({
apiKey: "test-api-key",
Expand Down Expand Up @@ -656,6 +683,23 @@ describe("AnthropicHandler", () => {
expect(model.reasoningBudget).toBeUndefined()
})

it("should handle Claude Opus 5 model correctly", () => {
const handler = new AnthropicHandler({
apiKey: "test-api-key",
apiModelId: "claude-opus-5",
})
const model = handler.getModel()
expect(model.id).toBe("claude-opus-5")
expect(model.info.maxTokens).toBe(128000)
expect(model.info.contextWindow).toBe(1000000)
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 enable 1M context for Claude 4.5 Sonnet when beta flag is set", () => {
const handler = new AnthropicHandler({
apiKey: "test-api-key",
Expand Down
64 changes: 64 additions & 0 deletions src/api/providers/__tests__/bedrock.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,37 @@ describe("AwsBedrockHandler", () => {
const model = handler.getModel()
expect(model.id).toBe("global.anthropic.claude-sonnet-5")
})

it("should return Claude Opus 5 model info", () => {
const handler = new AwsBedrockHandler({
apiModelId: "anthropic.claude-opus-5",
awsAccessKey: "test",
awsSecretKey: "test",
awsRegion: "us-east-1",
})

const model = handler.getModel()
expect(model.id).toBe("anthropic.claude-opus-5")
expect(model.info.contextWindow).toBe(1_000_000)
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 Opus 5 when awsUseGlobalInference is true", () => {
const handler = new AwsBedrockHandler({
apiModelId: "anthropic.claude-opus-5",
awsAccessKey: "test",
awsSecretKey: "test",
awsRegion: "us-east-1",
awsUseGlobalInference: true,
})

const model = handler.getModel()
expect(model.id).toBe("global.anthropic.claude-opus-5")
})
})

describe("1M context beta feature", () => {
Expand Down Expand Up @@ -1487,6 +1518,36 @@ describe("AwsBedrockHandler", () => {
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
// the createMessage adaptive-thinking branch for this specific model
// wouldn't be caught without a request-level test.
const opus5Handler = new AwsBedrockHandler({
apiModelId: "anthropic.claude-opus-5",
awsAccessKey: "test-access-key",
awsSecretKey: "test-secret-key",
awsRegion: "us-east-1",
enableReasoningEffort: true,
})

const generator = opus5Handler.createMessage("System prompt", messages)
await generator.next()

expect(mockConverseStreamCommand).toHaveBeenCalled()
const commandArg = mockConverseStreamCommand.mock.calls[0][0] as any

// Opus 5 uses the same adaptive-thinking contract as Opus 4.7/4.8 —
// budget_tokens causes a 400, so thinking.type is "adaptive" with effort.
expect(commandArg.additionalModelRequestFields?.thinking).toEqual({
type: "adaptive",
display: "summarized",
})
expect(commandArg.additionalModelRequestFields?.output_config).toEqual({ effort: "xhigh" })
// Opus 5 rejects sampling parameters: temperature must be omitted entirely.
expect(commandArg.inferenceConfig?.temperature).toBeUndefined()
})

it("should omit thinking and temperature for Claude Opus 4.8 when reasoning is disabled", async () => {
const opus48Handler = new AwsBedrockHandler({
apiModelId: "anthropic.claude-opus-4-8",
Expand Down Expand Up @@ -1620,6 +1681,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-opus-5")).toBe(true)
// Future-proof Sonnet patterns — guarded even before a registry entry exists.
expect(isAdaptiveThinkingModel("anthropic.claude-sonnet-4-7")).toBe(true)
expect(isAdaptiveThinkingModel("anthropic.claude-sonnet-4-8")).toBe(true)
Expand All @@ -1629,12 +1691,14 @@ 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-opus-5")).toBe(true)
expect(isAdaptiveThinkingModel("eu.anthropic.claude-sonnet-4-7")).toBe(true)
expect(isAdaptiveThinkingModel("global.anthropic.claude-opus-4-8")).toBe(true)
})

it("returns false for older / non-adaptive models", () => {
expect(isAdaptiveThinkingModel("anthropic.claude-opus-4-6-v1")).toBe(false)
expect(isAdaptiveThinkingModel("anthropic.claude-opus-4-5-20251101-v1:0")).toBe(false)
expect(isAdaptiveThinkingModel("anthropic.claude-sonnet-4-6")).toBe(false)
expect(isAdaptiveThinkingModel("anthropic.claude-3-5-sonnet-20241022-v2:0")).toBe(false)
expect(isAdaptiveThinkingModel("amazon.nova-lite-v1:0")).toBe(false)
Expand Down
Loading
Loading