@tmustier/pi-usage-extension 0.9.4 → 0.9.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.5] - 2026-09-22
4
+
5
+ ### Changed
6
+ - Treat Pi 0.87 `context_edit` entries as intentional request-prefix changes so they do not count as cache misses.
7
+
8
+ ### Internal
9
+ - Cache format bumped to **v6**. The first `/usage` open after upgrading performs a one-off rebuild.
10
+
3
11
  ## [0.9.4] - 2026-07-22
4
12
 
5
13
  ### Changed
package/README.md CHANGED
@@ -97,7 +97,7 @@ The **Insights** view has two sections, facts first:
97
97
  |---|---|
98
98
  | Resuming after a break | ≥ 2% of the period's cost (and ≥ $1) went to messages that re-sent a large conversation from scratch after a > 5 min idle gap — provider caches expire after a few minutes idle |
99
99
  | Switching models mid-conversation | ≥ 2% of cost (and ≥ $1) went to large-context misses right after the provider/model changed mid-session — the previous model's cache doesn't transfer |
100
- | Mid-session re-sends (prefix change) | ≥ 2% of cost (and ≥ $1) went to large-context misses with **no** idle gap, compaction, or model switch to explain them — something rewrote the request prefix |
100
+ | Mid-session re-sends (prefix change) | ≥ 2% of cost (and ≥ $1) went to large-context misses with **no** idle gap, compaction, context edit, or model switch to explain them — something unexpectedly rewrote the request prefix |
101
101
  | Session concentration | the top 5 sessions account for ≥ 35% of the period's cost |
102
102
  | Upfront tax | ≥ 8% of cost was the first message of a session (session starts pay for their whole prompt uncached) |
103
103
  | Cache leverage floor | fewer than 5 cached tokens served per fresh token paid (shown only above $5 / 1M fresh tokens, to avoid noise) |
package/data.ts CHANGED
@@ -194,12 +194,8 @@ export interface SessionMessage extends UsageAmount {
194
194
  /** Session entry id used to dedupe copied auxiliary entries; empty for assistant messages. */
195
195
  sourceId: string;
196
196
  timestamp: number;
197
- /**
198
- * True when a compaction entry occurred between the previous assistant
199
- * message and this one. Compaction legitimately changes the request prefix,
200
- * so such messages are excluded from prefix-change cache-miss accounting.
201
- */
202
- afterCompaction: boolean;
197
+ /** The request followed a compaction or context edit that intentionally changed its prefix. */
198
+ afterContextChange: boolean;
203
199
  }
204
200
 
205
201
  export interface ChildToolUsage {
@@ -300,6 +296,8 @@ const PATTERN_THINKING_COMPACT = Buffer.from('"type":"thinking_level_change"');
300
296
  const PATTERN_THINKING_SPACED = Buffer.from('"type": "thinking_level_change"');
301
297
  const PATTERN_COMPACTION_COMPACT = Buffer.from('"type":"compaction"');
302
298
  const PATTERN_COMPACTION_SPACED = Buffer.from('"type": "compaction"');
299
+ const PATTERN_CONTEXT_EDIT_COMPACT = Buffer.from('"type":"context_edit"');
300
+ const PATTERN_CONTEXT_EDIT_SPACED = Buffer.from('"type": "context_edit"');
303
301
  const PATTERN_BRANCH_SUMMARY_COMPACT = Buffer.from('"type":"branch_summary"');
304
302
  const PATTERN_BRANCH_SUMMARY_SPACED = Buffer.from('"type": "branch_summary"');
305
303
  // pi-subagents versions predating Pi 0.81 persisted child usage in details but
@@ -357,7 +355,7 @@ function auxiliaryMessage(usage: UsageAmount, timestamp: number, sourceId: strin
357
355
  sourceId,
358
356
  ...usage,
359
357
  timestamp,
360
- afterCompaction: false,
358
+ afterContextChange: false,
361
359
  };
362
360
  }
363
361
 
@@ -642,10 +640,12 @@ function lineMightBeRelevant(line: Buffer): boolean {
642
640
  head.includes(PATTERN_SESSION_COMPACT) ||
643
641
  head.includes(PATTERN_THINKING_COMPACT) ||
644
642
  head.includes(PATTERN_COMPACTION_COMPACT) ||
643
+ head.includes(PATTERN_CONTEXT_EDIT_COMPACT) ||
645
644
  head.includes(PATTERN_BRANCH_SUMMARY_COMPACT) ||
646
645
  head.includes(PATTERN_SESSION_SPACED) ||
647
646
  head.includes(PATTERN_THINKING_SPACED) ||
648
647
  head.includes(PATTERN_COMPACTION_SPACED) ||
648
+ head.includes(PATTERN_CONTEXT_EDIT_SPACED) ||
649
649
  head.includes(PATTERN_BRANCH_SUMMARY_SPACED)
650
650
  );
651
651
  }
@@ -665,7 +665,7 @@ export async function parseSessionBuffer(buffer: Buffer, signal?: AbortSignal):
665
665
  // message of a session. Replaying them in append order attributes each message
666
666
  // to the level active when it was produced.
667
667
  let thinkingLevel = "";
668
- let compactionPending = false;
668
+ let contextChangePending = false;
669
669
 
670
670
  let start = 0;
671
671
  let lineNumber = 0;
@@ -700,7 +700,9 @@ export async function parseSessionBuffer(buffer: Buffer, signal?: AbortSignal):
700
700
  } else if (entry.type === "compaction") {
701
701
  const usage = parseUsageAmount(entry.usage);
702
702
  if (usage) messages.push(auxiliaryMessage(usage, parsedTimestamp(undefined, entry.timestamp), typeof entry.id === "string" ? entry.id : ""));
703
- compactionPending = true;
703
+ contextChangePending = true;
704
+ } else if (entry.type === "context_edit") {
705
+ contextChangePending = true;
704
706
  } else if (entry.type === "branch_summary") {
705
707
  const usage = parseUsageAmount(entry.usage);
706
708
  if (usage) messages.push(auxiliaryMessage(usage, parsedTimestamp(undefined, entry.timestamp), typeof entry.id === "string" ? entry.id : ""));
@@ -721,9 +723,9 @@ export async function parseSessionBuffer(buffer: Buffer, signal?: AbortSignal):
721
723
  cacheWrite: msg.usage.cacheWrite || 0,
722
724
  reasoning: msg.usage.reasoning || 0,
723
725
  timestamp: msg.timestamp || (Number.isNaN(fallbackTs) ? 0 : fallbackTs),
724
- afterCompaction: compactionPending,
726
+ afterContextChange: contextChangePending,
725
727
  });
726
- compactionPending = false;
728
+ contextChangePending = false;
727
729
  }
728
730
  } else if (entry.type === "message" && entry.message?.role === "toolResult") {
729
731
  const msg = entry.message;
@@ -745,7 +747,7 @@ export async function parseSessionBuffer(buffer: Buffer, signal?: AbortSignal):
745
747
  // On-disk cache
746
748
  // =============================================================================
747
749
 
748
- const CACHE_VERSION = 5;
750
+ const CACHE_VERSION = 6;
749
751
 
750
752
  type CachedMessageTuple = [
751
753
  providerIdx: number,
@@ -758,7 +760,7 @@ type CachedMessageTuple = [
758
760
  timestamp: number,
759
761
  thinkingLevelIdx: number,
760
762
  reasoning: number,
761
- afterCompaction: 0 | 1,
763
+ afterContextChange: 0 | 1,
762
764
  auxiliary: 0 | 1,
763
765
  sourceIdIdx: number,
764
766
  ];
@@ -860,7 +862,7 @@ export async function loadUsageCache(cachePath: string): Promise<Map<string, Cac
860
862
  cacheWrite: Number(tuple[6]) || 0,
861
863
  timestamp: Number(tuple[7]) || 0,
862
864
  reasoning: Number(tuple[9]) || 0,
863
- afterCompaction: tuple[10] === 1,
865
+ afterContextChange: tuple[10] === 1,
864
866
  });
865
867
  }
866
868
  if (!valid) continue;
@@ -959,7 +961,7 @@ export async function saveUsageCache(cachePath: string, states: Map<string, Cach
959
961
  m.timestamp,
960
962
  intern(m.thinkingLevel),
961
963
  m.reasoning,
962
- m.afterCompaction ? 1 : 0,
964
+ m.afterContextChange ? 1 : 0,
963
965
  m.source === "auxiliary" ? 1 : 0,
964
966
  intern(m.source === "auxiliary" ? m.sourceId : ""),
965
967
  ]),
@@ -1220,7 +1222,7 @@ function addMessagesToUsageData(
1220
1222
  }
1221
1223
  if (mm.isSessionStart) raw.upfrontCost += msg.cost;
1222
1224
  if (
1223
- !msg.afterCompaction &&
1225
+ !msg.afterContextChange &&
1224
1226
  mm.prevCtx >= MISS_MIN_PREV_CONTEXT &&
1225
1227
  msg.cacheRead < Math.min(MISS_MAX_CACHE_READ, 0.3 * mm.prevCtx)
1226
1228
  ) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tmustier/pi-usage-extension",
3
- "version": "0.9.4",
3
+ "version": "0.9.5",
4
4
  "description": "Usage statistics dashboard for Pi sessions.",
5
5
  "license": "MIT",
6
6
  "author": "Thomas Mustier",
@@ -25,7 +25,7 @@
25
25
  "@earendil-works/pi-tui": "*"
26
26
  },
27
27
  "devDependencies": {
28
- "@earendil-works/pi-coding-agent": "^0.80.3",
29
- "@earendil-works/pi-tui": "^0.80.3"
28
+ "@earendil-works/pi-coding-agent": "0.87.0",
29
+ "@earendil-works/pi-tui": "0.87.0"
30
30
  }
31
31
  }