@d3ara1n/pi-editor-shell 0.2.1 → 0.3.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/package.json +1 -1
- package/src/index.ts +67 -16
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -53,14 +53,20 @@ const ICON = {
|
|
|
53
53
|
thinking: "\uf400", // oct-light-bulb
|
|
54
54
|
context: "\uf49b", // oct-cache
|
|
55
55
|
cache: "\u26a1", // ⚡ oct-zap
|
|
56
|
+
hitRate: "\uf140", // fa-bullseye(靶心,缓存命中率)
|
|
56
57
|
folder: "\uf07c", // fa-folder
|
|
57
58
|
} as const;
|
|
58
59
|
|
|
59
60
|
/** Minimal inline types to read cache-read totals without importing the
|
|
60
61
|
* full pi-ai message union tree. */
|
|
62
|
+
interface UsageSnap {
|
|
63
|
+
input?: number;
|
|
64
|
+
cacheRead?: number;
|
|
65
|
+
cacheWrite?: number;
|
|
66
|
+
}
|
|
61
67
|
interface MsgSnap {
|
|
62
68
|
role: string;
|
|
63
|
-
usage?:
|
|
69
|
+
usage?: UsageSnap;
|
|
64
70
|
}
|
|
65
71
|
interface EntrySnap {
|
|
66
72
|
type: string;
|
|
@@ -79,6 +85,28 @@ function sumCacheRead(ctx: { sessionManager: { getEntries(): unknown[] } }): num
|
|
|
79
85
|
return total;
|
|
80
86
|
}
|
|
81
87
|
|
|
88
|
+
/** Usage of the most recent assistant message — drives the per-turn
|
|
89
|
+
* cacheRead and the hit rate, matching pi's footer (last entry wins). */
|
|
90
|
+
function latestAssistantUsage(ctx: { sessionManager: { getEntries(): unknown[] } }): UsageSnap | undefined {
|
|
91
|
+
let latest: UsageSnap | undefined;
|
|
92
|
+
for (const entry of ctx.sessionManager.getEntries()) {
|
|
93
|
+
const e = entry as EntrySnap;
|
|
94
|
+
if (e.type !== "message" || e.message?.role !== "assistant" || !e.message.usage) continue;
|
|
95
|
+
latest = e.message.usage;
|
|
96
|
+
}
|
|
97
|
+
return latest;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Cache hit rate for a single turn: cacheRead / (input + cacheRead +
|
|
101
|
+
* cacheWrite) × 100 — same formula pi's footer uses for "CHxx%".
|
|
102
|
+
* Returns undefined when there's no usage or no prompt tokens. */
|
|
103
|
+
function cacheHitRate(u: UsageSnap | undefined): number | undefined {
|
|
104
|
+
if (!u) return undefined;
|
|
105
|
+
const prompt = (u.input ?? 0) + (u.cacheRead ?? 0) + (u.cacheWrite ?? 0);
|
|
106
|
+
if (prompt <= 0) return undefined;
|
|
107
|
+
return ((u.cacheRead ?? 0) / prompt) * 100;
|
|
108
|
+
}
|
|
109
|
+
|
|
82
110
|
/** Format a token count for display: 14000000 → "14.0M", 132000 → "132.0k". */
|
|
83
111
|
function formatTokens(n: number): string {
|
|
84
112
|
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
|
|
@@ -170,9 +198,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
170
198
|
let footerSnap: FooterSnap | undefined;
|
|
171
199
|
// CWD cached from session_start — used by turn_end to refresh git dirty.
|
|
172
200
|
let _cwd = "";
|
|
173
|
-
// cacheRead total, refreshed at session_start +
|
|
174
|
-
// provider reads
|
|
201
|
+
// cacheRead total + latest-turn usage, refreshed at session_start +
|
|
202
|
+
// agent_end. The render provider reads these instead of re-scanning
|
|
203
|
+
// entries every frame.
|
|
175
204
|
let _cacheTotal = 0;
|
|
205
|
+
let _latestUsage: UsageSnap | undefined;
|
|
176
206
|
|
|
177
207
|
// ── Phase-aware spinner + lifecycle ────────────────────────────
|
|
178
208
|
// Each event asks the editor for a phase; CardEditor.setSpinner is itself
|
|
@@ -189,9 +219,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
189
219
|
});
|
|
190
220
|
pi.on("tool_execution_start", () => editor?.setSpinner("exec"));
|
|
191
221
|
pi.on("agent_end", (_event, ctx) => {
|
|
192
|
-
// cacheRead totals are stable once a turn finishes —
|
|
193
|
-
// instead of on every render frame.
|
|
222
|
+
// cacheRead totals + latest usage are stable once a turn finishes —
|
|
223
|
+
// recompute here instead of on every render frame.
|
|
194
224
|
_cacheTotal = sumCacheRead(ctx);
|
|
225
|
+
_latestUsage = latestAssistantUsage(ctx);
|
|
195
226
|
editor?.setSpinner(null);
|
|
196
227
|
});
|
|
197
228
|
pi.on("session_shutdown", () => {
|
|
@@ -211,6 +242,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
211
242
|
_cwd = ctx.cwd;
|
|
212
243
|
config = loadEditorShellConfig(ctx.cwd);
|
|
213
244
|
_cacheTotal = sumCacheRead(ctx);
|
|
245
|
+
_latestUsage = latestAssistantUsage(ctx);
|
|
214
246
|
refreshGitDirty(ctx.cwd, () => editor?.requestRender());
|
|
215
247
|
|
|
216
248
|
// Fresh segments on every render — reads live ctx state, so thinking /
|
|
@@ -241,15 +273,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
241
273
|
const ctxWindow = usage?.contextWindow ?? ctx.model?.contextWindow;
|
|
242
274
|
const ctxText =
|
|
243
275
|
pct != null && ctxWindow
|
|
244
|
-
? `${
|
|
276
|
+
? `${pct.toFixed(1)}%/${(ctxWindow / 1000).toFixed(0)}k`
|
|
245
277
|
: "?/??k";
|
|
246
278
|
|
|
247
|
-
// Cache-read
|
|
248
|
-
// pi's
|
|
249
|
-
|
|
279
|
+
// Cache-read tokens — per-turn figure first, session total in parens,
|
|
280
|
+
// then hit rate (pi's "CHxx%" formula). All refreshed at agent_end and
|
|
281
|
+
// read from cache off the hot path.
|
|
282
|
+
const cacheReadNow = _latestUsage?.cacheRead ?? 0;
|
|
283
|
+
const hitRate = cacheHitRate(_latestUsage);
|
|
250
284
|
const cachePart =
|
|
251
|
-
|
|
252
|
-
? `${theme.fg("dim", " · ")}${theme.fg("warning", `${ICON.cache} ${formatTokens(
|
|
285
|
+
_cacheTotal > 0
|
|
286
|
+
? `${theme.fg("dim", " · ")}${theme.fg("warning", `${ICON.cache} ${formatTokens(cacheReadNow)} (${formatTokens(_cacheTotal)})${hitRate != null ? ` ${ICON.hitRate} ${hitRate.toFixed(1)}%` : ""}`)}`
|
|
253
287
|
: "";
|
|
254
288
|
|
|
255
289
|
// Git branch + dirty state — pi's format: ~/Projects (main).
|
|
@@ -333,7 +367,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
333
367
|
} else {
|
|
334
368
|
const pinned = new Set(config.pinnedStatus);
|
|
335
369
|
for (const [key, text] of entries.sort(([a], [b]) => a.localeCompare(b))) {
|
|
336
|
-
|
|
370
|
+
// The pin marker sits after status text, whose embedded reset
|
|
371
|
+
// would wash it to default white — re-wrap it in dim so it stays
|
|
372
|
+
// consistent with the surrounding text. (status text itself
|
|
373
|
+
// keeps its original color by design.)
|
|
374
|
+
const mark = pinned.has(key) ? ctx.ui.theme.fg("dim", " ← pinned") : "";
|
|
337
375
|
lines.push(` ${key}: ${text}${mark}`);
|
|
338
376
|
}
|
|
339
377
|
}
|
|
@@ -344,7 +382,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
344
382
|
lines.push("");
|
|
345
383
|
lines.push("[cache totals]");
|
|
346
384
|
const tokens = sumCacheRead(ctx);
|
|
347
|
-
lines.push(` cacheRead: ${tokens > 0 ? formatTokens(tokens) : "0"}`);
|
|
385
|
+
lines.push(` cacheRead (session): ${tokens > 0 ? formatTokens(tokens) : "0"}`);
|
|
386
|
+
const latest = latestAssistantUsage(ctx);
|
|
387
|
+
const now = latest?.cacheRead ?? 0;
|
|
388
|
+
lines.push(` cacheRead (this turn): ${formatTokens(now)}`);
|
|
389
|
+
const hr = cacheHitRate(latest);
|
|
390
|
+
lines.push(` hit rate: ${hr != null ? `${hr.toFixed(1)}%` : "n/a"}`);
|
|
348
391
|
|
|
349
392
|
lines.push("");
|
|
350
393
|
lines.push(`[context] cwd: ${ctx.cwd}`);
|
|
@@ -355,9 +398,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
355
398
|
lines.push(` git dirty: ${dirty || "clean"}`);
|
|
356
399
|
}
|
|
357
400
|
const m = ctx.model;
|
|
358
|
-
lines.push(` model: ${m ? `${m.provider}/${m.id}` : "none"}`);
|
|
359
|
-
|
|
360
|
-
|
|
401
|
+
lines.push(` model: ${m ? `${m.provider}/${m.id}:${pi.getThinkingLevel()}` : "none"}`);
|
|
402
|
+
|
|
403
|
+
// Wrap each line in dim explicitly. notify adds its own outer dim
|
|
404
|
+
// layer, but extension status text carries its own color codes that
|
|
405
|
+
// reset the foreground mid-message. Per-line wrapping re-asserts dim
|
|
406
|
+
// at the start of every line, so a status row's reset can't bleed past
|
|
407
|
+
// it: status stays in its original color, everything else reads dim.
|
|
408
|
+
ctx.ui.notify(
|
|
409
|
+
lines.map((l) => ctx.ui.theme.fg("dim", l)).join("\n"),
|
|
410
|
+
"info",
|
|
411
|
+
);
|
|
361
412
|
},
|
|
362
413
|
});
|
|
363
414
|
}
|