mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:00:42 +00:00
fix(gateway): honor all_proxy in env dispatcher
This commit is contained in:
@@ -38,6 +38,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Plugins/install: resolve plugin install destinations from the active profile state dir across CLI, ClawHub, marketplace, local path, and channel setup installs, so `openclaw --profile <name> plugins install ...` no longer writes into the default profile. Fixes #69960; carries forward #69971. Thanks @FrancisLyman and @Sanjays2402.
|
||||
- Plugins/registry: suppress duplicate-plugin startup warnings when a tracked npm-installed plugin intentionally overrides the bundled plugin with the same id. Carries forward #48673. Thanks @abdushsk.
|
||||
- Plugins/startup: reuse canonical realpath lookups throughout each plugin discovery pass, including package and manifest boundary checks, so Windows npm-global startups no longer repeat expensive path resolution for the same plugin roots. Fixes #65733. Thanks @welfo-beo.
|
||||
- Gateway/proxy: pass `ALL_PROXY` / `all_proxy` into the global Undici env-proxy dispatcher and provider proxy-fetch helper while keeping SSRF trusted-proxy auto-upgrade on `HTTP_PROXY` / `HTTPS_PROXY` only, so gateway/provider calls honor all-proxy setups without weakening guarded fetches. Fixes #43919. Thanks @RickyTong1.
|
||||
- Reply/link understanding: keep media and link preprocessing on stable runtime entrypoints and continue with raw message content if optional enrichment fails, so URL-bearing messages are no longer dropped after stale runtime chunk upgrades. Fixes #68466. Thanks @songshikang0111.
|
||||
- Discord: persist routed model-picker overrides when the hidden `/model` dispatch succeeds but the bound thread session store is still stale, including LM Studio suffixed model ids. Carries forward #61473. Thanks @Nanako0129.
|
||||
- Nodes/CLI: add `openclaw nodes remove --node <id|name|ip>` and `node.pair.remove` so stale gateway-owned node pairing records can be cleaned without hand-editing state files.
|
||||
|
||||
@@ -171,8 +171,10 @@ Provider-based audio transcription honors standard outbound proxy env vars:
|
||||
|
||||
- `HTTPS_PROXY`
|
||||
- `HTTP_PROXY`
|
||||
- `ALL_PROXY`
|
||||
- `https_proxy`
|
||||
- `http_proxy`
|
||||
- `all_proxy`
|
||||
|
||||
If no proxy env vars are set, direct egress is used. If proxy config is malformed, OpenClaw logs a warning and falls back to direct fetch.
|
||||
|
||||
|
||||
@@ -220,8 +220,10 @@ When provider-based **audio** and **video** media understanding is enabled, Open
|
||||
|
||||
- `HTTPS_PROXY`
|
||||
- `HTTP_PROXY`
|
||||
- `ALL_PROXY`
|
||||
- `https_proxy`
|
||||
- `http_proxy`
|
||||
- `all_proxy`
|
||||
|
||||
If no proxy env vars are set, media understanding uses direct egress. If the proxy value is malformed, OpenClaw logs a warning and falls back to direct fetch.
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ const getProgramContextMock = vi.hoisted(() => vi.fn(() => null));
|
||||
const registerCoreCliByNameMock = vi.hoisted(() => vi.fn());
|
||||
const registerSubCliByNameMock = vi.hoisted(() => vi.fn());
|
||||
const restoreTerminalStateMock = vi.hoisted(() => vi.fn());
|
||||
const hasEnvHttpProxyConfiguredMock = vi.hoisted(() => vi.fn(() => false));
|
||||
const hasEnvHttpProxyAgentConfiguredMock = vi.hoisted(() => vi.fn(() => false));
|
||||
const ensureGlobalUndiciEnvProxyDispatcherMock = vi.hoisted(() => vi.fn());
|
||||
const runCrestodianMock = vi.hoisted(() => vi.fn(async () => {}));
|
||||
const progressDoneMock = vi.hoisted(() => vi.fn());
|
||||
@@ -106,7 +106,7 @@ vi.mock("../terminal/restore.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("../infra/net/proxy-env.js", () => ({
|
||||
hasEnvHttpProxyConfigured: hasEnvHttpProxyConfiguredMock,
|
||||
hasEnvHttpProxyAgentConfigured: hasEnvHttpProxyAgentConfiguredMock,
|
||||
}));
|
||||
|
||||
vi.mock("../infra/net/undici-global-dispatcher.js", () => ({
|
||||
@@ -127,7 +127,7 @@ describe("runCli exit behavior", () => {
|
||||
hasMemoryRuntimeMock.mockReturnValue(false);
|
||||
outputPrecomputedBrowserHelpTextMock.mockReturnValue(false);
|
||||
outputPrecomputedRootHelpTextMock.mockReturnValue(false);
|
||||
hasEnvHttpProxyConfiguredMock.mockReturnValue(false);
|
||||
hasEnvHttpProxyAgentConfiguredMock.mockReturnValue(false);
|
||||
getProgramContextMock.mockReturnValue(null);
|
||||
delete process.env.OPENCLAW_DISABLE_CLI_STARTUP_HELP_FAST_PATH;
|
||||
});
|
||||
@@ -178,7 +178,7 @@ describe("runCli exit behavior", () => {
|
||||
await runCli(["node", "openclaw", "--help"]);
|
||||
|
||||
expect(outputPrecomputedRootHelpTextMock).toHaveBeenCalledTimes(1);
|
||||
expect(hasEnvHttpProxyConfiguredMock).not.toHaveBeenCalled();
|
||||
expect(hasEnvHttpProxyAgentConfiguredMock).not.toHaveBeenCalled();
|
||||
expect(ensureGlobalUndiciEnvProxyDispatcherMock).not.toHaveBeenCalled();
|
||||
expect(runCrestodianMock).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -201,7 +201,7 @@ describe("runCli exit behavior", () => {
|
||||
});
|
||||
|
||||
it("bootstraps env proxy before bare Crestodian startup", async () => {
|
||||
hasEnvHttpProxyConfiguredMock.mockReturnValue(true);
|
||||
hasEnvHttpProxyAgentConfiguredMock.mockReturnValue(true);
|
||||
const stdinTty = Object.getOwnPropertyDescriptor(process.stdin, "isTTY");
|
||||
const stdoutTty = Object.getOwnPropertyDescriptor(process.stdout, "isTTY");
|
||||
Object.defineProperty(process.stdin, "isTTY", { configurable: true, value: true });
|
||||
@@ -230,7 +230,7 @@ describe("runCli exit behavior", () => {
|
||||
});
|
||||
|
||||
it("bootstraps env proxy before modern onboard Crestodian startup", async () => {
|
||||
hasEnvHttpProxyConfiguredMock.mockReturnValue(true);
|
||||
hasEnvHttpProxyAgentConfiguredMock.mockReturnValue(true);
|
||||
|
||||
await runCli(["node", "openclaw", "onboard", "--modern", "--json"]);
|
||||
|
||||
|
||||
@@ -84,8 +84,8 @@ function isCommanderParseExit(error: unknown): error is { exitCode: number } {
|
||||
|
||||
async function ensureCliEnvProxyDispatcher(): Promise<void> {
|
||||
try {
|
||||
const { hasEnvHttpProxyConfigured } = await import("../infra/net/proxy-env.js");
|
||||
if (!hasEnvHttpProxyConfigured("https")) {
|
||||
const { hasEnvHttpProxyAgentConfigured } = await import("../infra/net/proxy-env.js");
|
||||
if (!hasEnvHttpProxyAgentConfigured()) {
|
||||
return;
|
||||
}
|
||||
const { ensureGlobalUndiciEnvProxyDispatcher } =
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
hasEnvHttpProxyAgentConfigured,
|
||||
hasEnvHttpProxyConfigured,
|
||||
hasProxyEnvConfigured,
|
||||
matchesNoProxy,
|
||||
resolveEnvHttpProxyAgentOptions,
|
||||
resolveEnvHttpProxyUrl,
|
||||
shouldUseEnvHttpProxyForUrl,
|
||||
} from "./proxy-env.js";
|
||||
@@ -96,6 +98,55 @@ describe("resolveEnvHttpProxyUrl", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveEnvHttpProxyAgentOptions", () => {
|
||||
it.each([
|
||||
{
|
||||
name: "maps HTTPS_PROXY to httpsProxy only",
|
||||
env: { HTTPS_PROXY: "http://https-proxy.test:8443" } as NodeJS.ProcessEnv,
|
||||
expected: { httpsProxy: "http://https-proxy.test:8443" },
|
||||
},
|
||||
{
|
||||
name: "uses HTTP_PROXY as HTTPS fallback",
|
||||
env: { HTTP_PROXY: "http://http-proxy.test:8080" } as NodeJS.ProcessEnv,
|
||||
expected: {
|
||||
httpProxy: "http://http-proxy.test:8080",
|
||||
httpsProxy: "http://http-proxy.test:8080",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "uses ALL_PROXY for both protocols",
|
||||
env: { ALL_PROXY: "socks5://all-proxy.test:1080" } as NodeJS.ProcessEnv,
|
||||
expected: {
|
||||
httpProxy: "socks5://all-proxy.test:1080",
|
||||
httpsProxy: "socks5://all-proxy.test:1080",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "lets protocol-specific proxy override ALL_PROXY",
|
||||
env: {
|
||||
ALL_PROXY: "socks5://all-proxy.test:1080",
|
||||
HTTP_PROXY: "http://http-proxy.test:8080",
|
||||
HTTPS_PROXY: "http://https-proxy.test:8443",
|
||||
} as NodeJS.ProcessEnv,
|
||||
expected: {
|
||||
httpProxy: "http://http-proxy.test:8080",
|
||||
httpsProxy: "http://https-proxy.test:8443",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "treats empty lower-case all_proxy as authoritative over upper-case ALL_PROXY",
|
||||
env: {
|
||||
all_proxy: "",
|
||||
ALL_PROXY: "socks5://upper-all-proxy.test:1080",
|
||||
} as NodeJS.ProcessEnv,
|
||||
expected: undefined,
|
||||
},
|
||||
])("$name", ({ env, expected }) => {
|
||||
expect(resolveEnvHttpProxyAgentOptions(env)).toEqual(expected);
|
||||
expect(hasEnvHttpProxyAgentConfigured(env)).toBe(expected !== undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe("matchesNoProxy", () => {
|
||||
it.each([
|
||||
{
|
||||
|
||||
@@ -25,6 +25,11 @@ function normalizeProxyEnvValue(value: string | undefined): string | null | unde
|
||||
return trimmed.length > 0 ? trimmed : null;
|
||||
}
|
||||
|
||||
export type EnvHttpProxyAgentProxyOptions = {
|
||||
httpProxy?: string;
|
||||
httpsProxy?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Match undici EnvHttpProxyAgent semantics for env-based HTTP/S proxy selection:
|
||||
* - lower-case vars take precedence over upper-case
|
||||
@@ -54,6 +59,37 @@ export function hasEnvHttpProxyConfigured(
|
||||
return resolveEnvHttpProxyUrl(protocol, env) !== undefined;
|
||||
}
|
||||
|
||||
function resolveEnvAllProxyUrl(env: NodeJS.ProcessEnv): string | undefined {
|
||||
const lowerAllProxy = normalizeProxyEnvValue(env.all_proxy);
|
||||
const allProxy =
|
||||
lowerAllProxy !== undefined ? lowerAllProxy : normalizeProxyEnvValue(env.ALL_PROXY);
|
||||
return allProxy ?? undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build explicit options for undici's EnvHttpProxyAgent.
|
||||
*
|
||||
* EnvHttpProxyAgent does not read ALL_PROXY itself, but it accepts explicit
|
||||
* HTTP/HTTPS proxy overrides. Keep this helper separate from the
|
||||
* HTTP(S)-only URL helpers so SSRF trusted-env proxy gates do not widen.
|
||||
*/
|
||||
export function resolveEnvHttpProxyAgentOptions(
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): EnvHttpProxyAgentProxyOptions | undefined {
|
||||
const allProxy = resolveEnvAllProxyUrl(env);
|
||||
const httpProxy = resolveEnvHttpProxyUrl("http", env) ?? allProxy;
|
||||
const httpsProxy = resolveEnvHttpProxyUrl("https", env) ?? httpProxy;
|
||||
const options: EnvHttpProxyAgentProxyOptions = {
|
||||
...(httpProxy ? { httpProxy } : {}),
|
||||
...(httpsProxy ? { httpsProxy } : {}),
|
||||
};
|
||||
return options.httpProxy || options.httpsProxy ? options : undefined;
|
||||
}
|
||||
|
||||
export function hasEnvHttpProxyAgentConfigured(env: NodeJS.ProcessEnv = process.env): boolean {
|
||||
return resolveEnvHttpProxyAgentOptions(env) !== undefined;
|
||||
}
|
||||
|
||||
export function shouldUseEnvHttpProxyForUrl(
|
||||
targetUrl: string,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
|
||||
@@ -29,9 +29,9 @@ const { ProxyAgent, EnvHttpProxyAgent, undiciFetch, proxyAgentSpy, envAgentSpy,
|
||||
}
|
||||
class EnvHttpProxyAgent {
|
||||
static lastCreated: EnvHttpProxyAgent | undefined;
|
||||
constructor() {
|
||||
constructor(public readonly options?: Record<string, unknown>) {
|
||||
EnvHttpProxyAgent.lastCreated = this;
|
||||
envAgentSpy();
|
||||
envAgentSpy(options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ describe("resolveProxyFetchFromEnv", () => {
|
||||
HTTPS_PROXY: "http://proxy.test:8080",
|
||||
});
|
||||
expect(fetchFn).toBeDefined();
|
||||
expect(envAgentSpy).toHaveBeenCalled();
|
||||
expect(envAgentSpy).toHaveBeenCalledWith({ httpsProxy: "http://proxy.test:8080" });
|
||||
|
||||
await fetchFn!("https://api.example.com");
|
||||
expect(undiciFetch).toHaveBeenCalledWith(
|
||||
@@ -174,7 +174,10 @@ describe("resolveProxyFetchFromEnv", () => {
|
||||
HTTP_PROXY: "http://fallback.test:3128",
|
||||
});
|
||||
expect(fetchFn).toBeDefined();
|
||||
expect(envAgentSpy).toHaveBeenCalled();
|
||||
expect(envAgentSpy).toHaveBeenCalledWith({
|
||||
httpProxy: "http://fallback.test:3128",
|
||||
httpsProxy: "http://fallback.test:3128",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns proxy fetch when lowercase https_proxy is set", () => {
|
||||
@@ -185,7 +188,7 @@ describe("resolveProxyFetchFromEnv", () => {
|
||||
https_proxy: "http://lower.test:1080",
|
||||
});
|
||||
expect(fetchFn).toBeDefined();
|
||||
expect(envAgentSpy).toHaveBeenCalled();
|
||||
expect(envAgentSpy).toHaveBeenCalledWith({ httpsProxy: "http://lower.test:1080" });
|
||||
});
|
||||
|
||||
it("returns proxy fetch when lowercase http_proxy is set", () => {
|
||||
@@ -196,7 +199,25 @@ describe("resolveProxyFetchFromEnv", () => {
|
||||
http_proxy: "http://lower-http.test:1080",
|
||||
});
|
||||
expect(fetchFn).toBeDefined();
|
||||
expect(envAgentSpy).toHaveBeenCalled();
|
||||
expect(envAgentSpy).toHaveBeenCalledWith({
|
||||
httpProxy: "http://lower-http.test:1080",
|
||||
httpsProxy: "http://lower-http.test:1080",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns proxy fetch when ALL_PROXY is set", () => {
|
||||
const fetchFn = resolveProxyFetchFromEnv({
|
||||
HTTPS_PROXY: "",
|
||||
HTTP_PROXY: "",
|
||||
https_proxy: "",
|
||||
http_proxy: "",
|
||||
ALL_PROXY: "socks5://all-proxy.test:1080",
|
||||
});
|
||||
expect(fetchFn).toBeDefined();
|
||||
expect(envAgentSpy).toHaveBeenCalledWith({
|
||||
httpProxy: "socks5://all-proxy.test:1080",
|
||||
httpsProxy: "socks5://all-proxy.test:1080",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns undefined when EnvHttpProxyAgent constructor throws", () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { EnvHttpProxyAgent, ProxyAgent, fetch as undiciFetch } from "undici";
|
||||
import { logWarn } from "../../logger.js";
|
||||
import { formatErrorMessage } from "../errors.js";
|
||||
import { hasEnvHttpProxyConfigured } from "./proxy-env.js";
|
||||
import { resolveEnvHttpProxyAgentOptions } from "./proxy-env.js";
|
||||
|
||||
export const PROXY_FETCH_PROXY_URL = Symbol.for("openclaw.proxyFetch.proxyUrl");
|
||||
type ProxyFetchWithMetadata = typeof fetch & {
|
||||
@@ -46,8 +46,7 @@ export function getProxyUrlFromFetch(fetchImpl?: typeof fetch): string | undefin
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a proxy-aware fetch from standard environment variables
|
||||
* (HTTPS_PROXY, HTTP_PROXY, https_proxy, http_proxy).
|
||||
* Resolve a proxy-aware fetch from standard environment variables.
|
||||
* Respects NO_PROXY / no_proxy exclusions via undici's EnvHttpProxyAgent.
|
||||
* Returns undefined when no proxy is configured.
|
||||
* Gracefully returns undefined if the proxy URL is malformed.
|
||||
@@ -55,11 +54,12 @@ export function getProxyUrlFromFetch(fetchImpl?: typeof fetch): string | undefin
|
||||
export function resolveProxyFetchFromEnv(
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): typeof fetch | undefined {
|
||||
if (!hasEnvHttpProxyConfigured("https", env)) {
|
||||
const proxyOptions = resolveEnvHttpProxyAgentOptions(env);
|
||||
if (!proxyOptions) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
const agent = new EnvHttpProxyAgent();
|
||||
const agent = new EnvHttpProxyAgent(proxyOptions);
|
||||
return ((input: RequestInfo | URL, init?: RequestInit) =>
|
||||
undiciFetch(input as string | URL, {
|
||||
...(init as Record<string, unknown>),
|
||||
|
||||
@@ -60,7 +60,8 @@ vi.mock("node:net", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("./proxy-env.js", () => ({
|
||||
hasEnvHttpProxyConfigured: vi.fn(() => false),
|
||||
hasEnvHttpProxyAgentConfigured: vi.fn(() => false),
|
||||
resolveEnvHttpProxyAgentOptions: vi.fn(() => undefined),
|
||||
}));
|
||||
|
||||
vi.mock("../wsl.js", () => ({
|
||||
@@ -68,7 +69,7 @@ vi.mock("../wsl.js", () => ({
|
||||
}));
|
||||
|
||||
import { isWSL2Sync } from "../wsl.js";
|
||||
import { hasEnvHttpProxyConfigured } from "./proxy-env.js";
|
||||
import { hasEnvHttpProxyAgentConfigured, resolveEnvHttpProxyAgentOptions } from "./proxy-env.js";
|
||||
let DEFAULT_UNDICI_STREAM_TIMEOUT_MS: typeof import("./undici-global-dispatcher.js").DEFAULT_UNDICI_STREAM_TIMEOUT_MS;
|
||||
let ensureGlobalUndiciEnvProxyDispatcher: typeof import("./undici-global-dispatcher.js").ensureGlobalUndiciEnvProxyDispatcher;
|
||||
let ensureGlobalUndiciStreamTimeouts: typeof import("./undici-global-dispatcher.js").ensureGlobalUndiciStreamTimeouts;
|
||||
@@ -91,7 +92,8 @@ describe("ensureGlobalUndiciStreamTimeouts", () => {
|
||||
resetGlobalUndiciStreamTimeoutsForTests();
|
||||
setCurrentDispatcher(new Agent());
|
||||
getDefaultAutoSelectFamily.mockReturnValue(undefined);
|
||||
vi.mocked(hasEnvHttpProxyConfigured).mockReturnValue(false);
|
||||
vi.mocked(hasEnvHttpProxyAgentConfigured).mockReturnValue(false);
|
||||
vi.mocked(resolveEnvHttpProxyAgentOptions).mockReturnValue(undefined);
|
||||
});
|
||||
|
||||
it("replaces default Agent dispatcher with extended stream timeouts", () => {
|
||||
@@ -127,6 +129,28 @@ describe("ensureGlobalUndiciStreamTimeouts", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves explicit env proxy options when replacing EnvHttpProxyAgent dispatcher", () => {
|
||||
vi.mocked(resolveEnvHttpProxyAgentOptions).mockReturnValue({
|
||||
httpProxy: "socks5://proxy.test:1080",
|
||||
httpsProxy: "socks5://proxy.test:1080",
|
||||
});
|
||||
setCurrentDispatcher(new EnvHttpProxyAgent());
|
||||
|
||||
ensureGlobalUndiciStreamTimeouts();
|
||||
|
||||
expect(setGlobalDispatcher).toHaveBeenCalledTimes(1);
|
||||
const next = getCurrentDispatcher() as { options?: Record<string, unknown> };
|
||||
expect(next).toBeInstanceOf(EnvHttpProxyAgent);
|
||||
expect(next.options).toEqual(
|
||||
expect.objectContaining({
|
||||
httpProxy: "socks5://proxy.test:1080",
|
||||
httpsProxy: "socks5://proxy.test:1080",
|
||||
bodyTimeout: DEFAULT_UNDICI_STREAM_TIMEOUT_MS,
|
||||
headersTimeout: DEFAULT_UNDICI_STREAM_TIMEOUT_MS,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("records timeout bridge but does not override unsupported custom proxy dispatcher types", () => {
|
||||
setCurrentDispatcher(new ProxyAgent("http://proxy.test:8080"));
|
||||
|
||||
@@ -201,11 +225,12 @@ describe("ensureGlobalUndiciEnvProxyDispatcher", () => {
|
||||
vi.clearAllMocks();
|
||||
resetGlobalUndiciStreamTimeoutsForTests();
|
||||
setCurrentDispatcher(new Agent());
|
||||
vi.mocked(hasEnvHttpProxyConfigured).mockReturnValue(false);
|
||||
vi.mocked(hasEnvHttpProxyAgentConfigured).mockReturnValue(false);
|
||||
vi.mocked(resolveEnvHttpProxyAgentOptions).mockReturnValue(undefined);
|
||||
});
|
||||
|
||||
it("installs EnvHttpProxyAgent when env HTTP proxy is configured on a default Agent", () => {
|
||||
vi.mocked(hasEnvHttpProxyConfigured).mockReturnValue(true);
|
||||
vi.mocked(hasEnvHttpProxyAgentConfigured).mockReturnValue(true);
|
||||
|
||||
ensureGlobalUndiciEnvProxyDispatcher();
|
||||
|
||||
@@ -213,8 +238,26 @@ describe("ensureGlobalUndiciEnvProxyDispatcher", () => {
|
||||
expect(getCurrentDispatcher()).toBeInstanceOf(EnvHttpProxyAgent);
|
||||
});
|
||||
|
||||
it("installs EnvHttpProxyAgent with explicit ALL_PROXY fallback options", () => {
|
||||
vi.mocked(hasEnvHttpProxyAgentConfigured).mockReturnValue(true);
|
||||
vi.mocked(resolveEnvHttpProxyAgentOptions).mockReturnValue({
|
||||
httpProxy: "socks5://proxy.test:1080",
|
||||
httpsProxy: "socks5://proxy.test:1080",
|
||||
});
|
||||
|
||||
ensureGlobalUndiciEnvProxyDispatcher();
|
||||
|
||||
expect(setGlobalDispatcher).toHaveBeenCalledTimes(1);
|
||||
const next = getCurrentDispatcher() as { options?: Record<string, unknown> };
|
||||
expect(next).toBeInstanceOf(EnvHttpProxyAgent);
|
||||
expect(next.options).toEqual({
|
||||
httpProxy: "socks5://proxy.test:1080",
|
||||
httpsProxy: "socks5://proxy.test:1080",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not override unsupported custom proxy dispatcher types", () => {
|
||||
vi.mocked(hasEnvHttpProxyConfigured).mockReturnValue(true);
|
||||
vi.mocked(hasEnvHttpProxyAgentConfigured).mockReturnValue(true);
|
||||
setCurrentDispatcher(new ProxyAgent("http://proxy.test:8080"));
|
||||
|
||||
ensureGlobalUndiciEnvProxyDispatcher();
|
||||
@@ -223,7 +266,7 @@ describe("ensureGlobalUndiciEnvProxyDispatcher", () => {
|
||||
});
|
||||
|
||||
it("retries proxy bootstrap after an unsupported dispatcher later becomes a default Agent", () => {
|
||||
vi.mocked(hasEnvHttpProxyConfigured).mockReturnValue(true);
|
||||
vi.mocked(hasEnvHttpProxyAgentConfigured).mockReturnValue(true);
|
||||
setCurrentDispatcher(new ProxyAgent("http://proxy.test:8080"));
|
||||
|
||||
ensureGlobalUndiciEnvProxyDispatcher();
|
||||
@@ -237,7 +280,7 @@ describe("ensureGlobalUndiciEnvProxyDispatcher", () => {
|
||||
});
|
||||
|
||||
it("is idempotent after proxy bootstrap succeeds", () => {
|
||||
vi.mocked(hasEnvHttpProxyConfigured).mockReturnValue(true);
|
||||
vi.mocked(hasEnvHttpProxyAgentConfigured).mockReturnValue(true);
|
||||
|
||||
ensureGlobalUndiciEnvProxyDispatcher();
|
||||
ensureGlobalUndiciEnvProxyDispatcher();
|
||||
@@ -246,7 +289,7 @@ describe("ensureGlobalUndiciEnvProxyDispatcher", () => {
|
||||
});
|
||||
|
||||
it("reinstalls env proxy if an external change later reverts the dispatcher to Agent", () => {
|
||||
vi.mocked(hasEnvHttpProxyConfigured).mockReturnValue(true);
|
||||
vi.mocked(hasEnvHttpProxyAgentConfigured).mockReturnValue(true);
|
||||
|
||||
ensureGlobalUndiciEnvProxyDispatcher();
|
||||
expect(setGlobalDispatcher).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as net from "node:net";
|
||||
import { Agent, EnvHttpProxyAgent, getGlobalDispatcher, setGlobalDispatcher } from "undici";
|
||||
import { isWSL2Sync } from "../wsl.js";
|
||||
import { hasEnvHttpProxyConfigured } from "./proxy-env.js";
|
||||
import { hasEnvHttpProxyAgentConfigured, resolveEnvHttpProxyAgentOptions } from "./proxy-env.js";
|
||||
|
||||
export const DEFAULT_UNDICI_STREAM_TIMEOUT_MS = 30 * 60 * 1000;
|
||||
|
||||
@@ -89,7 +89,7 @@ function resolveCurrentDispatcherKind(): DispatcherKind | null {
|
||||
}
|
||||
|
||||
export function ensureGlobalUndiciEnvProxyDispatcher(): void {
|
||||
const shouldUseEnvProxy = hasEnvHttpProxyConfigured("https");
|
||||
const shouldUseEnvProxy = hasEnvHttpProxyAgentConfigured();
|
||||
if (!shouldUseEnvProxy) {
|
||||
return;
|
||||
}
|
||||
@@ -108,7 +108,7 @@ export function ensureGlobalUndiciEnvProxyDispatcher(): void {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setGlobalDispatcher(new EnvHttpProxyAgent());
|
||||
setGlobalDispatcher(new EnvHttpProxyAgent(resolveEnvHttpProxyAgentOptions()));
|
||||
lastAppliedProxyBootstrap = true;
|
||||
} catch {
|
||||
// Best-effort bootstrap only.
|
||||
@@ -137,6 +137,7 @@ export function ensureGlobalUndiciStreamTimeouts(opts?: { timeoutMs?: number }):
|
||||
try {
|
||||
if (kind === "env-proxy") {
|
||||
const proxyOptions = {
|
||||
...resolveEnvHttpProxyAgentOptions(),
|
||||
bodyTimeout: timeoutMs,
|
||||
headersTimeout: timeoutMs,
|
||||
...(connect ? { connect } : {}),
|
||||
|
||||
Reference in New Issue
Block a user