fix(telegram): honor outbound media max bytes (#83478)

This commit is contained in:
狼哥
2026-05-23 06:38:54 +08:00
committed by GitHub
parent 1dd3b52cb7
commit f2d4f9328c
7 changed files with 52 additions and 4 deletions

View File

@@ -372,6 +372,7 @@ export function createTelegramBotCore(
groupAllowFrom,
replyToMode,
textLimit,
mediaMaxBytes,
useAccessGroups,
nativeEnabled,
nativeSkillsEnabled,

View File

@@ -1790,6 +1790,7 @@ describe("dispatchTelegramMessage draft streaming", () => {
it("keeps streamed final text in place when late media arrives", async () => {
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
const mediaMaxBytes = 50 * 1024 * 1024;
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
async ({ dispatcherOptions, replyOptions }) => {
await replyOptions?.onPartialReply?.({ text: "Photo" });
@@ -1801,10 +1802,14 @@ describe("dispatchTelegramMessage draft streaming", () => {
},
);
await dispatchWithContext({ context: createContext() });
await dispatchWithContext({
context: createContext(),
telegramCfg: { mediaMaxMb: 50 },
});
expect(answerDraftStream.clear).not.toHaveBeenCalled();
expect(answerDraftStream.update).toHaveBeenCalledWith("Photo");
expectDeliverRepliesParams({ mediaMaxBytes });
expectDeliveredReply(0, { text: undefined, mediaUrl: "https://example.com/a.png" });
});

View File

@@ -228,7 +228,7 @@ type DispatchTelegramMessageParams = {
textLimit: number;
telegramCfg: TelegramAccountConfig;
telegramDeps?: TelegramBotDeps;
opts: Pick<TelegramBotOptions, "token">;
opts: Pick<TelegramBotOptions, "token" | "mediaMaxMb">;
};
type TelegramReasoningLevel = "off" | "on" | "stream";
@@ -1001,6 +1001,7 @@ export const dispatchTelegramMessage = async ({
runtime,
bot,
mediaLocalRoots,
mediaMaxBytes: (opts.mediaMaxMb ?? telegramCfg.mediaMaxMb ?? 100) * 1024 * 1024,
replyToMode,
textLimit,
thread: threadSpec,

View File

@@ -59,8 +59,8 @@ function registerPlugCommand(params: PlugCommandHarnessParams = {}) {
registerTelegramNativeCommands({
...createNativeCommandTestParams(params.cfg ?? {}, {
bot: botHarness.bot,
...params.registerOverrides,
}),
...params.registerOverrides,
});
const handler = botHarness.commandHandlers.get("plug");
if (!handler) {
@@ -371,6 +371,7 @@ describe("registerTelegramNativeCommands", () => {
});
it("passes agent-scoped media roots for plugin command replies with media", async () => {
const mediaMaxBytes = 50 * 1024 * 1024;
const cfg: OpenClawConfig = {
agents: {
list: [{ id: "main", default: true }, { id: "work" }],
@@ -384,11 +385,15 @@ describe("registerTelegramNativeCommands", () => {
text: "with media",
mediaUrl: "/tmp/workspace-work/render.png",
},
registerOverrides: {
mediaMaxBytes,
} as Partial<Parameters<typeof registerTelegramNativeCommands>[0]>,
});
await handler(createPrivateCommandContext());
const deliverParams = firstDeliverRepliesParams();
expect(deliverParams.mediaMaxBytes).toBe(mediaMaxBytes);
const mediaLocalRoots = deliverParams.mediaLocalRoots as Array<string> | undefined;
expect(mediaLocalRoots?.some((root) => /[\\/]\.openclaw[\\/]workspace-work$/.test(root))).toBe(
true,

View File

@@ -467,6 +467,7 @@ export type RegisterTelegramNativeCommandsParams = {
groupAllowFrom?: Array<string | number>;
replyToMode: ReplyToMode;
textLimit: number;
mediaMaxBytes?: number;
useAccessGroups: boolean;
nativeEnabled: boolean;
nativeSkillsEnabled: boolean;
@@ -693,6 +694,7 @@ export const registerTelegramNativeCommands = ({
groupAllowFrom,
replyToMode,
textLimit,
mediaMaxBytes,
useAccessGroups,
nativeEnabled,
nativeSkillsEnabled,
@@ -933,6 +935,7 @@ export const registerTelegramNativeCommands = ({
runtime,
bot,
mediaLocalRoots: params.mediaLocalRoots,
mediaMaxBytes,
replyToMode,
textLimit,
thread: params.threadSpec,

View File

@@ -331,6 +331,7 @@ async function deliverMediaReply(params: {
thread?: TelegramThreadSpec | null;
tableMode?: MarkdownTableMode;
mediaLocalRoots?: readonly string[];
mediaMaxBytes?: number;
chunkText: ChunkTextFn;
mediaLoader: typeof loadWebMedia;
onVoiceRecording?: () => Promise<void> | void;
@@ -353,7 +354,10 @@ async function deliverMediaReply(params: {
const isFirstMedia = first;
const media = await params.mediaLoader(
mediaUrl,
buildOutboundMediaLoadOptions({ mediaLocalRoots: params.mediaLocalRoots }),
buildOutboundMediaLoadOptions({
mediaLocalRoots: params.mediaLocalRoots,
maxBytes: params.mediaMaxBytes,
}),
);
const kind = kindFromMime(media.contentType ?? undefined);
const isGif = isGifMedia({
@@ -693,6 +697,7 @@ export async function deliverReplies(params: {
runtime: RuntimeEnv;
bot: Bot;
mediaLocalRoots?: readonly string[];
mediaMaxBytes?: number;
replyToMode: ReplyToMode;
textLimit: number;
thread?: TelegramThreadSpec | null;
@@ -879,6 +884,7 @@ export async function deliverReplies(params: {
thread: params.thread,
tableMode: params.tableMode,
mediaLocalRoots: params.mediaLocalRoots,
mediaMaxBytes: params.mediaMaxBytes,
chunkText,
mediaLoader,
onVoiceRecording: params.onVoiceRecording,

View File

@@ -751,6 +751,33 @@ describe("deliverReplies", () => {
});
});
it("passes the configured media byte cap to media loading", async () => {
const runtime = createRuntime();
const sendPhoto = vi.fn().mockResolvedValue({
message_id: 13,
chat: { id: "123" },
});
const bot = createBot({ sendPhoto });
const mediaMaxBytes = 50 * 1024 * 1024;
mockMediaLoad("photo.jpg", "image/jpeg", "image");
await deliverReplies({
replies: [{ mediaUrl: "https://example.com/photo.jpg" }],
chatId: "123",
token: "tok",
runtime,
bot,
replyToMode: "off",
textLimit: 4000,
mediaMaxBytes,
});
expect(loadWebMedia).toHaveBeenCalledWith("https://example.com/photo.jpg", {
maxBytes: mediaMaxBytes,
});
});
it("includes link_preview_options when linkPreview is false", async () => {
const runtime = createRuntime();
const sendMessage = vi.fn().mockResolvedValue({