openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
737 lines (736 loc) • 29 kB
JavaScript
import http from "node:http";
import https from "node:https";
import { AsyncLocalStorage } from "node:async_hooks";
import { X509Certificate } from "node:crypto";
import net from "node:net";
import tls from "node:tls";
// The explicit peer entry avoids Bun's builtin while remaining statically bundleable.
import { Agent as UndiciAgent, Dispatcher, FormData as UndiciFormData, Headers as UndiciHeaders, Pool as UndiciPool, Request as UndiciRequest, Response as UndiciResponse, errors as undiciErrors, fetch as undiciFetch, getGlobalDispatcher, ProxyAgent as UndiciProxyAgent, setGlobalDispatcher, } from "undici/index.js";
import { createAmbientProxyResolver, EMPTY_PROXY_ENV, resolveAmbientProxyForUrl, readProxyEnv, } from "./env.js";
import { bindNodeHttpMethod, createDirectNodeAgent, createNodeProxyAgent, } from "./node-http.js";
import { formatUrl, ProxylineError, redactProxyUrl, resolveProxyTlsCa, } from "./shared.js";
import { PROXYLINE_DISPATCHER_BRAND } from "./dispatcher-brand.js";
let activeRuntime;
let activeHandle;
// Node's global fetch types come from bundled undici-types, while the runtime
// implementation intentionally delegates to this package's undici dependency.
const proxylineHeaders = UndiciHeaders;
const proxylineRequest = UndiciRequest;
const proxylineResponse = UndiciResponse;
const proxylineFormData = UndiciFormData;
function getRequestDispatcher(request) {
for (const symbol of Object.getOwnPropertySymbols(request)) {
if (symbol.description !== "dispatcher") {
continue;
}
return Reflect.get(request, symbol);
}
return undefined;
}
function isFetchRequestLike(value) {
if (typeof value !== "object" || value === null) {
return false;
}
const record = value;
return (typeof record.url === "string" &&
typeof record.method === "string" &&
typeof record.arrayBuffer === "function" &&
record.headers !== undefined);
}
async function createProxylineRequestFromRequestLike(request, options) {
const init = {
headers: request.headers,
method: request.method,
};
if (request.cache !== undefined) {
init.cache = request.cache;
}
if (request.credentials !== undefined) {
init.credentials = request.credentials;
}
if (request.integrity !== undefined) {
init.integrity = request.integrity;
}
if (request.keepalive !== undefined) {
init.keepalive = request.keepalive;
}
if (request.mode !== undefined) {
init.mode = request.mode;
}
if (request.redirect !== undefined) {
init.redirect = request.redirect;
}
if (request.referrer !== undefined) {
init.referrer = request.referrer;
}
if (request.referrerPolicy !== undefined) {
init.referrerPolicy = request.referrerPolicy;
}
if (options.preserveDispatcher) {
const dispatcher = getRequestDispatcher(request);
if (dispatcher !== undefined) {
Reflect.set(init, "dispatcher", dispatcher);
}
}
if (request.signal !== undefined) {
init.signal = request.signal;
}
if (options.includeBody &&
request.body !== null &&
request.method !== "GET" &&
request.method !== "HEAD") {
init.body = request.body;
init.duplex = "half";
}
const requestUnknown = Reflect.construct(proxylineRequest, [request.url, init]);
if (!(requestUnknown instanceof proxylineRequest)) {
throw new TypeError("Proxyline failed to normalize a fetch Request.");
}
return requestUnknown;
}
function requestInitOverridesBody(init) {
if (typeof init !== "object" || init === null) {
return false;
}
return "body" in init;
}
async function normalizeFetchInput(input, init, options) {
if ((input instanceof proxylineRequest && options.preserveDispatcher) || !isFetchRequestLike(input)) {
return input;
}
return await createProxylineRequestFromRequestLike(input, {
includeBody: !requestInitOverridesBody(init),
preserveDispatcher: options.preserveDispatcher,
});
}
function withManagedFetchDispatcher(init, dispatcher) {
if (init !== undefined &&
init !== null &&
typeof init !== "object" &&
typeof init !== "function") {
throw new TypeError(`Request constructor: Expected ${String(init)} to be one of: Null, Undefined, Object.`);
}
const sanitized = init === undefined || init === null ? {} : Object.create(init);
Reflect.defineProperty(sanitized, "dispatcher", {
configurable: true,
enumerable: true,
value: dispatcher,
writable: true,
});
return sanitized;
}
const proxylineFetch = async (input, init) => {
const managedDispatcher = activeRuntime?.mode === "managed"
? activeRuntime.installedDispatcher
: undefined;
const normalizedInput = await normalizeFetchInput(input, init, {
preserveDispatcher: managedDispatcher === undefined,
});
const normalizedInit = managedDispatcher === undefined
? init
: withManagedFetchDispatcher(init, managedDispatcher);
const response = await Reflect.apply(undiciFetch, undefined, normalizedInit === undefined ? [normalizedInput] : [normalizedInput, normalizedInit]);
if (!(response instanceof proxylineResponse)) {
throw new TypeError("Proxyline fetch returned a non-Response value.");
}
return response;
};
function normalizeProxyUrl(value) {
if (value === undefined) {
return undefined;
}
const url = value instanceof URL ? new URL(value.href) : new URL(value);
if (url.protocol !== "http:" && url.protocol !== "https:") {
throw new ProxylineError("UNSUPPORTED_PROXY_PROTOCOL", `Proxyline only supports http:// and https:// proxy endpoints in this slice: ${url.protocol}`);
}
return url;
}
function emit(onEvent, event) {
try {
onEvent?.(event);
}
catch {
// Observer failures must not fail install or leave patched globals unstoppable.
}
}
function isProxyableUrlProtocol(protocol) {
return protocol === "http:" ||
protocol === "https:" ||
protocol === "ws:" ||
protocol === "wss:";
}
function shouldBypassManagedProxy(bypassPolicy, bypasses, url, surface) {
if (bypasses.has(url, surface)) {
return true;
}
if (bypassPolicy === undefined) {
return false;
}
return bypassPolicy({ surface, url: formatUrl(url) });
}
function bypassKey(url, surface) {
return `${surface ?? "*"}\n${formatUrl(url)}`;
}
function createDynamicBypassRegistry() {
const counts = new Map();
const scopedBypasses = new AsyncLocalStorage();
const hasScopedBypass = (url, surface) => {
const scoped = scopedBypasses.getStore();
return scoped !== undefined &&
(scoped.has(bypassKey(url, surface)) || scoped.has(bypassKey(url, undefined)));
};
return {
add: (registration) => {
const key = bypassKey(registration.url, registration.surface);
counts.set(key, (counts.get(key) ?? 0) + 1);
let stopped = false;
return () => {
if (stopped) {
return;
}
stopped = true;
const next = (counts.get(key) ?? 1) - 1;
if (next <= 0) {
counts.delete(key);
}
else {
counts.set(key, next);
}
};
},
has: (url, surface) => hasScopedBypass(url, surface) ||
(counts.get(bypassKey(url, surface)) ?? 0) > 0 ||
(counts.get(bypassKey(url, undefined)) ?? 0) > 0,
runScoped: (registration, run) => {
const inherited = scopedBypasses.getStore();
const scoped = new Set(inherited);
scoped.add(bypassKey(registration.url, registration.surface));
return scopedBypasses.run(scoped, run);
},
};
}
function proxyEnvSnapshotKey(env) {
return JSON.stringify(env ?? EMPTY_PROXY_ENV);
}
function createManagedProxyResolver(proxyUrl, bypassPolicy, bypasses) {
const redactedProxyUrl = redactProxyUrl(proxyUrl);
return {
active: true,
describeProxy: () => redactedProxyUrl,
explain: (url, surface) => {
const formattedUrl = formatUrl(url);
if (!isProxyableUrlProtocol(new URL(url).protocol)) {
return {
kind: "direct",
reason: "managed-proxy-unsupported-url-scheme",
surface,
url: formattedUrl,
};
}
if (shouldBypassManagedProxy(bypassPolicy, bypasses, url, surface)) {
return {
kind: "direct",
reason: "managed-proxy-bypass-policy",
surface,
url: formattedUrl,
};
}
return {
kind: "proxied",
reason: "managed-proxy-active",
surface,
url: formattedUrl,
proxyUrl: redactedProxyUrl,
};
},
getProxyForUrl: (url, surface = "unknown") => {
const protocol = new URL(url).protocol;
return isProxyableUrlProtocol(protocol) &&
!shouldBypassManagedProxy(bypassPolicy, bypasses, url, surface)
? proxyUrl.href
: "";
},
};
}
function finiteNonNegativeInteger(value) {
return typeof value === "number" && Number.isFinite(value) && value >= 0
? Math.floor(value)
: undefined;
}
function finitePositiveInteger(value) {
return typeof value === "number" && Number.isFinite(value) && value > 0
? Math.floor(value)
: undefined;
}
function resolveUndiciBaseOptions(options) {
const bodyTimeout = finiteNonNegativeInteger(options?.bodyTimeout);
const headersTimeout = finiteNonNegativeInteger(options?.headersTimeout);
return {
...(options?.allowH2 !== undefined ? { allowH2: options.allowH2 } : {}),
...(bodyTimeout !== undefined ? { bodyTimeout } : {}),
...(headersTimeout !== undefined ? { headersTimeout } : {}),
...(options?.connect !== undefined
? {
connect: {
...(options.connect.autoSelectFamily !== undefined
? { autoSelectFamily: options.connect.autoSelectFamily }
: {}),
...(finitePositiveInteger(options.connect.autoSelectFamilyAttemptTimeout) !== undefined
? {
autoSelectFamilyAttemptTimeout: finitePositiveInteger(options.connect.autoSelectFamilyAttemptTimeout),
}
: {}),
},
}
: {}),
};
}
function createUndiciAgent(options) {
return new UndiciAgent(resolveUndiciBaseOptions(options));
}
function isObjectRecord(value) {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function stripIpServernameFromConnectOptions(options) {
if (!isObjectRecord(options) || typeof options.servername !== "string") {
return options;
}
const servername = options.servername.replace(/^\[|\]$/g, "");
if (net.isIP(servername) === 0) {
return options;
}
const next = { ...options };
delete next.servername;
return next;
}
function stripIpServernameFromConnect(connect) {
if (typeof connect !== "function") {
return connect;
}
return (options, callback) => connect(stripIpServernameFromConnectOptions(options), callback);
}
const checkProxyServerIdentity = (hostname, certificate) => {
const normalizedHostname = hostname.replace(/^\[|\]$/g, "");
if (net.isIP(normalizedHostname) === 6) {
try {
// Compare binary addresses so compressed and expanded IPv6 SAN forms remain equivalent.
if (new X509Certificate(certificate.raw).checkIP(normalizedHostname) !== undefined) {
return undefined;
}
}
catch {
// Let Node return its canonical certificate validation error below.
}
}
return tls.checkServerIdentity(normalizedHostname, certificate);
};
function createProxyClientFactory() {
return (origin, options) => {
const clientOptions = isObjectRecord(options)
? { ...options, connect: stripIpServernameFromConnect(options.connect) }
: options;
return new UndiciPool(origin, clientOptions);
};
}
function createUndiciProxyAgent(proxyUrl, options) {
const proxyHostname = new URL(proxyUrl).hostname.replace(/^\[|\]$/g, "");
const proxyTls = options.proxyCa !== undefined || net.isIP(proxyHostname) === 6
? {
...(options.proxyCa !== undefined ? { ca: options.proxyCa } : {}),
...(net.isIP(proxyHostname) === 6 ? { checkServerIdentity: checkProxyServerIdentity } : {}),
}
: undefined;
const dispatcherFactory = createProxyClientFactory();
return new UndiciProxyAgent({
...resolveUndiciBaseOptions(options.undici),
uri: proxyUrl,
clientFactory: dispatcherFactory,
factory: dispatcherFactory,
...(proxyTls !== undefined ? { proxyTls } : {}),
});
}
function normalizeUndiciDispatchOptions(options) {
if (options.origin === undefined || !/^https?:\/\//i.test(options.path)) {
return options;
}
const pathUrl = new URL(options.path);
return {
...options,
path: `${pathUrl.pathname}${pathUrl.search}`,
};
}
function createUndiciProxyDispatcher(options, dispatcherOptions) {
if (options.mode === "ambient") {
if (!options.active) {
return createUndiciAgent(dispatcherOptions.undici);
}
return new AmbientUndiciDispatcher(options.env, dispatcherOptions);
}
return new ManagedUndiciDispatcher(options.resolver, dispatcherOptions);
}
function reportClosedDispatchError(handler, error) {
const compatibleHandler = handler;
if (compatibleHandler.onResponseError !== undefined) {
compatibleHandler.onResponseError(null, error);
return false;
}
if (compatibleHandler.onError !== undefined) {
compatibleHandler.onError(error);
return false;
}
throw error;
}
class ManagedUndiciDispatcher extends Dispatcher {
[PROXYLINE_DISPATCHER_BRAND] = true;
#directDispatcher;
#dispatcherOptions;
#proxyDispatchers = new Map();
#resolver;
#closedError;
constructor(resolver, dispatcherOptions) {
super();
this.#resolver = resolver;
this.#dispatcherOptions = dispatcherOptions;
this.#directDispatcher = createUndiciAgent(dispatcherOptions.undici);
}
dispatch(options, handler) {
if (this.#closedError !== undefined) {
return reportClosedDispatchError(handler, this.#closedError);
}
const normalizedOptions = normalizeUndiciDispatchOptions(options);
const url = resolveUndiciDispatchUrl(normalizedOptions);
const proxyUrl = url === undefined ? "" : this.#resolver.getProxyForUrl(url, "undici");
const dispatcher = proxyUrl === "" ? this.#directDispatcher : this.#proxyDispatcher(proxyUrl);
return dispatcher.dispatch(normalizedOptions, handler);
}
close(callback) {
const closing = this.#closeAll();
if (callback === undefined) {
return closing;
}
closing.then(callback, callback);
}
destroy(errorOrCallback, callback) {
const error = typeof errorOrCallback === "function" ? null : errorOrCallback ?? null;
const destroyCallback = typeof errorOrCallback === "function" ? errorOrCallback : callback;
const destroying = this.#destroyAll(error);
if (destroyCallback === undefined) {
return destroying;
}
destroying.then(destroyCallback, destroyCallback);
}
#proxyDispatcher(proxyUrl) {
const existing = this.#proxyDispatchers.get(proxyUrl);
if (existing !== undefined) {
return existing;
}
const dispatcher = createUndiciProxyAgent(proxyUrl, this.#dispatcherOptions);
this.#proxyDispatchers.set(proxyUrl, dispatcher);
return dispatcher;
}
async #closeAll() {
this.#closedError ??= new undiciErrors.ClientClosedError();
const proxyDispatchers = [...this.#proxyDispatchers.values()];
this.#proxyDispatchers.clear();
await Promise.all([
this.#directDispatcher.close(),
...proxyDispatchers.map((dispatcher) => dispatcher.close()),
]);
}
async #destroyAll(error) {
this.#closedError ??= error ?? new undiciErrors.ClientDestroyedError();
const proxyDispatchers = [...this.#proxyDispatchers.values()];
this.#proxyDispatchers.clear();
await Promise.all([
this.#directDispatcher.destroy(error),
...proxyDispatchers.map((dispatcher) => dispatcher.destroy(error)),
]);
}
}
class AmbientUndiciDispatcher extends Dispatcher {
[PROXYLINE_DISPATCHER_BRAND] = true;
#directDispatcher;
#dispatcherOptions;
#env;
#proxyDispatchers = new Map();
#closedError;
constructor(env, dispatcherOptions) {
super();
this.#env = env;
this.#dispatcherOptions = dispatcherOptions;
this.#directDispatcher = createUndiciAgent(dispatcherOptions.undici);
}
dispatch(options, handler) {
if (this.#closedError !== undefined) {
return reportClosedDispatchError(handler, this.#closedError);
}
const normalizedOptions = normalizeUndiciDispatchOptions(options);
const url = resolveUndiciDispatchUrl(normalizedOptions);
const proxyUrl = url === undefined ? undefined : resolveAmbientProxyForUrl(url, this.#env);
const dispatcher = proxyUrl === undefined ? this.#directDispatcher : this.#proxyDispatcher(proxyUrl);
return dispatcher.dispatch(normalizedOptions, handler);
}
close(callback) {
const closing = this.#closeAll();
if (callback === undefined) {
return closing;
}
closing.then(callback, callback);
}
destroy(errorOrCallback, callback) {
const error = typeof errorOrCallback === "function" ? null : errorOrCallback ?? null;
const destroyCallback = typeof errorOrCallback === "function" ? errorOrCallback : callback;
const destroying = this.#destroyAll(error);
if (destroyCallback === undefined) {
return destroying;
}
destroying.then(destroyCallback, destroyCallback);
}
#proxyDispatcher(proxyUrl) {
const existing = this.#proxyDispatchers.get(proxyUrl);
if (existing !== undefined) {
return existing;
}
const dispatcher = createUndiciProxyAgent(proxyUrl, this.#dispatcherOptions);
this.#proxyDispatchers.set(proxyUrl, dispatcher);
return dispatcher;
}
async #closeAll() {
this.#closedError ??= new undiciErrors.ClientClosedError();
const proxyDispatchers = [...this.#proxyDispatchers.values()];
this.#proxyDispatchers.clear();
await Promise.all([
this.#directDispatcher.close(),
...proxyDispatchers.map((dispatcher) => dispatcher.close()),
]);
}
async #destroyAll(error) {
this.#closedError ??= error ?? new undiciErrors.ClientDestroyedError();
const proxyDispatchers = [...this.#proxyDispatchers.values()];
this.#proxyDispatchers.clear();
await Promise.all([
this.#directDispatcher.destroy(error),
...proxyDispatchers.map((dispatcher) => dispatcher.destroy(error)),
]);
}
}
function resolveUndiciDispatchUrl(options) {
if (options.origin !== undefined) {
const origin = options.origin.toString().replace(/\/$/, "");
const path = options.path.startsWith("/") ? options.path : `/${options.path}`;
return new URL(`${origin}${path}`).href;
}
try {
return new URL(options.path).href;
}
catch {
return undefined;
}
}
function restoreNodeHttpSnapshot(snapshot) {
http.request = snapshot.httpRequest;
http.get = snapshot.httpGet;
http.globalAgent = snapshot.httpGlobalAgent;
https.request = snapshot.httpsRequest;
https.get = snapshot.httpsGet;
https.globalAgent = snapshot.httpsGlobalAgent;
}
function installRuntime(resolver, dispatcherOptions, proxyCa, options) {
if (activeRuntime !== undefined) {
throw new ProxylineError("RUNTIME_ALREADY_ACTIVE", "Proxyline already has an active runtime.");
}
const snapshot = {
httpRequest: http.request,
httpGet: http.get,
httpGlobalAgent: http.globalAgent,
httpsRequest: https.request,
httpsGet: https.get,
httpsGlobalAgent: https.globalAgent,
};
const nodeHttpAgent = createNodeProxyAgent(resolver, proxyCa, "http");
const nodeHttpsAgent = createNodeProxyAgent(resolver, proxyCa, "https");
const originalDispatcher = getGlobalDispatcher();
const originalFetch = globalThis.fetch;
const originalFormData = globalThis.FormData;
const originalHeaders = globalThis.Headers;
const originalRequest = globalThis.Request;
const originalResponse = globalThis.Response;
const installedDispatcher = createUndiciProxyDispatcher(dispatcherOptions, {
proxyCa,
undici: options.undici,
});
const runtime = {
ambientEnv: options.ambientEnv,
bypassPolicy: options.bypassPolicy,
installedDispatcher,
mode: dispatcherOptions.mode,
nodeHttpAgent,
nodeHttpsAgent,
originalDispatcher,
originalFetch,
originalFormData,
originalHeaders,
originalRequest,
originalResponse,
proxyCa,
proxyUrl: options.proxyUrl?.href,
snapshot,
undiciOptions: options.undici,
};
activeRuntime = runtime;
try {
http.globalAgent = nodeHttpAgent;
https.globalAgent = nodeHttpsAgent;
http.request = bindNodeHttpMethod(snapshot.httpRequest, () => createNodeProxyAgent(resolver, proxyCa, "http"));
http.get = bindNodeHttpMethod(snapshot.httpGet, () => createNodeProxyAgent(resolver, proxyCa, "http"));
https.request = bindNodeHttpMethod(snapshot.httpsRequest, () => createNodeProxyAgent(resolver, proxyCa, "https"));
https.get = bindNodeHttpMethod(snapshot.httpsGet, () => createNodeProxyAgent(resolver, proxyCa, "https"));
setGlobalDispatcher(installedDispatcher);
globalThis.fetch = proxylineFetch;
globalThis.FormData = proxylineFormData;
globalThis.Headers = proxylineHeaders;
globalThis.Request = proxylineRequest;
globalThis.Response = proxylineResponse;
}
catch (error) {
restoreNodeHttpSnapshot(snapshot);
setGlobalDispatcher(originalDispatcher);
globalThis.fetch = originalFetch;
globalThis.FormData = originalFormData;
globalThis.Headers = originalHeaders;
globalThis.Request = originalRequest;
globalThis.Response = originalResponse;
activeRuntime = undefined;
void installedDispatcher.destroy();
nodeHttpAgent.destroy();
nodeHttpsAgent.destroy();
throw error;
}
return runtime;
}
function stopRuntime(runtime) {
if (activeRuntime !== runtime) {
return;
}
restoreNodeHttpSnapshot(runtime.snapshot);
setGlobalDispatcher(runtime.originalDispatcher);
globalThis.fetch = runtime.originalFetch;
globalThis.FormData = runtime.originalFormData;
globalThis.Headers = runtime.originalHeaders;
globalThis.Request = runtime.originalRequest;
globalThis.Response = runtime.originalResponse;
void runtime.installedDispatcher.destroy();
runtime.nodeHttpAgent.destroy();
runtime.nodeHttpsAgent.destroy();
activeRuntime = undefined;
activeHandle = undefined;
}
export function installProxyline(options) {
const proxyUrl = options.mode === "managed" ? normalizeProxyUrl(options.proxyUrl) : undefined;
const ambientEnv = proxyUrl === undefined ? readProxyEnv() : undefined;
if (options.mode === "managed" && proxyUrl === undefined) {
throw new ProxylineError("MANAGED_PROXY_URL_REQUIRED", "Proxyline managed mode requires an explicit proxyUrl.");
}
const activePolicy = options.ifActive ?? "error";
if (activeRuntime !== undefined) {
if (activePolicy === "replace") {
activeHandle?.stop();
}
else if (activePolicy === "reuse-compatible" &&
activeHandle !== undefined &&
activeRuntime.mode === options.mode &&
activeRuntime.proxyUrl === proxyUrl?.href &&
proxyEnvSnapshotKey(activeRuntime.ambientEnv) === proxyEnvSnapshotKey(ambientEnv) &&
activeRuntime.proxyCa === resolveProxyTlsCa(options.proxyTls) &&
activeRuntime.bypassPolicy === options.bypassPolicy &&
JSON.stringify(activeRuntime.undiciOptions ?? {}) === JSON.stringify(options.undici ?? {})) {
return activeHandle;
}
else {
throw new ProxylineError("RUNTIME_ALREADY_ACTIVE", "Proxyline already has an active runtime.");
}
}
let stopped = false;
const proxyCa = resolveProxyTlsCa(options.proxyTls);
const dynamicBypasses = createDynamicBypassRegistry();
const resolver = proxyUrl !== undefined
? createManagedProxyResolver(proxyUrl, options.bypassPolicy, dynamicBypasses)
: createAmbientProxyResolver(ambientEnv ?? EMPTY_PROXY_ENV);
const redactedProxyUrl = resolver.describeProxy();
const hasActiveProxy = resolver.active;
const runtime = hasActiveProxy
? installRuntime(resolver, proxyUrl !== undefined
? { mode: "managed", resolver }
: { mode: "ambient", env: ambientEnv ?? EMPTY_PROXY_ENV, active: hasActiveProxy }, proxyCa, {
ambientEnv,
bypassPolicy: options.bypassPolicy,
proxyUrl,
undici: options.undici,
})
: undefined;
const handle = {
mode: options.mode,
active: hasActiveProxy,
...(redactedProxyUrl ? { proxyUrl: redactedProxyUrl } : {}),
createNodeAgent: () => {
if (!hasActiveProxy || stopped) {
return createDirectNodeAgent();
}
return createNodeProxyAgent(resolver, proxyCa);
},
createUndiciDispatcher: () => stopped
? createUndiciAgent(options.undici)
: createUndiciProxyDispatcher(proxyUrl !== undefined
? { mode: "managed", resolver }
: { mode: "ambient", env: ambientEnv ?? EMPTY_PROXY_ENV, active: hasActiveProxy }, { proxyCa, undici: options.undici }),
createWebSocketAgent: () => {
if (!hasActiveProxy || stopped) {
return createDirectNodeAgent();
}
return createNodeProxyAgent(resolver, proxyCa);
},
explain: (url, explainOptions) => {
const decision = stopped
? {
kind: "direct",
reason: "runtime-stopped",
surface: explainOptions?.surface ?? "unknown",
url: formatUrl(url),
}
: resolver.explain(url, explainOptions?.surface ?? "unknown");
emit(options.onEvent, { type: "decision", decision });
return decision;
},
registerBypass: (registration) => {
if (stopped || proxyUrl === undefined) {
return () => { };
}
return dynamicBypasses.add(registration);
},
stop: () => {
if (stopped) {
return;
}
stopped = true;
if (runtime !== undefined) {
stopRuntime(runtime);
}
emit(options.onEvent, { type: "runtime.stopped", mode: options.mode });
},
withBypass: (registration, run) => {
if (stopped || proxyUrl === undefined) {
return run();
}
return dynamicBypasses.runScoped(registration, run);
},
};
activeHandle = hasActiveProxy ? handle : activeHandle;
emit(options.onEvent, {
type: "runtime.installed",
mode: options.mode,
active: hasActiveProxy,
...(redactedProxyUrl ? { proxyUrl: redactedProxyUrl } : {}),
});
return handle;
}
export const installGlobalProxy = installProxyline;