@omnicross/daemon 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.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
- 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
- ]);
2276
+ const config = await this.credentials.getFullConfig();
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
2325
  return this.claudeCollector.collectMany(accounts, { force: true });
1598
2326
  }
1599
2327
  /**
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.
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 });
2357
+ }
2358
+ /**
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?.();
@@ -5028,7 +5804,7 @@ async function handleSearchQuery(req, res, deps) {
5028
5804
  // src/admin/searchAdminView.ts
5029
5805
  var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
5030
5806
  var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
5031
- function isRecord(value) {
5807
+ function isRecord2(value) {
5032
5808
  return value !== null && typeof value === "object" && !Array.isArray(value);
5033
5809
  }
5034
5810
  function redactSearchServerConfig(search) {
@@ -5078,13 +5854,13 @@ function resolveSecretField(entry, field, stored) {
5078
5854
  else delete entry[field];
5079
5855
  }
5080
5856
  function preserveSearchSecrets(incoming, current) {
5081
- if (!isRecord(incoming)) return incoming;
5857
+ if (!isRecord2(incoming)) return incoming;
5082
5858
  const section = { ...incoming };
5083
5859
  const providersValue = section["providers"];
5084
- if (!isRecord(providersValue)) return section;
5860
+ if (!isRecord2(providersValue)) return section;
5085
5861
  const providers = {};
5086
5862
  for (const [id, entryValue] of Object.entries(providersValue)) {
5087
- if (!isRecord(entryValue)) {
5863
+ if (!isRecord2(entryValue)) {
5088
5864
  providers[id] = entryValue;
5089
5865
  continue;
5090
5866
  }
@@ -5162,7 +5938,7 @@ function parseKeyPolicyBody(body) {
5162
5938
  var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
5163
5939
  var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
5164
5940
  var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
5165
- function isRecord2(value) {
5941
+ function isRecord3(value) {
5166
5942
  return !!value && typeof value === "object" && !Array.isArray(value);
5167
5943
  }
5168
5944
  function nonBlank(value) {
@@ -5182,7 +5958,7 @@ function validateGatewayBindingsSegment(patch) {
5182
5958
  const ids = /* @__PURE__ */ new Set();
5183
5959
  raw.forEach((entry, index) => {
5184
5960
  const path2 = `bindings[${index}]`;
5185
- if (!isRecord2(entry)) {
5961
+ if (!isRecord3(entry)) {
5186
5962
  errors.push(`${path2} must be an object`);
5187
5963
  return;
5188
5964
  }
@@ -5211,12 +5987,12 @@ function validateGatewayBindingsSegment(patch) {
5211
5987
  } else if (entry.modelMappings.length > 100) {
5212
5988
  errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
5213
5989
  } else if (entry.modelMappings.some(
5214
- (mapping) => !isRecord2(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
5990
+ (mapping) => !isRecord3(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
5215
5991
  )) {
5216
5992
  errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
5217
5993
  }
5218
5994
  }
5219
- if (!isRecord2(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
5995
+ if (!isRecord3(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
5220
5996
  errors.push(`${path2}.target is invalid`);
5221
5997
  } else {
5222
5998
  if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
@@ -5231,7 +6007,7 @@ function validateGatewayBindingsSegment(patch) {
5231
6007
  }
5232
6008
  }
5233
6009
  if (entry.modelMap !== void 0) {
5234
- 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")) {
5235
6011
  errors.push(`${path2}.modelMap must contain string values`);
5236
6012
  }
5237
6013
  }
@@ -5525,7 +6301,8 @@ var PROVIDER_KEYS = {
5525
6301
  block: "opencodego",
5526
6302
  accounts: "opencodegoAccounts",
5527
6303
  active: "activeOpencodegoAccountId"
5528
- }
6304
+ },
6305
+ kimi: { block: "kimi", accounts: "kimiAccounts", active: "activeKimiAccountId" }
5529
6306
  };
5530
6307
  function clone(value) {
5531
6308
  return JSON.parse(JSON.stringify(value));
@@ -6047,7 +6824,7 @@ async function applyImport(packString, passphrase, mode, deps, parseProviderInpu
6047
6824
  }
6048
6825
 
6049
6826
  // src/admin/adminMigration.ts
6050
- function err3(status, message) {
6827
+ function err4(status, message) {
6051
6828
  return { status, body: { error: { type: "admin_api_error", message } } };
6052
6829
  }
6053
6830
  async function handleExport(body, deps) {
@@ -6057,30 +6834,30 @@ async function handleExport(body, deps) {
6057
6834
  return { status: 200, body: { pack, version: BUNDLE_VERSION } };
6058
6835
  } catch (error) {
6059
6836
  if (error instanceof WeakPassphraseError) {
6060
- return err3(400, error.message);
6837
+ return err4(400, error.message);
6061
6838
  }
6062
- return err3(500, "failed to build the migration pack");
6839
+ return err4(500, "failed to build the migration pack");
6063
6840
  }
6064
6841
  }
6065
6842
  async function handleImport(body, deps) {
6066
6843
  const blob = typeof body["blob"] === "string" ? body["blob"] : "";
6067
6844
  const passphrase = typeof body["passphrase"] === "string" ? body["passphrase"] : "";
6068
6845
  const mode = body["mode"] === "overwrite" ? "overwrite" : "merge";
6069
- if (!blob) return err3(400, "import requires { blob }");
6846
+ if (!blob) return err4(400, "import requires { blob }");
6070
6847
  try {
6071
6848
  const counts = await applyImport(blob, passphrase, mode, deps, deps.parseProviderInput);
6072
6849
  return { status: 200, body: counts };
6073
6850
  } catch (error) {
6074
6851
  if (error instanceof WeakPassphraseError) {
6075
- return err3(400, error.message);
6852
+ return err4(400, error.message);
6076
6853
  }
6077
- return err3(400, error instanceof Error ? error.message : "import failed");
6854
+ return err4(400, error instanceof Error ? error.message : "import failed");
6078
6855
  }
6079
6856
  }
6080
6857
 
6081
6858
  // src/admin/usagePricing.ts
6082
6859
  import { getSharedUsageThroughputTracker } from "@omnicross/core/usage";
6083
- var err4 = (status, message) => ({
6860
+ var err5 = (status, message) => ({
6084
6861
  status,
6085
6862
  body: { error: { type: "admin_api_error", message } }
6086
6863
  });
@@ -6093,7 +6870,7 @@ function parseRange(query2) {
6093
6870
  const startTs = parseFiniteInt(query2.get("startTs"));
6094
6871
  const endTs = parseFiniteInt(query2.get("endTs"));
6095
6872
  if (startTs === null || endTs === null) {
6096
- 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");
6097
6874
  }
6098
6875
  return { startTs, endTs };
6099
6876
  }
@@ -6118,14 +6895,14 @@ async function handleUsageGet(view, query2, deps) {
6118
6895
  case "timeseries": {
6119
6896
  const bucket = query2.get("bucket");
6120
6897
  if (bucket !== "hour" && bucket !== "day" && bucket !== "month") {
6121
- return err4(400, "bucket must be one of 'hour', 'day', 'month'");
6898
+ return err5(400, "bucket must be one of 'hour', 'day', 'month'");
6122
6899
  }
6123
6900
  const now = Date.now();
6124
6901
  const clamped = { startTs: range.startTs, endTs: Math.min(range.endTs, now) };
6125
6902
  if (clamped.startTs < clamped.endTs) {
6126
6903
  const projected = Math.ceil((clamped.endTs - clamped.startTs) / BUCKET_SPAN_MS[bucket]) + 1;
6127
6904
  if (projected > MAX_TIMESERIES_BUCKETS) {
6128
- return err4(
6905
+ return err5(
6129
6906
  400,
6130
6907
  `requested range projects ~${projected} '${bucket}' buckets (max ${MAX_TIMESERIES_BUCKETS}); narrow the range or use a coarser bucket`
6131
6908
  );
@@ -6148,7 +6925,7 @@ async function handleUsageGet(view, query2, deps) {
6148
6925
  };
6149
6926
  }
6150
6927
  default:
6151
- return err4(404, `unknown usage view '${view ?? ""}'`);
6928
+ return err5(404, `unknown usage view '${view ?? ""}'`);
6152
6929
  }
6153
6930
  }
6154
6931
  function poolKeyLabels(cfg) {
@@ -6197,7 +6974,7 @@ async function handlePricingList(deps) {
6197
6974
  async function handlePricingUpsert(body, deps) {
6198
6975
  const input = parsePricingEntryInput(body);
6199
6976
  if (!input) {
6200
- 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)");
6201
6978
  }
6202
6979
  const entry = await deps.pricingEngine.upsertManual(input);
6203
6980
  return { status: 200, body: { entry } };
@@ -6206,7 +6983,7 @@ async function handlePricingDelete(query2, deps) {
6206
6983
  const providerId = query2.get("providerId")?.trim() ?? "";
6207
6984
  const modelId = query2.get("modelId")?.trim() ?? "";
6208
6985
  if (!providerId || !modelId) {
6209
- return err4(400, "delete requires providerId and modelId query params");
6986
+ return err5(400, "delete requires providerId and modelId query params");
6210
6987
  }
6211
6988
  const deleted = await deps.pricingStore.delete(providerId, modelId);
6212
6989
  if (deleted) await deps.pricingEngine.invalidateCache();
@@ -6226,13 +7003,13 @@ async function handlePricingFetchLatest(deps) {
6226
7003
  }
6227
7004
  };
6228
7005
  } catch (e) {
6229
- 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)}`);
6230
7007
  }
6231
7008
  }
6232
7009
  async function handlePricingResolveConflicts(body, deps) {
6233
7010
  const raw = body["resolutions"];
6234
7011
  if (!Array.isArray(raw)) {
6235
- return err4(400, "resolve-conflicts requires { resolutions: [...] }");
7012
+ return err5(400, "resolve-conflicts requires { resolutions: [...] }");
6236
7013
  }
6237
7014
  const currentRows = await deps.pricingStore.getAll();
6238
7015
  const userEditedKeys = new Set(
@@ -6242,21 +7019,21 @@ async function handlePricingResolveConflicts(body, deps) {
6242
7019
  const pendingIncoming = /* @__PURE__ */ new Map();
6243
7020
  let staleCount = 0;
6244
7021
  for (const item of raw) {
6245
- if (!item || typeof item !== "object") return err4(400, "invalid resolution entry");
7022
+ if (!item || typeof item !== "object") return err5(400, "invalid resolution entry");
6246
7023
  const r = item;
6247
7024
  const action = r["action"];
6248
7025
  if (action !== "overwrite" && action !== "skip") {
6249
- return err4(400, "resolution action must be 'overwrite' or 'skip'");
7026
+ return err5(400, "resolution action must be 'overwrite' or 'skip'");
6250
7027
  }
6251
7028
  const providerId = typeof r["providerId"] === "string" && r["providerId"].trim() ? r["providerId"].trim() : "";
6252
7029
  const modelId = typeof r["modelId"] === "string" && r["modelId"].trim() ? r["modelId"].trim() : "";
6253
7030
  if (!providerId || !modelId) {
6254
- return err4(400, "each resolution requires top-level providerId and modelId");
7031
+ return err5(400, "each resolution requires top-level providerId and modelId");
6255
7032
  }
6256
7033
  const incoming = parsePricingEntryInput(r["incoming"]);
6257
- 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");
6258
7035
  if (incoming.providerId !== providerId || incoming.modelId !== modelId) {
6259
- 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");
6260
7037
  }
6261
7038
  const key = `${providerId}::${modelId}`;
6262
7039
  if (action === "overwrite" && !userEditedKeys.has(key)) {
@@ -6301,7 +7078,7 @@ function query(req) {
6301
7078
  }
6302
7079
  function allowanceProvider(value) {
6303
7080
  if (!value) return void 0;
6304
- return value === "claude" || value === "codex" ? value : null;
7081
+ return value === "claude" || value === "codex" || value === "kimi" || value === "opencodego" ? value : null;
6305
7082
  }
6306
7083
  async function handleAccountAllowanceApi(req, res, method, rest, service) {
6307
7084
  if (!service) return writeError2(res, 501, "account allowance service is not available");
@@ -6315,7 +7092,9 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
6315
7092
  const params = query(req);
6316
7093
  const pathProvider = rest.length >= 2 ? rest[0] : null;
6317
7094
  const providerId = allowanceProvider(pathProvider ?? params.get("providerId") ?? params.get("provider"));
6318
- 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
+ }
6319
7098
  const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
6320
7099
  const allowances = await service.list({ providerId, accountId });
6321
7100
  return writeJson3(res, 200, { allowances });
@@ -6325,10 +7104,37 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
6325
7104
  const requestedProvider = allowanceProvider(
6326
7105
  typeof body["providerId"] === "string" ? body["providerId"] : "claude"
6327
7106
  );
6328
- if (requestedProvider !== "claude") {
6329
- return writeError2(res, 400, "only Claude allowances support explicit refresh");
6330
- }
6331
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
+ }
6332
7138
  const allowances = await service.refreshClaude(accountId);
6333
7139
  if (accountId && allowances.length === 0) {
6334
7140
  return writeError2(res, 404, `Claude account '${accountId}' not found`);
@@ -6499,8 +7305,8 @@ async function handleAdminApi(req, res, path2, deps) {
6499
7305
  default:
6500
7306
  return writeJsonError(res, 404, `unknown admin resource '${resource}'`);
6501
7307
  }
6502
- } catch (err5) {
6503
- writeJsonError(res, 500, err5 instanceof Error ? err5.message : String(err5));
7308
+ } catch (err6) {
7309
+ writeJsonError(res, 500, err6 instanceof Error ? err6.message : String(err6));
6504
7310
  }
6505
7311
  }
6506
7312
  function requestQuery(req) {
@@ -6570,6 +7376,9 @@ async function handleProviders(req, res, method, rest, deps) {
6570
7376
  if (method === "POST" && rest.length === 4 && rest[1] === "keys" && rest[3] === "enabled") {
6571
7377
  return await handleToggleProviderKey(req, res, rest[0], rest[2], cfg, deps);
6572
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
+ }
6573
7382
  if (method === "PUT" && rest.length === 3 && rest[1] === "keys") {
6574
7383
  return await handleUpdateProviderKey(req, res, rest[0], rest[2], cfg, deps);
6575
7384
  }
@@ -6668,7 +7477,7 @@ async function handleDiscoverModels(res, id, cfg) {
6668
7477
  try {
6669
7478
  const headers = { Accept: "application/json" };
6670
7479
  if (resolvedKey) headers["Authorization"] = `Bearer ${resolvedKey}`;
6671
- const response = await fetchUpstream2(url, { method: "GET", headers }, { providerId: "byo" });
7480
+ const response = await fetchUpstream5(url, { method: "GET", headers }, { providerId: "byo" });
6672
7481
  if (!response.ok) {
6673
7482
  const text = await response.text().catch(() => "");
6674
7483
  let message = text.slice(0, 300);
@@ -6685,8 +7494,8 @@ async function handleDiscoverModels(res, id, cfg) {
6685
7494
  const data = await response.json();
6686
7495
  const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
6687
7496
  return writeJson4(res, 200, { models });
6688
- } catch (err5) {
6689
- const message = err5 instanceof Error ? err5.message : String(err5);
7497
+ } catch (err6) {
7498
+ const message = err6 instanceof Error ? err6.message : String(err6);
6690
7499
  return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
6691
7500
  }
6692
7501
  }
@@ -6727,7 +7536,7 @@ async function handleTestModel(req, res, id, cfg) {
6727
7536
  }
6728
7537
  const startedAt = Date.now();
6729
7538
  try {
6730
- const response = await fetchUpstream2(
7539
+ const response = await fetchUpstream5(
6731
7540
  url,
6732
7541
  { method: "POST", headers, body: JSON.stringify(payload) },
6733
7542
  { providerId: "byo" }
@@ -6749,8 +7558,8 @@ async function handleTestModel(req, res, id, cfg) {
6749
7558
  latencyMs,
6750
7559
  sample: extractSampleText(text, row.apiFormat)
6751
7560
  });
6752
- } catch (err5) {
6753
- const message = err5 instanceof Error ? err5.message : String(err5);
7561
+ } catch (err6) {
7562
+ const message = err6 instanceof Error ? err6.message : String(err6);
6754
7563
  return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
6755
7564
  }
6756
7565
  }
@@ -6792,7 +7601,30 @@ async function handleProviderKeys(res, id, cfg, deps) {
6792
7601
  const row = cfg.providers.find((p) => p.id === id);
6793
7602
  if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
6794
7603
  const cooldown = await deps.apiKeyPool.getKeyHealth(id);
6795
- 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
+ }
6796
7628
  }
6797
7629
  function parsePoolKeyInput(body, existing) {
6798
7630
  const out = {};
@@ -7537,12 +8369,12 @@ async function handleAccounts(req, res, method, rest, deps) {
7537
8369
  }
7538
8370
  return writeJson4(res, 200, { ok: true, affected: result.affected });
7539
8371
  }
7540
- if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
7541
- 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);
7542
8374
  return writeJson4(res, result.status, result.body);
7543
8375
  }
7544
- if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
7545
- 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);
7546
8378
  return writeJson4(res, result.status, result.body);
7547
8379
  }
7548
8380
  if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
@@ -7595,7 +8427,15 @@ async function handleAccounts(req, res, method, rest, deps) {
7595
8427
  return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
7596
8428
  }
7597
8429
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
7598
- 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);
7599
8439
  return writeJson4(res, result.status, result.body);
7600
8440
  }
7601
8441
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
@@ -8089,12 +8929,12 @@ async function handlePlayground(req, res, method, deps) {
8089
8929
  const payload = body["body"];
8090
8930
  const status = deps.outboundApiServer.getStatus();
8091
8931
  if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
8092
- const path2 = resolvePlaygroundPath(endpoint, isRecord3(payload) ? payload : {});
8932
+ const path2 = resolvePlaygroundPath(endpoint, isRecord4(payload) ? payload : {});
8093
8933
  if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
8094
8934
  const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
8095
8935
  await proxyToOutbound(res, status.port, path2, key, upstreamBody);
8096
8936
  }
8097
- function isRecord3(v) {
8937
+ function isRecord4(v) {
8098
8938
  return !!v && typeof v === "object" && !Array.isArray(v);
8099
8939
  }
8100
8940
  function proxyToOutbound(res, outboundPort, path2, key, body) {
@@ -8123,8 +8963,8 @@ function proxyToOutbound(res, outboundPort, path2, key, body) {
8123
8963
  });
8124
8964
  }
8125
8965
  );
8126
- upstream.on("error", (err5) => {
8127
- 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}`);
8128
8968
  else res.end();
8129
8969
  resolve11();
8130
8970
  });
@@ -8229,7 +9069,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
8229
9069
  }
8230
9070
 
8231
9071
  // src/admin/version.ts
8232
- var DAEMON_VERSION = true ? "0.3.0" : "0.0.0-dev";
9072
+ var DAEMON_VERSION = true ? "0.3.1" : "0.0.0-dev";
8233
9073
 
8234
9074
  // src/admin/AdminServer.ts
8235
9075
  var LOOPBACK_ADDR = "127.0.0.1";
@@ -8272,13 +9112,13 @@ var AdminServer = class {
8272
9112
  const server = http2.createServer((req, res) => {
8273
9113
  this.onRequest(req, res);
8274
9114
  });
8275
- const onError = (err5) => {
8276
- if (err5.code === "EADDRINUSE" && port !== 0) {
9115
+ const onError = (err6) => {
9116
+ if (err6.code === "EADDRINUSE" && port !== 0) {
8277
9117
  server.removeListener("error", onError);
8278
9118
  this.listen(bindAddr, 0).then(resolve11, reject);
8279
9119
  return;
8280
9120
  }
8281
- reject(err5);
9121
+ reject(err6);
8282
9122
  };
8283
9123
  server.on("error", onError);
8284
9124
  server.listen(port, bindAddr, () => {
@@ -8296,8 +9136,8 @@ var AdminServer = class {
8296
9136
  }
8297
9137
  /** Per-request handler: auth gate (when a token is set) → routing. */
8298
9138
  onRequest(req, res) {
8299
- void this.dispatch(req, res).catch((err5) => {
8300
- 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);
8301
9141
  this.deps.logger.error("[AdminServer] unhandled error:", message);
8302
9142
  if (!res.headersSent) {
8303
9143
  res.writeHead(500, { "Content-Type": "application/json" });
@@ -8561,18 +9401,18 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
8561
9401
  return;
8562
9402
  }
8563
9403
  signal?.addEventListener("abort", abort, { once: true });
8564
- server.on("error", (err5) => {
9404
+ server.on("error", (err6) => {
8565
9405
  if (settled) return;
8566
9406
  settled = true;
8567
9407
  clearTimeout(timer);
8568
- if (err5.code === "EADDRINUSE") {
9408
+ if (err6.code === "EADDRINUSE") {
8569
9409
  reject(
8570
9410
  new Error(
8571
9411
  `login: cannot bind ${LOOPBACK_HOST}:${LOOPBACK_PORT} (address in use) \u2014 another codex login or process is holding the port`
8572
9412
  )
8573
9413
  );
8574
9414
  } else {
8575
- reject(err5);
9415
+ reject(err6);
8576
9416
  }
8577
9417
  });
8578
9418
  const timer = setTimeout(() => {
@@ -8647,6 +9487,411 @@ function createPoolKeysLoader(getProviderRow, autoDisabled) {
8647
9487
  };
8648
9488
  }
8649
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
+
8650
9895
  // src/image-generation/ImageDoctorService.ts
8651
9896
  import {
8652
9897
  normalizeImageGenerationError
@@ -14763,21 +16008,23 @@ function bucketLabel(bucketStartTs, bucket) {
14763
16008
  }
14764
16009
 
14765
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
14766
16017
  import { randomBytes as randomBytes11 } from "crypto";
14767
16018
  import {
14768
16019
  closeSync as closeSync8,
14769
16020
  existsSync as existsSync18,
14770
16021
  fsyncSync as fsyncSync7,
14771
16022
  openSync as openSync8,
14772
- readFileSync as readFileSync15,
14773
16023
  renameSync as renameSync10,
14774
16024
  unlinkSync as unlinkSync12,
14775
16025
  writeFileSync as writeFileSync13
14776
16026
  } from "fs";
14777
16027
  import { basename as basename8, dirname as dirname14, join as join19 } from "path";
14778
- import {
14779
- validateOutboundPermissions as validateOutboundPermissions3
14780
- } from "@omnicross/core";
14781
16028
  function atomicReplaceUtf8(targetPath, contents) {
14782
16029
  const tempPath = join19(
14783
16030
  dirname14(targetPath),
@@ -14807,6 +16054,8 @@ function atomicReplaceUtf8(targetPath, contents) {
14807
16054
  throw error;
14808
16055
  }
14809
16056
  }
16057
+
16058
+ // src/ports/JsonOutboundKeyDb.ts
14810
16059
  var JsonOutboundKeyDb = class {
14811
16060
  /**
14812
16061
  * @param secretBox OPTIONAL reversible-secret codec. When present, a created
@@ -14949,7 +16198,7 @@ var JsonOutboundKeyDb = class {
14949
16198
  }
14950
16199
  /** Read the key rows, tolerating a missing/corrupt file (→ empty list). */
14951
16200
  readRows() {
14952
- if (!existsSync18(this.keysPath)) return [];
16201
+ if (!existsSync19(this.keysPath)) return [];
14953
16202
  try {
14954
16203
  const parsed = JSON.parse(readFileSync15(this.keysPath, "utf8"));
14955
16204
  return Array.isArray(parsed) ? parsed : [];
@@ -14968,7 +16217,7 @@ function applyPolicyField(row, field, value) {
14968
16217
  }
14969
16218
 
14970
16219
  // src/ports/JsonPricingStore.ts
14971
- 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";
14972
16221
  import { randomUUID as randomUUID5 } from "crypto";
14973
16222
  var JsonPricingStore = class {
14974
16223
  constructor(pricingPath) {
@@ -14983,7 +16232,7 @@ var JsonPricingStore = class {
14983
16232
  * otherwise unusable pricing table after a crash or manual file edit.
14984
16233
  */
14985
16234
  hasUsableSnapshot() {
14986
- if (!existsSync19(this.pricingPath)) return false;
16235
+ if (!existsSync20(this.pricingPath)) return false;
14987
16236
  try {
14988
16237
  const parsed = JSON.parse(readFileSync16(this.pricingPath, "utf8"));
14989
16238
  return Array.isArray(parsed) && parsed.some(isUsablePricingRow);
@@ -15098,7 +16347,7 @@ var JsonPricingStore = class {
15098
16347
  }
15099
16348
  /** Read the pricing rows, tolerating a missing/corrupt file (→ empty list). */
15100
16349
  readRows() {
15101
- if (!existsSync19(this.pricingPath)) return [];
16350
+ if (!existsSync20(this.pricingPath)) return [];
15102
16351
  try {
15103
16352
  const parsed = JSON.parse(readFileSync16(this.pricingPath, "utf8"));
15104
16353
  return Array.isArray(parsed) ? parsed : [];
@@ -15130,7 +16379,7 @@ function isUsablePricingRow(value) {
15130
16379
  }
15131
16380
 
15132
16381
  // src/pricing/PricingRefreshScheduler.ts
15133
- 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";
15134
16383
  var EMPTY_STATE2 = {
15135
16384
  lastAttemptAt: null,
15136
16385
  lastSuccessAt: null,
@@ -15168,7 +16417,7 @@ var PricingRefreshScheduler = class {
15168
16417
  this.timer = null;
15169
16418
  }
15170
16419
  getState() {
15171
- if (!existsSync20(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
16420
+ if (!existsSync21(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
15172
16421
  try {
15173
16422
  const value = JSON.parse(readFileSync17(this.statePath, "utf8"));
15174
16423
  return {
@@ -15233,7 +16482,7 @@ function finiteOrNull(value) {
15233
16482
  }
15234
16483
 
15235
16484
  // src/ports/JsonVoucherDb.ts
15236
- 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";
15237
16486
  var JsonVoucherDb = class {
15238
16487
  constructor(vouchersPath) {
15239
16488
  this.vouchersPath = vouchersPath;
@@ -15311,7 +16560,7 @@ var JsonVoucherDb = class {
15311
16560
  }
15312
16561
  /** Read the voucher rows, tolerating a missing/corrupt file (→ empty list). */
15313
16562
  readRows() {
15314
- if (!existsSync21(this.vouchersPath)) return [];
16563
+ if (!existsSync22(this.vouchersPath)) return [];
15315
16564
  try {
15316
16565
  const parsed = JSON.parse(readFileSync18(this.vouchersPath, "utf8"));
15317
16566
  return Array.isArray(parsed) ? parsed : [];
@@ -15325,16 +16574,17 @@ var JsonVoucherDb = class {
15325
16574
  };
15326
16575
 
15327
16576
  // src/ports/JsonSubscriptionCredentialStore.ts
15328
- 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";
15329
16578
  import { dirname as dirname15 } from "path";
15330
16579
  import { getSharedAccountHealth as getSharedAccountHealth2 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
15331
16580
  import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling3 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
15332
- import { fetchUpstream as fetchUpstream3 } from "@omnicross/core/pipeline/upstreamFetch";
16581
+ import { fetchUpstream as fetchUpstream7 } from "@omnicross/core/pipeline/upstreamFetch";
15333
16582
  import { getSharedIdentityStore as getSharedIdentityStore2 } from "@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore";
15334
16583
  import {
15335
16584
  claudeOAuth as claudeOAuth2,
15336
16585
  codexOAuth as codexOAuth2,
15337
- geminiOAuth as geminiOAuth2
16586
+ geminiOAuth as geminiOAuth2,
16587
+ kimiOAuth as kimiOAuth2
15338
16588
  } from "@omnicross/subscriptions";
15339
16589
 
15340
16590
  // src/ports/account-sync.ts
@@ -15379,7 +16629,7 @@ function findDuplicateCredentialIds(accounts) {
15379
16629
  }
15380
16630
 
15381
16631
  // src/ports/external-cli-credentials.ts
15382
- import { existsSync as existsSync22, readFileSync as readFileSync19 } from "fs";
16632
+ import { existsSync as existsSync23, readFileSync as readFileSync19 } from "fs";
15383
16633
  import { homedir as homedir4 } from "os";
15384
16634
  import { join as join20 } from "path";
15385
16635
  function externalStorePath(provider, home = homedir4()) {
@@ -15432,7 +16682,7 @@ function parseCodexTokensEnvelope(raw) {
15432
16682
  }
15433
16683
  function readExternalCliCredentials(provider, home = homedir4()) {
15434
16684
  const path2 = externalStorePath(provider, home);
15435
- if (!existsSync22(path2)) return null;
16685
+ if (!existsSync23(path2)) return null;
15436
16686
  let raw;
15437
16687
  try {
15438
16688
  const parsed = JSON.parse(readFileSync19(path2, "utf8"));
@@ -15458,16 +16708,18 @@ var JsonSubscriptionCredentialStore = class {
15458
16708
  * as on relay refresh egresses from the SAME proxy IP as the
15459
16709
  * account's traffic. NOT used by any read/write path.
15460
16710
  */
15461
- constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials) {
16711
+ constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials, atomicReplace = atomicReplaceUtf8) {
15462
16712
  this.tokensPath = tokensPath;
15463
16713
  this.box = box;
15464
16714
  this.fetchImpl = fetchImpl;
15465
16715
  this.externalCliReader = externalCliReader;
16716
+ this.atomicReplace = atomicReplace;
15466
16717
  }
15467
16718
  tokensPath;
15468
16719
  box;
15469
16720
  fetchImpl;
15470
16721
  externalCliReader;
16722
+ atomicReplace;
15471
16723
  /**
15472
16724
  * The proxy-aware `FetchLike` for one refresh round-trip (upstream-proxy M1). A
15473
16725
  * TEST-injected `fetchImpl` is returned verbatim; otherwise the refresh routes
@@ -15481,7 +16733,7 @@ var JsonSubscriptionCredentialStore = class {
15481
16733
  * a plaintext token pair into `upstream-trace.jsonl`.
15482
16734
  */
15483
16735
  buildRefreshFetch(providerId, accountId) {
15484
- 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 }));
15485
16737
  }
15486
16738
  /**
15487
16739
  * In-flight refresh coalescing. OAuth refresh tokens are
@@ -15522,7 +16774,7 @@ var JsonSubscriptionCredentialStore = class {
15522
16774
  * other hot reads. Never returns token material.
15523
16775
  */
15524
16776
  getAccountProxy(providerId, accountId) {
15525
- if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego") {
16777
+ if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego" && providerId !== "kimi") {
15526
16778
  return void 0;
15527
16779
  }
15528
16780
  return getAccountProxy(this.readConfig(), providerId, accountId);
@@ -15541,7 +16793,7 @@ var JsonSubscriptionCredentialStore = class {
15541
16793
  const fingerprintOn = identityStore.isEnabled();
15542
16794
  const now = Date.now();
15543
16795
  const out = {};
15544
- for (const provider of ["claude", "codex", "gemini", "opencodego"]) {
16796
+ for (const provider of ["claude", "codex", "gemini", "opencodego", "kimi"]) {
15545
16797
  const sanitized = sanitizeAccounts(config, provider);
15546
16798
  if (sanitized.length === 0) continue;
15547
16799
  for (const account of sanitized) {
@@ -15699,6 +16951,47 @@ var JsonSubscriptionCredentialStore = class {
15699
16951
  }
15700
16952
  });
15701
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
+ }
15702
16995
  /**
15703
16996
  * Refresh a SPECIFIC managed account by id (background scheduler sweep and
15704
16997
  * account-pool resolution). It uses only that account's stored refresh
@@ -15751,7 +17044,7 @@ var JsonSubscriptionCredentialStore = class {
15751
17044
  }
15752
17045
  const oauth = account.tokens;
15753
17046
  if (!oauth.accessToken) return null;
15754
- if (providerId === "codex" || providerId === "gemini") {
17047
+ if (providerId === "codex" || providerId === "gemini" || providerId === "kimi") {
15755
17048
  const expiresAtMs = oauth.expiresAt ? Date.parse(oauth.expiresAt) : 0;
15756
17049
  const expiringSoon = expiresAtMs > 0 && Date.now() >= expiresAtMs - ACCOUNT_REFRESH_LEAD_MS;
15757
17050
  if (expiringSoon && oauth.refreshToken) {
@@ -15840,8 +17133,23 @@ var JsonSubscriptionCredentialStore = class {
15840
17133
  }
15841
17134
  /** Dispatch one OAuth refresh round-trip to the provider's shared flow. */
15842
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
+ }
15843
17151
  const flow = provider === "claude" ? claudeOAuth2 : provider === "codex" ? codexOAuth2 : geminiOAuth2;
15844
- const r = await flow.refreshAccessToken(refreshToken, this.buildRefreshFetch(provider, accountId));
17152
+ const r = await flow.refreshAccessToken(refreshToken, refreshFetch);
15845
17153
  return {
15846
17154
  accessToken: r.accessToken,
15847
17155
  refreshToken: r.refreshToken,
@@ -16004,42 +17312,86 @@ var JsonSubscriptionCredentialStore = class {
16004
17312
  /** Write the merged config to disk as pretty JSON (mkdir parent if needed).
16005
17313
  * Encrypt-on-write: the token-material fields are encrypted (legacy plaintext
16006
17314
  * `enc:v1:`; already-`enc:`/`$ENV` untouched) before serializing, so any
16007
- * 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). */
16008
17320
  persist(config) {
16009
17321
  mkdirSync6(dirname15(this.tokensPath), { recursive: true });
16010
17322
  const encrypted = encryptTokens(config, this.box);
16011
- writeFileSync17(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n", "utf8");
17323
+ this.atomicReplace(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
16012
17324
  }
16013
17325
  /**
16014
- * Read + parse `tokens.json`, tolerating a missing/corrupt file, then DECRYPT
16015
- * the token-material fields so every getter returns plaintext (the
16016
- * subscription bearer path is byte-identical).
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.
16017
17333
  *
16018
- * The fs-read + JSON-parse tolerance is INSIDE the try (a missing or corrupt
16019
- * file empty `{ updatedAt: '' }`). The DECRYPT runs OUTSIDE the try, so a
16020
- * wrong/missing master key or a tampered `enc:` envelope FAILS FAST with the
16021
- * box's clear, secret-free error (secrets spec "/ UX":
16022
- * SHALL fail-fast, SHALL NOT a swallowed decrypt would report "no
16023
- * tokens" and silently send the WRONG bearer upstream 401). Mirrors
16024
- * `config.ts loadConfig`, which decrypts outside its parse try.
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.
16025
17340
  */
16026
17341
  readConfig() {
16027
- if (!existsSync23(this.tokensPath)) return { updatedAt: "" };
17342
+ if (!existsSync24(this.tokensPath)) return { updatedAt: "" };
16028
17343
  let parsed;
16029
17344
  try {
16030
17345
  const raw = JSON.parse(readFileSync20(this.tokensPath, "utf8"));
16031
- 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;
16032
17350
  } catch {
16033
- parsed = null;
17351
+ return this.quarantineCorrupt("unparseable JSON");
16034
17352
  }
16035
- if (!parsed) return { updatedAt: "" };
16036
17353
  const decrypted = decryptTokens(parsed, this.box);
16037
17354
  return migrateLazily(decrypted);
16038
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
+ }
16039
17391
  };
16040
17392
 
16041
17393
  // src/AccountHealthProbeScheduler.ts
16042
- import { fetchUpstream as fetchUpstream4 } from "@omnicross/core/pipeline/upstreamFetch";
17394
+ import { fetchUpstream as fetchUpstream8 } from "@omnicross/core/pipeline/upstreamFetch";
16043
17395
 
16044
17396
  // src/probe/CodexGenerationProbe.ts
16045
17397
  import {
@@ -16181,7 +17533,11 @@ var PROVIDER_PROBE_PLANS = {
16181
17533
  // billable/wrong endpoint). Upgrade to `{ kind:'upstream' }` once verified.
16182
17534
  codex: { kind: "local" },
16183
17535
  gemini: { kind: "local" },
16184
- 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" }
16185
17541
  };
16186
17542
  function probePlanFor(providerId) {
16187
17543
  return PROVIDER_PROBE_PLANS[providerId] ?? { kind: "local" };
@@ -16203,7 +17559,7 @@ var AccountHealthProbeScheduler = class {
16203
17559
  this.logger = logger;
16204
17560
  this.config = config;
16205
17561
  this.now = opts.now ?? Date.now;
16206
- this.fetchImpl = opts.fetchImpl ?? fetchUpstream4;
17562
+ this.fetchImpl = opts.fetchImpl ?? fetchUpstream8;
16207
17563
  this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
16208
17564
  this.planFor = opts.planFor ?? probePlanFor;
16209
17565
  }
@@ -16547,7 +17903,7 @@ var AccountHealthSweeper = class {
16547
17903
  };
16548
17904
 
16549
17905
  // src/audit/AuditPruneSweeper.ts
16550
- import { createReadStream as createReadStream3, createWriteStream as createWriteStream2, existsSync as existsSync25, readdirSync as readdirSync8, rmSync as rmSync4, unlinkSync as unlinkSync13 } from "fs";
17906
+ import { createReadStream as createReadStream3, createWriteStream as createWriteStream2, existsSync as existsSync26, readdirSync as readdirSync8, rmSync as rmSync4, unlinkSync as unlinkSync13 } from "fs";
16551
17907
  import { join as join22 } from "path";
16552
17908
  import { pipeline } from "stream/promises";
16553
17909
  import { createGzip } from "zlib";
@@ -16555,11 +17911,11 @@ import { createGzip } from "zlib";
16555
17911
  // src/audit/auditStats.ts
16556
17912
  import {
16557
17913
  createReadStream as createReadStream2,
16558
- existsSync as existsSync24,
17914
+ existsSync as existsSync25,
16559
17915
  readFileSync as readFileSync21,
16560
17916
  readdirSync as readdirSync7,
16561
17917
  statSync as statSync7,
16562
- writeFileSync as writeFileSync18
17918
+ writeFileSync as writeFileSync17
16563
17919
  } from "fs";
16564
17920
  import { basename as basename9, dirname as dirname16, join as join21 } from "path";
16565
17921
  var SIDECAR_VERSION = 1;
@@ -16569,7 +17925,7 @@ function auditStatsFileName(auditFile) {
16569
17925
  return auditFile.replace(/\.jsonl$/, ".stats.json");
16570
17926
  }
16571
17927
  function readPersisted(path2) {
16572
- if (!existsSync24(path2)) return null;
17928
+ if (!existsSync25(path2)) return null;
16573
17929
  try {
16574
17930
  const value = JSON.parse(readFileSync21(path2, "utf8"));
16575
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)) {
@@ -16601,7 +17957,7 @@ function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfte
16601
17957
  minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
16602
17958
  maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
16603
17959
  };
16604
- writeFileSync18(statsPath, JSON.stringify(next), "utf8");
17960
+ writeFileSync17(statsPath, JSON.stringify(next), "utf8");
16605
17961
  }
16606
17962
  function queryCovers(stats, from, to) {
16607
17963
  return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
@@ -16712,7 +18068,7 @@ function mergePersistedStats(previous, appended) {
16712
18068
  };
16713
18069
  }
16714
18070
  async function readAuditStats(auditDir, query2 = {}) {
16715
- if (!existsSync24(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
18071
+ if (!existsSync25(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
16716
18072
  const from = typeof query2.from === "number" ? query2.from : -Infinity;
16717
18073
  const to = typeof query2.to === "number" ? query2.to : Infinity;
16718
18074
  let sources;
@@ -16725,7 +18081,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16725
18081
  auditPath: join21(auditDir, name),
16726
18082
  statsPath: join21(auditDir, auditStatsFileName(name))
16727
18083
  }
16728
- ).filter((source) => existsSync24(source.auditPath));
18084
+ ).filter((source) => existsSync25(source.auditPath));
16729
18085
  } catch {
16730
18086
  return { requestCount: 0, errorCount: 0, complete: false };
16731
18087
  }
@@ -16751,7 +18107,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16751
18107
  total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
16752
18108
  total.complete = total.complete && scanned.filtered.complete;
16753
18109
  const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
16754
- if (current.complete) writeFileSync18(statsPath, JSON.stringify(current), "utf8");
18110
+ if (current.complete) writeFileSync17(statsPath, JSON.stringify(current), "utf8");
16755
18111
  } catch {
16756
18112
  total.complete = false;
16757
18113
  }
@@ -16760,7 +18116,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16760
18116
  }
16761
18117
 
16762
18118
  // src/audit/AuditPruneSweeper.ts
16763
- var DAY_MS = 24 * 60 * 6e4;
18119
+ var DAY_MS3 = 24 * 60 * 6e4;
16764
18120
  var SWEEP_INTERVAL_MS2 = 60 * 6e4;
16765
18121
  var ARCHIVE_BATCH = 64;
16766
18122
  var AuditPruneSweeper = class {
@@ -16823,8 +18179,8 @@ var AuditPruneSweeper = class {
16823
18179
  if (!this.config.enabled || this.sweeping) return 0;
16824
18180
  this.sweeping = true;
16825
18181
  try {
16826
- if (!existsSync25(this.auditDir)) return 0;
16827
- 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;
16828
18184
  let removed = 0;
16829
18185
  for (const name of readdirSync8(this.auditDir)) {
16830
18186
  const dateMs = auditFileDateMs(name);
@@ -16835,7 +18191,7 @@ var AuditPruneSweeper = class {
16835
18191
  } else {
16836
18192
  unlinkSync13(join22(this.auditDir, name));
16837
18193
  const statsPath = join22(this.auditDir, auditStatsFileName(name));
16838
- if (existsSync25(statsPath)) unlinkSync13(statsPath);
18194
+ if (existsSync26(statsPath)) unlinkSync13(statsPath);
16839
18195
  }
16840
18196
  removed += 1;
16841
18197
  } catch (error) {
@@ -16865,7 +18221,7 @@ var AuditPruneSweeper = class {
16865
18221
  if (!this.config.enabled || this.archiving) return 0;
16866
18222
  this.archiving = true;
16867
18223
  try {
16868
- if (!existsSync25(this.auditDir)) return 0;
18224
+ if (!existsSync26(this.auditDir)) return 0;
16869
18225
  const today = this.todayMidnight();
16870
18226
  let compressed = 0;
16871
18227
  for (const name of readdirSync8(this.auditDir)) {
@@ -16919,7 +18275,7 @@ var AuditPruneSweeper = class {
16919
18275
  const source = join22(bodiesPath, shard);
16920
18276
  const target = `${source}.gz`;
16921
18277
  try {
16922
- if (existsSync25(target)) {
18278
+ if (existsSync26(target)) {
16923
18279
  unlinkSync13(source);
16924
18280
  continue;
16925
18281
  }
@@ -16928,7 +18284,7 @@ var AuditPruneSweeper = class {
16928
18284
  compressed += 1;
16929
18285
  } catch (error) {
16930
18286
  try {
16931
- if (existsSync25(target)) unlinkSync13(target);
18287
+ if (existsSync26(target)) unlinkSync13(target);
16932
18288
  } catch {
16933
18289
  }
16934
18290
  this.logger.warn("[AuditPruneSweeper] failed to archive audit body shard", {
@@ -17081,7 +18437,7 @@ async function closeAll(writers) {
17081
18437
  // src/usage/UsagePruneSweeper.ts
17082
18438
  import { unlink as unlink3 } from "fs/promises";
17083
18439
  import { join as join24 } from "path";
17084
- var DAY_MS2 = 24 * 60 * 6e4;
18440
+ var DAY_MS4 = 24 * 60 * 6e4;
17085
18441
  var SWEEP_INTERVAL_MS3 = 60 * 6e4;
17086
18442
  var DEFAULT_USAGE_RETENTION_DAYS = 90;
17087
18443
  var UsagePruneSweeper = class {
@@ -17138,7 +18494,7 @@ var UsagePruneSweeper = class {
17138
18494
  this.sweeping = true;
17139
18495
  try {
17140
18496
  const retentionDays = this.config.retentionDays ?? DEFAULT_USAGE_RETENTION_DAYS;
17141
- const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS2;
18497
+ const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS4;
17142
18498
  let removed = 0;
17143
18499
  for (const entry of await listUsageDays(this.usageDir)) {
17144
18500
  if (!entry.hasShard) continue;
@@ -17196,7 +18552,7 @@ var UsagePruneSweeper = class {
17196
18552
  };
17197
18553
 
17198
18554
  // src/audit/auditReader.ts
17199
- import { existsSync as existsSync26, readdirSync as readdirSync9 } from "fs";
18555
+ import { existsSync as existsSync27, readdirSync as readdirSync9 } from "fs";
17200
18556
  import { join as join25 } from "path";
17201
18557
  var DEFAULT_LIMIT = 200;
17202
18558
  var MAX_LIMIT = 2e3;
@@ -17214,7 +18570,7 @@ function daySources(auditDir) {
17214
18570
  if (dateMs === null) continue;
17215
18571
  if (AUDIT_DAY_DIR_RE.test(name)) {
17216
18572
  const path2 = join25(auditDir, name, AUDIT_META_FILE);
17217
- if (existsSync26(path2)) sources.push({ path: path2, dateMs });
18573
+ if (existsSync27(path2)) sources.push({ path: path2, dateMs });
17218
18574
  } else if (AUDIT_FILE_RE.test(name)) {
17219
18575
  sources.push({ path: join25(auditDir, name), dateMs });
17220
18576
  }
@@ -17232,7 +18588,7 @@ function toMetaRecord(record) {
17232
18588
  return { ...meta, hasBody: true };
17233
18589
  }
17234
18590
  function readAuditRecords(auditDir, query2 = {}) {
17235
- if (!existsSync26(auditDir)) return [];
18591
+ if (!existsSync27(auditDir)) return [];
17236
18592
  const from = typeof query2.from === "number" ? query2.from : -Infinity;
17237
18593
  const to = typeof query2.to === "number" ? query2.to : Infinity;
17238
18594
  const limit = Math.min(MAX_LIMIT, Math.max(1, Math.trunc(query2.limit ?? DEFAULT_LIMIT)));
@@ -17260,7 +18616,7 @@ function readAuditRecords(auditDir, query2 = {}) {
17260
18616
  }
17261
18617
 
17262
18618
  // src/audit/AuditWriter.ts
17263
- 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";
17264
18620
  import { join as join26 } from "path";
17265
18621
  var AuditWriter = class {
17266
18622
  constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
@@ -17318,7 +18674,7 @@ var AuditWriter = class {
17318
18674
  const { requestBody: _req, responseBody: _res, ...meta } = record;
17319
18675
  const file = join26(dayPath, AUDIT_META_FILE);
17320
18676
  const line = JSON.stringify(meta) + "\n";
17321
- const bytesBefore = existsSync27(file) ? statSync8(file).size : 0;
18677
+ const bytesBefore = existsSync28(file) ? statSync8(file).size : 0;
17322
18678
  appendFileSync2(file, line, "utf8");
17323
18679
  try {
17324
18680
  updateAuditStatsAfterAppend(
@@ -17366,7 +18722,7 @@ var AuditWriter = class {
17366
18722
  import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync8 } from "fs";
17367
18723
  import { createHmac as createHmac5 } from "crypto";
17368
18724
  import { join as join27 } from "path";
17369
- import { fetchUpstream as fetchUpstream5 } from "@omnicross/core/pipeline/upstreamFetch";
18725
+ import { fetchUpstream as fetchUpstream9 } from "@omnicross/core/pipeline/upstreamFetch";
17370
18726
 
17371
18727
  // src/billing/billingFiles.ts
17372
18728
  var BILLING_FILE_RE = /^billing-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
@@ -17389,7 +18745,7 @@ var BillingPublisher = class {
17389
18745
  constructor(billingDir, logger, opts = {}) {
17390
18746
  this.billingDir = billingDir;
17391
18747
  this.logger = logger;
17392
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream5(url, init));
18748
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream9(url, init));
17393
18749
  this.defer = opts.defer ?? ((fn) => setTimeout(fn, 0));
17394
18750
  this.timeoutMs = opts.timeoutMs ?? BILLING_POST_TIMEOUT_MS;
17395
18751
  this.now = opts.now ?? Date.now;
@@ -17502,11 +18858,11 @@ var BillingPublisher = class {
17502
18858
  };
17503
18859
 
17504
18860
  // src/billing/billingReader.ts
17505
- 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";
17506
18862
  import { join as join28 } from "path";
17507
18863
  function readBillingLedger(billingDir) {
17508
18864
  const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
17509
- if (!existsSync28(billingDir)) return view;
18865
+ if (!existsSync29(billingDir)) return view;
17510
18866
  let files;
17511
18867
  try {
17512
18868
  files = readdirSync10(billingDir);
@@ -17639,7 +18995,7 @@ var BillingRetrySweeper = class {
17639
18995
  // src/TokenRefreshScheduler.ts
17640
18996
  var REFRESH_LEAD_MS2 = 5 * 6e4;
17641
18997
  var SWEEP_INTERVAL_MS5 = 6e4;
17642
- var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini"];
18998
+ var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini", "kimi"];
17643
18999
  var TokenRefreshScheduler = class {
17644
19000
  constructor(store, logger, intervalMs = SWEEP_INTERVAL_MS5, leadMs = REFRESH_LEAD_MS2) {
17645
19001
  this.store = store;
@@ -17722,6 +19078,8 @@ var TokenRefreshScheduler = class {
17722
19078
  return this.store.refreshCodexToken();
17723
19079
  case "gemini":
17724
19080
  return this.store.refreshGeminiToken();
19081
+ case "kimi":
19082
+ return this.store.refreshKimiToken();
17725
19083
  }
17726
19084
  }
17727
19085
  };
@@ -17800,7 +19158,7 @@ function createRouteLeaseSubscriptionPreflight(credentials) {
17800
19158
 
17801
19159
  // src/webhook/WebhookDispatcher.ts
17802
19160
  import { createHmac as createHmac6 } from "crypto";
17803
- import { fetchUpstream as fetchUpstream6 } from "@omnicross/core/pipeline/upstreamFetch";
19161
+ import { fetchUpstream as fetchUpstream10 } from "@omnicross/core/pipeline/upstreamFetch";
17804
19162
  var WEBHOOK_MAX_ATTEMPTS = 3;
17805
19163
  var WEBHOOK_QUEUE_MAX = 1e3;
17806
19164
  var WEBHOOK_SEND_TIMEOUT_MS = 1e4;
@@ -17820,7 +19178,7 @@ var WebhookDispatcher = class {
17820
19178
  sleep;
17821
19179
  now;
17822
19180
  constructor(opts = {}) {
17823
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream6(url, init));
19181
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream10(url, init));
17824
19182
  this.logger = opts.logger;
17825
19183
  this.maxAttempts = opts.maxAttempts ?? WEBHOOK_MAX_ATTEMPTS;
17826
19184
  this.queueMax = opts.queueMax ?? WEBHOOK_QUEUE_MAX;
@@ -17906,8 +19264,8 @@ var WebhookDispatcher = class {
17906
19264
  signal: AbortSignal.timeout(this.timeoutMs)
17907
19265
  });
17908
19266
  return res.ok ? { ok: true, status: res.status } : { ok: false, status: res.status };
17909
- } catch (err5) {
17910
- return { ok: false, error: err5 instanceof Error ? err5.message : String(err5) };
19267
+ } catch (err6) {
19268
+ return { ok: false, error: err6 instanceof Error ? err6.message : String(err6) };
17911
19269
  }
17912
19270
  }
17913
19271
  /**
@@ -18044,7 +19402,7 @@ function buildDaemon(config, paths) {
18044
19402
  setSecretBox(secretBox3);
18045
19403
  setSecretBox2(secretBox3);
18046
19404
  const decryptedConfig = decryptConfigSecrets(config, secretBox3);
18047
- const accountAllowanceStore = new AccountAllowanceStore3(
19405
+ const accountAllowanceStore = new AccountAllowanceStore6(
18048
19406
  Date.now,
18049
19407
  void 0,
18050
19408
  new JsonAccountAllowancePersistence(defaultAccountAllowancePath(paths.configPath))
@@ -18089,6 +19447,7 @@ function buildDaemon(config, paths) {
18089
19447
  );
18090
19448
  setGeminiCodeAssistResolver(getGeminiCodeAssistProjectResolver());
18091
19449
  const autoDisableStore = new AutoDisableStore();
19450
+ const providerKeyQuotaService = new ProviderKeyQuotaService(secretBox3);
18092
19451
  const apiKeyPool = new ApiKeyPoolService(
18093
19452
  createPoolKeysLoader((id) => llmConfig.getProviderRow(id), autoDisableStore),
18094
19453
  resolveEnvKey,
@@ -18105,7 +19464,7 @@ function buildDaemon(config, paths) {
18105
19464
  const pricingEngine = new PricingEngine(pricingStore, logger, {
18106
19465
  // Catalog egress follows the same global/env proxy policy as every other
18107
19466
  // daemon upstream call; no provider/account override applies here.
18108
- fetchImpl: ((input, init) => fetchUpstream7(String(input), init ?? {}))
19467
+ fetchImpl: ((input, init) => fetchUpstream11(String(input), init ?? {}))
18109
19468
  });
18110
19469
  const pricingRefreshScheduler = new PricingRefreshScheduler(
18111
19470
  pricingEngine,
@@ -18369,6 +19728,11 @@ function buildDaemon(config, paths) {
18369
19728
  // values themselves NEVER leave (masked via `maskProviderApiKey`).
18370
19729
  apiKeyPool,
18371
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,
18372
19736
  // Interactive OAuth login over admin HTTP (app-parity child 4, design
18373
19737
  // D1/D2-a). The in-memory pending-session store (NEVER serialized), the
18374
19738
  // injected token-exchange fetch (global `fetch` here; mocked in tests), and a
@@ -18385,7 +19749,7 @@ function buildDaemon(config, paths) {
18385
19749
  // — `server.proxy.byProvider[...]` was silently skipped — and the call was
18386
19750
  // excluded from the upstream trace, so a failing login left no evidence.
18387
19751
  // `redactBodies` keeps the code/verifier + minted token out of that trace.
18388
- oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => fetchUpstream7(url, init, { providerId, redactBodies: true }),
19752
+ oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => fetchUpstream11(url, init, { providerId, redactBodies: true }),
18389
19753
  subscriptionAccountAppender: credentialStore,
18390
19754
  // Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
18391
19755
  // + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
@@ -18393,6 +19757,10 @@ function buildDaemon(config, paths) {
18393
19757
  // can inject a mock so no real port is bound.
18394
19758
  codexSessions: new CodexOAuthSessionStore(),
18395
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(),
18396
19764
  // Migration pack (app-parity child 6, design D2/D3) — the concrete credential
18397
19765
  // store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
18398
19766
  // the multi-account append (`appendProviderAccount`, import re-encrypts at-
@@ -18451,7 +19819,7 @@ function buildDaemon(config, paths) {
18451
19819
  });
18452
19820
  const webhookDispatcher = new WebhookDispatcher({
18453
19821
  logger,
18454
- fetchImpl: (url, init) => fetchUpstream7(url, init)
19822
+ fetchImpl: (url, init) => fetchUpstream11(url, init)
18455
19823
  });
18456
19824
  setWebhookRuntime(webhookDispatcher, getSharedAccountHealth4());
18457
19825
  const auditWriter = new AuditWriter(auditDir, logger);
@@ -18528,7 +19896,7 @@ function buildDaemon(config, paths) {
18528
19896
  }
18529
19897
  function isTokensStoreReadable(tokensPath) {
18530
19898
  try {
18531
- if (!existsSync29(tokensPath)) return true;
19899
+ if (!existsSync30(tokensPath)) return true;
18532
19900
  accessSync(tokensPath, fsConstants.R_OK);
18533
19901
  return true;
18534
19902
  } catch {
@@ -18765,11 +20133,11 @@ async function runLiveProbe(url, key, fetchImpl = fetch) {
18765
20133
  status: res.status,
18766
20134
  estimateHeader: res.headers.get("x-omnicross-count-estimate")
18767
20135
  };
18768
- } catch (err5) {
20136
+ } catch (err6) {
18769
20137
  return {
18770
20138
  status: null,
18771
20139
  estimateHeader: null,
18772
- error: err5 instanceof Error ? err5.message : String(err5)
20140
+ error: err6 instanceof Error ? err6.message : String(err6)
18773
20141
  };
18774
20142
  }
18775
20143
  }
@@ -19090,7 +20458,7 @@ async function keysRevoke(db, id) {
19090
20458
  // src/commands/launch.ts
19091
20459
  import { spawn as spawn2 } from "child_process";
19092
20460
  import { randomUUID as randomUUID6 } from "crypto";
19093
- import { existsSync as existsSync30 } from "fs";
20461
+ import { existsSync as existsSync31 } from "fs";
19094
20462
  import { delimiter as delimiter2, join as join29 } from "path";
19095
20463
  import { parseArgs as parseArgs6 } from "util";
19096
20464
  import {
@@ -19138,7 +20506,7 @@ function resolveInPathDefault(candidate) {
19138
20506
  const segments = (process.env["PATH"] ?? "").split(delimiter2).filter(Boolean);
19139
20507
  for (const seg of segments) {
19140
20508
  const full = join29(seg, candidate);
19141
- if (existsSync30(full)) return full;
20509
+ if (existsSync31(full)) return full;
19142
20510
  }
19143
20511
  return null;
19144
20512
  }
@@ -19179,9 +20547,9 @@ async function runLaunch(argv, deps) {
19179
20547
  await daemon.llmConfig.ready();
19180
20548
  await daemon.migrateUsageStore();
19181
20549
  await daemon.providerProxy.start();
19182
- } catch (err5) {
20550
+ } catch (err6) {
19183
20551
  await shutdownLaunchDaemon(daemon);
19184
- throw err5;
20552
+ throw err6;
19185
20553
  }
19186
20554
  let launch;
19187
20555
  try {
@@ -19189,9 +20557,9 @@ async function runLaunch(argv, deps) {
19189
20557
  providerId: values.provider,
19190
20558
  model: values.model
19191
20559
  });
19192
- } catch (err5) {
20560
+ } catch (err6) {
19193
20561
  await shutdownLaunchDaemon(daemon);
19194
- throw err5;
20562
+ throw err6;
19195
20563
  }
19196
20564
  try {
19197
20565
  const plan = buildCliSpawnPlan({
@@ -19296,9 +20664,9 @@ function spawnCliInherit(plan) {
19296
20664
  process.removeListener("SIGINT", onSignal);
19297
20665
  process.removeListener("SIGTERM", onSignal);
19298
20666
  };
19299
- child.on("error", (err5) => {
20667
+ child.on("error", (err6) => {
19300
20668
  detach();
19301
- if (err5.code === "ENOENT") {
20669
+ if (err6.code === "ENOENT") {
19302
20670
  reject(
19303
20671
  new Error(
19304
20672
  `launch: "${plan.command}" not found on PATH \u2014 install the CLI first.`
@@ -19306,7 +20674,7 @@ function spawnCliInherit(plan) {
19306
20674
  );
19307
20675
  return;
19308
20676
  }
19309
- reject(err5);
20677
+ reject(err6);
19310
20678
  });
19311
20679
  child.on("exit", (code, signal) => {
19312
20680
  detach();
@@ -19319,9 +20687,14 @@ function spawnCliInherit(plan) {
19319
20687
  import { spawn as spawn3 } from "child_process";
19320
20688
  import { createInterface as createInterface2 } from "readline";
19321
20689
  import { parseArgs as parseArgs7 } from "util";
19322
- import { fetchUpstream as fetchUpstream8, setUpstreamProxyResolver as setUpstreamProxyResolver2 } from "@omnicross/core/pipeline/upstreamFetch";
19323
- import { claudeOAuth as claudeOAuth3, codexOAuth as codexOAuth3, geminiOAuth as geminiOAuth3 } from "@omnicross/subscriptions";
19324
- var PROVIDERS2 = ["claude", "codex", "gemini"];
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"];
19325
20698
  async function runLogin(argv, deps) {
19326
20699
  const { values, positionals } = parseArgs7({
19327
20700
  args: argv,
@@ -19347,14 +20720,16 @@ async function runLogin(argv, deps) {
19347
20720
  openBrowser: deps?.openBrowser ?? openBrowser,
19348
20721
  promptPaste: deps?.promptPaste ?? promptPaste,
19349
20722
  awaitLoopback: deps?.awaitLoopback ?? ((state) => awaitLoopbackCode(state)),
20723
+ awaitKimiDevice: deps?.awaitKimiDevice ?? ((fetchImpl) => runKimiDeviceFlow(fetchImpl, resolvedOpenBrowser)),
19350
20724
  tokensFetch: deps?.tokensFetch
19351
20725
  };
20726
+ const resolvedOpenBrowser = resolved.openBrowser;
19352
20727
  const box = resolveSecretBox(values["master-key-file"]);
19353
20728
  setSecretBox(box);
19354
20729
  setUpstreamProxyResolver2(createUpstreamProxyResolver());
19355
20730
  try {
19356
20731
  const tokensPath = defaultTokensPath(values.config);
19357
- 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 }));
19358
20733
  const store = new JsonSubscriptionCredentialStore(tokensPath, box, exchangeFetch);
19359
20734
  const expiresAt = await runProviderLogin(
19360
20735
  provider,
@@ -19373,6 +20748,7 @@ async function runLogin(argv, deps) {
19373
20748
  async function runProviderLogin(provider, store, deps, exchangeFetch, label) {
19374
20749
  if (provider === "codex") return loginCodex(store, deps, exchangeFetch, label);
19375
20750
  if (provider === "claude") return loginClaude(store, deps, exchangeFetch, label);
20751
+ if (provider === "kimi") return loginKimi(store, deps, exchangeFetch, label);
19376
20752
  return loginGemini(store, deps, exchangeFetch, label);
19377
20753
  }
19378
20754
  async function loginCodex(store, deps, exchangeFetch, label) {
@@ -19443,6 +20819,45 @@ async function loginGemini(store, deps, exchangeFetch, label) {
19443
20819
  logMasked("gemini", result.accessToken);
19444
20820
  return expiresAt;
19445
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
+ }
19446
20861
  function isLoginProvider(value) {
19447
20862
  return PROVIDERS2.includes(value);
19448
20863
  }
@@ -19640,7 +21055,7 @@ function providersRmKey(configPath, providerId, keyId) {
19640
21055
  }
19641
21056
 
19642
21057
  // src/commands/secrets.ts
19643
- import { existsSync as existsSync31, readFileSync as readFileSync24, writeFileSync as writeFileSync19 } from "fs";
21058
+ import { existsSync as existsSync32, readFileSync as readFileSync24 } from "fs";
19644
21059
  import { parseArgs as parseArgs9 } from "util";
19645
21060
  async function runSecrets(argv) {
19646
21061
  const { values, positionals } = parseArgs9({
@@ -19713,12 +21128,12 @@ function secretsStatus(args) {
19713
21128
  reportField("admin.token", cfg.admin.token);
19714
21129
  }
19715
21130
  const tokensPath = defaultTokensPath(args.config);
19716
- if (existsSync31(tokensPath)) {
21131
+ if (existsSync32(tokensPath)) {
19717
21132
  console.info(`Secret status for ${tokensPath}:`);
19718
21133
  reportTokenFields(tokensPath);
19719
21134
  }
19720
21135
  const integrationsPath = defaultIntegrationsPath(args.config);
19721
- if (existsSync31(integrationsPath)) {
21136
+ if (existsSync32(integrationsPath)) {
19722
21137
  const state = readRawJson(integrationsPath);
19723
21138
  const key = state.gatewayKey;
19724
21139
  if (key && typeof key === "object" && !Array.isArray(key)) {
@@ -19772,8 +21187,8 @@ async function secretsRotate(args) {
19772
21187
  const integrationsPath = defaultIntegrationsPath(args.config);
19773
21188
  try {
19774
21189
  cfg = loadConfig(args.config);
19775
- if (existsSync31(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, oldBox);
19776
- if (existsSync31(integrationsPath)) {
21190
+ if (existsSync32(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, oldBox);
21191
+ if (existsSync32(integrationsPath)) {
19777
21192
  integrationsPlain = new IntegrationStateStore(integrationsPath, oldBox).load();
19778
21193
  }
19779
21194
  } finally {
@@ -19808,13 +21223,13 @@ function secretsDecrypt(args) {
19808
21223
  let tokensPlain = null;
19809
21224
  try {
19810
21225
  cfg = loadConfig(args.config);
19811
- if (existsSync31(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, box);
21226
+ if (existsSync32(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, box);
19812
21227
  } finally {
19813
21228
  setSecretBox(null);
19814
21229
  }
19815
21230
  saveConfig(args.config, cfg);
19816
21231
  if (tokensPlain) {
19817
- writeFileSync19(tokensPath, JSON.stringify(tokensPlain, null, 2) + "\n", "utf8");
21232
+ atomicReplaceUtf8(tokensPath, JSON.stringify(tokensPlain, null, 2) + "\n");
19818
21233
  }
19819
21234
  console.info(`Decrypted secrets to plaintext in ${args.config}` + tokensSuffix(args.config));
19820
21235
  }
@@ -19839,13 +21254,13 @@ function readRawJson(path2) {
19839
21254
  }
19840
21255
  function encryptTokensFileInPlace(configPath, box) {
19841
21256
  const tokensPath = defaultTokensPath(configPath);
19842
- if (!existsSync31(tokensPath)) return;
21257
+ if (!existsSync32(tokensPath)) return;
19843
21258
  const plain = decryptTokensFile(tokensPath, box);
19844
21259
  writeTokensEncrypted(tokensPath, plain, box);
19845
21260
  }
19846
21261
  function rewriteIntegrationState(configPath, readBox, writeBox) {
19847
21262
  const path2 = defaultIntegrationsPath(configPath);
19848
- if (!existsSync31(path2)) return;
21263
+ if (!existsSync32(path2)) return;
19849
21264
  const state = new IntegrationStateStore(path2, readBox).load();
19850
21265
  new IntegrationStateStore(path2, writeBox).save(state);
19851
21266
  }
@@ -19858,7 +21273,7 @@ function writeTokensEncrypted(tokensPath, plain, box) {
19858
21273
  { updatedAt: "", ...plain },
19859
21274
  box
19860
21275
  );
19861
- writeFileSync19(tokensPath, JSON.stringify(encrypted, null, 2) + "\n", "utf8");
21276
+ atomicReplaceUtf8(tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
19862
21277
  }
19863
21278
  var TOKEN_FIELDS2 = {
19864
21279
  claude: ["accessToken", "refreshToken"],
@@ -19881,7 +21296,7 @@ function walkTokens(raw, fn) {
19881
21296
  return next;
19882
21297
  }
19883
21298
  function tokensSuffix(configPath) {
19884
- return existsSync31(defaultTokensPath(configPath)) ? " (+ tokens.json)" : "";
21299
+ return existsSync32(defaultTokensPath(configPath)) ? " (+ tokens.json)" : "";
19885
21300
  }
19886
21301
 
19887
21302
  // src/commands/start.ts
@@ -20148,7 +21563,7 @@ async function main() {
20148
21563
  process.exitCode = 1;
20149
21564
  }
20150
21565
  }
20151
- main().catch((err5) => {
20152
- console.error(err5 instanceof Error ? err5.message : String(err5));
21566
+ main().catch((err6) => {
21567
+ console.error(err6 instanceof Error ? err6.message : String(err6));
20153
21568
  process.exitCode = 1;
20154
21569
  });