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
119 changes: 45 additions & 74 deletions packages/types/src/providers/bedrock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,78 +360,49 @@ export const BEDROCK_MAX_TOKENS = 4096

export const BEDROCK_DEFAULT_CONTEXT = 128_000

export const BEDROCK_REGION_INFO: Record<
string,
{
regionId: string
description: string
pattern?: string
multiRegion?: boolean
}
> = {
/*
* This JSON generated by AWS's AI assistant - Amazon Q on March 29, 2025
*
* - Africa (Cape Town) region does not appear to support Amazon Bedrock at this time.
* - Some Asia Pacific regions, such as Asia Pacific (Hong Kong) and Asia Pacific (Jakarta), are not listed among the supported regions for Bedrock services.
* - Middle East regions, including Middle East (Bahrain) and Middle East (UAE), are not mentioned in the list of supported regions for Bedrock. [3]
* - China regions (Beijing and Ningxia) are not listed as supported for Amazon Bedrock.
* - Some newer or specialized AWS regions may not have Bedrock support yet.
*/
"us.": { regionId: "us-east-1", description: "US East (N. Virginia)", pattern: "us-", multiRegion: true },
"use.": { regionId: "us-east-1", description: "US East (N. Virginia)" },
"use1.": { regionId: "us-east-1", description: "US East (N. Virginia)" },
"use2.": { regionId: "us-east-2", description: "US East (Ohio)" },
"usw.": { regionId: "us-west-2", description: "US West (Oregon)" },
"usw2.": { regionId: "us-west-2", description: "US West (Oregon)" },
"ug.": {
regionId: "us-gov-west-1",
description: "AWS GovCloud (US-West)",
pattern: "us-gov-",
multiRegion: true,
},
"uge1.": { regionId: "us-gov-east-1", description: "AWS GovCloud (US-East)" },
"ugw1.": { regionId: "us-gov-west-1", description: "AWS GovCloud (US-West)" },
"eu.": { regionId: "eu-west-1", description: "Europe (Ireland)", pattern: "eu-", multiRegion: true },
"euw1.": { regionId: "eu-west-1", description: "Europe (Ireland)" },
"euw2.": { regionId: "eu-west-2", description: "Europe (London)" },
"euw3.": { regionId: "eu-west-3", description: "Europe (Paris)" },
"euc1.": { regionId: "eu-central-1", description: "Europe (Frankfurt)" },
"euc2.": { regionId: "eu-central-2", description: "Europe (Zurich)" },
"eun1.": { regionId: "eu-north-1", description: "Europe (Stockholm)" },
"eus1.": { regionId: "eu-south-1", description: "Europe (Milan)" },
"eus2.": { regionId: "eu-south-2", description: "Europe (Spain)" },
"ap.": {
regionId: "ap-southeast-1",
description: "Asia Pacific (Singapore)",
pattern: "ap-",
multiRegion: true,
},
"ape1.": { regionId: "ap-east-1", description: "Asia Pacific (Hong Kong)" },
"apne1.": { regionId: "ap-northeast-1", description: "Asia Pacific (Tokyo)" },
"apne2.": { regionId: "ap-northeast-2", description: "Asia Pacific (Seoul)" },
"apne3.": { regionId: "ap-northeast-3", description: "Asia Pacific (Osaka)" },
"aps1.": { regionId: "ap-south-1", description: "Asia Pacific (Mumbai)" },
"aps2.": { regionId: "ap-south-2", description: "Asia Pacific (Hyderabad)" },
"apse1.": { regionId: "ap-southeast-1", description: "Asia Pacific (Singapore)" },
"apse2.": { regionId: "ap-southeast-2", description: "Asia Pacific (Sydney)" },
"ca.": { regionId: "ca-central-1", description: "Canada (Central)", pattern: "ca-", multiRegion: true },
"cac1.": { regionId: "ca-central-1", description: "Canada (Central)" },
"sa.": { regionId: "sa-east-1", description: "South America (SΓ£o Paulo)", pattern: "sa-", multiRegion: true },
"sae1.": { regionId: "sa-east-1", description: "South America (SΓ£o Paulo)" },
// AWS Bedrock Inference Profile mapping based on official documentation
// https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-support.html
// This mapping is pre-ordered by pattern length (descending) to ensure more specific patterns match first
export const AWS_INFERENCE_PROFILE_MAPPING: Array<[string, string]> = [
// US Government Cloud β†’ ug. inference profile (most specific prefix first)
["us-gov-", "ug."],
// Americas regions β†’ us. inference profile
["us-", "us."],
// Europe regions β†’ eu. inference profile
["eu-", "eu."],
// Asia Pacific regions β†’ apac. inference profile
["ap-", "apac."],
// Canada regions β†’ ca. inference profile
["ca-", "ca."],
// South America regions β†’ sa. inference profile
["sa-", "sa."],
]

// These are not official - they weren't generated by Amazon Q nor were
// found in the AWS documentation but another Roo contributor found apac.
// Was needed so I've added the pattern of the other geo zones.
"apac.": { regionId: "ap-southeast-1", description: "Default APAC region", pattern: "ap-", multiRegion: true },
"emea.": { regionId: "eu-west-1", description: "Default EMEA region", pattern: "eu-", multiRegion: true },
"amer.": { regionId: "us-east-1", description: "Default Americas region", pattern: "us-", multiRegion: true },
}

export const BEDROCK_REGIONS = Object.values(BEDROCK_REGION_INFO)
// Extract all region IDs
.map((info) => ({ value: info.regionId, label: info.regionId }))
// Filter to unique region IDs (remove duplicates)
.filter((region, index, self) => index === self.findIndex((r) => r.value === region.value))
// Sort alphabetically by region ID
.sort((a, b) => a.value.localeCompare(b.value))
// AWS Bedrock supported regions for the regions dropdown
// Based on official AWS documentation
export const BEDROCK_REGIONS = [
{ value: "us-east-1", label: "us-east-1" },
{ value: "us-east-2", label: "us-east-2" },
{ value: "us-west-1", label: "us-west-1" },
{ value: "us-west-2", label: "us-west-2" },
{ value: "ap-northeast-1", label: "ap-northeast-1" },
{ value: "ap-northeast-2", label: "ap-northeast-2" },
{ value: "ap-northeast-3", label: "ap-northeast-3" },
{ value: "ap-south-1", label: "ap-south-1" },
{ value: "ap-south-2", label: "ap-south-2" },
{ value: "ap-southeast-1", label: "ap-southeast-1" },
{ value: "ap-southeast-2", label: "ap-southeast-2" },
{ value: "ap-east-1", label: "ap-east-1" },
{ value: "eu-central-1", label: "eu-central-1" },
{ value: "eu-central-2", label: "eu-central-2" },
{ value: "eu-west-1", label: "eu-west-1" },
{ value: "eu-west-2", label: "eu-west-2" },
{ value: "eu-west-3", label: "eu-west-3" },
{ value: "eu-north-1", label: "eu-north-1" },
{ value: "eu-south-1", label: "eu-south-1" },
{ value: "eu-south-2", label: "eu-south-2" },
{ value: "ca-central-1", label: "ca-central-1" },
{ value: "sa-east-1", label: "sa-east-1" },
{ value: "us-gov-east-1", label: "us-gov-east-1" },
{ value: "us-gov-west-1", label: "us-gov-west-1" },
].sort((a, b) => a.value.localeCompare(b.value))
249 changes: 249 additions & 0 deletions src/api/providers/__tests__/bedrock-inference-profiles.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
// npx vitest run src/api/providers/__tests__/bedrock-inference-profiles.spec.ts

import { AWS_INFERENCE_PROFILE_MAPPING } from "@roo-code/types"
import { AwsBedrockHandler } from "../bedrock"
import { ApiHandlerOptions } from "../../../shared/api"

// Mock AWS SDK
vitest.mock("@aws-sdk/client-bedrock-runtime", () => {
return {
BedrockRuntimeClient: vitest.fn().mockImplementation(() => ({
send: vitest.fn(),
config: { region: "us-east-1" },
})),
ConverseCommand: vitest.fn(),
ConverseStreamCommand: vitest.fn(),
}
})

describe("AWS Bedrock Inference Profiles", () => {
// Helper function to create a handler with specific options
const createHandler = (options: Partial<ApiHandlerOptions> = {}) => {
const defaultOptions: ApiHandlerOptions = {
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
awsRegion: "us-east-1",
...options,
}
return new AwsBedrockHandler(defaultOptions)
}

describe("AWS_INFERENCE_PROFILE_MAPPING constant", () => {
it("should contain all expected region mappings", () => {
expect(AWS_INFERENCE_PROFILE_MAPPING).toEqual([
["us-gov-", "ug."],
["us-", "us."],
["eu-", "eu."],
["ap-", "apac."],
["ca-", "ca."],
["sa-", "sa."],
])
})

it("should be ordered by pattern length (descending)", () => {
const lengths = AWS_INFERENCE_PROFILE_MAPPING.map(([pattern]) => pattern.length)
const sortedLengths = [...lengths].sort((a, b) => b - a)
expect(lengths).toEqual(sortedLengths)
})

it("should have valid inference profile prefixes", () => {
AWS_INFERENCE_PROFILE_MAPPING.forEach(([regionPattern, inferenceProfile]) => {
expect(regionPattern).toMatch(/^[a-z-]+$/)
expect(inferenceProfile).toMatch(/^[a-z]+\.$/)
})
})
})

describe("getPrefixForRegion function", () => {
it("should return correct prefix for US government regions", () => {
const handler = createHandler()
expect((handler as any).constructor.getPrefixForRegion("us-gov-east-1")).toBe("ug.")
expect((handler as any).constructor.getPrefixForRegion("us-gov-west-1")).toBe("ug.")
})

it("should return correct prefix for US commercial regions", () => {
const handler = createHandler()
expect((handler as any).constructor.getPrefixForRegion("us-east-1")).toBe("us.")
expect((handler as any).constructor.getPrefixForRegion("us-west-1")).toBe("us.")
expect((handler as any).constructor.getPrefixForRegion("us-west-2")).toBe("us.")
})

it("should return correct prefix for European regions", () => {
const handler = createHandler()
expect((handler as any).constructor.getPrefixForRegion("eu-west-1")).toBe("eu.")
expect((handler as any).constructor.getPrefixForRegion("eu-central-1")).toBe("eu.")
expect((handler as any).constructor.getPrefixForRegion("eu-north-1")).toBe("eu.")
expect((handler as any).constructor.getPrefixForRegion("eu-south-1")).toBe("eu.")
})

it("should return correct prefix for Asia Pacific regions", () => {
const handler = createHandler()
expect((handler as any).constructor.getPrefixForRegion("ap-southeast-1")).toBe("apac.")
expect((handler as any).constructor.getPrefixForRegion("ap-northeast-1")).toBe("apac.")
expect((handler as any).constructor.getPrefixForRegion("ap-south-1")).toBe("apac.")
expect((handler as any).constructor.getPrefixForRegion("ap-east-1")).toBe("apac.")
})

it("should return correct prefix for Canada regions", () => {
const handler = createHandler()
expect((handler as any).constructor.getPrefixForRegion("ca-central-1")).toBe("ca.")
expect((handler as any).constructor.getPrefixForRegion("ca-west-1")).toBe("ca.")
})

it("should return correct prefix for South America regions", () => {
const handler = createHandler()
expect((handler as any).constructor.getPrefixForRegion("sa-east-1")).toBe("sa.")
})

it("should return undefined for unsupported regions", () => {
const handler = createHandler()
expect((handler as any).constructor.getPrefixForRegion("af-south-1")).toBeUndefined()
expect((handler as any).constructor.getPrefixForRegion("me-south-1")).toBeUndefined()
expect((handler as any).constructor.getPrefixForRegion("cn-north-1")).toBeUndefined()
expect((handler as any).constructor.getPrefixForRegion("invalid-region")).toBeUndefined()
})

it("should prioritize longer patterns over shorter ones", () => {
const handler = createHandler()
// us-gov- should be matched before us-
expect((handler as any).constructor.getPrefixForRegion("us-gov-east-1")).toBe("ug.")
expect((handler as any).constructor.getPrefixForRegion("us-gov-west-1")).toBe("ug.")

// Regular us- regions should still work
expect((handler as any).constructor.getPrefixForRegion("us-east-1")).toBe("us.")
expect((handler as any).constructor.getPrefixForRegion("us-west-2")).toBe("us.")
})
})

describe("Cross-region inference integration", () => {
it("should apply ug. prefix for US government regions", () => {
const handler = createHandler({
awsUseCrossRegionInference: true,
awsRegion: "us-gov-east-1",
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
})

const model = handler.getModel()
expect(model.id).toBe("ug.anthropic.claude-3-sonnet-20240229-v1:0")
})

it("should apply us. prefix for US commercial regions", () => {
const handler = createHandler({
awsUseCrossRegionInference: true,
awsRegion: "us-east-1",
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
})

const model = handler.getModel()
expect(model.id).toBe("us.anthropic.claude-3-sonnet-20240229-v1:0")
})

it("should apply eu. prefix for European regions", () => {
const handler = createHandler({
awsUseCrossRegionInference: true,
awsRegion: "eu-west-1",
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
})

const model = handler.getModel()
expect(model.id).toBe("eu.anthropic.claude-3-sonnet-20240229-v1:0")
})

it("should apply apac. prefix for Asia Pacific regions", () => {
const handler = createHandler({
awsUseCrossRegionInference: true,
awsRegion: "ap-southeast-1",
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
})

const model = handler.getModel()
expect(model.id).toBe("apac.anthropic.claude-3-sonnet-20240229-v1:0")
})

it("should apply ca. prefix for Canada regions", () => {
const handler = createHandler({
awsUseCrossRegionInference: true,
awsRegion: "ca-central-1",
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
})

const model = handler.getModel()
expect(model.id).toBe("ca.anthropic.claude-3-sonnet-20240229-v1:0")
})

it("should apply sa. prefix for South America regions", () => {
const handler = createHandler({
awsUseCrossRegionInference: true,
awsRegion: "sa-east-1",
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
})

const model = handler.getModel()
expect(model.id).toBe("sa.anthropic.claude-3-sonnet-20240229-v1:0")
})

it("should not apply prefix when cross-region inference is disabled", () => {
const handler = createHandler({
awsUseCrossRegionInference: false,
awsRegion: "us-gov-east-1",
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
})

const model = handler.getModel()
expect(model.id).toBe("anthropic.claude-3-sonnet-20240229-v1:0")
})

it("should handle unsupported regions gracefully", () => {
const handler = createHandler({
awsUseCrossRegionInference: true,
awsRegion: "af-south-1", // Unsupported region
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
})

const model = handler.getModel()
// Should remain unchanged when no prefix is found
expect(model.id).toBe("anthropic.claude-3-sonnet-20240229-v1:0")
})

it("should work with different model IDs", () => {
const testModels = [
"anthropic.claude-3-haiku-20240307-v1:0",
"anthropic.claude-3-opus-20240229-v1:0",
"amazon.nova-pro-v1:0",
"meta.llama3-1-70b-instruct-v1:0",
]

testModels.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 prioritize us-gov- over us- in cross-region inference", () => {
// Test that us-gov-east-1 gets ug. prefix, not us.
const govHandler = createHandler({
awsUseCrossRegionInference: true,
awsRegion: "us-gov-east-1",
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
})

const govModel = govHandler.getModel()
expect(govModel.id).toBe("ug.anthropic.claude-3-sonnet-20240229-v1:0")

// Test that regular us-east-1 still gets us. prefix
const usHandler = createHandler({
awsUseCrossRegionInference: true,
awsRegion: "us-east-1",
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
})

const usModel = usHandler.getModel()
expect(usModel.id).toBe("us.anthropic.claude-3-sonnet-20240229-v1:0")
})
})
})
Loading
Loading