pi-tokenburn 0.6.0 → 0.7.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/extensions/core/aggregate.ts +4 -5
- package/extensions/core/config.ts +7 -3
- package/extensions/core/parser.ts +1 -1
- package/extensions/core/select.ts +9 -0
- package/extensions/features/breakdown/query.ts +4 -5
- package/extensions/features/report/chart.ts +11 -4
- package/extensions/features/status-line/status.ts +1 -2
- package/package.json +4 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Aggregation of parsed turns into period totals and time series. */
|
|
2
2
|
import { loadAll, type Turn } from "./parser.ts";
|
|
3
|
-
import { selectTurns, type UsageOptions } from "./select.ts";
|
|
3
|
+
import { cacheValue, selectTurns, type UsageOptions } from "./select.ts";
|
|
4
4
|
import { bucketLabel, bucketStart, type Unit } from "./time.ts";
|
|
5
5
|
import type { PeriodStats, TokenBurnReport } from "./types.ts";
|
|
6
6
|
|
|
@@ -56,10 +56,9 @@ export async function collectUsage(
|
|
|
56
56
|
p.cacheRead += t.cacheRead;
|
|
57
57
|
p.cacheWrite += t.cacheWrite;
|
|
58
58
|
p.cost += t.cost;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
59
|
+
const cv = cacheValue(t);
|
|
60
|
+
p.cacheBase += cv.base;
|
|
61
|
+
p.cacheActual += cv.actual;
|
|
63
62
|
};
|
|
64
63
|
|
|
65
64
|
for (const turns of await loadSelected(sessionsRoot, opts)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Config file (tokenburn.json), path resolution, budgets. */
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { readFile,
|
|
4
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
5
5
|
import { sanitizeFilter, sanitizePrices, type PriceMode, type PriceTable, type UsageFilter } from "./select.ts";
|
|
6
6
|
import { STATUS_WINDOWS, type PeriodStats, type Window } from "./types.ts";
|
|
7
7
|
|
|
@@ -140,8 +140,12 @@ export function freshConfig(): TokenBurnConfig {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
export async function saveConfig(cfg: TokenBurnConfig): Promise<void> {
|
|
143
|
-
|
|
144
|
-
await
|
|
143
|
+
const dir = getConfigDir();
|
|
144
|
+
await mkdir(dir, { recursive: true });
|
|
145
|
+
// Write beside, then rename: a crash or a second pi session never leaves half a file behind.
|
|
146
|
+
const tmp = join(dir, `tokenburn.json.${process.pid}.tmp`);
|
|
147
|
+
await writeFile(tmp, JSON.stringify(cfg, null, 2) + "\n", "utf8");
|
|
148
|
+
await rename(tmp, join(dir, "tokenburn.json"));
|
|
145
149
|
}
|
|
146
150
|
|
|
147
151
|
export function budgetsFor(cfg: TokenBurnConfig, w: Window): { tokens?: number; cost?: number } {
|
|
@@ -64,13 +64,13 @@ const GUARD_BYTES = 64;
|
|
|
64
64
|
const HEAD_BYTES = 256;
|
|
65
65
|
export const CHUNK_BYTES = 1 << 20;
|
|
66
66
|
|
|
67
|
-
/** A Turn from one JSONL line, or undefined when it carries no usage. */
|
|
68
67
|
/** A finite number, else 0 (logs can hold 1e999, strings, junk). */
|
|
69
68
|
const finite = (v: unknown): number => {
|
|
70
69
|
const n = Number(v);
|
|
71
70
|
return Number.isFinite(n) ? n : 0;
|
|
72
71
|
};
|
|
73
72
|
|
|
73
|
+
/** A Turn from one JSONL line, or undefined when it carries no usage. */
|
|
74
74
|
function turnFromLine(line: Buffer, meta: Meta): Turn | undefined {
|
|
75
75
|
if (meta.cwd === undefined && line.includes('"type":"session"')) {
|
|
76
76
|
try {
|
|
@@ -103,3 +103,12 @@ export function selectTurns(turns: Turn[], opts: UsageOptions | undefined): Turn
|
|
|
103
103
|
}
|
|
104
104
|
return out;
|
|
105
105
|
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* What the turn's cached tokens would have cost as plain input (`base`) and what they did cost
|
|
109
|
+
* (`actual`). Both 0 when the log has no input price for the turn (then no savings are claimed).
|
|
110
|
+
*/
|
|
111
|
+
export function cacheValue(t: Turn): { base: number; actual: number } {
|
|
112
|
+
if (!(t.input > 0 && t.costInput > 0)) return { base: 0, actual: 0 };
|
|
113
|
+
return { base: (t.cacheRead * t.costInput) / t.input, actual: t.costCacheRead };
|
|
114
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Grouped usage queries: per model, provider, project and session, over a period. */
|
|
2
2
|
import { HOUR_MS, loadSelected, turnTokens } from "../../core/aggregate.ts";
|
|
3
|
-
import type
|
|
3
|
+
import { cacheValue, type UsageOptions } from "../../core/select.ts";
|
|
4
4
|
import { bucketStart } from "../../core/time.ts";
|
|
5
5
|
|
|
6
6
|
export const PERIODS = ["hour", "day", "week", "month", "year", "all"] as const;
|
|
@@ -106,10 +106,9 @@ export async function collectGroups(
|
|
|
106
106
|
r.turns++;
|
|
107
107
|
if (t.ts < r.first) r.first = t.ts;
|
|
108
108
|
if (t.ts > r.last) r.last = t.ts;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
109
|
+
const cv = cacheValue(t);
|
|
110
|
+
r.cacheBase += cv.base;
|
|
111
|
+
r.cacheActual += cv.actual;
|
|
113
112
|
if (!seen.has(key)) {
|
|
114
113
|
seen.add(key);
|
|
115
114
|
r.sessions++;
|
|
@@ -68,21 +68,28 @@ export function renderChart(
|
|
|
68
68
|
if (h >= row) cell = "█";
|
|
69
69
|
else if (h > row - 1 && series[i].tokens > 0) cell = SPARK[Math.min(7, Math.max(0, Math.floor((h - (row - 1)) * 8) - 1))];
|
|
70
70
|
else if (row === 1 && series[i].tokens > 0) cell = "▁";
|
|
71
|
+
else if (row === 1 && budgetRow !== 1) cell = "·"; // an empty bucket, not a missing one
|
|
71
72
|
else cell = row === budgetRow ? "┄" : " ";
|
|
72
|
-
const filled = cell !== " " && cell !== "┄";
|
|
73
|
+
const filled = cell !== " " && cell !== "┄" && cell !== "·";
|
|
73
74
|
const body = cell.repeat(barW);
|
|
74
|
-
line += filled ? paint(kindOf(i), body) : cell === "┄" ? paint("budget", body) : body;
|
|
75
|
+
line += filled ? paint(kindOf(i), body) : cell === "┄" ? paint("budget", body) : cell === "·" ? paint("axis", body) : body;
|
|
75
76
|
line += row === budgetRow ? paint("budget", "┄") : " ";
|
|
76
77
|
}
|
|
77
78
|
lines.push(line);
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
lines.push(paint("axis", `${
|
|
81
|
+
lines.push(paint("axis", `${yLabels[2].padStart(yW)} └${"─".repeat(series.length * colW)}`));
|
|
81
82
|
const pad = " ".repeat(yW + 2);
|
|
82
83
|
const cols = series.map((p, i) => ({ l: columnLabels(p, unit), now: i === series.length - 1 }));
|
|
83
84
|
lines.push(pad + cols.map((c) => c.l[0].padEnd(colW)).join(""));
|
|
84
85
|
lines.push(paint("dim", pad + cols.map((c) => c.l[1].padEnd(colW)).join("")));
|
|
85
|
-
|
|
86
|
+
// The marker under the current bucket, with its value so you need not read it off the axis.
|
|
87
|
+
const latest = series[series.length - 1];
|
|
88
|
+
lines.push(
|
|
89
|
+
pad +
|
|
90
|
+
cols.map((c) => (c.now ? paint("now", "▲".padEnd(colW)) : " ".repeat(colW))).join("") +
|
|
91
|
+
(latest ? paint("now", `now ${formatNum(latest.tokens)} (${formatCost(latest.cost)})`) : ""),
|
|
92
|
+
);
|
|
86
93
|
|
|
87
94
|
const avg = series.length ? total / series.length : 0;
|
|
88
95
|
lines.push(
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/** Status line: window start, burn rate / cache hit / savings extras for the status window (pure). */
|
|
2
2
|
import type { TokenBurnConfig } from "../../core/config.ts";
|
|
3
3
|
import { formatCost, formatNum } from "../../core/format.ts";
|
|
4
|
+
import { HOUR_MS as HOUR } from "../../core/aggregate.ts";
|
|
4
5
|
import { bucketStart } from "../../core/time.ts";
|
|
5
6
|
import type { PeriodStats, Rate, TokenBurnReport, Window } from "../../core/types.ts";
|
|
6
7
|
import { cacheHitRatio } from "../breakdown/query.ts";
|
|
7
8
|
|
|
8
|
-
const HOUR = 3_600_000;
|
|
9
|
-
|
|
10
9
|
/** Start of a status window (`hour` = rolling last 60 minutes, `total` = 0). */
|
|
11
10
|
export function windowStart(w: Window, now: number): number {
|
|
12
11
|
if (w === "hour") return now - HOUR;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-tokenburn",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Pi extension for live token usage monitoring and budget tracking (Day / Week / Month / Total). 0 prompt tokens.",
|
|
5
5
|
"keywords": ["pi-package", "pi-extension", "tokenburn", "tokens", "analytics", "budget", "stats"],
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,6 +24,9 @@
|
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"@earendil-works/pi-coding-agent": "*"
|
|
26
26
|
},
|
|
27
|
+
"overrides": {
|
|
28
|
+
"undici": "8.12.3"
|
|
29
|
+
},
|
|
27
30
|
"devDependencies": {
|
|
28
31
|
"@earendil-works/pi-coding-agent": "^1.4.0",
|
|
29
32
|
"@types/bun": "^1.4.4",
|