-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Adding support for custom VPC endpoints when using AWS Bedrock models #3947
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 32 commits
cc01dd1
91b5557
3d3433b
019f2c6
941dcdf
5fcef88
cae91f2
8e9c4fa
24fe42b
1b16465
06e28fa
8f4d7c3
c5763b2
c9f7980
eb42c37
77328cf
e00780a
5cae994
03d5c03
a74b6fd
0b0ea16
8bd3701
617e68e
57a6fdf
7f90298
e40db2a
2720d07
b1e5152
bd398e4
fae7a6e
42be6dc
f32f146
01c7d2f
c5d2bf4
1ad74a4
e2f12af
afb7663
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "roo-cline": patch | ||
| --- | ||
|
|
||
| Add AWS Bedrock VPC endpoint support, allowing users to connect to Bedrock through private VPC endpoints. This includes UI configuration options, updated types, and comprehensive test coverage. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,178 @@ | ||
| // Mock AWS SDK credential providers | ||
| jest.mock("@aws-sdk/credential-providers", () => { | ||
| const mockFromIni = jest.fn().mockReturnValue({ | ||
| accessKeyId: "profile-access-key", | ||
| secretAccessKey: "profile-secret-key", | ||
| }) | ||
| return { fromIni: mockFromIni } | ||
| }) | ||
|
|
||
| // Mock BedrockRuntimeClient and ConverseStreamCommand | ||
| const mockBedrockRuntimeClient = jest.fn() | ||
| const mockSend = jest.fn().mockResolvedValue({ | ||
| stream: [], | ||
| }) | ||
|
|
||
| jest.mock("@aws-sdk/client-bedrock-runtime", () => ({ | ||
| BedrockRuntimeClient: mockBedrockRuntimeClient.mockImplementation(() => ({ | ||
| send: mockSend, | ||
| })), | ||
| ConverseStreamCommand: jest.fn(), | ||
| ConverseCommand: jest.fn(), | ||
| })) | ||
|
|
||
| import { AwsBedrockHandler } from "../bedrock" | ||
|
|
||
| describe("AWS Bedrock VPC Endpoint Functionality", () => { | ||
| beforeEach(() => { | ||
| // Clear all mocks before each test | ||
| jest.clearAllMocks() | ||
| }) | ||
|
|
||
| // Test Scenario 1: Input Validation Test | ||
| describe("VPC Endpoint URL Validation", () => { | ||
| it("should configure client with endpoint URL when both URL and enabled flag are provided", () => { | ||
| // Create handler with endpoint URL and enabled flag | ||
| new AwsBedrockHandler({ | ||
| apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", | ||
| awsAccessKey: "test-access-key", | ||
| awsSecretKey: "test-secret-key", | ||
| awsRegion: "us-east-1", | ||
| awsBedrockEndpoint: "https://bedrock-vpc.example.com", | ||
| awsBedrockEndpointEnabled: true, | ||
| }) | ||
|
|
||
| // Verify the client was created with the correct endpoint | ||
| expect(mockBedrockRuntimeClient).toHaveBeenCalledWith( | ||
| expect.objectContaining({ | ||
| region: "us-east-1", | ||
| endpoint: "https://bedrock-vpc.example.com", | ||
| }), | ||
| ) | ||
| }) | ||
|
|
||
| it("should not configure client with endpoint URL when URL is provided but enabled flag is false", () => { | ||
| // Create handler with endpoint URL but disabled flag | ||
| new AwsBedrockHandler({ | ||
| apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", | ||
| awsAccessKey: "test-access-key", | ||
| awsSecretKey: "test-secret-key", | ||
| awsRegion: "us-east-1", | ||
| awsBedrockEndpoint: "https://bedrock-vpc.example.com", | ||
| awsBedrockEndpointEnabled: false, | ||
| }) | ||
|
|
||
| // Verify the client was created without the endpoint | ||
| expect(mockBedrockRuntimeClient).toHaveBeenCalledWith( | ||
| expect.objectContaining({ | ||
| region: "us-east-1", | ||
| }), | ||
| ) | ||
|
|
||
| // Verify the endpoint property is not present | ||
| const clientConfig = mockBedrockRuntimeClient.mock.calls[0][0] | ||
| expect(clientConfig).not.toHaveProperty("endpoint") | ||
| }) | ||
| }) | ||
|
|
||
| // Test Scenario 2: Edge Case Tests | ||
| describe("Edge Cases", () => { | ||
| it("should handle empty endpoint URL gracefully", () => { | ||
| // Create handler with empty endpoint URL but enabled flag | ||
| new AwsBedrockHandler({ | ||
| apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", | ||
| awsAccessKey: "test-access-key", | ||
| awsSecretKey: "test-secret-key", | ||
| awsRegion: "us-east-1", | ||
| awsBedrockEndpoint: "", | ||
| awsBedrockEndpointEnabled: true, | ||
| }) | ||
|
|
||
| // Verify the client was created without the endpoint (since it's empty) | ||
| expect(mockBedrockRuntimeClient).toHaveBeenCalledWith( | ||
| expect.objectContaining({ | ||
| region: "us-east-1", | ||
| }), | ||
| ) | ||
|
|
||
| // Verify the endpoint property is not present | ||
| const clientConfig = mockBedrockRuntimeClient.mock.calls[0][0] | ||
| expect(clientConfig).not.toHaveProperty("endpoint") | ||
| }) | ||
|
|
||
| it("should handle undefined endpoint URL gracefully", () => { | ||
| // Create handler with undefined endpoint URL but enabled flag | ||
| new AwsBedrockHandler({ | ||
| apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", | ||
| awsAccessKey: "test-access-key", | ||
| awsSecretKey: "test-secret-key", | ||
| awsRegion: "us-east-1", | ||
| awsBedrockEndpoint: undefined, | ||
| awsBedrockEndpointEnabled: true, | ||
| }) | ||
|
|
||
| // Verify the client was created without the endpoint | ||
| expect(mockBedrockRuntimeClient).toHaveBeenCalledWith( | ||
| expect.objectContaining({ | ||
| region: "us-east-1", | ||
| }), | ||
| ) | ||
|
|
||
| // Verify the endpoint property is not present | ||
| const clientConfig = mockBedrockRuntimeClient.mock.calls[0][0] | ||
| expect(clientConfig).not.toHaveProperty("endpoint") | ||
| }) | ||
| }) | ||
|
|
||
| // Test Scenario 4: Error Handling Tests | ||
| describe("Error Handling", () => { | ||
| it("should handle invalid endpoint URLs by passing them directly to AWS SDK", () => { | ||
| // Create handler with an invalid URL format | ||
| new AwsBedrockHandler({ | ||
| apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", | ||
| awsAccessKey: "test-access-key", | ||
| awsSecretKey: "test-secret-key", | ||
| awsRegion: "us-east-1", | ||
| awsBedrockEndpoint: "invalid-url-format", | ||
| awsBedrockEndpointEnabled: true, | ||
| }) | ||
|
|
||
| // Verify the client was created with the invalid endpoint | ||
| // (AWS SDK will handle the validation/errors) | ||
| expect(mockBedrockRuntimeClient).toHaveBeenCalledWith( | ||
| expect.objectContaining({ | ||
| region: "us-east-1", | ||
| endpoint: "invalid-url-format", | ||
| }), | ||
| ) | ||
| }) | ||
| }) | ||
|
|
||
| // Test Scenario 5: Persistence Tests | ||
| describe("Persistence", () => { | ||
| it("should maintain consistent behavior across multiple requests", async () => { | ||
| // Create handler with endpoint URL and enabled flag | ||
| const handler = new AwsBedrockHandler({ | ||
| apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", | ||
| awsAccessKey: "test-access-key", | ||
| awsSecretKey: "test-secret-key", | ||
| awsRegion: "us-east-1", | ||
| awsBedrockEndpoint: "https://bedrock-vpc.example.com", | ||
| awsBedrockEndpointEnabled: true, | ||
| }) | ||
|
|
||
| // Reset mock to clear the constructor call | ||
| mockBedrockRuntimeClient.mockClear() | ||
|
|
||
| // Make a request | ||
| try { | ||
| await handler.completePrompt("Test prompt") | ||
| } catch (error) { | ||
| // Ignore errors, we're just testing the client configuration | ||
| } | ||
|
|
||
| // Verify the client was configured with the endpoint | ||
| expect(mockSend).toHaveBeenCalled() | ||
| }) | ||
| }) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| import { useCallback } from "react" | ||
| import { useCallback, useState, useEffect } from "react" | ||
| import { Checkbox } from "vscrui" | ||
| import { VSCodeTextField, VSCodeRadio, VSCodeRadioGroup } from "@vscode/webview-ui-toolkit/react" | ||
|
|
||
|
|
@@ -17,6 +17,12 @@ type BedrockProps = { | |
|
|
||
| export const Bedrock = ({ apiConfiguration, setApiConfigurationField, selectedModelInfo }: BedrockProps) => { | ||
| const { t } = useAppTranslation() | ||
| const [awsEndpointSelected, setAwsEndpointSelected] = useState(!!apiConfiguration?.awsBedrockEndpointEnabled) | ||
|
|
||
| // Update the endpoint enabled state when the configuration changes | ||
| useEffect(() => { | ||
| setAwsEndpointSelected(!!apiConfiguration?.awsBedrockEndpointEnabled) | ||
| }, [apiConfiguration?.awsBedrockEndpointEnabled]) | ||
|
|
||
| const handleInputChange = useCallback( | ||
| <K extends keyof ProviderSettings, E>( | ||
|
|
@@ -120,6 +126,31 @@ export const Bedrock = ({ apiConfiguration, setApiConfigurationField, selectedMo | |
| {t("settings:providers.cacheUsageNote")} | ||
| </div> | ||
| </div> | ||
| <Checkbox | ||
| checked={awsEndpointSelected} | ||
| onChange={(isChecked) => { | ||
| setAwsEndpointSelected(isChecked) | ||
| setApiConfigurationField("awsBedrockEndpointEnabled", isChecked) | ||
| }}> | ||
| Use custom VPC endpoint | ||
| </Checkbox> | ||
| {awsEndpointSelected && ( | ||
| <> | ||
| <VSCodeTextField | ||
| value={apiConfiguration?.awsBedrockEndpoint || ""} | ||
| style={{ width: "100%", marginTop: 3, marginBottom: 5 }} | ||
| type="url" | ||
| onInput={handleInputChange("awsBedrockEndpoint")} | ||
| placeholder="Enter VPC Endpoint URL (optional)" | ||
|
||
| data-testid="vpc-endpoint-input" | ||
| /> | ||
| <div className="text-sm text-vscode-descriptionForeground ml-6 mt-1 mb-3"> | ||
| Examples: | ||
|
||
| <div className="ml-2">• https://vpce-xxx.bedrock.region.vpce.amazonaws.com/</div> | ||
| <div className="ml-2">• https://gateway.my-company.com/route/app/bedrock</div> | ||
| </div> | ||
| </> | ||
| )} | ||
| </> | ||
| ) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you internationalize this string please?