letmecode 0.1.19 → 0.1.21

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,537 +1,2 @@
1
- import { execFile } from "node:child_process";
2
- import { createHash } from "node:crypto";
3
- import https from "node:https";
4
- import fs from "node:fs";
5
- import os from "node:os";
6
- import path from "node:path";
7
- import readline from "node:readline";
8
- import { promisify } from "node:util";
9
- import { UsageProviderBase, addUsageTotals, createEmptyUsageTotals, sumUsageTotals } from "./contract.js";
10
- import { addDailyUsage, buildDailyUsageRows, createDailyUsageAggregates } from "./daily.js";
11
- import { resolveUsageRate } from "./pricing.js";
12
- const execFileAsync = promisify(execFile);
13
- const RATE_CARD = {
14
- "gemini-3.5-flash": {
15
- input: 150,
16
- cacheRead: 15,
17
- cacheWrite: 150,
18
- cacheWrite5m: 150,
19
- cacheWrite1h: 150,
20
- output: 900
21
- },
22
- "gemini-3.1-pro": {
23
- input: 200,
24
- cacheRead: 20,
25
- cacheWrite: 200,
26
- cacheWrite5m: 200,
27
- cacheWrite1h: 200,
28
- output: 1200,
29
- longContext: {
30
- thresholdTokens: 200000,
31
- rate: {
32
- input: 400,
33
- cacheRead: 40,
34
- cacheWrite: 400,
35
- cacheWrite5m: 400,
36
- cacheWrite1h: 400,
37
- output: 1800
38
- }
39
- }
40
- },
41
- "gemini-3-flash": {
42
- input: 50,
43
- cacheRead: 5,
44
- cacheWrite: 50,
45
- cacheWrite5m: 50,
46
- cacheWrite1h: 50,
47
- output: 300
48
- },
49
- "claude-sonnet-4-6": {
50
- input: 300,
51
- cacheRead: 30,
52
- cacheWrite: 375,
53
- cacheWrite5m: 375,
54
- cacheWrite1h: 600,
55
- output: 1500
56
- },
57
- "claude-opus-4-6": {
58
- input: 500,
59
- cacheRead: 50,
60
- cacheWrite: 625,
61
- cacheWrite5m: 625,
62
- cacheWrite1h: 1000,
63
- output: 2500
64
- }
65
- };
66
- const UNPRICED_MODELS = new Set([
67
- "gpt-oss-120b"
68
- ]);
69
- const ANTIGRAVITY_QUOTA_SUMMARY_PATH = "/exa.language_server_pb.LanguageServerService/RetrieveUserQuotaSummary";
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
- }
99
- ];
100
- const MODEL_ALIASES = {
101
- "gemini-3-flash-a": "gemini-3-flash",
102
- "gemini-3-flash-preview": "gemini-3-flash",
103
- "gemini-3.1-pro-preview": "gemini-3.1-pro",
104
- "gemini-3.5-flash-preview": "gemini-3.5-flash",
105
- "claude-sonnet-4-6-20251201": "claude-sonnet-4-6",
106
- "claude-opus-4-6-20251201": "claude-opus-4-6"
107
- };
108
- export class AntigravityUsageProvider extends UsageProviderBase {
109
- constructor(options = {}) {
110
- super("antigravity", "Antigravity");
111
- this.collectUsage =
112
- options.collectUsage ?? readAntigravityUsageCache;
113
- this.collectQuota =
114
- options.collectQuota ??
115
- collectAntigravityQuotaFromLocalRpc;
116
- }
117
- async getStats(_options = {}) {
118
- const warnings = [];
119
- const [usageResult, quotaResult] = await Promise.allSettled([
120
- this.collectUsage(),
121
- this.collectQuota()
122
- ]);
123
- const records = usageResult.status === "fulfilled"
124
- ? usageResult.value
125
- : [];
126
- const quotaSnapshot = quotaResult.status === "fulfilled"
127
- ? quotaResult.value
128
- : null;
129
- if (usageResult.status === "rejected") {
130
- warnings.push("Could not read Antigravity token usage cache.");
131
- }
132
- if (quotaResult.status === "rejected") {
133
- warnings.push("Live Antigravity quota is unavailable. Ensure the Antigravity IDE is running.");
134
- }
135
- else if (quotaResult.value.entries.length === 0) {
136
- warnings.push("Antigravity local quota RPC responded, but no recognized model quota windows were found.");
137
- }
138
- const selectedRecords = deduplicateRecords(records);
139
- const duplicateEvents = records.length - selectedRecords.length;
140
- if (duplicateEvents > 0) {
141
- warnings.push(`Collapsed ${duplicateEvents} duplicate Antigravity usage response(s).`);
142
- }
143
- const byModel = new Map();
144
- const byDay = createDailyUsageAggregates();
145
- for (const record of selectedRecords) {
146
- const modelId = resolveModelId(record.modelId);
147
- const totals = usageRecordToTotals(modelId, record);
148
- addModelUsage(byModel, modelId, totals);
149
- addDailyUsage(byDay, record.timestamp, modelId, undefined, totals);
150
- }
151
- const modelUsage = [...byModel.entries()]
152
- .map(([modelId, totals]) => ({
153
- modelId,
154
- totals
155
- }))
156
- .sort((left, right) => right.totals.estimatedCredits -
157
- left.totals.estimatedCredits);
158
- const unknownPricedModels = modelUsage
159
- .filter((row) => !rateForModel(row.modelId, rowInputTokens(row)) && !UNPRICED_MODELS.has(row.modelId))
160
- .map((row) => row.modelId);
161
- if (unknownPricedModels.length > 0) {
162
- warnings.push(`No Antigravity estimated API-equivalent rate configured for: ${unknownPricedModels.join(", ")}.`);
163
- }
164
- const limitWindows = quotaSnapshot?.entries.map((quota) => buildAntigravityLimitWindow(quota, quotaSnapshot.planType, selectedRecords, quotaSnapshot.fetchedAt)) ?? [];
165
- return {
166
- providerId: this.id,
167
- providerLabel: this.label,
168
- summary: {
169
- filesScanned: records.length > 0 ? 1 : 0,
170
- linesRead: records.length,
171
- tokenEvents: selectedRecords.length,
172
- totals: sumUsageTotals(modelUsage.map((row) => row.totals)),
173
- distinctModels: modelUsage.map((row) => row.modelId),
174
- distinctPlanTypes: [
175
- ...new Set(limitWindows.map((window) => window.planType))
176
- ],
177
- rootLabel: "Tokscale usage + Antigravity local quota",
178
- rootPath: ANTIGRAVITY_CACHE_ROOT
179
- },
180
- modelUsage,
181
- dayUsage: buildDailyUsageRows(byDay),
182
- primaryLimitWindows: limitWindows.filter((window) => window.scope === "primary"),
183
- secondaryLimitWindows: limitWindows.filter((window) => window.scope === "secondary"),
184
- warnings,
185
- analytics: quotaSnapshot?.userIdHash
186
- ? {
187
- agentName: this.label.replace(/\s/g, ""),
188
- userIdHash: quotaSnapshot.userIdHash
189
- }
190
- : undefined
191
- };
192
- }
193
- }
194
- async function collectAntigravityQuotaFromLocalRpc() {
195
- const server = await findAntigravityLocalServer();
196
- if (!server) {
197
- throw new Error("Antigravity local language server was not found.");
198
- }
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
- ]);
210
- return {
211
- entries: parseAntigravityQuotaEntries(quota),
212
- fetchedAt: Date.now(),
213
- planType: parseAntigravityPlanType(status),
214
- userIdHash: parseAntigravityUserIdHash(status)
215
- };
216
- }
217
- function buildAntigravityLimitWindow(quota, planType, records, fetchedAt) {
218
- const startAt = quota.resetAt - quota.windowMinutes * 60000;
219
- const modelIds = new Set(quota.modelIds.map(resolveModelId));
220
- const byModel = new Map();
221
- for (const record of records) {
222
- const modelId = resolveModelId(record.modelId);
223
- if (record.timestamp < startAt ||
224
- record.timestamp >= quota.resetAt ||
225
- !modelIds.has(modelId)) {
226
- continue;
227
- }
228
- addModelUsage(byModel, modelId, usageRecordToTotals(modelId, record));
229
- }
230
- const modelUsage = [...byModel.entries()]
231
- .map(([modelId, totals]) => ({
232
- modelId,
233
- totals
234
- }))
235
- .sort((left, right) => right.totals.estimatedCredits -
236
- left.totals.estimatedCredits);
237
- const totals = sumUsageTotals(modelUsage.map((row) => row.totals));
238
- const usedPercent = clampPercent((1 - quota.remainingFraction) * 100);
239
- // Quota percentage is authoritative from Antigravity RPC. Token totals are
240
- // reconstructed from locally available Tokscale events inside the same time
241
- // window and may not match Antigravity's internal quota accounting exactly.
242
- return {
243
- scope: quota.scope,
244
- planType,
245
- limitId: quota.limitId,
246
- windowMinutes: quota.windowMinutes,
247
- startTimeUtcIso: new Date(quota.resetAt - quota.windowMinutes * 60000).toISOString(),
248
- endTimeUtcIso: new Date(quota.resetAt).toISOString(),
249
- firstSeenUtcIso: new Date(fetchedAt).toISOString(),
250
- lastSeenUtcIso: new Date(fetchedAt).toISOString(),
251
- minUsedPercent: usedPercent,
252
- maxUsedPercent: usedPercent,
253
- totals,
254
- modelUsage,
255
- eventCount: totals.eventCount
256
- };
257
- }
258
- async function findAntigravityLocalServer() {
259
- const process = await findAntigravityProcess();
260
- if (!process) {
261
- return null;
262
- }
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;
271
- }
272
- catch {
273
- // Try the next loopback listener owned by the same Antigravity process.
274
- }
275
- }
276
- return null;
277
- }
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;
283
- }
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;
292
- }
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
- };
302
- }
303
- }
304
- return null;
305
- }
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])))
316
- ];
317
- }
318
- function rpc(server, endpoint, payload = {}) {
319
- const body = JSON.stringify(payload);
320
- return new Promise((resolve, reject) => {
321
- const request = https.request({
322
- hostname: "127.0.0.1",
323
- port: server.port,
324
- path: endpoint,
325
- method: "POST",
326
- rejectUnauthorized: false,
327
- timeout: 5000,
328
- headers: {
329
- "X-Codeium-Csrf-Token": server.csrfToken,
330
- "Content-Type": "application/json",
331
- "Connect-Protocol-Version": "1"
332
- }
333
- }, (response) => {
334
- const chunks = [];
335
- response.on("data", (chunk) => chunks.push(chunk));
336
- response.on("end", () => {
337
- const responseBody = Buffer.concat(chunks).toString("utf8");
338
- if (!response.statusCode || response.statusCode >= 300) {
339
- reject(new Error(`RPC failed: ${response.statusCode ?? "unknown"}`));
340
- return;
341
- }
342
- try {
343
- resolve(responseBody ? JSON.parse(responseBody) : {});
344
- }
345
- catch (error) {
346
- reject(error);
347
- }
348
- });
349
- });
350
- request.on("timeout", () => {
351
- request.destroy(new Error(`Timed out reading Antigravity RPC ${endpoint}.`));
352
- });
353
- request.on("error", reject);
354
- request.end(body);
355
- });
356
- }
357
- export function parseAntigravityQuotaEntries(payload) {
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 [];
363
- }
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 ||
371
- !Number.isFinite(resetAt) ||
372
- typeof bucket.remainingFraction !== "number" ||
373
- bucket.remainingFraction < 0 ||
374
- bucket.remainingFraction > 1) {
375
- return [];
376
- }
377
- return [{
378
- limitId: bucket.bucketId,
379
- modelIds,
380
- remainingFraction: bucket.remainingFraction,
381
- resetAt,
382
- ...window
383
- }];
384
- });
385
- });
386
- }
387
- export function parseAntigravityPlanType(payload) {
388
- const planName = payload.response?.userStatus?.planStatus?.planInfo?.planName;
389
- return typeof planName === "string" && planName ? planName : "unknown";
390
- }
391
- export function parseAntigravityUserIdHash(payload) {
392
- const email = payload.response?.userStatus?.email;
393
- return typeof email === "string" && email
394
- ? createHash("md5").update(email).digest("hex")
395
- : null;
396
- }
397
- function resolveQuotaGroupModelIds(text) {
398
- return (QUOTA_MODEL_GROUPS.find(({ pattern }) => pattern.test(text.toLowerCase()))?.models ?? []);
399
- }
400
- function numberOrZero(value) {
401
- return typeof value === "number" && Number.isFinite(value)
402
- ? value
403
- : 0;
404
- }
405
- function clampPercent(value) {
406
- if (!Number.isFinite(value)) {
407
- return 0;
408
- }
409
- return Math.min(100, Math.max(0, value));
410
- }
411
- async function readAntigravityUsageCache() {
412
- const sessionsRoot = path.join(ANTIGRAVITY_CACHE_ROOT, "sessions");
413
- const records = [];
414
- for await (const filePath of walkJsonlFiles(sessionsRoot)) {
415
- const stream = fs.createReadStream(filePath, { encoding: "utf8" });
416
- const lineReader = readline.createInterface({
417
- input: stream,
418
- crlfDelay: Infinity
419
- });
420
- for await (const line of lineReader) {
421
- if (!line.trim()) {
422
- continue;
423
- }
424
- let payload;
425
- try {
426
- payload = JSON.parse(line);
427
- }
428
- catch {
429
- continue;
430
- }
431
- const record = usageRecordFromCacheEntry(payload);
432
- if (record) {
433
- records.push(record);
434
- }
435
- }
436
- }
437
- return records;
438
- }
439
- function usageRecordFromCacheEntry(value) {
440
- const entry = value && typeof value === "object"
441
- ? value
442
- : null;
443
- if (!entry || entry.type !== "usage") {
444
- return null;
445
- }
446
- const sessionId = typeof entry.sessionId === "string" ? entry.sessionId : "";
447
- const responseId = typeof entry.responseId === "string" ? entry.responseId : "";
448
- const modelId = typeof entry.modelId === "string" ? entry.modelId : "";
449
- const timestamp = numberOrZero(entry.timestamp);
450
- if (!sessionId || !responseId || !modelId || timestamp <= 0) {
451
- return null;
452
- }
453
- return {
454
- type: "usage",
455
- sessionId,
456
- responseId,
457
- timestamp,
458
- modelId,
459
- input: numberOrZero(entry.input),
460
- cacheRead: numberOrZero(entry.cacheRead),
461
- cacheWrite: numberOrZero(entry.cacheWrite),
462
- output: numberOrZero(entry.output),
463
- reasoning: numberOrZero(entry.reasoning)
464
- };
465
- }
466
- async function* walkJsonlFiles(directory) {
467
- let entries;
468
- try {
469
- entries = await fs.promises.readdir(directory, {
470
- withFileTypes: true
471
- });
472
- }
473
- catch {
474
- return;
475
- }
476
- for (const entry of entries) {
477
- const fullPath = path.join(directory, entry.name);
478
- if (entry.isDirectory()) {
479
- yield* walkJsonlFiles(fullPath);
480
- }
481
- else if (entry.isFile() && fullPath.endsWith(".jsonl")) {
482
- yield fullPath;
483
- }
484
- }
485
- }
486
- function deduplicateRecords(records) {
487
- const byKey = new Map();
488
- for (const record of records) {
489
- byKey.set(`${record.sessionId}:${record.responseId}`, record);
490
- }
491
- return [...byKey.values()];
492
- }
493
- function usageRecordToTotals(modelId, record) {
494
- return {
495
- inputTokens: record.input,
496
- outputTokens: record.output,
497
- cacheReadInputTokens: record.cacheRead,
498
- cacheWriteInputTokens: record.cacheWrite,
499
- cacheWrite5mInputTokens: 0,
500
- cacheWrite1hInputTokens: 0,
501
- reasoningOutputTokens: Math.min(record.reasoning, record.output),
502
- totalTokens: record.input +
503
- record.cacheRead +
504
- record.cacheWrite +
505
- record.output,
506
- estimatedCredits: creditsFor(modelId, record),
507
- eventCount: 1,
508
- cacheStatus: "known",
509
- estimatedCreditsStatus: rateForModel(modelId, record.input)
510
- ? "known"
511
- : "unavailable"
512
- };
513
- }
514
- function creditsFor(modelId, record) {
515
- const rate = rateForModel(modelId, record.input);
516
- if (!rate) {
517
- return 0;
518
- }
519
- return ((record.input / 1000000) * rate.input +
520
- (record.cacheRead / 1000000) * rate.cacheRead +
521
- (record.cacheWrite / 1000000) * rate.cacheWrite +
522
- (record.output / 1000000) * rate.output);
523
- }
524
- function rateForModel(modelId, inputTokens) {
525
- return resolveUsageRate(RATE_CARD, modelId, inputTokens);
526
- }
527
- function rowInputTokens(row) {
528
- return row.totals.inputTokens + row.totals.cacheReadInputTokens + row.totals.cacheWriteInputTokens;
529
- }
530
- function resolveModelId(modelId) {
531
- return MODEL_ALIASES[modelId] ?? (modelId || "unknown");
532
- }
533
- function addModelUsage(byModel, modelId, deltaTotals) {
534
- const totals = byModel.get(modelId) ?? createEmptyUsageTotals();
535
- addUsageTotals(totals, deltaTotals);
536
- byModel.set(modelId, totals);
537
- }
1
+ export { AntigravityUsageProvider } from "./antigravity/provider.js";
2
+ export { parseAntigravityQuotaEntries } from "./antigravity/quota-parser.js";