Add support for bedrock api keys (#6132)

Co-authored-by: Roo Code <roomote@roocode.com>
This commit is contained in:
John Richmond 2025-07-24 22:04:49 -07:00 committed by GitHub
parent b1406342a8
commit d62a260576
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 858 additions and 580 deletions

View file

@ -164,6 +164,7 @@ export const SECRET_STATE_KEYS = [
"glamaApiKey",
"openRouterApiKey",
"awsAccessKey",
"awsApiKey",
"awsSecretKey",
"awsSessionToken",
"openAiApiKey",

View file

@ -114,6 +114,8 @@ const bedrockSchema = apiModelIdProviderModelSchema.extend({
awsUsePromptCache: z.boolean().optional(),
awsProfile: z.string().optional(),
awsUseProfile: z.boolean().optional(),
awsApiKey: z.string().optional(),
awsUseApiKey: z.boolean().optional(),
awsCustomArn: z.string().optional(),
awsModelContextWindow: z.number().optional(),
awsBedrockEndpointEnabled: z.boolean().optional(),

View file

@ -360,7 +360,7 @@ export const BEDROCK_MAX_TOKENS = 4096
export const BEDROCK_DEFAULT_CONTEXT = 128_000
// AWS Bedrock Inference Profile mapping based on official documentation
// Amazon 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]> = [
@ -378,7 +378,7 @@ export const AWS_INFERENCE_PROFILE_MAPPING: Array<[string, string]> = [
["sa-", "sa."],
]
// AWS Bedrock supported regions for the regions dropdown
// Amazon Bedrock supported regions for the regions dropdown
// Based on official AWS documentation
export const BEDROCK_REGIONS = [
{ value: "us-east-1", label: "us-east-1" },

1147
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -16,7 +16,7 @@ vitest.mock("@aws-sdk/client-bedrock-runtime", () => {
}
})
describe("AWS Bedrock Inference Profiles", () => {
describe("Amazon Bedrock Inference Profiles", () => {
// Helper function to create a handler with specific options
const createHandler = (options: Partial<ApiHandlerOptions> = {}) => {
const defaultOptions: ApiHandlerOptions = {

View file

@ -278,5 +278,51 @@ describe("AwsBedrockHandler - Extended Thinking", () => {
expect(reasoningChunks[0].text).toBe("Let me think...")
expect(reasoningChunks[1].text).toBe(" about this problem.")
})
it("should support API key authentication", async () => {
handler = new AwsBedrockHandler({
apiProvider: "bedrock",
apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
awsRegion: "us-east-1",
awsUseApiKey: true,
awsApiKey: "test-api-key-token",
})
mockSend.mockResolvedValue({
stream: (async function* () {
yield { messageStart: { role: "assistant" } }
yield {
contentBlockStart: {
start: { text: "Hello from API key auth" },
contentBlockIndex: 0,
},
}
yield { metadata: { usage: { inputTokens: 100, outputTokens: 50 } } }
})(),
})
const messages = [{ role: "user" as const, content: "Test message" }]
const stream = handler.createMessage("System prompt", messages)
const chunks = []
for await (const chunk of stream) {
chunks.push(chunk)
}
// Verify the client was created with API key token
expect(BedrockRuntimeClient).toHaveBeenCalledWith(
expect.objectContaining({
region: "us-east-1",
token: { token: "test-api-key-token" },
authSchemePreference: ["httpBearerAuth"],
}),
)
// Verify the stream worked correctly
expect(mockSend).toHaveBeenCalledTimes(1)
const textChunks = chunks.filter((c) => c.type === "text")
expect(textChunks).toHaveLength(1)
expect(textChunks[0].text).toBe("Hello from API key auth")
})
})
})

View file

@ -29,7 +29,7 @@ import { BedrockRuntimeClient } from "@aws-sdk/client-bedrock-runtime"
// Get access to the mocked functions
const mockBedrockRuntimeClient = vi.mocked(BedrockRuntimeClient)
describe("AWS Bedrock VPC Endpoint Functionality", () => {
describe("Amazon Bedrock VPC Endpoint Functionality", () => {
beforeEach(() => {
// Clear all mocks before each test
vi.clearAllMocks()

View file

@ -222,7 +222,11 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
this.options.awsBedrockEndpointEnabled && { endpoint: this.options.awsBedrockEndpoint }),
}
if (this.options.awsUseProfile && this.options.awsProfile) {
if (this.options.awsUseApiKey && this.options.awsApiKey) {
// Use API key/token-based authentication if enabled and API key is set
clientConfig.token = { token: this.options.awsApiKey }
clientConfig.authSchemePreference = ["httpBearerAuth"] // Otherwise there's no end of credential problems.
} else if (this.options.awsUseProfile && this.options.awsProfile) {
// Use profile-based credentials if enabled and profile is set
clientConfig.credentials = fromIni({
profile: this.options.awsProfile,
@ -1078,7 +1082,7 @@ Please verify:
"throttl",
"rate",
"limit",
"bedrock is unable to process your request", // AWS Bedrock specific throttling message
"bedrock is unable to process your request", // Amazon Bedrock specific throttling message
"please wait",
"quota exceeded",
"service unavailable",
@ -1124,7 +1128,7 @@ Suggestions:
Please try:
1. Contact AWS support to request a quota increase
2. Reduce request frequency temporarily
3. Check your AWS Bedrock quotas in the AWS console
3. Check your Amazon Bedrock quotas in the AWS console
4. Consider using a different model or region with available capacity
`,
@ -1139,7 +1143,7 @@ Please try:
Please try:
1. Wait a few minutes and retry
2. Check the model status in AWS Bedrock console
2. Check the model status in Amazon Bedrock console
3. Verify the model is properly provisioned
`,
@ -1147,7 +1151,7 @@ Please try:
},
INTERNAL_SERVER_ERROR: {
patterns: ["internal server error", "internal error", "server error", "service error"],
messageTemplate: `AWS Bedrock internal server error. This is a temporary service issue.
messageTemplate: `Amazon Bedrock internal server error. This is a temporary service issue.
Please try:
1. Retry the request after a brief delay
@ -1184,7 +1188,7 @@ Please try:
],
messageTemplate: `Parameter validation error: {errorMessage}
This error indicates that the request parameters don't match AWS Bedrock's expected format.
This error indicates that the request parameters don't match Amazon Bedrock's expected format.
Common causes:
1. Extended thinking parameter format is incorrect
@ -1193,7 +1197,7 @@ Common causes:
Please check:
- Model supports the requested features (extended thinking, etc.)
- Parameter format matches AWS Bedrock specification
- Parameter format matches Amazon Bedrock specification
- Model ID is correct for the requested features`,
logLevel: "error",
},
@ -1218,7 +1222,7 @@ Please check:
return "THROTTLING"
}
// Check for AWS Bedrock specific throttling exception names
// Check for Amazon Bedrock specific throttling exception names
if ((error as any).name === "ThrottlingException" || (error as any).__type === "ThrottlingException") {
return "THROTTLING"
}

View file

@ -413,8 +413,8 @@
"@anthropic-ai/bedrock-sdk": "^0.10.2",
"@anthropic-ai/sdk": "^0.37.0",
"@anthropic-ai/vertex-sdk": "^0.7.0",
"@aws-sdk/client-bedrock-runtime": "^3.779.0",
"@aws-sdk/credential-providers": "^3.806.0",
"@aws-sdk/client-bedrock-runtime": "^3.848.0",
"@aws-sdk/credential-providers": "^3.848.0",
"@google/genai": "^1.0.0",
"@lmstudio/sdk": "^1.1.1",
"@mistralai/mistralai": "^1.3.6",

View file

@ -1,6 +1,6 @@
import { useCallback, useState, useEffect } from "react"
import { Checkbox } from "vscrui"
import { VSCodeTextField, VSCodeRadio, VSCodeRadioGroup } from "@vscode/webview-ui-toolkit/react"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { type ProviderSettings, type ModelInfo, BEDROCK_REGIONS } from "@roo-code/types"
@ -37,19 +37,51 @@ export const Bedrock = ({ apiConfiguration, setApiConfigurationField, selectedMo
return (
<>
<VSCodeRadioGroup
value={apiConfiguration?.awsUseProfile ? "profile" : "credentials"}
onChange={handleInputChange(
"awsUseProfile",
(e) => (e.target as HTMLInputElement).value === "profile",
)}>
<VSCodeRadio value="credentials">{t("settings:providers.awsCredentials")}</VSCodeRadio>
<VSCodeRadio value="profile">{t("settings:providers.awsProfile")}</VSCodeRadio>
</VSCodeRadioGroup>
<div>
<label className="block font-medium mb-1">Authentication Method</label>
<Select
value={
apiConfiguration?.awsUseApiKey
? "apikey"
: apiConfiguration?.awsUseProfile
? "profile"
: "credentials"
}
onValueChange={(value) => {
if (value === "apikey") {
setApiConfigurationField("awsUseApiKey", true)
setApiConfigurationField("awsUseProfile", false)
} else if (value === "profile") {
setApiConfigurationField("awsUseApiKey", false)
setApiConfigurationField("awsUseProfile", true)
} else {
setApiConfigurationField("awsUseApiKey", false)
setApiConfigurationField("awsUseProfile", false)
}
}}>
<SelectTrigger className="w-full">
<SelectValue placeholder={t("settings:common.select")} />
</SelectTrigger>
<SelectContent>
<SelectItem value="credentials">{t("settings:providers.awsCredentials")}</SelectItem>
<SelectItem value="profile">{t("settings:providers.awsProfile")}</SelectItem>
<SelectItem value="apikey">{t("settings:providers.awsApiKey")}</SelectItem>
</SelectContent>
</Select>
</div>
<div className="text-sm text-vscode-descriptionForeground -mt-3">
{t("settings:providers.apiKeyStorageNotice")}
</div>
{apiConfiguration?.awsUseProfile ? (
{apiConfiguration?.awsUseApiKey ? (
<VSCodeTextField
value={apiConfiguration?.awsApiKey || ""}
type="password"
onInput={handleInputChange("awsApiKey")}
placeholder={t("settings:placeholders.apiKey")}
className="w-full">
<label className="block font-medium mb-1">{t("settings:providers.awsApiKey")}</label>
</VSCodeTextField>
) : apiConfiguration?.awsUseProfile ? (
<VSCodeTextField
value={apiConfiguration?.awsProfile || ""}
onInput={handleInputChange("awsProfile")}

View file

@ -61,11 +61,16 @@ vi.mock("@src/i18n/TranslationContext", () => ({
// Mock the UI components
vi.mock("@src/components/ui", () => ({
Select: ({ children }: any) => <div>{children}</div>,
SelectContent: ({ children }: any) => <div>{children}</div>,
SelectItem: () => <div>Item</div>,
SelectTrigger: ({ children }: any) => <div>{children}</div>,
SelectValue: () => <div>Value</div>,
Select: ({ children, value, onValueChange }: any) => (
<select value={value} onChange={(e) => onValueChange && onValueChange(e.target.value)}>
{children}
</select>
),
SelectContent: ({ children }: any) => <>{children}</>,
SelectItem: ({ children, value }: any) => <option value={value}>{children}</option>,
SelectTrigger: ({ children }: any) => <>{children}</>,
SelectValue: () => null,
StandardTooltip: ({ children }: any) => <div>{children}</div>,
}))
// Mock the constants
@ -424,5 +429,126 @@ describe("Bedrock Component", () => {
expect(screen.getByTestId("vpc-endpoint-input")).toBeInTheDocument()
expect(screen.getByTestId("vpc-endpoint-input")).toHaveValue("https://updated-endpoint.aws.com")
})
// Test Scenario 6: Authentication Method Selection Tests
describe("Authentication Method Selection", () => {
it("should display credentials option as selected when neither awsUseProfile nor awsUseApiKey is true", () => {
const apiConfiguration: Partial<ProviderSettings> = {
awsUseProfile: false,
awsUseApiKey: false,
}
render(
<Bedrock
apiConfiguration={apiConfiguration as ProviderSettings}
setApiConfigurationField={mockSetApiConfigurationField}
/>,
)
// Find the first select element (authentication method)
const selectInputs = screen.getAllByRole("combobox")
const authSelect = selectInputs[0] as HTMLSelectElement
expect(authSelect).toHaveValue("credentials")
})
it("should display profile option as selected when awsUseProfile is true", () => {
const apiConfiguration: Partial<ProviderSettings> = {
awsUseProfile: true,
awsUseApiKey: false,
}
render(
<Bedrock
apiConfiguration={apiConfiguration as ProviderSettings}
setApiConfigurationField={mockSetApiConfigurationField}
/>,
)
const selectInputs = screen.getAllByRole("combobox")
const authSelect = selectInputs[0] as HTMLSelectElement
expect(authSelect).toHaveValue("profile")
})
it("should display apikey option as selected when awsUseApiKey is true", () => {
const apiConfiguration: Partial<ProviderSettings> = {
awsUseProfile: false,
awsUseApiKey: true,
}
render(
<Bedrock
apiConfiguration={apiConfiguration as ProviderSettings}
setApiConfigurationField={mockSetApiConfigurationField}
/>,
)
const selectInputs = screen.getAllByRole("combobox")
const authSelect = selectInputs[0] as HTMLSelectElement
expect(authSelect).toHaveValue("apikey")
})
it("should call setApiConfigurationField correctly when switching to profile", () => {
const apiConfiguration: Partial<ProviderSettings> = {
awsUseProfile: false,
awsUseApiKey: false,
}
render(
<Bedrock
apiConfiguration={apiConfiguration as ProviderSettings}
setApiConfigurationField={mockSetApiConfigurationField}
/>,
)
const selectInputs = screen.getAllByRole("combobox")
const authSelect = selectInputs[0] as HTMLSelectElement
fireEvent.change(authSelect, { target: { value: "profile" } })
expect(mockSetApiConfigurationField).toHaveBeenCalledWith("awsUseApiKey", false)
expect(mockSetApiConfigurationField).toHaveBeenCalledWith("awsUseProfile", true)
})
it("should call setApiConfigurationField correctly when switching to apikey", () => {
const apiConfiguration: Partial<ProviderSettings> = {
awsUseProfile: false,
awsUseApiKey: false,
}
render(
<Bedrock
apiConfiguration={apiConfiguration as ProviderSettings}
setApiConfigurationField={mockSetApiConfigurationField}
/>,
)
const selectInputs = screen.getAllByRole("combobox")
const authSelect = selectInputs[0] as HTMLSelectElement
fireEvent.change(authSelect, { target: { value: "apikey" } })
expect(mockSetApiConfigurationField).toHaveBeenCalledWith("awsUseApiKey", true)
expect(mockSetApiConfigurationField).toHaveBeenCalledWith("awsUseProfile", false)
})
it("should call setApiConfigurationField correctly when switching to credentials", () => {
const apiConfiguration: Partial<ProviderSettings> = {
awsUseProfile: true,
awsUseApiKey: false,
}
render(
<Bedrock
apiConfiguration={apiConfiguration as ProviderSettings}
setApiConfigurationField={mockSetApiConfigurationField}
/>,
)
const selectInputs = screen.getAllByRole("combobox")
const authSelect = selectInputs[0] as HTMLSelectElement
fireEvent.change(authSelect, { target: { value: "credentials" } })
expect(mockSetApiConfigurationField).toHaveBeenCalledWith("awsUseApiKey", false)
expect(mockSetApiConfigurationField).toHaveBeenCalledWith("awsUseProfile", false)
})
})
})
})

View file

@ -287,6 +287,7 @@
"litellmBaseUrl": "URL base de LiteLLM",
"awsCredentials": "Credencials d'AWS",
"awsProfile": "Perfil d'AWS",
"awsApiKey": "Clau d'API d'Amazon Bedrock",
"awsProfileName": "Nom del perfil d'AWS",
"awsAccessKey": "Clau d'accés d'AWS",
"awsSecretKey": "Clau secreta d'AWS",

View file

@ -287,6 +287,7 @@
"litellmBaseUrl": "LiteLLM Basis-URL",
"awsCredentials": "AWS Anmeldedaten",
"awsProfile": "AWS Profil",
"awsApiKey": "Amazon Bedrock API-Schlüssel",
"awsProfileName": "AWS Profilname",
"awsAccessKey": "AWS Zugangsschlüssel",
"awsSecretKey": "AWS Geheimschlüssel",

View file

@ -287,6 +287,7 @@
"litellmBaseUrl": "LiteLLM Base URL",
"awsCredentials": "AWS Credentials",
"awsProfile": "AWS Profile",
"awsApiKey": "Amazon Bedrock API Key",
"awsProfileName": "AWS Profile Name",
"awsAccessKey": "AWS Access Key",
"awsSecretKey": "AWS Secret Key",

View file

@ -287,6 +287,7 @@
"litellmBaseUrl": "URL base de LiteLLM",
"awsCredentials": "Credenciales de AWS",
"awsProfile": "Perfil de AWS",
"awsApiKey": "Clave de API de Amazon Bedrock",
"awsProfileName": "Nombre del perfil de AWS",
"awsAccessKey": "Clave de acceso de AWS",
"awsSecretKey": "Clave secreta de AWS",

View file

@ -287,6 +287,7 @@
"litellmBaseUrl": "URL de base LiteLLM",
"awsCredentials": "Identifiants AWS",
"awsProfile": "Profil AWS",
"awsApiKey": "Clé API Amazon Bedrock",
"awsProfileName": "Nom du profil AWS",
"awsAccessKey": "Clé d'accès AWS",
"awsSecretKey": "Clé secrète AWS",

View file

@ -221,7 +221,7 @@
"noProviderMatchFound": "कोई प्रदाता नहीं मिला",
"noMatchFound": "कोई मिलान प्रोफ़ाइल नहीं मिला",
"vscodeLmDescription": "VS कोड भाषा मॉडल API आपको अन्य VS कोड एक्सटेंशन (जैसे GitHub Copilot) द्वारा प्रदान किए गए मॉडल चलाने की अनुमति देता है। शुरू करने का सबसे आसान तरीका VS कोड मार्केटप्लेस से Copilot और Copilot चैट एक्सटेंशन इंस्टॉल करना है।",
"awsCustomArnUse": "आप जिस मॉडल का उपयोग करना चाहते हैं, उसके लिए एक वैध AWS बेडरॉक ARN दर्ज करें। प्रारूप उदाहरण:",
"awsCustomArnUse": "आप जिस मॉडल का उपयोग करना चाहते हैं, उसके लिए एक वैध Amazon बेडरॉक ARN दर्ज करें। प्रारूप उदाहरण:",
"awsCustomArnDesc": "सुनिश्चित करें कि ARN में क्षेत्र ऊपर चयनित AWS क्षेत्र से मेल खाता है।",
"openRouterApiKey": "OpenRouter API कुंजी",
"getOpenRouterApiKey": "OpenRouter API कुंजी प्राप्त करें",
@ -287,6 +287,7 @@
"litellmBaseUrl": "LiteLLM आधार URL",
"awsCredentials": "AWS क्रेडेंशियल्स",
"awsProfile": "AWS प्रोफाइल",
"awsApiKey": "Amazon बेडरॉक API कुंजी",
"awsProfileName": "AWS प्रोफाइल नाम",
"awsAccessKey": "AWS एक्सेस कुंजी",
"awsSecretKey": "AWS सीक्रेट कुंजी",

View file

@ -291,6 +291,7 @@
"litellmBaseUrl": "LiteLLM Base URL",
"awsCredentials": "AWS Credentials",
"awsProfile": "AWS Profile",
"awsApiKey": "Kunci API Amazon Bedrock",
"awsProfileName": "Nama AWS Profile",
"awsAccessKey": "AWS Access Key",
"awsSecretKey": "AWS Secret Key",

View file

@ -287,6 +287,7 @@
"litellmBaseUrl": "URL base LiteLLM",
"awsCredentials": "Credenziali AWS",
"awsProfile": "Profilo AWS",
"awsApiKey": "Chiave API Amazon Bedrock",
"awsProfileName": "Nome profilo AWS",
"awsAccessKey": "Chiave di accesso AWS",
"awsSecretKey": "Chiave segreta AWS",

View file

@ -287,6 +287,7 @@
"litellmBaseUrl": "LiteLLM ベースURL",
"awsCredentials": "AWS認証情報",
"awsProfile": "AWSプロファイル",
"awsApiKey": "Amazon Bedrock APIキー",
"awsProfileName": "AWSプロファイル名",
"awsAccessKey": "AWSアクセスキー",
"awsSecretKey": "AWSシークレットキー",

View file

@ -287,6 +287,7 @@
"litellmBaseUrl": "LiteLLM 기본 URL",
"awsCredentials": "AWS 자격 증명",
"awsProfile": "AWS 프로필",
"awsApiKey": "Amazon Bedrock API 키",
"awsProfileName": "AWS 프로필 이름",
"awsAccessKey": "AWS 액세스 키",
"awsSecretKey": "AWS 시크릿 키",

View file

@ -287,6 +287,7 @@
"litellmBaseUrl": "LiteLLM basis-URL",
"awsCredentials": "AWS-inloggegevens",
"awsProfile": "AWS-profiel",
"awsApiKey": "Amazon Bedrock API-sleutel",
"awsProfileName": "AWS-profielnaam",
"awsAccessKey": "AWS-toegangssleutel",
"awsSecretKey": "AWS-geheime sleutel",

View file

@ -287,6 +287,7 @@
"litellmBaseUrl": "URL bazowy LiteLLM",
"awsCredentials": "Poświadczenia AWS",
"awsProfile": "Profil AWS",
"awsApiKey": "Klucz API Amazon Bedrock",
"awsProfileName": "Nazwa profilu AWS",
"awsAccessKey": "Klucz dostępu AWS",
"awsSecretKey": "Klucz tajny AWS",

View file

@ -287,6 +287,7 @@
"litellmBaseUrl": "URL base LiteLLM",
"awsCredentials": "Credenciais AWS",
"awsProfile": "Perfil AWS",
"awsApiKey": "Chave de API Amazon Bedrock",
"awsProfileName": "Nome do Perfil AWS",
"awsAccessKey": "Chave de Acesso AWS",
"awsSecretKey": "Chave Secreta AWS",

View file

@ -287,6 +287,7 @@
"litellmBaseUrl": "Базовый URL LiteLLM",
"awsCredentials": "AWS-учётные данные",
"awsProfile": "Профиль AWS",
"awsApiKey": "Ключ API Amazon Bedrock",
"awsProfileName": "Имя профиля AWS",
"awsAccessKey": "AWS Access Key",
"awsSecretKey": "AWS Secret Key",

View file

@ -287,6 +287,7 @@
"litellmBaseUrl": "LiteLLM Temel URL",
"awsCredentials": "AWS Kimlik Bilgileri",
"awsProfile": "AWS Profili",
"awsApiKey": "Amazon Bedrock API Anahtarı",
"awsProfileName": "AWS Profil Adı",
"awsAccessKey": "AWS Erişim Anahtarı",
"awsSecretKey": "AWS Gizli Anahtarı",

View file

@ -287,6 +287,7 @@
"litellmBaseUrl": "URL cơ sở LiteLLM",
"awsCredentials": "Thông tin xác thực AWS",
"awsProfile": "Hồ sơ AWS",
"awsApiKey": "Khóa API Amazon Bedrock",
"awsProfileName": "Tên hồ sơ AWS",
"awsAccessKey": "Khóa truy cập AWS",
"awsSecretKey": "Khóa bí mật AWS",

View file

@ -287,6 +287,7 @@
"litellmBaseUrl": "LiteLLM 基础 URL",
"awsCredentials": "AWS 凭证",
"awsProfile": "AWS 配置文件",
"awsApiKey": "Amazon Bedrock API 密钥",
"awsProfileName": "AWS 配置文件名称",
"awsAccessKey": "AWS 访问密钥",
"awsSecretKey": "AWS 密钥",

View file

@ -287,6 +287,7 @@
"litellmBaseUrl": "LiteLLM 基礎 URL",
"awsCredentials": "AWS 認證",
"awsProfile": "AWS Profile",
"awsApiKey": "Amazon Bedrock API 金鑰",
"awsProfileName": "AWS Profile 名稱",
"awsAccessKey": "AWS Access Key",
"awsSecretKey": "AWS Secret Key",