fix(ai): vendor proxy env resolution

closes #4513
This commit is contained in:
Mario Zechner
2026-05-17 00:02:37 +02:00
parent 7afd80d788
commit c5831df689
4 changed files with 78 additions and 10 deletions

View File

@@ -1,11 +1,16 @@
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 };
const DEFAULT_PROXY_PORTS: Record<string, number> = {
ftp: 21,
gopher: 70,
http: 80,
https: 443,
ws: 80,
wss: 443,
};
export interface NodeHttpProxyAgents {
httpAgent: HttpAgent;
@@ -15,8 +20,76 @@ export interface NodeHttpProxyAgents {
export const UNSUPPORTED_PROXY_PROTOCOL_MESSAGE =
"Unsupported proxy protocol. SOCKS and PAC proxy URLs are not supported; use an HTTP or HTTPS proxy URL.";
function getProxyEnv(key: string): string {
return process.env[key.toLowerCase()] || process.env[key.toUpperCase()] || "";
}
function parseProxyTargetUrl(targetUrl: string | URL): URL | undefined {
if (targetUrl instanceof URL) {
return targetUrl;
}
try {
return new URL(targetUrl);
} catch {
return undefined;
}
}
function shouldProxyHostname(hostname: string, port: number): boolean {
const noProxy = getProxyEnv("no_proxy").toLowerCase();
if (!noProxy) {
return true;
}
if (noProxy === "*") {
return false;
}
return noProxy.split(/[,\s]/).every((proxy) => {
if (!proxy) {
return true;
}
const parsedProxy = proxy.match(/^(.+):(\d+)$/);
let proxyHostname = parsedProxy ? parsedProxy[1] : proxy;
const proxyPort = parsedProxy ? Number.parseInt(parsedProxy[2]!, 10) : 0;
if (proxyPort && proxyPort !== port) {
return true;
}
if (!/^[.*]/.test(proxyHostname)) {
return hostname !== proxyHostname;
}
if (proxyHostname.startsWith("*")) {
proxyHostname = proxyHostname.slice(1);
}
return !hostname.endsWith(proxyHostname);
});
}
function getProxyForUrl(targetUrl: string | URL): string {
const parsedUrl = parseProxyTargetUrl(targetUrl);
if (!parsedUrl?.protocol || !parsedUrl.host) {
return "";
}
const protocol = parsedUrl.protocol.split(":", 1)[0]!;
const hostname = parsedUrl.host.replace(/:\d*$/, "");
const port = Number.parseInt(parsedUrl.port, 10) || DEFAULT_PROXY_PORTS[protocol] || 0;
if (!shouldProxyHostname(hostname, port)) {
return "";
}
let proxy = getProxyEnv(`${protocol}_proxy`) || getProxyEnv("all_proxy");
if (proxy && !proxy.includes("://")) {
proxy = `${protocol}://${proxy}`;
}
return proxy;
}
export function resolveHttpProxyUrlForTarget(targetUrl: string | URL): URL | undefined {
const proxy = getProxyForUrl(targetUrl.toString());
const proxy = getProxyForUrl(targetUrl);
if (!proxy) {
return undefined;
}