@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.
- package/README.md +57 -0
- package/README_zh.md +57 -0
- package/bin/cli/args.js +13 -0
- package/bin/cli/help.js +5 -0
- package/bin/cli/index.js +2 -1
- package/bin/commands/index.js +3 -1
- package/bin/commands/list.js +44 -2
- package/bin/commands/usage.js +41 -0
- package/bin/index.js +7 -0
- package/bin/statusline/debug.js +1 -0
- package/bin/statusline/format.js +6 -9
- package/bin/statusline/index.js +46 -8
- package/bin/statusline/input.js +9 -91
- package/bin/statusline/usage/claude.js +181 -0
- package/bin/statusline/usage/codex.js +177 -0
- package/bin/statusline/usage.js +20 -76
- package/bin/usage/index.js +647 -50
- package/bin/usage/pricing.js +303 -0
- package/code-env.example.json +55 -0
- package/package.json +1 -1
- package/src/cli/args.ts +14 -0
- package/src/cli/help.ts +5 -0
- package/src/cli/index.ts +7 -1
- package/src/commands/index.ts +1 -0
- package/src/commands/list.ts +74 -4
- package/src/commands/usage.ts +53 -0
- package/src/index.ts +11 -0
- package/src/statusline/debug.ts +1 -1
- package/src/statusline/format.ts +9 -10
- package/src/statusline/index.ts +74 -24
- package/src/statusline/input.ts +13 -154
- package/src/statusline/types.ts +6 -0
- package/src/statusline/usage/claude.ts +299 -0
- package/src/statusline/usage/codex.ts +258 -0
- package/src/statusline/usage.ts +24 -119
- package/src/types.ts +27 -0
- package/src/usage/index.ts +779 -44
- package/src/usage/pricing.ts +323 -0
- package/PLAN.md +0 -33
package/bin/usage/index.js
CHANGED
|
@@ -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
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
121
|
+
const startMs = todayStart.getTime();
|
|
81
122
|
const tomorrowStart = new Date(todayStart);
|
|
82
123
|
tomorrowStart.setDate(todayStart.getDate() + 1);
|
|
83
|
-
|
|
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
|
-
|
|
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,
|
|
142
|
+
const addTotals = (map, key, amounts, ts) => {
|
|
93
143
|
if (!key)
|
|
94
144
|
return;
|
|
95
|
-
const current = map.get(key) ||
|
|
96
|
-
current.total +=
|
|
97
|
-
|
|
98
|
-
|
|
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
|
|
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
|
|
164
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
917
|
+
inputTokens,
|
|
517
918
|
outputTokens: maxOutput,
|
|
518
|
-
|
|
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
|
|
688
|
-
|
|
689
|
-
const
|
|
690
|
-
const
|
|
691
|
-
|
|
692
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
751
|
-
|
|
752
|
-
|
|
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 ||
|
|
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 {
|