letmecode 0.1.20 → 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.
- package/README.md +10 -27
- package/ink-app/dist/index.js +68 -35
- package/ink-app/dist/providers/antigravity/models.js +46 -0
- package/ink-app/dist/providers/antigravity/provider.js +288 -0
- package/ink-app/dist/providers/antigravity/quota-parser.js +49 -0
- package/ink-app/dist/providers/antigravity/rpc/client.js +54 -0
- package/ink-app/dist/providers/antigravity/rpc/discovery.js +84 -0
- package/ink-app/dist/providers/antigravity/rpc/quota.js +25 -0
- package/ink-app/dist/providers/antigravity/rpc/usage.js +80 -0
- package/ink-app/dist/providers/antigravity/types.js +1 -0
- package/ink-app/dist/providers/antigravity/usage-parse.js +23 -0
- package/ink-app/dist/providers/antigravity.js +2 -537
- package/ink-app/dist/providers/claude.js +71 -152
- package/ink-app/dist/providers/contract.js +5 -2
- package/ink-app/dist/providers/copilot/models.js +55 -0
- package/ink-app/dist/providers/copilot/otel/configure.js +134 -0
- package/ink-app/dist/providers/copilot/otel/discover.js +94 -0
- package/ink-app/dist/providers/copilot/otel/parse.js +228 -0
- package/ink-app/dist/providers/copilot/provider.js +259 -0
- package/ink-app/dist/providers/copilot/quota.js +257 -0
- package/ink-app/dist/providers/copilot/usage/aggregate.js +84 -0
- package/ink-app/dist/providers/copilot.js +4 -373
- package/ink-app/dist/providers/index.js +1 -1
- package/ink-app/dist/reporting.js +7 -1
- package/package.json +1 -1
|
@@ -1,537 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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";
|