mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 00:40:21 +00:00
Telegram: use Grammy types directly, add typed Probe/Audit to plugin interface (#8403)
* Telegram: replace duplicated types with Grammy imports, add Probe/Audit generics to plugin interface * Telegram: remove legacy forward metadata (deprecated in Bot API 7.0), simplify required-field checks * Telegram: clean up remaining legacy references and unnecessary casts * Telegram: keep RequestInit parameter type in proxy fetch (addresses review feedback) * Telegram: add exhaustiveness guard to resolveForwardOrigin switch
This commit is contained in:
@@ -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<typeof setTimeout>;
|
||||
};
|
||||
const textFragmentBuffer = new Map<string, TextFragmentEntry>();
|
||||
@@ -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<string, unknown>;
|
||||
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,
|
||||
|
||||
@@ -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?: {
|
||||
|
||||
@@ -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<typeof setTimeout>;
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<string, unknown>),
|
||||
dispatcher: agent,
|
||||
}) as unknown as Promise<Response>;
|
||||
return wrapFetchWithAbortSignal(fetcher);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user