@omnicross/daemon 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -1163,10 +1163,10 @@ var import_node_util2 = require("util");
1163
1163
  var import_outbound_api12 = require("@omnicross/core/outbound-api");
1164
1164
  var import_api4 = require("@omnicross/core/search/api");
1165
1165
  var import_http4 = require("@omnicross/core/search/http");
1166
- var import_search2 = require("@omnicross/core/search");
1166
+ var import_search3 = require("@omnicross/core/search");
1167
1167
 
1168
1168
  // src/bootstrap.ts
1169
- var import_node_fs35 = require("fs");
1169
+ var import_node_fs36 = require("fs");
1170
1170
  var import_node_path36 = require("path");
1171
1171
  var import_audit_types = require("@omnicross/contracts/audit-types");
1172
1172
  var import_billing_types = require("@omnicross/contracts/billing-types");
@@ -1176,16 +1176,16 @@ var import_ApiKeyPoolService = require("@omnicross/core/completion/ApiKeyPoolSer
1176
1176
  var import_outbound_api10 = require("@omnicross/core/outbound-api");
1177
1177
  var import_subscriptionRegistryPort = require("@omnicross/core/outbound-api/subscriptionRegistryPort");
1178
1178
  var import_SubscriptionAccountHealth4 = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
1179
- var import_AccountAllowanceStore4 = require("@omnicross/core/pipeline/AccountAllowanceStore");
1179
+ var import_AccountAllowanceStore7 = require("@omnicross/core/pipeline/AccountAllowanceStore");
1180
1180
  var import_AccountAllowanceScheduling5 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
1181
- var import_upstreamFetch8 = require("@omnicross/core/pipeline/upstreamFetch");
1181
+ var import_upstreamFetch13 = require("@omnicross/core/pipeline/upstreamFetch");
1182
1182
  var import_SubscriptionIdentityStore3 = require("@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore");
1183
1183
  var import_gemini_code_assist_resolver = require("@omnicross/core/ports/gemini-code-assist-resolver");
1184
1184
  var import_provider_proxy4 = require("@omnicross/core/provider-proxy");
1185
1185
  var import_cli_launcher2 = require("@omnicross/cli-launcher");
1186
1186
  var import_outbound_api11 = require("@omnicross/core/outbound-api");
1187
1187
  var import_usage2 = require("@omnicross/core/usage");
1188
- var import_subscriptions6 = require("@omnicross/subscriptions");
1188
+ var import_subscriptions9 = require("@omnicross/subscriptions");
1189
1189
 
1190
1190
  // src/admin/accountsCodexOAuth.ts
1191
1191
  var import_node_crypto3 = __toESM(require("crypto"), 1);
@@ -1292,8 +1292,83 @@ function handleCodexOAuthStatus(sessionId, deps) {
1292
1292
  return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
1293
1293
  }
1294
1294
 
1295
+ // src/admin/accountsKimiOAuth.ts
1296
+ var import_subscriptions2 = require("@omnicross/subscriptions");
1297
+ function err2(status, message) {
1298
+ return { status, body: { error: { type: "admin_api_error", message } } };
1299
+ }
1300
+ var DEFAULT_KIMI_OAUTH_TTL_MS = 15 * 6e4;
1301
+ async function handleKimiOAuthStart(deps) {
1302
+ if (deps.kimiSessions.isBusy()) {
1303
+ return err2(409, "a kimi sign-in is already in progress \u2014 finish it in the browser or cancel it");
1304
+ }
1305
+ const fetchImpl = deps.oauthExchangeFetch("kimi");
1306
+ const deviceId = import_subscriptions2.kimiOAuth.generateKimiDeviceId();
1307
+ const fingerprint = import_subscriptions2.kimiOAuth.kimiFingerprintHeaders(deviceId);
1308
+ let authorization;
1309
+ try {
1310
+ authorization = await import_subscriptions2.kimiOAuth.requestDeviceAuthorization(fetchImpl, fingerprint);
1311
+ } catch (e) {
1312
+ const reason = e instanceof Error ? e.message : "device authorization failed";
1313
+ return err2(502, `kimi device authorization failed: ${reason}`);
1314
+ }
1315
+ const { sessionId, signal } = deps.kimiSessions.begin();
1316
+ void runKimiDevicePoll(sessionId, authorization.deviceCode, deviceId, fingerprint, signal, deps).catch(() => deps.kimiSessions.settle(sessionId, "error", "kimi sign-in failed"));
1317
+ return {
1318
+ status: 200,
1319
+ body: {
1320
+ authUrl: authorization.verificationUriComplete ?? authorization.verificationUri,
1321
+ userCode: authorization.userCode,
1322
+ sessionId
1323
+ }
1324
+ };
1325
+ }
1326
+ async function runKimiDevicePoll(sessionId, deviceCode, deviceId, fingerprint, signal, deps) {
1327
+ const fetchImpl = deps.oauthExchangeFetch("kimi");
1328
+ const result = await import_subscriptions2.kimiOAuth.awaitDeviceToken(
1329
+ { userCode: "", deviceCode, verificationUri: "" },
1330
+ fetchImpl,
1331
+ {
1332
+ fingerprint,
1333
+ deadlineMs: DEFAULT_KIMI_OAUTH_TTL_MS,
1334
+ sleep: (ms) => new Promise((resolve11, reject) => {
1335
+ const onAbort = () => {
1336
+ clearTimeout(timer);
1337
+ reject(new Error("login: cancelled"));
1338
+ };
1339
+ const timer = setTimeout(() => {
1340
+ signal.removeEventListener("abort", onAbort);
1341
+ resolve11();
1342
+ }, ms);
1343
+ signal.addEventListener("abort", onAbort, { once: true });
1344
+ })
1345
+ }
1346
+ );
1347
+ const block = {
1348
+ authMethod: "oauth",
1349
+ status: "authorized",
1350
+ accessToken: result.accessToken,
1351
+ refreshToken: result.refreshToken,
1352
+ expiresAt: new Date(Date.now() + result.expiresIn * 1e3).toISOString(),
1353
+ accountId: import_subscriptions2.kimiOAuth.kimiAccountIdFromAccessToken(result.accessToken),
1354
+ deviceId,
1355
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
1356
+ };
1357
+ await deps.subscriptionAccountAppender.appendProviderAccount("kimi", block);
1358
+ deps.kimiSessions.settle(sessionId, "done");
1359
+ }
1360
+ function handleKimiOAuthCancel(sessionId, deps) {
1361
+ if (!deps.kimiSessions.cancel(sessionId)) return err2(404, "unknown or expired kimi sign-in session");
1362
+ return { status: 200, body: { ok: true } };
1363
+ }
1364
+ function handleKimiOAuthStatus(sessionId, deps) {
1365
+ const s = deps.kimiSessions.get(sessionId);
1366
+ if (!s) return err2(404, "unknown or expired kimi sign-in session");
1367
+ return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
1368
+ }
1369
+
1295
1370
  // src/allowance/AccountAllowanceService.ts
1296
- var import_AccountAllowanceStore2 = require("@omnicross/core/pipeline/AccountAllowanceStore");
1371
+ var import_AccountAllowanceStore5 = require("@omnicross/core/pipeline/AccountAllowanceStore");
1297
1372
  var import_AccountAllowanceScheduling = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
1298
1373
 
1299
1374
  // src/allowance/ClaudeAllowanceCollector.ts
@@ -1320,13 +1395,11 @@ function secondsUntil(instant, now) {
1320
1395
  function windowFromPayload(id, payload, now) {
1321
1396
  const usedPercent = finitePercent(payload?.utilization);
1322
1397
  const resetsAt = isoInstant(payload?.resets_at);
1323
- const isSonnet = id === "seven-day-sonnet";
1324
1398
  const isFiveHour = id === "five-hour";
1325
1399
  return {
1326
1400
  id,
1327
- label: isFiveHour ? "5 hours" : isSonnet ? "7 days \xB7 Sonnet" : "7 days",
1328
- scope: isSonnet ? "model-family" : "all",
1329
- modelFamily: isSonnet ? "sonnet" : void 0,
1401
+ label: isFiveHour ? "5 hours" : "7 days",
1402
+ scope: "all",
1330
1403
  usedPercent,
1331
1404
  windowMinutes: isFiveHour ? 5 * 60 : 7 * 24 * 60,
1332
1405
  resetsAt,
@@ -1334,6 +1407,44 @@ function windowFromPayload(id, payload, now) {
1334
1407
  state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
1335
1408
  };
1336
1409
  }
1410
+ function limitEntryWindow(entries, kind) {
1411
+ const entry = entries.find((candidate) => candidate.kind === kind);
1412
+ if (!entry) return void 0;
1413
+ return { utilization: entry.percent, resets_at: entry.resets_at };
1414
+ }
1415
+ function slugifyDisplayName(name) {
1416
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1417
+ }
1418
+ function scopedWeeklyWindows(entries, now) {
1419
+ const seen = /* @__PURE__ */ new Set();
1420
+ const windows = [];
1421
+ for (const entry of entries) {
1422
+ if (entry.kind !== "weekly_scoped") continue;
1423
+ const displayName = typeof entry.scope?.model?.display_name === "string" && entry.scope.model.display_name.trim() ? entry.scope.model.display_name.trim() : void 0;
1424
+ if (!displayName) continue;
1425
+ const slug = slugifyDisplayName(displayName);
1426
+ if (!slug || seen.has(slug)) continue;
1427
+ seen.add(slug);
1428
+ const usedPercent = finitePercent(entry.percent);
1429
+ const resetsAt = isoInstant(entry.resets_at);
1430
+ windows.push({
1431
+ id: `seven-day-${slug}`,
1432
+ label: `7 days \xB7 ${displayName}`,
1433
+ scope: "model-family",
1434
+ modelFamily: slug,
1435
+ usedPercent,
1436
+ windowMinutes: 7 * 24 * 60,
1437
+ resetsAt,
1438
+ remainingSeconds: secondsUntil(resetsAt, now),
1439
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
1440
+ });
1441
+ }
1442
+ return windows;
1443
+ }
1444
+ function parseLimitEntries(raw) {
1445
+ if (!Array.isArray(raw)) return [];
1446
+ return raw.filter((entry) => !!entry && typeof entry === "object");
1447
+ }
1337
1448
  function emptyClaudeWindows(state) {
1338
1449
  return [
1339
1450
  {
@@ -1351,15 +1462,6 @@ function emptyClaudeWindows(state) {
1351
1462
  usedPercent: null,
1352
1463
  windowMinutes: 7 * 24 * 60,
1353
1464
  state
1354
- },
1355
- {
1356
- id: "seven-day-sonnet",
1357
- label: "7 days \xB7 Sonnet",
1358
- scope: "model-family",
1359
- modelFamily: "sonnet",
1360
- usedPercent: null,
1361
- windowMinutes: 7 * 24 * 60,
1362
- state
1363
1465
  }
1364
1466
  ];
1365
1467
  }
@@ -1440,6 +1542,9 @@ var ClaudeAllowanceCollector = class {
1440
1542
  }
1441
1543
  const now = this.now();
1442
1544
  const usage = payload;
1545
+ const limitEntries = parseLimitEntries(usage.limits);
1546
+ const fiveHour = usage.five_hour ?? limitEntryWindow(limitEntries, "session");
1547
+ const sevenDay = usage.seven_day ?? limitEntryWindow(limitEntries, "weekly_all");
1443
1548
  const snapshot = {
1444
1549
  providerId: "claude",
1445
1550
  accountId,
@@ -1447,10 +1552,10 @@ var ClaudeAllowanceCollector = class {
1447
1552
  observedAt: new Date(now).toISOString(),
1448
1553
  expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
1449
1554
  windows: [
1450
- windowFromPayload("five-hour", usage.five_hour, now),
1451
- windowFromPayload("seven-day", usage.seven_day, now),
1452
- windowFromPayload("seven-day-sonnet", usage.seven_day_sonnet, now)
1453
- ]
1555
+ windowFromPayload("five-hour", fiveHour, now),
1556
+ windowFromPayload("seven-day", sevenDay, now),
1557
+ ...scopedWeeklyWindows(limitEntries, now)
1558
+ ].slice(0, 8)
1454
1559
  };
1455
1560
  this.store.set(snapshot);
1456
1561
  return snapshot;
@@ -1507,6 +1612,601 @@ var ClaudeAllowanceCollector = class {
1507
1612
  }
1508
1613
  };
1509
1614
 
1615
+ // src/allowance/CodexAllowanceCollector.ts
1616
+ var import_AccountAllowanceStore2 = require("@omnicross/core/pipeline/AccountAllowanceStore");
1617
+ var import_upstreamFetch2 = require("@omnicross/core/pipeline/upstreamFetch");
1618
+ var CODEX_ALLOWANCE_CACHE_MS = 5 * 6e4;
1619
+ var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
1620
+ var CODEX_CLI_USER_AGENT = "codex_cli_rs/0.144.5";
1621
+ function finiteNumber(value) {
1622
+ if (value === null || value === void 0 || value === "") return null;
1623
+ const parsed = typeof value === "number" ? value : Number(value);
1624
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
1625
+ }
1626
+ function finitePercent2(value) {
1627
+ const parsed = finiteNumber(value);
1628
+ return parsed !== null && parsed <= 100 ? parsed : null;
1629
+ }
1630
+ function epochMs(value) {
1631
+ return value > 1e11 ? value : value * 1e3;
1632
+ }
1633
+ function secondsUntil2(instant, now) {
1634
+ if (!instant) return void 0;
1635
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
1636
+ }
1637
+ function decodeJwtClaims(token) {
1638
+ const parts = token.split(".");
1639
+ if (parts.length !== 3) return void 0;
1640
+ try {
1641
+ const json2 = Buffer.from(parts[1], "base64url").toString("utf8");
1642
+ const parsed = JSON.parse(json2);
1643
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
1644
+ } catch {
1645
+ return void 0;
1646
+ }
1647
+ }
1648
+ function chatgptAccountIdFromClaims(claims) {
1649
+ const auth = claims?.["https://api.openai.com/auth"];
1650
+ if (!auth || typeof auth !== "object") return void 0;
1651
+ const accountId = auth.chatgpt_account_id;
1652
+ return typeof accountId === "string" && accountId.trim() ? accountId.trim() : void 0;
1653
+ }
1654
+ function resolveCodexChatGptAccountId(tokens) {
1655
+ if (tokens.accountId?.trim()) return tokens.accountId.trim();
1656
+ if (tokens.idToken) {
1657
+ const fromIdToken = chatgptAccountIdFromClaims(decodeJwtClaims(tokens.idToken));
1658
+ if (fromIdToken) return fromIdToken;
1659
+ }
1660
+ if (tokens.accessToken) {
1661
+ return chatgptAccountIdFromClaims(decodeJwtClaims(tokens.accessToken));
1662
+ }
1663
+ return void 0;
1664
+ }
1665
+ function windowFromPayload2(id, payload, now) {
1666
+ const usedPercent = finitePercent2(payload?.used_percent);
1667
+ const resetAtSeconds = finiteNumber(payload?.reset_at);
1668
+ const resetAfterSeconds = finiteNumber(payload?.reset_after_seconds);
1669
+ const windowSeconds = finiteNumber(payload?.limit_window_seconds);
1670
+ const resetsAt = resetAtSeconds !== null && resetAtSeconds > 0 ? new Date(epochMs(resetAtSeconds)).toISOString() : resetAfterSeconds !== null && resetAfterSeconds > 0 ? new Date(now + resetAfterSeconds * 1e3).toISOString() : void 0;
1671
+ const windowMinutes = windowSeconds !== null && windowSeconds > 0 ? Math.round(windowSeconds / 60) : void 0;
1672
+ return {
1673
+ id,
1674
+ label: id === "primary" ? "Primary" : "Secondary",
1675
+ scope: "all",
1676
+ usedPercent,
1677
+ ...windowMinutes !== void 0 ? { windowMinutes } : {},
1678
+ ...resetsAt !== void 0 ? { resetsAt } : {},
1679
+ remainingSeconds: secondsUntil2(resetsAt, now),
1680
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
1681
+ };
1682
+ }
1683
+ var CodexAllowanceCollector = class {
1684
+ constructor(credentials, store = (0, import_AccountAllowanceStore2.getSharedAccountAllowanceStore)(), fetchImpl = (url, init, accountId) => (0, import_upstreamFetch2.fetchUpstream)(url, init, { providerId: "codex", accountId, redactBodies: true }), now = Date.now) {
1685
+ this.credentials = credentials;
1686
+ this.store = store;
1687
+ this.fetchImpl = fetchImpl;
1688
+ this.now = now;
1689
+ }
1690
+ credentials;
1691
+ store;
1692
+ fetchImpl;
1693
+ now;
1694
+ inFlight = /* @__PURE__ */ new Map();
1695
+ async collectMany(accounts, options = {}) {
1696
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
1697
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
1698
+ }
1699
+ collect(account, options = {}) {
1700
+ const now = this.now();
1701
+ const unsupported = account.tokens.authMethod !== "oauth";
1702
+ if (unsupported) {
1703
+ const existing = this.store.get("codex", account.id, now);
1704
+ if (existing?.windows.every((window) => window.state === "unsupported")) {
1705
+ return Promise.resolve(existing);
1706
+ }
1707
+ const snapshot = this.unsupportedSnapshot(account.id, now);
1708
+ this.store.set(snapshot);
1709
+ return Promise.resolve(snapshot);
1710
+ }
1711
+ const cached = this.store.get("codex", account.id, now);
1712
+ if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
1713
+ return Promise.resolve(cached);
1714
+ }
1715
+ const running = this.inFlight.get(account.id);
1716
+ if (running) return running;
1717
+ const promise = this.fetchAccount(account.id, account.tokens).catch(() => this.failureSnapshot(account.id, "codex_usage_request_failed", this.now())).finally(() => this.inFlight.delete(account.id));
1718
+ this.inFlight.set(account.id, promise);
1719
+ return promise;
1720
+ }
1721
+ /**
1722
+ * A response-header snapshot stays a valid cache hit only while fresh; an
1723
+ * active oauth-usage snapshot is honored on the same 5-minute cadence as
1724
+ * Claude's (the poll is cheap and quota is the scheduling input).
1725
+ */
1726
+ isCacheValid(snapshot, now, refreshAheadMs) {
1727
+ if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
1728
+ const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
1729
+ const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
1730
+ return Number.isFinite(expiresAt) && expiresAt > now + ahead;
1731
+ }
1732
+ async fetchAccount(accountId, tokens) {
1733
+ let accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
1734
+ if (!accessToken) {
1735
+ return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
1736
+ }
1737
+ let response = await this.request(accountId, accessToken, tokens);
1738
+ if (response.status === 401) {
1739
+ const refreshed = await this.credentials.refreshAccountToken("codex", accountId);
1740
+ if (!refreshed) {
1741
+ return this.failureSnapshot(accountId, "codex_usage_unauthorized", this.now());
1742
+ }
1743
+ accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
1744
+ if (!accessToken) {
1745
+ return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
1746
+ }
1747
+ response = await this.request(accountId, accessToken, tokens);
1748
+ }
1749
+ if (response.status === 403) {
1750
+ const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "codex_usage_unsupported");
1751
+ this.store.set(snapshot2);
1752
+ return snapshot2;
1753
+ }
1754
+ if (!response.ok) {
1755
+ return this.failureSnapshot(accountId, "codex_usage_http_error", this.now());
1756
+ }
1757
+ let payload;
1758
+ try {
1759
+ payload = await response.json();
1760
+ } catch {
1761
+ return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
1762
+ }
1763
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
1764
+ return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
1765
+ }
1766
+ const now = this.now();
1767
+ const usage = payload.rate_limit;
1768
+ const previous = this.store.get("codex", accountId, now);
1769
+ const snapshot = {
1770
+ providerId: "codex",
1771
+ accountId,
1772
+ source: "oauth-usage-api",
1773
+ observedAt: new Date(now).toISOString(),
1774
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
1775
+ windows: [
1776
+ windowFromPayload2("primary", usage?.primary_window ?? void 0, now),
1777
+ windowFromPayload2("secondary", usage?.secondary_window ?? void 0, now)
1778
+ ],
1779
+ // The wham payload has no ratio field; keep the passively-observed value.
1780
+ ...previous?.primaryOverSecondaryLimitPercent !== void 0 ? { primaryOverSecondaryLimitPercent: previous.primaryOverSecondaryLimitPercent } : {}
1781
+ };
1782
+ this.store.set(snapshot);
1783
+ return snapshot;
1784
+ }
1785
+ request(accountId, accessToken, tokens) {
1786
+ const headers = {
1787
+ Authorization: `Bearer ${accessToken}`,
1788
+ Accept: "application/json",
1789
+ "User-Agent": CODEX_CLI_USER_AGENT
1790
+ };
1791
+ const chatgptAccountId = resolveCodexChatGptAccountId(tokens);
1792
+ if (chatgptAccountId) headers["ChatGPT-Account-Id"] = chatgptAccountId;
1793
+ return this.fetchImpl(CODEX_USAGE_URL, {
1794
+ method: "GET",
1795
+ headers,
1796
+ signal: AbortSignal.timeout(15e3)
1797
+ }, accountId);
1798
+ }
1799
+ failureSnapshot(accountId, code, now) {
1800
+ const existing = this.store.get("codex", accountId, now);
1801
+ const snapshot = existing ? {
1802
+ ...existing,
1803
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
1804
+ windows: existing.windows.map((window) => ({
1805
+ ...window,
1806
+ state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
1807
+ })),
1808
+ lastErrorCode: code
1809
+ } : {
1810
+ providerId: "codex",
1811
+ accountId,
1812
+ source: "oauth-usage-api",
1813
+ observedAt: new Date(now).toISOString(),
1814
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
1815
+ windows: [
1816
+ { id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unavailable" },
1817
+ { id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unavailable" }
1818
+ ],
1819
+ lastErrorCode: code
1820
+ };
1821
+ this.store.set(snapshot);
1822
+ return snapshot;
1823
+ }
1824
+ unsupportedSnapshot(accountId, now, code = "codex_usage_unsupported_auth") {
1825
+ return {
1826
+ providerId: "codex",
1827
+ accountId,
1828
+ source: "oauth-usage-api",
1829
+ observedAt: new Date(now).toISOString(),
1830
+ windows: [
1831
+ { id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unsupported" },
1832
+ { id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unsupported" }
1833
+ ],
1834
+ lastErrorCode: code
1835
+ };
1836
+ }
1837
+ };
1838
+
1839
+ // src/allowance/KimiAllowanceCollector.ts
1840
+ var import_AccountAllowanceStore3 = require("@omnicross/core/pipeline/AccountAllowanceStore");
1841
+ var import_upstreamFetch3 = require("@omnicross/core/pipeline/upstreamFetch");
1842
+ var import_subscriptions3 = require("@omnicross/subscriptions");
1843
+ var KIMI_ALLOWANCE_CACHE_MS = 5 * 6e4;
1844
+ var KIMI_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
1845
+ function finiteNumber2(value) {
1846
+ if (value === null || value === void 0 || value === "") return void 0;
1847
+ const parsed = typeof value === "number" ? value : Number(value);
1848
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
1849
+ }
1850
+ function isRecord(value) {
1851
+ return !!value && typeof value === "object" && !Array.isArray(value);
1852
+ }
1853
+ function parseResetMs(row, nowMs) {
1854
+ for (const key of ["reset_at", "resetAt", "reset_time", "resetTime"]) {
1855
+ const value = row[key];
1856
+ if (typeof value === "string" && value.trim()) {
1857
+ const parsed = Date.parse(value);
1858
+ if (Number.isFinite(parsed)) return parsed;
1859
+ }
1860
+ const numeric = finiteNumber2(value);
1861
+ if (numeric !== void 0 && numeric > 1e9) {
1862
+ return numeric > 1e12 ? numeric : numeric * 1e3;
1863
+ }
1864
+ }
1865
+ for (const key of ["reset_in", "resetIn", "ttl", "window"]) {
1866
+ const seconds = finiteNumber2(row[key]);
1867
+ if (seconds !== void 0) return nowMs + seconds * 1e3;
1868
+ }
1869
+ return void 0;
1870
+ }
1871
+ var MINUTE_MS = 6e4;
1872
+ var HOUR_MS = 36e5;
1873
+ var DAY_MS = 864e5;
1874
+ function canonicalWindow(durationMs) {
1875
+ if (durationMs === 5 * HOUR_MS) return { id: "five-hour", label: "5 hours", minutes: 300 };
1876
+ if (durationMs === 7 * DAY_MS) return { id: "seven-day", label: "7 days", minutes: 10080 };
1877
+ if (durationMs > 0 && durationMs % DAY_MS === 0) {
1878
+ const days = durationMs / DAY_MS;
1879
+ return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
1880
+ }
1881
+ if (durationMs > 0 && durationMs % HOUR_MS === 0) {
1882
+ const hours = durationMs / HOUR_MS;
1883
+ return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
1884
+ }
1885
+ return void 0;
1886
+ }
1887
+ function secondsUntil3(instant, now) {
1888
+ if (!instant) return void 0;
1889
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
1890
+ }
1891
+ function windowFromRow(row, fallback, now) {
1892
+ const usedPercent = row?.limit !== void 0 && row.limit > 0 && row.used !== void 0 ? Math.round(Math.min(100, row.used / row.limit * 100) * 10) / 10 : null;
1893
+ const resetsAt = row?.resetsAtMs !== void 0 ? new Date(row.resetsAtMs).toISOString() : void 0;
1894
+ return {
1895
+ id: fallback.id,
1896
+ label: fallback.label,
1897
+ scope: "all",
1898
+ usedPercent,
1899
+ windowMinutes: fallback.minutes,
1900
+ ...resetsAt !== void 0 ? { resetsAt } : {},
1901
+ remainingSeconds: secondsUntil3(resetsAt, now),
1902
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
1903
+ };
1904
+ }
1905
+ function parseKimiUsagePayload(payload, now) {
1906
+ if (!isRecord(payload)) return [];
1907
+ const byId = /* @__PURE__ */ new Map();
1908
+ const rowFrom = (data) => {
1909
+ const limit = finiteNumber2(data["limit"]);
1910
+ let used = finiteNumber2(data["used"]);
1911
+ const remaining = finiteNumber2(data["remaining"]);
1912
+ if (used === void 0 && remaining !== void 0 && limit !== void 0) {
1913
+ used = limit - remaining;
1914
+ }
1915
+ let windowDurationMs;
1916
+ const windowData = isRecord(data["window"]) ? data["window"] : void 0;
1917
+ const duration = finiteNumber2(windowData?.["duration"]);
1918
+ const timeUnit = typeof windowData?.["timeUnit"] === "string" ? windowData["timeUnit"].toUpperCase() : "";
1919
+ if (duration !== void 0) {
1920
+ if (timeUnit.includes("MINUTE")) windowDurationMs = duration * MINUTE_MS;
1921
+ else if (timeUnit.includes("HOUR")) windowDurationMs = duration * HOUR_MS;
1922
+ else if (timeUnit.includes("DAY")) windowDurationMs = duration * DAY_MS;
1923
+ else if (timeUnit.includes("WEEK")) windowDurationMs = duration * 7 * DAY_MS;
1924
+ else if (timeUnit.includes("SECOND")) windowDurationMs = duration * 1e3;
1925
+ }
1926
+ const resetsAtMs = parseResetMs(windowData && parseResetMs(windowData, now) !== void 0 ? windowData : data, now);
1927
+ return { used, limit, remaining, ...resetsAtMs !== void 0 ? { resetsAtMs } : {}, ...windowDurationMs !== void 0 ? { windowDurationMs } : {} };
1928
+ };
1929
+ if (isRecord(payload["usage"])) {
1930
+ const row = rowFrom(payload["usage"]);
1931
+ const window = windowFromRow({ ...row, resetsAtMs: row.resetsAtMs }, { id: "seven-day", label: "7 days", minutes: 10080 }, now);
1932
+ byId.set("seven-day", window);
1933
+ }
1934
+ if (Array.isArray(payload["limits"])) {
1935
+ for (const item of payload["limits"]) {
1936
+ if (!isRecord(item)) continue;
1937
+ const detail = isRecord(item["detail"]) ? item["detail"] : item;
1938
+ const row = rowFrom(detail);
1939
+ const canonical = row.windowDurationMs !== void 0 ? canonicalWindow(row.windowDurationMs) : void 0;
1940
+ if (!canonical) continue;
1941
+ const window = windowFromRow(row, canonical, now);
1942
+ const existing = byId.get(canonical.id);
1943
+ if (!existing || (window.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
1944
+ byId.set(canonical.id, window);
1945
+ }
1946
+ }
1947
+ }
1948
+ return [...byId.values()].sort((a, b) => (a.windowMinutes ?? Infinity) - (b.windowMinutes ?? Infinity)).slice(0, 4);
1949
+ }
1950
+ var KimiAllowanceCollector = class {
1951
+ constructor(credentials, store = (0, import_AccountAllowanceStore3.getSharedAccountAllowanceStore)(), fetchImpl = (url, init, accountId) => (0, import_upstreamFetch3.fetchUpstream)(url, init, { providerId: "kimi", accountId, redactBodies: true }), now = Date.now) {
1952
+ this.credentials = credentials;
1953
+ this.store = store;
1954
+ this.fetchImpl = fetchImpl;
1955
+ this.now = now;
1956
+ }
1957
+ credentials;
1958
+ store;
1959
+ fetchImpl;
1960
+ now;
1961
+ inFlight = /* @__PURE__ */ new Map();
1962
+ async collectMany(accounts, options = {}) {
1963
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
1964
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
1965
+ }
1966
+ collect(account, options = {}) {
1967
+ const now = this.now();
1968
+ if (account.tokens.authMethod !== "oauth") {
1969
+ const existing = this.store.get("kimi", account.id, now);
1970
+ if (existing?.windows.every((window) => window.state === "unsupported")) {
1971
+ return Promise.resolve(existing);
1972
+ }
1973
+ const snapshot = this.unsupportedSnapshot(account.id, now);
1974
+ this.store.set(snapshot);
1975
+ return Promise.resolve(snapshot);
1976
+ }
1977
+ const cached = this.store.get("kimi", account.id, now);
1978
+ if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
1979
+ return Promise.resolve(cached);
1980
+ }
1981
+ const running = this.inFlight.get(account.id);
1982
+ if (running) return running;
1983
+ const promise = this.fetchAccount(account.id, account.tokens).catch(() => this.failureSnapshot(account.id, "kimi_usage_request_failed", this.now())).finally(() => this.inFlight.delete(account.id));
1984
+ this.inFlight.set(account.id, promise);
1985
+ return promise;
1986
+ }
1987
+ isCacheValid(snapshot, now, refreshAheadMs) {
1988
+ if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
1989
+ const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
1990
+ const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
1991
+ return Number.isFinite(expiresAt) && expiresAt > now + ahead;
1992
+ }
1993
+ async fetchAccount(accountId, tokens) {
1994
+ let accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
1995
+ if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
1996
+ let response = await this.request(accountId, accessToken, tokens);
1997
+ if (response.status === 401) {
1998
+ const refreshed = await this.credentials.refreshAccountToken("kimi", accountId);
1999
+ if (!refreshed) return this.failureSnapshot(accountId, "kimi_usage_unauthorized", this.now());
2000
+ accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
2001
+ if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
2002
+ response = await this.request(accountId, accessToken, tokens);
2003
+ }
2004
+ if (response.status === 403) {
2005
+ const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "kimi_usage_unsupported");
2006
+ this.store.set(snapshot2);
2007
+ return snapshot2;
2008
+ }
2009
+ if (!response.ok) return this.failureSnapshot(accountId, "kimi_usage_http_error", this.now());
2010
+ let payload;
2011
+ try {
2012
+ payload = await response.json();
2013
+ } catch {
2014
+ return this.failureSnapshot(accountId, "kimi_usage_invalid_response", this.now());
2015
+ }
2016
+ const now = this.now();
2017
+ const windows = parseKimiUsagePayload(payload, now);
2018
+ const snapshot = {
2019
+ providerId: "kimi",
2020
+ accountId,
2021
+ source: "oauth-usage-api",
2022
+ observedAt: new Date(now).toISOString(),
2023
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
2024
+ windows: windows.length > 0 ? windows : [
2025
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
2026
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
2027
+ ],
2028
+ ...windows.length > 0 ? {} : { lastErrorCode: "kimi_usage_invalid_response" }
2029
+ };
2030
+ this.store.set(snapshot);
2031
+ return snapshot;
2032
+ }
2033
+ request(accountId, accessToken, tokens) {
2034
+ return this.fetchImpl(KIMI_USAGE_URL, {
2035
+ method: "GET",
2036
+ headers: {
2037
+ Authorization: `Bearer ${accessToken}`,
2038
+ Accept: "application/json",
2039
+ ...(0, import_subscriptions3.kimiFingerprintHeaders)(tokens.deviceId)
2040
+ },
2041
+ signal: AbortSignal.timeout(15e3)
2042
+ }, accountId);
2043
+ }
2044
+ failureSnapshot(accountId, code, now) {
2045
+ const existing = this.store.get("kimi", accountId, now);
2046
+ const snapshot = existing ? {
2047
+ ...existing,
2048
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
2049
+ windows: existing.windows.map((window) => ({
2050
+ ...window,
2051
+ state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
2052
+ })),
2053
+ lastErrorCode: code
2054
+ } : {
2055
+ providerId: "kimi",
2056
+ accountId,
2057
+ source: "oauth-usage-api",
2058
+ observedAt: new Date(now).toISOString(),
2059
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
2060
+ windows: [
2061
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
2062
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
2063
+ ],
2064
+ lastErrorCode: code
2065
+ };
2066
+ this.store.set(snapshot);
2067
+ return snapshot;
2068
+ }
2069
+ unsupportedSnapshot(accountId, now, code = "kimi_usage_unsupported_auth") {
2070
+ return {
2071
+ providerId: "kimi",
2072
+ accountId,
2073
+ source: "oauth-usage-api",
2074
+ observedAt: new Date(now).toISOString(),
2075
+ windows: [
2076
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unsupported" },
2077
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unsupported" }
2078
+ ],
2079
+ lastErrorCode: code
2080
+ };
2081
+ }
2082
+ };
2083
+
2084
+ // src/allowance/OpenCodeGoAllowanceCollector.ts
2085
+ var import_AccountAllowanceStore4 = require("@omnicross/core/pipeline/AccountAllowanceStore");
2086
+ var import_upstreamFetch4 = require("@omnicross/core/pipeline/upstreamFetch");
2087
+ var import_subscriptions4 = require("@omnicross/subscriptions");
2088
+ var OPENCODEGO_ALLOWANCE_CACHE_MS = 5 * 6e4;
2089
+ var OPENCODEGO_DEFAULT_GO_BASE = "https://opencode.ai/zen/go";
2090
+ function finitePercent3(value) {
2091
+ if (value === null || value === void 0 || value === "") return null;
2092
+ const parsed = typeof value === "number" ? value : Number(value);
2093
+ return Number.isFinite(parsed) && parsed >= 0 && parsed <= 100 ? parsed : null;
2094
+ }
2095
+ function isoInstant2(value) {
2096
+ if (typeof value !== "string" || !value.trim()) return void 0;
2097
+ const time = Date.parse(value);
2098
+ return Number.isFinite(time) ? new Date(time).toISOString() : void 0;
2099
+ }
2100
+ function secondsUntil4(instant, now) {
2101
+ if (!instant) return void 0;
2102
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
2103
+ }
2104
+ function windowFromPayload3(id, label, minutes, payload, now) {
2105
+ const statusRateLimited = payload?.status === "rate-limited";
2106
+ const usedPercent = statusRateLimited ? 100 : finitePercent3(payload?.percent);
2107
+ const resetsAt = isoInstant2(payload?.resetsAt);
2108
+ return {
2109
+ id,
2110
+ label,
2111
+ scope: "all",
2112
+ usedPercent,
2113
+ windowMinutes: minutes,
2114
+ ...resetsAt !== void 0 ? { resetsAt } : {},
2115
+ remainingSeconds: secondsUntil4(resetsAt, now),
2116
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
2117
+ };
2118
+ }
2119
+ var OpenCodeGoAllowanceCollector = class {
2120
+ constructor(credentials, store = (0, import_AccountAllowanceStore4.getSharedAccountAllowanceStore)(), fetchImpl = (url, init, accountId) => (0, import_upstreamFetch4.fetchUpstream)(url, init, { providerId: "opencodego", accountId, redactBodies: true }), now = Date.now) {
2121
+ this.credentials = credentials;
2122
+ this.store = store;
2123
+ this.fetchImpl = fetchImpl;
2124
+ this.now = now;
2125
+ }
2126
+ credentials;
2127
+ store;
2128
+ fetchImpl;
2129
+ now;
2130
+ inFlight = /* @__PURE__ */ new Map();
2131
+ async collectMany(accounts, options = {}) {
2132
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
2133
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
2134
+ }
2135
+ collect(account, options = {}) {
2136
+ const now = this.now();
2137
+ const cached = this.store.get("opencodego", account.id, now);
2138
+ if (!options.force && cached && (cached.windows.every((window) => window.state === "unsupported") || cached.expiresAt && Date.parse(cached.expiresAt) > now + (options.refreshAheadMs ?? 0))) {
2139
+ return Promise.resolve(cached);
2140
+ }
2141
+ const running = this.inFlight.get(account.id);
2142
+ if (running) return running;
2143
+ const promise = this.fetchAccount(account).catch(() => this.failureSnapshot(account.id, this.now())).finally(() => this.inFlight.delete(account.id));
2144
+ this.inFlight.set(account.id, promise);
2145
+ return promise;
2146
+ }
2147
+ async fetchAccount(account) {
2148
+ const apiKey = await this.credentials.getAccessTokenForAccount("opencodego", account.id);
2149
+ if (!apiKey) return this.failureSnapshot(account.id, this.now());
2150
+ const base = account.tokens.baseUrl ? (0, import_subscriptions4.normalizeOpenCodeGoBaseUrl)(account.tokens.baseUrl) : OPENCODEGO_DEFAULT_GO_BASE;
2151
+ const response = await this.fetchImpl(`${base}/v1/usage`, {
2152
+ method: "GET",
2153
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
2154
+ signal: AbortSignal.timeout(15e3)
2155
+ }, account.id);
2156
+ if (response.status === 401 || response.status === 403) {
2157
+ return this.failureSnapshot(account.id, this.now(), "opencodego_usage_unauthorized");
2158
+ }
2159
+ if (!response.ok) return this.failureSnapshot(account.id, this.now());
2160
+ let payload;
2161
+ try {
2162
+ payload = await response.json();
2163
+ } catch {
2164
+ return this.failureSnapshot(account.id, this.now());
2165
+ }
2166
+ const usage = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.usage : void 0;
2167
+ const now = this.now();
2168
+ const snapshot = {
2169
+ providerId: "opencodego",
2170
+ accountId: account.id,
2171
+ source: "oauth-usage-api",
2172
+ observedAt: new Date(now).toISOString(),
2173
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
2174
+ // Monthly deliberately omitted (module doc).
2175
+ windows: [
2176
+ windowFromPayload3("five-hour", "5 hours", 5 * 60, usage?.rolling ?? void 0, now),
2177
+ windowFromPayload3("seven-day", "7 days", 7 * 24 * 60, usage?.weekly ?? void 0, now)
2178
+ ]
2179
+ };
2180
+ this.store.set(snapshot);
2181
+ return snapshot;
2182
+ }
2183
+ failureSnapshot(accountId, now, code = "opencodego_usage_request_failed") {
2184
+ const existing = this.store.get("opencodego", accountId, now);
2185
+ const snapshot = existing ? {
2186
+ ...existing,
2187
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
2188
+ windows: existing.windows.map((window) => ({
2189
+ ...window,
2190
+ state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
2191
+ })),
2192
+ lastErrorCode: code
2193
+ } : {
2194
+ providerId: "opencodego",
2195
+ accountId,
2196
+ source: "oauth-usage-api",
2197
+ observedAt: new Date(now).toISOString(),
2198
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
2199
+ windows: [
2200
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
2201
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
2202
+ ],
2203
+ lastErrorCode: code
2204
+ };
2205
+ this.store.set(snapshot);
2206
+ return snapshot;
2207
+ }
2208
+ };
2209
+
1510
2210
  // src/allowance/AccountAllowanceService.ts
1511
2211
  function codexUnavailable(accountId, now) {
1512
2212
  return {
@@ -1522,26 +2222,30 @@ function codexUnavailable(accountId, now) {
1522
2222
  };
1523
2223
  }
1524
2224
  var AccountAllowanceService = class {
1525
- constructor(credentials, store = (0, import_AccountAllowanceStore2.getSharedAccountAllowanceStore)(), collector, now = Date.now) {
2225
+ constructor(credentials, store = (0, import_AccountAllowanceStore5.getSharedAccountAllowanceStore)(), collector, codexCollector, kimiCollector, opencodegoCollector, now = Date.now) {
1526
2226
  this.credentials = credentials;
1527
2227
  this.store = store;
1528
2228
  this.now = now;
1529
2229
  this.claudeCollector = collector ?? new ClaudeAllowanceCollector(credentials, store);
2230
+ this.codexCollector = codexCollector ?? new CodexAllowanceCollector(credentials, store);
2231
+ this.kimiCollector = kimiCollector ?? new KimiAllowanceCollector(credentials, store);
2232
+ this.opencodegoCollector = opencodegoCollector ?? new OpenCodeGoAllowanceCollector(credentials, store);
1530
2233
  }
1531
2234
  credentials;
1532
2235
  store;
1533
2236
  now;
1534
2237
  claudeCollector;
2238
+ codexCollector;
2239
+ kimiCollector;
2240
+ opencodegoCollector;
1535
2241
  /**
1536
- * Read all/filtered snapshots. Claude's five-minute cache is refreshed lazily;
1537
- * Codex remains passive and reports not-observed until a real model response.
2242
+ * Read all/filtered snapshots. Claude's and Codex's five-minute caches are
2243
+ * refreshed lazily on read (Codex polls `/backend-api/wham/usage`; the
2244
+ * passive `x-codex-*` header tap still feeds mid-flight updates).
1538
2245
  */
1539
2246
  async list(filter = {}) {
1540
2247
  const config = await this.credentials.getFullConfig();
1541
- this.store.pruneToKnownAccounts([
1542
- ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
1543
- ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
1544
- ]);
2248
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1545
2249
  const wantsClaude = !filter.providerId || filter.providerId === "claude";
1546
2250
  const claudeAccounts = (config.claudeAccounts ?? []).filter(
1547
2251
  (account) => !filter.accountId || account.id === filter.accountId
@@ -1552,39 +2256,90 @@ var AccountAllowanceService = class {
1552
2256
  (account) => !filter.accountId || account.id === filter.accountId
1553
2257
  );
1554
2258
  if (wantsCodex) {
2259
+ await this.codexCollector.collectMany(codexAccounts);
1555
2260
  for (const account of codexAccounts) {
1556
2261
  if (!this.store.get("codex", account.id)) this.store.set(codexUnavailable(account.id, this.now()));
1557
2262
  }
1558
2263
  }
2264
+ const wantsKimi = !filter.providerId || filter.providerId === "kimi";
2265
+ const kimiAccounts = (config.kimiAccounts ?? []).filter(
2266
+ (account) => !filter.accountId || account.id === filter.accountId
2267
+ );
2268
+ if (wantsKimi) await this.kimiCollector.collectMany(kimiAccounts);
2269
+ const wantsOpenCodeGo = !filter.providerId || filter.providerId === "opencodego";
2270
+ const opencodegoAccounts = (config.opencodegoAccounts ?? []).filter(
2271
+ (account) => !filter.accountId || account.id === filter.accountId
2272
+ );
2273
+ if (wantsOpenCodeGo) await this.opencodegoCollector.collectMany(opencodegoAccounts);
1559
2274
  const known = /* @__PURE__ */ new Set();
1560
2275
  if (wantsClaude) for (const account of claudeAccounts) known.add(`claude\0${account.id}`);
1561
2276
  if (wantsCodex) for (const account of codexAccounts) known.add(`codex\0${account.id}`);
2277
+ if (wantsKimi) for (const account of kimiAccounts) known.add(`kimi\0${account.id}`);
2278
+ if (wantsOpenCodeGo) for (const account of opencodegoAccounts) known.add(`opencodego\0${account.id}`);
1562
2279
  return this.store.list(filter).filter((snapshot) => known.has(`${snapshot.providerId}\0${snapshot.accountId}`));
1563
2280
  }
2281
+ knownAccounts(config) {
2282
+ return [
2283
+ ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
2284
+ ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id })),
2285
+ ...(config.kimiAccounts ?? []).map((account) => ({ providerId: "kimi", accountId: account.id })),
2286
+ ...(config.opencodegoAccounts ?? []).map((account) => ({ providerId: "opencodego", accountId: account.id }))
2287
+ ];
2288
+ }
1564
2289
  /** Force-refresh Claude usage for one account or every stored Claude account. */
1565
2290
  async refreshClaude(accountId) {
1566
2291
  const config = await this.credentials.getFullConfig();
1567
- this.store.pruneToKnownAccounts([
1568
- ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
1569
- ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
1570
- ]);
2292
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1571
2293
  const accounts = (config.claudeAccounts ?? []).filter(
1572
2294
  (account) => !accountId || account.id === accountId
1573
2295
  );
1574
2296
  return this.claudeCollector.collectMany(accounts, { force: true });
1575
2297
  }
1576
2298
  /**
1577
- * Keep Claude snapshots warm for allowance-aware routing. This deliberately
1578
- * excludes Codex (whose quota is learned from real response headers) and
1579
- * preserves the collector's cache + per-account in-flight coalescing.
2299
+ * Force-refresh Codex usage (`/backend-api/wham/usage`) for one account or
2300
+ * every stored Codex account. Replaces the old probe-request workaround
2301
+ * no quota is spent reading the usage endpoint.
2302
+ */
2303
+ async refreshCodex(accountId) {
2304
+ const config = await this.credentials.getFullConfig();
2305
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
2306
+ const accounts = (config.codexAccounts ?? []).filter(
2307
+ (account) => !accountId || account.id === accountId
2308
+ );
2309
+ return this.codexCollector.collectMany(accounts, { force: true });
2310
+ }
2311
+ /** Force-refresh OpenCodeGo usage (`{go}/v1/usage`) for one/all accounts. */
2312
+ async refreshOpenCodeGo(accountId) {
2313
+ const config = await this.credentials.getFullConfig();
2314
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
2315
+ const accounts = (config.opencodegoAccounts ?? []).filter(
2316
+ (account) => !accountId || account.id === accountId
2317
+ );
2318
+ return this.opencodegoCollector.collectMany(accounts, { force: true });
2319
+ }
2320
+ /** Force-refresh Kimi usage (`/coding/v1/usages`) for one/all accounts. */
2321
+ async refreshKimi(accountId) {
2322
+ const config = await this.credentials.getFullConfig();
2323
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
2324
+ const accounts = (config.kimiAccounts ?? []).filter(
2325
+ (account) => !accountId || account.id === accountId
2326
+ );
2327
+ return this.kimiCollector.collectMany(accounts, { force: true });
2328
+ }
2329
+ /**
2330
+ * Keep Claude + Codex + Kimi snapshots warm for allowance-aware routing. All
2331
+ * collectors preserve their cache + per-account in-flight coalescing; a tick
2332
+ * normally performs no network I/O. (Codex joined the warm path when it
2333
+ * gained an active `/wham/usage` collector — the passive `x-codex-*` header
2334
+ * tap alone could not keep the policy fed while idle.)
1580
2335
  */
1581
2336
  async maintainClaudeCache(refreshAheadMs) {
1582
2337
  const config = await this.credentials.getFullConfig();
1583
- this.store.pruneToKnownAccounts([
1584
- ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
1585
- ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
1586
- ]);
2338
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1587
2339
  await this.claudeCollector.collectMany(config.claudeAccounts ?? [], { refreshAheadMs });
2340
+ await this.codexCollector.collectMany(config.codexAccounts ?? [], { refreshAheadMs });
2341
+ await this.kimiCollector.collectMany(config.kimiAccounts ?? [], { refreshAheadMs });
2342
+ await this.opencodegoCollector.collectMany(config.opencodegoAccounts ?? [], { refreshAheadMs });
1588
2343
  }
1589
2344
  /** Remove a cache row as soon as an account is deleted by the admin path. */
1590
2345
  removeAccountSnapshot(providerId, accountId) {
@@ -1679,7 +2434,7 @@ var ClaudeAllowanceRefreshScheduler = class {
1679
2434
  var import_node_crypto4 = require("crypto");
1680
2435
  var import_node_fs6 = require("fs");
1681
2436
  var import_node_path6 = require("path");
1682
- var import_AccountAllowanceStore3 = require("@omnicross/core/pipeline/AccountAllowanceStore");
2437
+ var import_AccountAllowanceStore6 = require("@omnicross/core/pipeline/AccountAllowanceStore");
1683
2438
  var ACCOUNT_ALLOWANCE_CACHE_VERSION = 1;
1684
2439
  var MAX_PERSISTED_ALLOWANCE_SNAPSHOTS = 256;
1685
2440
  var MAX_ALLOWANCE_CACHE_BYTES = 1e6;
@@ -1708,7 +2463,7 @@ var JsonAccountAllowancePersistence = class {
1708
2463
  save(snapshots) {
1709
2464
  const rows = [];
1710
2465
  for (const snapshot of snapshots) {
1711
- const normalized2 = (0, import_AccountAllowanceStore3.normalizeAccountAllowanceSnapshot)(snapshot);
2466
+ const normalized2 = (0, import_AccountAllowanceStore6.normalizeAccountAllowanceSnapshot)(snapshot);
1712
2467
  if (!normalized2) continue;
1713
2468
  rows.push(normalized2);
1714
2469
  if (rows.length >= MAX_PERSISTED_ALLOWANCE_SNAPSHOTS) break;
@@ -1984,7 +2739,7 @@ var import_outbound_api5 = require("@omnicross/core/outbound-api");
1984
2739
  var import_image_generation_types = require("@omnicross/contracts/image-generation-types");
1985
2740
  var import_AccountAllowanceScheduling2 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
1986
2741
  var import_SubscriptionAccountHealth = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
1987
- var import_upstreamFetch3 = require("@omnicross/core/pipeline/upstreamFetch");
2742
+ var import_upstreamFetch7 = require("@omnicross/core/pipeline/upstreamFetch");
1988
2743
 
1989
2744
  // src/image-generation/imagesConfigValidation.ts
1990
2745
  var import_outbound_api = require("@omnicross/core/outbound-api");
@@ -3617,11 +4372,11 @@ function preserveOutboundProxySecrets(incoming, current) {
3617
4372
  }
3618
4373
 
3619
4374
  // src/proxy/upstreamProxyResolver.ts
3620
- var import_upstreamFetch2 = require("@omnicross/core/pipeline/upstreamFetch");
4375
+ var import_upstreamFetch5 = require("@omnicross/core/pipeline/upstreamFetch");
3621
4376
  var serverProxy;
3622
4377
  function setServerProxyConfig(proxy) {
3623
4378
  serverProxy = proxy;
3624
- (0, import_upstreamFetch2.bumpUpstreamProxyGeneration)();
4379
+ (0, import_upstreamFetch5.bumpUpstreamProxyGeneration)();
3625
4380
  }
3626
4381
  function getServerProxyConfig() {
3627
4382
  return serverProxy;
@@ -3689,14 +4444,15 @@ function createUpstreamProxyResolver(src = {}) {
3689
4444
  }
3690
4445
 
3691
4446
  // src/admin/accountsOAuth.ts
3692
- var import_subscriptions2 = require("@omnicross/subscriptions");
4447
+ var import_subscriptions5 = require("@omnicross/subscriptions");
3693
4448
 
3694
4449
  // src/admin/accountsWrite.ts
3695
4450
  var VALID_PROVIDER_IDS = [
3696
4451
  "claude",
3697
4452
  "codex",
3698
4453
  "gemini",
3699
- "opencodego"
4454
+ "opencodego",
4455
+ "kimi"
3700
4456
  ];
3701
4457
  function asSubscriptionProviderId(id) {
3702
4458
  return VALID_PROVIDER_IDS.includes(id) ? id : null;
@@ -3832,6 +4588,18 @@ function validateGemini(body) {
3832
4588
  copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "lastRefreshedAt", "errorMessage"]);
3833
4589
  return out;
3834
4590
  }
4591
+ function validateKimi(body) {
4592
+ const authMethod = str(body["authMethod"]);
4593
+ const status = str(body["status"]);
4594
+ if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
4595
+ if (!status || !TOKEN_STATUSES.has(status)) return null;
4596
+ const out = {
4597
+ authMethod,
4598
+ status
4599
+ };
4600
+ copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "accountId", "deviceId", "lastRefreshedAt", "errorMessage"]);
4601
+ return out;
4602
+ }
3835
4603
  function validateOpenCodeGo(body) {
3836
4604
  const authMethod = str(body["authMethod"]);
3837
4605
  const status = str(body["status"]);
@@ -3867,6 +4635,8 @@ function validateTokenBody(providerId, body) {
3867
4635
  return validateGemini(body);
3868
4636
  case "opencodego":
3869
4637
  return validateOpenCodeGo(body);
4638
+ case "kimi":
4639
+ return validateKimi(body);
3870
4640
  default:
3871
4641
  return null;
3872
4642
  }
@@ -3896,37 +4666,37 @@ async function statusEntryFor(reader, providerId) {
3896
4666
 
3897
4667
  // src/admin/accountsOAuth.ts
3898
4668
  var OAUTH_HTTP_PROVIDERS = /* @__PURE__ */ new Set(["claude", "gemini"]);
3899
- function err2(status, message) {
4669
+ function err3(status, message) {
3900
4670
  return { status, body: { error: { type: "admin_api_error", message } } };
3901
4671
  }
3902
4672
  function handleOAuthStart(providerId, deps) {
3903
4673
  if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
3904
- return err2(400, `oauth not available for provider '${providerId}'`);
4674
+ return err3(400, `oauth not available for provider '${providerId}'`);
3905
4675
  }
3906
- const flow = providerId === "claude" ? import_subscriptions2.claudeOAuth : import_subscriptions2.geminiOAuth;
4676
+ const flow = providerId === "claude" ? import_subscriptions5.claudeOAuth : import_subscriptions5.geminiOAuth;
3907
4677
  const { authUrl, codeVerifier, state } = flow.generateAuthParams();
3908
4678
  const sessionId = deps.oauthSessions.put({ providerId, codeVerifier, state });
3909
4679
  return { status: 200, body: { authUrl, sessionId } };
3910
4680
  }
3911
4681
  async function handleOAuthComplete(providerId, body, deps) {
3912
4682
  if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
3913
- return err2(400, `oauth not available for provider '${providerId}'`);
4683
+ return err3(400, `oauth not available for provider '${providerId}'`);
3914
4684
  }
3915
4685
  const sessionId = typeof body["sessionId"] === "string" ? body["sessionId"] : "";
3916
4686
  const rawCode = typeof body["code"] === "string" ? body["code"] : "";
3917
- if (!sessionId) return err2(400, "oauth complete requires { sessionId }");
3918
- if (!rawCode) return err2(400, "oauth complete requires { code }");
4687
+ if (!sessionId) return err3(400, "oauth complete requires { sessionId }");
4688
+ if (!rawCode) return err3(400, "oauth complete requires { code }");
3919
4689
  const session = deps.oauthSessions.peek(sessionId);
3920
- if (!session) return err2(410, "oauth session is unknown, expired, or already used");
4690
+ if (!session) return err3(410, "oauth session is unknown, expired, or already used");
3921
4691
  if (session.providerId !== providerId) {
3922
- return err2(400, `oauth session does not match provider '${providerId}'`);
4692
+ return err3(400, `oauth session does not match provider '${providerId}'`);
3923
4693
  }
3924
4694
  let code = rawCode.trim();
3925
4695
  if (providerId === "claude") {
3926
4696
  const [splitCode, pastedState] = code.split("#");
3927
- if (!splitCode) return err2(400, "no authorization code was provided");
4697
+ if (!splitCode) return err3(400, "no authorization code was provided");
3928
4698
  if (pastedState && pastedState !== session.state) {
3929
- return err2(400, "oauth state did not match (possible CSRF) \u2014 aborting");
4699
+ return err3(400, "oauth state did not match (possible CSRF) \u2014 aborting");
3930
4700
  }
3931
4701
  code = splitCode;
3932
4702
  }
@@ -3936,7 +4706,7 @@ async function handleOAuthComplete(providerId, body, deps) {
3936
4706
  block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state, exchangeFetch) : await exchangeGemini(code, session.codeVerifier, exchangeFetch);
3937
4707
  } catch (exchangeError) {
3938
4708
  const reason = exchangeError instanceof Error ? exchangeError.message : "token exchange failed";
3939
- return err2(502, `oauth token exchange failed for '${providerId}': ${reason}`);
4709
+ return err3(502, `oauth token exchange failed for '${providerId}': ${reason}`);
3940
4710
  }
3941
4711
  deps.oauthSessions.consume(sessionId);
3942
4712
  const label = typeof body["label"] === "string" && body["label"].trim() ? body["label"].trim() : void 0;
@@ -3945,7 +4715,7 @@ async function handleOAuthComplete(providerId, body, deps) {
3945
4715
  return { status: 200, body: status ? { account: status } : { ok: true } };
3946
4716
  }
3947
4717
  async function exchangeClaude(code, codeVerifier, state, exchangeFetch) {
3948
- const result = await import_subscriptions2.claudeOAuth.exchangeCodeForTokens(
4718
+ const result = await import_subscriptions5.claudeOAuth.exchangeCodeForTokens(
3949
4719
  { authorizationCode: code, codeVerifier, state },
3950
4720
  exchangeFetch
3951
4721
  );
@@ -3961,7 +4731,7 @@ async function exchangeClaude(code, codeVerifier, state, exchangeFetch) {
3961
4731
  };
3962
4732
  }
3963
4733
  async function exchangeGemini(code, codeVerifier, exchangeFetch) {
3964
- const result = await import_subscriptions2.geminiOAuth.exchangeCodeForTokens(code, codeVerifier, exchangeFetch);
4734
+ const result = await import_subscriptions5.geminiOAuth.exchangeCodeForTokens(code, codeVerifier, exchangeFetch);
3965
4735
  const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
3966
4736
  return {
3967
4737
  authMethod: "oauth",
@@ -4266,8 +5036,8 @@ function errBody(message) {
4266
5036
  return { error: { type: "admin_api_error", message } };
4267
5037
  }
4268
5038
  var defaultCommandRunner = (command) => new Promise((resolve11) => {
4269
- (0, import_node_child_process.exec)(command, { timeout: 18e4 }, (err5, _stdout, stderr) => {
4270
- if (err5) resolve11({ ok: false, error: stderr.trim() || err5.message });
5039
+ (0, import_node_child_process.exec)(command, { timeout: 18e4 }, (err6, _stdout, stderr) => {
5040
+ if (err6) resolve11({ ok: false, error: stderr.trim() || err6.message });
4271
5041
  else resolve11({ ok: true });
4272
5042
  });
4273
5043
  });
@@ -4313,8 +5083,8 @@ async function handleCliLaunch(cli, body, ctx) {
4313
5083
  providerId: typeof body["providerId"] === "string" ? body["providerId"] : void 0,
4314
5084
  model: typeof body["model"] === "string" ? body["model"] : void 0
4315
5085
  });
4316
- } catch (err5) {
4317
- return { status: 400, body: errBody(err5 instanceof Error ? err5.message : "no launch target") };
5086
+ } catch (err6) {
5087
+ return { status: 400, body: errBody(err6 instanceof Error ? err6.message : "no launch target") };
4318
5088
  }
4319
5089
  const id = (0, import_node_crypto7.randomUUID)();
4320
5090
  let leaseId2;
@@ -4342,9 +5112,9 @@ async function handleCliLaunch(cli, body, ctx) {
4342
5112
  } else {
4343
5113
  launch = await buildLaunchEnv(cli, ctx.llmConfig, target);
4344
5114
  }
4345
- } catch (err5) {
4346
- const status = err5 instanceof import_provider_proxy2.RouteLeaseError ? err5.status : 400;
4347
- return { status, body: errBody(err5 instanceof Error ? err5.message : "failed to build launch env") };
5115
+ } catch (err6) {
5116
+ const status = err6 instanceof import_provider_proxy2.RouteLeaseError ? err6.status : 400;
5117
+ return { status, body: errBody(err6 instanceof Error ? err6.message : "failed to build launch env") };
4348
5118
  }
4349
5119
  const cwd = typeof body["cwd"] === "string" && body["cwd"].trim() ? body["cwd"].trim() : void 0;
4350
5120
  const opener = ctx.opener ?? defaultTerminalOpener;
@@ -4372,9 +5142,9 @@ async function handleCliLaunch(cli, body, ctx) {
4372
5142
  onFailure: onSessionEnd
4373
5143
  });
4374
5144
  if (cleanup) openerCleanup = cleanup;
4375
- } catch (err5) {
5145
+ } catch (err6) {
4376
5146
  onSessionEnd();
4377
- return { status: 500, body: errBody(err5 instanceof Error ? err5.message : "failed to open terminal") };
5147
+ return { status: 500, body: errBody(err6 instanceof Error ? err6.message : "failed to open terminal") };
4378
5148
  }
4379
5149
  if (ended) {
4380
5150
  openerCleanup?.();
@@ -4509,6 +5279,7 @@ async function handleDashboard(deps) {
4509
5279
  var import_outbound_api2 = require("@omnicross/core/outbound-api");
4510
5280
  var import_api3 = require("@omnicross/core/search/api");
4511
5281
  var import_http3 = require("@omnicross/core/search/http");
5282
+ var import_search2 = require("@omnicross/core/search");
4512
5283
 
4513
5284
  // src/search/searchDoctorProjection.ts
4514
5285
  var import_search_types = require("@omnicross/contracts/search-types");
@@ -4569,7 +5340,7 @@ function buildSearchDoctorSnapshot(contributions = (0, import_http.builtinHttpSe
4569
5340
  }
4570
5341
  return rows;
4571
5342
  }
4572
- var SEARCH_DOCTOR_QUERY = "mozilla developer network http headers";
5343
+ var SEARCH_DOCTOR_QUERY = "MDN HTTP headers documentation";
4573
5344
  function classifyLiveSearchOutcome(providerId, outcome, checkedAt) {
4574
5345
  if (outcome.kind === "results") {
4575
5346
  if (outcome.count > 0) return { providerId, status: "healthy", checkedAt };
@@ -4628,6 +5399,7 @@ async function runSearchLiveChecks(contributions, now = () => (/* @__PURE__ */ n
4628
5399
  }
4629
5400
 
4630
5401
  // src/search/SearchAssembly.ts
5402
+ var import_upstreamFetch6 = require("@omnicross/core/pipeline/upstreamFetch");
4631
5403
  var import_search = require("@omnicross/core/search");
4632
5404
  var import_api2 = require("@omnicross/core/search/api");
4633
5405
  var import_http2 = require("@omnicross/core/search/http");
@@ -4644,11 +5416,24 @@ function searchPolicyFrom(config) {
4644
5416
  ...maxAttempts !== void 0 ? { maxAttempts } : {}
4645
5417
  };
4646
5418
  }
5419
+ function resolveSearchUpstreamDispatcher(url) {
5420
+ return (0, import_upstreamFetch6.resolveUpstreamDispatcher)({ url });
5421
+ }
5422
+ var searchUpstreamProxyConfig = createUpstreamProxyResolver();
5423
+ function resolveSearchUpstreamProxyConfig(url) {
5424
+ return searchUpstreamProxyConfig({ url });
5425
+ }
4647
5426
  function searchContributionsFrom(config) {
4648
5427
  return [
4649
- ...(0, import_http2.builtinHttpSearchContributions)(),
5428
+ ...(0, import_http2.builtinHttpSearchContributions)(
5429
+ (0, import_http2.createSearchHttpTransport)({
5430
+ resolveProxyDispatcher: resolveSearchUpstreamDispatcher,
5431
+ resolveProxyConfig: resolveSearchUpstreamProxyConfig
5432
+ })
5433
+ ),
4650
5434
  ...(0, import_api2.apiSearchContributions)(config.providers, {
4651
- egressPolicy: searchEgressPolicyFrom(config)
5435
+ egressPolicy: searchEgressPolicyFrom(config),
5436
+ resolveProxyDispatcher: resolveSearchUpstreamDispatcher
4652
5437
  })
4653
5438
  ];
4654
5439
  }
@@ -4792,6 +5577,18 @@ async function handleSearchDiagnostics(res, deps) {
4792
5577
  };
4793
5578
  return writeJson(res, 200, { diagnostics: snapshot });
4794
5579
  }
5580
+ function persistedSearchContributions(search, fetchImpl) {
5581
+ if (fetchImpl) {
5582
+ const egressPolicy = searchEgressPolicyFrom(search);
5583
+ return [
5584
+ ...(0, import_http3.builtinHttpSearchContributions)(
5585
+ (0, import_http3.createSearchHttpTransport)({ fetch: fetchImpl, egressPolicy })
5586
+ ),
5587
+ ...(0, import_api3.apiSearchContributions)(search.providers, { egressPolicy, fetchImpl })
5588
+ ];
5589
+ }
5590
+ return searchContributionsFrom(search);
5591
+ }
4795
5592
  async function handleSearchTest(req, res, deps) {
4796
5593
  const status = deps.searchStatus;
4797
5594
  const body = await readBodyOrReject(req, res);
@@ -4809,16 +5606,8 @@ async function handleSearchTest(req, res, deps) {
4809
5606
  if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
4810
5607
  return writeErr(res, 400, `search provider '${providerId}' is not configured`);
4811
5608
  }
4812
- const egressPolicy = searchEgressPolicyFrom(search);
4813
5609
  const fetchImpl = status.testFetch;
4814
- const transport = fetchImpl ? (0, import_http3.createSearchHttpTransport)({ fetch: fetchImpl, egressPolicy }) : void 0;
4815
- const contributions = [
4816
- ...(0, import_http3.builtinHttpSearchContributions)(transport),
4817
- ...(0, import_api3.apiSearchContributions)(search.providers, {
4818
- egressPolicy,
4819
- ...fetchImpl ? { fetchImpl } : {}
4820
- })
4821
- ];
5610
+ const contributions = persistedSearchContributions(search, fetchImpl);
4822
5611
  const contribution = contributions.find((c) => c.id === providerId);
4823
5612
  if (!contribution) {
4824
5613
  return writeErr(res, 400, `search provider '${providerId}' is not configured`);
@@ -4859,48 +5648,49 @@ async function handleSearchQuery(req, res, deps) {
4859
5648
  }
4860
5649
  if (QUERY_CONTROL_CHARS.test(query2)) {
4861
5650
  return writeErr(res, 400, "query must not contain control characters");
4862
- }
4863
- const persisted = await (0, import_outbound_api2.loadServerConfig)(deps.settingsStore);
4864
- const search = persisted.search ?? import_outbound_api2.DEFAULT_SEARCH_SERVER_CONFIG;
4865
- const providers = search.providers;
4866
- if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
4867
- return writeErr(res, 400, `search provider '${providerId}' is not configured`);
4868
- }
4869
- const egressPolicy = searchEgressPolicyFrom(search);
4870
- const fetchImpl = status.testFetch;
4871
- const transport = fetchImpl ? (0, import_http3.createSearchHttpTransport)({ fetch: fetchImpl, egressPolicy }) : void 0;
4872
- const contributions = [
4873
- ...(0, import_http3.builtinHttpSearchContributions)(transport),
4874
- ...(0, import_api3.apiSearchContributions)(search.providers, {
4875
- egressPolicy,
4876
- ...fetchImpl ? { fetchImpl } : {}
4877
- })
4878
- ];
4879
- const contribution = contributions.find((c) => c.id === providerId);
4880
- if (!contribution) {
5651
+ }
5652
+ const persisted = await (0, import_outbound_api2.loadServerConfig)(deps.settingsStore);
5653
+ const search = persisted.search ?? import_outbound_api2.DEFAULT_SEARCH_SERVER_CONFIG;
5654
+ const providers = search.providers;
5655
+ if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
4881
5656
  return writeErr(res, 400, `search provider '${providerId}' is not configured`);
4882
5657
  }
5658
+ const fetchImpl = status.testFetch;
5659
+ const runtime = (0, import_search2.createSearchRuntime)({
5660
+ contributions: persistedSearchContributions(search, fetchImpl),
5661
+ policy: {
5662
+ ...searchPolicyFrom(search),
5663
+ // The panel always walks: it answers "does a search WORK for this
5664
+ // operator", not "does this one provider behave" — that is `/test`'s
5665
+ // job. The persisted policy's allowlist still bounds the walk.
5666
+ fallbackEnabled: true,
5667
+ preferred: providerId
5668
+ }
5669
+ });
4883
5670
  const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
4884
5671
  try {
4885
- const results = await contribution.provider.search(query2, { maxResults: 5 });
5672
+ const orchestrated = await runtime.search({ query: query2, options: { maxResults: 5 } });
5673
+ const results = orchestrated.results;
4886
5674
  const sanitized = results.slice(0, SEARCH_QUERY_MAX_RESULTS).map((result) => ({
4887
5675
  title: sanitizeResultField(result.title, SEARCH_RESULT_FIELD_CAPS.title),
4888
5676
  url: sanitizeResultField(result.url, SEARCH_RESULT_FIELD_CAPS.url),
4889
5677
  content: sanitizeResultField(result.content, SEARCH_RESULT_FIELD_CAPS.content)
4890
5678
  }));
4891
- const diagnostic = sanitized.length === 0 ? { providerId: contribution.id, status: "healthy", checkedAt } : classifyLiveSearchOutcome(
4892
- contribution.id,
5679
+ const diagnostic = sanitized.length === 0 ? { providerId: orchestrated.providerId, status: "healthy", checkedAt } : classifyLiveSearchOutcome(
5680
+ orchestrated.providerId,
4893
5681
  { kind: "results", count: sanitized.length },
4894
5682
  checkedAt
4895
5683
  );
4896
5684
  const response = {
4897
5685
  diagnostic,
5686
+ providerUsed: orchestrated.providerId,
5687
+ fallbackCount: orchestrated.fallbackCount,
4898
5688
  resultCount: sanitized.length,
4899
5689
  results: sanitized
4900
5690
  };
4901
5691
  return writeJson(res, 200, { result: response });
4902
5692
  } catch (error) {
4903
- const diagnostic = classifyLiveSearchOutcome(contribution.id, { kind: "failure", error }, checkedAt);
5693
+ const diagnostic = classifyLiveSearchOutcome(providerId, { kind: "failure", error }, checkedAt);
4904
5694
  const response = { diagnostic };
4905
5695
  return writeJson(res, 200, { result: response });
4906
5696
  }
@@ -4909,7 +5699,7 @@ async function handleSearchQuery(req, res, deps) {
4909
5699
  // src/admin/searchAdminView.ts
4910
5700
  var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
4911
5701
  var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
4912
- function isRecord(value) {
5702
+ function isRecord2(value) {
4913
5703
  return value !== null && typeof value === "object" && !Array.isArray(value);
4914
5704
  }
4915
5705
  function redactSearchServerConfig(search) {
@@ -4959,13 +5749,13 @@ function resolveSecretField(entry, field, stored) {
4959
5749
  else delete entry[field];
4960
5750
  }
4961
5751
  function preserveSearchSecrets(incoming, current) {
4962
- if (!isRecord(incoming)) return incoming;
5752
+ if (!isRecord2(incoming)) return incoming;
4963
5753
  const section = { ...incoming };
4964
5754
  const providersValue = section["providers"];
4965
- if (!isRecord(providersValue)) return section;
5755
+ if (!isRecord2(providersValue)) return section;
4966
5756
  const providers = {};
4967
5757
  for (const [id, entryValue] of Object.entries(providersValue)) {
4968
- if (!isRecord(entryValue)) {
5758
+ if (!isRecord2(entryValue)) {
4969
5759
  providers[id] = entryValue;
4970
5760
  continue;
4971
5761
  }
@@ -5043,7 +5833,7 @@ function parseKeyPolicyBody(body) {
5043
5833
  var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
5044
5834
  var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
5045
5835
  var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
5046
- function isRecord2(value) {
5836
+ function isRecord3(value) {
5047
5837
  return !!value && typeof value === "object" && !Array.isArray(value);
5048
5838
  }
5049
5839
  function nonBlank(value) {
@@ -5063,7 +5853,7 @@ function validateGatewayBindingsSegment(patch) {
5063
5853
  const ids = /* @__PURE__ */ new Set();
5064
5854
  raw.forEach((entry, index) => {
5065
5855
  const path2 = `bindings[${index}]`;
5066
- if (!isRecord2(entry)) {
5856
+ if (!isRecord3(entry)) {
5067
5857
  errors.push(`${path2} must be an object`);
5068
5858
  return;
5069
5859
  }
@@ -5092,12 +5882,12 @@ function validateGatewayBindingsSegment(patch) {
5092
5882
  } else if (entry.modelMappings.length > 100) {
5093
5883
  errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
5094
5884
  } else if (entry.modelMappings.some(
5095
- (mapping) => !isRecord2(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
5885
+ (mapping) => !isRecord3(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
5096
5886
  )) {
5097
5887
  errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
5098
5888
  }
5099
5889
  }
5100
- if (!isRecord2(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
5890
+ if (!isRecord3(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
5101
5891
  errors.push(`${path2}.target is invalid`);
5102
5892
  } else {
5103
5893
  if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
@@ -5112,7 +5902,7 @@ function validateGatewayBindingsSegment(patch) {
5112
5902
  }
5113
5903
  }
5114
5904
  if (entry.modelMap !== void 0) {
5115
- if (!isRecord2(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
5905
+ if (!isRecord3(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
5116
5906
  errors.push(`${path2}.modelMap must contain string values`);
5117
5907
  }
5118
5908
  }
@@ -5394,7 +6184,8 @@ var PROVIDER_KEYS = {
5394
6184
  block: "opencodego",
5395
6185
  accounts: "opencodegoAccounts",
5396
6186
  active: "activeOpencodegoAccountId"
5397
- }
6187
+ },
6188
+ kimi: { block: "kimi", accounts: "kimiAccounts", active: "activeKimiAccountId" }
5398
6189
  };
5399
6190
  function clone(value) {
5400
6191
  return JSON.parse(JSON.stringify(value));
@@ -5916,7 +6707,7 @@ async function applyImport(packString, passphrase, mode, deps, parseProviderInpu
5916
6707
  }
5917
6708
 
5918
6709
  // src/admin/adminMigration.ts
5919
- function err3(status, message) {
6710
+ function err4(status, message) {
5920
6711
  return { status, body: { error: { type: "admin_api_error", message } } };
5921
6712
  }
5922
6713
  async function handleExport(body, deps) {
@@ -5926,30 +6717,30 @@ async function handleExport(body, deps) {
5926
6717
  return { status: 200, body: { pack, version: BUNDLE_VERSION } };
5927
6718
  } catch (error) {
5928
6719
  if (error instanceof WeakPassphraseError) {
5929
- return err3(400, error.message);
6720
+ return err4(400, error.message);
5930
6721
  }
5931
- return err3(500, "failed to build the migration pack");
6722
+ return err4(500, "failed to build the migration pack");
5932
6723
  }
5933
6724
  }
5934
6725
  async function handleImport(body, deps) {
5935
6726
  const blob = typeof body["blob"] === "string" ? body["blob"] : "";
5936
6727
  const passphrase = typeof body["passphrase"] === "string" ? body["passphrase"] : "";
5937
6728
  const mode = body["mode"] === "overwrite" ? "overwrite" : "merge";
5938
- if (!blob) return err3(400, "import requires { blob }");
6729
+ if (!blob) return err4(400, "import requires { blob }");
5939
6730
  try {
5940
6731
  const counts = await applyImport(blob, passphrase, mode, deps, deps.parseProviderInput);
5941
6732
  return { status: 200, body: counts };
5942
6733
  } catch (error) {
5943
6734
  if (error instanceof WeakPassphraseError) {
5944
- return err3(400, error.message);
6735
+ return err4(400, error.message);
5945
6736
  }
5946
- return err3(400, error instanceof Error ? error.message : "import failed");
6737
+ return err4(400, error instanceof Error ? error.message : "import failed");
5947
6738
  }
5948
6739
  }
5949
6740
 
5950
6741
  // src/admin/usagePricing.ts
5951
6742
  var import_usage = require("@omnicross/core/usage");
5952
- var err4 = (status, message) => ({
6743
+ var err5 = (status, message) => ({
5953
6744
  status,
5954
6745
  body: { error: { type: "admin_api_error", message } }
5955
6746
  });
@@ -5962,7 +6753,7 @@ function parseRange(query2) {
5962
6753
  const startTs = parseFiniteInt(query2.get("startTs"));
5963
6754
  const endTs = parseFiniteInt(query2.get("endTs"));
5964
6755
  if (startTs === null || endTs === null) {
5965
- return err4(400, "startTs and endTs are required finite-integer unix-millis query params");
6756
+ return err5(400, "startTs and endTs are required finite-integer unix-millis query params");
5966
6757
  }
5967
6758
  return { startTs, endTs };
5968
6759
  }
@@ -5987,14 +6778,14 @@ async function handleUsageGet(view, query2, deps) {
5987
6778
  case "timeseries": {
5988
6779
  const bucket = query2.get("bucket");
5989
6780
  if (bucket !== "hour" && bucket !== "day" && bucket !== "month") {
5990
- return err4(400, "bucket must be one of 'hour', 'day', 'month'");
6781
+ return err5(400, "bucket must be one of 'hour', 'day', 'month'");
5991
6782
  }
5992
6783
  const now = Date.now();
5993
6784
  const clamped = { startTs: range.startTs, endTs: Math.min(range.endTs, now) };
5994
6785
  if (clamped.startTs < clamped.endTs) {
5995
6786
  const projected = Math.ceil((clamped.endTs - clamped.startTs) / BUCKET_SPAN_MS[bucket]) + 1;
5996
6787
  if (projected > MAX_TIMESERIES_BUCKETS) {
5997
- return err4(
6788
+ return err5(
5998
6789
  400,
5999
6790
  `requested range projects ~${projected} '${bucket}' buckets (max ${MAX_TIMESERIES_BUCKETS}); narrow the range or use a coarser bucket`
6000
6791
  );
@@ -6017,7 +6808,7 @@ async function handleUsageGet(view, query2, deps) {
6017
6808
  };
6018
6809
  }
6019
6810
  default:
6020
- return err4(404, `unknown usage view '${view ?? ""}'`);
6811
+ return err5(404, `unknown usage view '${view ?? ""}'`);
6021
6812
  }
6022
6813
  }
6023
6814
  function poolKeyLabels(cfg) {
@@ -6066,7 +6857,7 @@ async function handlePricingList(deps) {
6066
6857
  async function handlePricingUpsert(body, deps) {
6067
6858
  const input = parsePricingEntryInput(body);
6068
6859
  if (!input) {
6069
- return err4(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
6860
+ return err5(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
6070
6861
  }
6071
6862
  const entry = await deps.pricingEngine.upsertManual(input);
6072
6863
  return { status: 200, body: { entry } };
@@ -6075,7 +6866,7 @@ async function handlePricingDelete(query2, deps) {
6075
6866
  const providerId = query2.get("providerId")?.trim() ?? "";
6076
6867
  const modelId = query2.get("modelId")?.trim() ?? "";
6077
6868
  if (!providerId || !modelId) {
6078
- return err4(400, "delete requires providerId and modelId query params");
6869
+ return err5(400, "delete requires providerId and modelId query params");
6079
6870
  }
6080
6871
  const deleted = await deps.pricingStore.delete(providerId, modelId);
6081
6872
  if (deleted) await deps.pricingEngine.invalidateCache();
@@ -6095,13 +6886,13 @@ async function handlePricingFetchLatest(deps) {
6095
6886
  }
6096
6887
  };
6097
6888
  } catch (e) {
6098
- return err4(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
6889
+ return err5(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
6099
6890
  }
6100
6891
  }
6101
6892
  async function handlePricingResolveConflicts(body, deps) {
6102
6893
  const raw = body["resolutions"];
6103
6894
  if (!Array.isArray(raw)) {
6104
- return err4(400, "resolve-conflicts requires { resolutions: [...] }");
6895
+ return err5(400, "resolve-conflicts requires { resolutions: [...] }");
6105
6896
  }
6106
6897
  const currentRows = await deps.pricingStore.getAll();
6107
6898
  const userEditedKeys = new Set(
@@ -6111,21 +6902,21 @@ async function handlePricingResolveConflicts(body, deps) {
6111
6902
  const pendingIncoming = /* @__PURE__ */ new Map();
6112
6903
  let staleCount = 0;
6113
6904
  for (const item of raw) {
6114
- if (!item || typeof item !== "object") return err4(400, "invalid resolution entry");
6905
+ if (!item || typeof item !== "object") return err5(400, "invalid resolution entry");
6115
6906
  const r = item;
6116
6907
  const action = r["action"];
6117
6908
  if (action !== "overwrite" && action !== "skip") {
6118
- return err4(400, "resolution action must be 'overwrite' or 'skip'");
6909
+ return err5(400, "resolution action must be 'overwrite' or 'skip'");
6119
6910
  }
6120
6911
  const providerId = typeof r["providerId"] === "string" && r["providerId"].trim() ? r["providerId"].trim() : "";
6121
6912
  const modelId = typeof r["modelId"] === "string" && r["modelId"].trim() ? r["modelId"].trim() : "";
6122
6913
  if (!providerId || !modelId) {
6123
- return err4(400, "each resolution requires top-level providerId and modelId");
6914
+ return err5(400, "each resolution requires top-level providerId and modelId");
6124
6915
  }
6125
6916
  const incoming = parsePricingEntryInput(r["incoming"]);
6126
- if (!incoming) return err4(400, "each resolution must echo a valid incoming pricing entry");
6917
+ if (!incoming) return err5(400, "each resolution must echo a valid incoming pricing entry");
6127
6918
  if (incoming.providerId !== providerId || incoming.modelId !== modelId) {
6128
- return err4(400, "resolution providerId/modelId must match the echoed incoming entry");
6919
+ return err5(400, "resolution providerId/modelId must match the echoed incoming entry");
6129
6920
  }
6130
6921
  const key = `${providerId}::${modelId}`;
6131
6922
  if (action === "overwrite" && !userEditedKeys.has(key)) {
@@ -6170,7 +6961,7 @@ function query(req) {
6170
6961
  }
6171
6962
  function allowanceProvider(value) {
6172
6963
  if (!value) return void 0;
6173
- return value === "claude" || value === "codex" ? value : null;
6964
+ return value === "claude" || value === "codex" || value === "kimi" || value === "opencodego" ? value : null;
6174
6965
  }
6175
6966
  async function handleAccountAllowanceApi(req, res, method, rest, service) {
6176
6967
  if (!service) return writeError2(res, 501, "account allowance service is not available");
@@ -6184,7 +6975,9 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
6184
6975
  const params = query(req);
6185
6976
  const pathProvider = rest.length >= 2 ? rest[0] : null;
6186
6977
  const providerId = allowanceProvider(pathProvider ?? params.get("providerId") ?? params.get("provider"));
6187
- if (providerId === null) return writeError2(res, 400, "providerId must be claude or codex");
6978
+ if (providerId === null) {
6979
+ return writeError2(res, 400, "providerId must be claude, codex, kimi, or opencodego");
6980
+ }
6188
6981
  const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
6189
6982
  const allowances = await service.list({ providerId, accountId });
6190
6983
  return writeJson3(res, 200, { allowances });
@@ -6194,10 +6987,37 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
6194
6987
  const requestedProvider = allowanceProvider(
6195
6988
  typeof body["providerId"] === "string" ? body["providerId"] : "claude"
6196
6989
  );
6197
- if (requestedProvider !== "claude") {
6198
- return writeError2(res, 400, "only Claude allowances support explicit refresh");
6199
- }
6200
6990
  const accountId = typeof body["accountId"] === "string" && body["accountId"].trim() ? body["accountId"].trim() : void 0;
6991
+ if (requestedProvider === "codex") {
6992
+ if (!service.refreshCodex) {
6993
+ return writeError2(res, 501, "codex allowance refresh is not available");
6994
+ }
6995
+ const allowances2 = await service.refreshCodex(accountId);
6996
+ if (accountId && allowances2.length === 0) {
6997
+ return writeError2(res, 404, `Codex account '${accountId}' not found`);
6998
+ }
6999
+ return writeJson3(res, 200, { allowances: allowances2 });
7000
+ }
7001
+ if (requestedProvider === "kimi") {
7002
+ if (!service.refreshKimi) {
7003
+ return writeError2(res, 501, "kimi allowance refresh is not available");
7004
+ }
7005
+ const allowances2 = await service.refreshKimi(accountId);
7006
+ if (accountId && allowances2.length === 0) {
7007
+ return writeError2(res, 404, `Kimi account '${accountId}' not found`);
7008
+ }
7009
+ return writeJson3(res, 200, { allowances: allowances2 });
7010
+ }
7011
+ if (requestedProvider === "opencodego") {
7012
+ if (!service.refreshOpenCodeGo) {
7013
+ return writeError2(res, 501, "opencodego allowance refresh is not available");
7014
+ }
7015
+ const allowances2 = await service.refreshOpenCodeGo(accountId);
7016
+ if (accountId && allowances2.length === 0) {
7017
+ return writeError2(res, 404, `OpenCodeGo account '${accountId}' not found`);
7018
+ }
7019
+ return writeJson3(res, 200, { allowances: allowances2 });
7020
+ }
6201
7021
  const allowances = await service.refreshClaude(accountId);
6202
7022
  if (accountId && allowances.length === 0) {
6203
7023
  return writeError2(res, 404, `Claude account '${accountId}' not found`);
@@ -6365,8 +7185,8 @@ async function handleAdminApi(req, res, path2, deps) {
6365
7185
  default:
6366
7186
  return writeJsonError(res, 404, `unknown admin resource '${resource}'`);
6367
7187
  }
6368
- } catch (err5) {
6369
- writeJsonError(res, 500, err5 instanceof Error ? err5.message : String(err5));
7188
+ } catch (err6) {
7189
+ writeJsonError(res, 500, err6 instanceof Error ? err6.message : String(err6));
6370
7190
  }
6371
7191
  }
6372
7192
  function requestQuery(req) {
@@ -6436,6 +7256,9 @@ async function handleProviders(req, res, method, rest, deps) {
6436
7256
  if (method === "POST" && rest.length === 4 && rest[1] === "keys" && rest[3] === "enabled") {
6437
7257
  return await handleToggleProviderKey(req, res, rest[0], rest[2], cfg, deps);
6438
7258
  }
7259
+ if (method === "POST" && rest.length === 5 && rest[1] === "keys" && rest[3] === "quota" && rest[4] === "refresh") {
7260
+ return await handleProviderKeyQuotaRefresh(res, rest[0], rest[2], cfg, deps);
7261
+ }
6439
7262
  if (method === "PUT" && rest.length === 3 && rest[1] === "keys") {
6440
7263
  return await handleUpdateProviderKey(req, res, rest[0], rest[2], cfg, deps);
6441
7264
  }
@@ -6534,7 +7357,7 @@ async function handleDiscoverModels(res, id, cfg) {
6534
7357
  try {
6535
7358
  const headers = { Accept: "application/json" };
6536
7359
  if (resolvedKey) headers["Authorization"] = `Bearer ${resolvedKey}`;
6537
- const response = await (0, import_upstreamFetch3.fetchUpstream)(url, { method: "GET", headers }, { providerId: "byo" });
7360
+ const response = await (0, import_upstreamFetch7.fetchUpstream)(url, { method: "GET", headers }, { providerId: "byo" });
6538
7361
  if (!response.ok) {
6539
7362
  const text = await response.text().catch(() => "");
6540
7363
  let message = text.slice(0, 300);
@@ -6551,8 +7374,8 @@ async function handleDiscoverModels(res, id, cfg) {
6551
7374
  const data = await response.json();
6552
7375
  const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
6553
7376
  return writeJson4(res, 200, { models });
6554
- } catch (err5) {
6555
- const message = err5 instanceof Error ? err5.message : String(err5);
7377
+ } catch (err6) {
7378
+ const message = err6 instanceof Error ? err6.message : String(err6);
6556
7379
  return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
6557
7380
  }
6558
7381
  }
@@ -6593,7 +7416,7 @@ async function handleTestModel(req, res, id, cfg) {
6593
7416
  }
6594
7417
  const startedAt = Date.now();
6595
7418
  try {
6596
- const response = await (0, import_upstreamFetch3.fetchUpstream)(
7419
+ const response = await (0, import_upstreamFetch7.fetchUpstream)(
6597
7420
  url,
6598
7421
  { method: "POST", headers, body: JSON.stringify(payload) },
6599
7422
  { providerId: "byo" }
@@ -6615,8 +7438,8 @@ async function handleTestModel(req, res, id, cfg) {
6615
7438
  latencyMs,
6616
7439
  sample: extractSampleText(text, row.apiFormat)
6617
7440
  });
6618
- } catch (err5) {
6619
- const message = err5 instanceof Error ? err5.message : String(err5);
7441
+ } catch (err6) {
7442
+ const message = err6 instanceof Error ? err6.message : String(err6);
6620
7443
  return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
6621
7444
  }
6622
7445
  }
@@ -6658,7 +7481,30 @@ async function handleProviderKeys(res, id, cfg, deps) {
6658
7481
  const row = cfg.providers.find((p) => p.id === id);
6659
7482
  if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
6660
7483
  const cooldown = await deps.apiKeyPool.getKeyHealth(id);
6661
- return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
7484
+ const views = toPoolKeyView(row, cooldown, deps);
7485
+ if (deps.providerKeyQuota) {
7486
+ const quotas = await Promise.allSettled(
7487
+ views.map((view) => deps.providerKeyQuota.quotaFor(row, view.id))
7488
+ );
7489
+ views.forEach((view, index) => {
7490
+ const settled = quotas[index];
7491
+ if (settled.status === "fulfilled" && settled.value) view.quota = settled.value;
7492
+ });
7493
+ }
7494
+ return writeJson4(res, 200, { keys: views });
7495
+ }
7496
+ async function handleProviderKeyQuotaRefresh(res, id, keyId, cfg, deps) {
7497
+ if (!deps.providerKeyQuota) return writeJsonError(res, 501, "provider key quota is not available");
7498
+ if (!id || !keyId) return writeJsonError(res, 400, "provider id and key id required in path");
7499
+ const row = cfg.providers.find((p) => p.id === id);
7500
+ if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
7501
+ try {
7502
+ const quota = await deps.providerKeyQuota.quotaFor(row, keyId, { force: true });
7503
+ if (!quota) return writeJsonError(res, 404, `no quota endpoint for key '${keyId}'`);
7504
+ return writeJson4(res, 200, { quota });
7505
+ } catch {
7506
+ return writeJsonError(res, 502, "quota refresh failed");
7507
+ }
6662
7508
  }
6663
7509
  function parsePoolKeyInput(body, existing) {
6664
7510
  const out = {};
@@ -7403,12 +8249,12 @@ async function handleAccounts(req, res, method, rest, deps) {
7403
8249
  }
7404
8250
  return writeJson4(res, 200, { ok: true, affected: result.affected });
7405
8251
  }
7406
- if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
7407
- const result = handleCodexOAuthStatus(rest[2], deps);
8252
+ if (method === "GET" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[3] === "status") {
8253
+ const result = rest[0] === "codex" ? handleCodexOAuthStatus(rest[2], deps) : handleKimiOAuthStatus(rest[2], deps);
7408
8254
  return writeJson4(res, result.status, result.body);
7409
8255
  }
7410
- if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
7411
- const result = handleCodexOAuthCancel(rest[2], deps);
8256
+ if (method === "DELETE" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[2]) {
8257
+ const result = rest[0] === "codex" ? handleCodexOAuthCancel(rest[2], deps) : handleKimiOAuthCancel(rest[2], deps);
7412
8258
  return writeJson4(res, result.status, result.body);
7413
8259
  }
7414
8260
  if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
@@ -7461,7 +8307,15 @@ async function handleAccounts(req, res, method, rest, deps) {
7461
8307
  return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
7462
8308
  }
7463
8309
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
7464
- const result = providerId === "codex" ? handleCodexOAuthStart(deps) : handleOAuthStart(providerId, deps);
8310
+ if (providerId === "codex") {
8311
+ const result2 = handleCodexOAuthStart(deps);
8312
+ return writeJson4(res, result2.status, result2.body);
8313
+ }
8314
+ if (providerId === "kimi") {
8315
+ const result2 = await handleKimiOAuthStart(deps);
8316
+ return writeJson4(res, result2.status, result2.body);
8317
+ }
8318
+ const result = handleOAuthStart(providerId, deps);
7465
8319
  return writeJson4(res, result.status, result.body);
7466
8320
  }
7467
8321
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
@@ -7955,12 +8809,12 @@ async function handlePlayground(req, res, method, deps) {
7955
8809
  const payload = body["body"];
7956
8810
  const status = deps.outboundApiServer.getStatus();
7957
8811
  if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
7958
- const path2 = resolvePlaygroundPath(endpoint, isRecord3(payload) ? payload : {});
8812
+ const path2 = resolvePlaygroundPath(endpoint, isRecord4(payload) ? payload : {});
7959
8813
  if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
7960
8814
  const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
7961
8815
  await proxyToOutbound(res, status.port, path2, key, upstreamBody);
7962
8816
  }
7963
- function isRecord3(v) {
8817
+ function isRecord4(v) {
7964
8818
  return !!v && typeof v === "object" && !Array.isArray(v);
7965
8819
  }
7966
8820
  function proxyToOutbound(res, outboundPort, path2, key, body) {
@@ -7989,8 +8843,8 @@ function proxyToOutbound(res, outboundPort, path2, key, body) {
7989
8843
  });
7990
8844
  }
7991
8845
  );
7992
- upstream.on("error", (err5) => {
7993
- if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err5.message}`);
8846
+ upstream.on("error", (err6) => {
8847
+ if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err6.message}`);
7994
8848
  else res.end();
7995
8849
  resolve11();
7996
8850
  });
@@ -8096,7 +8950,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
8096
8950
  }
8097
8951
 
8098
8952
  // src/admin/version.ts
8099
- var DAEMON_VERSION = true ? "0.2.1" : "0.0.0-dev";
8953
+ var DAEMON_VERSION = true ? "0.3.1" : "0.0.0-dev";
8100
8954
 
8101
8955
  // src/admin/AdminServer.ts
8102
8956
  var LOOPBACK_ADDR = "127.0.0.1";
@@ -8139,13 +8993,13 @@ var AdminServer = class {
8139
8993
  const server = import_node_http2.default.createServer((req, res) => {
8140
8994
  this.onRequest(req, res);
8141
8995
  });
8142
- const onError = (err5) => {
8143
- if (err5.code === "EADDRINUSE" && port !== 0) {
8996
+ const onError = (err6) => {
8997
+ if (err6.code === "EADDRINUSE" && port !== 0) {
8144
8998
  server.removeListener("error", onError);
8145
8999
  this.listen(bindAddr, 0).then(resolve11, reject);
8146
9000
  return;
8147
9001
  }
8148
- reject(err5);
9002
+ reject(err6);
8149
9003
  };
8150
9004
  server.on("error", onError);
8151
9005
  server.listen(port, bindAddr, () => {
@@ -8163,8 +9017,8 @@ var AdminServer = class {
8163
9017
  }
8164
9018
  /** Per-request handler: auth gate (when a token is set) → routing. */
8165
9019
  onRequest(req, res) {
8166
- void this.dispatch(req, res).catch((err5) => {
8167
- const message = err5 instanceof Error ? err5.message : String(err5);
9020
+ void this.dispatch(req, res).catch((err6) => {
9021
+ const message = err6 instanceof Error ? err6.message : String(err6);
8168
9022
  this.deps.logger.error("[AdminServer] unhandled error:", message);
8169
9023
  if (!res.headersSent) {
8170
9024
  res.writeHead(500, { "Content-Type": "application/json" });
@@ -8428,18 +9282,18 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
8428
9282
  return;
8429
9283
  }
8430
9284
  signal?.addEventListener("abort", abort, { once: true });
8431
- server.on("error", (err5) => {
9285
+ server.on("error", (err6) => {
8432
9286
  if (settled) return;
8433
9287
  settled = true;
8434
9288
  clearTimeout(timer);
8435
- if (err5.code === "EADDRINUSE") {
9289
+ if (err6.code === "EADDRINUSE") {
8436
9290
  reject(
8437
9291
  new Error(
8438
9292
  `login: cannot bind ${LOOPBACK_HOST}:${LOOPBACK_PORT} (address in use) \u2014 another codex login or process is holding the port`
8439
9293
  )
8440
9294
  );
8441
9295
  } else {
8442
- reject(err5);
9296
+ reject(err6);
8443
9297
  }
8444
9298
  });
8445
9299
  const timer = setTimeout(() => {
@@ -8514,10 +9368,415 @@ function createPoolKeysLoader(getProviderRow, autoDisabled) {
8514
9368
  };
8515
9369
  }
8516
9370
 
9371
+ // src/allowance/ProviderKeyQuotaService.ts
9372
+ var import_upstreamFetch8 = require("@omnicross/core/pipeline/upstreamFetch");
9373
+
9374
+ // src/allowance/ProviderKeyQuota.ts
9375
+ var MINUTE_MS2 = 6e4;
9376
+ var HOUR_MS2 = 60 * MINUTE_MS2;
9377
+ var DAY_MS2 = 24 * HOUR_MS2;
9378
+ var WEEK_MS = 7 * DAY_MS2;
9379
+ var MONTH_MS = 30 * DAY_MS2;
9380
+ function finiteNumber3(value) {
9381
+ if (value === null || value === void 0 || value === "") return void 0;
9382
+ const parsed = typeof value === "number" ? value : Number(value);
9383
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
9384
+ }
9385
+ function finitePercent4(value) {
9386
+ const parsed = finiteNumber3(value);
9387
+ return parsed !== void 0 && parsed <= 100 ? parsed : null;
9388
+ }
9389
+ function isoInstant3(value) {
9390
+ if (typeof value === "string" && value.trim()) {
9391
+ const time = Date.parse(value);
9392
+ if (Number.isFinite(time)) return new Date(time).toISOString();
9393
+ }
9394
+ const numeric = finiteNumber3(value);
9395
+ if (numeric !== void 0 && numeric > 1e9) {
9396
+ const ms = numeric > 1e12 ? numeric : numeric * 1e3;
9397
+ return new Date(ms).toISOString();
9398
+ }
9399
+ return void 0;
9400
+ }
9401
+ function secondsUntil5(instant, now) {
9402
+ if (!instant) return void 0;
9403
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
9404
+ }
9405
+ function isRecord5(value) {
9406
+ return !!value && typeof value === "object" && !Array.isArray(value);
9407
+ }
9408
+ function detectProviderKeyQuotaAdapter(baseUrl) {
9409
+ if (!baseUrl) return null;
9410
+ let url;
9411
+ try {
9412
+ url = new URL(baseUrl);
9413
+ } catch {
9414
+ return null;
9415
+ }
9416
+ const host = url.hostname.toLowerCase();
9417
+ const path2 = url.pathname.toLowerCase();
9418
+ if ((host === "api.z.ai" || host === "open.bigmodel.cn") && path2.includes("/coding")) {
9419
+ return "zai";
9420
+ }
9421
+ if ((host === "api.minimax.io" || host === "api.minimaxi.com") && // Token Plan rides the plain openai `/v1` (chat completions) surface; the
9422
+ // anthropic `/anthropic` rows are excluded (their usage impl is unverified).
9423
+ (path2 === "/v1" || path2 === "/v1/" || path2 === "" || path2 === "/")) {
9424
+ return "minimax-token-plan";
9425
+ }
9426
+ if (host === "api.code.umans.ai") return "umans";
9427
+ if (host === "api.synthetic.new") return "synthetic";
9428
+ return null;
9429
+ }
9430
+ function providerKeyQuotaUrl(adapter, baseUrl) {
9431
+ const origin = new URL(baseUrl).origin;
9432
+ if (adapter === "zai") return `${origin}/api/monitor/usage/quota/limit`;
9433
+ if (adapter === "minimax-token-plan") return `${origin}/v1/token_plan/remains`;
9434
+ if (adapter === "umans") return `${origin}/v1/usage`;
9435
+ return `${origin}/v2/quotas`;
9436
+ }
9437
+ function providerKeyQuotaAuthHeader(adapter, key) {
9438
+ return adapter === "zai" ? key : `Bearer ${key}`;
9439
+ }
9440
+ function zaiWindowDurationMs(item) {
9441
+ const count = item.number !== void 0 && item.number > 0 ? item.number : 1;
9442
+ switch (item.unit) {
9443
+ case 3:
9444
+ return count * HOUR_MS2;
9445
+ case 4:
9446
+ return count * DAY_MS2;
9447
+ case 5:
9448
+ return count * MONTH_MS;
9449
+ case 6:
9450
+ return WEEK_MS;
9451
+ default:
9452
+ return void 0;
9453
+ }
9454
+ }
9455
+ function zaiWindowIdLabel(durationMs) {
9456
+ if (durationMs === WEEK_MS) return { id: "seven-day", label: "7 days" };
9457
+ if (durationMs === 5 * HOUR_MS2) return { id: "five-hour", label: "5 hours" };
9458
+ if (durationMs === MONTH_MS) return { id: "thirty-day", label: "30 days" };
9459
+ if (durationMs !== void 0 && durationMs % DAY_MS2 === 0) {
9460
+ const days = durationMs / DAY_MS2;
9461
+ return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}` };
9462
+ }
9463
+ if (durationMs !== void 0 && durationMs % HOUR_MS2 === 0) {
9464
+ const hours = durationMs / HOUR_MS2;
9465
+ return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}` };
9466
+ }
9467
+ return { id: "quota", label: "Quota" };
9468
+ }
9469
+ function parseZaiQuotaPayload(payload, now) {
9470
+ if (!isRecord5(payload)) return null;
9471
+ const data = isRecord5(payload["data"]) ? payload["data"] : payload;
9472
+ if (payload["success"] === false) return null;
9473
+ const limits = Array.isArray(data["limits"]) ? data["limits"] : [];
9474
+ const byWindow = /* @__PURE__ */ new Map();
9475
+ for (const raw of limits) {
9476
+ if (!isRecord5(raw)) continue;
9477
+ const item = raw;
9478
+ if (item.type === void 0) continue;
9479
+ const details = raw["usageDetails"];
9480
+ if (Array.isArray(details) && details.some((d) => isRecord5(d) && d["modelCode"] === "zread")) {
9481
+ continue;
9482
+ }
9483
+ const durationMs = zaiWindowDurationMs(item);
9484
+ const { id, label } = zaiWindowIdLabel(durationMs);
9485
+ const limit = finiteNumber3(item.usage);
9486
+ const used = finiteNumber3(item.currentValue);
9487
+ const fromAbsolute = limit !== void 0 && used !== void 0 && limit > 0 ? Math.min(100, used / limit * 100) : void 0;
9488
+ const fromPercentage = finitePercent4(item.percentage) ?? void 0;
9489
+ const usedPercent = fromAbsolute !== void 0 ? Math.round(fromAbsolute * 10) / 10 : fromPercentage;
9490
+ if (usedPercent === void 0) continue;
9491
+ const resetsAt = isoInstant3(item.nextResetTime);
9492
+ const candidate = {
9493
+ id,
9494
+ label,
9495
+ scope: "all",
9496
+ usedPercent,
9497
+ ...durationMs !== void 0 ? { windowMinutes: Math.round(durationMs / MINUTE_MS2) } : {},
9498
+ ...resetsAt !== void 0 ? { resetsAt } : {},
9499
+ remainingSeconds: secondsUntil5(resetsAt, now),
9500
+ state: "fresh"
9501
+ };
9502
+ const existing = byWindow.get(id);
9503
+ if (!existing || (candidate.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
9504
+ byWindow.set(id, candidate);
9505
+ }
9506
+ }
9507
+ const windows = [...byWindow.values()].sort((a, b) => (a.windowMinutes ?? Number.POSITIVE_INFINITY) - (b.windowMinutes ?? Number.POSITIVE_INFINITY));
9508
+ return windows.length > 0 ? windows.slice(0, 4) : null;
9509
+ }
9510
+ var MINIMAX_STATUS_EXHAUSTED = 2;
9511
+ var MINIMAX_SHARED_BUCKET = "general";
9512
+ function parseMiniMaxBucket(value) {
9513
+ if (!isRecord5(value)) return null;
9514
+ const modelName = typeof value["model_name"] === "string" ? value["model_name"].trim() : "";
9515
+ if (!modelName) return null;
9516
+ const instant = (v) => {
9517
+ const n = finiteNumber3(v);
9518
+ return n !== void 0 && n > 1e9 ? n > 1e12 ? n : n * 1e3 : void 0;
9519
+ };
9520
+ return {
9521
+ modelName,
9522
+ intervalEnd: instant(value["end_time"]),
9523
+ intervalRemainingPercent: finiteNumber3(value["current_interval_remaining_percent"]),
9524
+ intervalStatus: finiteNumber3(value["current_interval_status"]),
9525
+ weeklyEnd: instant(value["weekly_end_time"]),
9526
+ weeklyRemainingPercent: finiteNumber3(value["current_weekly_remaining_percent"]),
9527
+ weeklyStatus: finiteNumber3(value["current_weekly_status"])
9528
+ };
9529
+ }
9530
+ function minimaxWindow(id, label, windowMinutes, resetsAtMs, remainingPercent, status, now) {
9531
+ const usedPercent = status === MINIMAX_STATUS_EXHAUSTED ? 100 : remainingPercent !== void 0 ? Math.round((100 - remainingPercent) * 10) / 10 : null;
9532
+ const resetsAt = resetsAtMs !== void 0 ? new Date(resetsAtMs).toISOString() : void 0;
9533
+ return {
9534
+ id,
9535
+ label,
9536
+ scope: "all",
9537
+ usedPercent,
9538
+ ...windowMinutes !== void 0 ? { windowMinutes } : {},
9539
+ ...resetsAt !== void 0 ? { resetsAt } : {},
9540
+ remainingSeconds: secondsUntil5(resetsAt, now),
9541
+ state: usedPercent !== null ? "fresh" : "unavailable"
9542
+ };
9543
+ }
9544
+ function parseMiniMaxTokenPlanPayload(payload, now) {
9545
+ if (!isRecord5(payload)) return null;
9546
+ const baseResp = payload["base_resp"];
9547
+ if (!isRecord5(baseResp) || baseResp["status_code"] !== 0) return null;
9548
+ const buckets = Array.isArray(payload["model_remains"]) ? payload["model_remains"] : [];
9549
+ let general = null;
9550
+ for (const raw of buckets) {
9551
+ const bucket = parseMiniMaxBucket(raw);
9552
+ if (bucket?.modelName === MINIMAX_SHARED_BUCKET) {
9553
+ general = bucket;
9554
+ break;
9555
+ }
9556
+ }
9557
+ if (!general) return null;
9558
+ return [
9559
+ minimaxWindow(
9560
+ "five-hour",
9561
+ "5 hours",
9562
+ 5 * 60,
9563
+ general.intervalEnd,
9564
+ general.intervalRemainingPercent,
9565
+ general.intervalStatus,
9566
+ now
9567
+ ),
9568
+ minimaxWindow(
9569
+ "seven-day",
9570
+ "7 days",
9571
+ Math.round(WEEK_MS / MINUTE_MS2),
9572
+ general.weeklyEnd,
9573
+ general.weeklyRemainingPercent,
9574
+ general.weeklyStatus,
9575
+ now
9576
+ )
9577
+ ];
9578
+ }
9579
+ function parseUmansUsagePayload(payload, now) {
9580
+ if (!isRecord5(payload)) return null;
9581
+ const limits = isRecord5(payload["limits"]) ? payload["limits"] : void 0;
9582
+ const requests = limits && isRecord5(limits["requests"]) ? limits["requests"] : void 0;
9583
+ const usage = isRecord5(payload["usage"]) ? payload["usage"] : void 0;
9584
+ const window = isRecord5(payload["window"]) ? payload["window"] : void 0;
9585
+ const hardCap = finiteNumber3(requests?.["hard_cap"]);
9586
+ const softLimit = finiteNumber3(requests?.["limit"]);
9587
+ const requestsInWindow = finiteNumber3(usage?.["requests_in_window"]);
9588
+ const weightedInWindow = finiteNumber3(usage?.["weighted_in_window"]);
9589
+ const resetsAt = isoInstant3(window?.["resets_at"]);
9590
+ let usedPercent = null;
9591
+ if (hardCap !== void 0 && hardCap > 0 && requestsInWindow !== void 0) {
9592
+ usedPercent = Math.round(Math.min(100, requestsInWindow / hardCap * 100) * 10) / 10;
9593
+ } else if (softLimit !== void 0 && softLimit > 0 && weightedInWindow !== void 0) {
9594
+ usedPercent = Math.round(Math.min(100, weightedInWindow / softLimit * 100) * 10) / 10;
9595
+ }
9596
+ if (usedPercent === null && resetsAt === void 0) return null;
9597
+ return [
9598
+ {
9599
+ id: "five-hour",
9600
+ label: "5 hours",
9601
+ scope: "all",
9602
+ usedPercent,
9603
+ windowMinutes: 5 * 60,
9604
+ ...resetsAt !== void 0 ? { resetsAt } : {},
9605
+ remainingSeconds: secondsUntil5(resetsAt, now),
9606
+ state: "fresh"
9607
+ }
9608
+ ];
9609
+ }
9610
+ function parseSyntheticQuotasPayload(payload, now) {
9611
+ if (!isRecord5(payload)) return null;
9612
+ const fiveHour = isRecord5(payload["rollingFiveHourLimit"]) ? payload["rollingFiveHourLimit"] : void 0;
9613
+ const weekly = isRecord5(payload["weeklyTokenLimit"]) ? payload["weeklyTokenLimit"] : void 0;
9614
+ const windows = [];
9615
+ if (fiveHour) {
9616
+ const max = finiteNumber3(fiveHour["max"]);
9617
+ const remaining = finiteNumber3(fiveHour["remaining"]);
9618
+ const usedPercent = max !== void 0 && max > 0 && remaining !== void 0 ? Math.round(Math.min(100, (max - remaining) / max * 100) * 10) / 10 : null;
9619
+ const resetsAt = isoInstant3(fiveHour["nextTickAt"]);
9620
+ windows.push({
9621
+ id: "five-hour",
9622
+ label: "5 hours",
9623
+ scope: "all",
9624
+ usedPercent,
9625
+ windowMinutes: 5 * 60,
9626
+ ...resetsAt !== void 0 ? { resetsAt } : {},
9627
+ remainingSeconds: secondsUntil5(resetsAt, now),
9628
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
9629
+ });
9630
+ }
9631
+ if (weekly) {
9632
+ const percentRemaining = finiteNumber3(weekly["percentRemaining"]);
9633
+ const usedPercent = percentRemaining !== void 0 ? Math.round(Math.min(100, Math.max(0, 100 - percentRemaining)) * 10) / 10 : null;
9634
+ const resetsAt = isoInstant3(weekly["nextRegenAt"]);
9635
+ windows.push({
9636
+ id: "seven-day",
9637
+ label: "7 days",
9638
+ scope: "all",
9639
+ usedPercent,
9640
+ windowMinutes: 7 * 24 * 60,
9641
+ ...resetsAt !== void 0 ? { resetsAt } : {},
9642
+ remainingSeconds: secondsUntil5(resetsAt, now),
9643
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
9644
+ });
9645
+ }
9646
+ return windows.length > 0 ? windows : null;
9647
+ }
9648
+
9649
+ // src/allowance/ProviderKeyQuotaService.ts
9650
+ function parseQuotaPayload(adapter, payload, now) {
9651
+ switch (adapter) {
9652
+ case "zai":
9653
+ return parseZaiQuotaPayload(payload, now);
9654
+ case "minimax-token-plan":
9655
+ return parseMiniMaxTokenPlanPayload(payload, now);
9656
+ case "umans":
9657
+ return parseUmansUsagePayload(payload, now);
9658
+ case "synthetic":
9659
+ return parseSyntheticQuotasPayload(payload, now);
9660
+ }
9661
+ }
9662
+ var PROVIDER_KEY_QUOTA_CACHE_MS = 5 * 6e4;
9663
+ function resolvedBaseUrl(row) {
9664
+ const modes = row.apiModes ?? [];
9665
+ const selected = row.selectedApiModeId ? modes.find((mode) => mode.id === row.selectedApiModeId) : void 0;
9666
+ const fallback = modes[0];
9667
+ const modeBase = selected?.baseUrl ?? fallback?.baseUrl;
9668
+ return modeBase ?? row.codingPlan?.baseUrl ?? row.baseUrl;
9669
+ }
9670
+ function rowKeyEntries(row) {
9671
+ const pool = (row.apiKeys ?? []).filter((entry) => entry.apiKey.length > 0);
9672
+ if (pool.length > 0) return pool.map((entry) => ({ id: entry.id, apiKey: entry.apiKey }));
9673
+ if (row.apiKey.length > 0) {
9674
+ return [{ id: `${row.id}:default`, apiKey: row.apiKey }];
9675
+ }
9676
+ return [];
9677
+ }
9678
+ var ProviderKeyQuotaService = class {
9679
+ constructor(box, fetchImpl = (url, init) => (0, import_upstreamFetch8.fetchUpstream)(url, init, { redactBodies: true }), now = Date.now) {
9680
+ this.box = box;
9681
+ this.fetchImpl = fetchImpl;
9682
+ this.now = now;
9683
+ }
9684
+ box;
9685
+ fetchImpl;
9686
+ now;
9687
+ cache = /* @__PURE__ */ new Map();
9688
+ inFlight = /* @__PURE__ */ new Map();
9689
+ /**
9690
+ * Quota for one key of a provider row, or `null` when the row has no quota
9691
+ * adapter / no such key. Cache-first; concurrent reads share one flight.
9692
+ */
9693
+ async quotaFor(row, keyId, options = {}) {
9694
+ const adapter = detectProviderKeyQuotaAdapter(resolvedBaseUrl(row));
9695
+ if (!adapter) return null;
9696
+ const entry = rowKeyEntries(row).find((candidate) => candidate.id === keyId);
9697
+ if (!entry) return null;
9698
+ const cacheKey = `${row.id}\0${keyId}`;
9699
+ const now = this.now();
9700
+ const cached = this.cache.get(cacheKey);
9701
+ if (!options.force && cached && Date.parse(cached.expiresAt) > now) return cached;
9702
+ const running = this.inFlight.get(cacheKey);
9703
+ if (running) return running;
9704
+ const promise = this.fetchQuota(adapter, row, entry.apiKey, cacheKey).catch((error) => {
9705
+ void error;
9706
+ const previous = this.cache.get(cacheKey);
9707
+ if (previous) {
9708
+ const degraded = {
9709
+ ...previous,
9710
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
9711
+ windows: previous.windows.map((window) => ({
9712
+ ...window,
9713
+ state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
9714
+ })),
9715
+ errorCode: "quota_request_failed"
9716
+ };
9717
+ this.cache.set(cacheKey, degraded);
9718
+ return degraded;
9719
+ }
9720
+ return null;
9721
+ }).finally(() => this.inFlight.delete(cacheKey));
9722
+ this.inFlight.set(cacheKey, promise);
9723
+ return promise;
9724
+ }
9725
+ /** Drop cached rows for a provider (key added/removed/rotated). */
9726
+ invalidateProvider(providerRowId) {
9727
+ for (const key of this.cache.keys()) {
9728
+ if (key.startsWith(`${providerRowId}\0`)) this.cache.delete(key);
9729
+ }
9730
+ }
9731
+ async fetchQuota(adapter, row, rawKey, cacheKey) {
9732
+ const baseUrl = resolvedBaseUrl(row);
9733
+ const url = providerKeyQuotaUrl(adapter, baseUrl);
9734
+ const key = this.box.decryptMaybe(rawKey);
9735
+ const now = this.now();
9736
+ const response = await this.fetchImpl(url, {
9737
+ method: "GET",
9738
+ headers: {
9739
+ Authorization: providerKeyQuotaAuthHeader(adapter, key),
9740
+ Accept: "application/json",
9741
+ "Content-Type": "application/json"
9742
+ },
9743
+ signal: AbortSignal.timeout(15e3)
9744
+ });
9745
+ if (response.status === 401 || response.status === 403) {
9746
+ const snapshot2 = {
9747
+ adapter,
9748
+ observedAt: new Date(now).toISOString(),
9749
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
9750
+ windows: [],
9751
+ errorCode: "quota_unauthorized"
9752
+ };
9753
+ this.cache.set(cacheKey, snapshot2);
9754
+ return snapshot2;
9755
+ }
9756
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
9757
+ let payload;
9758
+ try {
9759
+ payload = await response.json();
9760
+ } catch {
9761
+ throw new Error("invalid JSON");
9762
+ }
9763
+ const windows = parseQuotaPayload(adapter, payload, now);
9764
+ const snapshot = {
9765
+ adapter,
9766
+ observedAt: new Date(now).toISOString(),
9767
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
9768
+ windows: windows ?? [],
9769
+ ...windows ? {} : { errorCode: "quota_unavailable" }
9770
+ };
9771
+ this.cache.set(cacheKey, snapshot);
9772
+ return snapshot;
9773
+ }
9774
+ };
9775
+
8517
9776
  // src/image-generation/ImageDoctorService.ts
8518
9777
  var import_image_generation = require("@omnicross/core/image-generation");
8519
9778
  var import_outbound_api7 = require("@omnicross/core/outbound-api");
8520
- var import_subscriptions3 = require("@omnicross/subscriptions");
9779
+ var import_subscriptions6 = require("@omnicross/subscriptions");
8521
9780
 
8522
9781
  // src/image-generation/FileCodexImageCapabilityEvidenceSource.ts
8523
9782
  var import_node_crypto13 = require("crypto");
@@ -8955,7 +10214,7 @@ function createImageDoctorService(options) {
8955
10214
  paths,
8956
10215
  ttlMs: config.evidenceTtlMs
8957
10216
  }));
8958
- const createLiveVerifier = options.createLiveVerifier ?? ((strategy, config) => (0, import_subscriptions3.createCodexImageLiveVerifier)({
10217
+ const createLiveVerifier = options.createLiveVerifier ?? ((strategy, config) => (0, import_subscriptions6.createCodexImageLiveVerifier)({
8959
10218
  authStrategy: strategy,
8960
10219
  generationTimeoutMs: config.queue.generationTimeoutMs
8961
10220
  }));
@@ -9317,7 +10576,7 @@ var ImageCleanupService = class {
9317
10576
  var import_node_crypto16 = require("crypto");
9318
10577
  var import_image_generation5 = require("@omnicross/core/image-generation");
9319
10578
  var import_outbound_api8 = require("@omnicross/core/outbound-api");
9320
- var import_subscriptions4 = require("@omnicross/subscriptions");
10579
+ var import_subscriptions7 = require("@omnicross/subscriptions");
9321
10580
 
9322
10581
  // src/image-generation/ImageApiRuntimeResolver.ts
9323
10582
  var import_node_crypto14 = require("crypto");
@@ -9848,7 +11107,7 @@ function createImageRuntimeGeneration(options) {
9848
11107
  now: options.now ?? Date.now,
9849
11108
  referenceStore: options.storage.referenceStore,
9850
11109
  stateStore: options.storage.stateStore
9851
- }) : (0, import_subscriptions4.createCodexSubscriptionImageProvider)({
11110
+ }) : (0, import_subscriptions7.createCodexSubscriptionImageProvider)({
9852
11111
  authStrategy,
9853
11112
  evidenceSource: generationEvidenceSource,
9854
11113
  executionScheduler: scheduler,
@@ -14514,10 +15773,13 @@ function bucketLabel(bucketStartTs, bucket) {
14514
15773
  }
14515
15774
 
14516
15775
  // src/ports/JsonOutboundKeyDb.ts
15776
+ var import_node_fs23 = require("fs");
15777
+ var import_core3 = require("@omnicross/core");
15778
+
15779
+ // src/ports/atomicFile.ts
14517
15780
  var import_node_crypto22 = require("crypto");
14518
15781
  var import_node_fs22 = require("fs");
14519
15782
  var import_node_path25 = require("path");
14520
- var import_core3 = require("@omnicross/core");
14521
15783
  function atomicReplaceUtf8(targetPath, contents) {
14522
15784
  const tempPath = (0, import_node_path25.join)(
14523
15785
  (0, import_node_path25.dirname)(targetPath),
@@ -14547,6 +15809,8 @@ function atomicReplaceUtf8(targetPath, contents) {
14547
15809
  throw error;
14548
15810
  }
14549
15811
  }
15812
+
15813
+ // src/ports/JsonOutboundKeyDb.ts
14550
15814
  var JsonOutboundKeyDb = class {
14551
15815
  /**
14552
15816
  * @param secretBox OPTIONAL reversible-secret codec. When present, a created
@@ -14689,9 +15953,9 @@ var JsonOutboundKeyDb = class {
14689
15953
  }
14690
15954
  /** Read the key rows, tolerating a missing/corrupt file (→ empty list). */
14691
15955
  readRows() {
14692
- if (!(0, import_node_fs22.existsSync)(this.keysPath)) return [];
15956
+ if (!(0, import_node_fs23.existsSync)(this.keysPath)) return [];
14693
15957
  try {
14694
- const parsed = JSON.parse((0, import_node_fs22.readFileSync)(this.keysPath, "utf8"));
15958
+ const parsed = JSON.parse((0, import_node_fs23.readFileSync)(this.keysPath, "utf8"));
14695
15959
  return Array.isArray(parsed) ? parsed : [];
14696
15960
  } catch {
14697
15961
  return [];
@@ -14708,7 +15972,7 @@ function applyPolicyField(row, field, value) {
14708
15972
  }
14709
15973
 
14710
15974
  // src/ports/JsonPricingStore.ts
14711
- var import_node_fs23 = require("fs");
15975
+ var import_node_fs24 = require("fs");
14712
15976
  var import_node_crypto23 = require("crypto");
14713
15977
  var JsonPricingStore = class {
14714
15978
  constructor(pricingPath) {
@@ -14723,9 +15987,9 @@ var JsonPricingStore = class {
14723
15987
  * otherwise unusable pricing table after a crash or manual file edit.
14724
15988
  */
14725
15989
  hasUsableSnapshot() {
14726
- if (!(0, import_node_fs23.existsSync)(this.pricingPath)) return false;
15990
+ if (!(0, import_node_fs24.existsSync)(this.pricingPath)) return false;
14727
15991
  try {
14728
- const parsed = JSON.parse((0, import_node_fs23.readFileSync)(this.pricingPath, "utf8"));
15992
+ const parsed = JSON.parse((0, import_node_fs24.readFileSync)(this.pricingPath, "utf8"));
14729
15993
  return Array.isArray(parsed) && parsed.some(isUsablePricingRow);
14730
15994
  } catch {
14731
15995
  return false;
@@ -14838,9 +16102,9 @@ var JsonPricingStore = class {
14838
16102
  }
14839
16103
  /** Read the pricing rows, tolerating a missing/corrupt file (→ empty list). */
14840
16104
  readRows() {
14841
- if (!(0, import_node_fs23.existsSync)(this.pricingPath)) return [];
16105
+ if (!(0, import_node_fs24.existsSync)(this.pricingPath)) return [];
14842
16106
  try {
14843
- const parsed = JSON.parse((0, import_node_fs23.readFileSync)(this.pricingPath, "utf8"));
16107
+ const parsed = JSON.parse((0, import_node_fs24.readFileSync)(this.pricingPath, "utf8"));
14844
16108
  return Array.isArray(parsed) ? parsed : [];
14845
16109
  } catch {
14846
16110
  return [];
@@ -14849,18 +16113,18 @@ var JsonPricingStore = class {
14849
16113
  writeRows(rows) {
14850
16114
  const temporaryPath = `${this.pricingPath}.${process.pid}.${(0, import_node_crypto23.randomUUID)()}.tmp`;
14851
16115
  try {
14852
- (0, import_node_fs23.writeFileSync)(temporaryPath, JSON.stringify(rows, null, 2) + "\n", {
16116
+ (0, import_node_fs24.writeFileSync)(temporaryPath, JSON.stringify(rows, null, 2) + "\n", {
14853
16117
  encoding: "utf8",
14854
16118
  flag: "wx"
14855
16119
  });
14856
16120
  this.replaceFile(temporaryPath);
14857
16121
  } finally {
14858
- (0, import_node_fs23.rmSync)(temporaryPath, { force: true });
16122
+ (0, import_node_fs24.rmSync)(temporaryPath, { force: true });
14859
16123
  }
14860
16124
  }
14861
16125
  /** Isolated for deterministic failure testing; never removes the target. */
14862
16126
  replaceFile(temporaryPath) {
14863
- (0, import_node_fs23.renameSync)(temporaryPath, this.pricingPath);
16127
+ (0, import_node_fs24.renameSync)(temporaryPath, this.pricingPath);
14864
16128
  }
14865
16129
  };
14866
16130
  function isUsablePricingRow(value) {
@@ -14870,7 +16134,7 @@ function isUsablePricingRow(value) {
14870
16134
  }
14871
16135
 
14872
16136
  // src/pricing/PricingRefreshScheduler.ts
14873
- var import_node_fs24 = require("fs");
16137
+ var import_node_fs25 = require("fs");
14874
16138
  var EMPTY_STATE2 = {
14875
16139
  lastAttemptAt: null,
14876
16140
  lastSuccessAt: null,
@@ -14908,9 +16172,9 @@ var PricingRefreshScheduler = class {
14908
16172
  this.timer = null;
14909
16173
  }
14910
16174
  getState() {
14911
- if (!(0, import_node_fs24.existsSync)(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
16175
+ if (!(0, import_node_fs25.existsSync)(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
14912
16176
  try {
14913
- const value = JSON.parse((0, import_node_fs24.readFileSync)(this.statePath, "utf8"));
16177
+ const value = JSON.parse((0, import_node_fs25.readFileSync)(this.statePath, "utf8"));
14914
16178
  return {
14915
16179
  lastAttemptAt: finiteOrNull(value.lastAttemptAt),
14916
16180
  lastSuccessAt: finiteOrNull(value.lastSuccessAt),
@@ -14963,9 +16227,9 @@ var PricingRefreshScheduler = class {
14963
16227
  }
14964
16228
  writeState(state) {
14965
16229
  const temporaryPath = `${this.statePath}.tmp`;
14966
- (0, import_node_fs24.writeFileSync)(temporaryPath, `${JSON.stringify(state, null, 2)}
16230
+ (0, import_node_fs25.writeFileSync)(temporaryPath, `${JSON.stringify(state, null, 2)}
14967
16231
  `, "utf8");
14968
- (0, import_node_fs24.renameSync)(temporaryPath, this.statePath);
16232
+ (0, import_node_fs25.renameSync)(temporaryPath, this.statePath);
14969
16233
  }
14970
16234
  };
14971
16235
  function finiteOrNull(value) {
@@ -14973,7 +16237,7 @@ function finiteOrNull(value) {
14973
16237
  }
14974
16238
 
14975
16239
  // src/ports/JsonVoucherDb.ts
14976
- var import_node_fs25 = require("fs");
16240
+ var import_node_fs26 = require("fs");
14977
16241
  var JsonVoucherDb = class {
14978
16242
  constructor(vouchersPath) {
14979
16243
  this.vouchersPath = vouchersPath;
@@ -15051,27 +16315,27 @@ var JsonVoucherDb = class {
15051
16315
  }
15052
16316
  /** Read the voucher rows, tolerating a missing/corrupt file (→ empty list). */
15053
16317
  readRows() {
15054
- if (!(0, import_node_fs25.existsSync)(this.vouchersPath)) return [];
16318
+ if (!(0, import_node_fs26.existsSync)(this.vouchersPath)) return [];
15055
16319
  try {
15056
- const parsed = JSON.parse((0, import_node_fs25.readFileSync)(this.vouchersPath, "utf8"));
16320
+ const parsed = JSON.parse((0, import_node_fs26.readFileSync)(this.vouchersPath, "utf8"));
15057
16321
  return Array.isArray(parsed) ? parsed : [];
15058
16322
  } catch {
15059
16323
  return [];
15060
16324
  }
15061
16325
  }
15062
16326
  writeRows(rows) {
15063
- (0, import_node_fs25.writeFileSync)(this.vouchersPath, JSON.stringify(rows, null, 2) + "\n", "utf8");
16327
+ (0, import_node_fs26.writeFileSync)(this.vouchersPath, JSON.stringify(rows, null, 2) + "\n", "utf8");
15064
16328
  }
15065
16329
  };
15066
16330
 
15067
16331
  // src/ports/JsonSubscriptionCredentialStore.ts
15068
- var import_node_fs27 = require("fs");
16332
+ var import_node_fs28 = require("fs");
15069
16333
  var import_node_path27 = require("path");
15070
16334
  var import_SubscriptionAccountHealth2 = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
15071
16335
  var import_AccountAllowanceScheduling3 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
15072
- var import_upstreamFetch4 = require("@omnicross/core/pipeline/upstreamFetch");
16336
+ var import_upstreamFetch9 = require("@omnicross/core/pipeline/upstreamFetch");
15073
16337
  var import_SubscriptionIdentityStore2 = require("@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore");
15074
- var import_subscriptions5 = require("@omnicross/subscriptions");
16338
+ var import_subscriptions8 = require("@omnicross/subscriptions");
15075
16339
 
15076
16340
  // src/ports/account-sync.ts
15077
16341
  function viewOf(tokens) {
@@ -15115,7 +16379,7 @@ function findDuplicateCredentialIds(accounts) {
15115
16379
  }
15116
16380
 
15117
16381
  // src/ports/external-cli-credentials.ts
15118
- var import_node_fs26 = require("fs");
16382
+ var import_node_fs27 = require("fs");
15119
16383
  var import_node_os5 = require("os");
15120
16384
  var import_node_path26 = require("path");
15121
16385
  function externalStorePath(provider, home = (0, import_node_os5.homedir)()) {
@@ -15168,10 +16432,10 @@ function parseCodexTokensEnvelope(raw) {
15168
16432
  }
15169
16433
  function readExternalCliCredentials(provider, home = (0, import_node_os5.homedir)()) {
15170
16434
  const path2 = externalStorePath(provider, home);
15171
- if (!(0, import_node_fs26.existsSync)(path2)) return null;
16435
+ if (!(0, import_node_fs27.existsSync)(path2)) return null;
15172
16436
  let raw;
15173
16437
  try {
15174
- const parsed = JSON.parse((0, import_node_fs26.readFileSync)(path2, "utf8"));
16438
+ const parsed = JSON.parse((0, import_node_fs27.readFileSync)(path2, "utf8"));
15175
16439
  raw = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
15176
16440
  } catch {
15177
16441
  return null;
@@ -15194,16 +16458,18 @@ var JsonSubscriptionCredentialStore = class {
15194
16458
  * as on relay refresh egresses from the SAME proxy IP as the
15195
16459
  * account's traffic. NOT used by any read/write path.
15196
16460
  */
15197
- constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials) {
16461
+ constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials, atomicReplace = atomicReplaceUtf8) {
15198
16462
  this.tokensPath = tokensPath;
15199
16463
  this.box = box;
15200
16464
  this.fetchImpl = fetchImpl;
15201
16465
  this.externalCliReader = externalCliReader;
16466
+ this.atomicReplace = atomicReplace;
15202
16467
  }
15203
16468
  tokensPath;
15204
16469
  box;
15205
16470
  fetchImpl;
15206
16471
  externalCliReader;
16472
+ atomicReplace;
15207
16473
  /**
15208
16474
  * The proxy-aware `FetchLike` for one refresh round-trip (upstream-proxy M1). A
15209
16475
  * TEST-injected `fetchImpl` is returned verbatim; otherwise the refresh routes
@@ -15217,7 +16483,7 @@ var JsonSubscriptionCredentialStore = class {
15217
16483
  * a plaintext token pair into `upstream-trace.jsonl`.
15218
16484
  */
15219
16485
  buildRefreshFetch(providerId, accountId) {
15220
- return this.fetchImpl ?? ((url, init) => (0, import_upstreamFetch4.fetchUpstream)(url, init, { providerId, accountId, redactBodies: true }));
16486
+ return this.fetchImpl ?? ((url, init) => (0, import_upstreamFetch9.fetchUpstream)(url, init, { providerId, accountId, redactBodies: true }));
15221
16487
  }
15222
16488
  /**
15223
16489
  * In-flight refresh coalescing. OAuth refresh tokens are
@@ -15258,7 +16524,7 @@ var JsonSubscriptionCredentialStore = class {
15258
16524
  * other hot reads. Never returns token material.
15259
16525
  */
15260
16526
  getAccountProxy(providerId, accountId) {
15261
- if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego") {
16527
+ if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego" && providerId !== "kimi") {
15262
16528
  return void 0;
15263
16529
  }
15264
16530
  return getAccountProxy(this.readConfig(), providerId, accountId);
@@ -15277,7 +16543,7 @@ var JsonSubscriptionCredentialStore = class {
15277
16543
  const fingerprintOn = identityStore.isEnabled();
15278
16544
  const now = Date.now();
15279
16545
  const out = {};
15280
- for (const provider of ["claude", "codex", "gemini", "opencodego"]) {
16546
+ for (const provider of ["claude", "codex", "gemini", "opencodego", "kimi"]) {
15281
16547
  const sanitized = sanitizeAccounts(config, provider);
15282
16548
  if (sanitized.length === 0) continue;
15283
16549
  for (const account of sanitized) {
@@ -15343,7 +16609,7 @@ var JsonSubscriptionCredentialStore = class {
15343
16609
  this.materializeMigration(config);
15344
16610
  const refreshFetch = this.buildRefreshFetch("claude", capturedId);
15345
16611
  try {
15346
- const result = await import_subscriptions5.claudeOAuth.refreshAccessToken(claude.refreshToken, refreshFetch);
16612
+ const result = await import_subscriptions8.claudeOAuth.refreshAccessToken(claude.refreshToken, refreshFetch);
15347
16613
  const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
15348
16614
  const next = {
15349
16615
  ...claude,
@@ -15378,7 +16644,7 @@ var JsonSubscriptionCredentialStore = class {
15378
16644
  this.materializeMigration(config);
15379
16645
  const refreshFetch = this.buildRefreshFetch("codex", capturedId);
15380
16646
  try {
15381
- const result = await import_subscriptions5.codexOAuth.refreshAccessToken(codex.refreshToken, refreshFetch);
16647
+ const result = await import_subscriptions8.codexOAuth.refreshAccessToken(codex.refreshToken, refreshFetch);
15382
16648
  const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
15383
16649
  const next = {
15384
16650
  ...codex,
@@ -15416,7 +16682,7 @@ var JsonSubscriptionCredentialStore = class {
15416
16682
  this.materializeMigration(config);
15417
16683
  const refreshFetch = this.buildRefreshFetch("gemini", capturedId);
15418
16684
  try {
15419
- const result = await import_subscriptions5.geminiOAuth.refreshAccessToken(gemini.refreshToken, refreshFetch);
16685
+ const result = await import_subscriptions8.geminiOAuth.refreshAccessToken(gemini.refreshToken, refreshFetch);
15420
16686
  const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
15421
16687
  const next = {
15422
16688
  ...gemini,
@@ -15435,6 +16701,47 @@ var JsonSubscriptionCredentialStore = class {
15435
16701
  }
15436
16702
  });
15437
16703
  }
16704
+ /**
16705
+ * Refresh the Kimi Code (Moonshot) OAuth access token (device-flow grant).
16706
+ * Kimi ROTATES the refresh token, so the response's pair is written back
16707
+ * whole; the account's stable `deviceId` (fingerprint header input) is
16708
+ * preserved. The refresh call carries the CLI fingerprint headers. HONEST
16709
+ * `false` when no refresh_token.
16710
+ */
16711
+ async refreshKimiToken() {
16712
+ return this.coalesce("kimi:active", async () => {
16713
+ const config = this.readConfig();
16714
+ const active = getActiveAccount(config, "kimi");
16715
+ const kimi = active?.tokens;
16716
+ if (!active || !kimi?.refreshToken) return false;
16717
+ const capturedId = active.id;
16718
+ this.materializeMigration(config);
16719
+ const refreshFetch = this.buildRefreshFetch("kimi", capturedId);
16720
+ try {
16721
+ const result = await import_subscriptions8.kimiOAuth.refreshAccessToken(
16722
+ kimi.refreshToken,
16723
+ refreshFetch,
16724
+ import_subscriptions8.kimiOAuth.kimiFingerprintHeaders(kimi.deviceId)
16725
+ );
16726
+ const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
16727
+ const next = {
16728
+ ...kimi,
16729
+ accessToken: result.accessToken,
16730
+ refreshToken: result.refreshToken,
16731
+ expiresAt,
16732
+ status: "authorized",
16733
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
16734
+ errorMessage: void 0,
16735
+ syncWarning: void 0
16736
+ };
16737
+ this.writeBackById("kimi", capturedId, next);
16738
+ return true;
16739
+ } catch (error) {
16740
+ this.markExpiredById("kimi", capturedId, kimi, error);
16741
+ return false;
16742
+ }
16743
+ });
16744
+ }
15438
16745
  /**
15439
16746
  * Refresh a SPECIFIC managed account by id (background scheduler sweep and
15440
16747
  * account-pool resolution). It uses only that account's stored refresh
@@ -15487,7 +16794,7 @@ var JsonSubscriptionCredentialStore = class {
15487
16794
  }
15488
16795
  const oauth = account.tokens;
15489
16796
  if (!oauth.accessToken) return null;
15490
- if (providerId === "codex" || providerId === "gemini") {
16797
+ if (providerId === "codex" || providerId === "gemini" || providerId === "kimi") {
15491
16798
  const expiresAtMs = oauth.expiresAt ? Date.parse(oauth.expiresAt) : 0;
15492
16799
  const expiringSoon = expiresAtMs > 0 && Date.now() >= expiresAtMs - ACCOUNT_REFRESH_LEAD_MS;
15493
16800
  if (expiringSoon && oauth.refreshToken) {
@@ -15576,8 +16883,23 @@ var JsonSubscriptionCredentialStore = class {
15576
16883
  }
15577
16884
  /** Dispatch one OAuth refresh round-trip to the provider's shared flow. */
15578
16885
  async refreshUpstream(provider, refreshToken, accountId) {
15579
- const flow = provider === "claude" ? import_subscriptions5.claudeOAuth : provider === "codex" ? import_subscriptions5.codexOAuth : import_subscriptions5.geminiOAuth;
15580
- const r = await flow.refreshAccessToken(refreshToken, this.buildRefreshFetch(provider, accountId));
16886
+ const refreshFetch = this.buildRefreshFetch(provider, accountId);
16887
+ if (provider === "kimi") {
16888
+ const account = accountId ? getAccountById(this.readConfig(), "kimi", accountId) : void 0;
16889
+ const deviceId = account?.tokens?.deviceId;
16890
+ const r2 = await import_subscriptions8.kimiOAuth.refreshAccessToken(
16891
+ refreshToken,
16892
+ refreshFetch,
16893
+ import_subscriptions8.kimiOAuth.kimiFingerprintHeaders(deviceId)
16894
+ );
16895
+ return {
16896
+ accessToken: r2.accessToken,
16897
+ refreshToken: r2.refreshToken,
16898
+ expiresAt: new Date(Date.now() + r2.expiresIn * 1e3).toISOString()
16899
+ };
16900
+ }
16901
+ const flow = provider === "claude" ? import_subscriptions8.claudeOAuth : provider === "codex" ? import_subscriptions8.codexOAuth : import_subscriptions8.geminiOAuth;
16902
+ const r = await flow.refreshAccessToken(refreshToken, refreshFetch);
15581
16903
  return {
15582
16904
  accessToken: r.accessToken,
15583
16905
  refreshToken: r.refreshToken,
@@ -15740,42 +17062,86 @@ var JsonSubscriptionCredentialStore = class {
15740
17062
  /** Write the merged config to disk as pretty JSON (mkdir parent if needed).
15741
17063
  * Encrypt-on-write: the token-material fields are encrypted (legacy plaintext
15742
17064
  * `enc:v1:`; already-`enc:`/`$ENV` untouched) before serializing, so any
15743
- * write incl. child 4's future refresh writes lands encrypted. */
17065
+ * write incl. child 4's future refresh writes lands encrypted.
17066
+ * ATOMIC: temp + fsync + rename (`atomicReplaceUtf8`) — a failed or
17067
+ * interrupted write discards only the temp file; the prior `tokens.json`
17068
+ * survives byte-equal (bare `writeFileSync` truncate-writes lost every
17069
+ * account on a mid-write failure, 2026-09-06). */
15744
17070
  persist(config) {
15745
- (0, import_node_fs27.mkdirSync)((0, import_node_path27.dirname)(this.tokensPath), { recursive: true });
17071
+ (0, import_node_fs28.mkdirSync)((0, import_node_path27.dirname)(this.tokensPath), { recursive: true });
15746
17072
  const encrypted = encryptTokens(config, this.box);
15747
- (0, import_node_fs27.writeFileSync)(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n", "utf8");
17073
+ this.atomicReplace(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
15748
17074
  }
15749
17075
  /**
15750
- * Read + parse `tokens.json`, tolerating a missing/corrupt file, then DECRYPT
15751
- * the token-material fields so every getter returns plaintext (the
15752
- * subscription bearer path is byte-identical).
17076
+ * Read + parse `tokens.json`, then DECRYPT the token-material fields so every
17077
+ * getter returns plaintext (the subscription bearer path is byte-identical).
15753
17078
  *
15754
- * The fs-read + JSON-parse tolerance is INSIDE the try (a missing or corrupt
15755
- * file empty `{ updatedAt: '' }`). The DECRYPT runs OUTSIDE the try, so a
15756
- * wrong/missing master key or a tampered `enc:` envelope FAILS FAST with the
15757
- * box's clear, secret-free error (secrets spec "/ UX":
15758
- * SHALL fail-fast, SHALL NOT a swallowed decrypt would report "no
15759
- * tokens" and silently send the WRONG bearer upstream 401). Mirrors
15760
- * `config.ts loadConfig`, which decrypts outside its parse try.
17079
+ * A MISSING file is a legitimate first-boot state minimal `{ updatedAt: '' }`.
17080
+ * A file that EXISTS but cannot be parsed as a JSON object is CORRUPT
17081
+ * `quarantineCorrupt` moves it aside (once) before the empty config is
17082
+ * returned, so the unreadable accounts survive for manual recovery.
17083
+ *
17084
+ * The DECRYPT runs OUTSIDE any try, so a wrong/missing master key or a
17085
+ * tampered `enc:` envelope FAILS FAST with the box's clear, secret-free
17086
+ * error (secrets spec "/ UX": SHALL fail-fast, SHALL NOT a swallowed
17087
+ * decrypt would report "no tokens" and silently send the WRONG bearer
17088
+ * upstream 401). Mirrors `config.ts loadConfig`, which decrypts outside
17089
+ * its parse try.
15761
17090
  */
15762
17091
  readConfig() {
15763
- if (!(0, import_node_fs27.existsSync)(this.tokensPath)) return { updatedAt: "" };
17092
+ if (!(0, import_node_fs28.existsSync)(this.tokensPath)) return { updatedAt: "" };
15764
17093
  let parsed;
15765
17094
  try {
15766
- const raw = JSON.parse((0, import_node_fs27.readFileSync)(this.tokensPath, "utf8"));
15767
- parsed = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
17095
+ const raw = JSON.parse((0, import_node_fs28.readFileSync)(this.tokensPath, "utf8"));
17096
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
17097
+ return this.quarantineCorrupt("parsed JSON is not an object");
17098
+ }
17099
+ parsed = raw;
15768
17100
  } catch {
15769
- parsed = null;
17101
+ return this.quarantineCorrupt("unparseable JSON");
15770
17102
  }
15771
- if (!parsed) return { updatedAt: "" };
15772
17103
  const decrypted = decryptTokens(parsed, this.box);
15773
17104
  return migrateLazily(decrypted);
15774
17105
  }
17106
+ /** One-shot latch: a corrupt file is quarantined (or found unmovable) at
17107
+ * most once per process, so the hot read path never re-attempts or re-logs. */
17108
+ corruptQuarantined = false;
17109
+ /**
17110
+ * Quarantine a present-but-corrupt `tokens.json`, then treat it as empty.
17111
+ *
17112
+ * Renames the file to a sibling `tokens.json.corrupt-<stamp>` backup and
17113
+ * logs loudly (the daemon's stderr log; secret-free — reason + paths only).
17114
+ * The daemon KEEPS SERVING (API-key routing is unaffected; subscription
17115
+ * routing reports no credential, same as an absent file) while the corrupt
17116
+ * bytes survive for manual recovery — and, critically, the NEXT persist
17117
+ * (e.g. the user re-logging in) can no longer overwrite the only copy of
17118
+ * the old accounts, which is exactly how the 2026-09-06 incident turned a
17119
+ * recoverable truncated file into permanent account loss.
17120
+ *
17121
+ * Best-effort: if the rename fails (file locked, permissions), the corrupt
17122
+ * file is left in place and every later read still tolerates it as empty;
17123
+ * the latch still trips so the attempt + log happen exactly once.
17124
+ */
17125
+ quarantineCorrupt(reason) {
17126
+ if (!this.corruptQuarantined) {
17127
+ this.corruptQuarantined = true;
17128
+ const backup = `${this.tokensPath}.corrupt-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
17129
+ let moved = false;
17130
+ try {
17131
+ (0, import_node_fs28.renameSync)(this.tokensPath, backup);
17132
+ moved = true;
17133
+ } catch {
17134
+ }
17135
+ console.error(
17136
+ `[JsonSubscriptionCredentialStore] tokens.json is corrupt (${reason}); ` + (moved ? `moved to '${backup}' and treated as empty \u2014 recover accounts from that backup before re-adding them` : `could not move '${this.tokensPath}' \u2014 treated as empty`)
17137
+ );
17138
+ }
17139
+ return { updatedAt: "" };
17140
+ }
15775
17141
  };
15776
17142
 
15777
17143
  // src/AccountHealthProbeScheduler.ts
15778
- var import_upstreamFetch5 = require("@omnicross/core/pipeline/upstreamFetch");
17144
+ var import_upstreamFetch10 = require("@omnicross/core/pipeline/upstreamFetch");
15779
17145
 
15780
17146
  // src/probe/CodexGenerationProbe.ts
15781
17147
  var import_codexCliHeaders = require("@omnicross/core/provider-proxy/identity/codexCliHeaders");
@@ -15914,7 +17280,11 @@ var PROVIDER_PROBE_PLANS = {
15914
17280
  // billable/wrong endpoint). Upgrade to `{ kind:'upstream' }` once verified.
15915
17281
  codex: { kind: "local" },
15916
17282
  gemini: { kind: "local" },
15917
- opencodego: { kind: "local" }
17283
+ opencodego: { kind: "local" },
17284
+ // Kimi's `GET /coding/v1/usages` is a verified FREE authed GET (the allowance
17285
+ // collector uses it), but the probe path also needs the fingerprint headers —
17286
+ // keep the probe local until the collector covers the health surface.
17287
+ kimi: { kind: "local" }
15918
17288
  };
15919
17289
  function probePlanFor(providerId) {
15920
17290
  return PROVIDER_PROBE_PLANS[providerId] ?? { kind: "local" };
@@ -15936,7 +17306,7 @@ var AccountHealthProbeScheduler = class {
15936
17306
  this.logger = logger;
15937
17307
  this.config = config;
15938
17308
  this.now = opts.now ?? Date.now;
15939
- this.fetchImpl = opts.fetchImpl ?? import_upstreamFetch5.fetchUpstream;
17309
+ this.fetchImpl = opts.fetchImpl ?? import_upstreamFetch10.fetchUpstream;
15940
17310
  this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
15941
17311
  this.planFor = opts.planFor ?? probePlanFor;
15942
17312
  }
@@ -16280,13 +17650,13 @@ var AccountHealthSweeper = class {
16280
17650
  };
16281
17651
 
16282
17652
  // src/audit/AuditPruneSweeper.ts
16283
- var import_node_fs29 = require("fs");
17653
+ var import_node_fs30 = require("fs");
16284
17654
  var import_node_path29 = require("path");
16285
17655
  var import_promises6 = require("stream/promises");
16286
17656
  var import_node_zlib2 = require("zlib");
16287
17657
 
16288
17658
  // src/audit/auditStats.ts
16289
- var import_node_fs28 = require("fs");
17659
+ var import_node_fs29 = require("fs");
16290
17660
  var import_node_path28 = require("path");
16291
17661
  var SIDECAR_VERSION = 1;
16292
17662
  var META_PREFIX_BYTES = 64 * 1024;
@@ -16295,9 +17665,9 @@ function auditStatsFileName(auditFile) {
16295
17665
  return auditFile.replace(/\.jsonl$/, ".stats.json");
16296
17666
  }
16297
17667
  function readPersisted(path2) {
16298
- if (!(0, import_node_fs28.existsSync)(path2)) return null;
17668
+ if (!(0, import_node_fs29.existsSync)(path2)) return null;
16299
17669
  try {
16300
- const value = JSON.parse((0, import_node_fs28.readFileSync)(path2, "utf8"));
17670
+ const value = JSON.parse((0, import_node_fs29.readFileSync)(path2, "utf8"));
16301
17671
  if (value.version !== SIDECAR_VERSION || !Number.isSafeInteger(value.auditBytes) || (value.auditBytes ?? -1) < 0 || !Number.isSafeInteger(value.requestCount) || (value.requestCount ?? -1) < 0 || !Number.isSafeInteger(value.errorCount) || (value.errorCount ?? -1) < 0 || (value.errorCount ?? 0) > (value.requestCount ?? -1) || typeof value.complete !== "boolean" || value.minTs !== null && !Number.isFinite(value.minTs) || value.maxTs !== null && !Number.isFinite(value.maxTs)) {
16302
17672
  return null;
16303
17673
  }
@@ -16327,7 +17697,7 @@ function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfte
16327
17697
  minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
16328
17698
  maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
16329
17699
  };
16330
- (0, import_node_fs28.writeFileSync)(statsPath, JSON.stringify(next), "utf8");
17700
+ (0, import_node_fs29.writeFileSync)(statsPath, JSON.stringify(next), "utf8");
16331
17701
  }
16332
17702
  function queryCovers(stats, from, to) {
16333
17703
  return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
@@ -16385,7 +17755,7 @@ async function scanAuditFile(auditPath, startByte, auditBytes, from, to) {
16385
17755
  prefixTruncated = false;
16386
17756
  };
16387
17757
  if (auditBytes > startByte) {
16388
- const stream = (0, import_node_fs28.createReadStream)(auditPath, {
17758
+ const stream = (0, import_node_fs29.createReadStream)(auditPath, {
16389
17759
  start: startByte,
16390
17760
  end: auditBytes - 1,
16391
17761
  highWaterMark: READ_CHUNK_BYTES2
@@ -16438,12 +17808,12 @@ function mergePersistedStats(previous, appended) {
16438
17808
  };
16439
17809
  }
16440
17810
  async function readAuditStats(auditDir, query2 = {}) {
16441
- if (!(0, import_node_fs28.existsSync)(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
17811
+ if (!(0, import_node_fs29.existsSync)(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
16442
17812
  const from = typeof query2.from === "number" ? query2.from : -Infinity;
16443
17813
  const to = typeof query2.to === "number" ? query2.to : Infinity;
16444
17814
  let sources;
16445
17815
  try {
16446
- sources = (0, import_node_fs28.readdirSync)(auditDir).filter((name) => fileOverlaps(name, from, to)).sort().map(
17816
+ sources = (0, import_node_fs29.readdirSync)(auditDir).filter((name) => fileOverlaps(name, from, to)).sort().map(
16447
17817
  (name) => AUDIT_DAY_DIR_RE.test(name) ? {
16448
17818
  auditPath: (0, import_node_path28.join)(auditDir, name, AUDIT_META_FILE),
16449
17819
  statsPath: (0, import_node_path28.join)(auditDir, name, auditStatsFileName(AUDIT_META_FILE))
@@ -16451,14 +17821,14 @@ async function readAuditStats(auditDir, query2 = {}) {
16451
17821
  auditPath: (0, import_node_path28.join)(auditDir, name),
16452
17822
  statsPath: (0, import_node_path28.join)(auditDir, auditStatsFileName(name))
16453
17823
  }
16454
- ).filter((source) => (0, import_node_fs28.existsSync)(source.auditPath));
17824
+ ).filter((source) => (0, import_node_fs29.existsSync)(source.auditPath));
16455
17825
  } catch {
16456
17826
  return { requestCount: 0, errorCount: 0, complete: false };
16457
17827
  }
16458
17828
  const total = { requestCount: 0, errorCount: 0, complete: true };
16459
17829
  for (const { auditPath, statsPath } of sources) {
16460
17830
  try {
16461
- const auditBytes = (0, import_node_fs28.statSync)(auditPath).size;
17831
+ const auditBytes = (0, import_node_fs29.statSync)(auditPath).size;
16462
17832
  const persisted = readPersisted(statsPath);
16463
17833
  if (persisted && persisted.complete && persisted.auditBytes === auditBytes && queryCovers(persisted, from, to)) {
16464
17834
  total.requestCount += persisted.requestCount;
@@ -16477,7 +17847,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16477
17847
  total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
16478
17848
  total.complete = total.complete && scanned.filtered.complete;
16479
17849
  const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
16480
- if (current.complete) (0, import_node_fs28.writeFileSync)(statsPath, JSON.stringify(current), "utf8");
17850
+ if (current.complete) (0, import_node_fs29.writeFileSync)(statsPath, JSON.stringify(current), "utf8");
16481
17851
  } catch {
16482
17852
  total.complete = false;
16483
17853
  }
@@ -16486,7 +17856,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16486
17856
  }
16487
17857
 
16488
17858
  // src/audit/AuditPruneSweeper.ts
16489
- var DAY_MS = 24 * 60 * 6e4;
17859
+ var DAY_MS3 = 24 * 60 * 6e4;
16490
17860
  var SWEEP_INTERVAL_MS2 = 60 * 6e4;
16491
17861
  var ARCHIVE_BATCH = 64;
16492
17862
  var AuditPruneSweeper = class {
@@ -16549,19 +17919,19 @@ var AuditPruneSweeper = class {
16549
17919
  if (!this.config.enabled || this.sweeping) return 0;
16550
17920
  this.sweeping = true;
16551
17921
  try {
16552
- if (!(0, import_node_fs29.existsSync)(this.auditDir)) return 0;
16553
- const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS;
17922
+ if (!(0, import_node_fs30.existsSync)(this.auditDir)) return 0;
17923
+ const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS3;
16554
17924
  let removed = 0;
16555
- for (const name of (0, import_node_fs29.readdirSync)(this.auditDir)) {
17925
+ for (const name of (0, import_node_fs30.readdirSync)(this.auditDir)) {
16556
17926
  const dateMs = auditFileDateMs(name);
16557
17927
  if (dateMs === null || dateMs >= cutoff) continue;
16558
17928
  try {
16559
17929
  if (isAuditDayDir(name)) {
16560
- (0, import_node_fs29.rmSync)((0, import_node_path29.join)(this.auditDir, name), { recursive: true, force: true });
17930
+ (0, import_node_fs30.rmSync)((0, import_node_path29.join)(this.auditDir, name), { recursive: true, force: true });
16561
17931
  } else {
16562
- (0, import_node_fs29.unlinkSync)((0, import_node_path29.join)(this.auditDir, name));
17932
+ (0, import_node_fs30.unlinkSync)((0, import_node_path29.join)(this.auditDir, name));
16563
17933
  const statsPath = (0, import_node_path29.join)(this.auditDir, auditStatsFileName(name));
16564
- if ((0, import_node_fs29.existsSync)(statsPath)) (0, import_node_fs29.unlinkSync)(statsPath);
17934
+ if ((0, import_node_fs30.existsSync)(statsPath)) (0, import_node_fs30.unlinkSync)(statsPath);
16565
17935
  }
16566
17936
  removed += 1;
16567
17937
  } catch (error) {
@@ -16591,10 +17961,10 @@ var AuditPruneSweeper = class {
16591
17961
  if (!this.config.enabled || this.archiving) return 0;
16592
17962
  this.archiving = true;
16593
17963
  try {
16594
- if (!(0, import_node_fs29.existsSync)(this.auditDir)) return 0;
17964
+ if (!(0, import_node_fs30.existsSync)(this.auditDir)) return 0;
16595
17965
  const today = this.todayMidnight();
16596
17966
  let compressed = 0;
16597
- for (const name of (0, import_node_fs29.readdirSync)(this.auditDir)) {
17967
+ for (const name of (0, import_node_fs30.readdirSync)(this.auditDir)) {
16598
17968
  if (compressed >= ARCHIVE_BATCH) break;
16599
17969
  const dateMs = auditFileDateMs(name);
16600
17970
  if (dateMs === null || dateMs >= today || !isAuditDayDir(name)) continue;
@@ -16635,7 +18005,7 @@ var AuditPruneSweeper = class {
16635
18005
  async archiveDay(bodiesPath, budget) {
16636
18006
  let shards;
16637
18007
  try {
16638
- shards = (0, import_node_fs29.readdirSync)(bodiesPath).filter((file) => file.endsWith(".jsonl"));
18008
+ shards = (0, import_node_fs30.readdirSync)(bodiesPath).filter((file) => file.endsWith(".jsonl"));
16639
18009
  } catch {
16640
18010
  return 0;
16641
18011
  }
@@ -16645,16 +18015,16 @@ var AuditPruneSweeper = class {
16645
18015
  const source = (0, import_node_path29.join)(bodiesPath, shard);
16646
18016
  const target = `${source}.gz`;
16647
18017
  try {
16648
- if ((0, import_node_fs29.existsSync)(target)) {
16649
- (0, import_node_fs29.unlinkSync)(source);
18018
+ if ((0, import_node_fs30.existsSync)(target)) {
18019
+ (0, import_node_fs30.unlinkSync)(source);
16650
18020
  continue;
16651
18021
  }
16652
- await (0, import_promises6.pipeline)((0, import_node_fs29.createReadStream)(source), (0, import_node_zlib2.createGzip)(), (0, import_node_fs29.createWriteStream)(target));
16653
- (0, import_node_fs29.unlinkSync)(source);
18022
+ await (0, import_promises6.pipeline)((0, import_node_fs30.createReadStream)(source), (0, import_node_zlib2.createGzip)(), (0, import_node_fs30.createWriteStream)(target));
18023
+ (0, import_node_fs30.unlinkSync)(source);
16654
18024
  compressed += 1;
16655
18025
  } catch (error) {
16656
18026
  try {
16657
- if ((0, import_node_fs29.existsSync)(target)) (0, import_node_fs29.unlinkSync)(target);
18027
+ if ((0, import_node_fs30.existsSync)(target)) (0, import_node_fs30.unlinkSync)(target);
16658
18028
  } catch {
16659
18029
  }
16660
18030
  this.logger.warn("[AuditPruneSweeper] failed to archive audit body shard", {
@@ -16668,7 +18038,7 @@ var AuditPruneSweeper = class {
16668
18038
  };
16669
18039
 
16670
18040
  // src/usage/usageMigrate.ts
16671
- var import_node_fs30 = require("fs");
18041
+ var import_node_fs31 = require("fs");
16672
18042
  var import_promises7 = require("fs/promises");
16673
18043
  var import_node_path30 = require("path");
16674
18044
  var import_node_readline = require("readline");
@@ -16715,7 +18085,7 @@ async function migrateLegacyUsageEvents(opts) {
16715
18085
  let skipped = 0;
16716
18086
  try {
16717
18087
  const reader = (0, import_node_readline.createInterface)({
16718
- input: (0, import_node_fs30.createReadStream)(eventsPath, { encoding: "utf8" }),
18088
+ input: (0, import_node_fs31.createReadStream)(eventsPath, { encoding: "utf8" }),
16719
18089
  crlfDelay: Number.POSITIVE_INFINITY
16720
18090
  });
16721
18091
  for await (const line of reader) {
@@ -16807,7 +18177,7 @@ async function closeAll(writers) {
16807
18177
  // src/usage/UsagePruneSweeper.ts
16808
18178
  var import_promises8 = require("fs/promises");
16809
18179
  var import_node_path31 = require("path");
16810
- var DAY_MS2 = 24 * 60 * 6e4;
18180
+ var DAY_MS4 = 24 * 60 * 6e4;
16811
18181
  var SWEEP_INTERVAL_MS3 = 60 * 6e4;
16812
18182
  var DEFAULT_USAGE_RETENTION_DAYS = 90;
16813
18183
  var UsagePruneSweeper = class {
@@ -16864,7 +18234,7 @@ var UsagePruneSweeper = class {
16864
18234
  this.sweeping = true;
16865
18235
  try {
16866
18236
  const retentionDays = this.config.retentionDays ?? DEFAULT_USAGE_RETENTION_DAYS;
16867
- const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS2;
18237
+ const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS4;
16868
18238
  let removed = 0;
16869
18239
  for (const entry of await listUsageDays(this.usageDir)) {
16870
18240
  if (!entry.hasShard) continue;
@@ -16922,7 +18292,7 @@ var UsagePruneSweeper = class {
16922
18292
  };
16923
18293
 
16924
18294
  // src/audit/auditReader.ts
16925
- var import_node_fs31 = require("fs");
18295
+ var import_node_fs32 = require("fs");
16926
18296
  var import_node_path32 = require("path");
16927
18297
  var DEFAULT_LIMIT = 200;
16928
18298
  var MAX_LIMIT = 2e3;
@@ -16930,7 +18300,7 @@ var OVERSCAN = 256;
16930
18300
  function daySources(auditDir) {
16931
18301
  let names;
16932
18302
  try {
16933
- names = (0, import_node_fs31.readdirSync)(auditDir);
18303
+ names = (0, import_node_fs32.readdirSync)(auditDir);
16934
18304
  } catch {
16935
18305
  return [];
16936
18306
  }
@@ -16940,7 +18310,7 @@ function daySources(auditDir) {
16940
18310
  if (dateMs === null) continue;
16941
18311
  if (AUDIT_DAY_DIR_RE.test(name)) {
16942
18312
  const path2 = (0, import_node_path32.join)(auditDir, name, AUDIT_META_FILE);
16943
- if ((0, import_node_fs31.existsSync)(path2)) sources.push({ path: path2, dateMs });
18313
+ if ((0, import_node_fs32.existsSync)(path2)) sources.push({ path: path2, dateMs });
16944
18314
  } else if (AUDIT_FILE_RE.test(name)) {
16945
18315
  sources.push({ path: (0, import_node_path32.join)(auditDir, name), dateMs });
16946
18316
  }
@@ -16958,7 +18328,7 @@ function toMetaRecord(record) {
16958
18328
  return { ...meta, hasBody: true };
16959
18329
  }
16960
18330
  function readAuditRecords(auditDir, query2 = {}) {
16961
- if (!(0, import_node_fs31.existsSync)(auditDir)) return [];
18331
+ if (!(0, import_node_fs32.existsSync)(auditDir)) return [];
16962
18332
  const from = typeof query2.from === "number" ? query2.from : -Infinity;
16963
18333
  const to = typeof query2.to === "number" ? query2.to : Infinity;
16964
18334
  const limit = Math.min(MAX_LIMIT, Math.max(1, Math.trunc(query2.limit ?? DEFAULT_LIMIT)));
@@ -16986,7 +18356,7 @@ function readAuditRecords(auditDir, query2 = {}) {
16986
18356
  }
16987
18357
 
16988
18358
  // src/audit/AuditWriter.ts
16989
- var import_node_fs32 = require("fs");
18359
+ var import_node_fs33 = require("fs");
16990
18360
  var import_node_path33 = require("path");
16991
18361
  var AuditWriter = class {
16992
18362
  constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
@@ -17034,7 +18404,7 @@ var AuditWriter = class {
17034
18404
  /** Create a directory once per process and remember it. */
17035
18405
  ensureDir(path2) {
17036
18406
  if (!this.ensuredDirs.has(path2)) {
17037
- (0, import_node_fs32.mkdirSync)(path2, { recursive: true });
18407
+ (0, import_node_fs33.mkdirSync)(path2, { recursive: true });
17038
18408
  this.ensuredDirs.add(path2);
17039
18409
  }
17040
18410
  return path2;
@@ -17044,8 +18414,8 @@ var AuditWriter = class {
17044
18414
  const { requestBody: _req, responseBody: _res, ...meta } = record;
17045
18415
  const file = (0, import_node_path33.join)(dayPath, AUDIT_META_FILE);
17046
18416
  const line = JSON.stringify(meta) + "\n";
17047
- const bytesBefore = (0, import_node_fs32.existsSync)(file) ? (0, import_node_fs32.statSync)(file).size : 0;
17048
- (0, import_node_fs32.appendFileSync)(file, line, "utf8");
18417
+ const bytesBefore = (0, import_node_fs33.existsSync)(file) ? (0, import_node_fs33.statSync)(file).size : 0;
18418
+ (0, import_node_fs33.appendFileSync)(file, line, "utf8");
17049
18419
  try {
17050
18420
  updateAuditStatsAfterAppend(
17051
18421
  file,
@@ -17077,7 +18447,7 @@ var AuditWriter = class {
17077
18447
  const line = encodeBodyEntry(record, sessionKey, dayDir, this.bases);
17078
18448
  if (line === null) return;
17079
18449
  const bodiesPath = this.ensureDir((0, import_node_path33.join)(dayPath, AUDIT_BODIES_DIR));
17080
- (0, import_node_fs32.appendFileSync)((0, import_node_path33.join)(bodiesPath, auditBodyFileName(sessionKey)), line + "\n", "utf8");
18450
+ (0, import_node_fs33.appendFileSync)((0, import_node_path33.join)(bodiesPath, auditBodyFileName(sessionKey)), line + "\n", "utf8");
17081
18451
  } catch (error) {
17082
18452
  this.bases.forget(sessionKey);
17083
18453
  this.logger.warn("[AuditWriter] failed to append audit body shard", {
@@ -17089,10 +18459,10 @@ var AuditWriter = class {
17089
18459
  };
17090
18460
 
17091
18461
  // src/billing/BillingPublisher.ts
17092
- var import_node_fs33 = require("fs");
18462
+ var import_node_fs34 = require("fs");
17093
18463
  var import_node_crypto24 = require("crypto");
17094
18464
  var import_node_path34 = require("path");
17095
- var import_upstreamFetch6 = require("@omnicross/core/pipeline/upstreamFetch");
18465
+ var import_upstreamFetch11 = require("@omnicross/core/pipeline/upstreamFetch");
17096
18466
 
17097
18467
  // src/billing/billingFiles.ts
17098
18468
  var BILLING_FILE_RE = /^billing-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
@@ -17115,7 +18485,7 @@ var BillingPublisher = class {
17115
18485
  constructor(billingDir, logger, opts = {}) {
17116
18486
  this.billingDir = billingDir;
17117
18487
  this.logger = logger;
17118
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch6.fetchUpstream)(url, init));
18488
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch11.fetchUpstream)(url, init));
17119
18489
  this.defer = opts.defer ?? ((fn) => setTimeout(fn, 0));
17120
18490
  this.timeoutMs = opts.timeoutMs ?? BILLING_POST_TIMEOUT_MS;
17121
18491
  this.now = opts.now ?? Date.now;
@@ -17163,7 +18533,7 @@ var BillingPublisher = class {
17163
18533
  appendNow(event) {
17164
18534
  this.ensureDir();
17165
18535
  const file = (0, import_node_path34.join)(this.billingDir, billingFileName(event.ts));
17166
- (0, import_node_fs33.appendFileSync)(file, JSON.stringify(event) + "\n", "utf8");
18536
+ (0, import_node_fs34.appendFileSync)(file, JSON.stringify(event) + "\n", "utf8");
17167
18537
  }
17168
18538
  /**
17169
18539
  * One best-effort delivery attempt for an event ALREADY in the ledger. POSTs the
@@ -17213,7 +18583,7 @@ var BillingPublisher = class {
17213
18583
  try {
17214
18584
  this.ensureDir();
17215
18585
  const file = (0, import_node_path34.join)(this.billingDir, deliveredFileName(event.ts));
17216
- (0, import_node_fs33.appendFileSync)(file, JSON.stringify({ id: event.id, deliveredAt: this.now() }) + "\n", "utf8");
18586
+ (0, import_node_fs34.appendFileSync)(file, JSON.stringify({ id: event.id, deliveredAt: this.now() }) + "\n", "utf8");
17217
18587
  } catch (error) {
17218
18588
  this.logger.warn("[BillingPublisher] failed to append delivery marker", {
17219
18589
  error: error instanceof Error ? error.message : String(error)
@@ -17222,20 +18592,20 @@ var BillingPublisher = class {
17222
18592
  }
17223
18593
  ensureDir() {
17224
18594
  if (this.dirEnsured) return;
17225
- (0, import_node_fs33.mkdirSync)(this.billingDir, { recursive: true });
18595
+ (0, import_node_fs34.mkdirSync)(this.billingDir, { recursive: true });
17226
18596
  this.dirEnsured = true;
17227
18597
  }
17228
18598
  };
17229
18599
 
17230
18600
  // src/billing/billingReader.ts
17231
- var import_node_fs34 = require("fs");
18601
+ var import_node_fs35 = require("fs");
17232
18602
  var import_node_path35 = require("path");
17233
18603
  function readBillingLedger(billingDir) {
17234
18604
  const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
17235
- if (!(0, import_node_fs34.existsSync)(billingDir)) return view;
18605
+ if (!(0, import_node_fs35.existsSync)(billingDir)) return view;
17236
18606
  let files;
17237
18607
  try {
17238
- files = (0, import_node_fs34.readdirSync)(billingDir);
18608
+ files = (0, import_node_fs35.readdirSync)(billingDir);
17239
18609
  } catch {
17240
18610
  return view;
17241
18611
  }
@@ -17266,7 +18636,7 @@ function readBillingStatus(billingDir) {
17266
18636
  function parseLines(dir, file) {
17267
18637
  let raw;
17268
18638
  try {
17269
- raw = (0, import_node_fs34.readFileSync)((0, import_node_path35.join)(dir, file), "utf8");
18639
+ raw = (0, import_node_fs35.readFileSync)((0, import_node_path35.join)(dir, file), "utf8");
17270
18640
  } catch {
17271
18641
  return [];
17272
18642
  }
@@ -17365,7 +18735,7 @@ var BillingRetrySweeper = class {
17365
18735
  // src/TokenRefreshScheduler.ts
17366
18736
  var REFRESH_LEAD_MS2 = 5 * 6e4;
17367
18737
  var SWEEP_INTERVAL_MS5 = 6e4;
17368
- var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini"];
18738
+ var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini", "kimi"];
17369
18739
  var TokenRefreshScheduler = class {
17370
18740
  constructor(store, logger, intervalMs = SWEEP_INTERVAL_MS5, leadMs = REFRESH_LEAD_MS2) {
17371
18741
  this.store = store;
@@ -17448,6 +18818,8 @@ var TokenRefreshScheduler = class {
17448
18818
  return this.store.refreshCodexToken();
17449
18819
  case "gemini":
17450
18820
  return this.store.refreshGeminiToken();
18821
+ case "kimi":
18822
+ return this.store.refreshKimiToken();
17451
18823
  }
17452
18824
  }
17453
18825
  };
@@ -17524,7 +18896,7 @@ function createRouteLeaseSubscriptionPreflight(credentials) {
17524
18896
 
17525
18897
  // src/webhook/WebhookDispatcher.ts
17526
18898
  var import_node_crypto25 = require("crypto");
17527
- var import_upstreamFetch7 = require("@omnicross/core/pipeline/upstreamFetch");
18899
+ var import_upstreamFetch12 = require("@omnicross/core/pipeline/upstreamFetch");
17528
18900
  var WEBHOOK_MAX_ATTEMPTS = 3;
17529
18901
  var WEBHOOK_QUEUE_MAX = 1e3;
17530
18902
  var WEBHOOK_SEND_TIMEOUT_MS = 1e4;
@@ -17544,7 +18916,7 @@ var WebhookDispatcher = class {
17544
18916
  sleep;
17545
18917
  now;
17546
18918
  constructor(opts = {}) {
17547
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch7.fetchUpstream)(url, init));
18919
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch12.fetchUpstream)(url, init));
17548
18920
  this.logger = opts.logger;
17549
18921
  this.maxAttempts = opts.maxAttempts ?? WEBHOOK_MAX_ATTEMPTS;
17550
18922
  this.queueMax = opts.queueMax ?? WEBHOOK_QUEUE_MAX;
@@ -17630,8 +19002,8 @@ var WebhookDispatcher = class {
17630
19002
  signal: AbortSignal.timeout(this.timeoutMs)
17631
19003
  });
17632
19004
  return res.ok ? { ok: true, status: res.status } : { ok: false, status: res.status };
17633
- } catch (err5) {
17634
- return { ok: false, error: err5 instanceof Error ? err5.message : String(err5) };
19005
+ } catch (err6) {
19006
+ return { ok: false, error: err6 instanceof Error ? err6.message : String(err6) };
17635
19007
  }
17636
19008
  }
17637
19009
  /**
@@ -17750,7 +19122,7 @@ function installImageRuntimeBootstrapSession(initialGeneration) {
17750
19122
  function resolveLoggingConfig(configured, configPath) {
17751
19123
  const file = configured?.file ?? defaultDaemonLogPath(configPath);
17752
19124
  try {
17753
- (0, import_node_fs35.mkdirSync)(configured?.file ? (0, import_node_path36.dirname)(configured.file) : defaultLogDir(configPath), {
19125
+ (0, import_node_fs36.mkdirSync)(configured?.file ? (0, import_node_path36.dirname)(configured.file) : defaultLogDir(configPath), {
17754
19126
  recursive: true
17755
19127
  });
17756
19128
  } catch {
@@ -17768,12 +19140,12 @@ function buildDaemon(config, paths) {
17768
19140
  setSecretBox(secretBox3);
17769
19141
  setSecretBox2(secretBox3);
17770
19142
  const decryptedConfig = decryptConfigSecrets(config, secretBox3);
17771
- const accountAllowanceStore = new import_AccountAllowanceStore4.AccountAllowanceStore(
19143
+ const accountAllowanceStore = new import_AccountAllowanceStore7.AccountAllowanceStore(
17772
19144
  Date.now,
17773
19145
  void 0,
17774
19146
  new JsonAccountAllowancePersistence(defaultAccountAllowancePath(paths.configPath))
17775
19147
  );
17776
- (0, import_AccountAllowanceStore4.setSharedAccountAllowanceStore)(accountAllowanceStore);
19148
+ (0, import_AccountAllowanceStore7.setSharedAccountAllowanceStore)(accountAllowanceStore);
17777
19149
  (0, import_AccountAllowanceScheduling5.getSharedAccountAllowanceScheduling)().configure(
17778
19150
  (0, import_outbound_api10.normalizeServerConfig)(decryptedConfig.server).allowanceScheduling
17779
19151
  );
@@ -17798,21 +19170,22 @@ function buildDaemon(config, paths) {
17798
19170
  claudeAllowanceRefreshScheduler.configure(
17799
19171
  (0, import_outbound_api10.normalizeServerConfig)(decryptedConfig.server).allowanceScheduling
17800
19172
  );
17801
- const subscriptionAccounts = new import_subscriptions6.SubscriptionAccountService(credentialStore);
17802
- (0, import_subscriptions6.setSubscriptionAccountService)(subscriptionAccounts);
17803
- const subscriptionRegistry = new import_subscriptions6.SubscriptionProviderRegistry(
19173
+ const subscriptionAccounts = new import_subscriptions9.SubscriptionAccountService(credentialStore);
19174
+ (0, import_subscriptions9.setSubscriptionAccountService)(subscriptionAccounts);
19175
+ const subscriptionRegistry = new import_subscriptions9.SubscriptionProviderRegistry(
17804
19176
  subscriptionAccounts,
17805
19177
  credentialStore
17806
19178
  );
17807
- (0, import_subscriptions6.setSubscriptionProviderRegistry)(subscriptionRegistry);
19179
+ (0, import_subscriptions9.setSubscriptionProviderRegistry)(subscriptionRegistry);
17808
19180
  setServerProxyConfig(decryptedConfig.server?.proxy);
17809
- (0, import_upstreamFetch8.setUpstreamProxyResolver)(
19181
+ (0, import_upstreamFetch13.setUpstreamProxyResolver)(
17810
19182
  createUpstreamProxyResolver({
17811
19183
  getAccountProxy: (providerId, accountId) => credentialStore.getAccountProxy(providerId, accountId)
17812
19184
  })
17813
19185
  );
17814
19186
  (0, import_gemini_code_assist_resolver.setGeminiCodeAssistResolver)((0, import_GeminiCodeAssistProjectResolver.getGeminiCodeAssistProjectResolver)());
17815
19187
  const autoDisableStore = new AutoDisableStore();
19188
+ const providerKeyQuotaService = new ProviderKeyQuotaService(secretBox3);
17816
19189
  const apiKeyPool = new import_ApiKeyPoolService.ApiKeyPoolService(
17817
19190
  createPoolKeysLoader((id) => llmConfig.getProviderRow(id), autoDisableStore),
17818
19191
  resolveEnvKey,
@@ -17829,7 +19202,7 @@ function buildDaemon(config, paths) {
17829
19202
  const pricingEngine = new import_usage2.PricingEngine(pricingStore, logger, {
17830
19203
  // Catalog egress follows the same global/env proxy policy as every other
17831
19204
  // daemon upstream call; no provider/account override applies here.
17832
- fetchImpl: ((input, init) => (0, import_upstreamFetch8.fetchUpstream)(String(input), init ?? {}))
19205
+ fetchImpl: ((input, init) => (0, import_upstreamFetch13.fetchUpstream)(String(input), init ?? {}))
17833
19206
  });
17834
19207
  const pricingRefreshScheduler = new PricingRefreshScheduler(
17835
19208
  pricingEngine,
@@ -18093,6 +19466,11 @@ function buildDaemon(config, paths) {
18093
19466
  // values themselves NEVER leave (masked via `maskProviderApiKey`).
18094
19467
  apiKeyPool,
18095
19468
  autoDisableStore,
19469
+ // BYO provider-key quota (Z.AI coding plan, MiniMax Token Plan, …): a
19470
+ // read-through cached same-key usage probe surfaced on the keys view. The
19471
+ // key plaintext is resolved + decrypted inside the service and never
19472
+ // crosses back out.
19473
+ providerKeyQuota: providerKeyQuotaService,
18096
19474
  // Interactive OAuth login over admin HTTP (app-parity child 4, design
18097
19475
  // D1/D2-a). The in-memory pending-session store (NEVER serialized), the
18098
19476
  // injected token-exchange fetch (global `fetch` here; mocked in tests), and a
@@ -18109,7 +19487,7 @@ function buildDaemon(config, paths) {
18109
19487
  // — `server.proxy.byProvider[...]` was silently skipped — and the call was
18110
19488
  // excluded from the upstream trace, so a failing login left no evidence.
18111
19489
  // `redactBodies` keeps the code/verifier + minted token out of that trace.
18112
- oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => (0, import_upstreamFetch8.fetchUpstream)(url, init, { providerId, redactBodies: true }),
19490
+ oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => (0, import_upstreamFetch13.fetchUpstream)(url, init, { providerId, redactBodies: true }),
18113
19491
  subscriptionAccountAppender: credentialStore,
18114
19492
  // Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
18115
19493
  // + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
@@ -18117,6 +19495,10 @@ function buildDaemon(config, paths) {
18117
19495
  // can inject a mock so no real port is bound.
18118
19496
  codexSessions: new CodexOAuthSessionStore(),
18119
19497
  codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
19498
+ // Kimi interactive OAuth — the async DEVICE-CODE flow store (no port, no
19499
+ // paste; the app shows the verification URL + user code and polls the
19500
+ // token-free status). Token captured + persisted daemon-side.
19501
+ kimiSessions: new CodexOAuthSessionStore(),
18120
19502
  // Migration pack (app-parity child 6, design D2/D3) — the concrete credential
18121
19503
  // store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
18122
19504
  // the multi-account append (`appendProviderAccount`, import re-encrypts at-
@@ -18175,7 +19557,7 @@ function buildDaemon(config, paths) {
18175
19557
  });
18176
19558
  const webhookDispatcher = new WebhookDispatcher({
18177
19559
  logger,
18178
- fetchImpl: (url, init) => (0, import_upstreamFetch8.fetchUpstream)(url, init)
19560
+ fetchImpl: (url, init) => (0, import_upstreamFetch13.fetchUpstream)(url, init)
18179
19561
  });
18180
19562
  setWebhookRuntime(webhookDispatcher, (0, import_SubscriptionAccountHealth4.getSharedAccountHealth)());
18181
19563
  const auditWriter = new AuditWriter(auditDir, logger);
@@ -18252,8 +19634,8 @@ function buildDaemon(config, paths) {
18252
19634
  }
18253
19635
  function isTokensStoreReadable(tokensPath) {
18254
19636
  try {
18255
- if (!(0, import_node_fs35.existsSync)(tokensPath)) return true;
18256
- (0, import_node_fs35.accessSync)(tokensPath, import_node_fs35.constants.R_OK);
19637
+ if (!(0, import_node_fs36.existsSync)(tokensPath)) return true;
19638
+ (0, import_node_fs36.accessSync)(tokensPath, import_node_fs36.constants.R_OK);
18257
19639
  return true;
18258
19640
  } catch {
18259
19641
  return false;
@@ -18448,9 +19830,9 @@ async function runSearchDoctor(live, env = process.env, source = {}) {
18448
19830
  ];
18449
19831
  const declarations = source.runtime?.listProviders() ?? contributions;
18450
19832
  console.info("omnicross doctor search \u2014 builtin search contributions (offline, no network)");
18451
- const modes = source.config?.modes ?? import_search2.DEFAULT_SEARCH_FRONTEND_MODES;
19833
+ const modes = source.config?.modes ?? import_search3.DEFAULT_SEARCH_FRONTEND_MODES;
18452
19834
  console.info(
18453
- ` [i] frontend modes: ${import_search2.SEARCH_FRONTEND_NAMES.map((name) => `${name}=${modes[name]}`).join(", ")}`
19835
+ ` [i] frontend modes: ${import_search3.SEARCH_FRONTEND_NAMES.map((name) => `${name}=${modes[name]}`).join(", ")}`
18454
19836
  );
18455
19837
  console.info(
18456
19838
  " [i] codex mode applies immediately; responses/anthropic modes, provider config, egress allowlist and policy apply on daemon restart"
@@ -18489,11 +19871,11 @@ async function runLiveProbe(url, key, fetchImpl = fetch) {
18489
19871
  status: res.status,
18490
19872
  estimateHeader: res.headers.get("x-omnicross-count-estimate")
18491
19873
  };
18492
- } catch (err5) {
19874
+ } catch (err6) {
18493
19875
  return {
18494
19876
  status: null,
18495
19877
  estimateHeader: null,
18496
- error: err5 instanceof Error ? err5.message : String(err5)
19878
+ error: err6 instanceof Error ? err6.message : String(err6)
18497
19879
  };
18498
19880
  }
18499
19881
  }
@@ -18581,7 +19963,7 @@ async function runDoctor(argv, fetchImpl = fetch) {
18581
19963
  }
18582
19964
 
18583
19965
  // src/commands/import-ccr.ts
18584
- var import_node_fs36 = require("fs");
19966
+ var import_node_fs37 = require("fs");
18585
19967
  var import_node_util3 = require("util");
18586
19968
 
18587
19969
  // src/ccr-import.ts
@@ -18676,7 +20058,7 @@ async function runImportCcr(argv) {
18676
20058
  const outPath = values.out ?? "omnicross.config.json";
18677
20059
  let raw;
18678
20060
  try {
18679
- raw = JSON.parse((0, import_node_fs36.readFileSync)(ccrPath, "utf8"));
20061
+ raw = JSON.parse((0, import_node_fs37.readFileSync)(ccrPath, "utf8"));
18680
20062
  } catch {
18681
20063
  throw new Error(`import-ccr: cannot read or parse '${ccrPath}'`);
18682
20064
  }
@@ -18814,7 +20196,7 @@ async function keysRevoke(db, id) {
18814
20196
  // src/commands/launch.ts
18815
20197
  var import_node_child_process2 = require("child_process");
18816
20198
  var import_node_crypto26 = require("crypto");
18817
- var import_node_fs37 = require("fs");
20199
+ var import_node_fs38 = require("fs");
18818
20200
  var import_node_path38 = require("path");
18819
20201
  var import_node_util6 = require("util");
18820
20202
  var import_cli_launcher3 = require("@omnicross/cli-launcher");
@@ -18859,7 +20241,7 @@ function resolveInPathDefault(candidate) {
18859
20241
  const segments = (process.env["PATH"] ?? "").split(import_node_path38.delimiter).filter(Boolean);
18860
20242
  for (const seg of segments) {
18861
20243
  const full = (0, import_node_path38.join)(seg, candidate);
18862
- if ((0, import_node_fs37.existsSync)(full)) return full;
20244
+ if ((0, import_node_fs38.existsSync)(full)) return full;
18863
20245
  }
18864
20246
  return null;
18865
20247
  }
@@ -18900,9 +20282,9 @@ async function runLaunch(argv, deps) {
18900
20282
  await daemon.llmConfig.ready();
18901
20283
  await daemon.migrateUsageStore();
18902
20284
  await daemon.providerProxy.start();
18903
- } catch (err5) {
20285
+ } catch (err6) {
18904
20286
  await shutdownLaunchDaemon(daemon);
18905
- throw err5;
20287
+ throw err6;
18906
20288
  }
18907
20289
  let launch;
18908
20290
  try {
@@ -18910,9 +20292,9 @@ async function runLaunch(argv, deps) {
18910
20292
  providerId: values.provider,
18911
20293
  model: values.model
18912
20294
  });
18913
- } catch (err5) {
20295
+ } catch (err6) {
18914
20296
  await shutdownLaunchDaemon(daemon);
18915
- throw err5;
20297
+ throw err6;
18916
20298
  }
18917
20299
  try {
18918
20300
  const plan = buildCliSpawnPlan({
@@ -19017,9 +20399,9 @@ function spawnCliInherit(plan) {
19017
20399
  process.removeListener("SIGINT", onSignal);
19018
20400
  process.removeListener("SIGTERM", onSignal);
19019
20401
  };
19020
- child.on("error", (err5) => {
20402
+ child.on("error", (err6) => {
19021
20403
  detach();
19022
- if (err5.code === "ENOENT") {
20404
+ if (err6.code === "ENOENT") {
19023
20405
  reject(
19024
20406
  new Error(
19025
20407
  `launch: "${plan.command}" not found on PATH \u2014 install the CLI first.`
@@ -19027,7 +20409,7 @@ function spawnCliInherit(plan) {
19027
20409
  );
19028
20410
  return;
19029
20411
  }
19030
- reject(err5);
20412
+ reject(err6);
19031
20413
  });
19032
20414
  child.on("exit", (code, signal) => {
19033
20415
  detach();
@@ -19040,9 +20422,9 @@ function spawnCliInherit(plan) {
19040
20422
  var import_node_child_process3 = require("child_process");
19041
20423
  var import_node_readline2 = require("readline");
19042
20424
  var import_node_util7 = require("util");
19043
- var import_upstreamFetch9 = require("@omnicross/core/pipeline/upstreamFetch");
19044
- var import_subscriptions7 = require("@omnicross/subscriptions");
19045
- var PROVIDERS2 = ["claude", "codex", "gemini"];
20425
+ var import_upstreamFetch14 = require("@omnicross/core/pipeline/upstreamFetch");
20426
+ var import_subscriptions10 = require("@omnicross/subscriptions");
20427
+ var PROVIDERS2 = ["claude", "codex", "gemini", "kimi"];
19046
20428
  async function runLogin(argv, deps) {
19047
20429
  const { values, positionals } = (0, import_node_util7.parseArgs)({
19048
20430
  args: argv,
@@ -19068,14 +20450,16 @@ async function runLogin(argv, deps) {
19068
20450
  openBrowser: deps?.openBrowser ?? openBrowser,
19069
20451
  promptPaste: deps?.promptPaste ?? promptPaste,
19070
20452
  awaitLoopback: deps?.awaitLoopback ?? ((state) => awaitLoopbackCode(state)),
20453
+ awaitKimiDevice: deps?.awaitKimiDevice ?? ((fetchImpl) => runKimiDeviceFlow(fetchImpl, resolvedOpenBrowser)),
19071
20454
  tokensFetch: deps?.tokensFetch
19072
20455
  };
20456
+ const resolvedOpenBrowser = resolved.openBrowser;
19073
20457
  const box = resolveSecretBox(values["master-key-file"]);
19074
20458
  setSecretBox(box);
19075
- (0, import_upstreamFetch9.setUpstreamProxyResolver)(createUpstreamProxyResolver());
20459
+ (0, import_upstreamFetch14.setUpstreamProxyResolver)(createUpstreamProxyResolver());
19076
20460
  try {
19077
20461
  const tokensPath = defaultTokensPath(values.config);
19078
- const exchangeFetch = resolved.tokensFetch ?? ((url, init) => (0, import_upstreamFetch9.fetchUpstream)(url, init, { providerId: provider, redactBodies: true }));
20462
+ const exchangeFetch = resolved.tokensFetch ?? ((url, init) => (0, import_upstreamFetch14.fetchUpstream)(url, init, { providerId: provider, redactBodies: true }));
19079
20463
  const store = new JsonSubscriptionCredentialStore(tokensPath, box, exchangeFetch);
19080
20464
  const expiresAt = await runProviderLogin(
19081
20465
  provider,
@@ -19088,19 +20472,20 @@ async function runLogin(argv, deps) {
19088
20472
  console.info(` token: [stored, encrypted] expiresAt: ${expiresAt ?? "n/a"}`);
19089
20473
  } finally {
19090
20474
  setSecretBox(null);
19091
- (0, import_upstreamFetch9.setUpstreamProxyResolver)(null);
20475
+ (0, import_upstreamFetch14.setUpstreamProxyResolver)(null);
19092
20476
  }
19093
20477
  }
19094
20478
  async function runProviderLogin(provider, store, deps, exchangeFetch, label) {
19095
20479
  if (provider === "codex") return loginCodex(store, deps, exchangeFetch, label);
19096
20480
  if (provider === "claude") return loginClaude(store, deps, exchangeFetch, label);
20481
+ if (provider === "kimi") return loginKimi(store, deps, exchangeFetch, label);
19097
20482
  return loginGemini(store, deps, exchangeFetch, label);
19098
20483
  }
19099
20484
  async function loginCodex(store, deps, exchangeFetch, label) {
19100
- const { authUrl, codeVerifier, state } = import_subscriptions7.codexOAuth.generateAuthParams();
20485
+ const { authUrl, codeVerifier, state } = import_subscriptions10.codexOAuth.generateAuthParams();
19101
20486
  await presentUrl(authUrl, deps);
19102
20487
  const code = await deps.awaitLoopback(state);
19103
- const result = await import_subscriptions7.codexOAuth.exchangeCodeForTokens(
20488
+ const result = await import_subscriptions10.codexOAuth.exchangeCodeForTokens(
19104
20489
  { authorizationCode: code, codeVerifier, state },
19105
20490
  exchangeFetch
19106
20491
  );
@@ -19119,7 +20504,7 @@ async function loginCodex(store, deps, exchangeFetch, label) {
19119
20504
  return expiresAt;
19120
20505
  }
19121
20506
  async function loginClaude(store, deps, exchangeFetch, label) {
19122
- const { authUrl, codeVerifier, state } = import_subscriptions7.claudeOAuth.generateAuthParams();
20507
+ const { authUrl, codeVerifier, state } = import_subscriptions10.claudeOAuth.generateAuthParams();
19123
20508
  await presentUrl(authUrl, deps);
19124
20509
  const pasted = (await deps.promptPaste("Paste the authorization code (code#state): ")).trim();
19125
20510
  const [code, pastedState] = pasted.split("#");
@@ -19127,7 +20512,7 @@ async function loginClaude(store, deps, exchangeFetch, label) {
19127
20512
  if (pastedState && pastedState !== state) {
19128
20513
  throw new Error("login: pasted state did not match (possible CSRF) \u2014 aborting");
19129
20514
  }
19130
- const result = await import_subscriptions7.claudeOAuth.exchangeCodeForTokens(
20515
+ const result = await import_subscriptions10.claudeOAuth.exchangeCodeForTokens(
19131
20516
  { authorizationCode: code, codeVerifier, state },
19132
20517
  exchangeFetch
19133
20518
  );
@@ -19146,11 +20531,11 @@ async function loginClaude(store, deps, exchangeFetch, label) {
19146
20531
  return expiresAt;
19147
20532
  }
19148
20533
  async function loginGemini(store, deps, exchangeFetch, label) {
19149
- const { authUrl, codeVerifier } = import_subscriptions7.geminiOAuth.generateAuthParams();
20534
+ const { authUrl, codeVerifier } = import_subscriptions10.geminiOAuth.generateAuthParams();
19150
20535
  await presentUrl(authUrl, deps);
19151
20536
  const code = (await deps.promptPaste("Paste the authorization code: ")).trim();
19152
20537
  if (!code) throw new Error("login: no authorization code was pasted");
19153
- const result = await import_subscriptions7.geminiOAuth.exchangeCodeForTokens(code, codeVerifier, exchangeFetch);
20538
+ const result = await import_subscriptions10.geminiOAuth.exchangeCodeForTokens(code, codeVerifier, exchangeFetch);
19154
20539
  const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
19155
20540
  const block = {
19156
20541
  authMethod: "oauth",
@@ -19164,6 +20549,45 @@ async function loginGemini(store, deps, exchangeFetch, label) {
19164
20549
  logMasked("gemini", result.accessToken);
19165
20550
  return expiresAt;
19166
20551
  }
20552
+ async function runKimiDeviceFlow(exchangeFetch, openBrowserFn) {
20553
+ const deviceId = import_subscriptions10.kimiOAuth.generateKimiDeviceId();
20554
+ const fingerprint = import_subscriptions10.kimiOAuth.kimiFingerprintHeaders(deviceId);
20555
+ const authorization = await import_subscriptions10.kimiOAuth.requestDeviceAuthorization(exchangeFetch, fingerprint);
20556
+ const url = authorization.verificationUriComplete ?? authorization.verificationUri;
20557
+ console.info("Open this URL in your browser and approve the request:");
20558
+ console.info(` ${url}`);
20559
+ if (!authorization.verificationUriComplete) {
20560
+ console.info(` Then enter this code: ${authorization.userCode}`);
20561
+ }
20562
+ await openBrowserFn(url).catch(() => false);
20563
+ const result = await import_subscriptions10.kimiOAuth.awaitDeviceToken(authorization, exchangeFetch, {
20564
+ fingerprint,
20565
+ onPending: () => process.stdout.write(".")
20566
+ });
20567
+ console.info("");
20568
+ return {
20569
+ ...result,
20570
+ accountId: import_subscriptions10.kimiOAuth.kimiAccountIdFromAccessToken(result.accessToken),
20571
+ deviceId
20572
+ };
20573
+ }
20574
+ async function loginKimi(store, deps, exchangeFetch, label) {
20575
+ const result = await deps.awaitKimiDevice(exchangeFetch);
20576
+ const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
20577
+ const block = {
20578
+ authMethod: "oauth",
20579
+ status: "authorized",
20580
+ accessToken: result.accessToken,
20581
+ refreshToken: result.refreshToken,
20582
+ expiresAt,
20583
+ ...result.accountId ? { accountId: result.accountId } : {},
20584
+ deviceId: result.deviceId,
20585
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
20586
+ };
20587
+ await store.appendProviderAccount("kimi", block, label);
20588
+ logMasked("kimi", result.accessToken);
20589
+ return expiresAt;
20590
+ }
19167
20591
  function isLoginProvider(value) {
19168
20592
  return PROVIDERS2.includes(value);
19169
20593
  }
@@ -19361,7 +20785,7 @@ function providersRmKey(configPath, providerId, keyId) {
19361
20785
  }
19362
20786
 
19363
20787
  // src/commands/secrets.ts
19364
- var import_node_fs38 = require("fs");
20788
+ var import_node_fs39 = require("fs");
19365
20789
  var import_node_util9 = require("util");
19366
20790
  async function runSecrets(argv) {
19367
20791
  const { values, positionals } = (0, import_node_util9.parseArgs)({
@@ -19434,12 +20858,12 @@ function secretsStatus(args) {
19434
20858
  reportField("admin.token", cfg.admin.token);
19435
20859
  }
19436
20860
  const tokensPath = defaultTokensPath(args.config);
19437
- if ((0, import_node_fs38.existsSync)(tokensPath)) {
20861
+ if ((0, import_node_fs39.existsSync)(tokensPath)) {
19438
20862
  console.info(`Secret status for ${tokensPath}:`);
19439
20863
  reportTokenFields(tokensPath);
19440
20864
  }
19441
20865
  const integrationsPath = defaultIntegrationsPath(args.config);
19442
- if ((0, import_node_fs38.existsSync)(integrationsPath)) {
20866
+ if ((0, import_node_fs39.existsSync)(integrationsPath)) {
19443
20867
  const state = readRawJson(integrationsPath);
19444
20868
  const key = state.gatewayKey;
19445
20869
  if (key && typeof key === "object" && !Array.isArray(key)) {
@@ -19493,8 +20917,8 @@ async function secretsRotate(args) {
19493
20917
  const integrationsPath = defaultIntegrationsPath(args.config);
19494
20918
  try {
19495
20919
  cfg = loadConfig(args.config);
19496
- if ((0, import_node_fs38.existsSync)(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, oldBox);
19497
- if ((0, import_node_fs38.existsSync)(integrationsPath)) {
20920
+ if ((0, import_node_fs39.existsSync)(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, oldBox);
20921
+ if ((0, import_node_fs39.existsSync)(integrationsPath)) {
19498
20922
  integrationsPlain = new IntegrationStateStore(integrationsPath, oldBox).load();
19499
20923
  }
19500
20924
  } finally {
@@ -19529,20 +20953,20 @@ function secretsDecrypt(args) {
19529
20953
  let tokensPlain = null;
19530
20954
  try {
19531
20955
  cfg = loadConfig(args.config);
19532
- if ((0, import_node_fs38.existsSync)(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, box);
20956
+ if ((0, import_node_fs39.existsSync)(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, box);
19533
20957
  } finally {
19534
20958
  setSecretBox(null);
19535
20959
  }
19536
20960
  saveConfig(args.config, cfg);
19537
20961
  if (tokensPlain) {
19538
- (0, import_node_fs38.writeFileSync)(tokensPath, JSON.stringify(tokensPlain, null, 2) + "\n", "utf8");
20962
+ atomicReplaceUtf8(tokensPath, JSON.stringify(tokensPlain, null, 2) + "\n");
19539
20963
  }
19540
20964
  console.info(`Decrypted secrets to plaintext in ${args.config}` + tokensSuffix(args.config));
19541
20965
  }
19542
20966
  function readRawConfig(path2) {
19543
20967
  let parsed;
19544
20968
  try {
19545
- parsed = JSON.parse((0, import_node_fs38.readFileSync)(path2, "utf8"));
20969
+ parsed = JSON.parse((0, import_node_fs39.readFileSync)(path2, "utf8"));
19546
20970
  } catch {
19547
20971
  throw new Error(`secrets: cannot read or parse '${path2}'`);
19548
20972
  }
@@ -19550,7 +20974,7 @@ function readRawConfig(path2) {
19550
20974
  }
19551
20975
  function readRawJson(path2) {
19552
20976
  try {
19553
- const parsed = JSON.parse((0, import_node_fs38.readFileSync)(path2, "utf8"));
20977
+ const parsed = JSON.parse((0, import_node_fs39.readFileSync)(path2, "utf8"));
19554
20978
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
19555
20979
  return parsed;
19556
20980
  }
@@ -19560,13 +20984,13 @@ function readRawJson(path2) {
19560
20984
  }
19561
20985
  function encryptTokensFileInPlace(configPath, box) {
19562
20986
  const tokensPath = defaultTokensPath(configPath);
19563
- if (!(0, import_node_fs38.existsSync)(tokensPath)) return;
20987
+ if (!(0, import_node_fs39.existsSync)(tokensPath)) return;
19564
20988
  const plain = decryptTokensFile(tokensPath, box);
19565
20989
  writeTokensEncrypted(tokensPath, plain, box);
19566
20990
  }
19567
20991
  function rewriteIntegrationState(configPath, readBox, writeBox) {
19568
20992
  const path2 = defaultIntegrationsPath(configPath);
19569
- if (!(0, import_node_fs38.existsSync)(path2)) return;
20993
+ if (!(0, import_node_fs39.existsSync)(path2)) return;
19570
20994
  const state = new IntegrationStateStore(path2, readBox).load();
19571
20995
  new IntegrationStateStore(path2, writeBox).save(state);
19572
20996
  }
@@ -19579,7 +21003,7 @@ function writeTokensEncrypted(tokensPath, plain, box) {
19579
21003
  { updatedAt: "", ...plain },
19580
21004
  box
19581
21005
  );
19582
- (0, import_node_fs38.writeFileSync)(tokensPath, JSON.stringify(encrypted, null, 2) + "\n", "utf8");
21006
+ atomicReplaceUtf8(tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
19583
21007
  }
19584
21008
  var TOKEN_FIELDS2 = {
19585
21009
  claude: ["accessToken", "refreshToken"],
@@ -19602,7 +21026,7 @@ function walkTokens(raw, fn) {
19602
21026
  return next;
19603
21027
  }
19604
21028
  function tokensSuffix(configPath) {
19605
- return (0, import_node_fs38.existsSync)(defaultTokensPath(configPath)) ? " (+ tokens.json)" : "";
21029
+ return (0, import_node_fs39.existsSync)(defaultTokensPath(configPath)) ? " (+ tokens.json)" : "";
19606
21030
  }
19607
21031
 
19608
21032
  // src/commands/start.ts
@@ -19869,7 +21293,7 @@ async function main() {
19869
21293
  process.exitCode = 1;
19870
21294
  }
19871
21295
  }
19872
- main().catch((err5) => {
19873
- console.error(err5 instanceof Error ? err5.message : String(err5));
21296
+ main().catch((err6) => {
21297
+ console.error(err6 instanceof Error ? err6.message : String(err6));
19874
21298
  process.exitCode = 1;
19875
21299
  });