@pify/usage 0.9.1 → 0.9.2
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 +1 -1
- package/package.json +1 -1
- package/src/aggregate.ts +19 -5
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ By project (all time)
|
|
|
40
40
|
D--project-shop-api $6.60 · 3.6M tok
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
History counts every usage-bearing entry in pi's session JSONL — assistant turns plus the tool-result usage pi persists, and the summariser's own call, which pi records on the compaction and branch-summary entries themselves rather than on a message. Those summary calls are among the most expensive in a session, so counting them is what makes a session's total match what you were billed rather than what the visible messages add up to; they have no model of their own, so they group under a `compaction` / `branch_summary` label in the by-model view. The live footer and session totals fold the same summariser usage in as it happens (on compaction and tree navigation), so the number does not jump only after a reload. Negative and NaN fields clamp to zero, days are your **local** calendar days, and a per-file mtime cache keeps repeat scans instant.
|
|
43
|
+
History counts every usage-bearing entry in pi's session JSONL — assistant turns plus the tool-result usage pi persists, and the summariser's own call, which pi records on the compaction and branch-summary entries themselves rather than on a message. Those summary calls are among the most expensive in a session, so counting them is what makes a session's total match what you were billed rather than what the visible messages add up to; they have no model of their own, so they group under a `compaction` / `branch_summary` label in the by-model view. The live footer and session totals fold the same summariser usage in as it happens (on compaction and tree navigation), so the number does not jump only after a reload. pi 0.87's cache warmer spends real money outside any message too, as standalone `usage` entries; those are counted under their own model on reload and in history (pi raises no extension event for them, so the live footer picks them up at the next session start). Negative and NaN fields clamp to zero, days are your **local** calendar days, and a per-file mtime cache keeps repeat scans instant.
|
|
44
44
|
|
|
45
45
|
Per-project totals come for free: pi stores sessions one directory per project, so the dashboard can show where the money actually went.
|
|
46
46
|
|
package/package.json
CHANGED
package/src/aggregate.ts
CHANGED
|
@@ -76,18 +76,24 @@ export function windowTotals(byDay: Map<string, UsageTotals>, days: number, now:
|
|
|
76
76
|
* summariser's usage one level up, on the compaction / branch_summary entry
|
|
77
77
|
* itself (these entries have no `message`), so those are read from
|
|
78
78
|
* `entry.usage` — otherwise the most expensive calls in a session go
|
|
79
|
-
* uncounted.
|
|
80
|
-
*
|
|
79
|
+
* uncounted. pi 0.87's standalone `usage` entries (cache warming) also carry
|
|
80
|
+
* their usage on the entry. Returns null for entries without usage;
|
|
81
|
+
* negative/NaN fields clamp to 0.
|
|
81
82
|
*/
|
|
82
83
|
export function recordFromEntry(entry: unknown): UsageRecord | null {
|
|
83
84
|
if (!isRecord(entry)) return null;
|
|
84
85
|
const message = isRecord(entry.message) ? entry.message : null;
|
|
85
86
|
const entryType = typeof entry.type === "string" ? entry.type : "";
|
|
86
87
|
const isSummary = entryType === "compaction" || entryType === "branch_summary";
|
|
88
|
+
// pi 0.87 adds a standalone UsageEntry ({type:"usage", kind:"cache_warm",
|
|
89
|
+
// provider, model, usage}) for spend that belongs to no message — the cache
|
|
90
|
+
// warmer writes one per refresh, and pi folds it into its own session
|
|
91
|
+
// totals. It carries its own provider/model, unlike the summaries.
|
|
92
|
+
const isUsageEntry = entryType === "usage";
|
|
87
93
|
const usage =
|
|
88
94
|
message && isRecord(message.usage)
|
|
89
95
|
? message.usage
|
|
90
|
-
: isSummary && isRecord(entry.usage)
|
|
96
|
+
: (isSummary || isUsageEntry) && isRecord(entry.usage)
|
|
91
97
|
? entry.usage
|
|
92
98
|
: null;
|
|
93
99
|
if (!usage) return null;
|
|
@@ -105,9 +111,17 @@ export function recordFromEntry(entry: unknown): UsageRecord | null {
|
|
|
105
111
|
|
|
106
112
|
// A summary entry has no model/provider of its own; label the bucket by the
|
|
107
113
|
// entry type rather than threading a running "last model" through the scan.
|
|
114
|
+
// A usage entry names its model/provider itself; fall back to its kind.
|
|
115
|
+
const own = message ?? (isUsageEntry ? entry : null);
|
|
108
116
|
const model =
|
|
109
|
-
|
|
110
|
-
|
|
117
|
+
own && typeof own.model === "string"
|
|
118
|
+
? own.model
|
|
119
|
+
: isSummary
|
|
120
|
+
? entryType
|
|
121
|
+
: isUsageEntry && typeof entry.kind === "string"
|
|
122
|
+
? entry.kind
|
|
123
|
+
: "unknown";
|
|
124
|
+
const provider = own && typeof own.provider === "string" ? own.provider : "unknown";
|
|
111
125
|
|
|
112
126
|
return {
|
|
113
127
|
timestamp: Number.isFinite(ts) ? ts : 0,
|