@omnicross/daemon 0.3.0 → 0.4.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.
package/dist/cli.js CHANGED
@@ -1151,7 +1151,7 @@ import {
1151
1151
  } from "@omnicross/core/search";
1152
1152
 
1153
1153
  // src/bootstrap.ts
1154
- import { accessSync, constants as fsConstants, existsSync as existsSync29, mkdirSync as mkdirSync9 } from "fs";
1154
+ import { accessSync, constants as fsConstants, existsSync as existsSync30, mkdirSync as mkdirSync9 } from "fs";
1155
1155
  import { dirname as dirname17 } from "path";
1156
1156
  import { DEFAULT_AUDIT_CONFIG } from "@omnicross/contracts/audit-types";
1157
1157
  import { DEFAULT_BILLING_CONFIG } from "@omnicross/contracts/billing-types";
@@ -1170,14 +1170,14 @@ import { setSubscriptionRegistryForOutbound } from "@omnicross/core/outbound-api
1170
1170
  import { getSharedAccountHealth as getSharedAccountHealth4 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
1171
1171
  import {
1172
1172
  __resetSharedAccountAllowanceStoreForTests,
1173
- AccountAllowanceStore as AccountAllowanceStore3,
1173
+ AccountAllowanceStore as AccountAllowanceStore8,
1174
1174
  setSharedAccountAllowanceStore
1175
1175
  } from "@omnicross/core/pipeline/AccountAllowanceStore";
1176
1176
  import {
1177
1177
  __resetSharedAccountAllowanceSchedulingForTests,
1178
1178
  getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling5
1179
1179
  } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
1180
- import { fetchUpstream as fetchUpstream7, setUpstreamProxyResolver } from "@omnicross/core/pipeline/upstreamFetch";
1180
+ import { fetchUpstream as fetchUpstream13, setUpstreamProxyResolver } from "@omnicross/core/pipeline/upstreamFetch";
1181
1181
  import { __resetSharedIdentityStoreForTests } from "@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore";
1182
1182
  import { setGeminiCodeAssistResolver } from "@omnicross/core/ports/gemini-code-assist-resolver";
1183
1183
  import {
@@ -1307,9 +1307,263 @@ function handleCodexOAuthStatus(sessionId, deps) {
1307
1307
  return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
1308
1308
  }
1309
1309
 
1310
+ // src/admin/accountsKimiOAuth.ts
1311
+ import { kimiOAuth } from "@omnicross/subscriptions";
1312
+ function err2(status, message) {
1313
+ return { status, body: { error: { type: "admin_api_error", message } } };
1314
+ }
1315
+ var DEFAULT_KIMI_OAUTH_TTL_MS = 15 * 6e4;
1316
+ async function handleKimiOAuthStart(deps) {
1317
+ if (deps.kimiSessions.isBusy()) {
1318
+ return err2(409, "a kimi sign-in is already in progress \u2014 finish it in the browser or cancel it");
1319
+ }
1320
+ const fetchImpl = deps.oauthExchangeFetch("kimi");
1321
+ const deviceId = kimiOAuth.generateKimiDeviceId();
1322
+ const fingerprint = kimiOAuth.kimiFingerprintHeaders(deviceId);
1323
+ let authorization;
1324
+ try {
1325
+ authorization = await kimiOAuth.requestDeviceAuthorization(fetchImpl, fingerprint);
1326
+ } catch (e) {
1327
+ const reason = e instanceof Error ? e.message : "device authorization failed";
1328
+ return err2(502, `kimi device authorization failed: ${reason}`);
1329
+ }
1330
+ const { sessionId, signal } = deps.kimiSessions.begin();
1331
+ void runKimiDevicePoll(sessionId, authorization.deviceCode, deviceId, fingerprint, signal, deps).catch(() => deps.kimiSessions.settle(sessionId, "error", "kimi sign-in failed"));
1332
+ return {
1333
+ status: 200,
1334
+ body: {
1335
+ authUrl: authorization.verificationUriComplete ?? authorization.verificationUri,
1336
+ userCode: authorization.userCode,
1337
+ sessionId
1338
+ }
1339
+ };
1340
+ }
1341
+ async function runKimiDevicePoll(sessionId, deviceCode, deviceId, fingerprint, signal, deps) {
1342
+ const fetchImpl = deps.oauthExchangeFetch("kimi");
1343
+ const result = await kimiOAuth.awaitDeviceToken(
1344
+ { userCode: "", deviceCode, verificationUri: "" },
1345
+ fetchImpl,
1346
+ {
1347
+ fingerprint,
1348
+ deadlineMs: DEFAULT_KIMI_OAUTH_TTL_MS,
1349
+ sleep: (ms) => new Promise((resolve11, reject) => {
1350
+ const onAbort = () => {
1351
+ clearTimeout(timer);
1352
+ reject(new Error("login: cancelled"));
1353
+ };
1354
+ const timer = setTimeout(() => {
1355
+ signal.removeEventListener("abort", onAbort);
1356
+ resolve11();
1357
+ }, ms);
1358
+ signal.addEventListener("abort", onAbort, { once: true });
1359
+ })
1360
+ }
1361
+ );
1362
+ const block = {
1363
+ authMethod: "oauth",
1364
+ status: "authorized",
1365
+ accessToken: result.accessToken,
1366
+ refreshToken: result.refreshToken,
1367
+ expiresAt: new Date(Date.now() + result.expiresIn * 1e3).toISOString(),
1368
+ accountId: kimiOAuth.kimiAccountIdFromAccessToken(result.accessToken),
1369
+ deviceId,
1370
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
1371
+ };
1372
+ await deps.subscriptionAccountAppender.appendProviderAccount("kimi", block);
1373
+ deps.kimiSessions.settle(sessionId, "done");
1374
+ }
1375
+ function handleKimiOAuthCancel(sessionId, deps) {
1376
+ if (!deps.kimiSessions.cancel(sessionId)) return err2(404, "unknown or expired kimi sign-in session");
1377
+ return { status: 200, body: { ok: true } };
1378
+ }
1379
+ function handleKimiOAuthStatus(sessionId, deps) {
1380
+ const s = deps.kimiSessions.get(sessionId);
1381
+ if (!s) return err2(404, "unknown or expired kimi sign-in session");
1382
+ return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
1383
+ }
1384
+
1385
+ // src/admin/accountsGrokOAuth.ts
1386
+ import { grokOAuth } from "@omnicross/subscriptions";
1387
+ function err3(status, message) {
1388
+ return { status, body: { error: { type: "admin_api_error", message } } };
1389
+ }
1390
+ var DEFAULT_GROK_OAUTH_TTL_MS = 15 * 6e4;
1391
+ async function handleGrokOAuthStart(deps) {
1392
+ if (deps.grokSessions.isBusy()) {
1393
+ return err3(409, "a grok sign-in is already in progress \u2014 finish it in the browser or cancel it");
1394
+ }
1395
+ const fetchImpl = deps.oauthExchangeFetch("grok");
1396
+ let tokenEndpoint;
1397
+ try {
1398
+ tokenEndpoint = await grokOAuth.resolveGrokTokenEndpoint(fetchImpl);
1399
+ } catch (e) {
1400
+ const reason = e instanceof Error ? e.message : "OIDC discovery failed";
1401
+ return err3(502, `grok token-endpoint discovery failed: ${reason}`);
1402
+ }
1403
+ let authorization;
1404
+ try {
1405
+ authorization = await grokOAuth.requestGrokDeviceAuthorization(fetchImpl);
1406
+ } catch (e) {
1407
+ const reason = e instanceof Error ? e.message : "device authorization failed";
1408
+ return err3(502, `grok device authorization failed: ${reason}`);
1409
+ }
1410
+ const { sessionId, signal } = deps.grokSessions.begin();
1411
+ void runGrokDevicePoll(sessionId, tokenEndpoint, authorization.deviceCode, signal, deps).catch((e) => {
1412
+ const reason = e instanceof Error ? e.message : "grok sign-in failed";
1413
+ deps.grokSessions.settle(sessionId, "error", reason);
1414
+ });
1415
+ return {
1416
+ status: 200,
1417
+ body: {
1418
+ authUrl: authorization.verificationUriComplete ?? authorization.verificationUri,
1419
+ userCode: authorization.userCode,
1420
+ sessionId
1421
+ }
1422
+ };
1423
+ }
1424
+ async function runGrokDevicePoll(sessionId, tokenEndpoint, deviceCode, signal, deps) {
1425
+ const fetchImpl = deps.oauthExchangeFetch("grok");
1426
+ const result = await grokOAuth.awaitGrokDeviceToken(
1427
+ { userCode: "", deviceCode, verificationUri: "" },
1428
+ tokenEndpoint,
1429
+ fetchImpl,
1430
+ {
1431
+ deadlineMs: DEFAULT_GROK_OAUTH_TTL_MS,
1432
+ sleep: (ms) => new Promise((resolve11, reject) => {
1433
+ const onAbort = () => {
1434
+ clearTimeout(timer);
1435
+ reject(new Error("login: cancelled"));
1436
+ };
1437
+ const timer = setTimeout(() => {
1438
+ signal.removeEventListener("abort", onAbort);
1439
+ resolve11();
1440
+ }, ms);
1441
+ signal.addEventListener("abort", onAbort, { once: true });
1442
+ })
1443
+ }
1444
+ );
1445
+ const block = {
1446
+ authMethod: "oauth",
1447
+ status: "authorized",
1448
+ accessToken: result.accessToken,
1449
+ refreshToken: result.refreshToken,
1450
+ expiresAt: new Date(Date.now() + result.expiresIn * 1e3).toISOString(),
1451
+ accountId: grokOAuth.grokAccountIdFromAccessToken(result.accessToken),
1452
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
1453
+ };
1454
+ await deps.subscriptionAccountAppender.appendProviderAccount("grok", block);
1455
+ deps.grokSessions.settle(sessionId, "done");
1456
+ }
1457
+ function handleGrokOAuthCancel(sessionId, deps) {
1458
+ if (!deps.grokSessions.cancel(sessionId)) return err3(404, "unknown or expired grok sign-in session");
1459
+ return { status: 200, body: { ok: true } };
1460
+ }
1461
+ function handleGrokOAuthStatus(sessionId, deps) {
1462
+ const s = deps.grokSessions.get(sessionId);
1463
+ if (!s) return err3(404, "unknown or expired grok sign-in session");
1464
+ return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
1465
+ }
1466
+
1467
+ // src/admin/accountsCopilotOAuth.ts
1468
+ import { copilotOAuth } from "@omnicross/subscriptions";
1469
+ function err4(status, message) {
1470
+ return { status, body: { error: { type: "admin_api_error", message } } };
1471
+ }
1472
+ var DEFAULT_COPILOT_OAUTH_TTL_MS = 15 * 6e4;
1473
+ async function handleCopilotOAuthStart(deps, enterpriseUrlInput) {
1474
+ if (deps.copilotSessions.isBusy()) {
1475
+ return err4(409, "a copilot sign-in is already in progress \u2014 finish it in the browser or cancel it");
1476
+ }
1477
+ let enterpriseUrl;
1478
+ if (typeof enterpriseUrlInput === "string" && enterpriseUrlInput.trim()) {
1479
+ try {
1480
+ enterpriseUrl = copilotOAuth.normalizeCopilotEnterpriseDomain(enterpriseUrlInput);
1481
+ } catch (e) {
1482
+ const reason = e instanceof Error ? e.message : "invalid GitHub Enterprise domain";
1483
+ return err4(400, `copilot ${reason}`);
1484
+ }
1485
+ }
1486
+ const fetchImpl = deps.oauthExchangeFetch("copilot");
1487
+ let authorization;
1488
+ try {
1489
+ authorization = await copilotOAuth.requestCopilotDeviceAuthorization(fetchImpl, enterpriseUrl);
1490
+ } catch (e) {
1491
+ const reason = e instanceof Error ? e.message : "device authorization failed";
1492
+ return err4(502, `copilot device authorization failed: ${reason}`);
1493
+ }
1494
+ const { sessionId, signal } = deps.copilotSessions.begin();
1495
+ void runCopilotDevicePoll(sessionId, authorization.deviceCode, signal, deps, enterpriseUrl).catch((e) => {
1496
+ const reason = e instanceof Error ? e.message : "copilot sign-in failed";
1497
+ deps.copilotSessions.settle(sessionId, "error", reason);
1498
+ });
1499
+ return {
1500
+ status: 200,
1501
+ body: {
1502
+ authUrl: authorization.verificationUri,
1503
+ userCode: authorization.userCode,
1504
+ sessionId,
1505
+ ...enterpriseUrl ? { enterpriseUrl } : {}
1506
+ }
1507
+ };
1508
+ }
1509
+ async function runCopilotDevicePoll(sessionId, deviceCode, signal, deps, enterpriseUrl) {
1510
+ const fetchImpl = deps.oauthExchangeFetch("copilot");
1511
+ const result = await copilotOAuth.awaitCopilotDeviceToken(
1512
+ { userCode: "", deviceCode, verificationUri: "", interval: 5, expiresIn: 900 },
1513
+ fetchImpl,
1514
+ {
1515
+ deadlineMs: DEFAULT_COPILOT_OAUTH_TTL_MS,
1516
+ ...enterpriseUrl ? { enterpriseUrl } : {},
1517
+ sleep: (ms) => new Promise((resolve11, reject) => {
1518
+ const onAbort = () => {
1519
+ clearTimeout(timer);
1520
+ reject(new Error("login: cancelled"));
1521
+ };
1522
+ const timer = setTimeout(() => {
1523
+ signal.removeEventListener("abort", onAbort);
1524
+ resolve11();
1525
+ }, ms);
1526
+ signal.addEventListener("abort", onAbort, { once: true });
1527
+ })
1528
+ }
1529
+ );
1530
+ const identity = await copilotOAuth.fetchCopilotIdentity(result.accessToken, fetchImpl, enterpriseUrl);
1531
+ const apiEndpoint = await copilotOAuth.discoverCopilotApiEndpoint(result.accessToken, fetchImpl, enterpriseUrl);
1532
+ await copilotOAuth.enableAllCopilotModels(
1533
+ result.accessToken,
1534
+ { apiEndpoint, ...enterpriseUrl ? { enterpriseUrl } : {} },
1535
+ fetchImpl
1536
+ );
1537
+ const block = {
1538
+ authMethod: "oauth",
1539
+ status: "authorized",
1540
+ accessToken: result.accessToken,
1541
+ refreshToken: result.accessToken,
1542
+ expiresAt: new Date(Date.now() + copilotOAuth.COPILOT_FAR_FUTURE_MS).toISOString(),
1543
+ ...identity.accountId ? { accountId: identity.accountId } : {},
1544
+ ...identity.email ? { email: identity.email } : {},
1545
+ ...apiEndpoint ? { apiEndpoint } : {},
1546
+ ...enterpriseUrl ? { enterpriseUrl } : {},
1547
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
1548
+ };
1549
+ await deps.subscriptionAccountAppender.appendProviderAccount("copilot", block);
1550
+ deps.copilotSessions.settle(sessionId, "done");
1551
+ }
1552
+ function handleCopilotOAuthCancel(sessionId, deps) {
1553
+ if (!deps.copilotSessions.cancel(sessionId)) {
1554
+ return err4(404, "unknown or expired copilot sign-in session");
1555
+ }
1556
+ return { status: 200, body: { ok: true } };
1557
+ }
1558
+ function handleCopilotOAuthStatus(sessionId, deps) {
1559
+ const s = deps.copilotSessions.get(sessionId);
1560
+ if (!s) return err4(404, "unknown or expired copilot sign-in session");
1561
+ return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
1562
+ }
1563
+
1310
1564
  // src/allowance/AccountAllowanceService.ts
1311
1565
  import {
1312
- getSharedAccountAllowanceStore as getSharedAccountAllowanceStore2
1566
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore7
1313
1567
  } from "@omnicross/core/pipeline/AccountAllowanceStore";
1314
1568
  import {
1315
1569
  getSharedAccountAllowanceScheduling
@@ -1343,13 +1597,11 @@ function secondsUntil(instant, now) {
1343
1597
  function windowFromPayload(id, payload, now) {
1344
1598
  const usedPercent = finitePercent(payload?.utilization);
1345
1599
  const resetsAt = isoInstant(payload?.resets_at);
1346
- const isSonnet = id === "seven-day-sonnet";
1347
1600
  const isFiveHour = id === "five-hour";
1348
1601
  return {
1349
1602
  id,
1350
- label: isFiveHour ? "5 hours" : isSonnet ? "7 days \xB7 Sonnet" : "7 days",
1351
- scope: isSonnet ? "model-family" : "all",
1352
- modelFamily: isSonnet ? "sonnet" : void 0,
1603
+ label: isFiveHour ? "5 hours" : "7 days",
1604
+ scope: "all",
1353
1605
  usedPercent,
1354
1606
  windowMinutes: isFiveHour ? 5 * 60 : 7 * 24 * 60,
1355
1607
  resetsAt,
@@ -1357,6 +1609,44 @@ function windowFromPayload(id, payload, now) {
1357
1609
  state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
1358
1610
  };
1359
1611
  }
1612
+ function limitEntryWindow(entries, kind) {
1613
+ const entry = entries.find((candidate) => candidate.kind === kind);
1614
+ if (!entry) return void 0;
1615
+ return { utilization: entry.percent, resets_at: entry.resets_at };
1616
+ }
1617
+ function slugifyDisplayName(name) {
1618
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1619
+ }
1620
+ function scopedWeeklyWindows(entries, now) {
1621
+ const seen = /* @__PURE__ */ new Set();
1622
+ const windows = [];
1623
+ for (const entry of entries) {
1624
+ if (entry.kind !== "weekly_scoped") continue;
1625
+ const displayName = typeof entry.scope?.model?.display_name === "string" && entry.scope.model.display_name.trim() ? entry.scope.model.display_name.trim() : void 0;
1626
+ if (!displayName) continue;
1627
+ const slug = slugifyDisplayName(displayName);
1628
+ if (!slug || seen.has(slug)) continue;
1629
+ seen.add(slug);
1630
+ const usedPercent = finitePercent(entry.percent);
1631
+ const resetsAt = isoInstant(entry.resets_at);
1632
+ windows.push({
1633
+ id: `seven-day-${slug}`,
1634
+ label: `7 days \xB7 ${displayName}`,
1635
+ scope: "model-family",
1636
+ modelFamily: slug,
1637
+ usedPercent,
1638
+ windowMinutes: 7 * 24 * 60,
1639
+ resetsAt,
1640
+ remainingSeconds: secondsUntil(resetsAt, now),
1641
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
1642
+ });
1643
+ }
1644
+ return windows;
1645
+ }
1646
+ function parseLimitEntries(raw) {
1647
+ if (!Array.isArray(raw)) return [];
1648
+ return raw.filter((entry) => !!entry && typeof entry === "object");
1649
+ }
1360
1650
  function emptyClaudeWindows(state) {
1361
1651
  return [
1362
1652
  {
@@ -1374,15 +1664,6 @@ function emptyClaudeWindows(state) {
1374
1664
  usedPercent: null,
1375
1665
  windowMinutes: 7 * 24 * 60,
1376
1666
  state
1377
- },
1378
- {
1379
- id: "seven-day-sonnet",
1380
- label: "7 days \xB7 Sonnet",
1381
- scope: "model-family",
1382
- modelFamily: "sonnet",
1383
- usedPercent: null,
1384
- windowMinutes: 7 * 24 * 60,
1385
- state
1386
1667
  }
1387
1668
  ];
1388
1669
  }
@@ -1463,6 +1744,9 @@ var ClaudeAllowanceCollector = class {
1463
1744
  }
1464
1745
  const now = this.now();
1465
1746
  const usage = payload;
1747
+ const limitEntries = parseLimitEntries(usage.limits);
1748
+ const fiveHour = usage.five_hour ?? limitEntryWindow(limitEntries, "session");
1749
+ const sevenDay = usage.seven_day ?? limitEntryWindow(limitEntries, "weekly_all");
1466
1750
  const snapshot = {
1467
1751
  providerId: "claude",
1468
1752
  accountId,
@@ -1470,10 +1754,10 @@ var ClaudeAllowanceCollector = class {
1470
1754
  observedAt: new Date(now).toISOString(),
1471
1755
  expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
1472
1756
  windows: [
1473
- windowFromPayload("five-hour", usage.five_hour, now),
1474
- windowFromPayload("seven-day", usage.seven_day, now),
1475
- windowFromPayload("seven-day-sonnet", usage.seven_day_sonnet, now)
1476
- ]
1757
+ windowFromPayload("five-hour", fiveHour, now),
1758
+ windowFromPayload("seven-day", sevenDay, now),
1759
+ ...scopedWeeklyWindows(limitEntries, now)
1760
+ ].slice(0, 8)
1477
1761
  };
1478
1762
  this.store.set(snapshot);
1479
1763
  return snapshot;
@@ -1530,6 +1814,1086 @@ var ClaudeAllowanceCollector = class {
1530
1814
  }
1531
1815
  };
1532
1816
 
1817
+ // src/allowance/CodexAllowanceCollector.ts
1818
+ import {
1819
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore2
1820
+ } from "@omnicross/core/pipeline/AccountAllowanceStore";
1821
+ import { fetchUpstream as fetchUpstream2 } from "@omnicross/core/pipeline/upstreamFetch";
1822
+ var CODEX_ALLOWANCE_CACHE_MS = 5 * 6e4;
1823
+ var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
1824
+ var CODEX_CLI_USER_AGENT = "codex_cli_rs/0.144.5";
1825
+ function finiteNumber(value) {
1826
+ if (value === null || value === void 0 || value === "") return null;
1827
+ const parsed = typeof value === "number" ? value : Number(value);
1828
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
1829
+ }
1830
+ function finitePercent2(value) {
1831
+ const parsed = finiteNumber(value);
1832
+ return parsed !== null && parsed <= 100 ? parsed : null;
1833
+ }
1834
+ function epochMs(value) {
1835
+ return value > 1e11 ? value : value * 1e3;
1836
+ }
1837
+ function secondsUntil2(instant, now) {
1838
+ if (!instant) return void 0;
1839
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
1840
+ }
1841
+ function decodeJwtClaims(token) {
1842
+ const parts = token.split(".");
1843
+ if (parts.length !== 3) return void 0;
1844
+ try {
1845
+ const json2 = Buffer.from(parts[1], "base64url").toString("utf8");
1846
+ const parsed = JSON.parse(json2);
1847
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
1848
+ } catch {
1849
+ return void 0;
1850
+ }
1851
+ }
1852
+ function chatgptAccountIdFromClaims(claims) {
1853
+ const auth = claims?.["https://api.openai.com/auth"];
1854
+ if (!auth || typeof auth !== "object") return void 0;
1855
+ const accountId = auth.chatgpt_account_id;
1856
+ return typeof accountId === "string" && accountId.trim() ? accountId.trim() : void 0;
1857
+ }
1858
+ function resolveCodexChatGptAccountId(tokens) {
1859
+ if (tokens.accountId?.trim()) return tokens.accountId.trim();
1860
+ if (tokens.idToken) {
1861
+ const fromIdToken = chatgptAccountIdFromClaims(decodeJwtClaims(tokens.idToken));
1862
+ if (fromIdToken) return fromIdToken;
1863
+ }
1864
+ if (tokens.accessToken) {
1865
+ return chatgptAccountIdFromClaims(decodeJwtClaims(tokens.accessToken));
1866
+ }
1867
+ return void 0;
1868
+ }
1869
+ function windowFromPayload2(id, payload, now) {
1870
+ const usedPercent = finitePercent2(payload?.used_percent);
1871
+ const resetAtSeconds = finiteNumber(payload?.reset_at);
1872
+ const resetAfterSeconds = finiteNumber(payload?.reset_after_seconds);
1873
+ const windowSeconds = finiteNumber(payload?.limit_window_seconds);
1874
+ const resetsAt = resetAtSeconds !== null && resetAtSeconds > 0 ? new Date(epochMs(resetAtSeconds)).toISOString() : resetAfterSeconds !== null && resetAfterSeconds > 0 ? new Date(now + resetAfterSeconds * 1e3).toISOString() : void 0;
1875
+ const windowMinutes = windowSeconds !== null && windowSeconds > 0 ? Math.round(windowSeconds / 60) : void 0;
1876
+ return {
1877
+ id,
1878
+ label: id === "primary" ? "Primary" : "Secondary",
1879
+ scope: "all",
1880
+ usedPercent,
1881
+ ...windowMinutes !== void 0 ? { windowMinutes } : {},
1882
+ ...resetsAt !== void 0 ? { resetsAt } : {},
1883
+ remainingSeconds: secondsUntil2(resetsAt, now),
1884
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
1885
+ };
1886
+ }
1887
+ var CodexAllowanceCollector = class {
1888
+ constructor(credentials, store = getSharedAccountAllowanceStore2(), fetchImpl = (url, init, accountId) => fetchUpstream2(url, init, { providerId: "codex", accountId, redactBodies: true }), now = Date.now) {
1889
+ this.credentials = credentials;
1890
+ this.store = store;
1891
+ this.fetchImpl = fetchImpl;
1892
+ this.now = now;
1893
+ }
1894
+ credentials;
1895
+ store;
1896
+ fetchImpl;
1897
+ now;
1898
+ inFlight = /* @__PURE__ */ new Map();
1899
+ async collectMany(accounts, options = {}) {
1900
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
1901
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
1902
+ }
1903
+ collect(account, options = {}) {
1904
+ const now = this.now();
1905
+ const unsupported = account.tokens.authMethod !== "oauth";
1906
+ if (unsupported) {
1907
+ const existing = this.store.get("codex", account.id, now);
1908
+ if (existing?.windows.every((window) => window.state === "unsupported")) {
1909
+ return Promise.resolve(existing);
1910
+ }
1911
+ const snapshot = this.unsupportedSnapshot(account.id, now);
1912
+ this.store.set(snapshot);
1913
+ return Promise.resolve(snapshot);
1914
+ }
1915
+ const cached = this.store.get("codex", account.id, now);
1916
+ if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
1917
+ return Promise.resolve(cached);
1918
+ }
1919
+ const running = this.inFlight.get(account.id);
1920
+ if (running) return running;
1921
+ const promise = this.fetchAccount(account.id, account.tokens).catch(() => this.failureSnapshot(account.id, "codex_usage_request_failed", this.now())).finally(() => this.inFlight.delete(account.id));
1922
+ this.inFlight.set(account.id, promise);
1923
+ return promise;
1924
+ }
1925
+ /**
1926
+ * A response-header snapshot stays a valid cache hit only while fresh; an
1927
+ * active oauth-usage snapshot is honored on the same 5-minute cadence as
1928
+ * Claude's (the poll is cheap and quota is the scheduling input).
1929
+ */
1930
+ isCacheValid(snapshot, now, refreshAheadMs) {
1931
+ if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
1932
+ const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
1933
+ const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
1934
+ return Number.isFinite(expiresAt) && expiresAt > now + ahead;
1935
+ }
1936
+ async fetchAccount(accountId, tokens) {
1937
+ let accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
1938
+ if (!accessToken) {
1939
+ return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
1940
+ }
1941
+ let response = await this.request(accountId, accessToken, tokens);
1942
+ if (response.status === 401) {
1943
+ const refreshed = await this.credentials.refreshAccountToken("codex", accountId);
1944
+ if (!refreshed) {
1945
+ return this.failureSnapshot(accountId, "codex_usage_unauthorized", this.now());
1946
+ }
1947
+ accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
1948
+ if (!accessToken) {
1949
+ return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
1950
+ }
1951
+ response = await this.request(accountId, accessToken, tokens);
1952
+ }
1953
+ if (response.status === 403) {
1954
+ const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "codex_usage_unsupported");
1955
+ this.store.set(snapshot2);
1956
+ return snapshot2;
1957
+ }
1958
+ if (!response.ok) {
1959
+ return this.failureSnapshot(accountId, "codex_usage_http_error", this.now());
1960
+ }
1961
+ let payload;
1962
+ try {
1963
+ payload = await response.json();
1964
+ } catch {
1965
+ return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
1966
+ }
1967
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
1968
+ return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
1969
+ }
1970
+ const now = this.now();
1971
+ const usage = payload.rate_limit;
1972
+ const previous = this.store.get("codex", accountId, now);
1973
+ const snapshot = {
1974
+ providerId: "codex",
1975
+ accountId,
1976
+ source: "oauth-usage-api",
1977
+ observedAt: new Date(now).toISOString(),
1978
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
1979
+ windows: [
1980
+ windowFromPayload2("primary", usage?.primary_window ?? void 0, now),
1981
+ windowFromPayload2("secondary", usage?.secondary_window ?? void 0, now)
1982
+ ],
1983
+ // The wham payload has no ratio field; keep the passively-observed value.
1984
+ ...previous?.primaryOverSecondaryLimitPercent !== void 0 ? { primaryOverSecondaryLimitPercent: previous.primaryOverSecondaryLimitPercent } : {}
1985
+ };
1986
+ this.store.set(snapshot);
1987
+ return snapshot;
1988
+ }
1989
+ request(accountId, accessToken, tokens) {
1990
+ const headers = {
1991
+ Authorization: `Bearer ${accessToken}`,
1992
+ Accept: "application/json",
1993
+ "User-Agent": CODEX_CLI_USER_AGENT
1994
+ };
1995
+ const chatgptAccountId = resolveCodexChatGptAccountId(tokens);
1996
+ if (chatgptAccountId) headers["ChatGPT-Account-Id"] = chatgptAccountId;
1997
+ return this.fetchImpl(CODEX_USAGE_URL, {
1998
+ method: "GET",
1999
+ headers,
2000
+ signal: AbortSignal.timeout(15e3)
2001
+ }, accountId);
2002
+ }
2003
+ failureSnapshot(accountId, code, now) {
2004
+ const existing = this.store.get("codex", accountId, now);
2005
+ const snapshot = existing ? {
2006
+ ...existing,
2007
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
2008
+ windows: existing.windows.map((window) => ({
2009
+ ...window,
2010
+ state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
2011
+ })),
2012
+ lastErrorCode: code
2013
+ } : {
2014
+ providerId: "codex",
2015
+ accountId,
2016
+ source: "oauth-usage-api",
2017
+ observedAt: new Date(now).toISOString(),
2018
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
2019
+ windows: [
2020
+ { id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unavailable" },
2021
+ { id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unavailable" }
2022
+ ],
2023
+ lastErrorCode: code
2024
+ };
2025
+ this.store.set(snapshot);
2026
+ return snapshot;
2027
+ }
2028
+ unsupportedSnapshot(accountId, now, code = "codex_usage_unsupported_auth") {
2029
+ return {
2030
+ providerId: "codex",
2031
+ accountId,
2032
+ source: "oauth-usage-api",
2033
+ observedAt: new Date(now).toISOString(),
2034
+ windows: [
2035
+ { id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unsupported" },
2036
+ { id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unsupported" }
2037
+ ],
2038
+ lastErrorCode: code
2039
+ };
2040
+ }
2041
+ };
2042
+
2043
+ // src/allowance/KimiAllowanceCollector.ts
2044
+ import {
2045
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore3
2046
+ } from "@omnicross/core/pipeline/AccountAllowanceStore";
2047
+ import { fetchUpstream as fetchUpstream3 } from "@omnicross/core/pipeline/upstreamFetch";
2048
+ import { kimiFingerprintHeaders } from "@omnicross/subscriptions";
2049
+ var KIMI_ALLOWANCE_CACHE_MS = 5 * 6e4;
2050
+ var KIMI_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
2051
+ function finiteNumber2(value) {
2052
+ if (value === null || value === void 0 || value === "") return void 0;
2053
+ const parsed = typeof value === "number" ? value : Number(value);
2054
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
2055
+ }
2056
+ function isRecord(value) {
2057
+ return !!value && typeof value === "object" && !Array.isArray(value);
2058
+ }
2059
+ function parseResetMs(row, nowMs) {
2060
+ for (const key of ["reset_at", "resetAt", "reset_time", "resetTime"]) {
2061
+ const value = row[key];
2062
+ if (typeof value === "string" && value.trim()) {
2063
+ const parsed = Date.parse(value);
2064
+ if (Number.isFinite(parsed)) return parsed;
2065
+ }
2066
+ const numeric = finiteNumber2(value);
2067
+ if (numeric !== void 0 && numeric > 1e9) {
2068
+ return numeric > 1e12 ? numeric : numeric * 1e3;
2069
+ }
2070
+ }
2071
+ for (const key of ["reset_in", "resetIn", "ttl", "window"]) {
2072
+ const seconds = finiteNumber2(row[key]);
2073
+ if (seconds !== void 0) return nowMs + seconds * 1e3;
2074
+ }
2075
+ return void 0;
2076
+ }
2077
+ var MINUTE_MS = 6e4;
2078
+ var HOUR_MS = 36e5;
2079
+ var DAY_MS = 864e5;
2080
+ function canonicalWindow(durationMs) {
2081
+ if (durationMs === 5 * HOUR_MS) return { id: "five-hour", label: "5 hours", minutes: 300 };
2082
+ if (durationMs === 7 * DAY_MS) return { id: "seven-day", label: "7 days", minutes: 10080 };
2083
+ if (durationMs > 0 && durationMs % DAY_MS === 0) {
2084
+ const days = durationMs / DAY_MS;
2085
+ return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
2086
+ }
2087
+ if (durationMs > 0 && durationMs % HOUR_MS === 0) {
2088
+ const hours = durationMs / HOUR_MS;
2089
+ return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
2090
+ }
2091
+ return void 0;
2092
+ }
2093
+ function secondsUntil3(instant, now) {
2094
+ if (!instant) return void 0;
2095
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
2096
+ }
2097
+ function windowFromRow(row, fallback, now) {
2098
+ const usedPercent = row?.limit !== void 0 && row.limit > 0 && row.used !== void 0 ? Math.round(Math.min(100, row.used / row.limit * 100) * 10) / 10 : null;
2099
+ const resetsAt = row?.resetsAtMs !== void 0 ? new Date(row.resetsAtMs).toISOString() : void 0;
2100
+ return {
2101
+ id: fallback.id,
2102
+ label: fallback.label,
2103
+ scope: "all",
2104
+ usedPercent,
2105
+ windowMinutes: fallback.minutes,
2106
+ ...resetsAt !== void 0 ? { resetsAt } : {},
2107
+ remainingSeconds: secondsUntil3(resetsAt, now),
2108
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
2109
+ };
2110
+ }
2111
+ function parseKimiUsagePayload(payload, now) {
2112
+ if (!isRecord(payload)) return [];
2113
+ const byId = /* @__PURE__ */ new Map();
2114
+ const rowFrom = (data) => {
2115
+ const limit = finiteNumber2(data["limit"]);
2116
+ let used = finiteNumber2(data["used"]);
2117
+ const remaining = finiteNumber2(data["remaining"]);
2118
+ if (used === void 0 && remaining !== void 0 && limit !== void 0) {
2119
+ used = limit - remaining;
2120
+ }
2121
+ let windowDurationMs;
2122
+ const windowData = isRecord(data["window"]) ? data["window"] : void 0;
2123
+ const duration = finiteNumber2(windowData?.["duration"]);
2124
+ const timeUnit = typeof windowData?.["timeUnit"] === "string" ? windowData["timeUnit"].toUpperCase() : "";
2125
+ if (duration !== void 0) {
2126
+ if (timeUnit.includes("MINUTE")) windowDurationMs = duration * MINUTE_MS;
2127
+ else if (timeUnit.includes("HOUR")) windowDurationMs = duration * HOUR_MS;
2128
+ else if (timeUnit.includes("DAY")) windowDurationMs = duration * DAY_MS;
2129
+ else if (timeUnit.includes("WEEK")) windowDurationMs = duration * 7 * DAY_MS;
2130
+ else if (timeUnit.includes("SECOND")) windowDurationMs = duration * 1e3;
2131
+ }
2132
+ const resetsAtMs = parseResetMs(windowData && parseResetMs(windowData, now) !== void 0 ? windowData : data, now);
2133
+ return { used, limit, remaining, ...resetsAtMs !== void 0 ? { resetsAtMs } : {}, ...windowDurationMs !== void 0 ? { windowDurationMs } : {} };
2134
+ };
2135
+ if (isRecord(payload["usage"])) {
2136
+ const row = rowFrom(payload["usage"]);
2137
+ const window = windowFromRow({ ...row, resetsAtMs: row.resetsAtMs }, { id: "seven-day", label: "7 days", minutes: 10080 }, now);
2138
+ byId.set("seven-day", window);
2139
+ }
2140
+ if (Array.isArray(payload["limits"])) {
2141
+ for (const item of payload["limits"]) {
2142
+ if (!isRecord(item)) continue;
2143
+ const detail = isRecord(item["detail"]) ? item["detail"] : item;
2144
+ const row = rowFrom(detail);
2145
+ const canonical = row.windowDurationMs !== void 0 ? canonicalWindow(row.windowDurationMs) : void 0;
2146
+ if (!canonical) continue;
2147
+ const window = windowFromRow(row, canonical, now);
2148
+ const existing = byId.get(canonical.id);
2149
+ if (!existing || (window.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
2150
+ byId.set(canonical.id, window);
2151
+ }
2152
+ }
2153
+ }
2154
+ return [...byId.values()].sort((a, b) => (a.windowMinutes ?? Infinity) - (b.windowMinutes ?? Infinity)).slice(0, 4);
2155
+ }
2156
+ var KimiAllowanceCollector = class {
2157
+ constructor(credentials, store = getSharedAccountAllowanceStore3(), fetchImpl = (url, init, accountId) => fetchUpstream3(url, init, { providerId: "kimi", accountId, redactBodies: true }), now = Date.now) {
2158
+ this.credentials = credentials;
2159
+ this.store = store;
2160
+ this.fetchImpl = fetchImpl;
2161
+ this.now = now;
2162
+ }
2163
+ credentials;
2164
+ store;
2165
+ fetchImpl;
2166
+ now;
2167
+ inFlight = /* @__PURE__ */ new Map();
2168
+ async collectMany(accounts, options = {}) {
2169
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
2170
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
2171
+ }
2172
+ collect(account, options = {}) {
2173
+ const now = this.now();
2174
+ if (account.tokens.authMethod !== "oauth") {
2175
+ const existing = this.store.get("kimi", account.id, now);
2176
+ if (existing?.windows.every((window) => window.state === "unsupported")) {
2177
+ return Promise.resolve(existing);
2178
+ }
2179
+ const snapshot = this.unsupportedSnapshot(account.id, now);
2180
+ this.store.set(snapshot);
2181
+ return Promise.resolve(snapshot);
2182
+ }
2183
+ const cached = this.store.get("kimi", account.id, now);
2184
+ if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
2185
+ return Promise.resolve(cached);
2186
+ }
2187
+ const running = this.inFlight.get(account.id);
2188
+ if (running) return running;
2189
+ const promise = this.fetchAccount(account.id, account.tokens).catch(() => this.failureSnapshot(account.id, "kimi_usage_request_failed", this.now())).finally(() => this.inFlight.delete(account.id));
2190
+ this.inFlight.set(account.id, promise);
2191
+ return promise;
2192
+ }
2193
+ isCacheValid(snapshot, now, refreshAheadMs) {
2194
+ if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
2195
+ const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
2196
+ const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
2197
+ return Number.isFinite(expiresAt) && expiresAt > now + ahead;
2198
+ }
2199
+ async fetchAccount(accountId, tokens) {
2200
+ let accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
2201
+ if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
2202
+ let response = await this.request(accountId, accessToken, tokens);
2203
+ if (response.status === 401) {
2204
+ const refreshed = await this.credentials.refreshAccountToken("kimi", accountId);
2205
+ if (!refreshed) return this.failureSnapshot(accountId, "kimi_usage_unauthorized", this.now());
2206
+ accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
2207
+ if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
2208
+ response = await this.request(accountId, accessToken, tokens);
2209
+ }
2210
+ if (response.status === 403) {
2211
+ const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "kimi_usage_unsupported");
2212
+ this.store.set(snapshot2);
2213
+ return snapshot2;
2214
+ }
2215
+ if (!response.ok) return this.failureSnapshot(accountId, "kimi_usage_http_error", this.now());
2216
+ let payload;
2217
+ try {
2218
+ payload = await response.json();
2219
+ } catch {
2220
+ return this.failureSnapshot(accountId, "kimi_usage_invalid_response", this.now());
2221
+ }
2222
+ const now = this.now();
2223
+ const windows = parseKimiUsagePayload(payload, now);
2224
+ const snapshot = {
2225
+ providerId: "kimi",
2226
+ accountId,
2227
+ source: "oauth-usage-api",
2228
+ observedAt: new Date(now).toISOString(),
2229
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
2230
+ windows: windows.length > 0 ? windows : [
2231
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
2232
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
2233
+ ],
2234
+ ...windows.length > 0 ? {} : { lastErrorCode: "kimi_usage_invalid_response" }
2235
+ };
2236
+ this.store.set(snapshot);
2237
+ return snapshot;
2238
+ }
2239
+ request(accountId, accessToken, tokens) {
2240
+ return this.fetchImpl(KIMI_USAGE_URL, {
2241
+ method: "GET",
2242
+ headers: {
2243
+ Authorization: `Bearer ${accessToken}`,
2244
+ Accept: "application/json",
2245
+ ...kimiFingerprintHeaders(tokens.deviceId)
2246
+ },
2247
+ signal: AbortSignal.timeout(15e3)
2248
+ }, accountId);
2249
+ }
2250
+ failureSnapshot(accountId, code, now) {
2251
+ const existing = this.store.get("kimi", accountId, now);
2252
+ const snapshot = existing ? {
2253
+ ...existing,
2254
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
2255
+ windows: existing.windows.map((window) => ({
2256
+ ...window,
2257
+ state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
2258
+ })),
2259
+ lastErrorCode: code
2260
+ } : {
2261
+ providerId: "kimi",
2262
+ accountId,
2263
+ source: "oauth-usage-api",
2264
+ observedAt: new Date(now).toISOString(),
2265
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
2266
+ windows: [
2267
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
2268
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
2269
+ ],
2270
+ lastErrorCode: code
2271
+ };
2272
+ this.store.set(snapshot);
2273
+ return snapshot;
2274
+ }
2275
+ unsupportedSnapshot(accountId, now, code = "kimi_usage_unsupported_auth") {
2276
+ return {
2277
+ providerId: "kimi",
2278
+ accountId,
2279
+ source: "oauth-usage-api",
2280
+ observedAt: new Date(now).toISOString(),
2281
+ windows: [
2282
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unsupported" },
2283
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unsupported" }
2284
+ ],
2285
+ lastErrorCode: code
2286
+ };
2287
+ }
2288
+ };
2289
+
2290
+ // src/allowance/GrokAllowanceCollector.ts
2291
+ import {
2292
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore4
2293
+ } from "@omnicross/core/pipeline/AccountAllowanceStore";
2294
+ import { fetchUpstream as fetchUpstream4 } from "@omnicross/core/pipeline/upstreamFetch";
2295
+ var GROK_ALLOWANCE_CACHE_MS = 5 * 6e4;
2296
+ var GROK_BILLING_BASE = "https://cli-chat-proxy.grok.com";
2297
+ var GROK_BILLING_CREDITS_URL = `${GROK_BILLING_BASE}/v1/billing?format=credits`;
2298
+ var GROK_BILLING_MONTHLY_URL = `${GROK_BILLING_BASE}/v1/billing`;
2299
+ function isRecord2(value) {
2300
+ return !!value && typeof value === "object" && !Array.isArray(value);
2301
+ }
2302
+ function finiteNumber3(value) {
2303
+ if (value === null || value === void 0 || value === "") return void 0;
2304
+ const parsed = typeof value === "number" ? value : Number(value);
2305
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
2306
+ }
2307
+ function percent(value) {
2308
+ const parsed = finiteNumber3(value);
2309
+ return parsed !== void 0 && parsed <= 100 ? parsed : void 0;
2310
+ }
2311
+ function onDemandAmount(value) {
2312
+ return isRecord2(value) ? finiteNumber3(value["val"]) : void 0;
2313
+ }
2314
+ function confirmsNoMonthlyQuota(raw) {
2315
+ const limit = onDemandAmount(raw["monthlyLimit"]);
2316
+ if (limit !== void 0) return limit === 0;
2317
+ return parseWeeklyConfig(raw)?.inferredPercent === true;
2318
+ }
2319
+ function parseWeeklyConfig(raw) {
2320
+ const period = isRecord2(raw["currentPeriod"]) ? raw["currentPeriod"] : void 0;
2321
+ if (!period) return null;
2322
+ const start = typeof period["start"] === "string" ? Date.parse(period["start"]) : Number.NaN;
2323
+ const end = typeof period["end"] === "string" ? Date.parse(period["end"]) : Number.NaN;
2324
+ const type = typeof period["type"] === "string" ? period["type"] : "";
2325
+ if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) return null;
2326
+ if (!type.toUpperCase().includes("WEEK")) return null;
2327
+ const inferred = raw["creditUsagePercent"] === void 0 || raw["creditUsagePercent"] === null;
2328
+ let creditUsagePercent;
2329
+ if (inferred) {
2330
+ creditUsagePercent = end > Date.now() ? 0 : void 0;
2331
+ } else {
2332
+ creditUsagePercent = percent(raw["creditUsagePercent"]);
2333
+ }
2334
+ if (creditUsagePercent === void 0) return null;
2335
+ return {
2336
+ creditUsagePercent,
2337
+ inferredPercent: inferred,
2338
+ resetsAtMs: end,
2339
+ unified: raw["isUnifiedBillingUser"] === true
2340
+ };
2341
+ }
2342
+ function parseMonthlyConfig(raw) {
2343
+ const start = typeof raw["billingPeriodStart"] === "string" ? Date.parse(raw["billingPeriodStart"]) : Number.NaN;
2344
+ const end = typeof raw["billingPeriodEnd"] === "string" ? Date.parse(raw["billingPeriodEnd"]) : Number.NaN;
2345
+ if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) return null;
2346
+ const limit = onDemandAmount(raw["monthlyLimit"]);
2347
+ const used = onDemandAmount(raw["used"]);
2348
+ if (limit === void 0 || limit <= 0 || used === void 0) return null;
2349
+ return { used, limit, periodStartMs: start, periodEndMs: end };
2350
+ }
2351
+ function secondsUntil4(instant, now) {
2352
+ if (!instant) return void 0;
2353
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
2354
+ }
2355
+ var MINUTE_MS2 = 6e4;
2356
+ var DAY_MS2 = 864e5;
2357
+ var WEEK_MINUTES = 7 * 24 * 60;
2358
+ function weeklyWindow(config, now) {
2359
+ const resetsAt = new Date(config.resetsAtMs).toISOString();
2360
+ return {
2361
+ id: "seven-day",
2362
+ label: "7 days",
2363
+ scope: "all",
2364
+ usedPercent: config.creditUsagePercent,
2365
+ windowMinutes: WEEK_MINUTES,
2366
+ resetsAt,
2367
+ remainingSeconds: secondsUntil4(resetsAt, now),
2368
+ state: "fresh"
2369
+ };
2370
+ }
2371
+ function monthlyWindow(config, now) {
2372
+ const resetsAt = new Date(config.periodEndMs).toISOString();
2373
+ const days = Math.max(1, Math.round((config.periodEndMs - config.periodStartMs) / DAY_MS2));
2374
+ return {
2375
+ id: "thirty-day",
2376
+ label: days === 30 || days === 31 ? "30 days" : `${days} days`,
2377
+ scope: "all",
2378
+ usedPercent: Math.round(Math.min(100, config.used / config.limit * 100) * 10) / 10,
2379
+ windowMinutes: Math.round((config.periodEndMs - config.periodStartMs) / MINUTE_MS2),
2380
+ resetsAt,
2381
+ remainingSeconds: secondsUntil4(resetsAt, now),
2382
+ state: "fresh"
2383
+ };
2384
+ }
2385
+ function onDemandWindow(raw) {
2386
+ const cap = onDemandAmount(raw["onDemandCap"]);
2387
+ const used = onDemandAmount(raw["onDemandUsed"]);
2388
+ if (cap === void 0 || cap <= 0 || used === void 0) return null;
2389
+ return {
2390
+ id: "on-demand",
2391
+ label: "On-demand",
2392
+ scope: "all",
2393
+ usedPercent: Math.round(Math.min(100, used / cap * 100) * 10) / 10,
2394
+ state: "fresh"
2395
+ };
2396
+ }
2397
+ async function probeBilling(url, accessToken, accountId, fetchImpl) {
2398
+ try {
2399
+ const response = await fetchImpl(url, {
2400
+ method: "GET",
2401
+ headers: {
2402
+ Authorization: `Bearer ${accessToken}`,
2403
+ Accept: "application/json",
2404
+ "X-XAI-Token-Auth": "xai-grok-cli"
2405
+ },
2406
+ redirect: "error",
2407
+ signal: AbortSignal.timeout(15e3)
2408
+ }, accountId);
2409
+ if (!response.ok) return { status: response.status, payload: null };
2410
+ const payload = await response.json();
2411
+ return { status: response.status, payload: isRecord2(payload) ? payload : null };
2412
+ } catch {
2413
+ return { status: 0, payload: null };
2414
+ }
2415
+ }
2416
+ function parseGrokBillingPayloads(creditsPayload, monthlyPayload, now) {
2417
+ const creditsConfig = isRecord2(creditsPayload?.["config"]) ? creditsPayload["config"] : null;
2418
+ const monthlyConfig = isRecord2(monthlyPayload?.["config"]) ? monthlyPayload["config"] : null;
2419
+ let weekly = creditsConfig ? parseWeeklyConfig(creditsConfig) : null;
2420
+ const unifiedFlag = creditsConfig?.["isUnifiedBillingUser"] === true;
2421
+ let monthly = monthlyConfig ? parseMonthlyConfig(monthlyConfig) : null;
2422
+ if (weekly?.inferredPercent && unifiedFlag) {
2423
+ if (monthly) {
2424
+ weekly = null;
2425
+ } else if (!monthlyConfig || !confirmsNoMonthlyQuota(monthlyConfig)) {
2426
+ weekly = null;
2427
+ }
2428
+ }
2429
+ const windows = [];
2430
+ if (weekly) windows.push(weeklyWindow(weekly, now));
2431
+ if (monthly) windows.push(monthlyWindow(monthly, now));
2432
+ const onDemandSource = monthly && monthlyConfig ? monthlyConfig : creditsConfig;
2433
+ const onDemand = onDemandSource ? onDemandWindow(onDemandSource) : null;
2434
+ if (onDemand) windows.push(onDemand);
2435
+ return windows.length > 0 ? windows : null;
2436
+ }
2437
+ var GrokAllowanceCollector = class {
2438
+ constructor(credentials, store = getSharedAccountAllowanceStore4(), fetchImpl = (url, init, accountId) => fetchUpstream4(url, init, { providerId: "grok", accountId, redactBodies: true }), now = Date.now) {
2439
+ this.credentials = credentials;
2440
+ this.store = store;
2441
+ this.fetchImpl = fetchImpl;
2442
+ this.now = now;
2443
+ }
2444
+ credentials;
2445
+ store;
2446
+ fetchImpl;
2447
+ now;
2448
+ inFlight = /* @__PURE__ */ new Map();
2449
+ async collectMany(accounts, options = {}) {
2450
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
2451
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
2452
+ }
2453
+ collect(account, options = {}) {
2454
+ const now = this.now();
2455
+ if (account.tokens.authMethod !== "oauth") {
2456
+ const existing = this.store.get("grok", account.id, now);
2457
+ if (existing?.windows.every((window) => window.state === "unsupported")) {
2458
+ return Promise.resolve(existing);
2459
+ }
2460
+ const snapshot = this.unsupportedSnapshot(account.id, now);
2461
+ this.store.set(snapshot);
2462
+ return Promise.resolve(snapshot);
2463
+ }
2464
+ const cached = this.store.get("grok", account.id, now);
2465
+ if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
2466
+ return Promise.resolve(cached);
2467
+ }
2468
+ const running = this.inFlight.get(account.id);
2469
+ if (running) return running;
2470
+ const promise = this.fetchAccount(account.id).catch(() => this.failureSnapshot(account.id, "grok_usage_request_failed", this.now())).finally(() => this.inFlight.delete(account.id));
2471
+ this.inFlight.set(account.id, promise);
2472
+ return promise;
2473
+ }
2474
+ isCacheValid(snapshot, now, refreshAheadMs) {
2475
+ if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
2476
+ const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
2477
+ const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
2478
+ return Number.isFinite(expiresAt) && expiresAt > now + ahead;
2479
+ }
2480
+ async fetchAccount(accountId) {
2481
+ const probe = async () => {
2482
+ const accessToken = await this.credentials.getAccessTokenForAccount("grok", accountId);
2483
+ if (!accessToken) return { unauthorized: true, windows: null };
2484
+ const credits = await probeBilling(GROK_BILLING_CREDITS_URL, accessToken, accountId, this.fetchImpl);
2485
+ if (credits.status === 401 || credits.status === 403) return { unauthorized: true, windows: null };
2486
+ const creditsConfig = isRecord2(credits.payload?.["config"]) ? credits.payload["config"] : null;
2487
+ const weekly = creditsConfig ? parseWeeklyConfig(creditsConfig) : null;
2488
+ const monthly = !weekly || creditsConfig?.["isUnifiedBillingUser"] === true ? await probeBilling(GROK_BILLING_MONTHLY_URL, accessToken, accountId, this.fetchImpl) : { status: 200, payload: null };
2489
+ if (monthly.status === 401 || monthly.status === 403) return { unauthorized: true, windows: null };
2490
+ return {
2491
+ unauthorized: false,
2492
+ windows: parseGrokBillingPayloads(credits.payload, monthly.payload, this.now())
2493
+ };
2494
+ };
2495
+ let result = await probe();
2496
+ if (result.unauthorized) {
2497
+ const refreshed = await this.credentials.refreshAccountToken("grok", accountId);
2498
+ if (!refreshed) return this.failureSnapshot(accountId, "grok_usage_unauthorized", this.now());
2499
+ result = await probe();
2500
+ if (result.unauthorized) {
2501
+ return this.failureSnapshot(accountId, "grok_usage_unauthorized", this.now());
2502
+ }
2503
+ }
2504
+ const now = this.now();
2505
+ if (result.windows && result.windows.length > 0) {
2506
+ const snapshot = {
2507
+ providerId: "grok",
2508
+ accountId,
2509
+ source: "oauth-usage-api",
2510
+ observedAt: new Date(now).toISOString(),
2511
+ expiresAt: new Date(now + GROK_ALLOWANCE_CACHE_MS).toISOString(),
2512
+ windows: result.windows
2513
+ };
2514
+ this.store.set(snapshot);
2515
+ return snapshot;
2516
+ }
2517
+ return this.failureSnapshot(accountId, "grok_usage_invalid_response", now);
2518
+ }
2519
+ failureSnapshot(accountId, code, now) {
2520
+ const existing = this.store.get("grok", accountId, now);
2521
+ const snapshot = existing ? {
2522
+ ...existing,
2523
+ expiresAt: new Date(now + GROK_ALLOWANCE_CACHE_MS).toISOString(),
2524
+ windows: existing.windows.map((window) => ({
2525
+ ...window,
2526
+ state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
2527
+ })),
2528
+ lastErrorCode: code
2529
+ } : {
2530
+ providerId: "grok",
2531
+ accountId,
2532
+ source: "oauth-usage-api",
2533
+ observedAt: new Date(now).toISOString(),
2534
+ expiresAt: new Date(now + GROK_ALLOWANCE_CACHE_MS).toISOString(),
2535
+ windows: [
2536
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" },
2537
+ { id: "thirty-day", label: "30 days", scope: "all", usedPercent: null, state: "unavailable" }
2538
+ ],
2539
+ lastErrorCode: code
2540
+ };
2541
+ this.store.set(snapshot);
2542
+ return snapshot;
2543
+ }
2544
+ unsupportedSnapshot(accountId, now) {
2545
+ return {
2546
+ providerId: "grok",
2547
+ accountId,
2548
+ source: "oauth-usage-api",
2549
+ observedAt: new Date(now).toISOString(),
2550
+ windows: [
2551
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unsupported" },
2552
+ { id: "thirty-day", label: "30 days", scope: "all", usedPercent: null, state: "unsupported" }
2553
+ ],
2554
+ lastErrorCode: "grok_usage_unsupported_auth"
2555
+ };
2556
+ }
2557
+ };
2558
+
2559
+ // src/allowance/CopilotAllowanceCollector.ts
2560
+ import {
2561
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore5
2562
+ } from "@omnicross/core/pipeline/AccountAllowanceStore";
2563
+ import { fetchUpstream as fetchUpstream5 } from "@omnicross/core/pipeline/upstreamFetch";
2564
+ import { COPILOT_GITHUB_HEADERS, copilotGitHubApiBase } from "@omnicross/subscriptions";
2565
+ var COPILOT_ALLOWANCE_CACHE_MS = 5 * 6e4;
2566
+ function isRecord3(value) {
2567
+ return !!value && typeof value === "object" && !Array.isArray(value);
2568
+ }
2569
+ function finiteNumber4(value) {
2570
+ if (value === null || value === void 0 || value === "") return void 0;
2571
+ const parsed = typeof value === "number" ? value : Number(value);
2572
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
2573
+ }
2574
+ function booleanValue(value) {
2575
+ if (typeof value === "boolean") return value;
2576
+ if (value === "true") return true;
2577
+ if (value === "false") return false;
2578
+ return void 0;
2579
+ }
2580
+ function parseQuotaDetail(value) {
2581
+ if (!isRecord3(value)) return null;
2582
+ const entitlement = finiteNumber4(value["entitlement"]);
2583
+ const remaining = finiteNumber4(value["remaining"]);
2584
+ const percentRemaining = finiteNumber4(value["percent_remaining"]);
2585
+ const unlimited = booleanValue(value["unlimited"]);
2586
+ if (entitlement === void 0 || remaining === void 0 || percentRemaining === void 0 || unlimited === void 0) {
2587
+ return null;
2588
+ }
2589
+ return { entitlement, remaining, percentRemaining, unlimited };
2590
+ }
2591
+ function secondsUntil5(instant, now) {
2592
+ if (!instant) return void 0;
2593
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
2594
+ }
2595
+ function parseCopilotUserPayload(payload, now) {
2596
+ if (!isRecord3(payload)) return null;
2597
+ const snapshots = isRecord3(payload["quota_snapshots"]) ? payload["quota_snapshots"] : void 0;
2598
+ if (!snapshots) return null;
2599
+ const resetRaw = payload["quota_reset_date"];
2600
+ const resetsAt = typeof resetRaw === "string" && resetRaw.trim() && Number.isFinite(Date.parse(resetRaw)) ? new Date(Date.parse(resetRaw)).toISOString() : void 0;
2601
+ const windows = [];
2602
+ const premium = parseQuotaDetail(snapshots["premium_interactions"]);
2603
+ if (premium) {
2604
+ const usedPercent = premium.unlimited ? 0 : premium.entitlement > 0 ? Math.round(Math.min(100, (premium.entitlement - premium.remaining) / premium.entitlement * 100) * 10) / 10 : finiteNumber4(premium.percentRemaining) !== void 0 ? Math.round(Math.min(100, Math.max(0, 100 - premium.percentRemaining)) * 10) / 10 : null;
2605
+ if (usedPercent !== null) {
2606
+ windows.push({
2607
+ id: "thirty-day",
2608
+ label: "Monthly",
2609
+ scope: "all",
2610
+ usedPercent,
2611
+ windowMinutes: 30 * 24 * 60,
2612
+ ...resetsAt !== void 0 ? { resetsAt } : {},
2613
+ remainingSeconds: secondsUntil5(resetsAt, now),
2614
+ state: "fresh"
2615
+ });
2616
+ }
2617
+ }
2618
+ const chat = parseQuotaDetail(snapshots["chat"]);
2619
+ if (chat && !chat.unlimited && chat.entitlement > 0) {
2620
+ const usedPercent = Math.round(Math.min(100, (chat.entitlement - chat.remaining) / chat.entitlement * 100) * 10) / 10;
2621
+ windows.push({
2622
+ id: "chat-monthly",
2623
+ label: "Chat (monthly)",
2624
+ scope: "all",
2625
+ usedPercent,
2626
+ windowMinutes: 30 * 24 * 60,
2627
+ ...resetsAt !== void 0 ? { resetsAt } : {},
2628
+ remainingSeconds: secondsUntil5(resetsAt, now),
2629
+ state: "fresh"
2630
+ });
2631
+ }
2632
+ return windows.length > 0 ? windows : null;
2633
+ }
2634
+ function githubApiBase(tokens) {
2635
+ return copilotGitHubApiBase(tokens.enterpriseUrl);
2636
+ }
2637
+ var CopilotAllowanceCollector = class {
2638
+ constructor(credentials, store = getSharedAccountAllowanceStore5(), fetchImpl = (url, init, accountId) => fetchUpstream5(url, init, { providerId: "copilot", accountId, redactBodies: true }), now = Date.now) {
2639
+ this.credentials = credentials;
2640
+ this.store = store;
2641
+ this.fetchImpl = fetchImpl;
2642
+ this.now = now;
2643
+ }
2644
+ credentials;
2645
+ store;
2646
+ fetchImpl;
2647
+ now;
2648
+ inFlight = /* @__PURE__ */ new Map();
2649
+ async collectMany(accounts, options = {}) {
2650
+ const settled = await Promise.allSettled(
2651
+ accounts.map((account) => this.collect(account, options))
2652
+ );
2653
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
2654
+ }
2655
+ collect(account, options = {}) {
2656
+ const now = this.now();
2657
+ if (account.tokens.authMethod !== "oauth") {
2658
+ const existing = this.store.get("copilot", account.id, now);
2659
+ if (existing?.windows.every((window) => window.state === "unsupported")) {
2660
+ return Promise.resolve(existing);
2661
+ }
2662
+ const snapshot = this.unsupportedSnapshot(account.id, now);
2663
+ this.store.set(snapshot);
2664
+ return Promise.resolve(snapshot);
2665
+ }
2666
+ const cached = this.store.get("copilot", account.id, now);
2667
+ if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
2668
+ return Promise.resolve(cached);
2669
+ }
2670
+ const running = this.inFlight.get(account.id);
2671
+ if (running) return running;
2672
+ const promise = this.fetchAccount(account.id, account.tokens).catch(() => this.failureSnapshot(account.id, "copilot_usage_request_failed", this.now())).finally(() => this.inFlight.delete(account.id));
2673
+ this.inFlight.set(account.id, promise);
2674
+ return promise;
2675
+ }
2676
+ isCacheValid(snapshot, now, refreshAheadMs) {
2677
+ if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
2678
+ const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
2679
+ const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
2680
+ return Number.isFinite(expiresAt) && expiresAt > now + ahead;
2681
+ }
2682
+ async fetchAccount(accountId, tokens) {
2683
+ let accessToken = await this.credentials.getAccessTokenForAccount("copilot", accountId);
2684
+ if (!accessToken) return this.failureSnapshot(accountId, "copilot_usage_token_unavailable", this.now());
2685
+ let response = await this.request(accountId, accessToken, tokens);
2686
+ if (response.status === 401 || response.status === 403) {
2687
+ const refreshed = await this.credentials.refreshAccountToken("copilot", accountId);
2688
+ if (!refreshed) return this.failureSnapshot(accountId, "copilot_usage_unauthorized", this.now());
2689
+ accessToken = await this.credentials.getAccessTokenForAccount("copilot", accountId);
2690
+ if (!accessToken) return this.failureSnapshot(accountId, "copilot_usage_token_unavailable", this.now());
2691
+ response = await this.request(accountId, accessToken, tokens);
2692
+ if (response.status === 401 || response.status === 403) {
2693
+ return this.failureSnapshot(accountId, "copilot_usage_unauthorized", this.now());
2694
+ }
2695
+ }
2696
+ if (!response.ok) return this.failureSnapshot(accountId, "copilot_usage_http_error", this.now());
2697
+ let payload;
2698
+ try {
2699
+ payload = await response.json();
2700
+ } catch {
2701
+ return this.failureSnapshot(accountId, "copilot_usage_invalid_response", this.now());
2702
+ }
2703
+ const now = this.now();
2704
+ const windows = parseCopilotUserPayload(payload, now);
2705
+ const snapshot = {
2706
+ providerId: "copilot",
2707
+ accountId,
2708
+ source: "oauth-usage-api",
2709
+ observedAt: new Date(now).toISOString(),
2710
+ expiresAt: new Date(now + COPILOT_ALLOWANCE_CACHE_MS).toISOString(),
2711
+ windows: windows ?? [
2712
+ { id: "thirty-day", label: "Monthly", scope: "all", usedPercent: null, state: "unavailable" }
2713
+ ],
2714
+ ...windows ? {} : { lastErrorCode: "copilot_usage_invalid_response" }
2715
+ };
2716
+ this.store.set(snapshot);
2717
+ return snapshot;
2718
+ }
2719
+ request(accountId, accessToken, tokens) {
2720
+ return this.fetchImpl(`${githubApiBase(tokens)}/copilot_internal/user`, {
2721
+ method: "GET",
2722
+ headers: {
2723
+ Authorization: `Bearer ${accessToken}`,
2724
+ Accept: "application/json",
2725
+ "Content-Type": "application/json",
2726
+ ...COPILOT_GITHUB_HEADERS
2727
+ },
2728
+ signal: AbortSignal.timeout(15e3)
2729
+ }, accountId);
2730
+ }
2731
+ failureSnapshot(accountId, code, now) {
2732
+ const existing = this.store.get("copilot", accountId, now);
2733
+ const snapshot = existing ? {
2734
+ ...existing,
2735
+ expiresAt: new Date(now + COPILOT_ALLOWANCE_CACHE_MS).toISOString(),
2736
+ windows: existing.windows.map((window) => ({
2737
+ ...window,
2738
+ state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
2739
+ })),
2740
+ lastErrorCode: code
2741
+ } : {
2742
+ providerId: "copilot",
2743
+ accountId,
2744
+ source: "oauth-usage-api",
2745
+ observedAt: new Date(now).toISOString(),
2746
+ expiresAt: new Date(now + COPILOT_ALLOWANCE_CACHE_MS).toISOString(),
2747
+ windows: [
2748
+ { id: "thirty-day", label: "Monthly", scope: "all", usedPercent: null, state: "unavailable" }
2749
+ ],
2750
+ lastErrorCode: code
2751
+ };
2752
+ this.store.set(snapshot);
2753
+ return snapshot;
2754
+ }
2755
+ unsupportedSnapshot(accountId, now) {
2756
+ return {
2757
+ providerId: "copilot",
2758
+ accountId,
2759
+ source: "oauth-usage-api",
2760
+ observedAt: new Date(now).toISOString(),
2761
+ windows: [
2762
+ { id: "thirty-day", label: "Monthly", scope: "all", usedPercent: null, state: "unsupported" }
2763
+ ],
2764
+ lastErrorCode: "copilot_usage_unsupported_auth"
2765
+ };
2766
+ }
2767
+ };
2768
+
2769
+ // src/allowance/OpenCodeGoAllowanceCollector.ts
2770
+ import {
2771
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore6
2772
+ } from "@omnicross/core/pipeline/AccountAllowanceStore";
2773
+ import { fetchUpstream as fetchUpstream6 } from "@omnicross/core/pipeline/upstreamFetch";
2774
+ import { normalizeOpenCodeGoBaseUrl } from "@omnicross/subscriptions";
2775
+ var OPENCODEGO_ALLOWANCE_CACHE_MS = 5 * 6e4;
2776
+ var OPENCODEGO_DEFAULT_GO_BASE = "https://opencode.ai/zen/go";
2777
+ function finitePercent3(value) {
2778
+ if (value === null || value === void 0 || value === "") return null;
2779
+ const parsed = typeof value === "number" ? value : Number(value);
2780
+ return Number.isFinite(parsed) && parsed >= 0 && parsed <= 100 ? parsed : null;
2781
+ }
2782
+ function isoInstant2(value) {
2783
+ if (typeof value !== "string" || !value.trim()) return void 0;
2784
+ const time = Date.parse(value);
2785
+ return Number.isFinite(time) ? new Date(time).toISOString() : void 0;
2786
+ }
2787
+ function secondsUntil6(instant, now) {
2788
+ if (!instant) return void 0;
2789
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
2790
+ }
2791
+ function windowFromPayload3(id, label, minutes, payload, now) {
2792
+ const statusRateLimited = payload?.status === "rate-limited";
2793
+ const usedPercent = statusRateLimited ? 100 : finitePercent3(payload?.percent);
2794
+ const resetsAt = isoInstant2(payload?.resetsAt);
2795
+ return {
2796
+ id,
2797
+ label,
2798
+ scope: "all",
2799
+ usedPercent,
2800
+ windowMinutes: minutes,
2801
+ ...resetsAt !== void 0 ? { resetsAt } : {},
2802
+ remainingSeconds: secondsUntil6(resetsAt, now),
2803
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
2804
+ };
2805
+ }
2806
+ var OpenCodeGoAllowanceCollector = class {
2807
+ constructor(credentials, store = getSharedAccountAllowanceStore6(), fetchImpl = (url, init, accountId) => fetchUpstream6(url, init, { providerId: "opencodego", accountId, redactBodies: true }), now = Date.now) {
2808
+ this.credentials = credentials;
2809
+ this.store = store;
2810
+ this.fetchImpl = fetchImpl;
2811
+ this.now = now;
2812
+ }
2813
+ credentials;
2814
+ store;
2815
+ fetchImpl;
2816
+ now;
2817
+ inFlight = /* @__PURE__ */ new Map();
2818
+ async collectMany(accounts, options = {}) {
2819
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
2820
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
2821
+ }
2822
+ collect(account, options = {}) {
2823
+ const now = this.now();
2824
+ const cached = this.store.get("opencodego", account.id, now);
2825
+ if (!options.force && cached && (cached.windows.every((window) => window.state === "unsupported") || cached.expiresAt && Date.parse(cached.expiresAt) > now + (options.refreshAheadMs ?? 0))) {
2826
+ return Promise.resolve(cached);
2827
+ }
2828
+ const running = this.inFlight.get(account.id);
2829
+ if (running) return running;
2830
+ const promise = this.fetchAccount(account).catch(() => this.failureSnapshot(account.id, this.now())).finally(() => this.inFlight.delete(account.id));
2831
+ this.inFlight.set(account.id, promise);
2832
+ return promise;
2833
+ }
2834
+ async fetchAccount(account) {
2835
+ const apiKey = await this.credentials.getAccessTokenForAccount("opencodego", account.id);
2836
+ if (!apiKey) return this.failureSnapshot(account.id, this.now());
2837
+ const base = account.tokens.baseUrl ? normalizeOpenCodeGoBaseUrl(account.tokens.baseUrl) : OPENCODEGO_DEFAULT_GO_BASE;
2838
+ const response = await this.fetchImpl(`${base}/v1/usage`, {
2839
+ method: "GET",
2840
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
2841
+ signal: AbortSignal.timeout(15e3)
2842
+ }, account.id);
2843
+ if (response.status === 401 || response.status === 403) {
2844
+ return this.failureSnapshot(account.id, this.now(), "opencodego_usage_unauthorized");
2845
+ }
2846
+ if (!response.ok) return this.failureSnapshot(account.id, this.now());
2847
+ let payload;
2848
+ try {
2849
+ payload = await response.json();
2850
+ } catch {
2851
+ return this.failureSnapshot(account.id, this.now());
2852
+ }
2853
+ const usage = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.usage : void 0;
2854
+ const now = this.now();
2855
+ const snapshot = {
2856
+ providerId: "opencodego",
2857
+ accountId: account.id,
2858
+ source: "oauth-usage-api",
2859
+ observedAt: new Date(now).toISOString(),
2860
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
2861
+ // Monthly deliberately omitted (module doc).
2862
+ windows: [
2863
+ windowFromPayload3("five-hour", "5 hours", 5 * 60, usage?.rolling ?? void 0, now),
2864
+ windowFromPayload3("seven-day", "7 days", 7 * 24 * 60, usage?.weekly ?? void 0, now)
2865
+ ]
2866
+ };
2867
+ this.store.set(snapshot);
2868
+ return snapshot;
2869
+ }
2870
+ failureSnapshot(accountId, now, code = "opencodego_usage_request_failed") {
2871
+ const existing = this.store.get("opencodego", accountId, now);
2872
+ const snapshot = existing ? {
2873
+ ...existing,
2874
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
2875
+ windows: existing.windows.map((window) => ({
2876
+ ...window,
2877
+ state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
2878
+ })),
2879
+ lastErrorCode: code
2880
+ } : {
2881
+ providerId: "opencodego",
2882
+ accountId,
2883
+ source: "oauth-usage-api",
2884
+ observedAt: new Date(now).toISOString(),
2885
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
2886
+ windows: [
2887
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
2888
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
2889
+ ],
2890
+ lastErrorCode: code
2891
+ };
2892
+ this.store.set(snapshot);
2893
+ return snapshot;
2894
+ }
2895
+ };
2896
+
1533
2897
  // src/allowance/AccountAllowanceService.ts
1534
2898
  function codexUnavailable(accountId, now) {
1535
2899
  return {
@@ -1545,26 +2909,34 @@ function codexUnavailable(accountId, now) {
1545
2909
  };
1546
2910
  }
1547
2911
  var AccountAllowanceService = class {
1548
- constructor(credentials, store = getSharedAccountAllowanceStore2(), collector, now = Date.now) {
2912
+ constructor(credentials, store = getSharedAccountAllowanceStore7(), collector, codexCollector, kimiCollector, opencodegoCollector, grokCollector, copilotCollector, now = Date.now) {
1549
2913
  this.credentials = credentials;
1550
2914
  this.store = store;
1551
2915
  this.now = now;
1552
2916
  this.claudeCollector = collector ?? new ClaudeAllowanceCollector(credentials, store);
2917
+ this.codexCollector = codexCollector ?? new CodexAllowanceCollector(credentials, store);
2918
+ this.kimiCollector = kimiCollector ?? new KimiAllowanceCollector(credentials, store);
2919
+ this.opencodegoCollector = opencodegoCollector ?? new OpenCodeGoAllowanceCollector(credentials, store);
2920
+ this.grokCollector = grokCollector ?? new GrokAllowanceCollector(credentials, store);
2921
+ this.copilotCollector = copilotCollector ?? new CopilotAllowanceCollector(credentials, store);
1553
2922
  }
1554
2923
  credentials;
1555
2924
  store;
1556
2925
  now;
1557
2926
  claudeCollector;
2927
+ codexCollector;
2928
+ kimiCollector;
2929
+ grokCollector;
2930
+ copilotCollector;
2931
+ opencodegoCollector;
1558
2932
  /**
1559
- * Read all/filtered snapshots. Claude's five-minute cache is refreshed lazily;
1560
- * Codex remains passive and reports not-observed until a real model response.
2933
+ * Read all/filtered snapshots. Claude's and Codex's five-minute caches are
2934
+ * refreshed lazily on read (Codex polls `/backend-api/wham/usage`; the
2935
+ * passive `x-codex-*` header tap still feeds mid-flight updates).
1561
2936
  */
1562
2937
  async list(filter = {}) {
1563
2938
  const config = await this.credentials.getFullConfig();
1564
- this.store.pruneToKnownAccounts([
1565
- ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
1566
- ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
1567
- ]);
2939
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1568
2940
  const wantsClaude = !filter.providerId || filter.providerId === "claude";
1569
2941
  const claudeAccounts = (config.claudeAccounts ?? []).filter(
1570
2942
  (account) => !filter.accountId || account.id === filter.accountId
@@ -1575,39 +2947,124 @@ var AccountAllowanceService = class {
1575
2947
  (account) => !filter.accountId || account.id === filter.accountId
1576
2948
  );
1577
2949
  if (wantsCodex) {
2950
+ await this.codexCollector.collectMany(codexAccounts);
1578
2951
  for (const account of codexAccounts) {
1579
2952
  if (!this.store.get("codex", account.id)) this.store.set(codexUnavailable(account.id, this.now()));
1580
2953
  }
1581
2954
  }
2955
+ const wantsKimi = !filter.providerId || filter.providerId === "kimi";
2956
+ const kimiAccounts = (config.kimiAccounts ?? []).filter(
2957
+ (account) => !filter.accountId || account.id === filter.accountId
2958
+ );
2959
+ if (wantsKimi) await this.kimiCollector.collectMany(kimiAccounts);
2960
+ const wantsOpenCodeGo = !filter.providerId || filter.providerId === "opencodego";
2961
+ const opencodegoAccounts = (config.opencodegoAccounts ?? []).filter(
2962
+ (account) => !filter.accountId || account.id === filter.accountId
2963
+ );
2964
+ if (wantsOpenCodeGo) await this.opencodegoCollector.collectMany(opencodegoAccounts);
2965
+ const wantsGrok = !filter.providerId || filter.providerId === "grok";
2966
+ const grokAccounts = (config.grokAccounts ?? []).filter(
2967
+ (account) => !filter.accountId || account.id === filter.accountId
2968
+ );
2969
+ if (wantsGrok) await this.grokCollector.collectMany(grokAccounts);
2970
+ const wantsCopilot = !filter.providerId || filter.providerId === "copilot";
2971
+ const copilotAccounts = (config.copilotAccounts ?? []).filter(
2972
+ (account) => !filter.accountId || account.id === filter.accountId
2973
+ );
2974
+ if (wantsCopilot) await this.copilotCollector.collectMany(copilotAccounts);
1582
2975
  const known = /* @__PURE__ */ new Set();
1583
2976
  if (wantsClaude) for (const account of claudeAccounts) known.add(`claude\0${account.id}`);
1584
2977
  if (wantsCodex) for (const account of codexAccounts) known.add(`codex\0${account.id}`);
2978
+ if (wantsKimi) for (const account of kimiAccounts) known.add(`kimi\0${account.id}`);
2979
+ if (wantsOpenCodeGo) for (const account of opencodegoAccounts) known.add(`opencodego\0${account.id}`);
2980
+ if (wantsGrok) for (const account of grokAccounts) known.add(`grok\0${account.id}`);
2981
+ if (wantsCopilot) for (const account of copilotAccounts) known.add(`copilot\0${account.id}`);
1585
2982
  return this.store.list(filter).filter((snapshot) => known.has(`${snapshot.providerId}\0${snapshot.accountId}`));
1586
2983
  }
2984
+ knownAccounts(config) {
2985
+ return [
2986
+ ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
2987
+ ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id })),
2988
+ ...(config.kimiAccounts ?? []).map((account) => ({ providerId: "kimi", accountId: account.id })),
2989
+ ...(config.opencodegoAccounts ?? []).map((account) => ({ providerId: "opencodego", accountId: account.id })),
2990
+ ...(config.grokAccounts ?? []).map((account) => ({ providerId: "grok", accountId: account.id })),
2991
+ ...(config.copilotAccounts ?? []).map((account) => ({ providerId: "copilot", accountId: account.id }))
2992
+ ];
2993
+ }
1587
2994
  /** Force-refresh Claude usage for one account or every stored Claude account. */
1588
2995
  async refreshClaude(accountId) {
1589
2996
  const config = await this.credentials.getFullConfig();
1590
- this.store.pruneToKnownAccounts([
1591
- ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
1592
- ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
1593
- ]);
2997
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1594
2998
  const accounts = (config.claudeAccounts ?? []).filter(
1595
2999
  (account) => !accountId || account.id === accountId
1596
3000
  );
1597
3001
  return this.claudeCollector.collectMany(accounts, { force: true });
1598
3002
  }
1599
3003
  /**
1600
- * Keep Claude snapshots warm for allowance-aware routing. This deliberately
1601
- * excludes Codex (whose quota is learned from real response headers) and
1602
- * preserves the collector's cache + per-account in-flight coalescing.
3004
+ * Force-refresh Codex usage (`/backend-api/wham/usage`) for one account or
3005
+ * every stored Codex account. Replaces the old probe-request workaround
3006
+ * no quota is spent reading the usage endpoint.
3007
+ */
3008
+ async refreshCodex(accountId) {
3009
+ const config = await this.credentials.getFullConfig();
3010
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
3011
+ const accounts = (config.codexAccounts ?? []).filter(
3012
+ (account) => !accountId || account.id === accountId
3013
+ );
3014
+ return this.codexCollector.collectMany(accounts, { force: true });
3015
+ }
3016
+ /** Force-refresh OpenCodeGo usage (`{go}/v1/usage`) for one/all accounts. */
3017
+ async refreshOpenCodeGo(accountId) {
3018
+ const config = await this.credentials.getFullConfig();
3019
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
3020
+ const accounts = (config.opencodegoAccounts ?? []).filter(
3021
+ (account) => !accountId || account.id === accountId
3022
+ );
3023
+ return this.opencodegoCollector.collectMany(accounts, { force: true });
3024
+ }
3025
+ /** Force-refresh Kimi usage (`/coding/v1/usages`) for one/all accounts. */
3026
+ async refreshKimi(accountId) {
3027
+ const config = await this.credentials.getFullConfig();
3028
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
3029
+ const accounts = (config.kimiAccounts ?? []).filter(
3030
+ (account) => !accountId || account.id === accountId
3031
+ );
3032
+ return this.kimiCollector.collectMany(accounts, { force: true });
3033
+ }
3034
+ /** Force-refresh Copilot usage (copilot_internal/user) for one/all accounts. */
3035
+ async refreshCopilot(accountId) {
3036
+ const config = await this.credentials.getFullConfig();
3037
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
3038
+ const accounts = (config.copilotAccounts ?? []).filter(
3039
+ (account) => !accountId || account.id === accountId
3040
+ );
3041
+ return this.copilotCollector.collectMany(accounts, { force: true });
3042
+ }
3043
+ /** Force-refresh Grok usage (CLI billing proxy) for one/all accounts. */
3044
+ async refreshGrok(accountId) {
3045
+ const config = await this.credentials.getFullConfig();
3046
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
3047
+ const accounts = (config.grokAccounts ?? []).filter(
3048
+ (account) => !accountId || account.id === accountId
3049
+ );
3050
+ return this.grokCollector.collectMany(accounts, { force: true });
3051
+ }
3052
+ /**
3053
+ * Keep Claude + Codex + Kimi snapshots warm for allowance-aware routing. All
3054
+ * collectors preserve their cache + per-account in-flight coalescing; a tick
3055
+ * normally performs no network I/O. (Codex joined the warm path when it
3056
+ * gained an active `/wham/usage` collector — the passive `x-codex-*` header
3057
+ * tap alone could not keep the policy fed while idle.)
1603
3058
  */
1604
3059
  async maintainClaudeCache(refreshAheadMs) {
1605
3060
  const config = await this.credentials.getFullConfig();
1606
- this.store.pruneToKnownAccounts([
1607
- ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
1608
- ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
1609
- ]);
3061
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1610
3062
  await this.claudeCollector.collectMany(config.claudeAccounts ?? [], { refreshAheadMs });
3063
+ await this.codexCollector.collectMany(config.codexAccounts ?? [], { refreshAheadMs });
3064
+ await this.kimiCollector.collectMany(config.kimiAccounts ?? [], { refreshAheadMs });
3065
+ await this.opencodegoCollector.collectMany(config.opencodegoAccounts ?? [], { refreshAheadMs });
3066
+ await this.grokCollector.collectMany(config.grokAccounts ?? [], { refreshAheadMs });
3067
+ await this.copilotCollector.collectMany(config.copilotAccounts ?? [], { refreshAheadMs });
1611
3068
  }
1612
3069
  /** Remove a cache row as soon as an account is deleted by the admin path. */
1613
3070
  removeAccountSnapshot(providerId, accountId) {
@@ -2037,7 +3494,8 @@ import {
2037
3494
  } from "@omnicross/contracts/image-generation-types";
2038
3495
  import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling2 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
2039
3496
  import { getSharedAccountHealth } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
2040
- import { fetchUpstream as fetchUpstream2 } from "@omnicross/core/pipeline/upstreamFetch";
3497
+ import { fetchUpstream as fetchUpstream7 } from "@omnicross/core/pipeline/upstreamFetch";
3498
+ import { mergeExtraHeaders } from "@omnicross/core";
2041
3499
 
2042
3500
  // src/image-generation/imagesConfigValidation.ts
2043
3501
  import { validateImagesServerConfig } from "@omnicross/core/outbound-api";
@@ -2350,6 +3808,7 @@ async function applyServerConfigTransaction(current, next, deps) {
2350
3808
 
2351
3809
  // src/config.ts
2352
3810
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
3811
+ import { EXTRA_HEADER_RESERVED_NAMES } from "@omnicross/core";
2353
3812
  var DEFAULT_ADMIN_PORT = 8766;
2354
3813
  function validateAdmin(raw) {
2355
3814
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
@@ -2406,6 +3865,18 @@ var FORMAT_AXIS_TRANSFORMERS = [
2406
3865
  "openai-response",
2407
3866
  "gemini-code-assist"
2408
3867
  ];
3868
+ function validateExtraHeaders(raw) {
3869
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
3870
+ const reserved = EXTRA_HEADER_RESERVED_NAMES;
3871
+ const out = {};
3872
+ for (const [name, value] of Object.entries(raw)) {
3873
+ if (!name.trim()) continue;
3874
+ if (typeof value !== "string") continue;
3875
+ if (reserved.has(name.toLowerCase())) continue;
3876
+ out[name] = value;
3877
+ }
3878
+ return Object.keys(out).length > 0 ? out : void 0;
3879
+ }
2409
3880
  function validateApiKeys(raw) {
2410
3881
  if (!Array.isArray(raw)) return void 0;
2411
3882
  const out = [];
@@ -2619,6 +4090,9 @@ function validateProvider(raw, index) {
2619
4090
  apiVersion,
2620
4091
  maxConcurrency,
2621
4092
  modelsEndpoint,
4093
+ // Static extra headers: load-guard (reserved names dropped), collapse-to-
4094
+ // undefined; enforced by the outbound header funnel + admin probes.
4095
+ extraHeaders: validateExtraHeaders(p["extraHeaders"]),
2622
4096
  // Provider transformer config (app-parity child 5): load-guard, collapse-to-
2623
4097
  // undefined; non-secret; ENFORCED via resolveTransformerChain (parity-2 child 2).
2624
4098
  // Format-axis entries are stripped by `migrateFormatAxis` — `use[]` is the
@@ -3583,7 +5057,10 @@ function mapPresetToProvider(preset, opts) {
3583
5057
  apiFormat: resolved.format,
3584
5058
  baseUrl: opts.baseUrlOverride ?? preset.api_base_url,
3585
5059
  apiKey: opts.key,
3586
- models: Array.isArray(preset.models) ? preset.models : void 0
5060
+ models: Array.isArray(preset.models) ? preset.models : void 0,
5061
+ // Static identity headers (e.g. the Cline client set) survive the mapping —
5062
+ // the CLI-seeded row needs them as much as an admin-API-created one.
5063
+ extraHeaders: preset.extraHeaders
3587
5064
  };
3588
5065
  return { provider };
3589
5066
  }
@@ -3608,7 +5085,8 @@ function listMappablePresets() {
3608
5085
  description: preset.description,
3609
5086
  features: preset.features,
3610
5087
  website: preset.website,
3611
- modelsEndpoint: preset.modelsEndpoint
5088
+ modelsEndpoint: preset.modelsEndpoint,
5089
+ extraHeaders: preset.extraHeaders
3612
5090
  });
3613
5091
  }
3614
5092
  return { mappable, excluded };
@@ -3779,7 +5257,10 @@ var VALID_PROVIDER_IDS = [
3779
5257
  "claude",
3780
5258
  "codex",
3781
5259
  "gemini",
3782
- "opencodego"
5260
+ "opencodego",
5261
+ "kimi",
5262
+ "grok",
5263
+ "copilot"
3783
5264
  ];
3784
5265
  function asSubscriptionProviderId(id) {
3785
5266
  return VALID_PROVIDER_IDS.includes(id) ? id : null;
@@ -3915,6 +5396,52 @@ function validateGemini(body) {
3915
5396
  copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "lastRefreshedAt", "errorMessage"]);
3916
5397
  return out;
3917
5398
  }
5399
+ function validateKimi(body) {
5400
+ const authMethod = str(body["authMethod"]);
5401
+ const status = str(body["status"]);
5402
+ if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
5403
+ if (!status || !TOKEN_STATUSES.has(status)) return null;
5404
+ const out = {
5405
+ authMethod,
5406
+ status
5407
+ };
5408
+ copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "accountId", "deviceId", "lastRefreshedAt", "errorMessage"]);
5409
+ return out;
5410
+ }
5411
+ function validateGrok(body) {
5412
+ const authMethod = str(body["authMethod"]);
5413
+ const status = str(body["status"]);
5414
+ if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
5415
+ if (!status || !TOKEN_STATUSES.has(status)) return null;
5416
+ const out = {
5417
+ authMethod,
5418
+ status
5419
+ };
5420
+ copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "accountId", "lastRefreshedAt", "errorMessage"]);
5421
+ return out;
5422
+ }
5423
+ function validateCopilot(body) {
5424
+ const authMethod = str(body["authMethod"]);
5425
+ const status = str(body["status"]);
5426
+ if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
5427
+ if (!status || !TOKEN_STATUSES.has(status)) return null;
5428
+ const out = {
5429
+ authMethod,
5430
+ status
5431
+ };
5432
+ copyOptional(out, body, [
5433
+ "accessToken",
5434
+ "refreshToken",
5435
+ "expiresAt",
5436
+ "accountId",
5437
+ "email",
5438
+ "apiEndpoint",
5439
+ "enterpriseUrl",
5440
+ "lastRefreshedAt",
5441
+ "errorMessage"
5442
+ ]);
5443
+ return out;
5444
+ }
3918
5445
  function validateOpenCodeGo(body) {
3919
5446
  const authMethod = str(body["authMethod"]);
3920
5447
  const status = str(body["status"]);
@@ -3950,6 +5477,12 @@ function validateTokenBody(providerId, body) {
3950
5477
  return validateGemini(body);
3951
5478
  case "opencodego":
3952
5479
  return validateOpenCodeGo(body);
5480
+ case "kimi":
5481
+ return validateKimi(body);
5482
+ case "grok":
5483
+ return validateGrok(body);
5484
+ case "copilot":
5485
+ return validateCopilot(body);
3953
5486
  default:
3954
5487
  return null;
3955
5488
  }
@@ -3979,12 +5512,12 @@ async function statusEntryFor(reader, providerId) {
3979
5512
 
3980
5513
  // src/admin/accountsOAuth.ts
3981
5514
  var OAUTH_HTTP_PROVIDERS = /* @__PURE__ */ new Set(["claude", "gemini"]);
3982
- function err2(status, message) {
5515
+ function err5(status, message) {
3983
5516
  return { status, body: { error: { type: "admin_api_error", message } } };
3984
5517
  }
3985
5518
  function handleOAuthStart(providerId, deps) {
3986
5519
  if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
3987
- return err2(400, `oauth not available for provider '${providerId}'`);
5520
+ return err5(400, `oauth not available for provider '${providerId}'`);
3988
5521
  }
3989
5522
  const flow = providerId === "claude" ? claudeOAuth : geminiOAuth;
3990
5523
  const { authUrl, codeVerifier, state } = flow.generateAuthParams();
@@ -3993,23 +5526,23 @@ function handleOAuthStart(providerId, deps) {
3993
5526
  }
3994
5527
  async function handleOAuthComplete(providerId, body, deps) {
3995
5528
  if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
3996
- return err2(400, `oauth not available for provider '${providerId}'`);
5529
+ return err5(400, `oauth not available for provider '${providerId}'`);
3997
5530
  }
3998
5531
  const sessionId = typeof body["sessionId"] === "string" ? body["sessionId"] : "";
3999
5532
  const rawCode = typeof body["code"] === "string" ? body["code"] : "";
4000
- if (!sessionId) return err2(400, "oauth complete requires { sessionId }");
4001
- if (!rawCode) return err2(400, "oauth complete requires { code }");
5533
+ if (!sessionId) return err5(400, "oauth complete requires { sessionId }");
5534
+ if (!rawCode) return err5(400, "oauth complete requires { code }");
4002
5535
  const session = deps.oauthSessions.peek(sessionId);
4003
- if (!session) return err2(410, "oauth session is unknown, expired, or already used");
5536
+ if (!session) return err5(410, "oauth session is unknown, expired, or already used");
4004
5537
  if (session.providerId !== providerId) {
4005
- return err2(400, `oauth session does not match provider '${providerId}'`);
5538
+ return err5(400, `oauth session does not match provider '${providerId}'`);
4006
5539
  }
4007
5540
  let code = rawCode.trim();
4008
5541
  if (providerId === "claude") {
4009
5542
  const [splitCode, pastedState] = code.split("#");
4010
- if (!splitCode) return err2(400, "no authorization code was provided");
5543
+ if (!splitCode) return err5(400, "no authorization code was provided");
4011
5544
  if (pastedState && pastedState !== session.state) {
4012
- return err2(400, "oauth state did not match (possible CSRF) \u2014 aborting");
5545
+ return err5(400, "oauth state did not match (possible CSRF) \u2014 aborting");
4013
5546
  }
4014
5547
  code = splitCode;
4015
5548
  }
@@ -4019,7 +5552,7 @@ async function handleOAuthComplete(providerId, body, deps) {
4019
5552
  block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state, exchangeFetch) : await exchangeGemini(code, session.codeVerifier, exchangeFetch);
4020
5553
  } catch (exchangeError) {
4021
5554
  const reason = exchangeError instanceof Error ? exchangeError.message : "token exchange failed";
4022
- return err2(502, `oauth token exchange failed for '${providerId}': ${reason}`);
5555
+ return err5(502, `oauth token exchange failed for '${providerId}': ${reason}`);
4023
5556
  }
4024
5557
  deps.oauthSessions.consume(sessionId);
4025
5558
  const label = typeof body["label"] === "string" && body["label"].trim() ? body["label"].trim() : void 0;
@@ -4357,8 +5890,8 @@ function errBody(message) {
4357
5890
  return { error: { type: "admin_api_error", message } };
4358
5891
  }
4359
5892
  var defaultCommandRunner = (command) => new Promise((resolve11) => {
4360
- exec(command, { timeout: 18e4 }, (err5, _stdout, stderr) => {
4361
- if (err5) resolve11({ ok: false, error: stderr.trim() || err5.message });
5893
+ exec(command, { timeout: 18e4 }, (err8, _stdout, stderr) => {
5894
+ if (err8) resolve11({ ok: false, error: stderr.trim() || err8.message });
4362
5895
  else resolve11({ ok: true });
4363
5896
  });
4364
5897
  });
@@ -4404,8 +5937,8 @@ async function handleCliLaunch(cli, body, ctx) {
4404
5937
  providerId: typeof body["providerId"] === "string" ? body["providerId"] : void 0,
4405
5938
  model: typeof body["model"] === "string" ? body["model"] : void 0
4406
5939
  });
4407
- } catch (err5) {
4408
- return { status: 400, body: errBody(err5 instanceof Error ? err5.message : "no launch target") };
5940
+ } catch (err8) {
5941
+ return { status: 400, body: errBody(err8 instanceof Error ? err8.message : "no launch target") };
4409
5942
  }
4410
5943
  const id = randomUUID2();
4411
5944
  let leaseId2;
@@ -4433,9 +5966,9 @@ async function handleCliLaunch(cli, body, ctx) {
4433
5966
  } else {
4434
5967
  launch = await buildLaunchEnv(cli, ctx.llmConfig, target);
4435
5968
  }
4436
- } catch (err5) {
4437
- const status = err5 instanceof RouteLeaseError2 ? err5.status : 400;
4438
- return { status, body: errBody(err5 instanceof Error ? err5.message : "failed to build launch env") };
5969
+ } catch (err8) {
5970
+ const status = err8 instanceof RouteLeaseError2 ? err8.status : 400;
5971
+ return { status, body: errBody(err8 instanceof Error ? err8.message : "failed to build launch env") };
4439
5972
  }
4440
5973
  const cwd = typeof body["cwd"] === "string" && body["cwd"].trim() ? body["cwd"].trim() : void 0;
4441
5974
  const opener = ctx.opener ?? defaultTerminalOpener;
@@ -4463,9 +5996,9 @@ async function handleCliLaunch(cli, body, ctx) {
4463
5996
  onFailure: onSessionEnd
4464
5997
  });
4465
5998
  if (cleanup) openerCleanup = cleanup;
4466
- } catch (err5) {
5999
+ } catch (err8) {
4467
6000
  onSessionEnd();
4468
- return { status: 500, body: errBody(err5 instanceof Error ? err5.message : "failed to open terminal") };
6001
+ return { status: 500, body: errBody(err8 instanceof Error ? err8.message : "failed to open terminal") };
4469
6002
  }
4470
6003
  if (ended) {
4471
6004
  openerCleanup?.();
@@ -5028,7 +6561,7 @@ async function handleSearchQuery(req, res, deps) {
5028
6561
  // src/admin/searchAdminView.ts
5029
6562
  var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
5030
6563
  var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
5031
- function isRecord(value) {
6564
+ function isRecord4(value) {
5032
6565
  return value !== null && typeof value === "object" && !Array.isArray(value);
5033
6566
  }
5034
6567
  function redactSearchServerConfig(search) {
@@ -5078,13 +6611,13 @@ function resolveSecretField(entry, field, stored) {
5078
6611
  else delete entry[field];
5079
6612
  }
5080
6613
  function preserveSearchSecrets(incoming, current) {
5081
- if (!isRecord(incoming)) return incoming;
6614
+ if (!isRecord4(incoming)) return incoming;
5082
6615
  const section = { ...incoming };
5083
6616
  const providersValue = section["providers"];
5084
- if (!isRecord(providersValue)) return section;
6617
+ if (!isRecord4(providersValue)) return section;
5085
6618
  const providers = {};
5086
6619
  for (const [id, entryValue] of Object.entries(providersValue)) {
5087
- if (!isRecord(entryValue)) {
6620
+ if (!isRecord4(entryValue)) {
5088
6621
  providers[id] = entryValue;
5089
6622
  continue;
5090
6623
  }
@@ -5162,7 +6695,7 @@ function parseKeyPolicyBody(body) {
5162
6695
  var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
5163
6696
  var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
5164
6697
  var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
5165
- function isRecord2(value) {
6698
+ function isRecord5(value) {
5166
6699
  return !!value && typeof value === "object" && !Array.isArray(value);
5167
6700
  }
5168
6701
  function nonBlank(value) {
@@ -5182,7 +6715,7 @@ function validateGatewayBindingsSegment(patch) {
5182
6715
  const ids = /* @__PURE__ */ new Set();
5183
6716
  raw.forEach((entry, index) => {
5184
6717
  const path2 = `bindings[${index}]`;
5185
- if (!isRecord2(entry)) {
6718
+ if (!isRecord5(entry)) {
5186
6719
  errors.push(`${path2} must be an object`);
5187
6720
  return;
5188
6721
  }
@@ -5211,12 +6744,12 @@ function validateGatewayBindingsSegment(patch) {
5211
6744
  } else if (entry.modelMappings.length > 100) {
5212
6745
  errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
5213
6746
  } else if (entry.modelMappings.some(
5214
- (mapping) => !isRecord2(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
6747
+ (mapping) => !isRecord5(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
5215
6748
  )) {
5216
6749
  errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
5217
6750
  }
5218
6751
  }
5219
- if (!isRecord2(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
6752
+ if (!isRecord5(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
5220
6753
  errors.push(`${path2}.target is invalid`);
5221
6754
  } else {
5222
6755
  if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
@@ -5231,7 +6764,7 @@ function validateGatewayBindingsSegment(patch) {
5231
6764
  }
5232
6765
  }
5233
6766
  if (entry.modelMap !== void 0) {
5234
- if (!isRecord2(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
6767
+ if (!isRecord5(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
5235
6768
  errors.push(`${path2}.modelMap must contain string values`);
5236
6769
  }
5237
6770
  }
@@ -5525,7 +7058,10 @@ var PROVIDER_KEYS = {
5525
7058
  block: "opencodego",
5526
7059
  accounts: "opencodegoAccounts",
5527
7060
  active: "activeOpencodegoAccountId"
5528
- }
7061
+ },
7062
+ kimi: { block: "kimi", accounts: "kimiAccounts", active: "activeKimiAccountId" },
7063
+ grok: { block: "grok", accounts: "grokAccounts", active: "activeGrokAccountId" },
7064
+ copilot: { block: "copilot", accounts: "copilotAccounts", active: "activeCopilotAccountId" }
5529
7065
  };
5530
7066
  function clone(value) {
5531
7067
  return JSON.parse(JSON.stringify(value));
@@ -6047,7 +7583,7 @@ async function applyImport(packString, passphrase, mode, deps, parseProviderInpu
6047
7583
  }
6048
7584
 
6049
7585
  // src/admin/adminMigration.ts
6050
- function err3(status, message) {
7586
+ function err6(status, message) {
6051
7587
  return { status, body: { error: { type: "admin_api_error", message } } };
6052
7588
  }
6053
7589
  async function handleExport(body, deps) {
@@ -6057,30 +7593,30 @@ async function handleExport(body, deps) {
6057
7593
  return { status: 200, body: { pack, version: BUNDLE_VERSION } };
6058
7594
  } catch (error) {
6059
7595
  if (error instanceof WeakPassphraseError) {
6060
- return err3(400, error.message);
7596
+ return err6(400, error.message);
6061
7597
  }
6062
- return err3(500, "failed to build the migration pack");
7598
+ return err6(500, "failed to build the migration pack");
6063
7599
  }
6064
7600
  }
6065
7601
  async function handleImport(body, deps) {
6066
7602
  const blob = typeof body["blob"] === "string" ? body["blob"] : "";
6067
7603
  const passphrase = typeof body["passphrase"] === "string" ? body["passphrase"] : "";
6068
7604
  const mode = body["mode"] === "overwrite" ? "overwrite" : "merge";
6069
- if (!blob) return err3(400, "import requires { blob }");
7605
+ if (!blob) return err6(400, "import requires { blob }");
6070
7606
  try {
6071
7607
  const counts = await applyImport(blob, passphrase, mode, deps, deps.parseProviderInput);
6072
7608
  return { status: 200, body: counts };
6073
7609
  } catch (error) {
6074
7610
  if (error instanceof WeakPassphraseError) {
6075
- return err3(400, error.message);
7611
+ return err6(400, error.message);
6076
7612
  }
6077
- return err3(400, error instanceof Error ? error.message : "import failed");
7613
+ return err6(400, error instanceof Error ? error.message : "import failed");
6078
7614
  }
6079
7615
  }
6080
7616
 
6081
7617
  // src/admin/usagePricing.ts
6082
7618
  import { getSharedUsageThroughputTracker } from "@omnicross/core/usage";
6083
- var err4 = (status, message) => ({
7619
+ var err7 = (status, message) => ({
6084
7620
  status,
6085
7621
  body: { error: { type: "admin_api_error", message } }
6086
7622
  });
@@ -6093,7 +7629,7 @@ function parseRange(query2) {
6093
7629
  const startTs = parseFiniteInt(query2.get("startTs"));
6094
7630
  const endTs = parseFiniteInt(query2.get("endTs"));
6095
7631
  if (startTs === null || endTs === null) {
6096
- return err4(400, "startTs and endTs are required finite-integer unix-millis query params");
7632
+ return err7(400, "startTs and endTs are required finite-integer unix-millis query params");
6097
7633
  }
6098
7634
  return { startTs, endTs };
6099
7635
  }
@@ -6118,14 +7654,14 @@ async function handleUsageGet(view, query2, deps) {
6118
7654
  case "timeseries": {
6119
7655
  const bucket = query2.get("bucket");
6120
7656
  if (bucket !== "hour" && bucket !== "day" && bucket !== "month") {
6121
- return err4(400, "bucket must be one of 'hour', 'day', 'month'");
7657
+ return err7(400, "bucket must be one of 'hour', 'day', 'month'");
6122
7658
  }
6123
7659
  const now = Date.now();
6124
7660
  const clamped = { startTs: range.startTs, endTs: Math.min(range.endTs, now) };
6125
7661
  if (clamped.startTs < clamped.endTs) {
6126
7662
  const projected = Math.ceil((clamped.endTs - clamped.startTs) / BUCKET_SPAN_MS[bucket]) + 1;
6127
7663
  if (projected > MAX_TIMESERIES_BUCKETS) {
6128
- return err4(
7664
+ return err7(
6129
7665
  400,
6130
7666
  `requested range projects ~${projected} '${bucket}' buckets (max ${MAX_TIMESERIES_BUCKETS}); narrow the range or use a coarser bucket`
6131
7667
  );
@@ -6148,7 +7684,7 @@ async function handleUsageGet(view, query2, deps) {
6148
7684
  };
6149
7685
  }
6150
7686
  default:
6151
- return err4(404, `unknown usage view '${view ?? ""}'`);
7687
+ return err7(404, `unknown usage view '${view ?? ""}'`);
6152
7688
  }
6153
7689
  }
6154
7690
  function poolKeyLabels(cfg) {
@@ -6197,7 +7733,7 @@ async function handlePricingList(deps) {
6197
7733
  async function handlePricingUpsert(body, deps) {
6198
7734
  const input = parsePricingEntryInput(body);
6199
7735
  if (!input) {
6200
- return err4(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
7736
+ return err7(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
6201
7737
  }
6202
7738
  const entry = await deps.pricingEngine.upsertManual(input);
6203
7739
  return { status: 200, body: { entry } };
@@ -6206,7 +7742,7 @@ async function handlePricingDelete(query2, deps) {
6206
7742
  const providerId = query2.get("providerId")?.trim() ?? "";
6207
7743
  const modelId = query2.get("modelId")?.trim() ?? "";
6208
7744
  if (!providerId || !modelId) {
6209
- return err4(400, "delete requires providerId and modelId query params");
7745
+ return err7(400, "delete requires providerId and modelId query params");
6210
7746
  }
6211
7747
  const deleted = await deps.pricingStore.delete(providerId, modelId);
6212
7748
  if (deleted) await deps.pricingEngine.invalidateCache();
@@ -6226,13 +7762,13 @@ async function handlePricingFetchLatest(deps) {
6226
7762
  }
6227
7763
  };
6228
7764
  } catch (e) {
6229
- return err4(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
7765
+ return err7(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
6230
7766
  }
6231
7767
  }
6232
7768
  async function handlePricingResolveConflicts(body, deps) {
6233
7769
  const raw = body["resolutions"];
6234
7770
  if (!Array.isArray(raw)) {
6235
- return err4(400, "resolve-conflicts requires { resolutions: [...] }");
7771
+ return err7(400, "resolve-conflicts requires { resolutions: [...] }");
6236
7772
  }
6237
7773
  const currentRows = await deps.pricingStore.getAll();
6238
7774
  const userEditedKeys = new Set(
@@ -6242,21 +7778,21 @@ async function handlePricingResolveConflicts(body, deps) {
6242
7778
  const pendingIncoming = /* @__PURE__ */ new Map();
6243
7779
  let staleCount = 0;
6244
7780
  for (const item of raw) {
6245
- if (!item || typeof item !== "object") return err4(400, "invalid resolution entry");
7781
+ if (!item || typeof item !== "object") return err7(400, "invalid resolution entry");
6246
7782
  const r = item;
6247
7783
  const action = r["action"];
6248
7784
  if (action !== "overwrite" && action !== "skip") {
6249
- return err4(400, "resolution action must be 'overwrite' or 'skip'");
7785
+ return err7(400, "resolution action must be 'overwrite' or 'skip'");
6250
7786
  }
6251
7787
  const providerId = typeof r["providerId"] === "string" && r["providerId"].trim() ? r["providerId"].trim() : "";
6252
7788
  const modelId = typeof r["modelId"] === "string" && r["modelId"].trim() ? r["modelId"].trim() : "";
6253
7789
  if (!providerId || !modelId) {
6254
- return err4(400, "each resolution requires top-level providerId and modelId");
7790
+ return err7(400, "each resolution requires top-level providerId and modelId");
6255
7791
  }
6256
7792
  const incoming = parsePricingEntryInput(r["incoming"]);
6257
- if (!incoming) return err4(400, "each resolution must echo a valid incoming pricing entry");
7793
+ if (!incoming) return err7(400, "each resolution must echo a valid incoming pricing entry");
6258
7794
  if (incoming.providerId !== providerId || incoming.modelId !== modelId) {
6259
- return err4(400, "resolution providerId/modelId must match the echoed incoming entry");
7795
+ return err7(400, "resolution providerId/modelId must match the echoed incoming entry");
6260
7796
  }
6261
7797
  const key = `${providerId}::${modelId}`;
6262
7798
  if (action === "overwrite" && !userEditedKeys.has(key)) {
@@ -6301,7 +7837,7 @@ function query(req) {
6301
7837
  }
6302
7838
  function allowanceProvider(value) {
6303
7839
  if (!value) return void 0;
6304
- return value === "claude" || value === "codex" ? value : null;
7840
+ return value === "claude" || value === "codex" || value === "kimi" || value === "opencodego" || value === "grok" || value === "copilot" ? value : null;
6305
7841
  }
6306
7842
  async function handleAccountAllowanceApi(req, res, method, rest, service) {
6307
7843
  if (!service) return writeError2(res, 501, "account allowance service is not available");
@@ -6315,7 +7851,9 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
6315
7851
  const params = query(req);
6316
7852
  const pathProvider = rest.length >= 2 ? rest[0] : null;
6317
7853
  const providerId = allowanceProvider(pathProvider ?? params.get("providerId") ?? params.get("provider"));
6318
- if (providerId === null) return writeError2(res, 400, "providerId must be claude or codex");
7854
+ if (providerId === null) {
7855
+ return writeError2(res, 400, "providerId must be claude, codex, kimi, opencodego, grok, or copilot");
7856
+ }
6319
7857
  const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
6320
7858
  const allowances = await service.list({ providerId, accountId });
6321
7859
  return writeJson3(res, 200, { allowances });
@@ -6325,10 +7863,57 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
6325
7863
  const requestedProvider = allowanceProvider(
6326
7864
  typeof body["providerId"] === "string" ? body["providerId"] : "claude"
6327
7865
  );
6328
- if (requestedProvider !== "claude") {
6329
- return writeError2(res, 400, "only Claude allowances support explicit refresh");
6330
- }
6331
7866
  const accountId = typeof body["accountId"] === "string" && body["accountId"].trim() ? body["accountId"].trim() : void 0;
7867
+ if (requestedProvider === "codex") {
7868
+ if (!service.refreshCodex) {
7869
+ return writeError2(res, 501, "codex allowance refresh is not available");
7870
+ }
7871
+ const allowances2 = await service.refreshCodex(accountId);
7872
+ if (accountId && allowances2.length === 0) {
7873
+ return writeError2(res, 404, `Codex account '${accountId}' not found`);
7874
+ }
7875
+ return writeJson3(res, 200, { allowances: allowances2 });
7876
+ }
7877
+ if (requestedProvider === "kimi") {
7878
+ if (!service.refreshKimi) {
7879
+ return writeError2(res, 501, "kimi allowance refresh is not available");
7880
+ }
7881
+ const allowances2 = await service.refreshKimi(accountId);
7882
+ if (accountId && allowances2.length === 0) {
7883
+ return writeError2(res, 404, `Kimi account '${accountId}' not found`);
7884
+ }
7885
+ return writeJson3(res, 200, { allowances: allowances2 });
7886
+ }
7887
+ if (requestedProvider === "opencodego") {
7888
+ if (!service.refreshOpenCodeGo) {
7889
+ return writeError2(res, 501, "opencodego allowance refresh is not available");
7890
+ }
7891
+ const allowances2 = await service.refreshOpenCodeGo(accountId);
7892
+ if (accountId && allowances2.length === 0) {
7893
+ return writeError2(res, 404, `OpenCodeGo account '${accountId}' not found`);
7894
+ }
7895
+ return writeJson3(res, 200, { allowances: allowances2 });
7896
+ }
7897
+ if (requestedProvider === "copilot") {
7898
+ if (!service.refreshCopilot) {
7899
+ return writeError2(res, 501, "copilot allowance refresh is not available");
7900
+ }
7901
+ const allowances2 = await service.refreshCopilot(accountId);
7902
+ if (accountId && allowances2.length === 0) {
7903
+ return writeError2(res, 404, `Copilot account '${accountId}' not found`);
7904
+ }
7905
+ return writeJson3(res, 200, { allowances: allowances2 });
7906
+ }
7907
+ if (requestedProvider === "grok") {
7908
+ if (!service.refreshGrok) {
7909
+ return writeError2(res, 501, "grok allowance refresh is not available");
7910
+ }
7911
+ const allowances2 = await service.refreshGrok(accountId);
7912
+ if (accountId && allowances2.length === 0) {
7913
+ return writeError2(res, 404, `Grok account '${accountId}' not found`);
7914
+ }
7915
+ return writeJson3(res, 200, { allowances: allowances2 });
7916
+ }
6332
7917
  const allowances = await service.refreshClaude(accountId);
6333
7918
  if (accountId && allowances.length === 0) {
6334
7919
  return writeError2(res, 404, `Claude account '${accountId}' not found`);
@@ -6430,6 +8015,9 @@ function toProviderView(row) {
6430
8015
  apiVersion: row.apiVersion,
6431
8016
  maxConcurrency: row.maxConcurrency,
6432
8017
  modelsEndpoint: row.modelsEndpoint,
8018
+ // Static extra headers round-trip VERBATIM (non-secret identity values;
8019
+ // auth/content names were already dropped at the write/load gate).
8020
+ extraHeaders: row.extraHeaders,
6433
8021
  // app-parity child 5: transformer config round-trips VERBATIM (non-secret —
6434
8022
  // transform-rule names + options, no key material; absent stays absent).
6435
8023
  transformer: row.transformer,
@@ -6499,8 +8087,8 @@ async function handleAdminApi(req, res, path2, deps) {
6499
8087
  default:
6500
8088
  return writeJsonError(res, 404, `unknown admin resource '${resource}'`);
6501
8089
  }
6502
- } catch (err5) {
6503
- writeJsonError(res, 500, err5 instanceof Error ? err5.message : String(err5));
8090
+ } catch (err8) {
8091
+ writeJsonError(res, 500, err8 instanceof Error ? err8.message : String(err8));
6504
8092
  }
6505
8093
  }
6506
8094
  function requestQuery(req) {
@@ -6570,6 +8158,9 @@ async function handleProviders(req, res, method, rest, deps) {
6570
8158
  if (method === "POST" && rest.length === 4 && rest[1] === "keys" && rest[3] === "enabled") {
6571
8159
  return await handleToggleProviderKey(req, res, rest[0], rest[2], cfg, deps);
6572
8160
  }
8161
+ if (method === "POST" && rest.length === 5 && rest[1] === "keys" && rest[3] === "quota" && rest[4] === "refresh") {
8162
+ return await handleProviderKeyQuotaRefresh(res, rest[0], rest[2], cfg, deps);
8163
+ }
6573
8164
  if (method === "PUT" && rest.length === 3 && rest[1] === "keys") {
6574
8165
  return await handleUpdateProviderKey(req, res, rest[0], rest[2], cfg, deps);
6575
8166
  }
@@ -6655,6 +8246,9 @@ async function handleProviderReorder(req, res, cfg, deps) {
6655
8246
  persistProviders(cfg, deps);
6656
8247
  return writeJson4(res, 200, { ok: true, providers: cfg.providers.map(toProviderView) });
6657
8248
  }
8249
+ function expandRowExtraHeaders(row) {
8250
+ return mergeExtraHeaders({}, row.extraHeaders);
8251
+ }
6658
8252
  async function handleDiscoverModels(res, id, cfg) {
6659
8253
  if (!id) return writeJsonError(res, 400, "provider id required in path");
6660
8254
  const row = cfg.providers.find((p) => p.id === id);
@@ -6668,7 +8262,8 @@ async function handleDiscoverModels(res, id, cfg) {
6668
8262
  try {
6669
8263
  const headers = { Accept: "application/json" };
6670
8264
  if (resolvedKey) headers["Authorization"] = `Bearer ${resolvedKey}`;
6671
- const response = await fetchUpstream2(url, { method: "GET", headers }, { providerId: "byo" });
8265
+ Object.assign(headers, expandRowExtraHeaders(row));
8266
+ const response = await fetchUpstream7(url, { method: "GET", headers }, { providerId: "byo" });
6672
8267
  if (!response.ok) {
6673
8268
  const text = await response.text().catch(() => "");
6674
8269
  let message = text.slice(0, 300);
@@ -6685,8 +8280,8 @@ async function handleDiscoverModels(res, id, cfg) {
6685
8280
  const data = await response.json();
6686
8281
  const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
6687
8282
  return writeJson4(res, 200, { models });
6688
- } catch (err5) {
6689
- const message = err5 instanceof Error ? err5.message : String(err5);
8283
+ } catch (err8) {
8284
+ const message = err8 instanceof Error ? err8.message : String(err8);
6690
8285
  return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
6691
8286
  }
6692
8287
  }
@@ -6725,9 +8320,10 @@ async function handleTestModel(req, res, id, cfg) {
6725
8320
  messages: [{ role: "user", content: prompt }]
6726
8321
  };
6727
8322
  }
8323
+ Object.assign(headers, expandRowExtraHeaders(row));
6728
8324
  const startedAt = Date.now();
6729
8325
  try {
6730
- const response = await fetchUpstream2(
8326
+ const response = await fetchUpstream7(
6731
8327
  url,
6732
8328
  { method: "POST", headers, body: JSON.stringify(payload) },
6733
8329
  { providerId: "byo" }
@@ -6749,8 +8345,8 @@ async function handleTestModel(req, res, id, cfg) {
6749
8345
  latencyMs,
6750
8346
  sample: extractSampleText(text, row.apiFormat)
6751
8347
  });
6752
- } catch (err5) {
6753
- const message = err5 instanceof Error ? err5.message : String(err5);
8348
+ } catch (err8) {
8349
+ const message = err8 instanceof Error ? err8.message : String(err8);
6754
8350
  return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
6755
8351
  }
6756
8352
  }
@@ -6792,7 +8388,30 @@ async function handleProviderKeys(res, id, cfg, deps) {
6792
8388
  const row = cfg.providers.find((p) => p.id === id);
6793
8389
  if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
6794
8390
  const cooldown = await deps.apiKeyPool.getKeyHealth(id);
6795
- return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
8391
+ const views = toPoolKeyView(row, cooldown, deps);
8392
+ if (deps.providerKeyQuota) {
8393
+ const quotas = await Promise.allSettled(
8394
+ views.map((view) => deps.providerKeyQuota.quotaFor(row, view.id))
8395
+ );
8396
+ views.forEach((view, index) => {
8397
+ const settled = quotas[index];
8398
+ if (settled.status === "fulfilled" && settled.value) view.quota = settled.value;
8399
+ });
8400
+ }
8401
+ return writeJson4(res, 200, { keys: views });
8402
+ }
8403
+ async function handleProviderKeyQuotaRefresh(res, id, keyId, cfg, deps) {
8404
+ if (!deps.providerKeyQuota) return writeJsonError(res, 501, "provider key quota is not available");
8405
+ if (!id || !keyId) return writeJsonError(res, 400, "provider id and key id required in path");
8406
+ const row = cfg.providers.find((p) => p.id === id);
8407
+ if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
8408
+ try {
8409
+ const quota = await deps.providerKeyQuota.quotaFor(row, keyId, { force: true });
8410
+ if (!quota) return writeJsonError(res, 404, `no quota endpoint for key '${keyId}'`);
8411
+ return writeJson4(res, 200, { quota });
8412
+ } catch {
8413
+ return writeJsonError(res, 502, "quota refresh failed");
8414
+ }
6796
8415
  }
6797
8416
  function parsePoolKeyInput(body, existing) {
6798
8417
  const out = {};
@@ -7009,6 +8628,7 @@ function parseProviderInput(body, existing) {
7009
8628
  const apiVersion = typeof body["apiVersion"] === "string" && body["apiVersion"].length > 0 ? body["apiVersion"] : body["apiVersion"] === null ? void 0 : existing?.apiVersion;
7010
8629
  const modelsEndpoint = typeof body["modelsEndpoint"] === "string" && body["modelsEndpoint"].length > 0 ? body["modelsEndpoint"] : body["modelsEndpoint"] === null ? void 0 : existing?.modelsEndpoint;
7011
8630
  const maxConcurrency = typeof body["maxConcurrency"] === "number" && Number.isFinite(body["maxConcurrency"]) ? body["maxConcurrency"] : body["maxConcurrency"] === null ? void 0 : existing?.maxConcurrency;
8631
+ const extraHeaders = body["extraHeaders"] === null ? void 0 : body["extraHeaders"] === void 0 ? existing?.extraHeaders : validateExtraHeaders(body["extraHeaders"]);
7012
8632
  const transformer = body["transformer"] === null ? void 0 : parseTransformerInput(body["transformer"], existing?.transformer);
7013
8633
  const codingPlan = body["codingPlan"] === null ? void 0 : body["codingPlan"] === void 0 ? existing?.codingPlan : body["codingPlan"] && typeof body["codingPlan"] === "object" && !Array.isArray(body["codingPlan"]) ? parseCodingPlanInput(body["codingPlan"], existing?.codingPlan) : existing?.codingPlan;
7014
8634
  const apiModes = body["apiModes"] === null ? void 0 : parseApiModesInput(body["apiModes"], existing?.apiModes);
@@ -7034,6 +8654,7 @@ function parseProviderInput(body, existing) {
7034
8654
  apiVersion,
7035
8655
  maxConcurrency,
7036
8656
  modelsEndpoint,
8657
+ extraHeaders,
7037
8658
  transformer: migrated.transformer,
7038
8659
  codingPlan,
7039
8660
  apiModes,
@@ -7055,7 +8676,10 @@ function handlePresets(res, method) {
7055
8676
  description: p.description,
7056
8677
  features: p.features,
7057
8678
  website: p.website,
7058
- modelsEndpoint: p.modelsEndpoint
8679
+ modelsEndpoint: p.modelsEndpoint,
8680
+ // Static extra headers ride along so `addFromPreset` can seed them onto the
8681
+ // row (the write gateway re-validates via the shared allowlist).
8682
+ extraHeaders: p.extraHeaders
7059
8683
  }));
7060
8684
  return writeJson4(res, 200, { presets, excluded });
7061
8685
  }
@@ -7537,12 +9161,12 @@ async function handleAccounts(req, res, method, rest, deps) {
7537
9161
  }
7538
9162
  return writeJson4(res, 200, { ok: true, affected: result.affected });
7539
9163
  }
7540
- if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
7541
- const result = handleCodexOAuthStatus(rest[2], deps);
9164
+ if (method === "GET" && (rest[0] === "codex" || rest[0] === "kimi" || rest[0] === "grok" || rest[0] === "copilot") && rest[1] === "oauth" && rest[3] === "status") {
9165
+ const result = rest[0] === "codex" ? handleCodexOAuthStatus(rest[2], deps) : rest[0] === "kimi" ? handleKimiOAuthStatus(rest[2], deps) : rest[0] === "grok" ? handleGrokOAuthStatus(rest[2], deps) : handleCopilotOAuthStatus(rest[2], deps);
7542
9166
  return writeJson4(res, result.status, result.body);
7543
9167
  }
7544
- if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
7545
- const result = handleCodexOAuthCancel(rest[2], deps);
9168
+ if (method === "DELETE" && (rest[0] === "codex" || rest[0] === "kimi" || rest[0] === "grok" || rest[0] === "copilot") && rest[1] === "oauth" && rest[2]) {
9169
+ const result = rest[0] === "codex" ? handleCodexOAuthCancel(rest[2], deps) : rest[0] === "kimi" ? handleKimiOAuthCancel(rest[2], deps) : rest[0] === "grok" ? handleGrokOAuthCancel(rest[2], deps) : handleCopilotOAuthCancel(rest[2], deps);
7546
9170
  return writeJson4(res, result.status, result.body);
7547
9171
  }
7548
9172
  if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
@@ -7595,7 +9219,24 @@ async function handleAccounts(req, res, method, rest, deps) {
7595
9219
  return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
7596
9220
  }
7597
9221
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
7598
- const result = providerId === "codex" ? handleCodexOAuthStart(deps) : handleOAuthStart(providerId, deps);
9222
+ if (providerId === "codex") {
9223
+ const result2 = handleCodexOAuthStart(deps);
9224
+ return writeJson4(res, result2.status, result2.body);
9225
+ }
9226
+ if (providerId === "kimi") {
9227
+ const result2 = await handleKimiOAuthStart(deps);
9228
+ return writeJson4(res, result2.status, result2.body);
9229
+ }
9230
+ if (providerId === "grok") {
9231
+ const result2 = await handleGrokOAuthStart(deps);
9232
+ return writeJson4(res, result2.status, result2.body);
9233
+ }
9234
+ if (providerId === "copilot") {
9235
+ const body2 = await readJsonBody4(req);
9236
+ const result2 = await handleCopilotOAuthStart(deps, body2["enterpriseUrl"]);
9237
+ return writeJson4(res, result2.status, result2.body);
9238
+ }
9239
+ const result = handleOAuthStart(providerId, deps);
7599
9240
  return writeJson4(res, result.status, result.body);
7600
9241
  }
7601
9242
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
@@ -8089,12 +9730,12 @@ async function handlePlayground(req, res, method, deps) {
8089
9730
  const payload = body["body"];
8090
9731
  const status = deps.outboundApiServer.getStatus();
8091
9732
  if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
8092
- const path2 = resolvePlaygroundPath(endpoint, isRecord3(payload) ? payload : {});
9733
+ const path2 = resolvePlaygroundPath(endpoint, isRecord6(payload) ? payload : {});
8093
9734
  if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
8094
9735
  const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
8095
9736
  await proxyToOutbound(res, status.port, path2, key, upstreamBody);
8096
9737
  }
8097
- function isRecord3(v) {
9738
+ function isRecord6(v) {
8098
9739
  return !!v && typeof v === "object" && !Array.isArray(v);
8099
9740
  }
8100
9741
  function proxyToOutbound(res, outboundPort, path2, key, body) {
@@ -8123,8 +9764,8 @@ function proxyToOutbound(res, outboundPort, path2, key, body) {
8123
9764
  });
8124
9765
  }
8125
9766
  );
8126
- upstream.on("error", (err5) => {
8127
- if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err5.message}`);
9767
+ upstream.on("error", (err8) => {
9768
+ if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err8.message}`);
8128
9769
  else res.end();
8129
9770
  resolve11();
8130
9771
  });
@@ -8229,7 +9870,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
8229
9870
  }
8230
9871
 
8231
9872
  // src/admin/version.ts
8232
- var DAEMON_VERSION = true ? "0.3.0" : "0.0.0-dev";
9873
+ var DAEMON_VERSION = true ? "0.4.0" : "0.0.0-dev";
8233
9874
 
8234
9875
  // src/admin/AdminServer.ts
8235
9876
  var LOOPBACK_ADDR = "127.0.0.1";
@@ -8272,13 +9913,13 @@ var AdminServer = class {
8272
9913
  const server = http2.createServer((req, res) => {
8273
9914
  this.onRequest(req, res);
8274
9915
  });
8275
- const onError = (err5) => {
8276
- if (err5.code === "EADDRINUSE" && port !== 0) {
9916
+ const onError = (err8) => {
9917
+ if (err8.code === "EADDRINUSE" && port !== 0) {
8277
9918
  server.removeListener("error", onError);
8278
9919
  this.listen(bindAddr, 0).then(resolve11, reject);
8279
9920
  return;
8280
9921
  }
8281
- reject(err5);
9922
+ reject(err8);
8282
9923
  };
8283
9924
  server.on("error", onError);
8284
9925
  server.listen(port, bindAddr, () => {
@@ -8296,8 +9937,8 @@ var AdminServer = class {
8296
9937
  }
8297
9938
  /** Per-request handler: auth gate (when a token is set) → routing. */
8298
9939
  onRequest(req, res) {
8299
- void this.dispatch(req, res).catch((err5) => {
8300
- const message = err5 instanceof Error ? err5.message : String(err5);
9940
+ void this.dispatch(req, res).catch((err8) => {
9941
+ const message = err8 instanceof Error ? err8.message : String(err8);
8301
9942
  this.deps.logger.error("[AdminServer] unhandled error:", message);
8302
9943
  if (!res.headersSent) {
8303
9944
  res.writeHead(500, { "Content-Type": "application/json" });
@@ -8561,91 +10202,534 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
8561
10202
  return;
8562
10203
  }
8563
10204
  signal?.addEventListener("abort", abort, { once: true });
8564
- server.on("error", (err5) => {
10205
+ server.on("error", (err8) => {
8565
10206
  if (settled) return;
8566
10207
  settled = true;
8567
10208
  clearTimeout(timer);
8568
- if (err5.code === "EADDRINUSE") {
10209
+ if (err8.code === "EADDRINUSE") {
8569
10210
  reject(
8570
10211
  new Error(
8571
10212
  `login: cannot bind ${LOOPBACK_HOST}:${LOOPBACK_PORT} (address in use) \u2014 another codex login or process is holding the port`
8572
10213
  )
8573
10214
  );
8574
10215
  } else {
8575
- reject(err5);
10216
+ reject(err8);
8576
10217
  }
8577
10218
  });
8578
- const timer = setTimeout(() => {
8579
- finish(server, () => reject(new Error(`login: timed out after ${Math.round(timeoutMs / 1e3)}s waiting for the callback`)));
8580
- }, timeoutMs);
8581
- if (typeof timer.unref === "function") timer.unref();
8582
- server.listen(LOOPBACK_PORT, LOOPBACK_HOST);
8583
- });
10219
+ const timer = setTimeout(() => {
10220
+ finish(server, () => reject(new Error(`login: timed out after ${Math.round(timeoutMs / 1e3)}s waiting for the callback`)));
10221
+ }, timeoutMs);
10222
+ if (typeof timer.unref === "function") timer.unref();
10223
+ server.listen(LOOPBACK_PORT, LOOPBACK_HOST);
10224
+ });
10225
+ }
10226
+
10227
+ // src/pool/autoDisableStore.ts
10228
+ var AutoDisableStore = class {
10229
+ records = /* @__PURE__ */ new Map();
10230
+ /** Record (or overwrite) an auth-failure auto-disable for `keyId`. */
10231
+ markAutoDisabled(keyId, status, at) {
10232
+ this.records.set(keyId, { status, at, reason: "auth_failure" });
10233
+ }
10234
+ /** Whether `keyId` is currently auto-disabled in this process. */
10235
+ isDisabled(keyId) {
10236
+ return this.records.has(keyId);
10237
+ }
10238
+ /** Read the auto-disable record for `keyId`, or `undefined` when healthy. */
10239
+ get(keyId) {
10240
+ return this.records.get(keyId);
10241
+ }
10242
+ /** Clear all records (tests / teardown). */
10243
+ clear() {
10244
+ this.records.clear();
10245
+ }
10246
+ };
10247
+
10248
+ // src/pool/loadPoolKeys.ts
10249
+ var secretBox2 = null;
10250
+ function setSecretBox2(box) {
10251
+ secretBox2 = box;
10252
+ }
10253
+ function readKeyValue(rawKey) {
10254
+ return secretBox2 ? secretBox2.decryptMaybe(rawKey) : rawKey;
10255
+ }
10256
+ function normalizeEntry(providerId, entry, sortOrder, autoDisabled) {
10257
+ const enabledInConfig = entry.enabled !== false;
10258
+ const enabled = enabledInConfig && !autoDisabled.isDisabled(entry.id);
10259
+ return {
10260
+ id: entry.id,
10261
+ providerId,
10262
+ label: entry.label && entry.label.length > 0 ? entry.label : entry.id,
10263
+ apiKey: readKeyValue(entry.apiKey),
10264
+ enabled,
10265
+ weight: typeof entry.weight === "number" && Number.isFinite(entry.weight) ? entry.weight : 1,
10266
+ sortOrder
10267
+ };
10268
+ }
10269
+ function createPoolKeysLoader(getProviderRow, autoDisabled) {
10270
+ return async (providerId) => {
10271
+ const row = getProviderRow(providerId);
10272
+ if (!row) return [];
10273
+ const pool = (row.apiKeys ?? []).filter((k) => k.apiKey.length > 0);
10274
+ if (pool.length > 0) {
10275
+ return pool.map((entry, i) => normalizeEntry(providerId, entry, i, autoDisabled));
10276
+ }
10277
+ if (row.apiKey.length > 0) {
10278
+ return [
10279
+ normalizeEntry(
10280
+ providerId,
10281
+ { id: `${providerId}:default`, apiKey: row.apiKey, weight: 1, enabled: true },
10282
+ 0,
10283
+ autoDisabled
10284
+ )
10285
+ ];
10286
+ }
10287
+ return [];
10288
+ };
10289
+ }
10290
+
10291
+ // src/allowance/ProviderKeyQuotaService.ts
10292
+ import { mergeExtraHeaders as mergeExtraHeaders2 } from "@omnicross/core";
10293
+ import { fetchUpstream as fetchUpstream8 } from "@omnicross/core/pipeline/upstreamFetch";
10294
+
10295
+ // src/allowance/ProviderKeyQuota.ts
10296
+ var MINUTE_MS3 = 6e4;
10297
+ var HOUR_MS2 = 60 * MINUTE_MS3;
10298
+ var DAY_MS3 = 24 * HOUR_MS2;
10299
+ var WEEK_MS = 7 * DAY_MS3;
10300
+ var MONTH_MS = 30 * DAY_MS3;
10301
+ function finiteNumber5(value) {
10302
+ if (value === null || value === void 0 || value === "") return void 0;
10303
+ const parsed = typeof value === "number" ? value : Number(value);
10304
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
10305
+ }
10306
+ function finitePercent4(value) {
10307
+ const parsed = finiteNumber5(value);
10308
+ return parsed !== void 0 && parsed <= 100 ? parsed : null;
10309
+ }
10310
+ function isoInstant3(value) {
10311
+ if (typeof value === "string" && value.trim()) {
10312
+ const time = Date.parse(value);
10313
+ if (Number.isFinite(time)) return new Date(time).toISOString();
10314
+ }
10315
+ const numeric = finiteNumber5(value);
10316
+ if (numeric !== void 0 && numeric > 1e9) {
10317
+ const ms = numeric > 1e12 ? numeric : numeric * 1e3;
10318
+ return new Date(ms).toISOString();
10319
+ }
10320
+ return void 0;
10321
+ }
10322
+ function secondsUntil7(instant, now) {
10323
+ if (!instant) return void 0;
10324
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
10325
+ }
10326
+ function isRecord7(value) {
10327
+ return !!value && typeof value === "object" && !Array.isArray(value);
10328
+ }
10329
+ function detectProviderKeyQuotaAdapter(baseUrl) {
10330
+ if (!baseUrl) return null;
10331
+ let url;
10332
+ try {
10333
+ url = new URL(baseUrl);
10334
+ } catch {
10335
+ return null;
10336
+ }
10337
+ const host = url.hostname.toLowerCase();
10338
+ const path2 = url.pathname.toLowerCase();
10339
+ if ((host === "api.z.ai" || host === "open.bigmodel.cn") && path2.includes("/coding")) {
10340
+ return "zai";
10341
+ }
10342
+ if ((host === "api.minimax.io" || host === "api.minimaxi.com") && // Token Plan rides the plain openai `/v1` (chat completions) surface; the
10343
+ // anthropic `/anthropic` rows are excluded (their usage impl is unverified).
10344
+ (path2 === "/v1" || path2 === "/v1/" || path2 === "" || path2 === "/")) {
10345
+ return "minimax-token-plan";
10346
+ }
10347
+ if (host === "api.code.umans.ai") return "umans";
10348
+ if (host === "api.synthetic.new") return "synthetic";
10349
+ if (host === "api.cline.bot") return "cline-pass";
10350
+ return null;
10351
+ }
10352
+ function providerKeyQuotaUrl(adapter, baseUrl) {
10353
+ const origin = new URL(baseUrl).origin;
10354
+ if (adapter === "zai") return `${origin}/api/monitor/usage/quota/limit`;
10355
+ if (adapter === "minimax-token-plan") return `${origin}/v1/token_plan/remains`;
10356
+ if (adapter === "umans") return `${origin}/v1/usage`;
10357
+ if (adapter === "cline-pass") return `${origin}/api/v1/users/me/plan/usage-limits`;
10358
+ return `${origin}/v2/quotas`;
10359
+ }
10360
+ function providerKeyQuotaAuthHeader(adapter, key) {
10361
+ return adapter === "zai" ? key : `Bearer ${key}`;
10362
+ }
10363
+ function zaiWindowDurationMs(item) {
10364
+ const count = item.number !== void 0 && item.number > 0 ? item.number : 1;
10365
+ switch (item.unit) {
10366
+ case 3:
10367
+ return count * HOUR_MS2;
10368
+ case 4:
10369
+ return count * DAY_MS3;
10370
+ case 5:
10371
+ return count * MONTH_MS;
10372
+ case 6:
10373
+ return WEEK_MS;
10374
+ default:
10375
+ return void 0;
10376
+ }
10377
+ }
10378
+ function zaiWindowIdLabel(durationMs) {
10379
+ if (durationMs === WEEK_MS) return { id: "seven-day", label: "7 days" };
10380
+ if (durationMs === 5 * HOUR_MS2) return { id: "five-hour", label: "5 hours" };
10381
+ if (durationMs === MONTH_MS) return { id: "thirty-day", label: "30 days" };
10382
+ if (durationMs !== void 0 && durationMs % DAY_MS3 === 0) {
10383
+ const days = durationMs / DAY_MS3;
10384
+ return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}` };
10385
+ }
10386
+ if (durationMs !== void 0 && durationMs % HOUR_MS2 === 0) {
10387
+ const hours = durationMs / HOUR_MS2;
10388
+ return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}` };
10389
+ }
10390
+ return { id: "quota", label: "Quota" };
10391
+ }
10392
+ function parseZaiQuotaPayload(payload, now) {
10393
+ if (!isRecord7(payload)) return null;
10394
+ const data = isRecord7(payload["data"]) ? payload["data"] : payload;
10395
+ if (payload["success"] === false) return null;
10396
+ const limits = Array.isArray(data["limits"]) ? data["limits"] : [];
10397
+ const byWindow = /* @__PURE__ */ new Map();
10398
+ for (const raw of limits) {
10399
+ if (!isRecord7(raw)) continue;
10400
+ const item = raw;
10401
+ if (item.type === void 0) continue;
10402
+ const details = raw["usageDetails"];
10403
+ if (Array.isArray(details) && details.some((d) => isRecord7(d) && d["modelCode"] === "zread")) {
10404
+ continue;
10405
+ }
10406
+ const durationMs = zaiWindowDurationMs(item);
10407
+ const { id, label } = zaiWindowIdLabel(durationMs);
10408
+ const limit = finiteNumber5(item.usage);
10409
+ const used = finiteNumber5(item.currentValue);
10410
+ const fromAbsolute = limit !== void 0 && used !== void 0 && limit > 0 ? Math.min(100, used / limit * 100) : void 0;
10411
+ const fromPercentage = finitePercent4(item.percentage) ?? void 0;
10412
+ const usedPercent = fromAbsolute !== void 0 ? Math.round(fromAbsolute * 10) / 10 : fromPercentage;
10413
+ if (usedPercent === void 0) continue;
10414
+ const resetsAt = isoInstant3(item.nextResetTime);
10415
+ const candidate = {
10416
+ id,
10417
+ label,
10418
+ scope: "all",
10419
+ usedPercent,
10420
+ ...durationMs !== void 0 ? { windowMinutes: Math.round(durationMs / MINUTE_MS3) } : {},
10421
+ ...resetsAt !== void 0 ? { resetsAt } : {},
10422
+ remainingSeconds: secondsUntil7(resetsAt, now),
10423
+ state: "fresh"
10424
+ };
10425
+ const existing = byWindow.get(id);
10426
+ if (!existing || (candidate.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
10427
+ byWindow.set(id, candidate);
10428
+ }
10429
+ }
10430
+ const windows = [...byWindow.values()].sort((a, b) => (a.windowMinutes ?? Number.POSITIVE_INFINITY) - (b.windowMinutes ?? Number.POSITIVE_INFINITY));
10431
+ return windows.length > 0 ? windows.slice(0, 4) : null;
10432
+ }
10433
+ var MINIMAX_STATUS_EXHAUSTED = 2;
10434
+ var MINIMAX_SHARED_BUCKET = "general";
10435
+ function parseMiniMaxBucket(value) {
10436
+ if (!isRecord7(value)) return null;
10437
+ const modelName = typeof value["model_name"] === "string" ? value["model_name"].trim() : "";
10438
+ if (!modelName) return null;
10439
+ const instant = (v) => {
10440
+ const n = finiteNumber5(v);
10441
+ return n !== void 0 && n > 1e9 ? n > 1e12 ? n : n * 1e3 : void 0;
10442
+ };
10443
+ return {
10444
+ modelName,
10445
+ intervalEnd: instant(value["end_time"]),
10446
+ intervalRemainingPercent: finiteNumber5(value["current_interval_remaining_percent"]),
10447
+ intervalStatus: finiteNumber5(value["current_interval_status"]),
10448
+ weeklyEnd: instant(value["weekly_end_time"]),
10449
+ weeklyRemainingPercent: finiteNumber5(value["current_weekly_remaining_percent"]),
10450
+ weeklyStatus: finiteNumber5(value["current_weekly_status"])
10451
+ };
10452
+ }
10453
+ function minimaxWindow(id, label, windowMinutes, resetsAtMs, remainingPercent, status, now) {
10454
+ const usedPercent = status === MINIMAX_STATUS_EXHAUSTED ? 100 : remainingPercent !== void 0 ? Math.round((100 - remainingPercent) * 10) / 10 : null;
10455
+ const resetsAt = resetsAtMs !== void 0 ? new Date(resetsAtMs).toISOString() : void 0;
10456
+ return {
10457
+ id,
10458
+ label,
10459
+ scope: "all",
10460
+ usedPercent,
10461
+ ...windowMinutes !== void 0 ? { windowMinutes } : {},
10462
+ ...resetsAt !== void 0 ? { resetsAt } : {},
10463
+ remainingSeconds: secondsUntil7(resetsAt, now),
10464
+ state: usedPercent !== null ? "fresh" : "unavailable"
10465
+ };
10466
+ }
10467
+ function parseMiniMaxTokenPlanPayload(payload, now) {
10468
+ if (!isRecord7(payload)) return null;
10469
+ const baseResp = payload["base_resp"];
10470
+ if (!isRecord7(baseResp) || baseResp["status_code"] !== 0) return null;
10471
+ const buckets = Array.isArray(payload["model_remains"]) ? payload["model_remains"] : [];
10472
+ let general = null;
10473
+ for (const raw of buckets) {
10474
+ const bucket = parseMiniMaxBucket(raw);
10475
+ if (bucket?.modelName === MINIMAX_SHARED_BUCKET) {
10476
+ general = bucket;
10477
+ break;
10478
+ }
10479
+ }
10480
+ if (!general) return null;
10481
+ return [
10482
+ minimaxWindow(
10483
+ "five-hour",
10484
+ "5 hours",
10485
+ 5 * 60,
10486
+ general.intervalEnd,
10487
+ general.intervalRemainingPercent,
10488
+ general.intervalStatus,
10489
+ now
10490
+ ),
10491
+ minimaxWindow(
10492
+ "seven-day",
10493
+ "7 days",
10494
+ Math.round(WEEK_MS / MINUTE_MS3),
10495
+ general.weeklyEnd,
10496
+ general.weeklyRemainingPercent,
10497
+ general.weeklyStatus,
10498
+ now
10499
+ )
10500
+ ];
10501
+ }
10502
+ function parseUmansUsagePayload(payload, now) {
10503
+ if (!isRecord7(payload)) return null;
10504
+ const limits = isRecord7(payload["limits"]) ? payload["limits"] : void 0;
10505
+ const requests = limits && isRecord7(limits["requests"]) ? limits["requests"] : void 0;
10506
+ const usage = isRecord7(payload["usage"]) ? payload["usage"] : void 0;
10507
+ const window = isRecord7(payload["window"]) ? payload["window"] : void 0;
10508
+ const hardCap = finiteNumber5(requests?.["hard_cap"]);
10509
+ const softLimit = finiteNumber5(requests?.["limit"]);
10510
+ const requestsInWindow = finiteNumber5(usage?.["requests_in_window"]);
10511
+ const weightedInWindow = finiteNumber5(usage?.["weighted_in_window"]);
10512
+ const resetsAt = isoInstant3(window?.["resets_at"]);
10513
+ let usedPercent = null;
10514
+ if (hardCap !== void 0 && hardCap > 0 && requestsInWindow !== void 0) {
10515
+ usedPercent = Math.round(Math.min(100, requestsInWindow / hardCap * 100) * 10) / 10;
10516
+ } else if (softLimit !== void 0 && softLimit > 0 && weightedInWindow !== void 0) {
10517
+ usedPercent = Math.round(Math.min(100, weightedInWindow / softLimit * 100) * 10) / 10;
10518
+ }
10519
+ if (usedPercent === null && resetsAt === void 0) return null;
10520
+ return [
10521
+ {
10522
+ id: "five-hour",
10523
+ label: "5 hours",
10524
+ scope: "all",
10525
+ usedPercent,
10526
+ windowMinutes: 5 * 60,
10527
+ ...resetsAt !== void 0 ? { resetsAt } : {},
10528
+ remainingSeconds: secondsUntil7(resetsAt, now),
10529
+ state: "fresh"
10530
+ }
10531
+ ];
10532
+ }
10533
+ function parseSyntheticQuotasPayload(payload, now) {
10534
+ if (!isRecord7(payload)) return null;
10535
+ const fiveHour = isRecord7(payload["rollingFiveHourLimit"]) ? payload["rollingFiveHourLimit"] : void 0;
10536
+ const weekly = isRecord7(payload["weeklyTokenLimit"]) ? payload["weeklyTokenLimit"] : void 0;
10537
+ const windows = [];
10538
+ if (fiveHour) {
10539
+ const max = finiteNumber5(fiveHour["max"]);
10540
+ const remaining = finiteNumber5(fiveHour["remaining"]);
10541
+ const usedPercent = max !== void 0 && max > 0 && remaining !== void 0 ? Math.round(Math.min(100, (max - remaining) / max * 100) * 10) / 10 : null;
10542
+ const resetsAt = isoInstant3(fiveHour["nextTickAt"]);
10543
+ windows.push({
10544
+ id: "five-hour",
10545
+ label: "5 hours",
10546
+ scope: "all",
10547
+ usedPercent,
10548
+ windowMinutes: 5 * 60,
10549
+ ...resetsAt !== void 0 ? { resetsAt } : {},
10550
+ remainingSeconds: secondsUntil7(resetsAt, now),
10551
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
10552
+ });
10553
+ }
10554
+ if (weekly) {
10555
+ const percentRemaining = finiteNumber5(weekly["percentRemaining"]);
10556
+ const usedPercent = percentRemaining !== void 0 ? Math.round(Math.min(100, Math.max(0, 100 - percentRemaining)) * 10) / 10 : null;
10557
+ const resetsAt = isoInstant3(weekly["nextRegenAt"]);
10558
+ windows.push({
10559
+ id: "seven-day",
10560
+ label: "7 days",
10561
+ scope: "all",
10562
+ usedPercent,
10563
+ windowMinutes: 7 * 24 * 60,
10564
+ ...resetsAt !== void 0 ? { resetsAt } : {},
10565
+ remainingSeconds: secondsUntil7(resetsAt, now),
10566
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
10567
+ });
10568
+ }
10569
+ return windows.length > 0 ? windows : null;
10570
+ }
10571
+ var CLINE_WINDOW_CONFIG = {
10572
+ five_hour: { id: "five-hour", label: "5 hours", minutes: 5 * 60 },
10573
+ weekly: { id: "seven-day", label: "7 days", minutes: 7 * 24 * 60 },
10574
+ monthly: { id: "thirty-day", label: "30 days", minutes: 30 * 24 * 60 }
10575
+ };
10576
+ function parseClinePassUsageLimitsPayload(payload, now) {
10577
+ if (!isRecord7(payload)) return null;
10578
+ const data = isRecord7(payload["data"]) ? payload["data"] : payload;
10579
+ const limits = Array.isArray(data["limits"]) ? data["limits"] : [];
10580
+ const windows = [];
10581
+ for (const raw of limits) {
10582
+ if (!isRecord7(raw)) continue;
10583
+ const config = CLINE_WINDOW_CONFIG[typeof raw["type"] === "string" ? raw["type"] : ""];
10584
+ if (!config) continue;
10585
+ const usedPercent = finitePercent4(raw["percentUsed"]);
10586
+ if (usedPercent === null) continue;
10587
+ const resetsAt = isoInstant3(raw["resetsAt"]);
10588
+ windows.push({
10589
+ id: config.id,
10590
+ label: config.label,
10591
+ scope: "all",
10592
+ usedPercent,
10593
+ windowMinutes: config.minutes,
10594
+ ...resetsAt !== void 0 ? { resetsAt } : {},
10595
+ remainingSeconds: secondsUntil7(resetsAt, now),
10596
+ state: "fresh"
10597
+ });
10598
+ }
10599
+ return windows.length > 0 ? windows : null;
8584
10600
  }
8585
10601
 
8586
- // src/pool/autoDisableStore.ts
8587
- var AutoDisableStore = class {
8588
- records = /* @__PURE__ */ new Map();
8589
- /** Record (or overwrite) an auth-failure auto-disable for `keyId`. */
8590
- markAutoDisabled(keyId, status, at) {
8591
- this.records.set(keyId, { status, at, reason: "auth_failure" });
10602
+ // src/allowance/ProviderKeyQuotaService.ts
10603
+ function parseQuotaPayload(adapter, payload, now) {
10604
+ switch (adapter) {
10605
+ case "zai":
10606
+ return parseZaiQuotaPayload(payload, now);
10607
+ case "minimax-token-plan":
10608
+ return parseMiniMaxTokenPlanPayload(payload, now);
10609
+ case "umans":
10610
+ return parseUmansUsagePayload(payload, now);
10611
+ case "synthetic":
10612
+ return parseSyntheticQuotasPayload(payload, now);
10613
+ case "cline-pass":
10614
+ return parseClinePassUsageLimitsPayload(payload, now);
10615
+ }
10616
+ }
10617
+ var PROVIDER_KEY_QUOTA_CACHE_MS = 5 * 6e4;
10618
+ function resolvedBaseUrl(row) {
10619
+ const modes = row.apiModes ?? [];
10620
+ const selected = row.selectedApiModeId ? modes.find((mode) => mode.id === row.selectedApiModeId) : void 0;
10621
+ const fallback = modes[0];
10622
+ const modeBase = selected?.baseUrl ?? fallback?.baseUrl;
10623
+ return modeBase ?? row.codingPlan?.baseUrl ?? row.baseUrl;
10624
+ }
10625
+ function rowKeyEntries(row) {
10626
+ const pool = (row.apiKeys ?? []).filter((entry) => entry.apiKey.length > 0);
10627
+ if (pool.length > 0) return pool.map((entry) => ({ id: entry.id, apiKey: entry.apiKey }));
10628
+ if (row.apiKey.length > 0) {
10629
+ return [{ id: `${row.id}:default`, apiKey: row.apiKey }];
8592
10630
  }
8593
- /** Whether `keyId` is currently auto-disabled in this process. */
8594
- isDisabled(keyId) {
8595
- return this.records.has(keyId);
10631
+ return [];
10632
+ }
10633
+ var ProviderKeyQuotaService = class {
10634
+ constructor(box, fetchImpl = (url, init) => fetchUpstream8(url, init, { redactBodies: true }), now = Date.now) {
10635
+ this.box = box;
10636
+ this.fetchImpl = fetchImpl;
10637
+ this.now = now;
8596
10638
  }
8597
- /** Read the auto-disable record for `keyId`, or `undefined` when healthy. */
8598
- get(keyId) {
8599
- return this.records.get(keyId);
10639
+ box;
10640
+ fetchImpl;
10641
+ now;
10642
+ cache = /* @__PURE__ */ new Map();
10643
+ inFlight = /* @__PURE__ */ new Map();
10644
+ /**
10645
+ * Quota for one key of a provider row, or `null` when the row has no quota
10646
+ * adapter / no such key. Cache-first; concurrent reads share one flight.
10647
+ */
10648
+ async quotaFor(row, keyId, options = {}) {
10649
+ const adapter = detectProviderKeyQuotaAdapter(resolvedBaseUrl(row));
10650
+ if (!adapter) return null;
10651
+ const entry = rowKeyEntries(row).find((candidate) => candidate.id === keyId);
10652
+ if (!entry) return null;
10653
+ const cacheKey = `${row.id}\0${keyId}`;
10654
+ const now = this.now();
10655
+ const cached = this.cache.get(cacheKey);
10656
+ if (!options.force && cached && Date.parse(cached.expiresAt) > now) return cached;
10657
+ const running = this.inFlight.get(cacheKey);
10658
+ if (running) return running;
10659
+ const promise = this.fetchQuota(adapter, row, entry.apiKey, cacheKey).catch((error) => {
10660
+ void error;
10661
+ const previous = this.cache.get(cacheKey);
10662
+ if (previous) {
10663
+ const degraded = {
10664
+ ...previous,
10665
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
10666
+ windows: previous.windows.map((window) => ({
10667
+ ...window,
10668
+ state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
10669
+ })),
10670
+ errorCode: "quota_request_failed"
10671
+ };
10672
+ this.cache.set(cacheKey, degraded);
10673
+ return degraded;
10674
+ }
10675
+ return null;
10676
+ }).finally(() => this.inFlight.delete(cacheKey));
10677
+ this.inFlight.set(cacheKey, promise);
10678
+ return promise;
8600
10679
  }
8601
- /** Clear all records (tests / teardown). */
8602
- clear() {
8603
- this.records.clear();
10680
+ /** Drop cached rows for a provider (key added/removed/rotated). */
10681
+ invalidateProvider(providerRowId) {
10682
+ for (const key of this.cache.keys()) {
10683
+ if (key.startsWith(`${providerRowId}\0`)) this.cache.delete(key);
10684
+ }
8604
10685
  }
8605
- };
8606
-
8607
- // src/pool/loadPoolKeys.ts
8608
- var secretBox2 = null;
8609
- function setSecretBox2(box) {
8610
- secretBox2 = box;
8611
- }
8612
- function readKeyValue(rawKey) {
8613
- return secretBox2 ? secretBox2.decryptMaybe(rawKey) : rawKey;
8614
- }
8615
- function normalizeEntry(providerId, entry, sortOrder, autoDisabled) {
8616
- const enabledInConfig = entry.enabled !== false;
8617
- const enabled = enabledInConfig && !autoDisabled.isDisabled(entry.id);
8618
- return {
8619
- id: entry.id,
8620
- providerId,
8621
- label: entry.label && entry.label.length > 0 ? entry.label : entry.id,
8622
- apiKey: readKeyValue(entry.apiKey),
8623
- enabled,
8624
- weight: typeof entry.weight === "number" && Number.isFinite(entry.weight) ? entry.weight : 1,
8625
- sortOrder
8626
- };
8627
- }
8628
- function createPoolKeysLoader(getProviderRow, autoDisabled) {
8629
- return async (providerId) => {
8630
- const row = getProviderRow(providerId);
8631
- if (!row) return [];
8632
- const pool = (row.apiKeys ?? []).filter((k) => k.apiKey.length > 0);
8633
- if (pool.length > 0) {
8634
- return pool.map((entry, i) => normalizeEntry(providerId, entry, i, autoDisabled));
10686
+ async fetchQuota(adapter, row, rawKey, cacheKey) {
10687
+ const baseUrl = resolvedBaseUrl(row);
10688
+ const url = providerKeyQuotaUrl(adapter, baseUrl);
10689
+ const key = this.box.decryptMaybe(rawKey);
10690
+ const now = this.now();
10691
+ const response = await this.fetchImpl(url, {
10692
+ method: "GET",
10693
+ headers: {
10694
+ Authorization: providerKeyQuotaAuthHeader(adapter, key),
10695
+ Accept: "application/json",
10696
+ "Content-Type": "application/json",
10697
+ // The row's static identity headers ride along — the Cline usage
10698
+ // endpoint sits behind the SAME client-identity 403 gate as inference.
10699
+ ...mergeExtraHeaders2({}, row.extraHeaders)
10700
+ },
10701
+ signal: AbortSignal.timeout(15e3)
10702
+ });
10703
+ if (response.status === 401 || response.status === 403) {
10704
+ const snapshot2 = {
10705
+ adapter,
10706
+ observedAt: new Date(now).toISOString(),
10707
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
10708
+ windows: [],
10709
+ errorCode: "quota_unauthorized"
10710
+ };
10711
+ this.cache.set(cacheKey, snapshot2);
10712
+ return snapshot2;
8635
10713
  }
8636
- if (row.apiKey.length > 0) {
8637
- return [
8638
- normalizeEntry(
8639
- providerId,
8640
- { id: `${providerId}:default`, apiKey: row.apiKey, weight: 1, enabled: true },
8641
- 0,
8642
- autoDisabled
8643
- )
8644
- ];
10714
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
10715
+ let payload;
10716
+ try {
10717
+ payload = await response.json();
10718
+ } catch {
10719
+ throw new Error("invalid JSON");
8645
10720
  }
8646
- return [];
8647
- };
8648
- }
10721
+ const windows = parseQuotaPayload(adapter, payload, now);
10722
+ const snapshot = {
10723
+ adapter,
10724
+ observedAt: new Date(now).toISOString(),
10725
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
10726
+ windows: windows ?? [],
10727
+ ...windows ? {} : { errorCode: "quota_unavailable" }
10728
+ };
10729
+ this.cache.set(cacheKey, snapshot);
10730
+ return snapshot;
10731
+ }
10732
+ };
8649
10733
 
8650
10734
  // src/image-generation/ImageDoctorService.ts
8651
10735
  import {
@@ -13384,6 +15468,10 @@ function toLLMProvider(row) {
13384
15468
  // `parseProviderInput`), so customizations are preserved (the row value wins).
13385
15469
  apiModes: row.apiModes,
13386
15470
  selectedApiModeId: row.selectedApiModeId,
15471
+ // Static extra request headers ride along verbatim (load-guarded — no
15472
+ // auth/content names); core's `getProviderHeaders` merges them into every
15473
+ // BYO request, and the same-format relay path inherits that funnel.
15474
+ extraHeaders: row.extraHeaders,
13387
15475
  // Official-Anthropic signature handling only matters for the Anthropic
13388
15476
  // ingress (deferred → 502); leave it off for the BYO transform path.
13389
15477
  isOfficial: false
@@ -14763,21 +16851,23 @@ function bucketLabel(bucketStartTs, bucket) {
14763
16851
  }
14764
16852
 
14765
16853
  // src/ports/JsonOutboundKeyDb.ts
16854
+ import { existsSync as existsSync19, readFileSync as readFileSync15 } from "fs";
16855
+ import {
16856
+ validateOutboundPermissions as validateOutboundPermissions3
16857
+ } from "@omnicross/core";
16858
+
16859
+ // src/ports/atomicFile.ts
14766
16860
  import { randomBytes as randomBytes11 } from "crypto";
14767
16861
  import {
14768
16862
  closeSync as closeSync8,
14769
16863
  existsSync as existsSync18,
14770
16864
  fsyncSync as fsyncSync7,
14771
16865
  openSync as openSync8,
14772
- readFileSync as readFileSync15,
14773
16866
  renameSync as renameSync10,
14774
16867
  unlinkSync as unlinkSync12,
14775
16868
  writeFileSync as writeFileSync13
14776
16869
  } from "fs";
14777
16870
  import { basename as basename8, dirname as dirname14, join as join19 } from "path";
14778
- import {
14779
- validateOutboundPermissions as validateOutboundPermissions3
14780
- } from "@omnicross/core";
14781
16871
  function atomicReplaceUtf8(targetPath, contents) {
14782
16872
  const tempPath = join19(
14783
16873
  dirname14(targetPath),
@@ -14807,6 +16897,8 @@ function atomicReplaceUtf8(targetPath, contents) {
14807
16897
  throw error;
14808
16898
  }
14809
16899
  }
16900
+
16901
+ // src/ports/JsonOutboundKeyDb.ts
14810
16902
  var JsonOutboundKeyDb = class {
14811
16903
  /**
14812
16904
  * @param secretBox OPTIONAL reversible-secret codec. When present, a created
@@ -14949,7 +17041,7 @@ var JsonOutboundKeyDb = class {
14949
17041
  }
14950
17042
  /** Read the key rows, tolerating a missing/corrupt file (→ empty list). */
14951
17043
  readRows() {
14952
- if (!existsSync18(this.keysPath)) return [];
17044
+ if (!existsSync19(this.keysPath)) return [];
14953
17045
  try {
14954
17046
  const parsed = JSON.parse(readFileSync15(this.keysPath, "utf8"));
14955
17047
  return Array.isArray(parsed) ? parsed : [];
@@ -14968,7 +17060,7 @@ function applyPolicyField(row, field, value) {
14968
17060
  }
14969
17061
 
14970
17062
  // src/ports/JsonPricingStore.ts
14971
- import { existsSync as existsSync19, readFileSync as readFileSync16, renameSync as renameSync11, rmSync as rmSync3, writeFileSync as writeFileSync14 } from "fs";
17063
+ import { existsSync as existsSync20, readFileSync as readFileSync16, renameSync as renameSync11, rmSync as rmSync3, writeFileSync as writeFileSync14 } from "fs";
14972
17064
  import { randomUUID as randomUUID5 } from "crypto";
14973
17065
  var JsonPricingStore = class {
14974
17066
  constructor(pricingPath) {
@@ -14983,7 +17075,7 @@ var JsonPricingStore = class {
14983
17075
  * otherwise unusable pricing table after a crash or manual file edit.
14984
17076
  */
14985
17077
  hasUsableSnapshot() {
14986
- if (!existsSync19(this.pricingPath)) return false;
17078
+ if (!existsSync20(this.pricingPath)) return false;
14987
17079
  try {
14988
17080
  const parsed = JSON.parse(readFileSync16(this.pricingPath, "utf8"));
14989
17081
  return Array.isArray(parsed) && parsed.some(isUsablePricingRow);
@@ -15098,7 +17190,7 @@ var JsonPricingStore = class {
15098
17190
  }
15099
17191
  /** Read the pricing rows, tolerating a missing/corrupt file (→ empty list). */
15100
17192
  readRows() {
15101
- if (!existsSync19(this.pricingPath)) return [];
17193
+ if (!existsSync20(this.pricingPath)) return [];
15102
17194
  try {
15103
17195
  const parsed = JSON.parse(readFileSync16(this.pricingPath, "utf8"));
15104
17196
  return Array.isArray(parsed) ? parsed : [];
@@ -15130,7 +17222,7 @@ function isUsablePricingRow(value) {
15130
17222
  }
15131
17223
 
15132
17224
  // src/pricing/PricingRefreshScheduler.ts
15133
- import { existsSync as existsSync20, readFileSync as readFileSync17, renameSync as renameSync12, writeFileSync as writeFileSync15 } from "fs";
17225
+ import { existsSync as existsSync21, readFileSync as readFileSync17, renameSync as renameSync12, writeFileSync as writeFileSync15 } from "fs";
15134
17226
  var EMPTY_STATE2 = {
15135
17227
  lastAttemptAt: null,
15136
17228
  lastSuccessAt: null,
@@ -15168,7 +17260,7 @@ var PricingRefreshScheduler = class {
15168
17260
  this.timer = null;
15169
17261
  }
15170
17262
  getState() {
15171
- if (!existsSync20(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
17263
+ if (!existsSync21(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
15172
17264
  try {
15173
17265
  const value = JSON.parse(readFileSync17(this.statePath, "utf8"));
15174
17266
  return {
@@ -15233,7 +17325,7 @@ function finiteOrNull(value) {
15233
17325
  }
15234
17326
 
15235
17327
  // src/ports/JsonVoucherDb.ts
15236
- import { existsSync as existsSync21, readFileSync as readFileSync18, writeFileSync as writeFileSync16 } from "fs";
17328
+ import { existsSync as existsSync22, readFileSync as readFileSync18, writeFileSync as writeFileSync16 } from "fs";
15237
17329
  var JsonVoucherDb = class {
15238
17330
  constructor(vouchersPath) {
15239
17331
  this.vouchersPath = vouchersPath;
@@ -15311,7 +17403,7 @@ var JsonVoucherDb = class {
15311
17403
  }
15312
17404
  /** Read the voucher rows, tolerating a missing/corrupt file (→ empty list). */
15313
17405
  readRows() {
15314
- if (!existsSync21(this.vouchersPath)) return [];
17406
+ if (!existsSync22(this.vouchersPath)) return [];
15315
17407
  try {
15316
17408
  const parsed = JSON.parse(readFileSync18(this.vouchersPath, "utf8"));
15317
17409
  return Array.isArray(parsed) ? parsed : [];
@@ -15325,16 +17417,18 @@ var JsonVoucherDb = class {
15325
17417
  };
15326
17418
 
15327
17419
  // src/ports/JsonSubscriptionCredentialStore.ts
15328
- import { existsSync as existsSync23, mkdirSync as mkdirSync6, readFileSync as readFileSync20, writeFileSync as writeFileSync17 } from "fs";
17420
+ import { existsSync as existsSync24, mkdirSync as mkdirSync6, readFileSync as readFileSync20, renameSync as renameSync13 } from "fs";
15329
17421
  import { dirname as dirname15 } from "path";
15330
17422
  import { getSharedAccountHealth as getSharedAccountHealth2 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
15331
17423
  import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling3 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
15332
- import { fetchUpstream as fetchUpstream3 } from "@omnicross/core/pipeline/upstreamFetch";
17424
+ import { fetchUpstream as fetchUpstream9 } from "@omnicross/core/pipeline/upstreamFetch";
15333
17425
  import { getSharedIdentityStore as getSharedIdentityStore2 } from "@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore";
15334
17426
  import {
15335
17427
  claudeOAuth as claudeOAuth2,
15336
17428
  codexOAuth as codexOAuth2,
15337
- geminiOAuth as geminiOAuth2
17429
+ geminiOAuth as geminiOAuth2,
17430
+ grokOAuth as grokOAuth2,
17431
+ kimiOAuth as kimiOAuth2
15338
17432
  } from "@omnicross/subscriptions";
15339
17433
 
15340
17434
  // src/ports/account-sync.ts
@@ -15379,7 +17473,7 @@ function findDuplicateCredentialIds(accounts) {
15379
17473
  }
15380
17474
 
15381
17475
  // src/ports/external-cli-credentials.ts
15382
- import { existsSync as existsSync22, readFileSync as readFileSync19 } from "fs";
17476
+ import { existsSync as existsSync23, readFileSync as readFileSync19 } from "fs";
15383
17477
  import { homedir as homedir4 } from "os";
15384
17478
  import { join as join20 } from "path";
15385
17479
  function externalStorePath(provider, home = homedir4()) {
@@ -15432,7 +17526,7 @@ function parseCodexTokensEnvelope(raw) {
15432
17526
  }
15433
17527
  function readExternalCliCredentials(provider, home = homedir4()) {
15434
17528
  const path2 = externalStorePath(provider, home);
15435
- if (!existsSync22(path2)) return null;
17529
+ if (!existsSync23(path2)) return null;
15436
17530
  let raw;
15437
17531
  try {
15438
17532
  const parsed = JSON.parse(readFileSync19(path2, "utf8"));
@@ -15458,16 +17552,18 @@ var JsonSubscriptionCredentialStore = class {
15458
17552
  * as on relay refresh egresses from the SAME proxy IP as the
15459
17553
  * account's traffic. NOT used by any read/write path.
15460
17554
  */
15461
- constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials) {
17555
+ constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials, atomicReplace = atomicReplaceUtf8) {
15462
17556
  this.tokensPath = tokensPath;
15463
17557
  this.box = box;
15464
17558
  this.fetchImpl = fetchImpl;
15465
17559
  this.externalCliReader = externalCliReader;
17560
+ this.atomicReplace = atomicReplace;
15466
17561
  }
15467
17562
  tokensPath;
15468
17563
  box;
15469
17564
  fetchImpl;
15470
17565
  externalCliReader;
17566
+ atomicReplace;
15471
17567
  /**
15472
17568
  * The proxy-aware `FetchLike` for one refresh round-trip (upstream-proxy M1). A
15473
17569
  * TEST-injected `fetchImpl` is returned verbatim; otherwise the refresh routes
@@ -15481,7 +17577,7 @@ var JsonSubscriptionCredentialStore = class {
15481
17577
  * a plaintext token pair into `upstream-trace.jsonl`.
15482
17578
  */
15483
17579
  buildRefreshFetch(providerId, accountId) {
15484
- return this.fetchImpl ?? ((url, init) => fetchUpstream3(url, init, { providerId, accountId, redactBodies: true }));
17580
+ return this.fetchImpl ?? ((url, init) => fetchUpstream9(url, init, { providerId, accountId, redactBodies: true }));
15485
17581
  }
15486
17582
  /**
15487
17583
  * In-flight refresh coalescing. OAuth refresh tokens are
@@ -15522,7 +17618,7 @@ var JsonSubscriptionCredentialStore = class {
15522
17618
  * other hot reads. Never returns token material.
15523
17619
  */
15524
17620
  getAccountProxy(providerId, accountId) {
15525
- if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego") {
17621
+ if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego" && providerId !== "kimi" && providerId !== "grok" && providerId !== "copilot") {
15526
17622
  return void 0;
15527
17623
  }
15528
17624
  return getAccountProxy(this.readConfig(), providerId, accountId);
@@ -15541,7 +17637,7 @@ var JsonSubscriptionCredentialStore = class {
15541
17637
  const fingerprintOn = identityStore.isEnabled();
15542
17638
  const now = Date.now();
15543
17639
  const out = {};
15544
- for (const provider of ["claude", "codex", "gemini", "opencodego"]) {
17640
+ for (const provider of ["claude", "codex", "gemini", "opencodego", "kimi", "grok", "copilot"]) {
15545
17641
  const sanitized = sanitizeAccounts(config, provider);
15546
17642
  if (sanitized.length === 0) continue;
15547
17643
  for (const account of sanitized) {
@@ -15699,6 +17795,107 @@ var JsonSubscriptionCredentialStore = class {
15699
17795
  }
15700
17796
  });
15701
17797
  }
17798
+ /**
17799
+ * Refresh the Kimi Code (Moonshot) OAuth access token (device-flow grant).
17800
+ * Kimi ROTATES the refresh token, so the response's pair is written back
17801
+ * whole; the account's stable `deviceId` (fingerprint header input) is
17802
+ * preserved. The refresh call carries the CLI fingerprint headers. HONEST
17803
+ * `false` when no refresh_token.
17804
+ */
17805
+ async refreshKimiToken() {
17806
+ return this.coalesce("kimi:active", async () => {
17807
+ const config = this.readConfig();
17808
+ const active = getActiveAccount(config, "kimi");
17809
+ const kimi = active?.tokens;
17810
+ if (!active || !kimi?.refreshToken) return false;
17811
+ const capturedId = active.id;
17812
+ this.materializeMigration(config);
17813
+ const refreshFetch = this.buildRefreshFetch("kimi", capturedId);
17814
+ try {
17815
+ const result = await kimiOAuth2.refreshAccessToken(
17816
+ kimi.refreshToken,
17817
+ refreshFetch,
17818
+ kimiOAuth2.kimiFingerprintHeaders(kimi.deviceId)
17819
+ );
17820
+ const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
17821
+ const next = {
17822
+ ...kimi,
17823
+ accessToken: result.accessToken,
17824
+ refreshToken: result.refreshToken,
17825
+ expiresAt,
17826
+ status: "authorized",
17827
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
17828
+ errorMessage: void 0,
17829
+ syncWarning: void 0
17830
+ };
17831
+ this.writeBackById("kimi", capturedId, next);
17832
+ return true;
17833
+ } catch (error) {
17834
+ this.markExpiredById("kimi", capturedId, kimi, error);
17835
+ return false;
17836
+ }
17837
+ });
17838
+ }
17839
+ /**
17840
+ * Refresh the Grok (xAI SuperGrok) OAuth access token. The token endpoint is
17841
+ * resolved through OIDC discovery on every refresh (process-cached 1h by the
17842
+ * flow module) so a rotated endpoint document is picked up without a daemon
17843
+ * restart. HONEST `false` when no refresh_token.
17844
+ */
17845
+ async refreshGrokToken() {
17846
+ return this.coalesce("grok:active", async () => {
17847
+ const config = this.readConfig();
17848
+ const active = getActiveAccount(config, "grok");
17849
+ const grok = active?.tokens;
17850
+ if (!active || !grok?.refreshToken) return false;
17851
+ const capturedId = active.id;
17852
+ this.materializeMigration(config);
17853
+ const refreshFetch = this.buildRefreshFetch("grok", capturedId);
17854
+ try {
17855
+ const tokenEndpoint = await grokOAuth2.resolveGrokTokenEndpoint(refreshFetch);
17856
+ const result = await grokOAuth2.refreshGrokAccessToken(grok.refreshToken, tokenEndpoint, refreshFetch);
17857
+ const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
17858
+ const next = {
17859
+ ...grok,
17860
+ accessToken: result.accessToken,
17861
+ refreshToken: result.refreshToken,
17862
+ expiresAt,
17863
+ status: "authorized",
17864
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
17865
+ errorMessage: void 0,
17866
+ syncWarning: void 0
17867
+ };
17868
+ this.writeBackById("grok", capturedId, next);
17869
+ return true;
17870
+ } catch (error) {
17871
+ this.markExpiredById("grok", capturedId, grok, error);
17872
+ return false;
17873
+ }
17874
+ });
17875
+ }
17876
+ /**
17877
+ * "Refresh" a GitHub Copilot token — there is nothing to refresh (ghu_
17878
+ * tokens are long-lived with no exchange endpoint). A call here means the
17879
+ * strategy saw a 401 (the token was revoked); mark the account `expired`
17880
+ * with a re-authenticate message and return `false` (the proxy then declines
17881
+ * the retry instead of looping on a dead token).
17882
+ */
17883
+ async refreshCopilotToken() {
17884
+ return this.coalesce("copilot:active", async () => {
17885
+ const config = this.readConfig();
17886
+ const active = getActiveAccount(config, "copilot");
17887
+ const copilot = active?.tokens;
17888
+ if (!active || !copilot?.accessToken) return false;
17889
+ this.materializeMigration(config);
17890
+ this.markExpiredById(
17891
+ "copilot",
17892
+ active.id,
17893
+ copilot,
17894
+ new Error("GitHub Copilot tokens cannot be refreshed \u2014 re-authenticate the account")
17895
+ );
17896
+ return false;
17897
+ });
17898
+ }
15702
17899
  /**
15703
17900
  * Refresh a SPECIFIC managed account by id (background scheduler sweep and
15704
17901
  * account-pool resolution). It uses only that account's stored refresh
@@ -15751,7 +17948,7 @@ var JsonSubscriptionCredentialStore = class {
15751
17948
  }
15752
17949
  const oauth = account.tokens;
15753
17950
  if (!oauth.accessToken) return null;
15754
- if (providerId === "codex" || providerId === "gemini") {
17951
+ if (providerId === "codex" || providerId === "gemini" || providerId === "kimi" || providerId === "grok" || providerId === "copilot") {
15755
17952
  const expiresAtMs = oauth.expiresAt ? Date.parse(oauth.expiresAt) : 0;
15756
17953
  const expiringSoon = expiresAtMs > 0 && Date.now() >= expiresAtMs - ACCOUNT_REFRESH_LEAD_MS;
15757
17954
  if (expiringSoon && oauth.refreshToken) {
@@ -15840,8 +18037,35 @@ var JsonSubscriptionCredentialStore = class {
15840
18037
  }
15841
18038
  /** Dispatch one OAuth refresh round-trip to the provider's shared flow. */
15842
18039
  async refreshUpstream(provider, refreshToken, accountId) {
18040
+ const refreshFetch = this.buildRefreshFetch(provider, accountId);
18041
+ if (provider === "kimi") {
18042
+ const account = accountId ? getAccountById(this.readConfig(), "kimi", accountId) : void 0;
18043
+ const deviceId = account?.tokens?.deviceId;
18044
+ const r2 = await kimiOAuth2.refreshAccessToken(
18045
+ refreshToken,
18046
+ refreshFetch,
18047
+ kimiOAuth2.kimiFingerprintHeaders(deviceId)
18048
+ );
18049
+ return {
18050
+ accessToken: r2.accessToken,
18051
+ refreshToken: r2.refreshToken,
18052
+ expiresAt: new Date(Date.now() + r2.expiresIn * 1e3).toISOString()
18053
+ };
18054
+ }
18055
+ if (provider === "grok") {
18056
+ const tokenEndpoint = await grokOAuth2.resolveGrokTokenEndpoint(refreshFetch);
18057
+ const r2 = await grokOAuth2.refreshGrokAccessToken(refreshToken, tokenEndpoint, refreshFetch);
18058
+ return {
18059
+ accessToken: r2.accessToken,
18060
+ refreshToken: r2.refreshToken,
18061
+ expiresAt: new Date(Date.now() + r2.expiresIn * 1e3).toISOString()
18062
+ };
18063
+ }
18064
+ if (provider === "copilot") {
18065
+ throw new Error("GitHub Copilot tokens cannot be refreshed \u2014 re-authenticate the account");
18066
+ }
15843
18067
  const flow = provider === "claude" ? claudeOAuth2 : provider === "codex" ? codexOAuth2 : geminiOAuth2;
15844
- const r = await flow.refreshAccessToken(refreshToken, this.buildRefreshFetch(provider, accountId));
18068
+ const r = await flow.refreshAccessToken(refreshToken, refreshFetch);
15845
18069
  return {
15846
18070
  accessToken: r.accessToken,
15847
18071
  refreshToken: r.refreshToken,
@@ -16004,42 +18228,86 @@ var JsonSubscriptionCredentialStore = class {
16004
18228
  /** Write the merged config to disk as pretty JSON (mkdir parent if needed).
16005
18229
  * Encrypt-on-write: the token-material fields are encrypted (legacy plaintext
16006
18230
  * `enc:v1:`; already-`enc:`/`$ENV` untouched) before serializing, so any
16007
- * write incl. child 4's future refresh writes lands encrypted. */
18231
+ * write incl. child 4's future refresh writes lands encrypted.
18232
+ * ATOMIC: temp + fsync + rename (`atomicReplaceUtf8`) — a failed or
18233
+ * interrupted write discards only the temp file; the prior `tokens.json`
18234
+ * survives byte-equal (bare `writeFileSync` truncate-writes lost every
18235
+ * account on a mid-write failure, 2026-09-06). */
16008
18236
  persist(config) {
16009
18237
  mkdirSync6(dirname15(this.tokensPath), { recursive: true });
16010
18238
  const encrypted = encryptTokens(config, this.box);
16011
- writeFileSync17(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n", "utf8");
18239
+ this.atomicReplace(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
16012
18240
  }
16013
18241
  /**
16014
- * Read + parse `tokens.json`, tolerating a missing/corrupt file, then DECRYPT
16015
- * the token-material fields so every getter returns plaintext (the
16016
- * subscription bearer path is byte-identical).
18242
+ * Read + parse `tokens.json`, then DECRYPT the token-material fields so every
18243
+ * getter returns plaintext (the subscription bearer path is byte-identical).
18244
+ *
18245
+ * A MISSING file is a legitimate first-boot state → minimal `{ updatedAt: '' }`.
18246
+ * A file that EXISTS but cannot be parsed as a JSON object is CORRUPT →
18247
+ * `quarantineCorrupt` moves it aside (once) before the empty config is
18248
+ * returned, so the unreadable accounts survive for manual recovery.
16017
18249
  *
16018
- * The fs-read + JSON-parse tolerance is INSIDE the try (a missing or corrupt
16019
- * file empty `{ updatedAt: '' }`). The DECRYPT runs OUTSIDE the try, so a
16020
- * wrong/missing master key or a tampered `enc:` envelope FAILS FAST with the
16021
- * box's clear, secret-free error (secrets spec "/ UX":
16022
- * SHALL fail-fast, SHALL NOT a swallowed decrypt would report "no
16023
- * tokens" and silently send the WRONG bearer upstream 401). Mirrors
16024
- * `config.ts loadConfig`, which decrypts outside its parse try.
18250
+ * The DECRYPT runs OUTSIDE any try, so a wrong/missing master key or a
18251
+ * tampered `enc:` envelope FAILS FAST with the box's clear, secret-free
18252
+ * error (secrets spec "/ UX": SHALL fail-fast, SHALL NOT a swallowed
18253
+ * decrypt would report "no tokens" and silently send the WRONG bearer
18254
+ * upstream 401). Mirrors `config.ts loadConfig`, which decrypts outside
18255
+ * its parse try.
16025
18256
  */
16026
18257
  readConfig() {
16027
- if (!existsSync23(this.tokensPath)) return { updatedAt: "" };
18258
+ if (!existsSync24(this.tokensPath)) return { updatedAt: "" };
16028
18259
  let parsed;
16029
18260
  try {
16030
18261
  const raw = JSON.parse(readFileSync20(this.tokensPath, "utf8"));
16031
- parsed = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
18262
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
18263
+ return this.quarantineCorrupt("parsed JSON is not an object");
18264
+ }
18265
+ parsed = raw;
16032
18266
  } catch {
16033
- parsed = null;
18267
+ return this.quarantineCorrupt("unparseable JSON");
16034
18268
  }
16035
- if (!parsed) return { updatedAt: "" };
16036
18269
  const decrypted = decryptTokens(parsed, this.box);
16037
18270
  return migrateLazily(decrypted);
16038
18271
  }
18272
+ /** One-shot latch: a corrupt file is quarantined (or found unmovable) at
18273
+ * most once per process, so the hot read path never re-attempts or re-logs. */
18274
+ corruptQuarantined = false;
18275
+ /**
18276
+ * Quarantine a present-but-corrupt `tokens.json`, then treat it as empty.
18277
+ *
18278
+ * Renames the file to a sibling `tokens.json.corrupt-<stamp>` backup and
18279
+ * logs loudly (the daemon's stderr log; secret-free — reason + paths only).
18280
+ * The daemon KEEPS SERVING (API-key routing is unaffected; subscription
18281
+ * routing reports no credential, same as an absent file) while the corrupt
18282
+ * bytes survive for manual recovery — and, critically, the NEXT persist
18283
+ * (e.g. the user re-logging in) can no longer overwrite the only copy of
18284
+ * the old accounts, which is exactly how the 2026-09-06 incident turned a
18285
+ * recoverable truncated file into permanent account loss.
18286
+ *
18287
+ * Best-effort: if the rename fails (file locked, permissions), the corrupt
18288
+ * file is left in place and every later read still tolerates it as empty;
18289
+ * the latch still trips so the attempt + log happen exactly once.
18290
+ */
18291
+ quarantineCorrupt(reason) {
18292
+ if (!this.corruptQuarantined) {
18293
+ this.corruptQuarantined = true;
18294
+ const backup = `${this.tokensPath}.corrupt-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
18295
+ let moved = false;
18296
+ try {
18297
+ renameSync13(this.tokensPath, backup);
18298
+ moved = true;
18299
+ } catch {
18300
+ }
18301
+ console.error(
18302
+ `[JsonSubscriptionCredentialStore] tokens.json is corrupt (${reason}); ` + (moved ? `moved to '${backup}' and treated as empty \u2014 recover accounts from that backup before re-adding them` : `could not move '${this.tokensPath}' \u2014 treated as empty`)
18303
+ );
18304
+ }
18305
+ return { updatedAt: "" };
18306
+ }
16039
18307
  };
16040
18308
 
16041
18309
  // src/AccountHealthProbeScheduler.ts
16042
- import { fetchUpstream as fetchUpstream4 } from "@omnicross/core/pipeline/upstreamFetch";
18310
+ import { fetchUpstream as fetchUpstream10 } from "@omnicross/core/pipeline/upstreamFetch";
16043
18311
 
16044
18312
  // src/probe/CodexGenerationProbe.ts
16045
18313
  import {
@@ -16181,7 +18449,20 @@ var PROVIDER_PROBE_PLANS = {
16181
18449
  // billable/wrong endpoint). Upgrade to `{ kind:'upstream' }` once verified.
16182
18450
  codex: { kind: "local" },
16183
18451
  gemini: { kind: "local" },
16184
- opencodego: { kind: "local" }
18452
+ opencodego: { kind: "local" },
18453
+ // Kimi's `GET /coding/v1/usages` is a verified FREE authed GET (the allowance
18454
+ // collector uses it), but the probe path also needs the fingerprint headers —
18455
+ // keep the probe local until the collector covers the health surface.
18456
+ kimi: { kind: "local" },
18457
+ // Grok's billing proxy is a verified FREE authed GET (the allowance collector
18458
+ // uses it) but it REJECTS non-OAuth credentials and sits on a separate host
18459
+ // with its own product-gate header — keep the probe local, the collector
18460
+ // owns the health surface.
18461
+ grok: { kind: "local" },
18462
+ // The Copilot quota endpoint (copilot_internal/user) is a verified FREE
18463
+ // authed GET but lives on api.github.com with its own auth dialect and a
18464
+ // monthly-only window — the allowance collector owns the health surface.
18465
+ copilot: { kind: "local" }
16185
18466
  };
16186
18467
  function probePlanFor(providerId) {
16187
18468
  return PROVIDER_PROBE_PLANS[providerId] ?? { kind: "local" };
@@ -16203,7 +18484,7 @@ var AccountHealthProbeScheduler = class {
16203
18484
  this.logger = logger;
16204
18485
  this.config = config;
16205
18486
  this.now = opts.now ?? Date.now;
16206
- this.fetchImpl = opts.fetchImpl ?? fetchUpstream4;
18487
+ this.fetchImpl = opts.fetchImpl ?? fetchUpstream10;
16207
18488
  this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
16208
18489
  this.planFor = opts.planFor ?? probePlanFor;
16209
18490
  }
@@ -16547,7 +18828,7 @@ var AccountHealthSweeper = class {
16547
18828
  };
16548
18829
 
16549
18830
  // src/audit/AuditPruneSweeper.ts
16550
- import { createReadStream as createReadStream3, createWriteStream as createWriteStream2, existsSync as existsSync25, readdirSync as readdirSync8, rmSync as rmSync4, unlinkSync as unlinkSync13 } from "fs";
18831
+ import { createReadStream as createReadStream3, createWriteStream as createWriteStream2, existsSync as existsSync26, readdirSync as readdirSync8, rmSync as rmSync4, unlinkSync as unlinkSync13 } from "fs";
16551
18832
  import { join as join22 } from "path";
16552
18833
  import { pipeline } from "stream/promises";
16553
18834
  import { createGzip } from "zlib";
@@ -16555,11 +18836,11 @@ import { createGzip } from "zlib";
16555
18836
  // src/audit/auditStats.ts
16556
18837
  import {
16557
18838
  createReadStream as createReadStream2,
16558
- existsSync as existsSync24,
18839
+ existsSync as existsSync25,
16559
18840
  readFileSync as readFileSync21,
16560
18841
  readdirSync as readdirSync7,
16561
18842
  statSync as statSync7,
16562
- writeFileSync as writeFileSync18
18843
+ writeFileSync as writeFileSync17
16563
18844
  } from "fs";
16564
18845
  import { basename as basename9, dirname as dirname16, join as join21 } from "path";
16565
18846
  var SIDECAR_VERSION = 1;
@@ -16569,7 +18850,7 @@ function auditStatsFileName(auditFile) {
16569
18850
  return auditFile.replace(/\.jsonl$/, ".stats.json");
16570
18851
  }
16571
18852
  function readPersisted(path2) {
16572
- if (!existsSync24(path2)) return null;
18853
+ if (!existsSync25(path2)) return null;
16573
18854
  try {
16574
18855
  const value = JSON.parse(readFileSync21(path2, "utf8"));
16575
18856
  if (value.version !== SIDECAR_VERSION || !Number.isSafeInteger(value.auditBytes) || (value.auditBytes ?? -1) < 0 || !Number.isSafeInteger(value.requestCount) || (value.requestCount ?? -1) < 0 || !Number.isSafeInteger(value.errorCount) || (value.errorCount ?? -1) < 0 || (value.errorCount ?? 0) > (value.requestCount ?? -1) || typeof value.complete !== "boolean" || value.minTs !== null && !Number.isFinite(value.minTs) || value.maxTs !== null && !Number.isFinite(value.maxTs)) {
@@ -16601,7 +18882,7 @@ function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfte
16601
18882
  minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
16602
18883
  maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
16603
18884
  };
16604
- writeFileSync18(statsPath, JSON.stringify(next), "utf8");
18885
+ writeFileSync17(statsPath, JSON.stringify(next), "utf8");
16605
18886
  }
16606
18887
  function queryCovers(stats, from, to) {
16607
18888
  return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
@@ -16712,7 +18993,7 @@ function mergePersistedStats(previous, appended) {
16712
18993
  };
16713
18994
  }
16714
18995
  async function readAuditStats(auditDir, query2 = {}) {
16715
- if (!existsSync24(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
18996
+ if (!existsSync25(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
16716
18997
  const from = typeof query2.from === "number" ? query2.from : -Infinity;
16717
18998
  const to = typeof query2.to === "number" ? query2.to : Infinity;
16718
18999
  let sources;
@@ -16725,7 +19006,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16725
19006
  auditPath: join21(auditDir, name),
16726
19007
  statsPath: join21(auditDir, auditStatsFileName(name))
16727
19008
  }
16728
- ).filter((source) => existsSync24(source.auditPath));
19009
+ ).filter((source) => existsSync25(source.auditPath));
16729
19010
  } catch {
16730
19011
  return { requestCount: 0, errorCount: 0, complete: false };
16731
19012
  }
@@ -16751,7 +19032,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16751
19032
  total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
16752
19033
  total.complete = total.complete && scanned.filtered.complete;
16753
19034
  const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
16754
- if (current.complete) writeFileSync18(statsPath, JSON.stringify(current), "utf8");
19035
+ if (current.complete) writeFileSync17(statsPath, JSON.stringify(current), "utf8");
16755
19036
  } catch {
16756
19037
  total.complete = false;
16757
19038
  }
@@ -16760,7 +19041,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16760
19041
  }
16761
19042
 
16762
19043
  // src/audit/AuditPruneSweeper.ts
16763
- var DAY_MS = 24 * 60 * 6e4;
19044
+ var DAY_MS4 = 24 * 60 * 6e4;
16764
19045
  var SWEEP_INTERVAL_MS2 = 60 * 6e4;
16765
19046
  var ARCHIVE_BATCH = 64;
16766
19047
  var AuditPruneSweeper = class {
@@ -16823,8 +19104,8 @@ var AuditPruneSweeper = class {
16823
19104
  if (!this.config.enabled || this.sweeping) return 0;
16824
19105
  this.sweeping = true;
16825
19106
  try {
16826
- if (!existsSync25(this.auditDir)) return 0;
16827
- const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS;
19107
+ if (!existsSync26(this.auditDir)) return 0;
19108
+ const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS4;
16828
19109
  let removed = 0;
16829
19110
  for (const name of readdirSync8(this.auditDir)) {
16830
19111
  const dateMs = auditFileDateMs(name);
@@ -16835,7 +19116,7 @@ var AuditPruneSweeper = class {
16835
19116
  } else {
16836
19117
  unlinkSync13(join22(this.auditDir, name));
16837
19118
  const statsPath = join22(this.auditDir, auditStatsFileName(name));
16838
- if (existsSync25(statsPath)) unlinkSync13(statsPath);
19119
+ if (existsSync26(statsPath)) unlinkSync13(statsPath);
16839
19120
  }
16840
19121
  removed += 1;
16841
19122
  } catch (error) {
@@ -16865,7 +19146,7 @@ var AuditPruneSweeper = class {
16865
19146
  if (!this.config.enabled || this.archiving) return 0;
16866
19147
  this.archiving = true;
16867
19148
  try {
16868
- if (!existsSync25(this.auditDir)) return 0;
19149
+ if (!existsSync26(this.auditDir)) return 0;
16869
19150
  const today = this.todayMidnight();
16870
19151
  let compressed = 0;
16871
19152
  for (const name of readdirSync8(this.auditDir)) {
@@ -16919,7 +19200,7 @@ var AuditPruneSweeper = class {
16919
19200
  const source = join22(bodiesPath, shard);
16920
19201
  const target = `${source}.gz`;
16921
19202
  try {
16922
- if (existsSync25(target)) {
19203
+ if (existsSync26(target)) {
16923
19204
  unlinkSync13(source);
16924
19205
  continue;
16925
19206
  }
@@ -16928,7 +19209,7 @@ var AuditPruneSweeper = class {
16928
19209
  compressed += 1;
16929
19210
  } catch (error) {
16930
19211
  try {
16931
- if (existsSync25(target)) unlinkSync13(target);
19212
+ if (existsSync26(target)) unlinkSync13(target);
16932
19213
  } catch {
16933
19214
  }
16934
19215
  this.logger.warn("[AuditPruneSweeper] failed to archive audit body shard", {
@@ -17081,7 +19362,7 @@ async function closeAll(writers) {
17081
19362
  // src/usage/UsagePruneSweeper.ts
17082
19363
  import { unlink as unlink3 } from "fs/promises";
17083
19364
  import { join as join24 } from "path";
17084
- var DAY_MS2 = 24 * 60 * 6e4;
19365
+ var DAY_MS5 = 24 * 60 * 6e4;
17085
19366
  var SWEEP_INTERVAL_MS3 = 60 * 6e4;
17086
19367
  var DEFAULT_USAGE_RETENTION_DAYS = 90;
17087
19368
  var UsagePruneSweeper = class {
@@ -17138,7 +19419,7 @@ var UsagePruneSweeper = class {
17138
19419
  this.sweeping = true;
17139
19420
  try {
17140
19421
  const retentionDays = this.config.retentionDays ?? DEFAULT_USAGE_RETENTION_DAYS;
17141
- const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS2;
19422
+ const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS5;
17142
19423
  let removed = 0;
17143
19424
  for (const entry of await listUsageDays(this.usageDir)) {
17144
19425
  if (!entry.hasShard) continue;
@@ -17196,7 +19477,7 @@ var UsagePruneSweeper = class {
17196
19477
  };
17197
19478
 
17198
19479
  // src/audit/auditReader.ts
17199
- import { existsSync as existsSync26, readdirSync as readdirSync9 } from "fs";
19480
+ import { existsSync as existsSync27, readdirSync as readdirSync9 } from "fs";
17200
19481
  import { join as join25 } from "path";
17201
19482
  var DEFAULT_LIMIT = 200;
17202
19483
  var MAX_LIMIT = 2e3;
@@ -17214,7 +19495,7 @@ function daySources(auditDir) {
17214
19495
  if (dateMs === null) continue;
17215
19496
  if (AUDIT_DAY_DIR_RE.test(name)) {
17216
19497
  const path2 = join25(auditDir, name, AUDIT_META_FILE);
17217
- if (existsSync26(path2)) sources.push({ path: path2, dateMs });
19498
+ if (existsSync27(path2)) sources.push({ path: path2, dateMs });
17218
19499
  } else if (AUDIT_FILE_RE.test(name)) {
17219
19500
  sources.push({ path: join25(auditDir, name), dateMs });
17220
19501
  }
@@ -17232,7 +19513,7 @@ function toMetaRecord(record) {
17232
19513
  return { ...meta, hasBody: true };
17233
19514
  }
17234
19515
  function readAuditRecords(auditDir, query2 = {}) {
17235
- if (!existsSync26(auditDir)) return [];
19516
+ if (!existsSync27(auditDir)) return [];
17236
19517
  const from = typeof query2.from === "number" ? query2.from : -Infinity;
17237
19518
  const to = typeof query2.to === "number" ? query2.to : Infinity;
17238
19519
  const limit = Math.min(MAX_LIMIT, Math.max(1, Math.trunc(query2.limit ?? DEFAULT_LIMIT)));
@@ -17260,7 +19541,7 @@ function readAuditRecords(auditDir, query2 = {}) {
17260
19541
  }
17261
19542
 
17262
19543
  // src/audit/AuditWriter.ts
17263
- import { appendFileSync as appendFileSync2, existsSync as existsSync27, mkdirSync as mkdirSync7, statSync as statSync8 } from "fs";
19544
+ import { appendFileSync as appendFileSync2, existsSync as existsSync28, mkdirSync as mkdirSync7, statSync as statSync8 } from "fs";
17264
19545
  import { join as join26 } from "path";
17265
19546
  var AuditWriter = class {
17266
19547
  constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
@@ -17318,7 +19599,7 @@ var AuditWriter = class {
17318
19599
  const { requestBody: _req, responseBody: _res, ...meta } = record;
17319
19600
  const file = join26(dayPath, AUDIT_META_FILE);
17320
19601
  const line = JSON.stringify(meta) + "\n";
17321
- const bytesBefore = existsSync27(file) ? statSync8(file).size : 0;
19602
+ const bytesBefore = existsSync28(file) ? statSync8(file).size : 0;
17322
19603
  appendFileSync2(file, line, "utf8");
17323
19604
  try {
17324
19605
  updateAuditStatsAfterAppend(
@@ -17366,7 +19647,7 @@ var AuditWriter = class {
17366
19647
  import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync8 } from "fs";
17367
19648
  import { createHmac as createHmac5 } from "crypto";
17368
19649
  import { join as join27 } from "path";
17369
- import { fetchUpstream as fetchUpstream5 } from "@omnicross/core/pipeline/upstreamFetch";
19650
+ import { fetchUpstream as fetchUpstream11 } from "@omnicross/core/pipeline/upstreamFetch";
17370
19651
 
17371
19652
  // src/billing/billingFiles.ts
17372
19653
  var BILLING_FILE_RE = /^billing-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
@@ -17389,7 +19670,7 @@ var BillingPublisher = class {
17389
19670
  constructor(billingDir, logger, opts = {}) {
17390
19671
  this.billingDir = billingDir;
17391
19672
  this.logger = logger;
17392
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream5(url, init));
19673
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream11(url, init));
17393
19674
  this.defer = opts.defer ?? ((fn) => setTimeout(fn, 0));
17394
19675
  this.timeoutMs = opts.timeoutMs ?? BILLING_POST_TIMEOUT_MS;
17395
19676
  this.now = opts.now ?? Date.now;
@@ -17502,11 +19783,11 @@ var BillingPublisher = class {
17502
19783
  };
17503
19784
 
17504
19785
  // src/billing/billingReader.ts
17505
- import { existsSync as existsSync28, readdirSync as readdirSync10, readFileSync as readFileSync22 } from "fs";
19786
+ import { existsSync as existsSync29, readdirSync as readdirSync10, readFileSync as readFileSync22 } from "fs";
17506
19787
  import { join as join28 } from "path";
17507
19788
  function readBillingLedger(billingDir) {
17508
19789
  const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
17509
- if (!existsSync28(billingDir)) return view;
19790
+ if (!existsSync29(billingDir)) return view;
17510
19791
  let files;
17511
19792
  try {
17512
19793
  files = readdirSync10(billingDir);
@@ -17639,7 +19920,7 @@ var BillingRetrySweeper = class {
17639
19920
  // src/TokenRefreshScheduler.ts
17640
19921
  var REFRESH_LEAD_MS2 = 5 * 6e4;
17641
19922
  var SWEEP_INTERVAL_MS5 = 6e4;
17642
- var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini"];
19923
+ var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini", "kimi", "grok", "copilot"];
17643
19924
  var TokenRefreshScheduler = class {
17644
19925
  constructor(store, logger, intervalMs = SWEEP_INTERVAL_MS5, leadMs = REFRESH_LEAD_MS2) {
17645
19926
  this.store = store;
@@ -17722,6 +20003,14 @@ var TokenRefreshScheduler = class {
17722
20003
  return this.store.refreshCodexToken();
17723
20004
  case "gemini":
17724
20005
  return this.store.refreshGeminiToken();
20006
+ case "kimi":
20007
+ return this.store.refreshKimiToken();
20008
+ case "grok":
20009
+ return this.store.refreshGrokToken();
20010
+ // ghu_ tokens never near-expire (far-future expiresAt), so the sweep
20011
+ // never reaches this — the branch exists for union totality.
20012
+ case "copilot":
20013
+ return this.store.refreshCopilotToken();
17725
20014
  }
17726
20015
  }
17727
20016
  };
@@ -17800,7 +20089,7 @@ function createRouteLeaseSubscriptionPreflight(credentials) {
17800
20089
 
17801
20090
  // src/webhook/WebhookDispatcher.ts
17802
20091
  import { createHmac as createHmac6 } from "crypto";
17803
- import { fetchUpstream as fetchUpstream6 } from "@omnicross/core/pipeline/upstreamFetch";
20092
+ import { fetchUpstream as fetchUpstream12 } from "@omnicross/core/pipeline/upstreamFetch";
17804
20093
  var WEBHOOK_MAX_ATTEMPTS = 3;
17805
20094
  var WEBHOOK_QUEUE_MAX = 1e3;
17806
20095
  var WEBHOOK_SEND_TIMEOUT_MS = 1e4;
@@ -17820,7 +20109,7 @@ var WebhookDispatcher = class {
17820
20109
  sleep;
17821
20110
  now;
17822
20111
  constructor(opts = {}) {
17823
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream6(url, init));
20112
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream12(url, init));
17824
20113
  this.logger = opts.logger;
17825
20114
  this.maxAttempts = opts.maxAttempts ?? WEBHOOK_MAX_ATTEMPTS;
17826
20115
  this.queueMax = opts.queueMax ?? WEBHOOK_QUEUE_MAX;
@@ -17906,8 +20195,8 @@ var WebhookDispatcher = class {
17906
20195
  signal: AbortSignal.timeout(this.timeoutMs)
17907
20196
  });
17908
20197
  return res.ok ? { ok: true, status: res.status } : { ok: false, status: res.status };
17909
- } catch (err5) {
17910
- return { ok: false, error: err5 instanceof Error ? err5.message : String(err5) };
20198
+ } catch (err8) {
20199
+ return { ok: false, error: err8 instanceof Error ? err8.message : String(err8) };
17911
20200
  }
17912
20201
  }
17913
20202
  /**
@@ -18044,7 +20333,7 @@ function buildDaemon(config, paths) {
18044
20333
  setSecretBox(secretBox3);
18045
20334
  setSecretBox2(secretBox3);
18046
20335
  const decryptedConfig = decryptConfigSecrets(config, secretBox3);
18047
- const accountAllowanceStore = new AccountAllowanceStore3(
20336
+ const accountAllowanceStore = new AccountAllowanceStore8(
18048
20337
  Date.now,
18049
20338
  void 0,
18050
20339
  new JsonAccountAllowancePersistence(defaultAccountAllowancePath(paths.configPath))
@@ -18089,6 +20378,7 @@ function buildDaemon(config, paths) {
18089
20378
  );
18090
20379
  setGeminiCodeAssistResolver(getGeminiCodeAssistProjectResolver());
18091
20380
  const autoDisableStore = new AutoDisableStore();
20381
+ const providerKeyQuotaService = new ProviderKeyQuotaService(secretBox3);
18092
20382
  const apiKeyPool = new ApiKeyPoolService(
18093
20383
  createPoolKeysLoader((id) => llmConfig.getProviderRow(id), autoDisableStore),
18094
20384
  resolveEnvKey,
@@ -18105,7 +20395,7 @@ function buildDaemon(config, paths) {
18105
20395
  const pricingEngine = new PricingEngine(pricingStore, logger, {
18106
20396
  // Catalog egress follows the same global/env proxy policy as every other
18107
20397
  // daemon upstream call; no provider/account override applies here.
18108
- fetchImpl: ((input, init) => fetchUpstream7(String(input), init ?? {}))
20398
+ fetchImpl: ((input, init) => fetchUpstream13(String(input), init ?? {}))
18109
20399
  });
18110
20400
  const pricingRefreshScheduler = new PricingRefreshScheduler(
18111
20401
  pricingEngine,
@@ -18369,6 +20659,11 @@ function buildDaemon(config, paths) {
18369
20659
  // values themselves NEVER leave (masked via `maskProviderApiKey`).
18370
20660
  apiKeyPool,
18371
20661
  autoDisableStore,
20662
+ // BYO provider-key quota (Z.AI coding plan, MiniMax Token Plan, …): a
20663
+ // read-through cached same-key usage probe surfaced on the keys view. The
20664
+ // key plaintext is resolved + decrypted inside the service and never
20665
+ // crosses back out.
20666
+ providerKeyQuota: providerKeyQuotaService,
18372
20667
  // Interactive OAuth login over admin HTTP (app-parity child 4, design
18373
20668
  // D1/D2-a). The in-memory pending-session store (NEVER serialized), the
18374
20669
  // injected token-exchange fetch (global `fetch` here; mocked in tests), and a
@@ -18385,7 +20680,7 @@ function buildDaemon(config, paths) {
18385
20680
  // — `server.proxy.byProvider[...]` was silently skipped — and the call was
18386
20681
  // excluded from the upstream trace, so a failing login left no evidence.
18387
20682
  // `redactBodies` keeps the code/verifier + minted token out of that trace.
18388
- oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => fetchUpstream7(url, init, { providerId, redactBodies: true }),
20683
+ oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => fetchUpstream13(url, init, { providerId, redactBodies: true }),
18389
20684
  subscriptionAccountAppender: credentialStore,
18390
20685
  // Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
18391
20686
  // + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
@@ -18393,6 +20688,13 @@ function buildDaemon(config, paths) {
18393
20688
  // can inject a mock so no real port is bound.
18394
20689
  codexSessions: new CodexOAuthSessionStore(),
18395
20690
  codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
20691
+ // Kimi interactive OAuth — the async DEVICE-CODE flow store (no port, no
20692
+ // paste; the app shows the verification URL + user code and polls the
20693
+ // token-free status). Token captured + persisted daemon-side.
20694
+ kimiSessions: new CodexOAuthSessionStore(),
20695
+ // Grok interactive OAuth — the same async DEVICE-CODE shape as kimi.
20696
+ grokSessions: new CodexOAuthSessionStore(),
20697
+ copilotSessions: new CodexOAuthSessionStore(),
18396
20698
  // Migration pack (app-parity child 6, design D2/D3) — the concrete credential
18397
20699
  // store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
18398
20700
  // the multi-account append (`appendProviderAccount`, import re-encrypts at-
@@ -18451,7 +20753,7 @@ function buildDaemon(config, paths) {
18451
20753
  });
18452
20754
  const webhookDispatcher = new WebhookDispatcher({
18453
20755
  logger,
18454
- fetchImpl: (url, init) => fetchUpstream7(url, init)
20756
+ fetchImpl: (url, init) => fetchUpstream13(url, init)
18455
20757
  });
18456
20758
  setWebhookRuntime(webhookDispatcher, getSharedAccountHealth4());
18457
20759
  const auditWriter = new AuditWriter(auditDir, logger);
@@ -18528,7 +20830,7 @@ function buildDaemon(config, paths) {
18528
20830
  }
18529
20831
  function isTokensStoreReadable(tokensPath) {
18530
20832
  try {
18531
- if (!existsSync29(tokensPath)) return true;
20833
+ if (!existsSync30(tokensPath)) return true;
18532
20834
  accessSync(tokensPath, fsConstants.R_OK);
18533
20835
  return true;
18534
20836
  } catch {
@@ -18765,11 +21067,11 @@ async function runLiveProbe(url, key, fetchImpl = fetch) {
18765
21067
  status: res.status,
18766
21068
  estimateHeader: res.headers.get("x-omnicross-count-estimate")
18767
21069
  };
18768
- } catch (err5) {
21070
+ } catch (err8) {
18769
21071
  return {
18770
21072
  status: null,
18771
21073
  estimateHeader: null,
18772
- error: err5 instanceof Error ? err5.message : String(err5)
21074
+ error: err8 instanceof Error ? err8.message : String(err8)
18773
21075
  };
18774
21076
  }
18775
21077
  }
@@ -19090,7 +21392,7 @@ async function keysRevoke(db, id) {
19090
21392
  // src/commands/launch.ts
19091
21393
  import { spawn as spawn2 } from "child_process";
19092
21394
  import { randomUUID as randomUUID6 } from "crypto";
19093
- import { existsSync as existsSync30 } from "fs";
21395
+ import { existsSync as existsSync31 } from "fs";
19094
21396
  import { delimiter as delimiter2, join as join29 } from "path";
19095
21397
  import { parseArgs as parseArgs6 } from "util";
19096
21398
  import {
@@ -19138,7 +21440,7 @@ function resolveInPathDefault(candidate) {
19138
21440
  const segments = (process.env["PATH"] ?? "").split(delimiter2).filter(Boolean);
19139
21441
  for (const seg of segments) {
19140
21442
  const full = join29(seg, candidate);
19141
- if (existsSync30(full)) return full;
21443
+ if (existsSync31(full)) return full;
19142
21444
  }
19143
21445
  return null;
19144
21446
  }
@@ -19179,9 +21481,9 @@ async function runLaunch(argv, deps) {
19179
21481
  await daemon.llmConfig.ready();
19180
21482
  await daemon.migrateUsageStore();
19181
21483
  await daemon.providerProxy.start();
19182
- } catch (err5) {
21484
+ } catch (err8) {
19183
21485
  await shutdownLaunchDaemon(daemon);
19184
- throw err5;
21486
+ throw err8;
19185
21487
  }
19186
21488
  let launch;
19187
21489
  try {
@@ -19189,9 +21491,9 @@ async function runLaunch(argv, deps) {
19189
21491
  providerId: values.provider,
19190
21492
  model: values.model
19191
21493
  });
19192
- } catch (err5) {
21494
+ } catch (err8) {
19193
21495
  await shutdownLaunchDaemon(daemon);
19194
- throw err5;
21496
+ throw err8;
19195
21497
  }
19196
21498
  try {
19197
21499
  const plan = buildCliSpawnPlan({
@@ -19296,9 +21598,9 @@ function spawnCliInherit(plan) {
19296
21598
  process.removeListener("SIGINT", onSignal);
19297
21599
  process.removeListener("SIGTERM", onSignal);
19298
21600
  };
19299
- child.on("error", (err5) => {
21601
+ child.on("error", (err8) => {
19300
21602
  detach();
19301
- if (err5.code === "ENOENT") {
21603
+ if (err8.code === "ENOENT") {
19302
21604
  reject(
19303
21605
  new Error(
19304
21606
  `launch: "${plan.command}" not found on PATH \u2014 install the CLI first.`
@@ -19306,7 +21608,7 @@ function spawnCliInherit(plan) {
19306
21608
  );
19307
21609
  return;
19308
21610
  }
19309
- reject(err5);
21611
+ reject(err8);
19310
21612
  });
19311
21613
  child.on("exit", (code, signal) => {
19312
21614
  detach();
@@ -19319,9 +21621,16 @@ function spawnCliInherit(plan) {
19319
21621
  import { spawn as spawn3 } from "child_process";
19320
21622
  import { createInterface as createInterface2 } from "readline";
19321
21623
  import { parseArgs as parseArgs7 } from "util";
19322
- import { fetchUpstream as fetchUpstream8, setUpstreamProxyResolver as setUpstreamProxyResolver2 } from "@omnicross/core/pipeline/upstreamFetch";
19323
- import { claudeOAuth as claudeOAuth3, codexOAuth as codexOAuth3, geminiOAuth as geminiOAuth3 } from "@omnicross/subscriptions";
19324
- var PROVIDERS2 = ["claude", "codex", "gemini"];
21624
+ import { fetchUpstream as fetchUpstream14, setUpstreamProxyResolver as setUpstreamProxyResolver2 } from "@omnicross/core/pipeline/upstreamFetch";
21625
+ import {
21626
+ claudeOAuth as claudeOAuth3,
21627
+ codexOAuth as codexOAuth3,
21628
+ copilotOAuth as copilotOAuth3,
21629
+ geminiOAuth as geminiOAuth3,
21630
+ grokOAuth as grokOAuth3,
21631
+ kimiOAuth as kimiOAuth3
21632
+ } from "@omnicross/subscriptions";
21633
+ var PROVIDERS2 = ["claude", "codex", "gemini", "kimi", "grok", "copilot"];
19325
21634
  async function runLogin(argv, deps) {
19326
21635
  const { values, positionals } = parseArgs7({
19327
21636
  args: argv,
@@ -19329,7 +21638,9 @@ async function runLogin(argv, deps) {
19329
21638
  config: { type: "string", short: "c" },
19330
21639
  "master-key-file": { type: "string" },
19331
21640
  // Optional user label for the appended account (multi-account).
19332
- label: { type: "string" }
21641
+ label: { type: "string" },
21642
+ // Optional GitHub Enterprise domain for `login copilot` (GHE accounts).
21643
+ enterprise: { type: "string" }
19333
21644
  },
19334
21645
  allowPositionals: true
19335
21646
  });
@@ -19343,25 +21654,34 @@ async function runLogin(argv, deps) {
19343
21654
  if (!values.config) {
19344
21655
  throw new Error("login: --config <path> is required");
19345
21656
  }
21657
+ if (values.enterprise !== void 0 && provider !== "copilot") {
21658
+ throw new Error("login: --enterprise is only supported for the copilot provider");
21659
+ }
21660
+ const enterpriseDomain = values.enterprise !== void 0 ? copilotOAuth3.normalizeCopilotEnterpriseDomain(values.enterprise) : void 0;
19346
21661
  const resolved = {
19347
21662
  openBrowser: deps?.openBrowser ?? openBrowser,
19348
21663
  promptPaste: deps?.promptPaste ?? promptPaste,
19349
21664
  awaitLoopback: deps?.awaitLoopback ?? ((state) => awaitLoopbackCode(state)),
21665
+ awaitKimiDevice: deps?.awaitKimiDevice ?? ((fetchImpl) => runKimiDeviceFlow(fetchImpl, resolvedOpenBrowser)),
21666
+ awaitGrokDevice: deps?.awaitGrokDevice ?? ((fetchImpl) => runGrokDeviceFlow(fetchImpl, resolvedOpenBrowser)),
21667
+ awaitCopilotDevice: deps?.awaitCopilotDevice ?? ((fetchImpl, enterpriseUrl) => runCopilotDeviceFlow(fetchImpl, resolvedOpenBrowser, enterpriseUrl)),
19350
21668
  tokensFetch: deps?.tokensFetch
19351
21669
  };
21670
+ const resolvedOpenBrowser = resolved.openBrowser;
19352
21671
  const box = resolveSecretBox(values["master-key-file"]);
19353
21672
  setSecretBox(box);
19354
21673
  setUpstreamProxyResolver2(createUpstreamProxyResolver());
19355
21674
  try {
19356
21675
  const tokensPath = defaultTokensPath(values.config);
19357
- const exchangeFetch = resolved.tokensFetch ?? ((url, init) => fetchUpstream8(url, init, { providerId: provider, redactBodies: true }));
21676
+ const exchangeFetch = resolved.tokensFetch ?? ((url, init) => fetchUpstream14(url, init, { providerId: provider, redactBodies: true }));
19358
21677
  const store = new JsonSubscriptionCredentialStore(tokensPath, box, exchangeFetch);
19359
21678
  const expiresAt = await runProviderLogin(
19360
21679
  provider,
19361
21680
  store,
19362
21681
  resolved,
19363
21682
  exchangeFetch,
19364
- values.label
21683
+ values.label,
21684
+ enterpriseDomain
19365
21685
  );
19366
21686
  console.info(`Logged in to '${provider}' \u2192 ${tokensPath}`);
19367
21687
  console.info(` token: [stored, encrypted] expiresAt: ${expiresAt ?? "n/a"}`);
@@ -19370,9 +21690,12 @@ async function runLogin(argv, deps) {
19370
21690
  setUpstreamProxyResolver2(null);
19371
21691
  }
19372
21692
  }
19373
- async function runProviderLogin(provider, store, deps, exchangeFetch, label) {
21693
+ async function runProviderLogin(provider, store, deps, exchangeFetch, label, enterpriseUrl) {
19374
21694
  if (provider === "codex") return loginCodex(store, deps, exchangeFetch, label);
19375
21695
  if (provider === "claude") return loginClaude(store, deps, exchangeFetch, label);
21696
+ if (provider === "kimi") return loginKimi(store, deps, exchangeFetch, label);
21697
+ if (provider === "grok") return loginGrok(store, deps, exchangeFetch, label);
21698
+ if (provider === "copilot") return loginCopilot(store, deps, exchangeFetch, label, enterpriseUrl);
19376
21699
  return loginGemini(store, deps, exchangeFetch, label);
19377
21700
  }
19378
21701
  async function loginCodex(store, deps, exchangeFetch, label) {
@@ -19443,6 +21766,129 @@ async function loginGemini(store, deps, exchangeFetch, label) {
19443
21766
  logMasked("gemini", result.accessToken);
19444
21767
  return expiresAt;
19445
21768
  }
21769
+ async function runKimiDeviceFlow(exchangeFetch, openBrowserFn) {
21770
+ const deviceId = kimiOAuth3.generateKimiDeviceId();
21771
+ const fingerprint = kimiOAuth3.kimiFingerprintHeaders(deviceId);
21772
+ const authorization = await kimiOAuth3.requestDeviceAuthorization(exchangeFetch, fingerprint);
21773
+ const url = authorization.verificationUriComplete ?? authorization.verificationUri;
21774
+ console.info("Open this URL in your browser and approve the request:");
21775
+ console.info(` ${url}`);
21776
+ if (!authorization.verificationUriComplete) {
21777
+ console.info(` Then enter this code: ${authorization.userCode}`);
21778
+ }
21779
+ await openBrowserFn(url).catch(() => false);
21780
+ const result = await kimiOAuth3.awaitDeviceToken(authorization, exchangeFetch, {
21781
+ fingerprint,
21782
+ onPending: () => process.stdout.write(".")
21783
+ });
21784
+ console.info("");
21785
+ return {
21786
+ ...result,
21787
+ accountId: kimiOAuth3.kimiAccountIdFromAccessToken(result.accessToken),
21788
+ deviceId
21789
+ };
21790
+ }
21791
+ async function loginKimi(store, deps, exchangeFetch, label) {
21792
+ const result = await deps.awaitKimiDevice(exchangeFetch);
21793
+ const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
21794
+ const block = {
21795
+ authMethod: "oauth",
21796
+ status: "authorized",
21797
+ accessToken: result.accessToken,
21798
+ refreshToken: result.refreshToken,
21799
+ expiresAt,
21800
+ ...result.accountId ? { accountId: result.accountId } : {},
21801
+ deviceId: result.deviceId,
21802
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
21803
+ };
21804
+ await store.appendProviderAccount("kimi", block, label);
21805
+ logMasked("kimi", result.accessToken);
21806
+ return expiresAt;
21807
+ }
21808
+ async function runGrokDeviceFlow(exchangeFetch, openBrowserFn) {
21809
+ const tokenEndpoint = await grokOAuth3.resolveGrokTokenEndpoint(exchangeFetch);
21810
+ const authorization = await grokOAuth3.requestGrokDeviceAuthorization(exchangeFetch);
21811
+ const url = authorization.verificationUriComplete ?? authorization.verificationUri;
21812
+ console.info("Open this URL in your browser and approve the request:");
21813
+ console.info(` ${url}`);
21814
+ if (!authorization.verificationUriComplete) {
21815
+ console.info(` Then enter this code: ${authorization.userCode}`);
21816
+ }
21817
+ await openBrowserFn(url).catch(() => false);
21818
+ const result = await grokOAuth3.awaitGrokDeviceToken(authorization, tokenEndpoint, exchangeFetch, {
21819
+ onPending: () => process.stdout.write(".")
21820
+ });
21821
+ console.info("");
21822
+ return {
21823
+ ...result,
21824
+ accountId: grokOAuth3.grokAccountIdFromAccessToken(result.accessToken)
21825
+ };
21826
+ }
21827
+ async function loginGrok(store, deps, exchangeFetch, label) {
21828
+ const result = await deps.awaitGrokDevice(exchangeFetch);
21829
+ const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
21830
+ const block = {
21831
+ authMethod: "oauth",
21832
+ status: "authorized",
21833
+ accessToken: result.accessToken,
21834
+ refreshToken: result.refreshToken,
21835
+ expiresAt,
21836
+ ...result.accountId ? { accountId: result.accountId } : {},
21837
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
21838
+ };
21839
+ await store.appendProviderAccount("grok", block, label);
21840
+ logMasked("grok", result.accessToken);
21841
+ return expiresAt;
21842
+ }
21843
+ async function runCopilotDeviceFlow(exchangeFetch, openBrowserFn, enterpriseUrl) {
21844
+ if (enterpriseUrl) console.info(`Using GitHub Enterprise host: ${enterpriseUrl}`);
21845
+ const authorization = await copilotOAuth3.requestCopilotDeviceAuthorization(exchangeFetch, enterpriseUrl);
21846
+ const url = authorization.verificationUri;
21847
+ console.info("Open this URL in your browser and approve the request:");
21848
+ console.info(` ${url}`);
21849
+ console.info(` Then enter this code: ${authorization.userCode}`);
21850
+ await openBrowserFn(url).catch(() => false);
21851
+ const result = await copilotOAuth3.awaitCopilotDeviceToken(authorization, exchangeFetch, {
21852
+ onPending: () => process.stdout.write("."),
21853
+ ...enterpriseUrl ? { enterpriseUrl } : {}
21854
+ });
21855
+ console.info("");
21856
+ const identity = await copilotOAuth3.fetchCopilotIdentity(result.accessToken, exchangeFetch, enterpriseUrl);
21857
+ const apiEndpoint = await copilotOAuth3.discoverCopilotApiEndpoint(result.accessToken, exchangeFetch, enterpriseUrl);
21858
+ console.info("Enabling Copilot models (policy)...");
21859
+ await copilotOAuth3.enableAllCopilotModels(
21860
+ result.accessToken,
21861
+ { apiEndpoint, ...enterpriseUrl ? { enterpriseUrl } : {} },
21862
+ exchangeFetch
21863
+ );
21864
+ return {
21865
+ accessToken: result.accessToken,
21866
+ expiresIn: Math.floor(copilotOAuth3.COPILOT_FAR_FUTURE_MS / 1e3),
21867
+ ...identity,
21868
+ ...apiEndpoint ? { apiEndpoint } : {}
21869
+ };
21870
+ }
21871
+ async function loginCopilot(store, deps, exchangeFetch, label, enterpriseUrl) {
21872
+ const result = await deps.awaitCopilotDevice(exchangeFetch, enterpriseUrl);
21873
+ const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
21874
+ const block = {
21875
+ authMethod: "oauth",
21876
+ status: "authorized",
21877
+ accessToken: result.accessToken,
21878
+ // ghu_ tokens have no refresh lifecycle — the same token doubles as the
21879
+ // stored refresh credential so generic refresh paths stay well-formed.
21880
+ refreshToken: result.accessToken,
21881
+ expiresAt,
21882
+ ...result.accountId ? { accountId: result.accountId } : {},
21883
+ ...result.email ? { email: result.email } : {},
21884
+ ...result.apiEndpoint ? { apiEndpoint: result.apiEndpoint } : {},
21885
+ ...enterpriseUrl ? { enterpriseUrl } : {},
21886
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
21887
+ };
21888
+ await store.appendProviderAccount("copilot", block, label);
21889
+ logMasked("copilot", result.accessToken);
21890
+ return expiresAt;
21891
+ }
19446
21892
  function isLoginProvider(value) {
19447
21893
  return PROVIDERS2.includes(value);
19448
21894
  }
@@ -19640,7 +22086,7 @@ function providersRmKey(configPath, providerId, keyId) {
19640
22086
  }
19641
22087
 
19642
22088
  // src/commands/secrets.ts
19643
- import { existsSync as existsSync31, readFileSync as readFileSync24, writeFileSync as writeFileSync19 } from "fs";
22089
+ import { existsSync as existsSync32, readFileSync as readFileSync24 } from "fs";
19644
22090
  import { parseArgs as parseArgs9 } from "util";
19645
22091
  async function runSecrets(argv) {
19646
22092
  const { values, positionals } = parseArgs9({
@@ -19713,12 +22159,12 @@ function secretsStatus(args) {
19713
22159
  reportField("admin.token", cfg.admin.token);
19714
22160
  }
19715
22161
  const tokensPath = defaultTokensPath(args.config);
19716
- if (existsSync31(tokensPath)) {
22162
+ if (existsSync32(tokensPath)) {
19717
22163
  console.info(`Secret status for ${tokensPath}:`);
19718
22164
  reportTokenFields(tokensPath);
19719
22165
  }
19720
22166
  const integrationsPath = defaultIntegrationsPath(args.config);
19721
- if (existsSync31(integrationsPath)) {
22167
+ if (existsSync32(integrationsPath)) {
19722
22168
  const state = readRawJson(integrationsPath);
19723
22169
  const key = state.gatewayKey;
19724
22170
  if (key && typeof key === "object" && !Array.isArray(key)) {
@@ -19772,8 +22218,8 @@ async function secretsRotate(args) {
19772
22218
  const integrationsPath = defaultIntegrationsPath(args.config);
19773
22219
  try {
19774
22220
  cfg = loadConfig(args.config);
19775
- if (existsSync31(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, oldBox);
19776
- if (existsSync31(integrationsPath)) {
22221
+ if (existsSync32(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, oldBox);
22222
+ if (existsSync32(integrationsPath)) {
19777
22223
  integrationsPlain = new IntegrationStateStore(integrationsPath, oldBox).load();
19778
22224
  }
19779
22225
  } finally {
@@ -19808,13 +22254,13 @@ function secretsDecrypt(args) {
19808
22254
  let tokensPlain = null;
19809
22255
  try {
19810
22256
  cfg = loadConfig(args.config);
19811
- if (existsSync31(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, box);
22257
+ if (existsSync32(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, box);
19812
22258
  } finally {
19813
22259
  setSecretBox(null);
19814
22260
  }
19815
22261
  saveConfig(args.config, cfg);
19816
22262
  if (tokensPlain) {
19817
- writeFileSync19(tokensPath, JSON.stringify(tokensPlain, null, 2) + "\n", "utf8");
22263
+ atomicReplaceUtf8(tokensPath, JSON.stringify(tokensPlain, null, 2) + "\n");
19818
22264
  }
19819
22265
  console.info(`Decrypted secrets to plaintext in ${args.config}` + tokensSuffix(args.config));
19820
22266
  }
@@ -19839,13 +22285,13 @@ function readRawJson(path2) {
19839
22285
  }
19840
22286
  function encryptTokensFileInPlace(configPath, box) {
19841
22287
  const tokensPath = defaultTokensPath(configPath);
19842
- if (!existsSync31(tokensPath)) return;
22288
+ if (!existsSync32(tokensPath)) return;
19843
22289
  const plain = decryptTokensFile(tokensPath, box);
19844
22290
  writeTokensEncrypted(tokensPath, plain, box);
19845
22291
  }
19846
22292
  function rewriteIntegrationState(configPath, readBox, writeBox) {
19847
22293
  const path2 = defaultIntegrationsPath(configPath);
19848
- if (!existsSync31(path2)) return;
22294
+ if (!existsSync32(path2)) return;
19849
22295
  const state = new IntegrationStateStore(path2, readBox).load();
19850
22296
  new IntegrationStateStore(path2, writeBox).save(state);
19851
22297
  }
@@ -19858,7 +22304,7 @@ function writeTokensEncrypted(tokensPath, plain, box) {
19858
22304
  { updatedAt: "", ...plain },
19859
22305
  box
19860
22306
  );
19861
- writeFileSync19(tokensPath, JSON.stringify(encrypted, null, 2) + "\n", "utf8");
22307
+ atomicReplaceUtf8(tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
19862
22308
  }
19863
22309
  var TOKEN_FIELDS2 = {
19864
22310
  claude: ["accessToken", "refreshToken"],
@@ -19881,7 +22327,7 @@ function walkTokens(raw, fn) {
19881
22327
  return next;
19882
22328
  }
19883
22329
  function tokensSuffix(configPath) {
19884
- return existsSync31(defaultTokensPath(configPath)) ? " (+ tokens.json)" : "";
22330
+ return existsSync32(defaultTokensPath(configPath)) ? " (+ tokens.json)" : "";
19885
22331
  }
19886
22332
 
19887
22333
  // src/commands/start.ts
@@ -20148,7 +22594,7 @@ async function main() {
20148
22594
  process.exitCode = 1;
20149
22595
  }
20150
22596
  }
20151
- main().catch((err5) => {
20152
- console.error(err5 instanceof Error ? err5.message : String(err5));
22597
+ main().catch((err8) => {
22598
+ console.error(err8 instanceof Error ? err8.message : String(err8));
20153
22599
  process.exitCode = 1;
20154
22600
  });