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.
- package/ink-app/dist/cli-options.js +2 -2
- package/ink-app/dist/index.js +317 -136
- package/ink-app/dist/providers/antigravity.js +143 -394
- package/ink-app/dist/providers/claude.js +91 -120
- package/ink-app/dist/providers/index.js +1 -7
- package/ink-app/dist/reporting.js +30 -1
- package/package.json +11 -14
|
@@ -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
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
"
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
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:
|
|
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
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
|
223
|
-
|
|
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
|
|
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
|
|
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
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
|
|
307
|
-
|
|
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
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
-
|
|
378
|
-
|
|
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
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
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
|
-
|
|
441
|
-
|
|
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:
|
|
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
|
|
484
|
-
reject(new Error(`
|
|
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(
|
|
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
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
-
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
-
|
|
538
|
-
|
|
372
|
+
typeof bucket.remainingFraction !== "number" ||
|
|
373
|
+
bucket.remainingFraction < 0 ||
|
|
374
|
+
bucket.remainingFraction > 1) {
|
|
375
|
+
return [];
|
|
539
376
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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
|
-
|
|
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
|
|
636
|
-
return
|
|
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(
|
|
650
|
-
const sessionsRoot = path.join(
|
|
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 {
|