diff --git a/extensions/telegram/src/channel.ts b/extensions/telegram/src/channel.ts index a375281e40..8dbf4d0bd7 100644 --- a/extensions/telegram/src/channel.ts +++ b/extensions/telegram/src/channel.ts @@ -25,6 +25,7 @@ import { type ChannelPlugin, type OpenClawConfig, type ResolvedTelegramAccount, + type TelegramProbe, } from "openclaw/plugin-sdk"; import { getTelegramRuntime } from "./runtime.js"; @@ -60,7 +61,7 @@ function parseThreadId(threadId?: string | number | null) { const parsed = Number.parseInt(trimmed, 10); return Number.isFinite(parsed) ? parsed : undefined; } -export const telegramPlugin: ChannelPlugin = { +export const telegramPlugin: ChannelPlugin = { id: "telegram", meta: { ...meta, @@ -327,11 +328,7 @@ export const telegramPlugin: ChannelPlugin = { if (!groupIds.length && unresolvedGroups === 0 && !hasWildcardUnmentionedGroups) { return undefined; } - const botId = - (probe as { ok?: boolean; bot?: { id?: number } })?.ok && - (probe as { bot?: { id?: number } }).bot?.id != null - ? (probe as { bot: { id: number } }).bot.id - : null; + const botId = probe?.ok && probe.bot?.id != null ? probe.bot.id : null; if (!botId) { return { ok: unresolvedGroups === 0 && !hasWildcardUnmentionedGroups, @@ -357,15 +354,9 @@ export const telegramPlugin: ChannelPlugin = { cfg.channels?.telegram?.accounts?.[account.accountId]?.groups ?? cfg.channels?.telegram?.groups; const allowUnmentionedGroups = - Boolean( - groups?.["*"] && (groups["*"] as { requireMention?: boolean }).requireMention === false, - ) || + groups?.["*"]?.requireMention === false || Object.entries(groups ?? {}).some( - ([key, value]) => - key !== "*" && - Boolean(value) && - typeof value === "object" && - (value as { requireMention?: boolean }).requireMention === false, + ([key, value]) => key !== "*" && value?.requireMention === false, ); return { accountId: account.accountId, diff --git a/src/channels/plugins/types.adapters.ts b/src/channels/plugins/types.adapters.ts index f1f0720b0b..ab1473bf1e 100644 --- a/src/channels/plugins/types.adapters.ts +++ b/src/channels/plugins/types.adapters.ts @@ -105,7 +105,7 @@ export type ChannelOutboundAdapter = { sendPoll?: (ctx: ChannelPollContext) => Promise; }; -export type ChannelStatusAdapter = { +export type ChannelStatusAdapter = { defaultRuntime?: ChannelAccountSnapshot; buildChannelSummary?: (params: { account: ResolvedAccount; @@ -117,19 +117,19 @@ export type ChannelStatusAdapter = { account: ResolvedAccount; timeoutMs: number; cfg: OpenClawConfig; - }) => Promise; + }) => Promise; auditAccount?: (params: { account: ResolvedAccount; timeoutMs: number; cfg: OpenClawConfig; - probe?: unknown; - }) => Promise; + probe?: Probe; + }) => Promise; buildAccountSnapshot?: (params: { account: ResolvedAccount; cfg: OpenClawConfig; runtime?: ChannelAccountSnapshot; - probe?: unknown; - audit?: unknown; + probe?: Probe; + audit?: Audit; }) => ChannelAccountSnapshot | Promise; logSelfId?: (params: { account: ResolvedAccount; diff --git a/src/channels/plugins/types.plugin.ts b/src/channels/plugins/types.plugin.ts index 3e9b5d4dd7..044cbd5864 100644 --- a/src/channels/plugins/types.plugin.ts +++ b/src/channels/plugins/types.plugin.ts @@ -45,7 +45,7 @@ export type ChannelConfigSchema = { }; // oxlint-disable-next-line typescript/no-explicit-any -export type ChannelPlugin = { +export type ChannelPlugin = { id: ChannelId; meta: ChannelMeta; capabilities: ChannelCapabilities; @@ -65,7 +65,7 @@ export type ChannelPlugin = { groups?: ChannelGroupAdapter; mentions?: ChannelMentionAdapter; outbound?: ChannelOutboundAdapter; - status?: ChannelStatusAdapter; + status?: ChannelStatusAdapter; gatewayMethods?: string[]; gateway?: ChannelGatewayAdapter; auth?: ChannelAuthAdapter; diff --git a/src/plugin-sdk/index.ts b/src/plugin-sdk/index.ts index e010e3749d..f742547edc 100644 --- a/src/plugin-sdk/index.ts +++ b/src/plugin-sdk/index.ts @@ -306,6 +306,7 @@ export { normalizeTelegramMessagingTarget, } from "../channels/plugins/normalize/telegram.js"; export { collectTelegramStatusIssues } from "../channels/plugins/status-issues/telegram.js"; +export { type TelegramProbe } from "../telegram/probe.js"; // Channel: Signal export { diff --git a/src/telegram/bot-handlers.ts b/src/telegram/bot-handlers.ts index 33e6b18c00..694425447e 100644 --- a/src/telegram/bot-handlers.ts +++ b/src/telegram/bot-handlers.ts @@ -1,6 +1,6 @@ -import type { TelegramMessage } from "./bot/types.js"; -import { resolveDefaultAgentId } from "../agents/agent-scope.js"; // @ts-nocheck +import type { Message } from "@grammyjs/types"; +import { resolveDefaultAgentId } from "../agents/agent-scope.js"; import { hasControlCommand } from "../auto-reply/command-detection.js"; import { createInboundDebouncer, @@ -63,7 +63,7 @@ export const registerTelegramHandlers = ({ type TextFragmentEntry = { key: string; - messages: Array<{ msg: TelegramMessage; ctx: unknown; receivedAtMs: number }>; + messages: Array<{ msg: Message; ctx: unknown; receivedAtMs: number }>; timer: ReturnType; }; const textFragmentBuffer = new Map(); @@ -72,7 +72,7 @@ export const registerTelegramHandlers = ({ const debounceMs = resolveInboundDebounceMs({ cfg, channel: "telegram" }); type TelegramDebounceEntry = { ctx: unknown; - msg: TelegramMessage; + msg: Message; allMedia: Array<{ path: string; contentType?: string }>; storeAllowFrom: string[]; debounceKey: string | null; @@ -111,7 +111,7 @@ export const registerTelegramHandlers = ({ const baseCtx = first.ctx as { me?: unknown; getFile?: unknown } & Record; const getFile = typeof baseCtx.getFile === "function" ? baseCtx.getFile.bind(baseCtx) : async () => ({}); - const syntheticMessage: TelegramMessage = { + const syntheticMessage: Message = { ...first.msg, text: combinedText, caption: undefined, @@ -231,7 +231,7 @@ export const registerTelegramHandlers = ({ return; } - const syntheticMessage: TelegramMessage = { + const syntheticMessage: Message = { ...first.msg, text: combinedText, caption: undefined, @@ -557,7 +557,7 @@ export const registerTelegramHandlers = ({ if (modelCallback.type === "select") { const { provider, model } = modelCallback; // Process model selection as a synthetic message with /model command - const syntheticMessage: TelegramMessage = { + const syntheticMessage: Message = { ...callbackMessage, from: callback.from, text: `/model ${provider}/${model}`, @@ -582,7 +582,7 @@ export const registerTelegramHandlers = ({ return; } - const syntheticMessage: TelegramMessage = { + const syntheticMessage: Message = { ...callbackMessage, from: callback.from, text: data, diff --git a/src/telegram/bot-native-commands.ts b/src/telegram/bot-native-commands.ts index 311cfc2365..b48e6284d3 100644 --- a/src/telegram/bot-native-commands.ts +++ b/src/telegram/bot-native-commands.ts @@ -9,6 +9,7 @@ import type { TelegramTopicConfig, } from "../config/types.js"; import type { RuntimeEnv } from "../runtime.js"; +import type { TelegramContext } from "./bot/types.js"; import { resolveEffectiveMessagesConfig } from "../agents/identity.js"; import { resolveChunkMode } from "../auto-reply/chunk.js"; import { @@ -86,7 +87,7 @@ export type RegisterTelegramHandlerParams = { ) => { groupConfig?: TelegramGroupConfig; topicConfig?: TelegramTopicConfig }; shouldSkipUpdate: (ctx: TelegramUpdateKeyContext) => boolean; processMessage: ( - ctx: unknown, + ctx: TelegramContext, allMedia: Array<{ path: string; contentType?: string }>, storeAllowFrom: string[], options?: { diff --git a/src/telegram/bot-updates.ts b/src/telegram/bot-updates.ts index c59e9ac219..bf1422fc1e 100644 --- a/src/telegram/bot-updates.ts +++ b/src/telegram/bot-updates.ts @@ -1,4 +1,5 @@ -import type { TelegramContext, TelegramMessage } from "./bot/types.js"; +import type { Message } from "@grammyjs/types"; +import type { TelegramContext } from "./bot/types.js"; import { createDedupeCache } from "../infra/dedupe.js"; const MEDIA_GROUP_TIMEOUT_MS = 500; @@ -7,7 +8,7 @@ const RECENT_TELEGRAM_UPDATE_MAX = 2000; export type MediaGroupEntry = { messages: Array<{ - msg: TelegramMessage; + msg: Message; ctx: TelegramContext; }>; timer: ReturnType; @@ -16,12 +17,12 @@ export type MediaGroupEntry = { export type TelegramUpdateKeyContext = { update?: { update_id?: number; - message?: TelegramMessage; - edited_message?: TelegramMessage; + message?: Message; + edited_message?: Message; }; update_id?: number; - message?: TelegramMessage; - callbackQuery?: { id?: string; message?: TelegramMessage }; + message?: Message; + callbackQuery?: { id?: string; message?: Message }; }; export const resolveTelegramUpdateId = (ctx: TelegramUpdateKeyContext) => diff --git a/src/telegram/bot.ts b/src/telegram/bot.ts index d3f6c8f546..44cb38176e 100644 --- a/src/telegram/bot.ts +++ b/src/telegram/bot.ts @@ -2,11 +2,11 @@ import type { ApiClientOptions } from "grammy"; // @ts-nocheck import { sequentialize } from "@grammyjs/runner"; import { apiThrottler } from "@grammyjs/transformer-throttler"; -import { ReactionTypeEmoji } from "@grammyjs/types"; +import { type Message, ReactionTypeEmoji } from "@grammyjs/types"; import { Bot, webhookCallback } from "grammy"; import type { OpenClawConfig, ReplyToMode } from "../config/config.js"; import type { RuntimeEnv } from "../runtime.js"; -import type { TelegramContext, TelegramMessage } from "./bot/types.js"; +import type { TelegramContext } from "./bot/types.js"; import { resolveDefaultAgentId } from "../agents/agent-scope.js"; import { resolveTextChunkLimit } from "../auto-reply/chunk.js"; import { isControlCommandMessage } from "../auto-reply/command-detection.js"; @@ -67,11 +67,11 @@ export type TelegramBotOptions = { export function getTelegramSequentialKey(ctx: { chat?: { id?: number }; - message?: TelegramMessage; + message?: Message; update?: { - message?: TelegramMessage; - edited_message?: TelegramMessage; - callback_query?: { message?: TelegramMessage }; + message?: Message; + edited_message?: Message; + callback_query?: { message?: Message }; message_reaction?: { chat?: { id?: number } }; }; }): string { diff --git a/src/telegram/bot/helpers.test.ts b/src/telegram/bot/helpers.test.ts index a93e2d1b70..5b74959a64 100644 --- a/src/telegram/bot/helpers.test.ts +++ b/src/telegram/bot/helpers.test.ts @@ -100,40 +100,6 @@ describe("normalizeForwardedContext", () => { expect(ctx?.fromTitle).toBe("Hidden Name"); expect(ctx?.date).toBe(456); }); - - it("handles legacy forwards with signatures", () => { - const ctx = normalizeForwardedContext({ - forward_from_chat: { - title: "OpenClaw Updates", - username: "openclaw", - id: 99, - type: "channel", - }, - forward_signature: "Stan", - forward_date: 789, - // oxlint-disable-next-line typescript/no-explicit-any - } as any); - expect(ctx).not.toBeNull(); - expect(ctx?.from).toBe("OpenClaw Updates (Stan)"); - expect(ctx?.fromType).toBe("legacy_channel"); - expect(ctx?.fromId).toBe("99"); - expect(ctx?.fromUsername).toBe("openclaw"); - expect(ctx?.fromTitle).toBe("OpenClaw Updates"); - expect(ctx?.fromSignature).toBe("Stan"); - expect(ctx?.date).toBe(789); - }); - - it("handles legacy hidden sender names", () => { - const ctx = normalizeForwardedContext({ - forward_sender_name: "Legacy Hidden", - forward_date: 111, - // oxlint-disable-next-line typescript/no-explicit-any - } as any); - expect(ctx).not.toBeNull(); - expect(ctx?.from).toBe("Legacy Hidden"); - expect(ctx?.fromType).toBe("legacy_hidden_user"); - expect(ctx?.date).toBe(111); - }); }); describe("expandTextLinks", () => { diff --git a/src/telegram/bot/helpers.ts b/src/telegram/bot/helpers.ts index 5f91e7ab24..05e25cd11d 100644 --- a/src/telegram/bot/helpers.ts +++ b/src/telegram/bot/helpers.ts @@ -1,13 +1,5 @@ -import type { - TelegramForwardChat, - TelegramForwardOrigin, - TelegramForwardUser, - TelegramForwardedMessage, - TelegramLocation, - TelegramMessage, - TelegramStreamMode, - TelegramVenue, -} from "./types.js"; +import type { Chat, Message, MessageOrigin, User } from "@grammyjs/types"; +import type { TelegramStreamMode } from "./types.js"; import { formatLocationText, type NormalizedLocation } from "../../channels/location.js"; const TELEGRAM_GENERAL_TOPIC_ID = 1; @@ -107,14 +99,14 @@ export function buildTelegramGroupFrom(chatId: number | string, messageThreadId? return `telegram:group:${buildTelegramGroupPeerId(chatId, messageThreadId)}`; } -export function buildSenderName(msg: TelegramMessage) { +export function buildSenderName(msg: Message) { const name = [msg.from?.first_name, msg.from?.last_name].filter(Boolean).join(" ").trim() || msg.from?.username; return name || undefined; } -export function buildSenderLabel(msg: TelegramMessage, senderId?: number | string) { +export function buildSenderLabel(msg: Message, senderId?: number | string) { const name = buildSenderName(msg); const username = msg.from?.username ? `@${msg.from.username}` : undefined; let label = name; @@ -136,11 +128,7 @@ export function buildSenderLabel(msg: TelegramMessage, senderId?: number | strin return idPart ?? "id:unknown"; } -export function buildGroupLabel( - msg: TelegramMessage, - chatId: number | string, - messageThreadId?: number, -) { +export function buildGroupLabel(msg: Message, chatId: number | string, messageThreadId?: number) { const title = msg.chat?.title; const topicSuffix = messageThreadId != null ? ` topic:${messageThreadId}` : ""; if (title) { @@ -149,7 +137,7 @@ export function buildGroupLabel( return `group:${chatId}${topicSuffix}`; } -export function hasBotMention(msg: TelegramMessage, botUsername: string) { +export function hasBotMention(msg: Message, botUsername: string) { const text = (msg.text ?? msg.caption ?? "").toLowerCase(); if (text.includes(`@${botUsername}`)) { return true; @@ -218,7 +206,7 @@ export type TelegramReplyTarget = { kind: "reply" | "quote"; }; -export function describeReplyTarget(msg: TelegramMessage): TelegramReplyTarget | null { +export function describeReplyTarget(msg: Message): TelegramReplyTarget | null { const reply = msg.reply_to_message; const quote = msg.quote; let body = ""; @@ -275,28 +263,27 @@ export type TelegramForwardedContext = { fromSignature?: string; }; -function normalizeForwardedUserLabel(user: TelegramForwardUser) { +function normalizeForwardedUserLabel(user: User) { const name = [user.first_name, user.last_name].filter(Boolean).join(" ").trim(); const username = user.username?.trim() || undefined; - const id = user.id != null ? String(user.id) : undefined; + const id = String(user.id); const display = (name && username ? `${name} (@${username})` - : name || (username ? `@${username}` : undefined)) || (id ? `user:${id}` : undefined); + : name || (username ? `@${username}` : undefined)) || `user:${id}`; return { display, name: name || undefined, username, id }; } -function normalizeForwardedChatLabel(chat: TelegramForwardChat, fallbackKind: "chat" | "channel") { +function normalizeForwardedChatLabel(chat: Chat, fallbackKind: "chat" | "channel") { const title = chat.title?.trim() || undefined; const username = chat.username?.trim() || undefined; - const id = chat.id != null ? String(chat.id) : undefined; - const display = - title || (username ? `@${username}` : undefined) || (id ? `${fallbackKind}:${id}` : undefined); + const id = String(chat.id); + const display = title || (username ? `@${username}` : undefined) || `${fallbackKind}:${id}`; return { display, title, username, id }; } function buildForwardedContextFromUser(params: { - user: TelegramForwardUser; + user: User; date?: number; type: string; }): TelegramForwardedContext | null { @@ -332,13 +319,12 @@ function buildForwardedContextFromHiddenName(params: { } function buildForwardedContextFromChat(params: { - chat: TelegramForwardChat; + chat: Chat; date?: number; type: string; signature?: string; }): TelegramForwardedContext | null { - const fallbackKind = - params.type === "channel" || params.type === "legacy_channel" ? "channel" : "chat"; + const fallbackKind = params.type === "channel" ? "channel" : "chat"; const { display, title, username, id } = normalizeForwardedChatLabel(params.chat, fallbackKind); if (!display) { return null; @@ -356,101 +342,52 @@ function buildForwardedContextFromChat(params: { }; } -function resolveForwardOrigin( - origin: TelegramForwardOrigin, - signature?: string, -): TelegramForwardedContext | null { - if (origin.type === "user" && origin.sender_user) { - return buildForwardedContextFromUser({ - user: origin.sender_user, - date: origin.date, - type: "user", - }); +function resolveForwardOrigin(origin: MessageOrigin): TelegramForwardedContext | null { + switch (origin.type) { + case "user": + return buildForwardedContextFromUser({ + user: origin.sender_user, + date: origin.date, + type: "user", + }); + case "hidden_user": + return buildForwardedContextFromHiddenName({ + name: origin.sender_user_name, + date: origin.date, + type: "hidden_user", + }); + case "chat": + return buildForwardedContextFromChat({ + chat: origin.sender_chat, + date: origin.date, + type: "chat", + signature: origin.author_signature, + }); + case "channel": + return buildForwardedContextFromChat({ + chat: origin.chat, + date: origin.date, + type: "channel", + signature: origin.author_signature, + }); + default: + // Exhaustiveness guard: if Grammy adds a new MessageOrigin variant, + // TypeScript will flag this assignment as an error. + origin satisfies never; + return null; } - if (origin.type === "hidden_user") { - return buildForwardedContextFromHiddenName({ - name: origin.sender_user_name, - date: origin.date, - type: "hidden_user", - }); - } - if (origin.type === "chat" && origin.sender_chat) { - return buildForwardedContextFromChat({ - chat: origin.sender_chat, - date: origin.date, - type: "chat", - signature, - }); - } - if (origin.type === "channel" && origin.chat) { - return buildForwardedContextFromChat({ - chat: origin.chat, - date: origin.date, - type: "channel", - signature, - }); - } - return null; } -/** - * Extract forwarded message origin info from Telegram message. - * Supports both new forward_origin API and legacy forward_from/forward_from_chat fields. - */ -export function normalizeForwardedContext(msg: TelegramMessage): TelegramForwardedContext | null { - const forwardMsg = msg as TelegramForwardedMessage; - const signature = forwardMsg.forward_signature?.trim() || undefined; - - if (forwardMsg.forward_origin) { - const originContext = resolveForwardOrigin(forwardMsg.forward_origin, signature); - if (originContext) { - return originContext; - } +/** Extract forwarded message origin info from Telegram message. */ +export function normalizeForwardedContext(msg: Message): TelegramForwardedContext | null { + if (!msg.forward_origin) { + return null; } - - if (forwardMsg.forward_from_chat) { - const legacyType = - forwardMsg.forward_from_chat.type === "channel" ? "legacy_channel" : "legacy_chat"; - const legacyContext = buildForwardedContextFromChat({ - chat: forwardMsg.forward_from_chat, - date: forwardMsg.forward_date, - type: legacyType, - signature, - }); - if (legacyContext) { - return legacyContext; - } - } - - if (forwardMsg.forward_from) { - const legacyContext = buildForwardedContextFromUser({ - user: forwardMsg.forward_from, - date: forwardMsg.forward_date, - type: "legacy_user", - }); - if (legacyContext) { - return legacyContext; - } - } - - const hiddenContext = buildForwardedContextFromHiddenName({ - name: forwardMsg.forward_sender_name, - date: forwardMsg.forward_date, - type: "legacy_hidden_user", - }); - if (hiddenContext) { - return hiddenContext; - } - - return null; + return resolveForwardOrigin(msg.forward_origin); } -export function extractTelegramLocation(msg: TelegramMessage): NormalizedLocation | null { - const msgWithLocation = msg as { - location?: TelegramLocation; - venue?: TelegramVenue; - }; - const { venue, location } = msgWithLocation; +export function extractTelegramLocation(msg: Message): NormalizedLocation | null { + const { venue, location } = msg; if (venue) { return { diff --git a/src/telegram/bot/types.ts b/src/telegram/bot/types.ts index df3dba6d3e..3941e1f3b7 100644 --- a/src/telegram/bot/types.ts +++ b/src/telegram/bot/types.ts @@ -1,80 +1,20 @@ import type { Message } from "@grammyjs/types"; -export type TelegramQuote = { - text?: string; -}; - -export type TelegramMessage = Message & { - quote?: TelegramQuote; -}; - +/** App-specific stream mode for Telegram draft streaming. */ export type TelegramStreamMode = "off" | "partial" | "block"; -export type TelegramForwardOriginType = "user" | "hidden_user" | "chat" | "channel"; - -export type TelegramForwardUser = { - first_name?: string; - last_name?: string; - username?: string; - id?: number; -}; - -export type TelegramForwardChat = { - title?: string; - id?: number; - username?: string; - type?: string; -}; - -export type TelegramForwardOrigin = { - type: TelegramForwardOriginType; - sender_user?: TelegramForwardUser; - sender_user_name?: string; - sender_chat?: TelegramForwardChat; - chat?: TelegramForwardChat; - date?: number; -}; - -export type TelegramForwardMetadata = { - forward_origin?: TelegramForwardOrigin; - forward_from?: TelegramForwardUser; - forward_from_chat?: TelegramForwardChat; - forward_sender_name?: string; - forward_signature?: string; - forward_date?: number; -}; - -export type TelegramForwardedMessage = TelegramMessage & TelegramForwardMetadata; - +/** + * Minimal context projection from Grammy's Context class. + * Decouples the message processing pipeline from Grammy's full Context, + * and allows constructing synthetic contexts for debounced/combined messages. + */ export type TelegramContext = { - message: TelegramMessage; + message: Message; me?: { id?: number; username?: string }; - getFile: () => Promise<{ - file_path?: string; - }>; + getFile: () => Promise<{ file_path?: string }>; }; -/** Telegram Location object */ -export interface TelegramLocation { - latitude: number; - longitude: number; - horizontal_accuracy?: number; - live_period?: number; - heading?: number; -} - -/** Telegram Venue object */ -export interface TelegramVenue { - location: TelegramLocation; - title: string; - address: string; - foursquare_id?: string; - foursquare_type?: string; - google_place_id?: string; - google_place_type?: string; -} - -/** Telegram sticker metadata for context enrichment. */ +/** Telegram sticker metadata for context enrichment and caching. */ export interface StickerMetadata { /** Emoji associated with the sticker. */ emoji?: string; diff --git a/src/telegram/proxy.ts b/src/telegram/proxy.ts index 84251d7fee..f88b9d3926 100644 --- a/src/telegram/proxy.ts +++ b/src/telegram/proxy.ts @@ -1,11 +1,14 @@ -// @ts-nocheck import { ProxyAgent, fetch as undiciFetch } from "undici"; import { wrapFetchWithAbortSignal } from "../infra/fetch.js"; export function makeProxyFetch(proxyUrl: string): typeof fetch { const agent = new ProxyAgent(proxyUrl); - return wrapFetchWithAbortSignal((input: RequestInfo | URL, init?: RequestInit) => { - const base = init ? { ...init } : {}; - return undiciFetch(input, { ...base, dispatcher: agent }); - }); + // undici's fetch is runtime-compatible with global fetch but the types diverge + // on stream/body internals. Single cast at the boundary keeps the rest type-safe. + const fetcher = (input: RequestInfo | URL, init?: RequestInit) => + undiciFetch(input as string | URL, { + ...(init as Record), + dispatcher: agent, + }) as unknown as Promise; + return wrapFetchWithAbortSignal(fetcher); }