@praeviso/code-env-switch 0.1.4 → 0.1.6

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,11 +8,15 @@ exports.getClaudeSessionsPath = getClaudeSessionsPath;
8
8
  exports.formatTokenCount = formatTokenCount;
9
9
  exports.buildUsageTotals = buildUsageTotals;
10
10
  exports.readUsageTotalsIndex = readUsageTotalsIndex;
11
+ exports.readUsageCostIndex = readUsageCostIndex;
12
+ exports.readUsageSessionCost = readUsageSessionCost;
11
13
  exports.resolveUsageTotalsForProfile = resolveUsageTotalsForProfile;
14
+ exports.resolveUsageCostForProfile = resolveUsageCostForProfile;
12
15
  exports.syncUsageFromStatuslineInput = syncUsageFromStatuslineInput;
13
16
  exports.logProfileUse = logProfileUse;
14
17
  exports.logSessionBinding = logSessionBinding;
15
18
  exports.readSessionBindingIndex = readSessionBindingIndex;
19
+ exports.clearUsageHistory = clearUsageHistory;
16
20
  exports.readUsageRecords = readUsageRecords;
17
21
  exports.syncUsageFromSessions = syncUsageFromSessions;
18
22
  /**
@@ -23,6 +27,24 @@ const path = require("path");
23
27
  const os = require("os");
24
28
  const utils_1 = require("../shell/utils");
25
29
  const type_1 = require("../profile/type");
30
+ const debug_1 = require("../statusline/debug");
31
+ const pricing_1 = require("./pricing");
32
+ function resolveProfileForRecord(config, type, record) {
33
+ if (record.profileKey && config.profiles && config.profiles[record.profileKey]) {
34
+ return config.profiles[record.profileKey];
35
+ }
36
+ if (record.profileName && config.profiles) {
37
+ const matches = Object.entries(config.profiles).find(([key, entry]) => {
38
+ const displayName = (0, type_1.getProfileDisplayName)(key, entry, type || undefined);
39
+ return (displayName === record.profileName ||
40
+ entry.name === record.profileName ||
41
+ key === record.profileName);
42
+ });
43
+ if (matches)
44
+ return matches[1];
45
+ }
46
+ return null;
47
+ }
26
48
  function resolveDefaultConfigDir(configPath) {
27
49
  if (configPath)
28
50
  return path.dirname(configPath);
@@ -72,42 +94,135 @@ function formatTokenCount(value) {
72
94
  return `${(value / 1000000).toFixed(2)}M`;
73
95
  return `${(value / 1000000000).toFixed(2)}B`;
74
96
  }
75
- function buildUsageTotals(records) {
76
- const byKey = new Map();
77
- const byName = new Map();
97
+ function createUsageTotals() {
98
+ return {
99
+ today: 0,
100
+ total: 0,
101
+ todayInput: 0,
102
+ totalInput: 0,
103
+ todayOutput: 0,
104
+ totalOutput: 0,
105
+ todayCacheRead: 0,
106
+ totalCacheRead: 0,
107
+ todayCacheWrite: 0,
108
+ totalCacheWrite: 0,
109
+ };
110
+ }
111
+ function createUsageCostTotals() {
112
+ return { today: 0, total: 0, todayTokens: 0, totalTokens: 0 };
113
+ }
114
+ function toUsageNumber(value) {
115
+ const num = Number(value !== null && value !== void 0 ? value : 0);
116
+ return Number.isFinite(num) ? num : 0;
117
+ }
118
+ function getTodayWindow() {
78
119
  const todayStart = new Date();
79
120
  todayStart.setHours(0, 0, 0, 0);
80
- const todayStartMs = todayStart.getTime();
121
+ const startMs = todayStart.getTime();
81
122
  const tomorrowStart = new Date(todayStart);
82
123
  tomorrowStart.setDate(todayStart.getDate() + 1);
83
- const tomorrowStartMs = tomorrowStart.getTime();
124
+ return { startMs, endMs: tomorrowStart.getTime() };
125
+ }
126
+ function isTimestampInWindow(ts, startMs, endMs) {
127
+ if (!ts)
128
+ return false;
129
+ const time = new Date(ts).getTime();
130
+ if (Number.isNaN(time))
131
+ return false;
132
+ return time >= startMs && time < endMs;
133
+ }
134
+ function buildUsageTotals(records) {
135
+ var _a;
136
+ const byKey = new Map();
137
+ const byName = new Map();
138
+ const { startMs, endMs } = getTodayWindow();
84
139
  const isToday = (ts) => {
85
- if (!ts)
86
- return false;
87
- const time = new Date(ts).getTime();
88
- if (Number.isNaN(time))
89
- return false;
90
- return time >= todayStartMs && time < tomorrowStartMs;
140
+ return isTimestampInWindow(ts, startMs, endMs);
91
141
  };
92
- const addTotals = (map, key, amount, ts) => {
142
+ const addTotals = (map, key, amounts, ts) => {
93
143
  if (!key)
94
144
  return;
95
- const current = map.get(key) || { today: 0, total: 0 };
96
- current.total += amount;
97
- if (isToday(ts))
98
- current.today += amount;
145
+ const current = map.get(key) || createUsageTotals();
146
+ current.total += amounts.total;
147
+ current.totalInput += amounts.input;
148
+ current.totalOutput += amounts.output;
149
+ current.totalCacheRead += amounts.cacheRead;
150
+ current.totalCacheWrite += amounts.cacheWrite;
151
+ if (isToday(ts)) {
152
+ current.today += amounts.total;
153
+ current.todayInput += amounts.input;
154
+ current.todayOutput += amounts.output;
155
+ current.todayCacheRead += amounts.cacheRead;
156
+ current.todayCacheWrite += amounts.cacheWrite;
157
+ }
99
158
  map.set(key, current);
100
159
  };
101
160
  for (const record of records) {
102
161
  const type = normalizeUsageType(record.type) || "";
103
- const total = Number(record.totalTokens || 0);
162
+ const input = toUsageNumber(record.inputTokens);
163
+ const output = toUsageNumber(record.outputTokens);
164
+ const cacheRead = toUsageNumber(record.cacheReadTokens);
165
+ const cacheWrite = toUsageNumber(record.cacheWriteTokens);
166
+ const computedTotal = input + output + cacheRead + cacheWrite;
167
+ const rawTotal = Number((_a = record.totalTokens) !== null && _a !== void 0 ? _a : computedTotal);
168
+ const total = Number.isFinite(rawTotal)
169
+ ? Math.max(rawTotal, computedTotal)
170
+ : computedTotal;
104
171
  if (!Number.isFinite(total))
105
172
  continue;
106
173
  if (record.profileKey) {
107
- addTotals(byKey, `${type}||${record.profileKey}`, total, record.ts);
174
+ addTotals(byKey, `${type}||${record.profileKey}`, { total, input, output, cacheRead, cacheWrite }, record.ts);
108
175
  }
109
176
  if (record.profileName) {
110
- addTotals(byName, `${type}||${record.profileName}`, total, record.ts);
177
+ addTotals(byName, `${type}||${record.profileName}`, { total, input, output, cacheRead, cacheWrite }, record.ts);
178
+ }
179
+ }
180
+ return { byKey, byName };
181
+ }
182
+ function buildUsageCostIndex(records, config) {
183
+ const byKey = new Map();
184
+ const byName = new Map();
185
+ const { startMs, endMs } = getTodayWindow();
186
+ const addCost = (map, key, cost, tokens, ts) => {
187
+ if (!key)
188
+ return;
189
+ const current = map.get(key) || createUsageCostTotals();
190
+ current.total += cost;
191
+ current.totalTokens += tokens;
192
+ if (isTimestampInWindow(ts, startMs, endMs)) {
193
+ current.today += cost;
194
+ current.todayTokens += tokens;
195
+ }
196
+ map.set(key, current);
197
+ };
198
+ for (const record of records) {
199
+ const model = normalizeModelValue(record.model);
200
+ if (!model)
201
+ continue;
202
+ const type = normalizeUsageType(record.type) || "";
203
+ const profile = record.profileKey && config.profiles ? config.profiles[record.profileKey] : null;
204
+ const pricing = (0, pricing_1.resolvePricingForProfile)(config, profile || null, model);
205
+ if (!pricing)
206
+ continue;
207
+ const cost = (0, pricing_1.calculateUsageCost)({
208
+ totalTokens: record.totalTokens,
209
+ inputTokens: record.inputTokens,
210
+ outputTokens: record.outputTokens,
211
+ cacheReadTokens: record.cacheReadTokens,
212
+ cacheWriteTokens: record.cacheWriteTokens,
213
+ }, pricing);
214
+ if (cost === null || !Number.isFinite(cost))
215
+ continue;
216
+ const billedTokens = toUsageNumber(record.inputTokens) +
217
+ toUsageNumber(record.outputTokens) +
218
+ toUsageNumber(record.cacheReadTokens) +
219
+ toUsageNumber(record.cacheWriteTokens);
220
+ const billedTotal = billedTokens > 0 ? billedTokens : toUsageNumber(record.totalTokens);
221
+ if (record.profileKey) {
222
+ addCost(byKey, `${type}||${record.profileKey}`, cost, billedTotal, record.ts);
223
+ }
224
+ if (record.profileName) {
225
+ addCost(byName, `${type}||${record.profileName}`, cost, billedTotal, record.ts);
111
226
  }
112
227
  }
113
228
  return { byKey, byName };
@@ -121,6 +236,12 @@ function normalizeUsageType(type) {
121
236
  const trimmed = String(type).trim();
122
237
  return trimmed ? trimmed : null;
123
238
  }
239
+ function normalizeModelValue(value) {
240
+ if (typeof value !== "string")
241
+ return null;
242
+ const trimmed = value.trim();
243
+ return trimmed ? trimmed : null;
244
+ }
124
245
  function buildSessionKey(type, sessionId) {
125
246
  const normalized = normalizeUsageType(type || "");
126
247
  return normalized ? `${normalized}::${sessionId}` : sessionId;
@@ -153,6 +274,66 @@ function readUsageTotalsIndex(config, configPath, syncUsage) {
153
274
  return null;
154
275
  return buildUsageTotals(records);
155
276
  }
277
+ function readUsageCostIndex(config, configPath, syncUsage) {
278
+ const usagePath = getUsagePath(config, configPath);
279
+ if (!usagePath)
280
+ return null;
281
+ if (syncUsage) {
282
+ syncUsageFromSessions(config, configPath, usagePath);
283
+ }
284
+ const records = readUsageRecords(usagePath);
285
+ if (records.length === 0)
286
+ return null;
287
+ const costs = buildUsageCostIndex(records, config);
288
+ if (costs.byKey.size === 0 && costs.byName.size === 0)
289
+ return null;
290
+ return costs;
291
+ }
292
+ function readUsageSessionCost(config, configPath, type, sessionId, syncUsage) {
293
+ if (!sessionId)
294
+ return null;
295
+ const usagePath = getUsagePath(config, configPath);
296
+ if (!usagePath)
297
+ return null;
298
+ if (syncUsage) {
299
+ syncUsageFromSessions(config, configPath, usagePath);
300
+ }
301
+ const records = readUsageRecords(usagePath);
302
+ if (records.length === 0)
303
+ return null;
304
+ const normalizedType = normalizeUsageType(type);
305
+ let total = 0;
306
+ let hasCost = false;
307
+ for (const record of records) {
308
+ if (!record.sessionId)
309
+ continue;
310
+ if (record.sessionId !== sessionId)
311
+ continue;
312
+ if (normalizedType &&
313
+ normalizeUsageType(record.type) !== normalizedType) {
314
+ continue;
315
+ }
316
+ const model = normalizeModelValue(record.model);
317
+ if (!model)
318
+ continue;
319
+ const profile = resolveProfileForRecord(config, normalizedType, record);
320
+ const pricing = (0, pricing_1.resolvePricingForProfile)(config, profile, model);
321
+ if (!pricing)
322
+ continue;
323
+ const cost = (0, pricing_1.calculateUsageCost)({
324
+ totalTokens: record.totalTokens,
325
+ inputTokens: record.inputTokens,
326
+ outputTokens: record.outputTokens,
327
+ cacheReadTokens: record.cacheReadTokens,
328
+ cacheWriteTokens: record.cacheWriteTokens,
329
+ }, pricing);
330
+ if (cost === null || !Number.isFinite(cost))
331
+ continue;
332
+ total += cost;
333
+ hasCost = true;
334
+ }
335
+ return hasCost ? total : null;
336
+ }
156
337
  function resolveUsageTotalsForProfile(totals, type, profileKey, profileName) {
157
338
  const keyLookup = buildUsageLookupKey(type, profileKey);
158
339
  const nameLookup = buildUsageLookupKey(type, profileName);
@@ -160,14 +341,24 @@ function resolveUsageTotalsForProfile(totals, type, profileKey, profileName) {
160
341
  (nameLookup && totals.byName.get(nameLookup)) ||
161
342
  null);
162
343
  }
163
- function syncUsageFromStatuslineInput(config, configPath, type, profileKey, profileName, sessionId, totals, cwd) {
164
- var _a, _b, _c;
344
+ function resolveUsageCostForProfile(costs, type, profileKey, profileName) {
345
+ const keyLookup = buildUsageLookupKey(type, profileKey);
346
+ const nameLookup = buildUsageLookupKey(type, profileName);
347
+ return ((keyLookup && costs.byKey.get(keyLookup)) ||
348
+ (nameLookup && costs.byName.get(nameLookup)) ||
349
+ null);
350
+ }
351
+ function syncUsageFromStatuslineInput(config, configPath, type, profileKey, profileName, sessionId, totals, cwd, model) {
352
+ var _a, _b, _c, _d, _e;
165
353
  if (!sessionId)
166
354
  return;
167
355
  if (!totals)
168
356
  return;
169
357
  if (!profileKey && !profileName)
170
358
  return;
359
+ const resolvedModel = normalizeModelValue(model);
360
+ if (!resolvedModel)
361
+ return;
171
362
  const normalizedType = (0, type_1.normalizeType)(type || "");
172
363
  if (!normalizedType)
173
364
  return;
@@ -176,7 +367,12 @@ function syncUsageFromStatuslineInput(config, configPath, type, profileKey, prof
176
367
  return;
177
368
  const inputTokens = (_a = toFiniteNumber(totals.inputTokens)) !== null && _a !== void 0 ? _a : 0;
178
369
  const outputTokens = (_b = toFiniteNumber(totals.outputTokens)) !== null && _b !== void 0 ? _b : 0;
179
- const totalTokens = (_c = toFiniteNumber(totals.totalTokens)) !== null && _c !== void 0 ? _c : inputTokens + outputTokens;
370
+ const cacheReadTokens = (_c = toFiniteNumber(totals.cacheReadTokens)) !== null && _c !== void 0 ? _c : 0;
371
+ const cacheWriteTokens = (_d = toFiniteNumber(totals.cacheWriteTokens)) !== null && _d !== void 0 ? _d : 0;
372
+ const totalTokens = (_e = toFiniteNumber(totals.totalTokens)) !== null && _e !== void 0 ? _e : inputTokens +
373
+ outputTokens +
374
+ cacheReadTokens +
375
+ cacheWriteTokens;
180
376
  if (!Number.isFinite(totalTokens))
181
377
  return;
182
378
  const statePath = getUsageStatePath(usagePath, config);
@@ -189,25 +385,54 @@ function syncUsageFromStatuslineInput(config, configPath, type, profileKey, prof
189
385
  const sessions = state.sessions || {};
190
386
  const key = buildSessionKey(normalizedType, sessionId);
191
387
  const prev = sessions[key];
192
- const prevInput = prev ? prev.inputTokens : 0;
193
- const prevOutput = prev ? prev.outputTokens : 0;
194
- const prevTotal = prev ? prev.totalTokens : 0;
388
+ const prevInput = prev ? toUsageNumber(prev.inputTokens) : 0;
389
+ const prevOutput = prev ? toUsageNumber(prev.outputTokens) : 0;
390
+ const prevCacheRead = prev ? toUsageNumber(prev.cacheReadTokens) : 0;
391
+ const prevCacheWrite = prev ? toUsageNumber(prev.cacheWriteTokens) : 0;
392
+ const prevTotal = prev ? toUsageNumber(prev.totalTokens) : 0;
195
393
  let deltaInput = inputTokens - prevInput;
196
394
  let deltaOutput = outputTokens - prevOutput;
395
+ let deltaCacheRead = cacheReadTokens - prevCacheRead;
396
+ let deltaCacheWrite = cacheWriteTokens - prevCacheWrite;
197
397
  let deltaTotal = totalTokens - prevTotal;
198
- if (deltaTotal < 0 || deltaInput < 0 || deltaOutput < 0) {
398
+ if (deltaTotal < 0) {
399
+ // Session reset: treat current totals as fresh usage.
199
400
  deltaInput = inputTokens;
200
401
  deltaOutput = outputTokens;
402
+ deltaCacheRead = cacheReadTokens;
403
+ deltaCacheWrite = cacheWriteTokens;
201
404
  deltaTotal = totalTokens;
202
405
  }
406
+ else {
407
+ // Clamp negatives caused by reclassification (e.g. cache splits).
408
+ if (deltaInput < 0)
409
+ deltaInput = 0;
410
+ if (deltaOutput < 0)
411
+ deltaOutput = 0;
412
+ if (deltaCacheRead < 0)
413
+ deltaCacheRead = 0;
414
+ if (deltaCacheWrite < 0)
415
+ deltaCacheWrite = 0;
416
+ const breakdownTotal = deltaInput + deltaOutput + deltaCacheRead + deltaCacheWrite;
417
+ if (deltaTotal === 0 && breakdownTotal === 0) {
418
+ deltaTotal = 0;
419
+ }
420
+ else if (breakdownTotal > deltaTotal) {
421
+ deltaTotal = breakdownTotal;
422
+ }
423
+ }
203
424
  if (deltaTotal > 0) {
204
425
  const record = {
205
426
  ts: new Date().toISOString(),
206
427
  type: normalizedType,
207
428
  profileKey: profileKey || null,
208
429
  profileName: profileName || null,
430
+ model: resolvedModel,
431
+ sessionId,
209
432
  inputTokens: deltaInput,
210
433
  outputTokens: deltaOutput,
434
+ cacheReadTokens: deltaCacheRead,
435
+ cacheWriteTokens: deltaCacheWrite,
211
436
  totalTokens: deltaTotal,
212
437
  };
213
438
  appendUsageRecord(usagePath, record);
@@ -217,12 +442,16 @@ function syncUsageFromStatuslineInput(config, configPath, type, profileKey, prof
217
442
  type: normalizedType,
218
443
  inputTokens,
219
444
  outputTokens,
445
+ cacheReadTokens,
446
+ cacheWriteTokens,
220
447
  totalTokens,
221
448
  startTs: prev ? prev.startTs : now,
222
449
  endTs: now,
223
450
  cwd: cwd || (prev ? prev.cwd : null),
451
+ model: resolvedModel,
224
452
  };
225
453
  state.sessions = sessions;
454
+ updateUsageStateMetadata(state, usagePath);
226
455
  writeUsageState(statePath, state);
227
456
  }
228
457
  finally {
@@ -377,7 +606,15 @@ function readUsageState(statePath) {
377
606
  }
378
607
  const files = parsed.files && typeof parsed.files === "object" ? parsed.files : {};
379
608
  const sessions = parsed.sessions && typeof parsed.sessions === "object" ? parsed.sessions : {};
380
- return { version: 1, files, sessions };
609
+ const usageMtimeMs = Number(parsed.usageMtimeMs);
610
+ const usageSize = Number(parsed.usageSize);
611
+ return {
612
+ version: 1,
613
+ files,
614
+ sessions,
615
+ usageMtimeMs: Number.isFinite(usageMtimeMs) ? usageMtimeMs : undefined,
616
+ usageSize: Number.isFinite(usageSize) ? usageSize : undefined,
617
+ };
381
618
  }
382
619
  catch {
383
620
  return { version: 1, files: {}, sessions: {} };
@@ -388,7 +625,93 @@ function writeUsageState(statePath, state) {
388
625
  if (!fs.existsSync(dir)) {
389
626
  fs.mkdirSync(dir, { recursive: true });
390
627
  }
391
- fs.writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
628
+ const payload = `${JSON.stringify(state, null, 2)}\n`;
629
+ const tmpPath = `${statePath}.tmp`;
630
+ try {
631
+ fs.writeFileSync(tmpPath, payload, "utf8");
632
+ fs.renameSync(tmpPath, statePath);
633
+ }
634
+ catch {
635
+ try {
636
+ if (fs.existsSync(tmpPath))
637
+ fs.unlinkSync(tmpPath);
638
+ }
639
+ catch {
640
+ // ignore cleanup failures
641
+ }
642
+ fs.writeFileSync(statePath, payload, "utf8");
643
+ }
644
+ }
645
+ function addSiblingBackupPaths(targets, filePath) {
646
+ if (!filePath)
647
+ return;
648
+ const dir = path.dirname(filePath);
649
+ let entries = [];
650
+ try {
651
+ entries = fs.readdirSync(dir, { withFileTypes: true });
652
+ }
653
+ catch {
654
+ return;
655
+ }
656
+ const base = path.basename(filePath);
657
+ for (const entry of entries) {
658
+ if (!entry.isFile())
659
+ continue;
660
+ if (entry.name === base)
661
+ continue;
662
+ if (entry.name.startsWith(`${base}.`)) {
663
+ targets.add(path.join(dir, entry.name));
664
+ }
665
+ }
666
+ }
667
+ function clearUsageHistory(config, configPath) {
668
+ const targets = new Set();
669
+ const usagePath = getUsagePath(config, configPath);
670
+ if (usagePath) {
671
+ targets.add(usagePath);
672
+ addSiblingBackupPaths(targets, usagePath);
673
+ }
674
+ const statePath = usagePath ? getUsageStatePath(usagePath, config) : null;
675
+ if (statePath) {
676
+ targets.add(statePath);
677
+ targets.add(`${statePath}.lock`);
678
+ addSiblingBackupPaths(targets, statePath);
679
+ }
680
+ const profileLogPath = getProfileLogPath(config, configPath);
681
+ if (profileLogPath) {
682
+ targets.add(profileLogPath);
683
+ addSiblingBackupPaths(targets, profileLogPath);
684
+ }
685
+ const debugPath = (0, debug_1.getStatuslineDebugPath)(configPath);
686
+ if (debugPath) {
687
+ targets.add(debugPath);
688
+ addSiblingBackupPaths(targets, debugPath);
689
+ }
690
+ const removed = [];
691
+ const missing = [];
692
+ const failed = [];
693
+ for (const target of targets) {
694
+ if (!target)
695
+ continue;
696
+ if (!fs.existsSync(target)) {
697
+ missing.push(target);
698
+ continue;
699
+ }
700
+ try {
701
+ const stat = fs.statSync(target);
702
+ if (!stat.isFile())
703
+ continue;
704
+ fs.unlinkSync(target);
705
+ removed.push(target);
706
+ }
707
+ catch (err) {
708
+ failed.push({
709
+ path: target,
710
+ error: err instanceof Error ? err.message : String(err),
711
+ });
712
+ }
713
+ }
714
+ return { removed, missing, failed };
392
715
  }
393
716
  function collectSessionFiles(root) {
394
717
  if (!root || !fs.existsSync(root))
@@ -433,19 +756,80 @@ function updateMinMaxTs(current, ts) {
433
756
  current.end = ts;
434
757
  }
435
758
  }
759
+ function isPlainObject(value) {
760
+ return typeof value === "object" && value !== null && !Array.isArray(value);
761
+ }
762
+ function coerceModelFromValue(value) {
763
+ if (typeof value === "string")
764
+ return normalizeModelValue(value);
765
+ if (!isPlainObject(value))
766
+ return null;
767
+ return (normalizeModelValue(value.displayName) ||
768
+ normalizeModelValue(value.display_name) ||
769
+ normalizeModelValue(value.name) ||
770
+ normalizeModelValue(value.id) ||
771
+ normalizeModelValue(value.model) ||
772
+ normalizeModelValue(value.model_name) ||
773
+ normalizeModelValue(value.model_id) ||
774
+ normalizeModelValue(value.modelId) ||
775
+ null);
776
+ }
777
+ function pickModelFromObject(record) {
778
+ return (coerceModelFromValue(record.model) ||
779
+ normalizeModelValue(record.model_name) ||
780
+ normalizeModelValue(record.modelName) ||
781
+ normalizeModelValue(record.model_id) ||
782
+ normalizeModelValue(record.modelId) ||
783
+ normalizeModelValue(record.model_display_name) ||
784
+ normalizeModelValue(record.modelDisplayName) ||
785
+ null);
786
+ }
787
+ function extractModelFromRecord(record) {
788
+ const direct = pickModelFromObject(record);
789
+ if (direct)
790
+ return direct;
791
+ const message = isPlainObject(record.message)
792
+ ? record.message
793
+ : null;
794
+ if (message) {
795
+ const fromMessage = pickModelFromObject(message);
796
+ if (fromMessage)
797
+ return fromMessage;
798
+ }
799
+ const payload = isPlainObject(record.payload)
800
+ ? record.payload
801
+ : null;
802
+ if (payload) {
803
+ const fromPayload = pickModelFromObject(payload);
804
+ if (fromPayload)
805
+ return fromPayload;
806
+ const info = isPlainObject(payload.info)
807
+ ? payload.info
808
+ : null;
809
+ if (info) {
810
+ const fromInfo = pickModelFromObject(info);
811
+ if (fromInfo)
812
+ return fromInfo;
813
+ }
814
+ }
815
+ return null;
816
+ }
436
817
  function parseCodexSessionFile(filePath) {
437
818
  const raw = fs.readFileSync(filePath, "utf8");
438
819
  const lines = raw.split(/\r?\n/);
439
820
  let maxTotal = 0;
440
821
  let maxInput = 0;
441
822
  let maxOutput = 0;
823
+ let maxCachedInput = 0;
442
824
  let hasTotal = false;
443
825
  let sumLast = 0;
444
826
  let sumLastInput = 0;
445
827
  let sumLastOutput = 0;
828
+ let sumLastCachedInput = 0;
446
829
  const tsRange = { start: null, end: null };
447
830
  let cwd = null;
448
831
  let sessionId = null;
832
+ let model = null;
449
833
  for (const line of lines) {
450
834
  const trimmed = line.trim();
451
835
  if (!trimmed)
@@ -454,6 +838,11 @@ function parseCodexSessionFile(filePath) {
454
838
  const parsed = JSON.parse(trimmed);
455
839
  if (!parsed || typeof parsed !== "object")
456
840
  continue;
841
+ if (!model) {
842
+ const candidate = extractModelFromRecord(parsed);
843
+ if (candidate)
844
+ model = candidate;
845
+ }
457
846
  if (parsed.timestamp)
458
847
  updateMinMaxTs(tsRange, String(parsed.timestamp));
459
848
  if (!cwd && parsed.type === "session_meta") {
@@ -484,12 +873,16 @@ function parseCodexSessionFile(filePath) {
484
873
  maxTotal = totalTokens;
485
874
  const totalInput = Number(totalUsage.input_tokens);
486
875
  const totalOutput = Number(totalUsage.output_tokens);
876
+ const totalCached = Number(totalUsage.cached_input_tokens);
487
877
  if (Number.isFinite(totalInput) && totalInput > maxInput) {
488
878
  maxInput = totalInput;
489
879
  }
490
880
  if (Number.isFinite(totalOutput) && totalOutput > maxOutput) {
491
881
  maxOutput = totalOutput;
492
882
  }
883
+ if (Number.isFinite(totalCached) && totalCached > maxCachedInput) {
884
+ maxCachedInput = totalCached;
885
+ }
493
886
  }
494
887
  else {
495
888
  const lastTokens = Number(lastUsage.total_tokens);
@@ -497,10 +890,13 @@ function parseCodexSessionFile(filePath) {
497
890
  sumLast += lastTokens;
498
891
  const lastInput = Number(lastUsage.input_tokens);
499
892
  const lastOutput = Number(lastUsage.output_tokens);
893
+ const lastCached = Number(lastUsage.cached_input_tokens);
500
894
  if (Number.isFinite(lastInput))
501
895
  sumLastInput += lastInput;
502
896
  if (Number.isFinite(lastOutput))
503
897
  sumLastOutput += lastOutput;
898
+ if (Number.isFinite(lastCached))
899
+ sumLastCachedInput += lastCached;
504
900
  }
505
901
  }
506
902
  catch {
@@ -511,15 +907,23 @@ function parseCodexSessionFile(filePath) {
511
907
  maxTotal = sumLast;
512
908
  maxInput = sumLastInput;
513
909
  maxOutput = sumLastOutput;
910
+ maxCachedInput = sumLastCachedInput;
514
911
  }
912
+ const cacheReadTokens = Math.max(0, maxCachedInput);
913
+ const inputTokens = cacheReadTokens > 0 ? Math.max(0, maxInput - cacheReadTokens) : maxInput;
914
+ const computedTotal = inputTokens + maxOutput + cacheReadTokens;
915
+ const totalTokens = Math.max(maxTotal, computedTotal);
515
916
  return {
516
- inputTokens: maxInput,
917
+ inputTokens,
517
918
  outputTokens: maxOutput,
518
- totalTokens: maxTotal,
919
+ cacheReadTokens,
920
+ cacheWriteTokens: 0,
921
+ totalTokens,
519
922
  startTs: tsRange.start,
520
923
  endTs: tsRange.end,
521
924
  cwd,
522
925
  sessionId,
926
+ model,
523
927
  };
524
928
  }
525
929
  function parseClaudeSessionFile(filePath) {
@@ -529,9 +933,12 @@ function parseClaudeSessionFile(filePath) {
529
933
  let totalTokens = 0;
530
934
  let inputTokens = 0;
531
935
  let outputTokens = 0;
936
+ let cacheReadTokens = 0;
937
+ let cacheWriteTokens = 0;
532
938
  const tsRange = { start: null, end: null };
533
939
  let cwd = null;
534
940
  let sessionId = null;
941
+ let model = null;
535
942
  for (const line of lines) {
536
943
  const trimmed = line.trim();
537
944
  if (!trimmed)
@@ -540,6 +947,11 @@ function parseClaudeSessionFile(filePath) {
540
947
  const parsed = JSON.parse(trimmed);
541
948
  if (!parsed || typeof parsed !== "object")
542
949
  continue;
950
+ if (!model) {
951
+ const candidate = extractModelFromRecord(parsed);
952
+ if (candidate)
953
+ model = candidate;
954
+ }
543
955
  if (parsed.timestamp)
544
956
  updateMinMaxTs(tsRange, String(parsed.timestamp));
545
957
  if (!cwd && parsed.cwd)
@@ -559,6 +971,10 @@ function parseClaudeSessionFile(filePath) {
559
971
  inputTokens += input;
560
972
  if (Number.isFinite(output))
561
973
  outputTokens += output;
974
+ if (Number.isFinite(cacheCreate))
975
+ cacheWriteTokens += cacheCreate;
976
+ if (Number.isFinite(cacheRead))
977
+ cacheReadTokens += cacheRead;
562
978
  totalTokens +=
563
979
  (Number.isFinite(input) ? input : 0) +
564
980
  (Number.isFinite(output) ? output : 0) +
@@ -572,11 +988,14 @@ function parseClaudeSessionFile(filePath) {
572
988
  return {
573
989
  inputTokens,
574
990
  outputTokens,
991
+ cacheReadTokens,
992
+ cacheWriteTokens,
575
993
  totalTokens,
576
994
  startTs: tsRange.start,
577
995
  endTs: tsRange.end,
578
996
  cwd,
579
997
  sessionId,
998
+ model,
580
999
  };
581
1000
  }
582
1001
  const LOCK_STALE_MS = 10 * 60 * 1000;
@@ -662,6 +1081,88 @@ function releaseLock(lockPath, fd) {
662
1081
  // ignore
663
1082
  }
664
1083
  }
1084
+ function readUsageFileStat(usagePath) {
1085
+ if (!usagePath || !fs.existsSync(usagePath))
1086
+ return null;
1087
+ try {
1088
+ return fs.statSync(usagePath);
1089
+ }
1090
+ catch {
1091
+ return null;
1092
+ }
1093
+ }
1094
+ function buildUsageRecordKey(record) {
1095
+ var _a, _b, _c, _d;
1096
+ return JSON.stringify([
1097
+ record.ts,
1098
+ record.type,
1099
+ (_a = record.profileKey) !== null && _a !== void 0 ? _a : null,
1100
+ (_b = record.profileName) !== null && _b !== void 0 ? _b : null,
1101
+ (_c = record.model) !== null && _c !== void 0 ? _c : null,
1102
+ (_d = record.sessionId) !== null && _d !== void 0 ? _d : null,
1103
+ toUsageNumber(record.inputTokens),
1104
+ toUsageNumber(record.outputTokens),
1105
+ toUsageNumber(record.cacheReadTokens),
1106
+ toUsageNumber(record.cacheWriteTokens),
1107
+ toUsageNumber(record.totalTokens),
1108
+ ]);
1109
+ }
1110
+ function buildUsageSessionsFromRecords(records) {
1111
+ const sessions = {};
1112
+ const seen = new Set();
1113
+ for (const record of records) {
1114
+ if (!record.sessionId)
1115
+ continue;
1116
+ const normalizedType = normalizeUsageType(record.type);
1117
+ if (!normalizedType)
1118
+ continue;
1119
+ const recordKey = buildUsageRecordKey(record);
1120
+ if (seen.has(recordKey))
1121
+ continue;
1122
+ seen.add(recordKey);
1123
+ const sessionKey = buildSessionKey(normalizedType, record.sessionId);
1124
+ let entry = sessions[sessionKey];
1125
+ if (!entry) {
1126
+ entry = {
1127
+ type: normalizedType,
1128
+ inputTokens: 0,
1129
+ outputTokens: 0,
1130
+ cacheReadTokens: 0,
1131
+ cacheWriteTokens: 0,
1132
+ totalTokens: 0,
1133
+ startTs: null,
1134
+ endTs: null,
1135
+ cwd: null,
1136
+ model: record.model || null,
1137
+ };
1138
+ sessions[sessionKey] = entry;
1139
+ }
1140
+ entry.inputTokens += toUsageNumber(record.inputTokens);
1141
+ entry.outputTokens += toUsageNumber(record.outputTokens);
1142
+ entry.cacheReadTokens += toUsageNumber(record.cacheReadTokens);
1143
+ entry.cacheWriteTokens += toUsageNumber(record.cacheWriteTokens);
1144
+ entry.totalTokens += toUsageNumber(record.totalTokens);
1145
+ if (!entry.model && record.model)
1146
+ entry.model = record.model;
1147
+ if (record.ts) {
1148
+ const range = { start: entry.startTs, end: entry.endTs };
1149
+ updateMinMaxTs(range, record.ts);
1150
+ entry.startTs = range.start;
1151
+ entry.endTs = range.end;
1152
+ }
1153
+ }
1154
+ return sessions;
1155
+ }
1156
+ function updateUsageStateMetadata(state, usagePath) {
1157
+ const stat = readUsageFileStat(usagePath);
1158
+ if (!stat || !stat.isFile()) {
1159
+ state.usageMtimeMs = undefined;
1160
+ state.usageSize = undefined;
1161
+ return;
1162
+ }
1163
+ state.usageMtimeMs = stat.mtimeMs;
1164
+ state.usageSize = stat.size;
1165
+ }
665
1166
  function appendUsageRecord(usagePath, record) {
666
1167
  const dir = path.dirname(usagePath);
667
1168
  if (!fs.existsSync(dir)) {
@@ -670,12 +1171,13 @@ function appendUsageRecord(usagePath, record) {
670
1171
  fs.appendFileSync(usagePath, `${JSON.stringify(record)}\n`, "utf8");
671
1172
  }
672
1173
  function readUsageRecords(usagePath) {
673
- var _a, _b, _c, _d, _e;
1174
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
674
1175
  if (!usagePath || !fs.existsSync(usagePath))
675
1176
  return [];
676
1177
  const raw = fs.readFileSync(usagePath, "utf8");
677
1178
  const lines = raw.split(/\r?\n/);
678
1179
  const records = [];
1180
+ const seen = new Set();
679
1181
  for (const line of lines) {
680
1182
  const trimmed = line.trim();
681
1183
  if (!trimmed)
@@ -684,19 +1186,58 @@ function readUsageRecords(usagePath) {
684
1186
  const parsed = JSON.parse(trimmed);
685
1187
  if (!parsed || typeof parsed !== "object")
686
1188
  continue;
687
- const input = Number((_a = parsed.inputTokens) !== null && _a !== void 0 ? _a : 0);
688
- const output = Number((_b = parsed.outputTokens) !== null && _b !== void 0 ? _b : 0);
689
- const total = Number((_c = parsed.totalTokens) !== null && _c !== void 0 ? _c : input + output);
690
- const type = (0, type_1.normalizeType)(parsed.type) || String((_d = parsed.type) !== null && _d !== void 0 ? _d : "unknown");
691
- records.push({
692
- ts: String((_e = parsed.ts) !== null && _e !== void 0 ? _e : ""),
1189
+ const type = (0, type_1.normalizeType)(parsed.type) || String((_a = parsed.type) !== null && _a !== void 0 ? _a : "unknown");
1190
+ let input = Number((_b = parsed.inputTokens) !== null && _b !== void 0 ? _b : 0);
1191
+ const output = Number((_c = parsed.outputTokens) !== null && _c !== void 0 ? _c : 0);
1192
+ const cacheRead = Number((_f = (_e = (_d = parsed.cacheReadTokens) !== null && _d !== void 0 ? _d : parsed.cache_read_input_tokens) !== null && _e !== void 0 ? _e : parsed.cacheReadInputTokens) !== null && _f !== void 0 ? _f : 0);
1193
+ const cacheWrite = Number((_k = (_j = (_h = (_g = parsed.cacheWriteTokens) !== null && _g !== void 0 ? _g : parsed.cache_creation_input_tokens) !== null && _h !== void 0 ? _h : parsed.cache_write_input_tokens) !== null && _j !== void 0 ? _j : parsed.cacheWriteInputTokens) !== null && _k !== void 0 ? _k : 0);
1194
+ if (type === "codex" &&
1195
+ Number.isFinite(cacheRead) &&
1196
+ cacheRead > 0 &&
1197
+ Number.isFinite(input) &&
1198
+ Number.isFinite(output)) {
1199
+ const rawTotal = Number(parsed.totalTokens);
1200
+ if (Number.isFinite(rawTotal) && rawTotal <= input + output) {
1201
+ input = Math.max(0, input - cacheRead);
1202
+ }
1203
+ }
1204
+ const computedTotal = (Number.isFinite(input) ? input : 0) +
1205
+ (Number.isFinite(output) ? output : 0) +
1206
+ (Number.isFinite(cacheRead) ? cacheRead : 0) +
1207
+ (Number.isFinite(cacheWrite) ? cacheWrite : 0);
1208
+ const total = Number((_l = parsed.totalTokens) !== null && _l !== void 0 ? _l : computedTotal);
1209
+ const finalTotal = Number.isFinite(total)
1210
+ ? Math.max(total, computedTotal)
1211
+ : computedTotal;
1212
+ const model = normalizeModelValue(parsed.model) ||
1213
+ normalizeModelValue(parsed.model_name) ||
1214
+ normalizeModelValue(parsed.modelName) ||
1215
+ normalizeModelValue(parsed.model_id) ||
1216
+ normalizeModelValue(parsed.modelId) ||
1217
+ null;
1218
+ const sessionId = parsed.sessionId ||
1219
+ parsed.session_id ||
1220
+ parsed.sessionID ||
1221
+ parsed.session ||
1222
+ null;
1223
+ const record = {
1224
+ ts: String((_m = parsed.ts) !== null && _m !== void 0 ? _m : ""),
693
1225
  type,
694
1226
  profileKey: parsed.profileKey ? String(parsed.profileKey) : null,
695
1227
  profileName: parsed.profileName ? String(parsed.profileName) : null,
1228
+ model,
1229
+ sessionId: sessionId ? String(sessionId) : null,
696
1230
  inputTokens: Number.isFinite(input) ? input : 0,
697
1231
  outputTokens: Number.isFinite(output) ? output : 0,
698
- totalTokens: Number.isFinite(total) ? total : 0,
699
- });
1232
+ cacheReadTokens: Number.isFinite(cacheRead) ? cacheRead : 0,
1233
+ cacheWriteTokens: Number.isFinite(cacheWrite) ? cacheWrite : 0,
1234
+ totalTokens: Number.isFinite(finalTotal) ? finalTotal : 0,
1235
+ };
1236
+ const key = buildUsageRecordKey(record);
1237
+ if (seen.has(key))
1238
+ continue;
1239
+ seen.add(key);
1240
+ records.push(record);
700
1241
  }
701
1242
  catch {
702
1243
  // ignore invalid lines
@@ -705,6 +1246,7 @@ function readUsageRecords(usagePath) {
705
1246
  return records;
706
1247
  }
707
1248
  function syncUsageFromSessions(config, configPath, usagePath) {
1249
+ var _a, _b;
708
1250
  const statePath = getUsageStatePath(usagePath, config);
709
1251
  const lockPath = `${statePath}.lock`;
710
1252
  const lockFd = acquireLock(lockPath);
@@ -715,7 +1257,23 @@ function syncUsageFromSessions(config, configPath, usagePath) {
715
1257
  const logEntries = readProfileLogEntries([profileLogPath]);
716
1258
  const state = readUsageState(statePath);
717
1259
  const files = state.files || {};
718
- const sessions = state.sessions || {};
1260
+ let sessions = state.sessions || {};
1261
+ const usageStat = readUsageFileStat(usagePath);
1262
+ const hasUsageData = !!usageStat && usageStat.isFile() && usageStat.size > 0;
1263
+ const sessionsEmpty = Object.keys(sessions).length === 0;
1264
+ const hasUsageMeta = Number.isFinite((_a = state.usageMtimeMs) !== null && _a !== void 0 ? _a : Number.NaN) &&
1265
+ Number.isFinite((_b = state.usageSize) !== null && _b !== void 0 ? _b : Number.NaN);
1266
+ const usageOutOfSync = hasUsageData &&
1267
+ (!hasUsageMeta ||
1268
+ state.usageMtimeMs !== usageStat.mtimeMs ||
1269
+ state.usageSize !== usageStat.size);
1270
+ if (hasUsageData && (sessionsEmpty || usageOutOfSync)) {
1271
+ const records = readUsageRecords(usagePath);
1272
+ if (records.length > 0) {
1273
+ sessions = buildUsageSessionsFromRecords(records);
1274
+ }
1275
+ }
1276
+ state.sessions = sessions;
719
1277
  const codexFiles = collectSessionFiles(getCodexSessionsPath(config));
720
1278
  const claudeFiles = collectSessionFiles(getClaudeSessionsPath(config));
721
1279
  const processFile = (filePath, type) => {
@@ -747,30 +1305,52 @@ function syncUsageFromSessions(config, configPath, usagePath) {
747
1305
  return;
748
1306
  const sessionKey = stats.sessionId ? buildSessionKey(type, stats.sessionId) : null;
749
1307
  const sessionPrev = sessionKey ? sessions[sessionKey] : null;
750
- const prevInput = prev ? prev.inputTokens : 0;
751
- const prevOutput = prev ? prev.outputTokens : 0;
752
- const prevTotal = prev ? prev.totalTokens : 0;
1308
+ const resolvedModel = (sessionPrev && sessionPrev.model) ||
1309
+ stats.model ||
1310
+ (prev && prev.model) ||
1311
+ null;
1312
+ const prevInput = prev ? toUsageNumber(prev.inputTokens) : 0;
1313
+ const prevOutput = prev ? toUsageNumber(prev.outputTokens) : 0;
1314
+ const prevCacheRead = prev ? toUsageNumber(prev.cacheReadTokens) : 0;
1315
+ const prevCacheWrite = prev ? toUsageNumber(prev.cacheWriteTokens) : 0;
1316
+ const prevTotal = prev ? toUsageNumber(prev.totalTokens) : 0;
753
1317
  const prevInputMax = sessionPrev
754
- ? Math.max(prevInput, sessionPrev.inputTokens)
1318
+ ? Math.max(prevInput, toUsageNumber(sessionPrev.inputTokens))
755
1319
  : prevInput;
756
1320
  const prevOutputMax = sessionPrev
757
- ? Math.max(prevOutput, sessionPrev.outputTokens)
1321
+ ? Math.max(prevOutput, toUsageNumber(sessionPrev.outputTokens))
758
1322
  : prevOutput;
1323
+ const prevCacheReadMax = sessionPrev
1324
+ ? Math.max(prevCacheRead, toUsageNumber(sessionPrev.cacheReadTokens))
1325
+ : prevCacheRead;
1326
+ const prevCacheWriteMax = sessionPrev
1327
+ ? Math.max(prevCacheWrite, toUsageNumber(sessionPrev.cacheWriteTokens))
1328
+ : prevCacheWrite;
759
1329
  const prevTotalMax = sessionPrev
760
- ? Math.max(prevTotal, sessionPrev.totalTokens)
1330
+ ? Math.max(prevTotal, toUsageNumber(sessionPrev.totalTokens))
761
1331
  : prevTotal;
762
1332
  let deltaInput = stats.inputTokens - prevInputMax;
763
1333
  let deltaOutput = stats.outputTokens - prevOutputMax;
1334
+ let deltaCacheRead = stats.cacheReadTokens - prevCacheReadMax;
1335
+ let deltaCacheWrite = stats.cacheWriteTokens - prevCacheWriteMax;
764
1336
  let deltaTotal = stats.totalTokens - prevTotalMax;
765
- if (deltaTotal < 0 || deltaInput < 0 || deltaOutput < 0) {
1337
+ if (deltaTotal < 0 ||
1338
+ deltaInput < 0 ||
1339
+ deltaOutput < 0 ||
1340
+ deltaCacheRead < 0 ||
1341
+ deltaCacheWrite < 0) {
766
1342
  if (sessionPrev) {
767
1343
  deltaInput = 0;
768
1344
  deltaOutput = 0;
1345
+ deltaCacheRead = 0;
1346
+ deltaCacheWrite = 0;
769
1347
  deltaTotal = 0;
770
1348
  }
771
1349
  else {
772
1350
  deltaInput = stats.inputTokens;
773
1351
  deltaOutput = stats.outputTokens;
1352
+ deltaCacheRead = stats.cacheReadTokens;
1353
+ deltaCacheWrite = stats.cacheWriteTokens;
774
1354
  deltaTotal = stats.totalTokens;
775
1355
  }
776
1356
  }
@@ -780,30 +1360,43 @@ function syncUsageFromSessions(config, configPath, usagePath) {
780
1360
  type,
781
1361
  profileKey: resolved.match.profileKey,
782
1362
  profileName: resolved.match.profileName,
1363
+ model: resolvedModel,
1364
+ sessionId: stats.sessionId,
783
1365
  inputTokens: deltaInput,
784
1366
  outputTokens: deltaOutput,
1367
+ cacheReadTokens: deltaCacheRead,
1368
+ cacheWriteTokens: deltaCacheWrite,
785
1369
  totalTokens: deltaTotal,
786
1370
  };
787
1371
  appendUsageRecord(usagePath, record);
788
1372
  }
789
1373
  if (sessionKey) {
790
1374
  const nextInput = sessionPrev
791
- ? Math.max(sessionPrev.inputTokens, stats.inputTokens)
1375
+ ? Math.max(toUsageNumber(sessionPrev.inputTokens), stats.inputTokens)
792
1376
  : stats.inputTokens;
793
1377
  const nextOutput = sessionPrev
794
- ? Math.max(sessionPrev.outputTokens, stats.outputTokens)
1378
+ ? Math.max(toUsageNumber(sessionPrev.outputTokens), stats.outputTokens)
795
1379
  : stats.outputTokens;
1380
+ const nextCacheRead = sessionPrev
1381
+ ? Math.max(toUsageNumber(sessionPrev.cacheReadTokens), stats.cacheReadTokens)
1382
+ : stats.cacheReadTokens;
1383
+ const nextCacheWrite = sessionPrev
1384
+ ? Math.max(toUsageNumber(sessionPrev.cacheWriteTokens), stats.cacheWriteTokens)
1385
+ : stats.cacheWriteTokens;
796
1386
  const nextTotal = sessionPrev
797
- ? Math.max(sessionPrev.totalTokens, stats.totalTokens)
1387
+ ? Math.max(toUsageNumber(sessionPrev.totalTokens), stats.totalTokens)
798
1388
  : stats.totalTokens;
799
1389
  sessions[sessionKey] = {
800
1390
  type,
801
1391
  inputTokens: nextInput,
802
1392
  outputTokens: nextOutput,
1393
+ cacheReadTokens: nextCacheRead,
1394
+ cacheWriteTokens: nextCacheWrite,
803
1395
  totalTokens: nextTotal,
804
1396
  startTs: sessionPrev ? sessionPrev.startTs : stats.startTs,
805
1397
  endTs: stats.endTs || (sessionPrev ? sessionPrev.endTs : null),
806
1398
  cwd: stats.cwd || (sessionPrev ? sessionPrev.cwd : null),
1399
+ model: resolvedModel,
807
1400
  };
808
1401
  }
809
1402
  files[filePath] = {
@@ -812,10 +1405,13 @@ function syncUsageFromSessions(config, configPath, usagePath) {
812
1405
  type,
813
1406
  inputTokens: stats.inputTokens,
814
1407
  outputTokens: stats.outputTokens,
1408
+ cacheReadTokens: stats.cacheReadTokens,
1409
+ cacheWriteTokens: stats.cacheWriteTokens,
815
1410
  totalTokens: stats.totalTokens,
816
1411
  startTs: stats.startTs,
817
1412
  endTs: stats.endTs,
818
1413
  cwd: stats.cwd,
1414
+ model: resolvedModel,
819
1415
  };
820
1416
  };
821
1417
  for (const filePath of codexFiles)
@@ -824,6 +1420,7 @@ function syncUsageFromSessions(config, configPath, usagePath) {
824
1420
  processFile(filePath, "claude");
825
1421
  state.files = files;
826
1422
  state.sessions = sessions;
1423
+ updateUsageStateMetadata(state, usagePath);
827
1424
  writeUsageState(statePath, state);
828
1425
  }
829
1426
  finally {