@pify/usage 0.14.0 → 0.15.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/aggregate.ts +7 -0
- package/src/format.ts +24 -9
- package/src/types.ts +10 -1
package/package.json
CHANGED
package/src/aggregate.ts
CHANGED
|
@@ -15,6 +15,7 @@ export function addRecord(totals: UsageTotals, record: UsageRecord): UsageTotals
|
|
|
15
15
|
totals.cacheWrite += record.cacheWrite;
|
|
16
16
|
totals.totalTokens += record.totalTokens;
|
|
17
17
|
totals.cost += record.cost;
|
|
18
|
+
if (record.estimated) totals.estimatedCost += record.cost;
|
|
18
19
|
totals.messages += 1;
|
|
19
20
|
return totals;
|
|
20
21
|
}
|
|
@@ -65,6 +66,7 @@ export function windowTotals(byDay: Map<string, UsageTotals>, days: number, now:
|
|
|
65
66
|
totals.cacheWrite += t.cacheWrite;
|
|
66
67
|
totals.totalTokens += t.totalTokens;
|
|
67
68
|
totals.cost += t.cost;
|
|
69
|
+
totals.estimatedCost += t.estimatedCost;
|
|
68
70
|
totals.messages += t.messages;
|
|
69
71
|
}
|
|
70
72
|
return totals;
|
|
@@ -101,6 +103,10 @@ export function recordFromEntry(entry: unknown): UsageRecord | null {
|
|
|
101
103
|
const total = finite(usage.totalTokens);
|
|
102
104
|
const cost = isRecord(usage.cost) ? finite(usage.cost.total) : 0;
|
|
103
105
|
if (total === 0 && cost === 0) return null;
|
|
106
|
+
// pi 1.5.0: cost.total is the provider's billed figure only when
|
|
107
|
+
// totalFromProvider is true; otherwise it is a catalog estimate (and the
|
|
108
|
+
// field is absent on older entries, which are all estimates).
|
|
109
|
+
const estimated = cost > 0 && !(isRecord(usage.cost) && usage.cost.totalFromProvider === true);
|
|
104
110
|
|
|
105
111
|
const ts =
|
|
106
112
|
typeof entry.timestamp === "string"
|
|
@@ -134,6 +140,7 @@ export function recordFromEntry(entry: unknown): UsageRecord | null {
|
|
|
134
140
|
cacheWrite: finite(usage.cacheWrite),
|
|
135
141
|
totalTokens: total,
|
|
136
142
|
cost,
|
|
143
|
+
estimated,
|
|
137
144
|
};
|
|
138
145
|
}
|
|
139
146
|
|
package/src/format.ts
CHANGED
|
@@ -7,10 +7,21 @@ export function formatTokens(n: number): string {
|
|
|
7
7
|
return String(Math.round(n));
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* `estimated` prefixes a `≈` when the figure is (partly) a catalog estimate
|
|
12
|
+
* rather than a provider-billed total — see `UsageTotals.estimatedCost`. A
|
|
13
|
+
* zero cost is never marked: there is nothing to be approximate about.
|
|
14
|
+
*/
|
|
15
|
+
export function formatCost(n: number, estimated = false): string {
|
|
11
16
|
if (n === 0) return "$0";
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
const mark = estimated ? "≈" : "";
|
|
18
|
+
if (n < 0.01) return `${mark}<$0.01`;
|
|
19
|
+
return `${mark}$${n.toFixed(2)}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** True when a bucket's cost includes a catalog estimate, so it is not fully exact. */
|
|
23
|
+
export function costEstimated(totals: UsageTotals): boolean {
|
|
24
|
+
return totals.estimatedCost > 0;
|
|
14
25
|
}
|
|
15
26
|
|
|
16
27
|
/** Footer text: short, live. Null clears the indicator. */
|
|
@@ -27,7 +38,7 @@ function hasChildSpend(children: ChildSpendLike | undefined): children is ChildS
|
|
|
27
38
|
export function footerText(session: UsageTotals, children?: ChildSpendLike): string | undefined {
|
|
28
39
|
const agents = hasChildSpend(children) ? ` · agents ${formatCost(children.cost)}` : "";
|
|
29
40
|
if (session.messages === 0) return agents ? `📊 0 tok · $0${agents}` : undefined;
|
|
30
|
-
return `📊 ${formatTokens(session.totalTokens)} tok · ${formatCost(session.cost)}${agents}`;
|
|
41
|
+
return `📊 ${formatTokens(session.totalTokens)} tok · ${formatCost(session.cost, costEstimated(session))}${agents}`;
|
|
31
42
|
}
|
|
32
43
|
|
|
33
44
|
const GAUGE_CELLS = 6;
|
|
@@ -53,7 +64,7 @@ export function sessionBlock(session: UsageTotals, contextPct: number | null, ch
|
|
|
53
64
|
const lines = [
|
|
54
65
|
"Session",
|
|
55
66
|
` tokens in ${formatTokens(session.input)} · out ${formatTokens(session.output)} · cache ${formatTokens(session.cacheRead)} read / ${formatTokens(session.cacheWrite)} write`,
|
|
56
|
-
` cost ${formatCost(session.cost)} (${session.messages} responses)`,
|
|
67
|
+
` cost ${formatCost(session.cost, costEstimated(session))} (${session.messages} responses)`,
|
|
57
68
|
];
|
|
58
69
|
if (hasChildSpend(children)) {
|
|
59
70
|
// Child sessions of subagent/swarm/workflow run outside this branch; their
|
|
@@ -73,13 +84,17 @@ export function historyBlock(history: HistoryAggregate, now: number): string {
|
|
|
73
84
|
|
|
74
85
|
const lines = [
|
|
75
86
|
`History (${history.files} local session files)`,
|
|
76
|
-
` today ${formatCost(today.cost)} · ${formatTokens(today.totalTokens)} tok`,
|
|
77
|
-
` 7 days ${formatCost(week.cost)} · ${formatTokens(week.totalTokens)} tok`,
|
|
78
|
-
` 30 days ${formatCost(month.cost)} · ${formatTokens(month.totalTokens)} tok`,
|
|
87
|
+
` today ${formatCost(today.cost, costEstimated(today))} · ${formatTokens(today.totalTokens)} tok`,
|
|
88
|
+
` 7 days ${formatCost(week.cost, costEstimated(week))} · ${formatTokens(week.totalTokens)} tok`,
|
|
89
|
+
` 30 days ${formatCost(month.cost, costEstimated(month))} · ${formatTokens(month.totalTokens)} tok`,
|
|
79
90
|
];
|
|
80
91
|
|
|
81
92
|
pushBreakdown(lines, "By model (all time)", history.byModel, 6);
|
|
82
93
|
pushBreakdown(lines, "By project (all time)", history.byProject, 5);
|
|
94
|
+
// Explain the mark only when it actually appears in this view.
|
|
95
|
+
if (costEstimated(history.total)) {
|
|
96
|
+
lines.push("", "≈ estimated from the model catalog; unmarked figures are billed by the provider");
|
|
97
|
+
}
|
|
83
98
|
return lines.join("\n");
|
|
84
99
|
}
|
|
85
100
|
|
|
@@ -96,6 +111,6 @@ function pushBreakdown(
|
|
|
96
111
|
const width = rows.reduce((m, [name]) => Math.max(m, Math.min(name.length, 40)), 0);
|
|
97
112
|
for (const [name, t] of rows) {
|
|
98
113
|
const label = name.length > 40 ? `${name.slice(0, 39)}…` : name;
|
|
99
|
-
lines.push(` ${label.padEnd(width)} ${formatCost(t.cost)} · ${formatTokens(t.totalTokens)} tok`);
|
|
114
|
+
lines.push(` ${label.padEnd(width)} ${formatCost(t.cost, costEstimated(t))} · ${formatTokens(t.totalTokens)} tok`);
|
|
100
115
|
}
|
|
101
116
|
}
|
package/src/types.ts
CHANGED
|
@@ -10,11 +10,18 @@ export interface UsageTotals {
|
|
|
10
10
|
cacheWrite: number;
|
|
11
11
|
totalTokens: number;
|
|
12
12
|
cost: number;
|
|
13
|
+
/**
|
|
14
|
+
* The part of `cost` that is a catalog estimate rather than a figure the
|
|
15
|
+
* provider billed (pi's `usage.cost.totalFromProvider === false`). When this
|
|
16
|
+
* is above zero the bucket's cost is not fully exact, so it is shown with a
|
|
17
|
+
* `≈`. Zero with a non-zero `cost` means every record was provider-reported.
|
|
18
|
+
*/
|
|
19
|
+
estimatedCost: number;
|
|
13
20
|
messages: number;
|
|
14
21
|
}
|
|
15
22
|
|
|
16
23
|
export function emptyTotals(): UsageTotals {
|
|
17
|
-
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: 0, messages: 0 };
|
|
24
|
+
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: 0, estimatedCost: 0, messages: 0 };
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
/** One usage-bearing record extracted from a session file or live event. */
|
|
@@ -31,6 +38,8 @@ export interface UsageRecord {
|
|
|
31
38
|
cacheWrite: number;
|
|
32
39
|
totalTokens: number;
|
|
33
40
|
cost: number;
|
|
41
|
+
/** True when `cost` is a catalog estimate (pi's `usage.cost.totalFromProvider` is not true). */
|
|
42
|
+
estimated: boolean;
|
|
34
43
|
}
|
|
35
44
|
|
|
36
45
|
export interface HistoryAggregate {
|