Skip to content

Commit 000a9e5

Browse files
authored
feat: update AWS Bedrock cross-region inference profile mapping, Closed issue RooCodeInc#2704 (RooCodeInc#4973)
1 parent fb711a0 commit 000a9e5

File tree

4 files changed

+684
-153
lines changed

4 files changed

+684
-153
lines changed

packages/types/src/providers/bedrock.ts

Lines changed: 45 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -360,78 +360,49 @@ export const BEDROCK_MAX_TOKENS = 4096
360360

361361
export const BEDROCK_DEFAULT_CONTEXT = 128_000
362362

363-
export const BEDROCK_REGION_INFO: Record<
364-
string,
365-
{
366-
regionId: string
367-
description: string
368-
pattern?: string
369-
multiRegion?: boolean
370-
}
371-
> = {
372-
/*
373-
* This JSON generated by AWS's AI assistant - Amazon Q on March 29, 2025
374-
*
375-
* - Africa (Cape Town) region does not appear to support Amazon Bedrock at this time.
376-
* - Some Asia Pacific regions, such as Asia Pacific (Hong Kong) and Asia Pacific (Jakarta), are not listed among the supported regions for Bedrock services.
377-
* - Middle East regions, including Middle East (Bahrain) and Middle East (UAE), are not mentioned in the list of supported regions for Bedrock. [3]
378-
* - China regions (Beijing and Ningxia) are not listed as supported for Amazon Bedrock.
379-
* - Some newer or specialized AWS regions may not have Bedrock support yet.
380-
*/
381-
"us.": { regionId: "us-east-1", description: "US East (N. Virginia)", pattern: "us-", multiRegion: true },
382-
"use.": { regionId: "us-east-1", description: "US East (N. Virginia)" },
383-
"use1.": { regionId: "us-east-1", description: "US East (N. Virginia)" },
384-
"use2.": { regionId: "us-east-2", description: "US East (Ohio)" },
385-
"usw.": { regionId: "us-west-2", description: "US West (Oregon)" },
386-
"usw2.": { regionId: "us-west-2", description: "US West (Oregon)" },
387-
"ug.": {
388-
regionId: "us-gov-west-1",
389-
description: "AWS GovCloud (US-West)",
390-
pattern: "us-gov-",
391-
multiRegion: true,
392-
},
393-
"uge1.": { regionId: "us-gov-east-1", description: "AWS GovCloud (US-East)" },
394-
"ugw1.": { regionId: "us-gov-west-1", description: "AWS GovCloud (US-West)" },
395-
"eu.": { regionId: "eu-west-1", description: "Europe (Ireland)", pattern: "eu-", multiRegion: true },
396-
"euw1.": { regionId: "eu-west-1", description: "Europe (Ireland)" },
397-
"euw2.": { regionId: "eu-west-2", description: "Europe (London)" },
398-
"euw3.": { regionId: "eu-west-3", description: "Europe (Paris)" },
399-
"euc1.": { regionId: "eu-central-1", description: "Europe (Frankfurt)" },
400-
"euc2.": { regionId: "eu-central-2", description: "Europe (Zurich)" },
401-
"eun1.": { regionId: "eu-north-1", description: "Europe (Stockholm)" },
402-
"eus1.": { regionId: "eu-south-1", description: "Europe (Milan)" },
403-
"eus2.": { regionId: "eu-south-2", description: "Europe (Spain)" },
404-
"ap.": {
405-
regionId: "ap-southeast-1",
406-
description: "Asia Pacific (Singapore)",
407-
pattern: "ap-",
408-
multiRegion: true,
409-
},
410-
"ape1.": { regionId: "ap-east-1", description: "Asia Pacific (Hong Kong)" },
411-
"apne1.": { regionId: "ap-northeast-1", description: "Asia Pacific (Tokyo)" },
412-
"apne2.": { regionId: "ap-northeast-2", description: "Asia Pacific (Seoul)" },
413-
"apne3.": { regionId: "ap-northeast-3", description: "Asia Pacific (Osaka)" },
414-
"aps1.": { regionId: "ap-south-1", description: "Asia Pacific (Mumbai)" },
415-
"aps2.": { regionId: "ap-south-2", description: "Asia Pacific (Hyderabad)" },
416-
"apse1.": { regionId: "ap-southeast-1", description: "Asia Pacific (Singapore)" },
417-
"apse2.": { regionId: "ap-southeast-2", description: "Asia Pacific (Sydney)" },
418-
"ca.": { regionId: "ca-central-1", description: "Canada (Central)", pattern: "ca-", multiRegion: true },
419-
"cac1.": { regionId: "ca-central-1", description: "Canada (Central)" },
420-
"sa.": { regionId: "sa-east-1", description: "South America (São Paulo)", pattern: "sa-", multiRegion: true },
421-
"sae1.": { regionId: "sa-east-1", description: "South America (São Paulo)" },
363+
// AWS Bedrock Inference Profile mapping based on official documentation
364+
// https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-support.html
365+
// This mapping is pre-ordered by pattern length (descending) to ensure more specific patterns match first
366+
export const AWS_INFERENCE_PROFILE_MAPPING: Array<[string, string]> = [
367+
// US Government Cloud → ug. inference profile (most specific prefix first)
368+
["us-gov-", "ug."],
369+
// Americas regions → us. inference profile
370+
["us-", "us."],
371+
// Europe regions → eu. inference profile
372+
["eu-", "eu."],
373+
// Asia Pacific regions → apac. inference profile
374+
["ap-", "apac."],
375+
// Canada regions → ca. inference profile
376+
["ca-", "ca."],
377+
// South America regions → sa. inference profile
378+
["sa-", "sa."],
379+
]
422380

423-
// These are not official - they weren't generated by Amazon Q nor were
424-
// found in the AWS documentation but another Roo contributor found apac.
425-
// Was needed so I've added the pattern of the other geo zones.
426-
"apac.": { regionId: "ap-southeast-1", description: "Default APAC region", pattern: "ap-", multiRegion: true },
427-
"emea.": { regionId: "eu-west-1", description: "Default EMEA region", pattern: "eu-", multiRegion: true },
428-
"amer.": { regionId: "us-east-1", description: "Default Americas region", pattern: "us-", multiRegion: true },
429-
}
430-
431-
export const BEDROCK_REGIONS = Object.values(BEDROCK_REGION_INFO)
432-
// Extract all region IDs
433-
.map((info) => ({ value: info.regionId, label: info.regionId }))
434-
// Filter to unique region IDs (remove duplicates)
435-
.filter((region, index, self) => index === self.findIndex((r) => r.value === region.value))
436-
// Sort alphabetically by region ID
437-
.sort((a, b) => a.value.localeCompare(b.value))
381+
// AWS Bedrock supported regions for the regions dropdown
382+
// Based on official AWS documentation
383+
export const BEDROCK_REGIONS = [
384+
{ value: "us-east-1", label: "us-east-1" },
385+
{ value: "us-east-2", label: "us-east-2" },
386+
{ value: "us-west-1", label: "us-west-1" },
387+
{ value: "us-west-2", label: "us-west-2" },
388+
{ value: "ap-northeast-1", label: "ap-northeast-1" },
389+
{ value: "ap-northeast-2", label: "ap-northeast-2" },
390+
{ value: "ap-northeast-3", label: "ap-northeast-3" },
391+
{ value: "ap-south-1", label: "ap-south-1" },
392+
{ value: "ap-south-2", label: "ap-south-2" },
393+
{ value: "ap-southeast-1", label: "ap-southeast-1" },
394+
{ value: "ap-southeast-2", label: "ap-southeast-2" },
395+
{ value: "ap-east-1", label: "ap-east-1" },
396+
{ value: "eu-central-1", label: "eu-central-1" },
397+
{ value: "eu-central-2", label: "eu-central-2" },
398+
{ value: "eu-west-1", label: "eu-west-1" },
399+
{ value: "eu-west-2", label: "eu-west-2" },
400+
{ value: "eu-west-3", label: "eu-west-3" },
401+
{ value: "eu-north-1", label: "eu-north-1" },
402+
{ value: "eu-south-1", label: "eu-south-1" },
403+
{ value: "eu-south-2", label: "eu-south-2" },
404+
{ value: "ca-central-1", label: "ca-central-1" },
405+
{ value: "sa-east-1", label: "sa-east-1" },
406+
{ value: "us-gov-east-1", label: "us-gov-east-1" },
407+
{ value: "us-gov-west-1", label: "us-gov-west-1" },
408+
].sort((a, b) => a.value.localeCompare(b.value))
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
// npx vitest run src/api/providers/__tests__/bedrock-inference-profiles.spec.ts
2+
3+
import { AWS_INFERENCE_PROFILE_MAPPING } from "@roo-code/types"
4+
import { AwsBedrockHandler } from "../bedrock"
5+
import { ApiHandlerOptions } from "../../../shared/api"
6+
7+
// Mock AWS SDK
8+
vitest.mock("@aws-sdk/client-bedrock-runtime", () => {
9+
return {
10+
BedrockRuntimeClient: vitest.fn().mockImplementation(() => ({
11+
send: vitest.fn(),
12+
config: { region: "us-east-1" },
13+
})),
14+
ConverseCommand: vitest.fn(),
15+
ConverseStreamCommand: vitest.fn(),
16+
}
17+
})
18+
19+
describe("AWS Bedrock Inference Profiles", () => {
20+
// Helper function to create a handler with specific options
21+
const createHandler = (options: Partial<ApiHandlerOptions> = {}) => {
22+
const defaultOptions: ApiHandlerOptions = {
23+
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
24+
awsRegion: "us-east-1",
25+
...options,
26+
}
27+
return new AwsBedrockHandler(defaultOptions)
28+
}
29+
30+
describe("AWS_INFERENCE_PROFILE_MAPPING constant", () => {
31+
it("should contain all expected region mappings", () => {
32+
expect(AWS_INFERENCE_PROFILE_MAPPING).toEqual([
33+
["us-gov-", "ug."],
34+
["us-", "us."],
35+
["eu-", "eu."],
36+
["ap-", "apac."],
37+
["ca-", "ca."],
38+
["sa-", "sa."],
39+
])
40+
})
41+
42+
it("should be ordered by pattern length (descending)", () => {
43+
const lengths = AWS_INFERENCE_PROFILE_MAPPING.map(([pattern]) => pattern.length)
44+
const sortedLengths = [...lengths].sort((a, b) => b - a)
45+
expect(lengths).toEqual(sortedLengths)
46+
})
47+
48+
it("should have valid inference profile prefixes", () => {
49+
AWS_INFERENCE_PROFILE_MAPPING.forEach(([regionPattern, inferenceProfile]) => {
50+
expect(regionPattern).toMatch(/^[a-z-]+$/)
51+
expect(inferenceProfile).toMatch(/^[a-z]+\.$/)
52+
})
53+
})
54+
})
55+
56+
describe("getPrefixForRegion function", () => {
57+
it("should return correct prefix for US government regions", () => {
58+
const handler = createHandler()
59+
expect((handler as any).constructor.getPrefixForRegion("us-gov-east-1")).toBe("ug.")
60+
expect((handler as any).constructor.getPrefixForRegion("us-gov-west-1")).toBe("ug.")
61+
})
62+
63+
it("should return correct prefix for US commercial regions", () => {
64+
const handler = createHandler()
65+
expect((handler as any).constructor.getPrefixForRegion("us-east-1")).toBe("us.")
66+
expect((handler as any).constructor.getPrefixForRegion("us-west-1")).toBe("us.")
67+
expect((handler as any).constructor.getPrefixForRegion("us-west-2")).toBe("us.")
68+
})
69+
70+
it("should return correct prefix for European regions", () => {
71+
const handler = createHandler()
72+
expect((handler as any).constructor.getPrefixForRegion("eu-west-1")).toBe("eu.")
73+
expect((handler as any).constructor.getPrefixForRegion("eu-central-1")).toBe("eu.")
74+
expect((handler as any).constructor.getPrefixForRegion("eu-north-1")).toBe("eu.")
75+
expect((handler as any).constructor.getPrefixForRegion("eu-south-1")).toBe("eu.")
76+
})
77+
78+
it("should return correct prefix for Asia Pacific regions", () => {
79+
const handler = createHandler()
80+
expect((handler as any).constructor.getPrefixForRegion("ap-southeast-1")).toBe("apac.")
81+
expect((handler as any).constructor.getPrefixForRegion("ap-northeast-1")).toBe("apac.")
82+
expect((handler as any).constructor.getPrefixForRegion("ap-south-1")).toBe("apac.")
83+
expect((handler as any).constructor.getPrefixForRegion("ap-east-1")).toBe("apac.")
84+
})
85+
86+
it("should return correct prefix for Canada regions", () => {
87+
const handler = createHandler()
88+
expect((handler as any).constructor.getPrefixForRegion("ca-central-1")).toBe("ca.")
89+
expect((handler as any).constructor.getPrefixForRegion("ca-west-1")).toBe("ca.")
90+
})
91+
92+
it("should return correct prefix for South America regions", () => {
93+
const handler = createHandler()
94+
expect((handler as any).constructor.getPrefixForRegion("sa-east-1")).toBe("sa.")
95+
})
96+
97+
it("should return undefined for unsupported regions", () => {
98+
const handler = createHandler()
99+
expect((handler as any).constructor.getPrefixForRegion("af-south-1")).toBeUndefined()
100+
expect((handler as any).constructor.getPrefixForRegion("me-south-1")).toBeUndefined()
101+
expect((handler as any).constructor.getPrefixForRegion("cn-north-1")).toBeUndefined()
102+
expect((handler as any).constructor.getPrefixForRegion("invalid-region")).toBeUndefined()
103+
})
104+
105+
it("should prioritize longer patterns over shorter ones", () => {
106+
const handler = createHandler()
107+
// us-gov- should be matched before us-
108+
expect((handler as any).constructor.getPrefixForRegion("us-gov-east-1")).toBe("ug.")
109+
expect((handler as any).constructor.getPrefixForRegion("us-gov-west-1")).toBe("ug.")
110+
111+
// Regular us- regions should still work
112+
expect((handler as any).constructor.getPrefixForRegion("us-east-1")).toBe("us.")
113+
expect((handler as any).constructor.getPrefixForRegion("us-west-2")).toBe("us.")
114+
})
115+
})
116+
117+
describe("Cross-region inference integration", () => {
118+
it("should apply ug. prefix for US government regions", () => {
119+
const handler = createHandler({
120+
awsUseCrossRegionInference: true,
121+
awsRegion: "us-gov-east-1",
122+
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
123+
})
124+
125+
const model = handler.getModel()
126+
expect(model.id).toBe("ug.anthropic.claude-3-sonnet-20240229-v1:0")
127+
})
128+
129+
it("should apply us. prefix for US commercial regions", () => {
130+
const handler = createHandler({
131+
awsUseCrossRegionInference: true,
132+
awsRegion: "us-east-1",
133+
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
134+
})
135+
136+
const model = handler.getModel()
137+
expect(model.id).toBe("us.anthropic.claude-3-sonnet-20240229-v1:0")
138+
})
139+
140+
it("should apply eu. prefix for European regions", () => {
141+
const handler = createHandler({
142+
awsUseCrossRegionInference: true,
143+
awsRegion: "eu-west-1",
144+
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
145+
})
146+
147+
const model = handler.getModel()
148+
expect(model.id).toBe("eu.anthropic.claude-3-sonnet-20240229-v1:0")
149+
})
150+
151+
it("should apply apac. prefix for Asia Pacific regions", () => {
152+
const handler = createHandler({
153+
awsUseCrossRegionInference: true,
154+
awsRegion: "ap-southeast-1",
155+
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
156+
})
157+
158+
const model = handler.getModel()
159+
expect(model.id).toBe("apac.anthropic.claude-3-sonnet-20240229-v1:0")
160+
})
161+
162+
it("should apply ca. prefix for Canada regions", () => {
163+
const handler = createHandler({
164+
awsUseCrossRegionInference: true,
165+
awsRegion: "ca-central-1",
166+
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
167+
})
168+
169+
const model = handler.getModel()
170+
expect(model.id).toBe("ca.anthropic.claude-3-sonnet-20240229-v1:0")
171+
})
172+
173+
it("should apply sa. prefix for South America regions", () => {
174+
const handler = createHandler({
175+
awsUseCrossRegionInference: true,
176+
awsRegion: "sa-east-1",
177+
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
178+
})
179+
180+
const model = handler.getModel()
181+
expect(model.id).toBe("sa.anthropic.claude-3-sonnet-20240229-v1:0")
182+
})
183+
184+
it("should not apply prefix when cross-region inference is disabled", () => {
185+
const handler = createHandler({
186+
awsUseCrossRegionInference: false,
187+
awsRegion: "us-gov-east-1",
188+
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
189+
})
190+
191+
const model = handler.getModel()
192+
expect(model.id).toBe("anthropic.claude-3-sonnet-20240229-v1:0")
193+
})
194+
195+
it("should handle unsupported regions gracefully", () => {
196+
const handler = createHandler({
197+
awsUseCrossRegionInference: true,
198+
awsRegion: "af-south-1", // Unsupported region
199+
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
200+
})
201+
202+
const model = handler.getModel()
203+
// Should remain unchanged when no prefix is found
204+
expect(model.id).toBe("anthropic.claude-3-sonnet-20240229-v1:0")
205+
})
206+
207+
it("should work with different model IDs", () => {
208+
const testModels = [
209+
"anthropic.claude-3-haiku-20240307-v1:0",
210+
"anthropic.claude-3-opus-20240229-v1:0",
211+
"amazon.nova-pro-v1:0",
212+
"meta.llama3-1-70b-instruct-v1:0",
213+
]
214+
215+
testModels.forEach((modelId) => {
216+
const handler = createHandler({
217+
awsUseCrossRegionInference: true,
218+
awsRegion: "eu-west-1",
219+
apiModelId: modelId,
220+
})
221+
222+
const model = handler.getModel()
223+
expect(model.id).toBe(`eu.${modelId}`)
224+
})
225+
})
226+
227+
it("should prioritize us-gov- over us- in cross-region inference", () => {
228+
// Test that us-gov-east-1 gets ug. prefix, not us.
229+
const govHandler = createHandler({
230+
awsUseCrossRegionInference: true,
231+
awsRegion: "us-gov-east-1",
232+
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
233+
})
234+
235+
const govModel = govHandler.getModel()
236+
expect(govModel.id).toBe("ug.anthropic.claude-3-sonnet-20240229-v1:0")
237+
238+
// Test that regular us-east-1 still gets us. prefix
239+
const usHandler = createHandler({
240+
awsUseCrossRegionInference: true,
241+
awsRegion: "us-east-1",
242+
apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0",
243+
})
244+
245+
const usModel = usHandler.getModel()
246+
expect(usModel.id).toBe("us.anthropic.claude-3-sonnet-20240229-v1:0")
247+
})
248+
})
249+
})

0 commit comments

Comments
 (0)