codexmeter 1.0.26 → 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.
@@ -8,6 +8,7 @@ function normalizeEffortKey(effort) {
8
8
  if (k === 'medium') return 'medium';
9
9
  if (k === 'high') return 'high';
10
10
  if (k === 'xhigh') return 'xhigh';
11
+ if (k === 'max') return 'max';
11
12
  return k || 'unknown';
12
13
  }
13
14
 
@@ -49,33 +50,20 @@ export function createLiveAggregateState(tz) {
49
50
  };
50
51
  }
51
52
 
52
- export function createEmptyLivePatch() {
53
- return {
54
- overview: new Set(),
55
- repos: { total: new Set(), d7: new Set(), d30: new Set() },
56
- models: { total: new Set(), d7: new Set(), d30: new Set() },
57
- families: { total: new Set(), d7: new Set(), d30: new Set() },
58
- daily: new Set(),
59
- heatmap: new Set(),
60
- };
61
- }
62
-
63
- export function applySessionToLiveState(live, session, patch) {
53
+ export function applySessionToLiveState(live, session) {
64
54
  const rootId = session.root_thread_id || session.thread_id;
65
55
 
66
56
  for (const rangeKey of ['total', 'd7', 'd30']) {
67
57
  if (!overlapsLowerBound(session, live.lowerBounds[rangeKey])) continue;
68
58
 
69
59
  applyOverviewBucket(live.overview[rangeKey], session, rootId);
70
- patch.overview.add(rangeKey);
71
-
72
- applyRepoBucket(live.repos[rangeKey], live.repoTopKeys[rangeKey], session, patch.repos[rangeKey]);
73
- applyModelBucket(live.models[rangeKey], live.modelTopKeys[rangeKey], session, patch.models[rangeKey]);
74
- 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);
75
63
  }
76
64
 
77
- applyDailyBucket(live.daily, session, live.tz, live.toDayKey, patch.daily);
78
- 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);
79
67
  }
80
68
 
81
69
  export function buildLiveBootstrap(live) {
@@ -100,19 +88,6 @@ export function buildLiveSnapshot(live) {
100
88
  };
101
89
  }
102
90
 
103
- export function buildLivePatch(live, patch) {
104
- return {
105
- overview: Object.fromEntries(
106
- [...patch.overview].map((rangeKey) => [rangeKey, serializeOverviewBucket(live.overview[rangeKey], live.lowerBounds[rangeKey])])
107
- ),
108
- repos: serializePatchedTopRanges(live.repos, live.repoTopKeys, patch.repos, serializeRepoSummary),
109
- models: serializePatchedTopRanges(live.models, live.modelTopKeys, patch.models, serializeModelSummary),
110
- families: serializePatchedTopRanges(live.families, live.familyTopKeys, patch.families, serializeFamilySummary),
111
- daily: Object.fromEntries([...patch.daily].map((dayKey) => [dayKey, serializeDailyEntry(live.daily.get(dayKey))])),
112
- heatmap: Object.fromEntries([...patch.heatmap].map((dayKey) => [dayKey, serializeHeatmapEntry(live.heatmap.get(dayKey))])),
113
- };
114
- }
115
-
116
91
  function createTopKeyRanges() {
117
92
  return {
118
93
  total: [],
@@ -164,7 +139,7 @@ function applyOverviewBucket(bucket, session, rootId) {
164
139
  if (session.ended_at && session.ended_at > bucket.latest) bucket.latest = session.ended_at;
165
140
  }
166
141
 
167
- function applyRepoBucket(repoMap, topKeys, session, dirtySet) {
142
+ function applyRepoBucket(repoMap, topKeys, session) {
168
143
  const key = session.repo_label || 'unknown';
169
144
  if (!repoMap.has(key)) {
170
145
  repoMap.set(key, {
@@ -193,10 +168,9 @@ function applyRepoBucket(repoMap, topKeys, session, dirtySet) {
193
168
  addBreakdown(repo.by_family, session.agent_family || 'generic', session);
194
169
 
195
170
  updateTopKeys(repoMap, topKeys, key);
196
- dirtySet.add(key);
197
171
  }
198
172
 
199
- function applyModelBucket(modelMap, topKeys, session, dirtySet) {
173
+ function applyModelBucket(modelMap, topKeys, session) {
200
174
  const key = session.model_name || 'unknown';
201
175
  if (!modelMap.has(key)) {
202
176
  modelMap.set(key, { model_name: key, tokens: 0, cost: 0, cost_known: 0, exact_priced: 0, heuristic_priced: 0, sessions: 0, by_effort: {} });
@@ -213,7 +187,6 @@ function applyModelBucket(modelMap, topKeys, session, dirtySet) {
213
187
  addBreakdown(model.by_effort, normalizeEffortKey(session.reasoning_effort), session);
214
188
 
215
189
  updateTopKeys(modelMap, topKeys, key);
216
- dirtySet.add(key);
217
190
  }
218
191
 
219
192
  function addBreakdown(target, key, session) {
@@ -228,7 +201,7 @@ function addBreakdown(target, key, session) {
228
201
  }
229
202
  }
230
203
 
231
- function applyFamilyBucket(familyMap, topKeys, session, dirtySet) {
204
+ function applyFamilyBucket(familyMap, topKeys, session) {
232
205
  const key = session.agent_family || 'generic';
233
206
  if (!familyMap.has(key)) {
234
207
  familyMap.set(key, { family: key, tokens: 0, cost: 0, exact_priced: 0, heuristic_priced: 0, sessions: 0 });
@@ -242,10 +215,9 @@ function applyFamilyBucket(familyMap, topKeys, session, dirtySet) {
242
215
  if (session.cost_source === 'heuristic') family.heuristic_priced += 1;
243
216
  }
244
217
  updateTopKeys(familyMap, topKeys, key);
245
- dirtySet.add(key);
246
218
  }
247
219
 
248
- function applyDailyBucket(dayMap, session, tz, toDayKey, dirtySet) {
220
+ function applyDailyBucket(dayMap, session, tz, toDayKey) {
249
221
  if (!session.started_at || !session.ended_at) return;
250
222
 
251
223
  const startMs = session.started_at * 1000;
@@ -253,12 +225,11 @@ function applyDailyBucket(dayMap, session, tz, toDayKey, dirtySet) {
253
225
  const totalDur = endMs - startMs;
254
226
  if (totalDur > 0) {
255
227
  if (session.has_usage_by_day) {
256
- addSessionPresence(dayMap, startMs, endMs, totalDur, tz, session, dirtySet);
257
- addUsageByDay(dayMap, session, dirtySet);
228
+ addSessionPresence(dayMap, startMs, endMs, totalDur, tz, session);
229
+ addUsageByDay(dayMap, session);
258
230
  } else {
259
231
  for (const { dayKey, overlapMs } of splitIntervalByDay(startMs, endMs, tz)) {
260
232
  addToDay(dayMap, dayKey, session, overlapMs / totalDur);
261
- dirtySet.add(dayKey);
262
233
  }
263
234
  }
264
235
  }
@@ -266,12 +237,11 @@ function applyDailyBucket(dayMap, session, tz, toDayKey, dirtySet) {
266
237
  if (session.active_by_day) {
267
238
  for (const [dayKey, seconds] of Object.entries(session.active_by_day)) {
268
239
  addElapsedToDay(dayMap, dayKey, session, seconds || 0);
269
- dirtySet.add(dayKey);
270
240
  }
271
241
  }
272
242
  }
273
243
 
274
- function applyHeatmapBucket(dayMap, session, tz, toDayKey, dirtySet) {
244
+ function applyHeatmapBucket(dayMap, session, tz, toDayKey) {
275
245
  const startMs = session.started_at ? session.started_at * 1000 : null;
276
246
  const endMs = (session.ended_at || session.started_at) ? (session.ended_at || session.started_at) * 1000 : null;
277
247
  const totalDur = endMs - startMs;
@@ -282,20 +252,17 @@ function applyHeatmapBucket(dayMap, session, tz, toDayKey, dirtySet) {
282
252
  const day = ensureHeatmapDay(dayMap, dayKey);
283
253
  day.tokens += usageDay.tokens || 0;
284
254
  if (usageDay.cost !== null) day.cost += usageDay.cost;
285
- dirtySet.add(dayKey);
286
255
  }
287
256
  if (startMs !== null) {
288
257
  if (totalDur > 0) {
289
258
  for (const { dayKey, overlapMs } of splitIntervalByDay(startMs, endMs, tz)) {
290
259
  const day = ensureHeatmapDay(dayMap, dayKey);
291
260
  if ((overlapMs / totalDur) > 0.001) day.sessions += 1;
292
- dirtySet.add(dayKey);
293
261
  }
294
262
  } else {
295
263
  const startDay = toDayKey(startMs);
296
264
  const day = ensureHeatmapDay(dayMap, startDay);
297
265
  day.sessions += 1;
298
- dirtySet.add(startDay);
299
266
  }
300
267
  }
301
268
  } else if (startMs !== null) {
@@ -306,7 +273,6 @@ function applyHeatmapBucket(dayMap, session, tz, toDayKey, dirtySet) {
306
273
  day.tokens += (session.tokens_used || 0) * fraction;
307
274
  if (session.cost !== null) day.cost += session.cost * fraction;
308
275
  if (fraction > 0.001) day.sessions += 1;
309
- dirtySet.add(dayKey);
310
276
  }
311
277
  } else {
312
278
  const dayKey = toDayKey(startMs);
@@ -314,7 +280,6 @@ function applyHeatmapBucket(dayMap, session, tz, toDayKey, dirtySet) {
314
280
  day.tokens += session.tokens_used || 0;
315
281
  if (session.cost !== null) day.cost += session.cost;
316
282
  day.sessions += 1;
317
- dirtySet.add(dayKey);
318
283
  }
319
284
  }
320
285
 
@@ -322,15 +287,13 @@ function applyHeatmapBucket(dayMap, session, tz, toDayKey, dirtySet) {
322
287
  for (const [dayKey, seconds] of Object.entries(session.active_by_day)) {
323
288
  const day = ensureHeatmapDay(dayMap, dayKey);
324
289
  day.elapsed += seconds || 0;
325
- dirtySet.add(dayKey);
326
290
  }
327
291
  }
328
292
  }
329
293
 
330
- function addSessionPresence(dayMap, startMs, endMs, totalDur, tz, session, dirtySet) {
294
+ function addSessionPresence(dayMap, startMs, endMs, totalDur, tz, session) {
331
295
  for (const { dayKey, overlapMs } of splitIntervalByDay(startMs, endMs, tz)) {
332
296
  addPresenceToDay(dayMap, dayKey, session, overlapMs / totalDur);
333
- dirtySet.add(dayKey);
334
297
  }
335
298
  }
336
299
 
@@ -347,7 +310,7 @@ function addPresenceToDay(dayMap, dayKey, session, fraction) {
347
310
  if (fraction > 0.001) day.by_repo[repoKey].sessions += 1;
348
311
  }
349
312
 
350
- function addUsageByDay(dayMap, session, dirtySet) {
313
+ function addUsageByDay(dayMap, session) {
351
314
  for (const usageDay of session.usage_by_day || []) {
352
315
  const dayKey = usageDay.day;
353
316
  const day = ensureDay(dayMap, dayKey);
@@ -368,7 +331,6 @@ function addUsageByDay(dayMap, session, dirtySet) {
368
331
  if (!day.by_repo[repoKey]) day.by_repo[repoKey] = { tokens: 0, cost: 0, elapsed_seconds: 0, sessions: 0 };
369
332
  day.by_repo[repoKey].tokens += usageDay.tokens || 0;
370
333
  if (usageDay.cost !== null) day.by_repo[repoKey].cost += usageDay.cost;
371
- dirtySet.add(dayKey);
372
334
  }
373
335
  }
374
336
 
@@ -474,16 +436,6 @@ function serializeTopRanges(rangeMaps, topKeyRanges, projector) {
474
436
  };
475
437
  }
476
438
 
477
- function serializePatchedTopRanges(rangeMaps, topKeyRanges, dirtyRanges, projector) {
478
- const out = {};
479
- for (const rangeKey of ['total', 'd7', 'd30']) {
480
- if (dirtyRanges[rangeKey].size > 0) {
481
- out[rangeKey] = serializeTopRange(rangeMaps[rangeKey], topKeyRanges[rangeKey], projector);
482
- }
483
- }
484
- return out;
485
- }
486
-
487
439
  function serializeTopRange(map, topKeys, projector) {
488
440
  return topKeys
489
441
  .map((key) => map.get(key))
@@ -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
  }