codexmeter 1.0.25 → 1.0.27
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/bin/codexmeter.js +0 -38
- package/dist/assets/DailyUsage-DF-0V3BS.js +1 -0
- package/dist/assets/Models-CDwrE2OB.js +1 -0
- package/dist/assets/Overview-2lHDJwEq.js +2 -0
- package/dist/assets/OverviewVideoExport-C2TRW_hp.js +1 -0
- package/dist/assets/Repos-EbgAq5IT.js +1 -0
- package/dist/assets/Sessions-C2r1YNHt.js +4 -0
- package/dist/assets/animationsDefault-C_urdfl6.js +17 -0
- package/dist/assets/formatters-lxlIPan_.js +1 -0
- package/dist/assets/index-Bxd44dxc.js +10 -0
- package/dist/assets/subcharts-CA6MYRo3.js +1 -0
- package/dist/assets/useDailyStackPresentationTween-BWv0dKwE.js +3 -0
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/server/aggregator.js +1 -0
- package/server/cost-catalog.js +5 -2
- package/server/export-replay.js +2 -2
- package/server/index.js +2 -17
- package/server/ingest.js +50 -176
- package/server/live-state.js +47 -65
- package/server/normalize.js +9 -0
- package/server/pricing-fetch.js +7 -1
- package/server/rollout-reader.js +41 -19
- package/server/rollout-worker-pool.js +3 -71
- package/dist/assets/index-8ykzgYb0.js +0 -31
package/server/live-state.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { CACHE_ASSUMPTIONS } from './cost-catalog.js';
|
|
2
2
|
import { createDayKeyFormatter, splitIntervalByDay } from './day-key.js';
|
|
3
3
|
|
|
4
|
+
function normalizeEffortKey(effort) {
|
|
5
|
+
if (!effort) return 'unknown';
|
|
6
|
+
const k = String(effort).toLowerCase().trim().replace(/-/g, '');
|
|
7
|
+
if (k === 'low') return 'low';
|
|
8
|
+
if (k === 'medium') return 'medium';
|
|
9
|
+
if (k === 'high') return 'high';
|
|
10
|
+
if (k === 'xhigh') return 'xhigh';
|
|
11
|
+
if (k === 'max') return 'max';
|
|
12
|
+
return k || 'unknown';
|
|
13
|
+
}
|
|
14
|
+
|
|
4
15
|
export function createLiveAggregateState(tz) {
|
|
5
16
|
const now = Date.now() / 1000;
|
|
6
17
|
return {
|
|
@@ -39,33 +50,20 @@ export function createLiveAggregateState(tz) {
|
|
|
39
50
|
};
|
|
40
51
|
}
|
|
41
52
|
|
|
42
|
-
export function
|
|
43
|
-
return {
|
|
44
|
-
overview: new Set(),
|
|
45
|
-
repos: { total: new Set(), d7: new Set(), d30: new Set() },
|
|
46
|
-
models: { total: new Set(), d7: new Set(), d30: new Set() },
|
|
47
|
-
families: { total: new Set(), d7: new Set(), d30: new Set() },
|
|
48
|
-
daily: new Set(),
|
|
49
|
-
heatmap: new Set(),
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function applySessionToLiveState(live, session, patch) {
|
|
53
|
+
export function applySessionToLiveState(live, session) {
|
|
54
54
|
const rootId = session.root_thread_id || session.thread_id;
|
|
55
55
|
|
|
56
56
|
for (const rangeKey of ['total', 'd7', 'd30']) {
|
|
57
57
|
if (!overlapsLowerBound(session, live.lowerBounds[rangeKey])) continue;
|
|
58
58
|
|
|
59
59
|
applyOverviewBucket(live.overview[rangeKey], session, rootId);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
applyModelBucket(live.models[rangeKey], live.modelTopKeys[rangeKey], session, patch.models[rangeKey]);
|
|
64
|
-
applyFamilyBucket(live.families[rangeKey], live.familyTopKeys[rangeKey], session, patch.families[rangeKey]);
|
|
60
|
+
applyRepoBucket(live.repos[rangeKey], live.repoTopKeys[rangeKey], session);
|
|
61
|
+
applyModelBucket(live.models[rangeKey], live.modelTopKeys[rangeKey], session);
|
|
62
|
+
applyFamilyBucket(live.families[rangeKey], live.familyTopKeys[rangeKey], session);
|
|
65
63
|
}
|
|
66
64
|
|
|
67
|
-
applyDailyBucket(live.daily, session, live.tz, live.toDayKey
|
|
68
|
-
applyHeatmapBucket(live.heatmap, session, live.tz, live.toDayKey
|
|
65
|
+
applyDailyBucket(live.daily, session, live.tz, live.toDayKey);
|
|
66
|
+
applyHeatmapBucket(live.heatmap, session, live.tz, live.toDayKey);
|
|
69
67
|
}
|
|
70
68
|
|
|
71
69
|
export function buildLiveBootstrap(live) {
|
|
@@ -90,19 +88,6 @@ export function buildLiveSnapshot(live) {
|
|
|
90
88
|
};
|
|
91
89
|
}
|
|
92
90
|
|
|
93
|
-
export function buildLivePatch(live, patch) {
|
|
94
|
-
return {
|
|
95
|
-
overview: Object.fromEntries(
|
|
96
|
-
[...patch.overview].map((rangeKey) => [rangeKey, serializeOverviewBucket(live.overview[rangeKey], live.lowerBounds[rangeKey])])
|
|
97
|
-
),
|
|
98
|
-
repos: serializePatchedTopRanges(live.repos, live.repoTopKeys, patch.repos, serializeRepoSummary),
|
|
99
|
-
models: serializePatchedTopRanges(live.models, live.modelTopKeys, patch.models, serializeModelSummary),
|
|
100
|
-
families: serializePatchedTopRanges(live.families, live.familyTopKeys, patch.families, serializeFamilySummary),
|
|
101
|
-
daily: Object.fromEntries([...patch.daily].map((dayKey) => [dayKey, serializeDailyEntry(live.daily.get(dayKey))])),
|
|
102
|
-
heatmap: Object.fromEntries([...patch.heatmap].map((dayKey) => [dayKey, serializeHeatmapEntry(live.heatmap.get(dayKey))])),
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
91
|
function createTopKeyRanges() {
|
|
107
92
|
return {
|
|
108
93
|
total: [],
|
|
@@ -154,7 +139,7 @@ function applyOverviewBucket(bucket, session, rootId) {
|
|
|
154
139
|
if (session.ended_at && session.ended_at > bucket.latest) bucket.latest = session.ended_at;
|
|
155
140
|
}
|
|
156
141
|
|
|
157
|
-
function applyRepoBucket(repoMap, topKeys, session
|
|
142
|
+
function applyRepoBucket(repoMap, topKeys, session) {
|
|
158
143
|
const key = session.repo_label || 'unknown';
|
|
159
144
|
if (!repoMap.has(key)) {
|
|
160
145
|
repoMap.set(key, {
|
|
@@ -166,6 +151,8 @@ function applyRepoBucket(repoMap, topKeys, session, dirtySet) {
|
|
|
166
151
|
exact_priced: 0,
|
|
167
152
|
heuristic_priced: 0,
|
|
168
153
|
sessions: 0,
|
|
154
|
+
by_model: {},
|
|
155
|
+
by_family: {},
|
|
169
156
|
});
|
|
170
157
|
}
|
|
171
158
|
const repo = repoMap.get(key);
|
|
@@ -177,15 +164,16 @@ function applyRepoBucket(repoMap, topKeys, session, dirtySet) {
|
|
|
177
164
|
if (session.cost_source === 'exact') repo.exact_priced += 1;
|
|
178
165
|
if (session.cost_source === 'heuristic') repo.heuristic_priced += 1;
|
|
179
166
|
}
|
|
167
|
+
addBreakdown(repo.by_model, session.model_name || 'unknown', session);
|
|
168
|
+
addBreakdown(repo.by_family, session.agent_family || 'generic', session);
|
|
180
169
|
|
|
181
170
|
updateTopKeys(repoMap, topKeys, key);
|
|
182
|
-
dirtySet.add(key);
|
|
183
171
|
}
|
|
184
172
|
|
|
185
|
-
function applyModelBucket(modelMap, topKeys, session
|
|
173
|
+
function applyModelBucket(modelMap, topKeys, session) {
|
|
186
174
|
const key = session.model_name || 'unknown';
|
|
187
175
|
if (!modelMap.has(key)) {
|
|
188
|
-
modelMap.set(key, { model_name: key, tokens: 0, cost: 0, cost_known: 0, exact_priced: 0, heuristic_priced: 0, sessions: 0 });
|
|
176
|
+
modelMap.set(key, { model_name: key, tokens: 0, cost: 0, cost_known: 0, exact_priced: 0, heuristic_priced: 0, sessions: 0, by_effort: {} });
|
|
189
177
|
}
|
|
190
178
|
const model = modelMap.get(key);
|
|
191
179
|
model.tokens += session.tokens_used || 0;
|
|
@@ -196,12 +184,24 @@ function applyModelBucket(modelMap, topKeys, session, dirtySet) {
|
|
|
196
184
|
if (session.cost_source === 'exact') model.exact_priced += 1;
|
|
197
185
|
if (session.cost_source === 'heuristic') model.heuristic_priced += 1;
|
|
198
186
|
}
|
|
187
|
+
addBreakdown(model.by_effort, normalizeEffortKey(session.reasoning_effort), session);
|
|
199
188
|
|
|
200
189
|
updateTopKeys(modelMap, topKeys, key);
|
|
201
|
-
dirtySet.add(key);
|
|
202
190
|
}
|
|
203
191
|
|
|
204
|
-
function
|
|
192
|
+
function addBreakdown(target, key, session) {
|
|
193
|
+
if (!target[key]) target[key] = { tokens: 0, cost: 0, sessions: 0, exact_priced: 0, heuristic_priced: 0 };
|
|
194
|
+
const bucket = target[key];
|
|
195
|
+
bucket.tokens += session.tokens_used || 0;
|
|
196
|
+
bucket.sessions += 1;
|
|
197
|
+
if (session.cost !== null) {
|
|
198
|
+
bucket.cost += session.cost;
|
|
199
|
+
if (session.cost_source === 'exact') bucket.exact_priced += 1;
|
|
200
|
+
if (session.cost_source === 'heuristic') bucket.heuristic_priced += 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function applyFamilyBucket(familyMap, topKeys, session) {
|
|
205
205
|
const key = session.agent_family || 'generic';
|
|
206
206
|
if (!familyMap.has(key)) {
|
|
207
207
|
familyMap.set(key, { family: key, tokens: 0, cost: 0, exact_priced: 0, heuristic_priced: 0, sessions: 0 });
|
|
@@ -215,10 +215,9 @@ function applyFamilyBucket(familyMap, topKeys, session, dirtySet) {
|
|
|
215
215
|
if (session.cost_source === 'heuristic') family.heuristic_priced += 1;
|
|
216
216
|
}
|
|
217
217
|
updateTopKeys(familyMap, topKeys, key);
|
|
218
|
-
dirtySet.add(key);
|
|
219
218
|
}
|
|
220
219
|
|
|
221
|
-
function applyDailyBucket(dayMap, session, tz, toDayKey
|
|
220
|
+
function applyDailyBucket(dayMap, session, tz, toDayKey) {
|
|
222
221
|
if (!session.started_at || !session.ended_at) return;
|
|
223
222
|
|
|
224
223
|
const startMs = session.started_at * 1000;
|
|
@@ -226,12 +225,11 @@ function applyDailyBucket(dayMap, session, tz, toDayKey, dirtySet) {
|
|
|
226
225
|
const totalDur = endMs - startMs;
|
|
227
226
|
if (totalDur > 0) {
|
|
228
227
|
if (session.has_usage_by_day) {
|
|
229
|
-
addSessionPresence(dayMap, startMs, endMs, totalDur, tz, session
|
|
230
|
-
addUsageByDay(dayMap, session
|
|
228
|
+
addSessionPresence(dayMap, startMs, endMs, totalDur, tz, session);
|
|
229
|
+
addUsageByDay(dayMap, session);
|
|
231
230
|
} else {
|
|
232
231
|
for (const { dayKey, overlapMs } of splitIntervalByDay(startMs, endMs, tz)) {
|
|
233
232
|
addToDay(dayMap, dayKey, session, overlapMs / totalDur);
|
|
234
|
-
dirtySet.add(dayKey);
|
|
235
233
|
}
|
|
236
234
|
}
|
|
237
235
|
}
|
|
@@ -239,12 +237,11 @@ function applyDailyBucket(dayMap, session, tz, toDayKey, dirtySet) {
|
|
|
239
237
|
if (session.active_by_day) {
|
|
240
238
|
for (const [dayKey, seconds] of Object.entries(session.active_by_day)) {
|
|
241
239
|
addElapsedToDay(dayMap, dayKey, session, seconds || 0);
|
|
242
|
-
dirtySet.add(dayKey);
|
|
243
240
|
}
|
|
244
241
|
}
|
|
245
242
|
}
|
|
246
243
|
|
|
247
|
-
function applyHeatmapBucket(dayMap, session, tz, toDayKey
|
|
244
|
+
function applyHeatmapBucket(dayMap, session, tz, toDayKey) {
|
|
248
245
|
const startMs = session.started_at ? session.started_at * 1000 : null;
|
|
249
246
|
const endMs = (session.ended_at || session.started_at) ? (session.ended_at || session.started_at) * 1000 : null;
|
|
250
247
|
const totalDur = endMs - startMs;
|
|
@@ -255,20 +252,17 @@ function applyHeatmapBucket(dayMap, session, tz, toDayKey, dirtySet) {
|
|
|
255
252
|
const day = ensureHeatmapDay(dayMap, dayKey);
|
|
256
253
|
day.tokens += usageDay.tokens || 0;
|
|
257
254
|
if (usageDay.cost !== null) day.cost += usageDay.cost;
|
|
258
|
-
dirtySet.add(dayKey);
|
|
259
255
|
}
|
|
260
256
|
if (startMs !== null) {
|
|
261
257
|
if (totalDur > 0) {
|
|
262
258
|
for (const { dayKey, overlapMs } of splitIntervalByDay(startMs, endMs, tz)) {
|
|
263
259
|
const day = ensureHeatmapDay(dayMap, dayKey);
|
|
264
260
|
if ((overlapMs / totalDur) > 0.001) day.sessions += 1;
|
|
265
|
-
dirtySet.add(dayKey);
|
|
266
261
|
}
|
|
267
262
|
} else {
|
|
268
263
|
const startDay = toDayKey(startMs);
|
|
269
264
|
const day = ensureHeatmapDay(dayMap, startDay);
|
|
270
265
|
day.sessions += 1;
|
|
271
|
-
dirtySet.add(startDay);
|
|
272
266
|
}
|
|
273
267
|
}
|
|
274
268
|
} else if (startMs !== null) {
|
|
@@ -279,7 +273,6 @@ function applyHeatmapBucket(dayMap, session, tz, toDayKey, dirtySet) {
|
|
|
279
273
|
day.tokens += (session.tokens_used || 0) * fraction;
|
|
280
274
|
if (session.cost !== null) day.cost += session.cost * fraction;
|
|
281
275
|
if (fraction > 0.001) day.sessions += 1;
|
|
282
|
-
dirtySet.add(dayKey);
|
|
283
276
|
}
|
|
284
277
|
} else {
|
|
285
278
|
const dayKey = toDayKey(startMs);
|
|
@@ -287,7 +280,6 @@ function applyHeatmapBucket(dayMap, session, tz, toDayKey, dirtySet) {
|
|
|
287
280
|
day.tokens += session.tokens_used || 0;
|
|
288
281
|
if (session.cost !== null) day.cost += session.cost;
|
|
289
282
|
day.sessions += 1;
|
|
290
|
-
dirtySet.add(dayKey);
|
|
291
283
|
}
|
|
292
284
|
}
|
|
293
285
|
|
|
@@ -295,15 +287,13 @@ function applyHeatmapBucket(dayMap, session, tz, toDayKey, dirtySet) {
|
|
|
295
287
|
for (const [dayKey, seconds] of Object.entries(session.active_by_day)) {
|
|
296
288
|
const day = ensureHeatmapDay(dayMap, dayKey);
|
|
297
289
|
day.elapsed += seconds || 0;
|
|
298
|
-
dirtySet.add(dayKey);
|
|
299
290
|
}
|
|
300
291
|
}
|
|
301
292
|
}
|
|
302
293
|
|
|
303
|
-
function addSessionPresence(dayMap, startMs, endMs, totalDur, tz, session
|
|
294
|
+
function addSessionPresence(dayMap, startMs, endMs, totalDur, tz, session) {
|
|
304
295
|
for (const { dayKey, overlapMs } of splitIntervalByDay(startMs, endMs, tz)) {
|
|
305
296
|
addPresenceToDay(dayMap, dayKey, session, overlapMs / totalDur);
|
|
306
|
-
dirtySet.add(dayKey);
|
|
307
297
|
}
|
|
308
298
|
}
|
|
309
299
|
|
|
@@ -320,7 +310,7 @@ function addPresenceToDay(dayMap, dayKey, session, fraction) {
|
|
|
320
310
|
if (fraction > 0.001) day.by_repo[repoKey].sessions += 1;
|
|
321
311
|
}
|
|
322
312
|
|
|
323
|
-
function addUsageByDay(dayMap, session
|
|
313
|
+
function addUsageByDay(dayMap, session) {
|
|
324
314
|
for (const usageDay of session.usage_by_day || []) {
|
|
325
315
|
const dayKey = usageDay.day;
|
|
326
316
|
const day = ensureDay(dayMap, dayKey);
|
|
@@ -341,7 +331,6 @@ function addUsageByDay(dayMap, session, dirtySet) {
|
|
|
341
331
|
if (!day.by_repo[repoKey]) day.by_repo[repoKey] = { tokens: 0, cost: 0, elapsed_seconds: 0, sessions: 0 };
|
|
342
332
|
day.by_repo[repoKey].tokens += usageDay.tokens || 0;
|
|
343
333
|
if (usageDay.cost !== null) day.by_repo[repoKey].cost += usageDay.cost;
|
|
344
|
-
dirtySet.add(dayKey);
|
|
345
334
|
}
|
|
346
335
|
}
|
|
347
336
|
|
|
@@ -447,16 +436,6 @@ function serializeTopRanges(rangeMaps, topKeyRanges, projector) {
|
|
|
447
436
|
};
|
|
448
437
|
}
|
|
449
438
|
|
|
450
|
-
function serializePatchedTopRanges(rangeMaps, topKeyRanges, dirtyRanges, projector) {
|
|
451
|
-
const out = {};
|
|
452
|
-
for (const rangeKey of ['total', 'd7', 'd30']) {
|
|
453
|
-
if (dirtyRanges[rangeKey].size > 0) {
|
|
454
|
-
out[rangeKey] = serializeTopRange(rangeMaps[rangeKey], topKeyRanges[rangeKey], projector);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
return out;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
439
|
function serializeTopRange(map, topKeys, projector) {
|
|
461
440
|
return topKeys
|
|
462
441
|
.map((key) => map.get(key))
|
|
@@ -549,6 +528,8 @@ function serializeRepoSummary(value) {
|
|
|
549
528
|
exact_priced: value.exact_priced,
|
|
550
529
|
heuristic_priced: value.heuristic_priced,
|
|
551
530
|
sessions: value.sessions,
|
|
531
|
+
by_model: deepRoundClone(value.by_model || {}, ['tokens', 'cost', 'sessions', 'exact_priced', 'heuristic_priced']),
|
|
532
|
+
by_family: deepRoundClone(value.by_family || {}, ['tokens', 'cost', 'sessions', 'exact_priced', 'heuristic_priced']),
|
|
552
533
|
};
|
|
553
534
|
}
|
|
554
535
|
|
|
@@ -561,6 +542,7 @@ function serializeModelSummary(value) {
|
|
|
561
542
|
exact_priced: value.exact_priced,
|
|
562
543
|
heuristic_priced: value.heuristic_priced,
|
|
563
544
|
sessions: value.sessions,
|
|
545
|
+
by_effort: deepRoundClone(value.by_effort || {}, ['tokens', 'cost', 'sessions', 'exact_priced', 'heuristic_priced']),
|
|
564
546
|
};
|
|
565
547
|
}
|
|
566
548
|
|
package/server/normalize.js
CHANGED
|
@@ -124,6 +124,15 @@ function isLauncherSourceKind(sourceKind) {
|
|
|
124
124
|
|
|
125
125
|
const MODEL_ALIASES = {
|
|
126
126
|
'codex-mini-latest': 'o4-mini',
|
|
127
|
+
'gpt 5.6 sol': 'gpt-5.6-sol',
|
|
128
|
+
'gpt-5.6 sol': 'gpt-5.6-sol',
|
|
129
|
+
'openai/gpt-5.6-sol': 'gpt-5.6-sol',
|
|
130
|
+
'gpt 5.6 terra': 'gpt-5.6-terra',
|
|
131
|
+
'gpt-5.6 terra': 'gpt-5.6-terra',
|
|
132
|
+
'openai/gpt-5.6-terra': 'gpt-5.6-terra',
|
|
133
|
+
'gpt 5.6 luna': 'gpt-5.6-luna',
|
|
134
|
+
'gpt-5.6 luna': 'gpt-5.6-luna',
|
|
135
|
+
'openai/gpt-5.6-luna': 'gpt-5.6-luna',
|
|
127
136
|
'gpt 5.5': 'gpt-5.5',
|
|
128
137
|
'gpt-5.5': 'gpt-5.5',
|
|
129
138
|
'gpt 5 mini': 'gpt-5-mini',
|
package/server/pricing-fetch.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// Local pricing catalog for models used in codex-cli/codex app lifetime.
|
|
2
2
|
const FALLBACK = {
|
|
3
|
+
'gpt-5.6-sol': { input: 5.00, output: 30.00, cached_input: 0.50, cache_write: 6.25 },
|
|
4
|
+
'gpt-5.6-terra': { input: 2.50, output: 15.00, cached_input: 0.25, cache_write: 3.125 },
|
|
5
|
+
'gpt-5.6-luna': { input: 1.00, output: 6.00, cached_input: 0.10, cache_write: 1.25 },
|
|
3
6
|
'gpt-5.5': { input: 5.00, output: 30.00, cached_input: 0.50 },
|
|
4
7
|
'gpt-5.4': { input: 2.50, output: 15.00, cached_input: 0.25 },
|
|
5
8
|
'gpt-5-mini': { input: 0.75, output: 4.50, cached_input: 0.075},
|
|
@@ -29,7 +32,10 @@ const PRICING_URL = process.env.CODEXMETER_PRICING_URL || null;
|
|
|
29
32
|
function normalizeEntry(entry) {
|
|
30
33
|
if (!entry || typeof entry !== 'object' || entry.input == null || entry.output == null) return null;
|
|
31
34
|
const cached = entry.cached_input ?? entry.cachedInput ?? entry.input * 0.1;
|
|
32
|
-
|
|
35
|
+
const normalized = { input: entry.input, output: entry.output, cached_input: cached };
|
|
36
|
+
const cacheWrite = entry.cache_write ?? entry.cacheWrite ?? entry.cache_write_input;
|
|
37
|
+
if (cacheWrite != null) normalized.cache_write = cacheWrite;
|
|
38
|
+
return normalized;
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
function normalizePricing(raw) {
|
package/server/rollout-reader.js
CHANGED
|
@@ -65,6 +65,7 @@ export async function enrichFromRollout(rolloutPath, opts = {}) {
|
|
|
65
65
|
const usageByDay = new Map();
|
|
66
66
|
let lastInputTokens = 0;
|
|
67
67
|
let lastCachedInputTokens = 0;
|
|
68
|
+
let lastCacheWriteInputTokens = 0;
|
|
68
69
|
let lastOutputTokens = 0;
|
|
69
70
|
let lastReasoningOutputTokens = 0;
|
|
70
71
|
let lastTotalTokens = 0;
|
|
@@ -113,19 +114,21 @@ export async function enrichFromRollout(rolloutPath, opts = {}) {
|
|
|
113
114
|
if (obj.type === 'event_msg' && obj.payload?.type === 'token_count') {
|
|
114
115
|
const usage = obj.payload.info?.total_token_usage;
|
|
115
116
|
if (usage) {
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
const
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
117
|
+
const normalizedUsage = normalizeUsageTotals(usage);
|
|
118
|
+
const inputTokens = normalizedUsage.input_tokens;
|
|
119
|
+
const cachedInputTokens = normalizedUsage.cached_input_tokens;
|
|
120
|
+
const cacheWriteInputTokens = normalizedUsage.cache_write_input_tokens || 0;
|
|
121
|
+
const outputTokens = normalizedUsage.output_tokens;
|
|
122
|
+
const reasoningOutputTokens = normalizedUsage.reasoning_output_tokens;
|
|
123
|
+
const totalTokens = normalizedUsage.total_tokens;
|
|
124
|
+
|
|
125
|
+
result.usage_total = withCacheWrite({
|
|
123
126
|
input_tokens: inputTokens,
|
|
124
127
|
cached_input_tokens: cachedInputTokens,
|
|
125
128
|
output_tokens: outputTokens,
|
|
126
129
|
reasoning_output_tokens: reasoningOutputTokens,
|
|
127
130
|
total_tokens: totalTokens,
|
|
128
|
-
};
|
|
131
|
+
}, cacheWriteInputTokens);
|
|
129
132
|
|
|
130
133
|
const hasReset =
|
|
131
134
|
hasSeenUsage &&
|
|
@@ -138,30 +141,34 @@ export async function enrichFromRollout(rolloutPath, opts = {}) {
|
|
|
138
141
|
hasSeenUsage = false;
|
|
139
142
|
lastInputTokens = 0;
|
|
140
143
|
lastCachedInputTokens = 0;
|
|
144
|
+
lastCacheWriteInputTokens = 0;
|
|
141
145
|
lastOutputTokens = 0;
|
|
142
146
|
lastReasoningOutputTokens = 0;
|
|
143
147
|
lastTotalTokens = 0;
|
|
144
148
|
}
|
|
145
149
|
|
|
146
150
|
const usageDelta = hasSeenUsage
|
|
147
|
-
? {
|
|
151
|
+
? withCacheWrite({
|
|
148
152
|
input_tokens: Math.max(inputTokens - lastInputTokens, 0),
|
|
149
153
|
cached_input_tokens: Math.max(cachedInputTokens - lastCachedInputTokens, 0),
|
|
154
|
+
cache_write_input_tokens: Math.max(cacheWriteInputTokens - lastCacheWriteInputTokens, 0),
|
|
150
155
|
output_tokens: Math.max(outputTokens - lastOutputTokens, 0),
|
|
151
156
|
reasoning_output_tokens: Math.max(reasoningOutputTokens - lastReasoningOutputTokens, 0),
|
|
152
157
|
total_tokens: Math.max(totalTokens - lastTotalTokens, 0),
|
|
153
|
-
}
|
|
154
|
-
: {
|
|
158
|
+
}, Math.max(cacheWriteInputTokens - lastCacheWriteInputTokens, 0))
|
|
159
|
+
: withCacheWrite({
|
|
155
160
|
input_tokens: inputTokens,
|
|
156
161
|
cached_input_tokens: cachedInputTokens,
|
|
162
|
+
cache_write_input_tokens: cacheWriteInputTokens,
|
|
157
163
|
output_tokens: outputTokens,
|
|
158
164
|
reasoning_output_tokens: reasoningOutputTokens,
|
|
159
165
|
total_tokens: totalTokens,
|
|
160
|
-
};
|
|
166
|
+
}, cacheWriteInputTokens);
|
|
161
167
|
|
|
162
168
|
hasSeenUsage = true;
|
|
163
169
|
lastInputTokens = inputTokens;
|
|
164
170
|
lastCachedInputTokens = cachedInputTokens;
|
|
171
|
+
lastCacheWriteInputTokens = cacheWriteInputTokens;
|
|
165
172
|
lastOutputTokens = outputTokens;
|
|
166
173
|
lastReasoningOutputTokens = reasoningOutputTokens;
|
|
167
174
|
lastTotalTokens = totalTokens;
|
|
@@ -448,36 +455,39 @@ function isTokenCountEventLine(line) {
|
|
|
448
455
|
}
|
|
449
456
|
|
|
450
457
|
function normalizeUsageTotals(usage) {
|
|
451
|
-
return {
|
|
458
|
+
return withCacheWrite({
|
|
452
459
|
input_tokens: usage.input_tokens || 0,
|
|
453
|
-
cached_input_tokens: usage.cached_input_tokens
|
|
460
|
+
cached_input_tokens: usage.cached_input_tokens ?? usage.input_tokens_details?.cached_tokens ?? 0,
|
|
454
461
|
output_tokens: usage.output_tokens || 0,
|
|
455
462
|
reasoning_output_tokens: usage.reasoning_output_tokens || 0,
|
|
456
463
|
total_tokens: usage.total_tokens || 0,
|
|
457
|
-
};
|
|
464
|
+
}, usage.cache_write_input_tokens ?? usage.cache_write_tokens ?? usage.input_tokens_details?.cache_write_tokens ?? 0);
|
|
458
465
|
}
|
|
459
466
|
|
|
460
467
|
function splitUsageTotals(usage) {
|
|
461
468
|
const normalized = normalizeUsageTotals(usage || {});
|
|
462
469
|
const cachedInputTokens = Math.min(normalized.cached_input_tokens, normalized.input_tokens);
|
|
470
|
+
const cacheWriteInputTokens = Math.min(normalized.cache_write_input_tokens || 0, Math.max(normalized.input_tokens - cachedInputTokens, 0));
|
|
463
471
|
return {
|
|
464
|
-
uncached_input_tokens: Math.max(normalized.input_tokens - cachedInputTokens, 0),
|
|
472
|
+
uncached_input_tokens: Math.max(normalized.input_tokens - cachedInputTokens - cacheWriteInputTokens, 0),
|
|
465
473
|
cached_input_tokens: cachedInputTokens,
|
|
474
|
+
cache_write_input_tokens: cacheWriteInputTokens,
|
|
466
475
|
output_tokens: normalized.output_tokens,
|
|
467
476
|
reasoning_output_tokens: normalized.reasoning_output_tokens,
|
|
468
477
|
};
|
|
469
478
|
}
|
|
470
479
|
|
|
471
480
|
function combineUsageTotals(parts, currentTotalTokens = 0, previousTotalTokens = 0) {
|
|
472
|
-
const
|
|
481
|
+
const cacheWriteInputTokens = parts.cache_write_input_tokens || 0;
|
|
482
|
+
const inputTokens = (parts.uncached_input_tokens || 0) + (parts.cached_input_tokens || 0) + cacheWriteInputTokens;
|
|
473
483
|
const outputTokens = parts.output_tokens || 0;
|
|
474
|
-
return {
|
|
484
|
+
return withCacheWrite({
|
|
475
485
|
input_tokens: inputTokens,
|
|
476
486
|
cached_input_tokens: parts.cached_input_tokens || 0,
|
|
477
487
|
output_tokens: outputTokens,
|
|
478
488
|
reasoning_output_tokens: parts.reasoning_output_tokens || 0,
|
|
479
489
|
total_tokens: Math.max(inputTokens + outputTokens, Math.max(currentTotalTokens - previousTotalTokens, 0)),
|
|
480
|
-
};
|
|
490
|
+
}, cacheWriteInputTokens);
|
|
481
491
|
}
|
|
482
492
|
|
|
483
493
|
export function subtractUsageTotals(current, previous) {
|
|
@@ -486,6 +496,7 @@ export function subtractUsageTotals(current, previous) {
|
|
|
486
496
|
return combineUsageTotals({
|
|
487
497
|
uncached_input_tokens: Math.max(currentParts.uncached_input_tokens - previousParts.uncached_input_tokens, 0),
|
|
488
498
|
cached_input_tokens: Math.max(currentParts.cached_input_tokens - previousParts.cached_input_tokens, 0),
|
|
499
|
+
cache_write_input_tokens: Math.max(currentParts.cache_write_input_tokens - previousParts.cache_write_input_tokens, 0),
|
|
489
500
|
output_tokens: Math.max(currentParts.output_tokens - previousParts.output_tokens, 0),
|
|
490
501
|
reasoning_output_tokens: Math.max(currentParts.reasoning_output_tokens - previousParts.reasoning_output_tokens, 0),
|
|
491
502
|
}, current?.total_tokens || 0, previous?.total_tokens || 0);
|
|
@@ -495,6 +506,7 @@ export function hasUsageTotals(usage) {
|
|
|
495
506
|
return !!usage && (
|
|
496
507
|
(usage.input_tokens || 0) > 0 ||
|
|
497
508
|
(usage.cached_input_tokens || 0) > 0 ||
|
|
509
|
+
(usage.cache_write_input_tokens || 0) > 0 ||
|
|
498
510
|
(usage.output_tokens || 0) > 0 ||
|
|
499
511
|
(usage.reasoning_output_tokens || 0) > 0 ||
|
|
500
512
|
(usage.total_tokens || 0) > 0
|
|
@@ -505,6 +517,7 @@ function hasUsage(usage) {
|
|
|
505
517
|
return !!usage && (
|
|
506
518
|
usage.input_tokens > 0 ||
|
|
507
519
|
usage.cached_input_tokens > 0 ||
|
|
520
|
+
(usage.cache_write_input_tokens || 0) > 0 ||
|
|
508
521
|
usage.output_tokens > 0
|
|
509
522
|
);
|
|
510
523
|
}
|
|
@@ -521,5 +534,14 @@ function mergeUsageTotals(target, dayKey, usage) {
|
|
|
521
534
|
}
|
|
522
535
|
previous.input_tokens += usage.input_tokens || 0;
|
|
523
536
|
previous.cached_input_tokens += usage.cached_input_tokens || 0;
|
|
537
|
+
if (usage.cache_write_input_tokens) {
|
|
538
|
+
previous.cache_write_input_tokens = (previous.cache_write_input_tokens || 0) + usage.cache_write_input_tokens;
|
|
539
|
+
}
|
|
524
540
|
previous.output_tokens += usage.output_tokens || 0;
|
|
525
541
|
}
|
|
542
|
+
|
|
543
|
+
function withCacheWrite(usage, cacheWriteInputTokens) {
|
|
544
|
+
const value = Math.max(0, cacheWriteInputTokens || 0);
|
|
545
|
+
if (value > 0) usage.cache_write_input_tokens = value;
|
|
546
|
+
return usage;
|
|
547
|
+
}
|
|
@@ -2,11 +2,8 @@ import os from 'os';
|
|
|
2
2
|
import { Worker } from 'worker_threads';
|
|
3
3
|
|
|
4
4
|
export function createRolloutWorkerPool(opts = {}) {
|
|
5
|
-
const size = normalizePoolSize(
|
|
5
|
+
const size = normalizePoolSize();
|
|
6
6
|
const readerOptions = opts.readerOptions || {};
|
|
7
|
-
if (size <= 1) {
|
|
8
|
-
return createInlinePool({ readerOptions });
|
|
9
|
-
}
|
|
10
7
|
|
|
11
8
|
const workers = new Set();
|
|
12
9
|
const idleWorkers = [];
|
|
@@ -105,10 +102,6 @@ export function createRolloutWorkerPool(opts = {}) {
|
|
|
105
102
|
});
|
|
106
103
|
}
|
|
107
104
|
|
|
108
|
-
async function mapRollouts(rolloutPaths, timezone) {
|
|
109
|
-
return Promise.all(rolloutPaths.map((rolloutPath) => runTask(rolloutPath, timezone)));
|
|
110
|
-
}
|
|
111
|
-
|
|
112
105
|
async function mapRolloutsInChunks(rolloutPaths, timezone, { chunkSize = 100, onChunk } = {}) {
|
|
113
106
|
const results = new Array(rolloutPaths.length);
|
|
114
107
|
const completed = new Array(rolloutPaths.length).fill(false);
|
|
@@ -188,71 +181,10 @@ export function createRolloutWorkerPool(opts = {}) {
|
|
|
188
181
|
idleWorkers.length = 0;
|
|
189
182
|
}
|
|
190
183
|
|
|
191
|
-
return {
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function createInlinePool({ readerOptions = {} } = {}) {
|
|
195
|
-
return {
|
|
196
|
-
size: 1,
|
|
197
|
-
async mapRollouts(rolloutPaths, timezone) {
|
|
198
|
-
const { enrichFromRollout } = await import('./rollout-reader.js');
|
|
199
|
-
return Promise.all(
|
|
200
|
-
rolloutPaths.map(async (rolloutPath) => {
|
|
201
|
-
try {
|
|
202
|
-
const data = await enrichFromRollout(rolloutPath, { timezone, ...readerOptions });
|
|
203
|
-
return { ok: true, data, error: null };
|
|
204
|
-
} catch (error) {
|
|
205
|
-
return {
|
|
206
|
-
ok: false,
|
|
207
|
-
data: null,
|
|
208
|
-
error: error instanceof Error ? error.message : String(error),
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
})
|
|
212
|
-
);
|
|
213
|
-
},
|
|
214
|
-
async mapRolloutsInChunks(rolloutPaths, timezone, { chunkSize = 100, onChunk } = {}) {
|
|
215
|
-
const { enrichFromRollout } = await import('./rollout-reader.js');
|
|
216
|
-
const results = [];
|
|
217
|
-
const completed = [];
|
|
218
|
-
const safeChunkSize = Math.max(1, Number(chunkSize) || 1);
|
|
219
|
-
|
|
220
|
-
for (let index = 0; index < rolloutPaths.length; index += 1) {
|
|
221
|
-
try {
|
|
222
|
-
const data = await enrichFromRollout(rolloutPaths[index], { timezone, ...readerOptions });
|
|
223
|
-
const result = { ok: true, data, error: null };
|
|
224
|
-
results[index] = result;
|
|
225
|
-
completed.push({ index, result });
|
|
226
|
-
} catch (error) {
|
|
227
|
-
const result = {
|
|
228
|
-
ok: false,
|
|
229
|
-
data: null,
|
|
230
|
-
error: error instanceof Error ? error.message : String(error),
|
|
231
|
-
};
|
|
232
|
-
results[index] = result;
|
|
233
|
-
completed.push({ index, result });
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (typeof onChunk === 'function' && completed.length >= safeChunkSize) {
|
|
237
|
-
await onChunk(completed.splice(0, completed.length));
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (typeof onChunk === 'function' && completed.length) {
|
|
242
|
-
await onChunk(completed.splice(0, completed.length));
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return results;
|
|
246
|
-
},
|
|
247
|
-
async close() {},
|
|
248
|
-
};
|
|
184
|
+
return { mapRolloutsInChunks, close, size };
|
|
249
185
|
}
|
|
250
186
|
|
|
251
|
-
function normalizePoolSize(
|
|
252
|
-
if (size != null) {
|
|
253
|
-
return Math.max(1, Number(size) || 1);
|
|
254
|
-
}
|
|
255
|
-
|
|
187
|
+
function normalizePoolSize() {
|
|
256
188
|
const cpuCount = os.cpus()?.length || 4;
|
|
257
189
|
return Math.max(2, Math.min(cpuCount - 1, 8));
|
|
258
190
|
}
|