
























@@ -15,6 +15,7 @@ import {
1515redactForDevToolLog,
1616redactHomePath,
1717} from "../lib/dev-tooling-safety.ts";
18+import { readBoundedResponseText } from "../lib/bounded-response.ts";
18191920function writeStdoutLine(message: string): void {
2021process.stdout.write(`${message}\n`);
@@ -135,6 +136,7 @@ type FailureResult = {
135136const DISCORD_API_BASE = "https://discord.com/api/v10";
136137const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
137138const DEFAULT_OPENCLAW_CLI_TIMEOUT_MS = 60_000;
139+const DISCORD_RESPONSE_BODY_MAX_BYTES = 1024 * 1024;
138140const WEBHOOK_CLEANUP_TIMEOUT_MS = 10_000;
139141140142function sleep(ms: number): Promise<void> {
@@ -185,6 +187,41 @@ function parseNumber(value: string | undefined, fallback: number, label: string)
185187return parseStrictIntegerOption({ fallback, label, min: 1, raw: value });
186188}
187189190+function createDiscordResponseTooLargeError(message: string): Error {
191+const error = new Error(message);
192+(error as NodeJS.ErrnoException).code = "ETOOBIG";
193+return error;
194+}
195+196+function isTooLargeError(error: unknown): boolean {
197+return (error as NodeJS.ErrnoException | undefined)?.code === "ETOOBIG";
198+}
199+200+async function readDiscordResponseText(params: {
201+response: Response;
202+label: string;
203+signal: AbortSignal;
204+maxBytes: number;
205+}): Promise<string> {
206+return await readBoundedResponseText(params.response, params.label, params.maxBytes, {
207+createTooLargeError: createDiscordResponseTooLargeError,
208+signal: params.signal,
209+});
210+}
211+212+async function readDiscordResponseJson(params: {
213+response: Response;
214+label: string;
215+signal: AbortSignal;
216+maxBytes: number;
217+}): Promise<unknown> {
218+const text = await readDiscordResponseText(params);
219+if (!text) {
220+return {};
221+}
222+return JSON.parse(text);
223+}
224+188225function resolveStateDir(): string {
189226const override = process.env.OPENCLAW_STATE_DIR?.trim();
190227if (override) {
@@ -458,12 +495,14 @@ async function requestDiscordJson<T>(params: {
458495retries?: number;
459496timeoutMs?: number;
460497errorPrefix: string;
498+responseBodyMaxBytes?: number;
461499fetchImpl?: typeof fetch;
462500sleepImpl?: (ms: number) => Promise<void>;
463501}): Promise<T> {
464502const retries = params.retries ?? 6;
465503const fetchImpl = params.fetchImpl ?? fetch;
466504const sleepImpl = params.sleepImpl ?? sleep;
505+const responseBodyMaxBytes = params.responseBodyMaxBytes ?? DISCORD_RESPONSE_BODY_MAX_BYTES;
467506const deadlineMs = Date.now() + (params.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS);
468507const timeoutError = () =>
469508new Error(
@@ -488,7 +527,17 @@ async function requestDiscordJson<T>(params: {
488527if (response.status === 429) {
489528const bodyTimeoutMs = remainingTimeoutMs(deadlineMs);
490529const body = (await withTimeout({
491-operation: response.json().catch(() => ({})),
530+operation: readDiscordResponseJson({
531+ response,
532+label: `${params.errorPrefix} ${params.method} ${redactDiscordApiPath(params.path)}`,
533+signal: controller.signal,
534+maxBytes: responseBodyMaxBytes,
535+}).catch((error) => {
536+if (isTooLargeError(error)) {
537+throw error;
538+}
539+return {};
540+}),
492541timeoutMs: bodyTimeoutMs,
493542 timeoutError,
494543onTimeout: () => controller.abort(),
@@ -501,7 +550,12 @@ async function requestDiscordJson<T>(params: {
501550if (!response.ok) {
502551const bodyTimeoutMs = remainingTimeoutMs(deadlineMs);
503552const text = await withTimeout({
504-operation: response.text().catch(() => ""),
553+operation: readDiscordResponseText({
554+ response,
555+label: `${params.errorPrefix} ${params.method} ${redactDiscordApiPath(params.path)}`,
556+signal: controller.signal,
557+maxBytes: responseBodyMaxBytes,
558+}),
505559timeoutMs: bodyTimeoutMs,
506560 timeoutError,
507561onTimeout: () => controller.abort(),
@@ -519,7 +573,12 @@ async function requestDiscordJson<T>(params: {
519573520574const bodyTimeoutMs = remainingTimeoutMs(deadlineMs);
521575return (await withTimeout({
522-operation: response.json(),
576+operation: readDiscordResponseJson({
577+ response,
578+label: `${params.errorPrefix} ${params.method} ${redactDiscordApiPath(params.path)}`,
579+signal: controller.signal,
580+maxBytes: responseBodyMaxBytes,
581+}),
523582timeoutMs: bodyTimeoutMs,
524583 timeoutError,
525584onTimeout: () => controller.abort(),
@@ -988,7 +1047,9 @@ async function main(): Promise<number> {
9881047export const testing = {
9891048 parseDriverMode,
9901049 parseNumber,
1050+DISCORD_RESPONSE_BODY_MAX_BYTES,
9911051 redactDiscordApiPath,
1052+ readDiscordResponseText,
9921053 remainingTimeoutMs,
9931054 requestDiscordJson,
9941055 resolveStateDir,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。