import { mkdtemp, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationOptions, truncateHead, } from "@earendil-works/pi-coding-agent"; export interface WorkspaceOutput { text: string; truncated: boolean; fullOutputPath?: string; } export function buildRipgrepArgs( pattern: string, roots: readonly string[], glob?: string, ): string[] { return [...(glob ? ["--glob", glob] : []), "-e", pattern, "--", ...roots]; } export function resolveRipgrepOutput(result: { stdout: string; stderr: string; code: number; }): string { if (result.code === 1) return "No matches found."; if (result.code !== 0) { throw new Error( `ripgrep failed: ${result.stderr.trim() || `exit code ${result.code}`}`, ); } return result.stdout || "No matches found."; } export async function truncateWorkspaceOutput( output: string, options: TruncationOptions = { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES, }, ): Promise { const t = truncateHead(output, options); if (!t.truncated) return { text: t.content, truncated: false }; const fullOutputPath = join( await mkdtemp(join(tmpdir(), "pi-workspace-search-")), "output.txt", ); await writeFile(fullOutputPath, output, "utf8"); const notice = `\n\n[Output truncated: showing ${t.outputLines} of ${t.totalLines} lines ` + `(${formatSize(t.outputBytes)} of ${formatSize(t.totalBytes)}). ` + `Full output saved to: ${fullOutputPath}]`; return { text: t.content + notice, truncated: true, fullOutputPath }; }