openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
127 lines (126 loc) • 4.12 kB
JavaScript
import { j as resolveTimerTimeoutMs } from "./number-coercion-CJQ8TR--.js";
//#region packages/media-core/src/read-response-with-limit.ts
async function readChunkWithIdleTimeout(reader, chunkTimeoutMs, onIdleTimeout) {
let timeoutId;
let timedOut = false;
return await new Promise((resolve, reject) => {
const clear = () => {
if (timeoutId !== void 0) {
clearTimeout(timeoutId);
timeoutId = void 0;
}
};
const resolvedChunkTimeoutMs = resolveTimerTimeoutMs(chunkTimeoutMs, 1);
timeoutId = setTimeout(() => {
timedOut = true;
const error = onIdleTimeout?.({ chunkTimeoutMs: resolvedChunkTimeoutMs }) ?? /* @__PURE__ */ new Error(`Media download stalled: no data received for ${resolvedChunkTimeoutMs}ms`);
clear();
reader.cancel(error).catch(() => void 0);
reject(error);
}, resolvedChunkTimeoutMs);
reader.read().then((result) => {
clear();
if (!timedOut) resolve(result);
}, (err) => {
clear();
if (!timedOut) reject(toLintErrorObject(err, "Non-Error rejection"));
});
});
}
async function readResponsePrefix(res, maxBytes, opts) {
const chunkTimeoutMs = opts?.chunkTimeoutMs;
const body = res.body;
if (!body || typeof body.getReader !== "function") {
const fallback = Buffer.from(await res.arrayBuffer());
if (fallback.length > maxBytes) return {
buffer: fallback.subarray(0, maxBytes),
size: fallback.length,
truncated: true
};
return {
buffer: fallback,
size: fallback.length,
truncated: false
};
}
const reader = body.getReader();
const chunks = [];
let total = 0;
let size = 0;
let truncated = false;
try {
while (true) {
const { done, value } = chunkTimeoutMs ? await readChunkWithIdleTimeout(reader, chunkTimeoutMs, opts?.onIdleTimeout) : await reader.read();
if (done) {
size = total;
break;
}
if (!value?.length) continue;
const nextTotal = total + value.length;
if (nextTotal > maxBytes) {
const remaining = maxBytes - total;
if (remaining > 0) {
chunks.push(value.subarray(0, remaining));
total += remaining;
}
size = nextTotal;
truncated = true;
try {
await reader.cancel();
} catch {}
break;
}
chunks.push(value);
total = nextTotal;
size = total;
}
} finally {
try {
reader.releaseLock();
} catch {}
}
return {
buffer: Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)), total),
size,
truncated
};
}
/** Reads a response body under a byte cap, cancelling the stream on overflow or idle timeout. */
async function readResponseWithLimit(res, maxBytes, opts) {
const onOverflow = opts?.onOverflow ?? ((params) => /* @__PURE__ */ new Error(`Content too large: ${params.size} bytes (limit: ${params.maxBytes} bytes)`));
const prefix = await readResponsePrefix(res, maxBytes, {
chunkTimeoutMs: opts?.chunkTimeoutMs,
onIdleTimeout: opts?.onIdleTimeout
});
if (prefix.truncated) throw onOverflow({
size: prefix.size,
maxBytes,
res
});
return prefix.buffer;
}
/** Reads a small collapsed text prefix from a response body for diagnostics/errors. */
async function readResponseTextSnippet(res, opts) {
const maxBytes = opts?.maxBytes ?? 8 * 1024;
const maxChars = opts?.maxChars ?? 200;
const prefix = await readResponsePrefix(res, maxBytes, {
chunkTimeoutMs: opts?.chunkTimeoutMs,
onIdleTimeout: opts?.onIdleTimeout
});
if (prefix.buffer.length === 0) return;
const text = new TextDecoder().decode(prefix.buffer);
if (!text) return;
const collapsed = text.replace(/\s+/g, " ").trim();
if (!collapsed) return;
if (collapsed.length > maxChars) return `${collapsed.slice(0, maxChars)}…`;
return prefix.truncated ? `${collapsed}…` : collapsed;
}
function toLintErrorObject(value, fallbackMessage) {
if (value instanceof Error) return value;
if (typeof value === "string") return new Error(value);
const error = new Error(fallbackMessage, { cause: value });
if (typeof value === "object" && value !== null || typeof value === "function") Object.assign(error, value);
return error;
}
//#endregion
export { readResponseWithLimit as n, readResponseTextSnippet as t };