@ilya-lesikov/pi-pi 0.1.0 → 0.2.0
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/3p/pi-plannotator/apps/pi-extension/plannotator-browser.ts +6 -3
- package/3p/pi-plannotator/packages/shared/review-core.ts +22 -3
- package/3p/pi-plannotator/packages/shared/vcs-core.ts +1 -1
- package/3p/pi-subagents/src/index.ts +9 -1
- package/extensions/orchestrator/agents/brainstorm-reviewer.ts +1 -0
- package/extensions/orchestrator/agents/code-reviewer.ts +1 -0
- package/extensions/orchestrator/agents/plan-reviewer.ts +4 -1
- package/extensions/orchestrator/agents/planner.ts +13 -2
- package/extensions/orchestrator/agents/tool-routing.ts +9 -0
- package/extensions/orchestrator/command-handlers.test.ts +22 -25
- package/extensions/orchestrator/config.test.ts +1 -0
- package/extensions/orchestrator/config.ts +3 -1
- package/extensions/orchestrator/custom-footer.ts +146 -0
- package/extensions/orchestrator/event-handlers.test.ts +1 -0
- package/extensions/orchestrator/event-handlers.ts +73 -146
- package/extensions/orchestrator/flant-infra.ts +7 -2
- package/extensions/orchestrator/integration.test.ts +3 -1
- package/extensions/orchestrator/orchestrator.test.ts +1 -0
- package/extensions/orchestrator/orchestrator.ts +12 -4
- package/extensions/orchestrator/phases/machine.ts +38 -0
- package/extensions/orchestrator/phases/review-task.ts +32 -0
- package/extensions/orchestrator/pp-menu.ts +405 -49
- package/extensions/orchestrator/state.ts +8 -9
- package/extensions/orchestrator/usage-tracker.ts +306 -0
- package/package.json +1 -1
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
export interface ModelUsage {
|
|
6
|
+
inputTokens: number;
|
|
7
|
+
outputTokens: number;
|
|
8
|
+
cacheReadTokens: number;
|
|
9
|
+
cacheWriteTokens: number;
|
|
10
|
+
turns: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface UsageTracker {
|
|
14
|
+
recordTurn(modelId: string, provider: string, input: number, output: number, cacheRead: number, cacheWrite: number, cost: number): void;
|
|
15
|
+
recordSubagentCompletion(tokens: { input?: number; output?: number; total?: number }, cost?: number, meta?: { description?: string; modelId?: string; durationMs?: number; toolUses?: number }): void;
|
|
16
|
+
loadFromSummary(summary: Record<string, unknown>): void;
|
|
17
|
+
getTotalInputTokens(): number;
|
|
18
|
+
getTotalOutputTokens(): number;
|
|
19
|
+
getTotalCacheReadTokens(): number;
|
|
20
|
+
getTotalCacheWriteTokens(): number;
|
|
21
|
+
getTotalCost(): number;
|
|
22
|
+
getCacheHitRate(): number;
|
|
23
|
+
getPerModelUsage(): Record<string, ModelUsage>;
|
|
24
|
+
getSubagentTotals(): { inputTokens: number; outputTokens: number; cost: number };
|
|
25
|
+
getSubagentList(): SubagentUsage[];
|
|
26
|
+
toSummary(): object;
|
|
27
|
+
reset(): void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SubagentUsage {
|
|
31
|
+
description: string;
|
|
32
|
+
modelId: string;
|
|
33
|
+
inputTokens: number;
|
|
34
|
+
outputTokens: number;
|
|
35
|
+
cacheReadTokens: number;
|
|
36
|
+
cacheWriteTokens: number;
|
|
37
|
+
cost: number;
|
|
38
|
+
durationMs: number;
|
|
39
|
+
toolUses: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface TrackerState {
|
|
43
|
+
startedAt: string;
|
|
44
|
+
totalInputTokens: number;
|
|
45
|
+
totalOutputTokens: number;
|
|
46
|
+
totalCacheReadTokens: number;
|
|
47
|
+
totalCacheWriteTokens: number;
|
|
48
|
+
totalCost: number;
|
|
49
|
+
totalTurns: number;
|
|
50
|
+
subagentInputTokens: number;
|
|
51
|
+
subagentOutputTokens: number;
|
|
52
|
+
subagentCost: number;
|
|
53
|
+
models: Map<string, ModelUsage>;
|
|
54
|
+
subagents: SubagentUsage[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function toFiniteNumber(value: unknown): number {
|
|
58
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function resolveAgentDir(): string {
|
|
62
|
+
const envKey = "PI_CODING_AGENT_DIR";
|
|
63
|
+
const envDir = process.env[envKey];
|
|
64
|
+
if (envDir) {
|
|
65
|
+
if (envDir === "~") return homedir();
|
|
66
|
+
if (envDir.startsWith("~/")) return homedir() + envDir.slice(1);
|
|
67
|
+
return envDir;
|
|
68
|
+
}
|
|
69
|
+
return join(homedir(), ".pi", "agent");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function createInitialState(): TrackerState {
|
|
73
|
+
return {
|
|
74
|
+
startedAt: new Date().toISOString(),
|
|
75
|
+
totalInputTokens: 0,
|
|
76
|
+
totalOutputTokens: 0,
|
|
77
|
+
totalCacheReadTokens: 0,
|
|
78
|
+
totalCacheWriteTokens: 0,
|
|
79
|
+
totalCost: 0,
|
|
80
|
+
totalTurns: 0,
|
|
81
|
+
subagentInputTokens: 0,
|
|
82
|
+
subagentOutputTokens: 0,
|
|
83
|
+
subagentCost: 0,
|
|
84
|
+
models: new Map<string, ModelUsage>(),
|
|
85
|
+
subagents: [],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function createUsageTracker(): UsageTracker {
|
|
90
|
+
const state = createInitialState();
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
recordTurn(modelId: string, _provider: string, input: number, output: number, cacheRead: number, cacheWrite: number, cost: number): void {
|
|
94
|
+
const safeInput = toFiniteNumber(input);
|
|
95
|
+
const safeOutput = toFiniteNumber(output);
|
|
96
|
+
const safeCacheRead = toFiniteNumber(cacheRead);
|
|
97
|
+
const safeCacheWrite = toFiniteNumber(cacheWrite);
|
|
98
|
+
const safeCost = toFiniteNumber(cost);
|
|
99
|
+
|
|
100
|
+
state.totalInputTokens += safeInput;
|
|
101
|
+
state.totalOutputTokens += safeOutput;
|
|
102
|
+
state.totalCacheReadTokens += safeCacheRead;
|
|
103
|
+
state.totalCacheWriteTokens += safeCacheWrite;
|
|
104
|
+
state.totalCost += safeCost;
|
|
105
|
+
state.totalTurns += 1;
|
|
106
|
+
|
|
107
|
+
const key = modelId || "unknown-model";
|
|
108
|
+
const usage = state.models.get(key) ?? {
|
|
109
|
+
inputTokens: 0,
|
|
110
|
+
outputTokens: 0,
|
|
111
|
+
cacheReadTokens: 0,
|
|
112
|
+
cacheWriteTokens: 0,
|
|
113
|
+
turns: 0,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
usage.inputTokens += safeInput;
|
|
117
|
+
usage.outputTokens += safeOutput;
|
|
118
|
+
usage.cacheReadTokens += safeCacheRead;
|
|
119
|
+
usage.cacheWriteTokens += safeCacheWrite;
|
|
120
|
+
usage.turns += 1;
|
|
121
|
+
state.models.set(key, usage);
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
recordSubagentCompletion(
|
|
125
|
+
tokens: { input?: number; output?: number; cacheRead?: number; cacheWrite?: number; total?: number; cost?: number },
|
|
126
|
+
cost?: number,
|
|
127
|
+
meta?: { description?: string; modelId?: string; durationMs?: number; toolUses?: number },
|
|
128
|
+
): void {
|
|
129
|
+
const safeInput = toFiniteNumber(tokens.input);
|
|
130
|
+
const safeOutput = toFiniteNumber(tokens.output);
|
|
131
|
+
const safeTotal = toFiniteNumber(tokens.total);
|
|
132
|
+
const safeCacheRead = toFiniteNumber(tokens.cacheRead);
|
|
133
|
+
const safeCacheWrite = toFiniteNumber(tokens.cacheWrite);
|
|
134
|
+
const safeCost = toFiniteNumber(tokens.cost ?? cost);
|
|
135
|
+
|
|
136
|
+
const effectiveInput = safeInput === 0 && safeOutput === 0 ? safeTotal : safeInput;
|
|
137
|
+
|
|
138
|
+
state.subagentInputTokens += effectiveInput;
|
|
139
|
+
state.subagentOutputTokens += safeOutput;
|
|
140
|
+
state.subagentCost += safeCost;
|
|
141
|
+
|
|
142
|
+
state.subagents.push({
|
|
143
|
+
description: meta?.description ?? "unknown",
|
|
144
|
+
modelId: meta?.modelId ?? "unknown",
|
|
145
|
+
inputTokens: effectiveInput,
|
|
146
|
+
outputTokens: safeOutput,
|
|
147
|
+
cacheReadTokens: safeCacheRead,
|
|
148
|
+
cacheWriteTokens: safeCacheWrite,
|
|
149
|
+
cost: safeCost,
|
|
150
|
+
durationMs: toFiniteNumber(meta?.durationMs),
|
|
151
|
+
toolUses: toFiniteNumber(meta?.toolUses),
|
|
152
|
+
});
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
loadFromSummary(summary: Record<string, unknown>): void {
|
|
156
|
+
const totals = summary.totals as Record<string, unknown> | undefined;
|
|
157
|
+
const subagents = summary.subagents as Record<string, unknown> | undefined;
|
|
158
|
+
const models = summary.models as Record<string, Record<string, unknown>> | undefined;
|
|
159
|
+
if (totals) {
|
|
160
|
+
state.totalInputTokens = toFiniteNumber(totals.inputTokens);
|
|
161
|
+
state.totalOutputTokens = toFiniteNumber(totals.outputTokens);
|
|
162
|
+
state.totalCacheReadTokens = toFiniteNumber(totals.cacheReadTokens);
|
|
163
|
+
state.totalCacheWriteTokens = toFiniteNumber(totals.cacheWriteTokens);
|
|
164
|
+
state.totalCost = toFiniteNumber(totals.cost);
|
|
165
|
+
state.totalTurns = toFiniteNumber(totals.turns);
|
|
166
|
+
}
|
|
167
|
+
if (Array.isArray(summary.subagents)) {
|
|
168
|
+
for (const sa of summary.subagents as Record<string, unknown>[]) {
|
|
169
|
+
const entry: SubagentUsage = {
|
|
170
|
+
description: typeof sa.description === "string" ? sa.description : "unknown",
|
|
171
|
+
modelId: typeof sa.modelId === "string" ? sa.modelId : "unknown",
|
|
172
|
+
inputTokens: toFiniteNumber(sa.inputTokens),
|
|
173
|
+
outputTokens: toFiniteNumber(sa.outputTokens),
|
|
174
|
+
cacheReadTokens: toFiniteNumber(sa.cacheReadTokens),
|
|
175
|
+
cacheWriteTokens: toFiniteNumber(sa.cacheWriteTokens),
|
|
176
|
+
cost: toFiniteNumber(sa.cost),
|
|
177
|
+
durationMs: toFiniteNumber(sa.durationMs),
|
|
178
|
+
toolUses: toFiniteNumber(sa.toolUses),
|
|
179
|
+
};
|
|
180
|
+
state.subagents.push(entry);
|
|
181
|
+
state.subagentInputTokens += entry.inputTokens;
|
|
182
|
+
state.subagentOutputTokens += entry.outputTokens;
|
|
183
|
+
state.subagentCost += entry.cost;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (models) {
|
|
187
|
+
for (const [modelId, usage] of Object.entries(models)) {
|
|
188
|
+
state.models.set(modelId, {
|
|
189
|
+
inputTokens: toFiniteNumber(usage.inputTokens),
|
|
190
|
+
outputTokens: toFiniteNumber(usage.outputTokens),
|
|
191
|
+
cacheReadTokens: toFiniteNumber(usage.cacheReadTokens),
|
|
192
|
+
cacheWriteTokens: toFiniteNumber(usage.cacheWriteTokens),
|
|
193
|
+
turns: toFiniteNumber(usage.turns),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (typeof summary.startedAt === "string") {
|
|
198
|
+
state.startedAt = summary.startedAt;
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
getTotalInputTokens(): number {
|
|
203
|
+
return state.totalInputTokens;
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
getTotalOutputTokens(): number {
|
|
207
|
+
return state.totalOutputTokens;
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
getTotalCacheReadTokens(): number {
|
|
211
|
+
return state.totalCacheReadTokens;
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
getTotalCacheWriteTokens(): number {
|
|
215
|
+
return state.totalCacheWriteTokens;
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
getTotalCost(): number {
|
|
219
|
+
return state.totalCost;
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
getCacheHitRate(): number {
|
|
223
|
+
const denominator = state.totalCacheReadTokens + state.totalInputTokens;
|
|
224
|
+
if (denominator <= 0) return 0;
|
|
225
|
+
return state.totalCacheReadTokens / denominator;
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
getPerModelUsage(): Record<string, ModelUsage> {
|
|
229
|
+
const out: Record<string, ModelUsage> = {};
|
|
230
|
+
for (const [model, usage] of state.models.entries()) {
|
|
231
|
+
out[model] = { ...usage };
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
getSubagentTotals(): { inputTokens: number; outputTokens: number; cost: number } {
|
|
237
|
+
return {
|
|
238
|
+
inputTokens: state.subagentInputTokens,
|
|
239
|
+
outputTokens: state.subagentOutputTokens,
|
|
240
|
+
cost: state.subagentCost,
|
|
241
|
+
};
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
getSubagentList(): SubagentUsage[] {
|
|
245
|
+
return [...state.subagents];
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
toSummary(): object {
|
|
249
|
+
return {
|
|
250
|
+
startedAt: state.startedAt,
|
|
251
|
+
endedAt: new Date().toISOString(),
|
|
252
|
+
totals: {
|
|
253
|
+
inputTokens: state.totalInputTokens,
|
|
254
|
+
outputTokens: state.totalOutputTokens,
|
|
255
|
+
cacheReadTokens: state.totalCacheReadTokens,
|
|
256
|
+
cacheWriteTokens: state.totalCacheWriteTokens,
|
|
257
|
+
cost: Number(state.totalCost.toFixed(6)),
|
|
258
|
+
turns: state.totalTurns,
|
|
259
|
+
},
|
|
260
|
+
subagents: [...state.subagents],
|
|
261
|
+
models: this.getPerModelUsage(),
|
|
262
|
+
};
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
reset(): void {
|
|
266
|
+
const next = createInitialState();
|
|
267
|
+
state.startedAt = next.startedAt;
|
|
268
|
+
state.totalInputTokens = 0;
|
|
269
|
+
state.totalOutputTokens = 0;
|
|
270
|
+
state.totalCacheReadTokens = 0;
|
|
271
|
+
state.totalCacheWriteTokens = 0;
|
|
272
|
+
state.totalCost = 0;
|
|
273
|
+
state.totalTurns = 0;
|
|
274
|
+
state.subagentInputTokens = 0;
|
|
275
|
+
state.subagentOutputTokens = 0;
|
|
276
|
+
state.subagentCost = 0;
|
|
277
|
+
state.models.clear();
|
|
278
|
+
state.subagents = [];
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function loadUsageSummary(sessionId: string): Record<string, unknown> | null {
|
|
284
|
+
const usageDir = join(resolveAgentDir(), "extensions", "pp", "usage");
|
|
285
|
+
const filePath = join(usageDir, `${sessionId}.json`);
|
|
286
|
+
if (!existsSync(filePath)) return null;
|
|
287
|
+
try {
|
|
288
|
+
return JSON.parse(readFileSync(filePath, "utf-8")) as Record<string, unknown>;
|
|
289
|
+
} catch {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function dumpUsageSummary(tracker: UsageTracker, sessionId: string): void {
|
|
295
|
+
const usageDir = join(resolveAgentDir(), "extensions", "pp", "usage");
|
|
296
|
+
mkdirSync(usageDir, { recursive: true });
|
|
297
|
+
|
|
298
|
+
const summary = tracker.toSummary() as Record<string, unknown>;
|
|
299
|
+
const payload = {
|
|
300
|
+
sessionId,
|
|
301
|
+
...summary,
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const outPath = join(usageDir, `${sessionId}.json`);
|
|
305
|
+
writeFileSync(outPath, JSON.stringify(payload, null, 2) + "\n", "utf-8");
|
|
306
|
+
}
|