oira666_pi-subagent 0.2.19 → 0.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/types.ts CHANGED
@@ -1,311 +1,311 @@
1
- /**
2
- * Shared type definitions for the subagent extension.
3
- */
4
-
5
- import type { Message } from "@mariozechner/pi-ai";
6
-
7
- /** Context mode for delegated runs. */
8
- export type DelegationMode = "spawn";
9
-
10
- /** Default context mode for delegated runs. */
11
- export const DEFAULT_DELEGATION_MODE: DelegationMode = "spawn";
12
-
13
- /** Aggregated token usage from a subagent run. */
14
- export interface UsageStats {
15
- input: number;
16
- output: number;
17
- cacheRead: number;
18
- cacheWrite: number;
19
- cost: number;
20
- contextTokens: number;
21
- turns: number;
22
- }
23
-
24
- /** Tool calls made by an agent: toolName → call count */
25
- export type ToolCallCounts = Record<string, number>;
26
-
27
- export type LiveLogEntry =
28
- | { kind: "turn_start" }
29
- | { kind: "turn_end"; turn: number; inputTokens: number; outputTokens: number }
30
- | { kind: "tool_start"; toolName: string; args: Record<string, unknown> }
31
- | { kind: "tool_end"; toolName: string };
32
-
33
- export const MAX_LIVE_LOG_ENTRIES = 6;
34
-
35
- /** Result of a single subagent invocation. */
36
- export interface SingleResult {
37
- agent: string;
38
- agentSource: "user" | "project" | "builtin" | "unknown";
39
- task: string;
40
- exitCode: number;
41
- messages: Message[];
42
- stderr: string;
43
- usage: UsageStats;
44
- toolCalls: ToolCallCounts;
45
- model?: string;
46
- stopReason?: string;
47
- errorMessage?: string;
48
- /** Session directory used by this subagent process, when persisted. */
49
- sessionDir?: string;
50
- /** Number of LLM turns completed so far in this agent run. */
51
- completedTurns: number;
52
- /** True while an LLM call is currently in flight (between turn_start and turn_end). */
53
- turnInProgress: boolean;
54
- /**
55
- * Tools actively executing right now, keyed by toolCallId.
56
- * Present only while at least one tool is running; entries are added on
57
- * tool_execution_start and removed on tool_execution_end.
58
- */
59
- liveToolExecutions?: Record<string, { toolName: string; args: Record<string, unknown> }>;
60
- /**
61
- * Rolling buffer of the last MAX_LIVE_LOG_ENTRIES events for TUI display.
62
- * Populated while the agent is running; each entry is one display line.
63
- */
64
- liveLog: LiveLogEntry[];
65
- }
66
-
67
- /** A node in the per-subagent usage tree (own stats + recursive children) */
68
- export interface UsageTreeNode {
69
- agent: string;
70
- task: string;
71
- /** Token/cost usage for this agent's own turns only */
72
- ownUsage: UsageStats;
73
- /** Tool calls this agent made directly (all tools, including "subagent") */
74
- ownToolCalls: ToolCallCounts;
75
- /** ownUsage summed with all descendants recursively */
76
- aggregatedUsage: UsageStats;
77
- /** ownToolCalls merged with all descendants recursively */
78
- aggregatedToolCalls: ToolCallCounts;
79
- /** Nested subagent invocations, recursively populated */
80
- children: UsageTreeNode[];
81
- }
82
-
83
- /** Metadata attached to every tool result for rendering. */
84
- export interface SubagentDetails {
85
- mode: "single" | "parallel";
86
- delegationMode: DelegationMode;
87
- projectAgentsDir: string | null;
88
- results: SingleResult[];
89
- /** Usage summed across all results and all their nested descendants */
90
- aggregatedUsage: UsageStats;
91
- /** Tool calls merged across all results and all their nested descendants */
92
- aggregatedToolCalls: ToolCallCounts;
93
- /** Per-agent recursive usage breakdown */
94
- usageTree: UsageTreeNode[];
95
- }
96
-
97
- /** Nested subagent tool result captured from a delegated run. */
98
- export interface NestedSubagentResult {
99
- details: SubagentDetails;
100
- isError: boolean;
101
- toolCallId: string;
102
- }
103
-
104
- /** A display-friendly representation of a message part. */
105
- export type DisplayItem =
106
- | { type: "text"; text: string }
107
- | { type: "toolCall"; name: string; args: Record<string, unknown> };
108
-
109
- /** Create an empty UsageStats object. */
110
- export function emptyUsage(): UsageStats {
111
- return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 };
112
- }
113
-
114
- /** Sum usage across multiple results. */
115
- export function aggregateUsage(results: SingleResult[]): UsageStats {
116
- const total = emptyUsage();
117
- for (const r of results) {
118
- total.input += r.usage.input;
119
- total.output += r.usage.output;
120
- total.cacheRead += r.usage.cacheRead;
121
- total.cacheWrite += r.usage.cacheWrite;
122
- total.cost += r.usage.cost;
123
- total.turns += r.usage.turns;
124
- }
125
- return total;
126
- }
127
-
128
- /** Add delta into total in-place (contextTokens is a snapshot—not summed, left as-is in total) */
129
- export function addUsage(total: UsageStats, delta: UsageStats): void {
130
- total.input += delta.input;
131
- total.output += delta.output;
132
- total.cacheRead += delta.cacheRead;
133
- total.cacheWrite += delta.cacheWrite;
134
- total.cost += delta.cost;
135
- total.turns += delta.turns;
136
- }
137
-
138
- /** Merge tool call counts from `source` into `target` in-place */
139
- export function mergeToolCalls(target: ToolCallCounts, source: ToolCallCounts): void {
140
- for (const [name, count] of Object.entries(source)) {
141
- target[name] = (target[name] ?? 0) + count;
142
- }
143
- }
144
-
145
- /** Extract all tool calls made by assistant turns in a message list */
146
- export function extractToolCalls(messages: Message[]): ToolCallCounts {
147
- const counts: ToolCallCounts = {};
148
- for (const msg of messages) {
149
- if (msg.role !== "assistant") continue;
150
- for (const part of (msg.content as any[]) ?? []) {
151
- if ((part as any)?.type !== "toolCall") continue;
152
- const name: string = typeof (part as any).name === "string" ? (part as any).name : "unknown";
153
- counts[name] = (counts[name] ?? 0) + 1;
154
- }
155
- }
156
- return counts;
157
- }
158
-
159
- /** Build a UsageTreeNode for one result, recursing into nested subagent tool results */
160
- function buildUsageTreeNode(result: SingleResult): UsageTreeNode {
161
- const children: UsageTreeNode[] = [];
162
- for (const nested of getNestedSubagentResults(result.messages)) {
163
- for (const nestedResult of nested.details.results) {
164
- children.push(buildUsageTreeNode(nestedResult));
165
- }
166
- }
167
-
168
- const ownUsage = result.usage;
169
- const ownToolCalls: ToolCallCounts = result.toolCalls ?? extractToolCalls(result.messages);
170
-
171
- const aggregatedUsage = emptyUsage();
172
- addUsage(aggregatedUsage, ownUsage);
173
- for (const child of children) addUsage(aggregatedUsage, child.aggregatedUsage);
174
-
175
- const aggregatedToolCalls: ToolCallCounts = { ...ownToolCalls };
176
- for (const child of children) mergeToolCalls(aggregatedToolCalls, child.aggregatedToolCalls);
177
-
178
- return {
179
- agent: result.agent,
180
- task: result.task,
181
- ownUsage,
182
- ownToolCalls,
183
- aggregatedUsage,
184
- aggregatedToolCalls,
185
- children,
186
- };
187
- }
188
-
189
- /**
190
- * Construct a complete SubagentDetails with aggregated stats.
191
- * This replaces the plain object literal previously used by makeDetailsFactory.
192
- */
193
- export function buildSubagentDetails(
194
- mode: "single" | "parallel",
195
- delegationMode: DelegationMode,
196
- projectAgentsDir: string | null,
197
- results: SingleResult[],
198
- ): SubagentDetails {
199
- const usageTree = results.map(buildUsageTreeNode);
200
-
201
- const aggregatedUsage = emptyUsage();
202
- const aggregatedToolCalls: ToolCallCounts = {};
203
- for (const node of usageTree) {
204
- addUsage(aggregatedUsage, node.aggregatedUsage);
205
- mergeToolCalls(aggregatedToolCalls, node.aggregatedToolCalls);
206
- }
207
-
208
- return {
209
- mode,
210
- delegationMode,
211
- projectAgentsDir,
212
- results,
213
- aggregatedUsage,
214
- aggregatedToolCalls,
215
- usageTree,
216
- };
217
- }
218
-
219
- /** Whether a result represents an error. */
220
- export function isResultError(r: SingleResult): boolean {
221
- return r.exitCode > 0 || r.stopReason === "error" || r.stopReason === "aborted";
222
- }
223
-
224
- /** Check whether a value looks like SubagentDetails. */
225
- export function isSubagentDetails(value: unknown): value is SubagentDetails {
226
- if (!value || typeof value !== "object") return false;
227
- const maybe = value as Partial<SubagentDetails>;
228
- return (maybe.mode === "single" || maybe.mode === "parallel") &&
229
- maybe.delegationMode === "spawn" &&
230
- Array.isArray(maybe.results);
231
- }
232
-
233
- /** Extract the last assistant text from a message history. */
234
- export function getFinalOutput(messages: Message[]): string {
235
- for (let i = messages.length - 1; i >= 0; i--) {
236
- const msg = messages[i];
237
- if (msg.role === "assistant") {
238
- for (const part of msg.content) {
239
- if (part.type === "text") return part.text;
240
- }
241
- }
242
- }
243
- return "";
244
- }
245
-
246
- /** Extract all display-worthy items from a message history. */
247
- export function getDisplayItems(messages: Message[]): DisplayItem[] {
248
- const items: DisplayItem[] = [];
249
- for (const msg of messages) {
250
- if (msg.role === "assistant") {
251
- for (const part of msg.content) {
252
- if (part.type === "text") {
253
- items.push({ type: "text", text: part.text });
254
- } else if (part.type === "toolCall") {
255
- items.push({ type: "toolCall", name: part.name, args: part.arguments });
256
- }
257
- }
258
- }
259
- }
260
- return items;
261
- }
262
-
263
- /** Extract nested subagent tool results from a message history. */
264
- export function getNestedSubagentResults(messages: Message[]): NestedSubagentResult[] {
265
- const results: NestedSubagentResult[] = [];
266
- for (const msg of messages) {
267
- if (msg.role !== "toolResult") continue;
268
- if (msg.toolName !== "subagent") continue;
269
- if (!isSubagentDetails(msg.details)) continue;
270
- results.push({
271
- details: msg.details,
272
- isError: msg.isError,
273
- toolCallId: msg.toolCallId,
274
- });
275
- }
276
- return results;
277
- }
278
-
279
- function collectSubagentErrorLinesFromDetails(
280
- details: SubagentDetails,
281
- lines: string[],
282
- prefix = "",
283
- ): void {
284
- for (const result of details.results) {
285
- if (isResultError(result)) {
286
- const reason = result.errorMessage || result.stderr || result.stopReason || "failed";
287
- lines.push(`${prefix}${result.agent}: ${reason}`);
288
- }
289
- const nested = getNestedSubagentResults(result.messages);
290
- for (const child of nested) {
291
- if (child.isError) {
292
- collectSubagentErrorLinesFromDetails(
293
- child.details,
294
- lines,
295
- `${prefix}${result.agent} -> `,
296
- );
297
- }
298
- }
299
- }
300
- }
301
-
302
- /** Summarize nested subagent failures captured in a message history. */
303
- export function getNestedSubagentErrorSummary(messages: Message[]): string | null {
304
- const lines: string[] = [];
305
- for (const nested of getNestedSubagentResults(messages)) {
306
- if (!nested.isError) continue;
307
- collectSubagentErrorLinesFromDetails(nested.details, lines);
308
- }
309
- if (lines.length === 0) return null;
310
- return `Nested subagent failure: ${lines.join("; ")}`;
311
- }
1
+ /**
2
+ * Shared type definitions for the subagent extension.
3
+ */
4
+
5
+ import type { Message } from "@mariozechner/pi-ai";
6
+
7
+ /** Context mode for delegated runs. */
8
+ export type DelegationMode = "spawn";
9
+
10
+ /** Default context mode for delegated runs. */
11
+ export const DEFAULT_DELEGATION_MODE: DelegationMode = "spawn";
12
+
13
+ /** Aggregated token usage from a subagent run. */
14
+ export interface UsageStats {
15
+ input: number;
16
+ output: number;
17
+ cacheRead: number;
18
+ cacheWrite: number;
19
+ cost: number;
20
+ contextTokens: number;
21
+ turns: number;
22
+ }
23
+
24
+ /** Tool calls made by an agent: toolName → call count */
25
+ export type ToolCallCounts = Record<string, number>;
26
+
27
+ export type LiveLogEntry =
28
+ | { kind: "turn_start" }
29
+ | { kind: "turn_end"; turn: number; inputTokens: number; outputTokens: number }
30
+ | { kind: "tool_start"; toolName: string; args: Record<string, unknown> }
31
+ | { kind: "tool_end"; toolName: string };
32
+
33
+ export const MAX_LIVE_LOG_ENTRIES = 6;
34
+
35
+ /** Result of a single subagent invocation. */
36
+ export interface SingleResult {
37
+ agent: string;
38
+ agentSource: "user" | "project" | "builtin" | "unknown";
39
+ task: string;
40
+ exitCode: number;
41
+ messages: Message[];
42
+ stderr: string;
43
+ usage: UsageStats;
44
+ toolCalls: ToolCallCounts;
45
+ model?: string;
46
+ stopReason?: string;
47
+ errorMessage?: string;
48
+ /** Session directory used by this subagent process, when persisted. */
49
+ sessionDir?: string;
50
+ /** Number of LLM turns completed so far in this agent run. */
51
+ completedTurns: number;
52
+ /** True while an LLM call is currently in flight (between turn_start and turn_end). */
53
+ turnInProgress: boolean;
54
+ /**
55
+ * Tools actively executing right now, keyed by toolCallId.
56
+ * Present only while at least one tool is running; entries are added on
57
+ * tool_execution_start and removed on tool_execution_end.
58
+ */
59
+ liveToolExecutions?: Record<string, { toolName: string; args: Record<string, unknown> }>;
60
+ /**
61
+ * Rolling buffer of the last MAX_LIVE_LOG_ENTRIES events for TUI display.
62
+ * Populated while the agent is running; each entry is one display line.
63
+ */
64
+ liveLog: LiveLogEntry[];
65
+ }
66
+
67
+ /** A node in the per-subagent usage tree (own stats + recursive children) */
68
+ export interface UsageTreeNode {
69
+ agent: string;
70
+ task: string;
71
+ /** Token/cost usage for this agent's own turns only */
72
+ ownUsage: UsageStats;
73
+ /** Tool calls this agent made directly (all tools, including "subagent") */
74
+ ownToolCalls: ToolCallCounts;
75
+ /** ownUsage summed with all descendants recursively */
76
+ aggregatedUsage: UsageStats;
77
+ /** ownToolCalls merged with all descendants recursively */
78
+ aggregatedToolCalls: ToolCallCounts;
79
+ /** Nested subagent invocations, recursively populated */
80
+ children: UsageTreeNode[];
81
+ }
82
+
83
+ /** Metadata attached to every tool result for rendering. */
84
+ export interface SubagentDetails {
85
+ mode: "single" | "parallel";
86
+ delegationMode: DelegationMode;
87
+ projectAgentsDir: string | null;
88
+ results: SingleResult[];
89
+ /** Usage summed across all results and all their nested descendants */
90
+ aggregatedUsage: UsageStats;
91
+ /** Tool calls merged across all results and all their nested descendants */
92
+ aggregatedToolCalls: ToolCallCounts;
93
+ /** Per-agent recursive usage breakdown */
94
+ usageTree: UsageTreeNode[];
95
+ }
96
+
97
+ /** Nested subagent tool result captured from a delegated run. */
98
+ export interface NestedSubagentResult {
99
+ details: SubagentDetails;
100
+ isError: boolean;
101
+ toolCallId: string;
102
+ }
103
+
104
+ /** A display-friendly representation of a message part. */
105
+ export type DisplayItem =
106
+ | { type: "text"; text: string }
107
+ | { type: "toolCall"; name: string; args: Record<string, unknown> };
108
+
109
+ /** Create an empty UsageStats object. */
110
+ export function emptyUsage(): UsageStats {
111
+ return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 };
112
+ }
113
+
114
+ /** Sum usage across multiple results. */
115
+ export function aggregateUsage(results: SingleResult[]): UsageStats {
116
+ const total = emptyUsage();
117
+ for (const r of results) {
118
+ total.input += r.usage.input;
119
+ total.output += r.usage.output;
120
+ total.cacheRead += r.usage.cacheRead;
121
+ total.cacheWrite += r.usage.cacheWrite;
122
+ total.cost += r.usage.cost;
123
+ total.turns += r.usage.turns;
124
+ }
125
+ return total;
126
+ }
127
+
128
+ /** Add delta into total in-place (contextTokens is a snapshot—not summed, left as-is in total) */
129
+ export function addUsage(total: UsageStats, delta: UsageStats): void {
130
+ total.input += delta.input;
131
+ total.output += delta.output;
132
+ total.cacheRead += delta.cacheRead;
133
+ total.cacheWrite += delta.cacheWrite;
134
+ total.cost += delta.cost;
135
+ total.turns += delta.turns;
136
+ }
137
+
138
+ /** Merge tool call counts from `source` into `target` in-place */
139
+ export function mergeToolCalls(target: ToolCallCounts, source: ToolCallCounts): void {
140
+ for (const [name, count] of Object.entries(source)) {
141
+ target[name] = (target[name] ?? 0) + count;
142
+ }
143
+ }
144
+
145
+ /** Extract all tool calls made by assistant turns in a message list */
146
+ export function extractToolCalls(messages: Message[]): ToolCallCounts {
147
+ const counts: ToolCallCounts = {};
148
+ for (const msg of messages) {
149
+ if (msg.role !== "assistant") continue;
150
+ for (const part of (msg.content as any[]) ?? []) {
151
+ if ((part as any)?.type !== "toolCall") continue;
152
+ const name: string = typeof (part as any).name === "string" ? (part as any).name : "unknown";
153
+ counts[name] = (counts[name] ?? 0) + 1;
154
+ }
155
+ }
156
+ return counts;
157
+ }
158
+
159
+ /** Build a UsageTreeNode for one result, recursing into nested subagent tool results */
160
+ function buildUsageTreeNode(result: SingleResult): UsageTreeNode {
161
+ const children: UsageTreeNode[] = [];
162
+ for (const nested of getNestedSubagentResults(result.messages)) {
163
+ for (const nestedResult of nested.details.results) {
164
+ children.push(buildUsageTreeNode(nestedResult));
165
+ }
166
+ }
167
+
168
+ const ownUsage = result.usage;
169
+ const ownToolCalls: ToolCallCounts = result.toolCalls ?? extractToolCalls(result.messages);
170
+
171
+ const aggregatedUsage = emptyUsage();
172
+ addUsage(aggregatedUsage, ownUsage);
173
+ for (const child of children) addUsage(aggregatedUsage, child.aggregatedUsage);
174
+
175
+ const aggregatedToolCalls: ToolCallCounts = { ...ownToolCalls };
176
+ for (const child of children) mergeToolCalls(aggregatedToolCalls, child.aggregatedToolCalls);
177
+
178
+ return {
179
+ agent: result.agent,
180
+ task: result.task,
181
+ ownUsage,
182
+ ownToolCalls,
183
+ aggregatedUsage,
184
+ aggregatedToolCalls,
185
+ children,
186
+ };
187
+ }
188
+
189
+ /**
190
+ * Construct a complete SubagentDetails with aggregated stats.
191
+ * This replaces the plain object literal previously used by makeDetailsFactory.
192
+ */
193
+ export function buildSubagentDetails(
194
+ mode: "single" | "parallel",
195
+ delegationMode: DelegationMode,
196
+ projectAgentsDir: string | null,
197
+ results: SingleResult[],
198
+ ): SubagentDetails {
199
+ const usageTree = results.map(buildUsageTreeNode);
200
+
201
+ const aggregatedUsage = emptyUsage();
202
+ const aggregatedToolCalls: ToolCallCounts = {};
203
+ for (const node of usageTree) {
204
+ addUsage(aggregatedUsage, node.aggregatedUsage);
205
+ mergeToolCalls(aggregatedToolCalls, node.aggregatedToolCalls);
206
+ }
207
+
208
+ return {
209
+ mode,
210
+ delegationMode,
211
+ projectAgentsDir,
212
+ results,
213
+ aggregatedUsage,
214
+ aggregatedToolCalls,
215
+ usageTree,
216
+ };
217
+ }
218
+
219
+ /** Whether a result represents an error. */
220
+ export function isResultError(r: SingleResult): boolean {
221
+ return r.exitCode > 0 || r.stopReason === "error" || r.stopReason === "aborted";
222
+ }
223
+
224
+ /** Check whether a value looks like SubagentDetails. */
225
+ export function isSubagentDetails(value: unknown): value is SubagentDetails {
226
+ if (!value || typeof value !== "object") return false;
227
+ const maybe = value as Partial<SubagentDetails>;
228
+ return (maybe.mode === "single" || maybe.mode === "parallel") &&
229
+ maybe.delegationMode === "spawn" &&
230
+ Array.isArray(maybe.results);
231
+ }
232
+
233
+ /** Extract the last assistant text from a message history. */
234
+ export function getFinalOutput(messages: Message[]): string {
235
+ for (let i = messages.length - 1; i >= 0; i--) {
236
+ const msg = messages[i];
237
+ if (msg.role === "assistant") {
238
+ for (const part of msg.content) {
239
+ if (part.type === "text") return part.text;
240
+ }
241
+ }
242
+ }
243
+ return "";
244
+ }
245
+
246
+ /** Extract all display-worthy items from a message history. */
247
+ export function getDisplayItems(messages: Message[]): DisplayItem[] {
248
+ const items: DisplayItem[] = [];
249
+ for (const msg of messages) {
250
+ if (msg.role === "assistant") {
251
+ for (const part of msg.content) {
252
+ if (part.type === "text") {
253
+ items.push({ type: "text", text: part.text });
254
+ } else if (part.type === "toolCall") {
255
+ items.push({ type: "toolCall", name: part.name, args: part.arguments });
256
+ }
257
+ }
258
+ }
259
+ }
260
+ return items;
261
+ }
262
+
263
+ /** Extract nested subagent tool results from a message history. */
264
+ export function getNestedSubagentResults(messages: Message[]): NestedSubagentResult[] {
265
+ const results: NestedSubagentResult[] = [];
266
+ for (const msg of messages) {
267
+ if (msg.role !== "toolResult") continue;
268
+ if (msg.toolName !== "subagent") continue;
269
+ if (!isSubagentDetails(msg.details)) continue;
270
+ results.push({
271
+ details: msg.details,
272
+ isError: msg.isError,
273
+ toolCallId: msg.toolCallId,
274
+ });
275
+ }
276
+ return results;
277
+ }
278
+
279
+ function collectSubagentErrorLinesFromDetails(
280
+ details: SubagentDetails,
281
+ lines: string[],
282
+ prefix = "",
283
+ ): void {
284
+ for (const result of details.results) {
285
+ if (isResultError(result)) {
286
+ const reason = result.errorMessage || result.stderr || result.stopReason || "failed";
287
+ lines.push(`${prefix}${result.agent}: ${reason}`);
288
+ }
289
+ const nested = getNestedSubagentResults(result.messages);
290
+ for (const child of nested) {
291
+ if (child.isError) {
292
+ collectSubagentErrorLinesFromDetails(
293
+ child.details,
294
+ lines,
295
+ `${prefix}${result.agent} -> `,
296
+ );
297
+ }
298
+ }
299
+ }
300
+ }
301
+
302
+ /** Summarize nested subagent failures captured in a message history. */
303
+ export function getNestedSubagentErrorSummary(messages: Message[]): string | null {
304
+ const lines: string[] = [];
305
+ for (const nested of getNestedSubagentResults(messages)) {
306
+ if (!nested.isError) continue;
307
+ collectSubagentErrorLinesFromDetails(nested.details, lines);
308
+ }
309
+ if (lines.length === 0) return null;
310
+ return `Nested subagent failure: ${lines.join("; ")}`;
311
+ }