pi-metrics 0.2.2 → 0.2.3
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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/locales/index.json +0 -28
- package/package.json +7 -4
- package/src/tps.ts +0 -105
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Session metrics for the [Pi coding agent](https://github.com/earendil-works/pi):
|
|
|
11
11
|
- When the agent fully settles (`agent_settled` — including auto-retries, compaction continuations, or Esc interruption), a final line shows the **total elapsed time from message send to stop** (`⏱ Total elapsed 18.9s`).
|
|
12
12
|
- After each LLM turn, a notification reports TPS, TTFT, token counts, generation time, stalls, and blended cost when available.
|
|
13
13
|
- Telemetry is persisted as `tps` custom session entries and restored after session resume or `/tree` navigation.
|
|
14
|
-
-
|
|
14
|
+
- Metrics are exposed through session entries and notifications; this package does not register export slash commands.
|
|
15
15
|
|
|
16
16
|
## Migration from pi-tps
|
|
17
17
|
|
package/README.zh-CN.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
- AI 完全停止时(`agent_settled`,覆盖自动重试、compaction 续跑以及 Esc 中断)追加一行**从发出消息到停止的总耗时**(`⏱ 总耗时 18.9s`)。
|
|
12
12
|
- 每轮 LLM 调用结束后显示 TPS、TTFT、token 数、生成耗时、stall 和可用的综合成本。
|
|
13
13
|
- Telemetry 以 `tps` custom session entry 持久化,并在恢复 session 或 `/tree` 后恢复显示。
|
|
14
|
-
-
|
|
14
|
+
- Metrics 通过 session entry 和通知提供;本包不再注册导出类斜杠命令。
|
|
15
15
|
|
|
16
16
|
## 从 pi-tps 迁移
|
|
17
17
|
|
package/locales/index.json
CHANGED
|
@@ -38,33 +38,5 @@
|
|
|
38
38
|
"tpsRate": {
|
|
39
39
|
"zh-CN": "${value}/M",
|
|
40
40
|
"en-US": "${value}/M"
|
|
41
|
-
},
|
|
42
|
-
"tpsExportDescription": {
|
|
43
|
-
"zh-CN": "导出 TPS telemetry JSONL(--full 导出整个 session)",
|
|
44
|
-
"en-US": "Export TPS telemetry as JSONL (--full for the full session)"
|
|
45
|
-
},
|
|
46
|
-
"sessionExportDescription": {
|
|
47
|
-
"zh-CN": "导出 session JSONL(--full 导出所有分支)",
|
|
48
|
-
"en-US": "Export session JSONL (--full for all branches)"
|
|
49
|
-
},
|
|
50
|
-
"tpsFullFlag": {
|
|
51
|
-
"zh-CN": "--full(导出所有分支)",
|
|
52
|
-
"en-US": "--full (all branches)"
|
|
53
|
-
},
|
|
54
|
-
"tpsNoEntries": {
|
|
55
|
-
"zh-CN": "在 {scope} 中没有匹配的条目",
|
|
56
|
-
"en-US": "No matching entries found in {scope}"
|
|
57
|
-
},
|
|
58
|
-
"tpsAllEntries": {
|
|
59
|
-
"zh-CN": "全部条目",
|
|
60
|
-
"en-US": "all entries"
|
|
61
|
-
},
|
|
62
|
-
"tpsCurrentBranch": {
|
|
63
|
-
"zh-CN": "当前分支",
|
|
64
|
-
"en-US": "current branch"
|
|
65
|
-
},
|
|
66
|
-
"tpsExported": {
|
|
67
|
-
"zh-CN": "已导出 {count} 个条目 → {filepath}",
|
|
68
|
-
"en-US": "Exported {count} entries → {filepath}"
|
|
69
41
|
}
|
|
70
42
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-metrics",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Pi metrics extension: elapsed-time HUD and resilient TPS telemetry",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
},
|
|
23
23
|
"pi": {
|
|
24
24
|
"extensions": [
|
|
25
|
-
"./index.ts"
|
|
25
|
+
"./index.ts",
|
|
26
|
+
"../pi-extensions-i18n/index.ts"
|
|
26
27
|
]
|
|
27
28
|
},
|
|
28
29
|
"engines": {
|
|
@@ -54,8 +55,10 @@
|
|
|
54
55
|
"peerDependencies": {
|
|
55
56
|
"@earendil-works/pi-ai": ">=0.80.0 <0.81.0",
|
|
56
57
|
"@earendil-works/pi-coding-agent": ">=0.80.0 <0.81.0",
|
|
57
|
-
"@earendil-works/pi-tui": ">=0.80.0 <0.81.0"
|
|
58
|
-
|
|
58
|
+
"@earendil-works/pi-tui": ">=0.80.0 <0.81.0"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"pi-extensions-i18n": "^0.3.1"
|
|
59
62
|
},
|
|
60
63
|
"devDependencies": {
|
|
61
64
|
"@earendil-works/pi-ai": "0.80.10",
|
package/src/tps.ts
CHANGED
|
@@ -5,15 +5,10 @@
|
|
|
5
5
|
* elapsed-time HUD and generation telemetry share one lifecycle.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { execFileSync } from "node:child_process";
|
|
9
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
10
|
-
import { homedir } from "node:os";
|
|
11
|
-
import { join } from "node:path";
|
|
12
8
|
import { performance } from "node:perf_hooks";
|
|
13
9
|
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
14
10
|
import type {
|
|
15
11
|
ExtensionAPI,
|
|
16
|
-
ExtensionCommandContext,
|
|
17
12
|
ExtensionContext,
|
|
18
13
|
} from "@earendil-works/pi-coding-agent";
|
|
19
14
|
import { i18n } from "./i18n.ts";
|
|
@@ -345,54 +340,6 @@ function restoreTPSNotification(
|
|
|
345
340
|
}
|
|
346
341
|
}
|
|
347
342
|
|
|
348
|
-
function openDirectory(directory: string): void {
|
|
349
|
-
try {
|
|
350
|
-
execFileSync(process.platform === "darwin" ? "open" : "xdg-open", [directory], {
|
|
351
|
-
stdio: "ignore",
|
|
352
|
-
});
|
|
353
|
-
} catch {
|
|
354
|
-
// Opening an export directory is optional; the path is still reported.
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
function exportEntries(
|
|
359
|
-
entries: SessionEntryLike[],
|
|
360
|
-
full: boolean,
|
|
361
|
-
filterType: string | null,
|
|
362
|
-
directoryName: "pi-telemetry" | "pi-sessions",
|
|
363
|
-
prefix: "pi-telemetry" | "pi-session",
|
|
364
|
-
sessionId: string,
|
|
365
|
-
): { filepath: string; count: number } {
|
|
366
|
-
const isStructural = (entry: SessionEntryLike) =>
|
|
367
|
-
entry.type === "model_change" || entry.type === "branch_summary";
|
|
368
|
-
const exported = entries.filter(
|
|
369
|
-
(entry) => isStructural(entry) ||
|
|
370
|
-
(entry.type === "custom" && (!filterType || entry.customType === filterType)),
|
|
371
|
-
);
|
|
372
|
-
const byId = new Map(entries.map((entry) => [entry.id, entry]));
|
|
373
|
-
const exportedIds = new Set(exported.map((entry) => entry.id));
|
|
374
|
-
const rechainParentId = (entry: SessionEntryLike): string | null => {
|
|
375
|
-
let parentId = entry.parentId ?? null;
|
|
376
|
-
while (parentId) {
|
|
377
|
-
if (exportedIds.has(parentId)) return parentId;
|
|
378
|
-
parentId = byId.get(parentId)?.parentId ?? null;
|
|
379
|
-
}
|
|
380
|
-
return null;
|
|
381
|
-
};
|
|
382
|
-
const rechained = exported.map((entry) => ({ ...entry, parentId: rechainParentId(entry) }));
|
|
383
|
-
const cacheBase = process.env.XDG_CACHE_HOME || join(homedir(), ".cache");
|
|
384
|
-
const directory = join(cacheBase, directoryName);
|
|
385
|
-
mkdirSync(directory, { recursive: true });
|
|
386
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
387
|
-
const scope = [full ? "full" : "branch", filterType?.replace(/[^a-zA-Z0-9_-]/g, "-")]
|
|
388
|
-
.filter(Boolean)
|
|
389
|
-
.join("-");
|
|
390
|
-
const filepath = join(directory, `${prefix}-${scope}-${sessionId.slice(0, 8)}-${timestamp}.jsonl`);
|
|
391
|
-
writeFileSync(filepath, `${rechained.map((entry) => JSON.stringify(entry)).join("\n")}\n`);
|
|
392
|
-
openDirectory(directory);
|
|
393
|
-
return { filepath, count: exported.length };
|
|
394
|
-
}
|
|
395
|
-
|
|
396
343
|
export default function tpsExtension(pi: ExtensionAPI): void {
|
|
397
344
|
let currentTiming: TurnTiming | null = null;
|
|
398
345
|
let pendingNeuralwattBilledCost: { turnIndex: number; costUsd: number } | null = null;
|
|
@@ -402,7 +349,6 @@ export default function tpsExtension(pi: ExtensionAPI): void {
|
|
|
402
349
|
billedApplied: boolean;
|
|
403
350
|
ctx: ExtensionContext;
|
|
404
351
|
} | null = null;
|
|
405
|
-
let cachedEntries: Array<{ type?: string; customType?: string }> = [];
|
|
406
352
|
const tpsCaps = new Map<string, number>();
|
|
407
353
|
const restoreTimers = new Set<ReturnType<typeof setTimeout>>();
|
|
408
354
|
let unsubscribeNeuralwatt: (() => void) | undefined;
|
|
@@ -411,7 +357,6 @@ export default function tpsExtension(pi: ExtensionAPI): void {
|
|
|
411
357
|
currentTiming = null;
|
|
412
358
|
pendingNeuralwattBilledCost = null;
|
|
413
359
|
lastCommittedTurn = null;
|
|
414
|
-
cachedEntries = [];
|
|
415
360
|
for (const timer of restoreTimers) clearTimeout(timer);
|
|
416
361
|
restoreTimers.clear();
|
|
417
362
|
};
|
|
@@ -447,7 +392,6 @@ export default function tpsExtension(pi: ExtensionAPI): void {
|
|
|
447
392
|
committed.telemetry = corrected;
|
|
448
393
|
pi.appendEntry("tps", corrected);
|
|
449
394
|
pi.events?.emit("tps:telemetry", corrected);
|
|
450
|
-
cachedEntries.push({ type: "custom", customType: "tps" });
|
|
451
395
|
if (committed.ctx.hasUI) committed.ctx.ui.notify(composeDisplayString(corrected), "info");
|
|
452
396
|
});
|
|
453
397
|
|
|
@@ -459,14 +403,12 @@ export default function tpsExtension(pi: ExtensionAPI): void {
|
|
|
459
403
|
|
|
460
404
|
pi.on("session_start", (_event, ctx) => {
|
|
461
405
|
clearState();
|
|
462
|
-
cachedEntries = ctx.sessionManager.getEntries();
|
|
463
406
|
restoreTPSNotification(ctx, scheduleRestore);
|
|
464
407
|
});
|
|
465
408
|
|
|
466
409
|
pi.on("session_tree", (_event: SessionTreeEvent, ctx) => {
|
|
467
410
|
pendingNeuralwattBilledCost = null;
|
|
468
411
|
lastCommittedTurn = null;
|
|
469
|
-
cachedEntries = ctx.sessionManager.getEntries();
|
|
470
412
|
restoreTPSNotification(ctx, scheduleRestore);
|
|
471
413
|
});
|
|
472
414
|
|
|
@@ -572,53 +514,6 @@ export default function tpsExtension(pi: ExtensionAPI): void {
|
|
|
572
514
|
pi.appendEntry("tps", telemetry);
|
|
573
515
|
pi.events?.emit("tps:telemetry", telemetry);
|
|
574
516
|
if (ctx.hasUI) ctx.ui.notify(composeDisplayString(telemetry), "info");
|
|
575
|
-
cachedEntries.push({ type: "custom", customType: "tps" });
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
pi.registerCommand("tps-export", {
|
|
579
|
-
description: i18n.t("tpsExportDescription"),
|
|
580
|
-
getArgumentCompletions: (argumentPrefix: string) => {
|
|
581
|
-
if ("--full".startsWith(argumentPrefix)) return [{ value: "--full", label: i18n.t("tpsFullFlag") }];
|
|
582
|
-
const types = new Set<string>();
|
|
583
|
-
for (const entry of cachedEntries) {
|
|
584
|
-
if (entry.type === "custom" && entry.customType) types.add(entry.customType);
|
|
585
|
-
}
|
|
586
|
-
return [...types]
|
|
587
|
-
.filter((customType) => customType.startsWith(argumentPrefix))
|
|
588
|
-
.map((customType) => ({ value: customType, label: customType }));
|
|
589
|
-
},
|
|
590
|
-
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
591
|
-
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
592
|
-
const full = tokens.includes("--full");
|
|
593
|
-
const filterType = tokens.filter((token) => token !== "--full").join(" ") || null;
|
|
594
|
-
const entries = (full ? ctx.sessionManager.getEntries() : ctx.sessionManager.getBranch()) as SessionEntryLike[];
|
|
595
|
-
const isStructural = (entry: SessionEntryLike) => entry.type === "model_change" || entry.type === "branch_summary";
|
|
596
|
-
const matching = entries.filter((entry) => isStructural(entry) ||
|
|
597
|
-
(entry.type === "custom" && (!filterType || entry.customType === filterType)));
|
|
598
|
-
if (matching.length === 0) {
|
|
599
|
-
ctx.ui.notify(i18n.t("tpsNoEntries", { scope: full ? i18n.t("tpsAllEntries") : i18n.t("tpsCurrentBranch") }), "warning");
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
const sessionId = ctx.sessionManager.getSessionId?.() ?? "unknown";
|
|
603
|
-
const result = exportEntries(entries, full, filterType, "pi-telemetry", "pi-telemetry", sessionId);
|
|
604
|
-
ctx.ui.notify(i18n.t("tpsExported", { count: result.count, filepath: result.filepath }), "info");
|
|
605
|
-
},
|
|
606
517
|
});
|
|
607
518
|
|
|
608
|
-
pi.registerCommand("session-export", {
|
|
609
|
-
description: i18n.t("sessionExportDescription"),
|
|
610
|
-
getArgumentCompletions: (argumentPrefix: string) =>
|
|
611
|
-
"--full".startsWith(argumentPrefix) ? [{ value: "--full", label: i18n.t("tpsFullFlag") }] : [],
|
|
612
|
-
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
613
|
-
const full = args.trim().split(/\s+/).includes("--full");
|
|
614
|
-
const entries = (full ? ctx.sessionManager.getEntries() : ctx.sessionManager.getBranch()) as SessionEntryLike[];
|
|
615
|
-
if (entries.length === 0) {
|
|
616
|
-
ctx.ui.notify(i18n.t("tpsNoEntries", { scope: full ? i18n.t("tpsAllEntries") : i18n.t("tpsCurrentBranch") }), "warning");
|
|
617
|
-
return;
|
|
618
|
-
}
|
|
619
|
-
const sessionId = ctx.sessionManager.getSessionId?.() ?? "unknown";
|
|
620
|
-
const result = exportEntries(entries, full, null, "pi-sessions", "pi-session", sessionId);
|
|
621
|
-
ctx.ui.notify(i18n.t("tpsExported", { count: result.count, filepath: result.filepath }), "info");
|
|
622
|
-
},
|
|
623
|
-
});
|
|
624
519
|
}
|