letmecode 0.1.11 → 0.1.13

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.
@@ -1,6 +1,6 @@
1
1
  import { execFile } from "node:child_process";
2
+ import { createHash } from "node:crypto";
2
3
  import https from "node:https";
3
- import { createRequire } from "node:module";
4
4
  import fs from "node:fs";
5
5
  import os from "node:os";
6
6
  import path from "node:path";
@@ -9,7 +9,6 @@ import { promisify } from "node:util";
9
9
  import { UsageProviderBase, addUsageTotals, createEmptyUsageTotals, sumUsageTotals } from "./contract.js";
10
10
  import { addDailyUsage, buildDailyUsageRows, createDailyUsageAggregates } from "./daily.js";
11
11
  import { resolveUsageRate } from "./pricing.js";
12
- const require = createRequire(import.meta.url);
13
12
  const execFileAsync = promisify(execFile);
14
13
  const RATE_CARD = {
15
14
  "gemini-3.5-flash": {
@@ -67,20 +66,36 @@ const RATE_CARD = {
67
66
  const UNPRICED_MODELS = new Set([
68
67
  "gpt-oss-120b"
69
68
  ]);
70
- const ANTIGRAVITY_PRIMARY_WINDOW_MINUTES = 5 * 60;
71
- const ANTIGRAVITY_WEEKLY_WINDOW_MINUTES = 7 * 24 * 60;
72
69
  const ANTIGRAVITY_QUOTA_SUMMARY_PATH = "/exa.language_server_pb.LanguageServerService/RetrieveUserQuotaSummary";
73
- const ANTIGRAVITY_DEBUG_LOG_PATH = process.env.LETMECODE_ANTIGRAVITY_DEBUG_LOG ??
74
- path.join(os.tmpdir(), "letmecode-antigravity-debug.jsonl");
75
- const GEMINI_QUOTA_MODELS = [
76
- "gemini-3.5-flash",
77
- "gemini-3.1-pro",
78
- "gemini-3-flash"
79
- ];
80
- const THIRD_PARTY_QUOTA_MODELS = [
81
- "claude-opus-4-6",
82
- "claude-sonnet-4-6",
83
- "gpt-oss-120b"
70
+ const ANTIGRAVITY_USER_STATUS_PATH = "/exa.language_server_pb.LanguageServerService/GetUserStatus";
71
+ const ANTIGRAVITY_CACHE_ROOT = path.join(os.homedir(), ".config", "tokscale", "antigravity-cache");
72
+ const QUOTA_WINDOWS = {
73
+ "5h": {
74
+ scope: "primary",
75
+ windowMinutes: 300
76
+ },
77
+ weekly: {
78
+ scope: "secondary",
79
+ windowMinutes: 10080
80
+ }
81
+ };
82
+ const QUOTA_MODEL_GROUPS = [
83
+ {
84
+ pattern: /gemini/,
85
+ models: [
86
+ "gemini-3.5-flash",
87
+ "gemini-3.1-pro",
88
+ "gemini-3-flash"
89
+ ]
90
+ },
91
+ {
92
+ pattern: /claude|gpt/,
93
+ models: [
94
+ "claude-opus-4-6",
95
+ "claude-sonnet-4-6",
96
+ "gpt-oss-120b"
97
+ ]
98
+ }
84
99
  ];
85
100
  const MODEL_ALIASES = {
86
101
  "gemini-3-flash-a": "gemini-3-flash",
@@ -94,8 +109,7 @@ export class AntigravityUsageProvider extends UsageProviderBase {
94
109
  constructor(options = {}) {
95
110
  super("antigravity", "Antigravity");
96
111
  this.collectUsage =
97
- options.collectUsage ??
98
- collectAntigravityUsageFromTokscale;
112
+ options.collectUsage ?? readAntigravityUsageCache;
99
113
  this.collectQuota =
100
114
  options.collectQuota ??
101
115
  collectAntigravityQuotaFromLocalRpc;
@@ -113,7 +127,7 @@ export class AntigravityUsageProvider extends UsageProviderBase {
113
127
  ? quotaResult.value
114
128
  : null;
115
129
  if (usageResult.status === "rejected") {
116
- warnings.push("Could not synchronize Antigravity token usage through Tokscale.");
130
+ warnings.push("Could not read Antigravity token usage cache.");
117
131
  }
118
132
  if (quotaResult.status === "rejected") {
119
133
  warnings.push("Live Antigravity quota is unavailable. Ensure the Antigravity IDE is running.");
@@ -121,9 +135,6 @@ export class AntigravityUsageProvider extends UsageProviderBase {
121
135
  else if (quotaResult.value.entries.length === 0) {
122
136
  warnings.push("Antigravity local quota RPC responded, but no recognized model quota windows were found.");
123
137
  }
124
- if (isAntigravityDebugEnabled()) {
125
- warnings.push(`Antigravity debug log: ${ANTIGRAVITY_DEBUG_LOG_PATH}`);
126
- }
127
138
  const selectedRecords = deduplicateRecords(records);
128
139
  const duplicateEvents = records.length - selectedRecords.length;
129
140
  if (duplicateEvents > 0) {
@@ -150,7 +161,7 @@ export class AntigravityUsageProvider extends UsageProviderBase {
150
161
  if (unknownPricedModels.length > 0) {
151
162
  warnings.push(`No Antigravity estimated API-equivalent rate configured for: ${unknownPricedModels.join(", ")}.`);
152
163
  }
153
- const limitWindows = quotaSnapshot?.entries.map((quota) => buildAntigravityLimitWindow(quota, selectedRecords, quotaSnapshot.fetchedAt)) ?? [];
164
+ const limitWindows = quotaSnapshot?.entries.map((quota) => buildAntigravityLimitWindow(quota, quotaSnapshot.planType, selectedRecords, quotaSnapshot.fetchedAt)) ?? [];
154
165
  return {
155
166
  providerId: this.id,
156
167
  providerLabel: this.label,
@@ -164,80 +175,56 @@ export class AntigravityUsageProvider extends UsageProviderBase {
164
175
  ...new Set(limitWindows.map((window) => window.planType))
165
176
  ],
166
177
  rootLabel: "Tokscale usage + Antigravity local quota",
167
- rootPath: getAntigravityCacheRoot()
178
+ rootPath: ANTIGRAVITY_CACHE_ROOT
168
179
  },
169
180
  modelUsage,
170
181
  dayUsage: buildDailyUsageRows(byDay),
171
182
  primaryLimitWindows: limitWindows.filter((window) => window.scope === "primary"),
172
183
  secondaryLimitWindows: limitWindows.filter((window) => window.scope === "secondary"),
173
- warnings
184
+ warnings,
185
+ analytics: quotaSnapshot?.userIdHash
186
+ ? {
187
+ agentName: this.label.replace(/\s/g, ""),
188
+ userIdHash: quotaSnapshot.userIdHash
189
+ }
190
+ : undefined
174
191
  };
175
192
  }
176
193
  }
177
- export async function collectAntigravityUsage() {
178
- return collectAntigravityUsageFromTokscale();
179
- }
180
- export async function collectAntigravityQuota() {
181
- return collectAntigravityQuotaFromLocalRpc();
182
- }
183
- async function collectAntigravityUsageFromTokscale() {
184
- await runTokscale([
185
- "antigravity",
186
- "sync"
187
- ]);
188
- return readAntigravityUsageCache(getAntigravityCacheRoot());
189
- }
190
- async function runTokscale(args) {
191
- return execFileAsync(process.execPath, [require.resolve("@tokscale/cli/dist/index.js"), ...args], {
192
- encoding: "utf8",
193
- maxBuffer: 32 * 1024 * 1024
194
- });
195
- }
196
194
  async function collectAntigravityQuotaFromLocalRpc() {
197
195
  const server = await findAntigravityLocalServer();
198
196
  if (!server) {
199
197
  throw new Error("Antigravity local language server was not found.");
200
198
  }
201
- const fetchedAt = Date.now();
202
- const payload = await readAntigravityQuotaSummary(server);
203
- const entries = parseAntigravityQuotaEntries(payload);
204
- await writeAntigravityDebugEvent("quota-rpc-response", {
205
- port: server.port,
206
- path: ANTIGRAVITY_QUOTA_SUMMARY_PATH,
207
- entries: entries.map((entry) => ({
208
- limitId: entry.limitId,
209
- remainingFraction: entry.remainingFraction,
210
- resetAt: new Date(entry.resetAt).toISOString(),
211
- windowMinutes: entry.windowMinutes,
212
- scope: entry.scope,
213
- modelIds: entry.modelIds
214
- })),
215
- ...(isAntigravityRawDebugEnabled() ? { payload } : {})
216
- });
199
+ const [quota, status] = await Promise.all([
200
+ rpc(server, ANTIGRAVITY_QUOTA_SUMMARY_PATH),
201
+ rpc(server, ANTIGRAVITY_USER_STATUS_PATH, {
202
+ metadata: {
203
+ ideName: "antigravity",
204
+ extensionName: "antigravity",
205
+ ideVersion: "unknown",
206
+ locale: "en"
207
+ }
208
+ }).catch(() => null)
209
+ ]);
217
210
  return {
218
- entries,
219
- fetchedAt
211
+ entries: parseAntigravityQuotaEntries(quota),
212
+ fetchedAt: Date.now(),
213
+ planType: status.userStatus.planStatus.planInfo.planName ?? "unknown",
214
+ userIdHash: createHash("md5").update(status.userStatus.email).digest("hex")
220
215
  };
221
216
  }
222
- function recordsForQuotaWindow(quota, records) {
223
- if (quota.modelIds.length === 0) {
224
- return [];
225
- }
226
- const endMs = quota.resetAt;
227
- const startMs = endMs - quota.windowMinutes * 60000;
217
+ function buildAntigravityLimitWindow(quota, planType, records, fetchedAt) {
218
+ const startAt = quota.resetAt - quota.windowMinutes * 60000;
228
219
  const modelIds = new Set(quota.modelIds.map(resolveModelId));
229
- return records.filter((record) => {
230
- const modelId = resolveModelId(record.modelId);
231
- return (record.timestamp >= startMs &&
232
- record.timestamp < endMs &&
233
- modelIds.has(modelId));
234
- });
235
- }
236
- function buildAntigravityLimitWindow(quota, records, fetchedAt) {
237
- const matchingRecords = recordsForQuotaWindow(quota, records);
238
220
  const byModel = new Map();
239
- for (const record of matchingRecords) {
221
+ for (const record of records) {
240
222
  const modelId = resolveModelId(record.modelId);
223
+ if (record.timestamp < startAt ||
224
+ record.timestamp >= quota.resetAt ||
225
+ !modelIds.has(modelId)) {
226
+ continue;
227
+ }
241
228
  addModelUsage(byModel, modelId, usageRecordToTotals(modelId, record));
242
229
  }
243
230
  const modelUsage = [...byModel.entries()]
@@ -254,7 +241,7 @@ function buildAntigravityLimitWindow(quota, records, fetchedAt) {
254
241
  // window and may not match Antigravity's internal quota accounting exactly.
255
242
  return {
256
243
  scope: quota.scope,
257
- planType: quota.planType,
244
+ planType,
258
245
  limitId: quota.limitId,
259
246
  windowMinutes: quota.windowMinutes,
260
247
  startTimeUtcIso: new Date(quota.resetAt - quota.windowMinutes * 60000).toISOString(),
@@ -268,220 +255,88 @@ function buildAntigravityLimitWindow(quota, records, fetchedAt) {
268
255
  eventCount: totals.eventCount
269
256
  };
270
257
  }
271
- const antigravityHttpsAgent = new https.Agent({
272
- rejectUnauthorized: false
273
- });
274
258
  async function findAntigravityLocalServer() {
275
259
  const process = await findAntigravityProcess();
276
260
  if (!process) {
277
- await writeAntigravityDebugEvent("process-not-found", {});
278
- return null;
279
- }
280
- const ports = await findListeningLoopbackPorts(process.pid);
281
- await writeAntigravityDebugEvent("process-found", {
282
- pid: process.pid,
283
- ports
284
- });
285
- return probeAntigravityPorts(ports, process.csrfToken);
286
- }
287
- async function findAntigravityProcess() {
288
- const fromProc = await findAntigravityProcessFromProc();
289
- if (fromProc) {
290
- return fromProc;
291
- }
292
- return findAntigravityProcessFromPs();
293
- }
294
- async function findAntigravityProcessFromProc() {
295
- let entries;
296
- try {
297
- entries = await fs.promises.readdir("/proc", { withFileTypes: true });
298
- }
299
- catch {
300
261
  return null;
301
262
  }
302
- for (const entry of entries) {
303
- if (!entry.isDirectory() || !/^\d+$/.test(entry.name)) {
304
- continue;
263
+ for (const port of await findListeningPorts(process.pid)) {
264
+ const server = {
265
+ port,
266
+ csrfToken: process.csrfToken
267
+ };
268
+ try {
269
+ await rpc(server, ANTIGRAVITY_QUOTA_SUMMARY_PATH);
270
+ return server;
305
271
  }
306
- const pid = Number(entry.name);
307
- const cmdline = await readProcCmdline(path.join("/proc", entry.name, "cmdline"));
308
- const process = parseAntigravityProcessFromArgs(pid, cmdline);
309
- if (process) {
310
- return process;
272
+ catch {
273
+ // Try the next loopback listener owned by the same Antigravity process.
311
274
  }
312
275
  }
313
276
  return null;
314
277
  }
315
- async function readProcCmdline(filePath) {
316
- try {
317
- const content = await fs.promises.readFile(filePath);
318
- return content
319
- .toString("utf8")
320
- .split("\0")
321
- .filter(Boolean);
322
- }
323
- catch {
324
- return [];
325
- }
326
- }
327
- async function findAntigravityProcessFromPs() {
328
- try {
329
- const { stdout } = await execFileAsync("ps", ["-eo", "pid=,args="], {
330
- encoding: "utf8",
331
- maxBuffer: 4 * 1024 * 1024,
332
- timeout: 5000
333
- });
334
- for (const line of stdout.split(/\r?\n/)) {
335
- const match = line.match(/^\s*(\d+)\s+(.+)$/);
336
- if (!match) {
337
- continue;
338
- }
339
- const process = parseAntigravityProcessFromArgs(Number(match[1]), splitCommandLineForDiscovery(match[2]));
340
- if (process) {
341
- return process;
342
- }
278
+ async function findAntigravityProcess() {
279
+ const entries = await fs.promises.readdir("/proc").catch(() => []);
280
+ for (const entry of entries) {
281
+ if (!/^\d+$/.test(entry)) {
282
+ continue;
343
283
  }
344
- }
345
- catch {
346
- return null;
347
- }
348
- return null;
349
- }
350
- function splitCommandLineForDiscovery(value) {
351
- return value.match(/"[^"]*"|'[^']*'|\S+/g)?.map((part) => part.replace(/^['"]|['"]$/g, "")) ?? [];
352
- }
353
- function parseAntigravityProcessFromArgs(pid, args) {
354
- if (!Number.isInteger(pid) || pid <= 0 || !isAntigravityLanguageServerCommand(args)) {
355
- return null;
356
- }
357
- const csrfToken = readNamedArg(args, "--csrf_token")?.trim();
358
- if (!csrfToken) {
359
- return null;
360
- }
361
- return { pid, csrfToken };
362
- }
363
- function isAntigravityLanguageServerCommand(args) {
364
- const normalized = args.join(" ").toLowerCase();
365
- return (normalized.includes("antigravity") &&
366
- (normalized.includes("language-server") ||
367
- normalized.includes("language_server") ||
368
- normalized.includes("extension-server") ||
369
- normalized.includes("extension_server")));
370
- }
371
- function readNamedArg(args, name) {
372
- for (let index = 0; index < args.length; index += 1) {
373
- const arg = args[index];
374
- if (arg === name) {
375
- return args[index + 1] ?? null;
284
+ const args = await fs.promises
285
+ .readFile(`/proc/${entry}/cmdline`, "utf8")
286
+ .then((value) => value.split("\0").filter(Boolean))
287
+ .catch(() => []);
288
+ const command = args.join(" ").toLowerCase();
289
+ if (!command.includes("antigravity") ||
290
+ !/(language|extension)[_-]server/.test(command)) {
291
+ continue;
376
292
  }
377
- if (arg.startsWith(`${name}=`)) {
378
- return arg.slice(name.length + 1);
293
+ const tokenArg = args.find((arg) => arg.startsWith("--csrf_token="));
294
+ const tokenIndex = args.indexOf("--csrf_token");
295
+ const csrfToken = tokenArg?.slice("--csrf_token=".length) ??
296
+ args[tokenIndex + 1];
297
+ if (csrfToken) {
298
+ return {
299
+ pid: Number(entry),
300
+ csrfToken
301
+ };
379
302
  }
380
303
  }
381
304
  return null;
382
305
  }
383
- async function findListeningLoopbackPorts(pid) {
384
- const parsers = [
385
- () => findListeningLoopbackPortsWithSs(pid),
386
- () => findListeningLoopbackPortsWithLsof(pid)
306
+ async function findListeningPorts(pid) {
307
+ const { stdout } = await execFileAsync("ss", ["-H", "-ltnp"], { encoding: "utf8", timeout: 5000 });
308
+ return [
309
+ ...new Set(stdout
310
+ .split("\n")
311
+ .filter((line) => line.includes(`pid=${pid},`))
312
+ .flatMap((line) => [
313
+ ...line.matchAll(/(?:127\.0\.0\.1|\[::1\]):(\d+)/g)
314
+ ])
315
+ .map((match) => Number(match[1])))
387
316
  ];
388
- for (const parse of parsers) {
389
- const ports = await parse();
390
- if (ports.length > 0) {
391
- return ports;
392
- }
393
- }
394
- return [];
395
- }
396
- async function findListeningLoopbackPortsWithSs(pid) {
397
- try {
398
- const { stdout } = await execFileAsync("ss", ["-H", "-ltnp"], {
399
- encoding: "utf8",
400
- maxBuffer: 1024 * 1024,
401
- timeout: 5000
402
- });
403
- return uniquePorts(stdout
404
- .split(/\r?\n/)
405
- .filter((line) => line.includes(`pid=${pid},`) && isLoopbackListenLine(line))
406
- .map(extractPortFromListenLine));
407
- }
408
- catch {
409
- return [];
410
- }
411
- }
412
- async function findListeningLoopbackPortsWithLsof(pid) {
413
- try {
414
- const { stdout } = await execFileAsync("lsof", ["-Pan", "-p", String(pid), "-iTCP", "-sTCP:LISTEN"], {
415
- encoding: "utf8",
416
- maxBuffer: 1024 * 1024,
417
- timeout: 5000
418
- });
419
- return uniquePorts(stdout
420
- .split(/\r?\n/)
421
- .filter(isLoopbackListenLine)
422
- .map(extractPortFromListenLine));
423
- }
424
- catch {
425
- return [];
426
- }
427
- }
428
- function isLoopbackListenLine(line) {
429
- return /(?:127\.0\.0\.1|localhost|\[::1\]|::1):\d+\b/.test(line);
430
- }
431
- function extractPortFromListenLine(line) {
432
- const matches = [...line.matchAll(/(?:127\.0\.0\.1|localhost|\[::1\]|::1):(\d+)/g)];
433
- const value = matches.at(-1)?.[1];
434
- const port = value ? Number(value) : NaN;
435
- return Number.isInteger(port) && port >= 1 && port <= 65535 ? port : null;
436
- }
437
- function uniquePorts(ports) {
438
- return [...new Set(ports.filter((port) => port !== null))];
439
317
  }
440
- async function probeAntigravityPorts(ports, csrfToken) {
441
- for (const port of ports) {
442
- const server = { port, csrfToken };
443
- try {
444
- await readAntigravityQuotaSummary(server);
445
- await writeAntigravityDebugEvent("port-probe-ok", { port });
446
- return server;
447
- }
448
- catch (error) {
449
- await writeAntigravityDebugEvent("port-probe-failed", {
450
- port,
451
- error: error instanceof Error ? error.message : String(error)
452
- });
453
- // Try the next loopback listener owned by the same Antigravity process.
454
- }
455
- }
456
- return null;
457
- }
458
- async function readAntigravityQuotaSummary(server) {
459
- return requestAntigravityQuotaSummary(server);
460
- }
461
- function requestAntigravityQuotaSummary(server) {
462
- const body = "{}";
318
+ function rpc(server, endpoint, payload = {}) {
319
+ const body = JSON.stringify(payload);
463
320
  return new Promise((resolve, reject) => {
464
321
  const request = https.request({
465
322
  hostname: "127.0.0.1",
466
323
  port: server.port,
467
- path: ANTIGRAVITY_QUOTA_SUMMARY_PATH,
324
+ path: endpoint,
468
325
  method: "POST",
326
+ rejectUnauthorized: false,
469
327
  timeout: 5000,
470
- agent: antigravityHttpsAgent,
471
328
  headers: {
472
329
  "X-Codeium-Csrf-Token": server.csrfToken,
473
330
  "Content-Type": "application/json",
474
- "Connect-Protocol-Version": "1",
475
- Accept: "application/json",
476
- "Content-Length": Buffer.byteLength(body)
331
+ "Connect-Protocol-Version": "1"
477
332
  }
478
333
  }, (response) => {
479
334
  const chunks = [];
480
335
  response.on("data", (chunk) => chunks.push(chunk));
481
336
  response.on("end", () => {
482
337
  const responseBody = Buffer.concat(chunks).toString("utf8");
483
- if (!response.statusCode || response.statusCode < 200 || response.statusCode >= 300) {
484
- reject(new Error(`Unexpected Antigravity quota summary response: ${response.statusCode ?? "unknown"}`));
338
+ if (!response.statusCode || response.statusCode >= 300) {
339
+ reject(new Error(`RPC failed: ${response.statusCode ?? "unknown"}`));
485
340
  return;
486
341
  }
487
342
  try {
@@ -493,147 +348,44 @@ function requestAntigravityQuotaSummary(server) {
493
348
  });
494
349
  });
495
350
  request.on("timeout", () => {
496
- request.destroy(new Error("Timed out reading Antigravity quota summary."));
351
+ request.destroy(new Error(`Timed out reading Antigravity RPC ${endpoint}.`));
497
352
  });
498
353
  request.on("error", reject);
499
354
  request.end(body);
500
355
  });
501
356
  }
502
357
  export function parseAntigravityQuotaEntries(payload) {
503
- const root = asRecord(payload);
504
- const response = asRecord(root?.response);
505
- const groups = asArray(response?.groups);
506
- const entries = [];
507
- for (const groupValue of groups) {
508
- const group = asRecord(groupValue);
509
- if (!group) {
510
- continue;
511
- }
512
- const displayName = asString(group.displayName) ?? "";
513
- const description = asString(group.description) ?? "";
514
- const modelIds = resolveQuotaGroupModelIds(displayName, description);
515
- if (modelIds.length === 0) {
516
- void writeAntigravityDebugEvent("quota-group-skipped", {
517
- displayName,
518
- description
519
- });
520
- continue;
358
+ const groups = payload.response?.groups ?? [];
359
+ return groups.flatMap((group) => {
360
+ const modelIds = resolveQuotaGroupModelIds(`${group.displayName ?? ""} ${group.description ?? ""}`);
361
+ if (!modelIds.length) {
362
+ return [];
521
363
  }
522
- for (const bucketValue of asArray(group.buckets)) {
523
- const bucket = asRecord(bucketValue);
524
- if (!bucket) {
525
- continue;
526
- }
527
- const bucketId = asString(bucket.bucketId);
528
- const windowConfig = resolveQuotaWindow(asString(bucket.window));
529
- const remainingFraction = asFiniteNumber(bucket.remainingFraction);
530
- const resetTime = asString(bucket.resetTime);
531
- const resetAt = resetTime === null ? NaN : Date.parse(resetTime);
532
- if (!bucketId ||
533
- remainingFraction === null ||
534
- remainingFraction < 0 ||
535
- remainingFraction > 1 ||
364
+ return (group.buckets ?? []).flatMap((bucket) => {
365
+ const window = bucket.window
366
+ ? QUOTA_WINDOWS[bucket.window]
367
+ : undefined;
368
+ const resetAt = Date.parse(bucket.resetTime ?? "");
369
+ if (!bucket.bucketId ||
370
+ window === undefined ||
536
371
  !Number.isFinite(resetAt) ||
537
- !windowConfig) {
538
- continue;
372
+ typeof bucket.remainingFraction !== "number" ||
373
+ bucket.remainingFraction < 0 ||
374
+ bucket.remainingFraction > 1) {
375
+ return [];
539
376
  }
540
- entries.push({
541
- limitId: bucketId,
542
- modelIds,
543
- remainingFraction,
544
- resetAt,
545
- windowMinutes: windowConfig.windowMinutes,
546
- scope: windowConfig.scope,
547
- planType: "unknown"
548
- });
549
- }
550
- }
551
- return entries;
552
- }
553
- function resolveQuotaWindow(window) {
554
- switch (window) {
555
- case "5h":
556
- return {
557
- scope: "primary",
558
- windowMinutes: ANTIGRAVITY_PRIMARY_WINDOW_MINUTES
559
- };
560
- case "weekly":
561
- return {
562
- scope: "secondary",
563
- windowMinutes: ANTIGRAVITY_WEEKLY_WINDOW_MINUTES
564
- };
565
- default:
566
- return null;
567
- }
568
- }
569
- function resolveQuotaGroupModelIds(displayName, description) {
570
- const text = `${displayName} ${description}`.toLowerCase();
571
- if (text.includes("gemini")) {
572
- return GEMINI_QUOTA_MODELS;
573
- }
574
- if (text.includes("claude") || text.includes("gpt")) {
575
- return THIRD_PARTY_QUOTA_MODELS;
576
- }
577
- return [];
578
- }
579
- function asRecord(value) {
580
- return value && typeof value === "object" && !Array.isArray(value)
581
- ? value
582
- : null;
583
- }
584
- function asArray(value) {
585
- return Array.isArray(value) ? value : [];
586
- }
587
- function asString(value) {
588
- return typeof value === "string" && value.trim() ? value.trim() : null;
589
- }
590
- function asFiniteNumber(value) {
591
- return typeof value === "number" && Number.isFinite(value) ? value : null;
592
- }
593
- function isAntigravityDebugEnabled() {
594
- const value = process.env.LETMECODE_DEBUG_ANTIGRAVITY;
595
- return value === "1" || value === "true" || value === "yes";
596
- }
597
- function isAntigravityRawDebugEnabled() {
598
- const value = process.env.LETMECODE_DEBUG_ANTIGRAVITY_RAW;
599
- return value === "1" || value === "true" || value === "yes";
600
- }
601
- async function writeAntigravityDebugEvent(event, data) {
602
- if (!isAntigravityDebugEnabled()) {
603
- return;
604
- }
605
- const line = JSON.stringify({
606
- timestamp: new Date().toISOString(),
607
- event,
608
- data: redactDebugValue(data)
609
- });
610
- try {
611
- await fs.promises.mkdir(path.dirname(ANTIGRAVITY_DEBUG_LOG_PATH), {
612
- recursive: true
377
+ return [{
378
+ limitId: bucket.bucketId,
379
+ modelIds,
380
+ remainingFraction: bucket.remainingFraction,
381
+ resetAt,
382
+ ...window
383
+ }];
613
384
  });
614
- await fs.promises.appendFile(ANTIGRAVITY_DEBUG_LOG_PATH, `${line}\n`, "utf8");
615
- }
616
- catch {
617
- // Debug logging must never break provider stats collection.
618
- }
619
- }
620
- function redactDebugValue(value, key = "") {
621
- if (isSensitiveDebugKey(key)) {
622
- return "[redacted]";
623
- }
624
- if (Array.isArray(value)) {
625
- return value.map((item) => redactDebugValue(item));
626
- }
627
- if (!value || typeof value !== "object") {
628
- return value;
629
- }
630
- return Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [
631
- entryKey,
632
- redactDebugValue(entryValue, entryKey)
633
- ]));
385
+ });
634
386
  }
635
- function isSensitiveDebugKey(key) {
636
- return /token|csrf|authorization|cookie|email/i.test(key);
387
+ function resolveQuotaGroupModelIds(text) {
388
+ return (QUOTA_MODEL_GROUPS.find(({ pattern }) => pattern.test(text.toLowerCase()))?.models ?? []);
637
389
  }
638
390
  function numberOrZero(value) {
639
391
  return typeof value === "number" && Number.isFinite(value)
@@ -646,8 +398,8 @@ function clampPercent(value) {
646
398
  }
647
399
  return Math.min(100, Math.max(0, value));
648
400
  }
649
- async function readAntigravityUsageCache(cacheRoot) {
650
- const sessionsRoot = path.join(cacheRoot, "sessions");
401
+ async function readAntigravityUsageCache() {
402
+ const sessionsRoot = path.join(ANTIGRAVITY_CACHE_ROOT, "sessions");
651
403
  const records = [];
652
404
  for await (const filePath of walkJsonlFiles(sessionsRoot)) {
653
405
  const stream = fs.createReadStream(filePath, { encoding: "utf8" });
@@ -701,9 +453,6 @@ function usageRecordFromCacheEntry(value) {
701
453
  reasoning: numberOrZero(entry.reasoning)
702
454
  };
703
455
  }
704
- function getAntigravityCacheRoot() {
705
- return path.join(os.homedir(), ".config", "tokscale", "antigravity-cache");
706
- }
707
456
  async function* walkJsonlFiles(directory) {
708
457
  let entries;
709
458
  try {