@d3ara1n/pi-editor-shell 0.7.0 → 0.7.1

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 CHANGED
@@ -5,7 +5,7 @@ Replaces pi's default editor and status bar with a unified rounded-corner shell
5
5
  ## What shows up where
6
6
 
7
7
  - **Top border** — `  model ·  thinking-level ` (left) + pinned extension statuses (right, via `pinnedStatus` config)
8
- - **Bottom border** — `  ctx NN%/NNk|N.NM · ⚡ cacheRead (total)  hitRate% ` (left) + `  ~/Projects (main +2 ~1) ` (right, shows git branch + dirty state when in a repo)
8
+ - **Bottom border** — `  ctx NN%/NNk|N.NM · ⚡ cacheRead (total)  hitRate% ` (left) + `  ~/Projects (main +2 ~1) ` (right, shows git branch + dirty state when in a repo). Session hit rate via `/editor-shell:status`.
9
9
  - **Below shell** — Auto-wrapping extension status line (all `setStatus` entries not pinned to the top)
10
10
  - **Border color** follows pi's thinking-level / bash-mode indicator automatically.
11
11
 
@@ -93,5 +93,4 @@ Or add to `~/.pi/agent/settings.json`:
93
93
 
94
94
  ## Dependencies
95
95
 
96
- - [`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)
97
- - [`@earendil-works/pi-tui`](https://www.npmjs.com/package/@earendil-works/pi-tui)
96
+ None.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@d3ara1n/pi-editor-shell",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "description": "Replaces pi's default editor and status bar with a unified rounded-corner shell embedding status info in the border",
6
6
  "keywords": [
@@ -34,12 +34,12 @@ export type SpinnerPhase = "thinking" | "outputting" | "toolcall" | "exec";
34
34
  /** Spinner frames keyed by agent phase, chosen for semantic fit:
35
35
  * thinking — ●/○ pulse, doubled frames to halve the tempo
36
36
  * outputting — braille sand-pile (dots accumulate bottom-up, then clear)
37
- * toolcall — ▓▒░ breathing light (single char, fades in/out)
37
+ * toolcall — shade ramp breathing (░▒▓█▓▒, symmetric incl. full block)
38
38
  * exec — ◜◝◞◟ arc rotation (tool spinning, mechanical) */
39
39
  const SPINNERS: Record<SpinnerPhase, readonly string[]> = {
40
40
  thinking: ["●", "●", "○", "○"],
41
41
  outputting: ["⡀", "⣀", "⣄", "⣤", "⣦", "⣶", "⣷", "⣿"],
42
- toolcall: ["", "▒", "", "", "▒"],
42
+ toolcall: ["", "▒", "", "", "▓", "▒"],
43
43
  exec: ["◜", "◝", "◞", "◟"],
44
44
  };
45
45
 
package/src/index.ts CHANGED
@@ -86,8 +86,25 @@ interface EntrySnap {
86
86
  message?: MsgSnap;
87
87
  }
88
88
 
89
- /** Sum cache-read tokens across all assistant messages, matching pi's own
90
- * footer filtering (type === "message") and accumulation ("R14M"). */
89
+ /** Sum cache-related usage across all assistant messages on the session.
90
+ * Returns the same shape as UsageSnap so we can reuse cacheHitRate(). */
91
+ function sumSessionUsage(ctx: { sessionManager: { getEntries(): unknown[] } }): UsageSnap {
92
+ let input = 0;
93
+ let cacheRead = 0;
94
+ let cacheWrite = 0;
95
+ for (const entry of ctx.sessionManager.getEntries()) {
96
+ const e = entry as EntrySnap;
97
+ if (e.type !== "message" || e.message?.role !== "assistant" || !e.message.usage) continue;
98
+ input += e.message.usage.input ?? 0;
99
+ cacheRead += e.message.usage.cacheRead ?? 0;
100
+ cacheWrite += e.message.usage.cacheWrite ?? 0;
101
+ }
102
+ return { input, cacheRead, cacheWrite };
103
+ }
104
+
105
+ /** Sum cache-read tokens across all assistant messages — session total for
106
+ * the "(14.0M)" display in the border. Kept separate from sumSessionUsage
107
+ * to keep the hot path (agent_end) minimal. */
91
108
  function sumCacheRead(ctx: { sessionManager: { getEntries(): unknown[] } }): number {
92
109
  let total = 0;
93
110
  for (const entry of ctx.sessionManager.getEntries()) {
@@ -110,14 +127,31 @@ function latestAssistantUsage(ctx: { sessionManager: { getEntries(): unknown[] }
110
127
  return latest;
111
128
  }
112
129
 
113
- /** Cache hit rate for a single turn: cacheRead / (input + cacheRead +
114
- * cacheWrite) × 100 — same formula pi's footer uses for "CHxx%".
115
- * Returns undefined when there's no usage or no prompt tokens. */
130
+ /** Cache hit rate for a single turn.
131
+ *
132
+ * The `Usage.input` field has different semantics depending on the provider:
133
+ * - OpenAI/DeepSeek: `input` = fresh non-cached tokens (promptTokens −
134
+ * cacheRead − cacheWrite). Denominator = input + cacheRead + cacheWrite.
135
+ * - Anthropic: `input` = total input_tokens (already includes cacheRead +
136
+ * cacheWrite). Denominator = input alone (otherwise cache is counted twice).
137
+ *
138
+ * We detect the convention: if `input` can account for both cacheRead and
139
+ * cacheWrite (input >= cacheRead + cacheWrite), assume it is the total-input
140
+ * convention (Anthropic). Otherwise assume the fresh-only convention
141
+ * (OpenAI). Heuristic, not perfect, but correct for both conventions in
142
+ * practice; the real fix belongs in pi-ai where Usage is populated.
143
+ *
144
+ * Returns undefined when there is no usage or no prompt tokens. */
116
145
  function cacheHitRate(u: UsageSnap | undefined): number | undefined {
117
146
  if (!u) return undefined;
118
- const prompt = (u.input ?? 0) + (u.cacheRead ?? 0) + (u.cacheWrite ?? 0);
119
- if (prompt <= 0) return undefined;
120
- return ((u.cacheRead ?? 0) / prompt) * 100;
147
+ const nonCached = u.input ?? 0;
148
+ const cached = (u.cacheRead ?? 0) + (u.cacheWrite ?? 0);
149
+ // Anthropic convention: input already includes cacheRead + cacheWrite.
150
+ const totalPrompt = nonCached >= cached && cached > 0
151
+ ? nonCached
152
+ : nonCached + cached;
153
+ if (totalPrompt <= 0) return undefined;
154
+ return ((u.cacheRead ?? 0) / totalPrompt) * 100;
121
155
  }
122
156
 
123
157
  /** Format a token count for display: 14000000 → "14.0M", 132000 → "132.0k". */
@@ -300,6 +334,7 @@ export default function (pi: ExtensionAPI) {
300
334
  // Cache-read tokens — per-turn figure first, session total in parens,
301
335
  // then hit rate (pi's "CHxx%" formula). All refreshed at agent_end and
302
336
  // read from cache off the hot path.
337
+ // Session hit rate is available via /editor-shell:status.
303
338
  const cacheReadNow = _latestUsage?.cacheRead ?? 0;
304
339
  const hitRate = cacheHitRate(_latestUsage);
305
340
  const cachePart =
@@ -407,13 +442,17 @@ export default function (pi: ExtensionAPI) {
407
442
 
408
443
  lines.push("");
409
444
  lines.push("[cache totals]");
410
- const tokens = sumCacheRead(ctx);
411
- lines.push(` cacheRead (session): ${tokens > 0 ? formatTokens(tokens) : "0"}`);
445
+ const sess = sumSessionUsage(ctx);
446
+ lines.push(` session input: ${formatTokens(sess.input ?? 0)}`);
447
+ lines.push(` session cacheRead: ${formatTokens(sess.cacheRead ?? 0)}`);
448
+ lines.push(` session cacheWrite: ${formatTokens(sess.cacheWrite ?? 0)}`);
449
+ const sRate = cacheHitRate(sess);
450
+ lines.push(` session hit rate: ${sRate != null ? `${sRate.toFixed(1)}%` : "n/a"}`);
412
451
  const latest = latestAssistantUsage(ctx);
413
452
  const now = latest?.cacheRead ?? 0;
414
- lines.push(` cacheRead (this turn): ${formatTokens(now)}`);
453
+ lines.push(` this turn cacheRead: ${formatTokens(now)}`);
415
454
  const hr = cacheHitRate(latest);
416
- lines.push(` hit rate: ${hr != null ? `${hr.toFixed(1)}%` : "n/a"}`);
455
+ lines.push(` this turn hit rate: ${hr != null ? `${hr.toFixed(1)}%` : "n/a"}`);
417
456
 
418
457
  lines.push("");
419
458
  lines.push(`[context] cwd: ${ctx.cwd}`);