import type { AgentToolResult } from "@oh-my-pi/pi-agent-core"; import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai"; import type { OutputSummary, TruncationResult } from "@oh-my-pi/pi-tui/tools/streaming-output"; import type { MinimizedMeta, OutputMeta } from "@oh-my-pi/pi-tui/tools/output-meta"; import type { LimitsInput, TruncationMetaInput, TruncationSummaryOptions, TruncationTextOptions } from "./output-meta"; import { outputMeta } from "./output-meta"; type ToolContent = Array; type DetailsWithMeta = { meta?: OutputMeta }; export class ToolResultBuilder { #details: TDetails; #meta = outputMeta(); #content: ToolContent = []; #isError = false; #useless = false; constructor(details?: TDetails) { this.#details = details ?? ({} as TDetails); } text(text: string): this { this.#content = [{ type: "text", text }]; return this; } content(content: ToolContent): this { this.#content = content; return this; } truncation(result: TruncationResult, options: TruncationMetaInput): this { this.#meta.truncation(result, options); return this; } truncationFromSummary(summary: OutputSummary, options: TruncationSummaryOptions): this { this.#meta.truncationFromSummary(summary, options); return this; } truncationFromText(text: string, options: TruncationTextOptions): this { this.#meta.truncationFromText(text, options); return this; } limits(limits: LimitsInput): this { this.#meta.limits(limits); return this; } sourceUrl(value: string): this { this.#meta.sourceUrl(value); return this; } sourcePath(value: string): this { this.#meta.sourcePath(value); return this; } sourceInternal(value: string): this { this.#meta.sourceInternal(value); return this; } /** Mark the result as a bounded page of a file its source re-reads with line selectors. */ pagedSource(): this { this.#meta.pagedSource(); return this; } diagnostics(summary: string, messages: string[]): this { this.#meta.diagnostics(summary, messages); return this; } /** Record the bash minimizer's rewrite of this output, when there was one. */ minimized(value: MinimizedMeta | undefined): this { this.#meta.minimized(value); return this; } /** Flag the result as a non-throwing failure (agent-loop surfaces it as a tool error). */ error(value = true): this { this.#isError = value; return this; } /** Marks the result contextually useless — compaction may elide it once consumed. */ useless(value = true): this { this.#useless = value; return this; } done(): AgentToolResult { const meta = this.#meta.get(); if (meta) { this.#details.meta = meta; } const hasDetails = Object.entries(this.#details).some(([, value]) => value !== undefined); return { content: this.#content, details: hasDetails ? this.#details : undefined, ...(this.#isError ? { isError: true } : {}), ...(this.#useless && !this.#isError ? { useless: true } : {}), }; } } export function toolResult(details?: TDetails): ToolResultBuilder { return new ToolResultBuilder(details); }