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
42 changes: 41 additions & 1 deletion packages/types/src/providers/bedrock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,35 @@ export const bedrockModels = {
description:
"Claude Opus 5.5 is Anthropic's most capable model for complex agentic coding and enterprise work.",
},
"anthropic.claude-haiku-5-5": {
// 128K max output tokens, 1M native context window, 512-token minimum per
// cache checkpoint, and 4 max cache checkpoints per the AWS Bedrock model card:
// https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-haiku-5-5.html
maxTokens: 128_000,
supportsMaxTokens: true,
contextWindow: 1_000_000, // 1M context window native (no beta header required)
supportsImages: true,
supportsPromptCache: true,
supportsReasoningBudget: true,
supportsReasoningBinary: true,
supportsTemperature: false,
// Standard pricing (prompts of 100,000 tokens or fewer) from Anthropic's
// pricing page, which lists the same Claude list prices billed on Bedrock:
// https://platform.claude.com/docs/en/about-claude/pricing
// These fields price the standard tier only. This entry does not model
// the tiered long-context rates that prompts over 100,000 tokens bill at.
inputPrice: 0.1, // $0.10 per million input tokens
outputPrice: 0.5, // $0.50 per million output tokens
cacheWritesPrice: 0.125, // $0.125 per million tokens (5m cache writes)
cacheReadsPrice: 0.01, // $0.01 per million tokens (cache hits / refreshes)
// The same Bedrock model card lists 512 as the minimum tokens per cache
// checkpoint for Haiku 5.5 (matching the Opus 5.5 entry above).
minTokensPerCachePoint: 512,
maxCachePoints: 4,
cachableFields: ["system", "messages", "tools"],
description:
"Claude Haiku 5.5 is the fastest, most efficient Claude 5.5 model, built for high-volume, latency-sensitive coding and agent work.",
},
"anthropic.claude-fable-5-1": {
maxTokens: 128_000,
contextWindow: 1_000_000,
Expand Down Expand Up @@ -681,7 +710,16 @@ export const BEDROCK_1M_CONTEXT_MODEL_IDS = [
// intentionally absent.
// https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-sonnet-5.html
// https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-opus-5.html
export const BEDROCK_THINKING_DISABLE_MODEL_IDS = ["anthropic.claude-sonnet-5", "anthropic.claude-opus-5"] as const
// https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-haiku-5-5.html
export const BEDROCK_THINKING_DISABLE_MODEL_IDS = [
"anthropic.claude-sonnet-5",
"anthropic.claude-opus-5",
// Unlike adaptive-only Opus 5.5, Haiku 5.5 accepts an explicit
// `thinking: { type: "disabled" }` and turns its default adaptive thinking
// off. AWS caps effort at "high" while thinking is disabled.
// https://docs.aws.amazon.com/bedrock/latest/userguide/claude-messages-adaptive-thinking.html
"anthropic.claude-haiku-5-5",
] as const

// Amazon Bedrock models that support Global Inference profiles
// As of Nov 2025, AWS supports Global Inference for:
Expand All @@ -695,6 +733,7 @@ export const BEDROCK_THINKING_DISABLE_MODEL_IDS = ["anthropic.claude-sonnet-5",
// - Claude Opus 4.7
// - Claude Opus 5
// - Claude Opus 5.5
// - Claude Haiku 5.5
// - Claude Fable 5 and 5.1 (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 @@ -708,6 +747,7 @@ export const BEDROCK_GLOBAL_INFERENCE_MODEL_IDS = [
"anthropic.claude-opus-4-8",
"anthropic.claude-opus-5",
"anthropic.claude-opus-5-5",
"anthropic.claude-haiku-5-5",
"anthropic.claude-fable-5-1",
"anthropic.claude-fable-5",
] as const
Expand Down
155 changes: 139 additions & 16 deletions src/api/providers/__tests__/bedrock.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ describe("AwsBedrockHandler", () => {
["anthropic.claude-opus-4-8", 128_000],
["anthropic.claude-opus-5", 128_000],
["anthropic.claude-opus-5-5", 128_000],
["anthropic.claude-haiku-5-5", 128_000],
] as const)("exposes %s output ceiling without raising request defaults", async (apiModelId, ceiling) => {
for (const enableReasoningEffort of [true, false]) {
for (const modelMaxTokens of [undefined, ceiling]) {
Expand Down Expand Up @@ -1047,6 +1048,72 @@ describe("AwsBedrockHandler", () => {
const model = handler.getModel()
expect(model.id).toBe("global.anthropic.claude-opus-5-5")
})

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

const model = handler.getModel()
expect(model.id).toBe("anthropic.claude-haiku-5-5")
expect(model.info.maxTokens).toBe(128_000)
expect(model.info.contextWindow).toBe(1_000_000)
expect(model.info.inputPrice).toBe(0.1)
expect(model.info.outputPrice).toBe(0.5)
expect(model.info.cacheWritesPrice).toBe(0.125)
expect(model.info.cacheReadsPrice).toBe(0.01)
expect(model.info.minTokensPerCachePoint).toBe(512)
expect(model.info.maxCachePoints).toBe(4)
expect(model.info.supportsPromptCache).toBe(true)
expect(model.info.supportsReasoningBinary).toBe(true)
expect(model.info.supportsReasoningBudget).toBe(true)
expect(model.info.supportsTemperature).toBe(false)
expect(model.maxTokens).toBe(8192)
})

it.each([
{
label: "regional (us.)",
option: { awsUseCrossRegionInference: true },
expectedId: "us.anthropic.claude-haiku-5-5",
},
{
label: "global",
option: { awsUseGlobalInference: true },
expectedId: "global.anthropic.claude-haiku-5-5",
},
])("should apply the $label inference profile prefix for Claude Haiku 5.5", ({ option, expectedId }) => {
// The user-verified invocation path: regional and global prefixes
// must keep the known catalog entry's prompt-cache capability.
const handler = new AwsBedrockHandler({
apiModelId: "anthropic.claude-haiku-5-5",
awsAccessKey: "",
awsSecretKey: "",
awsRegion: "us-east-1",
...option,
})

const model = handler.getModel()
expect(model.id).toBe(expectedId)
expect(model.info.supportsPromptCache).toBe(true)
expect(model.info.minTokensPerCachePoint).toBe(512)
})

it("should resolve a stored us.-prefixed Claude Haiku 5.5 id to catalog info", () => {
const handler = new AwsBedrockHandler({
apiModelId: "us.anthropic.claude-haiku-5-5",
awsAccessKey: "",
awsSecretKey: "",
awsRegion: "us-east-1",
})

const model = handler.getModel()
expect(model.info.supportsPromptCache).toBe(true)
expect(model.info.inputPrice).toBe(0.1)
})
})

describe("1M context beta feature", () => {
Expand Down Expand Up @@ -1805,6 +1872,34 @@ describe("AwsBedrockHandler", () => {
expect(commandArg.inferenceConfig?.temperature).toBeUndefined()
})

it("should send adaptive thinking and omit temperature for Claude Haiku 5.5 when reasoning is enabled", async () => {
// Haiku 5.5 shares the Sonnet 5 / Opus 5 adaptive-thinking contract:
// budget_tokens causes a 400, so thinking.type must be "adaptive".
// effort and display are handler-internal defaults, not part of this
// scenario, so only the thinking type is pinned here.
const haiku55Handler = new AwsBedrockHandler({
apiModelId: "anthropic.claude-haiku-5-5",
awsAccessKey: "test-access-key",
awsSecretKey: "test-secret-key",
awsRegion: "us-east-1",
enableReasoningEffort: true,
})

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

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

expect(commandArg.additionalModelRequestFields).toEqual(
expect.objectContaining({
thinking: expect.objectContaining({ type: "adaptive" }),
}),
)
// Haiku 5.5 rejects sampling parameters: temperature must be omitted.
expect(commandArg.inferenceConfig?.temperature).toBeUndefined()
})

it("should omit thinking and temperature for Claude Opus 4.8 when reasoning is disabled", async () => {
const provider = new AwsBedrockHandler({
apiModelId: "anthropic.claude-opus-4-8",
Expand All @@ -1817,23 +1912,26 @@ describe("AwsBedrockHandler", () => {
expect(commandArg.inferenceConfig?.temperature).toBeUndefined()
})

it.each(["anthropic.claude-sonnet-5", "anthropic.claude-opus-5", "us.anthropic.claude-opus-5"])(
"explicitly disables thinking for %s when reasoning is disabled",
async (apiModelId) => {
const provider = new AwsBedrockHandler({
apiModelId,
enableReasoningEffort: false,
modelMaxTokens: 32_000,
})
await collectStream(provider.createMessage("System prompt", messages))
it.each([
"anthropic.claude-sonnet-5",
"anthropic.claude-opus-5",
"us.anthropic.claude-opus-5",
"anthropic.claude-haiku-5-5",
"us.anthropic.claude-haiku-5-5",
])("explicitly disables thinking for %s when reasoning is disabled", async (apiModelId) => {
const provider = new AwsBedrockHandler({
apiModelId,
enableReasoningEffort: false,
modelMaxTokens: 32_000,
})
await collectStream(provider.createMessage("System prompt", messages))

const commandArg = mockConverseStreamCommand.mock.calls[0][0]
expect(commandArg.additionalModelRequestFields).toEqual(
expect.objectContaining({ thinking: { type: "disabled" } }),
)
expect(commandArg.inferenceConfig?.temperature).toBeUndefined()
},
)
const commandArg = mockConverseStreamCommand.mock.calls[0][0]
expect(commandArg.additionalModelRequestFields).toEqual(
expect.objectContaining({ thinking: { type: "disabled" } }),
)
expect(commandArg.inferenceConfig?.temperature).toBeUndefined()
})

it.each([
"anthropic.claude-fable-5",
Expand Down Expand Up @@ -1927,6 +2025,28 @@ describe("AwsBedrockHandler", () => {
expect(commandArg.inferenceConfig?.temperature).toBeUndefined()
})

it("completePrompt should omit temperature for Claude Haiku 5.5 (non-stream path)", async () => {
// Regression guard for the non-stream path: completePrompt must guard
// temperature the same way createMessage does, otherwise adaptive-thinking
// models like Haiku 5.5 return a 400 from Bedrock.
const mockConverseCommand = vi.mocked(ConverseCommand)

const haiku55Handler = new AwsBedrockHandler({
apiModelId: "anthropic.claude-haiku-5-5",
awsAccessKey: "test-access-key",
awsSecretKey: "test-secret-key",
awsRegion: "us-east-1",
})

await haiku55Handler.completePrompt("Test prompt")

expect(mockConverseCommand).toHaveBeenCalled()
const commandArg = mockConverseCommand.mock.calls[0][0]

// Haiku 5.5 must NOT receive temperature in the non-stream inferenceConfig.
expect(commandArg.inferenceConfig?.temperature).toBeUndefined()
})

it("completePrompt should still send temperature for older Claude Opus 4.6 (non-stream path)", async () => {
// 4.6 and earlier still accept sampling parameters, so completePrompt must
// continue to send temperature for them.
Expand Down Expand Up @@ -2135,6 +2255,7 @@ describe("AwsBedrockHandler", () => {
expect(isAdaptiveThinkingModel("anthropic.claude-sonnet-5")).toBe(true)
expect(isAdaptiveThinkingModel("anthropic.claude-opus-5")).toBe(true)
expect(isAdaptiveThinkingModel("anthropic.claude-opus-5-5")).toBe(true)
expect(isAdaptiveThinkingModel("anthropic.claude-haiku-5-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 @@ -2146,6 +2267,7 @@ describe("AwsBedrockHandler", () => {
expect(isAdaptiveThinkingModel("global.anthropic.claude-sonnet-5")).toBe(true)
expect(isAdaptiveThinkingModel("global.anthropic.claude-opus-5")).toBe(true)
expect(isAdaptiveThinkingModel("global.anthropic.claude-opus-5-5")).toBe(true)
expect(isAdaptiveThinkingModel("global.anthropic.claude-haiku-5-5")).toBe(true)
expect(isAdaptiveThinkingModel("eu.anthropic.claude-sonnet-4-7")).toBe(true)
expect(isAdaptiveThinkingModel("global.anthropic.claude-opus-4-8")).toBe(true)
})
Expand All @@ -2155,6 +2277,7 @@ describe("AwsBedrockHandler", () => {
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("anthropic.claude-haiku-4-5-20251001-v1:0")).toBe(false)
expect(isAdaptiveThinkingModel("amazon.nova-lite-v1:0")).toBe(false)
})
})
Expand Down
14 changes: 8 additions & 6 deletions src/api/providers/bedrock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,12 +331,13 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
* Detect models that require the adaptive-thinking API contract.
*
* Starting with Claude Opus 4.7 (and the matching Sonnet 4.7), and continuing
* in Opus 4.8 / Sonnet 4.8, Claude Fable 5/5.1, Claude Sonnet 5, and Claude Opus 5,
* Anthropic removed sampling parameters (temperature/top_p/top_k) and replaced
* budget_tokens-based thinking with `thinking.type: "adaptive"` plus
* in Opus 4.8 / Sonnet 4.8, Claude Fable 5/5.1, Claude Sonnet 5, Claude Opus 5,
* and Claude Haiku 5.5, Anthropic removed sampling parameters (temperature/top_p/top_k)
* and replaced budget_tokens-based thinking with `thinking.type: "adaptive"` plus
* `output_config.effort`. The migration guide from 4.7 → 4.8 confirms there
* are no further breaking API changes, and Fable 5+ / Sonnet 5 / Opus 5 keep the
* same adaptive-thinking contract, so a single guard matches all generations.
* are no further breaking API changes, and Fable 5+ / Sonnet 5 / Opus 5 /
* Haiku 5.5 keep the same adaptive-thinking contract, so a single guard
* matches all generations.
* Shared by createMessage and completePrompt so both request paths omit
* temperature for these models (sending it causes a 400).
*
Expand All @@ -352,7 +353,8 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
baseModelId.includes("fable-5") ||
baseModelId.includes("sonnet-4-7") ||
baseModelId.includes("sonnet-4-8") ||
baseModelId.includes("sonnet-5")
baseModelId.includes("sonnet-5") ||
baseModelId.includes("haiku-5-5")
)
}

Expand Down
Loading