/** * @fileoverview Output truncation helper that stores the full payload and returns a trimmed inline preview. */ import { PI_TRUNCATION_LIMITS } from "../../defaults.ts"; import type { ResponseStorageMetadata } from "../../types.ts"; import { storeResponse } from "./store.ts"; import type { StoreResponseOptions } from "./store.ts"; export interface TruncatedOutput { text: string; truncated: boolean; metadata?: ResponseStorageMetadata; } export async function truncateAndStore( text: string, value: unknown, options: StoreResponseOptions = {}, ): Promise { const limits = PI_TRUNCATION_LIMITS; const byteLength = Buffer.byteLength(text); const lineLimited = firstLines(text, limits.maxLines); const overBytes = byteLength > limits.maxBytes; const overLines = lineLimited.length < text.length; if (!overBytes && !overLines) return { text, truncated: false }; const metadata = await storeResponse(value, options); return { text: trimToBytes(lineLimited, limits.maxBytes), truncated: true, metadata, }; } function firstLines(text: string, maxLines: number): string { if (maxLines <= 0) return ""; let lines = 1; for (const match of text.matchAll(/\r?\n/gu)) { if (lines >= maxLines) return text.slice(0, match.index); lines += 1; } return text; } function trimToBytes(text: string, maxBytes: number): string { if (Buffer.byteLength(text) <= maxBytes) return text; let low = 0; let high = text.length; while (low < high) { const mid = Math.ceil((low + high) / 2); if (Buffer.byteLength(text.slice(0, mid)) <= maxBytes) low = mid; else high = mid - 1; } return text.slice(0, low); }