diff --git a/packages/types/src/providers/bedrock.ts b/packages/types/src/providers/bedrock.ts index da40e98f433..bd65391c21f 100644 --- a/packages/types/src/providers/bedrock.ts +++ b/packages/types/src/providers/bedrock.ts @@ -565,6 +565,30 @@ export const BEDROCK_1M_CONTEXT_MODEL_IDS = [ "anthropic.claude-sonnet-4-5-20250929-v1:0", ] as const +// Amazon Bedrock models that support cross-region inference profiles +// Based on AWS documentation: https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-support.html +// Cross-region inference is primarily supported for Anthropic Claude models +export const BEDROCK_CROSS_REGION_INFERENCE_MODEL_IDS = [ + // Claude 4.x models + "anthropic.claude-sonnet-4-5-20250929-v1:0", + "anthropic.claude-sonnet-4-20250514-v1:0", + "anthropic.claude-opus-4-1-20250805-v1:0", + "anthropic.claude-opus-4-5-20251101-v1:0", + "anthropic.claude-opus-4-20250514-v1:0", + // Claude 3.7 models + "anthropic.claude-3-7-sonnet-20250219-v1:0", + // Claude 3.5 models + "anthropic.claude-3-5-sonnet-20241022-v2:0", + "anthropic.claude-3-5-haiku-20241022-v1:0", + "anthropic.claude-3-5-sonnet-20240620-v1:0", + // Claude Haiku 4.5 + "anthropic.claude-haiku-4-5-20251001-v1:0", + // Claude 3 models + "anthropic.claude-3-opus-20240229-v1:0", + "anthropic.claude-3-sonnet-20240229-v1:0", + "anthropic.claude-3-haiku-20240307-v1:0", +] as const + // Amazon Bedrock models that support Global Inference profiles // As of Nov 2025, AWS supports Global Inference for: // - Claude Sonnet 4 diff --git a/src/api/providers/__tests__/bedrock-inference-profiles.spec.ts b/src/api/providers/__tests__/bedrock-inference-profiles.spec.ts index dee3af3b916..fdbf383f73c 100644 --- a/src/api/providers/__tests__/bedrock-inference-profiles.spec.ts +++ b/src/api/providers/__tests__/bedrock-inference-profiles.spec.ts @@ -1,6 +1,6 @@ // npx vitest run src/api/providers/__tests__/bedrock-inference-profiles.spec.ts -import { AWS_INFERENCE_PROFILE_MAPPING } from "@roo-code/types" +import { AWS_INFERENCE_PROFILE_MAPPING, BEDROCK_CROSS_REGION_INFERENCE_MODEL_IDS } from "@roo-code/types" import { AwsBedrockHandler } from "../bedrock" import { ApiHandlerOptions } from "../../../shared/api" @@ -56,6 +56,37 @@ describe("Amazon Bedrock Inference Profiles", () => { }) }) + describe("BEDROCK_CROSS_REGION_INFERENCE_MODEL_IDS constant", () => { + it("should contain only Anthropic Claude models", () => { + BEDROCK_CROSS_REGION_INFERENCE_MODEL_IDS.forEach((modelId) => { + expect(modelId).toMatch(/^anthropic\.claude/) + }) + }) + + it("should not contain non-Claude models", () => { + const nonClaudePatterns = ["qwen", "amazon", "meta", "deepseek", "openai"] + BEDROCK_CROSS_REGION_INFERENCE_MODEL_IDS.forEach((modelId) => { + nonClaudePatterns.forEach((pattern) => { + expect(modelId.toLowerCase()).not.toContain(pattern) + }) + }) + }) + + it("should include common Claude models that support cross-region inference", () => { + const expectedModels = [ + "anthropic.claude-3-sonnet-20240229-v1:0", + "anthropic.claude-3-haiku-20240307-v1:0", + "anthropic.claude-3-opus-20240229-v1:0", + "anthropic.claude-3-5-sonnet-20241022-v2:0", + "anthropic.claude-3-5-haiku-20241022-v1:0", + ] + + expectedModels.forEach((modelId) => { + expect(BEDROCK_CROSS_REGION_INFERENCE_MODEL_IDS).toContain(modelId) + }) + }) + }) + describe("getPrefixForRegion function", () => { it("should return correct prefix for US government regions", () => { const handler = createHandler() @@ -213,15 +244,37 @@ describe("Amazon Bedrock Inference Profiles", () => { expect(model.id).toBe("anthropic.claude-3-sonnet-20240229-v1:0") }) - it("should work with different model IDs", () => { - const testModels = [ + it("should apply cross-region prefix only to supported Claude models", () => { + // Test Claude models that DO support cross-region inference + const supportedModels = [ "anthropic.claude-3-haiku-20240307-v1:0", "anthropic.claude-3-opus-20240229-v1:0", + "anthropic.claude-3-sonnet-20240229-v1:0", + "anthropic.claude-3-5-sonnet-20241022-v2:0", + ] + + supportedModels.forEach((modelId) => { + const handler = createHandler({ + awsUseCrossRegionInference: true, + awsRegion: "eu-west-1", + apiModelId: modelId, + }) + + const model = handler.getModel() + expect(model.id).toBe(`eu.${modelId}`) + }) + }) + + it("should NOT apply cross-region prefix to unsupported models", () => { + // Test models that do NOT support cross-region inference + const unsupportedModels = [ + "qwen.qwen3-coder-480b-a35b-v1:0", "amazon.nova-pro-v1:0", "meta.llama3-1-70b-instruct-v1:0", + "deepseek.r1-v1:0", ] - testModels.forEach((modelId) => { + unsupportedModels.forEach((modelId) => { const handler = createHandler({ awsUseCrossRegionInference: true, awsRegion: "eu-west-1", @@ -229,7 +282,8 @@ describe("Amazon Bedrock Inference Profiles", () => { }) const model = handler.getModel() - expect(model.id).toBe(`eu.${modelId}`) + // Should remain unchanged - no prefix applied + expect(model.id).toBe(modelId) }) }) diff --git a/src/api/providers/__tests__/bedrock.spec.ts b/src/api/providers/__tests__/bedrock.spec.ts index d728fbb91e0..e12950a0d5a 100644 --- a/src/api/providers/__tests__/bedrock.spec.ts +++ b/src/api/providers/__tests__/bedrock.spec.ts @@ -1084,13 +1084,15 @@ describe("AwsBedrockHandler", () => { }) describe("service tier with cross-region inference", () => { - it("should apply service tier pricing with cross-region inference prefix", () => { + it("should apply service tier pricing without cross-region prefix for unsupported models", () => { + // amazon.nova-lite-v1:0 supports service tiers but NOT cross-region inference + // Cross-region inference is only supported for Anthropic Claude models const handler = new AwsBedrockHandler({ apiModelId: supportedModelId, awsAccessKey: "test", awsSecretKey: "test", awsRegion: "us-east-1", - awsUseCrossRegionInference: true, + awsUseCrossRegionInference: true, // This should be ignored for non-Claude models awsBedrockServiceTier: "FLEX", }) @@ -1100,8 +1102,8 @@ describe("AwsBedrockHandler", () => { outputPrice: number } - // Model ID should have cross-region prefix - expect(model.id).toBe(`us.${supportedModelId}`) + // Model ID should NOT have cross-region prefix because amazon.nova-lite-v1:0 doesn't support it + expect(model.id).toBe(supportedModelId) // FLEX tier pricing should still be applied expect(model.info.inputPrice).toBe(baseModel.inputPrice * 0.5) diff --git a/src/api/providers/bedrock.ts b/src/api/providers/bedrock.ts index 761500750d0..0f96567634e 100644 --- a/src/api/providers/bedrock.ts +++ b/src/api/providers/bedrock.ts @@ -28,6 +28,7 @@ import { AWS_INFERENCE_PROFILE_MAPPING, BEDROCK_1M_CONTEXT_MODEL_IDS, BEDROCK_GLOBAL_INFERENCE_MODEL_IDS, + BEDROCK_CROSS_REGION_INFERENCE_MODEL_IDS, BEDROCK_SERVICE_TIER_MODEL_IDS, BEDROCK_SERVICE_TIER_PRICING, ApiProviderError, @@ -1105,11 +1106,16 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH ) { modelConfig.id = `global.${baseIdForGlobal}` } - // Otherwise, add cross-region inference prefix if enabled + // Otherwise, add cross-region inference prefix if enabled and model supports it else if (this.options.awsUseCrossRegionInference && this.options.awsRegion) { - const prefix = AwsBedrockHandler.getPrefixForRegion(this.options.awsRegion) - if (prefix) { - modelConfig.id = `${prefix}${modelConfig.id}` + // Only apply cross-region inference prefix to models that support it + // This prevents invalid model identifiers for models like Qwen that don't support cross-region inference + const baseIdForCrossRegion = this.parseBaseModelId(modelConfig.id) + if (BEDROCK_CROSS_REGION_INFERENCE_MODEL_IDS.includes(baseIdForCrossRegion as any)) { + const prefix = AwsBedrockHandler.getPrefixForRegion(this.options.awsRegion) + if (prefix) { + modelConfig.id = `${prefix}${modelConfig.id}` + } } } }