letmecode 0.1.8 → 0.1.9
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.
|
@@ -96,7 +96,7 @@ export class ClaudeUsageProvider extends UsageProviderBase {
|
|
|
96
96
|
const planType = typeof event.rateLimits?.plan_type === "string" ? event.rateLimits.plan_type : undefined;
|
|
97
97
|
const safeEventTimeMs = Number.isFinite(event.timestampMs) ? event.timestampMs : 0;
|
|
98
98
|
addDailyUsage(byDay, event.timestampMs, event.modelId, planType, event.totals);
|
|
99
|
-
applyRateLimits(windows, event.rateLimits, safeEventTimeMs, event.totals, planTypes);
|
|
99
|
+
applyRateLimits(windows, event.rateLimits, safeEventTimeMs, event.modelId, event.totals, planTypes);
|
|
100
100
|
}
|
|
101
101
|
parseTotals.tokenEvents = selectedEvents.length;
|
|
102
102
|
if (parseTotals.malformedLines > 0) {
|
|
@@ -872,9 +872,19 @@ function buildLiveLimitWindowRow(snapshot, planType, selectedEvents, now) {
|
|
|
872
872
|
minUsedPercent: snapshot.usedPercent,
|
|
873
873
|
maxUsedPercent: snapshot.usedPercent,
|
|
874
874
|
totals,
|
|
875
|
+
modelUsage: buildModelUsageRowsForEvents(inWindowEvents),
|
|
875
876
|
eventCount: totals.eventCount
|
|
876
877
|
};
|
|
877
878
|
}
|
|
879
|
+
function buildModelUsageRowsForEvents(events) {
|
|
880
|
+
const byModel = new Map();
|
|
881
|
+
for (const event of events) {
|
|
882
|
+
addModelUsage(byModel, event.modelId, event.totals);
|
|
883
|
+
}
|
|
884
|
+
return [...byModel.entries()]
|
|
885
|
+
.map(([modelId, totals]) => ({ modelId, totals }))
|
|
886
|
+
.sort((left, right) => right.totals.estimatedCredits - left.totals.estimatedCredits);
|
|
887
|
+
}
|
|
878
888
|
function toUtcIso(value) {
|
|
879
889
|
return new Date(value).toISOString().replace(".000Z", "Z");
|
|
880
890
|
}
|
|
@@ -349,7 +349,7 @@ async function parseSessionFile(filePath, byModel, byDay, windows, planTypes, kn
|
|
|
349
349
|
const rateLimits = asRecord(payload.rate_limits);
|
|
350
350
|
const planType = typeof rateLimits?.plan_type === "string" ? rateLimits.plan_type : undefined;
|
|
351
351
|
addDailyUsage(byDay, eventTimeMs, resolvedModelId, planType, deltaTotals);
|
|
352
|
-
applyRateLimits(windows, rateLimits, safeEventTimeMs, deltaTotals, planTypes);
|
|
352
|
+
applyRateLimits(windows, rateLimits, safeEventTimeMs, resolvedModelId, deltaTotals, planTypes);
|
|
353
353
|
}
|
|
354
354
|
return { linesRead, tokenEvents, malformedLines };
|
|
355
355
|
}
|
|
@@ -8,31 +8,35 @@ export function numberOrZero(value) {
|
|
|
8
8
|
export function asRecord(value) {
|
|
9
9
|
return value && typeof value === "object" ? value : null;
|
|
10
10
|
}
|
|
11
|
-
export function applyRateLimits(windows, rateLimits, eventTimeMs, deltaTotals, planTypes) {
|
|
11
|
+
export function applyRateLimits(windows, rateLimits, eventTimeMs, modelId, deltaTotals, planTypes) {
|
|
12
12
|
if (!rateLimits) {
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
15
|
if (typeof rateLimits.plan_type === "string") {
|
|
16
16
|
planTypes.add(rateLimits.plan_type);
|
|
17
17
|
}
|
|
18
|
-
upsertWindow(windows, "primary", rateLimits, asRecord(rateLimits.primary), eventTimeMs, deltaTotals);
|
|
19
|
-
upsertWindow(windows, "secondary", rateLimits, asRecord(rateLimits.secondary), eventTimeMs, deltaTotals);
|
|
18
|
+
upsertWindow(windows, "primary", rateLimits, asRecord(rateLimits.primary), eventTimeMs, modelId, deltaTotals);
|
|
19
|
+
upsertWindow(windows, "secondary", rateLimits, asRecord(rateLimits.secondary), eventTimeMs, modelId, deltaTotals);
|
|
20
20
|
}
|
|
21
21
|
export function buildWindowLists(windows) {
|
|
22
|
-
const rows = collapseNearbyWindows([...windows.values()].map((window) =>
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
22
|
+
const rows = collapseNearbyWindows([...windows.values()].map((window) => {
|
|
23
|
+
const usage = computeWindowUsage(window.events);
|
|
24
|
+
return {
|
|
25
|
+
scope: window.scope,
|
|
26
|
+
planType: window.planType,
|
|
27
|
+
limitId: window.limitId,
|
|
28
|
+
windowMinutes: window.windowMinutes,
|
|
29
|
+
startTimeUtcIso: formatIsoFromSeconds(window.minStartsAt),
|
|
30
|
+
endTimeUtcIso: formatIsoFromSeconds(window.maxResetsAt),
|
|
31
|
+
firstSeenUtcIso: formatIsoFromMilliseconds(window.firstSeenMs),
|
|
32
|
+
lastSeenUtcIso: formatIsoFromMilliseconds(window.lastSeenMs),
|
|
33
|
+
minUsedPercent: window.minUsedPercent,
|
|
34
|
+
maxUsedPercent: window.maxUsedPercent,
|
|
35
|
+
totals: usage.totals,
|
|
36
|
+
modelUsage: usage.modelUsage,
|
|
37
|
+
eventCount: 0
|
|
38
|
+
};
|
|
39
|
+
}))
|
|
36
40
|
.map((row) => ({
|
|
37
41
|
...row,
|
|
38
42
|
eventCount: row.totals.eventCount
|
|
@@ -71,7 +75,11 @@ function collapseNearbyWindows(rows) {
|
|
|
71
75
|
if (!existing) {
|
|
72
76
|
collapsed.set(key, {
|
|
73
77
|
...row,
|
|
74
|
-
totals: cloneUsageTotals(row.totals)
|
|
78
|
+
totals: cloneUsageTotals(row.totals),
|
|
79
|
+
modelUsage: row.modelUsage.map((entry) => ({
|
|
80
|
+
modelId: entry.modelId,
|
|
81
|
+
totals: cloneUsageTotals(entry.totals)
|
|
82
|
+
}))
|
|
75
83
|
});
|
|
76
84
|
continue;
|
|
77
85
|
}
|
|
@@ -86,28 +94,34 @@ function collapseNearbyWindows(rows) {
|
|
|
86
94
|
existing.minUsedPercent = Math.min(existing.minUsedPercent, row.minUsedPercent);
|
|
87
95
|
existing.maxUsedPercent = Math.max(existing.maxUsedPercent, row.maxUsedPercent);
|
|
88
96
|
addUsageTotals(existing.totals, row.totals);
|
|
97
|
+
existing.modelUsage = mergeModelUsageRows(existing.modelUsage, row.modelUsage);
|
|
89
98
|
existing.eventCount = existing.totals.eventCount;
|
|
90
99
|
}
|
|
91
100
|
return [...collapsed.values()];
|
|
92
101
|
}
|
|
93
|
-
function
|
|
102
|
+
function computeWindowUsage(events) {
|
|
94
103
|
// Session files are not guaranteed to be parsed in timestamp order, so
|
|
95
104
|
// saturation has to be applied after we sort the captured window events.
|
|
96
105
|
const totals = createEmptyUsageTotals();
|
|
106
|
+
const byModel = new Map();
|
|
97
107
|
let sawBelowCap = false;
|
|
98
108
|
let isExhausted = false;
|
|
99
109
|
for (const event of [...events].sort((left, right) => left.eventTimeMs - right.eventTimeMs)) {
|
|
100
110
|
sawBelowCap || (sawBelowCap = event.usedPercent < 100);
|
|
101
111
|
if (!isExhausted) {
|
|
102
112
|
addUsageTotals(totals, event.totals);
|
|
113
|
+
addWindowModelUsage(byModel, event.modelId, event.totals);
|
|
103
114
|
if (sawBelowCap && event.usedPercent >= 100) {
|
|
104
115
|
isExhausted = true;
|
|
105
116
|
}
|
|
106
117
|
}
|
|
107
118
|
}
|
|
108
|
-
return
|
|
119
|
+
return {
|
|
120
|
+
totals,
|
|
121
|
+
modelUsage: buildModelUsageRows(byModel)
|
|
122
|
+
};
|
|
109
123
|
}
|
|
110
|
-
function upsertWindow(windows, scope, rateLimits, window, eventTimeMs, deltaTotals) {
|
|
124
|
+
function upsertWindow(windows, scope, rateLimits, window, eventTimeMs, modelId, deltaTotals) {
|
|
111
125
|
if (!window) {
|
|
112
126
|
return;
|
|
113
127
|
}
|
|
@@ -132,7 +146,7 @@ function upsertWindow(windows, scope, rateLimits, window, eventTimeMs, deltaTota
|
|
|
132
146
|
lastSeenMs: eventTimeMs,
|
|
133
147
|
minUsedPercent: usedPercent,
|
|
134
148
|
maxUsedPercent: usedPercent,
|
|
135
|
-
events: [{ eventTimeMs, usedPercent, totals: cloneUsageTotals(deltaTotals) }]
|
|
149
|
+
events: [{ eventTimeMs, modelId, usedPercent, totals: cloneUsageTotals(deltaTotals) }]
|
|
136
150
|
});
|
|
137
151
|
return;
|
|
138
152
|
}
|
|
@@ -142,5 +156,25 @@ function upsertWindow(windows, scope, rateLimits, window, eventTimeMs, deltaTota
|
|
|
142
156
|
existing.lastSeenMs = Math.max(existing.lastSeenMs, eventTimeMs);
|
|
143
157
|
existing.minUsedPercent = Math.min(existing.minUsedPercent, usedPercent);
|
|
144
158
|
existing.maxUsedPercent = Math.max(existing.maxUsedPercent, usedPercent);
|
|
145
|
-
existing.events.push({ eventTimeMs, usedPercent, totals: cloneUsageTotals(deltaTotals) });
|
|
159
|
+
existing.events.push({ eventTimeMs, modelId, usedPercent, totals: cloneUsageTotals(deltaTotals) });
|
|
160
|
+
}
|
|
161
|
+
function addWindowModelUsage(byModel, modelId, totals) {
|
|
162
|
+
const existing = byModel.get(modelId);
|
|
163
|
+
if (!existing) {
|
|
164
|
+
byModel.set(modelId, cloneUsageTotals(totals));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
addUsageTotals(existing, totals);
|
|
168
|
+
}
|
|
169
|
+
function buildModelUsageRows(byModel) {
|
|
170
|
+
return [...byModel.entries()]
|
|
171
|
+
.map(([modelId, totals]) => ({ modelId, totals }))
|
|
172
|
+
.sort((left, right) => right.totals.estimatedCredits - left.totals.estimatedCredits);
|
|
173
|
+
}
|
|
174
|
+
function mergeModelUsageRows(left, right) {
|
|
175
|
+
const byModel = new Map();
|
|
176
|
+
for (const row of [...left, ...right]) {
|
|
177
|
+
addWindowModelUsage(byModel, row.modelId, row.totals);
|
|
178
|
+
}
|
|
179
|
+
return buildModelUsageRows(byModel);
|
|
146
180
|
}
|
|
@@ -37,25 +37,23 @@ function buildAnonymousUsageReport(stats, window, letmecodeVersion) {
|
|
|
37
37
|
used_percents: resolveReportedUsedPercents(window),
|
|
38
38
|
used_exhausted: window.maxUsedPercent >= 100,
|
|
39
39
|
value_dollars: roundDollars(window.totals.estimatedCredits * CREDIT_TO_DOLLARS),
|
|
40
|
-
usage_raw: buildUsageRaw(
|
|
40
|
+
usage_raw: buildUsageRaw(window.modelUsage),
|
|
41
41
|
letmecode_version: letmecodeVersion
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
-
function buildUsageRaw(
|
|
45
|
-
const usageRaw = {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
44
|
+
function buildUsageRaw(modelUsage) {
|
|
45
|
+
const usageRaw = {};
|
|
46
|
+
for (const row of modelUsage) {
|
|
47
|
+
usageRaw[row.modelId] = {
|
|
48
|
+
output: row.totals.outputTokens,
|
|
49
|
+
input_non_cache: row.totals.inputTokens,
|
|
50
|
+
input_cache_w5m: row.totals.cacheWrite5mInputTokens,
|
|
51
|
+
input_cache_w1h: row.totals.cacheWrite1hInputTokens,
|
|
52
|
+
input_cache_read: row.totals.cacheReadInputTokens
|
|
53
|
+
};
|
|
53
54
|
}
|
|
54
55
|
return usageRaw;
|
|
55
56
|
}
|
|
56
|
-
function isAnthropicProvider(providerId) {
|
|
57
|
-
return providerId === "claude" || providerId === "claude-vscode";
|
|
58
|
-
}
|
|
59
57
|
function resolveReportedUsedPercents(window) {
|
|
60
58
|
if (window.minUsedPercent === window.maxUsedPercent) {
|
|
61
59
|
return clampPercent(window.maxUsedPercent);
|