tokens-metric 0.4.1 → 0.4.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/dist/core/format.js +8 -0
- package/dist/core/parser.js +3 -0
- package/dist/core/tailer.js +1 -0
- package/dist/tui/index.js +13 -2
- package/package.json +1 -1
package/dist/core/format.js
CHANGED
|
@@ -53,6 +53,14 @@ export function categoryCostUSD(model, category, tokens) {
|
|
|
53
53
|
: p.cacheRead;
|
|
54
54
|
return (tokens * rate) / 1_000_000;
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Returns the context window size in tokens for a given model.
|
|
58
|
+
* All current Claude models share a 200k window. Falls back to 200k for
|
|
59
|
+
* unknown models so the bar is always renderable.
|
|
60
|
+
*/
|
|
61
|
+
export function contextWindowSize(_model) {
|
|
62
|
+
return 200_000;
|
|
63
|
+
}
|
|
56
64
|
export function fmtUSD(n) {
|
|
57
65
|
if (n < 0.01)
|
|
58
66
|
return `$${n.toFixed(4)}`;
|
package/dist/core/parser.js
CHANGED
|
@@ -79,6 +79,7 @@ export async function aggregateTranscript(path) {
|
|
|
79
79
|
totals: EMPTY_USAGE(),
|
|
80
80
|
byModel: {},
|
|
81
81
|
messageCount: 0,
|
|
82
|
+
lastMsgUsage: null,
|
|
82
83
|
};
|
|
83
84
|
const stream = createReadStream(path, { encoding: 'utf8' });
|
|
84
85
|
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
@@ -129,6 +130,8 @@ export function applyLine(stats, line) {
|
|
|
129
130
|
stats.totals = addUsage(stats.totals, u);
|
|
130
131
|
stats.byModel[model] = addUsage(stats.byModel[model] ?? EMPTY_USAGE(), u);
|
|
131
132
|
stats.messageCount += 1;
|
|
133
|
+
// Overwrite — we only care about the most recent turn's context footprint.
|
|
134
|
+
stats.lastMsgUsage = { ...EMPTY_USAGE(), ...u };
|
|
132
135
|
}
|
|
133
136
|
function numberOr0(v) {
|
|
134
137
|
return typeof v === 'number' && Number.isFinite(v) ? v : 0;
|
package/dist/core/tailer.js
CHANGED
package/dist/tui/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
7
7
|
import { findActiveTranscript, listTranscripts } from '../core/parser.js';
|
|
8
8
|
import { tailTranscript } from '../core/tailer.js';
|
|
9
9
|
import { detectAuth } from '../core/detect.js';
|
|
10
|
-
import { categoryCostUSD, estimateCostUSD, fmtNumber, fmtUSD } from '../core/format.js';
|
|
10
|
+
import { categoryCostUSD, contextWindowSize, estimateCostUSD, fmtNumber, fmtUSD } from '../core/format.js';
|
|
11
11
|
import { buildHistory, bucketCostUSD, bucketTokens, bucketTopModel, getTodaySessions, } from '../core/history.js';
|
|
12
12
|
import { totalTokens } from '../core/types.js';
|
|
13
13
|
import { anonymizePath } from '../core/privacy.js';
|
|
@@ -245,7 +245,14 @@ function BreakdownPanel({ stats, series, ratePerSec, }) {
|
|
|
245
245
|
const model = stats.lastModel ?? '';
|
|
246
246
|
const cacheDenom = u.input_tokens + u.cache_read_input_tokens;
|
|
247
247
|
const hitRatio = cacheDenom > 0 ? u.cache_read_input_tokens / cacheDenom : null;
|
|
248
|
-
|
|
248
|
+
// Context window fill — based on last message's input footprint
|
|
249
|
+
const ctxLimit = contextWindowSize(model);
|
|
250
|
+
const lastMsg = stats.lastMsgUsage;
|
|
251
|
+
const ctxUsed = lastMsg
|
|
252
|
+
? lastMsg.input_tokens + lastMsg.cache_read_input_tokens + lastMsg.cache_creation_input_tokens
|
|
253
|
+
: null;
|
|
254
|
+
const ctxRatio = ctxUsed !== null ? ctxUsed / ctxLimit : null;
|
|
255
|
+
return (_jsxs(Box, { flexDirection: "column", children: [ctxRatio !== null && (_jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Context " }), _jsx(Text, { color: ctxRatio > 0.9 ? 'red' : ctxRatio > 0.7 ? 'yellow' : 'green', children: ctxWindowBar(ctxRatio, BAR_WIDTH) }), _jsx(Text, { children: ' ' }), _jsx(Text, { bold: true, color: ctxRatio > 0.9 ? 'red' : ctxRatio > 0.7 ? 'yellow' : 'green', children: fmtNumber(ctxUsed) }), _jsxs(Text, { dimColor: true, children: [" / ", fmtNumber(ctxLimit), " "] }), _jsxs(Text, { color: ctxRatio > 0.9 ? 'red' : ctxRatio > 0.7 ? 'yellow' : 'green', children: [(ctxRatio * 100).toFixed(1), "%"] }), ctxRatio > 0.9 && _jsx(Text, { color: "red", children: " \u26A0 near limit" })] }), _jsxs(Text, { dimColor: true, children: [" last turn \u00B7 ", fmtNumber(ctxLimit - ctxUsed), " tokens remaining"] })] })), _jsx(BarRow, { label: "Input ", value: u.input_tokens, max: max, total: total, color: "cyan", cost: categoryCostUSD(model, 'input', u.input_tokens) }), _jsx(BarRow, { label: "Output ", value: u.output_tokens, max: max, total: total, color: "green", cost: categoryCostUSD(model, 'output', u.output_tokens) }), _jsx(BarRow, { label: "C. write ", value: u.cache_creation_input_tokens, max: max, total: total, color: "yellow", cost: categoryCostUSD(model, 'cacheWrite', u.cache_creation_input_tokens) }), _jsx(BarRow, { label: "C. read ", value: u.cache_read_input_tokens, max: max, total: total, color: "magenta", cost: categoryCostUSD(model, 'cacheRead', u.cache_read_input_tokens) }), hitRatio !== null && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Cache hit" }), _jsx(Text, { dimColor: true, children: " \u00B7 " }), _jsxs(Text, { color: hitRatio > 0.9 ? 'green' : hitRatio > 0.6 ? 'yellow' : 'red', children: [(hitRatio * 100).toFixed(1), "%"] }), _jsx(Text, { color: hitRatio > 0.9 ? 'green' : hitRatio > 0.6 ? 'yellow' : 'red', children: hitRatio > 0.9 ? ' ✓ excellent' : hitRatio > 0.6 ? ' ⚠ degraded' : ' ✗ poor' })] }) })), Object.keys(stats.byModel).length > 1 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "By model" }), Object.entries(stats.byModel).map(([m, mu]) => {
|
|
249
256
|
const c = estimateCostUSD(m, mu);
|
|
250
257
|
return (_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: shortModel(m) }), _jsx(Text, { dimColor: true, children: " \u03A3 " }), _jsx(Text, { children: fmtNumber(totalTokens(mu)) }), c !== null && (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: " ~" }), _jsx(Text, { children: fmtUSD(c) })] }))] }, m));
|
|
251
258
|
})] }))] }));
|
|
@@ -350,6 +357,10 @@ function displayCwd(cwd) {
|
|
|
350
357
|
return '~ (home)';
|
|
351
358
|
return out;
|
|
352
359
|
}
|
|
360
|
+
function ctxWindowBar(ratio, width) {
|
|
361
|
+
const filled = Math.round(Math.min(1, ratio) * width);
|
|
362
|
+
return '█'.repeat(filled) + '░'.repeat(width - filled);
|
|
363
|
+
}
|
|
353
364
|
function intensityColor(ratio) {
|
|
354
365
|
if (ratio <= 0)
|
|
355
366
|
return 'gray';
|