refactor(ai): use HTTP proxy agents for Bedrock

This commit is contained in:
Mario Zechner
2026-05-13 16:12:18 +02:00
parent e3faf41801
commit a5cca409d8
6 changed files with 127 additions and 224 deletions

View File

@@ -8,6 +8,7 @@
### Fixed
- Fixed Bedrock proxy handling to preserve `NO_PROXY` exclusions while using HTTP(S)-only proxy agents.
- Fixed GitHub Copilot Claude test coverage to use the current Claude Sonnet 4.6 model ID.
- Fixed OpenAI Responses requests for models that support disabling reasoning to send `reasoning.effort: "none"` when thinking is off.

View File

@@ -73,9 +73,10 @@
"@aws-sdk/client-bedrock-runtime": "^3.1030.0",
"@google/genai": "^1.40.0",
"@mistralai/mistralai": "^2.2.0",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.6",
"openai": "6.26.0",
"partial-json": "^0.1.7",
"proxy-agent": "^6.5.0",
"proxy-from-env": "^1.1.0",
"typebox": "^1.1.24"
},

View File

@@ -20,6 +20,7 @@ import {
type ToolConfiguration,
ToolResultStatus,
} from "@aws-sdk/client-bedrock-runtime";
import { NodeHttpHandler } from "@smithy/node-http-handler";
import type { DocumentType } from "@smithy/types";
import { calculateCost } from "../models.js";
import type {
@@ -42,6 +43,7 @@ import type {
} from "../types.js";
import { AssistantMessageEventStream } from "../utils/event-stream.js";
import { parseStreamingJson } from "../utils/json-parse.js";
import { createHttpProxyAgentsForTarget } from "../utils/node-http-proxy.js";
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
import { adjustMaxTokensForThinking, buildBaseOptions, clampReasoning } from "./simple-options.js";
import { transformMessages } from "./transform-messages.js";
@@ -156,30 +158,15 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream", BedrockOpt
};
}
if (
process.env.HTTP_PROXY ||
process.env.HTTPS_PROXY ||
process.env.NO_PROXY ||
process.env.http_proxy ||
process.env.https_proxy ||
process.env.no_proxy
) {
const nodeHttpHandler = await import("@smithy/node-http-handler");
const proxyAgent = await import("proxy-agent");
const agent = new proxyAgent.ProxyAgent();
const proxyAgents = createHttpProxyAgentsForTarget(model.baseUrl);
if (proxyAgents) {
// Bedrock runtime uses NodeHttp2Handler by default since v3.798.0, which is based
// on `http2` module and has no support for http agent.
// Use NodeHttpHandler to support http agent.
config.requestHandler = new nodeHttpHandler.NodeHttpHandler({
httpAgent: agent,
httpsAgent: agent,
});
// Use NodeHttpHandler to support HTTP(S) proxy agents.
config.requestHandler = new NodeHttpHandler(proxyAgents);
} else if (process.env.AWS_BEDROCK_FORCE_HTTP1 === "1") {
// Some custom endpoints require HTTP/1.1 instead of HTTP/2
const nodeHttpHandler = await import("@smithy/node-http-handler");
config.requestHandler = new nodeHttpHandler.NodeHttpHandler();
config.requestHandler = new NodeHttpHandler();
}
} else {
// Non-Node environment (browser): fall back to us-east-1 since

View File

@@ -0,0 +1,50 @@
import type { Agent as HttpAgent } from "node:http";
import type { Agent as HttpsAgent } from "node:https";
import { createRequire } from "node:module";
import { HttpProxyAgent } from "http-proxy-agent";
import { HttpsProxyAgent } from "https-proxy-agent";
const require = createRequire(import.meta.url);
const { getProxyForUrl } = require("proxy-from-env") as { getProxyForUrl: (url: string) => string };
export interface NodeHttpProxyAgents {
httpAgent: HttpAgent;
httpsAgent: HttpsAgent;
}
export const UNSUPPORTED_PROXY_PROTOCOL_MESSAGE =
"Unsupported proxy protocol. SOCKS and PAC proxy URLs are not supported; use an HTTP or HTTPS proxy URL.";
export function resolveHttpProxyUrlForTarget(targetUrl: string | URL): URL | undefined {
const proxy = getProxyForUrl(targetUrl.toString());
if (!proxy) {
return undefined;
}
let proxyUrl: URL;
try {
proxyUrl = new URL(proxy);
} catch (error) {
throw new Error(
`Invalid proxy URL ${JSON.stringify(proxy)}: ${error instanceof Error ? error.message : String(error)}`,
);
}
if (proxyUrl.protocol !== "http:" && proxyUrl.protocol !== "https:") {
throw new Error(`${UNSUPPORTED_PROXY_PROTOCOL_MESSAGE} Got ${proxyUrl.protocol}`);
}
return proxyUrl;
}
export function createHttpProxyAgentsForTarget(targetUrl: string | URL): NodeHttpProxyAgents | undefined {
const proxyUrl = resolveHttpProxyUrlForTarget(targetUrl);
if (!proxyUrl) {
return undefined;
}
return {
httpAgent: new HttpProxyAgent(proxyUrl),
httpsAgent: new HttpsProxyAgent(proxyUrl) as unknown as HttpsAgent,
};
}

View File

@@ -0,0 +1,65 @@
import { afterEach, describe, expect, it } from "vitest";
import { resolveHttpProxyUrlForTarget, UNSUPPORTED_PROXY_PROTOCOL_MESSAGE } from "../src/utils/node-http-proxy.js";
const PROXY_ENV_KEYS = [
"HTTP_PROXY",
"HTTPS_PROXY",
"NO_PROXY",
"ALL_PROXY",
"http_proxy",
"https_proxy",
"no_proxy",
"all_proxy",
"npm_config_http_proxy",
"npm_config_https_proxy",
"npm_config_proxy",
"npm_config_no_proxy",
] as const;
const originalEnv = new Map<string, string | undefined>();
for (const key of PROXY_ENV_KEYS) {
originalEnv.set(key, process.env[key]);
}
function resetProxyEnv(): void {
for (const key of PROXY_ENV_KEYS) {
delete process.env[key];
}
}
afterEach(() => {
resetProxyEnv();
for (const [key, value] of originalEnv) {
if (value !== undefined) {
process.env[key] = value;
}
}
});
describe("node HTTP proxy resolution", () => {
it("respects NO_PROXY exclusions", () => {
resetProxyEnv();
process.env.HTTPS_PROXY = "http://proxy.example:8080";
process.env.NO_PROXY = "bedrock-runtime.us-east-1.amazonaws.com";
expect(resolveHttpProxyUrlForTarget("https://bedrock-runtime.us-east-1.amazonaws.com")).toBeUndefined();
});
it("resolves HTTP and HTTPS proxy URLs", () => {
resetProxyEnv();
process.env.HTTPS_PROXY = "http://proxy.example:8080";
expect(resolveHttpProxyUrlForTarget("https://bedrock-runtime.us-east-1.amazonaws.com")?.toString()).toBe(
"http://proxy.example:8080/",
);
});
it("rejects SOCKS and PAC proxy URLs explicitly", () => {
resetProxyEnv();
process.env.HTTPS_PROXY = "socks5://proxy.example:1080";
expect(() => resolveHttpProxyUrlForTarget("https://bedrock-runtime.us-east-1.amazonaws.com")).toThrow(
UNSUPPORTED_PROXY_PROTOCOL_MESSAGE,
);
});
});