@kunkun.sh/kuota 0.0.0-stage → 0.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.
@@ -0,0 +1,707 @@
1
+ import { a as makeObservation, o as makeUnavailableSnapshot, s as normalizeWindow } from "./dist-CIjerpSd.mjs";
2
+ import { Effect } from "effect";
3
+ import { spawn } from "node:child_process";
4
+ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
5
+ import { homedir, tmpdir } from "node:os";
6
+ import { delimiter, join } from "node:path";
7
+ // ../providers/src/codex/protocol.ts
8
+ function asRecord(value) {
9
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
10
+ }
11
+ /** Parses only the non-sensitive subset of the Codex app-server v2 quota response. */
12
+ function parseCodexRateLimits(raw, fetchedAt = /* @__PURE__ */ new Date()) {
13
+ const response = asRecord(raw);
14
+ const limits = asRecord(response?.rateLimits);
15
+ const common = {
16
+ provider: "codex",
17
+ source: "codex-app-server",
18
+ fetchedAt: fetchedAt.toISOString()
19
+ };
20
+ if (!limits) return {
21
+ ...common,
22
+ status: "unavailable",
23
+ windows: [],
24
+ errorCode: "NO_RATE_LIMITS"
25
+ };
26
+ let malformed = false;
27
+ const windows = [];
28
+ for (const windowId of ["primary", "secondary"]) {
29
+ const window = asRecord(limits[windowId]);
30
+ if (!window) continue;
31
+ const usedPercent = window.usedPercent;
32
+ if (typeof usedPercent !== "number" || !Number.isFinite(usedPercent) || usedPercent < 0 || usedPercent > 100) malformed = true;
33
+ const reset = window.resetsAt;
34
+ const resetAt = typeof reset === "number" && Number.isFinite(reset) && reset >= 0 ? (/* @__PURE__ */ new Date(reset * 1e3)).toISOString() : null;
35
+ windows.push(normalizeWindow({
36
+ windowId,
37
+ usedPercent,
38
+ resetAt
39
+ }));
40
+ }
41
+ const blocked = response?.ordinaryUsageAllowed === false;
42
+ return {
43
+ ...common,
44
+ status: windows.length === 0 ? "unavailable" : malformed || blocked ? "partial" : "ok",
45
+ windows,
46
+ errorCode: windows.length === 0 ? "NO_WINDOWS" : blocked ? "ORDINARY_USAGE_BLOCKED" : malformed ? "INVALID_WINDOW" : null
47
+ };
48
+ }
49
+ // ../providers/src/codex/transport.ts
50
+ /** Fixed, safe error messages: never forward stderr or a raw RPC response. */
51
+ var CodexTransportError = class extends Error {
52
+ code;
53
+ constructor(code) {
54
+ super({
55
+ BINARY_NOT_FOUND: "Codex executable was not found",
56
+ TIMEOUT: "Codex app-server did not respond in time",
57
+ PROTOCOL_ERROR: "Codex app-server protocol failed"
58
+ }[code]);
59
+ this.code = code;
60
+ this.name = "CodexTransportError";
61
+ }
62
+ };
63
+ /** One bounded RPC session. Never spawns a bare Codex TUI and never prints raw output. */
64
+ function requestCodexRateLimits(options = {}) {
65
+ const binary = options.binary ?? "codex";
66
+ const args = options.args ?? ["app-server"];
67
+ const timeoutMs = Math.max(100, Math.min(options.timeoutMs ?? 15e3, 6e4));
68
+ return new Promise((resolve, reject) => {
69
+ const child = spawn(binary, [...args], {
70
+ stdio: [
71
+ "pipe",
72
+ "pipe",
73
+ "pipe"
74
+ ],
75
+ shell: false
76
+ });
77
+ let settled = false;
78
+ let phase = "initializing";
79
+ let buffer = "";
80
+ const finish = (error, result) => {
81
+ if (settled) return;
82
+ settled = true;
83
+ clearTimeout(timeout);
84
+ child.stdin.end();
85
+ if (!child.killed && child.exitCode === null) child.kill("SIGTERM");
86
+ if (error) reject(error);
87
+ else resolve(result);
88
+ };
89
+ const timeout = setTimeout(() => finish(new CodexTransportError("TIMEOUT")), timeoutMs);
90
+ const send = (message) => {
91
+ if (settled) return;
92
+ try {
93
+ child.stdin.write(`${JSON.stringify(message)}\n`);
94
+ } catch {
95
+ finish(new CodexTransportError("PROTOCOL_ERROR"));
96
+ }
97
+ };
98
+ child.once("error", (error) => {
99
+ finish(new CodexTransportError(error.code === "ENOENT" ? "BINARY_NOT_FOUND" : "PROTOCOL_ERROR"));
100
+ });
101
+ child.once("exit", () => finish(new CodexTransportError("PROTOCOL_ERROR")));
102
+ child.stdin.on("error", () => finish(new CodexTransportError("PROTOCOL_ERROR")));
103
+ child.stderr.resume();
104
+ const onMessage = (value) => {
105
+ const msg = value !== null && typeof value === "object" ? value : null;
106
+ if (!msg || typeof msg.id !== "number") return;
107
+ if (phase === "initializing" && msg.id === 1) {
108
+ if (msg.error || !msg.result) return finish(new CodexTransportError("PROTOCOL_ERROR"));
109
+ phase = "reading";
110
+ send({ method: "initialized" });
111
+ send({
112
+ id: 2,
113
+ method: "account/rateLimits/read"
114
+ });
115
+ } else if (phase === "reading" && msg.id === 2) {
116
+ if (msg.error || !msg.result) return finish(new CodexTransportError("PROTOCOL_ERROR"));
117
+ finish(void 0, msg.result);
118
+ }
119
+ };
120
+ child.stdout.on("data", (chunk) => {
121
+ if (settled) return;
122
+ buffer += chunk.toString("utf8");
123
+ if (buffer.length > 2e6) return finish(new CodexTransportError("PROTOCOL_ERROR"));
124
+ while (!settled) {
125
+ const index = buffer.indexOf("\n");
126
+ if (index < 0) break;
127
+ const line = buffer.slice(0, index).trim();
128
+ buffer = buffer.slice(index + 1);
129
+ if (!line) continue;
130
+ try {
131
+ onMessage(JSON.parse(line));
132
+ } catch {
133
+ finish(new CodexTransportError("PROTOCOL_ERROR"));
134
+ }
135
+ }
136
+ });
137
+ send({
138
+ id: 1,
139
+ method: "initialize",
140
+ params: { clientInfo: {
141
+ name: "kuota",
142
+ version: "0.1.0"
143
+ } }
144
+ });
145
+ });
146
+ }
147
+ // ../providers/src/codex/index.ts
148
+ function readCodex(options = {}) {
149
+ return Effect.promise(async () => {
150
+ try {
151
+ return parseCodexRateLimits(await requestCodexRateLimits(options));
152
+ } catch (error) {
153
+ const code = error instanceof CodexTransportError ? error.code : "UNKNOWN";
154
+ return makeUnavailableSnapshot("codex", code, code === "BINARY_NOT_FOUND" ? "unsupported" : "unavailable");
155
+ }
156
+ });
157
+ }
158
+ // ../providers/src/antigravity/index.ts
159
+ function record$1(value) {
160
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
161
+ }
162
+ function slug$1(value) {
163
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || "quota";
164
+ }
165
+ function groupId(value) {
166
+ if (/^gemini models?$/i.test(value)) return "gemini";
167
+ if (/^claude and gpt(?: models?)?$/i.test(value)) return "claude-gpt";
168
+ return slug$1(value);
169
+ }
170
+ function validFraction(value) {
171
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
172
+ }
173
+ /** Parses agy >=1.1.11's structured `-p /usage --output-format json` response. */
174
+ function parseAntigravityUsageReport(raw) {
175
+ const data = record$1(raw);
176
+ const command = record$1(data?.command);
177
+ const payload = record$1(command?.data);
178
+ if (data?.status !== "SUCCESS" || command?.name !== "usage" || !Array.isArray(payload?.groups)) throw new Error("INVALID_USAGE_REPORT");
179
+ const windows = [];
180
+ const seen = /* @__PURE__ */ new Set();
181
+ for (const group of payload.groups) {
182
+ const info = record$1(group);
183
+ if (!info || !Array.isArray(info.buckets)) continue;
184
+ const family = groupId(typeof info.name === "string" ? info.name : typeof info.display_name === "string" ? info.display_name : "quota");
185
+ for (const bucket of info.buckets) {
186
+ const value = record$1(bucket);
187
+ if (!value || value.disabled === true) continue;
188
+ const nested = record$1(value.remaining);
189
+ const remaining = value.remaining_fraction ?? nested?.remaining_fraction ?? (nested?.case === "remainingFraction" ? nested.value : void 0);
190
+ if (!validFraction(remaining)) continue;
191
+ const name = `${family}.${typeof value.window === "string" ? slug$1(value.window) : typeof value.id === "string" ? slug$1(value.id) : "quota"}`;
192
+ const windowId = seen.has(name) ? `${name}:${slug$1(String(value.id ?? value.bucket_id ?? "additional"))}` : name;
193
+ seen.add(windowId);
194
+ windows.push(normalizeWindow({
195
+ windowId,
196
+ usedPercent: Number(((1 - remaining) * 100).toFixed(2)),
197
+ resetAt: value.reset_time ?? value.resetTime
198
+ }));
199
+ }
200
+ }
201
+ if (windows.length === 0) throw new Error("NO_MEASURED_QUOTA");
202
+ return {
203
+ provider: "antigravity",
204
+ status: "ok",
205
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
206
+ source: "agy-cli-print",
207
+ windows,
208
+ errorCode: null
209
+ };
210
+ }
211
+ /** Version strings with suffixes are intentionally rejected to avoid guessing support. */
212
+ function parseAgyVersion(value) {
213
+ const match = /^(\d+)\.(\d+)\.(\d+)\s*$/.exec(value.trim());
214
+ if (!match) return false;
215
+ const major = Number(match[1]);
216
+ const minor = Number(match[2]);
217
+ const patch = Number(match[3]);
218
+ return major > 1 || major === 1 && (minor > 1 || minor === 1 && patch >= 11);
219
+ }
220
+ var AgyProbeError = class extends Error {
221
+ code;
222
+ constructor(code) {
223
+ super(code);
224
+ this.code = code;
225
+ }
226
+ };
227
+ /** Bound subprocess output and lifetime; never print raw stdout/stderr or OAuth URLs. */
228
+ function execute(binary, args, options) {
229
+ return new Promise((resolve, reject) => {
230
+ const child = spawn(binary, args, {
231
+ cwd: options.cwd,
232
+ env: options.env,
233
+ detached: process.platform !== "win32",
234
+ windowsHide: true,
235
+ stdio: [
236
+ "ignore",
237
+ "pipe",
238
+ "pipe"
239
+ ]
240
+ });
241
+ const chunks = [];
242
+ let bytes = 0;
243
+ let stderr = "";
244
+ let settled = false;
245
+ const stop = () => {
246
+ if (child.pid && process.platform !== "win32") try {
247
+ process.kill(-child.pid, "SIGTERM");
248
+ } catch {}
249
+ else try {
250
+ child.kill("SIGTERM");
251
+ } catch {}
252
+ };
253
+ const finish = (error) => {
254
+ if (settled) return;
255
+ settled = true;
256
+ clearTimeout(timer);
257
+ stop();
258
+ if (error) reject(error);
259
+ else resolve(Buffer.concat(chunks).toString("utf8"));
260
+ };
261
+ const timer = setTimeout(() => finish(new AgyProbeError("TIMEOUT")), options.timeoutMs);
262
+ child.stdout?.on("data", (chunk) => {
263
+ bytes += chunk.length;
264
+ if (bytes > options.maxOutputBytes) finish(new AgyProbeError("OUTPUT_TOO_LARGE"));
265
+ else if (!settled) chunks.push(chunk);
266
+ });
267
+ child.stderr?.on("data", (chunk) => {
268
+ if (stderr.length < 4096) stderr += chunk.toString("utf8").slice(0, 4096 - stderr.length);
269
+ });
270
+ child.once("error", (error) => finish(new AgyProbeError(error.code === "ENOENT" ? "BINARY_NOT_FOUND" : "CLI_FAILED")));
271
+ child.once("close", (code) => {
272
+ if (code === 0) finish();
273
+ else if (/not logged in|authentication required|sign.?in|please visit/i.test(stderr)) finish(new AgyProbeError("AUTH_REQUIRED"));
274
+ else finish(new AgyProbeError("CLI_FAILED"));
275
+ });
276
+ });
277
+ }
278
+ /** No new account login. Existing agy credentials only, guarded from browser launch and auto-update. */
279
+ function readAntigravity(options = {}) {
280
+ return Effect.promise(async () => {
281
+ const directory = await mkdtemp(join(tmpdir(), "kuota-agy-"));
282
+ try {
283
+ const script = "#!/bin/sh\nexit 1\n";
284
+ for (const name of [
285
+ "open",
286
+ "xdg-open",
287
+ "browser"
288
+ ]) await writeFile(join(directory, name), script, { mode: 448 });
289
+ const env = { ...process.env };
290
+ delete env.ANTIGRAVITY_OAUTH_CREDENTIALS_JSON;
291
+ env.AGY_CLI_DISABLE_AUTO_UPDATE = "true";
292
+ env.PATH = `${directory}${delimiter}${process.env.PATH ?? ""}`;
293
+ env.BROWSER = join(directory, "browser");
294
+ const binary = options.binary ?? "agy";
295
+ const context = {
296
+ cwd: directory,
297
+ env
298
+ };
299
+ if (!parseAgyVersion(await execute(binary, ["--version"], {
300
+ ...context,
301
+ timeoutMs: 3e3,
302
+ maxOutputBytes: 8192
303
+ }))) throw new AgyProbeError("UNSUPPORTED_VERSION");
304
+ const text = await execute(binary, [
305
+ "-p",
306
+ "/usage",
307
+ "--output-format",
308
+ "json",
309
+ "--print-timeout",
310
+ "90s"
311
+ ], {
312
+ ...context,
313
+ timeoutMs: options.timeoutMs ?? 2e4,
314
+ maxOutputBytes: options.maxOutputBytes ?? 1048576
315
+ });
316
+ try {
317
+ return parseAntigravityUsageReport(JSON.parse(text));
318
+ } catch {
319
+ throw new AgyProbeError("PROTOCOL_ERROR");
320
+ }
321
+ } catch (error) {
322
+ const code = error instanceof AgyProbeError ? error.code : "CLI_FAILED";
323
+ return makeUnavailableSnapshot("antigravity", code, code === "AUTH_REQUIRED" ? "unauthenticated" : "unavailable");
324
+ } finally {
325
+ await rm(directory, {
326
+ recursive: true,
327
+ force: true
328
+ });
329
+ }
330
+ });
331
+ }
332
+ // ../providers/src/claude/index.ts
333
+ function record(value) {
334
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
335
+ }
336
+ function validPercent(value) {
337
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 100;
338
+ }
339
+ function slug(value) {
340
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
341
+ }
342
+ /** Only a Claude subscription OAuth token works; MCP-only and API keys are not quota tokens. */
343
+ function extractClaudeOAuthToken(raw, now = Date.now()) {
344
+ const oauth = record(record(raw)?.claudeAiOauth);
345
+ const token = oauth?.accessToken;
346
+ const expiresAt = oauth?.expiresAt;
347
+ if (typeof token !== "string" || !token.trim() || !validExpiry(expiresAt, now)) return null;
348
+ return token.trim();
349
+ }
350
+ function validExpiry(value, now) {
351
+ return typeof value === "number" && Number.isFinite(value) && value > now;
352
+ }
353
+ /** Claude API reports utilization (used percent), not remaining percent. */
354
+ function parseClaudeOAuthUsage(raw) {
355
+ const payload = record(raw);
356
+ if (!payload) throw new Error("INVALID_CLAUDE_USAGE");
357
+ const windows = [];
358
+ const add = (windowId, value) => {
359
+ const data = record(value);
360
+ if (!validPercent(data?.utilization)) return;
361
+ windows.push(normalizeWindow({
362
+ windowId,
363
+ usedPercent: data.utilization,
364
+ resetAt: data.resets_at
365
+ }));
366
+ };
367
+ add("session.5h", payload.five_hour);
368
+ if (validPercent(record(payload.seven_day)?.utilization)) add("weekly.all", payload.seven_day);
369
+ else add("weekly.oauth-apps", payload.seven_day_oauth_apps);
370
+ add("weekly.sonnet", payload.seven_day_sonnet);
371
+ add("weekly.opus", payload.seven_day_opus);
372
+ for (const key of [
373
+ "seven_day_routines",
374
+ "seven_day_claude_routines",
375
+ "claude_routines",
376
+ "routines",
377
+ "seven_day_cowork",
378
+ "cowork"
379
+ ]) if (validPercent(record(payload[key])?.utilization)) {
380
+ add("weekly.routines", payload[key]);
381
+ break;
382
+ }
383
+ if (Array.isArray(payload.limits)) {
384
+ const seen = /* @__PURE__ */ new Set();
385
+ for (const entry of payload.limits) {
386
+ const limit = record(entry);
387
+ const model = record(record(limit?.scope)?.model);
388
+ if (limit?.kind !== "weekly_scoped" || !validPercent(limit.percent)) continue;
389
+ const id = slug(typeof model?.display_name === "string" ? model.display_name : typeof model?.id === "string" ? model.id : "");
390
+ if (!id || id === "all-models") continue;
391
+ const windowId = `weekly.${id}`;
392
+ if (seen.has(windowId) || windows.some((w) => w.windowId === windowId)) continue;
393
+ seen.add(windowId);
394
+ windows.push(normalizeWindow({
395
+ windowId,
396
+ usedPercent: limit.percent,
397
+ resetAt: limit.resets_at
398
+ }));
399
+ }
400
+ }
401
+ return {
402
+ provider: "claude",
403
+ source: "claude-oauth-usage",
404
+ status: windows.length ? "ok" : "unavailable",
405
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
406
+ windows,
407
+ errorCode: windows.length ? null : "NO_MEASURED_QUOTA"
408
+ };
409
+ }
410
+ const ENDPOINT = "https://api.anthropic.com/api/oauth/usage";
411
+ const MAX_BODY = 1048576;
412
+ /** Read at most 1 MiB, without placing raw HTTP error responses in logs or JSON output. */
413
+ async function readBoundedResponse(response) {
414
+ if (!response.body) return "";
415
+ const reader = response.body.getReader();
416
+ const buffers = [];
417
+ let total = 0;
418
+ try {
419
+ while (true) {
420
+ const { done, value } = await reader.read();
421
+ if (done) break;
422
+ total += value.byteLength;
423
+ if (total > MAX_BODY) throw new Error("RESPONSE_TOO_LARGE");
424
+ buffers.push(value);
425
+ }
426
+ const joined = new Uint8Array(total);
427
+ let offset = 0;
428
+ for (const buffer of buffers) {
429
+ joined.set(buffer, offset);
430
+ offset += buffer.byteLength;
431
+ }
432
+ return new TextDecoder().decode(joined);
433
+ } finally {
434
+ reader.releaseLock();
435
+ }
436
+ }
437
+ async function resolveToken(options) {
438
+ if (options.token) return options.token.trim() || null;
439
+ const fromEnvironment = process.env.CLAUDE_CODE_OAUTH_TOKEN;
440
+ if (fromEnvironment?.trim()) return fromEnvironment.trim();
441
+ try {
442
+ const file = options.credentialsFile ?? join(homedir(), ".claude", ".credentials.json");
443
+ return extractClaudeOAuthToken(JSON.parse(await readFile(file, "utf8")));
444
+ } catch {
445
+ return null;
446
+ }
447
+ }
448
+ /** No Keychain UI access, credential refresh, browser sign-in or CLI session side effects. */
449
+ function readClaude(options = {}) {
450
+ return Effect.promise(async () => {
451
+ const token = await resolveToken(options);
452
+ if (!token) return makeUnavailableSnapshot("claude", "AUTH_REQUIRED", "unauthenticated");
453
+ const controller = new AbortController();
454
+ const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? 12e3);
455
+ try {
456
+ const request = (url, agent) => (options.fetchImpl ?? fetch)(url, {
457
+ method: "GET",
458
+ redirect: "error",
459
+ cache: "no-store",
460
+ headers: {
461
+ Authorization: `Bearer ${token}`,
462
+ Accept: "application/json",
463
+ "Content-Type": "application/json",
464
+ "anthropic-beta": "oauth-2025-04-20",
465
+ "User-Agent": agent
466
+ },
467
+ signal: controller.signal
468
+ });
469
+ let response = await request(`${ENDPOINT}?cedar_ember=1`, "claude-cli/2.1.0 (external, cli)");
470
+ if (response.status === 400 || response.status === 403) response = await request(ENDPOINT, "claude-code/2.1.0");
471
+ if (response.status === 401) return makeUnavailableSnapshot("claude", "AUTH_REQUIRED", "unauthenticated");
472
+ if (response.status === 429) return makeUnavailableSnapshot("claude", "RATE_LIMITED");
473
+ if (response.status !== 200) return makeUnavailableSnapshot("claude", "HTTP_ERROR");
474
+ const content = await readBoundedResponse(response);
475
+ try {
476
+ return parseClaudeOAuthUsage(JSON.parse(content));
477
+ } catch {
478
+ return makeUnavailableSnapshot("claude", "PROTOCOL_ERROR");
479
+ }
480
+ } catch (error) {
481
+ return makeUnavailableSnapshot("claude", controller.signal.aborted ? "TIMEOUT" : error instanceof Error && error.message === "RESPONSE_TOO_LARGE" ? "OUTPUT_TOO_LARGE" : "NETWORK_ERROR");
482
+ } finally {
483
+ clearTimeout(timer);
484
+ }
485
+ });
486
+ }
487
+ // ../providers/src/registry.ts
488
+ const PROVIDER_METADATA = [
489
+ {
490
+ id: "codex",
491
+ label: "OpenAI Codex",
492
+ implemented: true,
493
+ source: "codex-app-server"
494
+ },
495
+ {
496
+ id: "claude",
497
+ label: "Claude Code",
498
+ implemented: true,
499
+ source: "claude-oauth-usage"
500
+ },
501
+ {
502
+ id: "antigravity",
503
+ label: "Google Antigravity",
504
+ implemented: true,
505
+ source: "agy-cli-print"
506
+ }
507
+ ];
508
+ /** Static names are extensible to runtime manifests in the later plugin phase. */
509
+ function isProviderId(value) {
510
+ return PROVIDER_METADATA.some((provider) => provider.id === value);
511
+ }
512
+ function builtInProviders() {
513
+ return [
514
+ {
515
+ id: "codex",
516
+ label: "OpenAI Codex",
517
+ read: () => readCodex()
518
+ },
519
+ {
520
+ id: "claude",
521
+ label: "Claude Code",
522
+ read: () => readClaude()
523
+ },
524
+ {
525
+ id: "antigravity",
526
+ label: "Google Antigravity",
527
+ read: () => readAntigravity()
528
+ }
529
+ ];
530
+ }
531
+ /** One failed adapter never prevents other providers from reporting. */
532
+ function readAll(ids, providers = builtInProviders()) {
533
+ const byId = new Map(providers.map((provider) => [provider.id, provider]));
534
+ return Effect.all(ids.map((id) => {
535
+ const provider = byId.get(id);
536
+ if (!provider) return Effect.succeed(makeUnavailableSnapshot(id, "NOT_REGISTERED", "unsupported"));
537
+ return provider.read().pipe(Effect.catchAllCause(() => Effect.succeed(makeUnavailableSnapshot(id, "PROVIDER_FAILED"))));
538
+ }), { concurrency: 3 });
539
+ }
540
+ // ../providers/src/service.ts
541
+ const COMMON = {
542
+ errorRetryMs: 6e4,
543
+ authRetryMs: 3e5,
544
+ rateLimitCooldownMs: 9e5,
545
+ staleMaxAgeMs: 864e5
546
+ };
547
+ const DEFAULT_POLICIES = {
548
+ codex: {
549
+ ...COMMON,
550
+ successTtlMs: 6e4
551
+ },
552
+ claude: {
553
+ ...COMMON,
554
+ successTtlMs: 9e5
555
+ },
556
+ antigravity: {
557
+ ...COMMON,
558
+ successTtlMs: 3e5
559
+ }
560
+ };
561
+ const emptySlot = (generation) => ({
562
+ lastGood: null,
563
+ lastFailure: null,
564
+ lastCheckedAtMs: 0,
565
+ nextAttemptAtMs: 0,
566
+ inflight: null,
567
+ generation
568
+ });
569
+ const isMeasurable = (s) => (s.status === "ok" || s.status === "partial") && s.windows.length > 0;
570
+ /**
571
+ * Process-local TTL cache with per-provider singleflight, failure cooldowns and honest
572
+ * stale labelling. Only normalized snapshots are retained.
573
+ */
574
+ function createQuotaService(options = {}) {
575
+ const now = options.now ?? Date.now;
576
+ const providers = new Map((options.providers ?? builtInProviders()).map((provider) => [provider.id, provider]));
577
+ const slots = /* @__PURE__ */ new Map();
578
+ const generations = /* @__PURE__ */ new Map();
579
+ const policyFor = (id) => ({
580
+ ...DEFAULT_POLICIES[id],
581
+ ...options.policies?.[id]
582
+ });
583
+ const slotFor = (id) => {
584
+ let slot = slots.get(id);
585
+ if (!slot) {
586
+ slot = emptySlot(generations.get(id) ?? 0);
587
+ slots.set(id, slot);
588
+ }
589
+ return slot;
590
+ };
591
+ const view = (id, freshProbe) => {
592
+ const slot = slots.get(id);
593
+ if (!slot) return null;
594
+ const t = now();
595
+ const policy = policyFor(id);
596
+ const good = slot.lastGood;
597
+ const goodMs = good ? Date.parse(good.fetchedAt) : NaN;
598
+ const goodUsable = good !== null && !(t - (Number.isFinite(goodMs) ? goodMs : slot.lastCheckedAtMs) > policy.staleMaxAgeMs);
599
+ const nextAttemptAtMs = slot.nextAttemptAtMs > t ? slot.nextAttemptAtMs : null;
600
+ if (good && goodUsable) {
601
+ const failed = slot.lastFailure;
602
+ return makeObservation({
603
+ snapshot: good,
604
+ freshness: failed ? "stale" : freshProbe ? "live" : "cached",
605
+ checkedAtMs: slot.lastCheckedAtMs,
606
+ nowMs: t,
607
+ nextAttemptAtMs,
608
+ latestErrorCode: failed?.errorCode ?? null
609
+ });
610
+ }
611
+ if (slot.lastFailure) return makeObservation({
612
+ snapshot: slot.lastFailure,
613
+ freshness: freshProbe ? "live" : "cached",
614
+ checkedAtMs: slot.lastCheckedAtMs,
615
+ nowMs: t,
616
+ nextAttemptAtMs,
617
+ latestErrorCode: slot.lastFailure.errorCode
618
+ });
619
+ return null;
620
+ };
621
+ const probe = (id, provider, slot) => {
622
+ const attemptAt = now();
623
+ const settled = Effect.runPromise(provider.read().pipe(Effect.catchAllCause(() => Effect.succeed({
624
+ ...makeUnavailableSnapshot(id, "PROVIDER_FAILED"),
625
+ fetchedAt: new Date(attemptAt).toISOString()
626
+ })))).catch(() => ({
627
+ ...makeUnavailableSnapshot(id, "PROVIDER_FAILED"),
628
+ fetchedAt: new Date(attemptAt).toISOString()
629
+ })).then((snapshot) => {
630
+ if (slots.get(id) !== slot || slot.generation !== (generations.get(id) ?? 0)) return;
631
+ const policy = policyFor(id);
632
+ const t = now();
633
+ slot.lastCheckedAtMs = attemptAt;
634
+ if (isMeasurable(snapshot)) {
635
+ slot.lastGood = snapshot;
636
+ slot.lastFailure = null;
637
+ slot.nextAttemptAtMs = 0;
638
+ return;
639
+ }
640
+ slot.lastFailure = snapshot;
641
+ if (snapshot.errorCode === "RATE_LIMITED") slot.nextAttemptAtMs = t + policy.rateLimitCooldownMs;
642
+ else if (snapshot.errorCode === "AUTH_REQUIRED" || snapshot.status === "unauthenticated") {
643
+ slot.lastGood = null;
644
+ slot.nextAttemptAtMs = t + policy.authRetryMs;
645
+ } else slot.nextAttemptAtMs = t + policy.errorRetryMs;
646
+ }).finally(() => {
647
+ if (slot.inflight === settled) slot.inflight = null;
648
+ });
649
+ slot.inflight = settled;
650
+ return settled;
651
+ };
652
+ const readOne = async (id, force) => {
653
+ const provider = providers.get(id);
654
+ if (!provider) {
655
+ const t = now();
656
+ return makeObservation({
657
+ snapshot: {
658
+ ...makeUnavailableSnapshot(id, "NOT_REGISTERED", "unsupported"),
659
+ fetchedAt: new Date(t).toISOString()
660
+ },
661
+ freshness: "live",
662
+ checkedAtMs: t,
663
+ nowMs: t,
664
+ latestErrorCode: "NOT_REGISTERED"
665
+ });
666
+ }
667
+ const slot = slotFor(id);
668
+ if (slot.inflight) {
669
+ await slot.inflight;
670
+ return view(id, true) ?? await readOne(id, force);
671
+ }
672
+ const t = now();
673
+ const coolingDown = slot.nextAttemptAtMs > t;
674
+ const goodMs = slot.lastGood ? Date.parse(slot.lastGood.fetchedAt) : NaN;
675
+ const fresh = slot.lastGood !== null && slot.lastFailure === null && Number.isFinite(goodMs) && t - goodMs < policyFor(id).successTtlMs;
676
+ if (coolingDown || fresh && !force) {
677
+ const cached = view(id, false);
678
+ if (cached) return cached;
679
+ }
680
+ await probe(id, provider, slot);
681
+ const result = view(id, true);
682
+ if (result) return result;
683
+ const t2 = now();
684
+ return makeObservation({
685
+ snapshot: makeUnavailableSnapshot(id, "INVALIDATED"),
686
+ freshness: "live",
687
+ checkedAtMs: t2,
688
+ nowMs: t2,
689
+ latestErrorCode: "INVALIDATED"
690
+ });
691
+ };
692
+ return {
693
+ read: (ids, opts) => Effect.promise(() => Promise.all(ids.map((id) => readOne(id, opts?.force === true)))),
694
+ peek: (id) => view(id, false),
695
+ invalidate: (id) => {
696
+ for (const target of id ? [id] : [
697
+ "codex",
698
+ "claude",
699
+ "antigravity"
700
+ ]) {
701
+ generations.set(target, (generations.get(target) ?? 0) + 1);
702
+ slots.delete(target);
703
+ }
704
+ }
705
+ };
706
+ }
707
+ export { readAll as a, parseAgyVersion as c, readCodex as d, CodexTransportError as f, isProviderId as i, parseAntigravityUsageReport as l, parseCodexRateLimits as m, PROVIDER_METADATA as n, parseClaudeOAuthUsage as o, requestCodexRateLimits as p, builtInProviders as r, readClaude as s, createQuotaService as t, readAntigravity as u };