@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/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/bootstrap.ts
2
- import { accessSync, constants as fsConstants, existsSync as existsSync29, mkdirSync as mkdirSync9 } from "fs";
2
+ import { accessSync, constants as fsConstants, existsSync as existsSync30, mkdirSync as mkdirSync9 } from "fs";
3
3
  import { dirname as dirname17 } from "path";
4
4
  import { DEFAULT_AUDIT_CONFIG } from "@omnicross/contracts/audit-types";
5
5
  import { DEFAULT_BILLING_CONFIG } from "@omnicross/contracts/billing-types";
@@ -18,14 +18,14 @@ import { setSubscriptionRegistryForOutbound } from "@omnicross/core/outbound-api
18
18
  import { getSharedAccountHealth as getSharedAccountHealth4 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
19
19
  import {
20
20
  __resetSharedAccountAllowanceStoreForTests,
21
- AccountAllowanceStore as AccountAllowanceStore3,
21
+ AccountAllowanceStore as AccountAllowanceStore6,
22
22
  setSharedAccountAllowanceStore
23
23
  } from "@omnicross/core/pipeline/AccountAllowanceStore";
24
24
  import {
25
25
  __resetSharedAccountAllowanceSchedulingForTests,
26
26
  getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling5
27
27
  } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
28
- import { fetchUpstream as fetchUpstream7, setUpstreamProxyResolver } from "@omnicross/core/pipeline/upstreamFetch";
28
+ import { fetchUpstream as fetchUpstream11, setUpstreamProxyResolver } from "@omnicross/core/pipeline/upstreamFetch";
29
29
  import { __resetSharedIdentityStoreForTests } from "@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore";
30
30
  import { setGeminiCodeAssistResolver } from "@omnicross/core/ports/gemini-code-assist-resolver";
31
31
  import {
@@ -155,9 +155,84 @@ function handleCodexOAuthStatus(sessionId, deps) {
155
155
  return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
156
156
  }
157
157
 
158
+ // src/admin/accountsKimiOAuth.ts
159
+ import { kimiOAuth } from "@omnicross/subscriptions";
160
+ function err2(status, message) {
161
+ return { status, body: { error: { type: "admin_api_error", message } } };
162
+ }
163
+ var DEFAULT_KIMI_OAUTH_TTL_MS = 15 * 6e4;
164
+ async function handleKimiOAuthStart(deps) {
165
+ if (deps.kimiSessions.isBusy()) {
166
+ return err2(409, "a kimi sign-in is already in progress \u2014 finish it in the browser or cancel it");
167
+ }
168
+ const fetchImpl = deps.oauthExchangeFetch("kimi");
169
+ const deviceId = kimiOAuth.generateKimiDeviceId();
170
+ const fingerprint = kimiOAuth.kimiFingerprintHeaders(deviceId);
171
+ let authorization;
172
+ try {
173
+ authorization = await kimiOAuth.requestDeviceAuthorization(fetchImpl, fingerprint);
174
+ } catch (e) {
175
+ const reason = e instanceof Error ? e.message : "device authorization failed";
176
+ return err2(502, `kimi device authorization failed: ${reason}`);
177
+ }
178
+ const { sessionId, signal } = deps.kimiSessions.begin();
179
+ void runKimiDevicePoll(sessionId, authorization.deviceCode, deviceId, fingerprint, signal, deps).catch(() => deps.kimiSessions.settle(sessionId, "error", "kimi sign-in failed"));
180
+ return {
181
+ status: 200,
182
+ body: {
183
+ authUrl: authorization.verificationUriComplete ?? authorization.verificationUri,
184
+ userCode: authorization.userCode,
185
+ sessionId
186
+ }
187
+ };
188
+ }
189
+ async function runKimiDevicePoll(sessionId, deviceCode, deviceId, fingerprint, signal, deps) {
190
+ const fetchImpl = deps.oauthExchangeFetch("kimi");
191
+ const result = await kimiOAuth.awaitDeviceToken(
192
+ { userCode: "", deviceCode, verificationUri: "" },
193
+ fetchImpl,
194
+ {
195
+ fingerprint,
196
+ deadlineMs: DEFAULT_KIMI_OAUTH_TTL_MS,
197
+ sleep: (ms) => new Promise((resolve10, reject) => {
198
+ const onAbort = () => {
199
+ clearTimeout(timer);
200
+ reject(new Error("login: cancelled"));
201
+ };
202
+ const timer = setTimeout(() => {
203
+ signal.removeEventListener("abort", onAbort);
204
+ resolve10();
205
+ }, ms);
206
+ signal.addEventListener("abort", onAbort, { once: true });
207
+ })
208
+ }
209
+ );
210
+ const block = {
211
+ authMethod: "oauth",
212
+ status: "authorized",
213
+ accessToken: result.accessToken,
214
+ refreshToken: result.refreshToken,
215
+ expiresAt: new Date(Date.now() + result.expiresIn * 1e3).toISOString(),
216
+ accountId: kimiOAuth.kimiAccountIdFromAccessToken(result.accessToken),
217
+ deviceId,
218
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
219
+ };
220
+ await deps.subscriptionAccountAppender.appendProviderAccount("kimi", block);
221
+ deps.kimiSessions.settle(sessionId, "done");
222
+ }
223
+ function handleKimiOAuthCancel(sessionId, deps) {
224
+ if (!deps.kimiSessions.cancel(sessionId)) return err2(404, "unknown or expired kimi sign-in session");
225
+ return { status: 200, body: { ok: true } };
226
+ }
227
+ function handleKimiOAuthStatus(sessionId, deps) {
228
+ const s = deps.kimiSessions.get(sessionId);
229
+ if (!s) return err2(404, "unknown or expired kimi sign-in session");
230
+ return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
231
+ }
232
+
158
233
  // src/allowance/AccountAllowanceService.ts
159
234
  import {
160
- getSharedAccountAllowanceStore as getSharedAccountAllowanceStore2
235
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore5
161
236
  } from "@omnicross/core/pipeline/AccountAllowanceStore";
162
237
  import {
163
238
  getSharedAccountAllowanceScheduling
@@ -191,13 +266,11 @@ function secondsUntil(instant, now) {
191
266
  function windowFromPayload(id, payload, now) {
192
267
  const usedPercent = finitePercent(payload?.utilization);
193
268
  const resetsAt = isoInstant(payload?.resets_at);
194
- const isSonnet = id === "seven-day-sonnet";
195
269
  const isFiveHour = id === "five-hour";
196
270
  return {
197
271
  id,
198
- label: isFiveHour ? "5 hours" : isSonnet ? "7 days \xB7 Sonnet" : "7 days",
199
- scope: isSonnet ? "model-family" : "all",
200
- modelFamily: isSonnet ? "sonnet" : void 0,
272
+ label: isFiveHour ? "5 hours" : "7 days",
273
+ scope: "all",
201
274
  usedPercent,
202
275
  windowMinutes: isFiveHour ? 5 * 60 : 7 * 24 * 60,
203
276
  resetsAt,
@@ -205,6 +278,44 @@ function windowFromPayload(id, payload, now) {
205
278
  state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
206
279
  };
207
280
  }
281
+ function limitEntryWindow(entries, kind) {
282
+ const entry = entries.find((candidate) => candidate.kind === kind);
283
+ if (!entry) return void 0;
284
+ return { utilization: entry.percent, resets_at: entry.resets_at };
285
+ }
286
+ function slugifyDisplayName(name) {
287
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
288
+ }
289
+ function scopedWeeklyWindows(entries, now) {
290
+ const seen = /* @__PURE__ */ new Set();
291
+ const windows = [];
292
+ for (const entry of entries) {
293
+ if (entry.kind !== "weekly_scoped") continue;
294
+ const displayName = typeof entry.scope?.model?.display_name === "string" && entry.scope.model.display_name.trim() ? entry.scope.model.display_name.trim() : void 0;
295
+ if (!displayName) continue;
296
+ const slug = slugifyDisplayName(displayName);
297
+ if (!slug || seen.has(slug)) continue;
298
+ seen.add(slug);
299
+ const usedPercent = finitePercent(entry.percent);
300
+ const resetsAt = isoInstant(entry.resets_at);
301
+ windows.push({
302
+ id: `seven-day-${slug}`,
303
+ label: `7 days \xB7 ${displayName}`,
304
+ scope: "model-family",
305
+ modelFamily: slug,
306
+ usedPercent,
307
+ windowMinutes: 7 * 24 * 60,
308
+ resetsAt,
309
+ remainingSeconds: secondsUntil(resetsAt, now),
310
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
311
+ });
312
+ }
313
+ return windows;
314
+ }
315
+ function parseLimitEntries(raw) {
316
+ if (!Array.isArray(raw)) return [];
317
+ return raw.filter((entry) => !!entry && typeof entry === "object");
318
+ }
208
319
  function emptyClaudeWindows(state) {
209
320
  return [
210
321
  {
@@ -222,15 +333,6 @@ function emptyClaudeWindows(state) {
222
333
  usedPercent: null,
223
334
  windowMinutes: 7 * 24 * 60,
224
335
  state
225
- },
226
- {
227
- id: "seven-day-sonnet",
228
- label: "7 days \xB7 Sonnet",
229
- scope: "model-family",
230
- modelFamily: "sonnet",
231
- usedPercent: null,
232
- windowMinutes: 7 * 24 * 60,
233
- state
234
336
  }
235
337
  ];
236
338
  }
@@ -311,6 +413,9 @@ var ClaudeAllowanceCollector = class {
311
413
  }
312
414
  const now = this.now();
313
415
  const usage = payload;
416
+ const limitEntries = parseLimitEntries(usage.limits);
417
+ const fiveHour = usage.five_hour ?? limitEntryWindow(limitEntries, "session");
418
+ const sevenDay = usage.seven_day ?? limitEntryWindow(limitEntries, "weekly_all");
314
419
  const snapshot = {
315
420
  providerId: "claude",
316
421
  accountId,
@@ -318,10 +423,10 @@ var ClaudeAllowanceCollector = class {
318
423
  observedAt: new Date(now).toISOString(),
319
424
  expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
320
425
  windows: [
321
- windowFromPayload("five-hour", usage.five_hour, now),
322
- windowFromPayload("seven-day", usage.seven_day, now),
323
- windowFromPayload("seven-day-sonnet", usage.seven_day_sonnet, now)
324
- ]
426
+ windowFromPayload("five-hour", fiveHour, now),
427
+ windowFromPayload("seven-day", sevenDay, now),
428
+ ...scopedWeeklyWindows(limitEntries, now)
429
+ ].slice(0, 8)
325
430
  };
326
431
  this.store.set(snapshot);
327
432
  return snapshot;
@@ -378,6 +483,607 @@ var ClaudeAllowanceCollector = class {
378
483
  }
379
484
  };
380
485
 
486
+ // src/allowance/CodexAllowanceCollector.ts
487
+ import {
488
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore2
489
+ } from "@omnicross/core/pipeline/AccountAllowanceStore";
490
+ import { fetchUpstream as fetchUpstream2 } from "@omnicross/core/pipeline/upstreamFetch";
491
+ var CODEX_ALLOWANCE_CACHE_MS = 5 * 6e4;
492
+ var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
493
+ var CODEX_CLI_USER_AGENT = "codex_cli_rs/0.144.5";
494
+ function finiteNumber(value) {
495
+ if (value === null || value === void 0 || value === "") return null;
496
+ const parsed = typeof value === "number" ? value : Number(value);
497
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
498
+ }
499
+ function finitePercent2(value) {
500
+ const parsed = finiteNumber(value);
501
+ return parsed !== null && parsed <= 100 ? parsed : null;
502
+ }
503
+ function epochMs(value) {
504
+ return value > 1e11 ? value : value * 1e3;
505
+ }
506
+ function secondsUntil2(instant, now) {
507
+ if (!instant) return void 0;
508
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
509
+ }
510
+ function decodeJwtClaims(token) {
511
+ const parts = token.split(".");
512
+ if (parts.length !== 3) return void 0;
513
+ try {
514
+ const json2 = Buffer.from(parts[1], "base64url").toString("utf8");
515
+ const parsed = JSON.parse(json2);
516
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
517
+ } catch {
518
+ return void 0;
519
+ }
520
+ }
521
+ function chatgptAccountIdFromClaims(claims) {
522
+ const auth = claims?.["https://api.openai.com/auth"];
523
+ if (!auth || typeof auth !== "object") return void 0;
524
+ const accountId = auth.chatgpt_account_id;
525
+ return typeof accountId === "string" && accountId.trim() ? accountId.trim() : void 0;
526
+ }
527
+ function resolveCodexChatGptAccountId(tokens) {
528
+ if (tokens.accountId?.trim()) return tokens.accountId.trim();
529
+ if (tokens.idToken) {
530
+ const fromIdToken = chatgptAccountIdFromClaims(decodeJwtClaims(tokens.idToken));
531
+ if (fromIdToken) return fromIdToken;
532
+ }
533
+ if (tokens.accessToken) {
534
+ return chatgptAccountIdFromClaims(decodeJwtClaims(tokens.accessToken));
535
+ }
536
+ return void 0;
537
+ }
538
+ function windowFromPayload2(id, payload, now) {
539
+ const usedPercent = finitePercent2(payload?.used_percent);
540
+ const resetAtSeconds = finiteNumber(payload?.reset_at);
541
+ const resetAfterSeconds = finiteNumber(payload?.reset_after_seconds);
542
+ const windowSeconds = finiteNumber(payload?.limit_window_seconds);
543
+ const resetsAt = resetAtSeconds !== null && resetAtSeconds > 0 ? new Date(epochMs(resetAtSeconds)).toISOString() : resetAfterSeconds !== null && resetAfterSeconds > 0 ? new Date(now + resetAfterSeconds * 1e3).toISOString() : void 0;
544
+ const windowMinutes = windowSeconds !== null && windowSeconds > 0 ? Math.round(windowSeconds / 60) : void 0;
545
+ return {
546
+ id,
547
+ label: id === "primary" ? "Primary" : "Secondary",
548
+ scope: "all",
549
+ usedPercent,
550
+ ...windowMinutes !== void 0 ? { windowMinutes } : {},
551
+ ...resetsAt !== void 0 ? { resetsAt } : {},
552
+ remainingSeconds: secondsUntil2(resetsAt, now),
553
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
554
+ };
555
+ }
556
+ var CodexAllowanceCollector = class {
557
+ constructor(credentials, store = getSharedAccountAllowanceStore2(), fetchImpl = (url, init, accountId) => fetchUpstream2(url, init, { providerId: "codex", accountId, redactBodies: true }), now = Date.now) {
558
+ this.credentials = credentials;
559
+ this.store = store;
560
+ this.fetchImpl = fetchImpl;
561
+ this.now = now;
562
+ }
563
+ credentials;
564
+ store;
565
+ fetchImpl;
566
+ now;
567
+ inFlight = /* @__PURE__ */ new Map();
568
+ async collectMany(accounts, options = {}) {
569
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
570
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
571
+ }
572
+ collect(account, options = {}) {
573
+ const now = this.now();
574
+ const unsupported = account.tokens.authMethod !== "oauth";
575
+ if (unsupported) {
576
+ const existing = this.store.get("codex", account.id, now);
577
+ if (existing?.windows.every((window) => window.state === "unsupported")) {
578
+ return Promise.resolve(existing);
579
+ }
580
+ const snapshot = this.unsupportedSnapshot(account.id, now);
581
+ this.store.set(snapshot);
582
+ return Promise.resolve(snapshot);
583
+ }
584
+ const cached = this.store.get("codex", account.id, now);
585
+ if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
586
+ return Promise.resolve(cached);
587
+ }
588
+ const running = this.inFlight.get(account.id);
589
+ if (running) return running;
590
+ 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));
591
+ this.inFlight.set(account.id, promise);
592
+ return promise;
593
+ }
594
+ /**
595
+ * A response-header snapshot stays a valid cache hit only while fresh; an
596
+ * active oauth-usage snapshot is honored on the same 5-minute cadence as
597
+ * Claude's (the poll is cheap and quota is the scheduling input).
598
+ */
599
+ isCacheValid(snapshot, now, refreshAheadMs) {
600
+ if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
601
+ const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
602
+ const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
603
+ return Number.isFinite(expiresAt) && expiresAt > now + ahead;
604
+ }
605
+ async fetchAccount(accountId, tokens) {
606
+ let accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
607
+ if (!accessToken) {
608
+ return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
609
+ }
610
+ let response = await this.request(accountId, accessToken, tokens);
611
+ if (response.status === 401) {
612
+ const refreshed = await this.credentials.refreshAccountToken("codex", accountId);
613
+ if (!refreshed) {
614
+ return this.failureSnapshot(accountId, "codex_usage_unauthorized", this.now());
615
+ }
616
+ accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
617
+ if (!accessToken) {
618
+ return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
619
+ }
620
+ response = await this.request(accountId, accessToken, tokens);
621
+ }
622
+ if (response.status === 403) {
623
+ const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "codex_usage_unsupported");
624
+ this.store.set(snapshot2);
625
+ return snapshot2;
626
+ }
627
+ if (!response.ok) {
628
+ return this.failureSnapshot(accountId, "codex_usage_http_error", this.now());
629
+ }
630
+ let payload;
631
+ try {
632
+ payload = await response.json();
633
+ } catch {
634
+ return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
635
+ }
636
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
637
+ return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
638
+ }
639
+ const now = this.now();
640
+ const usage = payload.rate_limit;
641
+ const previous = this.store.get("codex", accountId, now);
642
+ const snapshot = {
643
+ providerId: "codex",
644
+ accountId,
645
+ source: "oauth-usage-api",
646
+ observedAt: new Date(now).toISOString(),
647
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
648
+ windows: [
649
+ windowFromPayload2("primary", usage?.primary_window ?? void 0, now),
650
+ windowFromPayload2("secondary", usage?.secondary_window ?? void 0, now)
651
+ ],
652
+ // The wham payload has no ratio field; keep the passively-observed value.
653
+ ...previous?.primaryOverSecondaryLimitPercent !== void 0 ? { primaryOverSecondaryLimitPercent: previous.primaryOverSecondaryLimitPercent } : {}
654
+ };
655
+ this.store.set(snapshot);
656
+ return snapshot;
657
+ }
658
+ request(accountId, accessToken, tokens) {
659
+ const headers = {
660
+ Authorization: `Bearer ${accessToken}`,
661
+ Accept: "application/json",
662
+ "User-Agent": CODEX_CLI_USER_AGENT
663
+ };
664
+ const chatgptAccountId = resolveCodexChatGptAccountId(tokens);
665
+ if (chatgptAccountId) headers["ChatGPT-Account-Id"] = chatgptAccountId;
666
+ return this.fetchImpl(CODEX_USAGE_URL, {
667
+ method: "GET",
668
+ headers,
669
+ signal: AbortSignal.timeout(15e3)
670
+ }, accountId);
671
+ }
672
+ failureSnapshot(accountId, code, now) {
673
+ const existing = this.store.get("codex", accountId, now);
674
+ const snapshot = existing ? {
675
+ ...existing,
676
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
677
+ windows: existing.windows.map((window) => ({
678
+ ...window,
679
+ state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
680
+ })),
681
+ lastErrorCode: code
682
+ } : {
683
+ providerId: "codex",
684
+ accountId,
685
+ source: "oauth-usage-api",
686
+ observedAt: new Date(now).toISOString(),
687
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
688
+ windows: [
689
+ { id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unavailable" },
690
+ { id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unavailable" }
691
+ ],
692
+ lastErrorCode: code
693
+ };
694
+ this.store.set(snapshot);
695
+ return snapshot;
696
+ }
697
+ unsupportedSnapshot(accountId, now, code = "codex_usage_unsupported_auth") {
698
+ return {
699
+ providerId: "codex",
700
+ accountId,
701
+ source: "oauth-usage-api",
702
+ observedAt: new Date(now).toISOString(),
703
+ windows: [
704
+ { id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unsupported" },
705
+ { id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unsupported" }
706
+ ],
707
+ lastErrorCode: code
708
+ };
709
+ }
710
+ };
711
+
712
+ // src/allowance/KimiAllowanceCollector.ts
713
+ import {
714
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore3
715
+ } from "@omnicross/core/pipeline/AccountAllowanceStore";
716
+ import { fetchUpstream as fetchUpstream3 } from "@omnicross/core/pipeline/upstreamFetch";
717
+ import { kimiFingerprintHeaders } from "@omnicross/subscriptions";
718
+ var KIMI_ALLOWANCE_CACHE_MS = 5 * 6e4;
719
+ var KIMI_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
720
+ function finiteNumber2(value) {
721
+ if (value === null || value === void 0 || value === "") return void 0;
722
+ const parsed = typeof value === "number" ? value : Number(value);
723
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
724
+ }
725
+ function isRecord(value) {
726
+ return !!value && typeof value === "object" && !Array.isArray(value);
727
+ }
728
+ function parseResetMs(row, nowMs) {
729
+ for (const key of ["reset_at", "resetAt", "reset_time", "resetTime"]) {
730
+ const value = row[key];
731
+ if (typeof value === "string" && value.trim()) {
732
+ const parsed = Date.parse(value);
733
+ if (Number.isFinite(parsed)) return parsed;
734
+ }
735
+ const numeric = finiteNumber2(value);
736
+ if (numeric !== void 0 && numeric > 1e9) {
737
+ return numeric > 1e12 ? numeric : numeric * 1e3;
738
+ }
739
+ }
740
+ for (const key of ["reset_in", "resetIn", "ttl", "window"]) {
741
+ const seconds = finiteNumber2(row[key]);
742
+ if (seconds !== void 0) return nowMs + seconds * 1e3;
743
+ }
744
+ return void 0;
745
+ }
746
+ var MINUTE_MS = 6e4;
747
+ var HOUR_MS = 36e5;
748
+ var DAY_MS = 864e5;
749
+ function canonicalWindow(durationMs) {
750
+ if (durationMs === 5 * HOUR_MS) return { id: "five-hour", label: "5 hours", minutes: 300 };
751
+ if (durationMs === 7 * DAY_MS) return { id: "seven-day", label: "7 days", minutes: 10080 };
752
+ if (durationMs > 0 && durationMs % DAY_MS === 0) {
753
+ const days = durationMs / DAY_MS;
754
+ return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
755
+ }
756
+ if (durationMs > 0 && durationMs % HOUR_MS === 0) {
757
+ const hours = durationMs / HOUR_MS;
758
+ return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
759
+ }
760
+ return void 0;
761
+ }
762
+ function secondsUntil3(instant, now) {
763
+ if (!instant) return void 0;
764
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
765
+ }
766
+ function windowFromRow(row, fallback, now) {
767
+ 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;
768
+ const resetsAt = row?.resetsAtMs !== void 0 ? new Date(row.resetsAtMs).toISOString() : void 0;
769
+ return {
770
+ id: fallback.id,
771
+ label: fallback.label,
772
+ scope: "all",
773
+ usedPercent,
774
+ windowMinutes: fallback.minutes,
775
+ ...resetsAt !== void 0 ? { resetsAt } : {},
776
+ remainingSeconds: secondsUntil3(resetsAt, now),
777
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
778
+ };
779
+ }
780
+ function parseKimiUsagePayload(payload, now) {
781
+ if (!isRecord(payload)) return [];
782
+ const byId = /* @__PURE__ */ new Map();
783
+ const rowFrom = (data) => {
784
+ const limit = finiteNumber2(data["limit"]);
785
+ let used = finiteNumber2(data["used"]);
786
+ const remaining = finiteNumber2(data["remaining"]);
787
+ if (used === void 0 && remaining !== void 0 && limit !== void 0) {
788
+ used = limit - remaining;
789
+ }
790
+ let windowDurationMs;
791
+ const windowData = isRecord(data["window"]) ? data["window"] : void 0;
792
+ const duration = finiteNumber2(windowData?.["duration"]);
793
+ const timeUnit = typeof windowData?.["timeUnit"] === "string" ? windowData["timeUnit"].toUpperCase() : "";
794
+ if (duration !== void 0) {
795
+ if (timeUnit.includes("MINUTE")) windowDurationMs = duration * MINUTE_MS;
796
+ else if (timeUnit.includes("HOUR")) windowDurationMs = duration * HOUR_MS;
797
+ else if (timeUnit.includes("DAY")) windowDurationMs = duration * DAY_MS;
798
+ else if (timeUnit.includes("WEEK")) windowDurationMs = duration * 7 * DAY_MS;
799
+ else if (timeUnit.includes("SECOND")) windowDurationMs = duration * 1e3;
800
+ }
801
+ const resetsAtMs = parseResetMs(windowData && parseResetMs(windowData, now) !== void 0 ? windowData : data, now);
802
+ return { used, limit, remaining, ...resetsAtMs !== void 0 ? { resetsAtMs } : {}, ...windowDurationMs !== void 0 ? { windowDurationMs } : {} };
803
+ };
804
+ if (isRecord(payload["usage"])) {
805
+ const row = rowFrom(payload["usage"]);
806
+ const window = windowFromRow({ ...row, resetsAtMs: row.resetsAtMs }, { id: "seven-day", label: "7 days", minutes: 10080 }, now);
807
+ byId.set("seven-day", window);
808
+ }
809
+ if (Array.isArray(payload["limits"])) {
810
+ for (const item of payload["limits"]) {
811
+ if (!isRecord(item)) continue;
812
+ const detail = isRecord(item["detail"]) ? item["detail"] : item;
813
+ const row = rowFrom(detail);
814
+ const canonical = row.windowDurationMs !== void 0 ? canonicalWindow(row.windowDurationMs) : void 0;
815
+ if (!canonical) continue;
816
+ const window = windowFromRow(row, canonical, now);
817
+ const existing = byId.get(canonical.id);
818
+ if (!existing || (window.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
819
+ byId.set(canonical.id, window);
820
+ }
821
+ }
822
+ }
823
+ return [...byId.values()].sort((a, b) => (a.windowMinutes ?? Infinity) - (b.windowMinutes ?? Infinity)).slice(0, 4);
824
+ }
825
+ var KimiAllowanceCollector = class {
826
+ constructor(credentials, store = getSharedAccountAllowanceStore3(), fetchImpl = (url, init, accountId) => fetchUpstream3(url, init, { providerId: "kimi", accountId, redactBodies: true }), now = Date.now) {
827
+ this.credentials = credentials;
828
+ this.store = store;
829
+ this.fetchImpl = fetchImpl;
830
+ this.now = now;
831
+ }
832
+ credentials;
833
+ store;
834
+ fetchImpl;
835
+ now;
836
+ inFlight = /* @__PURE__ */ new Map();
837
+ async collectMany(accounts, options = {}) {
838
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
839
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
840
+ }
841
+ collect(account, options = {}) {
842
+ const now = this.now();
843
+ if (account.tokens.authMethod !== "oauth") {
844
+ const existing = this.store.get("kimi", account.id, now);
845
+ if (existing?.windows.every((window) => window.state === "unsupported")) {
846
+ return Promise.resolve(existing);
847
+ }
848
+ const snapshot = this.unsupportedSnapshot(account.id, now);
849
+ this.store.set(snapshot);
850
+ return Promise.resolve(snapshot);
851
+ }
852
+ const cached = this.store.get("kimi", account.id, now);
853
+ if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
854
+ return Promise.resolve(cached);
855
+ }
856
+ const running = this.inFlight.get(account.id);
857
+ if (running) return running;
858
+ 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));
859
+ this.inFlight.set(account.id, promise);
860
+ return promise;
861
+ }
862
+ isCacheValid(snapshot, now, refreshAheadMs) {
863
+ if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
864
+ const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
865
+ const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
866
+ return Number.isFinite(expiresAt) && expiresAt > now + ahead;
867
+ }
868
+ async fetchAccount(accountId, tokens) {
869
+ let accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
870
+ if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
871
+ let response = await this.request(accountId, accessToken, tokens);
872
+ if (response.status === 401) {
873
+ const refreshed = await this.credentials.refreshAccountToken("kimi", accountId);
874
+ if (!refreshed) return this.failureSnapshot(accountId, "kimi_usage_unauthorized", this.now());
875
+ accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
876
+ if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
877
+ response = await this.request(accountId, accessToken, tokens);
878
+ }
879
+ if (response.status === 403) {
880
+ const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "kimi_usage_unsupported");
881
+ this.store.set(snapshot2);
882
+ return snapshot2;
883
+ }
884
+ if (!response.ok) return this.failureSnapshot(accountId, "kimi_usage_http_error", this.now());
885
+ let payload;
886
+ try {
887
+ payload = await response.json();
888
+ } catch {
889
+ return this.failureSnapshot(accountId, "kimi_usage_invalid_response", this.now());
890
+ }
891
+ const now = this.now();
892
+ const windows = parseKimiUsagePayload(payload, now);
893
+ const snapshot = {
894
+ providerId: "kimi",
895
+ accountId,
896
+ source: "oauth-usage-api",
897
+ observedAt: new Date(now).toISOString(),
898
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
899
+ windows: windows.length > 0 ? windows : [
900
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
901
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
902
+ ],
903
+ ...windows.length > 0 ? {} : { lastErrorCode: "kimi_usage_invalid_response" }
904
+ };
905
+ this.store.set(snapshot);
906
+ return snapshot;
907
+ }
908
+ request(accountId, accessToken, tokens) {
909
+ return this.fetchImpl(KIMI_USAGE_URL, {
910
+ method: "GET",
911
+ headers: {
912
+ Authorization: `Bearer ${accessToken}`,
913
+ Accept: "application/json",
914
+ ...kimiFingerprintHeaders(tokens.deviceId)
915
+ },
916
+ signal: AbortSignal.timeout(15e3)
917
+ }, accountId);
918
+ }
919
+ failureSnapshot(accountId, code, now) {
920
+ const existing = this.store.get("kimi", accountId, now);
921
+ const snapshot = existing ? {
922
+ ...existing,
923
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
924
+ windows: existing.windows.map((window) => ({
925
+ ...window,
926
+ state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
927
+ })),
928
+ lastErrorCode: code
929
+ } : {
930
+ providerId: "kimi",
931
+ accountId,
932
+ source: "oauth-usage-api",
933
+ observedAt: new Date(now).toISOString(),
934
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
935
+ windows: [
936
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
937
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
938
+ ],
939
+ lastErrorCode: code
940
+ };
941
+ this.store.set(snapshot);
942
+ return snapshot;
943
+ }
944
+ unsupportedSnapshot(accountId, now, code = "kimi_usage_unsupported_auth") {
945
+ return {
946
+ providerId: "kimi",
947
+ accountId,
948
+ source: "oauth-usage-api",
949
+ observedAt: new Date(now).toISOString(),
950
+ windows: [
951
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unsupported" },
952
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unsupported" }
953
+ ],
954
+ lastErrorCode: code
955
+ };
956
+ }
957
+ };
958
+
959
+ // src/allowance/OpenCodeGoAllowanceCollector.ts
960
+ import {
961
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore4
962
+ } from "@omnicross/core/pipeline/AccountAllowanceStore";
963
+ import { fetchUpstream as fetchUpstream4 } from "@omnicross/core/pipeline/upstreamFetch";
964
+ import { normalizeOpenCodeGoBaseUrl } from "@omnicross/subscriptions";
965
+ var OPENCODEGO_ALLOWANCE_CACHE_MS = 5 * 6e4;
966
+ var OPENCODEGO_DEFAULT_GO_BASE = "https://opencode.ai/zen/go";
967
+ function finitePercent3(value) {
968
+ if (value === null || value === void 0 || value === "") return null;
969
+ const parsed = typeof value === "number" ? value : Number(value);
970
+ return Number.isFinite(parsed) && parsed >= 0 && parsed <= 100 ? parsed : null;
971
+ }
972
+ function isoInstant2(value) {
973
+ if (typeof value !== "string" || !value.trim()) return void 0;
974
+ const time = Date.parse(value);
975
+ return Number.isFinite(time) ? new Date(time).toISOString() : void 0;
976
+ }
977
+ function secondsUntil4(instant, now) {
978
+ if (!instant) return void 0;
979
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
980
+ }
981
+ function windowFromPayload3(id, label, minutes, payload, now) {
982
+ const statusRateLimited = payload?.status === "rate-limited";
983
+ const usedPercent = statusRateLimited ? 100 : finitePercent3(payload?.percent);
984
+ const resetsAt = isoInstant2(payload?.resetsAt);
985
+ return {
986
+ id,
987
+ label,
988
+ scope: "all",
989
+ usedPercent,
990
+ windowMinutes: minutes,
991
+ ...resetsAt !== void 0 ? { resetsAt } : {},
992
+ remainingSeconds: secondsUntil4(resetsAt, now),
993
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
994
+ };
995
+ }
996
+ var OpenCodeGoAllowanceCollector = class {
997
+ constructor(credentials, store = getSharedAccountAllowanceStore4(), fetchImpl = (url, init, accountId) => fetchUpstream4(url, init, { providerId: "opencodego", accountId, redactBodies: true }), now = Date.now) {
998
+ this.credentials = credentials;
999
+ this.store = store;
1000
+ this.fetchImpl = fetchImpl;
1001
+ this.now = now;
1002
+ }
1003
+ credentials;
1004
+ store;
1005
+ fetchImpl;
1006
+ now;
1007
+ inFlight = /* @__PURE__ */ new Map();
1008
+ async collectMany(accounts, options = {}) {
1009
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
1010
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
1011
+ }
1012
+ collect(account, options = {}) {
1013
+ const now = this.now();
1014
+ const cached = this.store.get("opencodego", account.id, now);
1015
+ if (!options.force && cached && (cached.windows.every((window) => window.state === "unsupported") || cached.expiresAt && Date.parse(cached.expiresAt) > now + (options.refreshAheadMs ?? 0))) {
1016
+ return Promise.resolve(cached);
1017
+ }
1018
+ const running = this.inFlight.get(account.id);
1019
+ if (running) return running;
1020
+ const promise = this.fetchAccount(account).catch(() => this.failureSnapshot(account.id, this.now())).finally(() => this.inFlight.delete(account.id));
1021
+ this.inFlight.set(account.id, promise);
1022
+ return promise;
1023
+ }
1024
+ async fetchAccount(account) {
1025
+ const apiKey = await this.credentials.getAccessTokenForAccount("opencodego", account.id);
1026
+ if (!apiKey) return this.failureSnapshot(account.id, this.now());
1027
+ const base = account.tokens.baseUrl ? normalizeOpenCodeGoBaseUrl(account.tokens.baseUrl) : OPENCODEGO_DEFAULT_GO_BASE;
1028
+ const response = await this.fetchImpl(`${base}/v1/usage`, {
1029
+ method: "GET",
1030
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
1031
+ signal: AbortSignal.timeout(15e3)
1032
+ }, account.id);
1033
+ if (response.status === 401 || response.status === 403) {
1034
+ return this.failureSnapshot(account.id, this.now(), "opencodego_usage_unauthorized");
1035
+ }
1036
+ if (!response.ok) return this.failureSnapshot(account.id, this.now());
1037
+ let payload;
1038
+ try {
1039
+ payload = await response.json();
1040
+ } catch {
1041
+ return this.failureSnapshot(account.id, this.now());
1042
+ }
1043
+ const usage = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.usage : void 0;
1044
+ const now = this.now();
1045
+ const snapshot = {
1046
+ providerId: "opencodego",
1047
+ accountId: account.id,
1048
+ source: "oauth-usage-api",
1049
+ observedAt: new Date(now).toISOString(),
1050
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
1051
+ // Monthly deliberately omitted (module doc).
1052
+ windows: [
1053
+ windowFromPayload3("five-hour", "5 hours", 5 * 60, usage?.rolling ?? void 0, now),
1054
+ windowFromPayload3("seven-day", "7 days", 7 * 24 * 60, usage?.weekly ?? void 0, now)
1055
+ ]
1056
+ };
1057
+ this.store.set(snapshot);
1058
+ return snapshot;
1059
+ }
1060
+ failureSnapshot(accountId, now, code = "opencodego_usage_request_failed") {
1061
+ const existing = this.store.get("opencodego", accountId, now);
1062
+ const snapshot = existing ? {
1063
+ ...existing,
1064
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
1065
+ windows: existing.windows.map((window) => ({
1066
+ ...window,
1067
+ state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
1068
+ })),
1069
+ lastErrorCode: code
1070
+ } : {
1071
+ providerId: "opencodego",
1072
+ accountId,
1073
+ source: "oauth-usage-api",
1074
+ observedAt: new Date(now).toISOString(),
1075
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
1076
+ windows: [
1077
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
1078
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
1079
+ ],
1080
+ lastErrorCode: code
1081
+ };
1082
+ this.store.set(snapshot);
1083
+ return snapshot;
1084
+ }
1085
+ };
1086
+
381
1087
  // src/allowance/AccountAllowanceService.ts
382
1088
  function codexUnavailable(accountId, now) {
383
1089
  return {
@@ -393,26 +1099,30 @@ function codexUnavailable(accountId, now) {
393
1099
  };
394
1100
  }
395
1101
  var AccountAllowanceService = class {
396
- constructor(credentials, store = getSharedAccountAllowanceStore2(), collector, now = Date.now) {
1102
+ constructor(credentials, store = getSharedAccountAllowanceStore5(), collector, codexCollector, kimiCollector, opencodegoCollector, now = Date.now) {
397
1103
  this.credentials = credentials;
398
1104
  this.store = store;
399
1105
  this.now = now;
400
1106
  this.claudeCollector = collector ?? new ClaudeAllowanceCollector(credentials, store);
1107
+ this.codexCollector = codexCollector ?? new CodexAllowanceCollector(credentials, store);
1108
+ this.kimiCollector = kimiCollector ?? new KimiAllowanceCollector(credentials, store);
1109
+ this.opencodegoCollector = opencodegoCollector ?? new OpenCodeGoAllowanceCollector(credentials, store);
401
1110
  }
402
1111
  credentials;
403
1112
  store;
404
1113
  now;
405
1114
  claudeCollector;
1115
+ codexCollector;
1116
+ kimiCollector;
1117
+ opencodegoCollector;
406
1118
  /**
407
- * Read all/filtered snapshots. Claude's five-minute cache is refreshed lazily;
408
- * Codex remains passive and reports not-observed until a real model response.
1119
+ * Read all/filtered snapshots. Claude's and Codex's five-minute caches are
1120
+ * refreshed lazily on read (Codex polls `/backend-api/wham/usage`; the
1121
+ * passive `x-codex-*` header tap still feeds mid-flight updates).
409
1122
  */
410
1123
  async list(filter = {}) {
411
1124
  const config = await this.credentials.getFullConfig();
412
- this.store.pruneToKnownAccounts([
413
- ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
414
- ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
415
- ]);
1125
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
416
1126
  const wantsClaude = !filter.providerId || filter.providerId === "claude";
417
1127
  const claudeAccounts = (config.claudeAccounts ?? []).filter(
418
1128
  (account) => !filter.accountId || account.id === filter.accountId
@@ -423,39 +1133,90 @@ var AccountAllowanceService = class {
423
1133
  (account) => !filter.accountId || account.id === filter.accountId
424
1134
  );
425
1135
  if (wantsCodex) {
1136
+ await this.codexCollector.collectMany(codexAccounts);
426
1137
  for (const account of codexAccounts) {
427
1138
  if (!this.store.get("codex", account.id)) this.store.set(codexUnavailable(account.id, this.now()));
428
1139
  }
429
1140
  }
1141
+ const wantsKimi = !filter.providerId || filter.providerId === "kimi";
1142
+ const kimiAccounts = (config.kimiAccounts ?? []).filter(
1143
+ (account) => !filter.accountId || account.id === filter.accountId
1144
+ );
1145
+ if (wantsKimi) await this.kimiCollector.collectMany(kimiAccounts);
1146
+ const wantsOpenCodeGo = !filter.providerId || filter.providerId === "opencodego";
1147
+ const opencodegoAccounts = (config.opencodegoAccounts ?? []).filter(
1148
+ (account) => !filter.accountId || account.id === filter.accountId
1149
+ );
1150
+ if (wantsOpenCodeGo) await this.opencodegoCollector.collectMany(opencodegoAccounts);
430
1151
  const known = /* @__PURE__ */ new Set();
431
1152
  if (wantsClaude) for (const account of claudeAccounts) known.add(`claude\0${account.id}`);
432
1153
  if (wantsCodex) for (const account of codexAccounts) known.add(`codex\0${account.id}`);
1154
+ if (wantsKimi) for (const account of kimiAccounts) known.add(`kimi\0${account.id}`);
1155
+ if (wantsOpenCodeGo) for (const account of opencodegoAccounts) known.add(`opencodego\0${account.id}`);
433
1156
  return this.store.list(filter).filter((snapshot) => known.has(`${snapshot.providerId}\0${snapshot.accountId}`));
434
1157
  }
1158
+ knownAccounts(config) {
1159
+ return [
1160
+ ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
1161
+ ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id })),
1162
+ ...(config.kimiAccounts ?? []).map((account) => ({ providerId: "kimi", accountId: account.id })),
1163
+ ...(config.opencodegoAccounts ?? []).map((account) => ({ providerId: "opencodego", accountId: account.id }))
1164
+ ];
1165
+ }
435
1166
  /** Force-refresh Claude usage for one account or every stored Claude account. */
436
1167
  async refreshClaude(accountId) {
437
1168
  const config = await this.credentials.getFullConfig();
438
- this.store.pruneToKnownAccounts([
439
- ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
440
- ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
441
- ]);
1169
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
442
1170
  const accounts = (config.claudeAccounts ?? []).filter(
443
1171
  (account) => !accountId || account.id === accountId
444
1172
  );
445
1173
  return this.claudeCollector.collectMany(accounts, { force: true });
446
1174
  }
447
1175
  /**
448
- * Keep Claude snapshots warm for allowance-aware routing. This deliberately
449
- * excludes Codex (whose quota is learned from real response headers) and
450
- * preserves the collector's cache + per-account in-flight coalescing.
1176
+ * Force-refresh Codex usage (`/backend-api/wham/usage`) for one account or
1177
+ * every stored Codex account. Replaces the old probe-request workaround
1178
+ * no quota is spent reading the usage endpoint.
1179
+ */
1180
+ async refreshCodex(accountId) {
1181
+ const config = await this.credentials.getFullConfig();
1182
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1183
+ const accounts = (config.codexAccounts ?? []).filter(
1184
+ (account) => !accountId || account.id === accountId
1185
+ );
1186
+ return this.codexCollector.collectMany(accounts, { force: true });
1187
+ }
1188
+ /** Force-refresh OpenCodeGo usage (`{go}/v1/usage`) for one/all accounts. */
1189
+ async refreshOpenCodeGo(accountId) {
1190
+ const config = await this.credentials.getFullConfig();
1191
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1192
+ const accounts = (config.opencodegoAccounts ?? []).filter(
1193
+ (account) => !accountId || account.id === accountId
1194
+ );
1195
+ return this.opencodegoCollector.collectMany(accounts, { force: true });
1196
+ }
1197
+ /** Force-refresh Kimi usage (`/coding/v1/usages`) for one/all accounts. */
1198
+ async refreshKimi(accountId) {
1199
+ const config = await this.credentials.getFullConfig();
1200
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1201
+ const accounts = (config.kimiAccounts ?? []).filter(
1202
+ (account) => !accountId || account.id === accountId
1203
+ );
1204
+ return this.kimiCollector.collectMany(accounts, { force: true });
1205
+ }
1206
+ /**
1207
+ * Keep Claude + Codex + Kimi snapshots warm for allowance-aware routing. All
1208
+ * collectors preserve their cache + per-account in-flight coalescing; a tick
1209
+ * normally performs no network I/O. (Codex joined the warm path when it
1210
+ * gained an active `/wham/usage` collector — the passive `x-codex-*` header
1211
+ * tap alone could not keep the policy fed while idle.)
451
1212
  */
452
1213
  async maintainClaudeCache(refreshAheadMs) {
453
1214
  const config = await this.credentials.getFullConfig();
454
- this.store.pruneToKnownAccounts([
455
- ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
456
- ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
457
- ]);
1215
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
458
1216
  await this.claudeCollector.collectMany(config.claudeAccounts ?? [], { refreshAheadMs });
1217
+ await this.codexCollector.collectMany(config.codexAccounts ?? [], { refreshAheadMs });
1218
+ await this.kimiCollector.collectMany(config.kimiAccounts ?? [], { refreshAheadMs });
1219
+ await this.opencodegoCollector.collectMany(config.opencodegoAccounts ?? [], { refreshAheadMs });
459
1220
  }
460
1221
  /** Remove a cache row as soon as an account is deleted by the admin path. */
461
1222
  removeAccountSnapshot(providerId, accountId) {
@@ -892,7 +1653,7 @@ import {
892
1653
  } from "@omnicross/contracts/image-generation-types";
893
1654
  import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling2 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
894
1655
  import { getSharedAccountHealth } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
895
- import { fetchUpstream as fetchUpstream2 } from "@omnicross/core/pipeline/upstreamFetch";
1656
+ import { fetchUpstream as fetchUpstream5 } from "@omnicross/core/pipeline/upstreamFetch";
896
1657
 
897
1658
  // src/image-generation/imagesConfigValidation.ts
898
1659
  import { validateImagesServerConfig } from "@omnicross/core/outbound-api";
@@ -2969,7 +3730,8 @@ var VALID_PROVIDER_IDS = [
2969
3730
  "claude",
2970
3731
  "codex",
2971
3732
  "gemini",
2972
- "opencodego"
3733
+ "opencodego",
3734
+ "kimi"
2973
3735
  ];
2974
3736
  function asSubscriptionProviderId(id) {
2975
3737
  return VALID_PROVIDER_IDS.includes(id) ? id : null;
@@ -3105,6 +3867,18 @@ function validateGemini(body) {
3105
3867
  copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "lastRefreshedAt", "errorMessage"]);
3106
3868
  return out;
3107
3869
  }
3870
+ function validateKimi(body) {
3871
+ const authMethod = str(body["authMethod"]);
3872
+ const status = str(body["status"]);
3873
+ if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
3874
+ if (!status || !TOKEN_STATUSES.has(status)) return null;
3875
+ const out = {
3876
+ authMethod,
3877
+ status
3878
+ };
3879
+ copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "accountId", "deviceId", "lastRefreshedAt", "errorMessage"]);
3880
+ return out;
3881
+ }
3108
3882
  function validateOpenCodeGo(body) {
3109
3883
  const authMethod = str(body["authMethod"]);
3110
3884
  const status = str(body["status"]);
@@ -3140,6 +3914,8 @@ function validateTokenBody(providerId, body) {
3140
3914
  return validateGemini(body);
3141
3915
  case "opencodego":
3142
3916
  return validateOpenCodeGo(body);
3917
+ case "kimi":
3918
+ return validateKimi(body);
3143
3919
  default:
3144
3920
  return null;
3145
3921
  }
@@ -3169,12 +3945,12 @@ async function statusEntryFor(reader, providerId) {
3169
3945
 
3170
3946
  // src/admin/accountsOAuth.ts
3171
3947
  var OAUTH_HTTP_PROVIDERS = /* @__PURE__ */ new Set(["claude", "gemini"]);
3172
- function err2(status, message) {
3948
+ function err3(status, message) {
3173
3949
  return { status, body: { error: { type: "admin_api_error", message } } };
3174
3950
  }
3175
3951
  function handleOAuthStart(providerId, deps) {
3176
3952
  if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
3177
- return err2(400, `oauth not available for provider '${providerId}'`);
3953
+ return err3(400, `oauth not available for provider '${providerId}'`);
3178
3954
  }
3179
3955
  const flow = providerId === "claude" ? claudeOAuth : geminiOAuth;
3180
3956
  const { authUrl, codeVerifier, state } = flow.generateAuthParams();
@@ -3183,23 +3959,23 @@ function handleOAuthStart(providerId, deps) {
3183
3959
  }
3184
3960
  async function handleOAuthComplete(providerId, body, deps) {
3185
3961
  if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
3186
- return err2(400, `oauth not available for provider '${providerId}'`);
3962
+ return err3(400, `oauth not available for provider '${providerId}'`);
3187
3963
  }
3188
3964
  const sessionId = typeof body["sessionId"] === "string" ? body["sessionId"] : "";
3189
3965
  const rawCode = typeof body["code"] === "string" ? body["code"] : "";
3190
- if (!sessionId) return err2(400, "oauth complete requires { sessionId }");
3191
- if (!rawCode) return err2(400, "oauth complete requires { code }");
3966
+ if (!sessionId) return err3(400, "oauth complete requires { sessionId }");
3967
+ if (!rawCode) return err3(400, "oauth complete requires { code }");
3192
3968
  const session = deps.oauthSessions.peek(sessionId);
3193
- if (!session) return err2(410, "oauth session is unknown, expired, or already used");
3969
+ if (!session) return err3(410, "oauth session is unknown, expired, or already used");
3194
3970
  if (session.providerId !== providerId) {
3195
- return err2(400, `oauth session does not match provider '${providerId}'`);
3971
+ return err3(400, `oauth session does not match provider '${providerId}'`);
3196
3972
  }
3197
3973
  let code = rawCode.trim();
3198
3974
  if (providerId === "claude") {
3199
3975
  const [splitCode, pastedState] = code.split("#");
3200
- if (!splitCode) return err2(400, "no authorization code was provided");
3976
+ if (!splitCode) return err3(400, "no authorization code was provided");
3201
3977
  if (pastedState && pastedState !== session.state) {
3202
- return err2(400, "oauth state did not match (possible CSRF) \u2014 aborting");
3978
+ return err3(400, "oauth state did not match (possible CSRF) \u2014 aborting");
3203
3979
  }
3204
3980
  code = splitCode;
3205
3981
  }
@@ -3209,7 +3985,7 @@ async function handleOAuthComplete(providerId, body, deps) {
3209
3985
  block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state, exchangeFetch) : await exchangeGemini(code, session.codeVerifier, exchangeFetch);
3210
3986
  } catch (exchangeError) {
3211
3987
  const reason = exchangeError instanceof Error ? exchangeError.message : "token exchange failed";
3212
- return err2(502, `oauth token exchange failed for '${providerId}': ${reason}`);
3988
+ return err3(502, `oauth token exchange failed for '${providerId}': ${reason}`);
3213
3989
  }
3214
3990
  deps.oauthSessions.consume(sessionId);
3215
3991
  const label = typeof body["label"] === "string" && body["label"].trim() ? body["label"].trim() : void 0;
@@ -3547,8 +4323,8 @@ function errBody(message) {
3547
4323
  return { error: { type: "admin_api_error", message } };
3548
4324
  }
3549
4325
  var defaultCommandRunner = (command) => new Promise((resolve10) => {
3550
- exec(command, { timeout: 18e4 }, (err5, _stdout, stderr) => {
3551
- if (err5) resolve10({ ok: false, error: stderr.trim() || err5.message });
4326
+ exec(command, { timeout: 18e4 }, (err6, _stdout, stderr) => {
4327
+ if (err6) resolve10({ ok: false, error: stderr.trim() || err6.message });
3552
4328
  else resolve10({ ok: true });
3553
4329
  });
3554
4330
  });
@@ -3594,8 +4370,8 @@ async function handleCliLaunch(cli, body, ctx) {
3594
4370
  providerId: typeof body["providerId"] === "string" ? body["providerId"] : void 0,
3595
4371
  model: typeof body["model"] === "string" ? body["model"] : void 0
3596
4372
  });
3597
- } catch (err5) {
3598
- return { status: 400, body: errBody(err5 instanceof Error ? err5.message : "no launch target") };
4373
+ } catch (err6) {
4374
+ return { status: 400, body: errBody(err6 instanceof Error ? err6.message : "no launch target") };
3599
4375
  }
3600
4376
  const id = randomUUID2();
3601
4377
  let leaseId2;
@@ -3623,9 +4399,9 @@ async function handleCliLaunch(cli, body, ctx) {
3623
4399
  } else {
3624
4400
  launch = await buildLaunchEnv(cli, ctx.llmConfig, target);
3625
4401
  }
3626
- } catch (err5) {
3627
- const status = err5 instanceof RouteLeaseError2 ? err5.status : 400;
3628
- return { status, body: errBody(err5 instanceof Error ? err5.message : "failed to build launch env") };
4402
+ } catch (err6) {
4403
+ const status = err6 instanceof RouteLeaseError2 ? err6.status : 400;
4404
+ return { status, body: errBody(err6 instanceof Error ? err6.message : "failed to build launch env") };
3629
4405
  }
3630
4406
  const cwd = typeof body["cwd"] === "string" && body["cwd"].trim() ? body["cwd"].trim() : void 0;
3631
4407
  const opener = ctx.opener ?? defaultTerminalOpener;
@@ -3653,9 +4429,9 @@ async function handleCliLaunch(cli, body, ctx) {
3653
4429
  onFailure: onSessionEnd
3654
4430
  });
3655
4431
  if (cleanup) openerCleanup = cleanup;
3656
- } catch (err5) {
4432
+ } catch (err6) {
3657
4433
  onSessionEnd();
3658
- return { status: 500, body: errBody(err5 instanceof Error ? err5.message : "failed to open terminal") };
4434
+ return { status: 500, body: errBody(err6 instanceof Error ? err6.message : "failed to open terminal") };
3659
4435
  }
3660
4436
  if (ended) {
3661
4437
  openerCleanup?.();
@@ -3789,7 +4565,8 @@ async function handleDashboard(deps) {
3789
4565
  // src/admin/searchAdminApi.ts
3790
4566
  import { DEFAULT_SEARCH_SERVER_CONFIG, loadServerConfig } from "@omnicross/core/outbound-api";
3791
4567
  import { apiSearchContributions as apiSearchContributions2 } from "@omnicross/core/search/api";
3792
- import { builtinHttpSearchContributions as builtinHttpSearchContributions3, createSearchHttpTransport } from "@omnicross/core/search/http";
4568
+ import { builtinHttpSearchContributions as builtinHttpSearchContributions3, createSearchHttpTransport as createSearchHttpTransport2 } from "@omnicross/core/search/http";
4569
+ import { createSearchRuntime as createSearchRuntime2 } from "@omnicross/core/search";
3793
4570
 
3794
4571
  // src/search/searchDoctorProjection.ts
3795
4572
  import { toSearchErrorShape } from "@omnicross/contracts/search-types";
@@ -3855,7 +4632,7 @@ function buildSearchDoctorSnapshot(contributions = builtinHttpSearchContribution
3855
4632
  }
3856
4633
  return rows;
3857
4634
  }
3858
- var SEARCH_DOCTOR_QUERY = "mozilla developer network http headers";
4635
+ var SEARCH_DOCTOR_QUERY = "MDN HTTP headers documentation";
3859
4636
  function classifyLiveSearchOutcome(providerId, outcome, checkedAt) {
3860
4637
  if (outcome.kind === "results") {
3861
4638
  if (outcome.count > 0) return { providerId, status: "healthy", checkedAt };
@@ -3900,9 +4677,13 @@ function classifySearchFailure(stage, code) {
3900
4677
  }
3901
4678
 
3902
4679
  // src/search/SearchAssembly.ts
4680
+ import { resolveUpstreamDispatcher } from "@omnicross/core/pipeline/upstreamFetch";
3903
4681
  import { createSearchRuntime } from "@omnicross/core/search";
3904
4682
  import { apiSearchContributions } from "@omnicross/core/search/api";
3905
- import { builtinHttpSearchContributions as builtinHttpSearchContributions2 } from "@omnicross/core/search/http";
4683
+ import {
4684
+ builtinHttpSearchContributions as builtinHttpSearchContributions2,
4685
+ createSearchHttpTransport
4686
+ } from "@omnicross/core/search/http";
3906
4687
  function searchEgressPolicyFrom(config) {
3907
4688
  const hosts = config.egress.allowedPrivateHosts;
3908
4689
  return hosts.length > 0 ? { allowedPrivateHosts: [...hosts] } : {};
@@ -3916,11 +4697,24 @@ function searchPolicyFrom(config) {
3916
4697
  ...maxAttempts !== void 0 ? { maxAttempts } : {}
3917
4698
  };
3918
4699
  }
4700
+ function resolveSearchUpstreamDispatcher(url) {
4701
+ return resolveUpstreamDispatcher({ url });
4702
+ }
4703
+ var searchUpstreamProxyConfig = createUpstreamProxyResolver();
4704
+ function resolveSearchUpstreamProxyConfig(url) {
4705
+ return searchUpstreamProxyConfig({ url });
4706
+ }
3919
4707
  function searchContributionsFrom(config) {
3920
4708
  return [
3921
- ...builtinHttpSearchContributions2(),
4709
+ ...builtinHttpSearchContributions2(
4710
+ createSearchHttpTransport({
4711
+ resolveProxyDispatcher: resolveSearchUpstreamDispatcher,
4712
+ resolveProxyConfig: resolveSearchUpstreamProxyConfig
4713
+ })
4714
+ ),
3922
4715
  ...apiSearchContributions(config.providers, {
3923
- egressPolicy: searchEgressPolicyFrom(config)
4716
+ egressPolicy: searchEgressPolicyFrom(config),
4717
+ resolveProxyDispatcher: resolveSearchUpstreamDispatcher
3924
4718
  })
3925
4719
  ];
3926
4720
  }
@@ -4064,6 +4858,18 @@ async function handleSearchDiagnostics(res, deps) {
4064
4858
  };
4065
4859
  return writeJson(res, 200, { diagnostics: snapshot });
4066
4860
  }
4861
+ function persistedSearchContributions(search, fetchImpl) {
4862
+ if (fetchImpl) {
4863
+ const egressPolicy = searchEgressPolicyFrom(search);
4864
+ return [
4865
+ ...builtinHttpSearchContributions3(
4866
+ createSearchHttpTransport2({ fetch: fetchImpl, egressPolicy })
4867
+ ),
4868
+ ...apiSearchContributions2(search.providers, { egressPolicy, fetchImpl })
4869
+ ];
4870
+ }
4871
+ return searchContributionsFrom(search);
4872
+ }
4067
4873
  async function handleSearchTest(req, res, deps) {
4068
4874
  const status = deps.searchStatus;
4069
4875
  const body = await readBodyOrReject(req, res);
@@ -4081,16 +4887,8 @@ async function handleSearchTest(req, res, deps) {
4081
4887
  if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
4082
4888
  return writeErr(res, 400, `search provider '${providerId}' is not configured`);
4083
4889
  }
4084
- const egressPolicy = searchEgressPolicyFrom(search);
4085
4890
  const fetchImpl = status.testFetch;
4086
- const transport = fetchImpl ? createSearchHttpTransport({ fetch: fetchImpl, egressPolicy }) : void 0;
4087
- const contributions = [
4088
- ...builtinHttpSearchContributions3(transport),
4089
- ...apiSearchContributions2(search.providers, {
4090
- egressPolicy,
4091
- ...fetchImpl ? { fetchImpl } : {}
4092
- })
4093
- ];
4891
+ const contributions = persistedSearchContributions(search, fetchImpl);
4094
4892
  const contribution = contributions.find((c) => c.id === providerId);
4095
4893
  if (!contribution) {
4096
4894
  return writeErr(res, 400, `search provider '${providerId}' is not configured`);
@@ -4138,41 +4936,42 @@ async function handleSearchQuery(req, res, deps) {
4138
4936
  if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
4139
4937
  return writeErr(res, 400, `search provider '${providerId}' is not configured`);
4140
4938
  }
4141
- const egressPolicy = searchEgressPolicyFrom(search);
4142
4939
  const fetchImpl = status.testFetch;
4143
- const transport = fetchImpl ? createSearchHttpTransport({ fetch: fetchImpl, egressPolicy }) : void 0;
4144
- const contributions = [
4145
- ...builtinHttpSearchContributions3(transport),
4146
- ...apiSearchContributions2(search.providers, {
4147
- egressPolicy,
4148
- ...fetchImpl ? { fetchImpl } : {}
4149
- })
4150
- ];
4151
- const contribution = contributions.find((c) => c.id === providerId);
4152
- if (!contribution) {
4153
- return writeErr(res, 400, `search provider '${providerId}' is not configured`);
4154
- }
4940
+ const runtime = createSearchRuntime2({
4941
+ contributions: persistedSearchContributions(search, fetchImpl),
4942
+ policy: {
4943
+ ...searchPolicyFrom(search),
4944
+ // The panel always walks: it answers "does a search WORK for this
4945
+ // operator", not "does this one provider behave" — that is `/test`'s
4946
+ // job. The persisted policy's allowlist still bounds the walk.
4947
+ fallbackEnabled: true,
4948
+ preferred: providerId
4949
+ }
4950
+ });
4155
4951
  const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
4156
4952
  try {
4157
- const results = await contribution.provider.search(query2, { maxResults: 5 });
4953
+ const orchestrated = await runtime.search({ query: query2, options: { maxResults: 5 } });
4954
+ const results = orchestrated.results;
4158
4955
  const sanitized = results.slice(0, SEARCH_QUERY_MAX_RESULTS).map((result) => ({
4159
4956
  title: sanitizeResultField(result.title, SEARCH_RESULT_FIELD_CAPS.title),
4160
4957
  url: sanitizeResultField(result.url, SEARCH_RESULT_FIELD_CAPS.url),
4161
4958
  content: sanitizeResultField(result.content, SEARCH_RESULT_FIELD_CAPS.content)
4162
4959
  }));
4163
- const diagnostic = sanitized.length === 0 ? { providerId: contribution.id, status: "healthy", checkedAt } : classifyLiveSearchOutcome(
4164
- contribution.id,
4960
+ const diagnostic = sanitized.length === 0 ? { providerId: orchestrated.providerId, status: "healthy", checkedAt } : classifyLiveSearchOutcome(
4961
+ orchestrated.providerId,
4165
4962
  { kind: "results", count: sanitized.length },
4166
4963
  checkedAt
4167
4964
  );
4168
4965
  const response = {
4169
4966
  diagnostic,
4967
+ providerUsed: orchestrated.providerId,
4968
+ fallbackCount: orchestrated.fallbackCount,
4170
4969
  resultCount: sanitized.length,
4171
4970
  results: sanitized
4172
4971
  };
4173
4972
  return writeJson(res, 200, { result: response });
4174
4973
  } catch (error) {
4175
- const diagnostic = classifyLiveSearchOutcome(contribution.id, { kind: "failure", error }, checkedAt);
4974
+ const diagnostic = classifyLiveSearchOutcome(providerId, { kind: "failure", error }, checkedAt);
4176
4975
  const response = { diagnostic };
4177
4976
  return writeJson(res, 200, { result: response });
4178
4977
  }
@@ -4181,7 +4980,7 @@ async function handleSearchQuery(req, res, deps) {
4181
4980
  // src/admin/searchAdminView.ts
4182
4981
  var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
4183
4982
  var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
4184
- function isRecord(value) {
4983
+ function isRecord2(value) {
4185
4984
  return value !== null && typeof value === "object" && !Array.isArray(value);
4186
4985
  }
4187
4986
  function redactSearchServerConfig(search) {
@@ -4231,13 +5030,13 @@ function resolveSecretField(entry, field, stored) {
4231
5030
  else delete entry[field];
4232
5031
  }
4233
5032
  function preserveSearchSecrets(incoming, current) {
4234
- if (!isRecord(incoming)) return incoming;
5033
+ if (!isRecord2(incoming)) return incoming;
4235
5034
  const section = { ...incoming };
4236
5035
  const providersValue = section["providers"];
4237
- if (!isRecord(providersValue)) return section;
5036
+ if (!isRecord2(providersValue)) return section;
4238
5037
  const providers = {};
4239
5038
  for (const [id, entryValue] of Object.entries(providersValue)) {
4240
- if (!isRecord(entryValue)) {
5039
+ if (!isRecord2(entryValue)) {
4241
5040
  providers[id] = entryValue;
4242
5041
  continue;
4243
5042
  }
@@ -4315,7 +5114,7 @@ function parseKeyPolicyBody(body) {
4315
5114
  var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
4316
5115
  var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
4317
5116
  var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
4318
- function isRecord2(value) {
5117
+ function isRecord3(value) {
4319
5118
  return !!value && typeof value === "object" && !Array.isArray(value);
4320
5119
  }
4321
5120
  function nonBlank(value) {
@@ -4335,7 +5134,7 @@ function validateGatewayBindingsSegment(patch) {
4335
5134
  const ids = /* @__PURE__ */ new Set();
4336
5135
  raw.forEach((entry, index) => {
4337
5136
  const path2 = `bindings[${index}]`;
4338
- if (!isRecord2(entry)) {
5137
+ if (!isRecord3(entry)) {
4339
5138
  errors.push(`${path2} must be an object`);
4340
5139
  return;
4341
5140
  }
@@ -4364,12 +5163,12 @@ function validateGatewayBindingsSegment(patch) {
4364
5163
  } else if (entry.modelMappings.length > 100) {
4365
5164
  errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
4366
5165
  } else if (entry.modelMappings.some(
4367
- (mapping) => !isRecord2(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
5166
+ (mapping) => !isRecord3(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
4368
5167
  )) {
4369
5168
  errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
4370
5169
  }
4371
5170
  }
4372
- if (!isRecord2(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
5171
+ if (!isRecord3(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
4373
5172
  errors.push(`${path2}.target is invalid`);
4374
5173
  } else {
4375
5174
  if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
@@ -4384,7 +5183,7 @@ function validateGatewayBindingsSegment(patch) {
4384
5183
  }
4385
5184
  }
4386
5185
  if (entry.modelMap !== void 0) {
4387
- if (!isRecord2(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
5186
+ if (!isRecord3(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
4388
5187
  errors.push(`${path2}.modelMap must contain string values`);
4389
5188
  }
4390
5189
  }
@@ -4694,7 +5493,8 @@ var PROVIDER_KEYS = {
4694
5493
  block: "opencodego",
4695
5494
  accounts: "opencodegoAccounts",
4696
5495
  active: "activeOpencodegoAccountId"
4697
- }
5496
+ },
5497
+ kimi: { block: "kimi", accounts: "kimiAccounts", active: "activeKimiAccountId" }
4698
5498
  };
4699
5499
  function clone(value) {
4700
5500
  return JSON.parse(JSON.stringify(value));
@@ -5216,7 +6016,7 @@ async function applyImport(packString, passphrase, mode, deps, parseProviderInpu
5216
6016
  }
5217
6017
 
5218
6018
  // src/admin/adminMigration.ts
5219
- function err3(status, message) {
6019
+ function err4(status, message) {
5220
6020
  return { status, body: { error: { type: "admin_api_error", message } } };
5221
6021
  }
5222
6022
  async function handleExport(body, deps) {
@@ -5226,30 +6026,30 @@ async function handleExport(body, deps) {
5226
6026
  return { status: 200, body: { pack, version: BUNDLE_VERSION } };
5227
6027
  } catch (error) {
5228
6028
  if (error instanceof WeakPassphraseError) {
5229
- return err3(400, error.message);
6029
+ return err4(400, error.message);
5230
6030
  }
5231
- return err3(500, "failed to build the migration pack");
6031
+ return err4(500, "failed to build the migration pack");
5232
6032
  }
5233
6033
  }
5234
6034
  async function handleImport(body, deps) {
5235
6035
  const blob = typeof body["blob"] === "string" ? body["blob"] : "";
5236
6036
  const passphrase = typeof body["passphrase"] === "string" ? body["passphrase"] : "";
5237
6037
  const mode = body["mode"] === "overwrite" ? "overwrite" : "merge";
5238
- if (!blob) return err3(400, "import requires { blob }");
6038
+ if (!blob) return err4(400, "import requires { blob }");
5239
6039
  try {
5240
6040
  const counts = await applyImport(blob, passphrase, mode, deps, deps.parseProviderInput);
5241
6041
  return { status: 200, body: counts };
5242
6042
  } catch (error) {
5243
6043
  if (error instanceof WeakPassphraseError) {
5244
- return err3(400, error.message);
6044
+ return err4(400, error.message);
5245
6045
  }
5246
- return err3(400, error instanceof Error ? error.message : "import failed");
6046
+ return err4(400, error instanceof Error ? error.message : "import failed");
5247
6047
  }
5248
6048
  }
5249
6049
 
5250
6050
  // src/admin/usagePricing.ts
5251
6051
  import { getSharedUsageThroughputTracker } from "@omnicross/core/usage";
5252
- var err4 = (status, message) => ({
6052
+ var err5 = (status, message) => ({
5253
6053
  status,
5254
6054
  body: { error: { type: "admin_api_error", message } }
5255
6055
  });
@@ -5262,7 +6062,7 @@ function parseRange(query2) {
5262
6062
  const startTs = parseFiniteInt(query2.get("startTs"));
5263
6063
  const endTs = parseFiniteInt(query2.get("endTs"));
5264
6064
  if (startTs === null || endTs === null) {
5265
- return err4(400, "startTs and endTs are required finite-integer unix-millis query params");
6065
+ return err5(400, "startTs and endTs are required finite-integer unix-millis query params");
5266
6066
  }
5267
6067
  return { startTs, endTs };
5268
6068
  }
@@ -5287,14 +6087,14 @@ async function handleUsageGet(view, query2, deps) {
5287
6087
  case "timeseries": {
5288
6088
  const bucket = query2.get("bucket");
5289
6089
  if (bucket !== "hour" && bucket !== "day" && bucket !== "month") {
5290
- return err4(400, "bucket must be one of 'hour', 'day', 'month'");
6090
+ return err5(400, "bucket must be one of 'hour', 'day', 'month'");
5291
6091
  }
5292
6092
  const now = Date.now();
5293
6093
  const clamped = { startTs: range.startTs, endTs: Math.min(range.endTs, now) };
5294
6094
  if (clamped.startTs < clamped.endTs) {
5295
6095
  const projected = Math.ceil((clamped.endTs - clamped.startTs) / BUCKET_SPAN_MS[bucket]) + 1;
5296
6096
  if (projected > MAX_TIMESERIES_BUCKETS) {
5297
- return err4(
6097
+ return err5(
5298
6098
  400,
5299
6099
  `requested range projects ~${projected} '${bucket}' buckets (max ${MAX_TIMESERIES_BUCKETS}); narrow the range or use a coarser bucket`
5300
6100
  );
@@ -5317,7 +6117,7 @@ async function handleUsageGet(view, query2, deps) {
5317
6117
  };
5318
6118
  }
5319
6119
  default:
5320
- return err4(404, `unknown usage view '${view ?? ""}'`);
6120
+ return err5(404, `unknown usage view '${view ?? ""}'`);
5321
6121
  }
5322
6122
  }
5323
6123
  function poolKeyLabels(cfg) {
@@ -5366,7 +6166,7 @@ async function handlePricingList(deps) {
5366
6166
  async function handlePricingUpsert(body, deps) {
5367
6167
  const input = parsePricingEntryInput(body);
5368
6168
  if (!input) {
5369
- return err4(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
6169
+ return err5(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
5370
6170
  }
5371
6171
  const entry = await deps.pricingEngine.upsertManual(input);
5372
6172
  return { status: 200, body: { entry } };
@@ -5375,7 +6175,7 @@ async function handlePricingDelete(query2, deps) {
5375
6175
  const providerId = query2.get("providerId")?.trim() ?? "";
5376
6176
  const modelId = query2.get("modelId")?.trim() ?? "";
5377
6177
  if (!providerId || !modelId) {
5378
- return err4(400, "delete requires providerId and modelId query params");
6178
+ return err5(400, "delete requires providerId and modelId query params");
5379
6179
  }
5380
6180
  const deleted = await deps.pricingStore.delete(providerId, modelId);
5381
6181
  if (deleted) await deps.pricingEngine.invalidateCache();
@@ -5395,13 +6195,13 @@ async function handlePricingFetchLatest(deps) {
5395
6195
  }
5396
6196
  };
5397
6197
  } catch (e) {
5398
- return err4(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
6198
+ return err5(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
5399
6199
  }
5400
6200
  }
5401
6201
  async function handlePricingResolveConflicts(body, deps) {
5402
6202
  const raw = body["resolutions"];
5403
6203
  if (!Array.isArray(raw)) {
5404
- return err4(400, "resolve-conflicts requires { resolutions: [...] }");
6204
+ return err5(400, "resolve-conflicts requires { resolutions: [...] }");
5405
6205
  }
5406
6206
  const currentRows = await deps.pricingStore.getAll();
5407
6207
  const userEditedKeys = new Set(
@@ -5411,21 +6211,21 @@ async function handlePricingResolveConflicts(body, deps) {
5411
6211
  const pendingIncoming = /* @__PURE__ */ new Map();
5412
6212
  let staleCount = 0;
5413
6213
  for (const item of raw) {
5414
- if (!item || typeof item !== "object") return err4(400, "invalid resolution entry");
6214
+ if (!item || typeof item !== "object") return err5(400, "invalid resolution entry");
5415
6215
  const r = item;
5416
6216
  const action = r["action"];
5417
6217
  if (action !== "overwrite" && action !== "skip") {
5418
- return err4(400, "resolution action must be 'overwrite' or 'skip'");
6218
+ return err5(400, "resolution action must be 'overwrite' or 'skip'");
5419
6219
  }
5420
6220
  const providerId = typeof r["providerId"] === "string" && r["providerId"].trim() ? r["providerId"].trim() : "";
5421
6221
  const modelId = typeof r["modelId"] === "string" && r["modelId"].trim() ? r["modelId"].trim() : "";
5422
6222
  if (!providerId || !modelId) {
5423
- return err4(400, "each resolution requires top-level providerId and modelId");
6223
+ return err5(400, "each resolution requires top-level providerId and modelId");
5424
6224
  }
5425
6225
  const incoming = parsePricingEntryInput(r["incoming"]);
5426
- if (!incoming) return err4(400, "each resolution must echo a valid incoming pricing entry");
6226
+ if (!incoming) return err5(400, "each resolution must echo a valid incoming pricing entry");
5427
6227
  if (incoming.providerId !== providerId || incoming.modelId !== modelId) {
5428
- return err4(400, "resolution providerId/modelId must match the echoed incoming entry");
6228
+ return err5(400, "resolution providerId/modelId must match the echoed incoming entry");
5429
6229
  }
5430
6230
  const key = `${providerId}::${modelId}`;
5431
6231
  if (action === "overwrite" && !userEditedKeys.has(key)) {
@@ -5470,7 +6270,7 @@ function query(req) {
5470
6270
  }
5471
6271
  function allowanceProvider(value) {
5472
6272
  if (!value) return void 0;
5473
- return value === "claude" || value === "codex" ? value : null;
6273
+ return value === "claude" || value === "codex" || value === "kimi" || value === "opencodego" ? value : null;
5474
6274
  }
5475
6275
  async function handleAccountAllowanceApi(req, res, method, rest, service) {
5476
6276
  if (!service) return writeError2(res, 501, "account allowance service is not available");
@@ -5484,7 +6284,9 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
5484
6284
  const params = query(req);
5485
6285
  const pathProvider = rest.length >= 2 ? rest[0] : null;
5486
6286
  const providerId = allowanceProvider(pathProvider ?? params.get("providerId") ?? params.get("provider"));
5487
- if (providerId === null) return writeError2(res, 400, "providerId must be claude or codex");
6287
+ if (providerId === null) {
6288
+ return writeError2(res, 400, "providerId must be claude, codex, kimi, or opencodego");
6289
+ }
5488
6290
  const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
5489
6291
  const allowances = await service.list({ providerId, accountId });
5490
6292
  return writeJson3(res, 200, { allowances });
@@ -5494,10 +6296,37 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
5494
6296
  const requestedProvider = allowanceProvider(
5495
6297
  typeof body["providerId"] === "string" ? body["providerId"] : "claude"
5496
6298
  );
5497
- if (requestedProvider !== "claude") {
5498
- return writeError2(res, 400, "only Claude allowances support explicit refresh");
5499
- }
5500
6299
  const accountId = typeof body["accountId"] === "string" && body["accountId"].trim() ? body["accountId"].trim() : void 0;
6300
+ if (requestedProvider === "codex") {
6301
+ if (!service.refreshCodex) {
6302
+ return writeError2(res, 501, "codex allowance refresh is not available");
6303
+ }
6304
+ const allowances2 = await service.refreshCodex(accountId);
6305
+ if (accountId && allowances2.length === 0) {
6306
+ return writeError2(res, 404, `Codex account '${accountId}' not found`);
6307
+ }
6308
+ return writeJson3(res, 200, { allowances: allowances2 });
6309
+ }
6310
+ if (requestedProvider === "kimi") {
6311
+ if (!service.refreshKimi) {
6312
+ return writeError2(res, 501, "kimi allowance refresh is not available");
6313
+ }
6314
+ const allowances2 = await service.refreshKimi(accountId);
6315
+ if (accountId && allowances2.length === 0) {
6316
+ return writeError2(res, 404, `Kimi account '${accountId}' not found`);
6317
+ }
6318
+ return writeJson3(res, 200, { allowances: allowances2 });
6319
+ }
6320
+ if (requestedProvider === "opencodego") {
6321
+ if (!service.refreshOpenCodeGo) {
6322
+ return writeError2(res, 501, "opencodego allowance refresh is not available");
6323
+ }
6324
+ const allowances2 = await service.refreshOpenCodeGo(accountId);
6325
+ if (accountId && allowances2.length === 0) {
6326
+ return writeError2(res, 404, `OpenCodeGo account '${accountId}' not found`);
6327
+ }
6328
+ return writeJson3(res, 200, { allowances: allowances2 });
6329
+ }
5501
6330
  const allowances = await service.refreshClaude(accountId);
5502
6331
  if (accountId && allowances.length === 0) {
5503
6332
  return writeError2(res, 404, `Claude account '${accountId}' not found`);
@@ -5668,8 +6497,8 @@ async function handleAdminApi(req, res, path2, deps) {
5668
6497
  default:
5669
6498
  return writeJsonError(res, 404, `unknown admin resource '${resource}'`);
5670
6499
  }
5671
- } catch (err5) {
5672
- writeJsonError(res, 500, err5 instanceof Error ? err5.message : String(err5));
6500
+ } catch (err6) {
6501
+ writeJsonError(res, 500, err6 instanceof Error ? err6.message : String(err6));
5673
6502
  }
5674
6503
  }
5675
6504
  function requestQuery(req) {
@@ -5739,6 +6568,9 @@ async function handleProviders(req, res, method, rest, deps) {
5739
6568
  if (method === "POST" && rest.length === 4 && rest[1] === "keys" && rest[3] === "enabled") {
5740
6569
  return await handleToggleProviderKey(req, res, rest[0], rest[2], cfg, deps);
5741
6570
  }
6571
+ if (method === "POST" && rest.length === 5 && rest[1] === "keys" && rest[3] === "quota" && rest[4] === "refresh") {
6572
+ return await handleProviderKeyQuotaRefresh(res, rest[0], rest[2], cfg, deps);
6573
+ }
5742
6574
  if (method === "PUT" && rest.length === 3 && rest[1] === "keys") {
5743
6575
  return await handleUpdateProviderKey(req, res, rest[0], rest[2], cfg, deps);
5744
6576
  }
@@ -5837,7 +6669,7 @@ async function handleDiscoverModels(res, id, cfg) {
5837
6669
  try {
5838
6670
  const headers = { Accept: "application/json" };
5839
6671
  if (resolvedKey) headers["Authorization"] = `Bearer ${resolvedKey}`;
5840
- const response = await fetchUpstream2(url, { method: "GET", headers }, { providerId: "byo" });
6672
+ const response = await fetchUpstream5(url, { method: "GET", headers }, { providerId: "byo" });
5841
6673
  if (!response.ok) {
5842
6674
  const text = await response.text().catch(() => "");
5843
6675
  let message = text.slice(0, 300);
@@ -5854,8 +6686,8 @@ async function handleDiscoverModels(res, id, cfg) {
5854
6686
  const data = await response.json();
5855
6687
  const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
5856
6688
  return writeJson4(res, 200, { models });
5857
- } catch (err5) {
5858
- const message = err5 instanceof Error ? err5.message : String(err5);
6689
+ } catch (err6) {
6690
+ const message = err6 instanceof Error ? err6.message : String(err6);
5859
6691
  return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
5860
6692
  }
5861
6693
  }
@@ -5896,7 +6728,7 @@ async function handleTestModel(req, res, id, cfg) {
5896
6728
  }
5897
6729
  const startedAt = Date.now();
5898
6730
  try {
5899
- const response = await fetchUpstream2(
6731
+ const response = await fetchUpstream5(
5900
6732
  url,
5901
6733
  { method: "POST", headers, body: JSON.stringify(payload) },
5902
6734
  { providerId: "byo" }
@@ -5918,8 +6750,8 @@ async function handleTestModel(req, res, id, cfg) {
5918
6750
  latencyMs,
5919
6751
  sample: extractSampleText(text, row.apiFormat)
5920
6752
  });
5921
- } catch (err5) {
5922
- const message = err5 instanceof Error ? err5.message : String(err5);
6753
+ } catch (err6) {
6754
+ const message = err6 instanceof Error ? err6.message : String(err6);
5923
6755
  return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
5924
6756
  }
5925
6757
  }
@@ -5961,7 +6793,30 @@ async function handleProviderKeys(res, id, cfg, deps) {
5961
6793
  const row = cfg.providers.find((p) => p.id === id);
5962
6794
  if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
5963
6795
  const cooldown = await deps.apiKeyPool.getKeyHealth(id);
5964
- return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
6796
+ const views = toPoolKeyView(row, cooldown, deps);
6797
+ if (deps.providerKeyQuota) {
6798
+ const quotas = await Promise.allSettled(
6799
+ views.map((view) => deps.providerKeyQuota.quotaFor(row, view.id))
6800
+ );
6801
+ views.forEach((view, index) => {
6802
+ const settled = quotas[index];
6803
+ if (settled.status === "fulfilled" && settled.value) view.quota = settled.value;
6804
+ });
6805
+ }
6806
+ return writeJson4(res, 200, { keys: views });
6807
+ }
6808
+ async function handleProviderKeyQuotaRefresh(res, id, keyId, cfg, deps) {
6809
+ if (!deps.providerKeyQuota) return writeJsonError(res, 501, "provider key quota is not available");
6810
+ if (!id || !keyId) return writeJsonError(res, 400, "provider id and key id required in path");
6811
+ const row = cfg.providers.find((p) => p.id === id);
6812
+ if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
6813
+ try {
6814
+ const quota = await deps.providerKeyQuota.quotaFor(row, keyId, { force: true });
6815
+ if (!quota) return writeJsonError(res, 404, `no quota endpoint for key '${keyId}'`);
6816
+ return writeJson4(res, 200, { quota });
6817
+ } catch {
6818
+ return writeJsonError(res, 502, "quota refresh failed");
6819
+ }
5965
6820
  }
5966
6821
  function parsePoolKeyInput(body, existing) {
5967
6822
  const out = {};
@@ -6706,12 +7561,12 @@ async function handleAccounts(req, res, method, rest, deps) {
6706
7561
  }
6707
7562
  return writeJson4(res, 200, { ok: true, affected: result.affected });
6708
7563
  }
6709
- if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
6710
- const result = handleCodexOAuthStatus(rest[2], deps);
7564
+ if (method === "GET" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[3] === "status") {
7565
+ const result = rest[0] === "codex" ? handleCodexOAuthStatus(rest[2], deps) : handleKimiOAuthStatus(rest[2], deps);
6711
7566
  return writeJson4(res, result.status, result.body);
6712
7567
  }
6713
- if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
6714
- const result = handleCodexOAuthCancel(rest[2], deps);
7568
+ if (method === "DELETE" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[2]) {
7569
+ const result = rest[0] === "codex" ? handleCodexOAuthCancel(rest[2], deps) : handleKimiOAuthCancel(rest[2], deps);
6715
7570
  return writeJson4(res, result.status, result.body);
6716
7571
  }
6717
7572
  if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
@@ -6764,7 +7619,15 @@ async function handleAccounts(req, res, method, rest, deps) {
6764
7619
  return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
6765
7620
  }
6766
7621
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
6767
- const result = providerId === "codex" ? handleCodexOAuthStart(deps) : handleOAuthStart(providerId, deps);
7622
+ if (providerId === "codex") {
7623
+ const result2 = handleCodexOAuthStart(deps);
7624
+ return writeJson4(res, result2.status, result2.body);
7625
+ }
7626
+ if (providerId === "kimi") {
7627
+ const result2 = await handleKimiOAuthStart(deps);
7628
+ return writeJson4(res, result2.status, result2.body);
7629
+ }
7630
+ const result = handleOAuthStart(providerId, deps);
6768
7631
  return writeJson4(res, result.status, result.body);
6769
7632
  }
6770
7633
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
@@ -7258,12 +8121,12 @@ async function handlePlayground(req, res, method, deps) {
7258
8121
  const payload = body["body"];
7259
8122
  const status = deps.outboundApiServer.getStatus();
7260
8123
  if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
7261
- const path2 = resolvePlaygroundPath(endpoint, isRecord3(payload) ? payload : {});
8124
+ const path2 = resolvePlaygroundPath(endpoint, isRecord4(payload) ? payload : {});
7262
8125
  if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
7263
8126
  const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
7264
8127
  await proxyToOutbound(res, status.port, path2, key, upstreamBody);
7265
8128
  }
7266
- function isRecord3(v) {
8129
+ function isRecord4(v) {
7267
8130
  return !!v && typeof v === "object" && !Array.isArray(v);
7268
8131
  }
7269
8132
  function proxyToOutbound(res, outboundPort, path2, key, body) {
@@ -7292,8 +8155,8 @@ function proxyToOutbound(res, outboundPort, path2, key, body) {
7292
8155
  });
7293
8156
  }
7294
8157
  );
7295
- upstream.on("error", (err5) => {
7296
- if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err5.message}`);
8158
+ upstream.on("error", (err6) => {
8159
+ if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err6.message}`);
7297
8160
  else res.end();
7298
8161
  resolve10();
7299
8162
  });
@@ -7398,7 +8261,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
7398
8261
  }
7399
8262
 
7400
8263
  // src/admin/version.ts
7401
- var DAEMON_VERSION = true ? "0.2.1" : "0.0.0-dev";
8264
+ var DAEMON_VERSION = true ? "0.3.1" : "0.0.0-dev";
7402
8265
 
7403
8266
  // src/admin/AdminServer.ts
7404
8267
  var LOOPBACK_ADDR = "127.0.0.1";
@@ -7441,13 +8304,13 @@ var AdminServer = class {
7441
8304
  const server = http2.createServer((req, res) => {
7442
8305
  this.onRequest(req, res);
7443
8306
  });
7444
- const onError = (err5) => {
7445
- if (err5.code === "EADDRINUSE" && port !== 0) {
8307
+ const onError = (err6) => {
8308
+ if (err6.code === "EADDRINUSE" && port !== 0) {
7446
8309
  server.removeListener("error", onError);
7447
8310
  this.listen(bindAddr, 0).then(resolve10, reject);
7448
8311
  return;
7449
8312
  }
7450
- reject(err5);
8313
+ reject(err6);
7451
8314
  };
7452
8315
  server.on("error", onError);
7453
8316
  server.listen(port, bindAddr, () => {
@@ -7465,8 +8328,8 @@ var AdminServer = class {
7465
8328
  }
7466
8329
  /** Per-request handler: auth gate (when a token is set) → routing. */
7467
8330
  onRequest(req, res) {
7468
- void this.dispatch(req, res).catch((err5) => {
7469
- const message = err5 instanceof Error ? err5.message : String(err5);
8331
+ void this.dispatch(req, res).catch((err6) => {
8332
+ const message = err6 instanceof Error ? err6.message : String(err6);
7470
8333
  this.deps.logger.error("[AdminServer] unhandled error:", message);
7471
8334
  if (!res.headersSent) {
7472
8335
  res.writeHead(500, { "Content-Type": "application/json" });
@@ -7730,18 +8593,18 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
7730
8593
  return;
7731
8594
  }
7732
8595
  signal?.addEventListener("abort", abort, { once: true });
7733
- server.on("error", (err5) => {
8596
+ server.on("error", (err6) => {
7734
8597
  if (settled) return;
7735
8598
  settled = true;
7736
8599
  clearTimeout(timer);
7737
- if (err5.code === "EADDRINUSE") {
8600
+ if (err6.code === "EADDRINUSE") {
7738
8601
  reject(
7739
8602
  new Error(
7740
8603
  `login: cannot bind ${LOOPBACK_HOST}:${LOOPBACK_PORT} (address in use) \u2014 another codex login or process is holding the port`
7741
8604
  )
7742
8605
  );
7743
8606
  } else {
7744
- reject(err5);
8607
+ reject(err6);
7745
8608
  }
7746
8609
  });
7747
8610
  const timer = setTimeout(() => {
@@ -7816,6 +8679,411 @@ function createPoolKeysLoader(getProviderRow, autoDisabled) {
7816
8679
  };
7817
8680
  }
7818
8681
 
8682
+ // src/allowance/ProviderKeyQuotaService.ts
8683
+ import { fetchUpstream as fetchUpstream6 } from "@omnicross/core/pipeline/upstreamFetch";
8684
+
8685
+ // src/allowance/ProviderKeyQuota.ts
8686
+ var MINUTE_MS2 = 6e4;
8687
+ var HOUR_MS2 = 60 * MINUTE_MS2;
8688
+ var DAY_MS2 = 24 * HOUR_MS2;
8689
+ var WEEK_MS = 7 * DAY_MS2;
8690
+ var MONTH_MS = 30 * DAY_MS2;
8691
+ function finiteNumber3(value) {
8692
+ if (value === null || value === void 0 || value === "") return void 0;
8693
+ const parsed = typeof value === "number" ? value : Number(value);
8694
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
8695
+ }
8696
+ function finitePercent4(value) {
8697
+ const parsed = finiteNumber3(value);
8698
+ return parsed !== void 0 && parsed <= 100 ? parsed : null;
8699
+ }
8700
+ function isoInstant3(value) {
8701
+ if (typeof value === "string" && value.trim()) {
8702
+ const time = Date.parse(value);
8703
+ if (Number.isFinite(time)) return new Date(time).toISOString();
8704
+ }
8705
+ const numeric = finiteNumber3(value);
8706
+ if (numeric !== void 0 && numeric > 1e9) {
8707
+ const ms = numeric > 1e12 ? numeric : numeric * 1e3;
8708
+ return new Date(ms).toISOString();
8709
+ }
8710
+ return void 0;
8711
+ }
8712
+ function secondsUntil5(instant, now) {
8713
+ if (!instant) return void 0;
8714
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
8715
+ }
8716
+ function isRecord5(value) {
8717
+ return !!value && typeof value === "object" && !Array.isArray(value);
8718
+ }
8719
+ function detectProviderKeyQuotaAdapter(baseUrl) {
8720
+ if (!baseUrl) return null;
8721
+ let url;
8722
+ try {
8723
+ url = new URL(baseUrl);
8724
+ } catch {
8725
+ return null;
8726
+ }
8727
+ const host = url.hostname.toLowerCase();
8728
+ const path2 = url.pathname.toLowerCase();
8729
+ if ((host === "api.z.ai" || host === "open.bigmodel.cn") && path2.includes("/coding")) {
8730
+ return "zai";
8731
+ }
8732
+ if ((host === "api.minimax.io" || host === "api.minimaxi.com") && // Token Plan rides the plain openai `/v1` (chat completions) surface; the
8733
+ // anthropic `/anthropic` rows are excluded (their usage impl is unverified).
8734
+ (path2 === "/v1" || path2 === "/v1/" || path2 === "" || path2 === "/")) {
8735
+ return "minimax-token-plan";
8736
+ }
8737
+ if (host === "api.code.umans.ai") return "umans";
8738
+ if (host === "api.synthetic.new") return "synthetic";
8739
+ return null;
8740
+ }
8741
+ function providerKeyQuotaUrl(adapter, baseUrl) {
8742
+ const origin = new URL(baseUrl).origin;
8743
+ if (adapter === "zai") return `${origin}/api/monitor/usage/quota/limit`;
8744
+ if (adapter === "minimax-token-plan") return `${origin}/v1/token_plan/remains`;
8745
+ if (adapter === "umans") return `${origin}/v1/usage`;
8746
+ return `${origin}/v2/quotas`;
8747
+ }
8748
+ function providerKeyQuotaAuthHeader(adapter, key) {
8749
+ return adapter === "zai" ? key : `Bearer ${key}`;
8750
+ }
8751
+ function zaiWindowDurationMs(item) {
8752
+ const count = item.number !== void 0 && item.number > 0 ? item.number : 1;
8753
+ switch (item.unit) {
8754
+ case 3:
8755
+ return count * HOUR_MS2;
8756
+ case 4:
8757
+ return count * DAY_MS2;
8758
+ case 5:
8759
+ return count * MONTH_MS;
8760
+ case 6:
8761
+ return WEEK_MS;
8762
+ default:
8763
+ return void 0;
8764
+ }
8765
+ }
8766
+ function zaiWindowIdLabel(durationMs) {
8767
+ if (durationMs === WEEK_MS) return { id: "seven-day", label: "7 days" };
8768
+ if (durationMs === 5 * HOUR_MS2) return { id: "five-hour", label: "5 hours" };
8769
+ if (durationMs === MONTH_MS) return { id: "thirty-day", label: "30 days" };
8770
+ if (durationMs !== void 0 && durationMs % DAY_MS2 === 0) {
8771
+ const days = durationMs / DAY_MS2;
8772
+ return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}` };
8773
+ }
8774
+ if (durationMs !== void 0 && durationMs % HOUR_MS2 === 0) {
8775
+ const hours = durationMs / HOUR_MS2;
8776
+ return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}` };
8777
+ }
8778
+ return { id: "quota", label: "Quota" };
8779
+ }
8780
+ function parseZaiQuotaPayload(payload, now) {
8781
+ if (!isRecord5(payload)) return null;
8782
+ const data = isRecord5(payload["data"]) ? payload["data"] : payload;
8783
+ if (payload["success"] === false) return null;
8784
+ const limits = Array.isArray(data["limits"]) ? data["limits"] : [];
8785
+ const byWindow = /* @__PURE__ */ new Map();
8786
+ for (const raw of limits) {
8787
+ if (!isRecord5(raw)) continue;
8788
+ const item = raw;
8789
+ if (item.type === void 0) continue;
8790
+ const details = raw["usageDetails"];
8791
+ if (Array.isArray(details) && details.some((d) => isRecord5(d) && d["modelCode"] === "zread")) {
8792
+ continue;
8793
+ }
8794
+ const durationMs = zaiWindowDurationMs(item);
8795
+ const { id, label } = zaiWindowIdLabel(durationMs);
8796
+ const limit = finiteNumber3(item.usage);
8797
+ const used = finiteNumber3(item.currentValue);
8798
+ const fromAbsolute = limit !== void 0 && used !== void 0 && limit > 0 ? Math.min(100, used / limit * 100) : void 0;
8799
+ const fromPercentage = finitePercent4(item.percentage) ?? void 0;
8800
+ const usedPercent = fromAbsolute !== void 0 ? Math.round(fromAbsolute * 10) / 10 : fromPercentage;
8801
+ if (usedPercent === void 0) continue;
8802
+ const resetsAt = isoInstant3(item.nextResetTime);
8803
+ const candidate = {
8804
+ id,
8805
+ label,
8806
+ scope: "all",
8807
+ usedPercent,
8808
+ ...durationMs !== void 0 ? { windowMinutes: Math.round(durationMs / MINUTE_MS2) } : {},
8809
+ ...resetsAt !== void 0 ? { resetsAt } : {},
8810
+ remainingSeconds: secondsUntil5(resetsAt, now),
8811
+ state: "fresh"
8812
+ };
8813
+ const existing = byWindow.get(id);
8814
+ if (!existing || (candidate.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
8815
+ byWindow.set(id, candidate);
8816
+ }
8817
+ }
8818
+ const windows = [...byWindow.values()].sort((a, b) => (a.windowMinutes ?? Number.POSITIVE_INFINITY) - (b.windowMinutes ?? Number.POSITIVE_INFINITY));
8819
+ return windows.length > 0 ? windows.slice(0, 4) : null;
8820
+ }
8821
+ var MINIMAX_STATUS_EXHAUSTED = 2;
8822
+ var MINIMAX_SHARED_BUCKET = "general";
8823
+ function parseMiniMaxBucket(value) {
8824
+ if (!isRecord5(value)) return null;
8825
+ const modelName = typeof value["model_name"] === "string" ? value["model_name"].trim() : "";
8826
+ if (!modelName) return null;
8827
+ const instant = (v) => {
8828
+ const n = finiteNumber3(v);
8829
+ return n !== void 0 && n > 1e9 ? n > 1e12 ? n : n * 1e3 : void 0;
8830
+ };
8831
+ return {
8832
+ modelName,
8833
+ intervalEnd: instant(value["end_time"]),
8834
+ intervalRemainingPercent: finiteNumber3(value["current_interval_remaining_percent"]),
8835
+ intervalStatus: finiteNumber3(value["current_interval_status"]),
8836
+ weeklyEnd: instant(value["weekly_end_time"]),
8837
+ weeklyRemainingPercent: finiteNumber3(value["current_weekly_remaining_percent"]),
8838
+ weeklyStatus: finiteNumber3(value["current_weekly_status"])
8839
+ };
8840
+ }
8841
+ function minimaxWindow(id, label, windowMinutes, resetsAtMs, remainingPercent, status, now) {
8842
+ const usedPercent = status === MINIMAX_STATUS_EXHAUSTED ? 100 : remainingPercent !== void 0 ? Math.round((100 - remainingPercent) * 10) / 10 : null;
8843
+ const resetsAt = resetsAtMs !== void 0 ? new Date(resetsAtMs).toISOString() : void 0;
8844
+ return {
8845
+ id,
8846
+ label,
8847
+ scope: "all",
8848
+ usedPercent,
8849
+ ...windowMinutes !== void 0 ? { windowMinutes } : {},
8850
+ ...resetsAt !== void 0 ? { resetsAt } : {},
8851
+ remainingSeconds: secondsUntil5(resetsAt, now),
8852
+ state: usedPercent !== null ? "fresh" : "unavailable"
8853
+ };
8854
+ }
8855
+ function parseMiniMaxTokenPlanPayload(payload, now) {
8856
+ if (!isRecord5(payload)) return null;
8857
+ const baseResp = payload["base_resp"];
8858
+ if (!isRecord5(baseResp) || baseResp["status_code"] !== 0) return null;
8859
+ const buckets = Array.isArray(payload["model_remains"]) ? payload["model_remains"] : [];
8860
+ let general = null;
8861
+ for (const raw of buckets) {
8862
+ const bucket = parseMiniMaxBucket(raw);
8863
+ if (bucket?.modelName === MINIMAX_SHARED_BUCKET) {
8864
+ general = bucket;
8865
+ break;
8866
+ }
8867
+ }
8868
+ if (!general) return null;
8869
+ return [
8870
+ minimaxWindow(
8871
+ "five-hour",
8872
+ "5 hours",
8873
+ 5 * 60,
8874
+ general.intervalEnd,
8875
+ general.intervalRemainingPercent,
8876
+ general.intervalStatus,
8877
+ now
8878
+ ),
8879
+ minimaxWindow(
8880
+ "seven-day",
8881
+ "7 days",
8882
+ Math.round(WEEK_MS / MINUTE_MS2),
8883
+ general.weeklyEnd,
8884
+ general.weeklyRemainingPercent,
8885
+ general.weeklyStatus,
8886
+ now
8887
+ )
8888
+ ];
8889
+ }
8890
+ function parseUmansUsagePayload(payload, now) {
8891
+ if (!isRecord5(payload)) return null;
8892
+ const limits = isRecord5(payload["limits"]) ? payload["limits"] : void 0;
8893
+ const requests = limits && isRecord5(limits["requests"]) ? limits["requests"] : void 0;
8894
+ const usage = isRecord5(payload["usage"]) ? payload["usage"] : void 0;
8895
+ const window = isRecord5(payload["window"]) ? payload["window"] : void 0;
8896
+ const hardCap = finiteNumber3(requests?.["hard_cap"]);
8897
+ const softLimit = finiteNumber3(requests?.["limit"]);
8898
+ const requestsInWindow = finiteNumber3(usage?.["requests_in_window"]);
8899
+ const weightedInWindow = finiteNumber3(usage?.["weighted_in_window"]);
8900
+ const resetsAt = isoInstant3(window?.["resets_at"]);
8901
+ let usedPercent = null;
8902
+ if (hardCap !== void 0 && hardCap > 0 && requestsInWindow !== void 0) {
8903
+ usedPercent = Math.round(Math.min(100, requestsInWindow / hardCap * 100) * 10) / 10;
8904
+ } else if (softLimit !== void 0 && softLimit > 0 && weightedInWindow !== void 0) {
8905
+ usedPercent = Math.round(Math.min(100, weightedInWindow / softLimit * 100) * 10) / 10;
8906
+ }
8907
+ if (usedPercent === null && resetsAt === void 0) return null;
8908
+ return [
8909
+ {
8910
+ id: "five-hour",
8911
+ label: "5 hours",
8912
+ scope: "all",
8913
+ usedPercent,
8914
+ windowMinutes: 5 * 60,
8915
+ ...resetsAt !== void 0 ? { resetsAt } : {},
8916
+ remainingSeconds: secondsUntil5(resetsAt, now),
8917
+ state: "fresh"
8918
+ }
8919
+ ];
8920
+ }
8921
+ function parseSyntheticQuotasPayload(payload, now) {
8922
+ if (!isRecord5(payload)) return null;
8923
+ const fiveHour = isRecord5(payload["rollingFiveHourLimit"]) ? payload["rollingFiveHourLimit"] : void 0;
8924
+ const weekly = isRecord5(payload["weeklyTokenLimit"]) ? payload["weeklyTokenLimit"] : void 0;
8925
+ const windows = [];
8926
+ if (fiveHour) {
8927
+ const max = finiteNumber3(fiveHour["max"]);
8928
+ const remaining = finiteNumber3(fiveHour["remaining"]);
8929
+ const usedPercent = max !== void 0 && max > 0 && remaining !== void 0 ? Math.round(Math.min(100, (max - remaining) / max * 100) * 10) / 10 : null;
8930
+ const resetsAt = isoInstant3(fiveHour["nextTickAt"]);
8931
+ windows.push({
8932
+ id: "five-hour",
8933
+ label: "5 hours",
8934
+ scope: "all",
8935
+ usedPercent,
8936
+ windowMinutes: 5 * 60,
8937
+ ...resetsAt !== void 0 ? { resetsAt } : {},
8938
+ remainingSeconds: secondsUntil5(resetsAt, now),
8939
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
8940
+ });
8941
+ }
8942
+ if (weekly) {
8943
+ const percentRemaining = finiteNumber3(weekly["percentRemaining"]);
8944
+ const usedPercent = percentRemaining !== void 0 ? Math.round(Math.min(100, Math.max(0, 100 - percentRemaining)) * 10) / 10 : null;
8945
+ const resetsAt = isoInstant3(weekly["nextRegenAt"]);
8946
+ windows.push({
8947
+ id: "seven-day",
8948
+ label: "7 days",
8949
+ scope: "all",
8950
+ usedPercent,
8951
+ windowMinutes: 7 * 24 * 60,
8952
+ ...resetsAt !== void 0 ? { resetsAt } : {},
8953
+ remainingSeconds: secondsUntil5(resetsAt, now),
8954
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
8955
+ });
8956
+ }
8957
+ return windows.length > 0 ? windows : null;
8958
+ }
8959
+
8960
+ // src/allowance/ProviderKeyQuotaService.ts
8961
+ function parseQuotaPayload(adapter, payload, now) {
8962
+ switch (adapter) {
8963
+ case "zai":
8964
+ return parseZaiQuotaPayload(payload, now);
8965
+ case "minimax-token-plan":
8966
+ return parseMiniMaxTokenPlanPayload(payload, now);
8967
+ case "umans":
8968
+ return parseUmansUsagePayload(payload, now);
8969
+ case "synthetic":
8970
+ return parseSyntheticQuotasPayload(payload, now);
8971
+ }
8972
+ }
8973
+ var PROVIDER_KEY_QUOTA_CACHE_MS = 5 * 6e4;
8974
+ function resolvedBaseUrl(row) {
8975
+ const modes = row.apiModes ?? [];
8976
+ const selected = row.selectedApiModeId ? modes.find((mode) => mode.id === row.selectedApiModeId) : void 0;
8977
+ const fallback = modes[0];
8978
+ const modeBase = selected?.baseUrl ?? fallback?.baseUrl;
8979
+ return modeBase ?? row.codingPlan?.baseUrl ?? row.baseUrl;
8980
+ }
8981
+ function rowKeyEntries(row) {
8982
+ const pool = (row.apiKeys ?? []).filter((entry) => entry.apiKey.length > 0);
8983
+ if (pool.length > 0) return pool.map((entry) => ({ id: entry.id, apiKey: entry.apiKey }));
8984
+ if (row.apiKey.length > 0) {
8985
+ return [{ id: `${row.id}:default`, apiKey: row.apiKey }];
8986
+ }
8987
+ return [];
8988
+ }
8989
+ var ProviderKeyQuotaService = class {
8990
+ constructor(box, fetchImpl = (url, init) => fetchUpstream6(url, init, { redactBodies: true }), now = Date.now) {
8991
+ this.box = box;
8992
+ this.fetchImpl = fetchImpl;
8993
+ this.now = now;
8994
+ }
8995
+ box;
8996
+ fetchImpl;
8997
+ now;
8998
+ cache = /* @__PURE__ */ new Map();
8999
+ inFlight = /* @__PURE__ */ new Map();
9000
+ /**
9001
+ * Quota for one key of a provider row, or `null` when the row has no quota
9002
+ * adapter / no such key. Cache-first; concurrent reads share one flight.
9003
+ */
9004
+ async quotaFor(row, keyId, options = {}) {
9005
+ const adapter = detectProviderKeyQuotaAdapter(resolvedBaseUrl(row));
9006
+ if (!adapter) return null;
9007
+ const entry = rowKeyEntries(row).find((candidate) => candidate.id === keyId);
9008
+ if (!entry) return null;
9009
+ const cacheKey = `${row.id}\0${keyId}`;
9010
+ const now = this.now();
9011
+ const cached = this.cache.get(cacheKey);
9012
+ if (!options.force && cached && Date.parse(cached.expiresAt) > now) return cached;
9013
+ const running = this.inFlight.get(cacheKey);
9014
+ if (running) return running;
9015
+ const promise = this.fetchQuota(adapter, row, entry.apiKey, cacheKey).catch((error) => {
9016
+ void error;
9017
+ const previous = this.cache.get(cacheKey);
9018
+ if (previous) {
9019
+ const degraded = {
9020
+ ...previous,
9021
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
9022
+ windows: previous.windows.map((window) => ({
9023
+ ...window,
9024
+ state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
9025
+ })),
9026
+ errorCode: "quota_request_failed"
9027
+ };
9028
+ this.cache.set(cacheKey, degraded);
9029
+ return degraded;
9030
+ }
9031
+ return null;
9032
+ }).finally(() => this.inFlight.delete(cacheKey));
9033
+ this.inFlight.set(cacheKey, promise);
9034
+ return promise;
9035
+ }
9036
+ /** Drop cached rows for a provider (key added/removed/rotated). */
9037
+ invalidateProvider(providerRowId) {
9038
+ for (const key of this.cache.keys()) {
9039
+ if (key.startsWith(`${providerRowId}\0`)) this.cache.delete(key);
9040
+ }
9041
+ }
9042
+ async fetchQuota(adapter, row, rawKey, cacheKey) {
9043
+ const baseUrl = resolvedBaseUrl(row);
9044
+ const url = providerKeyQuotaUrl(adapter, baseUrl);
9045
+ const key = this.box.decryptMaybe(rawKey);
9046
+ const now = this.now();
9047
+ const response = await this.fetchImpl(url, {
9048
+ method: "GET",
9049
+ headers: {
9050
+ Authorization: providerKeyQuotaAuthHeader(adapter, key),
9051
+ Accept: "application/json",
9052
+ "Content-Type": "application/json"
9053
+ },
9054
+ signal: AbortSignal.timeout(15e3)
9055
+ });
9056
+ if (response.status === 401 || response.status === 403) {
9057
+ const snapshot2 = {
9058
+ adapter,
9059
+ observedAt: new Date(now).toISOString(),
9060
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
9061
+ windows: [],
9062
+ errorCode: "quota_unauthorized"
9063
+ };
9064
+ this.cache.set(cacheKey, snapshot2);
9065
+ return snapshot2;
9066
+ }
9067
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
9068
+ let payload;
9069
+ try {
9070
+ payload = await response.json();
9071
+ } catch {
9072
+ throw new Error("invalid JSON");
9073
+ }
9074
+ const windows = parseQuotaPayload(adapter, payload, now);
9075
+ const snapshot = {
9076
+ adapter,
9077
+ observedAt: new Date(now).toISOString(),
9078
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
9079
+ windows: windows ?? [],
9080
+ ...windows ? {} : { errorCode: "quota_unavailable" }
9081
+ };
9082
+ this.cache.set(cacheKey, snapshot);
9083
+ return snapshot;
9084
+ }
9085
+ };
9086
+
7819
9087
  // src/commands/paths.ts
7820
9088
  import { dirname as dirname5, join as join5 } from "path";
7821
9089
  function defaultVouchersPath(configPath) {
@@ -13965,21 +15233,23 @@ function bucketLabel(bucketStartTs, bucket) {
13965
15233
  }
13966
15234
 
13967
15235
  // src/ports/JsonOutboundKeyDb.ts
15236
+ import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
15237
+ import {
15238
+ validateOutboundPermissions as validateOutboundPermissions3
15239
+ } from "@omnicross/core";
15240
+
15241
+ // src/ports/atomicFile.ts
13968
15242
  import { randomBytes as randomBytes11 } from "crypto";
13969
15243
  import {
13970
15244
  closeSync as closeSync7,
13971
15245
  existsSync as existsSync16,
13972
15246
  fsyncSync as fsyncSync7,
13973
15247
  openSync as openSync7,
13974
- readFileSync as readFileSync13,
13975
15248
  renameSync as renameSync9,
13976
15249
  unlinkSync as unlinkSync11,
13977
15250
  writeFileSync as writeFileSync12
13978
15251
  } from "fs";
13979
15252
  import { basename as basename8, dirname as dirname14, join as join16 } from "path";
13980
- import {
13981
- validateOutboundPermissions as validateOutboundPermissions3
13982
- } from "@omnicross/core";
13983
15253
  function atomicReplaceUtf8(targetPath, contents) {
13984
15254
  const tempPath = join16(
13985
15255
  dirname14(targetPath),
@@ -14009,6 +15279,8 @@ function atomicReplaceUtf8(targetPath, contents) {
14009
15279
  throw error;
14010
15280
  }
14011
15281
  }
15282
+
15283
+ // src/ports/JsonOutboundKeyDb.ts
14012
15284
  var JsonOutboundKeyDb = class {
14013
15285
  /**
14014
15286
  * @param secretBox OPTIONAL reversible-secret codec. When present, a created
@@ -14151,7 +15423,7 @@ var JsonOutboundKeyDb = class {
14151
15423
  }
14152
15424
  /** Read the key rows, tolerating a missing/corrupt file (→ empty list). */
14153
15425
  readRows() {
14154
- if (!existsSync16(this.keysPath)) return [];
15426
+ if (!existsSync17(this.keysPath)) return [];
14155
15427
  try {
14156
15428
  const parsed = JSON.parse(readFileSync13(this.keysPath, "utf8"));
14157
15429
  return Array.isArray(parsed) ? parsed : [];
@@ -14170,7 +15442,7 @@ function applyPolicyField(row, field, value) {
14170
15442
  }
14171
15443
 
14172
15444
  // src/ports/JsonPricingStore.ts
14173
- import { existsSync as existsSync17, readFileSync as readFileSync14, renameSync as renameSync10, rmSync as rmSync3, writeFileSync as writeFileSync13 } from "fs";
15445
+ import { existsSync as existsSync18, readFileSync as readFileSync14, renameSync as renameSync10, rmSync as rmSync3, writeFileSync as writeFileSync13 } from "fs";
14174
15446
  import { randomUUID as randomUUID5 } from "crypto";
14175
15447
  var JsonPricingStore = class {
14176
15448
  constructor(pricingPath) {
@@ -14185,7 +15457,7 @@ var JsonPricingStore = class {
14185
15457
  * otherwise unusable pricing table after a crash or manual file edit.
14186
15458
  */
14187
15459
  hasUsableSnapshot() {
14188
- if (!existsSync17(this.pricingPath)) return false;
15460
+ if (!existsSync18(this.pricingPath)) return false;
14189
15461
  try {
14190
15462
  const parsed = JSON.parse(readFileSync14(this.pricingPath, "utf8"));
14191
15463
  return Array.isArray(parsed) && parsed.some(isUsablePricingRow);
@@ -14300,7 +15572,7 @@ var JsonPricingStore = class {
14300
15572
  }
14301
15573
  /** Read the pricing rows, tolerating a missing/corrupt file (→ empty list). */
14302
15574
  readRows() {
14303
- if (!existsSync17(this.pricingPath)) return [];
15575
+ if (!existsSync18(this.pricingPath)) return [];
14304
15576
  try {
14305
15577
  const parsed = JSON.parse(readFileSync14(this.pricingPath, "utf8"));
14306
15578
  return Array.isArray(parsed) ? parsed : [];
@@ -14332,7 +15604,7 @@ function isUsablePricingRow(value) {
14332
15604
  }
14333
15605
 
14334
15606
  // src/pricing/PricingRefreshScheduler.ts
14335
- import { existsSync as existsSync18, readFileSync as readFileSync15, renameSync as renameSync11, writeFileSync as writeFileSync14 } from "fs";
15607
+ import { existsSync as existsSync19, readFileSync as readFileSync15, renameSync as renameSync11, writeFileSync as writeFileSync14 } from "fs";
14336
15608
  var EMPTY_STATE2 = {
14337
15609
  lastAttemptAt: null,
14338
15610
  lastSuccessAt: null,
@@ -14370,7 +15642,7 @@ var PricingRefreshScheduler = class {
14370
15642
  this.timer = null;
14371
15643
  }
14372
15644
  getState() {
14373
- if (!existsSync18(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
15645
+ if (!existsSync19(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
14374
15646
  try {
14375
15647
  const value = JSON.parse(readFileSync15(this.statePath, "utf8"));
14376
15648
  return {
@@ -14435,7 +15707,7 @@ function finiteOrNull(value) {
14435
15707
  }
14436
15708
 
14437
15709
  // src/ports/JsonVoucherDb.ts
14438
- import { existsSync as existsSync19, readFileSync as readFileSync16, writeFileSync as writeFileSync15 } from "fs";
15710
+ import { existsSync as existsSync20, readFileSync as readFileSync16, writeFileSync as writeFileSync15 } from "fs";
14439
15711
  var JsonVoucherDb = class {
14440
15712
  constructor(vouchersPath) {
14441
15713
  this.vouchersPath = vouchersPath;
@@ -14513,7 +15785,7 @@ var JsonVoucherDb = class {
14513
15785
  }
14514
15786
  /** Read the voucher rows, tolerating a missing/corrupt file (→ empty list). */
14515
15787
  readRows() {
14516
- if (!existsSync19(this.vouchersPath)) return [];
15788
+ if (!existsSync20(this.vouchersPath)) return [];
14517
15789
  try {
14518
15790
  const parsed = JSON.parse(readFileSync16(this.vouchersPath, "utf8"));
14519
15791
  return Array.isArray(parsed) ? parsed : [];
@@ -14527,16 +15799,17 @@ var JsonVoucherDb = class {
14527
15799
  };
14528
15800
 
14529
15801
  // src/ports/JsonSubscriptionCredentialStore.ts
14530
- import { existsSync as existsSync21, mkdirSync as mkdirSync6, readFileSync as readFileSync18, writeFileSync as writeFileSync16 } from "fs";
15802
+ import { existsSync as existsSync22, mkdirSync as mkdirSync6, readFileSync as readFileSync18, renameSync as renameSync12 } from "fs";
14531
15803
  import { dirname as dirname15 } from "path";
14532
15804
  import { getSharedAccountHealth as getSharedAccountHealth2 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
14533
15805
  import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling3 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
14534
- import { fetchUpstream as fetchUpstream3 } from "@omnicross/core/pipeline/upstreamFetch";
15806
+ import { fetchUpstream as fetchUpstream7 } from "@omnicross/core/pipeline/upstreamFetch";
14535
15807
  import { getSharedIdentityStore as getSharedIdentityStore2 } from "@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore";
14536
15808
  import {
14537
15809
  claudeOAuth as claudeOAuth2,
14538
15810
  codexOAuth as codexOAuth2,
14539
- geminiOAuth as geminiOAuth2
15811
+ geminiOAuth as geminiOAuth2,
15812
+ kimiOAuth as kimiOAuth2
14540
15813
  } from "@omnicross/subscriptions";
14541
15814
 
14542
15815
  // src/ports/account-sync.ts
@@ -14581,7 +15854,7 @@ function findDuplicateCredentialIds(accounts) {
14581
15854
  }
14582
15855
 
14583
15856
  // src/ports/external-cli-credentials.ts
14584
- import { existsSync as existsSync20, readFileSync as readFileSync17 } from "fs";
15857
+ import { existsSync as existsSync21, readFileSync as readFileSync17 } from "fs";
14585
15858
  import { homedir as homedir4 } from "os";
14586
15859
  import { join as join17 } from "path";
14587
15860
  function externalStorePath(provider, home = homedir4()) {
@@ -14634,7 +15907,7 @@ function parseCodexTokensEnvelope(raw) {
14634
15907
  }
14635
15908
  function readExternalCliCredentials(provider, home = homedir4()) {
14636
15909
  const path2 = externalStorePath(provider, home);
14637
- if (!existsSync20(path2)) return null;
15910
+ if (!existsSync21(path2)) return null;
14638
15911
  let raw;
14639
15912
  try {
14640
15913
  const parsed = JSON.parse(readFileSync17(path2, "utf8"));
@@ -14660,16 +15933,18 @@ var JsonSubscriptionCredentialStore = class {
14660
15933
  * as on relay refresh egresses from the SAME proxy IP as the
14661
15934
  * account's traffic. NOT used by any read/write path.
14662
15935
  */
14663
- constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials) {
15936
+ constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials, atomicReplace = atomicReplaceUtf8) {
14664
15937
  this.tokensPath = tokensPath;
14665
15938
  this.box = box;
14666
15939
  this.fetchImpl = fetchImpl;
14667
15940
  this.externalCliReader = externalCliReader;
15941
+ this.atomicReplace = atomicReplace;
14668
15942
  }
14669
15943
  tokensPath;
14670
15944
  box;
14671
15945
  fetchImpl;
14672
15946
  externalCliReader;
15947
+ atomicReplace;
14673
15948
  /**
14674
15949
  * The proxy-aware `FetchLike` for one refresh round-trip (upstream-proxy M1). A
14675
15950
  * TEST-injected `fetchImpl` is returned verbatim; otherwise the refresh routes
@@ -14683,7 +15958,7 @@ var JsonSubscriptionCredentialStore = class {
14683
15958
  * a plaintext token pair into `upstream-trace.jsonl`.
14684
15959
  */
14685
15960
  buildRefreshFetch(providerId, accountId) {
14686
- return this.fetchImpl ?? ((url, init) => fetchUpstream3(url, init, { providerId, accountId, redactBodies: true }));
15961
+ return this.fetchImpl ?? ((url, init) => fetchUpstream7(url, init, { providerId, accountId, redactBodies: true }));
14687
15962
  }
14688
15963
  /**
14689
15964
  * In-flight refresh coalescing. OAuth refresh tokens are
@@ -14724,7 +15999,7 @@ var JsonSubscriptionCredentialStore = class {
14724
15999
  * other hot reads. Never returns token material.
14725
16000
  */
14726
16001
  getAccountProxy(providerId, accountId) {
14727
- if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego") {
16002
+ if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego" && providerId !== "kimi") {
14728
16003
  return void 0;
14729
16004
  }
14730
16005
  return getAccountProxy(this.readConfig(), providerId, accountId);
@@ -14743,7 +16018,7 @@ var JsonSubscriptionCredentialStore = class {
14743
16018
  const fingerprintOn = identityStore.isEnabled();
14744
16019
  const now = Date.now();
14745
16020
  const out = {};
14746
- for (const provider of ["claude", "codex", "gemini", "opencodego"]) {
16021
+ for (const provider of ["claude", "codex", "gemini", "opencodego", "kimi"]) {
14747
16022
  const sanitized = sanitizeAccounts(config, provider);
14748
16023
  if (sanitized.length === 0) continue;
14749
16024
  for (const account of sanitized) {
@@ -14901,6 +16176,47 @@ var JsonSubscriptionCredentialStore = class {
14901
16176
  }
14902
16177
  });
14903
16178
  }
16179
+ /**
16180
+ * Refresh the Kimi Code (Moonshot) OAuth access token (device-flow grant).
16181
+ * Kimi ROTATES the refresh token, so the response's pair is written back
16182
+ * whole; the account's stable `deviceId` (fingerprint header input) is
16183
+ * preserved. The refresh call carries the CLI fingerprint headers. HONEST
16184
+ * `false` when no refresh_token.
16185
+ */
16186
+ async refreshKimiToken() {
16187
+ return this.coalesce("kimi:active", async () => {
16188
+ const config = this.readConfig();
16189
+ const active = getActiveAccount(config, "kimi");
16190
+ const kimi = active?.tokens;
16191
+ if (!active || !kimi?.refreshToken) return false;
16192
+ const capturedId = active.id;
16193
+ this.materializeMigration(config);
16194
+ const refreshFetch = this.buildRefreshFetch("kimi", capturedId);
16195
+ try {
16196
+ const result = await kimiOAuth2.refreshAccessToken(
16197
+ kimi.refreshToken,
16198
+ refreshFetch,
16199
+ kimiOAuth2.kimiFingerprintHeaders(kimi.deviceId)
16200
+ );
16201
+ const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
16202
+ const next = {
16203
+ ...kimi,
16204
+ accessToken: result.accessToken,
16205
+ refreshToken: result.refreshToken,
16206
+ expiresAt,
16207
+ status: "authorized",
16208
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
16209
+ errorMessage: void 0,
16210
+ syncWarning: void 0
16211
+ };
16212
+ this.writeBackById("kimi", capturedId, next);
16213
+ return true;
16214
+ } catch (error) {
16215
+ this.markExpiredById("kimi", capturedId, kimi, error);
16216
+ return false;
16217
+ }
16218
+ });
16219
+ }
14904
16220
  /**
14905
16221
  * Refresh a SPECIFIC managed account by id (background scheduler sweep and
14906
16222
  * account-pool resolution). It uses only that account's stored refresh
@@ -14953,7 +16269,7 @@ var JsonSubscriptionCredentialStore = class {
14953
16269
  }
14954
16270
  const oauth = account.tokens;
14955
16271
  if (!oauth.accessToken) return null;
14956
- if (providerId === "codex" || providerId === "gemini") {
16272
+ if (providerId === "codex" || providerId === "gemini" || providerId === "kimi") {
14957
16273
  const expiresAtMs = oauth.expiresAt ? Date.parse(oauth.expiresAt) : 0;
14958
16274
  const expiringSoon = expiresAtMs > 0 && Date.now() >= expiresAtMs - ACCOUNT_REFRESH_LEAD_MS;
14959
16275
  if (expiringSoon && oauth.refreshToken) {
@@ -15042,8 +16358,23 @@ var JsonSubscriptionCredentialStore = class {
15042
16358
  }
15043
16359
  /** Dispatch one OAuth refresh round-trip to the provider's shared flow. */
15044
16360
  async refreshUpstream(provider, refreshToken, accountId) {
16361
+ const refreshFetch = this.buildRefreshFetch(provider, accountId);
16362
+ if (provider === "kimi") {
16363
+ const account = accountId ? getAccountById(this.readConfig(), "kimi", accountId) : void 0;
16364
+ const deviceId = account?.tokens?.deviceId;
16365
+ const r2 = await kimiOAuth2.refreshAccessToken(
16366
+ refreshToken,
16367
+ refreshFetch,
16368
+ kimiOAuth2.kimiFingerprintHeaders(deviceId)
16369
+ );
16370
+ return {
16371
+ accessToken: r2.accessToken,
16372
+ refreshToken: r2.refreshToken,
16373
+ expiresAt: new Date(Date.now() + r2.expiresIn * 1e3).toISOString()
16374
+ };
16375
+ }
15045
16376
  const flow = provider === "claude" ? claudeOAuth2 : provider === "codex" ? codexOAuth2 : geminiOAuth2;
15046
- const r = await flow.refreshAccessToken(refreshToken, this.buildRefreshFetch(provider, accountId));
16377
+ const r = await flow.refreshAccessToken(refreshToken, refreshFetch);
15047
16378
  return {
15048
16379
  accessToken: r.accessToken,
15049
16380
  refreshToken: r.refreshToken,
@@ -15206,42 +16537,86 @@ var JsonSubscriptionCredentialStore = class {
15206
16537
  /** Write the merged config to disk as pretty JSON (mkdir parent if needed).
15207
16538
  * Encrypt-on-write: the token-material fields are encrypted (legacy plaintext
15208
16539
  * `enc:v1:`; already-`enc:`/`$ENV` untouched) before serializing, so any
15209
- * write incl. child 4's future refresh writes lands encrypted. */
16540
+ * write incl. child 4's future refresh writes lands encrypted.
16541
+ * ATOMIC: temp + fsync + rename (`atomicReplaceUtf8`) — a failed or
16542
+ * interrupted write discards only the temp file; the prior `tokens.json`
16543
+ * survives byte-equal (bare `writeFileSync` truncate-writes lost every
16544
+ * account on a mid-write failure, 2026-09-06). */
15210
16545
  persist(config) {
15211
16546
  mkdirSync6(dirname15(this.tokensPath), { recursive: true });
15212
16547
  const encrypted = encryptTokens(config, this.box);
15213
- writeFileSync16(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n", "utf8");
16548
+ this.atomicReplace(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
15214
16549
  }
15215
16550
  /**
15216
- * Read + parse `tokens.json`, tolerating a missing/corrupt file, then DECRYPT
15217
- * the token-material fields so every getter returns plaintext (the
15218
- * subscription bearer path is byte-identical).
16551
+ * Read + parse `tokens.json`, then DECRYPT the token-material fields so every
16552
+ * getter returns plaintext (the subscription bearer path is byte-identical).
16553
+ *
16554
+ * A MISSING file is a legitimate first-boot state → minimal `{ updatedAt: '' }`.
16555
+ * A file that EXISTS but cannot be parsed as a JSON object is CORRUPT →
16556
+ * `quarantineCorrupt` moves it aside (once) before the empty config is
16557
+ * returned, so the unreadable accounts survive for manual recovery.
15219
16558
  *
15220
- * The fs-read + JSON-parse tolerance is INSIDE the try (a missing or corrupt
15221
- * file empty `{ updatedAt: '' }`). The DECRYPT runs OUTSIDE the try, so a
15222
- * wrong/missing master key or a tampered `enc:` envelope FAILS FAST with the
15223
- * box's clear, secret-free error (secrets spec "/ UX":
15224
- * SHALL fail-fast, SHALL NOT a swallowed decrypt would report "no
15225
- * tokens" and silently send the WRONG bearer upstream 401). Mirrors
15226
- * `config.ts loadConfig`, which decrypts outside its parse try.
16559
+ * The DECRYPT runs OUTSIDE any try, so a wrong/missing master key or a
16560
+ * tampered `enc:` envelope FAILS FAST with the box's clear, secret-free
16561
+ * error (secrets spec "/ UX": SHALL fail-fast, SHALL NOT a swallowed
16562
+ * decrypt would report "no tokens" and silently send the WRONG bearer
16563
+ * upstream 401). Mirrors `config.ts loadConfig`, which decrypts outside
16564
+ * its parse try.
15227
16565
  */
15228
16566
  readConfig() {
15229
- if (!existsSync21(this.tokensPath)) return { updatedAt: "" };
16567
+ if (!existsSync22(this.tokensPath)) return { updatedAt: "" };
15230
16568
  let parsed;
15231
16569
  try {
15232
16570
  const raw = JSON.parse(readFileSync18(this.tokensPath, "utf8"));
15233
- parsed = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
16571
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
16572
+ return this.quarantineCorrupt("parsed JSON is not an object");
16573
+ }
16574
+ parsed = raw;
15234
16575
  } catch {
15235
- parsed = null;
16576
+ return this.quarantineCorrupt("unparseable JSON");
15236
16577
  }
15237
- if (!parsed) return { updatedAt: "" };
15238
16578
  const decrypted = decryptTokens(parsed, this.box);
15239
16579
  return migrateLazily(decrypted);
15240
16580
  }
16581
+ /** One-shot latch: a corrupt file is quarantined (or found unmovable) at
16582
+ * most once per process, so the hot read path never re-attempts or re-logs. */
16583
+ corruptQuarantined = false;
16584
+ /**
16585
+ * Quarantine a present-but-corrupt `tokens.json`, then treat it as empty.
16586
+ *
16587
+ * Renames the file to a sibling `tokens.json.corrupt-<stamp>` backup and
16588
+ * logs loudly (the daemon's stderr log; secret-free — reason + paths only).
16589
+ * The daemon KEEPS SERVING (API-key routing is unaffected; subscription
16590
+ * routing reports no credential, same as an absent file) while the corrupt
16591
+ * bytes survive for manual recovery — and, critically, the NEXT persist
16592
+ * (e.g. the user re-logging in) can no longer overwrite the only copy of
16593
+ * the old accounts, which is exactly how the 2026-09-06 incident turned a
16594
+ * recoverable truncated file into permanent account loss.
16595
+ *
16596
+ * Best-effort: if the rename fails (file locked, permissions), the corrupt
16597
+ * file is left in place and every later read still tolerates it as empty;
16598
+ * the latch still trips so the attempt + log happen exactly once.
16599
+ */
16600
+ quarantineCorrupt(reason) {
16601
+ if (!this.corruptQuarantined) {
16602
+ this.corruptQuarantined = true;
16603
+ const backup = `${this.tokensPath}.corrupt-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
16604
+ let moved = false;
16605
+ try {
16606
+ renameSync12(this.tokensPath, backup);
16607
+ moved = true;
16608
+ } catch {
16609
+ }
16610
+ console.error(
16611
+ `[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`)
16612
+ );
16613
+ }
16614
+ return { updatedAt: "" };
16615
+ }
15241
16616
  };
15242
16617
 
15243
16618
  // src/AccountHealthProbeScheduler.ts
15244
- import { fetchUpstream as fetchUpstream4 } from "@omnicross/core/pipeline/upstreamFetch";
16619
+ import { fetchUpstream as fetchUpstream8 } from "@omnicross/core/pipeline/upstreamFetch";
15245
16620
 
15246
16621
  // src/probe/CodexGenerationProbe.ts
15247
16622
  import {
@@ -15383,7 +16758,11 @@ var PROVIDER_PROBE_PLANS = {
15383
16758
  // billable/wrong endpoint). Upgrade to `{ kind:'upstream' }` once verified.
15384
16759
  codex: { kind: "local" },
15385
16760
  gemini: { kind: "local" },
15386
- opencodego: { kind: "local" }
16761
+ opencodego: { kind: "local" },
16762
+ // Kimi's `GET /coding/v1/usages` is a verified FREE authed GET (the allowance
16763
+ // collector uses it), but the probe path also needs the fingerprint headers —
16764
+ // keep the probe local until the collector covers the health surface.
16765
+ kimi: { kind: "local" }
15387
16766
  };
15388
16767
  function probePlanFor(providerId) {
15389
16768
  return PROVIDER_PROBE_PLANS[providerId] ?? { kind: "local" };
@@ -15405,7 +16784,7 @@ var AccountHealthProbeScheduler = class {
15405
16784
  this.logger = logger;
15406
16785
  this.config = config;
15407
16786
  this.now = opts.now ?? Date.now;
15408
- this.fetchImpl = opts.fetchImpl ?? fetchUpstream4;
16787
+ this.fetchImpl = opts.fetchImpl ?? fetchUpstream8;
15409
16788
  this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
15410
16789
  this.planFor = opts.planFor ?? probePlanFor;
15411
16790
  }
@@ -15749,13 +17128,13 @@ var AccountHealthSweeper = class {
15749
17128
  };
15750
17129
 
15751
17130
  // src/audit/AuditPruneSweeper.ts
15752
- import { createReadStream as createReadStream3, createWriteStream as createWriteStream2, existsSync as existsSync24, readdirSync as readdirSync6, rmSync as rmSync4, unlinkSync as unlinkSync13 } from "fs";
17131
+ import { createReadStream as createReadStream3, createWriteStream as createWriteStream2, existsSync as existsSync25, readdirSync as readdirSync6, rmSync as rmSync4, unlinkSync as unlinkSync13 } from "fs";
15753
17132
  import { join as join20 } from "path";
15754
17133
  import { pipeline } from "stream/promises";
15755
17134
  import { createGzip } from "zlib";
15756
17135
 
15757
17136
  // src/audit/auditDictionary.ts
15758
- import { existsSync as existsSync22, readdirSync as readdirSync4, readFileSync as readFileSync19, renameSync as renameSync12, unlinkSync as unlinkSync12, writeFileSync as writeFileSync17 } from "fs";
17137
+ import { existsSync as existsSync23, readdirSync as readdirSync4, readFileSync as readFileSync19, renameSync as renameSync13, unlinkSync as unlinkSync12, writeFileSync as writeFileSync16 } from "fs";
15759
17138
  import { join as join18 } from "path";
15760
17139
 
15761
17140
  // src/audit/auditBodyStore.ts
@@ -16013,9 +17392,9 @@ function chooseDictionary(anchors) {
16013
17392
  var EMPTY = { shards: 0, anchors: 0, savedBytes: 0 };
16014
17393
  function compactAuditDay(dayPath) {
16015
17394
  const bodiesPath = join18(dayPath, AUDIT_BODIES_DIR);
16016
- if (!existsSync22(bodiesPath)) return EMPTY;
17395
+ if (!existsSync23(bodiesPath)) return EMPTY;
16017
17396
  const dictPath = join18(bodiesPath, AUDIT_DICT_FILE);
16018
- if (existsSync22(dictPath) || existsSync22(`${dictPath}.gz`)) return EMPTY;
17397
+ if (existsSync23(dictPath) || existsSync23(`${dictPath}.gz`)) return EMPTY;
16019
17398
  const shardFiles = plainShards(bodiesPath);
16020
17399
  if (shardFiles.length < 2) return EMPTY;
16021
17400
  const loaded = /* @__PURE__ */ new Map();
@@ -16040,7 +17419,7 @@ function compactAuditDay(dayPath) {
16040
17419
  ts: 0,
16041
17420
  req: { base: null, anchor: "dict", pre: 0, suf: 0, ins: dictionary }
16042
17421
  };
16043
- writeFileSync17(dictPath, JSON.stringify(dictEntry) + "\n", "utf8");
17422
+ writeFileSync16(dictPath, JSON.stringify(dictEntry) + "\n", "utf8");
16044
17423
  const result = { shards: 0, anchors: 0, savedBytes: 0 };
16045
17424
  for (const [file, entries] of loaded) {
16046
17425
  let changed = false;
@@ -16060,11 +17439,11 @@ function compactAuditDay(dayPath) {
16060
17439
  const target = join18(bodiesPath, file);
16061
17440
  const temp = `${target}.compacting`;
16062
17441
  try {
16063
- writeFileSync17(temp, rewritten.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
16064
- renameSync12(temp, target);
17442
+ writeFileSync16(temp, rewritten.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
17443
+ renameSync13(temp, target);
16065
17444
  } catch {
16066
17445
  try {
16067
- if (existsSync22(temp)) unlinkSync12(temp);
17446
+ if (existsSync23(temp)) unlinkSync12(temp);
16068
17447
  } catch {
16069
17448
  }
16070
17449
  continue;
@@ -16083,7 +17462,7 @@ function compactAuditDay(dayPath) {
16083
17462
  }
16084
17463
  function compactAllClosedAuditDays(auditDir, now = Date.now) {
16085
17464
  const run = { days: 0, shards: 0, savedBytes: 0 };
16086
- if (!existsSync22(auditDir)) return run;
17465
+ if (!existsSync23(auditDir)) return run;
16087
17466
  const today = auditDayDirName(now());
16088
17467
  let names;
16089
17468
  try {
@@ -16108,11 +17487,11 @@ function compactAllClosedAuditDays(auditDir, now = Date.now) {
16108
17487
  // src/audit/auditStats.ts
16109
17488
  import {
16110
17489
  createReadStream as createReadStream2,
16111
- existsSync as existsSync23,
17490
+ existsSync as existsSync24,
16112
17491
  readFileSync as readFileSync20,
16113
17492
  readdirSync as readdirSync5,
16114
17493
  statSync as statSync5,
16115
- writeFileSync as writeFileSync18
17494
+ writeFileSync as writeFileSync17
16116
17495
  } from "fs";
16117
17496
  import { basename as basename9, dirname as dirname16, join as join19 } from "path";
16118
17497
  var SIDECAR_VERSION = 1;
@@ -16122,7 +17501,7 @@ function auditStatsFileName(auditFile) {
16122
17501
  return auditFile.replace(/\.jsonl$/, ".stats.json");
16123
17502
  }
16124
17503
  function readPersisted(path2) {
16125
- if (!existsSync23(path2)) return null;
17504
+ if (!existsSync24(path2)) return null;
16126
17505
  try {
16127
17506
  const value = JSON.parse(readFileSync20(path2, "utf8"));
16128
17507
  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)) {
@@ -16154,7 +17533,7 @@ function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfte
16154
17533
  minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
16155
17534
  maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
16156
17535
  };
16157
- writeFileSync18(statsPath, JSON.stringify(next), "utf8");
17536
+ writeFileSync17(statsPath, JSON.stringify(next), "utf8");
16158
17537
  }
16159
17538
  function queryCovers(stats, from, to) {
16160
17539
  return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
@@ -16265,7 +17644,7 @@ function mergePersistedStats(previous, appended) {
16265
17644
  };
16266
17645
  }
16267
17646
  async function readAuditStats(auditDir, query2 = {}) {
16268
- if (!existsSync23(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
17647
+ if (!existsSync24(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
16269
17648
  const from = typeof query2.from === "number" ? query2.from : -Infinity;
16270
17649
  const to = typeof query2.to === "number" ? query2.to : Infinity;
16271
17650
  let sources;
@@ -16278,7 +17657,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16278
17657
  auditPath: join19(auditDir, name),
16279
17658
  statsPath: join19(auditDir, auditStatsFileName(name))
16280
17659
  }
16281
- ).filter((source) => existsSync23(source.auditPath));
17660
+ ).filter((source) => existsSync24(source.auditPath));
16282
17661
  } catch {
16283
17662
  return { requestCount: 0, errorCount: 0, complete: false };
16284
17663
  }
@@ -16304,7 +17683,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16304
17683
  total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
16305
17684
  total.complete = total.complete && scanned.filtered.complete;
16306
17685
  const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
16307
- if (current.complete) writeFileSync18(statsPath, JSON.stringify(current), "utf8");
17686
+ if (current.complete) writeFileSync17(statsPath, JSON.stringify(current), "utf8");
16308
17687
  } catch {
16309
17688
  total.complete = false;
16310
17689
  }
@@ -16313,7 +17692,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16313
17692
  }
16314
17693
 
16315
17694
  // src/audit/AuditPruneSweeper.ts
16316
- var DAY_MS = 24 * 60 * 6e4;
17695
+ var DAY_MS3 = 24 * 60 * 6e4;
16317
17696
  var SWEEP_INTERVAL_MS2 = 60 * 6e4;
16318
17697
  var ARCHIVE_BATCH = 64;
16319
17698
  var AuditPruneSweeper = class {
@@ -16376,8 +17755,8 @@ var AuditPruneSweeper = class {
16376
17755
  if (!this.config.enabled || this.sweeping) return 0;
16377
17756
  this.sweeping = true;
16378
17757
  try {
16379
- if (!existsSync24(this.auditDir)) return 0;
16380
- const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS;
17758
+ if (!existsSync25(this.auditDir)) return 0;
17759
+ const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS3;
16381
17760
  let removed = 0;
16382
17761
  for (const name of readdirSync6(this.auditDir)) {
16383
17762
  const dateMs = auditFileDateMs(name);
@@ -16388,7 +17767,7 @@ var AuditPruneSweeper = class {
16388
17767
  } else {
16389
17768
  unlinkSync13(join20(this.auditDir, name));
16390
17769
  const statsPath = join20(this.auditDir, auditStatsFileName(name));
16391
- if (existsSync24(statsPath)) unlinkSync13(statsPath);
17770
+ if (existsSync25(statsPath)) unlinkSync13(statsPath);
16392
17771
  }
16393
17772
  removed += 1;
16394
17773
  } catch (error) {
@@ -16418,7 +17797,7 @@ var AuditPruneSweeper = class {
16418
17797
  if (!this.config.enabled || this.archiving) return 0;
16419
17798
  this.archiving = true;
16420
17799
  try {
16421
- if (!existsSync24(this.auditDir)) return 0;
17800
+ if (!existsSync25(this.auditDir)) return 0;
16422
17801
  const today = this.todayMidnight();
16423
17802
  let compressed = 0;
16424
17803
  for (const name of readdirSync6(this.auditDir)) {
@@ -16472,7 +17851,7 @@ var AuditPruneSweeper = class {
16472
17851
  const source = join20(bodiesPath, shard);
16473
17852
  const target = `${source}.gz`;
16474
17853
  try {
16475
- if (existsSync24(target)) {
17854
+ if (existsSync25(target)) {
16476
17855
  unlinkSync13(source);
16477
17856
  continue;
16478
17857
  }
@@ -16481,7 +17860,7 @@ var AuditPruneSweeper = class {
16481
17860
  compressed += 1;
16482
17861
  } catch (error) {
16483
17862
  try {
16484
- if (existsSync24(target)) unlinkSync13(target);
17863
+ if (existsSync25(target)) unlinkSync13(target);
16485
17864
  } catch {
16486
17865
  }
16487
17866
  this.logger.warn("[AuditPruneSweeper] failed to archive audit body shard", {
@@ -16634,7 +18013,7 @@ async function closeAll(writers) {
16634
18013
  // src/usage/UsagePruneSweeper.ts
16635
18014
  import { unlink as unlink3 } from "fs/promises";
16636
18015
  import { join as join22 } from "path";
16637
- var DAY_MS2 = 24 * 60 * 6e4;
18016
+ var DAY_MS4 = 24 * 60 * 6e4;
16638
18017
  var SWEEP_INTERVAL_MS3 = 60 * 6e4;
16639
18018
  var DEFAULT_USAGE_RETENTION_DAYS = 90;
16640
18019
  var UsagePruneSweeper = class {
@@ -16691,7 +18070,7 @@ var UsagePruneSweeper = class {
16691
18070
  this.sweeping = true;
16692
18071
  try {
16693
18072
  const retentionDays = this.config.retentionDays ?? DEFAULT_USAGE_RETENTION_DAYS;
16694
- const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS2;
18073
+ const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS4;
16695
18074
  let removed = 0;
16696
18075
  for (const entry of await listUsageDays(this.usageDir)) {
16697
18076
  if (!entry.hasShard) continue;
@@ -16749,7 +18128,7 @@ var UsagePruneSweeper = class {
16749
18128
  };
16750
18129
 
16751
18130
  // src/audit/auditBodyReader.ts
16752
- import { existsSync as existsSync25, readdirSync as readdirSync7, readFileSync as readFileSync21, statSync as statSync7 } from "fs";
18131
+ import { existsSync as existsSync26, readdirSync as readdirSync7, readFileSync as readFileSync21, statSync as statSync7 } from "fs";
16753
18132
  import { join as join23 } from "path";
16754
18133
  import { gunzipSync } from "zlib";
16755
18134
 
@@ -16813,7 +18192,7 @@ function forEachLineFromTail(path2, onLine) {
16813
18192
  function candidateDays(auditDir, ts) {
16814
18193
  if (typeof ts === "number" && Number.isFinite(ts)) {
16815
18194
  const named = auditDayDirName(ts);
16816
- if (existsSync25(join23(auditDir, named))) return [named];
18195
+ if (existsSync26(join23(auditDir, named))) return [named];
16817
18196
  }
16818
18197
  try {
16819
18198
  return readdirSync7(auditDir).filter(isAuditDayDir).sort().reverse();
@@ -16824,9 +18203,9 @@ function candidateDays(auditDir, ts) {
16824
18203
  function readShard(auditDir, day, sessionKey) {
16825
18204
  const base = join23(auditDir, day, AUDIT_BODIES_DIR, auditBodyFileName(sessionKey));
16826
18205
  try {
16827
- if (existsSync25(base)) return readFileSync21(base, "utf8");
18206
+ if (existsSync26(base)) return readFileSync21(base, "utf8");
16828
18207
  const gz = `${base}.gz`;
16829
- if (existsSync25(gz)) return gunzipSync(readFileSync21(gz)).toString("utf8");
18208
+ if (existsSync26(gz)) return gunzipSync(readFileSync21(gz)).toString("utf8");
16830
18209
  } catch {
16831
18210
  return null;
16832
18211
  }
@@ -16859,8 +18238,8 @@ function withDictionary(auditDir, day, entries) {
16859
18238
  const base = join23(auditDir, day, AUDIT_BODIES_DIR, AUDIT_DICT_FILE);
16860
18239
  let raw = null;
16861
18240
  try {
16862
- if (existsSync25(base)) raw = readFileSync21(base, "utf8");
16863
- else if (existsSync25(`${base}.gz`)) raw = gunzipSync(readFileSync21(`${base}.gz`)).toString("utf8");
18241
+ if (existsSync26(base)) raw = readFileSync21(base, "utf8");
18242
+ else if (existsSync26(`${base}.gz`)) raw = gunzipSync(readFileSync21(`${base}.gz`)).toString("utf8");
16864
18243
  } catch {
16865
18244
  return entries;
16866
18245
  }
@@ -16893,7 +18272,7 @@ function reconstructRequest(entries, entry) {
16893
18272
  }
16894
18273
  function readAuditBody(auditDir, query2) {
16895
18274
  if (!isSafeSessionKey(query2.sessionKey) || !query2.id) return {};
16896
- if (!existsSync25(auditDir)) return {};
18275
+ if (!existsSync26(auditDir)) return {};
16897
18276
  for (const day of candidateDays(auditDir, query2.ts)) {
16898
18277
  const raw = readShard(auditDir, day, query2.sessionKey);
16899
18278
  if (raw === null) continue;
@@ -16940,7 +18319,7 @@ function readLegacyInlineBody(auditDir, id) {
16940
18319
  }
16941
18320
 
16942
18321
  // src/audit/auditReader.ts
16943
- import { existsSync as existsSync26, readdirSync as readdirSync8 } from "fs";
18322
+ import { existsSync as existsSync27, readdirSync as readdirSync8 } from "fs";
16944
18323
  import { join as join24 } from "path";
16945
18324
  var DEFAULT_LIMIT = 200;
16946
18325
  var MAX_LIMIT = 2e3;
@@ -16958,7 +18337,7 @@ function daySources(auditDir) {
16958
18337
  if (dateMs === null) continue;
16959
18338
  if (AUDIT_DAY_DIR_RE.test(name)) {
16960
18339
  const path2 = join24(auditDir, name, AUDIT_META_FILE);
16961
- if (existsSync26(path2)) sources.push({ path: path2, dateMs });
18340
+ if (existsSync27(path2)) sources.push({ path: path2, dateMs });
16962
18341
  } else if (AUDIT_FILE_RE.test(name)) {
16963
18342
  sources.push({ path: join24(auditDir, name), dateMs });
16964
18343
  }
@@ -16976,7 +18355,7 @@ function toMetaRecord(record) {
16976
18355
  return { ...meta, hasBody: true };
16977
18356
  }
16978
18357
  function readAuditRecords(auditDir, query2 = {}) {
16979
- if (!existsSync26(auditDir)) return [];
18358
+ if (!existsSync27(auditDir)) return [];
16980
18359
  const from = typeof query2.from === "number" ? query2.from : -Infinity;
16981
18360
  const to = typeof query2.to === "number" ? query2.to : Infinity;
16982
18361
  const limit = Math.min(MAX_LIMIT, Math.max(1, Math.trunc(query2.limit ?? DEFAULT_LIMIT)));
@@ -17004,7 +18383,7 @@ function readAuditRecords(auditDir, query2 = {}) {
17004
18383
  }
17005
18384
 
17006
18385
  // src/audit/AuditWriter.ts
17007
- import { appendFileSync as appendFileSync2, existsSync as existsSync27, mkdirSync as mkdirSync7, statSync as statSync8 } from "fs";
18386
+ import { appendFileSync as appendFileSync2, existsSync as existsSync28, mkdirSync as mkdirSync7, statSync as statSync8 } from "fs";
17008
18387
  import { join as join25 } from "path";
17009
18388
  var AuditWriter = class {
17010
18389
  constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
@@ -17062,7 +18441,7 @@ var AuditWriter = class {
17062
18441
  const { requestBody: _req, responseBody: _res, ...meta } = record;
17063
18442
  const file = join25(dayPath, AUDIT_META_FILE);
17064
18443
  const line = JSON.stringify(meta) + "\n";
17065
- const bytesBefore = existsSync27(file) ? statSync8(file).size : 0;
18444
+ const bytesBefore = existsSync28(file) ? statSync8(file).size : 0;
17066
18445
  appendFileSync2(file, line, "utf8");
17067
18446
  try {
17068
18447
  updateAuditStatsAfterAppend(
@@ -17110,7 +18489,7 @@ var AuditWriter = class {
17110
18489
  import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync8 } from "fs";
17111
18490
  import { createHmac as createHmac5 } from "crypto";
17112
18491
  import { join as join26 } from "path";
17113
- import { fetchUpstream as fetchUpstream5 } from "@omnicross/core/pipeline/upstreamFetch";
18492
+ import { fetchUpstream as fetchUpstream9 } from "@omnicross/core/pipeline/upstreamFetch";
17114
18493
 
17115
18494
  // src/billing/billingFiles.ts
17116
18495
  var BILLING_FILE_RE = /^billing-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
@@ -17133,7 +18512,7 @@ var BillingPublisher = class {
17133
18512
  constructor(billingDir, logger, opts = {}) {
17134
18513
  this.billingDir = billingDir;
17135
18514
  this.logger = logger;
17136
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream5(url, init));
18515
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream9(url, init));
17137
18516
  this.defer = opts.defer ?? ((fn) => setTimeout(fn, 0));
17138
18517
  this.timeoutMs = opts.timeoutMs ?? BILLING_POST_TIMEOUT_MS;
17139
18518
  this.now = opts.now ?? Date.now;
@@ -17246,11 +18625,11 @@ var BillingPublisher = class {
17246
18625
  };
17247
18626
 
17248
18627
  // src/billing/billingReader.ts
17249
- import { existsSync as existsSync28, readdirSync as readdirSync9, readFileSync as readFileSync22 } from "fs";
18628
+ import { existsSync as existsSync29, readdirSync as readdirSync9, readFileSync as readFileSync22 } from "fs";
17250
18629
  import { join as join27 } from "path";
17251
18630
  function readBillingLedger(billingDir) {
17252
18631
  const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
17253
- if (!existsSync28(billingDir)) return view;
18632
+ if (!existsSync29(billingDir)) return view;
17254
18633
  let files;
17255
18634
  try {
17256
18635
  files = readdirSync9(billingDir);
@@ -17383,7 +18762,7 @@ var BillingRetrySweeper = class {
17383
18762
  // src/TokenRefreshScheduler.ts
17384
18763
  var REFRESH_LEAD_MS2 = 5 * 6e4;
17385
18764
  var SWEEP_INTERVAL_MS5 = 6e4;
17386
- var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini"];
18765
+ var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini", "kimi"];
17387
18766
  var TokenRefreshScheduler = class {
17388
18767
  constructor(store, logger, intervalMs = SWEEP_INTERVAL_MS5, leadMs = REFRESH_LEAD_MS2) {
17389
18768
  this.store = store;
@@ -17466,6 +18845,8 @@ var TokenRefreshScheduler = class {
17466
18845
  return this.store.refreshCodexToken();
17467
18846
  case "gemini":
17468
18847
  return this.store.refreshGeminiToken();
18848
+ case "kimi":
18849
+ return this.store.refreshKimiToken();
17469
18850
  }
17470
18851
  }
17471
18852
  };
@@ -17544,7 +18925,7 @@ function createRouteLeaseSubscriptionPreflight(credentials) {
17544
18925
 
17545
18926
  // src/webhook/WebhookDispatcher.ts
17546
18927
  import { createHmac as createHmac6 } from "crypto";
17547
- import { fetchUpstream as fetchUpstream6 } from "@omnicross/core/pipeline/upstreamFetch";
18928
+ import { fetchUpstream as fetchUpstream10 } from "@omnicross/core/pipeline/upstreamFetch";
17548
18929
  var WEBHOOK_MAX_ATTEMPTS = 3;
17549
18930
  var WEBHOOK_QUEUE_MAX = 1e3;
17550
18931
  var WEBHOOK_SEND_TIMEOUT_MS = 1e4;
@@ -17564,7 +18945,7 @@ var WebhookDispatcher = class {
17564
18945
  sleep;
17565
18946
  now;
17566
18947
  constructor(opts = {}) {
17567
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream6(url, init));
18948
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream10(url, init));
17568
18949
  this.logger = opts.logger;
17569
18950
  this.maxAttempts = opts.maxAttempts ?? WEBHOOK_MAX_ATTEMPTS;
17570
18951
  this.queueMax = opts.queueMax ?? WEBHOOK_QUEUE_MAX;
@@ -17650,8 +19031,8 @@ var WebhookDispatcher = class {
17650
19031
  signal: AbortSignal.timeout(this.timeoutMs)
17651
19032
  });
17652
19033
  return res.ok ? { ok: true, status: res.status } : { ok: false, status: res.status };
17653
- } catch (err5) {
17654
- return { ok: false, error: err5 instanceof Error ? err5.message : String(err5) };
19034
+ } catch (err6) {
19035
+ return { ok: false, error: err6 instanceof Error ? err6.message : String(err6) };
17655
19036
  }
17656
19037
  }
17657
19038
  /**
@@ -17793,7 +19174,7 @@ function buildDaemon(config, paths) {
17793
19174
  setSecretBox(secretBox3);
17794
19175
  setSecretBox2(secretBox3);
17795
19176
  const decryptedConfig = decryptConfigSecrets(config, secretBox3);
17796
- const accountAllowanceStore = new AccountAllowanceStore3(
19177
+ const accountAllowanceStore = new AccountAllowanceStore6(
17797
19178
  Date.now,
17798
19179
  void 0,
17799
19180
  new JsonAccountAllowancePersistence(defaultAccountAllowancePath(paths.configPath))
@@ -17838,6 +19219,7 @@ function buildDaemon(config, paths) {
17838
19219
  );
17839
19220
  setGeminiCodeAssistResolver(getGeminiCodeAssistProjectResolver());
17840
19221
  const autoDisableStore = new AutoDisableStore();
19222
+ const providerKeyQuotaService = new ProviderKeyQuotaService(secretBox3);
17841
19223
  const apiKeyPool = new ApiKeyPoolService(
17842
19224
  createPoolKeysLoader((id) => llmConfig.getProviderRow(id), autoDisableStore),
17843
19225
  resolveEnvKey,
@@ -17854,7 +19236,7 @@ function buildDaemon(config, paths) {
17854
19236
  const pricingEngine = new PricingEngine(pricingStore, logger, {
17855
19237
  // Catalog egress follows the same global/env proxy policy as every other
17856
19238
  // daemon upstream call; no provider/account override applies here.
17857
- fetchImpl: ((input, init) => fetchUpstream7(String(input), init ?? {}))
19239
+ fetchImpl: ((input, init) => fetchUpstream11(String(input), init ?? {}))
17858
19240
  });
17859
19241
  const pricingRefreshScheduler = new PricingRefreshScheduler(
17860
19242
  pricingEngine,
@@ -18118,6 +19500,11 @@ function buildDaemon(config, paths) {
18118
19500
  // values themselves NEVER leave (masked via `maskProviderApiKey`).
18119
19501
  apiKeyPool,
18120
19502
  autoDisableStore,
19503
+ // BYO provider-key quota (Z.AI coding plan, MiniMax Token Plan, …): a
19504
+ // read-through cached same-key usage probe surfaced on the keys view. The
19505
+ // key plaintext is resolved + decrypted inside the service and never
19506
+ // crosses back out.
19507
+ providerKeyQuota: providerKeyQuotaService,
18121
19508
  // Interactive OAuth login over admin HTTP (app-parity child 4, design
18122
19509
  // D1/D2-a). The in-memory pending-session store (NEVER serialized), the
18123
19510
  // injected token-exchange fetch (global `fetch` here; mocked in tests), and a
@@ -18134,7 +19521,7 @@ function buildDaemon(config, paths) {
18134
19521
  // — `server.proxy.byProvider[...]` was silently skipped — and the call was
18135
19522
  // excluded from the upstream trace, so a failing login left no evidence.
18136
19523
  // `redactBodies` keeps the code/verifier + minted token out of that trace.
18137
- oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => fetchUpstream7(url, init, { providerId, redactBodies: true }),
19524
+ oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => fetchUpstream11(url, init, { providerId, redactBodies: true }),
18138
19525
  subscriptionAccountAppender: credentialStore,
18139
19526
  // Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
18140
19527
  // + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
@@ -18142,6 +19529,10 @@ function buildDaemon(config, paths) {
18142
19529
  // can inject a mock so no real port is bound.
18143
19530
  codexSessions: new CodexOAuthSessionStore(),
18144
19531
  codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
19532
+ // Kimi interactive OAuth — the async DEVICE-CODE flow store (no port, no
19533
+ // paste; the app shows the verification URL + user code and polls the
19534
+ // token-free status). Token captured + persisted daemon-side.
19535
+ kimiSessions: new CodexOAuthSessionStore(),
18145
19536
  // Migration pack (app-parity child 6, design D2/D3) — the concrete credential
18146
19537
  // store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
18147
19538
  // the multi-account append (`appendProviderAccount`, import re-encrypts at-
@@ -18200,7 +19591,7 @@ function buildDaemon(config, paths) {
18200
19591
  });
18201
19592
  const webhookDispatcher = new WebhookDispatcher({
18202
19593
  logger,
18203
- fetchImpl: (url, init) => fetchUpstream7(url, init)
19594
+ fetchImpl: (url, init) => fetchUpstream11(url, init)
18204
19595
  });
18205
19596
  setWebhookRuntime(webhookDispatcher, getSharedAccountHealth4());
18206
19597
  const auditWriter = new AuditWriter(auditDir, logger);
@@ -18297,7 +19688,7 @@ function resetDaemonSingletonsForTests() {
18297
19688
  }
18298
19689
  function isTokensStoreReadable(tokensPath) {
18299
19690
  try {
18300
- if (!existsSync29(tokensPath)) return true;
19691
+ if (!existsSync30(tokensPath)) return true;
18301
19692
  accessSync(tokensPath, fsConstants.R_OK);
18302
19693
  return true;
18303
19694
  } catch {