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.
@@ -0,0 +1,49 @@
1
+ const QUOTA_WINDOWS = {
2
+ "5h": {
3
+ scope: "primary",
4
+ windowMinutes: 300
5
+ },
6
+ weekly: {
7
+ scope: "secondary",
8
+ windowMinutes: 10080
9
+ }
10
+ };
11
+ export function parseAntigravityQuotaEntries(groups) {
12
+ return groups.flatMap((group) => {
13
+ const modelScope = resolveQuotaGroupScope(`${group.displayName ?? ""} ${group.description ?? ""}`);
14
+ if (!modelScope) {
15
+ return [];
16
+ }
17
+ return (group.buckets ?? []).flatMap((bucket) => {
18
+ const window = bucket.window
19
+ ? QUOTA_WINDOWS[bucket.window]
20
+ : undefined;
21
+ const resetAt = Date.parse(bucket.resetTime ?? "");
22
+ if (!bucket.bucketId ||
23
+ window === undefined ||
24
+ !Number.isFinite(resetAt) ||
25
+ typeof bucket.remainingFraction !== "number" ||
26
+ bucket.remainingFraction < 0 ||
27
+ bucket.remainingFraction > 1) {
28
+ return [];
29
+ }
30
+ return [{
31
+ limitId: bucket.bucketId,
32
+ modelScope,
33
+ remainingFraction: bucket.remainingFraction,
34
+ resetAt,
35
+ ...window
36
+ }];
37
+ });
38
+ });
39
+ }
40
+ function resolveQuotaGroupScope(text) {
41
+ const normalized = text.toLowerCase();
42
+ if (/gemini/.test(normalized)) {
43
+ return "gemini";
44
+ }
45
+ if (/claude|gpt/.test(normalized)) {
46
+ return "third-party";
47
+ }
48
+ return null;
49
+ }
@@ -0,0 +1,54 @@
1
+ import https from "node:https";
2
+ export const ANTIGRAVITY_RPC_PATHS = {
3
+ quotaSummary: "/exa.language_server_pb.LanguageServerService/RetrieveUserQuotaSummary",
4
+ userStatus: "/exa.language_server_pb.LanguageServerService/GetUserStatus",
5
+ trajectories: "/exa.language_server_pb.LanguageServerService/GetAllCascadeTrajectories",
6
+ trajectorySteps: "/exa.language_server_pb.LanguageServerService/GetCascadeTrajectorySteps"
7
+ };
8
+ export async function rpc(server, endpoint, payload) {
9
+ const body = JSON.stringify(payload ?? {});
10
+ return new Promise((resolve, reject) => {
11
+ const request = https.request({
12
+ hostname: "127.0.0.1",
13
+ port: server.port,
14
+ path: endpoint,
15
+ method: "POST",
16
+ rejectUnauthorized: false,
17
+ timeout: 5000,
18
+ headers: {
19
+ "X-Codeium-Csrf-Token": server.csrfToken,
20
+ "Content-Type": "application/json",
21
+ "Connect-Protocol-Version": "1",
22
+ "Content-Length": Buffer.byteLength(body)
23
+ }
24
+ }, (response) => {
25
+ const chunks = [];
26
+ response.on("data", (chunk) => {
27
+ chunks.push(chunk);
28
+ });
29
+ response.on("end", () => {
30
+ const responseBody = Buffer.concat(chunks).toString("utf8");
31
+ if (response.statusCode === undefined ||
32
+ response.statusCode >= 300) {
33
+ reject(new Error(`Antigravity RPC ${endpoint} failed with status ${response.statusCode ?? "unknown"}.`));
34
+ return;
35
+ }
36
+ if (!responseBody) {
37
+ resolve({});
38
+ return;
39
+ }
40
+ try {
41
+ resolve(JSON.parse(responseBody));
42
+ }
43
+ catch (error) {
44
+ reject(new Error(`Antigravity RPC ${endpoint} returned invalid JSON.`, { cause: error }));
45
+ }
46
+ });
47
+ });
48
+ request.on("timeout", () => {
49
+ request.destroy(new Error(`Antigravity RPC ${endpoint} timed out.`));
50
+ });
51
+ request.on("error", reject);
52
+ request.end(body);
53
+ });
54
+ }
@@ -0,0 +1,84 @@
1
+ import fs from "node:fs";
2
+ import { execFile } from "node:child_process";
3
+ import { promisify } from "node:util";
4
+ import { ANTIGRAVITY_RPC_PATHS, rpc } from "./client.js";
5
+ const execFileAsync = promisify(execFile);
6
+ export async function findAntigravityLocalServer() {
7
+ const processes = await findAntigravityProcesses();
8
+ if (processes.length === 0) {
9
+ return null;
10
+ }
11
+ const portsByPid = await readListeningPortsByPid();
12
+ for (const process of processes) {
13
+ for (const port of portsByPid.get(process.pid) ?? []) {
14
+ const server = {
15
+ port,
16
+ csrfToken: process.csrfToken
17
+ };
18
+ try {
19
+ const quotaSummary = await rpc(server, ANTIGRAVITY_RPC_PATHS.quotaSummary);
20
+ return { server, quotaSummary };
21
+ }
22
+ catch {
23
+ // This port does not expose the expected Antigravity RPC API.
24
+ }
25
+ }
26
+ }
27
+ return null;
28
+ }
29
+ async function findAntigravityProcesses() {
30
+ const entries = await fs.promises
31
+ .readdir("/proc")
32
+ .catch(() => []);
33
+ const processes = [];
34
+ for (const entry of entries) {
35
+ if (!/^\d+$/.test(entry)) {
36
+ continue;
37
+ }
38
+ const args = await fs.promises
39
+ .readFile(`/proc/${entry}/cmdline`, "utf8")
40
+ .then((value) => value.split("\0").filter(Boolean))
41
+ .catch(() => []);
42
+ const command = args.join(" ").toLowerCase();
43
+ if (!command.includes("antigravity") ||
44
+ !/(language|extension)[_-]server/.test(command)) {
45
+ continue;
46
+ }
47
+ const inlineToken = args.find((arg) => arg.startsWith("--csrf_token="));
48
+ const tokenIndex = args.indexOf("--csrf_token");
49
+ const csrfToken = inlineToken?.slice("--csrf_token=".length) ??
50
+ (tokenIndex >= 0 ? args[tokenIndex + 1] : undefined);
51
+ if (!csrfToken) {
52
+ continue;
53
+ }
54
+ processes.push({
55
+ pid: Number(entry),
56
+ csrfToken
57
+ });
58
+ }
59
+ return processes;
60
+ }
61
+ async function readListeningPortsByPid() {
62
+ const { stdout } = await execFileAsync("ss", ["-H", "-ltnp"], {
63
+ encoding: "utf8",
64
+ timeout: 5000
65
+ });
66
+ const portsByPid = new Map();
67
+ for (const line of stdout.split("\n")) {
68
+ const loopbackPorts = [
69
+ ...line.matchAll(/(?:127\.0\.0\.1|\[::1\]):(\d+)/g)
70
+ ].map((match) => Number(match[1]));
71
+ if (loopbackPorts.length === 0) {
72
+ continue;
73
+ }
74
+ for (const pidMatch of line.matchAll(/pid=(\d+),/g)) {
75
+ const pid = Number(pidMatch[1]);
76
+ const ports = portsByPid.get(pid) ?? new Set();
77
+ for (const port of loopbackPorts) {
78
+ ports.add(port);
79
+ }
80
+ portsByPid.set(pid, ports);
81
+ }
82
+ }
83
+ return new Map([...portsByPid.entries()].map(([pid, ports]) => [pid, [...ports]]));
84
+ }
@@ -0,0 +1,25 @@
1
+ import { ANTIGRAVITY_RPC_PATHS, rpc } from "./client.js";
2
+ import { asRecord } from "../../limits.js";
3
+ const METADATA = {
4
+ ideName: "antigravity",
5
+ extensionName: "antigravity",
6
+ ideVersion: "unknown",
7
+ locale: "en"
8
+ };
9
+ /**
10
+ * Pulls the raw quota groups out of a RetrieveUserQuotaSummary payload.
11
+ * Validation and normalization are the parser's responsibility, so this only
12
+ * unwraps the envelope.
13
+ */
14
+ export function extractQuotaGroups(payload) {
15
+ const response = asRecord(asRecord(payload)?.response);
16
+ const groups = response?.groups;
17
+ return Array.isArray(groups) ? groups : [];
18
+ }
19
+ export async function fetchAntigravityUserStatus(server) {
20
+ const status = await rpc(server, ANTIGRAVITY_RPC_PATHS.userStatus, { metadata: METADATA }).catch(() => null);
21
+ return {
22
+ email: status?.userStatus?.email ?? null,
23
+ planName: status?.userStatus?.planStatus?.planInfo?.planName ?? null
24
+ };
25
+ }
@@ -0,0 +1,80 @@
1
+ import { ANTIGRAVITY_RPC_PATHS, rpc } from "./client.js";
2
+ const TRAJECTORY_METADATA = {
3
+ ideName: "antigravity",
4
+ extensionName: "antigravity"
5
+ };
6
+ const STEPS_CONCURRENCY = 8;
7
+ /**
8
+ * Reconstructs per-response model usage from the Antigravity local language
9
+ * server.
10
+ *
11
+ * Every billable model call is recorded on a trajectory step as
12
+ * `metadata.modelUsage`, identified by `responseId`, and usage is spread across
13
+ * several step types (planner responses, checkpoints, ...). This reads every
14
+ * step rather than filtering by type. `GetCascadeTrajectorySteps` returns the
15
+ * full step list for a cascade in one response, so each cascade is fetched
16
+ * once, and cascades are fetched concurrently. De-duplication of responses is
17
+ * the caller's responsibility.
18
+ */
19
+ export async function fetchAntigravityUsageRpcData(server) {
20
+ const trajectories = await rpc(server, ANTIGRAVITY_RPC_PATHS.trajectories, {
21
+ metadata: TRAJECTORY_METADATA
22
+ });
23
+ const cascades = Object.entries(trajectories.trajectorySummaries ?? {})
24
+ .map(([cascadeId, summary]) => ({
25
+ cascadeId,
26
+ stepCount: summary.stepCount ?? 0
27
+ }))
28
+ .filter((cascade) => cascade.stepCount > 0);
29
+ const perCascade = await mapWithConcurrency(cascades, STEPS_CONCURRENCY, (cascade) => fetchCascadeUsage(server, cascade.cascadeId, cascade.stepCount));
30
+ return perCascade.flat();
31
+ }
32
+ async function fetchCascadeUsage(server, cascadeId, stepCount) {
33
+ const response = await rpc(server, ANTIGRAVITY_RPC_PATHS.trajectorySteps, {
34
+ cascadeId,
35
+ startIndex: 0,
36
+ endIndex: stepCount
37
+ });
38
+ const usage = [];
39
+ for (const step of response.steps ?? []) {
40
+ const modelUsage = step.metadata?.modelUsage;
41
+ const created = step.metadata?.createdAt;
42
+ const responseId = modelUsage?.responseId;
43
+ const model = modelUsage?.model;
44
+ if (!modelUsage || !created || !responseId || !model) {
45
+ continue;
46
+ }
47
+ usage.push({
48
+ responseId,
49
+ cascadeId,
50
+ created,
51
+ model,
52
+ input: toTokenCount(modelUsage.inputTokens),
53
+ output: toTokenCount(modelUsage.outputTokens),
54
+ cacheRead: toTokenCount(modelUsage.cacheReadTokens),
55
+ reasoning: toTokenCount(modelUsage.thinkingOutputTokens)
56
+ });
57
+ }
58
+ return usage;
59
+ }
60
+ function toTokenCount(value) {
61
+ const parsed = typeof value === "string" ? Number(value) : value;
62
+ return typeof parsed === "number" && Number.isFinite(parsed) ? parsed : 0;
63
+ }
64
+ async function mapWithConcurrency(items, limit, task) {
65
+ const results = new Array(items.length);
66
+ let cursor = 0;
67
+ const workerCount = Math.max(1, Math.min(limit, items.length));
68
+ const workers = Array.from({ length: workerCount }, async () => {
69
+ while (true) {
70
+ const index = cursor;
71
+ cursor += 1;
72
+ if (index >= items.length) {
73
+ return;
74
+ }
75
+ results[index] = await task(items[index]);
76
+ }
77
+ });
78
+ await Promise.all(workers);
79
+ return results;
80
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import { fetchAntigravityUsageRpcData } from "./rpc/usage.js";
2
+ export async function collectUsageFromLocalRpc(server, options) {
3
+ const usage = await fetchAntigravityUsageRpcData(server);
4
+ options?.traceLogger?.log(`[Antigravity] usage: ${JSON.stringify(usage, null, 2)}`);
5
+ return usage.flatMap((entry) => {
6
+ const timestamp = Date.parse(entry.created);
7
+ if (!Number.isFinite(timestamp)) {
8
+ return [];
9
+ }
10
+ return [{
11
+ type: "usage",
12
+ sessionId: entry.cascadeId,
13
+ responseId: entry.responseId,
14
+ timestamp,
15
+ modelId: entry.model,
16
+ input: entry.input,
17
+ cacheRead: entry.cacheRead,
18
+ cacheWrite: 0,
19
+ output: entry.output,
20
+ reasoning: entry.reasoning
21
+ }];
22
+ });
23
+ }