@omnicross/daemon 0.2.1 → 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.js CHANGED
@@ -1151,7 +1151,7 @@ import {
1151
1151
  } from "@omnicross/core/search";
1152
1152
 
1153
1153
  // src/bootstrap.ts
1154
- import { accessSync, constants as fsConstants, existsSync as existsSync29, mkdirSync as mkdirSync9 } from "fs";
1154
+ import { accessSync, constants as fsConstants, existsSync as existsSync30, mkdirSync as mkdirSync9 } from "fs";
1155
1155
  import { dirname as dirname17 } from "path";
1156
1156
  import { DEFAULT_AUDIT_CONFIG } from "@omnicross/contracts/audit-types";
1157
1157
  import { DEFAULT_BILLING_CONFIG } from "@omnicross/contracts/billing-types";
@@ -1170,14 +1170,14 @@ import { setSubscriptionRegistryForOutbound } from "@omnicross/core/outbound-api
1170
1170
  import { getSharedAccountHealth as getSharedAccountHealth4 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
1171
1171
  import {
1172
1172
  __resetSharedAccountAllowanceStoreForTests,
1173
- AccountAllowanceStore as AccountAllowanceStore3,
1173
+ AccountAllowanceStore as AccountAllowanceStore6,
1174
1174
  setSharedAccountAllowanceStore
1175
1175
  } from "@omnicross/core/pipeline/AccountAllowanceStore";
1176
1176
  import {
1177
1177
  __resetSharedAccountAllowanceSchedulingForTests,
1178
1178
  getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling5
1179
1179
  } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
1180
- import { fetchUpstream as fetchUpstream7, setUpstreamProxyResolver } from "@omnicross/core/pipeline/upstreamFetch";
1180
+ import { fetchUpstream as fetchUpstream11, setUpstreamProxyResolver } from "@omnicross/core/pipeline/upstreamFetch";
1181
1181
  import { __resetSharedIdentityStoreForTests } from "@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore";
1182
1182
  import { setGeminiCodeAssistResolver } from "@omnicross/core/ports/gemini-code-assist-resolver";
1183
1183
  import {
@@ -1307,9 +1307,84 @@ function handleCodexOAuthStatus(sessionId, deps) {
1307
1307
  return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
1308
1308
  }
1309
1309
 
1310
+ // src/admin/accountsKimiOAuth.ts
1311
+ import { kimiOAuth } from "@omnicross/subscriptions";
1312
+ function err2(status, message) {
1313
+ return { status, body: { error: { type: "admin_api_error", message } } };
1314
+ }
1315
+ var DEFAULT_KIMI_OAUTH_TTL_MS = 15 * 6e4;
1316
+ async function handleKimiOAuthStart(deps) {
1317
+ if (deps.kimiSessions.isBusy()) {
1318
+ return err2(409, "a kimi sign-in is already in progress \u2014 finish it in the browser or cancel it");
1319
+ }
1320
+ const fetchImpl = deps.oauthExchangeFetch("kimi");
1321
+ const deviceId = kimiOAuth.generateKimiDeviceId();
1322
+ const fingerprint = kimiOAuth.kimiFingerprintHeaders(deviceId);
1323
+ let authorization;
1324
+ try {
1325
+ authorization = await kimiOAuth.requestDeviceAuthorization(fetchImpl, fingerprint);
1326
+ } catch (e) {
1327
+ const reason = e instanceof Error ? e.message : "device authorization failed";
1328
+ return err2(502, `kimi device authorization failed: ${reason}`);
1329
+ }
1330
+ const { sessionId, signal } = deps.kimiSessions.begin();
1331
+ void runKimiDevicePoll(sessionId, authorization.deviceCode, deviceId, fingerprint, signal, deps).catch(() => deps.kimiSessions.settle(sessionId, "error", "kimi sign-in failed"));
1332
+ return {
1333
+ status: 200,
1334
+ body: {
1335
+ authUrl: authorization.verificationUriComplete ?? authorization.verificationUri,
1336
+ userCode: authorization.userCode,
1337
+ sessionId
1338
+ }
1339
+ };
1340
+ }
1341
+ async function runKimiDevicePoll(sessionId, deviceCode, deviceId, fingerprint, signal, deps) {
1342
+ const fetchImpl = deps.oauthExchangeFetch("kimi");
1343
+ const result = await kimiOAuth.awaitDeviceToken(
1344
+ { userCode: "", deviceCode, verificationUri: "" },
1345
+ fetchImpl,
1346
+ {
1347
+ fingerprint,
1348
+ deadlineMs: DEFAULT_KIMI_OAUTH_TTL_MS,
1349
+ sleep: (ms) => new Promise((resolve11, reject) => {
1350
+ const onAbort = () => {
1351
+ clearTimeout(timer);
1352
+ reject(new Error("login: cancelled"));
1353
+ };
1354
+ const timer = setTimeout(() => {
1355
+ signal.removeEventListener("abort", onAbort);
1356
+ resolve11();
1357
+ }, ms);
1358
+ signal.addEventListener("abort", onAbort, { once: true });
1359
+ })
1360
+ }
1361
+ );
1362
+ const block = {
1363
+ authMethod: "oauth",
1364
+ status: "authorized",
1365
+ accessToken: result.accessToken,
1366
+ refreshToken: result.refreshToken,
1367
+ expiresAt: new Date(Date.now() + result.expiresIn * 1e3).toISOString(),
1368
+ accountId: kimiOAuth.kimiAccountIdFromAccessToken(result.accessToken),
1369
+ deviceId,
1370
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
1371
+ };
1372
+ await deps.subscriptionAccountAppender.appendProviderAccount("kimi", block);
1373
+ deps.kimiSessions.settle(sessionId, "done");
1374
+ }
1375
+ function handleKimiOAuthCancel(sessionId, deps) {
1376
+ if (!deps.kimiSessions.cancel(sessionId)) return err2(404, "unknown or expired kimi sign-in session");
1377
+ return { status: 200, body: { ok: true } };
1378
+ }
1379
+ function handleKimiOAuthStatus(sessionId, deps) {
1380
+ const s = deps.kimiSessions.get(sessionId);
1381
+ if (!s) return err2(404, "unknown or expired kimi sign-in session");
1382
+ return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
1383
+ }
1384
+
1310
1385
  // src/allowance/AccountAllowanceService.ts
1311
1386
  import {
1312
- getSharedAccountAllowanceStore as getSharedAccountAllowanceStore2
1387
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore5
1313
1388
  } from "@omnicross/core/pipeline/AccountAllowanceStore";
1314
1389
  import {
1315
1390
  getSharedAccountAllowanceScheduling
@@ -1343,13 +1418,11 @@ function secondsUntil(instant, now) {
1343
1418
  function windowFromPayload(id, payload, now) {
1344
1419
  const usedPercent = finitePercent(payload?.utilization);
1345
1420
  const resetsAt = isoInstant(payload?.resets_at);
1346
- const isSonnet = id === "seven-day-sonnet";
1347
1421
  const isFiveHour = id === "five-hour";
1348
1422
  return {
1349
1423
  id,
1350
- label: isFiveHour ? "5 hours" : isSonnet ? "7 days \xB7 Sonnet" : "7 days",
1351
- scope: isSonnet ? "model-family" : "all",
1352
- modelFamily: isSonnet ? "sonnet" : void 0,
1424
+ label: isFiveHour ? "5 hours" : "7 days",
1425
+ scope: "all",
1353
1426
  usedPercent,
1354
1427
  windowMinutes: isFiveHour ? 5 * 60 : 7 * 24 * 60,
1355
1428
  resetsAt,
@@ -1357,6 +1430,44 @@ function windowFromPayload(id, payload, now) {
1357
1430
  state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
1358
1431
  };
1359
1432
  }
1433
+ function limitEntryWindow(entries, kind) {
1434
+ const entry = entries.find((candidate) => candidate.kind === kind);
1435
+ if (!entry) return void 0;
1436
+ return { utilization: entry.percent, resets_at: entry.resets_at };
1437
+ }
1438
+ function slugifyDisplayName(name) {
1439
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1440
+ }
1441
+ function scopedWeeklyWindows(entries, now) {
1442
+ const seen = /* @__PURE__ */ new Set();
1443
+ const windows = [];
1444
+ for (const entry of entries) {
1445
+ if (entry.kind !== "weekly_scoped") continue;
1446
+ const displayName = typeof entry.scope?.model?.display_name === "string" && entry.scope.model.display_name.trim() ? entry.scope.model.display_name.trim() : void 0;
1447
+ if (!displayName) continue;
1448
+ const slug = slugifyDisplayName(displayName);
1449
+ if (!slug || seen.has(slug)) continue;
1450
+ seen.add(slug);
1451
+ const usedPercent = finitePercent(entry.percent);
1452
+ const resetsAt = isoInstant(entry.resets_at);
1453
+ windows.push({
1454
+ id: `seven-day-${slug}`,
1455
+ label: `7 days \xB7 ${displayName}`,
1456
+ scope: "model-family",
1457
+ modelFamily: slug,
1458
+ usedPercent,
1459
+ windowMinutes: 7 * 24 * 60,
1460
+ resetsAt,
1461
+ remainingSeconds: secondsUntil(resetsAt, now),
1462
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
1463
+ });
1464
+ }
1465
+ return windows;
1466
+ }
1467
+ function parseLimitEntries(raw) {
1468
+ if (!Array.isArray(raw)) return [];
1469
+ return raw.filter((entry) => !!entry && typeof entry === "object");
1470
+ }
1360
1471
  function emptyClaudeWindows(state) {
1361
1472
  return [
1362
1473
  {
@@ -1374,15 +1485,6 @@ function emptyClaudeWindows(state) {
1374
1485
  usedPercent: null,
1375
1486
  windowMinutes: 7 * 24 * 60,
1376
1487
  state
1377
- },
1378
- {
1379
- id: "seven-day-sonnet",
1380
- label: "7 days \xB7 Sonnet",
1381
- scope: "model-family",
1382
- modelFamily: "sonnet",
1383
- usedPercent: null,
1384
- windowMinutes: 7 * 24 * 60,
1385
- state
1386
1488
  }
1387
1489
  ];
1388
1490
  }
@@ -1463,6 +1565,9 @@ var ClaudeAllowanceCollector = class {
1463
1565
  }
1464
1566
  const now = this.now();
1465
1567
  const usage = payload;
1568
+ const limitEntries = parseLimitEntries(usage.limits);
1569
+ const fiveHour = usage.five_hour ?? limitEntryWindow(limitEntries, "session");
1570
+ const sevenDay = usage.seven_day ?? limitEntryWindow(limitEntries, "weekly_all");
1466
1571
  const snapshot = {
1467
1572
  providerId: "claude",
1468
1573
  accountId,
@@ -1470,10 +1575,10 @@ var ClaudeAllowanceCollector = class {
1470
1575
  observedAt: new Date(now).toISOString(),
1471
1576
  expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
1472
1577
  windows: [
1473
- windowFromPayload("five-hour", usage.five_hour, now),
1474
- windowFromPayload("seven-day", usage.seven_day, now),
1475
- windowFromPayload("seven-day-sonnet", usage.seven_day_sonnet, now)
1476
- ]
1578
+ windowFromPayload("five-hour", fiveHour, now),
1579
+ windowFromPayload("seven-day", sevenDay, now),
1580
+ ...scopedWeeklyWindows(limitEntries, now)
1581
+ ].slice(0, 8)
1477
1582
  };
1478
1583
  this.store.set(snapshot);
1479
1584
  return snapshot;
@@ -1530,6 +1635,607 @@ var ClaudeAllowanceCollector = class {
1530
1635
  }
1531
1636
  };
1532
1637
 
1638
+ // src/allowance/CodexAllowanceCollector.ts
1639
+ import {
1640
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore2
1641
+ } from "@omnicross/core/pipeline/AccountAllowanceStore";
1642
+ import { fetchUpstream as fetchUpstream2 } from "@omnicross/core/pipeline/upstreamFetch";
1643
+ var CODEX_ALLOWANCE_CACHE_MS = 5 * 6e4;
1644
+ var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
1645
+ var CODEX_CLI_USER_AGENT = "codex_cli_rs/0.144.5";
1646
+ function finiteNumber(value) {
1647
+ if (value === null || value === void 0 || value === "") return null;
1648
+ const parsed = typeof value === "number" ? value : Number(value);
1649
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
1650
+ }
1651
+ function finitePercent2(value) {
1652
+ const parsed = finiteNumber(value);
1653
+ return parsed !== null && parsed <= 100 ? parsed : null;
1654
+ }
1655
+ function epochMs(value) {
1656
+ return value > 1e11 ? value : value * 1e3;
1657
+ }
1658
+ function secondsUntil2(instant, now) {
1659
+ if (!instant) return void 0;
1660
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
1661
+ }
1662
+ function decodeJwtClaims(token) {
1663
+ const parts = token.split(".");
1664
+ if (parts.length !== 3) return void 0;
1665
+ try {
1666
+ const json2 = Buffer.from(parts[1], "base64url").toString("utf8");
1667
+ const parsed = JSON.parse(json2);
1668
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
1669
+ } catch {
1670
+ return void 0;
1671
+ }
1672
+ }
1673
+ function chatgptAccountIdFromClaims(claims) {
1674
+ const auth = claims?.["https://api.openai.com/auth"];
1675
+ if (!auth || typeof auth !== "object") return void 0;
1676
+ const accountId = auth.chatgpt_account_id;
1677
+ return typeof accountId === "string" && accountId.trim() ? accountId.trim() : void 0;
1678
+ }
1679
+ function resolveCodexChatGptAccountId(tokens) {
1680
+ if (tokens.accountId?.trim()) return tokens.accountId.trim();
1681
+ if (tokens.idToken) {
1682
+ const fromIdToken = chatgptAccountIdFromClaims(decodeJwtClaims(tokens.idToken));
1683
+ if (fromIdToken) return fromIdToken;
1684
+ }
1685
+ if (tokens.accessToken) {
1686
+ return chatgptAccountIdFromClaims(decodeJwtClaims(tokens.accessToken));
1687
+ }
1688
+ return void 0;
1689
+ }
1690
+ function windowFromPayload2(id, payload, now) {
1691
+ const usedPercent = finitePercent2(payload?.used_percent);
1692
+ const resetAtSeconds = finiteNumber(payload?.reset_at);
1693
+ const resetAfterSeconds = finiteNumber(payload?.reset_after_seconds);
1694
+ const windowSeconds = finiteNumber(payload?.limit_window_seconds);
1695
+ const resetsAt = resetAtSeconds !== null && resetAtSeconds > 0 ? new Date(epochMs(resetAtSeconds)).toISOString() : resetAfterSeconds !== null && resetAfterSeconds > 0 ? new Date(now + resetAfterSeconds * 1e3).toISOString() : void 0;
1696
+ const windowMinutes = windowSeconds !== null && windowSeconds > 0 ? Math.round(windowSeconds / 60) : void 0;
1697
+ return {
1698
+ id,
1699
+ label: id === "primary" ? "Primary" : "Secondary",
1700
+ scope: "all",
1701
+ usedPercent,
1702
+ ...windowMinutes !== void 0 ? { windowMinutes } : {},
1703
+ ...resetsAt !== void 0 ? { resetsAt } : {},
1704
+ remainingSeconds: secondsUntil2(resetsAt, now),
1705
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
1706
+ };
1707
+ }
1708
+ var CodexAllowanceCollector = class {
1709
+ constructor(credentials, store = getSharedAccountAllowanceStore2(), fetchImpl = (url, init, accountId) => fetchUpstream2(url, init, { providerId: "codex", accountId, redactBodies: true }), now = Date.now) {
1710
+ this.credentials = credentials;
1711
+ this.store = store;
1712
+ this.fetchImpl = fetchImpl;
1713
+ this.now = now;
1714
+ }
1715
+ credentials;
1716
+ store;
1717
+ fetchImpl;
1718
+ now;
1719
+ inFlight = /* @__PURE__ */ new Map();
1720
+ async collectMany(accounts, options = {}) {
1721
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
1722
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
1723
+ }
1724
+ collect(account, options = {}) {
1725
+ const now = this.now();
1726
+ const unsupported = account.tokens.authMethod !== "oauth";
1727
+ if (unsupported) {
1728
+ const existing = this.store.get("codex", account.id, now);
1729
+ if (existing?.windows.every((window) => window.state === "unsupported")) {
1730
+ return Promise.resolve(existing);
1731
+ }
1732
+ const snapshot = this.unsupportedSnapshot(account.id, now);
1733
+ this.store.set(snapshot);
1734
+ return Promise.resolve(snapshot);
1735
+ }
1736
+ const cached = this.store.get("codex", account.id, now);
1737
+ if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
1738
+ return Promise.resolve(cached);
1739
+ }
1740
+ const running = this.inFlight.get(account.id);
1741
+ if (running) return running;
1742
+ 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));
1743
+ this.inFlight.set(account.id, promise);
1744
+ return promise;
1745
+ }
1746
+ /**
1747
+ * A response-header snapshot stays a valid cache hit only while fresh; an
1748
+ * active oauth-usage snapshot is honored on the same 5-minute cadence as
1749
+ * Claude's (the poll is cheap and quota is the scheduling input).
1750
+ */
1751
+ isCacheValid(snapshot, now, refreshAheadMs) {
1752
+ if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
1753
+ const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
1754
+ const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
1755
+ return Number.isFinite(expiresAt) && expiresAt > now + ahead;
1756
+ }
1757
+ async fetchAccount(accountId, tokens) {
1758
+ let accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
1759
+ if (!accessToken) {
1760
+ return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
1761
+ }
1762
+ let response = await this.request(accountId, accessToken, tokens);
1763
+ if (response.status === 401) {
1764
+ const refreshed = await this.credentials.refreshAccountToken("codex", accountId);
1765
+ if (!refreshed) {
1766
+ return this.failureSnapshot(accountId, "codex_usage_unauthorized", this.now());
1767
+ }
1768
+ accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
1769
+ if (!accessToken) {
1770
+ return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
1771
+ }
1772
+ response = await this.request(accountId, accessToken, tokens);
1773
+ }
1774
+ if (response.status === 403) {
1775
+ const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "codex_usage_unsupported");
1776
+ this.store.set(snapshot2);
1777
+ return snapshot2;
1778
+ }
1779
+ if (!response.ok) {
1780
+ return this.failureSnapshot(accountId, "codex_usage_http_error", this.now());
1781
+ }
1782
+ let payload;
1783
+ try {
1784
+ payload = await response.json();
1785
+ } catch {
1786
+ return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
1787
+ }
1788
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
1789
+ return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
1790
+ }
1791
+ const now = this.now();
1792
+ const usage = payload.rate_limit;
1793
+ const previous = this.store.get("codex", accountId, now);
1794
+ const snapshot = {
1795
+ providerId: "codex",
1796
+ accountId,
1797
+ source: "oauth-usage-api",
1798
+ observedAt: new Date(now).toISOString(),
1799
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
1800
+ windows: [
1801
+ windowFromPayload2("primary", usage?.primary_window ?? void 0, now),
1802
+ windowFromPayload2("secondary", usage?.secondary_window ?? void 0, now)
1803
+ ],
1804
+ // The wham payload has no ratio field; keep the passively-observed value.
1805
+ ...previous?.primaryOverSecondaryLimitPercent !== void 0 ? { primaryOverSecondaryLimitPercent: previous.primaryOverSecondaryLimitPercent } : {}
1806
+ };
1807
+ this.store.set(snapshot);
1808
+ return snapshot;
1809
+ }
1810
+ request(accountId, accessToken, tokens) {
1811
+ const headers = {
1812
+ Authorization: `Bearer ${accessToken}`,
1813
+ Accept: "application/json",
1814
+ "User-Agent": CODEX_CLI_USER_AGENT
1815
+ };
1816
+ const chatgptAccountId = resolveCodexChatGptAccountId(tokens);
1817
+ if (chatgptAccountId) headers["ChatGPT-Account-Id"] = chatgptAccountId;
1818
+ return this.fetchImpl(CODEX_USAGE_URL, {
1819
+ method: "GET",
1820
+ headers,
1821
+ signal: AbortSignal.timeout(15e3)
1822
+ }, accountId);
1823
+ }
1824
+ failureSnapshot(accountId, code, now) {
1825
+ const existing = this.store.get("codex", accountId, now);
1826
+ const snapshot = existing ? {
1827
+ ...existing,
1828
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
1829
+ windows: existing.windows.map((window) => ({
1830
+ ...window,
1831
+ state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
1832
+ })),
1833
+ lastErrorCode: code
1834
+ } : {
1835
+ providerId: "codex",
1836
+ accountId,
1837
+ source: "oauth-usage-api",
1838
+ observedAt: new Date(now).toISOString(),
1839
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
1840
+ windows: [
1841
+ { id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unavailable" },
1842
+ { id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unavailable" }
1843
+ ],
1844
+ lastErrorCode: code
1845
+ };
1846
+ this.store.set(snapshot);
1847
+ return snapshot;
1848
+ }
1849
+ unsupportedSnapshot(accountId, now, code = "codex_usage_unsupported_auth") {
1850
+ return {
1851
+ providerId: "codex",
1852
+ accountId,
1853
+ source: "oauth-usage-api",
1854
+ observedAt: new Date(now).toISOString(),
1855
+ windows: [
1856
+ { id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unsupported" },
1857
+ { id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unsupported" }
1858
+ ],
1859
+ lastErrorCode: code
1860
+ };
1861
+ }
1862
+ };
1863
+
1864
+ // src/allowance/KimiAllowanceCollector.ts
1865
+ import {
1866
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore3
1867
+ } from "@omnicross/core/pipeline/AccountAllowanceStore";
1868
+ import { fetchUpstream as fetchUpstream3 } from "@omnicross/core/pipeline/upstreamFetch";
1869
+ import { kimiFingerprintHeaders } from "@omnicross/subscriptions";
1870
+ var KIMI_ALLOWANCE_CACHE_MS = 5 * 6e4;
1871
+ var KIMI_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
1872
+ function finiteNumber2(value) {
1873
+ if (value === null || value === void 0 || value === "") return void 0;
1874
+ const parsed = typeof value === "number" ? value : Number(value);
1875
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
1876
+ }
1877
+ function isRecord(value) {
1878
+ return !!value && typeof value === "object" && !Array.isArray(value);
1879
+ }
1880
+ function parseResetMs(row, nowMs) {
1881
+ for (const key of ["reset_at", "resetAt", "reset_time", "resetTime"]) {
1882
+ const value = row[key];
1883
+ if (typeof value === "string" && value.trim()) {
1884
+ const parsed = Date.parse(value);
1885
+ if (Number.isFinite(parsed)) return parsed;
1886
+ }
1887
+ const numeric = finiteNumber2(value);
1888
+ if (numeric !== void 0 && numeric > 1e9) {
1889
+ return numeric > 1e12 ? numeric : numeric * 1e3;
1890
+ }
1891
+ }
1892
+ for (const key of ["reset_in", "resetIn", "ttl", "window"]) {
1893
+ const seconds = finiteNumber2(row[key]);
1894
+ if (seconds !== void 0) return nowMs + seconds * 1e3;
1895
+ }
1896
+ return void 0;
1897
+ }
1898
+ var MINUTE_MS = 6e4;
1899
+ var HOUR_MS = 36e5;
1900
+ var DAY_MS = 864e5;
1901
+ function canonicalWindow(durationMs) {
1902
+ if (durationMs === 5 * HOUR_MS) return { id: "five-hour", label: "5 hours", minutes: 300 };
1903
+ if (durationMs === 7 * DAY_MS) return { id: "seven-day", label: "7 days", minutes: 10080 };
1904
+ if (durationMs > 0 && durationMs % DAY_MS === 0) {
1905
+ const days = durationMs / DAY_MS;
1906
+ return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
1907
+ }
1908
+ if (durationMs > 0 && durationMs % HOUR_MS === 0) {
1909
+ const hours = durationMs / HOUR_MS;
1910
+ return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
1911
+ }
1912
+ return void 0;
1913
+ }
1914
+ function secondsUntil3(instant, now) {
1915
+ if (!instant) return void 0;
1916
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
1917
+ }
1918
+ function windowFromRow(row, fallback, now) {
1919
+ 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;
1920
+ const resetsAt = row?.resetsAtMs !== void 0 ? new Date(row.resetsAtMs).toISOString() : void 0;
1921
+ return {
1922
+ id: fallback.id,
1923
+ label: fallback.label,
1924
+ scope: "all",
1925
+ usedPercent,
1926
+ windowMinutes: fallback.minutes,
1927
+ ...resetsAt !== void 0 ? { resetsAt } : {},
1928
+ remainingSeconds: secondsUntil3(resetsAt, now),
1929
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
1930
+ };
1931
+ }
1932
+ function parseKimiUsagePayload(payload, now) {
1933
+ if (!isRecord(payload)) return [];
1934
+ const byId = /* @__PURE__ */ new Map();
1935
+ const rowFrom = (data) => {
1936
+ const limit = finiteNumber2(data["limit"]);
1937
+ let used = finiteNumber2(data["used"]);
1938
+ const remaining = finiteNumber2(data["remaining"]);
1939
+ if (used === void 0 && remaining !== void 0 && limit !== void 0) {
1940
+ used = limit - remaining;
1941
+ }
1942
+ let windowDurationMs;
1943
+ const windowData = isRecord(data["window"]) ? data["window"] : void 0;
1944
+ const duration = finiteNumber2(windowData?.["duration"]);
1945
+ const timeUnit = typeof windowData?.["timeUnit"] === "string" ? windowData["timeUnit"].toUpperCase() : "";
1946
+ if (duration !== void 0) {
1947
+ if (timeUnit.includes("MINUTE")) windowDurationMs = duration * MINUTE_MS;
1948
+ else if (timeUnit.includes("HOUR")) windowDurationMs = duration * HOUR_MS;
1949
+ else if (timeUnit.includes("DAY")) windowDurationMs = duration * DAY_MS;
1950
+ else if (timeUnit.includes("WEEK")) windowDurationMs = duration * 7 * DAY_MS;
1951
+ else if (timeUnit.includes("SECOND")) windowDurationMs = duration * 1e3;
1952
+ }
1953
+ const resetsAtMs = parseResetMs(windowData && parseResetMs(windowData, now) !== void 0 ? windowData : data, now);
1954
+ return { used, limit, remaining, ...resetsAtMs !== void 0 ? { resetsAtMs } : {}, ...windowDurationMs !== void 0 ? { windowDurationMs } : {} };
1955
+ };
1956
+ if (isRecord(payload["usage"])) {
1957
+ const row = rowFrom(payload["usage"]);
1958
+ const window = windowFromRow({ ...row, resetsAtMs: row.resetsAtMs }, { id: "seven-day", label: "7 days", minutes: 10080 }, now);
1959
+ byId.set("seven-day", window);
1960
+ }
1961
+ if (Array.isArray(payload["limits"])) {
1962
+ for (const item of payload["limits"]) {
1963
+ if (!isRecord(item)) continue;
1964
+ const detail = isRecord(item["detail"]) ? item["detail"] : item;
1965
+ const row = rowFrom(detail);
1966
+ const canonical = row.windowDurationMs !== void 0 ? canonicalWindow(row.windowDurationMs) : void 0;
1967
+ if (!canonical) continue;
1968
+ const window = windowFromRow(row, canonical, now);
1969
+ const existing = byId.get(canonical.id);
1970
+ if (!existing || (window.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
1971
+ byId.set(canonical.id, window);
1972
+ }
1973
+ }
1974
+ }
1975
+ return [...byId.values()].sort((a, b) => (a.windowMinutes ?? Infinity) - (b.windowMinutes ?? Infinity)).slice(0, 4);
1976
+ }
1977
+ var KimiAllowanceCollector = class {
1978
+ constructor(credentials, store = getSharedAccountAllowanceStore3(), fetchImpl = (url, init, accountId) => fetchUpstream3(url, init, { providerId: "kimi", accountId, redactBodies: true }), now = Date.now) {
1979
+ this.credentials = credentials;
1980
+ this.store = store;
1981
+ this.fetchImpl = fetchImpl;
1982
+ this.now = now;
1983
+ }
1984
+ credentials;
1985
+ store;
1986
+ fetchImpl;
1987
+ now;
1988
+ inFlight = /* @__PURE__ */ new Map();
1989
+ async collectMany(accounts, options = {}) {
1990
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
1991
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
1992
+ }
1993
+ collect(account, options = {}) {
1994
+ const now = this.now();
1995
+ if (account.tokens.authMethod !== "oauth") {
1996
+ const existing = this.store.get("kimi", account.id, now);
1997
+ if (existing?.windows.every((window) => window.state === "unsupported")) {
1998
+ return Promise.resolve(existing);
1999
+ }
2000
+ const snapshot = this.unsupportedSnapshot(account.id, now);
2001
+ this.store.set(snapshot);
2002
+ return Promise.resolve(snapshot);
2003
+ }
2004
+ const cached = this.store.get("kimi", account.id, now);
2005
+ if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
2006
+ return Promise.resolve(cached);
2007
+ }
2008
+ const running = this.inFlight.get(account.id);
2009
+ if (running) return running;
2010
+ 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));
2011
+ this.inFlight.set(account.id, promise);
2012
+ return promise;
2013
+ }
2014
+ isCacheValid(snapshot, now, refreshAheadMs) {
2015
+ if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
2016
+ const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
2017
+ const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
2018
+ return Number.isFinite(expiresAt) && expiresAt > now + ahead;
2019
+ }
2020
+ async fetchAccount(accountId, tokens) {
2021
+ let accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
2022
+ if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
2023
+ let response = await this.request(accountId, accessToken, tokens);
2024
+ if (response.status === 401) {
2025
+ const refreshed = await this.credentials.refreshAccountToken("kimi", accountId);
2026
+ if (!refreshed) return this.failureSnapshot(accountId, "kimi_usage_unauthorized", this.now());
2027
+ accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
2028
+ if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
2029
+ response = await this.request(accountId, accessToken, tokens);
2030
+ }
2031
+ if (response.status === 403) {
2032
+ const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "kimi_usage_unsupported");
2033
+ this.store.set(snapshot2);
2034
+ return snapshot2;
2035
+ }
2036
+ if (!response.ok) return this.failureSnapshot(accountId, "kimi_usage_http_error", this.now());
2037
+ let payload;
2038
+ try {
2039
+ payload = await response.json();
2040
+ } catch {
2041
+ return this.failureSnapshot(accountId, "kimi_usage_invalid_response", this.now());
2042
+ }
2043
+ const now = this.now();
2044
+ const windows = parseKimiUsagePayload(payload, now);
2045
+ const snapshot = {
2046
+ providerId: "kimi",
2047
+ accountId,
2048
+ source: "oauth-usage-api",
2049
+ observedAt: new Date(now).toISOString(),
2050
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
2051
+ windows: windows.length > 0 ? windows : [
2052
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
2053
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
2054
+ ],
2055
+ ...windows.length > 0 ? {} : { lastErrorCode: "kimi_usage_invalid_response" }
2056
+ };
2057
+ this.store.set(snapshot);
2058
+ return snapshot;
2059
+ }
2060
+ request(accountId, accessToken, tokens) {
2061
+ return this.fetchImpl(KIMI_USAGE_URL, {
2062
+ method: "GET",
2063
+ headers: {
2064
+ Authorization: `Bearer ${accessToken}`,
2065
+ Accept: "application/json",
2066
+ ...kimiFingerprintHeaders(tokens.deviceId)
2067
+ },
2068
+ signal: AbortSignal.timeout(15e3)
2069
+ }, accountId);
2070
+ }
2071
+ failureSnapshot(accountId, code, now) {
2072
+ const existing = this.store.get("kimi", accountId, now);
2073
+ const snapshot = existing ? {
2074
+ ...existing,
2075
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
2076
+ windows: existing.windows.map((window) => ({
2077
+ ...window,
2078
+ state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
2079
+ })),
2080
+ lastErrorCode: code
2081
+ } : {
2082
+ providerId: "kimi",
2083
+ accountId,
2084
+ source: "oauth-usage-api",
2085
+ observedAt: new Date(now).toISOString(),
2086
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
2087
+ windows: [
2088
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
2089
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
2090
+ ],
2091
+ lastErrorCode: code
2092
+ };
2093
+ this.store.set(snapshot);
2094
+ return snapshot;
2095
+ }
2096
+ unsupportedSnapshot(accountId, now, code = "kimi_usage_unsupported_auth") {
2097
+ return {
2098
+ providerId: "kimi",
2099
+ accountId,
2100
+ source: "oauth-usage-api",
2101
+ observedAt: new Date(now).toISOString(),
2102
+ windows: [
2103
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unsupported" },
2104
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unsupported" }
2105
+ ],
2106
+ lastErrorCode: code
2107
+ };
2108
+ }
2109
+ };
2110
+
2111
+ // src/allowance/OpenCodeGoAllowanceCollector.ts
2112
+ import {
2113
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore4
2114
+ } from "@omnicross/core/pipeline/AccountAllowanceStore";
2115
+ import { fetchUpstream as fetchUpstream4 } from "@omnicross/core/pipeline/upstreamFetch";
2116
+ import { normalizeOpenCodeGoBaseUrl } from "@omnicross/subscriptions";
2117
+ var OPENCODEGO_ALLOWANCE_CACHE_MS = 5 * 6e4;
2118
+ var OPENCODEGO_DEFAULT_GO_BASE = "https://opencode.ai/zen/go";
2119
+ function finitePercent3(value) {
2120
+ if (value === null || value === void 0 || value === "") return null;
2121
+ const parsed = typeof value === "number" ? value : Number(value);
2122
+ return Number.isFinite(parsed) && parsed >= 0 && parsed <= 100 ? parsed : null;
2123
+ }
2124
+ function isoInstant2(value) {
2125
+ if (typeof value !== "string" || !value.trim()) return void 0;
2126
+ const time = Date.parse(value);
2127
+ return Number.isFinite(time) ? new Date(time).toISOString() : void 0;
2128
+ }
2129
+ function secondsUntil4(instant, now) {
2130
+ if (!instant) return void 0;
2131
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
2132
+ }
2133
+ function windowFromPayload3(id, label, minutes, payload, now) {
2134
+ const statusRateLimited = payload?.status === "rate-limited";
2135
+ const usedPercent = statusRateLimited ? 100 : finitePercent3(payload?.percent);
2136
+ const resetsAt = isoInstant2(payload?.resetsAt);
2137
+ return {
2138
+ id,
2139
+ label,
2140
+ scope: "all",
2141
+ usedPercent,
2142
+ windowMinutes: minutes,
2143
+ ...resetsAt !== void 0 ? { resetsAt } : {},
2144
+ remainingSeconds: secondsUntil4(resetsAt, now),
2145
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
2146
+ };
2147
+ }
2148
+ var OpenCodeGoAllowanceCollector = class {
2149
+ constructor(credentials, store = getSharedAccountAllowanceStore4(), fetchImpl = (url, init, accountId) => fetchUpstream4(url, init, { providerId: "opencodego", accountId, redactBodies: true }), now = Date.now) {
2150
+ this.credentials = credentials;
2151
+ this.store = store;
2152
+ this.fetchImpl = fetchImpl;
2153
+ this.now = now;
2154
+ }
2155
+ credentials;
2156
+ store;
2157
+ fetchImpl;
2158
+ now;
2159
+ inFlight = /* @__PURE__ */ new Map();
2160
+ async collectMany(accounts, options = {}) {
2161
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
2162
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
2163
+ }
2164
+ collect(account, options = {}) {
2165
+ const now = this.now();
2166
+ const cached = this.store.get("opencodego", account.id, now);
2167
+ if (!options.force && cached && (cached.windows.every((window) => window.state === "unsupported") || cached.expiresAt && Date.parse(cached.expiresAt) > now + (options.refreshAheadMs ?? 0))) {
2168
+ return Promise.resolve(cached);
2169
+ }
2170
+ const running = this.inFlight.get(account.id);
2171
+ if (running) return running;
2172
+ const promise = this.fetchAccount(account).catch(() => this.failureSnapshot(account.id, this.now())).finally(() => this.inFlight.delete(account.id));
2173
+ this.inFlight.set(account.id, promise);
2174
+ return promise;
2175
+ }
2176
+ async fetchAccount(account) {
2177
+ const apiKey = await this.credentials.getAccessTokenForAccount("opencodego", account.id);
2178
+ if (!apiKey) return this.failureSnapshot(account.id, this.now());
2179
+ const base = account.tokens.baseUrl ? normalizeOpenCodeGoBaseUrl(account.tokens.baseUrl) : OPENCODEGO_DEFAULT_GO_BASE;
2180
+ const response = await this.fetchImpl(`${base}/v1/usage`, {
2181
+ method: "GET",
2182
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
2183
+ signal: AbortSignal.timeout(15e3)
2184
+ }, account.id);
2185
+ if (response.status === 401 || response.status === 403) {
2186
+ return this.failureSnapshot(account.id, this.now(), "opencodego_usage_unauthorized");
2187
+ }
2188
+ if (!response.ok) return this.failureSnapshot(account.id, this.now());
2189
+ let payload;
2190
+ try {
2191
+ payload = await response.json();
2192
+ } catch {
2193
+ return this.failureSnapshot(account.id, this.now());
2194
+ }
2195
+ const usage = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.usage : void 0;
2196
+ const now = this.now();
2197
+ const snapshot = {
2198
+ providerId: "opencodego",
2199
+ accountId: account.id,
2200
+ source: "oauth-usage-api",
2201
+ observedAt: new Date(now).toISOString(),
2202
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
2203
+ // Monthly deliberately omitted (module doc).
2204
+ windows: [
2205
+ windowFromPayload3("five-hour", "5 hours", 5 * 60, usage?.rolling ?? void 0, now),
2206
+ windowFromPayload3("seven-day", "7 days", 7 * 24 * 60, usage?.weekly ?? void 0, now)
2207
+ ]
2208
+ };
2209
+ this.store.set(snapshot);
2210
+ return snapshot;
2211
+ }
2212
+ failureSnapshot(accountId, now, code = "opencodego_usage_request_failed") {
2213
+ const existing = this.store.get("opencodego", accountId, now);
2214
+ const snapshot = existing ? {
2215
+ ...existing,
2216
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
2217
+ windows: existing.windows.map((window) => ({
2218
+ ...window,
2219
+ state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
2220
+ })),
2221
+ lastErrorCode: code
2222
+ } : {
2223
+ providerId: "opencodego",
2224
+ accountId,
2225
+ source: "oauth-usage-api",
2226
+ observedAt: new Date(now).toISOString(),
2227
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
2228
+ windows: [
2229
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
2230
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
2231
+ ],
2232
+ lastErrorCode: code
2233
+ };
2234
+ this.store.set(snapshot);
2235
+ return snapshot;
2236
+ }
2237
+ };
2238
+
1533
2239
  // src/allowance/AccountAllowanceService.ts
1534
2240
  function codexUnavailable(accountId, now) {
1535
2241
  return {
@@ -1545,26 +2251,30 @@ function codexUnavailable(accountId, now) {
1545
2251
  };
1546
2252
  }
1547
2253
  var AccountAllowanceService = class {
1548
- constructor(credentials, store = getSharedAccountAllowanceStore2(), collector, now = Date.now) {
2254
+ constructor(credentials, store = getSharedAccountAllowanceStore5(), collector, codexCollector, kimiCollector, opencodegoCollector, now = Date.now) {
1549
2255
  this.credentials = credentials;
1550
2256
  this.store = store;
1551
2257
  this.now = now;
1552
2258
  this.claudeCollector = collector ?? new ClaudeAllowanceCollector(credentials, store);
2259
+ this.codexCollector = codexCollector ?? new CodexAllowanceCollector(credentials, store);
2260
+ this.kimiCollector = kimiCollector ?? new KimiAllowanceCollector(credentials, store);
2261
+ this.opencodegoCollector = opencodegoCollector ?? new OpenCodeGoAllowanceCollector(credentials, store);
1553
2262
  }
1554
2263
  credentials;
1555
2264
  store;
1556
2265
  now;
1557
2266
  claudeCollector;
2267
+ codexCollector;
2268
+ kimiCollector;
2269
+ opencodegoCollector;
1558
2270
  /**
1559
- * Read all/filtered snapshots. Claude's five-minute cache is refreshed lazily;
1560
- * Codex remains passive and reports not-observed until a real model response.
2271
+ * Read all/filtered snapshots. Claude's and Codex's five-minute caches are
2272
+ * refreshed lazily on read (Codex polls `/backend-api/wham/usage`; the
2273
+ * passive `x-codex-*` header tap still feeds mid-flight updates).
1561
2274
  */
1562
2275
  async list(filter = {}) {
1563
2276
  const config = await this.credentials.getFullConfig();
1564
- this.store.pruneToKnownAccounts([
1565
- ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
1566
- ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
1567
- ]);
2277
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1568
2278
  const wantsClaude = !filter.providerId || filter.providerId === "claude";
1569
2279
  const claudeAccounts = (config.claudeAccounts ?? []).filter(
1570
2280
  (account) => !filter.accountId || account.id === filter.accountId
@@ -1575,39 +2285,90 @@ var AccountAllowanceService = class {
1575
2285
  (account) => !filter.accountId || account.id === filter.accountId
1576
2286
  );
1577
2287
  if (wantsCodex) {
2288
+ await this.codexCollector.collectMany(codexAccounts);
1578
2289
  for (const account of codexAccounts) {
1579
2290
  if (!this.store.get("codex", account.id)) this.store.set(codexUnavailable(account.id, this.now()));
1580
2291
  }
1581
2292
  }
2293
+ const wantsKimi = !filter.providerId || filter.providerId === "kimi";
2294
+ const kimiAccounts = (config.kimiAccounts ?? []).filter(
2295
+ (account) => !filter.accountId || account.id === filter.accountId
2296
+ );
2297
+ if (wantsKimi) await this.kimiCollector.collectMany(kimiAccounts);
2298
+ const wantsOpenCodeGo = !filter.providerId || filter.providerId === "opencodego";
2299
+ const opencodegoAccounts = (config.opencodegoAccounts ?? []).filter(
2300
+ (account) => !filter.accountId || account.id === filter.accountId
2301
+ );
2302
+ if (wantsOpenCodeGo) await this.opencodegoCollector.collectMany(opencodegoAccounts);
1582
2303
  const known = /* @__PURE__ */ new Set();
1583
2304
  if (wantsClaude) for (const account of claudeAccounts) known.add(`claude\0${account.id}`);
1584
2305
  if (wantsCodex) for (const account of codexAccounts) known.add(`codex\0${account.id}`);
2306
+ if (wantsKimi) for (const account of kimiAccounts) known.add(`kimi\0${account.id}`);
2307
+ if (wantsOpenCodeGo) for (const account of opencodegoAccounts) known.add(`opencodego\0${account.id}`);
1585
2308
  return this.store.list(filter).filter((snapshot) => known.has(`${snapshot.providerId}\0${snapshot.accountId}`));
1586
2309
  }
2310
+ knownAccounts(config) {
2311
+ return [
2312
+ ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
2313
+ ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id })),
2314
+ ...(config.kimiAccounts ?? []).map((account) => ({ providerId: "kimi", accountId: account.id })),
2315
+ ...(config.opencodegoAccounts ?? []).map((account) => ({ providerId: "opencodego", accountId: account.id }))
2316
+ ];
2317
+ }
1587
2318
  /** Force-refresh Claude usage for one account or every stored Claude account. */
1588
2319
  async refreshClaude(accountId) {
1589
2320
  const config = await this.credentials.getFullConfig();
1590
- this.store.pruneToKnownAccounts([
1591
- ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
1592
- ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
1593
- ]);
2321
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1594
2322
  const accounts = (config.claudeAccounts ?? []).filter(
1595
2323
  (account) => !accountId || account.id === accountId
1596
2324
  );
1597
- return this.claudeCollector.collectMany(accounts, { force: true });
2325
+ return this.claudeCollector.collectMany(accounts, { force: true });
2326
+ }
2327
+ /**
2328
+ * Force-refresh Codex usage (`/backend-api/wham/usage`) for one account or
2329
+ * every stored Codex account. Replaces the old probe-request workaround —
2330
+ * no quota is spent reading the usage endpoint.
2331
+ */
2332
+ async refreshCodex(accountId) {
2333
+ const config = await this.credentials.getFullConfig();
2334
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
2335
+ const accounts = (config.codexAccounts ?? []).filter(
2336
+ (account) => !accountId || account.id === accountId
2337
+ );
2338
+ return this.codexCollector.collectMany(accounts, { force: true });
2339
+ }
2340
+ /** Force-refresh OpenCodeGo usage (`{go}/v1/usage`) for one/all accounts. */
2341
+ async refreshOpenCodeGo(accountId) {
2342
+ const config = await this.credentials.getFullConfig();
2343
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
2344
+ const accounts = (config.opencodegoAccounts ?? []).filter(
2345
+ (account) => !accountId || account.id === accountId
2346
+ );
2347
+ return this.opencodegoCollector.collectMany(accounts, { force: true });
2348
+ }
2349
+ /** Force-refresh Kimi usage (`/coding/v1/usages`) for one/all accounts. */
2350
+ async refreshKimi(accountId) {
2351
+ const config = await this.credentials.getFullConfig();
2352
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
2353
+ const accounts = (config.kimiAccounts ?? []).filter(
2354
+ (account) => !accountId || account.id === accountId
2355
+ );
2356
+ return this.kimiCollector.collectMany(accounts, { force: true });
1598
2357
  }
1599
2358
  /**
1600
- * Keep Claude snapshots warm for allowance-aware routing. This deliberately
1601
- * excludes Codex (whose quota is learned from real response headers) and
1602
- * preserves the collector's cache + per-account in-flight coalescing.
2359
+ * Keep Claude + Codex + Kimi snapshots warm for allowance-aware routing. All
2360
+ * collectors preserve their cache + per-account in-flight coalescing; a tick
2361
+ * normally performs no network I/O. (Codex joined the warm path when it
2362
+ * gained an active `/wham/usage` collector — the passive `x-codex-*` header
2363
+ * tap alone could not keep the policy fed while idle.)
1603
2364
  */
1604
2365
  async maintainClaudeCache(refreshAheadMs) {
1605
2366
  const config = await this.credentials.getFullConfig();
1606
- this.store.pruneToKnownAccounts([
1607
- ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
1608
- ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
1609
- ]);
2367
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1610
2368
  await this.claudeCollector.collectMany(config.claudeAccounts ?? [], { refreshAheadMs });
2369
+ await this.codexCollector.collectMany(config.codexAccounts ?? [], { refreshAheadMs });
2370
+ await this.kimiCollector.collectMany(config.kimiAccounts ?? [], { refreshAheadMs });
2371
+ await this.opencodegoCollector.collectMany(config.opencodegoAccounts ?? [], { refreshAheadMs });
1611
2372
  }
1612
2373
  /** Remove a cache row as soon as an account is deleted by the admin path. */
1613
2374
  removeAccountSnapshot(providerId, accountId) {
@@ -2037,7 +2798,7 @@ import {
2037
2798
  } from "@omnicross/contracts/image-generation-types";
2038
2799
  import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling2 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
2039
2800
  import { getSharedAccountHealth } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
2040
- import { fetchUpstream as fetchUpstream2 } from "@omnicross/core/pipeline/upstreamFetch";
2801
+ import { fetchUpstream as fetchUpstream5 } from "@omnicross/core/pipeline/upstreamFetch";
2041
2802
 
2042
2803
  // src/image-generation/imagesConfigValidation.ts
2043
2804
  import { validateImagesServerConfig } from "@omnicross/core/outbound-api";
@@ -3779,7 +4540,8 @@ var VALID_PROVIDER_IDS = [
3779
4540
  "claude",
3780
4541
  "codex",
3781
4542
  "gemini",
3782
- "opencodego"
4543
+ "opencodego",
4544
+ "kimi"
3783
4545
  ];
3784
4546
  function asSubscriptionProviderId(id) {
3785
4547
  return VALID_PROVIDER_IDS.includes(id) ? id : null;
@@ -3915,6 +4677,18 @@ function validateGemini(body) {
3915
4677
  copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "lastRefreshedAt", "errorMessage"]);
3916
4678
  return out;
3917
4679
  }
4680
+ function validateKimi(body) {
4681
+ const authMethod = str(body["authMethod"]);
4682
+ const status = str(body["status"]);
4683
+ if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
4684
+ if (!status || !TOKEN_STATUSES.has(status)) return null;
4685
+ const out = {
4686
+ authMethod,
4687
+ status
4688
+ };
4689
+ copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "accountId", "deviceId", "lastRefreshedAt", "errorMessage"]);
4690
+ return out;
4691
+ }
3918
4692
  function validateOpenCodeGo(body) {
3919
4693
  const authMethod = str(body["authMethod"]);
3920
4694
  const status = str(body["status"]);
@@ -3950,6 +4724,8 @@ function validateTokenBody(providerId, body) {
3950
4724
  return validateGemini(body);
3951
4725
  case "opencodego":
3952
4726
  return validateOpenCodeGo(body);
4727
+ case "kimi":
4728
+ return validateKimi(body);
3953
4729
  default:
3954
4730
  return null;
3955
4731
  }
@@ -3979,12 +4755,12 @@ async function statusEntryFor(reader, providerId) {
3979
4755
 
3980
4756
  // src/admin/accountsOAuth.ts
3981
4757
  var OAUTH_HTTP_PROVIDERS = /* @__PURE__ */ new Set(["claude", "gemini"]);
3982
- function err2(status, message) {
4758
+ function err3(status, message) {
3983
4759
  return { status, body: { error: { type: "admin_api_error", message } } };
3984
4760
  }
3985
4761
  function handleOAuthStart(providerId, deps) {
3986
4762
  if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
3987
- return err2(400, `oauth not available for provider '${providerId}'`);
4763
+ return err3(400, `oauth not available for provider '${providerId}'`);
3988
4764
  }
3989
4765
  const flow = providerId === "claude" ? claudeOAuth : geminiOAuth;
3990
4766
  const { authUrl, codeVerifier, state } = flow.generateAuthParams();
@@ -3993,23 +4769,23 @@ function handleOAuthStart(providerId, deps) {
3993
4769
  }
3994
4770
  async function handleOAuthComplete(providerId, body, deps) {
3995
4771
  if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
3996
- return err2(400, `oauth not available for provider '${providerId}'`);
4772
+ return err3(400, `oauth not available for provider '${providerId}'`);
3997
4773
  }
3998
4774
  const sessionId = typeof body["sessionId"] === "string" ? body["sessionId"] : "";
3999
4775
  const rawCode = typeof body["code"] === "string" ? body["code"] : "";
4000
- if (!sessionId) return err2(400, "oauth complete requires { sessionId }");
4001
- if (!rawCode) return err2(400, "oauth complete requires { code }");
4776
+ if (!sessionId) return err3(400, "oauth complete requires { sessionId }");
4777
+ if (!rawCode) return err3(400, "oauth complete requires { code }");
4002
4778
  const session = deps.oauthSessions.peek(sessionId);
4003
- if (!session) return err2(410, "oauth session is unknown, expired, or already used");
4779
+ if (!session) return err3(410, "oauth session is unknown, expired, or already used");
4004
4780
  if (session.providerId !== providerId) {
4005
- return err2(400, `oauth session does not match provider '${providerId}'`);
4781
+ return err3(400, `oauth session does not match provider '${providerId}'`);
4006
4782
  }
4007
4783
  let code = rawCode.trim();
4008
4784
  if (providerId === "claude") {
4009
4785
  const [splitCode, pastedState] = code.split("#");
4010
- if (!splitCode) return err2(400, "no authorization code was provided");
4786
+ if (!splitCode) return err3(400, "no authorization code was provided");
4011
4787
  if (pastedState && pastedState !== session.state) {
4012
- return err2(400, "oauth state did not match (possible CSRF) \u2014 aborting");
4788
+ return err3(400, "oauth state did not match (possible CSRF) \u2014 aborting");
4013
4789
  }
4014
4790
  code = splitCode;
4015
4791
  }
@@ -4019,7 +4795,7 @@ async function handleOAuthComplete(providerId, body, deps) {
4019
4795
  block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state, exchangeFetch) : await exchangeGemini(code, session.codeVerifier, exchangeFetch);
4020
4796
  } catch (exchangeError) {
4021
4797
  const reason = exchangeError instanceof Error ? exchangeError.message : "token exchange failed";
4022
- return err2(502, `oauth token exchange failed for '${providerId}': ${reason}`);
4798
+ return err3(502, `oauth token exchange failed for '${providerId}': ${reason}`);
4023
4799
  }
4024
4800
  deps.oauthSessions.consume(sessionId);
4025
4801
  const label = typeof body["label"] === "string" && body["label"].trim() ? body["label"].trim() : void 0;
@@ -4357,8 +5133,8 @@ function errBody(message) {
4357
5133
  return { error: { type: "admin_api_error", message } };
4358
5134
  }
4359
5135
  var defaultCommandRunner = (command) => new Promise((resolve11) => {
4360
- exec(command, { timeout: 18e4 }, (err5, _stdout, stderr) => {
4361
- if (err5) resolve11({ ok: false, error: stderr.trim() || err5.message });
5136
+ exec(command, { timeout: 18e4 }, (err6, _stdout, stderr) => {
5137
+ if (err6) resolve11({ ok: false, error: stderr.trim() || err6.message });
4362
5138
  else resolve11({ ok: true });
4363
5139
  });
4364
5140
  });
@@ -4404,8 +5180,8 @@ async function handleCliLaunch(cli, body, ctx) {
4404
5180
  providerId: typeof body["providerId"] === "string" ? body["providerId"] : void 0,
4405
5181
  model: typeof body["model"] === "string" ? body["model"] : void 0
4406
5182
  });
4407
- } catch (err5) {
4408
- return { status: 400, body: errBody(err5 instanceof Error ? err5.message : "no launch target") };
5183
+ } catch (err6) {
5184
+ return { status: 400, body: errBody(err6 instanceof Error ? err6.message : "no launch target") };
4409
5185
  }
4410
5186
  const id = randomUUID2();
4411
5187
  let leaseId2;
@@ -4433,9 +5209,9 @@ async function handleCliLaunch(cli, body, ctx) {
4433
5209
  } else {
4434
5210
  launch = await buildLaunchEnv(cli, ctx.llmConfig, target);
4435
5211
  }
4436
- } catch (err5) {
4437
- const status = err5 instanceof RouteLeaseError2 ? err5.status : 400;
4438
- return { status, body: errBody(err5 instanceof Error ? err5.message : "failed to build launch env") };
5212
+ } catch (err6) {
5213
+ const status = err6 instanceof RouteLeaseError2 ? err6.status : 400;
5214
+ return { status, body: errBody(err6 instanceof Error ? err6.message : "failed to build launch env") };
4439
5215
  }
4440
5216
  const cwd = typeof body["cwd"] === "string" && body["cwd"].trim() ? body["cwd"].trim() : void 0;
4441
5217
  const opener = ctx.opener ?? defaultTerminalOpener;
@@ -4463,9 +5239,9 @@ async function handleCliLaunch(cli, body, ctx) {
4463
5239
  onFailure: onSessionEnd
4464
5240
  });
4465
5241
  if (cleanup) openerCleanup = cleanup;
4466
- } catch (err5) {
5242
+ } catch (err6) {
4467
5243
  onSessionEnd();
4468
- return { status: 500, body: errBody(err5 instanceof Error ? err5.message : "failed to open terminal") };
5244
+ return { status: 500, body: errBody(err6 instanceof Error ? err6.message : "failed to open terminal") };
4469
5245
  }
4470
5246
  if (ended) {
4471
5247
  openerCleanup?.();
@@ -4599,7 +5375,8 @@ async function handleDashboard(deps) {
4599
5375
  // src/admin/searchAdminApi.ts
4600
5376
  import { DEFAULT_SEARCH_SERVER_CONFIG, loadServerConfig } from "@omnicross/core/outbound-api";
4601
5377
  import { apiSearchContributions as apiSearchContributions2 } from "@omnicross/core/search/api";
4602
- import { builtinHttpSearchContributions as builtinHttpSearchContributions3, createSearchHttpTransport } from "@omnicross/core/search/http";
5378
+ import { builtinHttpSearchContributions as builtinHttpSearchContributions3, createSearchHttpTransport as createSearchHttpTransport2 } from "@omnicross/core/search/http";
5379
+ import { createSearchRuntime as createSearchRuntime2 } from "@omnicross/core/search";
4603
5380
 
4604
5381
  // src/search/searchDoctorProjection.ts
4605
5382
  import { toSearchErrorShape } from "@omnicross/contracts/search-types";
@@ -4665,7 +5442,7 @@ function buildSearchDoctorSnapshot(contributions = builtinHttpSearchContribution
4665
5442
  }
4666
5443
  return rows;
4667
5444
  }
4668
- var SEARCH_DOCTOR_QUERY = "mozilla developer network http headers";
5445
+ var SEARCH_DOCTOR_QUERY = "MDN HTTP headers documentation";
4669
5446
  function classifyLiveSearchOutcome(providerId, outcome, checkedAt) {
4670
5447
  if (outcome.kind === "results") {
4671
5448
  if (outcome.count > 0) return { providerId, status: "healthy", checkedAt };
@@ -4724,9 +5501,13 @@ async function runSearchLiveChecks(contributions, now = () => (/* @__PURE__ */ n
4724
5501
  }
4725
5502
 
4726
5503
  // src/search/SearchAssembly.ts
5504
+ import { resolveUpstreamDispatcher } from "@omnicross/core/pipeline/upstreamFetch";
4727
5505
  import { createSearchRuntime } from "@omnicross/core/search";
4728
5506
  import { apiSearchContributions } from "@omnicross/core/search/api";
4729
- import { builtinHttpSearchContributions as builtinHttpSearchContributions2 } from "@omnicross/core/search/http";
5507
+ import {
5508
+ builtinHttpSearchContributions as builtinHttpSearchContributions2,
5509
+ createSearchHttpTransport
5510
+ } from "@omnicross/core/search/http";
4730
5511
  function searchEgressPolicyFrom(config) {
4731
5512
  const hosts = config.egress.allowedPrivateHosts;
4732
5513
  return hosts.length > 0 ? { allowedPrivateHosts: [...hosts] } : {};
@@ -4740,11 +5521,24 @@ function searchPolicyFrom(config) {
4740
5521
  ...maxAttempts !== void 0 ? { maxAttempts } : {}
4741
5522
  };
4742
5523
  }
5524
+ function resolveSearchUpstreamDispatcher(url) {
5525
+ return resolveUpstreamDispatcher({ url });
5526
+ }
5527
+ var searchUpstreamProxyConfig = createUpstreamProxyResolver();
5528
+ function resolveSearchUpstreamProxyConfig(url) {
5529
+ return searchUpstreamProxyConfig({ url });
5530
+ }
4743
5531
  function searchContributionsFrom(config) {
4744
5532
  return [
4745
- ...builtinHttpSearchContributions2(),
5533
+ ...builtinHttpSearchContributions2(
5534
+ createSearchHttpTransport({
5535
+ resolveProxyDispatcher: resolveSearchUpstreamDispatcher,
5536
+ resolveProxyConfig: resolveSearchUpstreamProxyConfig
5537
+ })
5538
+ ),
4746
5539
  ...apiSearchContributions(config.providers, {
4747
- egressPolicy: searchEgressPolicyFrom(config)
5540
+ egressPolicy: searchEgressPolicyFrom(config),
5541
+ resolveProxyDispatcher: resolveSearchUpstreamDispatcher
4748
5542
  })
4749
5543
  ];
4750
5544
  }
@@ -4888,6 +5682,18 @@ async function handleSearchDiagnostics(res, deps) {
4888
5682
  };
4889
5683
  return writeJson(res, 200, { diagnostics: snapshot });
4890
5684
  }
5685
+ function persistedSearchContributions(search, fetchImpl) {
5686
+ if (fetchImpl) {
5687
+ const egressPolicy = searchEgressPolicyFrom(search);
5688
+ return [
5689
+ ...builtinHttpSearchContributions3(
5690
+ createSearchHttpTransport2({ fetch: fetchImpl, egressPolicy })
5691
+ ),
5692
+ ...apiSearchContributions2(search.providers, { egressPolicy, fetchImpl })
5693
+ ];
5694
+ }
5695
+ return searchContributionsFrom(search);
5696
+ }
4891
5697
  async function handleSearchTest(req, res, deps) {
4892
5698
  const status = deps.searchStatus;
4893
5699
  const body = await readBodyOrReject(req, res);
@@ -4905,16 +5711,8 @@ async function handleSearchTest(req, res, deps) {
4905
5711
  if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
4906
5712
  return writeErr(res, 400, `search provider '${providerId}' is not configured`);
4907
5713
  }
4908
- const egressPolicy = searchEgressPolicyFrom(search);
4909
5714
  const fetchImpl = status.testFetch;
4910
- const transport = fetchImpl ? createSearchHttpTransport({ fetch: fetchImpl, egressPolicy }) : void 0;
4911
- const contributions = [
4912
- ...builtinHttpSearchContributions3(transport),
4913
- ...apiSearchContributions2(search.providers, {
4914
- egressPolicy,
4915
- ...fetchImpl ? { fetchImpl } : {}
4916
- })
4917
- ];
5715
+ const contributions = persistedSearchContributions(search, fetchImpl);
4918
5716
  const contribution = contributions.find((c) => c.id === providerId);
4919
5717
  if (!contribution) {
4920
5718
  return writeErr(res, 400, `search provider '${providerId}' is not configured`);
@@ -4962,41 +5760,42 @@ async function handleSearchQuery(req, res, deps) {
4962
5760
  if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
4963
5761
  return writeErr(res, 400, `search provider '${providerId}' is not configured`);
4964
5762
  }
4965
- const egressPolicy = searchEgressPolicyFrom(search);
4966
5763
  const fetchImpl = status.testFetch;
4967
- const transport = fetchImpl ? createSearchHttpTransport({ fetch: fetchImpl, egressPolicy }) : void 0;
4968
- const contributions = [
4969
- ...builtinHttpSearchContributions3(transport),
4970
- ...apiSearchContributions2(search.providers, {
4971
- egressPolicy,
4972
- ...fetchImpl ? { fetchImpl } : {}
4973
- })
4974
- ];
4975
- const contribution = contributions.find((c) => c.id === providerId);
4976
- if (!contribution) {
4977
- return writeErr(res, 400, `search provider '${providerId}' is not configured`);
4978
- }
5764
+ const runtime = createSearchRuntime2({
5765
+ contributions: persistedSearchContributions(search, fetchImpl),
5766
+ policy: {
5767
+ ...searchPolicyFrom(search),
5768
+ // The panel always walks: it answers "does a search WORK for this
5769
+ // operator", not "does this one provider behave" — that is `/test`'s
5770
+ // job. The persisted policy's allowlist still bounds the walk.
5771
+ fallbackEnabled: true,
5772
+ preferred: providerId
5773
+ }
5774
+ });
4979
5775
  const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
4980
5776
  try {
4981
- const results = await contribution.provider.search(query2, { maxResults: 5 });
5777
+ const orchestrated = await runtime.search({ query: query2, options: { maxResults: 5 } });
5778
+ const results = orchestrated.results;
4982
5779
  const sanitized = results.slice(0, SEARCH_QUERY_MAX_RESULTS).map((result) => ({
4983
5780
  title: sanitizeResultField(result.title, SEARCH_RESULT_FIELD_CAPS.title),
4984
5781
  url: sanitizeResultField(result.url, SEARCH_RESULT_FIELD_CAPS.url),
4985
5782
  content: sanitizeResultField(result.content, SEARCH_RESULT_FIELD_CAPS.content)
4986
5783
  }));
4987
- const diagnostic = sanitized.length === 0 ? { providerId: contribution.id, status: "healthy", checkedAt } : classifyLiveSearchOutcome(
4988
- contribution.id,
5784
+ const diagnostic = sanitized.length === 0 ? { providerId: orchestrated.providerId, status: "healthy", checkedAt } : classifyLiveSearchOutcome(
5785
+ orchestrated.providerId,
4989
5786
  { kind: "results", count: sanitized.length },
4990
5787
  checkedAt
4991
5788
  );
4992
5789
  const response = {
4993
5790
  diagnostic,
5791
+ providerUsed: orchestrated.providerId,
5792
+ fallbackCount: orchestrated.fallbackCount,
4994
5793
  resultCount: sanitized.length,
4995
5794
  results: sanitized
4996
5795
  };
4997
5796
  return writeJson(res, 200, { result: response });
4998
5797
  } catch (error) {
4999
- const diagnostic = classifyLiveSearchOutcome(contribution.id, { kind: "failure", error }, checkedAt);
5798
+ const diagnostic = classifyLiveSearchOutcome(providerId, { kind: "failure", error }, checkedAt);
5000
5799
  const response = { diagnostic };
5001
5800
  return writeJson(res, 200, { result: response });
5002
5801
  }
@@ -5005,7 +5804,7 @@ async function handleSearchQuery(req, res, deps) {
5005
5804
  // src/admin/searchAdminView.ts
5006
5805
  var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
5007
5806
  var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
5008
- function isRecord(value) {
5807
+ function isRecord2(value) {
5009
5808
  return value !== null && typeof value === "object" && !Array.isArray(value);
5010
5809
  }
5011
5810
  function redactSearchServerConfig(search) {
@@ -5055,13 +5854,13 @@ function resolveSecretField(entry, field, stored) {
5055
5854
  else delete entry[field];
5056
5855
  }
5057
5856
  function preserveSearchSecrets(incoming, current) {
5058
- if (!isRecord(incoming)) return incoming;
5857
+ if (!isRecord2(incoming)) return incoming;
5059
5858
  const section = { ...incoming };
5060
5859
  const providersValue = section["providers"];
5061
- if (!isRecord(providersValue)) return section;
5860
+ if (!isRecord2(providersValue)) return section;
5062
5861
  const providers = {};
5063
5862
  for (const [id, entryValue] of Object.entries(providersValue)) {
5064
- if (!isRecord(entryValue)) {
5863
+ if (!isRecord2(entryValue)) {
5065
5864
  providers[id] = entryValue;
5066
5865
  continue;
5067
5866
  }
@@ -5139,7 +5938,7 @@ function parseKeyPolicyBody(body) {
5139
5938
  var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
5140
5939
  var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
5141
5940
  var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
5142
- function isRecord2(value) {
5941
+ function isRecord3(value) {
5143
5942
  return !!value && typeof value === "object" && !Array.isArray(value);
5144
5943
  }
5145
5944
  function nonBlank(value) {
@@ -5159,7 +5958,7 @@ function validateGatewayBindingsSegment(patch) {
5159
5958
  const ids = /* @__PURE__ */ new Set();
5160
5959
  raw.forEach((entry, index) => {
5161
5960
  const path2 = `bindings[${index}]`;
5162
- if (!isRecord2(entry)) {
5961
+ if (!isRecord3(entry)) {
5163
5962
  errors.push(`${path2} must be an object`);
5164
5963
  return;
5165
5964
  }
@@ -5188,12 +5987,12 @@ function validateGatewayBindingsSegment(patch) {
5188
5987
  } else if (entry.modelMappings.length > 100) {
5189
5988
  errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
5190
5989
  } else if (entry.modelMappings.some(
5191
- (mapping) => !isRecord2(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
5990
+ (mapping) => !isRecord3(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
5192
5991
  )) {
5193
5992
  errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
5194
5993
  }
5195
5994
  }
5196
- if (!isRecord2(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
5995
+ if (!isRecord3(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
5197
5996
  errors.push(`${path2}.target is invalid`);
5198
5997
  } else {
5199
5998
  if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
@@ -5208,7 +6007,7 @@ function validateGatewayBindingsSegment(patch) {
5208
6007
  }
5209
6008
  }
5210
6009
  if (entry.modelMap !== void 0) {
5211
- if (!isRecord2(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
6010
+ if (!isRecord3(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
5212
6011
  errors.push(`${path2}.modelMap must contain string values`);
5213
6012
  }
5214
6013
  }
@@ -5502,7 +6301,8 @@ var PROVIDER_KEYS = {
5502
6301
  block: "opencodego",
5503
6302
  accounts: "opencodegoAccounts",
5504
6303
  active: "activeOpencodegoAccountId"
5505
- }
6304
+ },
6305
+ kimi: { block: "kimi", accounts: "kimiAccounts", active: "activeKimiAccountId" }
5506
6306
  };
5507
6307
  function clone(value) {
5508
6308
  return JSON.parse(JSON.stringify(value));
@@ -6024,7 +6824,7 @@ async function applyImport(packString, passphrase, mode, deps, parseProviderInpu
6024
6824
  }
6025
6825
 
6026
6826
  // src/admin/adminMigration.ts
6027
- function err3(status, message) {
6827
+ function err4(status, message) {
6028
6828
  return { status, body: { error: { type: "admin_api_error", message } } };
6029
6829
  }
6030
6830
  async function handleExport(body, deps) {
@@ -6034,30 +6834,30 @@ async function handleExport(body, deps) {
6034
6834
  return { status: 200, body: { pack, version: BUNDLE_VERSION } };
6035
6835
  } catch (error) {
6036
6836
  if (error instanceof WeakPassphraseError) {
6037
- return err3(400, error.message);
6837
+ return err4(400, error.message);
6038
6838
  }
6039
- return err3(500, "failed to build the migration pack");
6839
+ return err4(500, "failed to build the migration pack");
6040
6840
  }
6041
6841
  }
6042
6842
  async function handleImport(body, deps) {
6043
6843
  const blob = typeof body["blob"] === "string" ? body["blob"] : "";
6044
6844
  const passphrase = typeof body["passphrase"] === "string" ? body["passphrase"] : "";
6045
6845
  const mode = body["mode"] === "overwrite" ? "overwrite" : "merge";
6046
- if (!blob) return err3(400, "import requires { blob }");
6846
+ if (!blob) return err4(400, "import requires { blob }");
6047
6847
  try {
6048
6848
  const counts = await applyImport(blob, passphrase, mode, deps, deps.parseProviderInput);
6049
6849
  return { status: 200, body: counts };
6050
6850
  } catch (error) {
6051
6851
  if (error instanceof WeakPassphraseError) {
6052
- return err3(400, error.message);
6852
+ return err4(400, error.message);
6053
6853
  }
6054
- return err3(400, error instanceof Error ? error.message : "import failed");
6854
+ return err4(400, error instanceof Error ? error.message : "import failed");
6055
6855
  }
6056
6856
  }
6057
6857
 
6058
6858
  // src/admin/usagePricing.ts
6059
6859
  import { getSharedUsageThroughputTracker } from "@omnicross/core/usage";
6060
- var err4 = (status, message) => ({
6860
+ var err5 = (status, message) => ({
6061
6861
  status,
6062
6862
  body: { error: { type: "admin_api_error", message } }
6063
6863
  });
@@ -6070,7 +6870,7 @@ function parseRange(query2) {
6070
6870
  const startTs = parseFiniteInt(query2.get("startTs"));
6071
6871
  const endTs = parseFiniteInt(query2.get("endTs"));
6072
6872
  if (startTs === null || endTs === null) {
6073
- return err4(400, "startTs and endTs are required finite-integer unix-millis query params");
6873
+ return err5(400, "startTs and endTs are required finite-integer unix-millis query params");
6074
6874
  }
6075
6875
  return { startTs, endTs };
6076
6876
  }
@@ -6095,14 +6895,14 @@ async function handleUsageGet(view, query2, deps) {
6095
6895
  case "timeseries": {
6096
6896
  const bucket = query2.get("bucket");
6097
6897
  if (bucket !== "hour" && bucket !== "day" && bucket !== "month") {
6098
- return err4(400, "bucket must be one of 'hour', 'day', 'month'");
6898
+ return err5(400, "bucket must be one of 'hour', 'day', 'month'");
6099
6899
  }
6100
6900
  const now = Date.now();
6101
6901
  const clamped = { startTs: range.startTs, endTs: Math.min(range.endTs, now) };
6102
6902
  if (clamped.startTs < clamped.endTs) {
6103
6903
  const projected = Math.ceil((clamped.endTs - clamped.startTs) / BUCKET_SPAN_MS[bucket]) + 1;
6104
6904
  if (projected > MAX_TIMESERIES_BUCKETS) {
6105
- return err4(
6905
+ return err5(
6106
6906
  400,
6107
6907
  `requested range projects ~${projected} '${bucket}' buckets (max ${MAX_TIMESERIES_BUCKETS}); narrow the range or use a coarser bucket`
6108
6908
  );
@@ -6125,7 +6925,7 @@ async function handleUsageGet(view, query2, deps) {
6125
6925
  };
6126
6926
  }
6127
6927
  default:
6128
- return err4(404, `unknown usage view '${view ?? ""}'`);
6928
+ return err5(404, `unknown usage view '${view ?? ""}'`);
6129
6929
  }
6130
6930
  }
6131
6931
  function poolKeyLabels(cfg) {
@@ -6174,7 +6974,7 @@ async function handlePricingList(deps) {
6174
6974
  async function handlePricingUpsert(body, deps) {
6175
6975
  const input = parsePricingEntryInput(body);
6176
6976
  if (!input) {
6177
- return err4(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
6977
+ return err5(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
6178
6978
  }
6179
6979
  const entry = await deps.pricingEngine.upsertManual(input);
6180
6980
  return { status: 200, body: { entry } };
@@ -6183,7 +6983,7 @@ async function handlePricingDelete(query2, deps) {
6183
6983
  const providerId = query2.get("providerId")?.trim() ?? "";
6184
6984
  const modelId = query2.get("modelId")?.trim() ?? "";
6185
6985
  if (!providerId || !modelId) {
6186
- return err4(400, "delete requires providerId and modelId query params");
6986
+ return err5(400, "delete requires providerId and modelId query params");
6187
6987
  }
6188
6988
  const deleted = await deps.pricingStore.delete(providerId, modelId);
6189
6989
  if (deleted) await deps.pricingEngine.invalidateCache();
@@ -6203,13 +7003,13 @@ async function handlePricingFetchLatest(deps) {
6203
7003
  }
6204
7004
  };
6205
7005
  } catch (e) {
6206
- return err4(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
7006
+ return err5(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
6207
7007
  }
6208
7008
  }
6209
7009
  async function handlePricingResolveConflicts(body, deps) {
6210
7010
  const raw = body["resolutions"];
6211
7011
  if (!Array.isArray(raw)) {
6212
- return err4(400, "resolve-conflicts requires { resolutions: [...] }");
7012
+ return err5(400, "resolve-conflicts requires { resolutions: [...] }");
6213
7013
  }
6214
7014
  const currentRows = await deps.pricingStore.getAll();
6215
7015
  const userEditedKeys = new Set(
@@ -6219,21 +7019,21 @@ async function handlePricingResolveConflicts(body, deps) {
6219
7019
  const pendingIncoming = /* @__PURE__ */ new Map();
6220
7020
  let staleCount = 0;
6221
7021
  for (const item of raw) {
6222
- if (!item || typeof item !== "object") return err4(400, "invalid resolution entry");
7022
+ if (!item || typeof item !== "object") return err5(400, "invalid resolution entry");
6223
7023
  const r = item;
6224
7024
  const action = r["action"];
6225
7025
  if (action !== "overwrite" && action !== "skip") {
6226
- return err4(400, "resolution action must be 'overwrite' or 'skip'");
7026
+ return err5(400, "resolution action must be 'overwrite' or 'skip'");
6227
7027
  }
6228
7028
  const providerId = typeof r["providerId"] === "string" && r["providerId"].trim() ? r["providerId"].trim() : "";
6229
7029
  const modelId = typeof r["modelId"] === "string" && r["modelId"].trim() ? r["modelId"].trim() : "";
6230
7030
  if (!providerId || !modelId) {
6231
- return err4(400, "each resolution requires top-level providerId and modelId");
7031
+ return err5(400, "each resolution requires top-level providerId and modelId");
6232
7032
  }
6233
7033
  const incoming = parsePricingEntryInput(r["incoming"]);
6234
- if (!incoming) return err4(400, "each resolution must echo a valid incoming pricing entry");
7034
+ if (!incoming) return err5(400, "each resolution must echo a valid incoming pricing entry");
6235
7035
  if (incoming.providerId !== providerId || incoming.modelId !== modelId) {
6236
- return err4(400, "resolution providerId/modelId must match the echoed incoming entry");
7036
+ return err5(400, "resolution providerId/modelId must match the echoed incoming entry");
6237
7037
  }
6238
7038
  const key = `${providerId}::${modelId}`;
6239
7039
  if (action === "overwrite" && !userEditedKeys.has(key)) {
@@ -6278,7 +7078,7 @@ function query(req) {
6278
7078
  }
6279
7079
  function allowanceProvider(value) {
6280
7080
  if (!value) return void 0;
6281
- return value === "claude" || value === "codex" ? value : null;
7081
+ return value === "claude" || value === "codex" || value === "kimi" || value === "opencodego" ? value : null;
6282
7082
  }
6283
7083
  async function handleAccountAllowanceApi(req, res, method, rest, service) {
6284
7084
  if (!service) return writeError2(res, 501, "account allowance service is not available");
@@ -6292,7 +7092,9 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
6292
7092
  const params = query(req);
6293
7093
  const pathProvider = rest.length >= 2 ? rest[0] : null;
6294
7094
  const providerId = allowanceProvider(pathProvider ?? params.get("providerId") ?? params.get("provider"));
6295
- if (providerId === null) return writeError2(res, 400, "providerId must be claude or codex");
7095
+ if (providerId === null) {
7096
+ return writeError2(res, 400, "providerId must be claude, codex, kimi, or opencodego");
7097
+ }
6296
7098
  const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
6297
7099
  const allowances = await service.list({ providerId, accountId });
6298
7100
  return writeJson3(res, 200, { allowances });
@@ -6302,10 +7104,37 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
6302
7104
  const requestedProvider = allowanceProvider(
6303
7105
  typeof body["providerId"] === "string" ? body["providerId"] : "claude"
6304
7106
  );
6305
- if (requestedProvider !== "claude") {
6306
- return writeError2(res, 400, "only Claude allowances support explicit refresh");
6307
- }
6308
7107
  const accountId = typeof body["accountId"] === "string" && body["accountId"].trim() ? body["accountId"].trim() : void 0;
7108
+ if (requestedProvider === "codex") {
7109
+ if (!service.refreshCodex) {
7110
+ return writeError2(res, 501, "codex allowance refresh is not available");
7111
+ }
7112
+ const allowances2 = await service.refreshCodex(accountId);
7113
+ if (accountId && allowances2.length === 0) {
7114
+ return writeError2(res, 404, `Codex account '${accountId}' not found`);
7115
+ }
7116
+ return writeJson3(res, 200, { allowances: allowances2 });
7117
+ }
7118
+ if (requestedProvider === "kimi") {
7119
+ if (!service.refreshKimi) {
7120
+ return writeError2(res, 501, "kimi allowance refresh is not available");
7121
+ }
7122
+ const allowances2 = await service.refreshKimi(accountId);
7123
+ if (accountId && allowances2.length === 0) {
7124
+ return writeError2(res, 404, `Kimi account '${accountId}' not found`);
7125
+ }
7126
+ return writeJson3(res, 200, { allowances: allowances2 });
7127
+ }
7128
+ if (requestedProvider === "opencodego") {
7129
+ if (!service.refreshOpenCodeGo) {
7130
+ return writeError2(res, 501, "opencodego allowance refresh is not available");
7131
+ }
7132
+ const allowances2 = await service.refreshOpenCodeGo(accountId);
7133
+ if (accountId && allowances2.length === 0) {
7134
+ return writeError2(res, 404, `OpenCodeGo account '${accountId}' not found`);
7135
+ }
7136
+ return writeJson3(res, 200, { allowances: allowances2 });
7137
+ }
6309
7138
  const allowances = await service.refreshClaude(accountId);
6310
7139
  if (accountId && allowances.length === 0) {
6311
7140
  return writeError2(res, 404, `Claude account '${accountId}' not found`);
@@ -6476,8 +7305,8 @@ async function handleAdminApi(req, res, path2, deps) {
6476
7305
  default:
6477
7306
  return writeJsonError(res, 404, `unknown admin resource '${resource}'`);
6478
7307
  }
6479
- } catch (err5) {
6480
- writeJsonError(res, 500, err5 instanceof Error ? err5.message : String(err5));
7308
+ } catch (err6) {
7309
+ writeJsonError(res, 500, err6 instanceof Error ? err6.message : String(err6));
6481
7310
  }
6482
7311
  }
6483
7312
  function requestQuery(req) {
@@ -6547,6 +7376,9 @@ async function handleProviders(req, res, method, rest, deps) {
6547
7376
  if (method === "POST" && rest.length === 4 && rest[1] === "keys" && rest[3] === "enabled") {
6548
7377
  return await handleToggleProviderKey(req, res, rest[0], rest[2], cfg, deps);
6549
7378
  }
7379
+ if (method === "POST" && rest.length === 5 && rest[1] === "keys" && rest[3] === "quota" && rest[4] === "refresh") {
7380
+ return await handleProviderKeyQuotaRefresh(res, rest[0], rest[2], cfg, deps);
7381
+ }
6550
7382
  if (method === "PUT" && rest.length === 3 && rest[1] === "keys") {
6551
7383
  return await handleUpdateProviderKey(req, res, rest[0], rest[2], cfg, deps);
6552
7384
  }
@@ -6645,7 +7477,7 @@ async function handleDiscoverModels(res, id, cfg) {
6645
7477
  try {
6646
7478
  const headers = { Accept: "application/json" };
6647
7479
  if (resolvedKey) headers["Authorization"] = `Bearer ${resolvedKey}`;
6648
- const response = await fetchUpstream2(url, { method: "GET", headers }, { providerId: "byo" });
7480
+ const response = await fetchUpstream5(url, { method: "GET", headers }, { providerId: "byo" });
6649
7481
  if (!response.ok) {
6650
7482
  const text = await response.text().catch(() => "");
6651
7483
  let message = text.slice(0, 300);
@@ -6662,8 +7494,8 @@ async function handleDiscoverModels(res, id, cfg) {
6662
7494
  const data = await response.json();
6663
7495
  const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
6664
7496
  return writeJson4(res, 200, { models });
6665
- } catch (err5) {
6666
- const message = err5 instanceof Error ? err5.message : String(err5);
7497
+ } catch (err6) {
7498
+ const message = err6 instanceof Error ? err6.message : String(err6);
6667
7499
  return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
6668
7500
  }
6669
7501
  }
@@ -6704,7 +7536,7 @@ async function handleTestModel(req, res, id, cfg) {
6704
7536
  }
6705
7537
  const startedAt = Date.now();
6706
7538
  try {
6707
- const response = await fetchUpstream2(
7539
+ const response = await fetchUpstream5(
6708
7540
  url,
6709
7541
  { method: "POST", headers, body: JSON.stringify(payload) },
6710
7542
  { providerId: "byo" }
@@ -6726,8 +7558,8 @@ async function handleTestModel(req, res, id, cfg) {
6726
7558
  latencyMs,
6727
7559
  sample: extractSampleText(text, row.apiFormat)
6728
7560
  });
6729
- } catch (err5) {
6730
- const message = err5 instanceof Error ? err5.message : String(err5);
7561
+ } catch (err6) {
7562
+ const message = err6 instanceof Error ? err6.message : String(err6);
6731
7563
  return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
6732
7564
  }
6733
7565
  }
@@ -6769,7 +7601,30 @@ async function handleProviderKeys(res, id, cfg, deps) {
6769
7601
  const row = cfg.providers.find((p) => p.id === id);
6770
7602
  if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
6771
7603
  const cooldown = await deps.apiKeyPool.getKeyHealth(id);
6772
- return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
7604
+ const views = toPoolKeyView(row, cooldown, deps);
7605
+ if (deps.providerKeyQuota) {
7606
+ const quotas = await Promise.allSettled(
7607
+ views.map((view) => deps.providerKeyQuota.quotaFor(row, view.id))
7608
+ );
7609
+ views.forEach((view, index) => {
7610
+ const settled = quotas[index];
7611
+ if (settled.status === "fulfilled" && settled.value) view.quota = settled.value;
7612
+ });
7613
+ }
7614
+ return writeJson4(res, 200, { keys: views });
7615
+ }
7616
+ async function handleProviderKeyQuotaRefresh(res, id, keyId, cfg, deps) {
7617
+ if (!deps.providerKeyQuota) return writeJsonError(res, 501, "provider key quota is not available");
7618
+ if (!id || !keyId) return writeJsonError(res, 400, "provider id and key id required in path");
7619
+ const row = cfg.providers.find((p) => p.id === id);
7620
+ if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
7621
+ try {
7622
+ const quota = await deps.providerKeyQuota.quotaFor(row, keyId, { force: true });
7623
+ if (!quota) return writeJsonError(res, 404, `no quota endpoint for key '${keyId}'`);
7624
+ return writeJson4(res, 200, { quota });
7625
+ } catch {
7626
+ return writeJsonError(res, 502, "quota refresh failed");
7627
+ }
6773
7628
  }
6774
7629
  function parsePoolKeyInput(body, existing) {
6775
7630
  const out = {};
@@ -7514,12 +8369,12 @@ async function handleAccounts(req, res, method, rest, deps) {
7514
8369
  }
7515
8370
  return writeJson4(res, 200, { ok: true, affected: result.affected });
7516
8371
  }
7517
- if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
7518
- const result = handleCodexOAuthStatus(rest[2], deps);
8372
+ if (method === "GET" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[3] === "status") {
8373
+ const result = rest[0] === "codex" ? handleCodexOAuthStatus(rest[2], deps) : handleKimiOAuthStatus(rest[2], deps);
7519
8374
  return writeJson4(res, result.status, result.body);
7520
8375
  }
7521
- if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
7522
- const result = handleCodexOAuthCancel(rest[2], deps);
8376
+ if (method === "DELETE" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[2]) {
8377
+ const result = rest[0] === "codex" ? handleCodexOAuthCancel(rest[2], deps) : handleKimiOAuthCancel(rest[2], deps);
7523
8378
  return writeJson4(res, result.status, result.body);
7524
8379
  }
7525
8380
  if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
@@ -7572,7 +8427,15 @@ async function handleAccounts(req, res, method, rest, deps) {
7572
8427
  return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
7573
8428
  }
7574
8429
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
7575
- const result = providerId === "codex" ? handleCodexOAuthStart(deps) : handleOAuthStart(providerId, deps);
8430
+ if (providerId === "codex") {
8431
+ const result2 = handleCodexOAuthStart(deps);
8432
+ return writeJson4(res, result2.status, result2.body);
8433
+ }
8434
+ if (providerId === "kimi") {
8435
+ const result2 = await handleKimiOAuthStart(deps);
8436
+ return writeJson4(res, result2.status, result2.body);
8437
+ }
8438
+ const result = handleOAuthStart(providerId, deps);
7576
8439
  return writeJson4(res, result.status, result.body);
7577
8440
  }
7578
8441
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
@@ -8066,12 +8929,12 @@ async function handlePlayground(req, res, method, deps) {
8066
8929
  const payload = body["body"];
8067
8930
  const status = deps.outboundApiServer.getStatus();
8068
8931
  if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
8069
- const path2 = resolvePlaygroundPath(endpoint, isRecord3(payload) ? payload : {});
8932
+ const path2 = resolvePlaygroundPath(endpoint, isRecord4(payload) ? payload : {});
8070
8933
  if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
8071
8934
  const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
8072
8935
  await proxyToOutbound(res, status.port, path2, key, upstreamBody);
8073
8936
  }
8074
- function isRecord3(v) {
8937
+ function isRecord4(v) {
8075
8938
  return !!v && typeof v === "object" && !Array.isArray(v);
8076
8939
  }
8077
8940
  function proxyToOutbound(res, outboundPort, path2, key, body) {
@@ -8100,8 +8963,8 @@ function proxyToOutbound(res, outboundPort, path2, key, body) {
8100
8963
  });
8101
8964
  }
8102
8965
  );
8103
- upstream.on("error", (err5) => {
8104
- if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err5.message}`);
8966
+ upstream.on("error", (err6) => {
8967
+ if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err6.message}`);
8105
8968
  else res.end();
8106
8969
  resolve11();
8107
8970
  });
@@ -8206,7 +9069,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
8206
9069
  }
8207
9070
 
8208
9071
  // src/admin/version.ts
8209
- var DAEMON_VERSION = true ? "0.2.1" : "0.0.0-dev";
9072
+ var DAEMON_VERSION = true ? "0.3.1" : "0.0.0-dev";
8210
9073
 
8211
9074
  // src/admin/AdminServer.ts
8212
9075
  var LOOPBACK_ADDR = "127.0.0.1";
@@ -8249,13 +9112,13 @@ var AdminServer = class {
8249
9112
  const server = http2.createServer((req, res) => {
8250
9113
  this.onRequest(req, res);
8251
9114
  });
8252
- const onError = (err5) => {
8253
- if (err5.code === "EADDRINUSE" && port !== 0) {
9115
+ const onError = (err6) => {
9116
+ if (err6.code === "EADDRINUSE" && port !== 0) {
8254
9117
  server.removeListener("error", onError);
8255
9118
  this.listen(bindAddr, 0).then(resolve11, reject);
8256
9119
  return;
8257
9120
  }
8258
- reject(err5);
9121
+ reject(err6);
8259
9122
  };
8260
9123
  server.on("error", onError);
8261
9124
  server.listen(port, bindAddr, () => {
@@ -8273,8 +9136,8 @@ var AdminServer = class {
8273
9136
  }
8274
9137
  /** Per-request handler: auth gate (when a token is set) → routing. */
8275
9138
  onRequest(req, res) {
8276
- void this.dispatch(req, res).catch((err5) => {
8277
- const message = err5 instanceof Error ? err5.message : String(err5);
9139
+ void this.dispatch(req, res).catch((err6) => {
9140
+ const message = err6 instanceof Error ? err6.message : String(err6);
8278
9141
  this.deps.logger.error("[AdminServer] unhandled error:", message);
8279
9142
  if (!res.headersSent) {
8280
9143
  res.writeHead(500, { "Content-Type": "application/json" });
@@ -8538,18 +9401,18 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
8538
9401
  return;
8539
9402
  }
8540
9403
  signal?.addEventListener("abort", abort, { once: true });
8541
- server.on("error", (err5) => {
9404
+ server.on("error", (err6) => {
8542
9405
  if (settled) return;
8543
9406
  settled = true;
8544
9407
  clearTimeout(timer);
8545
- if (err5.code === "EADDRINUSE") {
9408
+ if (err6.code === "EADDRINUSE") {
8546
9409
  reject(
8547
9410
  new Error(
8548
9411
  `login: cannot bind ${LOOPBACK_HOST}:${LOOPBACK_PORT} (address in use) \u2014 another codex login or process is holding the port`
8549
9412
  )
8550
9413
  );
8551
9414
  } else {
8552
- reject(err5);
9415
+ reject(err6);
8553
9416
  }
8554
9417
  });
8555
9418
  const timer = setTimeout(() => {
@@ -8624,6 +9487,411 @@ function createPoolKeysLoader(getProviderRow, autoDisabled) {
8624
9487
  };
8625
9488
  }
8626
9489
 
9490
+ // src/allowance/ProviderKeyQuotaService.ts
9491
+ import { fetchUpstream as fetchUpstream6 } from "@omnicross/core/pipeline/upstreamFetch";
9492
+
9493
+ // src/allowance/ProviderKeyQuota.ts
9494
+ var MINUTE_MS2 = 6e4;
9495
+ var HOUR_MS2 = 60 * MINUTE_MS2;
9496
+ var DAY_MS2 = 24 * HOUR_MS2;
9497
+ var WEEK_MS = 7 * DAY_MS2;
9498
+ var MONTH_MS = 30 * DAY_MS2;
9499
+ function finiteNumber3(value) {
9500
+ if (value === null || value === void 0 || value === "") return void 0;
9501
+ const parsed = typeof value === "number" ? value : Number(value);
9502
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
9503
+ }
9504
+ function finitePercent4(value) {
9505
+ const parsed = finiteNumber3(value);
9506
+ return parsed !== void 0 && parsed <= 100 ? parsed : null;
9507
+ }
9508
+ function isoInstant3(value) {
9509
+ if (typeof value === "string" && value.trim()) {
9510
+ const time = Date.parse(value);
9511
+ if (Number.isFinite(time)) return new Date(time).toISOString();
9512
+ }
9513
+ const numeric = finiteNumber3(value);
9514
+ if (numeric !== void 0 && numeric > 1e9) {
9515
+ const ms = numeric > 1e12 ? numeric : numeric * 1e3;
9516
+ return new Date(ms).toISOString();
9517
+ }
9518
+ return void 0;
9519
+ }
9520
+ function secondsUntil5(instant, now) {
9521
+ if (!instant) return void 0;
9522
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
9523
+ }
9524
+ function isRecord5(value) {
9525
+ return !!value && typeof value === "object" && !Array.isArray(value);
9526
+ }
9527
+ function detectProviderKeyQuotaAdapter(baseUrl) {
9528
+ if (!baseUrl) return null;
9529
+ let url;
9530
+ try {
9531
+ url = new URL(baseUrl);
9532
+ } catch {
9533
+ return null;
9534
+ }
9535
+ const host = url.hostname.toLowerCase();
9536
+ const path2 = url.pathname.toLowerCase();
9537
+ if ((host === "api.z.ai" || host === "open.bigmodel.cn") && path2.includes("/coding")) {
9538
+ return "zai";
9539
+ }
9540
+ if ((host === "api.minimax.io" || host === "api.minimaxi.com") && // Token Plan rides the plain openai `/v1` (chat completions) surface; the
9541
+ // anthropic `/anthropic` rows are excluded (their usage impl is unverified).
9542
+ (path2 === "/v1" || path2 === "/v1/" || path2 === "" || path2 === "/")) {
9543
+ return "minimax-token-plan";
9544
+ }
9545
+ if (host === "api.code.umans.ai") return "umans";
9546
+ if (host === "api.synthetic.new") return "synthetic";
9547
+ return null;
9548
+ }
9549
+ function providerKeyQuotaUrl(adapter, baseUrl) {
9550
+ const origin = new URL(baseUrl).origin;
9551
+ if (adapter === "zai") return `${origin}/api/monitor/usage/quota/limit`;
9552
+ if (adapter === "minimax-token-plan") return `${origin}/v1/token_plan/remains`;
9553
+ if (adapter === "umans") return `${origin}/v1/usage`;
9554
+ return `${origin}/v2/quotas`;
9555
+ }
9556
+ function providerKeyQuotaAuthHeader(adapter, key) {
9557
+ return adapter === "zai" ? key : `Bearer ${key}`;
9558
+ }
9559
+ function zaiWindowDurationMs(item) {
9560
+ const count = item.number !== void 0 && item.number > 0 ? item.number : 1;
9561
+ switch (item.unit) {
9562
+ case 3:
9563
+ return count * HOUR_MS2;
9564
+ case 4:
9565
+ return count * DAY_MS2;
9566
+ case 5:
9567
+ return count * MONTH_MS;
9568
+ case 6:
9569
+ return WEEK_MS;
9570
+ default:
9571
+ return void 0;
9572
+ }
9573
+ }
9574
+ function zaiWindowIdLabel(durationMs) {
9575
+ if (durationMs === WEEK_MS) return { id: "seven-day", label: "7 days" };
9576
+ if (durationMs === 5 * HOUR_MS2) return { id: "five-hour", label: "5 hours" };
9577
+ if (durationMs === MONTH_MS) return { id: "thirty-day", label: "30 days" };
9578
+ if (durationMs !== void 0 && durationMs % DAY_MS2 === 0) {
9579
+ const days = durationMs / DAY_MS2;
9580
+ return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}` };
9581
+ }
9582
+ if (durationMs !== void 0 && durationMs % HOUR_MS2 === 0) {
9583
+ const hours = durationMs / HOUR_MS2;
9584
+ return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}` };
9585
+ }
9586
+ return { id: "quota", label: "Quota" };
9587
+ }
9588
+ function parseZaiQuotaPayload(payload, now) {
9589
+ if (!isRecord5(payload)) return null;
9590
+ const data = isRecord5(payload["data"]) ? payload["data"] : payload;
9591
+ if (payload["success"] === false) return null;
9592
+ const limits = Array.isArray(data["limits"]) ? data["limits"] : [];
9593
+ const byWindow = /* @__PURE__ */ new Map();
9594
+ for (const raw of limits) {
9595
+ if (!isRecord5(raw)) continue;
9596
+ const item = raw;
9597
+ if (item.type === void 0) continue;
9598
+ const details = raw["usageDetails"];
9599
+ if (Array.isArray(details) && details.some((d) => isRecord5(d) && d["modelCode"] === "zread")) {
9600
+ continue;
9601
+ }
9602
+ const durationMs = zaiWindowDurationMs(item);
9603
+ const { id, label } = zaiWindowIdLabel(durationMs);
9604
+ const limit = finiteNumber3(item.usage);
9605
+ const used = finiteNumber3(item.currentValue);
9606
+ const fromAbsolute = limit !== void 0 && used !== void 0 && limit > 0 ? Math.min(100, used / limit * 100) : void 0;
9607
+ const fromPercentage = finitePercent4(item.percentage) ?? void 0;
9608
+ const usedPercent = fromAbsolute !== void 0 ? Math.round(fromAbsolute * 10) / 10 : fromPercentage;
9609
+ if (usedPercent === void 0) continue;
9610
+ const resetsAt = isoInstant3(item.nextResetTime);
9611
+ const candidate = {
9612
+ id,
9613
+ label,
9614
+ scope: "all",
9615
+ usedPercent,
9616
+ ...durationMs !== void 0 ? { windowMinutes: Math.round(durationMs / MINUTE_MS2) } : {},
9617
+ ...resetsAt !== void 0 ? { resetsAt } : {},
9618
+ remainingSeconds: secondsUntil5(resetsAt, now),
9619
+ state: "fresh"
9620
+ };
9621
+ const existing = byWindow.get(id);
9622
+ if (!existing || (candidate.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
9623
+ byWindow.set(id, candidate);
9624
+ }
9625
+ }
9626
+ const windows = [...byWindow.values()].sort((a, b) => (a.windowMinutes ?? Number.POSITIVE_INFINITY) - (b.windowMinutes ?? Number.POSITIVE_INFINITY));
9627
+ return windows.length > 0 ? windows.slice(0, 4) : null;
9628
+ }
9629
+ var MINIMAX_STATUS_EXHAUSTED = 2;
9630
+ var MINIMAX_SHARED_BUCKET = "general";
9631
+ function parseMiniMaxBucket(value) {
9632
+ if (!isRecord5(value)) return null;
9633
+ const modelName = typeof value["model_name"] === "string" ? value["model_name"].trim() : "";
9634
+ if (!modelName) return null;
9635
+ const instant = (v) => {
9636
+ const n = finiteNumber3(v);
9637
+ return n !== void 0 && n > 1e9 ? n > 1e12 ? n : n * 1e3 : void 0;
9638
+ };
9639
+ return {
9640
+ modelName,
9641
+ intervalEnd: instant(value["end_time"]),
9642
+ intervalRemainingPercent: finiteNumber3(value["current_interval_remaining_percent"]),
9643
+ intervalStatus: finiteNumber3(value["current_interval_status"]),
9644
+ weeklyEnd: instant(value["weekly_end_time"]),
9645
+ weeklyRemainingPercent: finiteNumber3(value["current_weekly_remaining_percent"]),
9646
+ weeklyStatus: finiteNumber3(value["current_weekly_status"])
9647
+ };
9648
+ }
9649
+ function minimaxWindow(id, label, windowMinutes, resetsAtMs, remainingPercent, status, now) {
9650
+ const usedPercent = status === MINIMAX_STATUS_EXHAUSTED ? 100 : remainingPercent !== void 0 ? Math.round((100 - remainingPercent) * 10) / 10 : null;
9651
+ const resetsAt = resetsAtMs !== void 0 ? new Date(resetsAtMs).toISOString() : void 0;
9652
+ return {
9653
+ id,
9654
+ label,
9655
+ scope: "all",
9656
+ usedPercent,
9657
+ ...windowMinutes !== void 0 ? { windowMinutes } : {},
9658
+ ...resetsAt !== void 0 ? { resetsAt } : {},
9659
+ remainingSeconds: secondsUntil5(resetsAt, now),
9660
+ state: usedPercent !== null ? "fresh" : "unavailable"
9661
+ };
9662
+ }
9663
+ function parseMiniMaxTokenPlanPayload(payload, now) {
9664
+ if (!isRecord5(payload)) return null;
9665
+ const baseResp = payload["base_resp"];
9666
+ if (!isRecord5(baseResp) || baseResp["status_code"] !== 0) return null;
9667
+ const buckets = Array.isArray(payload["model_remains"]) ? payload["model_remains"] : [];
9668
+ let general = null;
9669
+ for (const raw of buckets) {
9670
+ const bucket = parseMiniMaxBucket(raw);
9671
+ if (bucket?.modelName === MINIMAX_SHARED_BUCKET) {
9672
+ general = bucket;
9673
+ break;
9674
+ }
9675
+ }
9676
+ if (!general) return null;
9677
+ return [
9678
+ minimaxWindow(
9679
+ "five-hour",
9680
+ "5 hours",
9681
+ 5 * 60,
9682
+ general.intervalEnd,
9683
+ general.intervalRemainingPercent,
9684
+ general.intervalStatus,
9685
+ now
9686
+ ),
9687
+ minimaxWindow(
9688
+ "seven-day",
9689
+ "7 days",
9690
+ Math.round(WEEK_MS / MINUTE_MS2),
9691
+ general.weeklyEnd,
9692
+ general.weeklyRemainingPercent,
9693
+ general.weeklyStatus,
9694
+ now
9695
+ )
9696
+ ];
9697
+ }
9698
+ function parseUmansUsagePayload(payload, now) {
9699
+ if (!isRecord5(payload)) return null;
9700
+ const limits = isRecord5(payload["limits"]) ? payload["limits"] : void 0;
9701
+ const requests = limits && isRecord5(limits["requests"]) ? limits["requests"] : void 0;
9702
+ const usage = isRecord5(payload["usage"]) ? payload["usage"] : void 0;
9703
+ const window = isRecord5(payload["window"]) ? payload["window"] : void 0;
9704
+ const hardCap = finiteNumber3(requests?.["hard_cap"]);
9705
+ const softLimit = finiteNumber3(requests?.["limit"]);
9706
+ const requestsInWindow = finiteNumber3(usage?.["requests_in_window"]);
9707
+ const weightedInWindow = finiteNumber3(usage?.["weighted_in_window"]);
9708
+ const resetsAt = isoInstant3(window?.["resets_at"]);
9709
+ let usedPercent = null;
9710
+ if (hardCap !== void 0 && hardCap > 0 && requestsInWindow !== void 0) {
9711
+ usedPercent = Math.round(Math.min(100, requestsInWindow / hardCap * 100) * 10) / 10;
9712
+ } else if (softLimit !== void 0 && softLimit > 0 && weightedInWindow !== void 0) {
9713
+ usedPercent = Math.round(Math.min(100, weightedInWindow / softLimit * 100) * 10) / 10;
9714
+ }
9715
+ if (usedPercent === null && resetsAt === void 0) return null;
9716
+ return [
9717
+ {
9718
+ id: "five-hour",
9719
+ label: "5 hours",
9720
+ scope: "all",
9721
+ usedPercent,
9722
+ windowMinutes: 5 * 60,
9723
+ ...resetsAt !== void 0 ? { resetsAt } : {},
9724
+ remainingSeconds: secondsUntil5(resetsAt, now),
9725
+ state: "fresh"
9726
+ }
9727
+ ];
9728
+ }
9729
+ function parseSyntheticQuotasPayload(payload, now) {
9730
+ if (!isRecord5(payload)) return null;
9731
+ const fiveHour = isRecord5(payload["rollingFiveHourLimit"]) ? payload["rollingFiveHourLimit"] : void 0;
9732
+ const weekly = isRecord5(payload["weeklyTokenLimit"]) ? payload["weeklyTokenLimit"] : void 0;
9733
+ const windows = [];
9734
+ if (fiveHour) {
9735
+ const max = finiteNumber3(fiveHour["max"]);
9736
+ const remaining = finiteNumber3(fiveHour["remaining"]);
9737
+ const usedPercent = max !== void 0 && max > 0 && remaining !== void 0 ? Math.round(Math.min(100, (max - remaining) / max * 100) * 10) / 10 : null;
9738
+ const resetsAt = isoInstant3(fiveHour["nextTickAt"]);
9739
+ windows.push({
9740
+ id: "five-hour",
9741
+ label: "5 hours",
9742
+ scope: "all",
9743
+ usedPercent,
9744
+ windowMinutes: 5 * 60,
9745
+ ...resetsAt !== void 0 ? { resetsAt } : {},
9746
+ remainingSeconds: secondsUntil5(resetsAt, now),
9747
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
9748
+ });
9749
+ }
9750
+ if (weekly) {
9751
+ const percentRemaining = finiteNumber3(weekly["percentRemaining"]);
9752
+ const usedPercent = percentRemaining !== void 0 ? Math.round(Math.min(100, Math.max(0, 100 - percentRemaining)) * 10) / 10 : null;
9753
+ const resetsAt = isoInstant3(weekly["nextRegenAt"]);
9754
+ windows.push({
9755
+ id: "seven-day",
9756
+ label: "7 days",
9757
+ scope: "all",
9758
+ usedPercent,
9759
+ windowMinutes: 7 * 24 * 60,
9760
+ ...resetsAt !== void 0 ? { resetsAt } : {},
9761
+ remainingSeconds: secondsUntil5(resetsAt, now),
9762
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
9763
+ });
9764
+ }
9765
+ return windows.length > 0 ? windows : null;
9766
+ }
9767
+
9768
+ // src/allowance/ProviderKeyQuotaService.ts
9769
+ function parseQuotaPayload(adapter, payload, now) {
9770
+ switch (adapter) {
9771
+ case "zai":
9772
+ return parseZaiQuotaPayload(payload, now);
9773
+ case "minimax-token-plan":
9774
+ return parseMiniMaxTokenPlanPayload(payload, now);
9775
+ case "umans":
9776
+ return parseUmansUsagePayload(payload, now);
9777
+ case "synthetic":
9778
+ return parseSyntheticQuotasPayload(payload, now);
9779
+ }
9780
+ }
9781
+ var PROVIDER_KEY_QUOTA_CACHE_MS = 5 * 6e4;
9782
+ function resolvedBaseUrl(row) {
9783
+ const modes = row.apiModes ?? [];
9784
+ const selected = row.selectedApiModeId ? modes.find((mode) => mode.id === row.selectedApiModeId) : void 0;
9785
+ const fallback = modes[0];
9786
+ const modeBase = selected?.baseUrl ?? fallback?.baseUrl;
9787
+ return modeBase ?? row.codingPlan?.baseUrl ?? row.baseUrl;
9788
+ }
9789
+ function rowKeyEntries(row) {
9790
+ const pool = (row.apiKeys ?? []).filter((entry) => entry.apiKey.length > 0);
9791
+ if (pool.length > 0) return pool.map((entry) => ({ id: entry.id, apiKey: entry.apiKey }));
9792
+ if (row.apiKey.length > 0) {
9793
+ return [{ id: `${row.id}:default`, apiKey: row.apiKey }];
9794
+ }
9795
+ return [];
9796
+ }
9797
+ var ProviderKeyQuotaService = class {
9798
+ constructor(box, fetchImpl = (url, init) => fetchUpstream6(url, init, { redactBodies: true }), now = Date.now) {
9799
+ this.box = box;
9800
+ this.fetchImpl = fetchImpl;
9801
+ this.now = now;
9802
+ }
9803
+ box;
9804
+ fetchImpl;
9805
+ now;
9806
+ cache = /* @__PURE__ */ new Map();
9807
+ inFlight = /* @__PURE__ */ new Map();
9808
+ /**
9809
+ * Quota for one key of a provider row, or `null` when the row has no quota
9810
+ * adapter / no such key. Cache-first; concurrent reads share one flight.
9811
+ */
9812
+ async quotaFor(row, keyId, options = {}) {
9813
+ const adapter = detectProviderKeyQuotaAdapter(resolvedBaseUrl(row));
9814
+ if (!adapter) return null;
9815
+ const entry = rowKeyEntries(row).find((candidate) => candidate.id === keyId);
9816
+ if (!entry) return null;
9817
+ const cacheKey = `${row.id}\0${keyId}`;
9818
+ const now = this.now();
9819
+ const cached = this.cache.get(cacheKey);
9820
+ if (!options.force && cached && Date.parse(cached.expiresAt) > now) return cached;
9821
+ const running = this.inFlight.get(cacheKey);
9822
+ if (running) return running;
9823
+ const promise = this.fetchQuota(adapter, row, entry.apiKey, cacheKey).catch((error) => {
9824
+ void error;
9825
+ const previous = this.cache.get(cacheKey);
9826
+ if (previous) {
9827
+ const degraded = {
9828
+ ...previous,
9829
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
9830
+ windows: previous.windows.map((window) => ({
9831
+ ...window,
9832
+ state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
9833
+ })),
9834
+ errorCode: "quota_request_failed"
9835
+ };
9836
+ this.cache.set(cacheKey, degraded);
9837
+ return degraded;
9838
+ }
9839
+ return null;
9840
+ }).finally(() => this.inFlight.delete(cacheKey));
9841
+ this.inFlight.set(cacheKey, promise);
9842
+ return promise;
9843
+ }
9844
+ /** Drop cached rows for a provider (key added/removed/rotated). */
9845
+ invalidateProvider(providerRowId) {
9846
+ for (const key of this.cache.keys()) {
9847
+ if (key.startsWith(`${providerRowId}\0`)) this.cache.delete(key);
9848
+ }
9849
+ }
9850
+ async fetchQuota(adapter, row, rawKey, cacheKey) {
9851
+ const baseUrl = resolvedBaseUrl(row);
9852
+ const url = providerKeyQuotaUrl(adapter, baseUrl);
9853
+ const key = this.box.decryptMaybe(rawKey);
9854
+ const now = this.now();
9855
+ const response = await this.fetchImpl(url, {
9856
+ method: "GET",
9857
+ headers: {
9858
+ Authorization: providerKeyQuotaAuthHeader(adapter, key),
9859
+ Accept: "application/json",
9860
+ "Content-Type": "application/json"
9861
+ },
9862
+ signal: AbortSignal.timeout(15e3)
9863
+ });
9864
+ if (response.status === 401 || response.status === 403) {
9865
+ const snapshot2 = {
9866
+ adapter,
9867
+ observedAt: new Date(now).toISOString(),
9868
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
9869
+ windows: [],
9870
+ errorCode: "quota_unauthorized"
9871
+ };
9872
+ this.cache.set(cacheKey, snapshot2);
9873
+ return snapshot2;
9874
+ }
9875
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
9876
+ let payload;
9877
+ try {
9878
+ payload = await response.json();
9879
+ } catch {
9880
+ throw new Error("invalid JSON");
9881
+ }
9882
+ const windows = parseQuotaPayload(adapter, payload, now);
9883
+ const snapshot = {
9884
+ adapter,
9885
+ observedAt: new Date(now).toISOString(),
9886
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
9887
+ windows: windows ?? [],
9888
+ ...windows ? {} : { errorCode: "quota_unavailable" }
9889
+ };
9890
+ this.cache.set(cacheKey, snapshot);
9891
+ return snapshot;
9892
+ }
9893
+ };
9894
+
8627
9895
  // src/image-generation/ImageDoctorService.ts
8628
9896
  import {
8629
9897
  normalizeImageGenerationError
@@ -14740,21 +16008,23 @@ function bucketLabel(bucketStartTs, bucket) {
14740
16008
  }
14741
16009
 
14742
16010
  // src/ports/JsonOutboundKeyDb.ts
16011
+ import { existsSync as existsSync19, readFileSync as readFileSync15 } from "fs";
16012
+ import {
16013
+ validateOutboundPermissions as validateOutboundPermissions3
16014
+ } from "@omnicross/core";
16015
+
16016
+ // src/ports/atomicFile.ts
14743
16017
  import { randomBytes as randomBytes11 } from "crypto";
14744
16018
  import {
14745
16019
  closeSync as closeSync8,
14746
16020
  existsSync as existsSync18,
14747
16021
  fsyncSync as fsyncSync7,
14748
16022
  openSync as openSync8,
14749
- readFileSync as readFileSync15,
14750
16023
  renameSync as renameSync10,
14751
16024
  unlinkSync as unlinkSync12,
14752
16025
  writeFileSync as writeFileSync13
14753
16026
  } from "fs";
14754
16027
  import { basename as basename8, dirname as dirname14, join as join19 } from "path";
14755
- import {
14756
- validateOutboundPermissions as validateOutboundPermissions3
14757
- } from "@omnicross/core";
14758
16028
  function atomicReplaceUtf8(targetPath, contents) {
14759
16029
  const tempPath = join19(
14760
16030
  dirname14(targetPath),
@@ -14784,6 +16054,8 @@ function atomicReplaceUtf8(targetPath, contents) {
14784
16054
  throw error;
14785
16055
  }
14786
16056
  }
16057
+
16058
+ // src/ports/JsonOutboundKeyDb.ts
14787
16059
  var JsonOutboundKeyDb = class {
14788
16060
  /**
14789
16061
  * @param secretBox OPTIONAL reversible-secret codec. When present, a created
@@ -14926,7 +16198,7 @@ var JsonOutboundKeyDb = class {
14926
16198
  }
14927
16199
  /** Read the key rows, tolerating a missing/corrupt file (→ empty list). */
14928
16200
  readRows() {
14929
- if (!existsSync18(this.keysPath)) return [];
16201
+ if (!existsSync19(this.keysPath)) return [];
14930
16202
  try {
14931
16203
  const parsed = JSON.parse(readFileSync15(this.keysPath, "utf8"));
14932
16204
  return Array.isArray(parsed) ? parsed : [];
@@ -14945,7 +16217,7 @@ function applyPolicyField(row, field, value) {
14945
16217
  }
14946
16218
 
14947
16219
  // src/ports/JsonPricingStore.ts
14948
- import { existsSync as existsSync19, readFileSync as readFileSync16, renameSync as renameSync11, rmSync as rmSync3, writeFileSync as writeFileSync14 } from "fs";
16220
+ import { existsSync as existsSync20, readFileSync as readFileSync16, renameSync as renameSync11, rmSync as rmSync3, writeFileSync as writeFileSync14 } from "fs";
14949
16221
  import { randomUUID as randomUUID5 } from "crypto";
14950
16222
  var JsonPricingStore = class {
14951
16223
  constructor(pricingPath) {
@@ -14960,7 +16232,7 @@ var JsonPricingStore = class {
14960
16232
  * otherwise unusable pricing table after a crash or manual file edit.
14961
16233
  */
14962
16234
  hasUsableSnapshot() {
14963
- if (!existsSync19(this.pricingPath)) return false;
16235
+ if (!existsSync20(this.pricingPath)) return false;
14964
16236
  try {
14965
16237
  const parsed = JSON.parse(readFileSync16(this.pricingPath, "utf8"));
14966
16238
  return Array.isArray(parsed) && parsed.some(isUsablePricingRow);
@@ -15075,7 +16347,7 @@ var JsonPricingStore = class {
15075
16347
  }
15076
16348
  /** Read the pricing rows, tolerating a missing/corrupt file (→ empty list). */
15077
16349
  readRows() {
15078
- if (!existsSync19(this.pricingPath)) return [];
16350
+ if (!existsSync20(this.pricingPath)) return [];
15079
16351
  try {
15080
16352
  const parsed = JSON.parse(readFileSync16(this.pricingPath, "utf8"));
15081
16353
  return Array.isArray(parsed) ? parsed : [];
@@ -15107,7 +16379,7 @@ function isUsablePricingRow(value) {
15107
16379
  }
15108
16380
 
15109
16381
  // src/pricing/PricingRefreshScheduler.ts
15110
- import { existsSync as existsSync20, readFileSync as readFileSync17, renameSync as renameSync12, writeFileSync as writeFileSync15 } from "fs";
16382
+ import { existsSync as existsSync21, readFileSync as readFileSync17, renameSync as renameSync12, writeFileSync as writeFileSync15 } from "fs";
15111
16383
  var EMPTY_STATE2 = {
15112
16384
  lastAttemptAt: null,
15113
16385
  lastSuccessAt: null,
@@ -15145,7 +16417,7 @@ var PricingRefreshScheduler = class {
15145
16417
  this.timer = null;
15146
16418
  }
15147
16419
  getState() {
15148
- if (!existsSync20(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
16420
+ if (!existsSync21(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
15149
16421
  try {
15150
16422
  const value = JSON.parse(readFileSync17(this.statePath, "utf8"));
15151
16423
  return {
@@ -15210,7 +16482,7 @@ function finiteOrNull(value) {
15210
16482
  }
15211
16483
 
15212
16484
  // src/ports/JsonVoucherDb.ts
15213
- import { existsSync as existsSync21, readFileSync as readFileSync18, writeFileSync as writeFileSync16 } from "fs";
16485
+ import { existsSync as existsSync22, readFileSync as readFileSync18, writeFileSync as writeFileSync16 } from "fs";
15214
16486
  var JsonVoucherDb = class {
15215
16487
  constructor(vouchersPath) {
15216
16488
  this.vouchersPath = vouchersPath;
@@ -15288,7 +16560,7 @@ var JsonVoucherDb = class {
15288
16560
  }
15289
16561
  /** Read the voucher rows, tolerating a missing/corrupt file (→ empty list). */
15290
16562
  readRows() {
15291
- if (!existsSync21(this.vouchersPath)) return [];
16563
+ if (!existsSync22(this.vouchersPath)) return [];
15292
16564
  try {
15293
16565
  const parsed = JSON.parse(readFileSync18(this.vouchersPath, "utf8"));
15294
16566
  return Array.isArray(parsed) ? parsed : [];
@@ -15302,16 +16574,17 @@ var JsonVoucherDb = class {
15302
16574
  };
15303
16575
 
15304
16576
  // src/ports/JsonSubscriptionCredentialStore.ts
15305
- import { existsSync as existsSync23, mkdirSync as mkdirSync6, readFileSync as readFileSync20, writeFileSync as writeFileSync17 } from "fs";
16577
+ import { existsSync as existsSync24, mkdirSync as mkdirSync6, readFileSync as readFileSync20, renameSync as renameSync13 } from "fs";
15306
16578
  import { dirname as dirname15 } from "path";
15307
16579
  import { getSharedAccountHealth as getSharedAccountHealth2 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
15308
16580
  import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling3 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
15309
- import { fetchUpstream as fetchUpstream3 } from "@omnicross/core/pipeline/upstreamFetch";
16581
+ import { fetchUpstream as fetchUpstream7 } from "@omnicross/core/pipeline/upstreamFetch";
15310
16582
  import { getSharedIdentityStore as getSharedIdentityStore2 } from "@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore";
15311
16583
  import {
15312
16584
  claudeOAuth as claudeOAuth2,
15313
16585
  codexOAuth as codexOAuth2,
15314
- geminiOAuth as geminiOAuth2
16586
+ geminiOAuth as geminiOAuth2,
16587
+ kimiOAuth as kimiOAuth2
15315
16588
  } from "@omnicross/subscriptions";
15316
16589
 
15317
16590
  // src/ports/account-sync.ts
@@ -15356,7 +16629,7 @@ function findDuplicateCredentialIds(accounts) {
15356
16629
  }
15357
16630
 
15358
16631
  // src/ports/external-cli-credentials.ts
15359
- import { existsSync as existsSync22, readFileSync as readFileSync19 } from "fs";
16632
+ import { existsSync as existsSync23, readFileSync as readFileSync19 } from "fs";
15360
16633
  import { homedir as homedir4 } from "os";
15361
16634
  import { join as join20 } from "path";
15362
16635
  function externalStorePath(provider, home = homedir4()) {
@@ -15409,7 +16682,7 @@ function parseCodexTokensEnvelope(raw) {
15409
16682
  }
15410
16683
  function readExternalCliCredentials(provider, home = homedir4()) {
15411
16684
  const path2 = externalStorePath(provider, home);
15412
- if (!existsSync22(path2)) return null;
16685
+ if (!existsSync23(path2)) return null;
15413
16686
  let raw;
15414
16687
  try {
15415
16688
  const parsed = JSON.parse(readFileSync19(path2, "utf8"));
@@ -15435,16 +16708,18 @@ var JsonSubscriptionCredentialStore = class {
15435
16708
  * as on relay refresh egresses from the SAME proxy IP as the
15436
16709
  * account's traffic. NOT used by any read/write path.
15437
16710
  */
15438
- constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials) {
16711
+ constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials, atomicReplace = atomicReplaceUtf8) {
15439
16712
  this.tokensPath = tokensPath;
15440
16713
  this.box = box;
15441
16714
  this.fetchImpl = fetchImpl;
15442
16715
  this.externalCliReader = externalCliReader;
16716
+ this.atomicReplace = atomicReplace;
15443
16717
  }
15444
16718
  tokensPath;
15445
16719
  box;
15446
16720
  fetchImpl;
15447
16721
  externalCliReader;
16722
+ atomicReplace;
15448
16723
  /**
15449
16724
  * The proxy-aware `FetchLike` for one refresh round-trip (upstream-proxy M1). A
15450
16725
  * TEST-injected `fetchImpl` is returned verbatim; otherwise the refresh routes
@@ -15458,7 +16733,7 @@ var JsonSubscriptionCredentialStore = class {
15458
16733
  * a plaintext token pair into `upstream-trace.jsonl`.
15459
16734
  */
15460
16735
  buildRefreshFetch(providerId, accountId) {
15461
- return this.fetchImpl ?? ((url, init) => fetchUpstream3(url, init, { providerId, accountId, redactBodies: true }));
16736
+ return this.fetchImpl ?? ((url, init) => fetchUpstream7(url, init, { providerId, accountId, redactBodies: true }));
15462
16737
  }
15463
16738
  /**
15464
16739
  * In-flight refresh coalescing. OAuth refresh tokens are
@@ -15499,7 +16774,7 @@ var JsonSubscriptionCredentialStore = class {
15499
16774
  * other hot reads. Never returns token material.
15500
16775
  */
15501
16776
  getAccountProxy(providerId, accountId) {
15502
- if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego") {
16777
+ if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego" && providerId !== "kimi") {
15503
16778
  return void 0;
15504
16779
  }
15505
16780
  return getAccountProxy(this.readConfig(), providerId, accountId);
@@ -15518,7 +16793,7 @@ var JsonSubscriptionCredentialStore = class {
15518
16793
  const fingerprintOn = identityStore.isEnabled();
15519
16794
  const now = Date.now();
15520
16795
  const out = {};
15521
- for (const provider of ["claude", "codex", "gemini", "opencodego"]) {
16796
+ for (const provider of ["claude", "codex", "gemini", "opencodego", "kimi"]) {
15522
16797
  const sanitized = sanitizeAccounts(config, provider);
15523
16798
  if (sanitized.length === 0) continue;
15524
16799
  for (const account of sanitized) {
@@ -15676,6 +16951,47 @@ var JsonSubscriptionCredentialStore = class {
15676
16951
  }
15677
16952
  });
15678
16953
  }
16954
+ /**
16955
+ * Refresh the Kimi Code (Moonshot) OAuth access token (device-flow grant).
16956
+ * Kimi ROTATES the refresh token, so the response's pair is written back
16957
+ * whole; the account's stable `deviceId` (fingerprint header input) is
16958
+ * preserved. The refresh call carries the CLI fingerprint headers. HONEST
16959
+ * `false` when no refresh_token.
16960
+ */
16961
+ async refreshKimiToken() {
16962
+ return this.coalesce("kimi:active", async () => {
16963
+ const config = this.readConfig();
16964
+ const active = getActiveAccount(config, "kimi");
16965
+ const kimi = active?.tokens;
16966
+ if (!active || !kimi?.refreshToken) return false;
16967
+ const capturedId = active.id;
16968
+ this.materializeMigration(config);
16969
+ const refreshFetch = this.buildRefreshFetch("kimi", capturedId);
16970
+ try {
16971
+ const result = await kimiOAuth2.refreshAccessToken(
16972
+ kimi.refreshToken,
16973
+ refreshFetch,
16974
+ kimiOAuth2.kimiFingerprintHeaders(kimi.deviceId)
16975
+ );
16976
+ const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
16977
+ const next = {
16978
+ ...kimi,
16979
+ accessToken: result.accessToken,
16980
+ refreshToken: result.refreshToken,
16981
+ expiresAt,
16982
+ status: "authorized",
16983
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
16984
+ errorMessage: void 0,
16985
+ syncWarning: void 0
16986
+ };
16987
+ this.writeBackById("kimi", capturedId, next);
16988
+ return true;
16989
+ } catch (error) {
16990
+ this.markExpiredById("kimi", capturedId, kimi, error);
16991
+ return false;
16992
+ }
16993
+ });
16994
+ }
15679
16995
  /**
15680
16996
  * Refresh a SPECIFIC managed account by id (background scheduler sweep and
15681
16997
  * account-pool resolution). It uses only that account's stored refresh
@@ -15728,7 +17044,7 @@ var JsonSubscriptionCredentialStore = class {
15728
17044
  }
15729
17045
  const oauth = account.tokens;
15730
17046
  if (!oauth.accessToken) return null;
15731
- if (providerId === "codex" || providerId === "gemini") {
17047
+ if (providerId === "codex" || providerId === "gemini" || providerId === "kimi") {
15732
17048
  const expiresAtMs = oauth.expiresAt ? Date.parse(oauth.expiresAt) : 0;
15733
17049
  const expiringSoon = expiresAtMs > 0 && Date.now() >= expiresAtMs - ACCOUNT_REFRESH_LEAD_MS;
15734
17050
  if (expiringSoon && oauth.refreshToken) {
@@ -15817,8 +17133,23 @@ var JsonSubscriptionCredentialStore = class {
15817
17133
  }
15818
17134
  /** Dispatch one OAuth refresh round-trip to the provider's shared flow. */
15819
17135
  async refreshUpstream(provider, refreshToken, accountId) {
17136
+ const refreshFetch = this.buildRefreshFetch(provider, accountId);
17137
+ if (provider === "kimi") {
17138
+ const account = accountId ? getAccountById(this.readConfig(), "kimi", accountId) : void 0;
17139
+ const deviceId = account?.tokens?.deviceId;
17140
+ const r2 = await kimiOAuth2.refreshAccessToken(
17141
+ refreshToken,
17142
+ refreshFetch,
17143
+ kimiOAuth2.kimiFingerprintHeaders(deviceId)
17144
+ );
17145
+ return {
17146
+ accessToken: r2.accessToken,
17147
+ refreshToken: r2.refreshToken,
17148
+ expiresAt: new Date(Date.now() + r2.expiresIn * 1e3).toISOString()
17149
+ };
17150
+ }
15820
17151
  const flow = provider === "claude" ? claudeOAuth2 : provider === "codex" ? codexOAuth2 : geminiOAuth2;
15821
- const r = await flow.refreshAccessToken(refreshToken, this.buildRefreshFetch(provider, accountId));
17152
+ const r = await flow.refreshAccessToken(refreshToken, refreshFetch);
15822
17153
  return {
15823
17154
  accessToken: r.accessToken,
15824
17155
  refreshToken: r.refreshToken,
@@ -15981,42 +17312,86 @@ var JsonSubscriptionCredentialStore = class {
15981
17312
  /** Write the merged config to disk as pretty JSON (mkdir parent if needed).
15982
17313
  * Encrypt-on-write: the token-material fields are encrypted (legacy plaintext
15983
17314
  * `enc:v1:`; already-`enc:`/`$ENV` untouched) before serializing, so any
15984
- * write incl. child 4's future refresh writes lands encrypted. */
17315
+ * write incl. child 4's future refresh writes lands encrypted.
17316
+ * ATOMIC: temp + fsync + rename (`atomicReplaceUtf8`) — a failed or
17317
+ * interrupted write discards only the temp file; the prior `tokens.json`
17318
+ * survives byte-equal (bare `writeFileSync` truncate-writes lost every
17319
+ * account on a mid-write failure, 2026-09-06). */
15985
17320
  persist(config) {
15986
17321
  mkdirSync6(dirname15(this.tokensPath), { recursive: true });
15987
17322
  const encrypted = encryptTokens(config, this.box);
15988
- writeFileSync17(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n", "utf8");
17323
+ this.atomicReplace(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
15989
17324
  }
15990
17325
  /**
15991
- * Read + parse `tokens.json`, tolerating a missing/corrupt file, then DECRYPT
15992
- * the token-material fields so every getter returns plaintext (the
15993
- * subscription bearer path is byte-identical).
17326
+ * Read + parse `tokens.json`, then DECRYPT the token-material fields so every
17327
+ * getter returns plaintext (the subscription bearer path is byte-identical).
17328
+ *
17329
+ * A MISSING file is a legitimate first-boot state → minimal `{ updatedAt: '' }`.
17330
+ * A file that EXISTS but cannot be parsed as a JSON object is CORRUPT →
17331
+ * `quarantineCorrupt` moves it aside (once) before the empty config is
17332
+ * returned, so the unreadable accounts survive for manual recovery.
15994
17333
  *
15995
- * The fs-read + JSON-parse tolerance is INSIDE the try (a missing or corrupt
15996
- * file empty `{ updatedAt: '' }`). The DECRYPT runs OUTSIDE the try, so a
15997
- * wrong/missing master key or a tampered `enc:` envelope FAILS FAST with the
15998
- * box's clear, secret-free error (secrets spec "/ UX":
15999
- * SHALL fail-fast, SHALL NOT a swallowed decrypt would report "no
16000
- * tokens" and silently send the WRONG bearer upstream 401). Mirrors
16001
- * `config.ts loadConfig`, which decrypts outside its parse try.
17334
+ * The DECRYPT runs OUTSIDE any try, so a wrong/missing master key or a
17335
+ * tampered `enc:` envelope FAILS FAST with the box's clear, secret-free
17336
+ * error (secrets spec "/ UX": SHALL fail-fast, SHALL NOT a swallowed
17337
+ * decrypt would report "no tokens" and silently send the WRONG bearer
17338
+ * upstream 401). Mirrors `config.ts loadConfig`, which decrypts outside
17339
+ * its parse try.
16002
17340
  */
16003
17341
  readConfig() {
16004
- if (!existsSync23(this.tokensPath)) return { updatedAt: "" };
17342
+ if (!existsSync24(this.tokensPath)) return { updatedAt: "" };
16005
17343
  let parsed;
16006
17344
  try {
16007
17345
  const raw = JSON.parse(readFileSync20(this.tokensPath, "utf8"));
16008
- parsed = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
17346
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
17347
+ return this.quarantineCorrupt("parsed JSON is not an object");
17348
+ }
17349
+ parsed = raw;
16009
17350
  } catch {
16010
- parsed = null;
17351
+ return this.quarantineCorrupt("unparseable JSON");
16011
17352
  }
16012
- if (!parsed) return { updatedAt: "" };
16013
17353
  const decrypted = decryptTokens(parsed, this.box);
16014
17354
  return migrateLazily(decrypted);
16015
17355
  }
17356
+ /** One-shot latch: a corrupt file is quarantined (or found unmovable) at
17357
+ * most once per process, so the hot read path never re-attempts or re-logs. */
17358
+ corruptQuarantined = false;
17359
+ /**
17360
+ * Quarantine a present-but-corrupt `tokens.json`, then treat it as empty.
17361
+ *
17362
+ * Renames the file to a sibling `tokens.json.corrupt-<stamp>` backup and
17363
+ * logs loudly (the daemon's stderr log; secret-free — reason + paths only).
17364
+ * The daemon KEEPS SERVING (API-key routing is unaffected; subscription
17365
+ * routing reports no credential, same as an absent file) while the corrupt
17366
+ * bytes survive for manual recovery — and, critically, the NEXT persist
17367
+ * (e.g. the user re-logging in) can no longer overwrite the only copy of
17368
+ * the old accounts, which is exactly how the 2026-09-06 incident turned a
17369
+ * recoverable truncated file into permanent account loss.
17370
+ *
17371
+ * Best-effort: if the rename fails (file locked, permissions), the corrupt
17372
+ * file is left in place and every later read still tolerates it as empty;
17373
+ * the latch still trips so the attempt + log happen exactly once.
17374
+ */
17375
+ quarantineCorrupt(reason) {
17376
+ if (!this.corruptQuarantined) {
17377
+ this.corruptQuarantined = true;
17378
+ const backup = `${this.tokensPath}.corrupt-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
17379
+ let moved = false;
17380
+ try {
17381
+ renameSync13(this.tokensPath, backup);
17382
+ moved = true;
17383
+ } catch {
17384
+ }
17385
+ console.error(
17386
+ `[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`)
17387
+ );
17388
+ }
17389
+ return { updatedAt: "" };
17390
+ }
16016
17391
  };
16017
17392
 
16018
17393
  // src/AccountHealthProbeScheduler.ts
16019
- import { fetchUpstream as fetchUpstream4 } from "@omnicross/core/pipeline/upstreamFetch";
17394
+ import { fetchUpstream as fetchUpstream8 } from "@omnicross/core/pipeline/upstreamFetch";
16020
17395
 
16021
17396
  // src/probe/CodexGenerationProbe.ts
16022
17397
  import {
@@ -16158,7 +17533,11 @@ var PROVIDER_PROBE_PLANS = {
16158
17533
  // billable/wrong endpoint). Upgrade to `{ kind:'upstream' }` once verified.
16159
17534
  codex: { kind: "local" },
16160
17535
  gemini: { kind: "local" },
16161
- opencodego: { kind: "local" }
17536
+ opencodego: { kind: "local" },
17537
+ // Kimi's `GET /coding/v1/usages` is a verified FREE authed GET (the allowance
17538
+ // collector uses it), but the probe path also needs the fingerprint headers —
17539
+ // keep the probe local until the collector covers the health surface.
17540
+ kimi: { kind: "local" }
16162
17541
  };
16163
17542
  function probePlanFor(providerId) {
16164
17543
  return PROVIDER_PROBE_PLANS[providerId] ?? { kind: "local" };
@@ -16180,7 +17559,7 @@ var AccountHealthProbeScheduler = class {
16180
17559
  this.logger = logger;
16181
17560
  this.config = config;
16182
17561
  this.now = opts.now ?? Date.now;
16183
- this.fetchImpl = opts.fetchImpl ?? fetchUpstream4;
17562
+ this.fetchImpl = opts.fetchImpl ?? fetchUpstream8;
16184
17563
  this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
16185
17564
  this.planFor = opts.planFor ?? probePlanFor;
16186
17565
  }
@@ -16524,7 +17903,7 @@ var AccountHealthSweeper = class {
16524
17903
  };
16525
17904
 
16526
17905
  // src/audit/AuditPruneSweeper.ts
16527
- import { createReadStream as createReadStream3, createWriteStream as createWriteStream2, existsSync as existsSync25, readdirSync as readdirSync8, rmSync as rmSync4, unlinkSync as unlinkSync13 } from "fs";
17906
+ import { createReadStream as createReadStream3, createWriteStream as createWriteStream2, existsSync as existsSync26, readdirSync as readdirSync8, rmSync as rmSync4, unlinkSync as unlinkSync13 } from "fs";
16528
17907
  import { join as join22 } from "path";
16529
17908
  import { pipeline } from "stream/promises";
16530
17909
  import { createGzip } from "zlib";
@@ -16532,11 +17911,11 @@ import { createGzip } from "zlib";
16532
17911
  // src/audit/auditStats.ts
16533
17912
  import {
16534
17913
  createReadStream as createReadStream2,
16535
- existsSync as existsSync24,
17914
+ existsSync as existsSync25,
16536
17915
  readFileSync as readFileSync21,
16537
17916
  readdirSync as readdirSync7,
16538
17917
  statSync as statSync7,
16539
- writeFileSync as writeFileSync18
17918
+ writeFileSync as writeFileSync17
16540
17919
  } from "fs";
16541
17920
  import { basename as basename9, dirname as dirname16, join as join21 } from "path";
16542
17921
  var SIDECAR_VERSION = 1;
@@ -16546,7 +17925,7 @@ function auditStatsFileName(auditFile) {
16546
17925
  return auditFile.replace(/\.jsonl$/, ".stats.json");
16547
17926
  }
16548
17927
  function readPersisted(path2) {
16549
- if (!existsSync24(path2)) return null;
17928
+ if (!existsSync25(path2)) return null;
16550
17929
  try {
16551
17930
  const value = JSON.parse(readFileSync21(path2, "utf8"));
16552
17931
  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)) {
@@ -16578,7 +17957,7 @@ function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfte
16578
17957
  minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
16579
17958
  maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
16580
17959
  };
16581
- writeFileSync18(statsPath, JSON.stringify(next), "utf8");
17960
+ writeFileSync17(statsPath, JSON.stringify(next), "utf8");
16582
17961
  }
16583
17962
  function queryCovers(stats, from, to) {
16584
17963
  return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
@@ -16689,7 +18068,7 @@ function mergePersistedStats(previous, appended) {
16689
18068
  };
16690
18069
  }
16691
18070
  async function readAuditStats(auditDir, query2 = {}) {
16692
- if (!existsSync24(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
18071
+ if (!existsSync25(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
16693
18072
  const from = typeof query2.from === "number" ? query2.from : -Infinity;
16694
18073
  const to = typeof query2.to === "number" ? query2.to : Infinity;
16695
18074
  let sources;
@@ -16702,7 +18081,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16702
18081
  auditPath: join21(auditDir, name),
16703
18082
  statsPath: join21(auditDir, auditStatsFileName(name))
16704
18083
  }
16705
- ).filter((source) => existsSync24(source.auditPath));
18084
+ ).filter((source) => existsSync25(source.auditPath));
16706
18085
  } catch {
16707
18086
  return { requestCount: 0, errorCount: 0, complete: false };
16708
18087
  }
@@ -16728,7 +18107,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16728
18107
  total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
16729
18108
  total.complete = total.complete && scanned.filtered.complete;
16730
18109
  const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
16731
- if (current.complete) writeFileSync18(statsPath, JSON.stringify(current), "utf8");
18110
+ if (current.complete) writeFileSync17(statsPath, JSON.stringify(current), "utf8");
16732
18111
  } catch {
16733
18112
  total.complete = false;
16734
18113
  }
@@ -16737,7 +18116,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16737
18116
  }
16738
18117
 
16739
18118
  // src/audit/AuditPruneSweeper.ts
16740
- var DAY_MS = 24 * 60 * 6e4;
18119
+ var DAY_MS3 = 24 * 60 * 6e4;
16741
18120
  var SWEEP_INTERVAL_MS2 = 60 * 6e4;
16742
18121
  var ARCHIVE_BATCH = 64;
16743
18122
  var AuditPruneSweeper = class {
@@ -16800,8 +18179,8 @@ var AuditPruneSweeper = class {
16800
18179
  if (!this.config.enabled || this.sweeping) return 0;
16801
18180
  this.sweeping = true;
16802
18181
  try {
16803
- if (!existsSync25(this.auditDir)) return 0;
16804
- const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS;
18182
+ if (!existsSync26(this.auditDir)) return 0;
18183
+ const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS3;
16805
18184
  let removed = 0;
16806
18185
  for (const name of readdirSync8(this.auditDir)) {
16807
18186
  const dateMs = auditFileDateMs(name);
@@ -16812,7 +18191,7 @@ var AuditPruneSweeper = class {
16812
18191
  } else {
16813
18192
  unlinkSync13(join22(this.auditDir, name));
16814
18193
  const statsPath = join22(this.auditDir, auditStatsFileName(name));
16815
- if (existsSync25(statsPath)) unlinkSync13(statsPath);
18194
+ if (existsSync26(statsPath)) unlinkSync13(statsPath);
16816
18195
  }
16817
18196
  removed += 1;
16818
18197
  } catch (error) {
@@ -16842,7 +18221,7 @@ var AuditPruneSweeper = class {
16842
18221
  if (!this.config.enabled || this.archiving) return 0;
16843
18222
  this.archiving = true;
16844
18223
  try {
16845
- if (!existsSync25(this.auditDir)) return 0;
18224
+ if (!existsSync26(this.auditDir)) return 0;
16846
18225
  const today = this.todayMidnight();
16847
18226
  let compressed = 0;
16848
18227
  for (const name of readdirSync8(this.auditDir)) {
@@ -16896,7 +18275,7 @@ var AuditPruneSweeper = class {
16896
18275
  const source = join22(bodiesPath, shard);
16897
18276
  const target = `${source}.gz`;
16898
18277
  try {
16899
- if (existsSync25(target)) {
18278
+ if (existsSync26(target)) {
16900
18279
  unlinkSync13(source);
16901
18280
  continue;
16902
18281
  }
@@ -16905,7 +18284,7 @@ var AuditPruneSweeper = class {
16905
18284
  compressed += 1;
16906
18285
  } catch (error) {
16907
18286
  try {
16908
- if (existsSync25(target)) unlinkSync13(target);
18287
+ if (existsSync26(target)) unlinkSync13(target);
16909
18288
  } catch {
16910
18289
  }
16911
18290
  this.logger.warn("[AuditPruneSweeper] failed to archive audit body shard", {
@@ -17058,7 +18437,7 @@ async function closeAll(writers) {
17058
18437
  // src/usage/UsagePruneSweeper.ts
17059
18438
  import { unlink as unlink3 } from "fs/promises";
17060
18439
  import { join as join24 } from "path";
17061
- var DAY_MS2 = 24 * 60 * 6e4;
18440
+ var DAY_MS4 = 24 * 60 * 6e4;
17062
18441
  var SWEEP_INTERVAL_MS3 = 60 * 6e4;
17063
18442
  var DEFAULT_USAGE_RETENTION_DAYS = 90;
17064
18443
  var UsagePruneSweeper = class {
@@ -17115,7 +18494,7 @@ var UsagePruneSweeper = class {
17115
18494
  this.sweeping = true;
17116
18495
  try {
17117
18496
  const retentionDays = this.config.retentionDays ?? DEFAULT_USAGE_RETENTION_DAYS;
17118
- const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS2;
18497
+ const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS4;
17119
18498
  let removed = 0;
17120
18499
  for (const entry of await listUsageDays(this.usageDir)) {
17121
18500
  if (!entry.hasShard) continue;
@@ -17173,7 +18552,7 @@ var UsagePruneSweeper = class {
17173
18552
  };
17174
18553
 
17175
18554
  // src/audit/auditReader.ts
17176
- import { existsSync as existsSync26, readdirSync as readdirSync9 } from "fs";
18555
+ import { existsSync as existsSync27, readdirSync as readdirSync9 } from "fs";
17177
18556
  import { join as join25 } from "path";
17178
18557
  var DEFAULT_LIMIT = 200;
17179
18558
  var MAX_LIMIT = 2e3;
@@ -17191,7 +18570,7 @@ function daySources(auditDir) {
17191
18570
  if (dateMs === null) continue;
17192
18571
  if (AUDIT_DAY_DIR_RE.test(name)) {
17193
18572
  const path2 = join25(auditDir, name, AUDIT_META_FILE);
17194
- if (existsSync26(path2)) sources.push({ path: path2, dateMs });
18573
+ if (existsSync27(path2)) sources.push({ path: path2, dateMs });
17195
18574
  } else if (AUDIT_FILE_RE.test(name)) {
17196
18575
  sources.push({ path: join25(auditDir, name), dateMs });
17197
18576
  }
@@ -17209,7 +18588,7 @@ function toMetaRecord(record) {
17209
18588
  return { ...meta, hasBody: true };
17210
18589
  }
17211
18590
  function readAuditRecords(auditDir, query2 = {}) {
17212
- if (!existsSync26(auditDir)) return [];
18591
+ if (!existsSync27(auditDir)) return [];
17213
18592
  const from = typeof query2.from === "number" ? query2.from : -Infinity;
17214
18593
  const to = typeof query2.to === "number" ? query2.to : Infinity;
17215
18594
  const limit = Math.min(MAX_LIMIT, Math.max(1, Math.trunc(query2.limit ?? DEFAULT_LIMIT)));
@@ -17237,7 +18616,7 @@ function readAuditRecords(auditDir, query2 = {}) {
17237
18616
  }
17238
18617
 
17239
18618
  // src/audit/AuditWriter.ts
17240
- import { appendFileSync as appendFileSync2, existsSync as existsSync27, mkdirSync as mkdirSync7, statSync as statSync8 } from "fs";
18619
+ import { appendFileSync as appendFileSync2, existsSync as existsSync28, mkdirSync as mkdirSync7, statSync as statSync8 } from "fs";
17241
18620
  import { join as join26 } from "path";
17242
18621
  var AuditWriter = class {
17243
18622
  constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
@@ -17295,7 +18674,7 @@ var AuditWriter = class {
17295
18674
  const { requestBody: _req, responseBody: _res, ...meta } = record;
17296
18675
  const file = join26(dayPath, AUDIT_META_FILE);
17297
18676
  const line = JSON.stringify(meta) + "\n";
17298
- const bytesBefore = existsSync27(file) ? statSync8(file).size : 0;
18677
+ const bytesBefore = existsSync28(file) ? statSync8(file).size : 0;
17299
18678
  appendFileSync2(file, line, "utf8");
17300
18679
  try {
17301
18680
  updateAuditStatsAfterAppend(
@@ -17343,7 +18722,7 @@ var AuditWriter = class {
17343
18722
  import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync8 } from "fs";
17344
18723
  import { createHmac as createHmac5 } from "crypto";
17345
18724
  import { join as join27 } from "path";
17346
- import { fetchUpstream as fetchUpstream5 } from "@omnicross/core/pipeline/upstreamFetch";
18725
+ import { fetchUpstream as fetchUpstream9 } from "@omnicross/core/pipeline/upstreamFetch";
17347
18726
 
17348
18727
  // src/billing/billingFiles.ts
17349
18728
  var BILLING_FILE_RE = /^billing-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
@@ -17366,7 +18745,7 @@ var BillingPublisher = class {
17366
18745
  constructor(billingDir, logger, opts = {}) {
17367
18746
  this.billingDir = billingDir;
17368
18747
  this.logger = logger;
17369
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream5(url, init));
18748
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream9(url, init));
17370
18749
  this.defer = opts.defer ?? ((fn) => setTimeout(fn, 0));
17371
18750
  this.timeoutMs = opts.timeoutMs ?? BILLING_POST_TIMEOUT_MS;
17372
18751
  this.now = opts.now ?? Date.now;
@@ -17479,11 +18858,11 @@ var BillingPublisher = class {
17479
18858
  };
17480
18859
 
17481
18860
  // src/billing/billingReader.ts
17482
- import { existsSync as existsSync28, readdirSync as readdirSync10, readFileSync as readFileSync22 } from "fs";
18861
+ import { existsSync as existsSync29, readdirSync as readdirSync10, readFileSync as readFileSync22 } from "fs";
17483
18862
  import { join as join28 } from "path";
17484
18863
  function readBillingLedger(billingDir) {
17485
18864
  const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
17486
- if (!existsSync28(billingDir)) return view;
18865
+ if (!existsSync29(billingDir)) return view;
17487
18866
  let files;
17488
18867
  try {
17489
18868
  files = readdirSync10(billingDir);
@@ -17616,7 +18995,7 @@ var BillingRetrySweeper = class {
17616
18995
  // src/TokenRefreshScheduler.ts
17617
18996
  var REFRESH_LEAD_MS2 = 5 * 6e4;
17618
18997
  var SWEEP_INTERVAL_MS5 = 6e4;
17619
- var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini"];
18998
+ var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini", "kimi"];
17620
18999
  var TokenRefreshScheduler = class {
17621
19000
  constructor(store, logger, intervalMs = SWEEP_INTERVAL_MS5, leadMs = REFRESH_LEAD_MS2) {
17622
19001
  this.store = store;
@@ -17699,6 +19078,8 @@ var TokenRefreshScheduler = class {
17699
19078
  return this.store.refreshCodexToken();
17700
19079
  case "gemini":
17701
19080
  return this.store.refreshGeminiToken();
19081
+ case "kimi":
19082
+ return this.store.refreshKimiToken();
17702
19083
  }
17703
19084
  }
17704
19085
  };
@@ -17777,7 +19158,7 @@ function createRouteLeaseSubscriptionPreflight(credentials) {
17777
19158
 
17778
19159
  // src/webhook/WebhookDispatcher.ts
17779
19160
  import { createHmac as createHmac6 } from "crypto";
17780
- import { fetchUpstream as fetchUpstream6 } from "@omnicross/core/pipeline/upstreamFetch";
19161
+ import { fetchUpstream as fetchUpstream10 } from "@omnicross/core/pipeline/upstreamFetch";
17781
19162
  var WEBHOOK_MAX_ATTEMPTS = 3;
17782
19163
  var WEBHOOK_QUEUE_MAX = 1e3;
17783
19164
  var WEBHOOK_SEND_TIMEOUT_MS = 1e4;
@@ -17797,7 +19178,7 @@ var WebhookDispatcher = class {
17797
19178
  sleep;
17798
19179
  now;
17799
19180
  constructor(opts = {}) {
17800
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream6(url, init));
19181
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream10(url, init));
17801
19182
  this.logger = opts.logger;
17802
19183
  this.maxAttempts = opts.maxAttempts ?? WEBHOOK_MAX_ATTEMPTS;
17803
19184
  this.queueMax = opts.queueMax ?? WEBHOOK_QUEUE_MAX;
@@ -17883,8 +19264,8 @@ var WebhookDispatcher = class {
17883
19264
  signal: AbortSignal.timeout(this.timeoutMs)
17884
19265
  });
17885
19266
  return res.ok ? { ok: true, status: res.status } : { ok: false, status: res.status };
17886
- } catch (err5) {
17887
- return { ok: false, error: err5 instanceof Error ? err5.message : String(err5) };
19267
+ } catch (err6) {
19268
+ return { ok: false, error: err6 instanceof Error ? err6.message : String(err6) };
17888
19269
  }
17889
19270
  }
17890
19271
  /**
@@ -18021,7 +19402,7 @@ function buildDaemon(config, paths) {
18021
19402
  setSecretBox(secretBox3);
18022
19403
  setSecretBox2(secretBox3);
18023
19404
  const decryptedConfig = decryptConfigSecrets(config, secretBox3);
18024
- const accountAllowanceStore = new AccountAllowanceStore3(
19405
+ const accountAllowanceStore = new AccountAllowanceStore6(
18025
19406
  Date.now,
18026
19407
  void 0,
18027
19408
  new JsonAccountAllowancePersistence(defaultAccountAllowancePath(paths.configPath))
@@ -18066,6 +19447,7 @@ function buildDaemon(config, paths) {
18066
19447
  );
18067
19448
  setGeminiCodeAssistResolver(getGeminiCodeAssistProjectResolver());
18068
19449
  const autoDisableStore = new AutoDisableStore();
19450
+ const providerKeyQuotaService = new ProviderKeyQuotaService(secretBox3);
18069
19451
  const apiKeyPool = new ApiKeyPoolService(
18070
19452
  createPoolKeysLoader((id) => llmConfig.getProviderRow(id), autoDisableStore),
18071
19453
  resolveEnvKey,
@@ -18082,7 +19464,7 @@ function buildDaemon(config, paths) {
18082
19464
  const pricingEngine = new PricingEngine(pricingStore, logger, {
18083
19465
  // Catalog egress follows the same global/env proxy policy as every other
18084
19466
  // daemon upstream call; no provider/account override applies here.
18085
- fetchImpl: ((input, init) => fetchUpstream7(String(input), init ?? {}))
19467
+ fetchImpl: ((input, init) => fetchUpstream11(String(input), init ?? {}))
18086
19468
  });
18087
19469
  const pricingRefreshScheduler = new PricingRefreshScheduler(
18088
19470
  pricingEngine,
@@ -18346,6 +19728,11 @@ function buildDaemon(config, paths) {
18346
19728
  // values themselves NEVER leave (masked via `maskProviderApiKey`).
18347
19729
  apiKeyPool,
18348
19730
  autoDisableStore,
19731
+ // BYO provider-key quota (Z.AI coding plan, MiniMax Token Plan, …): a
19732
+ // read-through cached same-key usage probe surfaced on the keys view. The
19733
+ // key plaintext is resolved + decrypted inside the service and never
19734
+ // crosses back out.
19735
+ providerKeyQuota: providerKeyQuotaService,
18349
19736
  // Interactive OAuth login over admin HTTP (app-parity child 4, design
18350
19737
  // D1/D2-a). The in-memory pending-session store (NEVER serialized), the
18351
19738
  // injected token-exchange fetch (global `fetch` here; mocked in tests), and a
@@ -18362,7 +19749,7 @@ function buildDaemon(config, paths) {
18362
19749
  // — `server.proxy.byProvider[...]` was silently skipped — and the call was
18363
19750
  // excluded from the upstream trace, so a failing login left no evidence.
18364
19751
  // `redactBodies` keeps the code/verifier + minted token out of that trace.
18365
- oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => fetchUpstream7(url, init, { providerId, redactBodies: true }),
19752
+ oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => fetchUpstream11(url, init, { providerId, redactBodies: true }),
18366
19753
  subscriptionAccountAppender: credentialStore,
18367
19754
  // Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
18368
19755
  // + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
@@ -18370,6 +19757,10 @@ function buildDaemon(config, paths) {
18370
19757
  // can inject a mock so no real port is bound.
18371
19758
  codexSessions: new CodexOAuthSessionStore(),
18372
19759
  codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
19760
+ // Kimi interactive OAuth — the async DEVICE-CODE flow store (no port, no
19761
+ // paste; the app shows the verification URL + user code and polls the
19762
+ // token-free status). Token captured + persisted daemon-side.
19763
+ kimiSessions: new CodexOAuthSessionStore(),
18373
19764
  // Migration pack (app-parity child 6, design D2/D3) — the concrete credential
18374
19765
  // store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
18375
19766
  // the multi-account append (`appendProviderAccount`, import re-encrypts at-
@@ -18428,7 +19819,7 @@ function buildDaemon(config, paths) {
18428
19819
  });
18429
19820
  const webhookDispatcher = new WebhookDispatcher({
18430
19821
  logger,
18431
- fetchImpl: (url, init) => fetchUpstream7(url, init)
19822
+ fetchImpl: (url, init) => fetchUpstream11(url, init)
18432
19823
  });
18433
19824
  setWebhookRuntime(webhookDispatcher, getSharedAccountHealth4());
18434
19825
  const auditWriter = new AuditWriter(auditDir, logger);
@@ -18505,7 +19896,7 @@ function buildDaemon(config, paths) {
18505
19896
  }
18506
19897
  function isTokensStoreReadable(tokensPath) {
18507
19898
  try {
18508
- if (!existsSync29(tokensPath)) return true;
19899
+ if (!existsSync30(tokensPath)) return true;
18509
19900
  accessSync(tokensPath, fsConstants.R_OK);
18510
19901
  return true;
18511
19902
  } catch {
@@ -18742,11 +20133,11 @@ async function runLiveProbe(url, key, fetchImpl = fetch) {
18742
20133
  status: res.status,
18743
20134
  estimateHeader: res.headers.get("x-omnicross-count-estimate")
18744
20135
  };
18745
- } catch (err5) {
20136
+ } catch (err6) {
18746
20137
  return {
18747
20138
  status: null,
18748
20139
  estimateHeader: null,
18749
- error: err5 instanceof Error ? err5.message : String(err5)
20140
+ error: err6 instanceof Error ? err6.message : String(err6)
18750
20141
  };
18751
20142
  }
18752
20143
  }
@@ -19067,7 +20458,7 @@ async function keysRevoke(db, id) {
19067
20458
  // src/commands/launch.ts
19068
20459
  import { spawn as spawn2 } from "child_process";
19069
20460
  import { randomUUID as randomUUID6 } from "crypto";
19070
- import { existsSync as existsSync30 } from "fs";
20461
+ import { existsSync as existsSync31 } from "fs";
19071
20462
  import { delimiter as delimiter2, join as join29 } from "path";
19072
20463
  import { parseArgs as parseArgs6 } from "util";
19073
20464
  import {
@@ -19115,7 +20506,7 @@ function resolveInPathDefault(candidate) {
19115
20506
  const segments = (process.env["PATH"] ?? "").split(delimiter2).filter(Boolean);
19116
20507
  for (const seg of segments) {
19117
20508
  const full = join29(seg, candidate);
19118
- if (existsSync30(full)) return full;
20509
+ if (existsSync31(full)) return full;
19119
20510
  }
19120
20511
  return null;
19121
20512
  }
@@ -19156,9 +20547,9 @@ async function runLaunch(argv, deps) {
19156
20547
  await daemon.llmConfig.ready();
19157
20548
  await daemon.migrateUsageStore();
19158
20549
  await daemon.providerProxy.start();
19159
- } catch (err5) {
20550
+ } catch (err6) {
19160
20551
  await shutdownLaunchDaemon(daemon);
19161
- throw err5;
20552
+ throw err6;
19162
20553
  }
19163
20554
  let launch;
19164
20555
  try {
@@ -19166,9 +20557,9 @@ async function runLaunch(argv, deps) {
19166
20557
  providerId: values.provider,
19167
20558
  model: values.model
19168
20559
  });
19169
- } catch (err5) {
20560
+ } catch (err6) {
19170
20561
  await shutdownLaunchDaemon(daemon);
19171
- throw err5;
20562
+ throw err6;
19172
20563
  }
19173
20564
  try {
19174
20565
  const plan = buildCliSpawnPlan({
@@ -19273,9 +20664,9 @@ function spawnCliInherit(plan) {
19273
20664
  process.removeListener("SIGINT", onSignal);
19274
20665
  process.removeListener("SIGTERM", onSignal);
19275
20666
  };
19276
- child.on("error", (err5) => {
20667
+ child.on("error", (err6) => {
19277
20668
  detach();
19278
- if (err5.code === "ENOENT") {
20669
+ if (err6.code === "ENOENT") {
19279
20670
  reject(
19280
20671
  new Error(
19281
20672
  `launch: "${plan.command}" not found on PATH \u2014 install the CLI first.`
@@ -19283,7 +20674,7 @@ function spawnCliInherit(plan) {
19283
20674
  );
19284
20675
  return;
19285
20676
  }
19286
- reject(err5);
20677
+ reject(err6);
19287
20678
  });
19288
20679
  child.on("exit", (code, signal) => {
19289
20680
  detach();
@@ -19296,9 +20687,14 @@ function spawnCliInherit(plan) {
19296
20687
  import { spawn as spawn3 } from "child_process";
19297
20688
  import { createInterface as createInterface2 } from "readline";
19298
20689
  import { parseArgs as parseArgs7 } from "util";
19299
- import { fetchUpstream as fetchUpstream8, setUpstreamProxyResolver as setUpstreamProxyResolver2 } from "@omnicross/core/pipeline/upstreamFetch";
19300
- import { claudeOAuth as claudeOAuth3, codexOAuth as codexOAuth3, geminiOAuth as geminiOAuth3 } from "@omnicross/subscriptions";
19301
- var PROVIDERS2 = ["claude", "codex", "gemini"];
20690
+ import { fetchUpstream as fetchUpstream12, setUpstreamProxyResolver as setUpstreamProxyResolver2 } from "@omnicross/core/pipeline/upstreamFetch";
20691
+ import {
20692
+ claudeOAuth as claudeOAuth3,
20693
+ codexOAuth as codexOAuth3,
20694
+ geminiOAuth as geminiOAuth3,
20695
+ kimiOAuth as kimiOAuth3
20696
+ } from "@omnicross/subscriptions";
20697
+ var PROVIDERS2 = ["claude", "codex", "gemini", "kimi"];
19302
20698
  async function runLogin(argv, deps) {
19303
20699
  const { values, positionals } = parseArgs7({
19304
20700
  args: argv,
@@ -19324,14 +20720,16 @@ async function runLogin(argv, deps) {
19324
20720
  openBrowser: deps?.openBrowser ?? openBrowser,
19325
20721
  promptPaste: deps?.promptPaste ?? promptPaste,
19326
20722
  awaitLoopback: deps?.awaitLoopback ?? ((state) => awaitLoopbackCode(state)),
20723
+ awaitKimiDevice: deps?.awaitKimiDevice ?? ((fetchImpl) => runKimiDeviceFlow(fetchImpl, resolvedOpenBrowser)),
19327
20724
  tokensFetch: deps?.tokensFetch
19328
20725
  };
20726
+ const resolvedOpenBrowser = resolved.openBrowser;
19329
20727
  const box = resolveSecretBox(values["master-key-file"]);
19330
20728
  setSecretBox(box);
19331
20729
  setUpstreamProxyResolver2(createUpstreamProxyResolver());
19332
20730
  try {
19333
20731
  const tokensPath = defaultTokensPath(values.config);
19334
- const exchangeFetch = resolved.tokensFetch ?? ((url, init) => fetchUpstream8(url, init, { providerId: provider, redactBodies: true }));
20732
+ const exchangeFetch = resolved.tokensFetch ?? ((url, init) => fetchUpstream12(url, init, { providerId: provider, redactBodies: true }));
19335
20733
  const store = new JsonSubscriptionCredentialStore(tokensPath, box, exchangeFetch);
19336
20734
  const expiresAt = await runProviderLogin(
19337
20735
  provider,
@@ -19350,6 +20748,7 @@ async function runLogin(argv, deps) {
19350
20748
  async function runProviderLogin(provider, store, deps, exchangeFetch, label) {
19351
20749
  if (provider === "codex") return loginCodex(store, deps, exchangeFetch, label);
19352
20750
  if (provider === "claude") return loginClaude(store, deps, exchangeFetch, label);
20751
+ if (provider === "kimi") return loginKimi(store, deps, exchangeFetch, label);
19353
20752
  return loginGemini(store, deps, exchangeFetch, label);
19354
20753
  }
19355
20754
  async function loginCodex(store, deps, exchangeFetch, label) {
@@ -19420,6 +20819,45 @@ async function loginGemini(store, deps, exchangeFetch, label) {
19420
20819
  logMasked("gemini", result.accessToken);
19421
20820
  return expiresAt;
19422
20821
  }
20822
+ async function runKimiDeviceFlow(exchangeFetch, openBrowserFn) {
20823
+ const deviceId = kimiOAuth3.generateKimiDeviceId();
20824
+ const fingerprint = kimiOAuth3.kimiFingerprintHeaders(deviceId);
20825
+ const authorization = await kimiOAuth3.requestDeviceAuthorization(exchangeFetch, fingerprint);
20826
+ const url = authorization.verificationUriComplete ?? authorization.verificationUri;
20827
+ console.info("Open this URL in your browser and approve the request:");
20828
+ console.info(` ${url}`);
20829
+ if (!authorization.verificationUriComplete) {
20830
+ console.info(` Then enter this code: ${authorization.userCode}`);
20831
+ }
20832
+ await openBrowserFn(url).catch(() => false);
20833
+ const result = await kimiOAuth3.awaitDeviceToken(authorization, exchangeFetch, {
20834
+ fingerprint,
20835
+ onPending: () => process.stdout.write(".")
20836
+ });
20837
+ console.info("");
20838
+ return {
20839
+ ...result,
20840
+ accountId: kimiOAuth3.kimiAccountIdFromAccessToken(result.accessToken),
20841
+ deviceId
20842
+ };
20843
+ }
20844
+ async function loginKimi(store, deps, exchangeFetch, label) {
20845
+ const result = await deps.awaitKimiDevice(exchangeFetch);
20846
+ const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
20847
+ const block = {
20848
+ authMethod: "oauth",
20849
+ status: "authorized",
20850
+ accessToken: result.accessToken,
20851
+ refreshToken: result.refreshToken,
20852
+ expiresAt,
20853
+ ...result.accountId ? { accountId: result.accountId } : {},
20854
+ deviceId: result.deviceId,
20855
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
20856
+ };
20857
+ await store.appendProviderAccount("kimi", block, label);
20858
+ logMasked("kimi", result.accessToken);
20859
+ return expiresAt;
20860
+ }
19423
20861
  function isLoginProvider(value) {
19424
20862
  return PROVIDERS2.includes(value);
19425
20863
  }
@@ -19617,7 +21055,7 @@ function providersRmKey(configPath, providerId, keyId) {
19617
21055
  }
19618
21056
 
19619
21057
  // src/commands/secrets.ts
19620
- import { existsSync as existsSync31, readFileSync as readFileSync24, writeFileSync as writeFileSync19 } from "fs";
21058
+ import { existsSync as existsSync32, readFileSync as readFileSync24 } from "fs";
19621
21059
  import { parseArgs as parseArgs9 } from "util";
19622
21060
  async function runSecrets(argv) {
19623
21061
  const { values, positionals } = parseArgs9({
@@ -19690,12 +21128,12 @@ function secretsStatus(args) {
19690
21128
  reportField("admin.token", cfg.admin.token);
19691
21129
  }
19692
21130
  const tokensPath = defaultTokensPath(args.config);
19693
- if (existsSync31(tokensPath)) {
21131
+ if (existsSync32(tokensPath)) {
19694
21132
  console.info(`Secret status for ${tokensPath}:`);
19695
21133
  reportTokenFields(tokensPath);
19696
21134
  }
19697
21135
  const integrationsPath = defaultIntegrationsPath(args.config);
19698
- if (existsSync31(integrationsPath)) {
21136
+ if (existsSync32(integrationsPath)) {
19699
21137
  const state = readRawJson(integrationsPath);
19700
21138
  const key = state.gatewayKey;
19701
21139
  if (key && typeof key === "object" && !Array.isArray(key)) {
@@ -19749,8 +21187,8 @@ async function secretsRotate(args) {
19749
21187
  const integrationsPath = defaultIntegrationsPath(args.config);
19750
21188
  try {
19751
21189
  cfg = loadConfig(args.config);
19752
- if (existsSync31(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, oldBox);
19753
- if (existsSync31(integrationsPath)) {
21190
+ if (existsSync32(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, oldBox);
21191
+ if (existsSync32(integrationsPath)) {
19754
21192
  integrationsPlain = new IntegrationStateStore(integrationsPath, oldBox).load();
19755
21193
  }
19756
21194
  } finally {
@@ -19785,13 +21223,13 @@ function secretsDecrypt(args) {
19785
21223
  let tokensPlain = null;
19786
21224
  try {
19787
21225
  cfg = loadConfig(args.config);
19788
- if (existsSync31(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, box);
21226
+ if (existsSync32(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, box);
19789
21227
  } finally {
19790
21228
  setSecretBox(null);
19791
21229
  }
19792
21230
  saveConfig(args.config, cfg);
19793
21231
  if (tokensPlain) {
19794
- writeFileSync19(tokensPath, JSON.stringify(tokensPlain, null, 2) + "\n", "utf8");
21232
+ atomicReplaceUtf8(tokensPath, JSON.stringify(tokensPlain, null, 2) + "\n");
19795
21233
  }
19796
21234
  console.info(`Decrypted secrets to plaintext in ${args.config}` + tokensSuffix(args.config));
19797
21235
  }
@@ -19816,13 +21254,13 @@ function readRawJson(path2) {
19816
21254
  }
19817
21255
  function encryptTokensFileInPlace(configPath, box) {
19818
21256
  const tokensPath = defaultTokensPath(configPath);
19819
- if (!existsSync31(tokensPath)) return;
21257
+ if (!existsSync32(tokensPath)) return;
19820
21258
  const plain = decryptTokensFile(tokensPath, box);
19821
21259
  writeTokensEncrypted(tokensPath, plain, box);
19822
21260
  }
19823
21261
  function rewriteIntegrationState(configPath, readBox, writeBox) {
19824
21262
  const path2 = defaultIntegrationsPath(configPath);
19825
- if (!existsSync31(path2)) return;
21263
+ if (!existsSync32(path2)) return;
19826
21264
  const state = new IntegrationStateStore(path2, readBox).load();
19827
21265
  new IntegrationStateStore(path2, writeBox).save(state);
19828
21266
  }
@@ -19835,7 +21273,7 @@ function writeTokensEncrypted(tokensPath, plain, box) {
19835
21273
  { updatedAt: "", ...plain },
19836
21274
  box
19837
21275
  );
19838
- writeFileSync19(tokensPath, JSON.stringify(encrypted, null, 2) + "\n", "utf8");
21276
+ atomicReplaceUtf8(tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
19839
21277
  }
19840
21278
  var TOKEN_FIELDS2 = {
19841
21279
  claude: ["accessToken", "refreshToken"],
@@ -19858,7 +21296,7 @@ function walkTokens(raw, fn) {
19858
21296
  return next;
19859
21297
  }
19860
21298
  function tokensSuffix(configPath) {
19861
- return existsSync31(defaultTokensPath(configPath)) ? " (+ tokens.json)" : "";
21299
+ return existsSync32(defaultTokensPath(configPath)) ? " (+ tokens.json)" : "";
19862
21300
  }
19863
21301
 
19864
21302
  // src/commands/start.ts
@@ -20125,7 +21563,7 @@ async function main() {
20125
21563
  process.exitCode = 1;
20126
21564
  }
20127
21565
  }
20128
- main().catch((err5) => {
20129
- console.error(err5 instanceof Error ? err5.message : String(err5));
21566
+ main().catch((err6) => {
21567
+ console.error(err6 instanceof Error ? err6.message : String(err6));
20130
21568
  process.exitCode = 1;
20131
21569
  });