import { dirname } from "node:path"; import type { ChildState } from "./types.js"; const CHILD_LIMIT = 16 * 1024; const TOTAL_LIMIT = 50 * 1024; const bytes = (value: string): number => Buffer.byteLength(value, "utf8"); const attr = (value: string): string => value .replace(/&/g, "&") .replace(/"/g, """) .replace(//g, ">"); function truncateCdata(value: string, maxBytes: number): string { if (maxBytes <= 0) return ""; let output = ""; let used = 0; for (let index = 0; index < value.length;) { const source = value.startsWith("]]>", index) ? "]]>" : String.fromCodePoint(value.codePointAt(index)!); const rendered = source === "]]>" ? "]]]]>" : source; const size = bytes(rendered); if (used + size > maxBytes) break; output += rendered; used += size; index += source.length; } return output; } export function buildEnvelope(children: ChildState[], totalLimit = TOTAL_LIMIT): string { const wrappers = children.map((child) => ({ open: `", })); const baseBytes = Math.max(0, children.length - 1) + wrappers.reduce((sum, wrapper) => sum + bytes(wrapper.open) + bytes(wrapper.close), 0); if (baseBytes > totalLimit) { const runDir = children[0]?.artifactPath ? dirname(children[0].artifactPath) : ""; const marker = ``; if (bytes(marker) > totalLimit) return ""; const output: string[] = []; let used = bytes(marker) + (children.length > 0 ? 1 : 0); for (let index = 0; index < children.length; index++) { const empty = `${wrappers[index]!.open}${wrappers[index]!.close}`; const added = bytes(empty) + (output.length > 0 ? 1 : 0); if (used + added > totalLimit) break; output.push(empty); used += added; } output.push(marker); return output.join("\n"); } let remaining = totalLimit - baseBytes; return children.map((child, index) => { const original = child.response || child.error || ""; const allowed = Math.max(0, Math.min(CHILD_LIMIT, remaining)); const content = truncateCdata(original, allowed); remaining -= bytes(content); return `${wrappers[index]!.open}${content}${wrappers[index]!.close}`; }).join("\n"); }