const DEFAULT_CAPTURED_OUTPUT_BYTES = 256 * 1024; const DEFAULT_JSON_RESPONSE_BYTES = 64 * 1024; function normalizeByteLimit(maxBytes) { return Math.max(1, Math.trunc(maxBytes)); } export function createCapturedOutputBuffer(maxBytes = DEFAULT_CAPTURED_OUTPUT_BYTES) { const limit = normalizeByteLimit(maxBytes); const chunks = []; let bufferedBytes = 0; let totalBytes = 0; let truncated = false; return { append(chunk) { if (chunk === null || chunk === undefined) return; const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); if (buffer.length === 0) return; chunks.push(buffer); bufferedBytes += buffer.length; totalBytes += buffer.length; while (bufferedBytes > limit && chunks.length > 0) { const overflow = bufferedBytes - limit; const head = chunks[0]; if (head.length <= overflow) { chunks.shift(); bufferedBytes -= head.length; truncated = true; continue; } chunks[0] = head.subarray(overflow); bufferedBytes -= overflow; truncated = true; } }, finish() { const body = Buffer.concat(chunks).toString("utf8"); if (!truncated) { return { text: body, truncated, totalBytes, }; } return { text: `[output truncated to last ${limit} bytes; total ${totalBytes} bytes]\n${body}`, truncated, totalBytes, }; }, }; } export async function parseJsonResponseWithLimit(response, maxBytes = DEFAULT_JSON_RESPONSE_BYTES) { const limit = normalizeByteLimit(maxBytes); const contentLength = Number.parseInt(response.headers.get("content-length") ?? "", 10); if (Number.isFinite(contentLength) && contentLength > limit) { throw new Error(`Response exceeds ${limit} bytes`); } if (!response.body) { return JSON.parse(""); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let text = ""; let totalBytes = 0; try { while (true) { const { done, value } = await reader.read(); if (done) break; totalBytes += value.byteLength; if (totalBytes > limit) { await reader.cancel("response too large"); throw new Error(`Response exceeds ${limit} bytes`); } text += decoder.decode(value, { stream: true }); } text += decoder.decode(); } finally { reader.releaseLock(); } return JSON.parse(text); }