


























@@ -19,10 +19,7 @@ import { danger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtim
1919import { getChildLogger } from "openclaw/plugin-sdk/runtime-env";
2020import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
2121import { createNonExitingRuntime, type RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
22-import {
23-normalizeOptionalLowercaseString,
24-normalizeOptionalString,
25-} from "openclaw/plugin-sdk/string-coerce-runtime";
22+import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
2623import { getOrCreateAccountThrottler } from "./account-throttler.js";
2724import { resolveTelegramAccount } from "./accounts.js";
2825import { normalizeTelegramApiRoot } from "./api-root.js";
@@ -36,9 +33,14 @@ import { resolveDefaultAgentId } from "./bot.agent.runtime.js";
3633import { apiThrottler, Bot, sequentialize, type ApiClientOptions } from "./bot.runtime.js";
3734import type { TelegramBotOptions } from "./bot.types.js";
3835import { buildTelegramGroupPeerId, resolveTelegramStreamMode } from "./bot/helpers.js";
36+import {
37+asTelegramClientFetch,
38+createTelegramClientFetch,
39+resolveTelegramClientTimeoutMinimumSeconds,
40+resolveTelegramClientTimeoutSeconds,
41+resolveTelegramOutboundClientTimeoutFloorSeconds,
42+} from "./client-fetch.js";
3943import { resolveTelegramTransport } from "./fetch.js";
40-import { tagTelegramNetworkError } from "./network-errors.js";
41-import { resolveTelegramRequestTimeoutMs } from "./request-timeouts.js";
4244import { createTelegramSendChatActionHandler } from "./sendchataction-401-backoff.js";
4345import { getTelegramSequentialKey } from "./sequential-key.js";
4446import { createTelegramThreadBindingManager } from "./thread-bindings.js";
@@ -67,113 +69,6 @@ export function setTelegramBotRuntimeForTest(runtime?: TelegramBotRuntime): void
6769telegramBotRuntimeForTest = runtime;
6870}
697170-type TelegramFetchInput = Parameters<NonNullable<ApiClientOptions["fetch"]>>[0];
71-type TelegramFetchInit = Parameters<NonNullable<ApiClientOptions["fetch"]>>[1];
72-type TelegramClientFetch = NonNullable<ApiClientOptions["fetch"]>;
73-type TelegramCompatFetch = (
74-input: TelegramFetchInput,
75-init?: TelegramFetchInit,
76-) => ReturnType<TelegramClientFetch>;
77-type TelegramAbortSignalLike = {
78-aborted: boolean;
79-reason?: unknown;
80-addEventListener: (type: "abort", listener: () => void, options?: { once?: boolean }) => void;
81-removeEventListener: (type: "abort", listener: () => void) => void;
82-};
83-84-function asTelegramClientFetch(
85-fetchImpl: TelegramCompatFetch | typeof globalThis.fetch,
86-): TelegramClientFetch {
87-return fetchImpl as unknown as TelegramClientFetch;
88-}
89-90-function asTelegramCompatFetch(fetchImpl: TelegramClientFetch): TelegramCompatFetch {
91-return fetchImpl as unknown as TelegramCompatFetch;
92-}
93-94-function isTelegramAbortSignalLike(value: unknown): value is TelegramAbortSignalLike {
95-return (
96-typeof value === "object" &&
97-value !== null &&
98-"aborted" in value &&
99-typeof (value as { aborted?: unknown }).aborted === "boolean" &&
100-typeof (value as { addEventListener?: unknown }).addEventListener === "function" &&
101-typeof (value as { removeEventListener?: unknown }).removeEventListener === "function"
102-);
103-}
104-105-function readRequestUrl(input: TelegramFetchInput): string | null {
106-if (typeof input === "string") {
107-return input;
108-}
109-if (input instanceof URL) {
110-return input.toString();
111-}
112-if (input instanceof Request) {
113-return input.url;
114-}
115-return null;
116-}
117-118-function extractTelegramApiMethod(input: TelegramFetchInput): string | null {
119-const url = readRequestUrl(input);
120-if (!url) {
121-return null;
122-}
123-try {
124-const pathname = new URL(url).pathname;
125-const segments = pathname.split("/").filter(Boolean);
126-const method = segments.length > 0 ? (segments.at(-1) ?? null) : null;
127-return normalizeOptionalLowercaseString(method) ?? null;
128-} catch {
129-return null;
130-}
131-}
132-133-const TELEGRAM_TIMEOUT_FALLBACK_METHODS = new Set([
134-"deletemycommands",
135-"deletewebhook",
136-"getme",
137-"sendchataction",
138-"setmycommands",
139-"setwebhook",
140-]);
141-function shouldRetryTimedOutTelegramControlRequest(method: string | null): boolean {
142-return method !== null && TELEGRAM_TIMEOUT_FALLBACK_METHODS.has(method);
143-}
144-145-function resolveTelegramClientTimeoutSeconds(params: {
146-value: unknown;
147-minimum?: number;
148-}): number | undefined {
149-const { value, minimum } = params;
150-if (typeof value !== "number" || !Number.isFinite(value)) {
151-return undefined;
152-}
153-const configured = Math.max(1, Math.floor(value));
154-if (typeof minimum !== "number" || !Number.isFinite(minimum)) {
155-return configured;
156-}
157-return Math.max(configured, Math.max(1, Math.floor(minimum)));
158-}
159-160-function resolveTelegramClientTimeoutMinimumSeconds(values: readonly (number | undefined)[]) {
161-let minimum: number | undefined;
162-for (const value of values) {
163-if (typeof value !== "number" || !Number.isFinite(value)) {
164-continue;
165-}
166-const normalized = Math.max(1, Math.ceil(value));
167-minimum = minimum === undefined ? normalized : Math.max(minimum, normalized);
168-}
169-return minimum;
170-}
171-172-function resolveTelegramOutboundClientTimeoutFloorSeconds(timeoutSeconds: unknown) {
173-const timeoutMs = resolveTelegramRequestTimeoutMs("sendmessage", timeoutSeconds);
174-return timeoutMs === undefined ? undefined : timeoutMs / 1000;
175-}
176-17772export function createTelegramBotCore(
17873opts: TelegramBotOptions & { telegramDeps: TelegramBotDeps },
17974): TelegramBotInstance {
@@ -214,123 +109,12 @@ export function createTelegramBotCore(
214109resolveTelegramTransport(opts.proxyFetch, {
215110network: telegramCfg.network,
216111});
217-const shouldProvideFetch = Boolean(telegramTransport.fetch);
218-// grammY's ApiClientOptions types still track `node-fetch` types; Node 22+ global fetch
219-// (undici) is structurally compatible at runtime but not assignable in TS.
220-const fetchForClient = telegramTransport.fetch
221- ? asTelegramCompatFetch(asTelegramClientFetch(telegramTransport.fetch))
222- : undefined;
223-224-// Wrap fetch so polling requests cannot hang indefinitely on a wedged network path,
225-// and so shutdown still aborts in-flight Telegram API requests immediately.
226-let finalFetch: TelegramCompatFetch | undefined = shouldProvideFetch ? fetchForClient : undefined;
227-if (finalFetch || opts.fetchAbortSignal) {
228-const baseFetch = finalFetch ?? asTelegramCompatFetch(asTelegramClientFetch(globalThis.fetch));
229-// Cast baseFetch to global fetch to avoid node-fetch ↔ global-fetch type divergence;
230-// they are runtime-compatible (the codebase already casts at every fetch boundary).
231-const callFetch = baseFetch;
232-// Use manual event forwarding instead of AbortSignal.any() to avoid the cross-realm
233-// AbortSignal issue in Node.js (grammY's signal may come from a different module context,
234-// causing "signals[0] must be an instance of AbortSignal" errors).
235-finalFetch = async (input: TelegramFetchInput, init?: TelegramFetchInit) => {
236-const method = extractTelegramApiMethod(input);
237-const requestTimeoutMs = resolveTelegramRequestTimeoutMs(method, telegramCfg?.timeoutSeconds);
238-const shutdownSignal = isTelegramAbortSignalLike(opts.fetchAbortSignal)
239- ? opts.fetchAbortSignal
240- : undefined;
241-const requestSignal = isTelegramAbortSignalLike(init?.signal) ? init.signal : undefined;
242-243-const runFetch = async () => {
244-const controller = new AbortController();
245-const abortWith = (signal: Pick<TelegramAbortSignalLike, "reason">) =>
246-controller.abort(signal.reason);
247-const onShutdown = () => {
248-if (shutdownSignal) {
249-abortWith(shutdownSignal);
250-}
251-};
252-let requestTimeout: ReturnType<typeof setTimeout> | undefined;
253-let onRequestAbort: (() => void) | undefined;
254-let requestTimedOut = false;
255-const timeoutError =
256-requestTimeoutMs !== undefined
257- ? new Error(`Telegram ${method} timed out after ${requestTimeoutMs}ms`)
258- : undefined;
259-260-if (shutdownSignal?.aborted) {
261-abortWith(shutdownSignal);
262-} else if (shutdownSignal) {
263-shutdownSignal.addEventListener("abort", onShutdown, { once: true });
264-}
265-if (requestSignal) {
266-if (requestSignal.aborted) {
267-abortWith(requestSignal);
268-} else {
269-onRequestAbort = () => abortWith(requestSignal);
270-requestSignal.addEventListener("abort", onRequestAbort);
271-}
272-}
273-if (requestTimeoutMs && timeoutError) {
274-requestTimeout = setTimeout(() => {
275-requestTimedOut = true;
276-controller.abort(timeoutError);
277-}, requestTimeoutMs);
278-requestTimeout.unref?.();
279-}
280-try {
281-return await callFetch(input, {
282- ...init,
283-signal: controller.signal,
284-});
285-} catch (err) {
286-if (requestTimedOut && timeoutError) {
287-throw timeoutError;
288-}
289-throw err;
290-} finally {
291-if (requestTimeout) {
292-clearTimeout(requestTimeout);
293-}
294-shutdownSignal?.removeEventListener("abort", onShutdown);
295-if (requestSignal && onRequestAbort) {
296-requestSignal.removeEventListener("abort", onRequestAbort);
297-}
298-}
299-};
300-301-try {
302-return await runFetch();
303-} catch (err) {
304-if (
305-requestTimeoutMs &&
306-shouldRetryTimedOutTelegramControlRequest(method) &&
307-!shutdownSignal?.aborted &&
308-!requestSignal?.aborted &&
309-telegramTransport.forceFallback?.("request-timeout")
310-) {
311-return await runFetch();
312-}
313-throw err;
314-}
315-};
316-}
317-if (finalFetch) {
318-const baseFetch = finalFetch;
319-finalFetch = (input: TelegramFetchInput, init?: TelegramFetchInit) => {
320-return Promise.resolve(baseFetch(input, init)).catch((err: unknown) => {
321-try {
322-tagTelegramNetworkError(err, {
323-method: extractTelegramApiMethod(input),
324-url: readRequestUrl(input),
325-});
326-} catch {
327-// Tagging is best-effort; preserve the original fetch failure if the
328-// error object cannot accept extra metadata.
329-}
330-throw err;
331-});
332-};
333-}
112+const finalFetch = createTelegramClientFetch({
113+fetchImpl: asTelegramClientFetch(telegramTransport.fetch),
114+timeoutSeconds: telegramCfg?.timeoutSeconds,
115+shutdownSignal: opts.fetchAbortSignal,
116+transport: telegramTransport,
117+});
334118335119const timeoutSeconds = resolveTelegramClientTimeoutSeconds({
336120value: telegramCfg?.timeoutSeconds,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。