1import type { AgentEvent } from "@mariozechner/pi-agent-core";
2
3const MAX_OUTPUT_LINES = 20;
4
5export interface OutputOptions {
6 verbose: boolean;
7}
8
9export function formatToolOutput(output: string): string {
10 const lines = output.split(/\r?\n/);
11 if (lines.length <= MAX_OUTPUT_LINES) {
12 return output;
13 }
14
15 const truncated = lines.slice(0, MAX_OUTPUT_LINES).join("\n");
16 return `${truncated}\n(+${lines.length - MAX_OUTPUT_LINES} more lines)`;
17}
18
19export function createEventLogger(options: OutputOptions) {
20 return (event: AgentEvent) => {
21 if (!options.verbose) return;
22
23 if (event.type === "tool_execution_start") {
24 console.error(`\n[tool] ${event.toolName}`);
25 console.error(JSON.stringify(event.args, null, 2));
26 }
27
28 if (event.type === "tool_execution_end") {
29 if (event.isError) {
30 console.error("[tool error]");
31 console.error(String(event.result));
32 return;
33 }
34
35 const text = (event.result?.content ?? [])
36 .filter((content: { type: string }) => content.type === "text")
37 .map((content: { text?: string }) => content.text ?? "")
38 .join("");
39
40 if (text) {
41 console.error(formatToolOutput(text));
42 }
43 }
44 };
45}
46
47export function printUsageSummary(
48 usage: { cost?: { total?: number }; totalTokens?: number; output?: number; input?: number } | undefined,
49 requestCount?: number,
50) {
51 if (!usage) return;
52
53 const tokens = usage.totalTokens ?? (usage.output ?? 0) + (usage.input ?? 0);
54 const rawCost = usage.cost?.total;
55 const cost = typeof rawCost === "number" && !isNaN(rawCost) && rawCost > 0 ? rawCost : undefined;
56
57 let line = `\nusage: ${tokens} tokens`;
58 if (requestCount !== undefined && requestCount > 0) {
59 line += ` across ${requestCount} ${requestCount === 1 ? "request" : "requests"}`;
60 }
61 if (cost !== undefined) {
62 line += `, cost $${cost.toFixed(4)}`;
63 }
64 console.error(line);
65}