@marshal/pi-turn-stats 0.1.6 → 0.1.8
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 +11 -8
- package/extensions/turn-stats.ts +67 -38
- package/package.json +6 -3
- package/turn-stats_en.mp4 +0 -0
package/README.md
CHANGED
|
@@ -7,10 +7,13 @@ A pi extension that automatically tracks per-exchange duration, token usage (inp
|
|
|
7
7
|
- **Stats card in conversation stream** — After each reply, a stats card is appended to the stream (does not enter LLM context).
|
|
8
8
|
- **Real-time status bar** — The bottom status bar shows the last exchange's duration, throughput, token count, and cost.
|
|
9
9
|
- **`/turnstats` command** — Appends a session cumulative stats card (total exchanges, total LLM calls, total tokens, total cost).
|
|
10
|
+
- **Auto i18n** — UI labels automatically switch between Chinese and English based on your system locale (`LANG` / `LC_ALL` / `Intl`).
|
|
10
11
|
|
|
11
12
|
## Demo
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
**English locale (`LANG=en`)**
|
|
15
|
+
|
|
16
|
+

|
|
14
17
|
|
|
15
18
|
## Screenshots
|
|
16
19
|
|
|
@@ -28,22 +31,22 @@ A pi extension that automatically tracks per-exchange duration, token usage (inp
|
|
|
28
31
|
|
|
29
32
|
```bash
|
|
30
33
|
# Install directly with pi
|
|
31
|
-
pi install
|
|
34
|
+
pi install npm:@marshal/pi-turn-stats
|
|
32
35
|
```
|
|
33
36
|
|
|
34
37
|
Or standard npm install:
|
|
35
38
|
|
|
36
39
|
```bash
|
|
37
|
-
npm install @marshal/pi-turn-stats
|
|
40
|
+
npm install -g @marshal/pi-turn-stats
|
|
38
41
|
```
|
|
39
42
|
|
|
40
|
-
> Note: `pi install
|
|
43
|
+
> Note: `pi install npm:...` registers the extension globally. Plain `npm install -g` only downloads the package without registering it as a pi extension.
|
|
41
44
|
|
|
42
45
|
**Alternative**: Install from GitHub (for source access or custom modifications).
|
|
43
46
|
|
|
44
47
|
```bash
|
|
45
|
-
# Install from GitHub repo
|
|
46
|
-
pi install
|
|
48
|
+
# Install from GitHub repo (SSH)
|
|
49
|
+
pi install git:git@github.com:MarshalW/pi-turn-stats
|
|
47
50
|
```
|
|
48
51
|
|
|
49
52
|
> Users in China are encouraged to use the npm method, as GitHub access may be unreliable.
|
|
@@ -66,6 +69,6 @@ pi install ./ # local install for testing
|
|
|
66
69
|
git tag vX.Y.Z && git push origin main --tags
|
|
67
70
|
# Publish to npm: npm version patch && npm publish --access public
|
|
68
71
|
# Consumer install:
|
|
69
|
-
# npm (recommended): pi install
|
|
70
|
-
# git (fallback): pi install
|
|
72
|
+
# npm (recommended): pi install npm:@marshal/pi-turn-stats
|
|
73
|
+
# git (fallback): pi install git:git@github.com:MarshalW/pi-turn-stats@vX.Y.Z
|
|
71
74
|
```
|
package/extensions/turn-stats.ts
CHANGED
|
@@ -164,6 +164,25 @@ function fmtCost(c: number): string {
|
|
|
164
164
|
return `$${c.toFixed(4)}`;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
/**
|
|
168
|
+
* After session replacement (e.g. /new) or reload, pi invalidates the old
|
|
169
|
+
* extension runner, but a run's `finally` can still deliver agent_settled to
|
|
170
|
+
* it; any ctx/`pi` access on the stale runner throws. Treat those as
|
|
171
|
+
* "no UI / no session" and skip side effects instead of erroring.
|
|
172
|
+
*/
|
|
173
|
+
function isStaleCtxError(err: unknown): boolean {
|
|
174
|
+
return err instanceof Error && err.message.includes("extension ctx is stale");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Run a side effect against ctx/`pi`; silently skip when the ctx is stale. */
|
|
178
|
+
function withStaleGuard(fn: () => void): void {
|
|
179
|
+
try {
|
|
180
|
+
fn();
|
|
181
|
+
} catch (err) {
|
|
182
|
+
if (!isStaleCtxError(err)) throw err;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
167
186
|
export default function (pi: ExtensionAPI) {
|
|
168
187
|
// ---- session cumulative stats ----
|
|
169
188
|
const sessionTotals = {
|
|
@@ -248,8 +267,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
248
267
|
|
|
249
268
|
// ===== Session start: init status bar =====
|
|
250
269
|
pi.on("session_start", (_event, ctx) => {
|
|
251
|
-
|
|
252
|
-
|
|
270
|
+
withStaleGuard(() => {
|
|
271
|
+
if (!ctx.hasUI) return;
|
|
272
|
+
ctx.ui.setStatus("turn-stats", ctx.ui.theme.fg("dim", t("statusWaiting")));
|
|
273
|
+
});
|
|
253
274
|
});
|
|
254
275
|
|
|
255
276
|
// ===== User submits: start timer =====
|
|
@@ -258,10 +279,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
258
279
|
startTime = Date.now();
|
|
259
280
|
turnCount = 0;
|
|
260
281
|
accum = emptyAccum();
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
282
|
+
withStaleGuard(() => {
|
|
283
|
+
lastModel = ctx.model?.id ?? "unknown";
|
|
284
|
+
if (ctx.hasUI) {
|
|
285
|
+
ctx.ui.setStatus("turn-stats", ctx.ui.theme.fg("dim", t("statusRunning")));
|
|
286
|
+
}
|
|
287
|
+
});
|
|
265
288
|
});
|
|
266
289
|
|
|
267
290
|
// ===== Each LLM turn ends: accumulate usage =====
|
|
@@ -297,27 +320,31 @@ export default function (pi: ExtensionAPI) {
|
|
|
297
320
|
sessionTotals.durationMs += durMs;
|
|
298
321
|
|
|
299
322
|
if (SHOW_CARD && turnCount > 0) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
323
|
+
withStaleGuard(() => {
|
|
324
|
+
pi.appendEntry<TurnStatsData>(ENTRY_TYPE, {
|
|
325
|
+
kind: "exchange",
|
|
326
|
+
startTime,
|
|
327
|
+
endTime,
|
|
328
|
+
turns: turnCount,
|
|
329
|
+
exchanges: 1,
|
|
330
|
+
...accum,
|
|
331
|
+
tokensPerSec: genTps,
|
|
332
|
+
model: lastModel,
|
|
333
|
+
});
|
|
309
334
|
});
|
|
310
335
|
}
|
|
311
336
|
|
|
312
|
-
|
|
313
|
-
ctx.
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
337
|
+
withStaleGuard(() => {
|
|
338
|
+
if (ctx.hasUI) {
|
|
339
|
+
ctx.ui.setStatus(
|
|
340
|
+
"turn-stats",
|
|
341
|
+
ctx.ui.theme.fg(
|
|
342
|
+
"dim",
|
|
343
|
+
t("statusDone", fmtDuration(durMs), fmtThroughput(genTps), fmtTokens(accum.totalTokens), fmtCost(accum.cost)),
|
|
344
|
+
),
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
321
348
|
});
|
|
322
349
|
|
|
323
350
|
// ===== /turnstats: append session cumulative stats card =====
|
|
@@ -326,20 +353,22 @@ export default function (pi: ExtensionAPI) {
|
|
|
326
353
|
handler: async () => {
|
|
327
354
|
const sessDurMs = sessionTotals.durationMs;
|
|
328
355
|
const sessGenTps = calcOutputPerSec(sessionTotals.output, sessDurMs);
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
356
|
+
withStaleGuard(() => {
|
|
357
|
+
pi.appendEntry<TurnStatsData>(ENTRY_TYPE, {
|
|
358
|
+
kind: "session",
|
|
359
|
+
startTime: sessDurMs > 0 ? Date.now() - sessDurMs : Date.now(),
|
|
360
|
+
endTime: Date.now(),
|
|
361
|
+
turns: sessionTotals.exchanges,
|
|
362
|
+
exchanges: sessionTotals.exchanges,
|
|
363
|
+
input: sessionTotals.input,
|
|
364
|
+
output: sessionTotals.output,
|
|
365
|
+
cacheRead: sessionTotals.cacheRead,
|
|
366
|
+
cacheWrite: sessionTotals.cacheWrite,
|
|
367
|
+
totalTokens: sessionTotals.totalTokens,
|
|
368
|
+
cost: sessionTotals.cost,
|
|
369
|
+
tokensPerSec: sessGenTps,
|
|
370
|
+
model: t("sessionModel"),
|
|
371
|
+
});
|
|
343
372
|
});
|
|
344
373
|
},
|
|
345
374
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marshal/pi-turn-stats",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "pi extension: per-exchange duration, token & cost stats for conversations (stream card + status bar + /turnstats command)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -12,11 +12,14 @@
|
|
|
12
12
|
"README.md",
|
|
13
13
|
"turn-stats.jpg",
|
|
14
14
|
"turn-stats.gif",
|
|
15
|
-
"turn-stats_en.png"
|
|
15
|
+
"turn-stats_en.png",
|
|
16
|
+
"turn-stats_en.mp4"
|
|
16
17
|
],
|
|
17
18
|
"pi": {
|
|
18
19
|
"extensions": [
|
|
19
20
|
"./extensions/turn-stats.ts"
|
|
20
|
-
]
|
|
21
|
+
],
|
|
22
|
+
"video": "https://github.com/MarshalW/pi-turn-stats/raw/main/turn-stats_en.mp4",
|
|
23
|
+
"image": "https://github.com/MarshalW/pi-turn-stats/raw/main/turn-stats_en.png"
|
|
21
24
|
}
|
|
22
25
|
}
|
|
Binary file
|