@praeviso/code-env-switch 0.1.3 → 0.1.5

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,7 +8,10 @@ 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;
@@ -23,6 +26,23 @@ const path = require("path");
23
26
  const os = require("os");
24
27
  const utils_1 = require("../shell/utils");
25
28
  const type_1 = require("../profile/type");
29
+ const pricing_1 = require("./pricing");
30
+ function resolveProfileForRecord(config, type, record) {
31
+ if (record.profileKey && config.profiles && config.profiles[record.profileKey]) {
32
+ return config.profiles[record.profileKey];
33
+ }
34
+ if (record.profileName && config.profiles) {
35
+ const matches = Object.entries(config.profiles).find(([key, entry]) => {
36
+ const displayName = (0, type_1.getProfileDisplayName)(key, entry, type || undefined);
37
+ return (displayName === record.profileName ||
38
+ entry.name === record.profileName ||
39
+ key === record.profileName);
40
+ });
41
+ if (matches)
42
+ return matches[1];
43
+ }
44
+ return null;
45
+ }
26
46
  function resolveDefaultConfigDir(configPath) {
27
47
  if (configPath)
28
48
  return path.dirname(configPath);
@@ -72,42 +92,135 @@ function formatTokenCount(value) {
72
92
  return `${(value / 1000000).toFixed(2)}M`;
73
93
  return `${(value / 1000000000).toFixed(2)}B`;
74
94
  }
75
- function buildUsageTotals(records) {
76
- const byKey = new Map();
77
- const byName = new Map();
95
+ function createUsageTotals() {
96
+ return {
97
+ today: 0,
98
+ total: 0,
99
+ todayInput: 0,
100
+ totalInput: 0,
101
+ todayOutput: 0,
102
+ totalOutput: 0,
103
+ todayCacheRead: 0,
104
+ totalCacheRead: 0,
105
+ todayCacheWrite: 0,
106
+ totalCacheWrite: 0,
107
+ };
108
+ }
109
+ function createUsageCostTotals() {
110
+ return { today: 0, total: 0, todayTokens: 0, totalTokens: 0 };
111
+ }
112
+ function toUsageNumber(value) {
113
+ const num = Number(value !== null && value !== void 0 ? value : 0);
114
+ return Number.isFinite(num) ? num : 0;
115
+ }
116
+ function getTodayWindow() {
78
117
  const todayStart = new Date();
79
118
  todayStart.setHours(0, 0, 0, 0);
80
- const todayStartMs = todayStart.getTime();
119
+ const startMs = todayStart.getTime();
81
120
  const tomorrowStart = new Date(todayStart);
82
121
  tomorrowStart.setDate(todayStart.getDate() + 1);
83
- const tomorrowStartMs = tomorrowStart.getTime();
122
+ return { startMs, endMs: tomorrowStart.getTime() };
123
+ }
124
+ function isTimestampInWindow(ts, startMs, endMs) {
125
+ if (!ts)
126
+ return false;
127
+ const time = new Date(ts).getTime();
128
+ if (Number.isNaN(time))
129
+ return false;
130
+ return time >= startMs && time < endMs;
131
+ }
132
+ function buildUsageTotals(records) {
133
+ var _a;
134
+ const byKey = new Map();
135
+ const byName = new Map();
136
+ const { startMs, endMs } = getTodayWindow();
84
137
  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;
138
+ return isTimestampInWindow(ts, startMs, endMs);
91
139
  };
92
- const addTotals = (map, key, amount, ts) => {
140
+ const addTotals = (map, key, amounts, ts) => {
93
141
  if (!key)
94
142
  return;
95
- const current = map.get(key) || { today: 0, total: 0 };
96
- current.total += amount;
97
- if (isToday(ts))
98
- current.today += amount;
143
+ const current = map.get(key) || createUsageTotals();
144
+ current.total += amounts.total;
145
+ current.totalInput += amounts.input;
146
+ current.totalOutput += amounts.output;
147
+ current.totalCacheRead += amounts.cacheRead;
148
+ current.totalCacheWrite += amounts.cacheWrite;
149
+ if (isToday(ts)) {
150
+ current.today += amounts.total;
151
+ current.todayInput += amounts.input;
152
+ current.todayOutput += amounts.output;
153
+ current.todayCacheRead += amounts.cacheRead;
154
+ current.todayCacheWrite += amounts.cacheWrite;
155
+ }
99
156
  map.set(key, current);
100
157
  };
101
158
  for (const record of records) {
102
159
  const type = normalizeUsageType(record.type) || "";
103
- const total = Number(record.totalTokens || 0);
160
+ const input = toUsageNumber(record.inputTokens);
161
+ const output = toUsageNumber(record.outputTokens);
162
+ const cacheRead = toUsageNumber(record.cacheReadTokens);
163
+ const cacheWrite = toUsageNumber(record.cacheWriteTokens);
164
+ const computedTotal = input + output + cacheRead + cacheWrite;
165
+ const rawTotal = Number((_a = record.totalTokens) !== null && _a !== void 0 ? _a : computedTotal);
166
+ const total = Number.isFinite(rawTotal)
167
+ ? Math.max(rawTotal, computedTotal)
168
+ : computedTotal;
104
169
  if (!Number.isFinite(total))
105
170
  continue;
106
171
  if (record.profileKey) {
107
- addTotals(byKey, `${type}||${record.profileKey}`, total, record.ts);
172
+ addTotals(byKey, `${type}||${record.profileKey}`, { total, input, output, cacheRead, cacheWrite }, record.ts);
108
173
  }
109
174
  if (record.profileName) {
110
- addTotals(byName, `${type}||${record.profileName}`, total, record.ts);
175
+ addTotals(byName, `${type}||${record.profileName}`, { total, input, output, cacheRead, cacheWrite }, record.ts);
176
+ }
177
+ }
178
+ return { byKey, byName };
179
+ }
180
+ function buildUsageCostIndex(records, config) {
181
+ const byKey = new Map();
182
+ const byName = new Map();
183
+ const { startMs, endMs } = getTodayWindow();
184
+ const addCost = (map, key, cost, tokens, ts) => {
185
+ if (!key)
186
+ return;
187
+ const current = map.get(key) || createUsageCostTotals();
188
+ current.total += cost;
189
+ current.totalTokens += tokens;
190
+ if (isTimestampInWindow(ts, startMs, endMs)) {
191
+ current.today += cost;
192
+ current.todayTokens += tokens;
193
+ }
194
+ map.set(key, current);
195
+ };
196
+ for (const record of records) {
197
+ const model = normalizeModelValue(record.model);
198
+ if (!model)
199
+ continue;
200
+ const type = normalizeUsageType(record.type) || "";
201
+ const profile = record.profileKey && config.profiles ? config.profiles[record.profileKey] : null;
202
+ const pricing = (0, pricing_1.resolvePricingForProfile)(config, profile || null, model);
203
+ if (!pricing)
204
+ continue;
205
+ const cost = (0, pricing_1.calculateUsageCost)({
206
+ totalTokens: record.totalTokens,
207
+ inputTokens: record.inputTokens,
208
+ outputTokens: record.outputTokens,
209
+ cacheReadTokens: record.cacheReadTokens,
210
+ cacheWriteTokens: record.cacheWriteTokens,
211
+ }, pricing);
212
+ if (cost === null || !Number.isFinite(cost))
213
+ continue;
214
+ const billedTokens = toUsageNumber(record.inputTokens) +
215
+ toUsageNumber(record.outputTokens) +
216
+ toUsageNumber(record.cacheReadTokens) +
217
+ toUsageNumber(record.cacheWriteTokens);
218
+ const billedTotal = billedTokens > 0 ? billedTokens : toUsageNumber(record.totalTokens);
219
+ if (record.profileKey) {
220
+ addCost(byKey, `${type}||${record.profileKey}`, cost, billedTotal, record.ts);
221
+ }
222
+ if (record.profileName) {
223
+ addCost(byName, `${type}||${record.profileName}`, cost, billedTotal, record.ts);
111
224
  }
112
225
  }
113
226
  return { byKey, byName };
@@ -121,6 +234,12 @@ function normalizeUsageType(type) {
121
234
  const trimmed = String(type).trim();
122
235
  return trimmed ? trimmed : null;
123
236
  }
237
+ function normalizeModelValue(value) {
238
+ if (typeof value !== "string")
239
+ return null;
240
+ const trimmed = value.trim();
241
+ return trimmed ? trimmed : null;
242
+ }
124
243
  function buildSessionKey(type, sessionId) {
125
244
  const normalized = normalizeUsageType(type || "");
126
245
  return normalized ? `${normalized}::${sessionId}` : sessionId;
@@ -153,6 +272,66 @@ function readUsageTotalsIndex(config, configPath, syncUsage) {
153
272
  return null;
154
273
  return buildUsageTotals(records);
155
274
  }
275
+ function readUsageCostIndex(config, configPath, syncUsage) {
276
+ const usagePath = getUsagePath(config, configPath);
277
+ if (!usagePath)
278
+ return null;
279
+ if (syncUsage) {
280
+ syncUsageFromSessions(config, configPath, usagePath);
281
+ }
282
+ const records = readUsageRecords(usagePath);
283
+ if (records.length === 0)
284
+ return null;
285
+ const costs = buildUsageCostIndex(records, config);
286
+ if (costs.byKey.size === 0 && costs.byName.size === 0)
287
+ return null;
288
+ return costs;
289
+ }
290
+ function readUsageSessionCost(config, configPath, type, sessionId, syncUsage) {
291
+ if (!sessionId)
292
+ return null;
293
+ const usagePath = getUsagePath(config, configPath);
294
+ if (!usagePath)
295
+ return null;
296
+ if (syncUsage) {
297
+ syncUsageFromSessions(config, configPath, usagePath);
298
+ }
299
+ const records = readUsageRecords(usagePath);
300
+ if (records.length === 0)
301
+ return null;
302
+ const normalizedType = normalizeUsageType(type);
303
+ let total = 0;
304
+ let hasCost = false;
305
+ for (const record of records) {
306
+ if (!record.sessionId)
307
+ continue;
308
+ if (record.sessionId !== sessionId)
309
+ continue;
310
+ if (normalizedType &&
311
+ normalizeUsageType(record.type) !== normalizedType) {
312
+ continue;
313
+ }
314
+ const model = normalizeModelValue(record.model);
315
+ if (!model)
316
+ continue;
317
+ const profile = resolveProfileForRecord(config, normalizedType, record);
318
+ const pricing = (0, pricing_1.resolvePricingForProfile)(config, profile, model);
319
+ if (!pricing)
320
+ continue;
321
+ const cost = (0, pricing_1.calculateUsageCost)({
322
+ totalTokens: record.totalTokens,
323
+ inputTokens: record.inputTokens,
324
+ outputTokens: record.outputTokens,
325
+ cacheReadTokens: record.cacheReadTokens,
326
+ cacheWriteTokens: record.cacheWriteTokens,
327
+ }, pricing);
328
+ if (cost === null || !Number.isFinite(cost))
329
+ continue;
330
+ total += cost;
331
+ hasCost = true;
332
+ }
333
+ return hasCost ? total : null;
334
+ }
156
335
  function resolveUsageTotalsForProfile(totals, type, profileKey, profileName) {
157
336
  const keyLookup = buildUsageLookupKey(type, profileKey);
158
337
  const nameLookup = buildUsageLookupKey(type, profileName);
@@ -160,14 +339,24 @@ function resolveUsageTotalsForProfile(totals, type, profileKey, profileName) {
160
339
  (nameLookup && totals.byName.get(nameLookup)) ||
161
340
  null);
162
341
  }
163
- function syncUsageFromStatuslineInput(config, configPath, type, profileKey, profileName, sessionId, totals, cwd) {
164
- var _a, _b, _c;
342
+ function resolveUsageCostForProfile(costs, type, profileKey, profileName) {
343
+ const keyLookup = buildUsageLookupKey(type, profileKey);
344
+ const nameLookup = buildUsageLookupKey(type, profileName);
345
+ return ((keyLookup && costs.byKey.get(keyLookup)) ||
346
+ (nameLookup && costs.byName.get(nameLookup)) ||
347
+ null);
348
+ }
349
+ function syncUsageFromStatuslineInput(config, configPath, type, profileKey, profileName, sessionId, totals, cwd, model) {
350
+ var _a, _b, _c, _d, _e;
165
351
  if (!sessionId)
166
352
  return;
167
353
  if (!totals)
168
354
  return;
169
355
  if (!profileKey && !profileName)
170
356
  return;
357
+ const resolvedModel = normalizeModelValue(model);
358
+ if (!resolvedModel)
359
+ return;
171
360
  const normalizedType = (0, type_1.normalizeType)(type || "");
172
361
  if (!normalizedType)
173
362
  return;
@@ -176,7 +365,12 @@ function syncUsageFromStatuslineInput(config, configPath, type, profileKey, prof
176
365
  return;
177
366
  const inputTokens = (_a = toFiniteNumber(totals.inputTokens)) !== null && _a !== void 0 ? _a : 0;
178
367
  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;
368
+ const cacheReadTokens = (_c = toFiniteNumber(totals.cacheReadTokens)) !== null && _c !== void 0 ? _c : 0;
369
+ const cacheWriteTokens = (_d = toFiniteNumber(totals.cacheWriteTokens)) !== null && _d !== void 0 ? _d : 0;
370
+ const totalTokens = (_e = toFiniteNumber(totals.totalTokens)) !== null && _e !== void 0 ? _e : inputTokens +
371
+ outputTokens +
372
+ cacheReadTokens +
373
+ cacheWriteTokens;
180
374
  if (!Number.isFinite(totalTokens))
181
375
  return;
182
376
  const statePath = getUsageStatePath(usagePath, config);
@@ -189,15 +383,25 @@ function syncUsageFromStatuslineInput(config, configPath, type, profileKey, prof
189
383
  const sessions = state.sessions || {};
190
384
  const key = buildSessionKey(normalizedType, sessionId);
191
385
  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;
386
+ const prevInput = prev ? toUsageNumber(prev.inputTokens) : 0;
387
+ const prevOutput = prev ? toUsageNumber(prev.outputTokens) : 0;
388
+ const prevCacheRead = prev ? toUsageNumber(prev.cacheReadTokens) : 0;
389
+ const prevCacheWrite = prev ? toUsageNumber(prev.cacheWriteTokens) : 0;
390
+ const prevTotal = prev ? toUsageNumber(prev.totalTokens) : 0;
195
391
  let deltaInput = inputTokens - prevInput;
196
392
  let deltaOutput = outputTokens - prevOutput;
393
+ let deltaCacheRead = cacheReadTokens - prevCacheRead;
394
+ let deltaCacheWrite = cacheWriteTokens - prevCacheWrite;
197
395
  let deltaTotal = totalTokens - prevTotal;
198
- if (deltaTotal < 0 || deltaInput < 0 || deltaOutput < 0) {
396
+ if (deltaTotal < 0 ||
397
+ deltaInput < 0 ||
398
+ deltaOutput < 0 ||
399
+ deltaCacheRead < 0 ||
400
+ deltaCacheWrite < 0) {
199
401
  deltaInput = inputTokens;
200
402
  deltaOutput = outputTokens;
403
+ deltaCacheRead = cacheReadTokens;
404
+ deltaCacheWrite = cacheWriteTokens;
201
405
  deltaTotal = totalTokens;
202
406
  }
203
407
  if (deltaTotal > 0) {
@@ -206,8 +410,12 @@ function syncUsageFromStatuslineInput(config, configPath, type, profileKey, prof
206
410
  type: normalizedType,
207
411
  profileKey: profileKey || null,
208
412
  profileName: profileName || null,
413
+ model: resolvedModel,
414
+ sessionId,
209
415
  inputTokens: deltaInput,
210
416
  outputTokens: deltaOutput,
417
+ cacheReadTokens: deltaCacheRead,
418
+ cacheWriteTokens: deltaCacheWrite,
211
419
  totalTokens: deltaTotal,
212
420
  };
213
421
  appendUsageRecord(usagePath, record);
@@ -217,10 +425,13 @@ function syncUsageFromStatuslineInput(config, configPath, type, profileKey, prof
217
425
  type: normalizedType,
218
426
  inputTokens,
219
427
  outputTokens,
428
+ cacheReadTokens,
429
+ cacheWriteTokens,
220
430
  totalTokens,
221
431
  startTs: prev ? prev.startTs : now,
222
432
  endTs: now,
223
433
  cwd: cwd || (prev ? prev.cwd : null),
434
+ model: resolvedModel,
224
435
  };
225
436
  state.sessions = sessions;
226
437
  writeUsageState(statePath, state);
@@ -433,6 +644,64 @@ function updateMinMaxTs(current, ts) {
433
644
  current.end = ts;
434
645
  }
435
646
  }
647
+ function isPlainObject(value) {
648
+ return typeof value === "object" && value !== null && !Array.isArray(value);
649
+ }
650
+ function coerceModelFromValue(value) {
651
+ if (typeof value === "string")
652
+ return normalizeModelValue(value);
653
+ if (!isPlainObject(value))
654
+ return null;
655
+ return (normalizeModelValue(value.displayName) ||
656
+ normalizeModelValue(value.display_name) ||
657
+ normalizeModelValue(value.name) ||
658
+ normalizeModelValue(value.id) ||
659
+ normalizeModelValue(value.model) ||
660
+ normalizeModelValue(value.model_name) ||
661
+ normalizeModelValue(value.model_id) ||
662
+ normalizeModelValue(value.modelId) ||
663
+ null);
664
+ }
665
+ function pickModelFromObject(record) {
666
+ return (coerceModelFromValue(record.model) ||
667
+ normalizeModelValue(record.model_name) ||
668
+ normalizeModelValue(record.modelName) ||
669
+ normalizeModelValue(record.model_id) ||
670
+ normalizeModelValue(record.modelId) ||
671
+ normalizeModelValue(record.model_display_name) ||
672
+ normalizeModelValue(record.modelDisplayName) ||
673
+ null);
674
+ }
675
+ function extractModelFromRecord(record) {
676
+ const direct = pickModelFromObject(record);
677
+ if (direct)
678
+ return direct;
679
+ const message = isPlainObject(record.message)
680
+ ? record.message
681
+ : null;
682
+ if (message) {
683
+ const fromMessage = pickModelFromObject(message);
684
+ if (fromMessage)
685
+ return fromMessage;
686
+ }
687
+ const payload = isPlainObject(record.payload)
688
+ ? record.payload
689
+ : null;
690
+ if (payload) {
691
+ const fromPayload = pickModelFromObject(payload);
692
+ if (fromPayload)
693
+ return fromPayload;
694
+ const info = isPlainObject(payload.info)
695
+ ? payload.info
696
+ : null;
697
+ if (info) {
698
+ const fromInfo = pickModelFromObject(info);
699
+ if (fromInfo)
700
+ return fromInfo;
701
+ }
702
+ }
703
+ return null;
704
+ }
436
705
  function parseCodexSessionFile(filePath) {
437
706
  const raw = fs.readFileSync(filePath, "utf8");
438
707
  const lines = raw.split(/\r?\n/);
@@ -446,6 +715,7 @@ function parseCodexSessionFile(filePath) {
446
715
  const tsRange = { start: null, end: null };
447
716
  let cwd = null;
448
717
  let sessionId = null;
718
+ let model = null;
449
719
  for (const line of lines) {
450
720
  const trimmed = line.trim();
451
721
  if (!trimmed)
@@ -454,6 +724,11 @@ function parseCodexSessionFile(filePath) {
454
724
  const parsed = JSON.parse(trimmed);
455
725
  if (!parsed || typeof parsed !== "object")
456
726
  continue;
727
+ if (!model) {
728
+ const candidate = extractModelFromRecord(parsed);
729
+ if (candidate)
730
+ model = candidate;
731
+ }
457
732
  if (parsed.timestamp)
458
733
  updateMinMaxTs(tsRange, String(parsed.timestamp));
459
734
  if (!cwd && parsed.type === "session_meta") {
@@ -515,11 +790,14 @@ function parseCodexSessionFile(filePath) {
515
790
  return {
516
791
  inputTokens: maxInput,
517
792
  outputTokens: maxOutput,
793
+ cacheReadTokens: 0,
794
+ cacheWriteTokens: 0,
518
795
  totalTokens: maxTotal,
519
796
  startTs: tsRange.start,
520
797
  endTs: tsRange.end,
521
798
  cwd,
522
799
  sessionId,
800
+ model,
523
801
  };
524
802
  }
525
803
  function parseClaudeSessionFile(filePath) {
@@ -529,9 +807,12 @@ function parseClaudeSessionFile(filePath) {
529
807
  let totalTokens = 0;
530
808
  let inputTokens = 0;
531
809
  let outputTokens = 0;
810
+ let cacheReadTokens = 0;
811
+ let cacheWriteTokens = 0;
532
812
  const tsRange = { start: null, end: null };
533
813
  let cwd = null;
534
814
  let sessionId = null;
815
+ let model = null;
535
816
  for (const line of lines) {
536
817
  const trimmed = line.trim();
537
818
  if (!trimmed)
@@ -540,6 +821,11 @@ function parseClaudeSessionFile(filePath) {
540
821
  const parsed = JSON.parse(trimmed);
541
822
  if (!parsed || typeof parsed !== "object")
542
823
  continue;
824
+ if (!model) {
825
+ const candidate = extractModelFromRecord(parsed);
826
+ if (candidate)
827
+ model = candidate;
828
+ }
543
829
  if (parsed.timestamp)
544
830
  updateMinMaxTs(tsRange, String(parsed.timestamp));
545
831
  if (!cwd && parsed.cwd)
@@ -559,6 +845,10 @@ function parseClaudeSessionFile(filePath) {
559
845
  inputTokens += input;
560
846
  if (Number.isFinite(output))
561
847
  outputTokens += output;
848
+ if (Number.isFinite(cacheCreate))
849
+ cacheWriteTokens += cacheCreate;
850
+ if (Number.isFinite(cacheRead))
851
+ cacheReadTokens += cacheRead;
562
852
  totalTokens +=
563
853
  (Number.isFinite(input) ? input : 0) +
564
854
  (Number.isFinite(output) ? output : 0) +
@@ -572,11 +862,14 @@ function parseClaudeSessionFile(filePath) {
572
862
  return {
573
863
  inputTokens,
574
864
  outputTokens,
865
+ cacheReadTokens,
866
+ cacheWriteTokens,
575
867
  totalTokens,
576
868
  startTs: tsRange.start,
577
869
  endTs: tsRange.end,
578
870
  cwd,
579
871
  sessionId,
872
+ model,
580
873
  };
581
874
  }
582
875
  const LOCK_STALE_MS = 10 * 60 * 1000;
@@ -670,7 +963,7 @@ function appendUsageRecord(usagePath, record) {
670
963
  fs.appendFileSync(usagePath, `${JSON.stringify(record)}\n`, "utf8");
671
964
  }
672
965
  function readUsageRecords(usagePath) {
673
- var _a, _b, _c, _d, _e;
966
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
674
967
  if (!usagePath || !fs.existsSync(usagePath))
675
968
  return [];
676
969
  const raw = fs.readFileSync(usagePath, "utf8");
@@ -686,16 +979,40 @@ function readUsageRecords(usagePath) {
686
979
  continue;
687
980
  const input = Number((_a = parsed.inputTokens) !== null && _a !== void 0 ? _a : 0);
688
981
  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");
982
+ const cacheRead = Number((_e = (_d = (_c = parsed.cacheReadTokens) !== null && _c !== void 0 ? _c : parsed.cache_read_input_tokens) !== null && _d !== void 0 ? _d : parsed.cacheReadInputTokens) !== null && _e !== void 0 ? _e : 0);
983
+ const cacheWrite = Number((_j = (_h = (_g = (_f = parsed.cacheWriteTokens) !== null && _f !== void 0 ? _f : parsed.cache_creation_input_tokens) !== null && _g !== void 0 ? _g : parsed.cache_write_input_tokens) !== null && _h !== void 0 ? _h : parsed.cacheWriteInputTokens) !== null && _j !== void 0 ? _j : 0);
984
+ const computedTotal = (Number.isFinite(input) ? input : 0) +
985
+ (Number.isFinite(output) ? output : 0) +
986
+ (Number.isFinite(cacheRead) ? cacheRead : 0) +
987
+ (Number.isFinite(cacheWrite) ? cacheWrite : 0);
988
+ const total = Number((_k = parsed.totalTokens) !== null && _k !== void 0 ? _k : computedTotal);
989
+ const finalTotal = Number.isFinite(total)
990
+ ? Math.max(total, computedTotal)
991
+ : computedTotal;
992
+ const type = (0, type_1.normalizeType)(parsed.type) || String((_l = parsed.type) !== null && _l !== void 0 ? _l : "unknown");
993
+ const model = normalizeModelValue(parsed.model) ||
994
+ normalizeModelValue(parsed.model_name) ||
995
+ normalizeModelValue(parsed.modelName) ||
996
+ normalizeModelValue(parsed.model_id) ||
997
+ normalizeModelValue(parsed.modelId) ||
998
+ null;
999
+ const sessionId = parsed.sessionId ||
1000
+ parsed.session_id ||
1001
+ parsed.sessionID ||
1002
+ parsed.session ||
1003
+ null;
691
1004
  records.push({
692
- ts: String((_e = parsed.ts) !== null && _e !== void 0 ? _e : ""),
1005
+ ts: String((_m = parsed.ts) !== null && _m !== void 0 ? _m : ""),
693
1006
  type,
694
1007
  profileKey: parsed.profileKey ? String(parsed.profileKey) : null,
695
1008
  profileName: parsed.profileName ? String(parsed.profileName) : null,
1009
+ model,
1010
+ sessionId: sessionId ? String(sessionId) : null,
696
1011
  inputTokens: Number.isFinite(input) ? input : 0,
697
1012
  outputTokens: Number.isFinite(output) ? output : 0,
698
- totalTokens: Number.isFinite(total) ? total : 0,
1013
+ cacheReadTokens: Number.isFinite(cacheRead) ? cacheRead : 0,
1014
+ cacheWriteTokens: Number.isFinite(cacheWrite) ? cacheWrite : 0,
1015
+ totalTokens: Number.isFinite(finalTotal) ? finalTotal : 0,
699
1016
  });
700
1017
  }
701
1018
  catch {
@@ -747,30 +1064,52 @@ function syncUsageFromSessions(config, configPath, usagePath) {
747
1064
  return;
748
1065
  const sessionKey = stats.sessionId ? buildSessionKey(type, stats.sessionId) : null;
749
1066
  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;
1067
+ const resolvedModel = (sessionPrev && sessionPrev.model) ||
1068
+ stats.model ||
1069
+ (prev && prev.model) ||
1070
+ null;
1071
+ const prevInput = prev ? toUsageNumber(prev.inputTokens) : 0;
1072
+ const prevOutput = prev ? toUsageNumber(prev.outputTokens) : 0;
1073
+ const prevCacheRead = prev ? toUsageNumber(prev.cacheReadTokens) : 0;
1074
+ const prevCacheWrite = prev ? toUsageNumber(prev.cacheWriteTokens) : 0;
1075
+ const prevTotal = prev ? toUsageNumber(prev.totalTokens) : 0;
753
1076
  const prevInputMax = sessionPrev
754
- ? Math.max(prevInput, sessionPrev.inputTokens)
1077
+ ? Math.max(prevInput, toUsageNumber(sessionPrev.inputTokens))
755
1078
  : prevInput;
756
1079
  const prevOutputMax = sessionPrev
757
- ? Math.max(prevOutput, sessionPrev.outputTokens)
1080
+ ? Math.max(prevOutput, toUsageNumber(sessionPrev.outputTokens))
758
1081
  : prevOutput;
1082
+ const prevCacheReadMax = sessionPrev
1083
+ ? Math.max(prevCacheRead, toUsageNumber(sessionPrev.cacheReadTokens))
1084
+ : prevCacheRead;
1085
+ const prevCacheWriteMax = sessionPrev
1086
+ ? Math.max(prevCacheWrite, toUsageNumber(sessionPrev.cacheWriteTokens))
1087
+ : prevCacheWrite;
759
1088
  const prevTotalMax = sessionPrev
760
- ? Math.max(prevTotal, sessionPrev.totalTokens)
1089
+ ? Math.max(prevTotal, toUsageNumber(sessionPrev.totalTokens))
761
1090
  : prevTotal;
762
1091
  let deltaInput = stats.inputTokens - prevInputMax;
763
1092
  let deltaOutput = stats.outputTokens - prevOutputMax;
1093
+ let deltaCacheRead = stats.cacheReadTokens - prevCacheReadMax;
1094
+ let deltaCacheWrite = stats.cacheWriteTokens - prevCacheWriteMax;
764
1095
  let deltaTotal = stats.totalTokens - prevTotalMax;
765
- if (deltaTotal < 0 || deltaInput < 0 || deltaOutput < 0) {
1096
+ if (deltaTotal < 0 ||
1097
+ deltaInput < 0 ||
1098
+ deltaOutput < 0 ||
1099
+ deltaCacheRead < 0 ||
1100
+ deltaCacheWrite < 0) {
766
1101
  if (sessionPrev) {
767
1102
  deltaInput = 0;
768
1103
  deltaOutput = 0;
1104
+ deltaCacheRead = 0;
1105
+ deltaCacheWrite = 0;
769
1106
  deltaTotal = 0;
770
1107
  }
771
1108
  else {
772
1109
  deltaInput = stats.inputTokens;
773
1110
  deltaOutput = stats.outputTokens;
1111
+ deltaCacheRead = stats.cacheReadTokens;
1112
+ deltaCacheWrite = stats.cacheWriteTokens;
774
1113
  deltaTotal = stats.totalTokens;
775
1114
  }
776
1115
  }
@@ -780,30 +1119,43 @@ function syncUsageFromSessions(config, configPath, usagePath) {
780
1119
  type,
781
1120
  profileKey: resolved.match.profileKey,
782
1121
  profileName: resolved.match.profileName,
1122
+ model: resolvedModel,
1123
+ sessionId: stats.sessionId,
783
1124
  inputTokens: deltaInput,
784
1125
  outputTokens: deltaOutput,
1126
+ cacheReadTokens: deltaCacheRead,
1127
+ cacheWriteTokens: deltaCacheWrite,
785
1128
  totalTokens: deltaTotal,
786
1129
  };
787
1130
  appendUsageRecord(usagePath, record);
788
1131
  }
789
1132
  if (sessionKey) {
790
1133
  const nextInput = sessionPrev
791
- ? Math.max(sessionPrev.inputTokens, stats.inputTokens)
1134
+ ? Math.max(toUsageNumber(sessionPrev.inputTokens), stats.inputTokens)
792
1135
  : stats.inputTokens;
793
1136
  const nextOutput = sessionPrev
794
- ? Math.max(sessionPrev.outputTokens, stats.outputTokens)
1137
+ ? Math.max(toUsageNumber(sessionPrev.outputTokens), stats.outputTokens)
795
1138
  : stats.outputTokens;
1139
+ const nextCacheRead = sessionPrev
1140
+ ? Math.max(toUsageNumber(sessionPrev.cacheReadTokens), stats.cacheReadTokens)
1141
+ : stats.cacheReadTokens;
1142
+ const nextCacheWrite = sessionPrev
1143
+ ? Math.max(toUsageNumber(sessionPrev.cacheWriteTokens), stats.cacheWriteTokens)
1144
+ : stats.cacheWriteTokens;
796
1145
  const nextTotal = sessionPrev
797
- ? Math.max(sessionPrev.totalTokens, stats.totalTokens)
1146
+ ? Math.max(toUsageNumber(sessionPrev.totalTokens), stats.totalTokens)
798
1147
  : stats.totalTokens;
799
1148
  sessions[sessionKey] = {
800
1149
  type,
801
1150
  inputTokens: nextInput,
802
1151
  outputTokens: nextOutput,
1152
+ cacheReadTokens: nextCacheRead,
1153
+ cacheWriteTokens: nextCacheWrite,
803
1154
  totalTokens: nextTotal,
804
1155
  startTs: sessionPrev ? sessionPrev.startTs : stats.startTs,
805
1156
  endTs: stats.endTs || (sessionPrev ? sessionPrev.endTs : null),
806
1157
  cwd: stats.cwd || (sessionPrev ? sessionPrev.cwd : null),
1158
+ model: resolvedModel,
807
1159
  };
808
1160
  }
809
1161
  files[filePath] = {
@@ -812,10 +1164,13 @@ function syncUsageFromSessions(config, configPath, usagePath) {
812
1164
  type,
813
1165
  inputTokens: stats.inputTokens,
814
1166
  outputTokens: stats.outputTokens,
1167
+ cacheReadTokens: stats.cacheReadTokens,
1168
+ cacheWriteTokens: stats.cacheWriteTokens,
815
1169
  totalTokens: stats.totalTokens,
816
1170
  startTs: stats.startTs,
817
1171
  endTs: stats.endTs,
818
1172
  cwd: stats.cwd,
1173
+ model: resolvedModel,
819
1174
  };
820
1175
  };
821
1176
  for (const filePath of codexFiles)