openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
174 lines (173 loc) • 6.61 kB
JavaScript
import { F as resolveTimerTimeoutMs } from "./number-coercion-CLj0HTDM.js";
import { t as decodeTextPrefix } from "./src-vebZIeLe.js";
import { l as toErrorObject } from "./error-coercion-D_-xJ90S.js";
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js";
//#region src/infra/http-response-body-timeout.ts
function createResponseBodyTimeoutError(message) {
const error = new Error(message);
error.name = "TimeoutError";
return error;
}
async function withResponseBodyTimeout(params) {
if (params.timeoutMs === void 0) return await params.read();
const timeoutMs = resolveTimerTimeoutMs(params.timeoutMs, 1);
let timeoutId;
let timeoutError;
return await new Promise((resolve, reject) => {
const clear = () => {
if (timeoutId !== void 0) {
clearTimeout(timeoutId);
timeoutId = void 0;
}
};
timeoutId = setTimeout(() => {
const error = params.onTimeout?.({ timeoutMs }) ?? createResponseBodyTimeoutError(`Response body timed out after ${timeoutMs}ms`);
timeoutError = error;
clear();
params.cancel(error).catch(() => void 0);
reject(error);
}, timeoutMs);
if (typeof timeoutId === "object" && "unref" in timeoutId) timeoutId.unref();
Promise.resolve().then(() => params.read(() => {
if (timeoutError) throw timeoutError;
timeoutId?.refresh();
})).then((value) => {
clear();
if (!timeoutError) resolve(value);
}, (error) => {
clear();
if (!timeoutError) reject(toErrorObject(error, "Non-Error rejection"));
});
});
}
/** Owns one refreshable idle deadline for a bounded response-body operation. */
function withResponseBodyIdleTimeout(reader, chunkTimeoutMs, onIdleTimeout, read) {
if (chunkTimeoutMs === void 0) return read();
return withResponseBodyTimeout({
timeoutMs: chunkTimeoutMs,
onTimeout: ({ timeoutMs }) => onIdleTimeout?.({ chunkTimeoutMs: timeoutMs }) ?? createResponseBodyTimeoutError(`Media download stalled: no data received for ${timeoutMs}ms`),
cancel: async (error) => await reader.cancel(error),
read
});
}
/** Reads one chunk, rejecting and cancelling the reader after an idle timeout. */
async function readChunkWithIdleTimeout(reader, chunkTimeoutMs, onIdleTimeout) {
return await withResponseBodyIdleTimeout(reader, chunkTimeoutMs, onIdleTimeout, () => reader.read());
}
//#endregion
//#region src/infra/http-response-body.ts
/** Requests cancellation only when no consumer has started reading the body. */
async function cancelUnreadResponseBody(response) {
if (response && !response.bodyUsed) response.body?.cancel().catch(() => void 0);
}
async function readResponsePrefixFromReader(reader, maxBytes, options) {
const chunks = [];
let size = 0;
let truncated = false;
try {
await withResponseBodyIdleTimeout(reader, options?.chunkTimeoutMs || void 0, options?.onIdleTimeout, async (refreshTimeout) => {
while (true) {
refreshTimeout?.();
const { done, value } = await reader.read();
if (done) break;
if (!value?.length) continue;
const remaining = maxBytes - size;
size += value.length;
if (size > maxBytes || options?.stopAtLimit && size === maxBytes) {
if (remaining > 0) chunks.push(value.subarray(0, remaining));
truncated = true;
reader.cancel().catch(() => void 0);
break;
}
chunks.push(value);
}
});
} finally {
try {
reader.releaseLock();
} catch {}
}
return {
materializeBuffer: () => Buffer.concat(chunks, Math.floor(Math.min(size, maxBytes))),
size,
truncated
};
}
async function readResponsePrefix(response, maxBytes, options) {
if (!Number.isFinite(maxBytes) || maxBytes < 0) throw new RangeError(`maxBytes must be a non-negative finite number: ${maxBytes}`);
let timeoutMs;
try {
timeoutMs = typeof options?.timeoutMs === "function" ? options.timeoutMs() : options?.timeoutMs;
} catch (error) {
response.body?.cancel(error).catch(() => void 0);
throw error;
}
const body = response.body;
if (!body || typeof body.getReader !== "function") return await withResponseBodyTimeout({
timeoutMs,
onTimeout: options?.onTimeout,
cancel: async (error) => await body?.cancel(error),
read: async () => {
const fallback = Buffer.from(await response.arrayBuffer());
const truncated = fallback.length > maxBytes;
return {
materializeBuffer: () => truncated ? fallback.subarray(0, maxBytes) : fallback,
size: fallback.length,
truncated
};
}
});
const reader = body.getReader();
return await withResponseBodyTimeout({
timeoutMs,
onTimeout: options?.onTimeout,
cancel: async (error) => await reader.cancel(error),
read: async () => await readResponsePrefixFromReader(reader, maxBytes, options)
});
}
/** Reads and decodes a bounded text prefix while cancelling unread overflow. */
async function readResponseTextPrefix(response, maxBytes, options) {
const prefix = await readResponsePrefix(response, maxBytes, {
...options,
stopAtLimit: true
});
return {
text: decodeTextPrefix(prefix.materializeBuffer(), { truncated: prefix.truncated }),
size: prefix.size,
truncated: prefix.truncated
};
}
/** Reads a response body under byte, idle, and overall timeout bounds. */
async function readResponseWithLimit(response, maxBytes, options) {
const onOverflow = options?.onOverflow;
const prefix = await readResponsePrefix(response, maxBytes, {
chunkTimeoutMs: options?.chunkTimeoutMs,
onIdleTimeout: options?.onIdleTimeout,
timeoutMs: options?.timeoutMs,
onTimeout: options?.onTimeout
});
if (prefix.truncated) throw onOverflow ? onOverflow({
size: prefix.size,
maxBytes,
res: response
}) : /* @__PURE__ */ new Error(`Content too large: ${prefix.size} bytes (limit: ${maxBytes} bytes)`);
return prefix.materializeBuffer();
}
/** Reads a small collapsed text prefix from a response body for diagnostics/errors. */
async function readResponseTextSnippet(response, options) {
const maxBytes = options?.maxBytes ?? 8192;
const maxChars = options?.maxChars ?? 200;
const prefix = await readResponseTextPrefix(response, maxBytes, {
chunkTimeoutMs: options?.chunkTimeoutMs,
onIdleTimeout: options?.onIdleTimeout,
timeoutMs: options?.timeoutMs,
onTimeout: options?.onTimeout
});
if (!prefix.text) return;
const collapsed = prefix.text.replace(/\s+/g, " ").trim();
if (!collapsed) return;
if (collapsed.length > maxChars) return `${truncateUtf16Safe(collapsed, maxChars)}…`;
return prefix.truncated ? `${collapsed}…` : collapsed;
}
//#endregion
export { readChunkWithIdleTimeout as a, readResponseWithLimit as i, readResponseTextPrefix as n, readResponseTextSnippet as r, cancelUnreadResponseBody as t };