letmecode 0.1.11 → 0.1.12
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.
|
@@ -61,8 +61,8 @@ export function buildHelpText() {
|
|
|
61
61
|
" q or Esc Quit",
|
|
62
62
|
"",
|
|
63
63
|
"Trace logging:",
|
|
64
|
-
" --log-to PATH writes Claude
|
|
65
|
-
" session root selection, parsed session file summaries,
|
|
64
|
+
" --log-to PATH writes Claude detection details,",
|
|
65
|
+
" session root selection, parsed session file summaries, aggregated usage selection,",
|
|
66
66
|
" every candidate binary path check, the final found/not-found result,",
|
|
67
67
|
" and the raw /usage command output plus live window matching details."
|
|
68
68
|
].join("\n");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import https from "node:https";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
4
5
|
import fs from "node:fs";
|
|
@@ -70,6 +71,7 @@ const UNPRICED_MODELS = new Set([
|
|
|
70
71
|
const ANTIGRAVITY_PRIMARY_WINDOW_MINUTES = 5 * 60;
|
|
71
72
|
const ANTIGRAVITY_WEEKLY_WINDOW_MINUTES = 7 * 24 * 60;
|
|
72
73
|
const ANTIGRAVITY_QUOTA_SUMMARY_PATH = "/exa.language_server_pb.LanguageServerService/RetrieveUserQuotaSummary";
|
|
74
|
+
const ANTIGRAVITY_USER_STATUS_PATH = "/exa.language_server_pb.LanguageServerService/GetUserStatus";
|
|
73
75
|
const ANTIGRAVITY_DEBUG_LOG_PATH = process.env.LETMECODE_ANTIGRAVITY_DEBUG_LOG ??
|
|
74
76
|
path.join(os.tmpdir(), "letmecode-antigravity-debug.jsonl");
|
|
75
77
|
const GEMINI_QUOTA_MODELS = [
|
|
@@ -170,7 +172,13 @@ export class AntigravityUsageProvider extends UsageProviderBase {
|
|
|
170
172
|
dayUsage: buildDailyUsageRows(byDay),
|
|
171
173
|
primaryLimitWindows: limitWindows.filter((window) => window.scope === "primary"),
|
|
172
174
|
secondaryLimitWindows: limitWindows.filter((window) => window.scope === "secondary"),
|
|
173
|
-
warnings
|
|
175
|
+
warnings,
|
|
176
|
+
analytics: quotaSnapshot?.userIdHash
|
|
177
|
+
? {
|
|
178
|
+
agentName: normalizeAnalyticsAgentName(this.label),
|
|
179
|
+
userIdHash: quotaSnapshot.userIdHash
|
|
180
|
+
}
|
|
181
|
+
: undefined
|
|
174
182
|
};
|
|
175
183
|
}
|
|
176
184
|
}
|
|
@@ -199,11 +207,29 @@ async function collectAntigravityQuotaFromLocalRpc() {
|
|
|
199
207
|
throw new Error("Antigravity local language server was not found.");
|
|
200
208
|
}
|
|
201
209
|
const fetchedAt = Date.now();
|
|
202
|
-
const
|
|
203
|
-
|
|
210
|
+
const [quotaResult, statusResult] = await Promise.allSettled([
|
|
211
|
+
readAntigravityQuotaSummary(server),
|
|
212
|
+
readAntigravityUserStatus(server)
|
|
213
|
+
]);
|
|
214
|
+
printAntigravityUserStatusResponse(statusResult);
|
|
215
|
+
if (quotaResult.status === "rejected") {
|
|
216
|
+
throw quotaResult.reason;
|
|
217
|
+
}
|
|
218
|
+
const entries = parseAntigravityQuotaEntries(quotaResult.value);
|
|
219
|
+
const planType = statusResult.status === "fulfilled"
|
|
220
|
+
? parseAntigravityPlanType(statusResult.value)
|
|
221
|
+
: "unknown";
|
|
222
|
+
const userIdHash = statusResult.status === "fulfilled"
|
|
223
|
+
? parseAntigravityUserIdHash(statusResult.value, normalizeAnalyticsAgentName("Antigravity"))
|
|
224
|
+
: null;
|
|
225
|
+
for (const entry of entries) {
|
|
226
|
+
entry.planType = planType;
|
|
227
|
+
}
|
|
204
228
|
await writeAntigravityDebugEvent("quota-rpc-response", {
|
|
205
229
|
port: server.port,
|
|
206
|
-
|
|
230
|
+
quotaPath: ANTIGRAVITY_QUOTA_SUMMARY_PATH,
|
|
231
|
+
userStatusPath: ANTIGRAVITY_USER_STATUS_PATH,
|
|
232
|
+
planType,
|
|
207
233
|
entries: entries.map((entry) => ({
|
|
208
234
|
limitId: entry.limitId,
|
|
209
235
|
remainingFraction: entry.remainingFraction,
|
|
@@ -212,13 +238,39 @@ async function collectAntigravityQuotaFromLocalRpc() {
|
|
|
212
238
|
scope: entry.scope,
|
|
213
239
|
modelIds: entry.modelIds
|
|
214
240
|
})),
|
|
215
|
-
...(isAntigravityRawDebugEnabled()
|
|
241
|
+
...(isAntigravityRawDebugEnabled()
|
|
242
|
+
? {
|
|
243
|
+
quotaPayload: quotaResult.value,
|
|
244
|
+
userStatusPayload: statusResult.status === "fulfilled"
|
|
245
|
+
? statusResult.value
|
|
246
|
+
: {
|
|
247
|
+
error: statusResult.reason instanceof Error
|
|
248
|
+
? statusResult.reason.message
|
|
249
|
+
: String(statusResult.reason)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
: {})
|
|
216
253
|
});
|
|
217
254
|
return {
|
|
218
255
|
entries,
|
|
219
|
-
fetchedAt
|
|
256
|
+
fetchedAt,
|
|
257
|
+
userIdHash
|
|
220
258
|
};
|
|
221
259
|
}
|
|
260
|
+
function printAntigravityUserStatusResponse(statusResult) {
|
|
261
|
+
try {
|
|
262
|
+
if (statusResult.status === "fulfilled") {
|
|
263
|
+
console.error("Antigravity user status response:", JSON.stringify(statusResult.value, null, 2));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
console.error("Antigravity user status response error:", statusResult.reason instanceof Error
|
|
267
|
+
? statusResult.reason.message
|
|
268
|
+
: String(statusResult.reason));
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
// Debug printing must never disturb usage collection.
|
|
272
|
+
}
|
|
273
|
+
}
|
|
222
274
|
function recordsForQuotaWindow(quota, records) {
|
|
223
275
|
if (quota.modelIds.length === 0) {
|
|
224
276
|
return [];
|
|
@@ -458,13 +510,29 @@ async function probeAntigravityPorts(ports, csrfToken) {
|
|
|
458
510
|
async function readAntigravityQuotaSummary(server) {
|
|
459
511
|
return requestAntigravityQuotaSummary(server);
|
|
460
512
|
}
|
|
513
|
+
async function readAntigravityUserStatus(server) {
|
|
514
|
+
return requestAntigravityUserStatus(server);
|
|
515
|
+
}
|
|
461
516
|
function requestAntigravityQuotaSummary(server) {
|
|
462
|
-
|
|
517
|
+
return requestAntigravityRpc(server, ANTIGRAVITY_QUOTA_SUMMARY_PATH, {});
|
|
518
|
+
}
|
|
519
|
+
function requestAntigravityUserStatus(server) {
|
|
520
|
+
return requestAntigravityRpc(server, ANTIGRAVITY_USER_STATUS_PATH, {
|
|
521
|
+
metadata: {
|
|
522
|
+
ideName: "antigravity",
|
|
523
|
+
extensionName: "antigravity",
|
|
524
|
+
ideVersion: "unknown",
|
|
525
|
+
locale: "en"
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
function requestAntigravityRpc(server, rpcPath, payload) {
|
|
530
|
+
const body = JSON.stringify(payload);
|
|
463
531
|
return new Promise((resolve, reject) => {
|
|
464
532
|
const request = https.request({
|
|
465
533
|
hostname: "127.0.0.1",
|
|
466
534
|
port: server.port,
|
|
467
|
-
path:
|
|
535
|
+
path: rpcPath,
|
|
468
536
|
method: "POST",
|
|
469
537
|
timeout: 5000,
|
|
470
538
|
agent: antigravityHttpsAgent,
|
|
@@ -481,7 +549,7 @@ function requestAntigravityQuotaSummary(server) {
|
|
|
481
549
|
response.on("end", () => {
|
|
482
550
|
const responseBody = Buffer.concat(chunks).toString("utf8");
|
|
483
551
|
if (!response.statusCode || response.statusCode < 200 || response.statusCode >= 300) {
|
|
484
|
-
reject(new Error(`Unexpected Antigravity
|
|
552
|
+
reject(new Error(`Unexpected Antigravity RPC response from ${rpcPath}: ${response.statusCode ?? "unknown"}`));
|
|
485
553
|
return;
|
|
486
554
|
}
|
|
487
555
|
try {
|
|
@@ -493,7 +561,7 @@ function requestAntigravityQuotaSummary(server) {
|
|
|
493
561
|
});
|
|
494
562
|
});
|
|
495
563
|
request.on("timeout", () => {
|
|
496
|
-
request.destroy(new Error(
|
|
564
|
+
request.destroy(new Error(`Timed out reading Antigravity RPC ${rpcPath}.`));
|
|
497
565
|
});
|
|
498
566
|
request.on("error", reject);
|
|
499
567
|
request.end(body);
|
|
@@ -550,6 +618,112 @@ export function parseAntigravityQuotaEntries(payload) {
|
|
|
550
618
|
}
|
|
551
619
|
return entries;
|
|
552
620
|
}
|
|
621
|
+
export function parseAntigravityPlanType(payload) {
|
|
622
|
+
const root = asRecord(payload);
|
|
623
|
+
const response = asRecord(root?.response);
|
|
624
|
+
const candidates = [
|
|
625
|
+
readNestedString(root, [
|
|
626
|
+
"userStatus",
|
|
627
|
+
"planStatus",
|
|
628
|
+
"planInfo",
|
|
629
|
+
"planName"
|
|
630
|
+
]),
|
|
631
|
+
readNestedString(root, [
|
|
632
|
+
"userStatus",
|
|
633
|
+
"planStatus",
|
|
634
|
+
"planInfo",
|
|
635
|
+
"planDisplayName"
|
|
636
|
+
]),
|
|
637
|
+
readNestedString(root, [
|
|
638
|
+
"userStatus",
|
|
639
|
+
"planStatus",
|
|
640
|
+
"planName"
|
|
641
|
+
]),
|
|
642
|
+
readNestedString(root, [
|
|
643
|
+
"userStatus",
|
|
644
|
+
"planName"
|
|
645
|
+
]),
|
|
646
|
+
readNestedString(response, [
|
|
647
|
+
"userStatus",
|
|
648
|
+
"planStatus",
|
|
649
|
+
"planInfo",
|
|
650
|
+
"planName"
|
|
651
|
+
]),
|
|
652
|
+
readNestedString(response, [
|
|
653
|
+
"userStatus",
|
|
654
|
+
"planStatus",
|
|
655
|
+
"planInfo",
|
|
656
|
+
"planDisplayName"
|
|
657
|
+
]),
|
|
658
|
+
readNestedString(response, [
|
|
659
|
+
"userStatus",
|
|
660
|
+
"planStatus",
|
|
661
|
+
"planName"
|
|
662
|
+
]),
|
|
663
|
+
readNestedString(response, [
|
|
664
|
+
"userStatus",
|
|
665
|
+
"planName"
|
|
666
|
+
]),
|
|
667
|
+
readNestedString(response, [
|
|
668
|
+
"planStatus",
|
|
669
|
+
"planInfo",
|
|
670
|
+
"planName"
|
|
671
|
+
]),
|
|
672
|
+
readNestedString(response, [
|
|
673
|
+
"planInfo",
|
|
674
|
+
"planName"
|
|
675
|
+
]),
|
|
676
|
+
readNestedString(response, ["planName"]),
|
|
677
|
+
readNestedString(root, ["planName"])
|
|
678
|
+
];
|
|
679
|
+
const rawPlan = candidates.find((value) => Boolean(value));
|
|
680
|
+
return normalizeAntigravityPlanType(rawPlan ?? null);
|
|
681
|
+
}
|
|
682
|
+
export function parseAntigravityUserIdHash(payload, agentName) {
|
|
683
|
+
const root = asRecord(payload);
|
|
684
|
+
const response = asRecord(root?.response);
|
|
685
|
+
const email = readNestedString(root, ["userStatus", "email"]) ??
|
|
686
|
+
readNestedString(response, ["userStatus", "email"]) ??
|
|
687
|
+
readNestedString(root, ["email"]) ??
|
|
688
|
+
readNestedString(response, ["email"]);
|
|
689
|
+
return buildUserIdHash([agentName, email ?? ""]);
|
|
690
|
+
}
|
|
691
|
+
function readNestedString(value, pathParts) {
|
|
692
|
+
let current = value;
|
|
693
|
+
for (const part of pathParts) {
|
|
694
|
+
const record = asRecord(current);
|
|
695
|
+
if (!record) {
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
698
|
+
current = record[part];
|
|
699
|
+
}
|
|
700
|
+
return asString(current);
|
|
701
|
+
}
|
|
702
|
+
function normalizeAntigravityPlanType(value) {
|
|
703
|
+
if (!value) {
|
|
704
|
+
return "unknown";
|
|
705
|
+
}
|
|
706
|
+
const normalized = value.toLowerCase();
|
|
707
|
+
if (normalized.includes("ultra")) {
|
|
708
|
+
return "ultra";
|
|
709
|
+
}
|
|
710
|
+
if (normalized.includes("pro") || normalized.includes("premium")) {
|
|
711
|
+
return "pro";
|
|
712
|
+
}
|
|
713
|
+
if (normalized.includes("free") || normalized.includes("standard")) {
|
|
714
|
+
return "free";
|
|
715
|
+
}
|
|
716
|
+
return value;
|
|
717
|
+
}
|
|
718
|
+
function normalizeAnalyticsAgentName(label) {
|
|
719
|
+
return label.replace(/\s+/g, "");
|
|
720
|
+
}
|
|
721
|
+
function buildUserIdHash(parts) {
|
|
722
|
+
if (parts.some((part) => !part)) {
|
|
723
|
+
return null;
|
|
724
|
+
}
|
|
725
|
+
return createHash("md5").update(parts.join("-")).digest("hex");
|
|
726
|
+
}
|
|
553
727
|
function resolveQuotaWindow(window) {
|
|
554
728
|
switch (window) {
|
|
555
729
|
case "5h":
|
|
@@ -28,7 +28,6 @@ const VSCODE_CLAUDE_EXTENSION_PREFIX = "anthropic.claude-code-";
|
|
|
28
28
|
const CLAUDE_SESSION_WINDOW_MINUTES = 5 * 60;
|
|
29
29
|
const CLAUDE_WEEK_WINDOW_MINUTES = 7 * 24 * 60;
|
|
30
30
|
const ANSI_ESCAPE_SEQUENCE = /\u001B\[[0-9;]*[A-Za-z]/g;
|
|
31
|
-
const DEFAULT_CLAUDE_ENTRYPOINTS = ["sdk-cli", "claude"];
|
|
32
31
|
const MONTH_INDEX_BY_LABEL = {
|
|
33
32
|
jan: 0,
|
|
34
33
|
feb: 1,
|
|
@@ -51,18 +50,16 @@ export class ClaudeUsageProvider extends UsageProviderBase {
|
|
|
51
50
|
constructor(options = {}) {
|
|
52
51
|
super(options.id ?? "claude", options.label ?? "Claude");
|
|
53
52
|
this.root = path.resolve(options.root ?? os.homedir());
|
|
54
|
-
this.entrypoints = new Set(options.entrypoints ?? DEFAULT_CLAUDE_ENTRYPOINTS);
|
|
55
|
-
this.usageCommandKind = options.usageCommandKind ?? "cli";
|
|
56
53
|
this.readUsageCommandOutput = options.readUsageCommandOutput;
|
|
57
54
|
this.readAuthStatusOutput = options.readAuthStatusOutput;
|
|
58
55
|
this.now = options.now ?? (() => new Date());
|
|
59
56
|
}
|
|
60
57
|
async getStats(options = {}) {
|
|
61
|
-
traceClaude(options.traceLogger,
|
|
62
|
-
const resolvedSessionsRoot = await resolveClaudeSessionsRoot(this.root,
|
|
58
|
+
traceClaude(options.traceLogger, `Starting stats collection with root=${this.root} (aggregating all Claude entrypoints).`);
|
|
59
|
+
const resolvedSessionsRoot = await resolveClaudeSessionsRoot(this.root, options.traceLogger);
|
|
63
60
|
const sessionsRoot = resolvedSessionsRoot.rootPath;
|
|
64
61
|
const agentName = normalizeAnalyticsAgentName(this.label);
|
|
65
|
-
const userIdHash = await readClaudeUserIdHash(this.root, this.
|
|
62
|
+
const userIdHash = await readClaudeUserIdHash(this.root, this.readAuthStatusOutput, agentName, options.traceLogger);
|
|
66
63
|
const byModel = new Map();
|
|
67
64
|
const byDay = createDailyUsageAggregates();
|
|
68
65
|
const windows = createLimitWindowAggregates();
|
|
@@ -75,11 +72,11 @@ export class ClaudeUsageProvider extends UsageProviderBase {
|
|
|
75
72
|
tokenEvents: 0,
|
|
76
73
|
malformedLines: 0
|
|
77
74
|
};
|
|
78
|
-
const parsedSessionFiles = await loadParsedClaudeSessionFiles(sessionsRoot,
|
|
79
|
-
traceClaude(options.traceLogger,
|
|
75
|
+
const parsedSessionFiles = await loadParsedClaudeSessionFiles(sessionsRoot, options.traceLogger);
|
|
76
|
+
traceClaude(options.traceLogger, `Loaded ${parsedSessionFiles.length} parsed session file(s) from ${sessionsRoot}.`);
|
|
80
77
|
for (const file of parsedSessionFiles) {
|
|
81
|
-
const matchingEvents = file.events
|
|
82
|
-
traceClaude(options.traceLogger,
|
|
78
|
+
const matchingEvents = file.events;
|
|
79
|
+
traceClaude(options.traceLogger, [
|
|
83
80
|
`Session file ${describeSessionFilePath(sessionsRoot, file.filePath)}:`,
|
|
84
81
|
`lines=${file.linesRead}`,
|
|
85
82
|
`malformed=${file.malformedLines}`,
|
|
@@ -103,7 +100,7 @@ export class ClaudeUsageProvider extends UsageProviderBase {
|
|
|
103
100
|
...parsedEvents.keyedEvents.values(),
|
|
104
101
|
...parsedEvents.unkeyedEvents.values()
|
|
105
102
|
];
|
|
106
|
-
traceClaude(options.traceLogger,
|
|
103
|
+
traceClaude(options.traceLogger, [
|
|
107
104
|
`Transcript selection summary: filesWithMatches=${parseTotals.filesScanned}/${parsedSessionFiles.length}`,
|
|
108
105
|
`selectedEvents=${selectedEvents.length}`,
|
|
109
106
|
`duplicateUsageKeys=${parsedEvents.duplicateUsageKeys}`,
|
|
@@ -111,7 +108,7 @@ export class ClaudeUsageProvider extends UsageProviderBase {
|
|
|
111
108
|
`duplicateUnkeyedEvents=${parsedEvents.duplicateUnkeyedEvents}`
|
|
112
109
|
].join(" "));
|
|
113
110
|
if (selectedEvents.length === 0 && parsedSessionFiles.length > 0) {
|
|
114
|
-
traceClaude(options.traceLogger,
|
|
111
|
+
traceClaude(options.traceLogger, "No assistant usage events were found in the parsed Claude session files.");
|
|
115
112
|
}
|
|
116
113
|
for (const event of selectedEvents) {
|
|
117
114
|
addModelUsage(byModel, event.modelId, event.totals);
|
|
@@ -150,7 +147,6 @@ export class ClaudeUsageProvider extends UsageProviderBase {
|
|
|
150
147
|
const [fallbackPrimaryLimitWindows, fallbackSecondaryLimitWindows] = buildWindowLists(windows);
|
|
151
148
|
const liveLimitWindows = await buildLiveLimitWindows({
|
|
152
149
|
root: this.root,
|
|
153
|
-
usageCommandKind: this.usageCommandKind,
|
|
154
150
|
readUsageCommandOutput: this.readUsageCommandOutput,
|
|
155
151
|
readAuthStatusOutput: this.readAuthStatusOutput,
|
|
156
152
|
traceLogger: options.traceLogger,
|
|
@@ -163,7 +159,7 @@ export class ClaudeUsageProvider extends UsageProviderBase {
|
|
|
163
159
|
const secondaryLimitWindows = liveLimitWindows.secondaryLimitWindows.length > 0
|
|
164
160
|
? liveLimitWindows.secondaryLimitWindows
|
|
165
161
|
: fallbackSecondaryLimitWindows;
|
|
166
|
-
traceClaude(options.traceLogger,
|
|
162
|
+
traceClaude(options.traceLogger, [
|
|
167
163
|
`Finished stats collection:`,
|
|
168
164
|
`filesScanned=${parseTotals.filesScanned}`,
|
|
169
165
|
`linesRead=${parseTotals.linesRead}`,
|
|
@@ -275,14 +271,14 @@ function resolveClaudeCacheWriteBreakdown(usage) {
|
|
|
275
271
|
function isSessionFile(filePath) {
|
|
276
272
|
return filePath.endsWith(".jsonl");
|
|
277
273
|
}
|
|
278
|
-
async function resolveClaudeSessionsRoot(root,
|
|
274
|
+
async function resolveClaudeSessionsRoot(root, traceLogger) {
|
|
279
275
|
const candidates = buildClaudeSessionsRootCandidates(root);
|
|
280
|
-
traceClaude(traceLogger,
|
|
276
|
+
traceClaude(traceLogger, `Checking ${candidates.length} Claude session root candidate(s).`);
|
|
281
277
|
for (const candidate of candidates) {
|
|
282
278
|
const exists = await isDirectory(candidate.rootPath);
|
|
283
|
-
traceClaude(traceLogger,
|
|
279
|
+
traceClaude(traceLogger, `Session root candidate ${candidate.rootLabel} -> ${candidate.rootPath} (${exists ? "exists" : "missing"}).`);
|
|
284
280
|
if (exists) {
|
|
285
|
-
traceClaude(traceLogger,
|
|
281
|
+
traceClaude(traceLogger, `Selected session root ${candidate.rootLabel} -> ${candidate.rootPath}.`);
|
|
286
282
|
return candidate;
|
|
287
283
|
}
|
|
288
284
|
}
|
|
@@ -290,7 +286,7 @@ async function resolveClaudeSessionsRoot(root, usageCommandKind, traceLogger) {
|
|
|
290
286
|
rootLabel: "~/.claude/projects",
|
|
291
287
|
rootPath: path.join(path.resolve(root), ".claude", "projects")
|
|
292
288
|
};
|
|
293
|
-
traceClaude(traceLogger,
|
|
289
|
+
traceClaude(traceLogger, `No session root candidate exists yet; defaulting to ${fallbackCandidate.rootLabel} -> ${fallbackCandidate.rootPath}.`);
|
|
294
290
|
return fallbackCandidate;
|
|
295
291
|
}
|
|
296
292
|
function buildClaudeSessionsRootCandidates(root) {
|
|
@@ -379,22 +375,22 @@ async function* walkSessionFiles(directory) {
|
|
|
379
375
|
}
|
|
380
376
|
}
|
|
381
377
|
}
|
|
382
|
-
async function loadParsedClaudeSessionFiles(sessionsRoot,
|
|
378
|
+
async function loadParsedClaudeSessionFiles(sessionsRoot, traceLogger) {
|
|
383
379
|
const cacheKey = path.resolve(sessionsRoot);
|
|
384
380
|
const cached = parsedClaudeSessionFilesCache.get(cacheKey);
|
|
385
381
|
if (cached) {
|
|
386
382
|
const files = await cached;
|
|
387
|
-
traceClaude(traceLogger,
|
|
383
|
+
traceClaude(traceLogger, `Session parse cache hit for ${sessionsRoot} (${files.length} file(s)).`);
|
|
388
384
|
return files;
|
|
389
385
|
}
|
|
390
386
|
const pending = (async () => {
|
|
391
387
|
const files = [];
|
|
392
|
-
traceClaude(traceLogger,
|
|
388
|
+
traceClaude(traceLogger, `Scanning session files under ${sessionsRoot}.`);
|
|
393
389
|
for await (const filePath of walkSessionFiles(sessionsRoot)) {
|
|
394
390
|
files.push(await parseSessionFile(filePath, sessionsRoot));
|
|
395
391
|
}
|
|
396
392
|
inferClaudeSessionFileSources(files);
|
|
397
|
-
traceClaude(traceLogger,
|
|
393
|
+
traceClaude(traceLogger, `Completed session file scan under ${sessionsRoot}: ${files.length} file(s) parsed.`);
|
|
398
394
|
return files;
|
|
399
395
|
})();
|
|
400
396
|
parsedClaudeSessionFilesCache.set(cacheKey, pending);
|
|
@@ -625,30 +621,17 @@ function shouldReplaceUsageEvent(previous, next) {
|
|
|
625
621
|
}
|
|
626
622
|
return false;
|
|
627
623
|
}
|
|
628
|
-
function matchesClaudeProviderEvent(event, file, entrypoints, usageCommandKind) {
|
|
629
|
-
if (entrypoints.has(event.entrypoint)) {
|
|
630
|
-
return true;
|
|
631
|
-
}
|
|
632
|
-
if (event.entrypoint !== "cli") {
|
|
633
|
-
return false;
|
|
634
|
-
}
|
|
635
|
-
if (usageCommandKind === "vscode") {
|
|
636
|
-
return file.sourceKind === "vscode";
|
|
637
|
-
}
|
|
638
|
-
return file.sourceKind === "cli";
|
|
639
|
-
}
|
|
640
624
|
function normalizeTimestamp(value) {
|
|
641
625
|
return Number.isFinite(value) ? value : Number.NEGATIVE_INFINITY;
|
|
642
626
|
}
|
|
643
627
|
function extractRateLimits(payloadObject, message) {
|
|
644
628
|
return asRecord(payloadObject.rate_limits) ?? asRecord(message?.rate_limits);
|
|
645
629
|
}
|
|
646
|
-
function traceClaude(traceLogger,
|
|
630
|
+
function traceClaude(traceLogger, message) {
|
|
647
631
|
if (!traceLogger) {
|
|
648
632
|
return;
|
|
649
633
|
}
|
|
650
|
-
|
|
651
|
-
traceLogger.log(`[${targetLabel}] ${message}`);
|
|
634
|
+
traceLogger.log(`[Claude] ${message}`);
|
|
652
635
|
}
|
|
653
636
|
function formatErrorMessage(error) {
|
|
654
637
|
if (!error) {
|
|
@@ -697,9 +680,6 @@ function summarizeDistinctValues(values, limit = 5) {
|
|
|
697
680
|
? `${visibleValues.join(", ")} (+${remainder} more)`
|
|
698
681
|
: visibleValues.join(", ");
|
|
699
682
|
}
|
|
700
|
-
function collectEntryPoints(files) {
|
|
701
|
-
return files.flatMap((file) => file.events.map((event) => event.entrypoint || "<empty>"));
|
|
702
|
-
}
|
|
703
683
|
function buildClaudeCommandEnvironment() {
|
|
704
684
|
return {
|
|
705
685
|
...process.env,
|
|
@@ -708,16 +688,16 @@ function buildClaudeCommandEnvironment() {
|
|
|
708
688
|
}
|
|
709
689
|
async function buildLiveLimitWindows(options) {
|
|
710
690
|
const [usageOutput, subscriptionType] = await Promise.all([
|
|
711
|
-
readClaudeUsageCommandOutput(options.root, options.
|
|
712
|
-
readClaudeSubscriptionType(options.root, options.
|
|
691
|
+
readClaudeUsageCommandOutput(options.root, options.readUsageCommandOutput, options.traceLogger),
|
|
692
|
+
readClaudeSubscriptionType(options.root, options.readAuthStatusOutput, options.traceLogger)
|
|
713
693
|
]);
|
|
714
694
|
const snapshots = parseLiveUsageWindowSnapshots(usageOutput, options.now);
|
|
715
|
-
traceClaude(options.traceLogger,
|
|
695
|
+
traceClaude(options.traceLogger, `Parsed ${snapshots.length} live usage snapshot(s) from /usage output.`);
|
|
716
696
|
if (snapshots.length === 0) {
|
|
717
|
-
traceClaude(options.traceLogger,
|
|
697
|
+
traceClaude(options.traceLogger, "No live usage snapshots matched the expected /usage format.");
|
|
718
698
|
}
|
|
719
699
|
const resolvedPlanType = subscriptionType || "live";
|
|
720
|
-
traceClaude(options.traceLogger,
|
|
700
|
+
traceClaude(options.traceLogger, `Resolved live plan type ${resolvedPlanType}.`);
|
|
721
701
|
const primaryLimitWindows = snapshots
|
|
722
702
|
.filter((snapshot) => snapshot.scope === "primary")
|
|
723
703
|
.map((snapshot) => buildLiveLimitWindowRow(snapshot, resolvedPlanType, options.selectedEvents, options.now));
|
|
@@ -732,7 +712,7 @@ async function buildLiveLimitWindows(options) {
|
|
|
732
712
|
if (!row) {
|
|
733
713
|
continue;
|
|
734
714
|
}
|
|
735
|
-
traceClaude(options.traceLogger,
|
|
715
|
+
traceClaude(options.traceLogger, [
|
|
736
716
|
`Live window ${snapshot.scope}/${snapshot.label}:`,
|
|
737
717
|
`used=${snapshot.usedPercent}%`,
|
|
738
718
|
`range=${row.startTimeUtcIso}->${row.endTimeUtcIso}`,
|
|
@@ -748,41 +728,41 @@ async function buildLiveLimitWindows(options) {
|
|
|
748
728
|
secondaryLimitWindows
|
|
749
729
|
};
|
|
750
730
|
}
|
|
751
|
-
async function readClaudeSubscriptionType(root,
|
|
752
|
-
const output = await readClaudeAuthStatusOutput(root,
|
|
731
|
+
async function readClaudeSubscriptionType(root, override, traceLogger) {
|
|
732
|
+
const output = await readClaudeAuthStatusOutput(root, override, traceLogger);
|
|
753
733
|
const subscriptionType = parseClaudeSubscriptionType(output);
|
|
754
734
|
if (output && !subscriptionType) {
|
|
755
|
-
traceClaude(traceLogger,
|
|
735
|
+
traceClaude(traceLogger, "Could not parse subscription type from auth status output.");
|
|
756
736
|
}
|
|
757
|
-
traceClaude(traceLogger,
|
|
737
|
+
traceClaude(traceLogger, `Subscription type result: ${subscriptionType ?? "<none>"}.`);
|
|
758
738
|
return subscriptionType;
|
|
759
739
|
}
|
|
760
|
-
async function readClaudeAuthStatusOutput(root,
|
|
740
|
+
async function readClaudeAuthStatusOutput(root, override, traceLogger) {
|
|
761
741
|
if (override) {
|
|
762
742
|
try {
|
|
763
743
|
const output = await override();
|
|
764
|
-
traceClaude(traceLogger,
|
|
744
|
+
traceClaude(traceLogger, "Using injected auth status output override.");
|
|
765
745
|
return output;
|
|
766
746
|
}
|
|
767
747
|
catch {
|
|
768
|
-
traceClaude(traceLogger,
|
|
748
|
+
traceClaude(traceLogger, "Injected auth status output override failed.");
|
|
769
749
|
return null;
|
|
770
750
|
}
|
|
771
751
|
}
|
|
772
|
-
const cacheKey =
|
|
752
|
+
const cacheKey = path.resolve(root);
|
|
773
753
|
const cached = claudeAuthStatusOutputCache.get(cacheKey);
|
|
774
754
|
if (cached) {
|
|
775
|
-
traceClaude(traceLogger,
|
|
755
|
+
traceClaude(traceLogger, "Auth status output cache hit.");
|
|
776
756
|
return cached;
|
|
777
757
|
}
|
|
778
758
|
const pending = (async () => {
|
|
779
|
-
const binaryPath = await resolveClaudeBinaryPath(root,
|
|
759
|
+
const binaryPath = await resolveClaudeBinaryPath(root, traceLogger);
|
|
780
760
|
if (!binaryPath) {
|
|
781
|
-
traceClaude(traceLogger,
|
|
761
|
+
traceClaude(traceLogger, "Skipping auth status command because no Claude binary was found.");
|
|
782
762
|
return null;
|
|
783
763
|
}
|
|
784
764
|
try {
|
|
785
|
-
traceClaude(traceLogger,
|
|
765
|
+
traceClaude(traceLogger, `Running auth status command with ${binaryPath} (TZ=UTC).`);
|
|
786
766
|
const { stdout, stderr } = await execFileAsync(binaryPath, ["auth", "status"], {
|
|
787
767
|
encoding: "utf8",
|
|
788
768
|
env: buildClaudeCommandEnvironment(),
|
|
@@ -791,12 +771,12 @@ async function readClaudeAuthStatusOutput(root, usageCommandKind, override, trac
|
|
|
791
771
|
windowsHide: true
|
|
792
772
|
});
|
|
793
773
|
const combined = [stdout, stderr].filter(Boolean).join("\n").trim();
|
|
794
|
-
traceClaude(traceLogger,
|
|
774
|
+
traceClaude(traceLogger, "Auth status command completed successfully.");
|
|
795
775
|
return combined || null;
|
|
796
776
|
}
|
|
797
777
|
catch (error) {
|
|
798
778
|
const combined = extractExecOutput(error);
|
|
799
|
-
traceClaude(traceLogger,
|
|
779
|
+
traceClaude(traceLogger, `Auth status command failed: ${formatErrorMessage(error)}.`);
|
|
800
780
|
return combined || null;
|
|
801
781
|
}
|
|
802
782
|
})();
|
|
@@ -833,43 +813,43 @@ function parseClaudeAuthStatusSnapshot(output) {
|
|
|
833
813
|
return null;
|
|
834
814
|
}
|
|
835
815
|
}
|
|
836
|
-
async function readClaudeUserIdHash(root,
|
|
837
|
-
const authStatusOutput = await readClaudeAuthStatusOutput(root,
|
|
816
|
+
async function readClaudeUserIdHash(root, override, agentName, traceLogger) {
|
|
817
|
+
const authStatusOutput = await readClaudeAuthStatusOutput(root, override, traceLogger);
|
|
838
818
|
const snapshot = parseClaudeAuthStatusSnapshot(authStatusOutput);
|
|
839
819
|
if (!snapshot) {
|
|
840
|
-
traceClaude(traceLogger,
|
|
820
|
+
traceClaude(traceLogger, "Auth status output did not yield an analytics identity snapshot.");
|
|
841
821
|
return null;
|
|
842
822
|
}
|
|
843
823
|
return buildUserIdHash([agentName, snapshot.email, snapshot.orgId, snapshot.orgName]);
|
|
844
824
|
}
|
|
845
|
-
async function readClaudeUsageCommandOutput(root,
|
|
825
|
+
async function readClaudeUsageCommandOutput(root, override, traceLogger) {
|
|
846
826
|
if (override) {
|
|
847
827
|
try {
|
|
848
828
|
const output = await override();
|
|
849
|
-
traceClaude(traceLogger,
|
|
850
|
-
traceClaude(traceLogger,
|
|
829
|
+
traceClaude(traceLogger, "Using injected /usage output override.");
|
|
830
|
+
traceClaude(traceLogger, `Usage returned:\n${describeUsageOutput(output)}`);
|
|
851
831
|
return output;
|
|
852
832
|
}
|
|
853
833
|
catch {
|
|
854
|
-
traceClaude(traceLogger,
|
|
834
|
+
traceClaude(traceLogger, "Injected /usage output override failed.");
|
|
855
835
|
return null;
|
|
856
836
|
}
|
|
857
837
|
}
|
|
858
|
-
const cacheKey =
|
|
838
|
+
const cacheKey = path.resolve(root);
|
|
859
839
|
const cached = claudeUsageOutputCache.get(cacheKey);
|
|
860
840
|
if (cached) {
|
|
861
|
-
traceClaude(traceLogger,
|
|
841
|
+
traceClaude(traceLogger, "Usage output cache hit.");
|
|
862
842
|
return cached;
|
|
863
843
|
}
|
|
864
844
|
const pending = (async () => {
|
|
865
|
-
const binaryPath = await resolveClaudeBinaryPath(root,
|
|
845
|
+
const binaryPath = await resolveClaudeBinaryPath(root, traceLogger);
|
|
866
846
|
if (!binaryPath) {
|
|
867
|
-
traceClaude(traceLogger,
|
|
868
|
-
traceClaude(traceLogger,
|
|
847
|
+
traceClaude(traceLogger, "Skipping /usage command because no Claude binary was found.");
|
|
848
|
+
traceClaude(traceLogger, "Usage returned:\n<not available>");
|
|
869
849
|
return null;
|
|
870
850
|
}
|
|
871
851
|
try {
|
|
872
|
-
traceClaude(traceLogger,
|
|
852
|
+
traceClaude(traceLogger, `Running /usage command with ${binaryPath} (TZ=UTC).`);
|
|
873
853
|
const { stdout, stderr } = await execFileAsync(binaryPath, ["-p", "/usage"], {
|
|
874
854
|
encoding: "utf8",
|
|
875
855
|
env: buildClaudeCommandEnvironment(),
|
|
@@ -878,14 +858,14 @@ async function readClaudeUsageCommandOutput(root, usageCommandKind, override, tr
|
|
|
878
858
|
windowsHide: true
|
|
879
859
|
});
|
|
880
860
|
const combined = [stdout, stderr].filter(Boolean).join("\n").trim();
|
|
881
|
-
traceClaude(traceLogger,
|
|
882
|
-
traceClaude(traceLogger,
|
|
861
|
+
traceClaude(traceLogger, "Usage command completed successfully.");
|
|
862
|
+
traceClaude(traceLogger, `Usage returned:\n${describeUsageOutput(combined || null)}`);
|
|
883
863
|
return combined || null;
|
|
884
864
|
}
|
|
885
865
|
catch (error) {
|
|
886
866
|
const combined = extractExecOutput(error);
|
|
887
|
-
traceClaude(traceLogger,
|
|
888
|
-
traceClaude(traceLogger,
|
|
867
|
+
traceClaude(traceLogger, `Usage command failed: ${formatErrorMessage(error)}.`);
|
|
868
|
+
traceClaude(traceLogger, `Usage returned:\n${describeUsageOutput(combined || null)}`);
|
|
889
869
|
return combined || null;
|
|
890
870
|
}
|
|
891
871
|
})();
|
|
@@ -900,68 +880,68 @@ function extractExecOutput(error) {
|
|
|
900
880
|
const stderr = typeof error.stderr === "string" ? error.stderr : "";
|
|
901
881
|
return [stdout, stderr].filter(Boolean).join("\n").trim();
|
|
902
882
|
}
|
|
903
|
-
async function resolveClaudeBinaryPath(root,
|
|
904
|
-
const cacheKey =
|
|
883
|
+
async function resolveClaudeBinaryPath(root, traceLogger) {
|
|
884
|
+
const cacheKey = path.resolve(root);
|
|
905
885
|
const cached = claudeBinaryPathCache.get(cacheKey);
|
|
906
886
|
if (cached) {
|
|
907
887
|
const binaryPath = await cached;
|
|
908
|
-
traceClaude(traceLogger,
|
|
888
|
+
traceClaude(traceLogger, `Binary detection cache hit: ${binaryPath ? `found ${binaryPath}` : "not found"}.`);
|
|
909
889
|
return binaryPath;
|
|
910
890
|
}
|
|
911
891
|
const pending = (async () => {
|
|
912
|
-
traceClaude(traceLogger,
|
|
913
|
-
const binaryPath =
|
|
914
|
-
|
|
915
|
-
: await resolveCliClaudeBinaryPath(root, traceLogger);
|
|
916
|
-
traceClaude(traceLogger, usageCommandKind, `Binary detection result: ${binaryPath ? `found ${binaryPath}` : "not found"}.`);
|
|
892
|
+
traceClaude(traceLogger, `Starting binary detection under ${root}.`);
|
|
893
|
+
const binaryPath = await resolveMergedClaudeBinaryPath(root, traceLogger);
|
|
894
|
+
traceClaude(traceLogger, `Binary detection result: ${binaryPath ? `found ${binaryPath}` : "not found"}.`);
|
|
917
895
|
return binaryPath;
|
|
918
896
|
})();
|
|
919
897
|
claudeBinaryPathCache.set(cacheKey, pending);
|
|
920
898
|
return pending;
|
|
921
899
|
}
|
|
922
|
-
async function
|
|
900
|
+
async function resolveMergedClaudeBinaryPath(root, traceLogger) {
|
|
901
|
+
const candidates = [
|
|
902
|
+
...(await resolveVsCodeClaudeBinaryCandidates(root, traceLogger)),
|
|
903
|
+
...resolveDirectClaudeBinaryCandidates(root)
|
|
904
|
+
];
|
|
905
|
+
for (const candidate of candidates) {
|
|
906
|
+
const accessCheck = await checkReadableExecutableFile(candidate);
|
|
907
|
+
traceClaude(traceLogger, `Checked ${candidate} -> ${accessCheck.ok ? "success" : `failure (${accessCheck.errorMessage ?? "unknown"})`}.`);
|
|
908
|
+
if (accessCheck.ok) {
|
|
909
|
+
return candidate;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
async function resolveVsCodeClaudeBinaryCandidates(root, traceLogger) {
|
|
923
915
|
const boosterDirectories = [
|
|
924
916
|
path.join(root, ".vscode", "extensions"),
|
|
925
917
|
path.join(root, ".vscode-server", "extensions"),
|
|
926
918
|
path.join(root, ".vscode-server-insiders", "extensions")
|
|
927
919
|
];
|
|
928
|
-
|
|
920
|
+
const candidates = [];
|
|
929
921
|
for (const directory of boosterDirectories) {
|
|
930
|
-
|
|
931
|
-
if (!firstFoundPath && binaryPath) {
|
|
932
|
-
firstFoundPath = binaryPath;
|
|
933
|
-
}
|
|
922
|
+
candidates.push(...(await resolveClaudeBinaryCandidatesFromExtensionDirectory(directory, traceLogger)));
|
|
934
923
|
}
|
|
935
|
-
return
|
|
924
|
+
return candidates;
|
|
936
925
|
}
|
|
937
|
-
async function
|
|
926
|
+
async function resolveClaudeBinaryCandidatesFromExtensionDirectory(directory, traceLogger) {
|
|
938
927
|
let entries;
|
|
939
|
-
traceClaude(traceLogger,
|
|
928
|
+
traceClaude(traceLogger, `Scanning extension directory ${directory}.`);
|
|
940
929
|
try {
|
|
941
930
|
entries = await fs.promises.readdir(directory, { withFileTypes: true });
|
|
942
931
|
}
|
|
943
932
|
catch (error) {
|
|
944
|
-
traceClaude(traceLogger,
|
|
945
|
-
return
|
|
933
|
+
traceClaude(traceLogger, `Could not read ${directory}: ${formatErrorMessage(error)}.`);
|
|
934
|
+
return [];
|
|
946
935
|
}
|
|
947
936
|
const candidates = entries
|
|
948
937
|
.filter((entry) => entry.isDirectory() && entry.name.startsWith(VSCODE_CLAUDE_EXTENSION_PREFIX))
|
|
949
938
|
.map((entry) => entry.name)
|
|
950
939
|
.sort(compareClaudeExtensionDirectoryNames);
|
|
951
940
|
if (candidates.length === 0) {
|
|
952
|
-
traceClaude(traceLogger,
|
|
953
|
-
return
|
|
954
|
-
}
|
|
955
|
-
let firstFoundPath = null;
|
|
956
|
-
for (const candidate of candidates) {
|
|
957
|
-
const binaryPath = path.join(directory, candidate, "resources", "native-binary", "claude");
|
|
958
|
-
const accessCheck = await checkReadableExecutableFile(binaryPath);
|
|
959
|
-
traceClaude(traceLogger, "vscode", `Checked ${binaryPath} -> ${accessCheck.ok ? "success" : `failure (${accessCheck.errorMessage ?? "unknown"})`}.`);
|
|
960
|
-
if (!firstFoundPath && accessCheck.ok) {
|
|
961
|
-
firstFoundPath = binaryPath;
|
|
962
|
-
}
|
|
941
|
+
traceClaude(traceLogger, `No Claude VSCode extension candidates found in ${directory}.`);
|
|
942
|
+
return [];
|
|
963
943
|
}
|
|
964
|
-
return
|
|
944
|
+
return candidates.map((candidate) => path.join(directory, candidate, "resources", "native-binary", "claude"));
|
|
965
945
|
}
|
|
966
946
|
function compareClaudeExtensionDirectoryNames(left, right) {
|
|
967
947
|
const leftVersion = extractClaudeExtensionVersion(left);
|
|
@@ -985,20 +965,11 @@ function extractClaudeExtensionVersion(directoryName) {
|
|
|
985
965
|
.map((part) => Number(part))
|
|
986
966
|
.filter((part) => Number.isFinite(part));
|
|
987
967
|
}
|
|
988
|
-
|
|
989
|
-
|
|
968
|
+
function resolveDirectClaudeBinaryCandidates(root) {
|
|
969
|
+
return [
|
|
990
970
|
path.join(root, ".local", "bin", "claude"),
|
|
991
971
|
path.join(root, "bin", "claude")
|
|
992
972
|
];
|
|
993
|
-
let firstFoundPath = null;
|
|
994
|
-
for (const candidate of directCandidates) {
|
|
995
|
-
const accessCheck = await checkReadableExecutableFile(candidate);
|
|
996
|
-
traceClaude(traceLogger, "cli", `Checked ${candidate} -> ${accessCheck.ok ? "success" : `failure (${accessCheck.errorMessage ?? "unknown"})`}.`);
|
|
997
|
-
if (!firstFoundPath && accessCheck.ok) {
|
|
998
|
-
firstFoundPath = candidate;
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
return firstFoundPath;
|
|
1002
973
|
}
|
|
1003
974
|
async function checkReadableExecutableFile(filePath) {
|
|
1004
975
|
try {
|
|
@@ -6,12 +6,6 @@ export function createProviders() {
|
|
|
6
6
|
return [
|
|
7
7
|
new CodexUsageProvider(),
|
|
8
8
|
new ClaudeUsageProvider(),
|
|
9
|
-
new ClaudeUsageProvider({
|
|
10
|
-
id: "claude-vscode",
|
|
11
|
-
label: "Claude VSCode",
|
|
12
|
-
entrypoints: ["claude-vscode"],
|
|
13
|
-
usageCommandKind: "vscode"
|
|
14
|
-
}),
|
|
15
9
|
new CopilotUsageProvider(),
|
|
16
10
|
new AntigravityUsageProvider()
|
|
17
11
|
];
|
|
@@ -29,6 +29,7 @@ export async function buildAnonymousUsagePayload(statsList) {
|
|
|
29
29
|
function buildAnonymousUsageReport(stats, window, letmecodeVersion) {
|
|
30
30
|
return {
|
|
31
31
|
agent: stats.analytics?.agentName ?? stats.providerLabel.replace(/\s+/g, ""),
|
|
32
|
+
model_type: resolveReportModelType(stats, window),
|
|
32
33
|
userid_hash: stats.analytics?.userIdHash ?? "",
|
|
33
34
|
plan_id: window.planType,
|
|
34
35
|
window_duration_seconds: window.windowMinutes * 60,
|
|
@@ -41,6 +42,31 @@ function buildAnonymousUsageReport(stats, window, letmecodeVersion) {
|
|
|
41
42
|
letmecode_version: letmecodeVersion
|
|
42
43
|
};
|
|
43
44
|
}
|
|
45
|
+
function resolveReportModelType(stats, window) {
|
|
46
|
+
if (stats.providerId === "antigravity") {
|
|
47
|
+
return resolveAntigravityReportModelType(stats, window);
|
|
48
|
+
}
|
|
49
|
+
if (window.limitId && window.limitId !== "unknown") {
|
|
50
|
+
return truncateSchemaString(window.limitId, 128);
|
|
51
|
+
}
|
|
52
|
+
if (window.modelUsage.length === 1) {
|
|
53
|
+
return truncateSchemaString(window.modelUsage[0]?.modelId ?? stats.providerId, 128);
|
|
54
|
+
}
|
|
55
|
+
return truncateSchemaString(stats.providerId, 128);
|
|
56
|
+
}
|
|
57
|
+
function resolveAntigravityReportModelType(stats, window) {
|
|
58
|
+
const limitId = window.limitId.toLowerCase();
|
|
59
|
+
if (limitId.includes("gemini")) {
|
|
60
|
+
return "gemini";
|
|
61
|
+
}
|
|
62
|
+
if (limitId.startsWith("3p") || limitId.includes("third-party")) {
|
|
63
|
+
return "third-party";
|
|
64
|
+
}
|
|
65
|
+
return truncateSchemaString(window.limitId || stats.providerId, 128);
|
|
66
|
+
}
|
|
67
|
+
function truncateSchemaString(value, maxLength) {
|
|
68
|
+
return value.length > maxLength ? value.slice(0, maxLength) : value;
|
|
69
|
+
}
|
|
44
70
|
function buildUsageRaw(modelUsage) {
|
|
45
71
|
const usageRaw = {};
|
|
46
72
|
for (const row of modelUsage) {
|
|
@@ -61,7 +87,10 @@ function resolveReportedUsedPercents(window) {
|
|
|
61
87
|
return clampPercent(window.maxUsedPercent - window.minUsedPercent);
|
|
62
88
|
}
|
|
63
89
|
function clampPercent(value) {
|
|
64
|
-
|
|
90
|
+
if (!Number.isFinite(value)) {
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
return Math.max(0, Math.min(100, value));
|
|
65
94
|
}
|
|
66
95
|
function roundDollars(value) {
|
|
67
96
|
return Number(value.toFixed(6));
|