@omnicross/daemon 0.3.0 → 0.3.1

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.cjs CHANGED
@@ -1166,7 +1166,7 @@ var import_http4 = require("@omnicross/core/search/http");
1166
1166
  var import_search3 = require("@omnicross/core/search");
1167
1167
 
1168
1168
  // src/bootstrap.ts
1169
- var import_node_fs35 = require("fs");
1169
+ var import_node_fs36 = require("fs");
1170
1170
  var import_node_path36 = require("path");
1171
1171
  var import_audit_types = require("@omnicross/contracts/audit-types");
1172
1172
  var import_billing_types = require("@omnicross/contracts/billing-types");
@@ -1176,16 +1176,16 @@ var import_ApiKeyPoolService = require("@omnicross/core/completion/ApiKeyPoolSer
1176
1176
  var import_outbound_api10 = require("@omnicross/core/outbound-api");
1177
1177
  var import_subscriptionRegistryPort = require("@omnicross/core/outbound-api/subscriptionRegistryPort");
1178
1178
  var import_SubscriptionAccountHealth4 = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
1179
- var import_AccountAllowanceStore4 = require("@omnicross/core/pipeline/AccountAllowanceStore");
1179
+ var import_AccountAllowanceStore7 = require("@omnicross/core/pipeline/AccountAllowanceStore");
1180
1180
  var import_AccountAllowanceScheduling5 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
1181
- var import_upstreamFetch9 = require("@omnicross/core/pipeline/upstreamFetch");
1181
+ var import_upstreamFetch13 = require("@omnicross/core/pipeline/upstreamFetch");
1182
1182
  var import_SubscriptionIdentityStore3 = require("@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore");
1183
1183
  var import_gemini_code_assist_resolver = require("@omnicross/core/ports/gemini-code-assist-resolver");
1184
1184
  var import_provider_proxy4 = require("@omnicross/core/provider-proxy");
1185
1185
  var import_cli_launcher2 = require("@omnicross/cli-launcher");
1186
1186
  var import_outbound_api11 = require("@omnicross/core/outbound-api");
1187
1187
  var import_usage2 = require("@omnicross/core/usage");
1188
- var import_subscriptions6 = require("@omnicross/subscriptions");
1188
+ var import_subscriptions9 = require("@omnicross/subscriptions");
1189
1189
 
1190
1190
  // src/admin/accountsCodexOAuth.ts
1191
1191
  var import_node_crypto3 = __toESM(require("crypto"), 1);
@@ -1292,8 +1292,83 @@ function handleCodexOAuthStatus(sessionId, deps) {
1292
1292
  return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
1293
1293
  }
1294
1294
 
1295
+ // src/admin/accountsKimiOAuth.ts
1296
+ var import_subscriptions2 = require("@omnicross/subscriptions");
1297
+ function err2(status, message) {
1298
+ return { status, body: { error: { type: "admin_api_error", message } } };
1299
+ }
1300
+ var DEFAULT_KIMI_OAUTH_TTL_MS = 15 * 6e4;
1301
+ async function handleKimiOAuthStart(deps) {
1302
+ if (deps.kimiSessions.isBusy()) {
1303
+ return err2(409, "a kimi sign-in is already in progress \u2014 finish it in the browser or cancel it");
1304
+ }
1305
+ const fetchImpl = deps.oauthExchangeFetch("kimi");
1306
+ const deviceId = import_subscriptions2.kimiOAuth.generateKimiDeviceId();
1307
+ const fingerprint = import_subscriptions2.kimiOAuth.kimiFingerprintHeaders(deviceId);
1308
+ let authorization;
1309
+ try {
1310
+ authorization = await import_subscriptions2.kimiOAuth.requestDeviceAuthorization(fetchImpl, fingerprint);
1311
+ } catch (e) {
1312
+ const reason = e instanceof Error ? e.message : "device authorization failed";
1313
+ return err2(502, `kimi device authorization failed: ${reason}`);
1314
+ }
1315
+ const { sessionId, signal } = deps.kimiSessions.begin();
1316
+ void runKimiDevicePoll(sessionId, authorization.deviceCode, deviceId, fingerprint, signal, deps).catch(() => deps.kimiSessions.settle(sessionId, "error", "kimi sign-in failed"));
1317
+ return {
1318
+ status: 200,
1319
+ body: {
1320
+ authUrl: authorization.verificationUriComplete ?? authorization.verificationUri,
1321
+ userCode: authorization.userCode,
1322
+ sessionId
1323
+ }
1324
+ };
1325
+ }
1326
+ async function runKimiDevicePoll(sessionId, deviceCode, deviceId, fingerprint, signal, deps) {
1327
+ const fetchImpl = deps.oauthExchangeFetch("kimi");
1328
+ const result = await import_subscriptions2.kimiOAuth.awaitDeviceToken(
1329
+ { userCode: "", deviceCode, verificationUri: "" },
1330
+ fetchImpl,
1331
+ {
1332
+ fingerprint,
1333
+ deadlineMs: DEFAULT_KIMI_OAUTH_TTL_MS,
1334
+ sleep: (ms) => new Promise((resolve11, reject) => {
1335
+ const onAbort = () => {
1336
+ clearTimeout(timer);
1337
+ reject(new Error("login: cancelled"));
1338
+ };
1339
+ const timer = setTimeout(() => {
1340
+ signal.removeEventListener("abort", onAbort);
1341
+ resolve11();
1342
+ }, ms);
1343
+ signal.addEventListener("abort", onAbort, { once: true });
1344
+ })
1345
+ }
1346
+ );
1347
+ const block = {
1348
+ authMethod: "oauth",
1349
+ status: "authorized",
1350
+ accessToken: result.accessToken,
1351
+ refreshToken: result.refreshToken,
1352
+ expiresAt: new Date(Date.now() + result.expiresIn * 1e3).toISOString(),
1353
+ accountId: import_subscriptions2.kimiOAuth.kimiAccountIdFromAccessToken(result.accessToken),
1354
+ deviceId,
1355
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
1356
+ };
1357
+ await deps.subscriptionAccountAppender.appendProviderAccount("kimi", block);
1358
+ deps.kimiSessions.settle(sessionId, "done");
1359
+ }
1360
+ function handleKimiOAuthCancel(sessionId, deps) {
1361
+ if (!deps.kimiSessions.cancel(sessionId)) return err2(404, "unknown or expired kimi sign-in session");
1362
+ return { status: 200, body: { ok: true } };
1363
+ }
1364
+ function handleKimiOAuthStatus(sessionId, deps) {
1365
+ const s = deps.kimiSessions.get(sessionId);
1366
+ if (!s) return err2(404, "unknown or expired kimi sign-in session");
1367
+ return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
1368
+ }
1369
+
1295
1370
  // src/allowance/AccountAllowanceService.ts
1296
- var import_AccountAllowanceStore2 = require("@omnicross/core/pipeline/AccountAllowanceStore");
1371
+ var import_AccountAllowanceStore5 = require("@omnicross/core/pipeline/AccountAllowanceStore");
1297
1372
  var import_AccountAllowanceScheduling = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
1298
1373
 
1299
1374
  // src/allowance/ClaudeAllowanceCollector.ts
@@ -1320,13 +1395,11 @@ function secondsUntil(instant, now) {
1320
1395
  function windowFromPayload(id, payload, now) {
1321
1396
  const usedPercent = finitePercent(payload?.utilization);
1322
1397
  const resetsAt = isoInstant(payload?.resets_at);
1323
- const isSonnet = id === "seven-day-sonnet";
1324
1398
  const isFiveHour = id === "five-hour";
1325
1399
  return {
1326
1400
  id,
1327
- label: isFiveHour ? "5 hours" : isSonnet ? "7 days \xB7 Sonnet" : "7 days",
1328
- scope: isSonnet ? "model-family" : "all",
1329
- modelFamily: isSonnet ? "sonnet" : void 0,
1401
+ label: isFiveHour ? "5 hours" : "7 days",
1402
+ scope: "all",
1330
1403
  usedPercent,
1331
1404
  windowMinutes: isFiveHour ? 5 * 60 : 7 * 24 * 60,
1332
1405
  resetsAt,
@@ -1334,6 +1407,44 @@ function windowFromPayload(id, payload, now) {
1334
1407
  state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
1335
1408
  };
1336
1409
  }
1410
+ function limitEntryWindow(entries, kind) {
1411
+ const entry = entries.find((candidate) => candidate.kind === kind);
1412
+ if (!entry) return void 0;
1413
+ return { utilization: entry.percent, resets_at: entry.resets_at };
1414
+ }
1415
+ function slugifyDisplayName(name) {
1416
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1417
+ }
1418
+ function scopedWeeklyWindows(entries, now) {
1419
+ const seen = /* @__PURE__ */ new Set();
1420
+ const windows = [];
1421
+ for (const entry of entries) {
1422
+ if (entry.kind !== "weekly_scoped") continue;
1423
+ const displayName = typeof entry.scope?.model?.display_name === "string" && entry.scope.model.display_name.trim() ? entry.scope.model.display_name.trim() : void 0;
1424
+ if (!displayName) continue;
1425
+ const slug = slugifyDisplayName(displayName);
1426
+ if (!slug || seen.has(slug)) continue;
1427
+ seen.add(slug);
1428
+ const usedPercent = finitePercent(entry.percent);
1429
+ const resetsAt = isoInstant(entry.resets_at);
1430
+ windows.push({
1431
+ id: `seven-day-${slug}`,
1432
+ label: `7 days \xB7 ${displayName}`,
1433
+ scope: "model-family",
1434
+ modelFamily: slug,
1435
+ usedPercent,
1436
+ windowMinutes: 7 * 24 * 60,
1437
+ resetsAt,
1438
+ remainingSeconds: secondsUntil(resetsAt, now),
1439
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
1440
+ });
1441
+ }
1442
+ return windows;
1443
+ }
1444
+ function parseLimitEntries(raw) {
1445
+ if (!Array.isArray(raw)) return [];
1446
+ return raw.filter((entry) => !!entry && typeof entry === "object");
1447
+ }
1337
1448
  function emptyClaudeWindows(state) {
1338
1449
  return [
1339
1450
  {
@@ -1351,15 +1462,6 @@ function emptyClaudeWindows(state) {
1351
1462
  usedPercent: null,
1352
1463
  windowMinutes: 7 * 24 * 60,
1353
1464
  state
1354
- },
1355
- {
1356
- id: "seven-day-sonnet",
1357
- label: "7 days \xB7 Sonnet",
1358
- scope: "model-family",
1359
- modelFamily: "sonnet",
1360
- usedPercent: null,
1361
- windowMinutes: 7 * 24 * 60,
1362
- state
1363
1465
  }
1364
1466
  ];
1365
1467
  }
@@ -1440,6 +1542,9 @@ var ClaudeAllowanceCollector = class {
1440
1542
  }
1441
1543
  const now = this.now();
1442
1544
  const usage = payload;
1545
+ const limitEntries = parseLimitEntries(usage.limits);
1546
+ const fiveHour = usage.five_hour ?? limitEntryWindow(limitEntries, "session");
1547
+ const sevenDay = usage.seven_day ?? limitEntryWindow(limitEntries, "weekly_all");
1443
1548
  const snapshot = {
1444
1549
  providerId: "claude",
1445
1550
  accountId,
@@ -1447,10 +1552,10 @@ var ClaudeAllowanceCollector = class {
1447
1552
  observedAt: new Date(now).toISOString(),
1448
1553
  expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
1449
1554
  windows: [
1450
- windowFromPayload("five-hour", usage.five_hour, now),
1451
- windowFromPayload("seven-day", usage.seven_day, now),
1452
- windowFromPayload("seven-day-sonnet", usage.seven_day_sonnet, now)
1453
- ]
1555
+ windowFromPayload("five-hour", fiveHour, now),
1556
+ windowFromPayload("seven-day", sevenDay, now),
1557
+ ...scopedWeeklyWindows(limitEntries, now)
1558
+ ].slice(0, 8)
1454
1559
  };
1455
1560
  this.store.set(snapshot);
1456
1561
  return snapshot;
@@ -1507,6 +1612,601 @@ var ClaudeAllowanceCollector = class {
1507
1612
  }
1508
1613
  };
1509
1614
 
1615
+ // src/allowance/CodexAllowanceCollector.ts
1616
+ var import_AccountAllowanceStore2 = require("@omnicross/core/pipeline/AccountAllowanceStore");
1617
+ var import_upstreamFetch2 = require("@omnicross/core/pipeline/upstreamFetch");
1618
+ var CODEX_ALLOWANCE_CACHE_MS = 5 * 6e4;
1619
+ var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
1620
+ var CODEX_CLI_USER_AGENT = "codex_cli_rs/0.144.5";
1621
+ function finiteNumber(value) {
1622
+ if (value === null || value === void 0 || value === "") return null;
1623
+ const parsed = typeof value === "number" ? value : Number(value);
1624
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
1625
+ }
1626
+ function finitePercent2(value) {
1627
+ const parsed = finiteNumber(value);
1628
+ return parsed !== null && parsed <= 100 ? parsed : null;
1629
+ }
1630
+ function epochMs(value) {
1631
+ return value > 1e11 ? value : value * 1e3;
1632
+ }
1633
+ function secondsUntil2(instant, now) {
1634
+ if (!instant) return void 0;
1635
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
1636
+ }
1637
+ function decodeJwtClaims(token) {
1638
+ const parts = token.split(".");
1639
+ if (parts.length !== 3) return void 0;
1640
+ try {
1641
+ const json2 = Buffer.from(parts[1], "base64url").toString("utf8");
1642
+ const parsed = JSON.parse(json2);
1643
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
1644
+ } catch {
1645
+ return void 0;
1646
+ }
1647
+ }
1648
+ function chatgptAccountIdFromClaims(claims) {
1649
+ const auth = claims?.["https://api.openai.com/auth"];
1650
+ if (!auth || typeof auth !== "object") return void 0;
1651
+ const accountId = auth.chatgpt_account_id;
1652
+ return typeof accountId === "string" && accountId.trim() ? accountId.trim() : void 0;
1653
+ }
1654
+ function resolveCodexChatGptAccountId(tokens) {
1655
+ if (tokens.accountId?.trim()) return tokens.accountId.trim();
1656
+ if (tokens.idToken) {
1657
+ const fromIdToken = chatgptAccountIdFromClaims(decodeJwtClaims(tokens.idToken));
1658
+ if (fromIdToken) return fromIdToken;
1659
+ }
1660
+ if (tokens.accessToken) {
1661
+ return chatgptAccountIdFromClaims(decodeJwtClaims(tokens.accessToken));
1662
+ }
1663
+ return void 0;
1664
+ }
1665
+ function windowFromPayload2(id, payload, now) {
1666
+ const usedPercent = finitePercent2(payload?.used_percent);
1667
+ const resetAtSeconds = finiteNumber(payload?.reset_at);
1668
+ const resetAfterSeconds = finiteNumber(payload?.reset_after_seconds);
1669
+ const windowSeconds = finiteNumber(payload?.limit_window_seconds);
1670
+ const resetsAt = resetAtSeconds !== null && resetAtSeconds > 0 ? new Date(epochMs(resetAtSeconds)).toISOString() : resetAfterSeconds !== null && resetAfterSeconds > 0 ? new Date(now + resetAfterSeconds * 1e3).toISOString() : void 0;
1671
+ const windowMinutes = windowSeconds !== null && windowSeconds > 0 ? Math.round(windowSeconds / 60) : void 0;
1672
+ return {
1673
+ id,
1674
+ label: id === "primary" ? "Primary" : "Secondary",
1675
+ scope: "all",
1676
+ usedPercent,
1677
+ ...windowMinutes !== void 0 ? { windowMinutes } : {},
1678
+ ...resetsAt !== void 0 ? { resetsAt } : {},
1679
+ remainingSeconds: secondsUntil2(resetsAt, now),
1680
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
1681
+ };
1682
+ }
1683
+ var CodexAllowanceCollector = class {
1684
+ constructor(credentials, store = (0, import_AccountAllowanceStore2.getSharedAccountAllowanceStore)(), fetchImpl = (url, init, accountId) => (0, import_upstreamFetch2.fetchUpstream)(url, init, { providerId: "codex", accountId, redactBodies: true }), now = Date.now) {
1685
+ this.credentials = credentials;
1686
+ this.store = store;
1687
+ this.fetchImpl = fetchImpl;
1688
+ this.now = now;
1689
+ }
1690
+ credentials;
1691
+ store;
1692
+ fetchImpl;
1693
+ now;
1694
+ inFlight = /* @__PURE__ */ new Map();
1695
+ async collectMany(accounts, options = {}) {
1696
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
1697
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
1698
+ }
1699
+ collect(account, options = {}) {
1700
+ const now = this.now();
1701
+ const unsupported = account.tokens.authMethod !== "oauth";
1702
+ if (unsupported) {
1703
+ const existing = this.store.get("codex", account.id, now);
1704
+ if (existing?.windows.every((window) => window.state === "unsupported")) {
1705
+ return Promise.resolve(existing);
1706
+ }
1707
+ const snapshot = this.unsupportedSnapshot(account.id, now);
1708
+ this.store.set(snapshot);
1709
+ return Promise.resolve(snapshot);
1710
+ }
1711
+ const cached = this.store.get("codex", account.id, now);
1712
+ if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
1713
+ return Promise.resolve(cached);
1714
+ }
1715
+ const running = this.inFlight.get(account.id);
1716
+ if (running) return running;
1717
+ 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));
1718
+ this.inFlight.set(account.id, promise);
1719
+ return promise;
1720
+ }
1721
+ /**
1722
+ * A response-header snapshot stays a valid cache hit only while fresh; an
1723
+ * active oauth-usage snapshot is honored on the same 5-minute cadence as
1724
+ * Claude's (the poll is cheap and quota is the scheduling input).
1725
+ */
1726
+ isCacheValid(snapshot, now, refreshAheadMs) {
1727
+ if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
1728
+ const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
1729
+ const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
1730
+ return Number.isFinite(expiresAt) && expiresAt > now + ahead;
1731
+ }
1732
+ async fetchAccount(accountId, tokens) {
1733
+ let accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
1734
+ if (!accessToken) {
1735
+ return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
1736
+ }
1737
+ let response = await this.request(accountId, accessToken, tokens);
1738
+ if (response.status === 401) {
1739
+ const refreshed = await this.credentials.refreshAccountToken("codex", accountId);
1740
+ if (!refreshed) {
1741
+ return this.failureSnapshot(accountId, "codex_usage_unauthorized", this.now());
1742
+ }
1743
+ accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
1744
+ if (!accessToken) {
1745
+ return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
1746
+ }
1747
+ response = await this.request(accountId, accessToken, tokens);
1748
+ }
1749
+ if (response.status === 403) {
1750
+ const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "codex_usage_unsupported");
1751
+ this.store.set(snapshot2);
1752
+ return snapshot2;
1753
+ }
1754
+ if (!response.ok) {
1755
+ return this.failureSnapshot(accountId, "codex_usage_http_error", this.now());
1756
+ }
1757
+ let payload;
1758
+ try {
1759
+ payload = await response.json();
1760
+ } catch {
1761
+ return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
1762
+ }
1763
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
1764
+ return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
1765
+ }
1766
+ const now = this.now();
1767
+ const usage = payload.rate_limit;
1768
+ const previous = this.store.get("codex", accountId, now);
1769
+ const snapshot = {
1770
+ providerId: "codex",
1771
+ accountId,
1772
+ source: "oauth-usage-api",
1773
+ observedAt: new Date(now).toISOString(),
1774
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
1775
+ windows: [
1776
+ windowFromPayload2("primary", usage?.primary_window ?? void 0, now),
1777
+ windowFromPayload2("secondary", usage?.secondary_window ?? void 0, now)
1778
+ ],
1779
+ // The wham payload has no ratio field; keep the passively-observed value.
1780
+ ...previous?.primaryOverSecondaryLimitPercent !== void 0 ? { primaryOverSecondaryLimitPercent: previous.primaryOverSecondaryLimitPercent } : {}
1781
+ };
1782
+ this.store.set(snapshot);
1783
+ return snapshot;
1784
+ }
1785
+ request(accountId, accessToken, tokens) {
1786
+ const headers = {
1787
+ Authorization: `Bearer ${accessToken}`,
1788
+ Accept: "application/json",
1789
+ "User-Agent": CODEX_CLI_USER_AGENT
1790
+ };
1791
+ const chatgptAccountId = resolveCodexChatGptAccountId(tokens);
1792
+ if (chatgptAccountId) headers["ChatGPT-Account-Id"] = chatgptAccountId;
1793
+ return this.fetchImpl(CODEX_USAGE_URL, {
1794
+ method: "GET",
1795
+ headers,
1796
+ signal: AbortSignal.timeout(15e3)
1797
+ }, accountId);
1798
+ }
1799
+ failureSnapshot(accountId, code, now) {
1800
+ const existing = this.store.get("codex", accountId, now);
1801
+ const snapshot = existing ? {
1802
+ ...existing,
1803
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
1804
+ windows: existing.windows.map((window) => ({
1805
+ ...window,
1806
+ state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
1807
+ })),
1808
+ lastErrorCode: code
1809
+ } : {
1810
+ providerId: "codex",
1811
+ accountId,
1812
+ source: "oauth-usage-api",
1813
+ observedAt: new Date(now).toISOString(),
1814
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
1815
+ windows: [
1816
+ { id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unavailable" },
1817
+ { id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unavailable" }
1818
+ ],
1819
+ lastErrorCode: code
1820
+ };
1821
+ this.store.set(snapshot);
1822
+ return snapshot;
1823
+ }
1824
+ unsupportedSnapshot(accountId, now, code = "codex_usage_unsupported_auth") {
1825
+ return {
1826
+ providerId: "codex",
1827
+ accountId,
1828
+ source: "oauth-usage-api",
1829
+ observedAt: new Date(now).toISOString(),
1830
+ windows: [
1831
+ { id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unsupported" },
1832
+ { id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unsupported" }
1833
+ ],
1834
+ lastErrorCode: code
1835
+ };
1836
+ }
1837
+ };
1838
+
1839
+ // src/allowance/KimiAllowanceCollector.ts
1840
+ var import_AccountAllowanceStore3 = require("@omnicross/core/pipeline/AccountAllowanceStore");
1841
+ var import_upstreamFetch3 = require("@omnicross/core/pipeline/upstreamFetch");
1842
+ var import_subscriptions3 = require("@omnicross/subscriptions");
1843
+ var KIMI_ALLOWANCE_CACHE_MS = 5 * 6e4;
1844
+ var KIMI_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
1845
+ function finiteNumber2(value) {
1846
+ if (value === null || value === void 0 || value === "") return void 0;
1847
+ const parsed = typeof value === "number" ? value : Number(value);
1848
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
1849
+ }
1850
+ function isRecord(value) {
1851
+ return !!value && typeof value === "object" && !Array.isArray(value);
1852
+ }
1853
+ function parseResetMs(row, nowMs) {
1854
+ for (const key of ["reset_at", "resetAt", "reset_time", "resetTime"]) {
1855
+ const value = row[key];
1856
+ if (typeof value === "string" && value.trim()) {
1857
+ const parsed = Date.parse(value);
1858
+ if (Number.isFinite(parsed)) return parsed;
1859
+ }
1860
+ const numeric = finiteNumber2(value);
1861
+ if (numeric !== void 0 && numeric > 1e9) {
1862
+ return numeric > 1e12 ? numeric : numeric * 1e3;
1863
+ }
1864
+ }
1865
+ for (const key of ["reset_in", "resetIn", "ttl", "window"]) {
1866
+ const seconds = finiteNumber2(row[key]);
1867
+ if (seconds !== void 0) return nowMs + seconds * 1e3;
1868
+ }
1869
+ return void 0;
1870
+ }
1871
+ var MINUTE_MS = 6e4;
1872
+ var HOUR_MS = 36e5;
1873
+ var DAY_MS = 864e5;
1874
+ function canonicalWindow(durationMs) {
1875
+ if (durationMs === 5 * HOUR_MS) return { id: "five-hour", label: "5 hours", minutes: 300 };
1876
+ if (durationMs === 7 * DAY_MS) return { id: "seven-day", label: "7 days", minutes: 10080 };
1877
+ if (durationMs > 0 && durationMs % DAY_MS === 0) {
1878
+ const days = durationMs / DAY_MS;
1879
+ return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
1880
+ }
1881
+ if (durationMs > 0 && durationMs % HOUR_MS === 0) {
1882
+ const hours = durationMs / HOUR_MS;
1883
+ return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
1884
+ }
1885
+ return void 0;
1886
+ }
1887
+ function secondsUntil3(instant, now) {
1888
+ if (!instant) return void 0;
1889
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
1890
+ }
1891
+ function windowFromRow(row, fallback, now) {
1892
+ 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;
1893
+ const resetsAt = row?.resetsAtMs !== void 0 ? new Date(row.resetsAtMs).toISOString() : void 0;
1894
+ return {
1895
+ id: fallback.id,
1896
+ label: fallback.label,
1897
+ scope: "all",
1898
+ usedPercent,
1899
+ windowMinutes: fallback.minutes,
1900
+ ...resetsAt !== void 0 ? { resetsAt } : {},
1901
+ remainingSeconds: secondsUntil3(resetsAt, now),
1902
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
1903
+ };
1904
+ }
1905
+ function parseKimiUsagePayload(payload, now) {
1906
+ if (!isRecord(payload)) return [];
1907
+ const byId = /* @__PURE__ */ new Map();
1908
+ const rowFrom = (data) => {
1909
+ const limit = finiteNumber2(data["limit"]);
1910
+ let used = finiteNumber2(data["used"]);
1911
+ const remaining = finiteNumber2(data["remaining"]);
1912
+ if (used === void 0 && remaining !== void 0 && limit !== void 0) {
1913
+ used = limit - remaining;
1914
+ }
1915
+ let windowDurationMs;
1916
+ const windowData = isRecord(data["window"]) ? data["window"] : void 0;
1917
+ const duration = finiteNumber2(windowData?.["duration"]);
1918
+ const timeUnit = typeof windowData?.["timeUnit"] === "string" ? windowData["timeUnit"].toUpperCase() : "";
1919
+ if (duration !== void 0) {
1920
+ if (timeUnit.includes("MINUTE")) windowDurationMs = duration * MINUTE_MS;
1921
+ else if (timeUnit.includes("HOUR")) windowDurationMs = duration * HOUR_MS;
1922
+ else if (timeUnit.includes("DAY")) windowDurationMs = duration * DAY_MS;
1923
+ else if (timeUnit.includes("WEEK")) windowDurationMs = duration * 7 * DAY_MS;
1924
+ else if (timeUnit.includes("SECOND")) windowDurationMs = duration * 1e3;
1925
+ }
1926
+ const resetsAtMs = parseResetMs(windowData && parseResetMs(windowData, now) !== void 0 ? windowData : data, now);
1927
+ return { used, limit, remaining, ...resetsAtMs !== void 0 ? { resetsAtMs } : {}, ...windowDurationMs !== void 0 ? { windowDurationMs } : {} };
1928
+ };
1929
+ if (isRecord(payload["usage"])) {
1930
+ const row = rowFrom(payload["usage"]);
1931
+ const window = windowFromRow({ ...row, resetsAtMs: row.resetsAtMs }, { id: "seven-day", label: "7 days", minutes: 10080 }, now);
1932
+ byId.set("seven-day", window);
1933
+ }
1934
+ if (Array.isArray(payload["limits"])) {
1935
+ for (const item of payload["limits"]) {
1936
+ if (!isRecord(item)) continue;
1937
+ const detail = isRecord(item["detail"]) ? item["detail"] : item;
1938
+ const row = rowFrom(detail);
1939
+ const canonical = row.windowDurationMs !== void 0 ? canonicalWindow(row.windowDurationMs) : void 0;
1940
+ if (!canonical) continue;
1941
+ const window = windowFromRow(row, canonical, now);
1942
+ const existing = byId.get(canonical.id);
1943
+ if (!existing || (window.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
1944
+ byId.set(canonical.id, window);
1945
+ }
1946
+ }
1947
+ }
1948
+ return [...byId.values()].sort((a, b) => (a.windowMinutes ?? Infinity) - (b.windowMinutes ?? Infinity)).slice(0, 4);
1949
+ }
1950
+ var KimiAllowanceCollector = class {
1951
+ constructor(credentials, store = (0, import_AccountAllowanceStore3.getSharedAccountAllowanceStore)(), fetchImpl = (url, init, accountId) => (0, import_upstreamFetch3.fetchUpstream)(url, init, { providerId: "kimi", accountId, redactBodies: true }), now = Date.now) {
1952
+ this.credentials = credentials;
1953
+ this.store = store;
1954
+ this.fetchImpl = fetchImpl;
1955
+ this.now = now;
1956
+ }
1957
+ credentials;
1958
+ store;
1959
+ fetchImpl;
1960
+ now;
1961
+ inFlight = /* @__PURE__ */ new Map();
1962
+ async collectMany(accounts, options = {}) {
1963
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
1964
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
1965
+ }
1966
+ collect(account, options = {}) {
1967
+ const now = this.now();
1968
+ if (account.tokens.authMethod !== "oauth") {
1969
+ const existing = this.store.get("kimi", account.id, now);
1970
+ if (existing?.windows.every((window) => window.state === "unsupported")) {
1971
+ return Promise.resolve(existing);
1972
+ }
1973
+ const snapshot = this.unsupportedSnapshot(account.id, now);
1974
+ this.store.set(snapshot);
1975
+ return Promise.resolve(snapshot);
1976
+ }
1977
+ const cached = this.store.get("kimi", account.id, now);
1978
+ if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
1979
+ return Promise.resolve(cached);
1980
+ }
1981
+ const running = this.inFlight.get(account.id);
1982
+ if (running) return running;
1983
+ 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));
1984
+ this.inFlight.set(account.id, promise);
1985
+ return promise;
1986
+ }
1987
+ isCacheValid(snapshot, now, refreshAheadMs) {
1988
+ if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
1989
+ const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
1990
+ const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
1991
+ return Number.isFinite(expiresAt) && expiresAt > now + ahead;
1992
+ }
1993
+ async fetchAccount(accountId, tokens) {
1994
+ let accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
1995
+ if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
1996
+ let response = await this.request(accountId, accessToken, tokens);
1997
+ if (response.status === 401) {
1998
+ const refreshed = await this.credentials.refreshAccountToken("kimi", accountId);
1999
+ if (!refreshed) return this.failureSnapshot(accountId, "kimi_usage_unauthorized", this.now());
2000
+ accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
2001
+ if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
2002
+ response = await this.request(accountId, accessToken, tokens);
2003
+ }
2004
+ if (response.status === 403) {
2005
+ const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "kimi_usage_unsupported");
2006
+ this.store.set(snapshot2);
2007
+ return snapshot2;
2008
+ }
2009
+ if (!response.ok) return this.failureSnapshot(accountId, "kimi_usage_http_error", this.now());
2010
+ let payload;
2011
+ try {
2012
+ payload = await response.json();
2013
+ } catch {
2014
+ return this.failureSnapshot(accountId, "kimi_usage_invalid_response", this.now());
2015
+ }
2016
+ const now = this.now();
2017
+ const windows = parseKimiUsagePayload(payload, now);
2018
+ const snapshot = {
2019
+ providerId: "kimi",
2020
+ accountId,
2021
+ source: "oauth-usage-api",
2022
+ observedAt: new Date(now).toISOString(),
2023
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
2024
+ windows: windows.length > 0 ? windows : [
2025
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
2026
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
2027
+ ],
2028
+ ...windows.length > 0 ? {} : { lastErrorCode: "kimi_usage_invalid_response" }
2029
+ };
2030
+ this.store.set(snapshot);
2031
+ return snapshot;
2032
+ }
2033
+ request(accountId, accessToken, tokens) {
2034
+ return this.fetchImpl(KIMI_USAGE_URL, {
2035
+ method: "GET",
2036
+ headers: {
2037
+ Authorization: `Bearer ${accessToken}`,
2038
+ Accept: "application/json",
2039
+ ...(0, import_subscriptions3.kimiFingerprintHeaders)(tokens.deviceId)
2040
+ },
2041
+ signal: AbortSignal.timeout(15e3)
2042
+ }, accountId);
2043
+ }
2044
+ failureSnapshot(accountId, code, now) {
2045
+ const existing = this.store.get("kimi", accountId, now);
2046
+ const snapshot = existing ? {
2047
+ ...existing,
2048
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
2049
+ windows: existing.windows.map((window) => ({
2050
+ ...window,
2051
+ state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
2052
+ })),
2053
+ lastErrorCode: code
2054
+ } : {
2055
+ providerId: "kimi",
2056
+ accountId,
2057
+ source: "oauth-usage-api",
2058
+ observedAt: new Date(now).toISOString(),
2059
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
2060
+ windows: [
2061
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
2062
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
2063
+ ],
2064
+ lastErrorCode: code
2065
+ };
2066
+ this.store.set(snapshot);
2067
+ return snapshot;
2068
+ }
2069
+ unsupportedSnapshot(accountId, now, code = "kimi_usage_unsupported_auth") {
2070
+ return {
2071
+ providerId: "kimi",
2072
+ accountId,
2073
+ source: "oauth-usage-api",
2074
+ observedAt: new Date(now).toISOString(),
2075
+ windows: [
2076
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unsupported" },
2077
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unsupported" }
2078
+ ],
2079
+ lastErrorCode: code
2080
+ };
2081
+ }
2082
+ };
2083
+
2084
+ // src/allowance/OpenCodeGoAllowanceCollector.ts
2085
+ var import_AccountAllowanceStore4 = require("@omnicross/core/pipeline/AccountAllowanceStore");
2086
+ var import_upstreamFetch4 = require("@omnicross/core/pipeline/upstreamFetch");
2087
+ var import_subscriptions4 = require("@omnicross/subscriptions");
2088
+ var OPENCODEGO_ALLOWANCE_CACHE_MS = 5 * 6e4;
2089
+ var OPENCODEGO_DEFAULT_GO_BASE = "https://opencode.ai/zen/go";
2090
+ function finitePercent3(value) {
2091
+ if (value === null || value === void 0 || value === "") return null;
2092
+ const parsed = typeof value === "number" ? value : Number(value);
2093
+ return Number.isFinite(parsed) && parsed >= 0 && parsed <= 100 ? parsed : null;
2094
+ }
2095
+ function isoInstant2(value) {
2096
+ if (typeof value !== "string" || !value.trim()) return void 0;
2097
+ const time = Date.parse(value);
2098
+ return Number.isFinite(time) ? new Date(time).toISOString() : void 0;
2099
+ }
2100
+ function secondsUntil4(instant, now) {
2101
+ if (!instant) return void 0;
2102
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
2103
+ }
2104
+ function windowFromPayload3(id, label, minutes, payload, now) {
2105
+ const statusRateLimited = payload?.status === "rate-limited";
2106
+ const usedPercent = statusRateLimited ? 100 : finitePercent3(payload?.percent);
2107
+ const resetsAt = isoInstant2(payload?.resetsAt);
2108
+ return {
2109
+ id,
2110
+ label,
2111
+ scope: "all",
2112
+ usedPercent,
2113
+ windowMinutes: minutes,
2114
+ ...resetsAt !== void 0 ? { resetsAt } : {},
2115
+ remainingSeconds: secondsUntil4(resetsAt, now),
2116
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
2117
+ };
2118
+ }
2119
+ var OpenCodeGoAllowanceCollector = class {
2120
+ constructor(credentials, store = (0, import_AccountAllowanceStore4.getSharedAccountAllowanceStore)(), fetchImpl = (url, init, accountId) => (0, import_upstreamFetch4.fetchUpstream)(url, init, { providerId: "opencodego", accountId, redactBodies: true }), now = Date.now) {
2121
+ this.credentials = credentials;
2122
+ this.store = store;
2123
+ this.fetchImpl = fetchImpl;
2124
+ this.now = now;
2125
+ }
2126
+ credentials;
2127
+ store;
2128
+ fetchImpl;
2129
+ now;
2130
+ inFlight = /* @__PURE__ */ new Map();
2131
+ async collectMany(accounts, options = {}) {
2132
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
2133
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
2134
+ }
2135
+ collect(account, options = {}) {
2136
+ const now = this.now();
2137
+ const cached = this.store.get("opencodego", account.id, now);
2138
+ if (!options.force && cached && (cached.windows.every((window) => window.state === "unsupported") || cached.expiresAt && Date.parse(cached.expiresAt) > now + (options.refreshAheadMs ?? 0))) {
2139
+ return Promise.resolve(cached);
2140
+ }
2141
+ const running = this.inFlight.get(account.id);
2142
+ if (running) return running;
2143
+ const promise = this.fetchAccount(account).catch(() => this.failureSnapshot(account.id, this.now())).finally(() => this.inFlight.delete(account.id));
2144
+ this.inFlight.set(account.id, promise);
2145
+ return promise;
2146
+ }
2147
+ async fetchAccount(account) {
2148
+ const apiKey = await this.credentials.getAccessTokenForAccount("opencodego", account.id);
2149
+ if (!apiKey) return this.failureSnapshot(account.id, this.now());
2150
+ const base = account.tokens.baseUrl ? (0, import_subscriptions4.normalizeOpenCodeGoBaseUrl)(account.tokens.baseUrl) : OPENCODEGO_DEFAULT_GO_BASE;
2151
+ const response = await this.fetchImpl(`${base}/v1/usage`, {
2152
+ method: "GET",
2153
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
2154
+ signal: AbortSignal.timeout(15e3)
2155
+ }, account.id);
2156
+ if (response.status === 401 || response.status === 403) {
2157
+ return this.failureSnapshot(account.id, this.now(), "opencodego_usage_unauthorized");
2158
+ }
2159
+ if (!response.ok) return this.failureSnapshot(account.id, this.now());
2160
+ let payload;
2161
+ try {
2162
+ payload = await response.json();
2163
+ } catch {
2164
+ return this.failureSnapshot(account.id, this.now());
2165
+ }
2166
+ const usage = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.usage : void 0;
2167
+ const now = this.now();
2168
+ const snapshot = {
2169
+ providerId: "opencodego",
2170
+ accountId: account.id,
2171
+ source: "oauth-usage-api",
2172
+ observedAt: new Date(now).toISOString(),
2173
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
2174
+ // Monthly deliberately omitted (module doc).
2175
+ windows: [
2176
+ windowFromPayload3("five-hour", "5 hours", 5 * 60, usage?.rolling ?? void 0, now),
2177
+ windowFromPayload3("seven-day", "7 days", 7 * 24 * 60, usage?.weekly ?? void 0, now)
2178
+ ]
2179
+ };
2180
+ this.store.set(snapshot);
2181
+ return snapshot;
2182
+ }
2183
+ failureSnapshot(accountId, now, code = "opencodego_usage_request_failed") {
2184
+ const existing = this.store.get("opencodego", accountId, now);
2185
+ const snapshot = existing ? {
2186
+ ...existing,
2187
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
2188
+ windows: existing.windows.map((window) => ({
2189
+ ...window,
2190
+ state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
2191
+ })),
2192
+ lastErrorCode: code
2193
+ } : {
2194
+ providerId: "opencodego",
2195
+ accountId,
2196
+ source: "oauth-usage-api",
2197
+ observedAt: new Date(now).toISOString(),
2198
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
2199
+ windows: [
2200
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
2201
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
2202
+ ],
2203
+ lastErrorCode: code
2204
+ };
2205
+ this.store.set(snapshot);
2206
+ return snapshot;
2207
+ }
2208
+ };
2209
+
1510
2210
  // src/allowance/AccountAllowanceService.ts
1511
2211
  function codexUnavailable(accountId, now) {
1512
2212
  return {
@@ -1522,26 +2222,30 @@ function codexUnavailable(accountId, now) {
1522
2222
  };
1523
2223
  }
1524
2224
  var AccountAllowanceService = class {
1525
- constructor(credentials, store = (0, import_AccountAllowanceStore2.getSharedAccountAllowanceStore)(), collector, now = Date.now) {
2225
+ constructor(credentials, store = (0, import_AccountAllowanceStore5.getSharedAccountAllowanceStore)(), collector, codexCollector, kimiCollector, opencodegoCollector, now = Date.now) {
1526
2226
  this.credentials = credentials;
1527
2227
  this.store = store;
1528
2228
  this.now = now;
1529
2229
  this.claudeCollector = collector ?? new ClaudeAllowanceCollector(credentials, store);
2230
+ this.codexCollector = codexCollector ?? new CodexAllowanceCollector(credentials, store);
2231
+ this.kimiCollector = kimiCollector ?? new KimiAllowanceCollector(credentials, store);
2232
+ this.opencodegoCollector = opencodegoCollector ?? new OpenCodeGoAllowanceCollector(credentials, store);
1530
2233
  }
1531
2234
  credentials;
1532
2235
  store;
1533
2236
  now;
1534
2237
  claudeCollector;
2238
+ codexCollector;
2239
+ kimiCollector;
2240
+ opencodegoCollector;
1535
2241
  /**
1536
- * Read all/filtered snapshots. Claude's five-minute cache is refreshed lazily;
1537
- * Codex remains passive and reports not-observed until a real model response.
2242
+ * Read all/filtered snapshots. Claude's and Codex's five-minute caches are
2243
+ * refreshed lazily on read (Codex polls `/backend-api/wham/usage`; the
2244
+ * passive `x-codex-*` header tap still feeds mid-flight updates).
1538
2245
  */
1539
2246
  async list(filter = {}) {
1540
2247
  const config = await this.credentials.getFullConfig();
1541
- this.store.pruneToKnownAccounts([
1542
- ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
1543
- ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
1544
- ]);
2248
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1545
2249
  const wantsClaude = !filter.providerId || filter.providerId === "claude";
1546
2250
  const claudeAccounts = (config.claudeAccounts ?? []).filter(
1547
2251
  (account) => !filter.accountId || account.id === filter.accountId
@@ -1552,39 +2256,90 @@ var AccountAllowanceService = class {
1552
2256
  (account) => !filter.accountId || account.id === filter.accountId
1553
2257
  );
1554
2258
  if (wantsCodex) {
2259
+ await this.codexCollector.collectMany(codexAccounts);
1555
2260
  for (const account of codexAccounts) {
1556
2261
  if (!this.store.get("codex", account.id)) this.store.set(codexUnavailable(account.id, this.now()));
1557
2262
  }
1558
2263
  }
2264
+ const wantsKimi = !filter.providerId || filter.providerId === "kimi";
2265
+ const kimiAccounts = (config.kimiAccounts ?? []).filter(
2266
+ (account) => !filter.accountId || account.id === filter.accountId
2267
+ );
2268
+ if (wantsKimi) await this.kimiCollector.collectMany(kimiAccounts);
2269
+ const wantsOpenCodeGo = !filter.providerId || filter.providerId === "opencodego";
2270
+ const opencodegoAccounts = (config.opencodegoAccounts ?? []).filter(
2271
+ (account) => !filter.accountId || account.id === filter.accountId
2272
+ );
2273
+ if (wantsOpenCodeGo) await this.opencodegoCollector.collectMany(opencodegoAccounts);
1559
2274
  const known = /* @__PURE__ */ new Set();
1560
2275
  if (wantsClaude) for (const account of claudeAccounts) known.add(`claude\0${account.id}`);
1561
2276
  if (wantsCodex) for (const account of codexAccounts) known.add(`codex\0${account.id}`);
2277
+ if (wantsKimi) for (const account of kimiAccounts) known.add(`kimi\0${account.id}`);
2278
+ if (wantsOpenCodeGo) for (const account of opencodegoAccounts) known.add(`opencodego\0${account.id}`);
1562
2279
  return this.store.list(filter).filter((snapshot) => known.has(`${snapshot.providerId}\0${snapshot.accountId}`));
1563
2280
  }
2281
+ knownAccounts(config) {
2282
+ return [
2283
+ ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
2284
+ ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id })),
2285
+ ...(config.kimiAccounts ?? []).map((account) => ({ providerId: "kimi", accountId: account.id })),
2286
+ ...(config.opencodegoAccounts ?? []).map((account) => ({ providerId: "opencodego", accountId: account.id }))
2287
+ ];
2288
+ }
1564
2289
  /** Force-refresh Claude usage for one account or every stored Claude account. */
1565
2290
  async refreshClaude(accountId) {
1566
2291
  const config = await this.credentials.getFullConfig();
1567
- this.store.pruneToKnownAccounts([
1568
- ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
1569
- ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
1570
- ]);
2292
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1571
2293
  const accounts = (config.claudeAccounts ?? []).filter(
1572
2294
  (account) => !accountId || account.id === accountId
1573
2295
  );
1574
2296
  return this.claudeCollector.collectMany(accounts, { force: true });
1575
2297
  }
1576
2298
  /**
1577
- * Keep Claude snapshots warm for allowance-aware routing. This deliberately
1578
- * excludes Codex (whose quota is learned from real response headers) and
1579
- * preserves the collector's cache + per-account in-flight coalescing.
2299
+ * Force-refresh Codex usage (`/backend-api/wham/usage`) for one account or
2300
+ * every stored Codex account. Replaces the old probe-request workaround
2301
+ * no quota is spent reading the usage endpoint.
2302
+ */
2303
+ async refreshCodex(accountId) {
2304
+ const config = await this.credentials.getFullConfig();
2305
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
2306
+ const accounts = (config.codexAccounts ?? []).filter(
2307
+ (account) => !accountId || account.id === accountId
2308
+ );
2309
+ return this.codexCollector.collectMany(accounts, { force: true });
2310
+ }
2311
+ /** Force-refresh OpenCodeGo usage (`{go}/v1/usage`) for one/all accounts. */
2312
+ async refreshOpenCodeGo(accountId) {
2313
+ const config = await this.credentials.getFullConfig();
2314
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
2315
+ const accounts = (config.opencodegoAccounts ?? []).filter(
2316
+ (account) => !accountId || account.id === accountId
2317
+ );
2318
+ return this.opencodegoCollector.collectMany(accounts, { force: true });
2319
+ }
2320
+ /** Force-refresh Kimi usage (`/coding/v1/usages`) for one/all accounts. */
2321
+ async refreshKimi(accountId) {
2322
+ const config = await this.credentials.getFullConfig();
2323
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
2324
+ const accounts = (config.kimiAccounts ?? []).filter(
2325
+ (account) => !accountId || account.id === accountId
2326
+ );
2327
+ return this.kimiCollector.collectMany(accounts, { force: true });
2328
+ }
2329
+ /**
2330
+ * Keep Claude + Codex + Kimi snapshots warm for allowance-aware routing. All
2331
+ * collectors preserve their cache + per-account in-flight coalescing; a tick
2332
+ * normally performs no network I/O. (Codex joined the warm path when it
2333
+ * gained an active `/wham/usage` collector — the passive `x-codex-*` header
2334
+ * tap alone could not keep the policy fed while idle.)
1580
2335
  */
1581
2336
  async maintainClaudeCache(refreshAheadMs) {
1582
2337
  const config = await this.credentials.getFullConfig();
1583
- this.store.pruneToKnownAccounts([
1584
- ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
1585
- ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
1586
- ]);
2338
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1587
2339
  await this.claudeCollector.collectMany(config.claudeAccounts ?? [], { refreshAheadMs });
2340
+ await this.codexCollector.collectMany(config.codexAccounts ?? [], { refreshAheadMs });
2341
+ await this.kimiCollector.collectMany(config.kimiAccounts ?? [], { refreshAheadMs });
2342
+ await this.opencodegoCollector.collectMany(config.opencodegoAccounts ?? [], { refreshAheadMs });
1588
2343
  }
1589
2344
  /** Remove a cache row as soon as an account is deleted by the admin path. */
1590
2345
  removeAccountSnapshot(providerId, accountId) {
@@ -1679,7 +2434,7 @@ var ClaudeAllowanceRefreshScheduler = class {
1679
2434
  var import_node_crypto4 = require("crypto");
1680
2435
  var import_node_fs6 = require("fs");
1681
2436
  var import_node_path6 = require("path");
1682
- var import_AccountAllowanceStore3 = require("@omnicross/core/pipeline/AccountAllowanceStore");
2437
+ var import_AccountAllowanceStore6 = require("@omnicross/core/pipeline/AccountAllowanceStore");
1683
2438
  var ACCOUNT_ALLOWANCE_CACHE_VERSION = 1;
1684
2439
  var MAX_PERSISTED_ALLOWANCE_SNAPSHOTS = 256;
1685
2440
  var MAX_ALLOWANCE_CACHE_BYTES = 1e6;
@@ -1708,7 +2463,7 @@ var JsonAccountAllowancePersistence = class {
1708
2463
  save(snapshots) {
1709
2464
  const rows = [];
1710
2465
  for (const snapshot of snapshots) {
1711
- const normalized2 = (0, import_AccountAllowanceStore3.normalizeAccountAllowanceSnapshot)(snapshot);
2466
+ const normalized2 = (0, import_AccountAllowanceStore6.normalizeAccountAllowanceSnapshot)(snapshot);
1712
2467
  if (!normalized2) continue;
1713
2468
  rows.push(normalized2);
1714
2469
  if (rows.length >= MAX_PERSISTED_ALLOWANCE_SNAPSHOTS) break;
@@ -1984,7 +2739,7 @@ var import_outbound_api5 = require("@omnicross/core/outbound-api");
1984
2739
  var import_image_generation_types = require("@omnicross/contracts/image-generation-types");
1985
2740
  var import_AccountAllowanceScheduling2 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
1986
2741
  var import_SubscriptionAccountHealth = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
1987
- var import_upstreamFetch4 = require("@omnicross/core/pipeline/upstreamFetch");
2742
+ var import_upstreamFetch7 = require("@omnicross/core/pipeline/upstreamFetch");
1988
2743
 
1989
2744
  // src/image-generation/imagesConfigValidation.ts
1990
2745
  var import_outbound_api = require("@omnicross/core/outbound-api");
@@ -3617,11 +4372,11 @@ function preserveOutboundProxySecrets(incoming, current) {
3617
4372
  }
3618
4373
 
3619
4374
  // src/proxy/upstreamProxyResolver.ts
3620
- var import_upstreamFetch2 = require("@omnicross/core/pipeline/upstreamFetch");
4375
+ var import_upstreamFetch5 = require("@omnicross/core/pipeline/upstreamFetch");
3621
4376
  var serverProxy;
3622
4377
  function setServerProxyConfig(proxy) {
3623
4378
  serverProxy = proxy;
3624
- (0, import_upstreamFetch2.bumpUpstreamProxyGeneration)();
4379
+ (0, import_upstreamFetch5.bumpUpstreamProxyGeneration)();
3625
4380
  }
3626
4381
  function getServerProxyConfig() {
3627
4382
  return serverProxy;
@@ -3689,14 +4444,15 @@ function createUpstreamProxyResolver(src = {}) {
3689
4444
  }
3690
4445
 
3691
4446
  // src/admin/accountsOAuth.ts
3692
- var import_subscriptions2 = require("@omnicross/subscriptions");
4447
+ var import_subscriptions5 = require("@omnicross/subscriptions");
3693
4448
 
3694
4449
  // src/admin/accountsWrite.ts
3695
4450
  var VALID_PROVIDER_IDS = [
3696
4451
  "claude",
3697
4452
  "codex",
3698
4453
  "gemini",
3699
- "opencodego"
4454
+ "opencodego",
4455
+ "kimi"
3700
4456
  ];
3701
4457
  function asSubscriptionProviderId(id) {
3702
4458
  return VALID_PROVIDER_IDS.includes(id) ? id : null;
@@ -3820,7 +4576,19 @@ function validateCodex(body) {
3820
4576
  ]);
3821
4577
  return out;
3822
4578
  }
3823
- function validateGemini(body) {
4579
+ function validateGemini(body) {
4580
+ const authMethod = str(body["authMethod"]);
4581
+ const status = str(body["status"]);
4582
+ if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
4583
+ if (!status || !TOKEN_STATUSES.has(status)) return null;
4584
+ const out = {
4585
+ authMethod,
4586
+ status
4587
+ };
4588
+ copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "lastRefreshedAt", "errorMessage"]);
4589
+ return out;
4590
+ }
4591
+ function validateKimi(body) {
3824
4592
  const authMethod = str(body["authMethod"]);
3825
4593
  const status = str(body["status"]);
3826
4594
  if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
@@ -3829,7 +4597,7 @@ function validateGemini(body) {
3829
4597
  authMethod,
3830
4598
  status
3831
4599
  };
3832
- copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "lastRefreshedAt", "errorMessage"]);
4600
+ copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "accountId", "deviceId", "lastRefreshedAt", "errorMessage"]);
3833
4601
  return out;
3834
4602
  }
3835
4603
  function validateOpenCodeGo(body) {
@@ -3867,6 +4635,8 @@ function validateTokenBody(providerId, body) {
3867
4635
  return validateGemini(body);
3868
4636
  case "opencodego":
3869
4637
  return validateOpenCodeGo(body);
4638
+ case "kimi":
4639
+ return validateKimi(body);
3870
4640
  default:
3871
4641
  return null;
3872
4642
  }
@@ -3896,37 +4666,37 @@ async function statusEntryFor(reader, providerId) {
3896
4666
 
3897
4667
  // src/admin/accountsOAuth.ts
3898
4668
  var OAUTH_HTTP_PROVIDERS = /* @__PURE__ */ new Set(["claude", "gemini"]);
3899
- function err2(status, message) {
4669
+ function err3(status, message) {
3900
4670
  return { status, body: { error: { type: "admin_api_error", message } } };
3901
4671
  }
3902
4672
  function handleOAuthStart(providerId, deps) {
3903
4673
  if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
3904
- return err2(400, `oauth not available for provider '${providerId}'`);
4674
+ return err3(400, `oauth not available for provider '${providerId}'`);
3905
4675
  }
3906
- const flow = providerId === "claude" ? import_subscriptions2.claudeOAuth : import_subscriptions2.geminiOAuth;
4676
+ const flow = providerId === "claude" ? import_subscriptions5.claudeOAuth : import_subscriptions5.geminiOAuth;
3907
4677
  const { authUrl, codeVerifier, state } = flow.generateAuthParams();
3908
4678
  const sessionId = deps.oauthSessions.put({ providerId, codeVerifier, state });
3909
4679
  return { status: 200, body: { authUrl, sessionId } };
3910
4680
  }
3911
4681
  async function handleOAuthComplete(providerId, body, deps) {
3912
4682
  if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
3913
- return err2(400, `oauth not available for provider '${providerId}'`);
4683
+ return err3(400, `oauth not available for provider '${providerId}'`);
3914
4684
  }
3915
4685
  const sessionId = typeof body["sessionId"] === "string" ? body["sessionId"] : "";
3916
4686
  const rawCode = typeof body["code"] === "string" ? body["code"] : "";
3917
- if (!sessionId) return err2(400, "oauth complete requires { sessionId }");
3918
- if (!rawCode) return err2(400, "oauth complete requires { code }");
4687
+ if (!sessionId) return err3(400, "oauth complete requires { sessionId }");
4688
+ if (!rawCode) return err3(400, "oauth complete requires { code }");
3919
4689
  const session = deps.oauthSessions.peek(sessionId);
3920
- if (!session) return err2(410, "oauth session is unknown, expired, or already used");
4690
+ if (!session) return err3(410, "oauth session is unknown, expired, or already used");
3921
4691
  if (session.providerId !== providerId) {
3922
- return err2(400, `oauth session does not match provider '${providerId}'`);
4692
+ return err3(400, `oauth session does not match provider '${providerId}'`);
3923
4693
  }
3924
4694
  let code = rawCode.trim();
3925
4695
  if (providerId === "claude") {
3926
4696
  const [splitCode, pastedState] = code.split("#");
3927
- if (!splitCode) return err2(400, "no authorization code was provided");
4697
+ if (!splitCode) return err3(400, "no authorization code was provided");
3928
4698
  if (pastedState && pastedState !== session.state) {
3929
- return err2(400, "oauth state did not match (possible CSRF) \u2014 aborting");
4699
+ return err3(400, "oauth state did not match (possible CSRF) \u2014 aborting");
3930
4700
  }
3931
4701
  code = splitCode;
3932
4702
  }
@@ -3936,7 +4706,7 @@ async function handleOAuthComplete(providerId, body, deps) {
3936
4706
  block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state, exchangeFetch) : await exchangeGemini(code, session.codeVerifier, exchangeFetch);
3937
4707
  } catch (exchangeError) {
3938
4708
  const reason = exchangeError instanceof Error ? exchangeError.message : "token exchange failed";
3939
- return err2(502, `oauth token exchange failed for '${providerId}': ${reason}`);
4709
+ return err3(502, `oauth token exchange failed for '${providerId}': ${reason}`);
3940
4710
  }
3941
4711
  deps.oauthSessions.consume(sessionId);
3942
4712
  const label = typeof body["label"] === "string" && body["label"].trim() ? body["label"].trim() : void 0;
@@ -3945,7 +4715,7 @@ async function handleOAuthComplete(providerId, body, deps) {
3945
4715
  return { status: 200, body: status ? { account: status } : { ok: true } };
3946
4716
  }
3947
4717
  async function exchangeClaude(code, codeVerifier, state, exchangeFetch) {
3948
- const result = await import_subscriptions2.claudeOAuth.exchangeCodeForTokens(
4718
+ const result = await import_subscriptions5.claudeOAuth.exchangeCodeForTokens(
3949
4719
  { authorizationCode: code, codeVerifier, state },
3950
4720
  exchangeFetch
3951
4721
  );
@@ -3961,7 +4731,7 @@ async function exchangeClaude(code, codeVerifier, state, exchangeFetch) {
3961
4731
  };
3962
4732
  }
3963
4733
  async function exchangeGemini(code, codeVerifier, exchangeFetch) {
3964
- const result = await import_subscriptions2.geminiOAuth.exchangeCodeForTokens(code, codeVerifier, exchangeFetch);
4734
+ const result = await import_subscriptions5.geminiOAuth.exchangeCodeForTokens(code, codeVerifier, exchangeFetch);
3965
4735
  const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
3966
4736
  return {
3967
4737
  authMethod: "oauth",
@@ -4266,8 +5036,8 @@ function errBody(message) {
4266
5036
  return { error: { type: "admin_api_error", message } };
4267
5037
  }
4268
5038
  var defaultCommandRunner = (command) => new Promise((resolve11) => {
4269
- (0, import_node_child_process.exec)(command, { timeout: 18e4 }, (err5, _stdout, stderr) => {
4270
- if (err5) resolve11({ ok: false, error: stderr.trim() || err5.message });
5039
+ (0, import_node_child_process.exec)(command, { timeout: 18e4 }, (err6, _stdout, stderr) => {
5040
+ if (err6) resolve11({ ok: false, error: stderr.trim() || err6.message });
4271
5041
  else resolve11({ ok: true });
4272
5042
  });
4273
5043
  });
@@ -4313,8 +5083,8 @@ async function handleCliLaunch(cli, body, ctx) {
4313
5083
  providerId: typeof body["providerId"] === "string" ? body["providerId"] : void 0,
4314
5084
  model: typeof body["model"] === "string" ? body["model"] : void 0
4315
5085
  });
4316
- } catch (err5) {
4317
- return { status: 400, body: errBody(err5 instanceof Error ? err5.message : "no launch target") };
5086
+ } catch (err6) {
5087
+ return { status: 400, body: errBody(err6 instanceof Error ? err6.message : "no launch target") };
4318
5088
  }
4319
5089
  const id = (0, import_node_crypto7.randomUUID)();
4320
5090
  let leaseId2;
@@ -4342,9 +5112,9 @@ async function handleCliLaunch(cli, body, ctx) {
4342
5112
  } else {
4343
5113
  launch = await buildLaunchEnv(cli, ctx.llmConfig, target);
4344
5114
  }
4345
- } catch (err5) {
4346
- const status = err5 instanceof import_provider_proxy2.RouteLeaseError ? err5.status : 400;
4347
- return { status, body: errBody(err5 instanceof Error ? err5.message : "failed to build launch env") };
5115
+ } catch (err6) {
5116
+ const status = err6 instanceof import_provider_proxy2.RouteLeaseError ? err6.status : 400;
5117
+ return { status, body: errBody(err6 instanceof Error ? err6.message : "failed to build launch env") };
4348
5118
  }
4349
5119
  const cwd = typeof body["cwd"] === "string" && body["cwd"].trim() ? body["cwd"].trim() : void 0;
4350
5120
  const opener = ctx.opener ?? defaultTerminalOpener;
@@ -4372,9 +5142,9 @@ async function handleCliLaunch(cli, body, ctx) {
4372
5142
  onFailure: onSessionEnd
4373
5143
  });
4374
5144
  if (cleanup) openerCleanup = cleanup;
4375
- } catch (err5) {
5145
+ } catch (err6) {
4376
5146
  onSessionEnd();
4377
- return { status: 500, body: errBody(err5 instanceof Error ? err5.message : "failed to open terminal") };
5147
+ return { status: 500, body: errBody(err6 instanceof Error ? err6.message : "failed to open terminal") };
4378
5148
  }
4379
5149
  if (ended) {
4380
5150
  openerCleanup?.();
@@ -4629,7 +5399,7 @@ async function runSearchLiveChecks(contributions, now = () => (/* @__PURE__ */ n
4629
5399
  }
4630
5400
 
4631
5401
  // src/search/SearchAssembly.ts
4632
- var import_upstreamFetch3 = require("@omnicross/core/pipeline/upstreamFetch");
5402
+ var import_upstreamFetch6 = require("@omnicross/core/pipeline/upstreamFetch");
4633
5403
  var import_search = require("@omnicross/core/search");
4634
5404
  var import_api2 = require("@omnicross/core/search/api");
4635
5405
  var import_http2 = require("@omnicross/core/search/http");
@@ -4647,7 +5417,7 @@ function searchPolicyFrom(config) {
4647
5417
  };
4648
5418
  }
4649
5419
  function resolveSearchUpstreamDispatcher(url) {
4650
- return (0, import_upstreamFetch3.resolveUpstreamDispatcher)({ url });
5420
+ return (0, import_upstreamFetch6.resolveUpstreamDispatcher)({ url });
4651
5421
  }
4652
5422
  var searchUpstreamProxyConfig = createUpstreamProxyResolver();
4653
5423
  function resolveSearchUpstreamProxyConfig(url) {
@@ -4929,7 +5699,7 @@ async function handleSearchQuery(req, res, deps) {
4929
5699
  // src/admin/searchAdminView.ts
4930
5700
  var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
4931
5701
  var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
4932
- function isRecord(value) {
5702
+ function isRecord2(value) {
4933
5703
  return value !== null && typeof value === "object" && !Array.isArray(value);
4934
5704
  }
4935
5705
  function redactSearchServerConfig(search) {
@@ -4979,13 +5749,13 @@ function resolveSecretField(entry, field, stored) {
4979
5749
  else delete entry[field];
4980
5750
  }
4981
5751
  function preserveSearchSecrets(incoming, current) {
4982
- if (!isRecord(incoming)) return incoming;
5752
+ if (!isRecord2(incoming)) return incoming;
4983
5753
  const section = { ...incoming };
4984
5754
  const providersValue = section["providers"];
4985
- if (!isRecord(providersValue)) return section;
5755
+ if (!isRecord2(providersValue)) return section;
4986
5756
  const providers = {};
4987
5757
  for (const [id, entryValue] of Object.entries(providersValue)) {
4988
- if (!isRecord(entryValue)) {
5758
+ if (!isRecord2(entryValue)) {
4989
5759
  providers[id] = entryValue;
4990
5760
  continue;
4991
5761
  }
@@ -5063,7 +5833,7 @@ function parseKeyPolicyBody(body) {
5063
5833
  var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
5064
5834
  var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
5065
5835
  var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
5066
- function isRecord2(value) {
5836
+ function isRecord3(value) {
5067
5837
  return !!value && typeof value === "object" && !Array.isArray(value);
5068
5838
  }
5069
5839
  function nonBlank(value) {
@@ -5083,7 +5853,7 @@ function validateGatewayBindingsSegment(patch) {
5083
5853
  const ids = /* @__PURE__ */ new Set();
5084
5854
  raw.forEach((entry, index) => {
5085
5855
  const path2 = `bindings[${index}]`;
5086
- if (!isRecord2(entry)) {
5856
+ if (!isRecord3(entry)) {
5087
5857
  errors.push(`${path2} must be an object`);
5088
5858
  return;
5089
5859
  }
@@ -5112,12 +5882,12 @@ function validateGatewayBindingsSegment(patch) {
5112
5882
  } else if (entry.modelMappings.length > 100) {
5113
5883
  errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
5114
5884
  } else if (entry.modelMappings.some(
5115
- (mapping) => !isRecord2(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
5885
+ (mapping) => !isRecord3(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
5116
5886
  )) {
5117
5887
  errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
5118
5888
  }
5119
5889
  }
5120
- if (!isRecord2(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
5890
+ if (!isRecord3(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
5121
5891
  errors.push(`${path2}.target is invalid`);
5122
5892
  } else {
5123
5893
  if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
@@ -5132,7 +5902,7 @@ function validateGatewayBindingsSegment(patch) {
5132
5902
  }
5133
5903
  }
5134
5904
  if (entry.modelMap !== void 0) {
5135
- if (!isRecord2(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
5905
+ if (!isRecord3(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
5136
5906
  errors.push(`${path2}.modelMap must contain string values`);
5137
5907
  }
5138
5908
  }
@@ -5414,7 +6184,8 @@ var PROVIDER_KEYS = {
5414
6184
  block: "opencodego",
5415
6185
  accounts: "opencodegoAccounts",
5416
6186
  active: "activeOpencodegoAccountId"
5417
- }
6187
+ },
6188
+ kimi: { block: "kimi", accounts: "kimiAccounts", active: "activeKimiAccountId" }
5418
6189
  };
5419
6190
  function clone(value) {
5420
6191
  return JSON.parse(JSON.stringify(value));
@@ -5936,7 +6707,7 @@ async function applyImport(packString, passphrase, mode, deps, parseProviderInpu
5936
6707
  }
5937
6708
 
5938
6709
  // src/admin/adminMigration.ts
5939
- function err3(status, message) {
6710
+ function err4(status, message) {
5940
6711
  return { status, body: { error: { type: "admin_api_error", message } } };
5941
6712
  }
5942
6713
  async function handleExport(body, deps) {
@@ -5946,30 +6717,30 @@ async function handleExport(body, deps) {
5946
6717
  return { status: 200, body: { pack, version: BUNDLE_VERSION } };
5947
6718
  } catch (error) {
5948
6719
  if (error instanceof WeakPassphraseError) {
5949
- return err3(400, error.message);
6720
+ return err4(400, error.message);
5950
6721
  }
5951
- return err3(500, "failed to build the migration pack");
6722
+ return err4(500, "failed to build the migration pack");
5952
6723
  }
5953
6724
  }
5954
6725
  async function handleImport(body, deps) {
5955
6726
  const blob = typeof body["blob"] === "string" ? body["blob"] : "";
5956
6727
  const passphrase = typeof body["passphrase"] === "string" ? body["passphrase"] : "";
5957
6728
  const mode = body["mode"] === "overwrite" ? "overwrite" : "merge";
5958
- if (!blob) return err3(400, "import requires { blob }");
6729
+ if (!blob) return err4(400, "import requires { blob }");
5959
6730
  try {
5960
6731
  const counts = await applyImport(blob, passphrase, mode, deps, deps.parseProviderInput);
5961
6732
  return { status: 200, body: counts };
5962
6733
  } catch (error) {
5963
6734
  if (error instanceof WeakPassphraseError) {
5964
- return err3(400, error.message);
6735
+ return err4(400, error.message);
5965
6736
  }
5966
- return err3(400, error instanceof Error ? error.message : "import failed");
6737
+ return err4(400, error instanceof Error ? error.message : "import failed");
5967
6738
  }
5968
6739
  }
5969
6740
 
5970
6741
  // src/admin/usagePricing.ts
5971
6742
  var import_usage = require("@omnicross/core/usage");
5972
- var err4 = (status, message) => ({
6743
+ var err5 = (status, message) => ({
5973
6744
  status,
5974
6745
  body: { error: { type: "admin_api_error", message } }
5975
6746
  });
@@ -5982,7 +6753,7 @@ function parseRange(query2) {
5982
6753
  const startTs = parseFiniteInt(query2.get("startTs"));
5983
6754
  const endTs = parseFiniteInt(query2.get("endTs"));
5984
6755
  if (startTs === null || endTs === null) {
5985
- return err4(400, "startTs and endTs are required finite-integer unix-millis query params");
6756
+ return err5(400, "startTs and endTs are required finite-integer unix-millis query params");
5986
6757
  }
5987
6758
  return { startTs, endTs };
5988
6759
  }
@@ -6007,14 +6778,14 @@ async function handleUsageGet(view, query2, deps) {
6007
6778
  case "timeseries": {
6008
6779
  const bucket = query2.get("bucket");
6009
6780
  if (bucket !== "hour" && bucket !== "day" && bucket !== "month") {
6010
- return err4(400, "bucket must be one of 'hour', 'day', 'month'");
6781
+ return err5(400, "bucket must be one of 'hour', 'day', 'month'");
6011
6782
  }
6012
6783
  const now = Date.now();
6013
6784
  const clamped = { startTs: range.startTs, endTs: Math.min(range.endTs, now) };
6014
6785
  if (clamped.startTs < clamped.endTs) {
6015
6786
  const projected = Math.ceil((clamped.endTs - clamped.startTs) / BUCKET_SPAN_MS[bucket]) + 1;
6016
6787
  if (projected > MAX_TIMESERIES_BUCKETS) {
6017
- return err4(
6788
+ return err5(
6018
6789
  400,
6019
6790
  `requested range projects ~${projected} '${bucket}' buckets (max ${MAX_TIMESERIES_BUCKETS}); narrow the range or use a coarser bucket`
6020
6791
  );
@@ -6037,7 +6808,7 @@ async function handleUsageGet(view, query2, deps) {
6037
6808
  };
6038
6809
  }
6039
6810
  default:
6040
- return err4(404, `unknown usage view '${view ?? ""}'`);
6811
+ return err5(404, `unknown usage view '${view ?? ""}'`);
6041
6812
  }
6042
6813
  }
6043
6814
  function poolKeyLabels(cfg) {
@@ -6086,7 +6857,7 @@ async function handlePricingList(deps) {
6086
6857
  async function handlePricingUpsert(body, deps) {
6087
6858
  const input = parsePricingEntryInput(body);
6088
6859
  if (!input) {
6089
- return err4(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
6860
+ return err5(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
6090
6861
  }
6091
6862
  const entry = await deps.pricingEngine.upsertManual(input);
6092
6863
  return { status: 200, body: { entry } };
@@ -6095,7 +6866,7 @@ async function handlePricingDelete(query2, deps) {
6095
6866
  const providerId = query2.get("providerId")?.trim() ?? "";
6096
6867
  const modelId = query2.get("modelId")?.trim() ?? "";
6097
6868
  if (!providerId || !modelId) {
6098
- return err4(400, "delete requires providerId and modelId query params");
6869
+ return err5(400, "delete requires providerId and modelId query params");
6099
6870
  }
6100
6871
  const deleted = await deps.pricingStore.delete(providerId, modelId);
6101
6872
  if (deleted) await deps.pricingEngine.invalidateCache();
@@ -6115,13 +6886,13 @@ async function handlePricingFetchLatest(deps) {
6115
6886
  }
6116
6887
  };
6117
6888
  } catch (e) {
6118
- return err4(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
6889
+ return err5(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
6119
6890
  }
6120
6891
  }
6121
6892
  async function handlePricingResolveConflicts(body, deps) {
6122
6893
  const raw = body["resolutions"];
6123
6894
  if (!Array.isArray(raw)) {
6124
- return err4(400, "resolve-conflicts requires { resolutions: [...] }");
6895
+ return err5(400, "resolve-conflicts requires { resolutions: [...] }");
6125
6896
  }
6126
6897
  const currentRows = await deps.pricingStore.getAll();
6127
6898
  const userEditedKeys = new Set(
@@ -6131,21 +6902,21 @@ async function handlePricingResolveConflicts(body, deps) {
6131
6902
  const pendingIncoming = /* @__PURE__ */ new Map();
6132
6903
  let staleCount = 0;
6133
6904
  for (const item of raw) {
6134
- if (!item || typeof item !== "object") return err4(400, "invalid resolution entry");
6905
+ if (!item || typeof item !== "object") return err5(400, "invalid resolution entry");
6135
6906
  const r = item;
6136
6907
  const action = r["action"];
6137
6908
  if (action !== "overwrite" && action !== "skip") {
6138
- return err4(400, "resolution action must be 'overwrite' or 'skip'");
6909
+ return err5(400, "resolution action must be 'overwrite' or 'skip'");
6139
6910
  }
6140
6911
  const providerId = typeof r["providerId"] === "string" && r["providerId"].trim() ? r["providerId"].trim() : "";
6141
6912
  const modelId = typeof r["modelId"] === "string" && r["modelId"].trim() ? r["modelId"].trim() : "";
6142
6913
  if (!providerId || !modelId) {
6143
- return err4(400, "each resolution requires top-level providerId and modelId");
6914
+ return err5(400, "each resolution requires top-level providerId and modelId");
6144
6915
  }
6145
6916
  const incoming = parsePricingEntryInput(r["incoming"]);
6146
- if (!incoming) return err4(400, "each resolution must echo a valid incoming pricing entry");
6917
+ if (!incoming) return err5(400, "each resolution must echo a valid incoming pricing entry");
6147
6918
  if (incoming.providerId !== providerId || incoming.modelId !== modelId) {
6148
- return err4(400, "resolution providerId/modelId must match the echoed incoming entry");
6919
+ return err5(400, "resolution providerId/modelId must match the echoed incoming entry");
6149
6920
  }
6150
6921
  const key = `${providerId}::${modelId}`;
6151
6922
  if (action === "overwrite" && !userEditedKeys.has(key)) {
@@ -6190,7 +6961,7 @@ function query(req) {
6190
6961
  }
6191
6962
  function allowanceProvider(value) {
6192
6963
  if (!value) return void 0;
6193
- return value === "claude" || value === "codex" ? value : null;
6964
+ return value === "claude" || value === "codex" || value === "kimi" || value === "opencodego" ? value : null;
6194
6965
  }
6195
6966
  async function handleAccountAllowanceApi(req, res, method, rest, service) {
6196
6967
  if (!service) return writeError2(res, 501, "account allowance service is not available");
@@ -6204,7 +6975,9 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
6204
6975
  const params = query(req);
6205
6976
  const pathProvider = rest.length >= 2 ? rest[0] : null;
6206
6977
  const providerId = allowanceProvider(pathProvider ?? params.get("providerId") ?? params.get("provider"));
6207
- if (providerId === null) return writeError2(res, 400, "providerId must be claude or codex");
6978
+ if (providerId === null) {
6979
+ return writeError2(res, 400, "providerId must be claude, codex, kimi, or opencodego");
6980
+ }
6208
6981
  const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
6209
6982
  const allowances = await service.list({ providerId, accountId });
6210
6983
  return writeJson3(res, 200, { allowances });
@@ -6214,10 +6987,37 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
6214
6987
  const requestedProvider = allowanceProvider(
6215
6988
  typeof body["providerId"] === "string" ? body["providerId"] : "claude"
6216
6989
  );
6217
- if (requestedProvider !== "claude") {
6218
- return writeError2(res, 400, "only Claude allowances support explicit refresh");
6219
- }
6220
6990
  const accountId = typeof body["accountId"] === "string" && body["accountId"].trim() ? body["accountId"].trim() : void 0;
6991
+ if (requestedProvider === "codex") {
6992
+ if (!service.refreshCodex) {
6993
+ return writeError2(res, 501, "codex allowance refresh is not available");
6994
+ }
6995
+ const allowances2 = await service.refreshCodex(accountId);
6996
+ if (accountId && allowances2.length === 0) {
6997
+ return writeError2(res, 404, `Codex account '${accountId}' not found`);
6998
+ }
6999
+ return writeJson3(res, 200, { allowances: allowances2 });
7000
+ }
7001
+ if (requestedProvider === "kimi") {
7002
+ if (!service.refreshKimi) {
7003
+ return writeError2(res, 501, "kimi allowance refresh is not available");
7004
+ }
7005
+ const allowances2 = await service.refreshKimi(accountId);
7006
+ if (accountId && allowances2.length === 0) {
7007
+ return writeError2(res, 404, `Kimi account '${accountId}' not found`);
7008
+ }
7009
+ return writeJson3(res, 200, { allowances: allowances2 });
7010
+ }
7011
+ if (requestedProvider === "opencodego") {
7012
+ if (!service.refreshOpenCodeGo) {
7013
+ return writeError2(res, 501, "opencodego allowance refresh is not available");
7014
+ }
7015
+ const allowances2 = await service.refreshOpenCodeGo(accountId);
7016
+ if (accountId && allowances2.length === 0) {
7017
+ return writeError2(res, 404, `OpenCodeGo account '${accountId}' not found`);
7018
+ }
7019
+ return writeJson3(res, 200, { allowances: allowances2 });
7020
+ }
6221
7021
  const allowances = await service.refreshClaude(accountId);
6222
7022
  if (accountId && allowances.length === 0) {
6223
7023
  return writeError2(res, 404, `Claude account '${accountId}' not found`);
@@ -6385,8 +7185,8 @@ async function handleAdminApi(req, res, path2, deps) {
6385
7185
  default:
6386
7186
  return writeJsonError(res, 404, `unknown admin resource '${resource}'`);
6387
7187
  }
6388
- } catch (err5) {
6389
- writeJsonError(res, 500, err5 instanceof Error ? err5.message : String(err5));
7188
+ } catch (err6) {
7189
+ writeJsonError(res, 500, err6 instanceof Error ? err6.message : String(err6));
6390
7190
  }
6391
7191
  }
6392
7192
  function requestQuery(req) {
@@ -6456,6 +7256,9 @@ async function handleProviders(req, res, method, rest, deps) {
6456
7256
  if (method === "POST" && rest.length === 4 && rest[1] === "keys" && rest[3] === "enabled") {
6457
7257
  return await handleToggleProviderKey(req, res, rest[0], rest[2], cfg, deps);
6458
7258
  }
7259
+ if (method === "POST" && rest.length === 5 && rest[1] === "keys" && rest[3] === "quota" && rest[4] === "refresh") {
7260
+ return await handleProviderKeyQuotaRefresh(res, rest[0], rest[2], cfg, deps);
7261
+ }
6459
7262
  if (method === "PUT" && rest.length === 3 && rest[1] === "keys") {
6460
7263
  return await handleUpdateProviderKey(req, res, rest[0], rest[2], cfg, deps);
6461
7264
  }
@@ -6554,7 +7357,7 @@ async function handleDiscoverModels(res, id, cfg) {
6554
7357
  try {
6555
7358
  const headers = { Accept: "application/json" };
6556
7359
  if (resolvedKey) headers["Authorization"] = `Bearer ${resolvedKey}`;
6557
- const response = await (0, import_upstreamFetch4.fetchUpstream)(url, { method: "GET", headers }, { providerId: "byo" });
7360
+ const response = await (0, import_upstreamFetch7.fetchUpstream)(url, { method: "GET", headers }, { providerId: "byo" });
6558
7361
  if (!response.ok) {
6559
7362
  const text = await response.text().catch(() => "");
6560
7363
  let message = text.slice(0, 300);
@@ -6571,8 +7374,8 @@ async function handleDiscoverModels(res, id, cfg) {
6571
7374
  const data = await response.json();
6572
7375
  const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
6573
7376
  return writeJson4(res, 200, { models });
6574
- } catch (err5) {
6575
- const message = err5 instanceof Error ? err5.message : String(err5);
7377
+ } catch (err6) {
7378
+ const message = err6 instanceof Error ? err6.message : String(err6);
6576
7379
  return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
6577
7380
  }
6578
7381
  }
@@ -6613,7 +7416,7 @@ async function handleTestModel(req, res, id, cfg) {
6613
7416
  }
6614
7417
  const startedAt = Date.now();
6615
7418
  try {
6616
- const response = await (0, import_upstreamFetch4.fetchUpstream)(
7419
+ const response = await (0, import_upstreamFetch7.fetchUpstream)(
6617
7420
  url,
6618
7421
  { method: "POST", headers, body: JSON.stringify(payload) },
6619
7422
  { providerId: "byo" }
@@ -6635,8 +7438,8 @@ async function handleTestModel(req, res, id, cfg) {
6635
7438
  latencyMs,
6636
7439
  sample: extractSampleText(text, row.apiFormat)
6637
7440
  });
6638
- } catch (err5) {
6639
- const message = err5 instanceof Error ? err5.message : String(err5);
7441
+ } catch (err6) {
7442
+ const message = err6 instanceof Error ? err6.message : String(err6);
6640
7443
  return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
6641
7444
  }
6642
7445
  }
@@ -6678,7 +7481,30 @@ async function handleProviderKeys(res, id, cfg, deps) {
6678
7481
  const row = cfg.providers.find((p) => p.id === id);
6679
7482
  if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
6680
7483
  const cooldown = await deps.apiKeyPool.getKeyHealth(id);
6681
- return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
7484
+ const views = toPoolKeyView(row, cooldown, deps);
7485
+ if (deps.providerKeyQuota) {
7486
+ const quotas = await Promise.allSettled(
7487
+ views.map((view) => deps.providerKeyQuota.quotaFor(row, view.id))
7488
+ );
7489
+ views.forEach((view, index) => {
7490
+ const settled = quotas[index];
7491
+ if (settled.status === "fulfilled" && settled.value) view.quota = settled.value;
7492
+ });
7493
+ }
7494
+ return writeJson4(res, 200, { keys: views });
7495
+ }
7496
+ async function handleProviderKeyQuotaRefresh(res, id, keyId, cfg, deps) {
7497
+ if (!deps.providerKeyQuota) return writeJsonError(res, 501, "provider key quota is not available");
7498
+ if (!id || !keyId) return writeJsonError(res, 400, "provider id and key id required in path");
7499
+ const row = cfg.providers.find((p) => p.id === id);
7500
+ if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
7501
+ try {
7502
+ const quota = await deps.providerKeyQuota.quotaFor(row, keyId, { force: true });
7503
+ if (!quota) return writeJsonError(res, 404, `no quota endpoint for key '${keyId}'`);
7504
+ return writeJson4(res, 200, { quota });
7505
+ } catch {
7506
+ return writeJsonError(res, 502, "quota refresh failed");
7507
+ }
6682
7508
  }
6683
7509
  function parsePoolKeyInput(body, existing) {
6684
7510
  const out = {};
@@ -7423,12 +8249,12 @@ async function handleAccounts(req, res, method, rest, deps) {
7423
8249
  }
7424
8250
  return writeJson4(res, 200, { ok: true, affected: result.affected });
7425
8251
  }
7426
- if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
7427
- const result = handleCodexOAuthStatus(rest[2], deps);
8252
+ if (method === "GET" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[3] === "status") {
8253
+ const result = rest[0] === "codex" ? handleCodexOAuthStatus(rest[2], deps) : handleKimiOAuthStatus(rest[2], deps);
7428
8254
  return writeJson4(res, result.status, result.body);
7429
8255
  }
7430
- if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
7431
- const result = handleCodexOAuthCancel(rest[2], deps);
8256
+ if (method === "DELETE" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[2]) {
8257
+ const result = rest[0] === "codex" ? handleCodexOAuthCancel(rest[2], deps) : handleKimiOAuthCancel(rest[2], deps);
7432
8258
  return writeJson4(res, result.status, result.body);
7433
8259
  }
7434
8260
  if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
@@ -7481,7 +8307,15 @@ async function handleAccounts(req, res, method, rest, deps) {
7481
8307
  return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
7482
8308
  }
7483
8309
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
7484
- const result = providerId === "codex" ? handleCodexOAuthStart(deps) : handleOAuthStart(providerId, deps);
8310
+ if (providerId === "codex") {
8311
+ const result2 = handleCodexOAuthStart(deps);
8312
+ return writeJson4(res, result2.status, result2.body);
8313
+ }
8314
+ if (providerId === "kimi") {
8315
+ const result2 = await handleKimiOAuthStart(deps);
8316
+ return writeJson4(res, result2.status, result2.body);
8317
+ }
8318
+ const result = handleOAuthStart(providerId, deps);
7485
8319
  return writeJson4(res, result.status, result.body);
7486
8320
  }
7487
8321
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
@@ -7975,12 +8809,12 @@ async function handlePlayground(req, res, method, deps) {
7975
8809
  const payload = body["body"];
7976
8810
  const status = deps.outboundApiServer.getStatus();
7977
8811
  if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
7978
- const path2 = resolvePlaygroundPath(endpoint, isRecord3(payload) ? payload : {});
8812
+ const path2 = resolvePlaygroundPath(endpoint, isRecord4(payload) ? payload : {});
7979
8813
  if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
7980
8814
  const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
7981
8815
  await proxyToOutbound(res, status.port, path2, key, upstreamBody);
7982
8816
  }
7983
- function isRecord3(v) {
8817
+ function isRecord4(v) {
7984
8818
  return !!v && typeof v === "object" && !Array.isArray(v);
7985
8819
  }
7986
8820
  function proxyToOutbound(res, outboundPort, path2, key, body) {
@@ -8009,8 +8843,8 @@ function proxyToOutbound(res, outboundPort, path2, key, body) {
8009
8843
  });
8010
8844
  }
8011
8845
  );
8012
- upstream.on("error", (err5) => {
8013
- if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err5.message}`);
8846
+ upstream.on("error", (err6) => {
8847
+ if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err6.message}`);
8014
8848
  else res.end();
8015
8849
  resolve11();
8016
8850
  });
@@ -8116,7 +8950,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
8116
8950
  }
8117
8951
 
8118
8952
  // src/admin/version.ts
8119
- var DAEMON_VERSION = true ? "0.3.0" : "0.0.0-dev";
8953
+ var DAEMON_VERSION = true ? "0.3.1" : "0.0.0-dev";
8120
8954
 
8121
8955
  // src/admin/AdminServer.ts
8122
8956
  var LOOPBACK_ADDR = "127.0.0.1";
@@ -8159,13 +8993,13 @@ var AdminServer = class {
8159
8993
  const server = import_node_http2.default.createServer((req, res) => {
8160
8994
  this.onRequest(req, res);
8161
8995
  });
8162
- const onError = (err5) => {
8163
- if (err5.code === "EADDRINUSE" && port !== 0) {
8996
+ const onError = (err6) => {
8997
+ if (err6.code === "EADDRINUSE" && port !== 0) {
8164
8998
  server.removeListener("error", onError);
8165
8999
  this.listen(bindAddr, 0).then(resolve11, reject);
8166
9000
  return;
8167
9001
  }
8168
- reject(err5);
9002
+ reject(err6);
8169
9003
  };
8170
9004
  server.on("error", onError);
8171
9005
  server.listen(port, bindAddr, () => {
@@ -8183,8 +9017,8 @@ var AdminServer = class {
8183
9017
  }
8184
9018
  /** Per-request handler: auth gate (when a token is set) → routing. */
8185
9019
  onRequest(req, res) {
8186
- void this.dispatch(req, res).catch((err5) => {
8187
- const message = err5 instanceof Error ? err5.message : String(err5);
9020
+ void this.dispatch(req, res).catch((err6) => {
9021
+ const message = err6 instanceof Error ? err6.message : String(err6);
8188
9022
  this.deps.logger.error("[AdminServer] unhandled error:", message);
8189
9023
  if (!res.headersSent) {
8190
9024
  res.writeHead(500, { "Content-Type": "application/json" });
@@ -8448,18 +9282,18 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
8448
9282
  return;
8449
9283
  }
8450
9284
  signal?.addEventListener("abort", abort, { once: true });
8451
- server.on("error", (err5) => {
9285
+ server.on("error", (err6) => {
8452
9286
  if (settled) return;
8453
9287
  settled = true;
8454
9288
  clearTimeout(timer);
8455
- if (err5.code === "EADDRINUSE") {
9289
+ if (err6.code === "EADDRINUSE") {
8456
9290
  reject(
8457
9291
  new Error(
8458
9292
  `login: cannot bind ${LOOPBACK_HOST}:${LOOPBACK_PORT} (address in use) \u2014 another codex login or process is holding the port`
8459
9293
  )
8460
9294
  );
8461
9295
  } else {
8462
- reject(err5);
9296
+ reject(err6);
8463
9297
  }
8464
9298
  });
8465
9299
  const timer = setTimeout(() => {
@@ -8534,10 +9368,415 @@ function createPoolKeysLoader(getProviderRow, autoDisabled) {
8534
9368
  };
8535
9369
  }
8536
9370
 
9371
+ // src/allowance/ProviderKeyQuotaService.ts
9372
+ var import_upstreamFetch8 = require("@omnicross/core/pipeline/upstreamFetch");
9373
+
9374
+ // src/allowance/ProviderKeyQuota.ts
9375
+ var MINUTE_MS2 = 6e4;
9376
+ var HOUR_MS2 = 60 * MINUTE_MS2;
9377
+ var DAY_MS2 = 24 * HOUR_MS2;
9378
+ var WEEK_MS = 7 * DAY_MS2;
9379
+ var MONTH_MS = 30 * DAY_MS2;
9380
+ function finiteNumber3(value) {
9381
+ if (value === null || value === void 0 || value === "") return void 0;
9382
+ const parsed = typeof value === "number" ? value : Number(value);
9383
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
9384
+ }
9385
+ function finitePercent4(value) {
9386
+ const parsed = finiteNumber3(value);
9387
+ return parsed !== void 0 && parsed <= 100 ? parsed : null;
9388
+ }
9389
+ function isoInstant3(value) {
9390
+ if (typeof value === "string" && value.trim()) {
9391
+ const time = Date.parse(value);
9392
+ if (Number.isFinite(time)) return new Date(time).toISOString();
9393
+ }
9394
+ const numeric = finiteNumber3(value);
9395
+ if (numeric !== void 0 && numeric > 1e9) {
9396
+ const ms = numeric > 1e12 ? numeric : numeric * 1e3;
9397
+ return new Date(ms).toISOString();
9398
+ }
9399
+ return void 0;
9400
+ }
9401
+ function secondsUntil5(instant, now) {
9402
+ if (!instant) return void 0;
9403
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
9404
+ }
9405
+ function isRecord5(value) {
9406
+ return !!value && typeof value === "object" && !Array.isArray(value);
9407
+ }
9408
+ function detectProviderKeyQuotaAdapter(baseUrl) {
9409
+ if (!baseUrl) return null;
9410
+ let url;
9411
+ try {
9412
+ url = new URL(baseUrl);
9413
+ } catch {
9414
+ return null;
9415
+ }
9416
+ const host = url.hostname.toLowerCase();
9417
+ const path2 = url.pathname.toLowerCase();
9418
+ if ((host === "api.z.ai" || host === "open.bigmodel.cn") && path2.includes("/coding")) {
9419
+ return "zai";
9420
+ }
9421
+ if ((host === "api.minimax.io" || host === "api.minimaxi.com") && // Token Plan rides the plain openai `/v1` (chat completions) surface; the
9422
+ // anthropic `/anthropic` rows are excluded (their usage impl is unverified).
9423
+ (path2 === "/v1" || path2 === "/v1/" || path2 === "" || path2 === "/")) {
9424
+ return "minimax-token-plan";
9425
+ }
9426
+ if (host === "api.code.umans.ai") return "umans";
9427
+ if (host === "api.synthetic.new") return "synthetic";
9428
+ return null;
9429
+ }
9430
+ function providerKeyQuotaUrl(adapter, baseUrl) {
9431
+ const origin = new URL(baseUrl).origin;
9432
+ if (adapter === "zai") return `${origin}/api/monitor/usage/quota/limit`;
9433
+ if (adapter === "minimax-token-plan") return `${origin}/v1/token_plan/remains`;
9434
+ if (adapter === "umans") return `${origin}/v1/usage`;
9435
+ return `${origin}/v2/quotas`;
9436
+ }
9437
+ function providerKeyQuotaAuthHeader(adapter, key) {
9438
+ return adapter === "zai" ? key : `Bearer ${key}`;
9439
+ }
9440
+ function zaiWindowDurationMs(item) {
9441
+ const count = item.number !== void 0 && item.number > 0 ? item.number : 1;
9442
+ switch (item.unit) {
9443
+ case 3:
9444
+ return count * HOUR_MS2;
9445
+ case 4:
9446
+ return count * DAY_MS2;
9447
+ case 5:
9448
+ return count * MONTH_MS;
9449
+ case 6:
9450
+ return WEEK_MS;
9451
+ default:
9452
+ return void 0;
9453
+ }
9454
+ }
9455
+ function zaiWindowIdLabel(durationMs) {
9456
+ if (durationMs === WEEK_MS) return { id: "seven-day", label: "7 days" };
9457
+ if (durationMs === 5 * HOUR_MS2) return { id: "five-hour", label: "5 hours" };
9458
+ if (durationMs === MONTH_MS) return { id: "thirty-day", label: "30 days" };
9459
+ if (durationMs !== void 0 && durationMs % DAY_MS2 === 0) {
9460
+ const days = durationMs / DAY_MS2;
9461
+ return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}` };
9462
+ }
9463
+ if (durationMs !== void 0 && durationMs % HOUR_MS2 === 0) {
9464
+ const hours = durationMs / HOUR_MS2;
9465
+ return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}` };
9466
+ }
9467
+ return { id: "quota", label: "Quota" };
9468
+ }
9469
+ function parseZaiQuotaPayload(payload, now) {
9470
+ if (!isRecord5(payload)) return null;
9471
+ const data = isRecord5(payload["data"]) ? payload["data"] : payload;
9472
+ if (payload["success"] === false) return null;
9473
+ const limits = Array.isArray(data["limits"]) ? data["limits"] : [];
9474
+ const byWindow = /* @__PURE__ */ new Map();
9475
+ for (const raw of limits) {
9476
+ if (!isRecord5(raw)) continue;
9477
+ const item = raw;
9478
+ if (item.type === void 0) continue;
9479
+ const details = raw["usageDetails"];
9480
+ if (Array.isArray(details) && details.some((d) => isRecord5(d) && d["modelCode"] === "zread")) {
9481
+ continue;
9482
+ }
9483
+ const durationMs = zaiWindowDurationMs(item);
9484
+ const { id, label } = zaiWindowIdLabel(durationMs);
9485
+ const limit = finiteNumber3(item.usage);
9486
+ const used = finiteNumber3(item.currentValue);
9487
+ const fromAbsolute = limit !== void 0 && used !== void 0 && limit > 0 ? Math.min(100, used / limit * 100) : void 0;
9488
+ const fromPercentage = finitePercent4(item.percentage) ?? void 0;
9489
+ const usedPercent = fromAbsolute !== void 0 ? Math.round(fromAbsolute * 10) / 10 : fromPercentage;
9490
+ if (usedPercent === void 0) continue;
9491
+ const resetsAt = isoInstant3(item.nextResetTime);
9492
+ const candidate = {
9493
+ id,
9494
+ label,
9495
+ scope: "all",
9496
+ usedPercent,
9497
+ ...durationMs !== void 0 ? { windowMinutes: Math.round(durationMs / MINUTE_MS2) } : {},
9498
+ ...resetsAt !== void 0 ? { resetsAt } : {},
9499
+ remainingSeconds: secondsUntil5(resetsAt, now),
9500
+ state: "fresh"
9501
+ };
9502
+ const existing = byWindow.get(id);
9503
+ if (!existing || (candidate.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
9504
+ byWindow.set(id, candidate);
9505
+ }
9506
+ }
9507
+ const windows = [...byWindow.values()].sort((a, b) => (a.windowMinutes ?? Number.POSITIVE_INFINITY) - (b.windowMinutes ?? Number.POSITIVE_INFINITY));
9508
+ return windows.length > 0 ? windows.slice(0, 4) : null;
9509
+ }
9510
+ var MINIMAX_STATUS_EXHAUSTED = 2;
9511
+ var MINIMAX_SHARED_BUCKET = "general";
9512
+ function parseMiniMaxBucket(value) {
9513
+ if (!isRecord5(value)) return null;
9514
+ const modelName = typeof value["model_name"] === "string" ? value["model_name"].trim() : "";
9515
+ if (!modelName) return null;
9516
+ const instant = (v) => {
9517
+ const n = finiteNumber3(v);
9518
+ return n !== void 0 && n > 1e9 ? n > 1e12 ? n : n * 1e3 : void 0;
9519
+ };
9520
+ return {
9521
+ modelName,
9522
+ intervalEnd: instant(value["end_time"]),
9523
+ intervalRemainingPercent: finiteNumber3(value["current_interval_remaining_percent"]),
9524
+ intervalStatus: finiteNumber3(value["current_interval_status"]),
9525
+ weeklyEnd: instant(value["weekly_end_time"]),
9526
+ weeklyRemainingPercent: finiteNumber3(value["current_weekly_remaining_percent"]),
9527
+ weeklyStatus: finiteNumber3(value["current_weekly_status"])
9528
+ };
9529
+ }
9530
+ function minimaxWindow(id, label, windowMinutes, resetsAtMs, remainingPercent, status, now) {
9531
+ const usedPercent = status === MINIMAX_STATUS_EXHAUSTED ? 100 : remainingPercent !== void 0 ? Math.round((100 - remainingPercent) * 10) / 10 : null;
9532
+ const resetsAt = resetsAtMs !== void 0 ? new Date(resetsAtMs).toISOString() : void 0;
9533
+ return {
9534
+ id,
9535
+ label,
9536
+ scope: "all",
9537
+ usedPercent,
9538
+ ...windowMinutes !== void 0 ? { windowMinutes } : {},
9539
+ ...resetsAt !== void 0 ? { resetsAt } : {},
9540
+ remainingSeconds: secondsUntil5(resetsAt, now),
9541
+ state: usedPercent !== null ? "fresh" : "unavailable"
9542
+ };
9543
+ }
9544
+ function parseMiniMaxTokenPlanPayload(payload, now) {
9545
+ if (!isRecord5(payload)) return null;
9546
+ const baseResp = payload["base_resp"];
9547
+ if (!isRecord5(baseResp) || baseResp["status_code"] !== 0) return null;
9548
+ const buckets = Array.isArray(payload["model_remains"]) ? payload["model_remains"] : [];
9549
+ let general = null;
9550
+ for (const raw of buckets) {
9551
+ const bucket = parseMiniMaxBucket(raw);
9552
+ if (bucket?.modelName === MINIMAX_SHARED_BUCKET) {
9553
+ general = bucket;
9554
+ break;
9555
+ }
9556
+ }
9557
+ if (!general) return null;
9558
+ return [
9559
+ minimaxWindow(
9560
+ "five-hour",
9561
+ "5 hours",
9562
+ 5 * 60,
9563
+ general.intervalEnd,
9564
+ general.intervalRemainingPercent,
9565
+ general.intervalStatus,
9566
+ now
9567
+ ),
9568
+ minimaxWindow(
9569
+ "seven-day",
9570
+ "7 days",
9571
+ Math.round(WEEK_MS / MINUTE_MS2),
9572
+ general.weeklyEnd,
9573
+ general.weeklyRemainingPercent,
9574
+ general.weeklyStatus,
9575
+ now
9576
+ )
9577
+ ];
9578
+ }
9579
+ function parseUmansUsagePayload(payload, now) {
9580
+ if (!isRecord5(payload)) return null;
9581
+ const limits = isRecord5(payload["limits"]) ? payload["limits"] : void 0;
9582
+ const requests = limits && isRecord5(limits["requests"]) ? limits["requests"] : void 0;
9583
+ const usage = isRecord5(payload["usage"]) ? payload["usage"] : void 0;
9584
+ const window = isRecord5(payload["window"]) ? payload["window"] : void 0;
9585
+ const hardCap = finiteNumber3(requests?.["hard_cap"]);
9586
+ const softLimit = finiteNumber3(requests?.["limit"]);
9587
+ const requestsInWindow = finiteNumber3(usage?.["requests_in_window"]);
9588
+ const weightedInWindow = finiteNumber3(usage?.["weighted_in_window"]);
9589
+ const resetsAt = isoInstant3(window?.["resets_at"]);
9590
+ let usedPercent = null;
9591
+ if (hardCap !== void 0 && hardCap > 0 && requestsInWindow !== void 0) {
9592
+ usedPercent = Math.round(Math.min(100, requestsInWindow / hardCap * 100) * 10) / 10;
9593
+ } else if (softLimit !== void 0 && softLimit > 0 && weightedInWindow !== void 0) {
9594
+ usedPercent = Math.round(Math.min(100, weightedInWindow / softLimit * 100) * 10) / 10;
9595
+ }
9596
+ if (usedPercent === null && resetsAt === void 0) return null;
9597
+ return [
9598
+ {
9599
+ id: "five-hour",
9600
+ label: "5 hours",
9601
+ scope: "all",
9602
+ usedPercent,
9603
+ windowMinutes: 5 * 60,
9604
+ ...resetsAt !== void 0 ? { resetsAt } : {},
9605
+ remainingSeconds: secondsUntil5(resetsAt, now),
9606
+ state: "fresh"
9607
+ }
9608
+ ];
9609
+ }
9610
+ function parseSyntheticQuotasPayload(payload, now) {
9611
+ if (!isRecord5(payload)) return null;
9612
+ const fiveHour = isRecord5(payload["rollingFiveHourLimit"]) ? payload["rollingFiveHourLimit"] : void 0;
9613
+ const weekly = isRecord5(payload["weeklyTokenLimit"]) ? payload["weeklyTokenLimit"] : void 0;
9614
+ const windows = [];
9615
+ if (fiveHour) {
9616
+ const max = finiteNumber3(fiveHour["max"]);
9617
+ const remaining = finiteNumber3(fiveHour["remaining"]);
9618
+ const usedPercent = max !== void 0 && max > 0 && remaining !== void 0 ? Math.round(Math.min(100, (max - remaining) / max * 100) * 10) / 10 : null;
9619
+ const resetsAt = isoInstant3(fiveHour["nextTickAt"]);
9620
+ windows.push({
9621
+ id: "five-hour",
9622
+ label: "5 hours",
9623
+ scope: "all",
9624
+ usedPercent,
9625
+ windowMinutes: 5 * 60,
9626
+ ...resetsAt !== void 0 ? { resetsAt } : {},
9627
+ remainingSeconds: secondsUntil5(resetsAt, now),
9628
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
9629
+ });
9630
+ }
9631
+ if (weekly) {
9632
+ const percentRemaining = finiteNumber3(weekly["percentRemaining"]);
9633
+ const usedPercent = percentRemaining !== void 0 ? Math.round(Math.min(100, Math.max(0, 100 - percentRemaining)) * 10) / 10 : null;
9634
+ const resetsAt = isoInstant3(weekly["nextRegenAt"]);
9635
+ windows.push({
9636
+ id: "seven-day",
9637
+ label: "7 days",
9638
+ scope: "all",
9639
+ usedPercent,
9640
+ windowMinutes: 7 * 24 * 60,
9641
+ ...resetsAt !== void 0 ? { resetsAt } : {},
9642
+ remainingSeconds: secondsUntil5(resetsAt, now),
9643
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
9644
+ });
9645
+ }
9646
+ return windows.length > 0 ? windows : null;
9647
+ }
9648
+
9649
+ // src/allowance/ProviderKeyQuotaService.ts
9650
+ function parseQuotaPayload(adapter, payload, now) {
9651
+ switch (adapter) {
9652
+ case "zai":
9653
+ return parseZaiQuotaPayload(payload, now);
9654
+ case "minimax-token-plan":
9655
+ return parseMiniMaxTokenPlanPayload(payload, now);
9656
+ case "umans":
9657
+ return parseUmansUsagePayload(payload, now);
9658
+ case "synthetic":
9659
+ return parseSyntheticQuotasPayload(payload, now);
9660
+ }
9661
+ }
9662
+ var PROVIDER_KEY_QUOTA_CACHE_MS = 5 * 6e4;
9663
+ function resolvedBaseUrl(row) {
9664
+ const modes = row.apiModes ?? [];
9665
+ const selected = row.selectedApiModeId ? modes.find((mode) => mode.id === row.selectedApiModeId) : void 0;
9666
+ const fallback = modes[0];
9667
+ const modeBase = selected?.baseUrl ?? fallback?.baseUrl;
9668
+ return modeBase ?? row.codingPlan?.baseUrl ?? row.baseUrl;
9669
+ }
9670
+ function rowKeyEntries(row) {
9671
+ const pool = (row.apiKeys ?? []).filter((entry) => entry.apiKey.length > 0);
9672
+ if (pool.length > 0) return pool.map((entry) => ({ id: entry.id, apiKey: entry.apiKey }));
9673
+ if (row.apiKey.length > 0) {
9674
+ return [{ id: `${row.id}:default`, apiKey: row.apiKey }];
9675
+ }
9676
+ return [];
9677
+ }
9678
+ var ProviderKeyQuotaService = class {
9679
+ constructor(box, fetchImpl = (url, init) => (0, import_upstreamFetch8.fetchUpstream)(url, init, { redactBodies: true }), now = Date.now) {
9680
+ this.box = box;
9681
+ this.fetchImpl = fetchImpl;
9682
+ this.now = now;
9683
+ }
9684
+ box;
9685
+ fetchImpl;
9686
+ now;
9687
+ cache = /* @__PURE__ */ new Map();
9688
+ inFlight = /* @__PURE__ */ new Map();
9689
+ /**
9690
+ * Quota for one key of a provider row, or `null` when the row has no quota
9691
+ * adapter / no such key. Cache-first; concurrent reads share one flight.
9692
+ */
9693
+ async quotaFor(row, keyId, options = {}) {
9694
+ const adapter = detectProviderKeyQuotaAdapter(resolvedBaseUrl(row));
9695
+ if (!adapter) return null;
9696
+ const entry = rowKeyEntries(row).find((candidate) => candidate.id === keyId);
9697
+ if (!entry) return null;
9698
+ const cacheKey = `${row.id}\0${keyId}`;
9699
+ const now = this.now();
9700
+ const cached = this.cache.get(cacheKey);
9701
+ if (!options.force && cached && Date.parse(cached.expiresAt) > now) return cached;
9702
+ const running = this.inFlight.get(cacheKey);
9703
+ if (running) return running;
9704
+ const promise = this.fetchQuota(adapter, row, entry.apiKey, cacheKey).catch((error) => {
9705
+ void error;
9706
+ const previous = this.cache.get(cacheKey);
9707
+ if (previous) {
9708
+ const degraded = {
9709
+ ...previous,
9710
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
9711
+ windows: previous.windows.map((window) => ({
9712
+ ...window,
9713
+ state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
9714
+ })),
9715
+ errorCode: "quota_request_failed"
9716
+ };
9717
+ this.cache.set(cacheKey, degraded);
9718
+ return degraded;
9719
+ }
9720
+ return null;
9721
+ }).finally(() => this.inFlight.delete(cacheKey));
9722
+ this.inFlight.set(cacheKey, promise);
9723
+ return promise;
9724
+ }
9725
+ /** Drop cached rows for a provider (key added/removed/rotated). */
9726
+ invalidateProvider(providerRowId) {
9727
+ for (const key of this.cache.keys()) {
9728
+ if (key.startsWith(`${providerRowId}\0`)) this.cache.delete(key);
9729
+ }
9730
+ }
9731
+ async fetchQuota(adapter, row, rawKey, cacheKey) {
9732
+ const baseUrl = resolvedBaseUrl(row);
9733
+ const url = providerKeyQuotaUrl(adapter, baseUrl);
9734
+ const key = this.box.decryptMaybe(rawKey);
9735
+ const now = this.now();
9736
+ const response = await this.fetchImpl(url, {
9737
+ method: "GET",
9738
+ headers: {
9739
+ Authorization: providerKeyQuotaAuthHeader(adapter, key),
9740
+ Accept: "application/json",
9741
+ "Content-Type": "application/json"
9742
+ },
9743
+ signal: AbortSignal.timeout(15e3)
9744
+ });
9745
+ if (response.status === 401 || response.status === 403) {
9746
+ const snapshot2 = {
9747
+ adapter,
9748
+ observedAt: new Date(now).toISOString(),
9749
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
9750
+ windows: [],
9751
+ errorCode: "quota_unauthorized"
9752
+ };
9753
+ this.cache.set(cacheKey, snapshot2);
9754
+ return snapshot2;
9755
+ }
9756
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
9757
+ let payload;
9758
+ try {
9759
+ payload = await response.json();
9760
+ } catch {
9761
+ throw new Error("invalid JSON");
9762
+ }
9763
+ const windows = parseQuotaPayload(adapter, payload, now);
9764
+ const snapshot = {
9765
+ adapter,
9766
+ observedAt: new Date(now).toISOString(),
9767
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
9768
+ windows: windows ?? [],
9769
+ ...windows ? {} : { errorCode: "quota_unavailable" }
9770
+ };
9771
+ this.cache.set(cacheKey, snapshot);
9772
+ return snapshot;
9773
+ }
9774
+ };
9775
+
8537
9776
  // src/image-generation/ImageDoctorService.ts
8538
9777
  var import_image_generation = require("@omnicross/core/image-generation");
8539
9778
  var import_outbound_api7 = require("@omnicross/core/outbound-api");
8540
- var import_subscriptions3 = require("@omnicross/subscriptions");
9779
+ var import_subscriptions6 = require("@omnicross/subscriptions");
8541
9780
 
8542
9781
  // src/image-generation/FileCodexImageCapabilityEvidenceSource.ts
8543
9782
  var import_node_crypto13 = require("crypto");
@@ -8975,7 +10214,7 @@ function createImageDoctorService(options) {
8975
10214
  paths,
8976
10215
  ttlMs: config.evidenceTtlMs
8977
10216
  }));
8978
- const createLiveVerifier = options.createLiveVerifier ?? ((strategy, config) => (0, import_subscriptions3.createCodexImageLiveVerifier)({
10217
+ const createLiveVerifier = options.createLiveVerifier ?? ((strategy, config) => (0, import_subscriptions6.createCodexImageLiveVerifier)({
8979
10218
  authStrategy: strategy,
8980
10219
  generationTimeoutMs: config.queue.generationTimeoutMs
8981
10220
  }));
@@ -9337,7 +10576,7 @@ var ImageCleanupService = class {
9337
10576
  var import_node_crypto16 = require("crypto");
9338
10577
  var import_image_generation5 = require("@omnicross/core/image-generation");
9339
10578
  var import_outbound_api8 = require("@omnicross/core/outbound-api");
9340
- var import_subscriptions4 = require("@omnicross/subscriptions");
10579
+ var import_subscriptions7 = require("@omnicross/subscriptions");
9341
10580
 
9342
10581
  // src/image-generation/ImageApiRuntimeResolver.ts
9343
10582
  var import_node_crypto14 = require("crypto");
@@ -9868,7 +11107,7 @@ function createImageRuntimeGeneration(options) {
9868
11107
  now: options.now ?? Date.now,
9869
11108
  referenceStore: options.storage.referenceStore,
9870
11109
  stateStore: options.storage.stateStore
9871
- }) : (0, import_subscriptions4.createCodexSubscriptionImageProvider)({
11110
+ }) : (0, import_subscriptions7.createCodexSubscriptionImageProvider)({
9872
11111
  authStrategy,
9873
11112
  evidenceSource: generationEvidenceSource,
9874
11113
  executionScheduler: scheduler,
@@ -14534,10 +15773,13 @@ function bucketLabel(bucketStartTs, bucket) {
14534
15773
  }
14535
15774
 
14536
15775
  // src/ports/JsonOutboundKeyDb.ts
15776
+ var import_node_fs23 = require("fs");
15777
+ var import_core3 = require("@omnicross/core");
15778
+
15779
+ // src/ports/atomicFile.ts
14537
15780
  var import_node_crypto22 = require("crypto");
14538
15781
  var import_node_fs22 = require("fs");
14539
15782
  var import_node_path25 = require("path");
14540
- var import_core3 = require("@omnicross/core");
14541
15783
  function atomicReplaceUtf8(targetPath, contents) {
14542
15784
  const tempPath = (0, import_node_path25.join)(
14543
15785
  (0, import_node_path25.dirname)(targetPath),
@@ -14567,6 +15809,8 @@ function atomicReplaceUtf8(targetPath, contents) {
14567
15809
  throw error;
14568
15810
  }
14569
15811
  }
15812
+
15813
+ // src/ports/JsonOutboundKeyDb.ts
14570
15814
  var JsonOutboundKeyDb = class {
14571
15815
  /**
14572
15816
  * @param secretBox OPTIONAL reversible-secret codec. When present, a created
@@ -14709,9 +15953,9 @@ var JsonOutboundKeyDb = class {
14709
15953
  }
14710
15954
  /** Read the key rows, tolerating a missing/corrupt file (→ empty list). */
14711
15955
  readRows() {
14712
- if (!(0, import_node_fs22.existsSync)(this.keysPath)) return [];
15956
+ if (!(0, import_node_fs23.existsSync)(this.keysPath)) return [];
14713
15957
  try {
14714
- const parsed = JSON.parse((0, import_node_fs22.readFileSync)(this.keysPath, "utf8"));
15958
+ const parsed = JSON.parse((0, import_node_fs23.readFileSync)(this.keysPath, "utf8"));
14715
15959
  return Array.isArray(parsed) ? parsed : [];
14716
15960
  } catch {
14717
15961
  return [];
@@ -14728,7 +15972,7 @@ function applyPolicyField(row, field, value) {
14728
15972
  }
14729
15973
 
14730
15974
  // src/ports/JsonPricingStore.ts
14731
- var import_node_fs23 = require("fs");
15975
+ var import_node_fs24 = require("fs");
14732
15976
  var import_node_crypto23 = require("crypto");
14733
15977
  var JsonPricingStore = class {
14734
15978
  constructor(pricingPath) {
@@ -14743,9 +15987,9 @@ var JsonPricingStore = class {
14743
15987
  * otherwise unusable pricing table after a crash or manual file edit.
14744
15988
  */
14745
15989
  hasUsableSnapshot() {
14746
- if (!(0, import_node_fs23.existsSync)(this.pricingPath)) return false;
15990
+ if (!(0, import_node_fs24.existsSync)(this.pricingPath)) return false;
14747
15991
  try {
14748
- const parsed = JSON.parse((0, import_node_fs23.readFileSync)(this.pricingPath, "utf8"));
15992
+ const parsed = JSON.parse((0, import_node_fs24.readFileSync)(this.pricingPath, "utf8"));
14749
15993
  return Array.isArray(parsed) && parsed.some(isUsablePricingRow);
14750
15994
  } catch {
14751
15995
  return false;
@@ -14858,9 +16102,9 @@ var JsonPricingStore = class {
14858
16102
  }
14859
16103
  /** Read the pricing rows, tolerating a missing/corrupt file (→ empty list). */
14860
16104
  readRows() {
14861
- if (!(0, import_node_fs23.existsSync)(this.pricingPath)) return [];
16105
+ if (!(0, import_node_fs24.existsSync)(this.pricingPath)) return [];
14862
16106
  try {
14863
- const parsed = JSON.parse((0, import_node_fs23.readFileSync)(this.pricingPath, "utf8"));
16107
+ const parsed = JSON.parse((0, import_node_fs24.readFileSync)(this.pricingPath, "utf8"));
14864
16108
  return Array.isArray(parsed) ? parsed : [];
14865
16109
  } catch {
14866
16110
  return [];
@@ -14869,18 +16113,18 @@ var JsonPricingStore = class {
14869
16113
  writeRows(rows) {
14870
16114
  const temporaryPath = `${this.pricingPath}.${process.pid}.${(0, import_node_crypto23.randomUUID)()}.tmp`;
14871
16115
  try {
14872
- (0, import_node_fs23.writeFileSync)(temporaryPath, JSON.stringify(rows, null, 2) + "\n", {
16116
+ (0, import_node_fs24.writeFileSync)(temporaryPath, JSON.stringify(rows, null, 2) + "\n", {
14873
16117
  encoding: "utf8",
14874
16118
  flag: "wx"
14875
16119
  });
14876
16120
  this.replaceFile(temporaryPath);
14877
16121
  } finally {
14878
- (0, import_node_fs23.rmSync)(temporaryPath, { force: true });
16122
+ (0, import_node_fs24.rmSync)(temporaryPath, { force: true });
14879
16123
  }
14880
16124
  }
14881
16125
  /** Isolated for deterministic failure testing; never removes the target. */
14882
16126
  replaceFile(temporaryPath) {
14883
- (0, import_node_fs23.renameSync)(temporaryPath, this.pricingPath);
16127
+ (0, import_node_fs24.renameSync)(temporaryPath, this.pricingPath);
14884
16128
  }
14885
16129
  };
14886
16130
  function isUsablePricingRow(value) {
@@ -14890,7 +16134,7 @@ function isUsablePricingRow(value) {
14890
16134
  }
14891
16135
 
14892
16136
  // src/pricing/PricingRefreshScheduler.ts
14893
- var import_node_fs24 = require("fs");
16137
+ var import_node_fs25 = require("fs");
14894
16138
  var EMPTY_STATE2 = {
14895
16139
  lastAttemptAt: null,
14896
16140
  lastSuccessAt: null,
@@ -14928,9 +16172,9 @@ var PricingRefreshScheduler = class {
14928
16172
  this.timer = null;
14929
16173
  }
14930
16174
  getState() {
14931
- if (!(0, import_node_fs24.existsSync)(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
16175
+ if (!(0, import_node_fs25.existsSync)(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
14932
16176
  try {
14933
- const value = JSON.parse((0, import_node_fs24.readFileSync)(this.statePath, "utf8"));
16177
+ const value = JSON.parse((0, import_node_fs25.readFileSync)(this.statePath, "utf8"));
14934
16178
  return {
14935
16179
  lastAttemptAt: finiteOrNull(value.lastAttemptAt),
14936
16180
  lastSuccessAt: finiteOrNull(value.lastSuccessAt),
@@ -14983,9 +16227,9 @@ var PricingRefreshScheduler = class {
14983
16227
  }
14984
16228
  writeState(state) {
14985
16229
  const temporaryPath = `${this.statePath}.tmp`;
14986
- (0, import_node_fs24.writeFileSync)(temporaryPath, `${JSON.stringify(state, null, 2)}
16230
+ (0, import_node_fs25.writeFileSync)(temporaryPath, `${JSON.stringify(state, null, 2)}
14987
16231
  `, "utf8");
14988
- (0, import_node_fs24.renameSync)(temporaryPath, this.statePath);
16232
+ (0, import_node_fs25.renameSync)(temporaryPath, this.statePath);
14989
16233
  }
14990
16234
  };
14991
16235
  function finiteOrNull(value) {
@@ -14993,7 +16237,7 @@ function finiteOrNull(value) {
14993
16237
  }
14994
16238
 
14995
16239
  // src/ports/JsonVoucherDb.ts
14996
- var import_node_fs25 = require("fs");
16240
+ var import_node_fs26 = require("fs");
14997
16241
  var JsonVoucherDb = class {
14998
16242
  constructor(vouchersPath) {
14999
16243
  this.vouchersPath = vouchersPath;
@@ -15071,27 +16315,27 @@ var JsonVoucherDb = class {
15071
16315
  }
15072
16316
  /** Read the voucher rows, tolerating a missing/corrupt file (→ empty list). */
15073
16317
  readRows() {
15074
- if (!(0, import_node_fs25.existsSync)(this.vouchersPath)) return [];
16318
+ if (!(0, import_node_fs26.existsSync)(this.vouchersPath)) return [];
15075
16319
  try {
15076
- const parsed = JSON.parse((0, import_node_fs25.readFileSync)(this.vouchersPath, "utf8"));
16320
+ const parsed = JSON.parse((0, import_node_fs26.readFileSync)(this.vouchersPath, "utf8"));
15077
16321
  return Array.isArray(parsed) ? parsed : [];
15078
16322
  } catch {
15079
16323
  return [];
15080
16324
  }
15081
16325
  }
15082
16326
  writeRows(rows) {
15083
- (0, import_node_fs25.writeFileSync)(this.vouchersPath, JSON.stringify(rows, null, 2) + "\n", "utf8");
16327
+ (0, import_node_fs26.writeFileSync)(this.vouchersPath, JSON.stringify(rows, null, 2) + "\n", "utf8");
15084
16328
  }
15085
16329
  };
15086
16330
 
15087
16331
  // src/ports/JsonSubscriptionCredentialStore.ts
15088
- var import_node_fs27 = require("fs");
16332
+ var import_node_fs28 = require("fs");
15089
16333
  var import_node_path27 = require("path");
15090
16334
  var import_SubscriptionAccountHealth2 = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
15091
16335
  var import_AccountAllowanceScheduling3 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
15092
- var import_upstreamFetch5 = require("@omnicross/core/pipeline/upstreamFetch");
16336
+ var import_upstreamFetch9 = require("@omnicross/core/pipeline/upstreamFetch");
15093
16337
  var import_SubscriptionIdentityStore2 = require("@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore");
15094
- var import_subscriptions5 = require("@omnicross/subscriptions");
16338
+ var import_subscriptions8 = require("@omnicross/subscriptions");
15095
16339
 
15096
16340
  // src/ports/account-sync.ts
15097
16341
  function viewOf(tokens) {
@@ -15135,7 +16379,7 @@ function findDuplicateCredentialIds(accounts) {
15135
16379
  }
15136
16380
 
15137
16381
  // src/ports/external-cli-credentials.ts
15138
- var import_node_fs26 = require("fs");
16382
+ var import_node_fs27 = require("fs");
15139
16383
  var import_node_os5 = require("os");
15140
16384
  var import_node_path26 = require("path");
15141
16385
  function externalStorePath(provider, home = (0, import_node_os5.homedir)()) {
@@ -15188,10 +16432,10 @@ function parseCodexTokensEnvelope(raw) {
15188
16432
  }
15189
16433
  function readExternalCliCredentials(provider, home = (0, import_node_os5.homedir)()) {
15190
16434
  const path2 = externalStorePath(provider, home);
15191
- if (!(0, import_node_fs26.existsSync)(path2)) return null;
16435
+ if (!(0, import_node_fs27.existsSync)(path2)) return null;
15192
16436
  let raw;
15193
16437
  try {
15194
- const parsed = JSON.parse((0, import_node_fs26.readFileSync)(path2, "utf8"));
16438
+ const parsed = JSON.parse((0, import_node_fs27.readFileSync)(path2, "utf8"));
15195
16439
  raw = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
15196
16440
  } catch {
15197
16441
  return null;
@@ -15214,16 +16458,18 @@ var JsonSubscriptionCredentialStore = class {
15214
16458
  * as on relay refresh egresses from the SAME proxy IP as the
15215
16459
  * account's traffic. NOT used by any read/write path.
15216
16460
  */
15217
- constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials) {
16461
+ constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials, atomicReplace = atomicReplaceUtf8) {
15218
16462
  this.tokensPath = tokensPath;
15219
16463
  this.box = box;
15220
16464
  this.fetchImpl = fetchImpl;
15221
16465
  this.externalCliReader = externalCliReader;
16466
+ this.atomicReplace = atomicReplace;
15222
16467
  }
15223
16468
  tokensPath;
15224
16469
  box;
15225
16470
  fetchImpl;
15226
16471
  externalCliReader;
16472
+ atomicReplace;
15227
16473
  /**
15228
16474
  * The proxy-aware `FetchLike` for one refresh round-trip (upstream-proxy M1). A
15229
16475
  * TEST-injected `fetchImpl` is returned verbatim; otherwise the refresh routes
@@ -15237,7 +16483,7 @@ var JsonSubscriptionCredentialStore = class {
15237
16483
  * a plaintext token pair into `upstream-trace.jsonl`.
15238
16484
  */
15239
16485
  buildRefreshFetch(providerId, accountId) {
15240
- return this.fetchImpl ?? ((url, init) => (0, import_upstreamFetch5.fetchUpstream)(url, init, { providerId, accountId, redactBodies: true }));
16486
+ return this.fetchImpl ?? ((url, init) => (0, import_upstreamFetch9.fetchUpstream)(url, init, { providerId, accountId, redactBodies: true }));
15241
16487
  }
15242
16488
  /**
15243
16489
  * In-flight refresh coalescing. OAuth refresh tokens are
@@ -15278,7 +16524,7 @@ var JsonSubscriptionCredentialStore = class {
15278
16524
  * other hot reads. Never returns token material.
15279
16525
  */
15280
16526
  getAccountProxy(providerId, accountId) {
15281
- if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego") {
16527
+ if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego" && providerId !== "kimi") {
15282
16528
  return void 0;
15283
16529
  }
15284
16530
  return getAccountProxy(this.readConfig(), providerId, accountId);
@@ -15297,7 +16543,7 @@ var JsonSubscriptionCredentialStore = class {
15297
16543
  const fingerprintOn = identityStore.isEnabled();
15298
16544
  const now = Date.now();
15299
16545
  const out = {};
15300
- for (const provider of ["claude", "codex", "gemini", "opencodego"]) {
16546
+ for (const provider of ["claude", "codex", "gemini", "opencodego", "kimi"]) {
15301
16547
  const sanitized = sanitizeAccounts(config, provider);
15302
16548
  if (sanitized.length === 0) continue;
15303
16549
  for (const account of sanitized) {
@@ -15363,7 +16609,7 @@ var JsonSubscriptionCredentialStore = class {
15363
16609
  this.materializeMigration(config);
15364
16610
  const refreshFetch = this.buildRefreshFetch("claude", capturedId);
15365
16611
  try {
15366
- const result = await import_subscriptions5.claudeOAuth.refreshAccessToken(claude.refreshToken, refreshFetch);
16612
+ const result = await import_subscriptions8.claudeOAuth.refreshAccessToken(claude.refreshToken, refreshFetch);
15367
16613
  const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
15368
16614
  const next = {
15369
16615
  ...claude,
@@ -15398,7 +16644,7 @@ var JsonSubscriptionCredentialStore = class {
15398
16644
  this.materializeMigration(config);
15399
16645
  const refreshFetch = this.buildRefreshFetch("codex", capturedId);
15400
16646
  try {
15401
- const result = await import_subscriptions5.codexOAuth.refreshAccessToken(codex.refreshToken, refreshFetch);
16647
+ const result = await import_subscriptions8.codexOAuth.refreshAccessToken(codex.refreshToken, refreshFetch);
15402
16648
  const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
15403
16649
  const next = {
15404
16650
  ...codex,
@@ -15436,7 +16682,7 @@ var JsonSubscriptionCredentialStore = class {
15436
16682
  this.materializeMigration(config);
15437
16683
  const refreshFetch = this.buildRefreshFetch("gemini", capturedId);
15438
16684
  try {
15439
- const result = await import_subscriptions5.geminiOAuth.refreshAccessToken(gemini.refreshToken, refreshFetch);
16685
+ const result = await import_subscriptions8.geminiOAuth.refreshAccessToken(gemini.refreshToken, refreshFetch);
15440
16686
  const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
15441
16687
  const next = {
15442
16688
  ...gemini,
@@ -15455,6 +16701,47 @@ var JsonSubscriptionCredentialStore = class {
15455
16701
  }
15456
16702
  });
15457
16703
  }
16704
+ /**
16705
+ * Refresh the Kimi Code (Moonshot) OAuth access token (device-flow grant).
16706
+ * Kimi ROTATES the refresh token, so the response's pair is written back
16707
+ * whole; the account's stable `deviceId` (fingerprint header input) is
16708
+ * preserved. The refresh call carries the CLI fingerprint headers. HONEST
16709
+ * `false` when no refresh_token.
16710
+ */
16711
+ async refreshKimiToken() {
16712
+ return this.coalesce("kimi:active", async () => {
16713
+ const config = this.readConfig();
16714
+ const active = getActiveAccount(config, "kimi");
16715
+ const kimi = active?.tokens;
16716
+ if (!active || !kimi?.refreshToken) return false;
16717
+ const capturedId = active.id;
16718
+ this.materializeMigration(config);
16719
+ const refreshFetch = this.buildRefreshFetch("kimi", capturedId);
16720
+ try {
16721
+ const result = await import_subscriptions8.kimiOAuth.refreshAccessToken(
16722
+ kimi.refreshToken,
16723
+ refreshFetch,
16724
+ import_subscriptions8.kimiOAuth.kimiFingerprintHeaders(kimi.deviceId)
16725
+ );
16726
+ const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
16727
+ const next = {
16728
+ ...kimi,
16729
+ accessToken: result.accessToken,
16730
+ refreshToken: result.refreshToken,
16731
+ expiresAt,
16732
+ status: "authorized",
16733
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
16734
+ errorMessage: void 0,
16735
+ syncWarning: void 0
16736
+ };
16737
+ this.writeBackById("kimi", capturedId, next);
16738
+ return true;
16739
+ } catch (error) {
16740
+ this.markExpiredById("kimi", capturedId, kimi, error);
16741
+ return false;
16742
+ }
16743
+ });
16744
+ }
15458
16745
  /**
15459
16746
  * Refresh a SPECIFIC managed account by id (background scheduler sweep and
15460
16747
  * account-pool resolution). It uses only that account's stored refresh
@@ -15507,7 +16794,7 @@ var JsonSubscriptionCredentialStore = class {
15507
16794
  }
15508
16795
  const oauth = account.tokens;
15509
16796
  if (!oauth.accessToken) return null;
15510
- if (providerId === "codex" || providerId === "gemini") {
16797
+ if (providerId === "codex" || providerId === "gemini" || providerId === "kimi") {
15511
16798
  const expiresAtMs = oauth.expiresAt ? Date.parse(oauth.expiresAt) : 0;
15512
16799
  const expiringSoon = expiresAtMs > 0 && Date.now() >= expiresAtMs - ACCOUNT_REFRESH_LEAD_MS;
15513
16800
  if (expiringSoon && oauth.refreshToken) {
@@ -15596,8 +16883,23 @@ var JsonSubscriptionCredentialStore = class {
15596
16883
  }
15597
16884
  /** Dispatch one OAuth refresh round-trip to the provider's shared flow. */
15598
16885
  async refreshUpstream(provider, refreshToken, accountId) {
15599
- const flow = provider === "claude" ? import_subscriptions5.claudeOAuth : provider === "codex" ? import_subscriptions5.codexOAuth : import_subscriptions5.geminiOAuth;
15600
- const r = await flow.refreshAccessToken(refreshToken, this.buildRefreshFetch(provider, accountId));
16886
+ const refreshFetch = this.buildRefreshFetch(provider, accountId);
16887
+ if (provider === "kimi") {
16888
+ const account = accountId ? getAccountById(this.readConfig(), "kimi", accountId) : void 0;
16889
+ const deviceId = account?.tokens?.deviceId;
16890
+ const r2 = await import_subscriptions8.kimiOAuth.refreshAccessToken(
16891
+ refreshToken,
16892
+ refreshFetch,
16893
+ import_subscriptions8.kimiOAuth.kimiFingerprintHeaders(deviceId)
16894
+ );
16895
+ return {
16896
+ accessToken: r2.accessToken,
16897
+ refreshToken: r2.refreshToken,
16898
+ expiresAt: new Date(Date.now() + r2.expiresIn * 1e3).toISOString()
16899
+ };
16900
+ }
16901
+ const flow = provider === "claude" ? import_subscriptions8.claudeOAuth : provider === "codex" ? import_subscriptions8.codexOAuth : import_subscriptions8.geminiOAuth;
16902
+ const r = await flow.refreshAccessToken(refreshToken, refreshFetch);
15601
16903
  return {
15602
16904
  accessToken: r.accessToken,
15603
16905
  refreshToken: r.refreshToken,
@@ -15760,42 +17062,86 @@ var JsonSubscriptionCredentialStore = class {
15760
17062
  /** Write the merged config to disk as pretty JSON (mkdir parent if needed).
15761
17063
  * Encrypt-on-write: the token-material fields are encrypted (legacy plaintext
15762
17064
  * `enc:v1:`; already-`enc:`/`$ENV` untouched) before serializing, so any
15763
- * write incl. child 4's future refresh writes lands encrypted. */
17065
+ * write incl. child 4's future refresh writes lands encrypted.
17066
+ * ATOMIC: temp + fsync + rename (`atomicReplaceUtf8`) — a failed or
17067
+ * interrupted write discards only the temp file; the prior `tokens.json`
17068
+ * survives byte-equal (bare `writeFileSync` truncate-writes lost every
17069
+ * account on a mid-write failure, 2026-09-06). */
15764
17070
  persist(config) {
15765
- (0, import_node_fs27.mkdirSync)((0, import_node_path27.dirname)(this.tokensPath), { recursive: true });
17071
+ (0, import_node_fs28.mkdirSync)((0, import_node_path27.dirname)(this.tokensPath), { recursive: true });
15766
17072
  const encrypted = encryptTokens(config, this.box);
15767
- (0, import_node_fs27.writeFileSync)(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n", "utf8");
17073
+ this.atomicReplace(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
15768
17074
  }
15769
17075
  /**
15770
- * Read + parse `tokens.json`, tolerating a missing/corrupt file, then DECRYPT
15771
- * the token-material fields so every getter returns plaintext (the
15772
- * subscription bearer path is byte-identical).
17076
+ * Read + parse `tokens.json`, then DECRYPT the token-material fields so every
17077
+ * getter returns plaintext (the subscription bearer path is byte-identical).
17078
+ *
17079
+ * A MISSING file is a legitimate first-boot state → minimal `{ updatedAt: '' }`.
17080
+ * A file that EXISTS but cannot be parsed as a JSON object is CORRUPT →
17081
+ * `quarantineCorrupt` moves it aside (once) before the empty config is
17082
+ * returned, so the unreadable accounts survive for manual recovery.
15773
17083
  *
15774
- * The fs-read + JSON-parse tolerance is INSIDE the try (a missing or corrupt
15775
- * file empty `{ updatedAt: '' }`). The DECRYPT runs OUTSIDE the try, so a
15776
- * wrong/missing master key or a tampered `enc:` envelope FAILS FAST with the
15777
- * box's clear, secret-free error (secrets spec "/ UX":
15778
- * SHALL fail-fast, SHALL NOT a swallowed decrypt would report "no
15779
- * tokens" and silently send the WRONG bearer upstream 401). Mirrors
15780
- * `config.ts loadConfig`, which decrypts outside its parse try.
17084
+ * The DECRYPT runs OUTSIDE any try, so a wrong/missing master key or a
17085
+ * tampered `enc:` envelope FAILS FAST with the box's clear, secret-free
17086
+ * error (secrets spec "/ UX": SHALL fail-fast, SHALL NOT a swallowed
17087
+ * decrypt would report "no tokens" and silently send the WRONG bearer
17088
+ * upstream 401). Mirrors `config.ts loadConfig`, which decrypts outside
17089
+ * its parse try.
15781
17090
  */
15782
17091
  readConfig() {
15783
- if (!(0, import_node_fs27.existsSync)(this.tokensPath)) return { updatedAt: "" };
17092
+ if (!(0, import_node_fs28.existsSync)(this.tokensPath)) return { updatedAt: "" };
15784
17093
  let parsed;
15785
17094
  try {
15786
- const raw = JSON.parse((0, import_node_fs27.readFileSync)(this.tokensPath, "utf8"));
15787
- parsed = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
17095
+ const raw = JSON.parse((0, import_node_fs28.readFileSync)(this.tokensPath, "utf8"));
17096
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
17097
+ return this.quarantineCorrupt("parsed JSON is not an object");
17098
+ }
17099
+ parsed = raw;
15788
17100
  } catch {
15789
- parsed = null;
17101
+ return this.quarantineCorrupt("unparseable JSON");
15790
17102
  }
15791
- if (!parsed) return { updatedAt: "" };
15792
17103
  const decrypted = decryptTokens(parsed, this.box);
15793
17104
  return migrateLazily(decrypted);
15794
17105
  }
17106
+ /** One-shot latch: a corrupt file is quarantined (or found unmovable) at
17107
+ * most once per process, so the hot read path never re-attempts or re-logs. */
17108
+ corruptQuarantined = false;
17109
+ /**
17110
+ * Quarantine a present-but-corrupt `tokens.json`, then treat it as empty.
17111
+ *
17112
+ * Renames the file to a sibling `tokens.json.corrupt-<stamp>` backup and
17113
+ * logs loudly (the daemon's stderr log; secret-free — reason + paths only).
17114
+ * The daemon KEEPS SERVING (API-key routing is unaffected; subscription
17115
+ * routing reports no credential, same as an absent file) while the corrupt
17116
+ * bytes survive for manual recovery — and, critically, the NEXT persist
17117
+ * (e.g. the user re-logging in) can no longer overwrite the only copy of
17118
+ * the old accounts, which is exactly how the 2026-09-06 incident turned a
17119
+ * recoverable truncated file into permanent account loss.
17120
+ *
17121
+ * Best-effort: if the rename fails (file locked, permissions), the corrupt
17122
+ * file is left in place and every later read still tolerates it as empty;
17123
+ * the latch still trips so the attempt + log happen exactly once.
17124
+ */
17125
+ quarantineCorrupt(reason) {
17126
+ if (!this.corruptQuarantined) {
17127
+ this.corruptQuarantined = true;
17128
+ const backup = `${this.tokensPath}.corrupt-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
17129
+ let moved = false;
17130
+ try {
17131
+ (0, import_node_fs28.renameSync)(this.tokensPath, backup);
17132
+ moved = true;
17133
+ } catch {
17134
+ }
17135
+ console.error(
17136
+ `[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`)
17137
+ );
17138
+ }
17139
+ return { updatedAt: "" };
17140
+ }
15795
17141
  };
15796
17142
 
15797
17143
  // src/AccountHealthProbeScheduler.ts
15798
- var import_upstreamFetch6 = require("@omnicross/core/pipeline/upstreamFetch");
17144
+ var import_upstreamFetch10 = require("@omnicross/core/pipeline/upstreamFetch");
15799
17145
 
15800
17146
  // src/probe/CodexGenerationProbe.ts
15801
17147
  var import_codexCliHeaders = require("@omnicross/core/provider-proxy/identity/codexCliHeaders");
@@ -15934,7 +17280,11 @@ var PROVIDER_PROBE_PLANS = {
15934
17280
  // billable/wrong endpoint). Upgrade to `{ kind:'upstream' }` once verified.
15935
17281
  codex: { kind: "local" },
15936
17282
  gemini: { kind: "local" },
15937
- opencodego: { kind: "local" }
17283
+ opencodego: { kind: "local" },
17284
+ // Kimi's `GET /coding/v1/usages` is a verified FREE authed GET (the allowance
17285
+ // collector uses it), but the probe path also needs the fingerprint headers —
17286
+ // keep the probe local until the collector covers the health surface.
17287
+ kimi: { kind: "local" }
15938
17288
  };
15939
17289
  function probePlanFor(providerId) {
15940
17290
  return PROVIDER_PROBE_PLANS[providerId] ?? { kind: "local" };
@@ -15956,7 +17306,7 @@ var AccountHealthProbeScheduler = class {
15956
17306
  this.logger = logger;
15957
17307
  this.config = config;
15958
17308
  this.now = opts.now ?? Date.now;
15959
- this.fetchImpl = opts.fetchImpl ?? import_upstreamFetch6.fetchUpstream;
17309
+ this.fetchImpl = opts.fetchImpl ?? import_upstreamFetch10.fetchUpstream;
15960
17310
  this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
15961
17311
  this.planFor = opts.planFor ?? probePlanFor;
15962
17312
  }
@@ -16300,13 +17650,13 @@ var AccountHealthSweeper = class {
16300
17650
  };
16301
17651
 
16302
17652
  // src/audit/AuditPruneSweeper.ts
16303
- var import_node_fs29 = require("fs");
17653
+ var import_node_fs30 = require("fs");
16304
17654
  var import_node_path29 = require("path");
16305
17655
  var import_promises6 = require("stream/promises");
16306
17656
  var import_node_zlib2 = require("zlib");
16307
17657
 
16308
17658
  // src/audit/auditStats.ts
16309
- var import_node_fs28 = require("fs");
17659
+ var import_node_fs29 = require("fs");
16310
17660
  var import_node_path28 = require("path");
16311
17661
  var SIDECAR_VERSION = 1;
16312
17662
  var META_PREFIX_BYTES = 64 * 1024;
@@ -16315,9 +17665,9 @@ function auditStatsFileName(auditFile) {
16315
17665
  return auditFile.replace(/\.jsonl$/, ".stats.json");
16316
17666
  }
16317
17667
  function readPersisted(path2) {
16318
- if (!(0, import_node_fs28.existsSync)(path2)) return null;
17668
+ if (!(0, import_node_fs29.existsSync)(path2)) return null;
16319
17669
  try {
16320
- const value = JSON.parse((0, import_node_fs28.readFileSync)(path2, "utf8"));
17670
+ const value = JSON.parse((0, import_node_fs29.readFileSync)(path2, "utf8"));
16321
17671
  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)) {
16322
17672
  return null;
16323
17673
  }
@@ -16347,7 +17697,7 @@ function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfte
16347
17697
  minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
16348
17698
  maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
16349
17699
  };
16350
- (0, import_node_fs28.writeFileSync)(statsPath, JSON.stringify(next), "utf8");
17700
+ (0, import_node_fs29.writeFileSync)(statsPath, JSON.stringify(next), "utf8");
16351
17701
  }
16352
17702
  function queryCovers(stats, from, to) {
16353
17703
  return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
@@ -16405,7 +17755,7 @@ async function scanAuditFile(auditPath, startByte, auditBytes, from, to) {
16405
17755
  prefixTruncated = false;
16406
17756
  };
16407
17757
  if (auditBytes > startByte) {
16408
- const stream = (0, import_node_fs28.createReadStream)(auditPath, {
17758
+ const stream = (0, import_node_fs29.createReadStream)(auditPath, {
16409
17759
  start: startByte,
16410
17760
  end: auditBytes - 1,
16411
17761
  highWaterMark: READ_CHUNK_BYTES2
@@ -16458,12 +17808,12 @@ function mergePersistedStats(previous, appended) {
16458
17808
  };
16459
17809
  }
16460
17810
  async function readAuditStats(auditDir, query2 = {}) {
16461
- if (!(0, import_node_fs28.existsSync)(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
17811
+ if (!(0, import_node_fs29.existsSync)(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
16462
17812
  const from = typeof query2.from === "number" ? query2.from : -Infinity;
16463
17813
  const to = typeof query2.to === "number" ? query2.to : Infinity;
16464
17814
  let sources;
16465
17815
  try {
16466
- sources = (0, import_node_fs28.readdirSync)(auditDir).filter((name) => fileOverlaps(name, from, to)).sort().map(
17816
+ sources = (0, import_node_fs29.readdirSync)(auditDir).filter((name) => fileOverlaps(name, from, to)).sort().map(
16467
17817
  (name) => AUDIT_DAY_DIR_RE.test(name) ? {
16468
17818
  auditPath: (0, import_node_path28.join)(auditDir, name, AUDIT_META_FILE),
16469
17819
  statsPath: (0, import_node_path28.join)(auditDir, name, auditStatsFileName(AUDIT_META_FILE))
@@ -16471,14 +17821,14 @@ async function readAuditStats(auditDir, query2 = {}) {
16471
17821
  auditPath: (0, import_node_path28.join)(auditDir, name),
16472
17822
  statsPath: (0, import_node_path28.join)(auditDir, auditStatsFileName(name))
16473
17823
  }
16474
- ).filter((source) => (0, import_node_fs28.existsSync)(source.auditPath));
17824
+ ).filter((source) => (0, import_node_fs29.existsSync)(source.auditPath));
16475
17825
  } catch {
16476
17826
  return { requestCount: 0, errorCount: 0, complete: false };
16477
17827
  }
16478
17828
  const total = { requestCount: 0, errorCount: 0, complete: true };
16479
17829
  for (const { auditPath, statsPath } of sources) {
16480
17830
  try {
16481
- const auditBytes = (0, import_node_fs28.statSync)(auditPath).size;
17831
+ const auditBytes = (0, import_node_fs29.statSync)(auditPath).size;
16482
17832
  const persisted = readPersisted(statsPath);
16483
17833
  if (persisted && persisted.complete && persisted.auditBytes === auditBytes && queryCovers(persisted, from, to)) {
16484
17834
  total.requestCount += persisted.requestCount;
@@ -16497,7 +17847,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16497
17847
  total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
16498
17848
  total.complete = total.complete && scanned.filtered.complete;
16499
17849
  const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
16500
- if (current.complete) (0, import_node_fs28.writeFileSync)(statsPath, JSON.stringify(current), "utf8");
17850
+ if (current.complete) (0, import_node_fs29.writeFileSync)(statsPath, JSON.stringify(current), "utf8");
16501
17851
  } catch {
16502
17852
  total.complete = false;
16503
17853
  }
@@ -16506,7 +17856,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16506
17856
  }
16507
17857
 
16508
17858
  // src/audit/AuditPruneSweeper.ts
16509
- var DAY_MS = 24 * 60 * 6e4;
17859
+ var DAY_MS3 = 24 * 60 * 6e4;
16510
17860
  var SWEEP_INTERVAL_MS2 = 60 * 6e4;
16511
17861
  var ARCHIVE_BATCH = 64;
16512
17862
  var AuditPruneSweeper = class {
@@ -16569,19 +17919,19 @@ var AuditPruneSweeper = class {
16569
17919
  if (!this.config.enabled || this.sweeping) return 0;
16570
17920
  this.sweeping = true;
16571
17921
  try {
16572
- if (!(0, import_node_fs29.existsSync)(this.auditDir)) return 0;
16573
- const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS;
17922
+ if (!(0, import_node_fs30.existsSync)(this.auditDir)) return 0;
17923
+ const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS3;
16574
17924
  let removed = 0;
16575
- for (const name of (0, import_node_fs29.readdirSync)(this.auditDir)) {
17925
+ for (const name of (0, import_node_fs30.readdirSync)(this.auditDir)) {
16576
17926
  const dateMs = auditFileDateMs(name);
16577
17927
  if (dateMs === null || dateMs >= cutoff) continue;
16578
17928
  try {
16579
17929
  if (isAuditDayDir(name)) {
16580
- (0, import_node_fs29.rmSync)((0, import_node_path29.join)(this.auditDir, name), { recursive: true, force: true });
17930
+ (0, import_node_fs30.rmSync)((0, import_node_path29.join)(this.auditDir, name), { recursive: true, force: true });
16581
17931
  } else {
16582
- (0, import_node_fs29.unlinkSync)((0, import_node_path29.join)(this.auditDir, name));
17932
+ (0, import_node_fs30.unlinkSync)((0, import_node_path29.join)(this.auditDir, name));
16583
17933
  const statsPath = (0, import_node_path29.join)(this.auditDir, auditStatsFileName(name));
16584
- if ((0, import_node_fs29.existsSync)(statsPath)) (0, import_node_fs29.unlinkSync)(statsPath);
17934
+ if ((0, import_node_fs30.existsSync)(statsPath)) (0, import_node_fs30.unlinkSync)(statsPath);
16585
17935
  }
16586
17936
  removed += 1;
16587
17937
  } catch (error) {
@@ -16611,10 +17961,10 @@ var AuditPruneSweeper = class {
16611
17961
  if (!this.config.enabled || this.archiving) return 0;
16612
17962
  this.archiving = true;
16613
17963
  try {
16614
- if (!(0, import_node_fs29.existsSync)(this.auditDir)) return 0;
17964
+ if (!(0, import_node_fs30.existsSync)(this.auditDir)) return 0;
16615
17965
  const today = this.todayMidnight();
16616
17966
  let compressed = 0;
16617
- for (const name of (0, import_node_fs29.readdirSync)(this.auditDir)) {
17967
+ for (const name of (0, import_node_fs30.readdirSync)(this.auditDir)) {
16618
17968
  if (compressed >= ARCHIVE_BATCH) break;
16619
17969
  const dateMs = auditFileDateMs(name);
16620
17970
  if (dateMs === null || dateMs >= today || !isAuditDayDir(name)) continue;
@@ -16655,7 +18005,7 @@ var AuditPruneSweeper = class {
16655
18005
  async archiveDay(bodiesPath, budget) {
16656
18006
  let shards;
16657
18007
  try {
16658
- shards = (0, import_node_fs29.readdirSync)(bodiesPath).filter((file) => file.endsWith(".jsonl"));
18008
+ shards = (0, import_node_fs30.readdirSync)(bodiesPath).filter((file) => file.endsWith(".jsonl"));
16659
18009
  } catch {
16660
18010
  return 0;
16661
18011
  }
@@ -16665,16 +18015,16 @@ var AuditPruneSweeper = class {
16665
18015
  const source = (0, import_node_path29.join)(bodiesPath, shard);
16666
18016
  const target = `${source}.gz`;
16667
18017
  try {
16668
- if ((0, import_node_fs29.existsSync)(target)) {
16669
- (0, import_node_fs29.unlinkSync)(source);
18018
+ if ((0, import_node_fs30.existsSync)(target)) {
18019
+ (0, import_node_fs30.unlinkSync)(source);
16670
18020
  continue;
16671
18021
  }
16672
- await (0, import_promises6.pipeline)((0, import_node_fs29.createReadStream)(source), (0, import_node_zlib2.createGzip)(), (0, import_node_fs29.createWriteStream)(target));
16673
- (0, import_node_fs29.unlinkSync)(source);
18022
+ await (0, import_promises6.pipeline)((0, import_node_fs30.createReadStream)(source), (0, import_node_zlib2.createGzip)(), (0, import_node_fs30.createWriteStream)(target));
18023
+ (0, import_node_fs30.unlinkSync)(source);
16674
18024
  compressed += 1;
16675
18025
  } catch (error) {
16676
18026
  try {
16677
- if ((0, import_node_fs29.existsSync)(target)) (0, import_node_fs29.unlinkSync)(target);
18027
+ if ((0, import_node_fs30.existsSync)(target)) (0, import_node_fs30.unlinkSync)(target);
16678
18028
  } catch {
16679
18029
  }
16680
18030
  this.logger.warn("[AuditPruneSweeper] failed to archive audit body shard", {
@@ -16688,7 +18038,7 @@ var AuditPruneSweeper = class {
16688
18038
  };
16689
18039
 
16690
18040
  // src/usage/usageMigrate.ts
16691
- var import_node_fs30 = require("fs");
18041
+ var import_node_fs31 = require("fs");
16692
18042
  var import_promises7 = require("fs/promises");
16693
18043
  var import_node_path30 = require("path");
16694
18044
  var import_node_readline = require("readline");
@@ -16735,7 +18085,7 @@ async function migrateLegacyUsageEvents(opts) {
16735
18085
  let skipped = 0;
16736
18086
  try {
16737
18087
  const reader = (0, import_node_readline.createInterface)({
16738
- input: (0, import_node_fs30.createReadStream)(eventsPath, { encoding: "utf8" }),
18088
+ input: (0, import_node_fs31.createReadStream)(eventsPath, { encoding: "utf8" }),
16739
18089
  crlfDelay: Number.POSITIVE_INFINITY
16740
18090
  });
16741
18091
  for await (const line of reader) {
@@ -16827,7 +18177,7 @@ async function closeAll(writers) {
16827
18177
  // src/usage/UsagePruneSweeper.ts
16828
18178
  var import_promises8 = require("fs/promises");
16829
18179
  var import_node_path31 = require("path");
16830
- var DAY_MS2 = 24 * 60 * 6e4;
18180
+ var DAY_MS4 = 24 * 60 * 6e4;
16831
18181
  var SWEEP_INTERVAL_MS3 = 60 * 6e4;
16832
18182
  var DEFAULT_USAGE_RETENTION_DAYS = 90;
16833
18183
  var UsagePruneSweeper = class {
@@ -16884,7 +18234,7 @@ var UsagePruneSweeper = class {
16884
18234
  this.sweeping = true;
16885
18235
  try {
16886
18236
  const retentionDays = this.config.retentionDays ?? DEFAULT_USAGE_RETENTION_DAYS;
16887
- const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS2;
18237
+ const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS4;
16888
18238
  let removed = 0;
16889
18239
  for (const entry of await listUsageDays(this.usageDir)) {
16890
18240
  if (!entry.hasShard) continue;
@@ -16942,7 +18292,7 @@ var UsagePruneSweeper = class {
16942
18292
  };
16943
18293
 
16944
18294
  // src/audit/auditReader.ts
16945
- var import_node_fs31 = require("fs");
18295
+ var import_node_fs32 = require("fs");
16946
18296
  var import_node_path32 = require("path");
16947
18297
  var DEFAULT_LIMIT = 200;
16948
18298
  var MAX_LIMIT = 2e3;
@@ -16950,7 +18300,7 @@ var OVERSCAN = 256;
16950
18300
  function daySources(auditDir) {
16951
18301
  let names;
16952
18302
  try {
16953
- names = (0, import_node_fs31.readdirSync)(auditDir);
18303
+ names = (0, import_node_fs32.readdirSync)(auditDir);
16954
18304
  } catch {
16955
18305
  return [];
16956
18306
  }
@@ -16960,7 +18310,7 @@ function daySources(auditDir) {
16960
18310
  if (dateMs === null) continue;
16961
18311
  if (AUDIT_DAY_DIR_RE.test(name)) {
16962
18312
  const path2 = (0, import_node_path32.join)(auditDir, name, AUDIT_META_FILE);
16963
- if ((0, import_node_fs31.existsSync)(path2)) sources.push({ path: path2, dateMs });
18313
+ if ((0, import_node_fs32.existsSync)(path2)) sources.push({ path: path2, dateMs });
16964
18314
  } else if (AUDIT_FILE_RE.test(name)) {
16965
18315
  sources.push({ path: (0, import_node_path32.join)(auditDir, name), dateMs });
16966
18316
  }
@@ -16978,7 +18328,7 @@ function toMetaRecord(record) {
16978
18328
  return { ...meta, hasBody: true };
16979
18329
  }
16980
18330
  function readAuditRecords(auditDir, query2 = {}) {
16981
- if (!(0, import_node_fs31.existsSync)(auditDir)) return [];
18331
+ if (!(0, import_node_fs32.existsSync)(auditDir)) return [];
16982
18332
  const from = typeof query2.from === "number" ? query2.from : -Infinity;
16983
18333
  const to = typeof query2.to === "number" ? query2.to : Infinity;
16984
18334
  const limit = Math.min(MAX_LIMIT, Math.max(1, Math.trunc(query2.limit ?? DEFAULT_LIMIT)));
@@ -17006,7 +18356,7 @@ function readAuditRecords(auditDir, query2 = {}) {
17006
18356
  }
17007
18357
 
17008
18358
  // src/audit/AuditWriter.ts
17009
- var import_node_fs32 = require("fs");
18359
+ var import_node_fs33 = require("fs");
17010
18360
  var import_node_path33 = require("path");
17011
18361
  var AuditWriter = class {
17012
18362
  constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
@@ -17054,7 +18404,7 @@ var AuditWriter = class {
17054
18404
  /** Create a directory once per process and remember it. */
17055
18405
  ensureDir(path2) {
17056
18406
  if (!this.ensuredDirs.has(path2)) {
17057
- (0, import_node_fs32.mkdirSync)(path2, { recursive: true });
18407
+ (0, import_node_fs33.mkdirSync)(path2, { recursive: true });
17058
18408
  this.ensuredDirs.add(path2);
17059
18409
  }
17060
18410
  return path2;
@@ -17064,8 +18414,8 @@ var AuditWriter = class {
17064
18414
  const { requestBody: _req, responseBody: _res, ...meta } = record;
17065
18415
  const file = (0, import_node_path33.join)(dayPath, AUDIT_META_FILE);
17066
18416
  const line = JSON.stringify(meta) + "\n";
17067
- const bytesBefore = (0, import_node_fs32.existsSync)(file) ? (0, import_node_fs32.statSync)(file).size : 0;
17068
- (0, import_node_fs32.appendFileSync)(file, line, "utf8");
18417
+ const bytesBefore = (0, import_node_fs33.existsSync)(file) ? (0, import_node_fs33.statSync)(file).size : 0;
18418
+ (0, import_node_fs33.appendFileSync)(file, line, "utf8");
17069
18419
  try {
17070
18420
  updateAuditStatsAfterAppend(
17071
18421
  file,
@@ -17097,7 +18447,7 @@ var AuditWriter = class {
17097
18447
  const line = encodeBodyEntry(record, sessionKey, dayDir, this.bases);
17098
18448
  if (line === null) return;
17099
18449
  const bodiesPath = this.ensureDir((0, import_node_path33.join)(dayPath, AUDIT_BODIES_DIR));
17100
- (0, import_node_fs32.appendFileSync)((0, import_node_path33.join)(bodiesPath, auditBodyFileName(sessionKey)), line + "\n", "utf8");
18450
+ (0, import_node_fs33.appendFileSync)((0, import_node_path33.join)(bodiesPath, auditBodyFileName(sessionKey)), line + "\n", "utf8");
17101
18451
  } catch (error) {
17102
18452
  this.bases.forget(sessionKey);
17103
18453
  this.logger.warn("[AuditWriter] failed to append audit body shard", {
@@ -17109,10 +18459,10 @@ var AuditWriter = class {
17109
18459
  };
17110
18460
 
17111
18461
  // src/billing/BillingPublisher.ts
17112
- var import_node_fs33 = require("fs");
18462
+ var import_node_fs34 = require("fs");
17113
18463
  var import_node_crypto24 = require("crypto");
17114
18464
  var import_node_path34 = require("path");
17115
- var import_upstreamFetch7 = require("@omnicross/core/pipeline/upstreamFetch");
18465
+ var import_upstreamFetch11 = require("@omnicross/core/pipeline/upstreamFetch");
17116
18466
 
17117
18467
  // src/billing/billingFiles.ts
17118
18468
  var BILLING_FILE_RE = /^billing-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
@@ -17135,7 +18485,7 @@ var BillingPublisher = class {
17135
18485
  constructor(billingDir, logger, opts = {}) {
17136
18486
  this.billingDir = billingDir;
17137
18487
  this.logger = logger;
17138
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch7.fetchUpstream)(url, init));
18488
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch11.fetchUpstream)(url, init));
17139
18489
  this.defer = opts.defer ?? ((fn) => setTimeout(fn, 0));
17140
18490
  this.timeoutMs = opts.timeoutMs ?? BILLING_POST_TIMEOUT_MS;
17141
18491
  this.now = opts.now ?? Date.now;
@@ -17183,7 +18533,7 @@ var BillingPublisher = class {
17183
18533
  appendNow(event) {
17184
18534
  this.ensureDir();
17185
18535
  const file = (0, import_node_path34.join)(this.billingDir, billingFileName(event.ts));
17186
- (0, import_node_fs33.appendFileSync)(file, JSON.stringify(event) + "\n", "utf8");
18536
+ (0, import_node_fs34.appendFileSync)(file, JSON.stringify(event) + "\n", "utf8");
17187
18537
  }
17188
18538
  /**
17189
18539
  * One best-effort delivery attempt for an event ALREADY in the ledger. POSTs the
@@ -17233,7 +18583,7 @@ var BillingPublisher = class {
17233
18583
  try {
17234
18584
  this.ensureDir();
17235
18585
  const file = (0, import_node_path34.join)(this.billingDir, deliveredFileName(event.ts));
17236
- (0, import_node_fs33.appendFileSync)(file, JSON.stringify({ id: event.id, deliveredAt: this.now() }) + "\n", "utf8");
18586
+ (0, import_node_fs34.appendFileSync)(file, JSON.stringify({ id: event.id, deliveredAt: this.now() }) + "\n", "utf8");
17237
18587
  } catch (error) {
17238
18588
  this.logger.warn("[BillingPublisher] failed to append delivery marker", {
17239
18589
  error: error instanceof Error ? error.message : String(error)
@@ -17242,20 +18592,20 @@ var BillingPublisher = class {
17242
18592
  }
17243
18593
  ensureDir() {
17244
18594
  if (this.dirEnsured) return;
17245
- (0, import_node_fs33.mkdirSync)(this.billingDir, { recursive: true });
18595
+ (0, import_node_fs34.mkdirSync)(this.billingDir, { recursive: true });
17246
18596
  this.dirEnsured = true;
17247
18597
  }
17248
18598
  };
17249
18599
 
17250
18600
  // src/billing/billingReader.ts
17251
- var import_node_fs34 = require("fs");
18601
+ var import_node_fs35 = require("fs");
17252
18602
  var import_node_path35 = require("path");
17253
18603
  function readBillingLedger(billingDir) {
17254
18604
  const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
17255
- if (!(0, import_node_fs34.existsSync)(billingDir)) return view;
18605
+ if (!(0, import_node_fs35.existsSync)(billingDir)) return view;
17256
18606
  let files;
17257
18607
  try {
17258
- files = (0, import_node_fs34.readdirSync)(billingDir);
18608
+ files = (0, import_node_fs35.readdirSync)(billingDir);
17259
18609
  } catch {
17260
18610
  return view;
17261
18611
  }
@@ -17286,7 +18636,7 @@ function readBillingStatus(billingDir) {
17286
18636
  function parseLines(dir, file) {
17287
18637
  let raw;
17288
18638
  try {
17289
- raw = (0, import_node_fs34.readFileSync)((0, import_node_path35.join)(dir, file), "utf8");
18639
+ raw = (0, import_node_fs35.readFileSync)((0, import_node_path35.join)(dir, file), "utf8");
17290
18640
  } catch {
17291
18641
  return [];
17292
18642
  }
@@ -17385,7 +18735,7 @@ var BillingRetrySweeper = class {
17385
18735
  // src/TokenRefreshScheduler.ts
17386
18736
  var REFRESH_LEAD_MS2 = 5 * 6e4;
17387
18737
  var SWEEP_INTERVAL_MS5 = 6e4;
17388
- var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini"];
18738
+ var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini", "kimi"];
17389
18739
  var TokenRefreshScheduler = class {
17390
18740
  constructor(store, logger, intervalMs = SWEEP_INTERVAL_MS5, leadMs = REFRESH_LEAD_MS2) {
17391
18741
  this.store = store;
@@ -17468,6 +18818,8 @@ var TokenRefreshScheduler = class {
17468
18818
  return this.store.refreshCodexToken();
17469
18819
  case "gemini":
17470
18820
  return this.store.refreshGeminiToken();
18821
+ case "kimi":
18822
+ return this.store.refreshKimiToken();
17471
18823
  }
17472
18824
  }
17473
18825
  };
@@ -17544,7 +18896,7 @@ function createRouteLeaseSubscriptionPreflight(credentials) {
17544
18896
 
17545
18897
  // src/webhook/WebhookDispatcher.ts
17546
18898
  var import_node_crypto25 = require("crypto");
17547
- var import_upstreamFetch8 = require("@omnicross/core/pipeline/upstreamFetch");
18899
+ var import_upstreamFetch12 = require("@omnicross/core/pipeline/upstreamFetch");
17548
18900
  var WEBHOOK_MAX_ATTEMPTS = 3;
17549
18901
  var WEBHOOK_QUEUE_MAX = 1e3;
17550
18902
  var WEBHOOK_SEND_TIMEOUT_MS = 1e4;
@@ -17564,7 +18916,7 @@ var WebhookDispatcher = class {
17564
18916
  sleep;
17565
18917
  now;
17566
18918
  constructor(opts = {}) {
17567
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch8.fetchUpstream)(url, init));
18919
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch12.fetchUpstream)(url, init));
17568
18920
  this.logger = opts.logger;
17569
18921
  this.maxAttempts = opts.maxAttempts ?? WEBHOOK_MAX_ATTEMPTS;
17570
18922
  this.queueMax = opts.queueMax ?? WEBHOOK_QUEUE_MAX;
@@ -17650,8 +19002,8 @@ var WebhookDispatcher = class {
17650
19002
  signal: AbortSignal.timeout(this.timeoutMs)
17651
19003
  });
17652
19004
  return res.ok ? { ok: true, status: res.status } : { ok: false, status: res.status };
17653
- } catch (err5) {
17654
- return { ok: false, error: err5 instanceof Error ? err5.message : String(err5) };
19005
+ } catch (err6) {
19006
+ return { ok: false, error: err6 instanceof Error ? err6.message : String(err6) };
17655
19007
  }
17656
19008
  }
17657
19009
  /**
@@ -17770,7 +19122,7 @@ function installImageRuntimeBootstrapSession(initialGeneration) {
17770
19122
  function resolveLoggingConfig(configured, configPath) {
17771
19123
  const file = configured?.file ?? defaultDaemonLogPath(configPath);
17772
19124
  try {
17773
- (0, import_node_fs35.mkdirSync)(configured?.file ? (0, import_node_path36.dirname)(configured.file) : defaultLogDir(configPath), {
19125
+ (0, import_node_fs36.mkdirSync)(configured?.file ? (0, import_node_path36.dirname)(configured.file) : defaultLogDir(configPath), {
17774
19126
  recursive: true
17775
19127
  });
17776
19128
  } catch {
@@ -17788,12 +19140,12 @@ function buildDaemon(config, paths) {
17788
19140
  setSecretBox(secretBox3);
17789
19141
  setSecretBox2(secretBox3);
17790
19142
  const decryptedConfig = decryptConfigSecrets(config, secretBox3);
17791
- const accountAllowanceStore = new import_AccountAllowanceStore4.AccountAllowanceStore(
19143
+ const accountAllowanceStore = new import_AccountAllowanceStore7.AccountAllowanceStore(
17792
19144
  Date.now,
17793
19145
  void 0,
17794
19146
  new JsonAccountAllowancePersistence(defaultAccountAllowancePath(paths.configPath))
17795
19147
  );
17796
- (0, import_AccountAllowanceStore4.setSharedAccountAllowanceStore)(accountAllowanceStore);
19148
+ (0, import_AccountAllowanceStore7.setSharedAccountAllowanceStore)(accountAllowanceStore);
17797
19149
  (0, import_AccountAllowanceScheduling5.getSharedAccountAllowanceScheduling)().configure(
17798
19150
  (0, import_outbound_api10.normalizeServerConfig)(decryptedConfig.server).allowanceScheduling
17799
19151
  );
@@ -17818,21 +19170,22 @@ function buildDaemon(config, paths) {
17818
19170
  claudeAllowanceRefreshScheduler.configure(
17819
19171
  (0, import_outbound_api10.normalizeServerConfig)(decryptedConfig.server).allowanceScheduling
17820
19172
  );
17821
- const subscriptionAccounts = new import_subscriptions6.SubscriptionAccountService(credentialStore);
17822
- (0, import_subscriptions6.setSubscriptionAccountService)(subscriptionAccounts);
17823
- const subscriptionRegistry = new import_subscriptions6.SubscriptionProviderRegistry(
19173
+ const subscriptionAccounts = new import_subscriptions9.SubscriptionAccountService(credentialStore);
19174
+ (0, import_subscriptions9.setSubscriptionAccountService)(subscriptionAccounts);
19175
+ const subscriptionRegistry = new import_subscriptions9.SubscriptionProviderRegistry(
17824
19176
  subscriptionAccounts,
17825
19177
  credentialStore
17826
19178
  );
17827
- (0, import_subscriptions6.setSubscriptionProviderRegistry)(subscriptionRegistry);
19179
+ (0, import_subscriptions9.setSubscriptionProviderRegistry)(subscriptionRegistry);
17828
19180
  setServerProxyConfig(decryptedConfig.server?.proxy);
17829
- (0, import_upstreamFetch9.setUpstreamProxyResolver)(
19181
+ (0, import_upstreamFetch13.setUpstreamProxyResolver)(
17830
19182
  createUpstreamProxyResolver({
17831
19183
  getAccountProxy: (providerId, accountId) => credentialStore.getAccountProxy(providerId, accountId)
17832
19184
  })
17833
19185
  );
17834
19186
  (0, import_gemini_code_assist_resolver.setGeminiCodeAssistResolver)((0, import_GeminiCodeAssistProjectResolver.getGeminiCodeAssistProjectResolver)());
17835
19187
  const autoDisableStore = new AutoDisableStore();
19188
+ const providerKeyQuotaService = new ProviderKeyQuotaService(secretBox3);
17836
19189
  const apiKeyPool = new import_ApiKeyPoolService.ApiKeyPoolService(
17837
19190
  createPoolKeysLoader((id) => llmConfig.getProviderRow(id), autoDisableStore),
17838
19191
  resolveEnvKey,
@@ -17849,7 +19202,7 @@ function buildDaemon(config, paths) {
17849
19202
  const pricingEngine = new import_usage2.PricingEngine(pricingStore, logger, {
17850
19203
  // Catalog egress follows the same global/env proxy policy as every other
17851
19204
  // daemon upstream call; no provider/account override applies here.
17852
- fetchImpl: ((input, init) => (0, import_upstreamFetch9.fetchUpstream)(String(input), init ?? {}))
19205
+ fetchImpl: ((input, init) => (0, import_upstreamFetch13.fetchUpstream)(String(input), init ?? {}))
17853
19206
  });
17854
19207
  const pricingRefreshScheduler = new PricingRefreshScheduler(
17855
19208
  pricingEngine,
@@ -18113,6 +19466,11 @@ function buildDaemon(config, paths) {
18113
19466
  // values themselves NEVER leave (masked via `maskProviderApiKey`).
18114
19467
  apiKeyPool,
18115
19468
  autoDisableStore,
19469
+ // BYO provider-key quota (Z.AI coding plan, MiniMax Token Plan, …): a
19470
+ // read-through cached same-key usage probe surfaced on the keys view. The
19471
+ // key plaintext is resolved + decrypted inside the service and never
19472
+ // crosses back out.
19473
+ providerKeyQuota: providerKeyQuotaService,
18116
19474
  // Interactive OAuth login over admin HTTP (app-parity child 4, design
18117
19475
  // D1/D2-a). The in-memory pending-session store (NEVER serialized), the
18118
19476
  // injected token-exchange fetch (global `fetch` here; mocked in tests), and a
@@ -18129,7 +19487,7 @@ function buildDaemon(config, paths) {
18129
19487
  // — `server.proxy.byProvider[...]` was silently skipped — and the call was
18130
19488
  // excluded from the upstream trace, so a failing login left no evidence.
18131
19489
  // `redactBodies` keeps the code/verifier + minted token out of that trace.
18132
- oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => (0, import_upstreamFetch9.fetchUpstream)(url, init, { providerId, redactBodies: true }),
19490
+ oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => (0, import_upstreamFetch13.fetchUpstream)(url, init, { providerId, redactBodies: true }),
18133
19491
  subscriptionAccountAppender: credentialStore,
18134
19492
  // Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
18135
19493
  // + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
@@ -18137,6 +19495,10 @@ function buildDaemon(config, paths) {
18137
19495
  // can inject a mock so no real port is bound.
18138
19496
  codexSessions: new CodexOAuthSessionStore(),
18139
19497
  codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
19498
+ // Kimi interactive OAuth — the async DEVICE-CODE flow store (no port, no
19499
+ // paste; the app shows the verification URL + user code and polls the
19500
+ // token-free status). Token captured + persisted daemon-side.
19501
+ kimiSessions: new CodexOAuthSessionStore(),
18140
19502
  // Migration pack (app-parity child 6, design D2/D3) — the concrete credential
18141
19503
  // store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
18142
19504
  // the multi-account append (`appendProviderAccount`, import re-encrypts at-
@@ -18195,7 +19557,7 @@ function buildDaemon(config, paths) {
18195
19557
  });
18196
19558
  const webhookDispatcher = new WebhookDispatcher({
18197
19559
  logger,
18198
- fetchImpl: (url, init) => (0, import_upstreamFetch9.fetchUpstream)(url, init)
19560
+ fetchImpl: (url, init) => (0, import_upstreamFetch13.fetchUpstream)(url, init)
18199
19561
  });
18200
19562
  setWebhookRuntime(webhookDispatcher, (0, import_SubscriptionAccountHealth4.getSharedAccountHealth)());
18201
19563
  const auditWriter = new AuditWriter(auditDir, logger);
@@ -18272,8 +19634,8 @@ function buildDaemon(config, paths) {
18272
19634
  }
18273
19635
  function isTokensStoreReadable(tokensPath) {
18274
19636
  try {
18275
- if (!(0, import_node_fs35.existsSync)(tokensPath)) return true;
18276
- (0, import_node_fs35.accessSync)(tokensPath, import_node_fs35.constants.R_OK);
19637
+ if (!(0, import_node_fs36.existsSync)(tokensPath)) return true;
19638
+ (0, import_node_fs36.accessSync)(tokensPath, import_node_fs36.constants.R_OK);
18277
19639
  return true;
18278
19640
  } catch {
18279
19641
  return false;
@@ -18509,11 +19871,11 @@ async function runLiveProbe(url, key, fetchImpl = fetch) {
18509
19871
  status: res.status,
18510
19872
  estimateHeader: res.headers.get("x-omnicross-count-estimate")
18511
19873
  };
18512
- } catch (err5) {
19874
+ } catch (err6) {
18513
19875
  return {
18514
19876
  status: null,
18515
19877
  estimateHeader: null,
18516
- error: err5 instanceof Error ? err5.message : String(err5)
19878
+ error: err6 instanceof Error ? err6.message : String(err6)
18517
19879
  };
18518
19880
  }
18519
19881
  }
@@ -18601,7 +19963,7 @@ async function runDoctor(argv, fetchImpl = fetch) {
18601
19963
  }
18602
19964
 
18603
19965
  // src/commands/import-ccr.ts
18604
- var import_node_fs36 = require("fs");
19966
+ var import_node_fs37 = require("fs");
18605
19967
  var import_node_util3 = require("util");
18606
19968
 
18607
19969
  // src/ccr-import.ts
@@ -18696,7 +20058,7 @@ async function runImportCcr(argv) {
18696
20058
  const outPath = values.out ?? "omnicross.config.json";
18697
20059
  let raw;
18698
20060
  try {
18699
- raw = JSON.parse((0, import_node_fs36.readFileSync)(ccrPath, "utf8"));
20061
+ raw = JSON.parse((0, import_node_fs37.readFileSync)(ccrPath, "utf8"));
18700
20062
  } catch {
18701
20063
  throw new Error(`import-ccr: cannot read or parse '${ccrPath}'`);
18702
20064
  }
@@ -18834,7 +20196,7 @@ async function keysRevoke(db, id) {
18834
20196
  // src/commands/launch.ts
18835
20197
  var import_node_child_process2 = require("child_process");
18836
20198
  var import_node_crypto26 = require("crypto");
18837
- var import_node_fs37 = require("fs");
20199
+ var import_node_fs38 = require("fs");
18838
20200
  var import_node_path38 = require("path");
18839
20201
  var import_node_util6 = require("util");
18840
20202
  var import_cli_launcher3 = require("@omnicross/cli-launcher");
@@ -18879,7 +20241,7 @@ function resolveInPathDefault(candidate) {
18879
20241
  const segments = (process.env["PATH"] ?? "").split(import_node_path38.delimiter).filter(Boolean);
18880
20242
  for (const seg of segments) {
18881
20243
  const full = (0, import_node_path38.join)(seg, candidate);
18882
- if ((0, import_node_fs37.existsSync)(full)) return full;
20244
+ if ((0, import_node_fs38.existsSync)(full)) return full;
18883
20245
  }
18884
20246
  return null;
18885
20247
  }
@@ -18920,9 +20282,9 @@ async function runLaunch(argv, deps) {
18920
20282
  await daemon.llmConfig.ready();
18921
20283
  await daemon.migrateUsageStore();
18922
20284
  await daemon.providerProxy.start();
18923
- } catch (err5) {
20285
+ } catch (err6) {
18924
20286
  await shutdownLaunchDaemon(daemon);
18925
- throw err5;
20287
+ throw err6;
18926
20288
  }
18927
20289
  let launch;
18928
20290
  try {
@@ -18930,9 +20292,9 @@ async function runLaunch(argv, deps) {
18930
20292
  providerId: values.provider,
18931
20293
  model: values.model
18932
20294
  });
18933
- } catch (err5) {
20295
+ } catch (err6) {
18934
20296
  await shutdownLaunchDaemon(daemon);
18935
- throw err5;
20297
+ throw err6;
18936
20298
  }
18937
20299
  try {
18938
20300
  const plan = buildCliSpawnPlan({
@@ -19037,9 +20399,9 @@ function spawnCliInherit(plan) {
19037
20399
  process.removeListener("SIGINT", onSignal);
19038
20400
  process.removeListener("SIGTERM", onSignal);
19039
20401
  };
19040
- child.on("error", (err5) => {
20402
+ child.on("error", (err6) => {
19041
20403
  detach();
19042
- if (err5.code === "ENOENT") {
20404
+ if (err6.code === "ENOENT") {
19043
20405
  reject(
19044
20406
  new Error(
19045
20407
  `launch: "${plan.command}" not found on PATH \u2014 install the CLI first.`
@@ -19047,7 +20409,7 @@ function spawnCliInherit(plan) {
19047
20409
  );
19048
20410
  return;
19049
20411
  }
19050
- reject(err5);
20412
+ reject(err6);
19051
20413
  });
19052
20414
  child.on("exit", (code, signal) => {
19053
20415
  detach();
@@ -19060,9 +20422,9 @@ function spawnCliInherit(plan) {
19060
20422
  var import_node_child_process3 = require("child_process");
19061
20423
  var import_node_readline2 = require("readline");
19062
20424
  var import_node_util7 = require("util");
19063
- var import_upstreamFetch10 = require("@omnicross/core/pipeline/upstreamFetch");
19064
- var import_subscriptions7 = require("@omnicross/subscriptions");
19065
- var PROVIDERS2 = ["claude", "codex", "gemini"];
20425
+ var import_upstreamFetch14 = require("@omnicross/core/pipeline/upstreamFetch");
20426
+ var import_subscriptions10 = require("@omnicross/subscriptions");
20427
+ var PROVIDERS2 = ["claude", "codex", "gemini", "kimi"];
19066
20428
  async function runLogin(argv, deps) {
19067
20429
  const { values, positionals } = (0, import_node_util7.parseArgs)({
19068
20430
  args: argv,
@@ -19088,14 +20450,16 @@ async function runLogin(argv, deps) {
19088
20450
  openBrowser: deps?.openBrowser ?? openBrowser,
19089
20451
  promptPaste: deps?.promptPaste ?? promptPaste,
19090
20452
  awaitLoopback: deps?.awaitLoopback ?? ((state) => awaitLoopbackCode(state)),
20453
+ awaitKimiDevice: deps?.awaitKimiDevice ?? ((fetchImpl) => runKimiDeviceFlow(fetchImpl, resolvedOpenBrowser)),
19091
20454
  tokensFetch: deps?.tokensFetch
19092
20455
  };
20456
+ const resolvedOpenBrowser = resolved.openBrowser;
19093
20457
  const box = resolveSecretBox(values["master-key-file"]);
19094
20458
  setSecretBox(box);
19095
- (0, import_upstreamFetch10.setUpstreamProxyResolver)(createUpstreamProxyResolver());
20459
+ (0, import_upstreamFetch14.setUpstreamProxyResolver)(createUpstreamProxyResolver());
19096
20460
  try {
19097
20461
  const tokensPath = defaultTokensPath(values.config);
19098
- const exchangeFetch = resolved.tokensFetch ?? ((url, init) => (0, import_upstreamFetch10.fetchUpstream)(url, init, { providerId: provider, redactBodies: true }));
20462
+ const exchangeFetch = resolved.tokensFetch ?? ((url, init) => (0, import_upstreamFetch14.fetchUpstream)(url, init, { providerId: provider, redactBodies: true }));
19099
20463
  const store = new JsonSubscriptionCredentialStore(tokensPath, box, exchangeFetch);
19100
20464
  const expiresAt = await runProviderLogin(
19101
20465
  provider,
@@ -19108,19 +20472,20 @@ async function runLogin(argv, deps) {
19108
20472
  console.info(` token: [stored, encrypted] expiresAt: ${expiresAt ?? "n/a"}`);
19109
20473
  } finally {
19110
20474
  setSecretBox(null);
19111
- (0, import_upstreamFetch10.setUpstreamProxyResolver)(null);
20475
+ (0, import_upstreamFetch14.setUpstreamProxyResolver)(null);
19112
20476
  }
19113
20477
  }
19114
20478
  async function runProviderLogin(provider, store, deps, exchangeFetch, label) {
19115
20479
  if (provider === "codex") return loginCodex(store, deps, exchangeFetch, label);
19116
20480
  if (provider === "claude") return loginClaude(store, deps, exchangeFetch, label);
20481
+ if (provider === "kimi") return loginKimi(store, deps, exchangeFetch, label);
19117
20482
  return loginGemini(store, deps, exchangeFetch, label);
19118
20483
  }
19119
20484
  async function loginCodex(store, deps, exchangeFetch, label) {
19120
- const { authUrl, codeVerifier, state } = import_subscriptions7.codexOAuth.generateAuthParams();
20485
+ const { authUrl, codeVerifier, state } = import_subscriptions10.codexOAuth.generateAuthParams();
19121
20486
  await presentUrl(authUrl, deps);
19122
20487
  const code = await deps.awaitLoopback(state);
19123
- const result = await import_subscriptions7.codexOAuth.exchangeCodeForTokens(
20488
+ const result = await import_subscriptions10.codexOAuth.exchangeCodeForTokens(
19124
20489
  { authorizationCode: code, codeVerifier, state },
19125
20490
  exchangeFetch
19126
20491
  );
@@ -19139,7 +20504,7 @@ async function loginCodex(store, deps, exchangeFetch, label) {
19139
20504
  return expiresAt;
19140
20505
  }
19141
20506
  async function loginClaude(store, deps, exchangeFetch, label) {
19142
- const { authUrl, codeVerifier, state } = import_subscriptions7.claudeOAuth.generateAuthParams();
20507
+ const { authUrl, codeVerifier, state } = import_subscriptions10.claudeOAuth.generateAuthParams();
19143
20508
  await presentUrl(authUrl, deps);
19144
20509
  const pasted = (await deps.promptPaste("Paste the authorization code (code#state): ")).trim();
19145
20510
  const [code, pastedState] = pasted.split("#");
@@ -19147,7 +20512,7 @@ async function loginClaude(store, deps, exchangeFetch, label) {
19147
20512
  if (pastedState && pastedState !== state) {
19148
20513
  throw new Error("login: pasted state did not match (possible CSRF) \u2014 aborting");
19149
20514
  }
19150
- const result = await import_subscriptions7.claudeOAuth.exchangeCodeForTokens(
20515
+ const result = await import_subscriptions10.claudeOAuth.exchangeCodeForTokens(
19151
20516
  { authorizationCode: code, codeVerifier, state },
19152
20517
  exchangeFetch
19153
20518
  );
@@ -19166,11 +20531,11 @@ async function loginClaude(store, deps, exchangeFetch, label) {
19166
20531
  return expiresAt;
19167
20532
  }
19168
20533
  async function loginGemini(store, deps, exchangeFetch, label) {
19169
- const { authUrl, codeVerifier } = import_subscriptions7.geminiOAuth.generateAuthParams();
20534
+ const { authUrl, codeVerifier } = import_subscriptions10.geminiOAuth.generateAuthParams();
19170
20535
  await presentUrl(authUrl, deps);
19171
20536
  const code = (await deps.promptPaste("Paste the authorization code: ")).trim();
19172
20537
  if (!code) throw new Error("login: no authorization code was pasted");
19173
- const result = await import_subscriptions7.geminiOAuth.exchangeCodeForTokens(code, codeVerifier, exchangeFetch);
20538
+ const result = await import_subscriptions10.geminiOAuth.exchangeCodeForTokens(code, codeVerifier, exchangeFetch);
19174
20539
  const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
19175
20540
  const block = {
19176
20541
  authMethod: "oauth",
@@ -19184,6 +20549,45 @@ async function loginGemini(store, deps, exchangeFetch, label) {
19184
20549
  logMasked("gemini", result.accessToken);
19185
20550
  return expiresAt;
19186
20551
  }
20552
+ async function runKimiDeviceFlow(exchangeFetch, openBrowserFn) {
20553
+ const deviceId = import_subscriptions10.kimiOAuth.generateKimiDeviceId();
20554
+ const fingerprint = import_subscriptions10.kimiOAuth.kimiFingerprintHeaders(deviceId);
20555
+ const authorization = await import_subscriptions10.kimiOAuth.requestDeviceAuthorization(exchangeFetch, fingerprint);
20556
+ const url = authorization.verificationUriComplete ?? authorization.verificationUri;
20557
+ console.info("Open this URL in your browser and approve the request:");
20558
+ console.info(` ${url}`);
20559
+ if (!authorization.verificationUriComplete) {
20560
+ console.info(` Then enter this code: ${authorization.userCode}`);
20561
+ }
20562
+ await openBrowserFn(url).catch(() => false);
20563
+ const result = await import_subscriptions10.kimiOAuth.awaitDeviceToken(authorization, exchangeFetch, {
20564
+ fingerprint,
20565
+ onPending: () => process.stdout.write(".")
20566
+ });
20567
+ console.info("");
20568
+ return {
20569
+ ...result,
20570
+ accountId: import_subscriptions10.kimiOAuth.kimiAccountIdFromAccessToken(result.accessToken),
20571
+ deviceId
20572
+ };
20573
+ }
20574
+ async function loginKimi(store, deps, exchangeFetch, label) {
20575
+ const result = await deps.awaitKimiDevice(exchangeFetch);
20576
+ const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
20577
+ const block = {
20578
+ authMethod: "oauth",
20579
+ status: "authorized",
20580
+ accessToken: result.accessToken,
20581
+ refreshToken: result.refreshToken,
20582
+ expiresAt,
20583
+ ...result.accountId ? { accountId: result.accountId } : {},
20584
+ deviceId: result.deviceId,
20585
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
20586
+ };
20587
+ await store.appendProviderAccount("kimi", block, label);
20588
+ logMasked("kimi", result.accessToken);
20589
+ return expiresAt;
20590
+ }
19187
20591
  function isLoginProvider(value) {
19188
20592
  return PROVIDERS2.includes(value);
19189
20593
  }
@@ -19381,7 +20785,7 @@ function providersRmKey(configPath, providerId, keyId) {
19381
20785
  }
19382
20786
 
19383
20787
  // src/commands/secrets.ts
19384
- var import_node_fs38 = require("fs");
20788
+ var import_node_fs39 = require("fs");
19385
20789
  var import_node_util9 = require("util");
19386
20790
  async function runSecrets(argv) {
19387
20791
  const { values, positionals } = (0, import_node_util9.parseArgs)({
@@ -19454,12 +20858,12 @@ function secretsStatus(args) {
19454
20858
  reportField("admin.token", cfg.admin.token);
19455
20859
  }
19456
20860
  const tokensPath = defaultTokensPath(args.config);
19457
- if ((0, import_node_fs38.existsSync)(tokensPath)) {
20861
+ if ((0, import_node_fs39.existsSync)(tokensPath)) {
19458
20862
  console.info(`Secret status for ${tokensPath}:`);
19459
20863
  reportTokenFields(tokensPath);
19460
20864
  }
19461
20865
  const integrationsPath = defaultIntegrationsPath(args.config);
19462
- if ((0, import_node_fs38.existsSync)(integrationsPath)) {
20866
+ if ((0, import_node_fs39.existsSync)(integrationsPath)) {
19463
20867
  const state = readRawJson(integrationsPath);
19464
20868
  const key = state.gatewayKey;
19465
20869
  if (key && typeof key === "object" && !Array.isArray(key)) {
@@ -19513,8 +20917,8 @@ async function secretsRotate(args) {
19513
20917
  const integrationsPath = defaultIntegrationsPath(args.config);
19514
20918
  try {
19515
20919
  cfg = loadConfig(args.config);
19516
- if ((0, import_node_fs38.existsSync)(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, oldBox);
19517
- if ((0, import_node_fs38.existsSync)(integrationsPath)) {
20920
+ if ((0, import_node_fs39.existsSync)(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, oldBox);
20921
+ if ((0, import_node_fs39.existsSync)(integrationsPath)) {
19518
20922
  integrationsPlain = new IntegrationStateStore(integrationsPath, oldBox).load();
19519
20923
  }
19520
20924
  } finally {
@@ -19549,20 +20953,20 @@ function secretsDecrypt(args) {
19549
20953
  let tokensPlain = null;
19550
20954
  try {
19551
20955
  cfg = loadConfig(args.config);
19552
- if ((0, import_node_fs38.existsSync)(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, box);
20956
+ if ((0, import_node_fs39.existsSync)(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, box);
19553
20957
  } finally {
19554
20958
  setSecretBox(null);
19555
20959
  }
19556
20960
  saveConfig(args.config, cfg);
19557
20961
  if (tokensPlain) {
19558
- (0, import_node_fs38.writeFileSync)(tokensPath, JSON.stringify(tokensPlain, null, 2) + "\n", "utf8");
20962
+ atomicReplaceUtf8(tokensPath, JSON.stringify(tokensPlain, null, 2) + "\n");
19559
20963
  }
19560
20964
  console.info(`Decrypted secrets to plaintext in ${args.config}` + tokensSuffix(args.config));
19561
20965
  }
19562
20966
  function readRawConfig(path2) {
19563
20967
  let parsed;
19564
20968
  try {
19565
- parsed = JSON.parse((0, import_node_fs38.readFileSync)(path2, "utf8"));
20969
+ parsed = JSON.parse((0, import_node_fs39.readFileSync)(path2, "utf8"));
19566
20970
  } catch {
19567
20971
  throw new Error(`secrets: cannot read or parse '${path2}'`);
19568
20972
  }
@@ -19570,7 +20974,7 @@ function readRawConfig(path2) {
19570
20974
  }
19571
20975
  function readRawJson(path2) {
19572
20976
  try {
19573
- const parsed = JSON.parse((0, import_node_fs38.readFileSync)(path2, "utf8"));
20977
+ const parsed = JSON.parse((0, import_node_fs39.readFileSync)(path2, "utf8"));
19574
20978
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
19575
20979
  return parsed;
19576
20980
  }
@@ -19580,13 +20984,13 @@ function readRawJson(path2) {
19580
20984
  }
19581
20985
  function encryptTokensFileInPlace(configPath, box) {
19582
20986
  const tokensPath = defaultTokensPath(configPath);
19583
- if (!(0, import_node_fs38.existsSync)(tokensPath)) return;
20987
+ if (!(0, import_node_fs39.existsSync)(tokensPath)) return;
19584
20988
  const plain = decryptTokensFile(tokensPath, box);
19585
20989
  writeTokensEncrypted(tokensPath, plain, box);
19586
20990
  }
19587
20991
  function rewriteIntegrationState(configPath, readBox, writeBox) {
19588
20992
  const path2 = defaultIntegrationsPath(configPath);
19589
- if (!(0, import_node_fs38.existsSync)(path2)) return;
20993
+ if (!(0, import_node_fs39.existsSync)(path2)) return;
19590
20994
  const state = new IntegrationStateStore(path2, readBox).load();
19591
20995
  new IntegrationStateStore(path2, writeBox).save(state);
19592
20996
  }
@@ -19599,7 +21003,7 @@ function writeTokensEncrypted(tokensPath, plain, box) {
19599
21003
  { updatedAt: "", ...plain },
19600
21004
  box
19601
21005
  );
19602
- (0, import_node_fs38.writeFileSync)(tokensPath, JSON.stringify(encrypted, null, 2) + "\n", "utf8");
21006
+ atomicReplaceUtf8(tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
19603
21007
  }
19604
21008
  var TOKEN_FIELDS2 = {
19605
21009
  claude: ["accessToken", "refreshToken"],
@@ -19622,7 +21026,7 @@ function walkTokens(raw, fn) {
19622
21026
  return next;
19623
21027
  }
19624
21028
  function tokensSuffix(configPath) {
19625
- return (0, import_node_fs38.existsSync)(defaultTokensPath(configPath)) ? " (+ tokens.json)" : "";
21029
+ return (0, import_node_fs39.existsSync)(defaultTokensPath(configPath)) ? " (+ tokens.json)" : "";
19626
21030
  }
19627
21031
 
19628
21032
  // src/commands/start.ts
@@ -19889,7 +21293,7 @@ async function main() {
19889
21293
  process.exitCode = 1;
19890
21294
  }
19891
21295
  }
19892
- main().catch((err5) => {
19893
- console.error(err5 instanceof Error ? err5.message : String(err5));
21296
+ main().catch((err6) => {
21297
+ console.error(err6 instanceof Error ? err6.message : String(err6));
19894
21298
  process.exitCode = 1;
19895
21299
  });