@openlimiter/core 2.0.3 → 2.1.0

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.
Files changed (67) hide show
  1. package/dist/acquire/cadence.d.ts.map +1 -1
  2. package/dist/acquire/cadence.js +3 -2
  3. package/dist/acquire/cadence.js.map +1 -1
  4. package/dist/acquire/codex-app-server.d.ts +29 -0
  5. package/dist/acquire/codex-app-server.d.ts.map +1 -0
  6. package/dist/acquire/codex-app-server.js +218 -0
  7. package/dist/acquire/codex-app-server.js.map +1 -0
  8. package/dist/acquire/credentials.d.ts +6 -21
  9. package/dist/acquire/credentials.d.ts.map +1 -1
  10. package/dist/acquire/credentials.js +17 -46
  11. package/dist/acquire/credentials.js.map +1 -1
  12. package/dist/acquire/identity.d.ts +1 -1
  13. package/dist/acquire/identity.js +1 -1
  14. package/dist/acquire/index.d.ts +1 -1
  15. package/dist/acquire/index.d.ts.map +1 -1
  16. package/dist/acquire/index.js +1 -1
  17. package/dist/acquire/index.js.map +1 -1
  18. package/dist/acquire/providers.d.ts +8 -15
  19. package/dist/acquire/providers.d.ts.map +1 -1
  20. package/dist/acquire/providers.js +33 -36
  21. package/dist/acquire/providers.js.map +1 -1
  22. package/dist/acquire/runner.d.ts +4 -20
  23. package/dist/acquire/runner.d.ts.map +1 -1
  24. package/dist/acquire/runner.js +27 -156
  25. package/dist/acquire/runner.js.map +1 -1
  26. package/dist/acquire/transport.d.ts +26 -20
  27. package/dist/acquire/transport.d.ts.map +1 -1
  28. package/dist/acquire/transport.js +6 -19
  29. package/dist/acquire/transport.js.map +1 -1
  30. package/dist/cache.d.ts +19 -4
  31. package/dist/cache.d.ts.map +1 -1
  32. package/dist/cache.js +159 -68
  33. package/dist/cache.js.map +1 -1
  34. package/dist/collection.d.ts.map +1 -1
  35. package/dist/collection.js +18 -9
  36. package/dist/collection.js.map +1 -1
  37. package/dist/contracts/claude-presentation.json +49 -0
  38. package/dist/contracts/wire.d.ts.map +1 -1
  39. package/dist/contracts/wire.js +11 -3
  40. package/dist/contracts/wire.js.map +1 -1
  41. package/dist/data-rules.d.ts +2 -0
  42. package/dist/data-rules.d.ts.map +1 -1
  43. package/dist/data-rules.js +34 -4
  44. package/dist/data-rules.js.map +1 -1
  45. package/dist/index.d.ts +1 -0
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +1 -0
  48. package/dist/index.js.map +1 -1
  49. package/dist/merge.d.ts.map +1 -1
  50. package/dist/merge.js +16 -4
  51. package/dist/merge.js.map +1 -1
  52. package/dist/policy.d.ts.map +1 -1
  53. package/dist/policy.js +5 -1
  54. package/dist/policy.js.map +1 -1
  55. package/dist/provider-presentation.d.ts +52 -0
  56. package/dist/provider-presentation.d.ts.map +1 -0
  57. package/dist/provider-presentation.js +264 -0
  58. package/dist/provider-presentation.js.map +1 -0
  59. package/dist/types.d.ts +2 -1
  60. package/dist/types.d.ts.map +1 -1
  61. package/dist/types.js +1 -0
  62. package/dist/types.js.map +1 -1
  63. package/package.json +1 -1
  64. package/dist/acquire/antigravity-probe.d.ts +0 -142
  65. package/dist/acquire/antigravity-probe.d.ts.map +0 -1
  66. package/dist/acquire/antigravity-probe.js +0 -655
  67. package/dist/acquire/antigravity-probe.js.map +0 -1
@@ -1,655 +0,0 @@
1
- /**
2
- * The Antigravity loopback probe.
3
- *
4
- * Antigravity quota is served live by a running agy process on a loopback port.
5
- * It speaks Connect protocol (POST with Connect-Protocol-Version: 1) and
6
- * answers RetrieveUserQuotaSummary over loopback HTTPS with a self-signed
7
- * certificate. When an agy process is alive on this machine, probing this
8
- * loopback endpoint gives an honest, authoritative reading with zero cloud
9
- * impersonation.
10
- *
11
- * When no agy process is running, this probe reports that fact plainly and
12
- * never attempts to spawn agy.
13
- */
14
- import { execFile } from "node:child_process";
15
- import { realpath, stat } from "node:fs/promises";
16
- import https from "node:https";
17
- import http from "node:http";
18
- import path from "node:path";
19
- import { antigravityMeter } from "./antigravity-meters.js";
20
- import { OPENLIMITER_USER_AGENT } from "./identity.js";
21
- import { windowsSystemTool } from "../windows-system-tool.js";
22
- export const AGY_QUOTA_PATH = "/exa.language_server_pb.LanguageServerService/RetrieveUserQuotaSummary";
23
- export const AGY_NOT_RUNNING_SENTENCE = "Open Antigravity once to refresh";
24
- export const AGY_PROBE_TIMEOUT_MILLISECONDS = 3_000;
25
- export const MAX_AGY_RESPONSE_BYTES = 64 * 1024; // 64 KB
26
- export const TOTAL_AGY_PROBE_DEADLINE_MS = 5_000; // 5 seconds
27
- export const ANTIGRAVITY_PROBE_LABELS = {
28
- credentialOrigin: "official-local-tool",
29
- dataInterfaceStatus: "internal-endpoint",
30
- automationRisk: "high",
31
- verification: "UNVERIFIED"
32
- };
33
- /**
34
- * Parse the Connect protocol RetrieveUserQuotaSummary response.
35
- *
36
- * Preserves Gemini and third party identities through the shared desktop mapping.
37
- * Percentage is rounded to 1 decimal place: (1 - remainingFraction) * 100.
38
- */
39
- export function parseAgyQuotaSummary(payload, now) {
40
- if (typeof payload !== "object" || payload === null)
41
- return null;
42
- const root = payload;
43
- const unwrapped = typeof root["response"] === "object" && root["response"] !== null
44
- ? root["response"]
45
- : root;
46
- const groups = unwrapped["groups"];
47
- if (!Array.isArray(groups))
48
- return null;
49
- const meters = [];
50
- for (const entry of groups) {
51
- if (typeof entry !== "object" || entry === null)
52
- continue;
53
- const buckets = entry["buckets"];
54
- if (!Array.isArray(buckets))
55
- continue;
56
- for (const item of buckets) {
57
- if (typeof item !== "object" || item === null)
58
- continue;
59
- const b = item;
60
- const bucketId = typeof b["bucketId"] === "string" ? b["bucketId"] :
61
- typeof b["poolPrefix"] === "string" ? b["poolPrefix"] : "";
62
- const windowName = typeof b["window"] === "string" ? b["window"].toLowerCase() : "";
63
- const mapped = antigravityMeter(bucketId, windowName);
64
- if (mapped === null)
65
- continue;
66
- const { meter: meterCode, durationSeconds } = mapped;
67
- const remaining = typeof b["remainingFraction"] === "number" ? b["remainingFraction"] : null;
68
- if (remaining === null || Number.isNaN(remaining))
69
- continue;
70
- const fraction = Math.max(0, Math.min(1, remaining));
71
- const value = Math.round(Math.max(0, Math.min(100, (1 - fraction) * 100)) * 10) / 10;
72
- const resetTime = typeof b["resetTime"] === "string" ? b["resetTime"] : null;
73
- const resetSeconds = b["resetsInSeconds"];
74
- const resetAt = resetTime !== null && !Number.isNaN(Date.parse(resetTime))
75
- ? new Date(resetTime).toISOString()
76
- : typeof resetSeconds === "number" && Number.isFinite(resetSeconds) && resetSeconds >= 0 && resetSeconds <= 604800
77
- ? new Date(Date.parse(now) + resetSeconds * 1000).toISOString() : undefined;
78
- const expiresAt = new Date(new Date(now).getTime() + 300_000).toISOString();
79
- meters.push({
80
- provider: "ANTIGRAVITY",
81
- meter: meterCode,
82
- value,
83
- unit: "PERCENT",
84
- window: { kind: "rolling", durationSeconds },
85
- resetAt,
86
- source: "internal_payload",
87
- precision: "estimated",
88
- observedAt: now,
89
- expiresAt,
90
- labels: ANTIGRAVITY_PROBE_LABELS
91
- });
92
- }
93
- }
94
- return meters.length > 0 ? meters : null;
95
- }
96
- function execFilePromise(executable, args, timeout) {
97
- return new Promise((resolve) => {
98
- execFile(executable, [...args], {
99
- timeout,
100
- windowsHide: true,
101
- ...(process.platform === "win32" && path.win32.isAbsolute(executable)
102
- ? { cwd: path.win32.dirname(executable) }
103
- : {})
104
- }, (error, stdout) => {
105
- if (error) {
106
- resolve({ ok: false });
107
- }
108
- else {
109
- resolve({ ok: true, stdout: stdout.toString() });
110
- }
111
- });
112
- });
113
- }
114
- /**
115
- * Resolve a pid to the absolute path of the executable running it, the one
116
- * piece of proof `isTrustedAgyExecutable` actually checks.
117
- *
118
- * Windows' own `tasklist` names a process but never its path, so a caller
119
- * stuck on that fallback (CIM itself already carries a path, see
120
- * `enumerateAgyListeningPorts`) has nothing to verify a candidate against but
121
- * the bare string "agy.exe", which is exactly the name an impostor process
122
- * picks for itself. This asks Windows the same question CIM already answers,
123
- * for one pid at a time; POSIX reads `/proc/<pid>/exe`'s own symlink on
124
- * Linux, where the kernel itself is the source of truth, and asks `ps` on
125
- * every other POSIX platform. A pid whose executable cannot be read this way
126
- * answers null, which every caller here treats as "cannot be trusted", never
127
- * as "trust it anyway".
128
- */
129
- export async function resolveAgyExecutablePath(pid, platform = process.platform, runCommand = execFilePromise, env = process.env, resolvePath = realpath) {
130
- if (!/^\d+$/u.test(pid))
131
- return null;
132
- if (platform === "win32") {
133
- const script = "$ErrorActionPreference='SilentlyContinue';" +
134
- `(Get-CimInstance Win32_Process -Filter "ProcessId=${pid}").ExecutablePath`;
135
- const result = await runCommand(windowsSystemTool("WindowsPowerShell", "v1.0", "powershell.exe", env), ["-NoProfile", "-NonInteractive", "-Command", script], AGY_PROBE_TIMEOUT_MILLISECONDS);
136
- if (!result.ok)
137
- return null;
138
- const trimmed = result.stdout.trim();
139
- return trimmed.length > 0 ? trimmed : null;
140
- }
141
- if (platform === "linux") {
142
- try {
143
- return await resolvePath(`/proc/${pid}/exe`);
144
- }
145
- catch {
146
- return null;
147
- }
148
- }
149
- const result = await runCommand("ps", ["-o", "comm=", "-p", pid], AGY_PROBE_TIMEOUT_MILLISECONDS);
150
- if (!result.ok)
151
- return null;
152
- const trimmed = result.stdout.trim();
153
- if (trimmed.length === 0)
154
- return null;
155
- if (path.posix.isAbsolute(trimmed)) {
156
- try {
157
- return await resolvePath(trimmed);
158
- }
159
- catch {
160
- return null;
161
- }
162
- }
163
- const args = await runCommand("ps", ["-o", "args=", "-p", pid], AGY_PROBE_TIMEOUT_MILLISECONDS);
164
- if (!args.ok)
165
- return null;
166
- const firstToken = args.stdout.trim().split(/\s+/u)[0];
167
- if (firstToken === undefined || firstToken.length === 0)
168
- return null;
169
- if (path.posix.isAbsolute(firstToken)) {
170
- try {
171
- return await resolvePath(firstToken);
172
- }
173
- catch {
174
- return null;
175
- }
176
- }
177
- for (const directory of (env["PATH"] ?? "").split(":")) {
178
- if (directory.length === 0)
179
- continue;
180
- try {
181
- return await resolvePath(path.posix.join(directory, firstToken));
182
- }
183
- catch {
184
- /* Try the next PATH entry. */
185
- }
186
- }
187
- return null;
188
- }
189
- /**
190
- * Extract loopback port from an address string (e.g. "127.0.0.1:57737" or "[::1]:57737").
191
- */
192
- export function parseLoopbackPort(address) {
193
- const lastColon = address.lastIndexOf(":");
194
- if (lastColon === -1)
195
- return null;
196
- let host = address.slice(0, lastColon);
197
- const portStr = address.slice(lastColon + 1);
198
- if (host.startsWith("[") && host.endsWith("]")) {
199
- host = host.slice(1, -1);
200
- }
201
- if (host !== "127.0.0.1" && host !== "::1") {
202
- return null;
203
- }
204
- const port = Number.parseInt(portStr, 10);
205
- if (Number.isNaN(port) || port <= 0 || port > 65535) {
206
- return null;
207
- }
208
- return port;
209
- }
210
- /**
211
- * Language-agnostic Windows netstat parser.
212
- *
213
- * Windows netstat lines have 5 whitespace-delimited tokens:
214
- * Protocol, Local Address, Foreign Address, State, PID.
215
- * A listening socket always has a wildcard foreign address (0.0.0.0:0, [::]:0, *:*).
216
- * Matching this wildcard avoids relying on English "LISTENING" or "TCP".
217
- */
218
- export function parseNetstatPorts(report, pids) {
219
- const pidSet = pids instanceof Set ? pids : new Set(pids);
220
- const ports = [];
221
- for (const line of report.split(/\r?\n/)) {
222
- const fields = line.trim().split(/\s+/);
223
- if (fields.length !== 5) {
224
- continue;
225
- }
226
- const owner = fields[4];
227
- if (!pidSet.has(owner)) {
228
- continue;
229
- }
230
- const foreign = fields[2];
231
- if (foreign !== "0.0.0.0:0" && foreign !== "[::]:0" && foreign !== "*:*") {
232
- continue;
233
- }
234
- const localAddress = fields[1];
235
- if (localAddress === undefined) {
236
- continue;
237
- }
238
- const port = parseLoopbackPort(localAddress);
239
- if (port !== null && !ports.includes(port)) {
240
- ports.push(port);
241
- }
242
- }
243
- return ports;
244
- }
245
- /**
246
- * Parse output from lsof, handling both standard tabular output and "-F pn" output.
247
- */
248
- export function parseLsofOutput(output) {
249
- const results = [];
250
- let currentPid = null;
251
- for (const line of output.split(/\r?\n/)) {
252
- const trimmed = line.trim();
253
- if (trimmed.startsWith("p")) {
254
- currentPid = trimmed.slice(1).trim();
255
- continue;
256
- }
257
- if (trimmed.startsWith("n")) {
258
- if (currentPid) {
259
- const port = parseLoopbackPort(trimmed.slice(1).trim());
260
- if (port !== null) {
261
- results.push({ pid: currentPid, port });
262
- }
263
- }
264
- continue;
265
- }
266
- const parts = trimmed.split(/\s+/);
267
- if (parts.length >= 8) {
268
- const pid = parts[1];
269
- if (pid === undefined) {
270
- continue;
271
- }
272
- for (const part of parts) {
273
- const port = parseLoopbackPort(part);
274
- if (port !== null) {
275
- results.push({ pid, port });
276
- break;
277
- }
278
- }
279
- }
280
- }
281
- return results;
282
- }
283
- /**
284
- * Canonical install directories, never broad download or application data roots.
285
- */
286
- export function getAgyInstallRoots(platform = process.platform, env = process.env) {
287
- const roots = [];
288
- const hostPath = platform === "win32" ? path.win32 : path.posix;
289
- if (platform === "win32") {
290
- const keys = ["ProgramFiles", "ProgramFiles(x86)", "ProgramW6432"];
291
- for (const key of keys) {
292
- const val = env[key];
293
- if (val && val.trim().length > 0) {
294
- roots.push(hostPath.join(val.trim(), "Antigravity"));
295
- }
296
- }
297
- const local = env["LOCALAPPDATA"];
298
- if (local)
299
- roots.push(hostPath.join(local, "Programs", "Antigravity"));
300
- const userProfile = env["USERPROFILE"];
301
- if (userProfile && userProfile.trim().length > 0) {
302
- roots.push(hostPath.join(userProfile.trim(), "AppData", "Local", "Programs", "Antigravity"));
303
- }
304
- }
305
- else {
306
- for (const fixed of ["/usr/bin", "/usr/local/bin", "/opt/google", "/Applications/Antigravity.app/Contents/MacOS"]) {
307
- roots.push(fixed);
308
- }
309
- const home = env["HOME"];
310
- if (home && home.trim().length > 0) {
311
- roots.push(path.join(home.trim(), ".local", "bin"));
312
- roots.push(path.join(home.trim(), "Applications", "Antigravity.app", "Contents", "MacOS"));
313
- }
314
- }
315
- return roots;
316
- }
317
- /**
318
- * Validate that an executable path is trusted:
319
- * 1. Absolute path, no parent traversal ("..")
320
- * 2. Named agy.exe (Windows) or agy (non-Windows)
321
- * 3. Matches the executable directly inside a canonical install directory
322
- */
323
- export function isTrustedAgyExecutable(executablePath, platform = process.platform, roots = getAgyInstallRoots(platform)) {
324
- if (!executablePath || typeof executablePath !== "string")
325
- return false;
326
- const p = platform === "win32" ? path.win32 : path.posix;
327
- const normalized = p.normalize(executablePath);
328
- if (executablePath.includes("..") || normalized.split(p.sep).includes("..")) {
329
- return false;
330
- }
331
- if (!p.isAbsolute(executablePath)) {
332
- return false;
333
- }
334
- const baseName = p.basename(normalized);
335
- const expectedName = platform === "win32" ? "agy.exe" : "agy";
336
- if (platform === "win32") {
337
- if (baseName.toLowerCase() !== expectedName.toLowerCase()) {
338
- return false;
339
- }
340
- }
341
- else {
342
- if (baseName !== expectedName) {
343
- return false;
344
- }
345
- }
346
- const normPath = platform === "win32" ? normalized.toLowerCase() : normalized;
347
- return roots.some((root) => {
348
- const normRoot = platform === "win32" ? p.normalize(root).toLowerCase() : p.normalize(root);
349
- const relative = p.relative(normRoot, normPath);
350
- return relative.length > 0 &&
351
- !relative.startsWith(".." + p.sep) &&
352
- relative !== ".." &&
353
- !p.isAbsolute(relative);
354
- });
355
- }
356
- /**
357
- * Enumerate listening loopback ports belonging to running agy processes.
358
- */
359
- export async function enumerateAgyListeningPorts(options) {
360
- const platform = options?.platform ?? process.platform;
361
- const runner = options?.runCommand ?? execFilePromise;
362
- if (platform === "win32") {
363
- const roots = getAgyInstallRoots("win32", options?.env ?? process.env);
364
- const trustedPids = new Set();
365
- const cimScript = "$ErrorActionPreference='SilentlyContinue';" +
366
- "$me=[System.Security.Principal.WindowsIdentity]::GetCurrent().Name;" +
367
- "Get-CimInstance Win32_Process -Filter \"Name='agy.exe'\" | ForEach-Object {" +
368
- "$owner=(Invoke-CimMethod -InputObject $_ -MethodName GetOwner);" +
369
- "if ($owner -and $owner.User -and (\"$($owner.Domain)\\$($owner.User)\" -eq $me)) {" +
370
- "\"$($_.ProcessId)|$($_.ExecutablePath)\" } }";
371
- const cimRes = await runner(windowsSystemTool("WindowsPowerShell", "v1.0", "powershell.exe", options?.env ?? process.env), ["-NoProfile", "-NonInteractive", "-Command", cimScript], AGY_PROBE_TIMEOUT_MILLISECONDS);
372
- if (cimRes.ok && cimRes.stdout.trim().length > 0) {
373
- for (const line of cimRes.stdout.split(/\r?\n/)) {
374
- const parts = line.trim().split("|");
375
- if (parts.length >= 2) {
376
- const rawPid = parts[0];
377
- if (rawPid === undefined) {
378
- continue;
379
- }
380
- const pid = rawPid.trim();
381
- const exePath = parts.slice(1).join("|").trim();
382
- if (isTrustedAgyExecutable(exePath, "win32", roots)) {
383
- trustedPids.add(pid);
384
- }
385
- }
386
- }
387
- }
388
- else {
389
- // A name and executable resolver cannot establish a process owner.
390
- // Fail closed if the owner filtered enumeration is unavailable.
391
- return [];
392
- }
393
- if (trustedPids.size === 0)
394
- return [];
395
- const netstatRes = await runner(windowsSystemTool("netstat.exe", options?.env ?? process.env), ["-ano", "-p", "tcp"], AGY_PROBE_TIMEOUT_MILLISECONDS);
396
- if (!netstatRes.ok)
397
- return [];
398
- return parseNetstatPorts(netstatRes.stdout, trustedPids);
399
- }
400
- const roots = getAgyInstallRoots(platform, options?.env ?? process.env);
401
- const lsofRes = await runner("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-c", "agy"], AGY_PROBE_TIMEOUT_MILLISECONDS);
402
- if (!lsofRes.ok)
403
- return [];
404
- const pidPorts = parseLsofOutput(lsofRes.stdout);
405
- if (pidPorts.length === 0)
406
- return [];
407
- const verifiedPids = new Map();
408
- const ports = [];
409
- for (const { pid, port } of pidPorts) {
410
- let trusted = verifiedPids.get(pid);
411
- if (trusted === undefined) {
412
- if (!/^\d+$/u.test(pid))
413
- continue;
414
- const owner = await runner("ps", ["-o", "uid=", "-p", pid], AGY_PROBE_TIMEOUT_MILLISECONDS);
415
- const currentUid = options?.currentUserId ?? process.getuid?.();
416
- if (!owner.ok || currentUid === undefined || owner.stdout.trim() !== String(currentUid)) {
417
- verifiedPids.set(pid, false);
418
- continue;
419
- }
420
- if (options?.resolveExecutablePath) {
421
- const exePath = await options.resolveExecutablePath(pid);
422
- const ownerUid = exePath === null
423
- ? null
424
- : await (options.resolveExecutableOwner ?? (async (candidate) => {
425
- try {
426
- return (await stat(candidate)).uid ?? null;
427
- }
428
- catch {
429
- return null;
430
- }
431
- }))(exePath);
432
- const mode = exePath === null
433
- ? null
434
- : await (options.resolveExecutableMode ?? (async (candidate) => {
435
- try {
436
- return (await stat(candidate)).mode ?? null;
437
- }
438
- catch {
439
- return null;
440
- }
441
- }))(exePath);
442
- trusted = exePath !== null &&
443
- (ownerUid === currentUid || ownerUid === 0) &&
444
- (mode === null || (mode & 0o002) === 0) &&
445
- isTrustedAgyExecutable(exePath, platform, roots);
446
- }
447
- else {
448
- const exePath = await resolveAgyExecutablePath(pid, platform, runner, options?.env ?? process.env);
449
- if (exePath !== null) {
450
- const ownerUid = await (options?.resolveExecutableOwner ?? (async (candidate) => {
451
- try {
452
- return (await stat(candidate)).uid ?? null;
453
- }
454
- catch {
455
- return null;
456
- }
457
- }))(exePath);
458
- const mode = await (options?.resolveExecutableMode ?? (async (candidate) => {
459
- try {
460
- return (await stat(candidate)).mode ?? null;
461
- }
462
- catch {
463
- return null;
464
- }
465
- }))(exePath);
466
- trusted = (ownerUid === currentUid || ownerUid === 0) &&
467
- (mode === null || (mode & 0o002) === 0) &&
468
- isTrustedAgyExecutable(exePath, platform, roots);
469
- }
470
- else {
471
- /* The executable this pid runs could not be read at all, on the
472
- one platform this file resolves it without an injected
473
- resolver. That is a reason to skip the port, not to trust it: a
474
- `lsof` name match is exactly what an impostor process names
475
- itself to get. */
476
- trusted = false;
477
- }
478
- }
479
- verifiedPids.set(pid, trusted);
480
- }
481
- if (trusted && !ports.includes(port)) {
482
- ports.push(port);
483
- }
484
- }
485
- return ports;
486
- }
487
- function httpPost(isHttps, port, path, timeoutMs, signal) {
488
- return new Promise((resolve) => {
489
- if (signal?.aborted) {
490
- resolve(null);
491
- return;
492
- }
493
- const transport = isHttps ? https : http;
494
- const body = "{}";
495
- let settled = false;
496
- const onAbort = () => {
497
- if (!settled) {
498
- settled = true;
499
- req.destroy();
500
- resolve(null);
501
- }
502
- };
503
- if (signal) {
504
- signal.addEventListener("abort", onAbort, { once: true });
505
- }
506
- const req = transport.request({
507
- hostname: "127.0.0.1",
508
- port,
509
- path,
510
- method: "POST",
511
- headers: {
512
- "Content-Type": "application/json",
513
- "Connect-Protocol-Version": "1",
514
- "User-Agent": OPENLIMITER_USER_AGENT,
515
- "Content-Length": Buffer.byteLength(body)
516
- },
517
- rejectUnauthorized: false,
518
- timeout: timeoutMs
519
- }, (res) => {
520
- if (res.statusCode !== 200) {
521
- res.resume();
522
- if (!settled) {
523
- settled = true;
524
- resolve(null);
525
- }
526
- return;
527
- }
528
- let totalBytes = 0;
529
- let data = "";
530
- res.on("data", (chunk) => {
531
- totalBytes += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
532
- if (totalBytes > MAX_AGY_RESPONSE_BYTES) {
533
- req.destroy();
534
- res.destroy();
535
- if (!settled) {
536
- settled = true;
537
- resolve(null);
538
- }
539
- return;
540
- }
541
- data += chunk;
542
- });
543
- res.on("end", () => {
544
- if (settled)
545
- return;
546
- settled = true;
547
- try {
548
- resolve(JSON.parse(data));
549
- }
550
- catch {
551
- resolve(null);
552
- }
553
- });
554
- res.on("error", () => {
555
- if (!settled) {
556
- settled = true;
557
- resolve(null);
558
- }
559
- });
560
- });
561
- req.on("error", () => {
562
- if (!settled) {
563
- settled = true;
564
- resolve(null);
565
- }
566
- });
567
- req.on("timeout", () => {
568
- req.destroy();
569
- if (!settled) {
570
- settled = true;
571
- resolve(null);
572
- }
573
- });
574
- req.write(body);
575
- req.end();
576
- });
577
- }
578
- /**
579
- * Probe an agy loopback port for quota summary.
580
- * Attempts HTTPS with self-signed certificate acceptance first, then plain HTTP.
581
- */
582
- export async function probeAgyLoopback(port, timeoutMs = AGY_PROBE_TIMEOUT_MILLISECONDS, signal) {
583
- const httpsRes = await httpPost(true, port, AGY_QUOTA_PATH, timeoutMs, signal);
584
- if (httpsRes !== null)
585
- return httpsRes;
586
- return await httpPost(false, port, AGY_QUOTA_PATH, timeoutMs, signal);
587
- }
588
- /**
589
- * Run the Antigravity probe:
590
- * 1. Discover listening ports for running agy processes.
591
- * 2. If no processes found, report "not_running".
592
- * 3. Probe loopback ports and parse quota summary.
593
- * Enforces a hard total deadline across all ports probed.
594
- */
595
- export async function probeAntigravity(options) {
596
- const now = options?.now ?? new Date().toISOString();
597
- const totalTimeoutMs = options?.totalDeadlineMs ?? TOTAL_AGY_PROBE_DEADLINE_MS;
598
- const abortController = new AbortController();
599
- const timeoutId = setTimeout(() => {
600
- abortController.abort();
601
- }, totalTimeoutMs);
602
- try {
603
- const enumeratePorts = options?.enumeratePorts ??
604
- (() => enumerateAgyListeningPorts({
605
- ...(options?.lookup?.platform !== undefined ? { platform: options.lookup.platform } : {}),
606
- ...(options?.runCommand !== undefined ? { runCommand: options.runCommand } : {}),
607
- ...(options?.resolveExecutablePath !== undefined
608
- ? { resolveExecutablePath: options.resolveExecutablePath }
609
- : {})
610
- }));
611
- const probePort = options?.probePort ??
612
- ((port) => probeAgyLoopback(port, AGY_PROBE_TIMEOUT_MILLISECONDS, abortController.signal));
613
- if (abortController.signal.aborted) {
614
- return { ok: false, reason: "unreachable" };
615
- }
616
- const ports = await enumeratePorts();
617
- if (abortController.signal.aborted) {
618
- return { ok: false, reason: "unreachable" };
619
- }
620
- if (ports.length === 0) {
621
- return { ok: false, reason: "not_running" };
622
- }
623
- for (const port of ports) {
624
- if (abortController.signal.aborted) {
625
- return { ok: false, reason: "unreachable" };
626
- }
627
- const probePromise = probePort(port);
628
- const payload = await Promise.race([
629
- probePromise,
630
- new Promise((resolve) => {
631
- if (abortController.signal.aborted) {
632
- resolve(null);
633
- }
634
- else {
635
- abortController.signal.addEventListener("abort", () => resolve(null), { once: true });
636
- }
637
- })
638
- ]);
639
- if (abortController.signal.aborted) {
640
- return { ok: false, reason: "unreachable" };
641
- }
642
- if (payload !== null) {
643
- const meters = parseAgyQuotaSummary(payload, now);
644
- if (meters !== null && meters.length > 0) {
645
- return { ok: true, meters };
646
- }
647
- }
648
- }
649
- return { ok: false, reason: "unreachable" };
650
- }
651
- finally {
652
- clearTimeout(timeoutId);
653
- }
654
- }
655
- //# sourceMappingURL=antigravity-probe.js.map