@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 +8 -0
- package/README.md +1 -1
- package/data.ts +18 -16
- package/package.json +3 -3
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
726
|
+
afterContextChange: contextChangePending,
|
|
725
727
|
});
|
|
726
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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": "
|
|
29
|
-
"@earendil-works/pi-tui": "
|
|
28
|
+
"@earendil-works/pi-coding-agent": "0.87.0",
|
|
29
|
+
"@earendil-works/pi-tui": "0.87.0"
|
|
30
30
|
}
|
|
31
31
|
}
|