From a58d22b49565e44dc07dc203ad9314e39c77d142 Mon Sep 17 00:00:00 2001 From: Roomote Date: Mon, 19 Oct 2026 22:40:58 +0000 Subject: [PATCH 1/2] feat(providers): add Anthropic Claude Haiku 5.5 to AWS Bedrock model catalog --- packages/types/src/providers/bedrock.ts | 37 ++++++- src/api/providers/__tests__/bedrock.spec.ts | 112 +++++++++++++++++--- src/api/providers/bedrock.ts | 14 +-- 3 files changed, 140 insertions(+), 23 deletions(-) diff --git a/packages/types/src/providers/bedrock.ts b/packages/types/src/providers/bedrock.ts index fdbdcf7f27..2e5498736d 100644 --- a/packages/types/src/providers/bedrock.ts +++ b/packages/types/src/providers/bedrock.ts @@ -277,6 +277,34 @@ 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 + // Prompts over 100,000 tokens bill at tiered long-context rates. + 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, @@ -681,7 +709,12 @@ 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", + "anthropic.claude-haiku-5-5", +] as const // Amazon Bedrock models that support Global Inference profiles // As of Nov 2025, AWS supports Global Inference for: @@ -695,6 +728,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", @@ -708,6 +742,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 diff --git a/src/api/providers/__tests__/bedrock.spec.ts b/src/api/providers/__tests__/bedrock.spec.ts index 248149a303..deade9512f 100644 --- a/src/api/providers/__tests__/bedrock.spec.ts +++ b/src/api/providers/__tests__/bedrock.spec.ts @@ -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]) { @@ -1047,6 +1048,79 @@ 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("should apply inference profile prefixes for Claude Haiku 5.5", () => { + // The user-verified invocation path: regional (us.) and global prefixes + // must keep the known catalog entry's prompt-cache capability. + for (const [option, expectedId] of [ + [{ awsUseCrossRegionInference: true }, "us.anthropic.claude-haiku-5-5"], + [{ awsUseGlobalInference: true }, "global.anthropic.claude-haiku-5-5"], + ] as const) { + 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) + }) + + it("should apply global inference prefix for Claude Haiku 5.5 when awsUseGlobalInference is true", () => { + const handler = new AwsBedrockHandler({ + apiModelId: "anthropic.claude-haiku-5-5", + awsAccessKey: "", + awsSecretKey: "", + awsRegion: "us-east-1", + awsUseGlobalInference: true, + }) + + const model = handler.getModel() + expect(model.id).toBe("global.anthropic.claude-haiku-5-5") + }) }) describe("1M context beta feature", () => { @@ -1817,23 +1891,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", @@ -2135,6 +2212,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) @@ -2146,6 +2224,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) }) @@ -2155,6 +2234,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) }) }) diff --git a/src/api/providers/bedrock.ts b/src/api/providers/bedrock.ts index 5a66d6c193..e09cc0b17b 100644 --- a/src/api/providers/bedrock.ts +++ b/src/api/providers/bedrock.ts @@ -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). * @@ -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") ) } From cda55a616a12f621e1aebdd87f8841592aa67fc4 Mon Sep 17 00:00:00 2001 From: Elliott de Launay Date: Tue, 20 Oct 2026 03:15:56 +0000 Subject: [PATCH 2/2] test(bedrock): strengthen Haiku 5.5 request coverage --- packages/types/src/providers/bedrock.ts | 7 +- src/api/providers/__tests__/bedrock.spec.ts | 99 +++++++++++++++------ 2 files changed, 77 insertions(+), 29 deletions(-) diff --git a/packages/types/src/providers/bedrock.ts b/packages/types/src/providers/bedrock.ts index 2e5498736d..1e89f3e6ba 100644 --- a/packages/types/src/providers/bedrock.ts +++ b/packages/types/src/providers/bedrock.ts @@ -292,7 +292,8 @@ export const bedrockModels = { // 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 - // Prompts over 100,000 tokens bill at tiered long-context rates. + // 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) @@ -713,6 +714,10 @@ export const BEDROCK_1M_CONTEXT_MODEL_IDS = [ 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 diff --git a/src/api/providers/__tests__/bedrock.spec.ts b/src/api/providers/__tests__/bedrock.spec.ts index deade9512f..fcc98b87b1 100644 --- a/src/api/providers/__tests__/bedrock.spec.ts +++ b/src/api/providers/__tests__/bedrock.spec.ts @@ -1074,52 +1074,45 @@ describe("AwsBedrockHandler", () => { expect(model.maxTokens).toBe(8192) }) - it("should apply inference profile prefixes for Claude Haiku 5.5", () => { - // The user-verified invocation path: regional (us.) and global prefixes + 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. - for (const [option, expectedId] of [ - [{ awsUseCrossRegionInference: true }, "us.anthropic.claude-haiku-5-5"], - [{ awsUseGlobalInference: true }, "global.anthropic.claude-haiku-5-5"], - ] as const) { - 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", + 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.inputPrice).toBe(0.1) + expect(model.info.minTokensPerCachePoint).toBe(512) }) - it("should apply global inference prefix for Claude Haiku 5.5 when awsUseGlobalInference is true", () => { + it("should resolve a stored us.-prefixed Claude Haiku 5.5 id to catalog info", () => { const handler = new AwsBedrockHandler({ - apiModelId: "anthropic.claude-haiku-5-5", + apiModelId: "us.anthropic.claude-haiku-5-5", awsAccessKey: "", awsSecretKey: "", awsRegion: "us-east-1", - awsUseGlobalInference: true, }) const model = handler.getModel() - expect(model.id).toBe("global.anthropic.claude-haiku-5-5") + expect(model.info.supportsPromptCache).toBe(true) + expect(model.info.inputPrice).toBe(0.1) }) }) @@ -1879,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", @@ -2004,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.