forked from nicobailon/pi-subagents
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathformatters.ts
More file actions
116 lines (104 loc) · 3.86 KB
/
formatters.ts
File metadata and controls
116 lines (104 loc) · 3.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/**
* Formatting utilities for display output
*/
import * as fs from "node:fs";
import * as path from "node:path";
import type { Usage, SingleResult } from "./types.js";
import type { ChainStep, SequentialStep } from "./settings.js";
import { isParallelStep } from "./settings.js";
/**
* Format token count with k suffix for large numbers
*/
export function formatTokens(n: number): string {
return n < 1000 ? String(n) : n < 10000 ? `${(n / 1000).toFixed(1)}k` : `${Math.round(n / 1000)}k`;
}
/**
* Format usage statistics into a compact string
*/
export function formatUsage(u: Usage, model?: string): string {
const parts: string[] = [];
if (u.turns) parts.push(`${u.turns} turn${u.turns > 1 ? "s" : ""}`);
if (u.input) parts.push(`in:${formatTokens(u.input)}`);
if (u.output) parts.push(`out:${formatTokens(u.output)}`);
if (u.cacheRead) parts.push(`R${formatTokens(u.cacheRead)}`);
if (u.cacheWrite) parts.push(`W${formatTokens(u.cacheWrite)}`);
if (u.cost) parts.push(`$${u.cost.toFixed(4)}`);
if (model) parts.push(model);
return parts.join(" ");
}
/**
* Format duration in human-readable form
*/
export function formatDuration(ms: number): string {
if (ms < 1000) return `${ms}ms`;
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
return `${Math.floor(ms / 60000)}m${Math.floor((ms % 60000) / 1000)}s`;
}
/**
* Build a summary string for a completed/failed chain
*/
export function buildChainSummary(
steps: ChainStep[],
results: SingleResult[],
chainDir: string,
status: "completed" | "failed",
failedStep?: { index: number; error: string },
): string {
// Build step names for display
const stepNames = steps
.map((s) => (isParallelStep(s) ? `parallel[${s.parallel.length}]` : (s as SequentialStep).agent))
.join(" → ");
// Calculate total duration from results
const totalDuration = results.reduce((sum, r) => sum + (r.progress?.durationMs || 0), 0);
const durationStr = formatDuration(totalDuration);
// Check for progress.md
const progressPath = path.join(chainDir, "progress.md");
const hasProgress = fs.existsSync(progressPath);
const allSkills = new Set<string>();
for (const r of results) {
if (r.skills) r.skills.forEach((s) => allSkills.add(s));
}
const skillsLine = allSkills.size > 0 ? `🔧 Skills: ${[...allSkills].join(", ")}` : "";
if (status === "completed") {
const stepWord = results.length === 1 ? "step" : "steps";
return `✅ Chain completed: ${stepNames} (${results.length} ${stepWord}, ${durationStr})${skillsLine ? `\n${skillsLine}` : ""}
📋 Progress: ${hasProgress ? progressPath : "(none)"}
📁 Artifacts: ${chainDir}`;
} else {
const stepInfo = failedStep ? ` at step ${failedStep.index + 1}` : "";
const errorInfo = failedStep?.error ? `: ${failedStep.error}` : "";
return `❌ Chain failed${stepInfo}${errorInfo}${skillsLine ? `\n${skillsLine}` : ""}
📋 Progress: ${hasProgress ? progressPath : "(none)"}
📁 Artifacts: ${chainDir}`;
}
}
/**
* Format a tool call for display
*/
export function formatToolCall(name: string, args: Record<string, unknown>): string {
switch (name) {
case "bash":
return `$ ${((args.command as string) || "").slice(0, 60)}${(args.command as string)?.length > 60 ? "..." : ""}`;
case "read":
return `read ${shortenPath((args.path || args.file_path || "") as string)}`;
case "write":
return `write ${shortenPath((args.path || args.file_path || "") as string)}`;
case "edit":
return `edit ${shortenPath((args.path || args.file_path || "") as string)}`;
default: {
const s = JSON.stringify(args);
return `${name} ${s.slice(0, 40)}${s.length > 40 ? "..." : ""}`;
}
}
}
/**
* Shorten a path by replacing home directory with ~
*/
export function shortenPath(p: string): string {
const home = process.env.HOME;
// Only shorten if HOME is defined and non-empty, and path starts with it
if (home && p.startsWith(home)) {
return `~${p.slice(home.length)}`;
}
return p;
}