letmecode 0.1.12 → 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/index.js +317 -136
- package/ink-app/dist/providers/antigravity.js +133 -558
- package/ink-app/dist/providers/index.js +1 -1
- package/package.json +11 -14
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import https from "node:https";
|
|
4
|
-
import { createRequire } from "node:module";
|
|
5
4
|
import fs from "node:fs";
|
|
6
5
|
import os from "node:os";
|
|
7
6
|
import path from "node:path";
|
|
@@ -10,7 +9,6 @@ import { promisify } from "node:util";
|
|
|
10
9
|
import { UsageProviderBase, addUsageTotals, createEmptyUsageTotals, sumUsageTotals } from "./contract.js";
|
|
11
10
|
import { addDailyUsage, buildDailyUsageRows, createDailyUsageAggregates } from "./daily.js";
|
|
12
11
|
import { resolveUsageRate } from "./pricing.js";
|
|
13
|
-
const require = createRequire(import.meta.url);
|
|
14
12
|
const execFileAsync = promisify(execFile);
|
|
15
13
|
const RATE_CARD = {
|
|
16
14
|
"gemini-3.5-flash": {
|
|
@@ -68,21 +66,36 @@ const RATE_CARD = {
|
|
|
68
66
|
const UNPRICED_MODELS = new Set([
|
|
69
67
|
"gpt-oss-120b"
|
|
70
68
|
]);
|
|
71
|
-
const ANTIGRAVITY_PRIMARY_WINDOW_MINUTES = 5 * 60;
|
|
72
|
-
const ANTIGRAVITY_WEEKLY_WINDOW_MINUTES = 7 * 24 * 60;
|
|
73
69
|
const ANTIGRAVITY_QUOTA_SUMMARY_PATH = "/exa.language_server_pb.LanguageServerService/RetrieveUserQuotaSummary";
|
|
74
70
|
const ANTIGRAVITY_USER_STATUS_PATH = "/exa.language_server_pb.LanguageServerService/GetUserStatus";
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
+
}
|
|
86
99
|
];
|
|
87
100
|
const MODEL_ALIASES = {
|
|
88
101
|
"gemini-3-flash-a": "gemini-3-flash",
|
|
@@ -96,8 +109,7 @@ export class AntigravityUsageProvider extends UsageProviderBase {
|
|
|
96
109
|
constructor(options = {}) {
|
|
97
110
|
super("antigravity", "Antigravity");
|
|
98
111
|
this.collectUsage =
|
|
99
|
-
options.collectUsage ??
|
|
100
|
-
collectAntigravityUsageFromTokscale;
|
|
112
|
+
options.collectUsage ?? readAntigravityUsageCache;
|
|
101
113
|
this.collectQuota =
|
|
102
114
|
options.collectQuota ??
|
|
103
115
|
collectAntigravityQuotaFromLocalRpc;
|
|
@@ -115,7 +127,7 @@ export class AntigravityUsageProvider extends UsageProviderBase {
|
|
|
115
127
|
? quotaResult.value
|
|
116
128
|
: null;
|
|
117
129
|
if (usageResult.status === "rejected") {
|
|
118
|
-
warnings.push("Could not
|
|
130
|
+
warnings.push("Could not read Antigravity token usage cache.");
|
|
119
131
|
}
|
|
120
132
|
if (quotaResult.status === "rejected") {
|
|
121
133
|
warnings.push("Live Antigravity quota is unavailable. Ensure the Antigravity IDE is running.");
|
|
@@ -123,9 +135,6 @@ export class AntigravityUsageProvider extends UsageProviderBase {
|
|
|
123
135
|
else if (quotaResult.value.entries.length === 0) {
|
|
124
136
|
warnings.push("Antigravity local quota RPC responded, but no recognized model quota windows were found.");
|
|
125
137
|
}
|
|
126
|
-
if (isAntigravityDebugEnabled()) {
|
|
127
|
-
warnings.push(`Antigravity debug log: ${ANTIGRAVITY_DEBUG_LOG_PATH}`);
|
|
128
|
-
}
|
|
129
138
|
const selectedRecords = deduplicateRecords(records);
|
|
130
139
|
const duplicateEvents = records.length - selectedRecords.length;
|
|
131
140
|
if (duplicateEvents > 0) {
|
|
@@ -152,7 +161,7 @@ export class AntigravityUsageProvider extends UsageProviderBase {
|
|
|
152
161
|
if (unknownPricedModels.length > 0) {
|
|
153
162
|
warnings.push(`No Antigravity estimated API-equivalent rate configured for: ${unknownPricedModels.join(", ")}.`);
|
|
154
163
|
}
|
|
155
|
-
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)) ?? [];
|
|
156
165
|
return {
|
|
157
166
|
providerId: this.id,
|
|
158
167
|
providerLabel: this.label,
|
|
@@ -166,7 +175,7 @@ export class AntigravityUsageProvider extends UsageProviderBase {
|
|
|
166
175
|
...new Set(limitWindows.map((window) => window.planType))
|
|
167
176
|
],
|
|
168
177
|
rootLabel: "Tokscale usage + Antigravity local quota",
|
|
169
|
-
rootPath:
|
|
178
|
+
rootPath: ANTIGRAVITY_CACHE_ROOT
|
|
170
179
|
},
|
|
171
180
|
modelUsage,
|
|
172
181
|
dayUsage: buildDailyUsageRows(byDay),
|
|
@@ -175,121 +184,47 @@ export class AntigravityUsageProvider extends UsageProviderBase {
|
|
|
175
184
|
warnings,
|
|
176
185
|
analytics: quotaSnapshot?.userIdHash
|
|
177
186
|
? {
|
|
178
|
-
agentName:
|
|
187
|
+
agentName: this.label.replace(/\s/g, ""),
|
|
179
188
|
userIdHash: quotaSnapshot.userIdHash
|
|
180
189
|
}
|
|
181
190
|
: undefined
|
|
182
191
|
};
|
|
183
192
|
}
|
|
184
193
|
}
|
|
185
|
-
export async function collectAntigravityUsage() {
|
|
186
|
-
return collectAntigravityUsageFromTokscale();
|
|
187
|
-
}
|
|
188
|
-
export async function collectAntigravityQuota() {
|
|
189
|
-
return collectAntigravityQuotaFromLocalRpc();
|
|
190
|
-
}
|
|
191
|
-
async function collectAntigravityUsageFromTokscale() {
|
|
192
|
-
await runTokscale([
|
|
193
|
-
"antigravity",
|
|
194
|
-
"sync"
|
|
195
|
-
]);
|
|
196
|
-
return readAntigravityUsageCache(getAntigravityCacheRoot());
|
|
197
|
-
}
|
|
198
|
-
async function runTokscale(args) {
|
|
199
|
-
return execFileAsync(process.execPath, [require.resolve("@tokscale/cli/dist/index.js"), ...args], {
|
|
200
|
-
encoding: "utf8",
|
|
201
|
-
maxBuffer: 32 * 1024 * 1024
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
194
|
async function collectAntigravityQuotaFromLocalRpc() {
|
|
205
195
|
const server = await findAntigravityLocalServer();
|
|
206
196
|
if (!server) {
|
|
207
197
|
throw new Error("Antigravity local language server was not found.");
|
|
208
198
|
}
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
}
|
|
228
|
-
await writeAntigravityDebugEvent("quota-rpc-response", {
|
|
229
|
-
port: server.port,
|
|
230
|
-
quotaPath: ANTIGRAVITY_QUOTA_SUMMARY_PATH,
|
|
231
|
-
userStatusPath: ANTIGRAVITY_USER_STATUS_PATH,
|
|
232
|
-
planType,
|
|
233
|
-
entries: entries.map((entry) => ({
|
|
234
|
-
limitId: entry.limitId,
|
|
235
|
-
remainingFraction: entry.remainingFraction,
|
|
236
|
-
resetAt: new Date(entry.resetAt).toISOString(),
|
|
237
|
-
windowMinutes: entry.windowMinutes,
|
|
238
|
-
scope: entry.scope,
|
|
239
|
-
modelIds: entry.modelIds
|
|
240
|
-
})),
|
|
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
|
-
}
|
|
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"
|
|
251
207
|
}
|
|
252
|
-
|
|
253
|
-
|
|
208
|
+
}).catch(() => null)
|
|
209
|
+
]);
|
|
254
210
|
return {
|
|
255
|
-
entries,
|
|
256
|
-
fetchedAt,
|
|
257
|
-
|
|
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")
|
|
258
215
|
};
|
|
259
216
|
}
|
|
260
|
-
function
|
|
261
|
-
|
|
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
|
-
}
|
|
274
|
-
function recordsForQuotaWindow(quota, records) {
|
|
275
|
-
if (quota.modelIds.length === 0) {
|
|
276
|
-
return [];
|
|
277
|
-
}
|
|
278
|
-
const endMs = quota.resetAt;
|
|
279
|
-
const startMs = endMs - quota.windowMinutes * 60000;
|
|
217
|
+
function buildAntigravityLimitWindow(quota, planType, records, fetchedAt) {
|
|
218
|
+
const startAt = quota.resetAt - quota.windowMinutes * 60000;
|
|
280
219
|
const modelIds = new Set(quota.modelIds.map(resolveModelId));
|
|
281
|
-
return records.filter((record) => {
|
|
282
|
-
const modelId = resolveModelId(record.modelId);
|
|
283
|
-
return (record.timestamp >= startMs &&
|
|
284
|
-
record.timestamp < endMs &&
|
|
285
|
-
modelIds.has(modelId));
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
function buildAntigravityLimitWindow(quota, records, fetchedAt) {
|
|
289
|
-
const matchingRecords = recordsForQuotaWindow(quota, records);
|
|
290
220
|
const byModel = new Map();
|
|
291
|
-
for (const record of
|
|
221
|
+
for (const record of records) {
|
|
292
222
|
const modelId = resolveModelId(record.modelId);
|
|
223
|
+
if (record.timestamp < startAt ||
|
|
224
|
+
record.timestamp >= quota.resetAt ||
|
|
225
|
+
!modelIds.has(modelId)) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
293
228
|
addModelUsage(byModel, modelId, usageRecordToTotals(modelId, record));
|
|
294
229
|
}
|
|
295
230
|
const modelUsage = [...byModel.entries()]
|
|
@@ -306,7 +241,7 @@ function buildAntigravityLimitWindow(quota, records, fetchedAt) {
|
|
|
306
241
|
// window and may not match Antigravity's internal quota accounting exactly.
|
|
307
242
|
return {
|
|
308
243
|
scope: quota.scope,
|
|
309
|
-
planType
|
|
244
|
+
planType,
|
|
310
245
|
limitId: quota.limitId,
|
|
311
246
|
windowMinutes: quota.windowMinutes,
|
|
312
247
|
startTimeUtcIso: new Date(quota.resetAt - quota.windowMinutes * 60000).toISOString(),
|
|
@@ -320,236 +255,88 @@ function buildAntigravityLimitWindow(quota, records, fetchedAt) {
|
|
|
320
255
|
eventCount: totals.eventCount
|
|
321
256
|
};
|
|
322
257
|
}
|
|
323
|
-
const antigravityHttpsAgent = new https.Agent({
|
|
324
|
-
rejectUnauthorized: false
|
|
325
|
-
});
|
|
326
258
|
async function findAntigravityLocalServer() {
|
|
327
259
|
const process = await findAntigravityProcess();
|
|
328
260
|
if (!process) {
|
|
329
|
-
await writeAntigravityDebugEvent("process-not-found", {});
|
|
330
261
|
return null;
|
|
331
262
|
}
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const fromProc = await findAntigravityProcessFromProc();
|
|
341
|
-
if (fromProc) {
|
|
342
|
-
return fromProc;
|
|
343
|
-
}
|
|
344
|
-
return findAntigravityProcessFromPs();
|
|
345
|
-
}
|
|
346
|
-
async function findAntigravityProcessFromProc() {
|
|
347
|
-
let entries;
|
|
348
|
-
try {
|
|
349
|
-
entries = await fs.promises.readdir("/proc", { withFileTypes: true });
|
|
350
|
-
}
|
|
351
|
-
catch {
|
|
352
|
-
return null;
|
|
353
|
-
}
|
|
354
|
-
for (const entry of entries) {
|
|
355
|
-
if (!entry.isDirectory() || !/^\d+$/.test(entry.name)) {
|
|
356
|
-
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;
|
|
357
271
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
const process = parseAntigravityProcessFromArgs(pid, cmdline);
|
|
361
|
-
if (process) {
|
|
362
|
-
return process;
|
|
272
|
+
catch {
|
|
273
|
+
// Try the next loopback listener owned by the same Antigravity process.
|
|
363
274
|
}
|
|
364
275
|
}
|
|
365
276
|
return null;
|
|
366
277
|
}
|
|
367
|
-
async function
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
.split("\0")
|
|
373
|
-
.filter(Boolean);
|
|
374
|
-
}
|
|
375
|
-
catch {
|
|
376
|
-
return [];
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
async function findAntigravityProcessFromPs() {
|
|
380
|
-
try {
|
|
381
|
-
const { stdout } = await execFileAsync("ps", ["-eo", "pid=,args="], {
|
|
382
|
-
encoding: "utf8",
|
|
383
|
-
maxBuffer: 4 * 1024 * 1024,
|
|
384
|
-
timeout: 5000
|
|
385
|
-
});
|
|
386
|
-
for (const line of stdout.split(/\r?\n/)) {
|
|
387
|
-
const match = line.match(/^\s*(\d+)\s+(.+)$/);
|
|
388
|
-
if (!match) {
|
|
389
|
-
continue;
|
|
390
|
-
}
|
|
391
|
-
const process = parseAntigravityProcessFromArgs(Number(match[1]), splitCommandLineForDiscovery(match[2]));
|
|
392
|
-
if (process) {
|
|
393
|
-
return process;
|
|
394
|
-
}
|
|
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;
|
|
395
283
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
}
|
|
405
|
-
function parseAntigravityProcessFromArgs(pid, args) {
|
|
406
|
-
if (!Number.isInteger(pid) || pid <= 0 || !isAntigravityLanguageServerCommand(args)) {
|
|
407
|
-
return null;
|
|
408
|
-
}
|
|
409
|
-
const csrfToken = readNamedArg(args, "--csrf_token")?.trim();
|
|
410
|
-
if (!csrfToken) {
|
|
411
|
-
return null;
|
|
412
|
-
}
|
|
413
|
-
return { pid, csrfToken };
|
|
414
|
-
}
|
|
415
|
-
function isAntigravityLanguageServerCommand(args) {
|
|
416
|
-
const normalized = args.join(" ").toLowerCase();
|
|
417
|
-
return (normalized.includes("antigravity") &&
|
|
418
|
-
(normalized.includes("language-server") ||
|
|
419
|
-
normalized.includes("language_server") ||
|
|
420
|
-
normalized.includes("extension-server") ||
|
|
421
|
-
normalized.includes("extension_server")));
|
|
422
|
-
}
|
|
423
|
-
function readNamedArg(args, name) {
|
|
424
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
425
|
-
const arg = args[index];
|
|
426
|
-
if (arg === name) {
|
|
427
|
-
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;
|
|
428
292
|
}
|
|
429
|
-
|
|
430
|
-
|
|
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
|
+
};
|
|
431
302
|
}
|
|
432
303
|
}
|
|
433
304
|
return null;
|
|
434
305
|
}
|
|
435
|
-
async function
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
|
|
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])))
|
|
439
316
|
];
|
|
440
|
-
for (const parse of parsers) {
|
|
441
|
-
const ports = await parse();
|
|
442
|
-
if (ports.length > 0) {
|
|
443
|
-
return ports;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
return [];
|
|
447
|
-
}
|
|
448
|
-
async function findListeningLoopbackPortsWithSs(pid) {
|
|
449
|
-
try {
|
|
450
|
-
const { stdout } = await execFileAsync("ss", ["-H", "-ltnp"], {
|
|
451
|
-
encoding: "utf8",
|
|
452
|
-
maxBuffer: 1024 * 1024,
|
|
453
|
-
timeout: 5000
|
|
454
|
-
});
|
|
455
|
-
return uniquePorts(stdout
|
|
456
|
-
.split(/\r?\n/)
|
|
457
|
-
.filter((line) => line.includes(`pid=${pid},`) && isLoopbackListenLine(line))
|
|
458
|
-
.map(extractPortFromListenLine));
|
|
459
|
-
}
|
|
460
|
-
catch {
|
|
461
|
-
return [];
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
async function findListeningLoopbackPortsWithLsof(pid) {
|
|
465
|
-
try {
|
|
466
|
-
const { stdout } = await execFileAsync("lsof", ["-Pan", "-p", String(pid), "-iTCP", "-sTCP:LISTEN"], {
|
|
467
|
-
encoding: "utf8",
|
|
468
|
-
maxBuffer: 1024 * 1024,
|
|
469
|
-
timeout: 5000
|
|
470
|
-
});
|
|
471
|
-
return uniquePorts(stdout
|
|
472
|
-
.split(/\r?\n/)
|
|
473
|
-
.filter(isLoopbackListenLine)
|
|
474
|
-
.map(extractPortFromListenLine));
|
|
475
|
-
}
|
|
476
|
-
catch {
|
|
477
|
-
return [];
|
|
478
|
-
}
|
|
479
317
|
}
|
|
480
|
-
function
|
|
481
|
-
return /(?:127\.0\.0\.1|localhost|\[::1\]|::1):\d+\b/.test(line);
|
|
482
|
-
}
|
|
483
|
-
function extractPortFromListenLine(line) {
|
|
484
|
-
const matches = [...line.matchAll(/(?:127\.0\.0\.1|localhost|\[::1\]|::1):(\d+)/g)];
|
|
485
|
-
const value = matches.at(-1)?.[1];
|
|
486
|
-
const port = value ? Number(value) : NaN;
|
|
487
|
-
return Number.isInteger(port) && port >= 1 && port <= 65535 ? port : null;
|
|
488
|
-
}
|
|
489
|
-
function uniquePorts(ports) {
|
|
490
|
-
return [...new Set(ports.filter((port) => port !== null))];
|
|
491
|
-
}
|
|
492
|
-
async function probeAntigravityPorts(ports, csrfToken) {
|
|
493
|
-
for (const port of ports) {
|
|
494
|
-
const server = { port, csrfToken };
|
|
495
|
-
try {
|
|
496
|
-
await readAntigravityQuotaSummary(server);
|
|
497
|
-
await writeAntigravityDebugEvent("port-probe-ok", { port });
|
|
498
|
-
return server;
|
|
499
|
-
}
|
|
500
|
-
catch (error) {
|
|
501
|
-
await writeAntigravityDebugEvent("port-probe-failed", {
|
|
502
|
-
port,
|
|
503
|
-
error: error instanceof Error ? error.message : String(error)
|
|
504
|
-
});
|
|
505
|
-
// Try the next loopback listener owned by the same Antigravity process.
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
return null;
|
|
509
|
-
}
|
|
510
|
-
async function readAntigravityQuotaSummary(server) {
|
|
511
|
-
return requestAntigravityQuotaSummary(server);
|
|
512
|
-
}
|
|
513
|
-
async function readAntigravityUserStatus(server) {
|
|
514
|
-
return requestAntigravityUserStatus(server);
|
|
515
|
-
}
|
|
516
|
-
function requestAntigravityQuotaSummary(server) {
|
|
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) {
|
|
318
|
+
function rpc(server, endpoint, payload = {}) {
|
|
530
319
|
const body = JSON.stringify(payload);
|
|
531
320
|
return new Promise((resolve, reject) => {
|
|
532
321
|
const request = https.request({
|
|
533
322
|
hostname: "127.0.0.1",
|
|
534
323
|
port: server.port,
|
|
535
|
-
path:
|
|
324
|
+
path: endpoint,
|
|
536
325
|
method: "POST",
|
|
326
|
+
rejectUnauthorized: false,
|
|
537
327
|
timeout: 5000,
|
|
538
|
-
agent: antigravityHttpsAgent,
|
|
539
328
|
headers: {
|
|
540
329
|
"X-Codeium-Csrf-Token": server.csrfToken,
|
|
541
330
|
"Content-Type": "application/json",
|
|
542
|
-
"Connect-Protocol-Version": "1"
|
|
543
|
-
Accept: "application/json",
|
|
544
|
-
"Content-Length": Buffer.byteLength(body)
|
|
331
|
+
"Connect-Protocol-Version": "1"
|
|
545
332
|
}
|
|
546
333
|
}, (response) => {
|
|
547
334
|
const chunks = [];
|
|
548
335
|
response.on("data", (chunk) => chunks.push(chunk));
|
|
549
336
|
response.on("end", () => {
|
|
550
337
|
const responseBody = Buffer.concat(chunks).toString("utf8");
|
|
551
|
-
if (!response.statusCode || response.statusCode
|
|
552
|
-
reject(new Error(`
|
|
338
|
+
if (!response.statusCode || response.statusCode >= 300) {
|
|
339
|
+
reject(new Error(`RPC failed: ${response.statusCode ?? "unknown"}`));
|
|
553
340
|
return;
|
|
554
341
|
}
|
|
555
342
|
try {
|
|
@@ -561,253 +348,44 @@ function requestAntigravityRpc(server, rpcPath, payload) {
|
|
|
561
348
|
});
|
|
562
349
|
});
|
|
563
350
|
request.on("timeout", () => {
|
|
564
|
-
request.destroy(new Error(`Timed out reading Antigravity RPC ${
|
|
351
|
+
request.destroy(new Error(`Timed out reading Antigravity RPC ${endpoint}.`));
|
|
565
352
|
});
|
|
566
353
|
request.on("error", reject);
|
|
567
354
|
request.end(body);
|
|
568
355
|
});
|
|
569
356
|
}
|
|
570
357
|
export function parseAntigravityQuotaEntries(payload) {
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const group = asRecord(groupValue);
|
|
577
|
-
if (!group) {
|
|
578
|
-
continue;
|
|
579
|
-
}
|
|
580
|
-
const displayName = asString(group.displayName) ?? "";
|
|
581
|
-
const description = asString(group.description) ?? "";
|
|
582
|
-
const modelIds = resolveQuotaGroupModelIds(displayName, description);
|
|
583
|
-
if (modelIds.length === 0) {
|
|
584
|
-
void writeAntigravityDebugEvent("quota-group-skipped", {
|
|
585
|
-
displayName,
|
|
586
|
-
description
|
|
587
|
-
});
|
|
588
|
-
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 [];
|
|
589
363
|
}
|
|
590
|
-
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
const remainingFraction = asFiniteNumber(bucket.remainingFraction);
|
|
598
|
-
const resetTime = asString(bucket.resetTime);
|
|
599
|
-
const resetAt = resetTime === null ? NaN : Date.parse(resetTime);
|
|
600
|
-
if (!bucketId ||
|
|
601
|
-
remainingFraction === null ||
|
|
602
|
-
remainingFraction < 0 ||
|
|
603
|
-
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 ||
|
|
604
371
|
!Number.isFinite(resetAt) ||
|
|
605
|
-
|
|
606
|
-
|
|
372
|
+
typeof bucket.remainingFraction !== "number" ||
|
|
373
|
+
bucket.remainingFraction < 0 ||
|
|
374
|
+
bucket.remainingFraction > 1) {
|
|
375
|
+
return [];
|
|
607
376
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
planType: "unknown"
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
return entries;
|
|
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
|
-
}
|
|
727
|
-
function resolveQuotaWindow(window) {
|
|
728
|
-
switch (window) {
|
|
729
|
-
case "5h":
|
|
730
|
-
return {
|
|
731
|
-
scope: "primary",
|
|
732
|
-
windowMinutes: ANTIGRAVITY_PRIMARY_WINDOW_MINUTES
|
|
733
|
-
};
|
|
734
|
-
case "weekly":
|
|
735
|
-
return {
|
|
736
|
-
scope: "secondary",
|
|
737
|
-
windowMinutes: ANTIGRAVITY_WEEKLY_WINDOW_MINUTES
|
|
738
|
-
};
|
|
739
|
-
default:
|
|
740
|
-
return null;
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
function resolveQuotaGroupModelIds(displayName, description) {
|
|
744
|
-
const text = `${displayName} ${description}`.toLowerCase();
|
|
745
|
-
if (text.includes("gemini")) {
|
|
746
|
-
return GEMINI_QUOTA_MODELS;
|
|
747
|
-
}
|
|
748
|
-
if (text.includes("claude") || text.includes("gpt")) {
|
|
749
|
-
return THIRD_PARTY_QUOTA_MODELS;
|
|
750
|
-
}
|
|
751
|
-
return [];
|
|
752
|
-
}
|
|
753
|
-
function asRecord(value) {
|
|
754
|
-
return value && typeof value === "object" && !Array.isArray(value)
|
|
755
|
-
? value
|
|
756
|
-
: null;
|
|
757
|
-
}
|
|
758
|
-
function asArray(value) {
|
|
759
|
-
return Array.isArray(value) ? value : [];
|
|
760
|
-
}
|
|
761
|
-
function asString(value) {
|
|
762
|
-
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
763
|
-
}
|
|
764
|
-
function asFiniteNumber(value) {
|
|
765
|
-
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
766
|
-
}
|
|
767
|
-
function isAntigravityDebugEnabled() {
|
|
768
|
-
const value = process.env.LETMECODE_DEBUG_ANTIGRAVITY;
|
|
769
|
-
return value === "1" || value === "true" || value === "yes";
|
|
770
|
-
}
|
|
771
|
-
function isAntigravityRawDebugEnabled() {
|
|
772
|
-
const value = process.env.LETMECODE_DEBUG_ANTIGRAVITY_RAW;
|
|
773
|
-
return value === "1" || value === "true" || value === "yes";
|
|
774
|
-
}
|
|
775
|
-
async function writeAntigravityDebugEvent(event, data) {
|
|
776
|
-
if (!isAntigravityDebugEnabled()) {
|
|
777
|
-
return;
|
|
778
|
-
}
|
|
779
|
-
const line = JSON.stringify({
|
|
780
|
-
timestamp: new Date().toISOString(),
|
|
781
|
-
event,
|
|
782
|
-
data: redactDebugValue(data)
|
|
783
|
-
});
|
|
784
|
-
try {
|
|
785
|
-
await fs.promises.mkdir(path.dirname(ANTIGRAVITY_DEBUG_LOG_PATH), {
|
|
786
|
-
recursive: true
|
|
377
|
+
return [{
|
|
378
|
+
limitId: bucket.bucketId,
|
|
379
|
+
modelIds,
|
|
380
|
+
remainingFraction: bucket.remainingFraction,
|
|
381
|
+
resetAt,
|
|
382
|
+
...window
|
|
383
|
+
}];
|
|
787
384
|
});
|
|
788
|
-
|
|
789
|
-
}
|
|
790
|
-
catch {
|
|
791
|
-
// Debug logging must never break provider stats collection.
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
function redactDebugValue(value, key = "") {
|
|
795
|
-
if (isSensitiveDebugKey(key)) {
|
|
796
|
-
return "[redacted]";
|
|
797
|
-
}
|
|
798
|
-
if (Array.isArray(value)) {
|
|
799
|
-
return value.map((item) => redactDebugValue(item));
|
|
800
|
-
}
|
|
801
|
-
if (!value || typeof value !== "object") {
|
|
802
|
-
return value;
|
|
803
|
-
}
|
|
804
|
-
return Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [
|
|
805
|
-
entryKey,
|
|
806
|
-
redactDebugValue(entryValue, entryKey)
|
|
807
|
-
]));
|
|
385
|
+
});
|
|
808
386
|
}
|
|
809
|
-
function
|
|
810
|
-
return
|
|
387
|
+
function resolveQuotaGroupModelIds(text) {
|
|
388
|
+
return (QUOTA_MODEL_GROUPS.find(({ pattern }) => pattern.test(text.toLowerCase()))?.models ?? []);
|
|
811
389
|
}
|
|
812
390
|
function numberOrZero(value) {
|
|
813
391
|
return typeof value === "number" && Number.isFinite(value)
|
|
@@ -820,8 +398,8 @@ function clampPercent(value) {
|
|
|
820
398
|
}
|
|
821
399
|
return Math.min(100, Math.max(0, value));
|
|
822
400
|
}
|
|
823
|
-
async function readAntigravityUsageCache(
|
|
824
|
-
const sessionsRoot = path.join(
|
|
401
|
+
async function readAntigravityUsageCache() {
|
|
402
|
+
const sessionsRoot = path.join(ANTIGRAVITY_CACHE_ROOT, "sessions");
|
|
825
403
|
const records = [];
|
|
826
404
|
for await (const filePath of walkJsonlFiles(sessionsRoot)) {
|
|
827
405
|
const stream = fs.createReadStream(filePath, { encoding: "utf8" });
|
|
@@ -875,9 +453,6 @@ function usageRecordFromCacheEntry(value) {
|
|
|
875
453
|
reasoning: numberOrZero(entry.reasoning)
|
|
876
454
|
};
|
|
877
455
|
}
|
|
878
|
-
function getAntigravityCacheRoot() {
|
|
879
|
-
return path.join(os.homedir(), ".config", "tokscale", "antigravity-cache");
|
|
880
|
-
}
|
|
881
456
|
async function* walkJsonlFiles(directory) {
|
|
882
457
|
let entries;
|
|
883
458
|
try {
|