Files
oh-my-pi/packages/coding-agent/src/tools/tool-result.ts
T
can1357 4356298182 fix(coding-agent/tools): handled non-zero bash exits as error results
- Tagged non-zero bash command completions as error results, capturing `exitCode` and keeping exit notices in returned text.
- Updated the shell renderer to hide duplicate exit notices from output while surfacing failed command status in the footer.
- Added tests for non-zero versus zero-exit bash results and footer rendering of failed commands.
2026-05-30 16:31:48 +02:00

95 lines
2.5 KiB
TypeScript

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 "../session/streaming-output";
import type { OutputMeta, TruncationOptions, TruncationSummaryOptions, TruncationTextOptions } from "./output-meta";
import { outputMeta } from "./output-meta";
type ToolContent = Array<TextContent | ImageContent>;
type DetailsWithMeta = { meta?: OutputMeta };
export class ToolResultBuilder<TDetails extends DetailsWithMeta> {
#details: TDetails;
#meta = outputMeta();
#content: ToolContent = [];
#isError = 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: TruncationOptions): 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: { matchLimit?: number; resultLimit?: number; headLimit?: number; columnMax?: number }): 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;
}
diagnostics(summary: string, messages: string[]): this {
this.#meta.diagnostics(summary, messages);
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;
}
done(): AgentToolResult<TDetails> {
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 } : {}),
};
}
}
export function toolResult<TDetails extends DetailsWithMeta>(details?: TDetails): ToolResultBuilder<TDetails> {
return new ToolResultBuilder(details);
}