openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
645 lines (644 loc) • 24.9 kB
JavaScript
import { a as normalizeLowercaseStringOrEmpty, s as normalizeOptionalLowercaseString } from "./string-coerce-mnp54Vah.js";
import { t as isTruthyEnvValue } from "./env-Di49gJwo.js";
import { C as resolveExpiresAtMsFromDurationMs, m as isFutureDateTimestampMs } from "./number-coercion-CJQ8TR--.js";
import { i as formatErrorMessage } from "./errors-BXgSefBE.js";
import { t as createSubsystemLogger } from "./subsystem-BzXSmsuh.js";
import { n as isWSL2Sync } from "./wsl-Bb_g7JpO.js";
import { n as hasEnvHttpProxyAgentConfigured, o as resolveEnvHttpProxyAgentOptions } from "./proxy-env-B9aW4MXJ.js";
import { n as createHttp1EnvHttpProxyAgent, r as createHttp1ProxyAgent } from "./undici-runtime-CdQVyjAR.js";
import { o as createPinnedLookup } from "./ssrf-CvPEXMGn.js";
import { i as resolveEffectiveDebugProxyUrl } from "./env-ckWue_wY.js";
import "./error-runtime-C8vbtAJt.js";
import { n as getProxyUrlFromFetch, r as makeProxyFetch } from "./proxy-fetch-Ii-XBOip.js";
import "./number-runtime-DBLVDypr.js";
import "./runtime-env-D_2q8-VK.js";
import "./string-coerce-runtime-CEGJWkQ_.js";
import { t as resolveFetch } from "./fetch-C0IyrVwh.js";
import "./fetch-runtime-hJGvq3C8.js";
import { t as captureHttpExchange } from "./runtime-DrwkIUiy.js";
import "./proxy-capture-x_2F2qw1.js";
import { t as resolveRequestUrl } from "./request-url-vv3NYC-S.js";
import process$1 from "node:process";
import { randomUUID } from "node:crypto";
import * as dns from "node:dns";
import { Agent, fetch } from "undici";
//#region extensions/telegram/src/api-root.ts
const DEFAULT_TELEGRAM_API_ROOT = "https://api.telegram.org";
const TELEGRAM_BOT_ENDPOINT_SEGMENT_RE = /^bot\d+:[^/]+$/u;
function isTelegramBotEndpointSegment(segment) {
try {
return TELEGRAM_BOT_ENDPOINT_SEGMENT_RE.test(decodeURIComponent(segment));
} catch {
return TELEGRAM_BOT_ENDPOINT_SEGMENT_RE.test(segment);
}
}
function normalizeTelegramApiRoot(apiRoot) {
const trimmed = apiRoot?.trim();
if (!trimmed) return DEFAULT_TELEGRAM_API_ROOT;
let normalized = trimmed.replace(/\/+$/u, "");
try {
const url = new URL(normalized);
const segments = url.pathname.split("/").filter(Boolean);
if (segments.length > 0 && isTelegramBotEndpointSegment(segments[segments.length - 1] ?? "")) {
segments.pop();
url.pathname = segments.length > 0 ? `/${segments.join("/")}` : "/";
url.search = "";
url.hash = "";
normalized = url.toString().replace(/\/+$/u, "");
}
} catch {}
return normalized;
}
function hasTelegramBotEndpointApiRoot(apiRoot) {
if (typeof apiRoot !== "string" || !apiRoot.trim()) return false;
try {
const segments = new URL(apiRoot.trim()).pathname.split("/").filter(Boolean);
const last = segments[segments.length - 1];
return Boolean(last && isTelegramBotEndpointSegment(last));
} catch {
return false;
}
}
//#endregion
//#region extensions/telegram/src/network-config.ts
const TELEGRAM_DISABLE_AUTO_SELECT_FAMILY_ENV = "OPENCLAW_TELEGRAM_DISABLE_AUTO_SELECT_FAMILY";
const TELEGRAM_ENABLE_AUTO_SELECT_FAMILY_ENV = "OPENCLAW_TELEGRAM_ENABLE_AUTO_SELECT_FAMILY";
const TELEGRAM_DNS_RESULT_ORDER_ENV = "OPENCLAW_TELEGRAM_DNS_RESULT_ORDER";
let wsl2SyncCache;
function isWSL2SyncCached() {
if (typeof wsl2SyncCache === "boolean") return wsl2SyncCache;
wsl2SyncCache = isWSL2Sync();
return wsl2SyncCache;
}
function resolveTelegramAutoSelectFamilyDecision(params) {
const env = params?.env ?? process$1.env;
const nodeMajor = typeof params?.nodeMajor === "number" ? params.nodeMajor : Number(process$1.versions.node.split(".")[0]);
if (isTruthyEnvValue(env["OPENCLAW_TELEGRAM_ENABLE_AUTO_SELECT_FAMILY"])) return {
value: true,
source: `env:${TELEGRAM_ENABLE_AUTO_SELECT_FAMILY_ENV}`
};
if (isTruthyEnvValue(env["OPENCLAW_TELEGRAM_DISABLE_AUTO_SELECT_FAMILY"])) return {
value: false,
source: `env:${TELEGRAM_DISABLE_AUTO_SELECT_FAMILY_ENV}`
};
if (typeof params?.network?.autoSelectFamily === "boolean") return {
value: params.network.autoSelectFamily,
source: "config"
};
if (isWSL2SyncCached()) return {
value: false,
source: "default-wsl2"
};
if (Number.isFinite(nodeMajor) && nodeMajor >= 22) return {
value: true,
source: "default-node22"
};
return { value: null };
}
/**
* Resolve DNS result order setting for Telegram network requests.
* Some networks/ISPs have issues with IPv6 causing fetch failures.
* Setting "ipv4first" prioritizes IPv4 addresses in DNS resolution.
*
* Priority:
* 1. Environment variable OPENCLAW_TELEGRAM_DNS_RESULT_ORDER
* 2. Config: channels.telegram.network.dnsResultOrder
* 3. Process default: dns.getDefaultResultOrder()
* 4. Default: "ipv4first" on Node 22+ (to work around common IPv6 issues)
*/
function resolveTelegramDnsResultOrderDecision(params) {
const env = params?.env ?? process$1.env;
const nodeMajor = typeof params?.nodeMajor === "number" ? params.nodeMajor : Number(process$1.versions.node.split(".")[0]);
const envValue = normalizeOptionalLowercaseString(env[TELEGRAM_DNS_RESULT_ORDER_ENV]);
if (envValue === "ipv4first" || envValue === "verbatim") return {
value: envValue,
source: `env:${TELEGRAM_DNS_RESULT_ORDER_ENV}`
};
const configValue = normalizeOptionalLowercaseString((params?.network)?.dnsResultOrder);
if (configValue === "ipv4first" || configValue === "verbatim") return {
value: configValue,
source: "config"
};
const processDefaultValue = normalizeOptionalLowercaseString(params && "defaultResultOrder" in params ? params.defaultResultOrder : dns.getDefaultResultOrder?.());
if (processDefaultValue === "ipv4first" || processDefaultValue === "verbatim") return {
value: processDefaultValue,
source: "process-default"
};
if (Number.isFinite(nodeMajor) && nodeMajor >= 22) return {
value: "ipv4first",
source: "default-node22"
};
return { value: null };
}
//#endregion
//#region extensions/telegram/src/fetch.ts
const log = createSubsystemLogger("telegram/network");
const TELEGRAM_AUTO_SELECT_FAMILY_ATTEMPT_TIMEOUT_MS = 300;
const TELEGRAM_API_HOSTNAME = "api.telegram.org";
const TELEGRAM_FALLBACK_IPS = ["149.154.167.220"];
const TELEGRAM_DISPATCHER_KEEP_ALIVE_TIMEOUT_MS = 3e4;
const TELEGRAM_DISPATCHER_KEEP_ALIVE_MAX_TIMEOUT_MS = 6e5;
const TELEGRAM_DISPATCHER_CONNECTIONS_PER_ORIGIN = 10;
const TELEGRAM_DISPATCHER_PIPELINING = 1;
const TELEGRAM_STICKY_FALLBACK_PRIMARY_PROBE_SUCCESS_THRESHOLD = 5;
const TELEGRAM_TRANSPORT_ATTEMPT_FAILURE_THRESHOLD = 5;
const TELEGRAM_TRANSPORT_ATTEMPT_INITIAL_COOLDOWN_MS = 1e4;
const TELEGRAM_TRANSPORT_ATTEMPT_MAX_COOLDOWN_MS = 6e4;
function telegramAgentPoolOptions() {
return {
allowH2: false,
keepAliveTimeout: TELEGRAM_DISPATCHER_KEEP_ALIVE_TIMEOUT_MS,
keepAliveMaxTimeout: TELEGRAM_DISPATCHER_KEEP_ALIVE_MAX_TIMEOUT_MS,
connections: TELEGRAM_DISPATCHER_CONNECTIONS_PER_ORIGIN,
pipelining: TELEGRAM_DISPATCHER_PIPELINING
};
}
const FALLBACK_RETRY_ERROR_CODES = [
"ETIMEDOUT",
"ENETDOWN",
"ENETUNREACH",
"EHOSTUNREACH",
"UND_ERR_CONNECT_TIMEOUT",
"UND_ERR_SOCKET"
];
function normalizeDnsResultOrder(value) {
if (value === "ipv4first" || value === "verbatim") return value;
return null;
}
function createDnsResultOrderLookup(order) {
if (!order) return;
const lookup = dns.lookup;
return (hostname, options, callback) => {
lookup(hostname, {
...typeof options === "number" ? { family: options } : options ? { ...options } : {},
order,
verbatim: order === "verbatim"
}, callback);
};
}
const TELEGRAM_KEEPALIVE_INITIAL_DELAY_MS = 3e4;
function buildTelegramConnectOptions(params) {
const connect = {
keepAlive: true,
keepAliveInitialDelay: TELEGRAM_KEEPALIVE_INITIAL_DELAY_MS
};
if (params.forceIpv4) {
connect.family = 4;
connect.autoSelectFamily = false;
} else if (typeof params.autoSelectFamily === "boolean") {
connect.autoSelectFamily = params.autoSelectFamily;
connect.autoSelectFamilyAttemptTimeout = TELEGRAM_AUTO_SELECT_FAMILY_ATTEMPT_TIMEOUT_MS;
}
const lookup = createDnsResultOrderLookup(params.dnsResultOrder);
if (lookup) connect.lookup = lookup;
return connect;
}
function shouldBypassEnvProxyForTelegramApi(env = process.env) {
const noProxyValue = env.no_proxy ?? env.NO_PROXY ?? "";
if (!noProxyValue) return false;
if (noProxyValue === "*") return true;
const targetHostname = normalizeLowercaseStringOrEmpty(TELEGRAM_API_HOSTNAME);
const targetPort = 443;
const noProxyEntries = noProxyValue.split(/[,\s]/);
for (const entry of noProxyEntries) {
if (!entry) continue;
const parsed = entry.match(/^(.+):(\d+)$/);
const entryHostname = normalizeLowercaseStringOrEmpty((parsed ? parsed[1] : entry).replace(/^\*?\./, ""));
const entryPort = parsed ? Number.parseInt(parsed[2], 10) : 0;
if (entryPort && entryPort !== targetPort) continue;
if (targetHostname === entryHostname || targetHostname.slice(-(entryHostname.length + 1)) === `.${entryHostname}`) return true;
}
return false;
}
function hasEnvHttpProxyForTelegramApi(env = process.env) {
return hasEnvHttpProxyAgentConfigured(env);
}
function resolveOpenClawProxyUrlForTelegram(env = process.env) {
const proxyUrl = env.OPENCLAW_PROXY_URL?.trim();
return proxyUrl ? proxyUrl : void 0;
}
function resolveTelegramDispatcherPolicy(params) {
const connect = buildTelegramConnectOptions({
autoSelectFamily: params.autoSelectFamily,
dnsResultOrder: params.dnsResultOrder,
forceIpv4: params.forceIpv4
});
const explicitProxyUrl = params.proxyUrl?.trim();
if (explicitProxyUrl) return {
policy: {
mode: "explicit-proxy",
proxyUrl: explicitProxyUrl,
allowPrivateProxy: true,
proxyTls: { ...connect }
},
mode: "explicit-proxy"
};
if (params.useEnvProxy) return {
policy: {
mode: "env-proxy",
connect: { ...connect },
proxyTls: { ...connect }
},
mode: "env-proxy"
};
return {
policy: {
mode: "direct",
connect: { ...connect }
},
mode: "direct"
};
}
function withPinnedLookup(options, pinnedHostname) {
if (!pinnedHostname) return options ? { ...options } : void 0;
const lookup = createPinnedLookup({
hostname: pinnedHostname.hostname,
addresses: [...pinnedHostname.addresses],
fallback: dns.lookup
});
return options ? {
...options,
lookup
} : { lookup };
}
function createTelegramDispatcher(policy) {
const poolOptions = telegramAgentPoolOptions();
if (policy.mode === "explicit-proxy") {
const requestTlsOptions = withPinnedLookup(policy.proxyTls, policy.pinnedHostname);
const proxyOptions = {
uri: policy.proxyUrl,
...poolOptions,
...requestTlsOptions ? { requestTls: requestTlsOptions } : {}
};
try {
return {
dispatcher: createHttp1ProxyAgent(proxyOptions),
mode: "explicit-proxy",
effectivePolicy: policy
};
} catch (err) {
const reason = formatErrorMessage(err);
throw new Error(`explicit proxy dispatcher init failed: ${reason}`, { cause: err });
}
}
if (policy.mode === "env-proxy") {
const connectOptions = withPinnedLookup(policy.connect, policy.pinnedHostname);
const proxyTlsOptions = withPinnedLookup(policy.proxyTls, policy.pinnedHostname);
const proxyOptions = {
...poolOptions,
...resolveEnvHttpProxyAgentOptions(),
...connectOptions ? { connect: connectOptions } : {},
...proxyTlsOptions ? { proxyTls: proxyTlsOptions } : {}
};
try {
return {
dispatcher: createHttp1EnvHttpProxyAgent(proxyOptions),
mode: "env-proxy",
effectivePolicy: policy
};
} catch (err) {
log.warn(`env proxy dispatcher init failed; falling back to direct dispatcher: ${formatErrorMessage(err)}`);
const directPolicy = {
mode: "direct",
...connectOptions ? { connect: connectOptions } : {}
};
return {
dispatcher: new Agent({
...poolOptions,
...directPolicy.connect ? { connect: directPolicy.connect } : {}
}),
mode: "direct",
effectivePolicy: directPolicy
};
}
}
const connectOptions = withPinnedLookup(policy.connect, policy.pinnedHostname);
return {
dispatcher: new Agent({
...poolOptions,
...connectOptions ? { connect: connectOptions } : {}
}),
mode: "direct",
effectivePolicy: policy
};
}
function withDispatcherIfMissing(init, dispatcher) {
if (init?.dispatcher) return init ?? {};
return init ? {
...init,
dispatcher
} : { dispatcher };
}
function resolveWrappedFetch(fetchImpl) {
return resolveFetch(fetchImpl) ?? fetchImpl;
}
function logResolverNetworkDecisions(params) {
if (params.autoSelectDecision.value !== null) {
const sourceLabel = params.autoSelectDecision.source ? ` (${params.autoSelectDecision.source})` : "";
log.debug(`autoSelectFamily=${params.autoSelectDecision.value}${sourceLabel}`);
}
if (params.dnsDecision.value !== null) {
const sourceLabel = params.dnsDecision.source ? ` (${params.dnsDecision.source})` : "";
log.debug(`dnsResultOrder=${params.dnsDecision.value}${sourceLabel}`);
}
}
function collectErrorCodes(err) {
const codes = /* @__PURE__ */ new Set();
const queue = [err];
const seen = /* @__PURE__ */ new Set();
let queueIndex = 0;
while (queueIndex < queue.length) {
const current = queue[queueIndex++];
if (!current || seen.has(current)) continue;
seen.add(current);
if (typeof current === "object") {
const code = current.code;
if (typeof code === "string" && code.trim()) codes.add(code.trim().toUpperCase());
const cause = current.cause;
if (cause && !seen.has(cause)) queue.push(cause);
const errors = current.errors;
if (Array.isArray(errors)) {
for (const nested of errors) if (nested && !seen.has(nested)) queue.push(nested);
}
}
}
return codes;
}
function formatErrorCodes(err) {
const codes = [...collectErrorCodes(err)];
return codes.length > 0 ? codes.join(",") : "none";
}
var TelegramTransportAttemptUnhealthyError = class extends Error {
constructor(unhealthyUntilMs) {
const remainingMs = Math.max(0, unhealthyUntilMs - Date.now());
super(`telegram transport attempt temporarily unhealthy; retry after ${remainingMs}ms`);
this.name = "TelegramTransportAttemptUnhealthyError";
}
};
function shouldUseTelegramTransportFallback(err) {
if (err instanceof TelegramTransportAttemptUnhealthyError) return true;
const ctx = {
message: err && typeof err === "object" && "message" in err ? normalizeLowercaseStringOrEmpty(String(err.message)) : "",
codes: collectErrorCodes(err)
};
const hasFetchFailedEnvelope = ctx.message.includes("fetch failed");
return FALLBACK_RETRY_ERROR_CODES.some((code) => ctx.codes.has(code)) || hasFetchFailedEnvelope && ctx.codes.size === 0;
}
function shouldRetryTelegramTransportFallback(err) {
return shouldUseTelegramTransportFallback(err);
}
function createTelegramTransportAttempts(params) {
params.ownedDispatchers.add(params.defaultDispatcher.dispatcher);
const attempts = [{
createDispatcher: () => params.defaultDispatcher.dispatcher,
exportAttempt: { dispatcherPolicy: params.defaultDispatcher.effectivePolicy }
}];
if (!params.allowFallback || !params.fallbackPolicy) return attempts;
const fallbackPolicy = params.fallbackPolicy;
const ownedDispatchers = params.ownedDispatchers;
let ipv4Dispatcher = null;
attempts.push({
createDispatcher: () => {
if (!ipv4Dispatcher) {
ipv4Dispatcher = createTelegramDispatcher(fallbackPolicy).dispatcher;
ownedDispatchers.add(ipv4Dispatcher);
}
return ipv4Dispatcher;
},
exportAttempt: { dispatcherPolicy: fallbackPolicy },
logLevel: "debug",
logMessage: "fetch fallback: enabling sticky IPv4-only dispatcher"
});
if (TELEGRAM_FALLBACK_IPS.length === 0) return attempts;
const fallbackIpPolicy = {
...fallbackPolicy,
pinnedHostname: {
hostname: TELEGRAM_API_HOSTNAME,
addresses: [...TELEGRAM_FALLBACK_IPS]
}
};
let fallbackIpDispatcher = null;
attempts.push({
createDispatcher: () => {
if (!fallbackIpDispatcher) {
fallbackIpDispatcher = createTelegramDispatcher(fallbackIpPolicy).dispatcher;
ownedDispatchers.add(fallbackIpDispatcher);
}
return fallbackIpDispatcher;
},
exportAttempt: { dispatcherPolicy: fallbackIpPolicy },
logLevel: "warn",
logMessage: "fetch fallback: DNS-resolved IP unreachable; trying alternative Telegram API IP"
});
return attempts;
}
async function destroyOwnedDispatchers(dispatchers) {
await Promise.all([...dispatchers].map(async (dispatcher) => {
try {
await dispatcher.destroy();
} catch {}
}));
}
function resolveTelegramTransport(proxyFetch, options) {
const autoSelectDecision = resolveTelegramAutoSelectFamilyDecision({ network: options?.network });
const dnsDecision = resolveTelegramDnsResultOrderDecision({ network: options?.network });
logResolverNetworkDecisions({
autoSelectDecision,
dnsDecision
});
const effectiveProxyFetch = proxyFetch ?? (() => {
const debugProxyUrl = resolveEffectiveDebugProxyUrl(void 0);
return debugProxyUrl ? makeProxyFetch(debugProxyUrl) : void 0;
})();
const explicitProxyUrl = effectiveProxyFetch ? getProxyUrlFromFetch(effectiveProxyFetch) : void 0;
const hasEnvProxy = !explicitProxyUrl && hasEnvHttpProxyForTelegramApi();
const managedProxyUrl = !effectiveProxyFetch && !hasEnvProxy ? resolveOpenClawProxyUrlForTelegram() : void 0;
const resolvedExplicitProxyUrl = explicitProxyUrl ?? managedProxyUrl;
const undiciSourceFetch = resolveWrappedFetch(fetch);
const sourceFetch = resolvedExplicitProxyUrl ? undiciSourceFetch : effectiveProxyFetch ? resolveWrappedFetch(effectiveProxyFetch) : undiciSourceFetch;
const dnsResultOrder = normalizeDnsResultOrder(dnsDecision.value);
if (effectiveProxyFetch && !explicitProxyUrl) return {
fetch: sourceFetch,
sourceFetch,
close: async () => {}
};
const useEnvProxy = !resolvedExplicitProxyUrl && hasEnvProxy;
const defaultDispatcher = createTelegramDispatcher(resolveTelegramDispatcherPolicy({
autoSelectFamily: autoSelectDecision.value,
dnsResultOrder,
useEnvProxy,
forceIpv4: false,
proxyUrl: resolvedExplicitProxyUrl
}).policy);
const shouldBypassEnvProxy = shouldBypassEnvProxyForTelegramApi();
const allowStickyFallback = defaultDispatcher.mode === "direct" || defaultDispatcher.mode === "env-proxy" && shouldBypassEnvProxy;
const fallbackDispatcherPolicy = allowStickyFallback ? resolveTelegramDispatcherPolicy({
autoSelectFamily: false,
dnsResultOrder: "ipv4first",
useEnvProxy: defaultDispatcher.mode === "env-proxy",
forceIpv4: true,
proxyUrl: resolvedExplicitProxyUrl
}).policy : void 0;
const ownedDispatchers = /* @__PURE__ */ new Set();
const transportAttempts = createTelegramTransportAttempts({
defaultDispatcher,
allowFallback: allowStickyFallback,
fallbackPolicy: fallbackDispatcherPolicy,
ownedDispatchers
});
let stickyAttemptIndex = 0;
let stickySuccessCount = 0;
let primaryProbeDue = false;
const attemptHealth = transportAttempts.map(() => ({
consecutiveFailures: 0,
cooldownMs: TELEGRAM_TRANSPORT_ATTEMPT_INITIAL_COOLDOWN_MS,
unhealthyUntilMs: 0
}));
const resetStickyRecoveryProbe = () => {
stickySuccessCount = 0;
primaryProbeDue = false;
};
const getAttemptCooldownError = (attemptIndex) => {
const health = attemptHealth[attemptIndex];
if (!isFutureDateTimestampMs(health.unhealthyUntilMs)) return null;
return new TelegramTransportAttemptUnhealthyError(health.unhealthyUntilMs);
};
const recordAttemptFailure = (attemptIndex, err) => {
if (!shouldUseTelegramTransportFallback(err)) return;
const health = attemptHealth[attemptIndex];
health.consecutiveFailures += 1;
if (health.consecutiveFailures < TELEGRAM_TRANSPORT_ATTEMPT_FAILURE_THRESHOLD) return;
const cooldownMs = Math.min(TELEGRAM_TRANSPORT_ATTEMPT_MAX_COOLDOWN_MS, Math.max(TELEGRAM_TRANSPORT_ATTEMPT_INITIAL_COOLDOWN_MS, health.cooldownMs));
health.consecutiveFailures = 0;
health.cooldownMs = Math.min(TELEGRAM_TRANSPORT_ATTEMPT_MAX_COOLDOWN_MS, cooldownMs * 2);
const unhealthyUntilMs = resolveExpiresAtMsFromDurationMs(cooldownMs);
if (unhealthyUntilMs === void 0) {
health.unhealthyUntilMs = 0;
return;
}
health.unhealthyUntilMs = unhealthyUntilMs;
log.warn(`telegram transport attempt marked temporarily unhealthy for ${cooldownMs}ms (codes=${formatErrorCodes(err)})`);
};
const promoteStickyAttempt = (nextIndex, err, reason) => {
if (nextIndex <= stickyAttemptIndex || nextIndex >= transportAttempts.length) return false;
const nextAttempt = transportAttempts[nextIndex];
if (nextAttempt.logMessage) {
const reasonText = reason ? `, reason=${reason}` : "";
const logLine = `${nextAttempt.logMessage} (codes=${formatErrorCodes(err)}${reasonText})`;
if (nextAttempt.logLevel === "debug") log.debug(logLine);
else log.warn(logLine);
}
stickyAttemptIndex = nextIndex;
resetStickyRecoveryProbe();
return true;
};
const recordSuccessfulAttempt = (attemptIndex) => {
const health = attemptHealth[attemptIndex];
health.consecutiveFailures = 0;
health.cooldownMs = TELEGRAM_TRANSPORT_ATTEMPT_INITIAL_COOLDOWN_MS;
health.unhealthyUntilMs = 0;
if (stickyAttemptIndex === 0) {
resetStickyRecoveryProbe();
return;
}
if (attemptIndex < stickyAttemptIndex) {
log.debug(`fetch fallback: recovered from attempt ${stickyAttemptIndex} to attempt ${attemptIndex}`);
stickyAttemptIndex = attemptIndex;
resetStickyRecoveryProbe();
return;
}
if (attemptIndex !== stickyAttemptIndex) return;
stickySuccessCount += 1;
if (stickySuccessCount >= TELEGRAM_STICKY_FALLBACK_PRIMARY_PROBE_SUCCESS_THRESHOLD) {
stickySuccessCount = 0;
primaryProbeDue = true;
log.debug("fetch fallback: scheduling primary dispatcher recovery probe");
}
};
const resolvedFetch = (async (input, init) => {
const callerProvidedDispatcher = Boolean(init?.dispatcher);
const stickyStartIndex = Math.min(stickyAttemptIndex, transportAttempts.length - 1);
const stickyCooldownError = callerProvidedDispatcher ? null : getAttemptCooldownError(stickyStartIndex);
const primaryProbe = !callerProvidedDispatcher && stickyStartIndex > 0 && (primaryProbeDue || stickyCooldownError !== null);
const startIndex = primaryProbe ? 0 : stickyStartIndex;
if (primaryProbe) {
primaryProbeDue = false;
log.debug(stickyCooldownError ? "fetch fallback: re-probing primary dispatcher while sticky fallback is cooling down" : "fetch fallback: re-probing primary dispatcher after sticky fallback successes");
}
let err;
if (callerProvidedDispatcher) try {
const response = await sourceFetch(input, init);
captureHttpExchange({
url: resolveRequestUrl(input),
method: init?.method ?? "GET",
requestHeaders: init?.headers,
requestBody: init?.body ?? null,
response,
flowId: randomUUID(),
meta: { subsystem: "telegram-fetch" }
});
return response;
} catch (caught) {
if (!shouldUseTelegramTransportFallback(caught)) throw caught;
return sourceFetch(input, init ?? {});
}
for (let attemptIndex = startIndex; attemptIndex < transportAttempts.length; attemptIndex += 1) {
const attempt = transportAttempts[attemptIndex];
if (attemptIndex > startIndex) promoteStickyAttempt(attemptIndex, err);
const cooldownError = getAttemptCooldownError(attemptIndex);
if (cooldownError) {
err = cooldownError;
continue;
}
try {
const response = await sourceFetch(input, withDispatcherIfMissing(init, attempt.createDispatcher()));
captureHttpExchange({
url: resolveRequestUrl(input),
method: init?.method ?? "GET",
requestHeaders: init?.headers,
requestBody: init?.body ?? null,
response,
flowId: randomUUID(),
meta: attemptIndex === startIndex ? { subsystem: "telegram-fetch" } : {
subsystem: "telegram-fetch",
fallbackAttempt: attemptIndex
}
});
recordSuccessfulAttempt(attemptIndex);
return response;
} catch (caught) {
err = caught;
if (!shouldUseTelegramTransportFallback(err)) throw err;
recordAttemptFailure(attemptIndex, err);
}
}
throw err;
});
let closed = false;
const close = async () => {
if (closed) return;
closed = true;
const toDestroy = [...ownedDispatchers];
ownedDispatchers.clear();
await destroyOwnedDispatchers(toDestroy);
};
return {
fetch: resolvedFetch,
sourceFetch,
dispatcherAttempts: transportAttempts.map((attempt) => attempt.exportAttempt),
forceFallback: (reason) => promoteStickyAttempt(stickyAttemptIndex + 1, /* @__PURE__ */ new Error("forced fallback"), reason),
close
};
}
function resolveTelegramFetch(proxyFetch, options) {
return resolveTelegramTransport(proxyFetch, options).fetch;
}
/**
* Resolve the Telegram Bot API base URL from an optional `apiRoot` config value.
* Returns a trimmed URL without trailing slash, or the standard default.
*/
function resolveTelegramApiBase(apiRoot) {
return normalizeTelegramApiRoot(apiRoot);
}
//#endregion
export { hasTelegramBotEndpointApiRoot as a, shouldRetryTelegramTransportFallback as i, resolveTelegramFetch as n, normalizeTelegramApiRoot as o, resolveTelegramTransport as r, resolveTelegramApiBase as t };