Skip to content

Commit f2d4f93

Browse files
authored
fix(telegram): honor outbound media max bytes (#83478)
1 parent 1dd3b52 commit f2d4f93

7 files changed

Lines changed: 52 additions & 4 deletions

File tree

extensions/telegram/src/bot-core.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ export function createTelegramBotCore(
372372
groupAllowFrom,
373373
replyToMode,
374374
textLimit,
375+
mediaMaxBytes,
375376
useAccessGroups,
376377
nativeEnabled,
377378
nativeSkillsEnabled,

extensions/telegram/src/bot-message-dispatch.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1790,6 +1790,7 @@ describe("dispatchTelegramMessage draft streaming", () => {
17901790

17911791
it("keeps streamed final text in place when late media arrives", async () => {
17921792
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
1793+
const mediaMaxBytes = 50 * 1024 * 1024;
17931794
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
17941795
async ({ dispatcherOptions, replyOptions }) => {
17951796
await replyOptions?.onPartialReply?.({ text: "Photo" });
@@ -1801,10 +1802,14 @@ describe("dispatchTelegramMessage draft streaming", () => {
18011802
},
18021803
);
18031804

1804-
await dispatchWithContext({ context: createContext() });
1805+
await dispatchWithContext({
1806+
context: createContext(),
1807+
telegramCfg: { mediaMaxMb: 50 },
1808+
});
18051809

18061810
expect(answerDraftStream.clear).not.toHaveBeenCalled();
18071811
expect(answerDraftStream.update).toHaveBeenCalledWith("Photo");
1812+
expectDeliverRepliesParams({ mediaMaxBytes });
18081813
expectDeliveredReply(0, { text: undefined, mediaUrl: "https://example.com/a.png" });
18091814
});
18101815

extensions/telegram/src/bot-message-dispatch.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ type DispatchTelegramMessageParams = {
228228
textLimit: number;
229229
telegramCfg: TelegramAccountConfig;
230230
telegramDeps?: TelegramBotDeps;
231-
opts: Pick<TelegramBotOptions, "token">;
231+
opts: Pick<TelegramBotOptions, "token" | "mediaMaxMb">;
232232
};
233233

234234
type TelegramReasoningLevel = "off" | "on" | "stream";
@@ -1001,6 +1001,7 @@ export const dispatchTelegramMessage = async ({
10011001
runtime,
10021002
bot,
10031003
mediaLocalRoots,
1004+
mediaMaxBytes: (opts.mediaMaxMb ?? telegramCfg.mediaMaxMb ?? 100) * 1024 * 1024,
10041005
replyToMode,
10051006
textLimit,
10061007
thread: threadSpec,

extensions/telegram/src/bot-native-commands.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ function registerPlugCommand(params: PlugCommandHarnessParams = {}) {
5959
registerTelegramNativeCommands({
6060
...createNativeCommandTestParams(params.cfg ?? {}, {
6161
bot: botHarness.bot,
62-
...params.registerOverrides,
6362
}),
63+
...params.registerOverrides,
6464
});
6565
const handler = botHarness.commandHandlers.get("plug");
6666
if (!handler) {
@@ -371,6 +371,7 @@ describe("registerTelegramNativeCommands", () => {
371371
});
372372

373373
it("passes agent-scoped media roots for plugin command replies with media", async () => {
374+
const mediaMaxBytes = 50 * 1024 * 1024;
374375
const cfg: OpenClawConfig = {
375376
agents: {
376377
list: [{ id: "main", default: true }, { id: "work" }],
@@ -384,11 +385,15 @@ describe("registerTelegramNativeCommands", () => {
384385
text: "with media",
385386
mediaUrl: "/tmp/workspace-work/render.png",
386387
},
388+
registerOverrides: {
389+
mediaMaxBytes,
390+
} as Partial<Parameters<typeof registerTelegramNativeCommands>[0]>,
387391
});
388392

389393
await handler(createPrivateCommandContext());
390394

391395
const deliverParams = firstDeliverRepliesParams();
396+
expect(deliverParams.mediaMaxBytes).toBe(mediaMaxBytes);
392397
const mediaLocalRoots = deliverParams.mediaLocalRoots as Array<string> | undefined;
393398
expect(mediaLocalRoots?.some((root) => /[\\/]\.openclaw[\\/]workspace-work$/.test(root))).toBe(
394399
true,

extensions/telegram/src/bot-native-commands.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ export type RegisterTelegramNativeCommandsParams = {
467467
groupAllowFrom?: Array<string | number>;
468468
replyToMode: ReplyToMode;
469469
textLimit: number;
470+
mediaMaxBytes?: number;
470471
useAccessGroups: boolean;
471472
nativeEnabled: boolean;
472473
nativeSkillsEnabled: boolean;
@@ -693,6 +694,7 @@ export const registerTelegramNativeCommands = ({
693694
groupAllowFrom,
694695
replyToMode,
695696
textLimit,
697+
mediaMaxBytes,
696698
useAccessGroups,
697699
nativeEnabled,
698700
nativeSkillsEnabled,
@@ -933,6 +935,7 @@ export const registerTelegramNativeCommands = ({
933935
runtime,
934936
bot,
935937
mediaLocalRoots: params.mediaLocalRoots,
938+
mediaMaxBytes,
936939
replyToMode,
937940
textLimit,
938941
thread: params.threadSpec,

extensions/telegram/src/bot/delivery.replies.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ async function deliverMediaReply(params: {
331331
thread?: TelegramThreadSpec | null;
332332
tableMode?: MarkdownTableMode;
333333
mediaLocalRoots?: readonly string[];
334+
mediaMaxBytes?: number;
334335
chunkText: ChunkTextFn;
335336
mediaLoader: typeof loadWebMedia;
336337
onVoiceRecording?: () => Promise<void> | void;
@@ -353,7 +354,10 @@ async function deliverMediaReply(params: {
353354
const isFirstMedia = first;
354355
const media = await params.mediaLoader(
355356
mediaUrl,
356-
buildOutboundMediaLoadOptions({ mediaLocalRoots: params.mediaLocalRoots }),
357+
buildOutboundMediaLoadOptions({
358+
mediaLocalRoots: params.mediaLocalRoots,
359+
maxBytes: params.mediaMaxBytes,
360+
}),
357361
);
358362
const kind = kindFromMime(media.contentType ?? undefined);
359363
const isGif = isGifMedia({
@@ -693,6 +697,7 @@ export async function deliverReplies(params: {
693697
runtime: RuntimeEnv;
694698
bot: Bot;
695699
mediaLocalRoots?: readonly string[];
700+
mediaMaxBytes?: number;
696701
replyToMode: ReplyToMode;
697702
textLimit: number;
698703
thread?: TelegramThreadSpec | null;
@@ -879,6 +884,7 @@ export async function deliverReplies(params: {
879884
thread: params.thread,
880885
tableMode: params.tableMode,
881886
mediaLocalRoots: params.mediaLocalRoots,
887+
mediaMaxBytes: params.mediaMaxBytes,
882888
chunkText,
883889
mediaLoader,
884890
onVoiceRecording: params.onVoiceRecording,

extensions/telegram/src/bot/delivery.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,33 @@ describe("deliverReplies", () => {
751751
});
752752
});
753753

754+
it("passes the configured media byte cap to media loading", async () => {
755+
const runtime = createRuntime();
756+
const sendPhoto = vi.fn().mockResolvedValue({
757+
message_id: 13,
758+
chat: { id: "123" },
759+
});
760+
const bot = createBot({ sendPhoto });
761+
const mediaMaxBytes = 50 * 1024 * 1024;
762+
763+
mockMediaLoad("photo.jpg", "image/jpeg", "image");
764+
765+
await deliverReplies({
766+
replies: [{ mediaUrl: "https://example.com/photo.jpg" }],
767+
chatId: "123",
768+
token: "tok",
769+
runtime,
770+
bot,
771+
replyToMode: "off",
772+
textLimit: 4000,
773+
mediaMaxBytes,
774+
});
775+
776+
expect(loadWebMedia).toHaveBeenCalledWith("https://example.com/photo.jpg", {
777+
maxBytes: mediaMaxBytes,
778+
});
779+
});
780+
754781
it("includes link_preview_options when linkPreview is false", async () => {
755782
const runtime = createRuntime();
756783
const sendMessage = vi.fn().mockResolvedValue({

0 commit comments

Comments
 (0)