Skip to content

Commit cfd7c96

Browse files
committed
Fix bedrock DNS resolution when behind a corporate proxy
When behind a corporate proxy, Node.js resolves DNS locally before contacting the proxy. If the proxy is the only path to the endpoint, the request fails with ENOTFOUND. HttpsProxyAgent uses CONNECT tunneling so the proxy handles DNS resolution. Configure NodeHttpHandler with HttpsProxyAgent when a system proxy is detected.
1 parent 8d4ed32 commit cfd7c96

6 files changed

Lines changed: 127 additions & 0 deletions

File tree

.changeset/itchy-moles-thank.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"zoo-code": patch
3+
---
4+
5+
Fix bedrock DNS resolution when behind corporate proxy

pnpm-lock.yaml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/api/providers/__tests__/bedrock.spec.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@ vi.mock("@aws-sdk/credential-providers", () => {
1818
return { fromIni: mockFromIni }
1919
})
2020

21+
vi.mock("../../../utils/networkProxy", () => ({
22+
getSystemProxyUrl: vi.fn().mockReturnValue(undefined),
23+
}))
24+
25+
vi.mock("@smithy/node-http-handler", () => ({
26+
NodeHttpHandler: vi.fn(),
27+
}))
28+
29+
vi.mock("https-proxy-agent", () => ({
30+
HttpsProxyAgent: vi.fn(),
31+
}))
32+
2133
// Mock BedrockRuntimeClient and ConverseStreamCommand
2234
vi.mock("@aws-sdk/client-bedrock-runtime", () => {
2335
const mockSend = vi.fn().mockResolvedValue({
@@ -46,10 +58,16 @@ import {
4658
} from "@roo-code/types"
4759

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

5065
// Get access to the mocked functions
5166
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand)
5267
const mockBedrockRuntimeClient = vi.mocked(BedrockRuntimeClient)
68+
const mockGetSystemProxyUrl = vi.mocked(getSystemProxyUrl)
69+
const mockNodeHttpHandler = vi.mocked(NodeHttpHandler)
70+
const mockHttpsProxyAgent = vi.mocked(HttpsProxyAgent)
5371

5472
describe("AwsBedrockHandler", () => {
5573
let handler: AwsBedrockHandler
@@ -118,6 +136,64 @@ describe("AwsBedrockHandler", () => {
118136
})
119137
})
120138

139+
describe("proxy configuration", () => {
140+
afterEach(() => {
141+
mockGetSystemProxyUrl.mockReturnValue(undefined)
142+
})
143+
144+
it("should configure NodeHttpHandler with HttpsProxyAgent when proxy URL is set", () => {
145+
mockGetSystemProxyUrl.mockReturnValue("http://proxy.corp.local:3128")
146+
147+
new AwsBedrockHandler({
148+
apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
149+
awsAccessKey: "test-access-key",
150+
awsSecretKey: "test-secret-key",
151+
awsRegion: "us-east-1",
152+
})
153+
154+
expect(mockHttpsProxyAgent).toHaveBeenCalledWith("http://proxy.corp.local:3128")
155+
expect(mockNodeHttpHandler).toHaveBeenCalledWith(
156+
expect.objectContaining({
157+
httpsAgent: expect.anything(),
158+
requestTimeout: 0,
159+
}),
160+
)
161+
expect(mockBedrockRuntimeClient).toHaveBeenLastCalledWith(
162+
expect.objectContaining({ requestHandler: expect.anything() }),
163+
)
164+
})
165+
166+
it("should not create a proxy requestHandler when no proxy is configured", () => {
167+
new AwsBedrockHandler({
168+
apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
169+
awsAccessKey: "test-access-key",
170+
awsSecretKey: "test-secret-key",
171+
awsRegion: "us-east-1",
172+
})
173+
174+
expect(mockNodeHttpHandler).not.toHaveBeenCalled()
175+
expect(mockBedrockRuntimeClient.mock.lastCall?.[0]?.requestHandler).toBeUndefined()
176+
})
177+
178+
it("should apply proxy for API key authentication", () => {
179+
mockGetSystemProxyUrl.mockReturnValue("http://proxy.corp.local:3128")
180+
181+
new AwsBedrockHandler({
182+
apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
183+
awsUseApiKey: true,
184+
awsApiKey: "test-api-key",
185+
awsRegion: "us-east-1",
186+
})
187+
188+
expect(mockNodeHttpHandler).toHaveBeenCalledWith(
189+
expect.objectContaining({
190+
httpsAgent: expect.anything(),
191+
requestTimeout: 0,
192+
}),
193+
)
194+
})
195+
})
196+
121197
describe("region mapping and cross-region inference", () => {
122198
describe("getPrefixForRegion", () => {
123199
it("should return correct prefix for US regions", () => {

src/api/providers/bedrock.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ import {
1010
ToolConfiguration,
1111
ToolChoice,
1212
} from "@aws-sdk/client-bedrock-runtime"
13+
import { NodeHttpHandler } from "@smithy/node-http-handler"
1314
import OpenAI from "openai"
1415
import { fromIni } from "@aws-sdk/credential-providers"
1516
import { Anthropic } from "@anthropic-ai/sdk"
17+
import { HttpsProxyAgent } from "https-proxy-agent"
1618

1719
import {
1820
type ModelInfo,
@@ -44,6 +46,7 @@ import { convertToBedrockConverseMessages as sharedConverter } from "../transfor
4446
import { getModelParams } from "../transform/model-params"
4547
import { shouldUseReasoningBudget } from "../../shared/api"
4648
import { normalizeToolSchema } from "../../utils/json-schema"
49+
import { getSystemProxyUrl } from "../../utils/networkProxy"
4750
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
4851

4952
/************************************************************************************
@@ -294,6 +297,17 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
294297
}
295298
}
296299

300+
// When a corporate proxy is configured, Node resolves DNS locally before tunneling,
301+
// causing ENOTFOUND for endpoints that only the proxy can reach. HttpsProxyAgent
302+
// uses CONNECT tunneling so the proxy handles DNS resolution instead.
303+
const proxyUrl = getSystemProxyUrl()
304+
if (proxyUrl) {
305+
clientConfig.requestHandler = new NodeHttpHandler({
306+
httpsAgent: new HttpsProxyAgent(proxyUrl),
307+
requestTimeout: 0,
308+
})
309+
}
310+
297311
this.client = new BedrockRuntimeClient(clientConfig)
298312
}
299313

src/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@
461461
"@anthropic-ai/vertex-sdk": "^0.18.0",
462462
"@aws-sdk/client-bedrock-runtime": "^3.922.0",
463463
"@aws-sdk/credential-providers": "^3.922.0",
464+
"@smithy/node-http-handler": "^4.0.0",
464465
"@google/genai": "^1.29.1",
465466
"@lmstudio/sdk": "^1.1.1",
466467
"@mistralai/mistralai": "^1.9.18",
@@ -485,6 +486,7 @@
485486
"get-folder-size": "^5.0.0",
486487
"global-agent": "^3.0.0",
487488
"google-auth-library": "^9.15.1",
489+
"https-proxy-agent": "^7.0.0",
488490
"gray-matter": "^4.0.3",
489491
"i18next": "^25.0.0",
490492
"ignore": "^7.0.3",

src/utils/networkProxy.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,30 @@ export function isDebugMode(): boolean {
346346
return extensionContext.extensionMode === vscode.ExtensionMode.Development
347347
}
348348

349+
/**
350+
* Get the proxy URL from environment variables or VS Code settings.
351+
* Works in all extension modes (production and debug).
352+
*/
353+
export function getSystemProxyUrl(): string | undefined {
354+
// Standard proxy environment variables (HTTPS takes precedence over HTTP)
355+
const fromEnv =
356+
process.env.HTTPS_PROXY ||
357+
process.env.https_proxy ||
358+
process.env.HTTP_PROXY ||
359+
process.env.http_proxy
360+
if (fromEnv) return fromEnv
361+
362+
// Fall back to VS Code's http.proxy setting
363+
try {
364+
const vsCodeProxy = vscode.workspace.getConfiguration("http").get<string>("proxy")
365+
if (vsCodeProxy?.trim()) return vsCodeProxy.trim()
366+
} catch {
367+
// VS Code API may be unavailable in test environments
368+
}
369+
370+
return undefined
371+
}
372+
349373
/**
350374
* Log a message to the output channel if available.
351375
*/

0 commit comments

Comments
 (0)