Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/itchy-moles-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"zoo-code": patch
---

Fix bedrock DNS resolution when behind corporate proxy
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

76 changes: 76 additions & 0 deletions src/api/providers/__tests__/bedrock.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ vi.mock("@aws-sdk/credential-providers", () => {
return { fromIni: mockFromIni }
})

vi.mock("../../../utils/networkProxy", () => ({
getSystemProxyUrl: vi.fn().mockReturnValue(undefined),
}))

vi.mock("@smithy/node-http-handler", () => ({
NodeHttpHandler: vi.fn(),
}))

vi.mock("https-proxy-agent", () => ({
HttpsProxyAgent: vi.fn(),
}))

// Mock BedrockRuntimeClient and ConverseStreamCommand
vi.mock("@aws-sdk/client-bedrock-runtime", () => {
const mockSend = vi.fn().mockResolvedValue({
Expand Down Expand Up @@ -46,10 +58,16 @@ import {
} from "@roo-code/types"

import type { Anthropic } from "@anthropic-ai/sdk"
import { getSystemProxyUrl } from "../../../utils/networkProxy"
import { NodeHttpHandler } from "@smithy/node-http-handler"
import { HttpsProxyAgent } from "https-proxy-agent"

// Get access to the mocked functions
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand)
const mockBedrockRuntimeClient = vi.mocked(BedrockRuntimeClient)
const mockGetSystemProxyUrl = vi.mocked(getSystemProxyUrl)
const mockNodeHttpHandler = vi.mocked(NodeHttpHandler)
const mockHttpsProxyAgent = vi.mocked(HttpsProxyAgent)

describe("AwsBedrockHandler", () => {
let handler: AwsBedrockHandler
Expand Down Expand Up @@ -118,6 +136,64 @@ describe("AwsBedrockHandler", () => {
})
})

describe("proxy configuration", () => {
afterEach(() => {
mockGetSystemProxyUrl.mockReturnValue(undefined)
})

it("should configure NodeHttpHandler with HttpsProxyAgent when proxy URL is set", () => {
mockGetSystemProxyUrl.mockReturnValue("http://proxy.corp.local:3128")

new AwsBedrockHandler({
apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
awsAccessKey: "test-access-key",
awsSecretKey: "test-secret-key",
awsRegion: "us-east-1",
})

expect(mockHttpsProxyAgent).toHaveBeenCalledWith("http://proxy.corp.local:3128")
expect(mockNodeHttpHandler).toHaveBeenCalledWith(
expect.objectContaining({
httpsAgent: expect.anything(),
requestTimeout: 0,
}),
)
expect(mockBedrockRuntimeClient).toHaveBeenLastCalledWith(
expect.objectContaining({ requestHandler: expect.anything() }),
)
})

it("should not create a proxy requestHandler when no proxy is configured", () => {
new AwsBedrockHandler({
apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
awsAccessKey: "test-access-key",
awsSecretKey: "test-secret-key",
awsRegion: "us-east-1",
})

expect(mockNodeHttpHandler).not.toHaveBeenCalled()
expect(mockBedrockRuntimeClient.mock.lastCall?.[0]?.requestHandler).toBeUndefined()
})

it("should apply proxy for API key authentication", () => {
mockGetSystemProxyUrl.mockReturnValue("http://proxy.corp.local:3128")

new AwsBedrockHandler({
apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
awsUseApiKey: true,
awsApiKey: "test-api-key",
awsRegion: "us-east-1",
})

expect(mockNodeHttpHandler).toHaveBeenCalledWith(
expect.objectContaining({
httpsAgent: expect.anything(),
requestTimeout: 0,
}),
)
})
})

describe("region mapping and cross-region inference", () => {
describe("getPrefixForRegion", () => {
it("should return correct prefix for US regions", () => {
Expand Down
14 changes: 14 additions & 0 deletions src/api/providers/bedrock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import {
ToolConfiguration,
ToolChoice,
} from "@aws-sdk/client-bedrock-runtime"
import { NodeHttpHandler } from "@smithy/node-http-handler"
import OpenAI from "openai"
import { fromIni } from "@aws-sdk/credential-providers"
import { Anthropic } from "@anthropic-ai/sdk"
import { HttpsProxyAgent } from "https-proxy-agent"

import {
type ModelInfo,
Expand Down Expand Up @@ -44,6 +46,7 @@ import { convertToBedrockConverseMessages as sharedConverter } from "../transfor
import { getModelParams } from "../transform/model-params"
import { shouldUseReasoningBudget } from "../../shared/api"
import { normalizeToolSchema } from "../../utils/json-schema"
import { getSystemProxyUrl } from "../../utils/networkProxy"
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"

/************************************************************************************
Expand Down Expand Up @@ -294,6 +297,17 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
}
}

// When a corporate proxy is configured, Node resolves DNS locally before tunneling,
// causing ENOTFOUND for endpoints that only the proxy can reach. HttpsProxyAgent
// uses CONNECT tunneling so the proxy handles DNS resolution instead.
const proxyUrl = getSystemProxyUrl()
if (proxyUrl) {
clientConfig.requestHandler = new NodeHttpHandler({
httpsAgent: new HttpsProxyAgent(proxyUrl),
requestTimeout: 0,
})
}

this.client = new BedrockRuntimeClient(clientConfig)
}

Expand Down
2 changes: 2 additions & 0 deletions src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@
"@anthropic-ai/vertex-sdk": "^0.19.0",
"@aws-sdk/client-bedrock-runtime": "^3.922.0",
"@aws-sdk/credential-providers": "^3.922.0",
"@smithy/node-http-handler": "^4.0.0",
"@google/genai": "^1.29.1",
"@lmstudio/sdk": "^1.1.1",
"@mistralai/mistralai": "^1.9.18",
Expand All @@ -486,6 +487,7 @@
"get-folder-size": "^5.0.0",
"global-agent": "^3.0.0",
"google-auth-library": "^10.2.0",
"https-proxy-agent": "^7.0.0",
"gray-matter": "^4.0.3",
"i18next": "^25.0.0",
"ignore": "^7.0.3",
Expand Down
24 changes: 24 additions & 0 deletions src/utils/networkProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,30 @@ export function isDebugMode(): boolean {
return extensionContext.extensionMode === vscode.ExtensionMode.Development
}

/**
* Get the proxy URL from environment variables or VS Code settings.
* Works in all extension modes (production and debug).
*/
export function getSystemProxyUrl(): string | undefined {
// Standard proxy environment variables (HTTPS takes precedence over HTTP)
const fromEnv =
process.env.HTTPS_PROXY ||
process.env.https_proxy ||
process.env.HTTP_PROXY ||
process.env.http_proxy
if (fromEnv) return fromEnv
Comment on lines +354 to +360

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Trim environment proxy values before returning them.

Unlike the VS Code fallback, environment values are not trimmed. A whitespace-only HTTPS_PROXY/HTTP_PROXY value is truthy and will be passed to HttpsProxyAgent as an invalid proxy URL, preventing Bedrock client initialization. Normalize the selected environment value and treat blank values as unset.

Proposed fix
-	const fromEnv =
+	const fromEnv = (
 		process.env.HTTPS_PROXY ||
 		process.env.https_proxy ||
 		process.env.HTTP_PROXY ||
 		process.env.http_proxy
-	if (fromEnv) return fromEnv
+	)?.trim()
+	if (fromEnv) return fromEnv
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Standard proxy environment variables (HTTPS takes precedence over HTTP)
const fromEnv =
process.env.HTTPS_PROXY ||
process.env.https_proxy ||
process.env.HTTP_PROXY ||
process.env.http_proxy
if (fromEnv) return fromEnv
// Standard proxy environment variables (HTTPS takes precedence over HTTP)
const fromEnv = (
process.env.HTTPS_PROXY ||
process.env.https_proxy ||
process.env.HTTP_PROXY ||
process.env.http_proxy
)?.trim()
if (fromEnv) return fromEnv
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/networkProxy.ts` around lines 354 - 360, Update the environment
proxy selection in the surrounding proxy-resolution function to trim the chosen
HTTPS_PROXY/https_proxy/HTTP_PROXY/http_proxy value before returning it, and
treat the trimmed result as unset when blank so resolution can continue to the
fallback path. Preserve the existing HTTPS-over-HTTP precedence.


// Fall back to VS Code's http.proxy setting
try {
const vsCodeProxy = vscode.workspace.getConfiguration("http").get<string>("proxy")
if (vsCodeProxy?.trim()) return vsCodeProxy.trim()
} catch {
// VS Code API may be unavailable in test environments
}

return undefined
}

/**
* Log a message to the output channel if available.
*/
Expand Down
Loading