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.
@@ -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 createEmptyLivePatch() {
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
- patch.overview.add(rangeKey);
61
-
62
- applyRepoBucket(live.repos[rangeKey], live.repoTopKeys[rangeKey], session, patch.repos[rangeKey]);
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, patch.daily);
68
- applyHeatmapBucket(live.heatmap, session, live.tz, live.toDayKey, patch.heatmap);
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, dirtySet) {
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, dirtySet) {
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 applyFamilyBucket(familyMap, topKeys, session, dirtySet) {
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, dirtySet) {
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, dirtySet);
230
- addUsageByDay(dayMap, session, dirtySet);
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, dirtySet) {
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, dirtySet) {
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, dirtySet) {
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
 
@@ -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',
@@ -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
- return { input: entry.input, output: entry.output, cached_input: cached };
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) {
@@ -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 inputTokens = usage.input_tokens || 0;
117
- const cachedInputTokens = usage.cached_input_tokens || 0;
118
- const outputTokens = usage.output_tokens || 0;
119
- const reasoningOutputTokens = usage.reasoning_output_tokens || 0;
120
- const totalTokens = usage.total_tokens || 0;
121
-
122
- result.usage_total = {
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 || 0,
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 inputTokens = (parts.uncached_input_tokens || 0) + (parts.cached_input_tokens || 0);
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(opts.size);
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 { mapRollouts, mapRolloutsInChunks, close, size };
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(size) {
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
  }