@diogonzafe/tokenwatch 0.9.0 → 0.10.1
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 +41 -1
- package/dist/adapters.cjs +16 -6
- package/dist/adapters.cjs.map +1 -1
- package/dist/adapters.d.cts +2 -1
- package/dist/adapters.d.ts +2 -1
- package/dist/adapters.js +16 -6
- package/dist/adapters.js.map +1 -1
- package/dist/cli.js +114 -11
- package/dist/cli.js.map +1 -1
- package/dist/exporters.d.cts +1 -1
- package/dist/exporters.d.ts +1 -1
- package/dist/{index-fD5QLTWg.d.cts → index-B5OF0YCl.d.cts} +9 -0
- package/dist/{index-fD5QLTWg.d.ts → index-B5OF0YCl.d.ts} +9 -0
- package/dist/index.cjs +57 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +57 -27
- package/dist/index.js.map +1 -1
- package/dist/langchain.d.cts +1 -1
- package/dist/langchain.d.ts +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -30,6 +30,7 @@ function rowToEntry(r) {
|
|
|
30
30
|
...r["user_id"] != null && { userId: r["user_id"] },
|
|
31
31
|
...r["feature"] != null && { feature: r["feature"] },
|
|
32
32
|
...r["app_id"] != null && { appId: r["app_id"] },
|
|
33
|
+
...r["metadata"] != null && { metadata: r["metadata"] },
|
|
33
34
|
timestamp: r["timestamp"] instanceof Date ? r["timestamp"].toISOString() : r["timestamp"]
|
|
34
35
|
};
|
|
35
36
|
}
|
|
@@ -59,6 +60,7 @@ var init_postgres = __esm({
|
|
|
59
60
|
user_id TEXT,
|
|
60
61
|
feature TEXT,
|
|
61
62
|
app_id TEXT,
|
|
63
|
+
metadata JSONB,
|
|
62
64
|
timestamp TIMESTAMPTZ NOT NULL
|
|
63
65
|
)
|
|
64
66
|
`);
|
|
@@ -67,7 +69,8 @@ var init_postgres = __esm({
|
|
|
67
69
|
"ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS feature TEXT",
|
|
68
70
|
"ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS cached_tokens INTEGER NOT NULL DEFAULT 0",
|
|
69
71
|
"ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS cache_creation_tokens INTEGER NOT NULL DEFAULT 0",
|
|
70
|
-
"ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS app_id TEXT"
|
|
72
|
+
"ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS app_id TEXT",
|
|
73
|
+
"ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS metadata JSONB"
|
|
71
74
|
]) {
|
|
72
75
|
await this.client.query(col).catch(() => {
|
|
73
76
|
});
|
|
@@ -77,8 +80,8 @@ var init_postgres = __esm({
|
|
|
77
80
|
this.client.query(
|
|
78
81
|
`INSERT INTO tokenwatch_usage
|
|
79
82
|
(model, input_tokens, output_tokens, reasoning_tokens, cached_tokens, cache_creation_tokens,
|
|
80
|
-
cost_usd, session_id, user_id, feature, app_id, timestamp)
|
|
81
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`,
|
|
83
|
+
cost_usd, session_id, user_id, feature, app_id, metadata, timestamp)
|
|
84
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`,
|
|
82
85
|
[
|
|
83
86
|
entry.model,
|
|
84
87
|
entry.inputTokens,
|
|
@@ -91,6 +94,7 @@ var init_postgres = __esm({
|
|
|
91
94
|
entry.userId ?? null,
|
|
92
95
|
entry.feature ?? null,
|
|
93
96
|
entry.appId ?? null,
|
|
97
|
+
entry.metadata ?? null,
|
|
94
98
|
entry.timestamp
|
|
95
99
|
]
|
|
96
100
|
).catch((err) => {
|
|
@@ -137,6 +141,7 @@ function rowToEntry2(r) {
|
|
|
137
141
|
...r["user_id"] != null && { userId: r["user_id"] },
|
|
138
142
|
...r["feature"] != null && { feature: r["feature"] },
|
|
139
143
|
...r["app_id"] != null && { appId: r["app_id"] },
|
|
144
|
+
...r["metadata"] != null && { metadata: typeof r["metadata"] === "string" ? JSON.parse(r["metadata"]) : r["metadata"] },
|
|
140
145
|
timestamp: r["timestamp"] instanceof Date ? r["timestamp"].toISOString() : r["timestamp"]
|
|
141
146
|
};
|
|
142
147
|
}
|
|
@@ -166,6 +171,7 @@ var init_mysql = __esm({
|
|
|
166
171
|
user_id VARCHAR(255),
|
|
167
172
|
feature VARCHAR(255),
|
|
168
173
|
app_id VARCHAR(255),
|
|
174
|
+
metadata JSON,
|
|
169
175
|
timestamp DATETIME(3) NOT NULL
|
|
170
176
|
)
|
|
171
177
|
`);
|
|
@@ -175,7 +181,8 @@ var init_mysql = __esm({
|
|
|
175
181
|
ADD COLUMN IF NOT EXISTS feature VARCHAR(255),
|
|
176
182
|
ADD COLUMN IF NOT EXISTS cached_tokens INT NOT NULL DEFAULT 0,
|
|
177
183
|
ADD COLUMN IF NOT EXISTS cache_creation_tokens INT NOT NULL DEFAULT 0,
|
|
178
|
-
ADD COLUMN IF NOT EXISTS app_id VARCHAR(255)
|
|
184
|
+
ADD COLUMN IF NOT EXISTS app_id VARCHAR(255),
|
|
185
|
+
ADD COLUMN IF NOT EXISTS metadata JSON
|
|
179
186
|
`).catch(() => {
|
|
180
187
|
});
|
|
181
188
|
}
|
|
@@ -183,8 +190,8 @@ var init_mysql = __esm({
|
|
|
183
190
|
this.client.execute(
|
|
184
191
|
`INSERT INTO tokenwatch_usage
|
|
185
192
|
(model, input_tokens, output_tokens, reasoning_tokens, cached_tokens, cache_creation_tokens,
|
|
186
|
-
cost_usd, session_id, user_id, feature, app_id, timestamp)
|
|
187
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
193
|
+
cost_usd, session_id, user_id, feature, app_id, metadata, timestamp)
|
|
194
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
188
195
|
[
|
|
189
196
|
entry.model,
|
|
190
197
|
entry.inputTokens,
|
|
@@ -197,6 +204,7 @@ var init_mysql = __esm({
|
|
|
197
204
|
entry.userId ?? null,
|
|
198
205
|
entry.feature ?? null,
|
|
199
206
|
entry.appId ?? null,
|
|
207
|
+
entry.metadata != null ? JSON.stringify(entry.metadata) : null,
|
|
200
208
|
entry.timestamp
|
|
201
209
|
]
|
|
202
210
|
).catch((err) => {
|
|
@@ -240,6 +248,7 @@ function docToEntry(doc) {
|
|
|
240
248
|
...doc.userId != null && { userId: doc.userId },
|
|
241
249
|
...doc.feature != null && { feature: doc.feature },
|
|
242
250
|
...doc.appId != null && { appId: doc.appId },
|
|
251
|
+
...doc.metadata != null && { metadata: doc.metadata },
|
|
243
252
|
timestamp: doc.timestamp
|
|
244
253
|
};
|
|
245
254
|
}
|
|
@@ -274,6 +283,7 @@ var init_mongodb = __esm({
|
|
|
274
283
|
userId: entry.userId ?? null,
|
|
275
284
|
...entry.feature !== void 0 && { feature: entry.feature },
|
|
276
285
|
...entry.appId !== void 0 && { appId: entry.appId },
|
|
286
|
+
...entry.metadata !== void 0 && { metadata: entry.metadata },
|
|
277
287
|
timestamp: entry.timestamp
|
|
278
288
|
}).catch((err) => {
|
|
279
289
|
console.warn("[tokenwatch] MongoStorage.record failed:", err);
|
|
@@ -404,6 +414,7 @@ var SqliteStorage = class {
|
|
|
404
414
|
user_id TEXT,
|
|
405
415
|
feature TEXT,
|
|
406
416
|
app_id TEXT,
|
|
417
|
+
metadata TEXT,
|
|
407
418
|
timestamp TEXT NOT NULL
|
|
408
419
|
)
|
|
409
420
|
`);
|
|
@@ -423,13 +434,16 @@ var SqliteStorage = class {
|
|
|
423
434
|
if (!cols.includes("app_id")) {
|
|
424
435
|
this.db.exec(`ALTER TABLE usage ADD COLUMN app_id TEXT`);
|
|
425
436
|
}
|
|
437
|
+
if (!cols.includes("metadata")) {
|
|
438
|
+
this.db.exec(`ALTER TABLE usage ADD COLUMN metadata TEXT`);
|
|
439
|
+
}
|
|
426
440
|
}
|
|
427
441
|
record(entry) {
|
|
428
442
|
this.db.prepare(
|
|
429
443
|
`INSERT INTO usage
|
|
430
444
|
(model, input_tokens, output_tokens, reasoning_tokens, cached_tokens, cache_creation_tokens,
|
|
431
|
-
cost_usd, session_id, user_id, feature, app_id, timestamp)
|
|
432
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
445
|
+
cost_usd, session_id, user_id, feature, app_id, metadata, timestamp)
|
|
446
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
433
447
|
).run(
|
|
434
448
|
entry.model,
|
|
435
449
|
entry.inputTokens,
|
|
@@ -442,6 +456,7 @@ var SqliteStorage = class {
|
|
|
442
456
|
entry.userId ?? null,
|
|
443
457
|
entry.feature ?? null,
|
|
444
458
|
entry.appId ?? null,
|
|
459
|
+
entry.metadata != null ? JSON.stringify(entry.metadata) : null,
|
|
445
460
|
entry.timestamp
|
|
446
461
|
);
|
|
447
462
|
}
|
|
@@ -459,6 +474,7 @@ var SqliteStorage = class {
|
|
|
459
474
|
...r.user_id != null && { userId: r.user_id },
|
|
460
475
|
...r.feature != null && { feature: r.feature },
|
|
461
476
|
...r.app_id != null && { appId: r.app_id },
|
|
477
|
+
...r.metadata != null && { metadata: JSON.parse(r.metadata) },
|
|
462
478
|
timestamp: r.timestamp
|
|
463
479
|
}));
|
|
464
480
|
}
|
|
@@ -536,6 +552,7 @@ var CloudExporter = class {
|
|
|
536
552
|
sessionId: entry.sessionId,
|
|
537
553
|
userId: entry.userId,
|
|
538
554
|
feature: entry.feature,
|
|
555
|
+
metadata: entry.metadata,
|
|
539
556
|
timestamp: entry.timestamp
|
|
540
557
|
})
|
|
541
558
|
}).catch(() => {
|
|
@@ -2200,6 +2217,7 @@ ${issues}`);
|
|
|
2200
2217
|
const byUser = {};
|
|
2201
2218
|
const byFeature = {};
|
|
2202
2219
|
const byApp = {};
|
|
2220
|
+
const byMetadata = {};
|
|
2203
2221
|
let totalInput = 0;
|
|
2204
2222
|
let totalOutput = 0;
|
|
2205
2223
|
let totalCost = 0;
|
|
@@ -2241,6 +2259,14 @@ ${issues}`);
|
|
|
2241
2259
|
a.costUSD += e.costUSD;
|
|
2242
2260
|
a.calls += 1;
|
|
2243
2261
|
}
|
|
2262
|
+
if (e.metadata) {
|
|
2263
|
+
for (const [key, val] of Object.entries(e.metadata)) {
|
|
2264
|
+
const group = byMetadata[key] ??= {};
|
|
2265
|
+
const slot = group[val] ??= { costUSD: 0, calls: 0 };
|
|
2266
|
+
slot.costUSD += e.costUSD;
|
|
2267
|
+
slot.calls += 1;
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2244
2270
|
}
|
|
2245
2271
|
if (options && entries.length > 0) {
|
|
2246
2272
|
periodFrom = entries[0]?.timestamp ?? periodFrom;
|
|
@@ -2253,6 +2279,7 @@ ${issues}`);
|
|
|
2253
2279
|
byUser,
|
|
2254
2280
|
byFeature,
|
|
2255
2281
|
byApp,
|
|
2282
|
+
byMetadata,
|
|
2256
2283
|
period: { from: periodFrom, to: lastTimestamp },
|
|
2257
2284
|
...pricesUpdatedAt ? { pricesUpdatedAt } : {}
|
|
2258
2285
|
};
|
|
@@ -2357,7 +2384,7 @@ ${issues}`);
|
|
|
2357
2384
|
}
|
|
2358
2385
|
async function exportCSV() {
|
|
2359
2386
|
const entries = await Promise.resolve(storage.getAll());
|
|
2360
|
-
const header = "timestamp,model,inputTokens,outputTokens,reasoningTokens,cachedTokens,cacheCreationTokens,costUSD,sessionId,userId,feature,appId";
|
|
2387
|
+
const header = "timestamp,model,inputTokens,outputTokens,reasoningTokens,cachedTokens,cacheCreationTokens,costUSD,sessionId,userId,feature,appId,metadata";
|
|
2361
2388
|
const rows = entries.map(
|
|
2362
2389
|
(e) => [
|
|
2363
2390
|
csvEscape(e.timestamp),
|
|
@@ -2371,7 +2398,8 @@ ${issues}`);
|
|
|
2371
2398
|
csvEscape(e.sessionId ?? ""),
|
|
2372
2399
|
csvEscape(e.userId ?? ""),
|
|
2373
2400
|
csvEscape(e.feature ?? ""),
|
|
2374
|
-
csvEscape(e.appId ?? "")
|
|
2401
|
+
csvEscape(e.appId ?? ""),
|
|
2402
|
+
csvEscape(e.metadata ? JSON.stringify(e.metadata) : "")
|
|
2375
2403
|
].join(",")
|
|
2376
2404
|
);
|
|
2377
2405
|
return [header, ...rows].join("\n");
|
|
@@ -2490,6 +2518,7 @@ async function getDashboardData(storage, filter) {
|
|
|
2490
2518
|
const byUser = {};
|
|
2491
2519
|
const byFeature = {};
|
|
2492
2520
|
const byApp = {};
|
|
2521
|
+
const byMetadata = {};
|
|
2493
2522
|
let totalInput = 0;
|
|
2494
2523
|
let totalOutput = 0;
|
|
2495
2524
|
let totalCost = 0;
|
|
@@ -2528,6 +2557,14 @@ async function getDashboardData(storage, filter) {
|
|
|
2528
2557
|
a.costUSD += e.costUSD;
|
|
2529
2558
|
a.calls += 1;
|
|
2530
2559
|
}
|
|
2560
|
+
if (e.metadata) {
|
|
2561
|
+
for (const [key, val] of Object.entries(e.metadata)) {
|
|
2562
|
+
const group = byMetadata[key] ??= {};
|
|
2563
|
+
const slot = group[val] ??= { costUSD: 0, calls: 0 };
|
|
2564
|
+
slot.costUSD += e.costUSD;
|
|
2565
|
+
slot.calls += 1;
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2531
2568
|
}
|
|
2532
2569
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2533
2570
|
const periodFrom = entries[0]?.timestamp ?? now;
|
|
@@ -2540,6 +2577,7 @@ async function getDashboardData(storage, filter) {
|
|
|
2540
2577
|
byUser,
|
|
2541
2578
|
byFeature,
|
|
2542
2579
|
byApp,
|
|
2580
|
+
byMetadata,
|
|
2543
2581
|
period: { from: periodFrom, to: periodTo }
|
|
2544
2582
|
};
|
|
2545
2583
|
const forecastWindowMs = 24 * 60 * 60 * 1e3;
|
|
@@ -3515,7 +3553,65 @@ function ModelTable({ models, total, t, onRowClick, exampleHover }) {
|
|
|
3515
3553
|
</section>
|
|
3516
3554
|
);
|
|
3517
3555
|
}
|
|
3518
|
-
|
|
3556
|
+
function MetaGroup({ label, rows, t }) {
|
|
3557
|
+
const [open, setOpen] = useStateT(true);
|
|
3558
|
+
const { fmtUSD, fmtInt } = window.TW;
|
|
3559
|
+
const total = rows.reduce(function(s, r) { return s + r[1].costUSD; }, 0);
|
|
3560
|
+
return (
|
|
3561
|
+
<section className="tw-section">
|
|
3562
|
+
<div className="tw-sec-head" style={{ cursor: 'pointer' }} onClick={() => setOpen(function(v) { return !v; })}>
|
|
3563
|
+
<h3 style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
|
3564
|
+
<button className="tw-collapse" style={{ transform: open ? 'none' : 'rotate(-90deg)' }}><Ico.chevron /></button>
|
|
3565
|
+
{label}
|
|
3566
|
+
</h3>
|
|
3567
|
+
<span className="tw-sec-sub">{rows.length + ' values \xB7 ' + fmtUSD(total, 4)}</span>
|
|
3568
|
+
</div>
|
|
3569
|
+
{open && (
|
|
3570
|
+
<div className="tw-table-wrap">
|
|
3571
|
+
<table className={'tw-table' + (t.density === 'compact' ? ' compact' : '')}>
|
|
3572
|
+
<thead>
|
|
3573
|
+
<tr>
|
|
3574
|
+
<th style={{ textAlign: 'left' }}>Value</th>
|
|
3575
|
+
<th style={{ textAlign: 'right' }}>Cost</th>
|
|
3576
|
+
<th style={{ textAlign: 'right' }}>Calls</th>
|
|
3577
|
+
<th style={{ textAlign: 'right' }}>Avg / call</th>
|
|
3578
|
+
</tr>
|
|
3579
|
+
</thead>
|
|
3580
|
+
<tbody>
|
|
3581
|
+
{rows.map(function(r) {
|
|
3582
|
+
var val = r[0], stats = r[1];
|
|
3583
|
+
return (
|
|
3584
|
+
<tr key={val} className="tw-row">
|
|
3585
|
+
<td>{val}</td>
|
|
3586
|
+
<td style={{ textAlign: 'right' }}>{fmtUSD(stats.costUSD, 4)}</td>
|
|
3587
|
+
<td style={{ textAlign: 'right' }}>{fmtInt(stats.calls)}</td>
|
|
3588
|
+
<td style={{ textAlign: 'right' }}>{fmtUSD(stats.costUSD / Math.max(stats.calls, 1), 4)}</td>
|
|
3589
|
+
</tr>
|
|
3590
|
+
);
|
|
3591
|
+
})}
|
|
3592
|
+
</tbody>
|
|
3593
|
+
</table>
|
|
3594
|
+
</div>
|
|
3595
|
+
)}
|
|
3596
|
+
</section>
|
|
3597
|
+
);
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3600
|
+
function MetadataSection({ byMetadata, t }) {
|
|
3601
|
+
var keys = Object.keys(byMetadata || {});
|
|
3602
|
+
if (keys.length === 0) return null;
|
|
3603
|
+
return (
|
|
3604
|
+
<div>
|
|
3605
|
+
{keys.map(function(key) {
|
|
3606
|
+
var group = byMetadata[key];
|
|
3607
|
+
var rows = Object.entries(group).sort(function(a, b) { return b[1].costUSD - a[1].costUSD; });
|
|
3608
|
+
return <MetaGroup key={key} label={key} rows={rows} t={t} />;
|
|
3609
|
+
})}
|
|
3610
|
+
</div>
|
|
3611
|
+
);
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3614
|
+
Object.assign(window, { ModelTable, MetaGroup, MetadataSection });
|
|
3519
3615
|
</script>
|
|
3520
3616
|
|
|
3521
3617
|
<script type="text/babel">
|
|
@@ -3824,6 +3920,7 @@ Object.assign(window, {
|
|
|
3824
3920
|
BASE_MODELS, RANGES, modelsForRange, kpisForRange,
|
|
3825
3921
|
seriesForRange, seedFeed, makeCall, callsForModel,
|
|
3826
3922
|
FEATURES, FEATURE_COLOR, BUDGET, _buildSeries: buildSeries,
|
|
3923
|
+
byMetadata: {},
|
|
3827
3924
|
},
|
|
3828
3925
|
});
|
|
3829
3926
|
|
|
@@ -3868,6 +3965,7 @@ Object.assign(window, {
|
|
|
3868
3965
|
var elapsed = window.TW.BUDGET.cycleDays - window.TW.BUDGET.daysLeft;
|
|
3869
3966
|
window.TW.BUDGET.used = fc.burnRatePerHour * 24 * Math.max(elapsed, 1);
|
|
3870
3967
|
}
|
|
3968
|
+
if (r.byMetadata) window.TW.byMetadata = r.byMetadata;
|
|
3871
3969
|
window.dispatchEvent(new CustomEvent('tw-data-update'));
|
|
3872
3970
|
}
|
|
3873
3971
|
var evtSource = null;
|
|
@@ -4010,6 +4108,10 @@ function App() {
|
|
|
4010
4108
|
|
|
4011
4109
|
const models = kpis.models;
|
|
4012
4110
|
|
|
4111
|
+
const byMetadata = useMemoApp(() => {
|
|
4112
|
+
return window.TW.byMetadata || {};
|
|
4113
|
+
}, [_sseV]);
|
|
4114
|
+
|
|
4013
4115
|
useEffectApp(() => {
|
|
4014
4116
|
const onKey = (e) => {
|
|
4015
4117
|
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
|
|
@@ -4045,6 +4147,7 @@ function App() {
|
|
|
4045
4147
|
<ChartsRow range={range} models={models} kpis={kpis} series={series} t={t} />
|
|
4046
4148
|
<ModelTable models={models} total={kpis.cost} t={t} exampleHover={t.smartHighlight}
|
|
4047
4149
|
onRowClick={(m) => setSelModel({ ...m, share: m.cost / Math.max(kpis.cost, 0.0001) })} />
|
|
4150
|
+
<MetadataSection byMetadata={byMetadata} t={t} />
|
|
4048
4151
|
<ForecastSection t={t} />
|
|
4049
4152
|
<LiveActivity t={t} />
|
|
4050
4153
|
</main>
|