fix(telegram): classify hosted media size errors consistently (#120757)

This commit is contained in:
Peter Steinberger
2026-08-08 16:44:44 -07:00
committed by GitHub
parent 019dd0e086
commit 6972ef80b7
3 changed files with 52 additions and 14 deletions
@@ -2,9 +2,48 @@ import { MediaFetchError } from "openclaw/plugin-sdk/media-runtime";
import { describe, expect, it } from "vitest";
import {
isDurablyRetryableInboundMediaError,
isMediaSizeLimitError,
isRecoverableMediaGroupError,
TelegramBotApiFileTooLargeError,
} from "./bot-handlers.media.js";
describe("isMediaSizeLimitError", () => {
it.each([
{
name: "content-length limit",
error: new MediaFetchError("max_bytes", "content length 11 exceeds maxBytes 10"),
expected: true,
},
{
name: "streaming payload limit",
error: new MediaFetchError("max_bytes", "payload exceeds maxBytes 10"),
expected: true,
},
{
name: "Telegram Bot API file limit",
error: new TelegramBotApiFileTooLargeError(new Error("Bad Request: file is too big")),
expected: true,
},
{
name: "legacy untyped size error",
error: new Error("media exceeds 20 MB limit"),
expected: true,
},
{
name: "fetch failure with misleading size text",
error: new MediaFetchError("fetch_failed", "media exceeds 20 MB limit"),
expected: false,
},
{
name: "HTTP failure with misleading size text",
error: new MediaFetchError("http_error", "media exceeds 20 MB limit", { status: 500 }),
expected: false,
},
])("classifies $name by its authoritative error code", ({ error, expected }) => {
expect(isMediaSizeLimitError(error)).toBe(expected);
});
});
describe("isDurablyRetryableInboundMediaError", () => {
const networkCause = () => Object.assign(new Error("read ECONNRESET"), { code: "ECONNRESET" });
const abortCause = () => Object.assign(new Error("aborted"), { name: "AbortError" });
@@ -19,8 +19,8 @@ export class TelegramBotApiFileTooLargeError extends MediaFetchError {
}
export function isMediaSizeLimitError(err: unknown): boolean {
if (err instanceof TelegramBotApiFileTooLargeError) {
return true;
if (err instanceof MediaFetchError) {
return err.code === "max_bytes";
}
const errMsg = String(err);
return errMsg.includes("exceeds") && errMsg.includes("MB limit");
@@ -420,7 +420,7 @@ describe("createTelegramBot channel_post media", () => {
const response = await params.fetchImpl(params.url);
const buffer = new Uint8Array(await response.arrayBuffer());
if (buffer.length > params.maxBytes) {
throw new MediaFetchError("max_bytes", `media exceeds ${params.maxBytes} MB limit`);
throw new MediaFetchError("max_bytes", `payload exceeds maxBytes ${params.maxBytes}`);
}
return {
path: "/tmp/telegram-media.bin",
@@ -507,7 +507,6 @@ describe("createTelegramBot channel_post media", () => {
it("dispatches an oversized channel_post as a type-only media fact", async () => {
setOpenChannelPostConfig();
const fetchSpy = createImageFetchSpy({
body: new Uint8Array([0xff, 0xd8, 0xff, 0x00]),
contentType: "image/jpeg",
@@ -525,6 +524,7 @@ describe("createTelegramBot channel_post media", () => {
);
expect(replySpy).toHaveBeenCalledOnce();
expect(sendMessageSpy).not.toHaveBeenCalled();
expectTypeOnlyMediaPayload("image");
fetchSpy.mockRestore();
});
@@ -608,21 +608,21 @@ describe("createTelegramBot channel_post media", () => {
cause: Object.assign(new Error("aborted"), { name: "AbortError" }),
}),
result: { kind: "failed-retryable", error: expect.any(MediaFetchError) },
warnings: 0,
warning: undefined,
},
{
name: "permanent oversized media",
messageId: 98077,
error: new MediaFetchError("max_bytes", "Failed to fetch media: payload exceeds maxBytes 10"),
result: { kind: "completed" },
warnings: 1,
warning: "⚠️ File too large. Maximum size is 100MB.",
},
{
name: "permanent SSRF rejection",
messageId: 98078,
error: new MediaFetchError("fetch_failed", "blocked by SSRF guard: private address"),
result: { kind: "completed" },
warnings: 1,
warning: "⚠️ Failed to download media. Please try again.",
},
])("preserves durable replay handling for $name (#98076)", async (testCase) => {
setOpenTelegramDirectConfig();
@@ -640,13 +640,12 @@ describe("createTelegramBot channel_post media", () => {
withTelegramSpooledReplayUpdate(update, () => handler(ctx)),
);
expect(result).toEqual(testCase.result);
expect(sendMessageSpy).toHaveBeenCalledTimes(testCase.warnings);
expect(replySpy).toHaveBeenCalledTimes(testCase.warnings);
expect(sendMessageSpy.mock.calls[0]?.[1]).toEqual(
[undefined, "⚠️ Failed to download media. Please try again."][testCase.warnings],
);
if (testCase.warnings) {
expectTelegramDownloadWarning(testCase.messageId);
const expectedWarnings = testCase.warning ? 1 : 0;
expect(sendMessageSpy).toHaveBeenCalledTimes(expectedWarnings);
expect(replySpy).toHaveBeenCalledTimes(expectedWarnings);
expect(sendMessageSpy.mock.calls[0]?.[1]).toBe(testCase.warning);
if (testCase.warning) {
expectTelegramDownloadWarning(testCase.messageId, testCase.warning);
expectTypeOnlyMediaPayload("document");
}
});