@omnicross/daemon 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -58,7 +58,7 @@ __export(src_exports, {
58
58
  module.exports = __toCommonJS(src_exports);
59
59
 
60
60
  // src/bootstrap.ts
61
- var import_node_fs34 = require("fs");
61
+ var import_node_fs35 = require("fs");
62
62
  var import_node_path35 = require("path");
63
63
  var import_audit_types = require("@omnicross/contracts/audit-types");
64
64
  var import_billing_types = require("@omnicross/contracts/billing-types");
@@ -68,16 +68,16 @@ var import_ApiKeyPoolService = require("@omnicross/core/completion/ApiKeyPoolSer
68
68
  var import_outbound_api10 = require("@omnicross/core/outbound-api");
69
69
  var import_subscriptionRegistryPort = require("@omnicross/core/outbound-api/subscriptionRegistryPort");
70
70
  var import_SubscriptionAccountHealth4 = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
71
- var import_AccountAllowanceStore4 = require("@omnicross/core/pipeline/AccountAllowanceStore");
71
+ var import_AccountAllowanceStore7 = require("@omnicross/core/pipeline/AccountAllowanceStore");
72
72
  var import_AccountAllowanceScheduling5 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
73
- var import_upstreamFetch9 = require("@omnicross/core/pipeline/upstreamFetch");
73
+ var import_upstreamFetch13 = require("@omnicross/core/pipeline/upstreamFetch");
74
74
  var import_SubscriptionIdentityStore3 = require("@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore");
75
75
  var import_gemini_code_assist_resolver = require("@omnicross/core/ports/gemini-code-assist-resolver");
76
76
  var import_provider_proxy4 = require("@omnicross/core/provider-proxy");
77
77
  var import_cli_launcher2 = require("@omnicross/cli-launcher");
78
78
  var import_outbound_api11 = require("@omnicross/core/outbound-api");
79
79
  var import_usage2 = require("@omnicross/core/usage");
80
- var import_subscriptions6 = require("@omnicross/subscriptions");
80
+ var import_subscriptions9 = require("@omnicross/subscriptions");
81
81
 
82
82
  // src/admin/accountsCodexOAuth.ts
83
83
  var import_node_crypto = __toESM(require("crypto"), 1);
@@ -184,8 +184,83 @@ function handleCodexOAuthStatus(sessionId, deps) {
184
184
  return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
185
185
  }
186
186
 
187
+ // src/admin/accountsKimiOAuth.ts
188
+ var import_subscriptions2 = require("@omnicross/subscriptions");
189
+ function err2(status, message) {
190
+ return { status, body: { error: { type: "admin_api_error", message } } };
191
+ }
192
+ var DEFAULT_KIMI_OAUTH_TTL_MS = 15 * 6e4;
193
+ async function handleKimiOAuthStart(deps) {
194
+ if (deps.kimiSessions.isBusy()) {
195
+ return err2(409, "a kimi sign-in is already in progress \u2014 finish it in the browser or cancel it");
196
+ }
197
+ const fetchImpl = deps.oauthExchangeFetch("kimi");
198
+ const deviceId = import_subscriptions2.kimiOAuth.generateKimiDeviceId();
199
+ const fingerprint = import_subscriptions2.kimiOAuth.kimiFingerprintHeaders(deviceId);
200
+ let authorization;
201
+ try {
202
+ authorization = await import_subscriptions2.kimiOAuth.requestDeviceAuthorization(fetchImpl, fingerprint);
203
+ } catch (e) {
204
+ const reason = e instanceof Error ? e.message : "device authorization failed";
205
+ return err2(502, `kimi device authorization failed: ${reason}`);
206
+ }
207
+ const { sessionId, signal } = deps.kimiSessions.begin();
208
+ void runKimiDevicePoll(sessionId, authorization.deviceCode, deviceId, fingerprint, signal, deps).catch(() => deps.kimiSessions.settle(sessionId, "error", "kimi sign-in failed"));
209
+ return {
210
+ status: 200,
211
+ body: {
212
+ authUrl: authorization.verificationUriComplete ?? authorization.verificationUri,
213
+ userCode: authorization.userCode,
214
+ sessionId
215
+ }
216
+ };
217
+ }
218
+ async function runKimiDevicePoll(sessionId, deviceCode, deviceId, fingerprint, signal, deps) {
219
+ const fetchImpl = deps.oauthExchangeFetch("kimi");
220
+ const result = await import_subscriptions2.kimiOAuth.awaitDeviceToken(
221
+ { userCode: "", deviceCode, verificationUri: "" },
222
+ fetchImpl,
223
+ {
224
+ fingerprint,
225
+ deadlineMs: DEFAULT_KIMI_OAUTH_TTL_MS,
226
+ sleep: (ms) => new Promise((resolve10, reject) => {
227
+ const onAbort = () => {
228
+ clearTimeout(timer);
229
+ reject(new Error("login: cancelled"));
230
+ };
231
+ const timer = setTimeout(() => {
232
+ signal.removeEventListener("abort", onAbort);
233
+ resolve10();
234
+ }, ms);
235
+ signal.addEventListener("abort", onAbort, { once: true });
236
+ })
237
+ }
238
+ );
239
+ const block = {
240
+ authMethod: "oauth",
241
+ status: "authorized",
242
+ accessToken: result.accessToken,
243
+ refreshToken: result.refreshToken,
244
+ expiresAt: new Date(Date.now() + result.expiresIn * 1e3).toISOString(),
245
+ accountId: import_subscriptions2.kimiOAuth.kimiAccountIdFromAccessToken(result.accessToken),
246
+ deviceId,
247
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
248
+ };
249
+ await deps.subscriptionAccountAppender.appendProviderAccount("kimi", block);
250
+ deps.kimiSessions.settle(sessionId, "done");
251
+ }
252
+ function handleKimiOAuthCancel(sessionId, deps) {
253
+ if (!deps.kimiSessions.cancel(sessionId)) return err2(404, "unknown or expired kimi sign-in session");
254
+ return { status: 200, body: { ok: true } };
255
+ }
256
+ function handleKimiOAuthStatus(sessionId, deps) {
257
+ const s = deps.kimiSessions.get(sessionId);
258
+ if (!s) return err2(404, "unknown or expired kimi sign-in session");
259
+ return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
260
+ }
261
+
187
262
  // src/allowance/AccountAllowanceService.ts
188
- var import_AccountAllowanceStore2 = require("@omnicross/core/pipeline/AccountAllowanceStore");
263
+ var import_AccountAllowanceStore5 = require("@omnicross/core/pipeline/AccountAllowanceStore");
189
264
  var import_AccountAllowanceScheduling = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
190
265
 
191
266
  // src/allowance/ClaudeAllowanceCollector.ts
@@ -212,13 +287,11 @@ function secondsUntil(instant, now) {
212
287
  function windowFromPayload(id, payload, now) {
213
288
  const usedPercent = finitePercent(payload?.utilization);
214
289
  const resetsAt = isoInstant(payload?.resets_at);
215
- const isSonnet = id === "seven-day-sonnet";
216
290
  const isFiveHour = id === "five-hour";
217
291
  return {
218
292
  id,
219
- label: isFiveHour ? "5 hours" : isSonnet ? "7 days \xB7 Sonnet" : "7 days",
220
- scope: isSonnet ? "model-family" : "all",
221
- modelFamily: isSonnet ? "sonnet" : void 0,
293
+ label: isFiveHour ? "5 hours" : "7 days",
294
+ scope: "all",
222
295
  usedPercent,
223
296
  windowMinutes: isFiveHour ? 5 * 60 : 7 * 24 * 60,
224
297
  resetsAt,
@@ -226,6 +299,44 @@ function windowFromPayload(id, payload, now) {
226
299
  state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
227
300
  };
228
301
  }
302
+ function limitEntryWindow(entries, kind) {
303
+ const entry = entries.find((candidate) => candidate.kind === kind);
304
+ if (!entry) return void 0;
305
+ return { utilization: entry.percent, resets_at: entry.resets_at };
306
+ }
307
+ function slugifyDisplayName(name) {
308
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
309
+ }
310
+ function scopedWeeklyWindows(entries, now) {
311
+ const seen = /* @__PURE__ */ new Set();
312
+ const windows = [];
313
+ for (const entry of entries) {
314
+ if (entry.kind !== "weekly_scoped") continue;
315
+ const displayName = typeof entry.scope?.model?.display_name === "string" && entry.scope.model.display_name.trim() ? entry.scope.model.display_name.trim() : void 0;
316
+ if (!displayName) continue;
317
+ const slug = slugifyDisplayName(displayName);
318
+ if (!slug || seen.has(slug)) continue;
319
+ seen.add(slug);
320
+ const usedPercent = finitePercent(entry.percent);
321
+ const resetsAt = isoInstant(entry.resets_at);
322
+ windows.push({
323
+ id: `seven-day-${slug}`,
324
+ label: `7 days \xB7 ${displayName}`,
325
+ scope: "model-family",
326
+ modelFamily: slug,
327
+ usedPercent,
328
+ windowMinutes: 7 * 24 * 60,
329
+ resetsAt,
330
+ remainingSeconds: secondsUntil(resetsAt, now),
331
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
332
+ });
333
+ }
334
+ return windows;
335
+ }
336
+ function parseLimitEntries(raw) {
337
+ if (!Array.isArray(raw)) return [];
338
+ return raw.filter((entry) => !!entry && typeof entry === "object");
339
+ }
229
340
  function emptyClaudeWindows(state) {
230
341
  return [
231
342
  {
@@ -243,15 +354,6 @@ function emptyClaudeWindows(state) {
243
354
  usedPercent: null,
244
355
  windowMinutes: 7 * 24 * 60,
245
356
  state
246
- },
247
- {
248
- id: "seven-day-sonnet",
249
- label: "7 days \xB7 Sonnet",
250
- scope: "model-family",
251
- modelFamily: "sonnet",
252
- usedPercent: null,
253
- windowMinutes: 7 * 24 * 60,
254
- state
255
357
  }
256
358
  ];
257
359
  }
@@ -332,6 +434,9 @@ var ClaudeAllowanceCollector = class {
332
434
  }
333
435
  const now = this.now();
334
436
  const usage = payload;
437
+ const limitEntries = parseLimitEntries(usage.limits);
438
+ const fiveHour = usage.five_hour ?? limitEntryWindow(limitEntries, "session");
439
+ const sevenDay = usage.seven_day ?? limitEntryWindow(limitEntries, "weekly_all");
335
440
  const snapshot = {
336
441
  providerId: "claude",
337
442
  accountId,
@@ -339,10 +444,10 @@ var ClaudeAllowanceCollector = class {
339
444
  observedAt: new Date(now).toISOString(),
340
445
  expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
341
446
  windows: [
342
- windowFromPayload("five-hour", usage.five_hour, now),
343
- windowFromPayload("seven-day", usage.seven_day, now),
344
- windowFromPayload("seven-day-sonnet", usage.seven_day_sonnet, now)
345
- ]
447
+ windowFromPayload("five-hour", fiveHour, now),
448
+ windowFromPayload("seven-day", sevenDay, now),
449
+ ...scopedWeeklyWindows(limitEntries, now)
450
+ ].slice(0, 8)
346
451
  };
347
452
  this.store.set(snapshot);
348
453
  return snapshot;
@@ -399,6 +504,601 @@ var ClaudeAllowanceCollector = class {
399
504
  }
400
505
  };
401
506
 
507
+ // src/allowance/CodexAllowanceCollector.ts
508
+ var import_AccountAllowanceStore2 = require("@omnicross/core/pipeline/AccountAllowanceStore");
509
+ var import_upstreamFetch2 = require("@omnicross/core/pipeline/upstreamFetch");
510
+ var CODEX_ALLOWANCE_CACHE_MS = 5 * 6e4;
511
+ var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
512
+ var CODEX_CLI_USER_AGENT = "codex_cli_rs/0.144.5";
513
+ function finiteNumber(value) {
514
+ if (value === null || value === void 0 || value === "") return null;
515
+ const parsed = typeof value === "number" ? value : Number(value);
516
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
517
+ }
518
+ function finitePercent2(value) {
519
+ const parsed = finiteNumber(value);
520
+ return parsed !== null && parsed <= 100 ? parsed : null;
521
+ }
522
+ function epochMs(value) {
523
+ return value > 1e11 ? value : value * 1e3;
524
+ }
525
+ function secondsUntil2(instant, now) {
526
+ if (!instant) return void 0;
527
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
528
+ }
529
+ function decodeJwtClaims(token) {
530
+ const parts = token.split(".");
531
+ if (parts.length !== 3) return void 0;
532
+ try {
533
+ const json2 = Buffer.from(parts[1], "base64url").toString("utf8");
534
+ const parsed = JSON.parse(json2);
535
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
536
+ } catch {
537
+ return void 0;
538
+ }
539
+ }
540
+ function chatgptAccountIdFromClaims(claims) {
541
+ const auth = claims?.["https://api.openai.com/auth"];
542
+ if (!auth || typeof auth !== "object") return void 0;
543
+ const accountId = auth.chatgpt_account_id;
544
+ return typeof accountId === "string" && accountId.trim() ? accountId.trim() : void 0;
545
+ }
546
+ function resolveCodexChatGptAccountId(tokens) {
547
+ if (tokens.accountId?.trim()) return tokens.accountId.trim();
548
+ if (tokens.idToken) {
549
+ const fromIdToken = chatgptAccountIdFromClaims(decodeJwtClaims(tokens.idToken));
550
+ if (fromIdToken) return fromIdToken;
551
+ }
552
+ if (tokens.accessToken) {
553
+ return chatgptAccountIdFromClaims(decodeJwtClaims(tokens.accessToken));
554
+ }
555
+ return void 0;
556
+ }
557
+ function windowFromPayload2(id, payload, now) {
558
+ const usedPercent = finitePercent2(payload?.used_percent);
559
+ const resetAtSeconds = finiteNumber(payload?.reset_at);
560
+ const resetAfterSeconds = finiteNumber(payload?.reset_after_seconds);
561
+ const windowSeconds = finiteNumber(payload?.limit_window_seconds);
562
+ const resetsAt = resetAtSeconds !== null && resetAtSeconds > 0 ? new Date(epochMs(resetAtSeconds)).toISOString() : resetAfterSeconds !== null && resetAfterSeconds > 0 ? new Date(now + resetAfterSeconds * 1e3).toISOString() : void 0;
563
+ const windowMinutes = windowSeconds !== null && windowSeconds > 0 ? Math.round(windowSeconds / 60) : void 0;
564
+ return {
565
+ id,
566
+ label: id === "primary" ? "Primary" : "Secondary",
567
+ scope: "all",
568
+ usedPercent,
569
+ ...windowMinutes !== void 0 ? { windowMinutes } : {},
570
+ ...resetsAt !== void 0 ? { resetsAt } : {},
571
+ remainingSeconds: secondsUntil2(resetsAt, now),
572
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
573
+ };
574
+ }
575
+ var CodexAllowanceCollector = class {
576
+ constructor(credentials, store = (0, import_AccountAllowanceStore2.getSharedAccountAllowanceStore)(), fetchImpl = (url, init, accountId) => (0, import_upstreamFetch2.fetchUpstream)(url, init, { providerId: "codex", accountId, redactBodies: true }), now = Date.now) {
577
+ this.credentials = credentials;
578
+ this.store = store;
579
+ this.fetchImpl = fetchImpl;
580
+ this.now = now;
581
+ }
582
+ credentials;
583
+ store;
584
+ fetchImpl;
585
+ now;
586
+ inFlight = /* @__PURE__ */ new Map();
587
+ async collectMany(accounts, options = {}) {
588
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
589
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
590
+ }
591
+ collect(account, options = {}) {
592
+ const now = this.now();
593
+ const unsupported = account.tokens.authMethod !== "oauth";
594
+ if (unsupported) {
595
+ const existing = this.store.get("codex", account.id, now);
596
+ if (existing?.windows.every((window) => window.state === "unsupported")) {
597
+ return Promise.resolve(existing);
598
+ }
599
+ const snapshot = this.unsupportedSnapshot(account.id, now);
600
+ this.store.set(snapshot);
601
+ return Promise.resolve(snapshot);
602
+ }
603
+ const cached = this.store.get("codex", account.id, now);
604
+ if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
605
+ return Promise.resolve(cached);
606
+ }
607
+ const running = this.inFlight.get(account.id);
608
+ if (running) return running;
609
+ 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));
610
+ this.inFlight.set(account.id, promise);
611
+ return promise;
612
+ }
613
+ /**
614
+ * A response-header snapshot stays a valid cache hit only while fresh; an
615
+ * active oauth-usage snapshot is honored on the same 5-minute cadence as
616
+ * Claude's (the poll is cheap and quota is the scheduling input).
617
+ */
618
+ isCacheValid(snapshot, now, refreshAheadMs) {
619
+ if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
620
+ const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
621
+ const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
622
+ return Number.isFinite(expiresAt) && expiresAt > now + ahead;
623
+ }
624
+ async fetchAccount(accountId, tokens) {
625
+ let accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
626
+ if (!accessToken) {
627
+ return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
628
+ }
629
+ let response = await this.request(accountId, accessToken, tokens);
630
+ if (response.status === 401) {
631
+ const refreshed = await this.credentials.refreshAccountToken("codex", accountId);
632
+ if (!refreshed) {
633
+ return this.failureSnapshot(accountId, "codex_usage_unauthorized", this.now());
634
+ }
635
+ accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
636
+ if (!accessToken) {
637
+ return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
638
+ }
639
+ response = await this.request(accountId, accessToken, tokens);
640
+ }
641
+ if (response.status === 403) {
642
+ const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "codex_usage_unsupported");
643
+ this.store.set(snapshot2);
644
+ return snapshot2;
645
+ }
646
+ if (!response.ok) {
647
+ return this.failureSnapshot(accountId, "codex_usage_http_error", this.now());
648
+ }
649
+ let payload;
650
+ try {
651
+ payload = await response.json();
652
+ } catch {
653
+ return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
654
+ }
655
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
656
+ return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
657
+ }
658
+ const now = this.now();
659
+ const usage = payload.rate_limit;
660
+ const previous = this.store.get("codex", accountId, now);
661
+ const snapshot = {
662
+ providerId: "codex",
663
+ accountId,
664
+ source: "oauth-usage-api",
665
+ observedAt: new Date(now).toISOString(),
666
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
667
+ windows: [
668
+ windowFromPayload2("primary", usage?.primary_window ?? void 0, now),
669
+ windowFromPayload2("secondary", usage?.secondary_window ?? void 0, now)
670
+ ],
671
+ // The wham payload has no ratio field; keep the passively-observed value.
672
+ ...previous?.primaryOverSecondaryLimitPercent !== void 0 ? { primaryOverSecondaryLimitPercent: previous.primaryOverSecondaryLimitPercent } : {}
673
+ };
674
+ this.store.set(snapshot);
675
+ return snapshot;
676
+ }
677
+ request(accountId, accessToken, tokens) {
678
+ const headers = {
679
+ Authorization: `Bearer ${accessToken}`,
680
+ Accept: "application/json",
681
+ "User-Agent": CODEX_CLI_USER_AGENT
682
+ };
683
+ const chatgptAccountId = resolveCodexChatGptAccountId(tokens);
684
+ if (chatgptAccountId) headers["ChatGPT-Account-Id"] = chatgptAccountId;
685
+ return this.fetchImpl(CODEX_USAGE_URL, {
686
+ method: "GET",
687
+ headers,
688
+ signal: AbortSignal.timeout(15e3)
689
+ }, accountId);
690
+ }
691
+ failureSnapshot(accountId, code, now) {
692
+ const existing = this.store.get("codex", accountId, now);
693
+ const snapshot = existing ? {
694
+ ...existing,
695
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
696
+ windows: existing.windows.map((window) => ({
697
+ ...window,
698
+ state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
699
+ })),
700
+ lastErrorCode: code
701
+ } : {
702
+ providerId: "codex",
703
+ accountId,
704
+ source: "oauth-usage-api",
705
+ observedAt: new Date(now).toISOString(),
706
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
707
+ windows: [
708
+ { id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unavailable" },
709
+ { id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unavailable" }
710
+ ],
711
+ lastErrorCode: code
712
+ };
713
+ this.store.set(snapshot);
714
+ return snapshot;
715
+ }
716
+ unsupportedSnapshot(accountId, now, code = "codex_usage_unsupported_auth") {
717
+ return {
718
+ providerId: "codex",
719
+ accountId,
720
+ source: "oauth-usage-api",
721
+ observedAt: new Date(now).toISOString(),
722
+ windows: [
723
+ { id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unsupported" },
724
+ { id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unsupported" }
725
+ ],
726
+ lastErrorCode: code
727
+ };
728
+ }
729
+ };
730
+
731
+ // src/allowance/KimiAllowanceCollector.ts
732
+ var import_AccountAllowanceStore3 = require("@omnicross/core/pipeline/AccountAllowanceStore");
733
+ var import_upstreamFetch3 = require("@omnicross/core/pipeline/upstreamFetch");
734
+ var import_subscriptions3 = require("@omnicross/subscriptions");
735
+ var KIMI_ALLOWANCE_CACHE_MS = 5 * 6e4;
736
+ var KIMI_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
737
+ function finiteNumber2(value) {
738
+ if (value === null || value === void 0 || value === "") return void 0;
739
+ const parsed = typeof value === "number" ? value : Number(value);
740
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
741
+ }
742
+ function isRecord(value) {
743
+ return !!value && typeof value === "object" && !Array.isArray(value);
744
+ }
745
+ function parseResetMs(row, nowMs) {
746
+ for (const key of ["reset_at", "resetAt", "reset_time", "resetTime"]) {
747
+ const value = row[key];
748
+ if (typeof value === "string" && value.trim()) {
749
+ const parsed = Date.parse(value);
750
+ if (Number.isFinite(parsed)) return parsed;
751
+ }
752
+ const numeric = finiteNumber2(value);
753
+ if (numeric !== void 0 && numeric > 1e9) {
754
+ return numeric > 1e12 ? numeric : numeric * 1e3;
755
+ }
756
+ }
757
+ for (const key of ["reset_in", "resetIn", "ttl", "window"]) {
758
+ const seconds = finiteNumber2(row[key]);
759
+ if (seconds !== void 0) return nowMs + seconds * 1e3;
760
+ }
761
+ return void 0;
762
+ }
763
+ var MINUTE_MS = 6e4;
764
+ var HOUR_MS = 36e5;
765
+ var DAY_MS = 864e5;
766
+ function canonicalWindow(durationMs) {
767
+ if (durationMs === 5 * HOUR_MS) return { id: "five-hour", label: "5 hours", minutes: 300 };
768
+ if (durationMs === 7 * DAY_MS) return { id: "seven-day", label: "7 days", minutes: 10080 };
769
+ if (durationMs > 0 && durationMs % DAY_MS === 0) {
770
+ const days = durationMs / DAY_MS;
771
+ return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
772
+ }
773
+ if (durationMs > 0 && durationMs % HOUR_MS === 0) {
774
+ const hours = durationMs / HOUR_MS;
775
+ return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
776
+ }
777
+ return void 0;
778
+ }
779
+ function secondsUntil3(instant, now) {
780
+ if (!instant) return void 0;
781
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
782
+ }
783
+ function windowFromRow(row, fallback, now) {
784
+ 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;
785
+ const resetsAt = row?.resetsAtMs !== void 0 ? new Date(row.resetsAtMs).toISOString() : void 0;
786
+ return {
787
+ id: fallback.id,
788
+ label: fallback.label,
789
+ scope: "all",
790
+ usedPercent,
791
+ windowMinutes: fallback.minutes,
792
+ ...resetsAt !== void 0 ? { resetsAt } : {},
793
+ remainingSeconds: secondsUntil3(resetsAt, now),
794
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
795
+ };
796
+ }
797
+ function parseKimiUsagePayload(payload, now) {
798
+ if (!isRecord(payload)) return [];
799
+ const byId = /* @__PURE__ */ new Map();
800
+ const rowFrom = (data) => {
801
+ const limit = finiteNumber2(data["limit"]);
802
+ let used = finiteNumber2(data["used"]);
803
+ const remaining = finiteNumber2(data["remaining"]);
804
+ if (used === void 0 && remaining !== void 0 && limit !== void 0) {
805
+ used = limit - remaining;
806
+ }
807
+ let windowDurationMs;
808
+ const windowData = isRecord(data["window"]) ? data["window"] : void 0;
809
+ const duration = finiteNumber2(windowData?.["duration"]);
810
+ const timeUnit = typeof windowData?.["timeUnit"] === "string" ? windowData["timeUnit"].toUpperCase() : "";
811
+ if (duration !== void 0) {
812
+ if (timeUnit.includes("MINUTE")) windowDurationMs = duration * MINUTE_MS;
813
+ else if (timeUnit.includes("HOUR")) windowDurationMs = duration * HOUR_MS;
814
+ else if (timeUnit.includes("DAY")) windowDurationMs = duration * DAY_MS;
815
+ else if (timeUnit.includes("WEEK")) windowDurationMs = duration * 7 * DAY_MS;
816
+ else if (timeUnit.includes("SECOND")) windowDurationMs = duration * 1e3;
817
+ }
818
+ const resetsAtMs = parseResetMs(windowData && parseResetMs(windowData, now) !== void 0 ? windowData : data, now);
819
+ return { used, limit, remaining, ...resetsAtMs !== void 0 ? { resetsAtMs } : {}, ...windowDurationMs !== void 0 ? { windowDurationMs } : {} };
820
+ };
821
+ if (isRecord(payload["usage"])) {
822
+ const row = rowFrom(payload["usage"]);
823
+ const window = windowFromRow({ ...row, resetsAtMs: row.resetsAtMs }, { id: "seven-day", label: "7 days", minutes: 10080 }, now);
824
+ byId.set("seven-day", window);
825
+ }
826
+ if (Array.isArray(payload["limits"])) {
827
+ for (const item of payload["limits"]) {
828
+ if (!isRecord(item)) continue;
829
+ const detail = isRecord(item["detail"]) ? item["detail"] : item;
830
+ const row = rowFrom(detail);
831
+ const canonical = row.windowDurationMs !== void 0 ? canonicalWindow(row.windowDurationMs) : void 0;
832
+ if (!canonical) continue;
833
+ const window = windowFromRow(row, canonical, now);
834
+ const existing = byId.get(canonical.id);
835
+ if (!existing || (window.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
836
+ byId.set(canonical.id, window);
837
+ }
838
+ }
839
+ }
840
+ return [...byId.values()].sort((a, b) => (a.windowMinutes ?? Infinity) - (b.windowMinutes ?? Infinity)).slice(0, 4);
841
+ }
842
+ var KimiAllowanceCollector = class {
843
+ constructor(credentials, store = (0, import_AccountAllowanceStore3.getSharedAccountAllowanceStore)(), fetchImpl = (url, init, accountId) => (0, import_upstreamFetch3.fetchUpstream)(url, init, { providerId: "kimi", accountId, redactBodies: true }), now = Date.now) {
844
+ this.credentials = credentials;
845
+ this.store = store;
846
+ this.fetchImpl = fetchImpl;
847
+ this.now = now;
848
+ }
849
+ credentials;
850
+ store;
851
+ fetchImpl;
852
+ now;
853
+ inFlight = /* @__PURE__ */ new Map();
854
+ async collectMany(accounts, options = {}) {
855
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
856
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
857
+ }
858
+ collect(account, options = {}) {
859
+ const now = this.now();
860
+ if (account.tokens.authMethod !== "oauth") {
861
+ const existing = this.store.get("kimi", account.id, now);
862
+ if (existing?.windows.every((window) => window.state === "unsupported")) {
863
+ return Promise.resolve(existing);
864
+ }
865
+ const snapshot = this.unsupportedSnapshot(account.id, now);
866
+ this.store.set(snapshot);
867
+ return Promise.resolve(snapshot);
868
+ }
869
+ const cached = this.store.get("kimi", account.id, now);
870
+ if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
871
+ return Promise.resolve(cached);
872
+ }
873
+ const running = this.inFlight.get(account.id);
874
+ if (running) return running;
875
+ 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));
876
+ this.inFlight.set(account.id, promise);
877
+ return promise;
878
+ }
879
+ isCacheValid(snapshot, now, refreshAheadMs) {
880
+ if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
881
+ const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
882
+ const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
883
+ return Number.isFinite(expiresAt) && expiresAt > now + ahead;
884
+ }
885
+ async fetchAccount(accountId, tokens) {
886
+ let accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
887
+ if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
888
+ let response = await this.request(accountId, accessToken, tokens);
889
+ if (response.status === 401) {
890
+ const refreshed = await this.credentials.refreshAccountToken("kimi", accountId);
891
+ if (!refreshed) return this.failureSnapshot(accountId, "kimi_usage_unauthorized", this.now());
892
+ accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
893
+ if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
894
+ response = await this.request(accountId, accessToken, tokens);
895
+ }
896
+ if (response.status === 403) {
897
+ const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "kimi_usage_unsupported");
898
+ this.store.set(snapshot2);
899
+ return snapshot2;
900
+ }
901
+ if (!response.ok) return this.failureSnapshot(accountId, "kimi_usage_http_error", this.now());
902
+ let payload;
903
+ try {
904
+ payload = await response.json();
905
+ } catch {
906
+ return this.failureSnapshot(accountId, "kimi_usage_invalid_response", this.now());
907
+ }
908
+ const now = this.now();
909
+ const windows = parseKimiUsagePayload(payload, now);
910
+ const snapshot = {
911
+ providerId: "kimi",
912
+ accountId,
913
+ source: "oauth-usage-api",
914
+ observedAt: new Date(now).toISOString(),
915
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
916
+ windows: windows.length > 0 ? windows : [
917
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
918
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
919
+ ],
920
+ ...windows.length > 0 ? {} : { lastErrorCode: "kimi_usage_invalid_response" }
921
+ };
922
+ this.store.set(snapshot);
923
+ return snapshot;
924
+ }
925
+ request(accountId, accessToken, tokens) {
926
+ return this.fetchImpl(KIMI_USAGE_URL, {
927
+ method: "GET",
928
+ headers: {
929
+ Authorization: `Bearer ${accessToken}`,
930
+ Accept: "application/json",
931
+ ...(0, import_subscriptions3.kimiFingerprintHeaders)(tokens.deviceId)
932
+ },
933
+ signal: AbortSignal.timeout(15e3)
934
+ }, accountId);
935
+ }
936
+ failureSnapshot(accountId, code, now) {
937
+ const existing = this.store.get("kimi", accountId, now);
938
+ const snapshot = existing ? {
939
+ ...existing,
940
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
941
+ windows: existing.windows.map((window) => ({
942
+ ...window,
943
+ state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
944
+ })),
945
+ lastErrorCode: code
946
+ } : {
947
+ providerId: "kimi",
948
+ accountId,
949
+ source: "oauth-usage-api",
950
+ observedAt: new Date(now).toISOString(),
951
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
952
+ windows: [
953
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
954
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
955
+ ],
956
+ lastErrorCode: code
957
+ };
958
+ this.store.set(snapshot);
959
+ return snapshot;
960
+ }
961
+ unsupportedSnapshot(accountId, now, code = "kimi_usage_unsupported_auth") {
962
+ return {
963
+ providerId: "kimi",
964
+ accountId,
965
+ source: "oauth-usage-api",
966
+ observedAt: new Date(now).toISOString(),
967
+ windows: [
968
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unsupported" },
969
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unsupported" }
970
+ ],
971
+ lastErrorCode: code
972
+ };
973
+ }
974
+ };
975
+
976
+ // src/allowance/OpenCodeGoAllowanceCollector.ts
977
+ var import_AccountAllowanceStore4 = require("@omnicross/core/pipeline/AccountAllowanceStore");
978
+ var import_upstreamFetch4 = require("@omnicross/core/pipeline/upstreamFetch");
979
+ var import_subscriptions4 = require("@omnicross/subscriptions");
980
+ var OPENCODEGO_ALLOWANCE_CACHE_MS = 5 * 6e4;
981
+ var OPENCODEGO_DEFAULT_GO_BASE = "https://opencode.ai/zen/go";
982
+ function finitePercent3(value) {
983
+ if (value === null || value === void 0 || value === "") return null;
984
+ const parsed = typeof value === "number" ? value : Number(value);
985
+ return Number.isFinite(parsed) && parsed >= 0 && parsed <= 100 ? parsed : null;
986
+ }
987
+ function isoInstant2(value) {
988
+ if (typeof value !== "string" || !value.trim()) return void 0;
989
+ const time = Date.parse(value);
990
+ return Number.isFinite(time) ? new Date(time).toISOString() : void 0;
991
+ }
992
+ function secondsUntil4(instant, now) {
993
+ if (!instant) return void 0;
994
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
995
+ }
996
+ function windowFromPayload3(id, label, minutes, payload, now) {
997
+ const statusRateLimited = payload?.status === "rate-limited";
998
+ const usedPercent = statusRateLimited ? 100 : finitePercent3(payload?.percent);
999
+ const resetsAt = isoInstant2(payload?.resetsAt);
1000
+ return {
1001
+ id,
1002
+ label,
1003
+ scope: "all",
1004
+ usedPercent,
1005
+ windowMinutes: minutes,
1006
+ ...resetsAt !== void 0 ? { resetsAt } : {},
1007
+ remainingSeconds: secondsUntil4(resetsAt, now),
1008
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
1009
+ };
1010
+ }
1011
+ var OpenCodeGoAllowanceCollector = class {
1012
+ constructor(credentials, store = (0, import_AccountAllowanceStore4.getSharedAccountAllowanceStore)(), fetchImpl = (url, init, accountId) => (0, import_upstreamFetch4.fetchUpstream)(url, init, { providerId: "opencodego", accountId, redactBodies: true }), now = Date.now) {
1013
+ this.credentials = credentials;
1014
+ this.store = store;
1015
+ this.fetchImpl = fetchImpl;
1016
+ this.now = now;
1017
+ }
1018
+ credentials;
1019
+ store;
1020
+ fetchImpl;
1021
+ now;
1022
+ inFlight = /* @__PURE__ */ new Map();
1023
+ async collectMany(accounts, options = {}) {
1024
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
1025
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
1026
+ }
1027
+ collect(account, options = {}) {
1028
+ const now = this.now();
1029
+ const cached = this.store.get("opencodego", account.id, now);
1030
+ if (!options.force && cached && (cached.windows.every((window) => window.state === "unsupported") || cached.expiresAt && Date.parse(cached.expiresAt) > now + (options.refreshAheadMs ?? 0))) {
1031
+ return Promise.resolve(cached);
1032
+ }
1033
+ const running = this.inFlight.get(account.id);
1034
+ if (running) return running;
1035
+ const promise = this.fetchAccount(account).catch(() => this.failureSnapshot(account.id, this.now())).finally(() => this.inFlight.delete(account.id));
1036
+ this.inFlight.set(account.id, promise);
1037
+ return promise;
1038
+ }
1039
+ async fetchAccount(account) {
1040
+ const apiKey = await this.credentials.getAccessTokenForAccount("opencodego", account.id);
1041
+ if (!apiKey) return this.failureSnapshot(account.id, this.now());
1042
+ const base = account.tokens.baseUrl ? (0, import_subscriptions4.normalizeOpenCodeGoBaseUrl)(account.tokens.baseUrl) : OPENCODEGO_DEFAULT_GO_BASE;
1043
+ const response = await this.fetchImpl(`${base}/v1/usage`, {
1044
+ method: "GET",
1045
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
1046
+ signal: AbortSignal.timeout(15e3)
1047
+ }, account.id);
1048
+ if (response.status === 401 || response.status === 403) {
1049
+ return this.failureSnapshot(account.id, this.now(), "opencodego_usage_unauthorized");
1050
+ }
1051
+ if (!response.ok) return this.failureSnapshot(account.id, this.now());
1052
+ let payload;
1053
+ try {
1054
+ payload = await response.json();
1055
+ } catch {
1056
+ return this.failureSnapshot(account.id, this.now());
1057
+ }
1058
+ const usage = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.usage : void 0;
1059
+ const now = this.now();
1060
+ const snapshot = {
1061
+ providerId: "opencodego",
1062
+ accountId: account.id,
1063
+ source: "oauth-usage-api",
1064
+ observedAt: new Date(now).toISOString(),
1065
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
1066
+ // Monthly deliberately omitted (module doc).
1067
+ windows: [
1068
+ windowFromPayload3("five-hour", "5 hours", 5 * 60, usage?.rolling ?? void 0, now),
1069
+ windowFromPayload3("seven-day", "7 days", 7 * 24 * 60, usage?.weekly ?? void 0, now)
1070
+ ]
1071
+ };
1072
+ this.store.set(snapshot);
1073
+ return snapshot;
1074
+ }
1075
+ failureSnapshot(accountId, now, code = "opencodego_usage_request_failed") {
1076
+ const existing = this.store.get("opencodego", accountId, now);
1077
+ const snapshot = existing ? {
1078
+ ...existing,
1079
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
1080
+ windows: existing.windows.map((window) => ({
1081
+ ...window,
1082
+ state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
1083
+ })),
1084
+ lastErrorCode: code
1085
+ } : {
1086
+ providerId: "opencodego",
1087
+ accountId,
1088
+ source: "oauth-usage-api",
1089
+ observedAt: new Date(now).toISOString(),
1090
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
1091
+ windows: [
1092
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
1093
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
1094
+ ],
1095
+ lastErrorCode: code
1096
+ };
1097
+ this.store.set(snapshot);
1098
+ return snapshot;
1099
+ }
1100
+ };
1101
+
402
1102
  // src/allowance/AccountAllowanceService.ts
403
1103
  function codexUnavailable(accountId, now) {
404
1104
  return {
@@ -414,26 +1114,30 @@ function codexUnavailable(accountId, now) {
414
1114
  };
415
1115
  }
416
1116
  var AccountAllowanceService = class {
417
- constructor(credentials, store = (0, import_AccountAllowanceStore2.getSharedAccountAllowanceStore)(), collector, now = Date.now) {
1117
+ constructor(credentials, store = (0, import_AccountAllowanceStore5.getSharedAccountAllowanceStore)(), collector, codexCollector, kimiCollector, opencodegoCollector, now = Date.now) {
418
1118
  this.credentials = credentials;
419
1119
  this.store = store;
420
1120
  this.now = now;
421
1121
  this.claudeCollector = collector ?? new ClaudeAllowanceCollector(credentials, store);
1122
+ this.codexCollector = codexCollector ?? new CodexAllowanceCollector(credentials, store);
1123
+ this.kimiCollector = kimiCollector ?? new KimiAllowanceCollector(credentials, store);
1124
+ this.opencodegoCollector = opencodegoCollector ?? new OpenCodeGoAllowanceCollector(credentials, store);
422
1125
  }
423
1126
  credentials;
424
1127
  store;
425
1128
  now;
426
1129
  claudeCollector;
1130
+ codexCollector;
1131
+ kimiCollector;
1132
+ opencodegoCollector;
427
1133
  /**
428
- * Read all/filtered snapshots. Claude's five-minute cache is refreshed lazily;
429
- * Codex remains passive and reports not-observed until a real model response.
1134
+ * Read all/filtered snapshots. Claude's and Codex's five-minute caches are
1135
+ * refreshed lazily on read (Codex polls `/backend-api/wham/usage`; the
1136
+ * passive `x-codex-*` header tap still feeds mid-flight updates).
430
1137
  */
431
1138
  async list(filter = {}) {
432
1139
  const config = await this.credentials.getFullConfig();
433
- this.store.pruneToKnownAccounts([
434
- ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
435
- ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
436
- ]);
1140
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
437
1141
  const wantsClaude = !filter.providerId || filter.providerId === "claude";
438
1142
  const claudeAccounts = (config.claudeAccounts ?? []).filter(
439
1143
  (account) => !filter.accountId || account.id === filter.accountId
@@ -444,39 +1148,90 @@ var AccountAllowanceService = class {
444
1148
  (account) => !filter.accountId || account.id === filter.accountId
445
1149
  );
446
1150
  if (wantsCodex) {
1151
+ await this.codexCollector.collectMany(codexAccounts);
447
1152
  for (const account of codexAccounts) {
448
1153
  if (!this.store.get("codex", account.id)) this.store.set(codexUnavailable(account.id, this.now()));
449
1154
  }
450
1155
  }
1156
+ const wantsKimi = !filter.providerId || filter.providerId === "kimi";
1157
+ const kimiAccounts = (config.kimiAccounts ?? []).filter(
1158
+ (account) => !filter.accountId || account.id === filter.accountId
1159
+ );
1160
+ if (wantsKimi) await this.kimiCollector.collectMany(kimiAccounts);
1161
+ const wantsOpenCodeGo = !filter.providerId || filter.providerId === "opencodego";
1162
+ const opencodegoAccounts = (config.opencodegoAccounts ?? []).filter(
1163
+ (account) => !filter.accountId || account.id === filter.accountId
1164
+ );
1165
+ if (wantsOpenCodeGo) await this.opencodegoCollector.collectMany(opencodegoAccounts);
451
1166
  const known = /* @__PURE__ */ new Set();
452
1167
  if (wantsClaude) for (const account of claudeAccounts) known.add(`claude\0${account.id}`);
453
1168
  if (wantsCodex) for (const account of codexAccounts) known.add(`codex\0${account.id}`);
1169
+ if (wantsKimi) for (const account of kimiAccounts) known.add(`kimi\0${account.id}`);
1170
+ if (wantsOpenCodeGo) for (const account of opencodegoAccounts) known.add(`opencodego\0${account.id}`);
454
1171
  return this.store.list(filter).filter((snapshot) => known.has(`${snapshot.providerId}\0${snapshot.accountId}`));
455
1172
  }
1173
+ knownAccounts(config) {
1174
+ return [
1175
+ ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
1176
+ ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id })),
1177
+ ...(config.kimiAccounts ?? []).map((account) => ({ providerId: "kimi", accountId: account.id })),
1178
+ ...(config.opencodegoAccounts ?? []).map((account) => ({ providerId: "opencodego", accountId: account.id }))
1179
+ ];
1180
+ }
456
1181
  /** Force-refresh Claude usage for one account or every stored Claude account. */
457
1182
  async refreshClaude(accountId) {
458
1183
  const config = await this.credentials.getFullConfig();
459
- this.store.pruneToKnownAccounts([
460
- ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
461
- ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
462
- ]);
463
- const accounts = (config.claudeAccounts ?? []).filter(
1184
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1185
+ const accounts = (config.claudeAccounts ?? []).filter(
1186
+ (account) => !accountId || account.id === accountId
1187
+ );
1188
+ return this.claudeCollector.collectMany(accounts, { force: true });
1189
+ }
1190
+ /**
1191
+ * Force-refresh Codex usage (`/backend-api/wham/usage`) for one account or
1192
+ * every stored Codex account. Replaces the old probe-request workaround —
1193
+ * no quota is spent reading the usage endpoint.
1194
+ */
1195
+ async refreshCodex(accountId) {
1196
+ const config = await this.credentials.getFullConfig();
1197
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1198
+ const accounts = (config.codexAccounts ?? []).filter(
1199
+ (account) => !accountId || account.id === accountId
1200
+ );
1201
+ return this.codexCollector.collectMany(accounts, { force: true });
1202
+ }
1203
+ /** Force-refresh OpenCodeGo usage (`{go}/v1/usage`) for one/all accounts. */
1204
+ async refreshOpenCodeGo(accountId) {
1205
+ const config = await this.credentials.getFullConfig();
1206
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1207
+ const accounts = (config.opencodegoAccounts ?? []).filter(
1208
+ (account) => !accountId || account.id === accountId
1209
+ );
1210
+ return this.opencodegoCollector.collectMany(accounts, { force: true });
1211
+ }
1212
+ /** Force-refresh Kimi usage (`/coding/v1/usages`) for one/all accounts. */
1213
+ async refreshKimi(accountId) {
1214
+ const config = await this.credentials.getFullConfig();
1215
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1216
+ const accounts = (config.kimiAccounts ?? []).filter(
464
1217
  (account) => !accountId || account.id === accountId
465
1218
  );
466
- return this.claudeCollector.collectMany(accounts, { force: true });
1219
+ return this.kimiCollector.collectMany(accounts, { force: true });
467
1220
  }
468
1221
  /**
469
- * Keep Claude snapshots warm for allowance-aware routing. This deliberately
470
- * excludes Codex (whose quota is learned from real response headers) and
471
- * preserves the collector's cache + per-account in-flight coalescing.
1222
+ * Keep Claude + Codex + Kimi snapshots warm for allowance-aware routing. All
1223
+ * collectors preserve their cache + per-account in-flight coalescing; a tick
1224
+ * normally performs no network I/O. (Codex joined the warm path when it
1225
+ * gained an active `/wham/usage` collector — the passive `x-codex-*` header
1226
+ * tap alone could not keep the policy fed while idle.)
472
1227
  */
473
1228
  async maintainClaudeCache(refreshAheadMs) {
474
1229
  const config = await this.credentials.getFullConfig();
475
- this.store.pruneToKnownAccounts([
476
- ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
477
- ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
478
- ]);
1230
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
479
1231
  await this.claudeCollector.collectMany(config.claudeAccounts ?? [], { refreshAheadMs });
1232
+ await this.codexCollector.collectMany(config.codexAccounts ?? [], { refreshAheadMs });
1233
+ await this.kimiCollector.collectMany(config.kimiAccounts ?? [], { refreshAheadMs });
1234
+ await this.opencodegoCollector.collectMany(config.opencodegoAccounts ?? [], { refreshAheadMs });
480
1235
  }
481
1236
  /** Remove a cache row as soon as an account is deleted by the admin path. */
482
1237
  removeAccountSnapshot(providerId, accountId) {
@@ -571,7 +1326,7 @@ var ClaudeAllowanceRefreshScheduler = class {
571
1326
  var import_node_crypto2 = require("crypto");
572
1327
  var import_node_fs = require("fs");
573
1328
  var import_node_path = require("path");
574
- var import_AccountAllowanceStore3 = require("@omnicross/core/pipeline/AccountAllowanceStore");
1329
+ var import_AccountAllowanceStore6 = require("@omnicross/core/pipeline/AccountAllowanceStore");
575
1330
  var ACCOUNT_ALLOWANCE_CACHE_VERSION = 1;
576
1331
  var MAX_PERSISTED_ALLOWANCE_SNAPSHOTS = 256;
577
1332
  var MAX_ALLOWANCE_CACHE_BYTES = 1e6;
@@ -600,7 +1355,7 @@ var JsonAccountAllowancePersistence = class {
600
1355
  save(snapshots) {
601
1356
  const rows = [];
602
1357
  for (const snapshot of snapshots) {
603
- const normalized2 = (0, import_AccountAllowanceStore3.normalizeAccountAllowanceSnapshot)(snapshot);
1358
+ const normalized2 = (0, import_AccountAllowanceStore6.normalizeAccountAllowanceSnapshot)(snapshot);
604
1359
  if (!normalized2) continue;
605
1360
  rows.push(normalized2);
606
1361
  if (rows.length >= MAX_PERSISTED_ALLOWANCE_SNAPSHOTS) break;
@@ -883,7 +1638,7 @@ var import_outbound_api5 = require("@omnicross/core/outbound-api");
883
1638
  var import_image_generation_types = require("@omnicross/contracts/image-generation-types");
884
1639
  var import_AccountAllowanceScheduling2 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
885
1640
  var import_SubscriptionAccountHealth = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
886
- var import_upstreamFetch4 = require("@omnicross/core/pipeline/upstreamFetch");
1641
+ var import_upstreamFetch7 = require("@omnicross/core/pipeline/upstreamFetch");
887
1642
 
888
1643
  // src/image-generation/imagesConfigValidation.ts
889
1644
  var import_outbound_api = require("@omnicross/core/outbound-api");
@@ -2851,11 +3606,11 @@ function preserveOutboundProxySecrets(incoming, current) {
2851
3606
  }
2852
3607
 
2853
3608
  // src/proxy/upstreamProxyResolver.ts
2854
- var import_upstreamFetch2 = require("@omnicross/core/pipeline/upstreamFetch");
3609
+ var import_upstreamFetch5 = require("@omnicross/core/pipeline/upstreamFetch");
2855
3610
  var serverProxy;
2856
3611
  function setServerProxyConfig(proxy) {
2857
3612
  serverProxy = proxy;
2858
- (0, import_upstreamFetch2.bumpUpstreamProxyGeneration)();
3613
+ (0, import_upstreamFetch5.bumpUpstreamProxyGeneration)();
2859
3614
  }
2860
3615
  function getServerProxyConfig() {
2861
3616
  return serverProxy;
@@ -2923,14 +3678,15 @@ function createUpstreamProxyResolver(src = {}) {
2923
3678
  }
2924
3679
 
2925
3680
  // src/admin/accountsOAuth.ts
2926
- var import_subscriptions2 = require("@omnicross/subscriptions");
3681
+ var import_subscriptions5 = require("@omnicross/subscriptions");
2927
3682
 
2928
3683
  // src/admin/accountsWrite.ts
2929
3684
  var VALID_PROVIDER_IDS = [
2930
3685
  "claude",
2931
3686
  "codex",
2932
3687
  "gemini",
2933
- "opencodego"
3688
+ "opencodego",
3689
+ "kimi"
2934
3690
  ];
2935
3691
  function asSubscriptionProviderId(id) {
2936
3692
  return VALID_PROVIDER_IDS.includes(id) ? id : null;
@@ -3066,6 +3822,18 @@ function validateGemini(body) {
3066
3822
  copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "lastRefreshedAt", "errorMessage"]);
3067
3823
  return out;
3068
3824
  }
3825
+ function validateKimi(body) {
3826
+ const authMethod = str(body["authMethod"]);
3827
+ const status = str(body["status"]);
3828
+ if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
3829
+ if (!status || !TOKEN_STATUSES.has(status)) return null;
3830
+ const out = {
3831
+ authMethod,
3832
+ status
3833
+ };
3834
+ copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "accountId", "deviceId", "lastRefreshedAt", "errorMessage"]);
3835
+ return out;
3836
+ }
3069
3837
  function validateOpenCodeGo(body) {
3070
3838
  const authMethod = str(body["authMethod"]);
3071
3839
  const status = str(body["status"]);
@@ -3101,6 +3869,8 @@ function validateTokenBody(providerId, body) {
3101
3869
  return validateGemini(body);
3102
3870
  case "opencodego":
3103
3871
  return validateOpenCodeGo(body);
3872
+ case "kimi":
3873
+ return validateKimi(body);
3104
3874
  default:
3105
3875
  return null;
3106
3876
  }
@@ -3130,37 +3900,37 @@ async function statusEntryFor(reader, providerId) {
3130
3900
 
3131
3901
  // src/admin/accountsOAuth.ts
3132
3902
  var OAUTH_HTTP_PROVIDERS = /* @__PURE__ */ new Set(["claude", "gemini"]);
3133
- function err2(status, message) {
3903
+ function err3(status, message) {
3134
3904
  return { status, body: { error: { type: "admin_api_error", message } } };
3135
3905
  }
3136
3906
  function handleOAuthStart(providerId, deps) {
3137
3907
  if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
3138
- return err2(400, `oauth not available for provider '${providerId}'`);
3908
+ return err3(400, `oauth not available for provider '${providerId}'`);
3139
3909
  }
3140
- const flow = providerId === "claude" ? import_subscriptions2.claudeOAuth : import_subscriptions2.geminiOAuth;
3910
+ const flow = providerId === "claude" ? import_subscriptions5.claudeOAuth : import_subscriptions5.geminiOAuth;
3141
3911
  const { authUrl, codeVerifier, state } = flow.generateAuthParams();
3142
3912
  const sessionId = deps.oauthSessions.put({ providerId, codeVerifier, state });
3143
3913
  return { status: 200, body: { authUrl, sessionId } };
3144
3914
  }
3145
3915
  async function handleOAuthComplete(providerId, body, deps) {
3146
3916
  if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
3147
- return err2(400, `oauth not available for provider '${providerId}'`);
3917
+ return err3(400, `oauth not available for provider '${providerId}'`);
3148
3918
  }
3149
3919
  const sessionId = typeof body["sessionId"] === "string" ? body["sessionId"] : "";
3150
3920
  const rawCode = typeof body["code"] === "string" ? body["code"] : "";
3151
- if (!sessionId) return err2(400, "oauth complete requires { sessionId }");
3152
- if (!rawCode) return err2(400, "oauth complete requires { code }");
3921
+ if (!sessionId) return err3(400, "oauth complete requires { sessionId }");
3922
+ if (!rawCode) return err3(400, "oauth complete requires { code }");
3153
3923
  const session = deps.oauthSessions.peek(sessionId);
3154
- if (!session) return err2(410, "oauth session is unknown, expired, or already used");
3924
+ if (!session) return err3(410, "oauth session is unknown, expired, or already used");
3155
3925
  if (session.providerId !== providerId) {
3156
- return err2(400, `oauth session does not match provider '${providerId}'`);
3926
+ return err3(400, `oauth session does not match provider '${providerId}'`);
3157
3927
  }
3158
3928
  let code = rawCode.trim();
3159
3929
  if (providerId === "claude") {
3160
3930
  const [splitCode, pastedState] = code.split("#");
3161
- if (!splitCode) return err2(400, "no authorization code was provided");
3931
+ if (!splitCode) return err3(400, "no authorization code was provided");
3162
3932
  if (pastedState && pastedState !== session.state) {
3163
- return err2(400, "oauth state did not match (possible CSRF) \u2014 aborting");
3933
+ return err3(400, "oauth state did not match (possible CSRF) \u2014 aborting");
3164
3934
  }
3165
3935
  code = splitCode;
3166
3936
  }
@@ -3170,7 +3940,7 @@ async function handleOAuthComplete(providerId, body, deps) {
3170
3940
  block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state, exchangeFetch) : await exchangeGemini(code, session.codeVerifier, exchangeFetch);
3171
3941
  } catch (exchangeError) {
3172
3942
  const reason = exchangeError instanceof Error ? exchangeError.message : "token exchange failed";
3173
- return err2(502, `oauth token exchange failed for '${providerId}': ${reason}`);
3943
+ return err3(502, `oauth token exchange failed for '${providerId}': ${reason}`);
3174
3944
  }
3175
3945
  deps.oauthSessions.consume(sessionId);
3176
3946
  const label = typeof body["label"] === "string" && body["label"].trim() ? body["label"].trim() : void 0;
@@ -3179,7 +3949,7 @@ async function handleOAuthComplete(providerId, body, deps) {
3179
3949
  return { status: 200, body: status ? { account: status } : { ok: true } };
3180
3950
  }
3181
3951
  async function exchangeClaude(code, codeVerifier, state, exchangeFetch) {
3182
- const result = await import_subscriptions2.claudeOAuth.exchangeCodeForTokens(
3952
+ const result = await import_subscriptions5.claudeOAuth.exchangeCodeForTokens(
3183
3953
  { authorizationCode: code, codeVerifier, state },
3184
3954
  exchangeFetch
3185
3955
  );
@@ -3195,7 +3965,7 @@ async function exchangeClaude(code, codeVerifier, state, exchangeFetch) {
3195
3965
  };
3196
3966
  }
3197
3967
  async function exchangeGemini(code, codeVerifier, exchangeFetch) {
3198
- const result = await import_subscriptions2.geminiOAuth.exchangeCodeForTokens(code, codeVerifier, exchangeFetch);
3968
+ const result = await import_subscriptions5.geminiOAuth.exchangeCodeForTokens(code, codeVerifier, exchangeFetch);
3199
3969
  const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
3200
3970
  return {
3201
3971
  authMethod: "oauth",
@@ -3500,8 +4270,8 @@ function errBody(message) {
3500
4270
  return { error: { type: "admin_api_error", message } };
3501
4271
  }
3502
4272
  var defaultCommandRunner = (command) => new Promise((resolve10) => {
3503
- (0, import_node_child_process.exec)(command, { timeout: 18e4 }, (err5, _stdout, stderr) => {
3504
- if (err5) resolve10({ ok: false, error: stderr.trim() || err5.message });
4273
+ (0, import_node_child_process.exec)(command, { timeout: 18e4 }, (err6, _stdout, stderr) => {
4274
+ if (err6) resolve10({ ok: false, error: stderr.trim() || err6.message });
3505
4275
  else resolve10({ ok: true });
3506
4276
  });
3507
4277
  });
@@ -3547,8 +4317,8 @@ async function handleCliLaunch(cli, body, ctx) {
3547
4317
  providerId: typeof body["providerId"] === "string" ? body["providerId"] : void 0,
3548
4318
  model: typeof body["model"] === "string" ? body["model"] : void 0
3549
4319
  });
3550
- } catch (err5) {
3551
- return { status: 400, body: errBody(err5 instanceof Error ? err5.message : "no launch target") };
4320
+ } catch (err6) {
4321
+ return { status: 400, body: errBody(err6 instanceof Error ? err6.message : "no launch target") };
3552
4322
  }
3553
4323
  const id = (0, import_node_crypto7.randomUUID)();
3554
4324
  let leaseId2;
@@ -3576,9 +4346,9 @@ async function handleCliLaunch(cli, body, ctx) {
3576
4346
  } else {
3577
4347
  launch = await buildLaunchEnv(cli, ctx.llmConfig, target);
3578
4348
  }
3579
- } catch (err5) {
3580
- const status = err5 instanceof import_provider_proxy2.RouteLeaseError ? err5.status : 400;
3581
- return { status, body: errBody(err5 instanceof Error ? err5.message : "failed to build launch env") };
4349
+ } catch (err6) {
4350
+ const status = err6 instanceof import_provider_proxy2.RouteLeaseError ? err6.status : 400;
4351
+ return { status, body: errBody(err6 instanceof Error ? err6.message : "failed to build launch env") };
3582
4352
  }
3583
4353
  const cwd = typeof body["cwd"] === "string" && body["cwd"].trim() ? body["cwd"].trim() : void 0;
3584
4354
  const opener = ctx.opener ?? defaultTerminalOpener;
@@ -3606,9 +4376,9 @@ async function handleCliLaunch(cli, body, ctx) {
3606
4376
  onFailure: onSessionEnd
3607
4377
  });
3608
4378
  if (cleanup) openerCleanup = cleanup;
3609
- } catch (err5) {
4379
+ } catch (err6) {
3610
4380
  onSessionEnd();
3611
- return { status: 500, body: errBody(err5 instanceof Error ? err5.message : "failed to open terminal") };
4381
+ return { status: 500, body: errBody(err6 instanceof Error ? err6.message : "failed to open terminal") };
3612
4382
  }
3613
4383
  if (ended) {
3614
4384
  openerCleanup?.();
@@ -3849,7 +4619,7 @@ function classifySearchFailure(stage, code) {
3849
4619
  }
3850
4620
 
3851
4621
  // src/search/SearchAssembly.ts
3852
- var import_upstreamFetch3 = require("@omnicross/core/pipeline/upstreamFetch");
4622
+ var import_upstreamFetch6 = require("@omnicross/core/pipeline/upstreamFetch");
3853
4623
  var import_search = require("@omnicross/core/search");
3854
4624
  var import_api2 = require("@omnicross/core/search/api");
3855
4625
  var import_http2 = require("@omnicross/core/search/http");
@@ -3867,7 +4637,7 @@ function searchPolicyFrom(config) {
3867
4637
  };
3868
4638
  }
3869
4639
  function resolveSearchUpstreamDispatcher(url) {
3870
- return (0, import_upstreamFetch3.resolveUpstreamDispatcher)({ url });
4640
+ return (0, import_upstreamFetch6.resolveUpstreamDispatcher)({ url });
3871
4641
  }
3872
4642
  var searchUpstreamProxyConfig = createUpstreamProxyResolver();
3873
4643
  function resolveSearchUpstreamProxyConfig(url) {
@@ -4149,7 +4919,7 @@ async function handleSearchQuery(req, res, deps) {
4149
4919
  // src/admin/searchAdminView.ts
4150
4920
  var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
4151
4921
  var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
4152
- function isRecord(value) {
4922
+ function isRecord2(value) {
4153
4923
  return value !== null && typeof value === "object" && !Array.isArray(value);
4154
4924
  }
4155
4925
  function redactSearchServerConfig(search) {
@@ -4199,13 +4969,13 @@ function resolveSecretField(entry, field, stored) {
4199
4969
  else delete entry[field];
4200
4970
  }
4201
4971
  function preserveSearchSecrets(incoming, current) {
4202
- if (!isRecord(incoming)) return incoming;
4972
+ if (!isRecord2(incoming)) return incoming;
4203
4973
  const section = { ...incoming };
4204
4974
  const providersValue = section["providers"];
4205
- if (!isRecord(providersValue)) return section;
4975
+ if (!isRecord2(providersValue)) return section;
4206
4976
  const providers = {};
4207
4977
  for (const [id, entryValue] of Object.entries(providersValue)) {
4208
- if (!isRecord(entryValue)) {
4978
+ if (!isRecord2(entryValue)) {
4209
4979
  providers[id] = entryValue;
4210
4980
  continue;
4211
4981
  }
@@ -4283,7 +5053,7 @@ function parseKeyPolicyBody(body) {
4283
5053
  var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
4284
5054
  var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
4285
5055
  var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
4286
- function isRecord2(value) {
5056
+ function isRecord3(value) {
4287
5057
  return !!value && typeof value === "object" && !Array.isArray(value);
4288
5058
  }
4289
5059
  function nonBlank(value) {
@@ -4303,7 +5073,7 @@ function validateGatewayBindingsSegment(patch) {
4303
5073
  const ids = /* @__PURE__ */ new Set();
4304
5074
  raw.forEach((entry, index) => {
4305
5075
  const path2 = `bindings[${index}]`;
4306
- if (!isRecord2(entry)) {
5076
+ if (!isRecord3(entry)) {
4307
5077
  errors.push(`${path2} must be an object`);
4308
5078
  return;
4309
5079
  }
@@ -4332,12 +5102,12 @@ function validateGatewayBindingsSegment(patch) {
4332
5102
  } else if (entry.modelMappings.length > 100) {
4333
5103
  errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
4334
5104
  } else if (entry.modelMappings.some(
4335
- (mapping) => !isRecord2(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
5105
+ (mapping) => !isRecord3(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
4336
5106
  )) {
4337
5107
  errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
4338
5108
  }
4339
5109
  }
4340
- if (!isRecord2(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
5110
+ if (!isRecord3(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
4341
5111
  errors.push(`${path2}.target is invalid`);
4342
5112
  } else {
4343
5113
  if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
@@ -4352,7 +5122,7 @@ function validateGatewayBindingsSegment(patch) {
4352
5122
  }
4353
5123
  }
4354
5124
  if (entry.modelMap !== void 0) {
4355
- if (!isRecord2(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
5125
+ if (!isRecord3(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
4356
5126
  errors.push(`${path2}.modelMap must contain string values`);
4357
5127
  }
4358
5128
  }
@@ -4650,7 +5420,8 @@ var PROVIDER_KEYS = {
4650
5420
  block: "opencodego",
4651
5421
  accounts: "opencodegoAccounts",
4652
5422
  active: "activeOpencodegoAccountId"
4653
- }
5423
+ },
5424
+ kimi: { block: "kimi", accounts: "kimiAccounts", active: "activeKimiAccountId" }
4654
5425
  };
4655
5426
  function clone(value) {
4656
5427
  return JSON.parse(JSON.stringify(value));
@@ -5172,7 +5943,7 @@ async function applyImport(packString, passphrase, mode, deps, parseProviderInpu
5172
5943
  }
5173
5944
 
5174
5945
  // src/admin/adminMigration.ts
5175
- function err3(status, message) {
5946
+ function err4(status, message) {
5176
5947
  return { status, body: { error: { type: "admin_api_error", message } } };
5177
5948
  }
5178
5949
  async function handleExport(body, deps) {
@@ -5182,30 +5953,30 @@ async function handleExport(body, deps) {
5182
5953
  return { status: 200, body: { pack, version: BUNDLE_VERSION } };
5183
5954
  } catch (error) {
5184
5955
  if (error instanceof WeakPassphraseError) {
5185
- return err3(400, error.message);
5956
+ return err4(400, error.message);
5186
5957
  }
5187
- return err3(500, "failed to build the migration pack");
5958
+ return err4(500, "failed to build the migration pack");
5188
5959
  }
5189
5960
  }
5190
5961
  async function handleImport(body, deps) {
5191
5962
  const blob = typeof body["blob"] === "string" ? body["blob"] : "";
5192
5963
  const passphrase = typeof body["passphrase"] === "string" ? body["passphrase"] : "";
5193
5964
  const mode = body["mode"] === "overwrite" ? "overwrite" : "merge";
5194
- if (!blob) return err3(400, "import requires { blob }");
5965
+ if (!blob) return err4(400, "import requires { blob }");
5195
5966
  try {
5196
5967
  const counts = await applyImport(blob, passphrase, mode, deps, deps.parseProviderInput);
5197
5968
  return { status: 200, body: counts };
5198
5969
  } catch (error) {
5199
5970
  if (error instanceof WeakPassphraseError) {
5200
- return err3(400, error.message);
5971
+ return err4(400, error.message);
5201
5972
  }
5202
- return err3(400, error instanceof Error ? error.message : "import failed");
5973
+ return err4(400, error instanceof Error ? error.message : "import failed");
5203
5974
  }
5204
5975
  }
5205
5976
 
5206
5977
  // src/admin/usagePricing.ts
5207
5978
  var import_usage = require("@omnicross/core/usage");
5208
- var err4 = (status, message) => ({
5979
+ var err5 = (status, message) => ({
5209
5980
  status,
5210
5981
  body: { error: { type: "admin_api_error", message } }
5211
5982
  });
@@ -5218,7 +5989,7 @@ function parseRange(query2) {
5218
5989
  const startTs = parseFiniteInt(query2.get("startTs"));
5219
5990
  const endTs = parseFiniteInt(query2.get("endTs"));
5220
5991
  if (startTs === null || endTs === null) {
5221
- return err4(400, "startTs and endTs are required finite-integer unix-millis query params");
5992
+ return err5(400, "startTs and endTs are required finite-integer unix-millis query params");
5222
5993
  }
5223
5994
  return { startTs, endTs };
5224
5995
  }
@@ -5243,14 +6014,14 @@ async function handleUsageGet(view, query2, deps) {
5243
6014
  case "timeseries": {
5244
6015
  const bucket = query2.get("bucket");
5245
6016
  if (bucket !== "hour" && bucket !== "day" && bucket !== "month") {
5246
- return err4(400, "bucket must be one of 'hour', 'day', 'month'");
6017
+ return err5(400, "bucket must be one of 'hour', 'day', 'month'");
5247
6018
  }
5248
6019
  const now = Date.now();
5249
6020
  const clamped = { startTs: range.startTs, endTs: Math.min(range.endTs, now) };
5250
6021
  if (clamped.startTs < clamped.endTs) {
5251
6022
  const projected = Math.ceil((clamped.endTs - clamped.startTs) / BUCKET_SPAN_MS[bucket]) + 1;
5252
6023
  if (projected > MAX_TIMESERIES_BUCKETS) {
5253
- return err4(
6024
+ return err5(
5254
6025
  400,
5255
6026
  `requested range projects ~${projected} '${bucket}' buckets (max ${MAX_TIMESERIES_BUCKETS}); narrow the range or use a coarser bucket`
5256
6027
  );
@@ -5273,7 +6044,7 @@ async function handleUsageGet(view, query2, deps) {
5273
6044
  };
5274
6045
  }
5275
6046
  default:
5276
- return err4(404, `unknown usage view '${view ?? ""}'`);
6047
+ return err5(404, `unknown usage view '${view ?? ""}'`);
5277
6048
  }
5278
6049
  }
5279
6050
  function poolKeyLabels(cfg) {
@@ -5322,7 +6093,7 @@ async function handlePricingList(deps) {
5322
6093
  async function handlePricingUpsert(body, deps) {
5323
6094
  const input = parsePricingEntryInput(body);
5324
6095
  if (!input) {
5325
- return err4(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
6096
+ return err5(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
5326
6097
  }
5327
6098
  const entry = await deps.pricingEngine.upsertManual(input);
5328
6099
  return { status: 200, body: { entry } };
@@ -5331,7 +6102,7 @@ async function handlePricingDelete(query2, deps) {
5331
6102
  const providerId = query2.get("providerId")?.trim() ?? "";
5332
6103
  const modelId = query2.get("modelId")?.trim() ?? "";
5333
6104
  if (!providerId || !modelId) {
5334
- return err4(400, "delete requires providerId and modelId query params");
6105
+ return err5(400, "delete requires providerId and modelId query params");
5335
6106
  }
5336
6107
  const deleted = await deps.pricingStore.delete(providerId, modelId);
5337
6108
  if (deleted) await deps.pricingEngine.invalidateCache();
@@ -5351,13 +6122,13 @@ async function handlePricingFetchLatest(deps) {
5351
6122
  }
5352
6123
  };
5353
6124
  } catch (e) {
5354
- return err4(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
6125
+ return err5(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
5355
6126
  }
5356
6127
  }
5357
6128
  async function handlePricingResolveConflicts(body, deps) {
5358
6129
  const raw = body["resolutions"];
5359
6130
  if (!Array.isArray(raw)) {
5360
- return err4(400, "resolve-conflicts requires { resolutions: [...] }");
6131
+ return err5(400, "resolve-conflicts requires { resolutions: [...] }");
5361
6132
  }
5362
6133
  const currentRows = await deps.pricingStore.getAll();
5363
6134
  const userEditedKeys = new Set(
@@ -5367,21 +6138,21 @@ async function handlePricingResolveConflicts(body, deps) {
5367
6138
  const pendingIncoming = /* @__PURE__ */ new Map();
5368
6139
  let staleCount = 0;
5369
6140
  for (const item of raw) {
5370
- if (!item || typeof item !== "object") return err4(400, "invalid resolution entry");
6141
+ if (!item || typeof item !== "object") return err5(400, "invalid resolution entry");
5371
6142
  const r = item;
5372
6143
  const action = r["action"];
5373
6144
  if (action !== "overwrite" && action !== "skip") {
5374
- return err4(400, "resolution action must be 'overwrite' or 'skip'");
6145
+ return err5(400, "resolution action must be 'overwrite' or 'skip'");
5375
6146
  }
5376
6147
  const providerId = typeof r["providerId"] === "string" && r["providerId"].trim() ? r["providerId"].trim() : "";
5377
6148
  const modelId = typeof r["modelId"] === "string" && r["modelId"].trim() ? r["modelId"].trim() : "";
5378
6149
  if (!providerId || !modelId) {
5379
- return err4(400, "each resolution requires top-level providerId and modelId");
6150
+ return err5(400, "each resolution requires top-level providerId and modelId");
5380
6151
  }
5381
6152
  const incoming = parsePricingEntryInput(r["incoming"]);
5382
- if (!incoming) return err4(400, "each resolution must echo a valid incoming pricing entry");
6153
+ if (!incoming) return err5(400, "each resolution must echo a valid incoming pricing entry");
5383
6154
  if (incoming.providerId !== providerId || incoming.modelId !== modelId) {
5384
- return err4(400, "resolution providerId/modelId must match the echoed incoming entry");
6155
+ return err5(400, "resolution providerId/modelId must match the echoed incoming entry");
5385
6156
  }
5386
6157
  const key = `${providerId}::${modelId}`;
5387
6158
  if (action === "overwrite" && !userEditedKeys.has(key)) {
@@ -5426,7 +6197,7 @@ function query(req) {
5426
6197
  }
5427
6198
  function allowanceProvider(value) {
5428
6199
  if (!value) return void 0;
5429
- return value === "claude" || value === "codex" ? value : null;
6200
+ return value === "claude" || value === "codex" || value === "kimi" || value === "opencodego" ? value : null;
5430
6201
  }
5431
6202
  async function handleAccountAllowanceApi(req, res, method, rest, service) {
5432
6203
  if (!service) return writeError2(res, 501, "account allowance service is not available");
@@ -5440,7 +6211,9 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
5440
6211
  const params = query(req);
5441
6212
  const pathProvider = rest.length >= 2 ? rest[0] : null;
5442
6213
  const providerId = allowanceProvider(pathProvider ?? params.get("providerId") ?? params.get("provider"));
5443
- if (providerId === null) return writeError2(res, 400, "providerId must be claude or codex");
6214
+ if (providerId === null) {
6215
+ return writeError2(res, 400, "providerId must be claude, codex, kimi, or opencodego");
6216
+ }
5444
6217
  const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
5445
6218
  const allowances = await service.list({ providerId, accountId });
5446
6219
  return writeJson3(res, 200, { allowances });
@@ -5450,10 +6223,37 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
5450
6223
  const requestedProvider = allowanceProvider(
5451
6224
  typeof body["providerId"] === "string" ? body["providerId"] : "claude"
5452
6225
  );
5453
- if (requestedProvider !== "claude") {
5454
- return writeError2(res, 400, "only Claude allowances support explicit refresh");
5455
- }
5456
6226
  const accountId = typeof body["accountId"] === "string" && body["accountId"].trim() ? body["accountId"].trim() : void 0;
6227
+ if (requestedProvider === "codex") {
6228
+ if (!service.refreshCodex) {
6229
+ return writeError2(res, 501, "codex allowance refresh is not available");
6230
+ }
6231
+ const allowances2 = await service.refreshCodex(accountId);
6232
+ if (accountId && allowances2.length === 0) {
6233
+ return writeError2(res, 404, `Codex account '${accountId}' not found`);
6234
+ }
6235
+ return writeJson3(res, 200, { allowances: allowances2 });
6236
+ }
6237
+ if (requestedProvider === "kimi") {
6238
+ if (!service.refreshKimi) {
6239
+ return writeError2(res, 501, "kimi allowance refresh is not available");
6240
+ }
6241
+ const allowances2 = await service.refreshKimi(accountId);
6242
+ if (accountId && allowances2.length === 0) {
6243
+ return writeError2(res, 404, `Kimi account '${accountId}' not found`);
6244
+ }
6245
+ return writeJson3(res, 200, { allowances: allowances2 });
6246
+ }
6247
+ if (requestedProvider === "opencodego") {
6248
+ if (!service.refreshOpenCodeGo) {
6249
+ return writeError2(res, 501, "opencodego allowance refresh is not available");
6250
+ }
6251
+ const allowances2 = await service.refreshOpenCodeGo(accountId);
6252
+ if (accountId && allowances2.length === 0) {
6253
+ return writeError2(res, 404, `OpenCodeGo account '${accountId}' not found`);
6254
+ }
6255
+ return writeJson3(res, 200, { allowances: allowances2 });
6256
+ }
5457
6257
  const allowances = await service.refreshClaude(accountId);
5458
6258
  if (accountId && allowances.length === 0) {
5459
6259
  return writeError2(res, 404, `Claude account '${accountId}' not found`);
@@ -5621,8 +6421,8 @@ async function handleAdminApi(req, res, path2, deps) {
5621
6421
  default:
5622
6422
  return writeJsonError(res, 404, `unknown admin resource '${resource}'`);
5623
6423
  }
5624
- } catch (err5) {
5625
- writeJsonError(res, 500, err5 instanceof Error ? err5.message : String(err5));
6424
+ } catch (err6) {
6425
+ writeJsonError(res, 500, err6 instanceof Error ? err6.message : String(err6));
5626
6426
  }
5627
6427
  }
5628
6428
  function requestQuery(req) {
@@ -5692,6 +6492,9 @@ async function handleProviders(req, res, method, rest, deps) {
5692
6492
  if (method === "POST" && rest.length === 4 && rest[1] === "keys" && rest[3] === "enabled") {
5693
6493
  return await handleToggleProviderKey(req, res, rest[0], rest[2], cfg, deps);
5694
6494
  }
6495
+ if (method === "POST" && rest.length === 5 && rest[1] === "keys" && rest[3] === "quota" && rest[4] === "refresh") {
6496
+ return await handleProviderKeyQuotaRefresh(res, rest[0], rest[2], cfg, deps);
6497
+ }
5695
6498
  if (method === "PUT" && rest.length === 3 && rest[1] === "keys") {
5696
6499
  return await handleUpdateProviderKey(req, res, rest[0], rest[2], cfg, deps);
5697
6500
  }
@@ -5790,7 +6593,7 @@ async function handleDiscoverModels(res, id, cfg) {
5790
6593
  try {
5791
6594
  const headers = { Accept: "application/json" };
5792
6595
  if (resolvedKey) headers["Authorization"] = `Bearer ${resolvedKey}`;
5793
- const response = await (0, import_upstreamFetch4.fetchUpstream)(url, { method: "GET", headers }, { providerId: "byo" });
6596
+ const response = await (0, import_upstreamFetch7.fetchUpstream)(url, { method: "GET", headers }, { providerId: "byo" });
5794
6597
  if (!response.ok) {
5795
6598
  const text = await response.text().catch(() => "");
5796
6599
  let message = text.slice(0, 300);
@@ -5807,8 +6610,8 @@ async function handleDiscoverModels(res, id, cfg) {
5807
6610
  const data = await response.json();
5808
6611
  const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
5809
6612
  return writeJson4(res, 200, { models });
5810
- } catch (err5) {
5811
- const message = err5 instanceof Error ? err5.message : String(err5);
6613
+ } catch (err6) {
6614
+ const message = err6 instanceof Error ? err6.message : String(err6);
5812
6615
  return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
5813
6616
  }
5814
6617
  }
@@ -5849,7 +6652,7 @@ async function handleTestModel(req, res, id, cfg) {
5849
6652
  }
5850
6653
  const startedAt = Date.now();
5851
6654
  try {
5852
- const response = await (0, import_upstreamFetch4.fetchUpstream)(
6655
+ const response = await (0, import_upstreamFetch7.fetchUpstream)(
5853
6656
  url,
5854
6657
  { method: "POST", headers, body: JSON.stringify(payload) },
5855
6658
  { providerId: "byo" }
@@ -5871,8 +6674,8 @@ async function handleTestModel(req, res, id, cfg) {
5871
6674
  latencyMs,
5872
6675
  sample: extractSampleText(text, row.apiFormat)
5873
6676
  });
5874
- } catch (err5) {
5875
- const message = err5 instanceof Error ? err5.message : String(err5);
6677
+ } catch (err6) {
6678
+ const message = err6 instanceof Error ? err6.message : String(err6);
5876
6679
  return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
5877
6680
  }
5878
6681
  }
@@ -5914,7 +6717,30 @@ async function handleProviderKeys(res, id, cfg, deps) {
5914
6717
  const row = cfg.providers.find((p) => p.id === id);
5915
6718
  if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
5916
6719
  const cooldown = await deps.apiKeyPool.getKeyHealth(id);
5917
- return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
6720
+ const views = toPoolKeyView(row, cooldown, deps);
6721
+ if (deps.providerKeyQuota) {
6722
+ const quotas = await Promise.allSettled(
6723
+ views.map((view) => deps.providerKeyQuota.quotaFor(row, view.id))
6724
+ );
6725
+ views.forEach((view, index) => {
6726
+ const settled = quotas[index];
6727
+ if (settled.status === "fulfilled" && settled.value) view.quota = settled.value;
6728
+ });
6729
+ }
6730
+ return writeJson4(res, 200, { keys: views });
6731
+ }
6732
+ async function handleProviderKeyQuotaRefresh(res, id, keyId, cfg, deps) {
6733
+ if (!deps.providerKeyQuota) return writeJsonError(res, 501, "provider key quota is not available");
6734
+ if (!id || !keyId) return writeJsonError(res, 400, "provider id and key id required in path");
6735
+ const row = cfg.providers.find((p) => p.id === id);
6736
+ if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
6737
+ try {
6738
+ const quota = await deps.providerKeyQuota.quotaFor(row, keyId, { force: true });
6739
+ if (!quota) return writeJsonError(res, 404, `no quota endpoint for key '${keyId}'`);
6740
+ return writeJson4(res, 200, { quota });
6741
+ } catch {
6742
+ return writeJsonError(res, 502, "quota refresh failed");
6743
+ }
5918
6744
  }
5919
6745
  function parsePoolKeyInput(body, existing) {
5920
6746
  const out = {};
@@ -6659,12 +7485,12 @@ async function handleAccounts(req, res, method, rest, deps) {
6659
7485
  }
6660
7486
  return writeJson4(res, 200, { ok: true, affected: result.affected });
6661
7487
  }
6662
- if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
6663
- const result = handleCodexOAuthStatus(rest[2], deps);
7488
+ if (method === "GET" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[3] === "status") {
7489
+ const result = rest[0] === "codex" ? handleCodexOAuthStatus(rest[2], deps) : handleKimiOAuthStatus(rest[2], deps);
6664
7490
  return writeJson4(res, result.status, result.body);
6665
7491
  }
6666
- if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
6667
- const result = handleCodexOAuthCancel(rest[2], deps);
7492
+ if (method === "DELETE" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[2]) {
7493
+ const result = rest[0] === "codex" ? handleCodexOAuthCancel(rest[2], deps) : handleKimiOAuthCancel(rest[2], deps);
6668
7494
  return writeJson4(res, result.status, result.body);
6669
7495
  }
6670
7496
  if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
@@ -6717,7 +7543,15 @@ async function handleAccounts(req, res, method, rest, deps) {
6717
7543
  return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
6718
7544
  }
6719
7545
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
6720
- const result = providerId === "codex" ? handleCodexOAuthStart(deps) : handleOAuthStart(providerId, deps);
7546
+ if (providerId === "codex") {
7547
+ const result2 = handleCodexOAuthStart(deps);
7548
+ return writeJson4(res, result2.status, result2.body);
7549
+ }
7550
+ if (providerId === "kimi") {
7551
+ const result2 = await handleKimiOAuthStart(deps);
7552
+ return writeJson4(res, result2.status, result2.body);
7553
+ }
7554
+ const result = handleOAuthStart(providerId, deps);
6721
7555
  return writeJson4(res, result.status, result.body);
6722
7556
  }
6723
7557
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
@@ -7211,12 +8045,12 @@ async function handlePlayground(req, res, method, deps) {
7211
8045
  const payload = body["body"];
7212
8046
  const status = deps.outboundApiServer.getStatus();
7213
8047
  if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
7214
- const path2 = resolvePlaygroundPath(endpoint, isRecord3(payload) ? payload : {});
8048
+ const path2 = resolvePlaygroundPath(endpoint, isRecord4(payload) ? payload : {});
7215
8049
  if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
7216
8050
  const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
7217
8051
  await proxyToOutbound(res, status.port, path2, key, upstreamBody);
7218
8052
  }
7219
- function isRecord3(v) {
8053
+ function isRecord4(v) {
7220
8054
  return !!v && typeof v === "object" && !Array.isArray(v);
7221
8055
  }
7222
8056
  function proxyToOutbound(res, outboundPort, path2, key, body) {
@@ -7245,8 +8079,8 @@ function proxyToOutbound(res, outboundPort, path2, key, body) {
7245
8079
  });
7246
8080
  }
7247
8081
  );
7248
- upstream.on("error", (err5) => {
7249
- if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err5.message}`);
8082
+ upstream.on("error", (err6) => {
8083
+ if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err6.message}`);
7250
8084
  else res.end();
7251
8085
  resolve10();
7252
8086
  });
@@ -7352,7 +8186,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
7352
8186
  }
7353
8187
 
7354
8188
  // src/admin/version.ts
7355
- var DAEMON_VERSION = true ? "0.3.0" : "0.0.0-dev";
8189
+ var DAEMON_VERSION = true ? "0.3.1" : "0.0.0-dev";
7356
8190
 
7357
8191
  // src/admin/AdminServer.ts
7358
8192
  var LOOPBACK_ADDR = "127.0.0.1";
@@ -7395,13 +8229,13 @@ var AdminServer = class {
7395
8229
  const server = import_node_http2.default.createServer((req, res) => {
7396
8230
  this.onRequest(req, res);
7397
8231
  });
7398
- const onError = (err5) => {
7399
- if (err5.code === "EADDRINUSE" && port !== 0) {
8232
+ const onError = (err6) => {
8233
+ if (err6.code === "EADDRINUSE" && port !== 0) {
7400
8234
  server.removeListener("error", onError);
7401
8235
  this.listen(bindAddr, 0).then(resolve10, reject);
7402
8236
  return;
7403
8237
  }
7404
- reject(err5);
8238
+ reject(err6);
7405
8239
  };
7406
8240
  server.on("error", onError);
7407
8241
  server.listen(port, bindAddr, () => {
@@ -7419,8 +8253,8 @@ var AdminServer = class {
7419
8253
  }
7420
8254
  /** Per-request handler: auth gate (when a token is set) → routing. */
7421
8255
  onRequest(req, res) {
7422
- void this.dispatch(req, res).catch((err5) => {
7423
- const message = err5 instanceof Error ? err5.message : String(err5);
8256
+ void this.dispatch(req, res).catch((err6) => {
8257
+ const message = err6 instanceof Error ? err6.message : String(err6);
7424
8258
  this.deps.logger.error("[AdminServer] unhandled error:", message);
7425
8259
  if (!res.headersSent) {
7426
8260
  res.writeHead(500, { "Content-Type": "application/json" });
@@ -7684,18 +8518,18 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
7684
8518
  return;
7685
8519
  }
7686
8520
  signal?.addEventListener("abort", abort, { once: true });
7687
- server.on("error", (err5) => {
8521
+ server.on("error", (err6) => {
7688
8522
  if (settled) return;
7689
8523
  settled = true;
7690
8524
  clearTimeout(timer);
7691
- if (err5.code === "EADDRINUSE") {
8525
+ if (err6.code === "EADDRINUSE") {
7692
8526
  reject(
7693
8527
  new Error(
7694
8528
  `login: cannot bind ${LOOPBACK_HOST}:${LOOPBACK_PORT} (address in use) \u2014 another codex login or process is holding the port`
7695
8529
  )
7696
8530
  );
7697
8531
  } else {
7698
- reject(err5);
8532
+ reject(err6);
7699
8533
  }
7700
8534
  });
7701
8535
  const timer = setTimeout(() => {
@@ -7770,6 +8604,411 @@ function createPoolKeysLoader(getProviderRow, autoDisabled) {
7770
8604
  };
7771
8605
  }
7772
8606
 
8607
+ // src/allowance/ProviderKeyQuotaService.ts
8608
+ var import_upstreamFetch8 = require("@omnicross/core/pipeline/upstreamFetch");
8609
+
8610
+ // src/allowance/ProviderKeyQuota.ts
8611
+ var MINUTE_MS2 = 6e4;
8612
+ var HOUR_MS2 = 60 * MINUTE_MS2;
8613
+ var DAY_MS2 = 24 * HOUR_MS2;
8614
+ var WEEK_MS = 7 * DAY_MS2;
8615
+ var MONTH_MS = 30 * DAY_MS2;
8616
+ function finiteNumber3(value) {
8617
+ if (value === null || value === void 0 || value === "") return void 0;
8618
+ const parsed = typeof value === "number" ? value : Number(value);
8619
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
8620
+ }
8621
+ function finitePercent4(value) {
8622
+ const parsed = finiteNumber3(value);
8623
+ return parsed !== void 0 && parsed <= 100 ? parsed : null;
8624
+ }
8625
+ function isoInstant3(value) {
8626
+ if (typeof value === "string" && value.trim()) {
8627
+ const time = Date.parse(value);
8628
+ if (Number.isFinite(time)) return new Date(time).toISOString();
8629
+ }
8630
+ const numeric = finiteNumber3(value);
8631
+ if (numeric !== void 0 && numeric > 1e9) {
8632
+ const ms = numeric > 1e12 ? numeric : numeric * 1e3;
8633
+ return new Date(ms).toISOString();
8634
+ }
8635
+ return void 0;
8636
+ }
8637
+ function secondsUntil5(instant, now) {
8638
+ if (!instant) return void 0;
8639
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
8640
+ }
8641
+ function isRecord5(value) {
8642
+ return !!value && typeof value === "object" && !Array.isArray(value);
8643
+ }
8644
+ function detectProviderKeyQuotaAdapter(baseUrl) {
8645
+ if (!baseUrl) return null;
8646
+ let url;
8647
+ try {
8648
+ url = new URL(baseUrl);
8649
+ } catch {
8650
+ return null;
8651
+ }
8652
+ const host = url.hostname.toLowerCase();
8653
+ const path2 = url.pathname.toLowerCase();
8654
+ if ((host === "api.z.ai" || host === "open.bigmodel.cn") && path2.includes("/coding")) {
8655
+ return "zai";
8656
+ }
8657
+ if ((host === "api.minimax.io" || host === "api.minimaxi.com") && // Token Plan rides the plain openai `/v1` (chat completions) surface; the
8658
+ // anthropic `/anthropic` rows are excluded (their usage impl is unverified).
8659
+ (path2 === "/v1" || path2 === "/v1/" || path2 === "" || path2 === "/")) {
8660
+ return "minimax-token-plan";
8661
+ }
8662
+ if (host === "api.code.umans.ai") return "umans";
8663
+ if (host === "api.synthetic.new") return "synthetic";
8664
+ return null;
8665
+ }
8666
+ function providerKeyQuotaUrl(adapter, baseUrl) {
8667
+ const origin = new URL(baseUrl).origin;
8668
+ if (adapter === "zai") return `${origin}/api/monitor/usage/quota/limit`;
8669
+ if (adapter === "minimax-token-plan") return `${origin}/v1/token_plan/remains`;
8670
+ if (adapter === "umans") return `${origin}/v1/usage`;
8671
+ return `${origin}/v2/quotas`;
8672
+ }
8673
+ function providerKeyQuotaAuthHeader(adapter, key) {
8674
+ return adapter === "zai" ? key : `Bearer ${key}`;
8675
+ }
8676
+ function zaiWindowDurationMs(item) {
8677
+ const count = item.number !== void 0 && item.number > 0 ? item.number : 1;
8678
+ switch (item.unit) {
8679
+ case 3:
8680
+ return count * HOUR_MS2;
8681
+ case 4:
8682
+ return count * DAY_MS2;
8683
+ case 5:
8684
+ return count * MONTH_MS;
8685
+ case 6:
8686
+ return WEEK_MS;
8687
+ default:
8688
+ return void 0;
8689
+ }
8690
+ }
8691
+ function zaiWindowIdLabel(durationMs) {
8692
+ if (durationMs === WEEK_MS) return { id: "seven-day", label: "7 days" };
8693
+ if (durationMs === 5 * HOUR_MS2) return { id: "five-hour", label: "5 hours" };
8694
+ if (durationMs === MONTH_MS) return { id: "thirty-day", label: "30 days" };
8695
+ if (durationMs !== void 0 && durationMs % DAY_MS2 === 0) {
8696
+ const days = durationMs / DAY_MS2;
8697
+ return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}` };
8698
+ }
8699
+ if (durationMs !== void 0 && durationMs % HOUR_MS2 === 0) {
8700
+ const hours = durationMs / HOUR_MS2;
8701
+ return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}` };
8702
+ }
8703
+ return { id: "quota", label: "Quota" };
8704
+ }
8705
+ function parseZaiQuotaPayload(payload, now) {
8706
+ if (!isRecord5(payload)) return null;
8707
+ const data = isRecord5(payload["data"]) ? payload["data"] : payload;
8708
+ if (payload["success"] === false) return null;
8709
+ const limits = Array.isArray(data["limits"]) ? data["limits"] : [];
8710
+ const byWindow = /* @__PURE__ */ new Map();
8711
+ for (const raw of limits) {
8712
+ if (!isRecord5(raw)) continue;
8713
+ const item = raw;
8714
+ if (item.type === void 0) continue;
8715
+ const details = raw["usageDetails"];
8716
+ if (Array.isArray(details) && details.some((d) => isRecord5(d) && d["modelCode"] === "zread")) {
8717
+ continue;
8718
+ }
8719
+ const durationMs = zaiWindowDurationMs(item);
8720
+ const { id, label } = zaiWindowIdLabel(durationMs);
8721
+ const limit = finiteNumber3(item.usage);
8722
+ const used = finiteNumber3(item.currentValue);
8723
+ const fromAbsolute = limit !== void 0 && used !== void 0 && limit > 0 ? Math.min(100, used / limit * 100) : void 0;
8724
+ const fromPercentage = finitePercent4(item.percentage) ?? void 0;
8725
+ const usedPercent = fromAbsolute !== void 0 ? Math.round(fromAbsolute * 10) / 10 : fromPercentage;
8726
+ if (usedPercent === void 0) continue;
8727
+ const resetsAt = isoInstant3(item.nextResetTime);
8728
+ const candidate = {
8729
+ id,
8730
+ label,
8731
+ scope: "all",
8732
+ usedPercent,
8733
+ ...durationMs !== void 0 ? { windowMinutes: Math.round(durationMs / MINUTE_MS2) } : {},
8734
+ ...resetsAt !== void 0 ? { resetsAt } : {},
8735
+ remainingSeconds: secondsUntil5(resetsAt, now),
8736
+ state: "fresh"
8737
+ };
8738
+ const existing = byWindow.get(id);
8739
+ if (!existing || (candidate.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
8740
+ byWindow.set(id, candidate);
8741
+ }
8742
+ }
8743
+ const windows = [...byWindow.values()].sort((a, b) => (a.windowMinutes ?? Number.POSITIVE_INFINITY) - (b.windowMinutes ?? Number.POSITIVE_INFINITY));
8744
+ return windows.length > 0 ? windows.slice(0, 4) : null;
8745
+ }
8746
+ var MINIMAX_STATUS_EXHAUSTED = 2;
8747
+ var MINIMAX_SHARED_BUCKET = "general";
8748
+ function parseMiniMaxBucket(value) {
8749
+ if (!isRecord5(value)) return null;
8750
+ const modelName = typeof value["model_name"] === "string" ? value["model_name"].trim() : "";
8751
+ if (!modelName) return null;
8752
+ const instant = (v) => {
8753
+ const n = finiteNumber3(v);
8754
+ return n !== void 0 && n > 1e9 ? n > 1e12 ? n : n * 1e3 : void 0;
8755
+ };
8756
+ return {
8757
+ modelName,
8758
+ intervalEnd: instant(value["end_time"]),
8759
+ intervalRemainingPercent: finiteNumber3(value["current_interval_remaining_percent"]),
8760
+ intervalStatus: finiteNumber3(value["current_interval_status"]),
8761
+ weeklyEnd: instant(value["weekly_end_time"]),
8762
+ weeklyRemainingPercent: finiteNumber3(value["current_weekly_remaining_percent"]),
8763
+ weeklyStatus: finiteNumber3(value["current_weekly_status"])
8764
+ };
8765
+ }
8766
+ function minimaxWindow(id, label, windowMinutes, resetsAtMs, remainingPercent, status, now) {
8767
+ const usedPercent = status === MINIMAX_STATUS_EXHAUSTED ? 100 : remainingPercent !== void 0 ? Math.round((100 - remainingPercent) * 10) / 10 : null;
8768
+ const resetsAt = resetsAtMs !== void 0 ? new Date(resetsAtMs).toISOString() : void 0;
8769
+ return {
8770
+ id,
8771
+ label,
8772
+ scope: "all",
8773
+ usedPercent,
8774
+ ...windowMinutes !== void 0 ? { windowMinutes } : {},
8775
+ ...resetsAt !== void 0 ? { resetsAt } : {},
8776
+ remainingSeconds: secondsUntil5(resetsAt, now),
8777
+ state: usedPercent !== null ? "fresh" : "unavailable"
8778
+ };
8779
+ }
8780
+ function parseMiniMaxTokenPlanPayload(payload, now) {
8781
+ if (!isRecord5(payload)) return null;
8782
+ const baseResp = payload["base_resp"];
8783
+ if (!isRecord5(baseResp) || baseResp["status_code"] !== 0) return null;
8784
+ const buckets = Array.isArray(payload["model_remains"]) ? payload["model_remains"] : [];
8785
+ let general = null;
8786
+ for (const raw of buckets) {
8787
+ const bucket = parseMiniMaxBucket(raw);
8788
+ if (bucket?.modelName === MINIMAX_SHARED_BUCKET) {
8789
+ general = bucket;
8790
+ break;
8791
+ }
8792
+ }
8793
+ if (!general) return null;
8794
+ return [
8795
+ minimaxWindow(
8796
+ "five-hour",
8797
+ "5 hours",
8798
+ 5 * 60,
8799
+ general.intervalEnd,
8800
+ general.intervalRemainingPercent,
8801
+ general.intervalStatus,
8802
+ now
8803
+ ),
8804
+ minimaxWindow(
8805
+ "seven-day",
8806
+ "7 days",
8807
+ Math.round(WEEK_MS / MINUTE_MS2),
8808
+ general.weeklyEnd,
8809
+ general.weeklyRemainingPercent,
8810
+ general.weeklyStatus,
8811
+ now
8812
+ )
8813
+ ];
8814
+ }
8815
+ function parseUmansUsagePayload(payload, now) {
8816
+ if (!isRecord5(payload)) return null;
8817
+ const limits = isRecord5(payload["limits"]) ? payload["limits"] : void 0;
8818
+ const requests = limits && isRecord5(limits["requests"]) ? limits["requests"] : void 0;
8819
+ const usage = isRecord5(payload["usage"]) ? payload["usage"] : void 0;
8820
+ const window = isRecord5(payload["window"]) ? payload["window"] : void 0;
8821
+ const hardCap = finiteNumber3(requests?.["hard_cap"]);
8822
+ const softLimit = finiteNumber3(requests?.["limit"]);
8823
+ const requestsInWindow = finiteNumber3(usage?.["requests_in_window"]);
8824
+ const weightedInWindow = finiteNumber3(usage?.["weighted_in_window"]);
8825
+ const resetsAt = isoInstant3(window?.["resets_at"]);
8826
+ let usedPercent = null;
8827
+ if (hardCap !== void 0 && hardCap > 0 && requestsInWindow !== void 0) {
8828
+ usedPercent = Math.round(Math.min(100, requestsInWindow / hardCap * 100) * 10) / 10;
8829
+ } else if (softLimit !== void 0 && softLimit > 0 && weightedInWindow !== void 0) {
8830
+ usedPercent = Math.round(Math.min(100, weightedInWindow / softLimit * 100) * 10) / 10;
8831
+ }
8832
+ if (usedPercent === null && resetsAt === void 0) return null;
8833
+ return [
8834
+ {
8835
+ id: "five-hour",
8836
+ label: "5 hours",
8837
+ scope: "all",
8838
+ usedPercent,
8839
+ windowMinutes: 5 * 60,
8840
+ ...resetsAt !== void 0 ? { resetsAt } : {},
8841
+ remainingSeconds: secondsUntil5(resetsAt, now),
8842
+ state: "fresh"
8843
+ }
8844
+ ];
8845
+ }
8846
+ function parseSyntheticQuotasPayload(payload, now) {
8847
+ if (!isRecord5(payload)) return null;
8848
+ const fiveHour = isRecord5(payload["rollingFiveHourLimit"]) ? payload["rollingFiveHourLimit"] : void 0;
8849
+ const weekly = isRecord5(payload["weeklyTokenLimit"]) ? payload["weeklyTokenLimit"] : void 0;
8850
+ const windows = [];
8851
+ if (fiveHour) {
8852
+ const max = finiteNumber3(fiveHour["max"]);
8853
+ const remaining = finiteNumber3(fiveHour["remaining"]);
8854
+ const usedPercent = max !== void 0 && max > 0 && remaining !== void 0 ? Math.round(Math.min(100, (max - remaining) / max * 100) * 10) / 10 : null;
8855
+ const resetsAt = isoInstant3(fiveHour["nextTickAt"]);
8856
+ windows.push({
8857
+ id: "five-hour",
8858
+ label: "5 hours",
8859
+ scope: "all",
8860
+ usedPercent,
8861
+ windowMinutes: 5 * 60,
8862
+ ...resetsAt !== void 0 ? { resetsAt } : {},
8863
+ remainingSeconds: secondsUntil5(resetsAt, now),
8864
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
8865
+ });
8866
+ }
8867
+ if (weekly) {
8868
+ const percentRemaining = finiteNumber3(weekly["percentRemaining"]);
8869
+ const usedPercent = percentRemaining !== void 0 ? Math.round(Math.min(100, Math.max(0, 100 - percentRemaining)) * 10) / 10 : null;
8870
+ const resetsAt = isoInstant3(weekly["nextRegenAt"]);
8871
+ windows.push({
8872
+ id: "seven-day",
8873
+ label: "7 days",
8874
+ scope: "all",
8875
+ usedPercent,
8876
+ windowMinutes: 7 * 24 * 60,
8877
+ ...resetsAt !== void 0 ? { resetsAt } : {},
8878
+ remainingSeconds: secondsUntil5(resetsAt, now),
8879
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
8880
+ });
8881
+ }
8882
+ return windows.length > 0 ? windows : null;
8883
+ }
8884
+
8885
+ // src/allowance/ProviderKeyQuotaService.ts
8886
+ function parseQuotaPayload(adapter, payload, now) {
8887
+ switch (adapter) {
8888
+ case "zai":
8889
+ return parseZaiQuotaPayload(payload, now);
8890
+ case "minimax-token-plan":
8891
+ return parseMiniMaxTokenPlanPayload(payload, now);
8892
+ case "umans":
8893
+ return parseUmansUsagePayload(payload, now);
8894
+ case "synthetic":
8895
+ return parseSyntheticQuotasPayload(payload, now);
8896
+ }
8897
+ }
8898
+ var PROVIDER_KEY_QUOTA_CACHE_MS = 5 * 6e4;
8899
+ function resolvedBaseUrl(row) {
8900
+ const modes = row.apiModes ?? [];
8901
+ const selected = row.selectedApiModeId ? modes.find((mode) => mode.id === row.selectedApiModeId) : void 0;
8902
+ const fallback = modes[0];
8903
+ const modeBase = selected?.baseUrl ?? fallback?.baseUrl;
8904
+ return modeBase ?? row.codingPlan?.baseUrl ?? row.baseUrl;
8905
+ }
8906
+ function rowKeyEntries(row) {
8907
+ const pool = (row.apiKeys ?? []).filter((entry) => entry.apiKey.length > 0);
8908
+ if (pool.length > 0) return pool.map((entry) => ({ id: entry.id, apiKey: entry.apiKey }));
8909
+ if (row.apiKey.length > 0) {
8910
+ return [{ id: `${row.id}:default`, apiKey: row.apiKey }];
8911
+ }
8912
+ return [];
8913
+ }
8914
+ var ProviderKeyQuotaService = class {
8915
+ constructor(box, fetchImpl = (url, init) => (0, import_upstreamFetch8.fetchUpstream)(url, init, { redactBodies: true }), now = Date.now) {
8916
+ this.box = box;
8917
+ this.fetchImpl = fetchImpl;
8918
+ this.now = now;
8919
+ }
8920
+ box;
8921
+ fetchImpl;
8922
+ now;
8923
+ cache = /* @__PURE__ */ new Map();
8924
+ inFlight = /* @__PURE__ */ new Map();
8925
+ /**
8926
+ * Quota for one key of a provider row, or `null` when the row has no quota
8927
+ * adapter / no such key. Cache-first; concurrent reads share one flight.
8928
+ */
8929
+ async quotaFor(row, keyId, options = {}) {
8930
+ const adapter = detectProviderKeyQuotaAdapter(resolvedBaseUrl(row));
8931
+ if (!adapter) return null;
8932
+ const entry = rowKeyEntries(row).find((candidate) => candidate.id === keyId);
8933
+ if (!entry) return null;
8934
+ const cacheKey = `${row.id}\0${keyId}`;
8935
+ const now = this.now();
8936
+ const cached = this.cache.get(cacheKey);
8937
+ if (!options.force && cached && Date.parse(cached.expiresAt) > now) return cached;
8938
+ const running = this.inFlight.get(cacheKey);
8939
+ if (running) return running;
8940
+ const promise = this.fetchQuota(adapter, row, entry.apiKey, cacheKey).catch((error) => {
8941
+ void error;
8942
+ const previous = this.cache.get(cacheKey);
8943
+ if (previous) {
8944
+ const degraded = {
8945
+ ...previous,
8946
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
8947
+ windows: previous.windows.map((window) => ({
8948
+ ...window,
8949
+ state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
8950
+ })),
8951
+ errorCode: "quota_request_failed"
8952
+ };
8953
+ this.cache.set(cacheKey, degraded);
8954
+ return degraded;
8955
+ }
8956
+ return null;
8957
+ }).finally(() => this.inFlight.delete(cacheKey));
8958
+ this.inFlight.set(cacheKey, promise);
8959
+ return promise;
8960
+ }
8961
+ /** Drop cached rows for a provider (key added/removed/rotated). */
8962
+ invalidateProvider(providerRowId) {
8963
+ for (const key of this.cache.keys()) {
8964
+ if (key.startsWith(`${providerRowId}\0`)) this.cache.delete(key);
8965
+ }
8966
+ }
8967
+ async fetchQuota(adapter, row, rawKey, cacheKey) {
8968
+ const baseUrl = resolvedBaseUrl(row);
8969
+ const url = providerKeyQuotaUrl(adapter, baseUrl);
8970
+ const key = this.box.decryptMaybe(rawKey);
8971
+ const now = this.now();
8972
+ const response = await this.fetchImpl(url, {
8973
+ method: "GET",
8974
+ headers: {
8975
+ Authorization: providerKeyQuotaAuthHeader(adapter, key),
8976
+ Accept: "application/json",
8977
+ "Content-Type": "application/json"
8978
+ },
8979
+ signal: AbortSignal.timeout(15e3)
8980
+ });
8981
+ if (response.status === 401 || response.status === 403) {
8982
+ const snapshot2 = {
8983
+ adapter,
8984
+ observedAt: new Date(now).toISOString(),
8985
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
8986
+ windows: [],
8987
+ errorCode: "quota_unauthorized"
8988
+ };
8989
+ this.cache.set(cacheKey, snapshot2);
8990
+ return snapshot2;
8991
+ }
8992
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
8993
+ let payload;
8994
+ try {
8995
+ payload = await response.json();
8996
+ } catch {
8997
+ throw new Error("invalid JSON");
8998
+ }
8999
+ const windows = parseQuotaPayload(adapter, payload, now);
9000
+ const snapshot = {
9001
+ adapter,
9002
+ observedAt: new Date(now).toISOString(),
9003
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
9004
+ windows: windows ?? [],
9005
+ ...windows ? {} : { errorCode: "quota_unavailable" }
9006
+ };
9007
+ this.cache.set(cacheKey, snapshot);
9008
+ return snapshot;
9009
+ }
9010
+ };
9011
+
7773
9012
  // src/commands/paths.ts
7774
9013
  var import_node_path9 = require("path");
7775
9014
  function defaultVouchersPath(configPath) {
@@ -7806,7 +9045,7 @@ function defaultBillingDir(configPath) {
7806
9045
  // src/image-generation/ImageDoctorService.ts
7807
9046
  var import_image_generation = require("@omnicross/core/image-generation");
7808
9047
  var import_outbound_api7 = require("@omnicross/core/outbound-api");
7809
- var import_subscriptions3 = require("@omnicross/subscriptions");
9048
+ var import_subscriptions6 = require("@omnicross/subscriptions");
7810
9049
 
7811
9050
  // src/image-generation/FileCodexImageCapabilityEvidenceSource.ts
7812
9051
  var import_node_crypto13 = require("crypto");
@@ -8244,7 +9483,7 @@ function createImageDoctorService(options) {
8244
9483
  paths,
8245
9484
  ttlMs: config.evidenceTtlMs
8246
9485
  }));
8247
- const createLiveVerifier = options.createLiveVerifier ?? ((strategy, config) => (0, import_subscriptions3.createCodexImageLiveVerifier)({
9486
+ const createLiveVerifier = options.createLiveVerifier ?? ((strategy, config) => (0, import_subscriptions6.createCodexImageLiveVerifier)({
8248
9487
  authStrategy: strategy,
8249
9488
  generationTimeoutMs: config.queue.generationTimeoutMs
8250
9489
  }));
@@ -8606,7 +9845,7 @@ var ImageCleanupService = class {
8606
9845
  var import_node_crypto16 = require("crypto");
8607
9846
  var import_image_generation5 = require("@omnicross/core/image-generation");
8608
9847
  var import_outbound_api8 = require("@omnicross/core/outbound-api");
8609
- var import_subscriptions4 = require("@omnicross/subscriptions");
9848
+ var import_subscriptions7 = require("@omnicross/subscriptions");
8610
9849
 
8611
9850
  // src/image-generation/ImageApiRuntimeResolver.ts
8612
9851
  var import_node_crypto14 = require("crypto");
@@ -9137,7 +10376,7 @@ function createImageRuntimeGeneration(options) {
9137
10376
  now: options.now ?? Date.now,
9138
10377
  referenceStore: options.storage.referenceStore,
9139
10378
  stateStore: options.storage.stateStore
9140
- }) : (0, import_subscriptions4.createCodexSubscriptionImageProvider)({
10379
+ }) : (0, import_subscriptions7.createCodexSubscriptionImageProvider)({
9141
10380
  authStrategy,
9142
10381
  evidenceSource: generationEvidenceSource,
9143
10382
  executionScheduler: scheduler,
@@ -13803,10 +15042,13 @@ function bucketLabel(bucketStartTs, bucket) {
13803
15042
  }
13804
15043
 
13805
15044
  // src/ports/JsonOutboundKeyDb.ts
15045
+ var import_node_fs19 = require("fs");
15046
+ var import_core3 = require("@omnicross/core");
15047
+
15048
+ // src/ports/atomicFile.ts
13806
15049
  var import_node_crypto22 = require("crypto");
13807
15050
  var import_node_fs18 = require("fs");
13808
15051
  var import_node_path22 = require("path");
13809
- var import_core3 = require("@omnicross/core");
13810
15052
  function atomicReplaceUtf8(targetPath, contents) {
13811
15053
  const tempPath = (0, import_node_path22.join)(
13812
15054
  (0, import_node_path22.dirname)(targetPath),
@@ -13836,6 +15078,8 @@ function atomicReplaceUtf8(targetPath, contents) {
13836
15078
  throw error;
13837
15079
  }
13838
15080
  }
15081
+
15082
+ // src/ports/JsonOutboundKeyDb.ts
13839
15083
  var JsonOutboundKeyDb = class {
13840
15084
  /**
13841
15085
  * @param secretBox OPTIONAL reversible-secret codec. When present, a created
@@ -13978,9 +15222,9 @@ var JsonOutboundKeyDb = class {
13978
15222
  }
13979
15223
  /** Read the key rows, tolerating a missing/corrupt file (→ empty list). */
13980
15224
  readRows() {
13981
- if (!(0, import_node_fs18.existsSync)(this.keysPath)) return [];
15225
+ if (!(0, import_node_fs19.existsSync)(this.keysPath)) return [];
13982
15226
  try {
13983
- const parsed = JSON.parse((0, import_node_fs18.readFileSync)(this.keysPath, "utf8"));
15227
+ const parsed = JSON.parse((0, import_node_fs19.readFileSync)(this.keysPath, "utf8"));
13984
15228
  return Array.isArray(parsed) ? parsed : [];
13985
15229
  } catch {
13986
15230
  return [];
@@ -13997,7 +15241,7 @@ function applyPolicyField(row, field, value) {
13997
15241
  }
13998
15242
 
13999
15243
  // src/ports/JsonPricingStore.ts
14000
- var import_node_fs19 = require("fs");
15244
+ var import_node_fs20 = require("fs");
14001
15245
  var import_node_crypto23 = require("crypto");
14002
15246
  var JsonPricingStore = class {
14003
15247
  constructor(pricingPath) {
@@ -14012,9 +15256,9 @@ var JsonPricingStore = class {
14012
15256
  * otherwise unusable pricing table after a crash or manual file edit.
14013
15257
  */
14014
15258
  hasUsableSnapshot() {
14015
- if (!(0, import_node_fs19.existsSync)(this.pricingPath)) return false;
15259
+ if (!(0, import_node_fs20.existsSync)(this.pricingPath)) return false;
14016
15260
  try {
14017
- const parsed = JSON.parse((0, import_node_fs19.readFileSync)(this.pricingPath, "utf8"));
15261
+ const parsed = JSON.parse((0, import_node_fs20.readFileSync)(this.pricingPath, "utf8"));
14018
15262
  return Array.isArray(parsed) && parsed.some(isUsablePricingRow);
14019
15263
  } catch {
14020
15264
  return false;
@@ -14127,9 +15371,9 @@ var JsonPricingStore = class {
14127
15371
  }
14128
15372
  /** Read the pricing rows, tolerating a missing/corrupt file (→ empty list). */
14129
15373
  readRows() {
14130
- if (!(0, import_node_fs19.existsSync)(this.pricingPath)) return [];
15374
+ if (!(0, import_node_fs20.existsSync)(this.pricingPath)) return [];
14131
15375
  try {
14132
- const parsed = JSON.parse((0, import_node_fs19.readFileSync)(this.pricingPath, "utf8"));
15376
+ const parsed = JSON.parse((0, import_node_fs20.readFileSync)(this.pricingPath, "utf8"));
14133
15377
  return Array.isArray(parsed) ? parsed : [];
14134
15378
  } catch {
14135
15379
  return [];
@@ -14138,18 +15382,18 @@ var JsonPricingStore = class {
14138
15382
  writeRows(rows) {
14139
15383
  const temporaryPath = `${this.pricingPath}.${process.pid}.${(0, import_node_crypto23.randomUUID)()}.tmp`;
14140
15384
  try {
14141
- (0, import_node_fs19.writeFileSync)(temporaryPath, JSON.stringify(rows, null, 2) + "\n", {
15385
+ (0, import_node_fs20.writeFileSync)(temporaryPath, JSON.stringify(rows, null, 2) + "\n", {
14142
15386
  encoding: "utf8",
14143
15387
  flag: "wx"
14144
15388
  });
14145
15389
  this.replaceFile(temporaryPath);
14146
15390
  } finally {
14147
- (0, import_node_fs19.rmSync)(temporaryPath, { force: true });
15391
+ (0, import_node_fs20.rmSync)(temporaryPath, { force: true });
14148
15392
  }
14149
15393
  }
14150
15394
  /** Isolated for deterministic failure testing; never removes the target. */
14151
15395
  replaceFile(temporaryPath) {
14152
- (0, import_node_fs19.renameSync)(temporaryPath, this.pricingPath);
15396
+ (0, import_node_fs20.renameSync)(temporaryPath, this.pricingPath);
14153
15397
  }
14154
15398
  };
14155
15399
  function isUsablePricingRow(value) {
@@ -14159,7 +15403,7 @@ function isUsablePricingRow(value) {
14159
15403
  }
14160
15404
 
14161
15405
  // src/pricing/PricingRefreshScheduler.ts
14162
- var import_node_fs20 = require("fs");
15406
+ var import_node_fs21 = require("fs");
14163
15407
  var EMPTY_STATE2 = {
14164
15408
  lastAttemptAt: null,
14165
15409
  lastSuccessAt: null,
@@ -14197,9 +15441,9 @@ var PricingRefreshScheduler = class {
14197
15441
  this.timer = null;
14198
15442
  }
14199
15443
  getState() {
14200
- if (!(0, import_node_fs20.existsSync)(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
15444
+ if (!(0, import_node_fs21.existsSync)(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
14201
15445
  try {
14202
- const value = JSON.parse((0, import_node_fs20.readFileSync)(this.statePath, "utf8"));
15446
+ const value = JSON.parse((0, import_node_fs21.readFileSync)(this.statePath, "utf8"));
14203
15447
  return {
14204
15448
  lastAttemptAt: finiteOrNull(value.lastAttemptAt),
14205
15449
  lastSuccessAt: finiteOrNull(value.lastSuccessAt),
@@ -14252,9 +15496,9 @@ var PricingRefreshScheduler = class {
14252
15496
  }
14253
15497
  writeState(state) {
14254
15498
  const temporaryPath = `${this.statePath}.tmp`;
14255
- (0, import_node_fs20.writeFileSync)(temporaryPath, `${JSON.stringify(state, null, 2)}
15499
+ (0, import_node_fs21.writeFileSync)(temporaryPath, `${JSON.stringify(state, null, 2)}
14256
15500
  `, "utf8");
14257
- (0, import_node_fs20.renameSync)(temporaryPath, this.statePath);
15501
+ (0, import_node_fs21.renameSync)(temporaryPath, this.statePath);
14258
15502
  }
14259
15503
  };
14260
15504
  function finiteOrNull(value) {
@@ -14262,7 +15506,7 @@ function finiteOrNull(value) {
14262
15506
  }
14263
15507
 
14264
15508
  // src/ports/JsonVoucherDb.ts
14265
- var import_node_fs21 = require("fs");
15509
+ var import_node_fs22 = require("fs");
14266
15510
  var JsonVoucherDb = class {
14267
15511
  constructor(vouchersPath) {
14268
15512
  this.vouchersPath = vouchersPath;
@@ -14340,27 +15584,27 @@ var JsonVoucherDb = class {
14340
15584
  }
14341
15585
  /** Read the voucher rows, tolerating a missing/corrupt file (→ empty list). */
14342
15586
  readRows() {
14343
- if (!(0, import_node_fs21.existsSync)(this.vouchersPath)) return [];
15587
+ if (!(0, import_node_fs22.existsSync)(this.vouchersPath)) return [];
14344
15588
  try {
14345
- const parsed = JSON.parse((0, import_node_fs21.readFileSync)(this.vouchersPath, "utf8"));
15589
+ const parsed = JSON.parse((0, import_node_fs22.readFileSync)(this.vouchersPath, "utf8"));
14346
15590
  return Array.isArray(parsed) ? parsed : [];
14347
15591
  } catch {
14348
15592
  return [];
14349
15593
  }
14350
15594
  }
14351
15595
  writeRows(rows) {
14352
- (0, import_node_fs21.writeFileSync)(this.vouchersPath, JSON.stringify(rows, null, 2) + "\n", "utf8");
15596
+ (0, import_node_fs22.writeFileSync)(this.vouchersPath, JSON.stringify(rows, null, 2) + "\n", "utf8");
14353
15597
  }
14354
15598
  };
14355
15599
 
14356
15600
  // src/ports/JsonSubscriptionCredentialStore.ts
14357
- var import_node_fs23 = require("fs");
15601
+ var import_node_fs24 = require("fs");
14358
15602
  var import_node_path24 = require("path");
14359
15603
  var import_SubscriptionAccountHealth2 = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
14360
15604
  var import_AccountAllowanceScheduling3 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
14361
- var import_upstreamFetch5 = require("@omnicross/core/pipeline/upstreamFetch");
15605
+ var import_upstreamFetch9 = require("@omnicross/core/pipeline/upstreamFetch");
14362
15606
  var import_SubscriptionIdentityStore2 = require("@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore");
14363
- var import_subscriptions5 = require("@omnicross/subscriptions");
15607
+ var import_subscriptions8 = require("@omnicross/subscriptions");
14364
15608
 
14365
15609
  // src/ports/account-sync.ts
14366
15610
  function viewOf(tokens) {
@@ -14404,7 +15648,7 @@ function findDuplicateCredentialIds(accounts) {
14404
15648
  }
14405
15649
 
14406
15650
  // src/ports/external-cli-credentials.ts
14407
- var import_node_fs22 = require("fs");
15651
+ var import_node_fs23 = require("fs");
14408
15652
  var import_node_os5 = require("os");
14409
15653
  var import_node_path23 = require("path");
14410
15654
  function externalStorePath(provider, home = (0, import_node_os5.homedir)()) {
@@ -14457,10 +15701,10 @@ function parseCodexTokensEnvelope(raw) {
14457
15701
  }
14458
15702
  function readExternalCliCredentials(provider, home = (0, import_node_os5.homedir)()) {
14459
15703
  const path2 = externalStorePath(provider, home);
14460
- if (!(0, import_node_fs22.existsSync)(path2)) return null;
15704
+ if (!(0, import_node_fs23.existsSync)(path2)) return null;
14461
15705
  let raw;
14462
15706
  try {
14463
- const parsed = JSON.parse((0, import_node_fs22.readFileSync)(path2, "utf8"));
15707
+ const parsed = JSON.parse((0, import_node_fs23.readFileSync)(path2, "utf8"));
14464
15708
  raw = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
14465
15709
  } catch {
14466
15710
  return null;
@@ -14483,16 +15727,18 @@ var JsonSubscriptionCredentialStore = class {
14483
15727
  * as on relay refresh egresses from the SAME proxy IP as the
14484
15728
  * account's traffic. NOT used by any read/write path.
14485
15729
  */
14486
- constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials) {
15730
+ constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials, atomicReplace = atomicReplaceUtf8) {
14487
15731
  this.tokensPath = tokensPath;
14488
15732
  this.box = box;
14489
15733
  this.fetchImpl = fetchImpl;
14490
15734
  this.externalCliReader = externalCliReader;
15735
+ this.atomicReplace = atomicReplace;
14491
15736
  }
14492
15737
  tokensPath;
14493
15738
  box;
14494
15739
  fetchImpl;
14495
15740
  externalCliReader;
15741
+ atomicReplace;
14496
15742
  /**
14497
15743
  * The proxy-aware `FetchLike` for one refresh round-trip (upstream-proxy M1). A
14498
15744
  * TEST-injected `fetchImpl` is returned verbatim; otherwise the refresh routes
@@ -14506,7 +15752,7 @@ var JsonSubscriptionCredentialStore = class {
14506
15752
  * a plaintext token pair into `upstream-trace.jsonl`.
14507
15753
  */
14508
15754
  buildRefreshFetch(providerId, accountId) {
14509
- return this.fetchImpl ?? ((url, init) => (0, import_upstreamFetch5.fetchUpstream)(url, init, { providerId, accountId, redactBodies: true }));
15755
+ return this.fetchImpl ?? ((url, init) => (0, import_upstreamFetch9.fetchUpstream)(url, init, { providerId, accountId, redactBodies: true }));
14510
15756
  }
14511
15757
  /**
14512
15758
  * In-flight refresh coalescing. OAuth refresh tokens are
@@ -14547,7 +15793,7 @@ var JsonSubscriptionCredentialStore = class {
14547
15793
  * other hot reads. Never returns token material.
14548
15794
  */
14549
15795
  getAccountProxy(providerId, accountId) {
14550
- if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego") {
15796
+ if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego" && providerId !== "kimi") {
14551
15797
  return void 0;
14552
15798
  }
14553
15799
  return getAccountProxy(this.readConfig(), providerId, accountId);
@@ -14566,7 +15812,7 @@ var JsonSubscriptionCredentialStore = class {
14566
15812
  const fingerprintOn = identityStore.isEnabled();
14567
15813
  const now = Date.now();
14568
15814
  const out = {};
14569
- for (const provider of ["claude", "codex", "gemini", "opencodego"]) {
15815
+ for (const provider of ["claude", "codex", "gemini", "opencodego", "kimi"]) {
14570
15816
  const sanitized = sanitizeAccounts(config, provider);
14571
15817
  if (sanitized.length === 0) continue;
14572
15818
  for (const account of sanitized) {
@@ -14632,7 +15878,7 @@ var JsonSubscriptionCredentialStore = class {
14632
15878
  this.materializeMigration(config);
14633
15879
  const refreshFetch = this.buildRefreshFetch("claude", capturedId);
14634
15880
  try {
14635
- const result = await import_subscriptions5.claudeOAuth.refreshAccessToken(claude.refreshToken, refreshFetch);
15881
+ const result = await import_subscriptions8.claudeOAuth.refreshAccessToken(claude.refreshToken, refreshFetch);
14636
15882
  const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
14637
15883
  const next = {
14638
15884
  ...claude,
@@ -14667,7 +15913,7 @@ var JsonSubscriptionCredentialStore = class {
14667
15913
  this.materializeMigration(config);
14668
15914
  const refreshFetch = this.buildRefreshFetch("codex", capturedId);
14669
15915
  try {
14670
- const result = await import_subscriptions5.codexOAuth.refreshAccessToken(codex.refreshToken, refreshFetch);
15916
+ const result = await import_subscriptions8.codexOAuth.refreshAccessToken(codex.refreshToken, refreshFetch);
14671
15917
  const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
14672
15918
  const next = {
14673
15919
  ...codex,
@@ -14705,7 +15951,7 @@ var JsonSubscriptionCredentialStore = class {
14705
15951
  this.materializeMigration(config);
14706
15952
  const refreshFetch = this.buildRefreshFetch("gemini", capturedId);
14707
15953
  try {
14708
- const result = await import_subscriptions5.geminiOAuth.refreshAccessToken(gemini.refreshToken, refreshFetch);
15954
+ const result = await import_subscriptions8.geminiOAuth.refreshAccessToken(gemini.refreshToken, refreshFetch);
14709
15955
  const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
14710
15956
  const next = {
14711
15957
  ...gemini,
@@ -14724,6 +15970,47 @@ var JsonSubscriptionCredentialStore = class {
14724
15970
  }
14725
15971
  });
14726
15972
  }
15973
+ /**
15974
+ * Refresh the Kimi Code (Moonshot) OAuth access token (device-flow grant).
15975
+ * Kimi ROTATES the refresh token, so the response's pair is written back
15976
+ * whole; the account's stable `deviceId` (fingerprint header input) is
15977
+ * preserved. The refresh call carries the CLI fingerprint headers. HONEST
15978
+ * `false` when no refresh_token.
15979
+ */
15980
+ async refreshKimiToken() {
15981
+ return this.coalesce("kimi:active", async () => {
15982
+ const config = this.readConfig();
15983
+ const active = getActiveAccount(config, "kimi");
15984
+ const kimi = active?.tokens;
15985
+ if (!active || !kimi?.refreshToken) return false;
15986
+ const capturedId = active.id;
15987
+ this.materializeMigration(config);
15988
+ const refreshFetch = this.buildRefreshFetch("kimi", capturedId);
15989
+ try {
15990
+ const result = await import_subscriptions8.kimiOAuth.refreshAccessToken(
15991
+ kimi.refreshToken,
15992
+ refreshFetch,
15993
+ import_subscriptions8.kimiOAuth.kimiFingerprintHeaders(kimi.deviceId)
15994
+ );
15995
+ const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
15996
+ const next = {
15997
+ ...kimi,
15998
+ accessToken: result.accessToken,
15999
+ refreshToken: result.refreshToken,
16000
+ expiresAt,
16001
+ status: "authorized",
16002
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
16003
+ errorMessage: void 0,
16004
+ syncWarning: void 0
16005
+ };
16006
+ this.writeBackById("kimi", capturedId, next);
16007
+ return true;
16008
+ } catch (error) {
16009
+ this.markExpiredById("kimi", capturedId, kimi, error);
16010
+ return false;
16011
+ }
16012
+ });
16013
+ }
14727
16014
  /**
14728
16015
  * Refresh a SPECIFIC managed account by id (background scheduler sweep and
14729
16016
  * account-pool resolution). It uses only that account's stored refresh
@@ -14776,7 +16063,7 @@ var JsonSubscriptionCredentialStore = class {
14776
16063
  }
14777
16064
  const oauth = account.tokens;
14778
16065
  if (!oauth.accessToken) return null;
14779
- if (providerId === "codex" || providerId === "gemini") {
16066
+ if (providerId === "codex" || providerId === "gemini" || providerId === "kimi") {
14780
16067
  const expiresAtMs = oauth.expiresAt ? Date.parse(oauth.expiresAt) : 0;
14781
16068
  const expiringSoon = expiresAtMs > 0 && Date.now() >= expiresAtMs - ACCOUNT_REFRESH_LEAD_MS;
14782
16069
  if (expiringSoon && oauth.refreshToken) {
@@ -14865,8 +16152,23 @@ var JsonSubscriptionCredentialStore = class {
14865
16152
  }
14866
16153
  /** Dispatch one OAuth refresh round-trip to the provider's shared flow. */
14867
16154
  async refreshUpstream(provider, refreshToken, accountId) {
14868
- const flow = provider === "claude" ? import_subscriptions5.claudeOAuth : provider === "codex" ? import_subscriptions5.codexOAuth : import_subscriptions5.geminiOAuth;
14869
- const r = await flow.refreshAccessToken(refreshToken, this.buildRefreshFetch(provider, accountId));
16155
+ const refreshFetch = this.buildRefreshFetch(provider, accountId);
16156
+ if (provider === "kimi") {
16157
+ const account = accountId ? getAccountById(this.readConfig(), "kimi", accountId) : void 0;
16158
+ const deviceId = account?.tokens?.deviceId;
16159
+ const r2 = await import_subscriptions8.kimiOAuth.refreshAccessToken(
16160
+ refreshToken,
16161
+ refreshFetch,
16162
+ import_subscriptions8.kimiOAuth.kimiFingerprintHeaders(deviceId)
16163
+ );
16164
+ return {
16165
+ accessToken: r2.accessToken,
16166
+ refreshToken: r2.refreshToken,
16167
+ expiresAt: new Date(Date.now() + r2.expiresIn * 1e3).toISOString()
16168
+ };
16169
+ }
16170
+ const flow = provider === "claude" ? import_subscriptions8.claudeOAuth : provider === "codex" ? import_subscriptions8.codexOAuth : import_subscriptions8.geminiOAuth;
16171
+ const r = await flow.refreshAccessToken(refreshToken, refreshFetch);
14870
16172
  return {
14871
16173
  accessToken: r.accessToken,
14872
16174
  refreshToken: r.refreshToken,
@@ -15029,42 +16331,86 @@ var JsonSubscriptionCredentialStore = class {
15029
16331
  /** Write the merged config to disk as pretty JSON (mkdir parent if needed).
15030
16332
  * Encrypt-on-write: the token-material fields are encrypted (legacy plaintext
15031
16333
  * `enc:v1:`; already-`enc:`/`$ENV` untouched) before serializing, so any
15032
- * write incl. child 4's future refresh writes lands encrypted. */
16334
+ * write incl. child 4's future refresh writes lands encrypted.
16335
+ * ATOMIC: temp + fsync + rename (`atomicReplaceUtf8`) — a failed or
16336
+ * interrupted write discards only the temp file; the prior `tokens.json`
16337
+ * survives byte-equal (bare `writeFileSync` truncate-writes lost every
16338
+ * account on a mid-write failure, 2026-09-06). */
15033
16339
  persist(config) {
15034
- (0, import_node_fs23.mkdirSync)((0, import_node_path24.dirname)(this.tokensPath), { recursive: true });
16340
+ (0, import_node_fs24.mkdirSync)((0, import_node_path24.dirname)(this.tokensPath), { recursive: true });
15035
16341
  const encrypted = encryptTokens(config, this.box);
15036
- (0, import_node_fs23.writeFileSync)(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n", "utf8");
16342
+ this.atomicReplace(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
15037
16343
  }
15038
16344
  /**
15039
- * Read + parse `tokens.json`, tolerating a missing/corrupt file, then DECRYPT
15040
- * the token-material fields so every getter returns plaintext (the
15041
- * subscription bearer path is byte-identical).
16345
+ * Read + parse `tokens.json`, then DECRYPT the token-material fields so every
16346
+ * getter returns plaintext (the subscription bearer path is byte-identical).
16347
+ *
16348
+ * A MISSING file is a legitimate first-boot state → minimal `{ updatedAt: '' }`.
16349
+ * A file that EXISTS but cannot be parsed as a JSON object is CORRUPT →
16350
+ * `quarantineCorrupt` moves it aside (once) before the empty config is
16351
+ * returned, so the unreadable accounts survive for manual recovery.
15042
16352
  *
15043
- * The fs-read + JSON-parse tolerance is INSIDE the try (a missing or corrupt
15044
- * file empty `{ updatedAt: '' }`). The DECRYPT runs OUTSIDE the try, so a
15045
- * wrong/missing master key or a tampered `enc:` envelope FAILS FAST with the
15046
- * box's clear, secret-free error (secrets spec "/ UX":
15047
- * SHALL fail-fast, SHALL NOT a swallowed decrypt would report "no
15048
- * tokens" and silently send the WRONG bearer upstream 401). Mirrors
15049
- * `config.ts loadConfig`, which decrypts outside its parse try.
16353
+ * The DECRYPT runs OUTSIDE any try, so a wrong/missing master key or a
16354
+ * tampered `enc:` envelope FAILS FAST with the box's clear, secret-free
16355
+ * error (secrets spec "/ UX": SHALL fail-fast, SHALL NOT a swallowed
16356
+ * decrypt would report "no tokens" and silently send the WRONG bearer
16357
+ * upstream 401). Mirrors `config.ts loadConfig`, which decrypts outside
16358
+ * its parse try.
15050
16359
  */
15051
16360
  readConfig() {
15052
- if (!(0, import_node_fs23.existsSync)(this.tokensPath)) return { updatedAt: "" };
16361
+ if (!(0, import_node_fs24.existsSync)(this.tokensPath)) return { updatedAt: "" };
15053
16362
  let parsed;
15054
16363
  try {
15055
- const raw = JSON.parse((0, import_node_fs23.readFileSync)(this.tokensPath, "utf8"));
15056
- parsed = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
16364
+ const raw = JSON.parse((0, import_node_fs24.readFileSync)(this.tokensPath, "utf8"));
16365
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
16366
+ return this.quarantineCorrupt("parsed JSON is not an object");
16367
+ }
16368
+ parsed = raw;
15057
16369
  } catch {
15058
- parsed = null;
16370
+ return this.quarantineCorrupt("unparseable JSON");
15059
16371
  }
15060
- if (!parsed) return { updatedAt: "" };
15061
16372
  const decrypted = decryptTokens(parsed, this.box);
15062
16373
  return migrateLazily(decrypted);
15063
16374
  }
16375
+ /** One-shot latch: a corrupt file is quarantined (or found unmovable) at
16376
+ * most once per process, so the hot read path never re-attempts or re-logs. */
16377
+ corruptQuarantined = false;
16378
+ /**
16379
+ * Quarantine a present-but-corrupt `tokens.json`, then treat it as empty.
16380
+ *
16381
+ * Renames the file to a sibling `tokens.json.corrupt-<stamp>` backup and
16382
+ * logs loudly (the daemon's stderr log; secret-free — reason + paths only).
16383
+ * The daemon KEEPS SERVING (API-key routing is unaffected; subscription
16384
+ * routing reports no credential, same as an absent file) while the corrupt
16385
+ * bytes survive for manual recovery — and, critically, the NEXT persist
16386
+ * (e.g. the user re-logging in) can no longer overwrite the only copy of
16387
+ * the old accounts, which is exactly how the 2026-09-06 incident turned a
16388
+ * recoverable truncated file into permanent account loss.
16389
+ *
16390
+ * Best-effort: if the rename fails (file locked, permissions), the corrupt
16391
+ * file is left in place and every later read still tolerates it as empty;
16392
+ * the latch still trips so the attempt + log happen exactly once.
16393
+ */
16394
+ quarantineCorrupt(reason) {
16395
+ if (!this.corruptQuarantined) {
16396
+ this.corruptQuarantined = true;
16397
+ const backup = `${this.tokensPath}.corrupt-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
16398
+ let moved = false;
16399
+ try {
16400
+ (0, import_node_fs24.renameSync)(this.tokensPath, backup);
16401
+ moved = true;
16402
+ } catch {
16403
+ }
16404
+ console.error(
16405
+ `[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`)
16406
+ );
16407
+ }
16408
+ return { updatedAt: "" };
16409
+ }
15064
16410
  };
15065
16411
 
15066
16412
  // src/AccountHealthProbeScheduler.ts
15067
- var import_upstreamFetch6 = require("@omnicross/core/pipeline/upstreamFetch");
16413
+ var import_upstreamFetch10 = require("@omnicross/core/pipeline/upstreamFetch");
15068
16414
 
15069
16415
  // src/probe/CodexGenerationProbe.ts
15070
16416
  var import_codexCliHeaders = require("@omnicross/core/provider-proxy/identity/codexCliHeaders");
@@ -15203,7 +16549,11 @@ var PROVIDER_PROBE_PLANS = {
15203
16549
  // billable/wrong endpoint). Upgrade to `{ kind:'upstream' }` once verified.
15204
16550
  codex: { kind: "local" },
15205
16551
  gemini: { kind: "local" },
15206
- opencodego: { kind: "local" }
16552
+ opencodego: { kind: "local" },
16553
+ // Kimi's `GET /coding/v1/usages` is a verified FREE authed GET (the allowance
16554
+ // collector uses it), but the probe path also needs the fingerprint headers —
16555
+ // keep the probe local until the collector covers the health surface.
16556
+ kimi: { kind: "local" }
15207
16557
  };
15208
16558
  function probePlanFor(providerId) {
15209
16559
  return PROVIDER_PROBE_PLANS[providerId] ?? { kind: "local" };
@@ -15225,7 +16575,7 @@ var AccountHealthProbeScheduler = class {
15225
16575
  this.logger = logger;
15226
16576
  this.config = config;
15227
16577
  this.now = opts.now ?? Date.now;
15228
- this.fetchImpl = opts.fetchImpl ?? import_upstreamFetch6.fetchUpstream;
16578
+ this.fetchImpl = opts.fetchImpl ?? import_upstreamFetch10.fetchUpstream;
15229
16579
  this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
15230
16580
  this.planFor = opts.planFor ?? probePlanFor;
15231
16581
  }
@@ -15569,13 +16919,13 @@ var AccountHealthSweeper = class {
15569
16919
  };
15570
16920
 
15571
16921
  // src/audit/AuditPruneSweeper.ts
15572
- var import_node_fs26 = require("fs");
16922
+ var import_node_fs27 = require("fs");
15573
16923
  var import_node_path27 = require("path");
15574
16924
  var import_promises6 = require("stream/promises");
15575
16925
  var import_node_zlib = require("zlib");
15576
16926
 
15577
16927
  // src/audit/auditDictionary.ts
15578
- var import_node_fs24 = require("fs");
16928
+ var import_node_fs25 = require("fs");
15579
16929
  var import_node_path25 = require("path");
15580
16930
 
15581
16931
  // src/audit/auditBodyStore.ts
@@ -15808,7 +17158,7 @@ function parseEntries(raw) {
15808
17158
  }
15809
17159
  function plainShards(bodiesPath) {
15810
17160
  try {
15811
- return (0, import_node_fs24.readdirSync)(bodiesPath).filter(
17161
+ return (0, import_node_fs25.readdirSync)(bodiesPath).filter(
15812
17162
  (file) => file.endsWith(".jsonl") && isSafeSessionKey(file.slice(0, -".jsonl".length))
15813
17163
  );
15814
17164
  } catch {
@@ -15833,9 +17183,9 @@ function chooseDictionary(anchors) {
15833
17183
  var EMPTY = { shards: 0, anchors: 0, savedBytes: 0 };
15834
17184
  function compactAuditDay(dayPath) {
15835
17185
  const bodiesPath = (0, import_node_path25.join)(dayPath, AUDIT_BODIES_DIR);
15836
- if (!(0, import_node_fs24.existsSync)(bodiesPath)) return EMPTY;
17186
+ if (!(0, import_node_fs25.existsSync)(bodiesPath)) return EMPTY;
15837
17187
  const dictPath = (0, import_node_path25.join)(bodiesPath, AUDIT_DICT_FILE);
15838
- if ((0, import_node_fs24.existsSync)(dictPath) || (0, import_node_fs24.existsSync)(`${dictPath}.gz`)) return EMPTY;
17188
+ if ((0, import_node_fs25.existsSync)(dictPath) || (0, import_node_fs25.existsSync)(`${dictPath}.gz`)) return EMPTY;
15839
17189
  const shardFiles = plainShards(bodiesPath);
15840
17190
  if (shardFiles.length < 2) return EMPTY;
15841
17191
  const loaded = /* @__PURE__ */ new Map();
@@ -15843,7 +17193,7 @@ function compactAuditDay(dayPath) {
15843
17193
  for (const file of shardFiles) {
15844
17194
  let entries;
15845
17195
  try {
15846
- entries = parseEntries((0, import_node_fs24.readFileSync)((0, import_node_path25.join)(bodiesPath, file), "utf8"));
17196
+ entries = parseEntries((0, import_node_fs25.readFileSync)((0, import_node_path25.join)(bodiesPath, file), "utf8"));
15847
17197
  } catch {
15848
17198
  continue;
15849
17199
  }
@@ -15860,7 +17210,7 @@ function compactAuditDay(dayPath) {
15860
17210
  ts: 0,
15861
17211
  req: { base: null, anchor: "dict", pre: 0, suf: 0, ins: dictionary }
15862
17212
  };
15863
- (0, import_node_fs24.writeFileSync)(dictPath, JSON.stringify(dictEntry) + "\n", "utf8");
17213
+ (0, import_node_fs25.writeFileSync)(dictPath, JSON.stringify(dictEntry) + "\n", "utf8");
15864
17214
  const result = { shards: 0, anchors: 0, savedBytes: 0 };
15865
17215
  for (const [file, entries] of loaded) {
15866
17216
  let changed = false;
@@ -15880,11 +17230,11 @@ function compactAuditDay(dayPath) {
15880
17230
  const target = (0, import_node_path25.join)(bodiesPath, file);
15881
17231
  const temp = `${target}.compacting`;
15882
17232
  try {
15883
- (0, import_node_fs24.writeFileSync)(temp, rewritten.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
15884
- (0, import_node_fs24.renameSync)(temp, target);
17233
+ (0, import_node_fs25.writeFileSync)(temp, rewritten.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
17234
+ (0, import_node_fs25.renameSync)(temp, target);
15885
17235
  } catch {
15886
17236
  try {
15887
- if ((0, import_node_fs24.existsSync)(temp)) (0, import_node_fs24.unlinkSync)(temp);
17237
+ if ((0, import_node_fs25.existsSync)(temp)) (0, import_node_fs25.unlinkSync)(temp);
15888
17238
  } catch {
15889
17239
  }
15890
17240
  continue;
@@ -15895,7 +17245,7 @@ function compactAuditDay(dayPath) {
15895
17245
  }
15896
17246
  if (result.shards === 0) {
15897
17247
  try {
15898
- (0, import_node_fs24.unlinkSync)(dictPath);
17248
+ (0, import_node_fs25.unlinkSync)(dictPath);
15899
17249
  } catch {
15900
17250
  }
15901
17251
  }
@@ -15903,11 +17253,11 @@ function compactAuditDay(dayPath) {
15903
17253
  }
15904
17254
  function compactAllClosedAuditDays(auditDir, now = Date.now) {
15905
17255
  const run = { days: 0, shards: 0, savedBytes: 0 };
15906
- if (!(0, import_node_fs24.existsSync)(auditDir)) return run;
17256
+ if (!(0, import_node_fs25.existsSync)(auditDir)) return run;
15907
17257
  const today = auditDayDirName(now());
15908
17258
  let names;
15909
17259
  try {
15910
- names = (0, import_node_fs24.readdirSync)(auditDir).filter(isAuditDayDir).sort();
17260
+ names = (0, import_node_fs25.readdirSync)(auditDir).filter(isAuditDayDir).sort();
15911
17261
  } catch {
15912
17262
  return run;
15913
17263
  }
@@ -15926,7 +17276,7 @@ function compactAllClosedAuditDays(auditDir, now = Date.now) {
15926
17276
  }
15927
17277
 
15928
17278
  // src/audit/auditStats.ts
15929
- var import_node_fs25 = require("fs");
17279
+ var import_node_fs26 = require("fs");
15930
17280
  var import_node_path26 = require("path");
15931
17281
  var SIDECAR_VERSION = 1;
15932
17282
  var META_PREFIX_BYTES = 64 * 1024;
@@ -15935,9 +17285,9 @@ function auditStatsFileName(auditFile) {
15935
17285
  return auditFile.replace(/\.jsonl$/, ".stats.json");
15936
17286
  }
15937
17287
  function readPersisted(path2) {
15938
- if (!(0, import_node_fs25.existsSync)(path2)) return null;
17288
+ if (!(0, import_node_fs26.existsSync)(path2)) return null;
15939
17289
  try {
15940
- const value = JSON.parse((0, import_node_fs25.readFileSync)(path2, "utf8"));
17290
+ const value = JSON.parse((0, import_node_fs26.readFileSync)(path2, "utf8"));
15941
17291
  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)) {
15942
17292
  return null;
15943
17293
  }
@@ -15967,7 +17317,7 @@ function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfte
15967
17317
  minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
15968
17318
  maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
15969
17319
  };
15970
- (0, import_node_fs25.writeFileSync)(statsPath, JSON.stringify(next), "utf8");
17320
+ (0, import_node_fs26.writeFileSync)(statsPath, JSON.stringify(next), "utf8");
15971
17321
  }
15972
17322
  function queryCovers(stats, from, to) {
15973
17323
  return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
@@ -16025,7 +17375,7 @@ async function scanAuditFile(auditPath, startByte, auditBytes, from, to) {
16025
17375
  prefixTruncated = false;
16026
17376
  };
16027
17377
  if (auditBytes > startByte) {
16028
- const stream = (0, import_node_fs25.createReadStream)(auditPath, {
17378
+ const stream = (0, import_node_fs26.createReadStream)(auditPath, {
16029
17379
  start: startByte,
16030
17380
  end: auditBytes - 1,
16031
17381
  highWaterMark: READ_CHUNK_BYTES2
@@ -16078,12 +17428,12 @@ function mergePersistedStats(previous, appended) {
16078
17428
  };
16079
17429
  }
16080
17430
  async function readAuditStats(auditDir, query2 = {}) {
16081
- if (!(0, import_node_fs25.existsSync)(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
17431
+ if (!(0, import_node_fs26.existsSync)(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
16082
17432
  const from = typeof query2.from === "number" ? query2.from : -Infinity;
16083
17433
  const to = typeof query2.to === "number" ? query2.to : Infinity;
16084
17434
  let sources;
16085
17435
  try {
16086
- sources = (0, import_node_fs25.readdirSync)(auditDir).filter((name) => fileOverlaps(name, from, to)).sort().map(
17436
+ sources = (0, import_node_fs26.readdirSync)(auditDir).filter((name) => fileOverlaps(name, from, to)).sort().map(
16087
17437
  (name) => AUDIT_DAY_DIR_RE.test(name) ? {
16088
17438
  auditPath: (0, import_node_path26.join)(auditDir, name, AUDIT_META_FILE),
16089
17439
  statsPath: (0, import_node_path26.join)(auditDir, name, auditStatsFileName(AUDIT_META_FILE))
@@ -16091,14 +17441,14 @@ async function readAuditStats(auditDir, query2 = {}) {
16091
17441
  auditPath: (0, import_node_path26.join)(auditDir, name),
16092
17442
  statsPath: (0, import_node_path26.join)(auditDir, auditStatsFileName(name))
16093
17443
  }
16094
- ).filter((source) => (0, import_node_fs25.existsSync)(source.auditPath));
17444
+ ).filter((source) => (0, import_node_fs26.existsSync)(source.auditPath));
16095
17445
  } catch {
16096
17446
  return { requestCount: 0, errorCount: 0, complete: false };
16097
17447
  }
16098
17448
  const total = { requestCount: 0, errorCount: 0, complete: true };
16099
17449
  for (const { auditPath, statsPath } of sources) {
16100
17450
  try {
16101
- const auditBytes = (0, import_node_fs25.statSync)(auditPath).size;
17451
+ const auditBytes = (0, import_node_fs26.statSync)(auditPath).size;
16102
17452
  const persisted = readPersisted(statsPath);
16103
17453
  if (persisted && persisted.complete && persisted.auditBytes === auditBytes && queryCovers(persisted, from, to)) {
16104
17454
  total.requestCount += persisted.requestCount;
@@ -16117,7 +17467,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16117
17467
  total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
16118
17468
  total.complete = total.complete && scanned.filtered.complete;
16119
17469
  const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
16120
- if (current.complete) (0, import_node_fs25.writeFileSync)(statsPath, JSON.stringify(current), "utf8");
17470
+ if (current.complete) (0, import_node_fs26.writeFileSync)(statsPath, JSON.stringify(current), "utf8");
16121
17471
  } catch {
16122
17472
  total.complete = false;
16123
17473
  }
@@ -16126,7 +17476,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16126
17476
  }
16127
17477
 
16128
17478
  // src/audit/AuditPruneSweeper.ts
16129
- var DAY_MS = 24 * 60 * 6e4;
17479
+ var DAY_MS3 = 24 * 60 * 6e4;
16130
17480
  var SWEEP_INTERVAL_MS2 = 60 * 6e4;
16131
17481
  var ARCHIVE_BATCH = 64;
16132
17482
  var AuditPruneSweeper = class {
@@ -16189,19 +17539,19 @@ var AuditPruneSweeper = class {
16189
17539
  if (!this.config.enabled || this.sweeping) return 0;
16190
17540
  this.sweeping = true;
16191
17541
  try {
16192
- if (!(0, import_node_fs26.existsSync)(this.auditDir)) return 0;
16193
- const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS;
17542
+ if (!(0, import_node_fs27.existsSync)(this.auditDir)) return 0;
17543
+ const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS3;
16194
17544
  let removed = 0;
16195
- for (const name of (0, import_node_fs26.readdirSync)(this.auditDir)) {
17545
+ for (const name of (0, import_node_fs27.readdirSync)(this.auditDir)) {
16196
17546
  const dateMs = auditFileDateMs(name);
16197
17547
  if (dateMs === null || dateMs >= cutoff) continue;
16198
17548
  try {
16199
17549
  if (isAuditDayDir(name)) {
16200
- (0, import_node_fs26.rmSync)((0, import_node_path27.join)(this.auditDir, name), { recursive: true, force: true });
17550
+ (0, import_node_fs27.rmSync)((0, import_node_path27.join)(this.auditDir, name), { recursive: true, force: true });
16201
17551
  } else {
16202
- (0, import_node_fs26.unlinkSync)((0, import_node_path27.join)(this.auditDir, name));
17552
+ (0, import_node_fs27.unlinkSync)((0, import_node_path27.join)(this.auditDir, name));
16203
17553
  const statsPath = (0, import_node_path27.join)(this.auditDir, auditStatsFileName(name));
16204
- if ((0, import_node_fs26.existsSync)(statsPath)) (0, import_node_fs26.unlinkSync)(statsPath);
17554
+ if ((0, import_node_fs27.existsSync)(statsPath)) (0, import_node_fs27.unlinkSync)(statsPath);
16205
17555
  }
16206
17556
  removed += 1;
16207
17557
  } catch (error) {
@@ -16231,10 +17581,10 @@ var AuditPruneSweeper = class {
16231
17581
  if (!this.config.enabled || this.archiving) return 0;
16232
17582
  this.archiving = true;
16233
17583
  try {
16234
- if (!(0, import_node_fs26.existsSync)(this.auditDir)) return 0;
17584
+ if (!(0, import_node_fs27.existsSync)(this.auditDir)) return 0;
16235
17585
  const today = this.todayMidnight();
16236
17586
  let compressed = 0;
16237
- for (const name of (0, import_node_fs26.readdirSync)(this.auditDir)) {
17587
+ for (const name of (0, import_node_fs27.readdirSync)(this.auditDir)) {
16238
17588
  if (compressed >= ARCHIVE_BATCH) break;
16239
17589
  const dateMs = auditFileDateMs(name);
16240
17590
  if (dateMs === null || dateMs >= today || !isAuditDayDir(name)) continue;
@@ -16275,7 +17625,7 @@ var AuditPruneSweeper = class {
16275
17625
  async archiveDay(bodiesPath, budget) {
16276
17626
  let shards;
16277
17627
  try {
16278
- shards = (0, import_node_fs26.readdirSync)(bodiesPath).filter((file) => file.endsWith(".jsonl"));
17628
+ shards = (0, import_node_fs27.readdirSync)(bodiesPath).filter((file) => file.endsWith(".jsonl"));
16279
17629
  } catch {
16280
17630
  return 0;
16281
17631
  }
@@ -16285,16 +17635,16 @@ var AuditPruneSweeper = class {
16285
17635
  const source = (0, import_node_path27.join)(bodiesPath, shard);
16286
17636
  const target = `${source}.gz`;
16287
17637
  try {
16288
- if ((0, import_node_fs26.existsSync)(target)) {
16289
- (0, import_node_fs26.unlinkSync)(source);
17638
+ if ((0, import_node_fs27.existsSync)(target)) {
17639
+ (0, import_node_fs27.unlinkSync)(source);
16290
17640
  continue;
16291
17641
  }
16292
- await (0, import_promises6.pipeline)((0, import_node_fs26.createReadStream)(source), (0, import_node_zlib.createGzip)(), (0, import_node_fs26.createWriteStream)(target));
16293
- (0, import_node_fs26.unlinkSync)(source);
17642
+ await (0, import_promises6.pipeline)((0, import_node_fs27.createReadStream)(source), (0, import_node_zlib.createGzip)(), (0, import_node_fs27.createWriteStream)(target));
17643
+ (0, import_node_fs27.unlinkSync)(source);
16294
17644
  compressed += 1;
16295
17645
  } catch (error) {
16296
17646
  try {
16297
- if ((0, import_node_fs26.existsSync)(target)) (0, import_node_fs26.unlinkSync)(target);
17647
+ if ((0, import_node_fs27.existsSync)(target)) (0, import_node_fs27.unlinkSync)(target);
16298
17648
  } catch {
16299
17649
  }
16300
17650
  this.logger.warn("[AuditPruneSweeper] failed to archive audit body shard", {
@@ -16308,7 +17658,7 @@ var AuditPruneSweeper = class {
16308
17658
  };
16309
17659
 
16310
17660
  // src/usage/usageMigrate.ts
16311
- var import_node_fs27 = require("fs");
17661
+ var import_node_fs28 = require("fs");
16312
17662
  var import_promises7 = require("fs/promises");
16313
17663
  var import_node_path28 = require("path");
16314
17664
  var import_node_readline = require("readline");
@@ -16355,7 +17705,7 @@ async function migrateLegacyUsageEvents(opts) {
16355
17705
  let skipped = 0;
16356
17706
  try {
16357
17707
  const reader = (0, import_node_readline.createInterface)({
16358
- input: (0, import_node_fs27.createReadStream)(eventsPath, { encoding: "utf8" }),
17708
+ input: (0, import_node_fs28.createReadStream)(eventsPath, { encoding: "utf8" }),
16359
17709
  crlfDelay: Number.POSITIVE_INFINITY
16360
17710
  });
16361
17711
  for await (const line of reader) {
@@ -16447,7 +17797,7 @@ async function closeAll(writers) {
16447
17797
  // src/usage/UsagePruneSweeper.ts
16448
17798
  var import_promises8 = require("fs/promises");
16449
17799
  var import_node_path29 = require("path");
16450
- var DAY_MS2 = 24 * 60 * 6e4;
17800
+ var DAY_MS4 = 24 * 60 * 6e4;
16451
17801
  var SWEEP_INTERVAL_MS3 = 60 * 6e4;
16452
17802
  var DEFAULT_USAGE_RETENTION_DAYS = 90;
16453
17803
  var UsagePruneSweeper = class {
@@ -16504,7 +17854,7 @@ var UsagePruneSweeper = class {
16504
17854
  this.sweeping = true;
16505
17855
  try {
16506
17856
  const retentionDays = this.config.retentionDays ?? DEFAULT_USAGE_RETENTION_DAYS;
16507
- const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS2;
17857
+ const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS4;
16508
17858
  let removed = 0;
16509
17859
  for (const entry of await listUsageDays(this.usageDir)) {
16510
17860
  if (!entry.hasShard) continue;
@@ -16562,12 +17912,12 @@ var UsagePruneSweeper = class {
16562
17912
  };
16563
17913
 
16564
17914
  // src/audit/auditBodyReader.ts
16565
- var import_node_fs29 = require("fs");
17915
+ var import_node_fs30 = require("fs");
16566
17916
  var import_node_path30 = require("path");
16567
17917
  var import_node_zlib2 = require("zlib");
16568
17918
 
16569
17919
  // src/audit/auditJsonl.ts
16570
- var import_node_fs28 = require("fs");
17920
+ var import_node_fs29 = require("fs");
16571
17921
  var WINDOW_BYTES = 1 << 20;
16572
17922
  var MAX_LINE_BYTES = 32 * 1024 * 1024;
16573
17923
  var NEWLINE2 = 10;
@@ -16575,9 +17925,9 @@ function forEachLineFromTail(path2, onLine) {
16575
17925
  let fd;
16576
17926
  let end;
16577
17927
  try {
16578
- end = (0, import_node_fs28.statSync)(path2).size;
17928
+ end = (0, import_node_fs29.statSync)(path2).size;
16579
17929
  if (end === 0) return;
16580
- fd = (0, import_node_fs28.openSync)(path2, "r");
17930
+ fd = (0, import_node_fs29.openSync)(path2, "r");
16581
17931
  } catch {
16582
17932
  return;
16583
17933
  }
@@ -16588,7 +17938,7 @@ function forEachLineFromTail(path2, onLine) {
16588
17938
  const window = Buffer.allocUnsafe(end - start);
16589
17939
  let read;
16590
17940
  try {
16591
- read = (0, import_node_fs28.readSync)(fd, window, 0, end - start, start);
17941
+ read = (0, import_node_fs29.readSync)(fd, window, 0, end - start, start);
16592
17942
  } catch {
16593
17943
  return;
16594
17944
  }
@@ -16616,7 +17966,7 @@ function forEachLineFromTail(path2, onLine) {
16616
17966
  }
16617
17967
  } finally {
16618
17968
  try {
16619
- (0, import_node_fs28.closeSync)(fd);
17969
+ (0, import_node_fs29.closeSync)(fd);
16620
17970
  } catch {
16621
17971
  }
16622
17972
  }
@@ -16626,10 +17976,10 @@ function forEachLineFromTail(path2, onLine) {
16626
17976
  function candidateDays(auditDir, ts) {
16627
17977
  if (typeof ts === "number" && Number.isFinite(ts)) {
16628
17978
  const named = auditDayDirName(ts);
16629
- if ((0, import_node_fs29.existsSync)((0, import_node_path30.join)(auditDir, named))) return [named];
17979
+ if ((0, import_node_fs30.existsSync)((0, import_node_path30.join)(auditDir, named))) return [named];
16630
17980
  }
16631
17981
  try {
16632
- return (0, import_node_fs29.readdirSync)(auditDir).filter(isAuditDayDir).sort().reverse();
17982
+ return (0, import_node_fs30.readdirSync)(auditDir).filter(isAuditDayDir).sort().reverse();
16633
17983
  } catch {
16634
17984
  return [];
16635
17985
  }
@@ -16637,9 +17987,9 @@ function candidateDays(auditDir, ts) {
16637
17987
  function readShard(auditDir, day, sessionKey) {
16638
17988
  const base = (0, import_node_path30.join)(auditDir, day, AUDIT_BODIES_DIR, auditBodyFileName(sessionKey));
16639
17989
  try {
16640
- if ((0, import_node_fs29.existsSync)(base)) return (0, import_node_fs29.readFileSync)(base, "utf8");
17990
+ if ((0, import_node_fs30.existsSync)(base)) return (0, import_node_fs30.readFileSync)(base, "utf8");
16641
17991
  const gz = `${base}.gz`;
16642
- if ((0, import_node_fs29.existsSync)(gz)) return (0, import_node_zlib2.gunzipSync)((0, import_node_fs29.readFileSync)(gz)).toString("utf8");
17992
+ if ((0, import_node_fs30.existsSync)(gz)) return (0, import_node_zlib2.gunzipSync)((0, import_node_fs30.readFileSync)(gz)).toString("utf8");
16643
17993
  } catch {
16644
17994
  return null;
16645
17995
  }
@@ -16672,8 +18022,8 @@ function withDictionary(auditDir, day, entries) {
16672
18022
  const base = (0, import_node_path30.join)(auditDir, day, AUDIT_BODIES_DIR, AUDIT_DICT_FILE);
16673
18023
  let raw = null;
16674
18024
  try {
16675
- if ((0, import_node_fs29.existsSync)(base)) raw = (0, import_node_fs29.readFileSync)(base, "utf8");
16676
- else if ((0, import_node_fs29.existsSync)(`${base}.gz`)) raw = (0, import_node_zlib2.gunzipSync)((0, import_node_fs29.readFileSync)(`${base}.gz`)).toString("utf8");
18025
+ if ((0, import_node_fs30.existsSync)(base)) raw = (0, import_node_fs30.readFileSync)(base, "utf8");
18026
+ else if ((0, import_node_fs30.existsSync)(`${base}.gz`)) raw = (0, import_node_zlib2.gunzipSync)((0, import_node_fs30.readFileSync)(`${base}.gz`)).toString("utf8");
16677
18027
  } catch {
16678
18028
  return entries;
16679
18029
  }
@@ -16706,7 +18056,7 @@ function reconstructRequest(entries, entry) {
16706
18056
  }
16707
18057
  function readAuditBody(auditDir, query2) {
16708
18058
  if (!isSafeSessionKey(query2.sessionKey) || !query2.id) return {};
16709
- if (!(0, import_node_fs29.existsSync)(auditDir)) return {};
18059
+ if (!(0, import_node_fs30.existsSync)(auditDir)) return {};
16710
18060
  for (const day of candidateDays(auditDir, query2.ts)) {
16711
18061
  const raw = readShard(auditDir, day, query2.sessionKey);
16712
18062
  if (raw === null) continue;
@@ -16724,7 +18074,7 @@ function readAuditBody(auditDir, query2) {
16724
18074
  function readLegacyInlineBody(auditDir, id) {
16725
18075
  let names;
16726
18076
  try {
16727
- names = (0, import_node_fs29.readdirSync)(auditDir).filter((name) => AUDIT_FILE_RE.test(name)).sort().reverse();
18077
+ names = (0, import_node_fs30.readdirSync)(auditDir).filter((name) => AUDIT_FILE_RE.test(name)).sort().reverse();
16728
18078
  } catch {
16729
18079
  return {};
16730
18080
  }
@@ -16753,7 +18103,7 @@ function readLegacyInlineBody(auditDir, id) {
16753
18103
  }
16754
18104
 
16755
18105
  // src/audit/auditReader.ts
16756
- var import_node_fs30 = require("fs");
18106
+ var import_node_fs31 = require("fs");
16757
18107
  var import_node_path31 = require("path");
16758
18108
  var DEFAULT_LIMIT = 200;
16759
18109
  var MAX_LIMIT = 2e3;
@@ -16761,7 +18111,7 @@ var OVERSCAN = 256;
16761
18111
  function daySources(auditDir) {
16762
18112
  let names;
16763
18113
  try {
16764
- names = (0, import_node_fs30.readdirSync)(auditDir);
18114
+ names = (0, import_node_fs31.readdirSync)(auditDir);
16765
18115
  } catch {
16766
18116
  return [];
16767
18117
  }
@@ -16771,7 +18121,7 @@ function daySources(auditDir) {
16771
18121
  if (dateMs === null) continue;
16772
18122
  if (AUDIT_DAY_DIR_RE.test(name)) {
16773
18123
  const path2 = (0, import_node_path31.join)(auditDir, name, AUDIT_META_FILE);
16774
- if ((0, import_node_fs30.existsSync)(path2)) sources.push({ path: path2, dateMs });
18124
+ if ((0, import_node_fs31.existsSync)(path2)) sources.push({ path: path2, dateMs });
16775
18125
  } else if (AUDIT_FILE_RE.test(name)) {
16776
18126
  sources.push({ path: (0, import_node_path31.join)(auditDir, name), dateMs });
16777
18127
  }
@@ -16789,7 +18139,7 @@ function toMetaRecord(record) {
16789
18139
  return { ...meta, hasBody: true };
16790
18140
  }
16791
18141
  function readAuditRecords(auditDir, query2 = {}) {
16792
- if (!(0, import_node_fs30.existsSync)(auditDir)) return [];
18142
+ if (!(0, import_node_fs31.existsSync)(auditDir)) return [];
16793
18143
  const from = typeof query2.from === "number" ? query2.from : -Infinity;
16794
18144
  const to = typeof query2.to === "number" ? query2.to : Infinity;
16795
18145
  const limit = Math.min(MAX_LIMIT, Math.max(1, Math.trunc(query2.limit ?? DEFAULT_LIMIT)));
@@ -16817,7 +18167,7 @@ function readAuditRecords(auditDir, query2 = {}) {
16817
18167
  }
16818
18168
 
16819
18169
  // src/audit/AuditWriter.ts
16820
- var import_node_fs31 = require("fs");
18170
+ var import_node_fs32 = require("fs");
16821
18171
  var import_node_path32 = require("path");
16822
18172
  var AuditWriter = class {
16823
18173
  constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
@@ -16865,7 +18215,7 @@ var AuditWriter = class {
16865
18215
  /** Create a directory once per process and remember it. */
16866
18216
  ensureDir(path2) {
16867
18217
  if (!this.ensuredDirs.has(path2)) {
16868
- (0, import_node_fs31.mkdirSync)(path2, { recursive: true });
18218
+ (0, import_node_fs32.mkdirSync)(path2, { recursive: true });
16869
18219
  this.ensuredDirs.add(path2);
16870
18220
  }
16871
18221
  return path2;
@@ -16875,8 +18225,8 @@ var AuditWriter = class {
16875
18225
  const { requestBody: _req, responseBody: _res, ...meta } = record;
16876
18226
  const file = (0, import_node_path32.join)(dayPath, AUDIT_META_FILE);
16877
18227
  const line = JSON.stringify(meta) + "\n";
16878
- const bytesBefore = (0, import_node_fs31.existsSync)(file) ? (0, import_node_fs31.statSync)(file).size : 0;
16879
- (0, import_node_fs31.appendFileSync)(file, line, "utf8");
18228
+ const bytesBefore = (0, import_node_fs32.existsSync)(file) ? (0, import_node_fs32.statSync)(file).size : 0;
18229
+ (0, import_node_fs32.appendFileSync)(file, line, "utf8");
16880
18230
  try {
16881
18231
  updateAuditStatsAfterAppend(
16882
18232
  file,
@@ -16908,7 +18258,7 @@ var AuditWriter = class {
16908
18258
  const line = encodeBodyEntry(record, sessionKey, dayDir, this.bases);
16909
18259
  if (line === null) return;
16910
18260
  const bodiesPath = this.ensureDir((0, import_node_path32.join)(dayPath, AUDIT_BODIES_DIR));
16911
- (0, import_node_fs31.appendFileSync)((0, import_node_path32.join)(bodiesPath, auditBodyFileName(sessionKey)), line + "\n", "utf8");
18261
+ (0, import_node_fs32.appendFileSync)((0, import_node_path32.join)(bodiesPath, auditBodyFileName(sessionKey)), line + "\n", "utf8");
16912
18262
  } catch (error) {
16913
18263
  this.bases.forget(sessionKey);
16914
18264
  this.logger.warn("[AuditWriter] failed to append audit body shard", {
@@ -16920,10 +18270,10 @@ var AuditWriter = class {
16920
18270
  };
16921
18271
 
16922
18272
  // src/billing/BillingPublisher.ts
16923
- var import_node_fs32 = require("fs");
18273
+ var import_node_fs33 = require("fs");
16924
18274
  var import_node_crypto24 = require("crypto");
16925
18275
  var import_node_path33 = require("path");
16926
- var import_upstreamFetch7 = require("@omnicross/core/pipeline/upstreamFetch");
18276
+ var import_upstreamFetch11 = require("@omnicross/core/pipeline/upstreamFetch");
16927
18277
 
16928
18278
  // src/billing/billingFiles.ts
16929
18279
  var BILLING_FILE_RE = /^billing-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
@@ -16946,7 +18296,7 @@ var BillingPublisher = class {
16946
18296
  constructor(billingDir, logger, opts = {}) {
16947
18297
  this.billingDir = billingDir;
16948
18298
  this.logger = logger;
16949
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch7.fetchUpstream)(url, init));
18299
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch11.fetchUpstream)(url, init));
16950
18300
  this.defer = opts.defer ?? ((fn) => setTimeout(fn, 0));
16951
18301
  this.timeoutMs = opts.timeoutMs ?? BILLING_POST_TIMEOUT_MS;
16952
18302
  this.now = opts.now ?? Date.now;
@@ -16994,7 +18344,7 @@ var BillingPublisher = class {
16994
18344
  appendNow(event) {
16995
18345
  this.ensureDir();
16996
18346
  const file = (0, import_node_path33.join)(this.billingDir, billingFileName(event.ts));
16997
- (0, import_node_fs32.appendFileSync)(file, JSON.stringify(event) + "\n", "utf8");
18347
+ (0, import_node_fs33.appendFileSync)(file, JSON.stringify(event) + "\n", "utf8");
16998
18348
  }
16999
18349
  /**
17000
18350
  * One best-effort delivery attempt for an event ALREADY in the ledger. POSTs the
@@ -17044,7 +18394,7 @@ var BillingPublisher = class {
17044
18394
  try {
17045
18395
  this.ensureDir();
17046
18396
  const file = (0, import_node_path33.join)(this.billingDir, deliveredFileName(event.ts));
17047
- (0, import_node_fs32.appendFileSync)(file, JSON.stringify({ id: event.id, deliveredAt: this.now() }) + "\n", "utf8");
18397
+ (0, import_node_fs33.appendFileSync)(file, JSON.stringify({ id: event.id, deliveredAt: this.now() }) + "\n", "utf8");
17048
18398
  } catch (error) {
17049
18399
  this.logger.warn("[BillingPublisher] failed to append delivery marker", {
17050
18400
  error: error instanceof Error ? error.message : String(error)
@@ -17053,20 +18403,20 @@ var BillingPublisher = class {
17053
18403
  }
17054
18404
  ensureDir() {
17055
18405
  if (this.dirEnsured) return;
17056
- (0, import_node_fs32.mkdirSync)(this.billingDir, { recursive: true });
18406
+ (0, import_node_fs33.mkdirSync)(this.billingDir, { recursive: true });
17057
18407
  this.dirEnsured = true;
17058
18408
  }
17059
18409
  };
17060
18410
 
17061
18411
  // src/billing/billingReader.ts
17062
- var import_node_fs33 = require("fs");
18412
+ var import_node_fs34 = require("fs");
17063
18413
  var import_node_path34 = require("path");
17064
18414
  function readBillingLedger(billingDir) {
17065
18415
  const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
17066
- if (!(0, import_node_fs33.existsSync)(billingDir)) return view;
18416
+ if (!(0, import_node_fs34.existsSync)(billingDir)) return view;
17067
18417
  let files;
17068
18418
  try {
17069
- files = (0, import_node_fs33.readdirSync)(billingDir);
18419
+ files = (0, import_node_fs34.readdirSync)(billingDir);
17070
18420
  } catch {
17071
18421
  return view;
17072
18422
  }
@@ -17097,7 +18447,7 @@ function readBillingStatus(billingDir) {
17097
18447
  function parseLines(dir, file) {
17098
18448
  let raw;
17099
18449
  try {
17100
- raw = (0, import_node_fs33.readFileSync)((0, import_node_path34.join)(dir, file), "utf8");
18450
+ raw = (0, import_node_fs34.readFileSync)((0, import_node_path34.join)(dir, file), "utf8");
17101
18451
  } catch {
17102
18452
  return [];
17103
18453
  }
@@ -17196,7 +18546,7 @@ var BillingRetrySweeper = class {
17196
18546
  // src/TokenRefreshScheduler.ts
17197
18547
  var REFRESH_LEAD_MS2 = 5 * 6e4;
17198
18548
  var SWEEP_INTERVAL_MS5 = 6e4;
17199
- var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini"];
18549
+ var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini", "kimi"];
17200
18550
  var TokenRefreshScheduler = class {
17201
18551
  constructor(store, logger, intervalMs = SWEEP_INTERVAL_MS5, leadMs = REFRESH_LEAD_MS2) {
17202
18552
  this.store = store;
@@ -17279,6 +18629,8 @@ var TokenRefreshScheduler = class {
17279
18629
  return this.store.refreshCodexToken();
17280
18630
  case "gemini":
17281
18631
  return this.store.refreshGeminiToken();
18632
+ case "kimi":
18633
+ return this.store.refreshKimiToken();
17282
18634
  }
17283
18635
  }
17284
18636
  };
@@ -17355,7 +18707,7 @@ function createRouteLeaseSubscriptionPreflight(credentials) {
17355
18707
 
17356
18708
  // src/webhook/WebhookDispatcher.ts
17357
18709
  var import_node_crypto25 = require("crypto");
17358
- var import_upstreamFetch8 = require("@omnicross/core/pipeline/upstreamFetch");
18710
+ var import_upstreamFetch12 = require("@omnicross/core/pipeline/upstreamFetch");
17359
18711
  var WEBHOOK_MAX_ATTEMPTS = 3;
17360
18712
  var WEBHOOK_QUEUE_MAX = 1e3;
17361
18713
  var WEBHOOK_SEND_TIMEOUT_MS = 1e4;
@@ -17375,7 +18727,7 @@ var WebhookDispatcher = class {
17375
18727
  sleep;
17376
18728
  now;
17377
18729
  constructor(opts = {}) {
17378
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch8.fetchUpstream)(url, init));
18730
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch12.fetchUpstream)(url, init));
17379
18731
  this.logger = opts.logger;
17380
18732
  this.maxAttempts = opts.maxAttempts ?? WEBHOOK_MAX_ATTEMPTS;
17381
18733
  this.queueMax = opts.queueMax ?? WEBHOOK_QUEUE_MAX;
@@ -17461,8 +18813,8 @@ var WebhookDispatcher = class {
17461
18813
  signal: AbortSignal.timeout(this.timeoutMs)
17462
18814
  });
17463
18815
  return res.ok ? { ok: true, status: res.status } : { ok: false, status: res.status };
17464
- } catch (err5) {
17465
- return { ok: false, error: err5 instanceof Error ? err5.message : String(err5) };
18816
+ } catch (err6) {
18817
+ return { ok: false, error: err6 instanceof Error ? err6.message : String(err6) };
17466
18818
  }
17467
18819
  }
17468
18820
  /**
@@ -17586,7 +18938,7 @@ function resetImageRuntimeBootstrapSession() {
17586
18938
  function resolveLoggingConfig(configured, configPath) {
17587
18939
  const file = configured?.file ?? defaultDaemonLogPath(configPath);
17588
18940
  try {
17589
- (0, import_node_fs34.mkdirSync)(configured?.file ? (0, import_node_path35.dirname)(configured.file) : defaultLogDir(configPath), {
18941
+ (0, import_node_fs35.mkdirSync)(configured?.file ? (0, import_node_path35.dirname)(configured.file) : defaultLogDir(configPath), {
17590
18942
  recursive: true
17591
18943
  });
17592
18944
  } catch {
@@ -17604,12 +18956,12 @@ function buildDaemon(config, paths) {
17604
18956
  setSecretBox(secretBox3);
17605
18957
  setSecretBox2(secretBox3);
17606
18958
  const decryptedConfig = decryptConfigSecrets(config, secretBox3);
17607
- const accountAllowanceStore = new import_AccountAllowanceStore4.AccountAllowanceStore(
18959
+ const accountAllowanceStore = new import_AccountAllowanceStore7.AccountAllowanceStore(
17608
18960
  Date.now,
17609
18961
  void 0,
17610
18962
  new JsonAccountAllowancePersistence(defaultAccountAllowancePath(paths.configPath))
17611
18963
  );
17612
- (0, import_AccountAllowanceStore4.setSharedAccountAllowanceStore)(accountAllowanceStore);
18964
+ (0, import_AccountAllowanceStore7.setSharedAccountAllowanceStore)(accountAllowanceStore);
17613
18965
  (0, import_AccountAllowanceScheduling5.getSharedAccountAllowanceScheduling)().configure(
17614
18966
  (0, import_outbound_api10.normalizeServerConfig)(decryptedConfig.server).allowanceScheduling
17615
18967
  );
@@ -17634,21 +18986,22 @@ function buildDaemon(config, paths) {
17634
18986
  claudeAllowanceRefreshScheduler.configure(
17635
18987
  (0, import_outbound_api10.normalizeServerConfig)(decryptedConfig.server).allowanceScheduling
17636
18988
  );
17637
- const subscriptionAccounts = new import_subscriptions6.SubscriptionAccountService(credentialStore);
17638
- (0, import_subscriptions6.setSubscriptionAccountService)(subscriptionAccounts);
17639
- const subscriptionRegistry = new import_subscriptions6.SubscriptionProviderRegistry(
18989
+ const subscriptionAccounts = new import_subscriptions9.SubscriptionAccountService(credentialStore);
18990
+ (0, import_subscriptions9.setSubscriptionAccountService)(subscriptionAccounts);
18991
+ const subscriptionRegistry = new import_subscriptions9.SubscriptionProviderRegistry(
17640
18992
  subscriptionAccounts,
17641
18993
  credentialStore
17642
18994
  );
17643
- (0, import_subscriptions6.setSubscriptionProviderRegistry)(subscriptionRegistry);
18995
+ (0, import_subscriptions9.setSubscriptionProviderRegistry)(subscriptionRegistry);
17644
18996
  setServerProxyConfig(decryptedConfig.server?.proxy);
17645
- (0, import_upstreamFetch9.setUpstreamProxyResolver)(
18997
+ (0, import_upstreamFetch13.setUpstreamProxyResolver)(
17646
18998
  createUpstreamProxyResolver({
17647
18999
  getAccountProxy: (providerId, accountId) => credentialStore.getAccountProxy(providerId, accountId)
17648
19000
  })
17649
19001
  );
17650
19002
  (0, import_gemini_code_assist_resolver.setGeminiCodeAssistResolver)((0, import_GeminiCodeAssistProjectResolver.getGeminiCodeAssistProjectResolver)());
17651
19003
  const autoDisableStore = new AutoDisableStore();
19004
+ const providerKeyQuotaService = new ProviderKeyQuotaService(secretBox3);
17652
19005
  const apiKeyPool = new import_ApiKeyPoolService.ApiKeyPoolService(
17653
19006
  createPoolKeysLoader((id) => llmConfig.getProviderRow(id), autoDisableStore),
17654
19007
  resolveEnvKey,
@@ -17665,7 +19018,7 @@ function buildDaemon(config, paths) {
17665
19018
  const pricingEngine = new import_usage2.PricingEngine(pricingStore, logger, {
17666
19019
  // Catalog egress follows the same global/env proxy policy as every other
17667
19020
  // daemon upstream call; no provider/account override applies here.
17668
- fetchImpl: ((input, init) => (0, import_upstreamFetch9.fetchUpstream)(String(input), init ?? {}))
19021
+ fetchImpl: ((input, init) => (0, import_upstreamFetch13.fetchUpstream)(String(input), init ?? {}))
17669
19022
  });
17670
19023
  const pricingRefreshScheduler = new PricingRefreshScheduler(
17671
19024
  pricingEngine,
@@ -17929,6 +19282,11 @@ function buildDaemon(config, paths) {
17929
19282
  // values themselves NEVER leave (masked via `maskProviderApiKey`).
17930
19283
  apiKeyPool,
17931
19284
  autoDisableStore,
19285
+ // BYO provider-key quota (Z.AI coding plan, MiniMax Token Plan, …): a
19286
+ // read-through cached same-key usage probe surfaced on the keys view. The
19287
+ // key plaintext is resolved + decrypted inside the service and never
19288
+ // crosses back out.
19289
+ providerKeyQuota: providerKeyQuotaService,
17932
19290
  // Interactive OAuth login over admin HTTP (app-parity child 4, design
17933
19291
  // D1/D2-a). The in-memory pending-session store (NEVER serialized), the
17934
19292
  // injected token-exchange fetch (global `fetch` here; mocked in tests), and a
@@ -17945,7 +19303,7 @@ function buildDaemon(config, paths) {
17945
19303
  // — `server.proxy.byProvider[...]` was silently skipped — and the call was
17946
19304
  // excluded from the upstream trace, so a failing login left no evidence.
17947
19305
  // `redactBodies` keeps the code/verifier + minted token out of that trace.
17948
- oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => (0, import_upstreamFetch9.fetchUpstream)(url, init, { providerId, redactBodies: true }),
19306
+ oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => (0, import_upstreamFetch13.fetchUpstream)(url, init, { providerId, redactBodies: true }),
17949
19307
  subscriptionAccountAppender: credentialStore,
17950
19308
  // Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
17951
19309
  // + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
@@ -17953,6 +19311,10 @@ function buildDaemon(config, paths) {
17953
19311
  // can inject a mock so no real port is bound.
17954
19312
  codexSessions: new CodexOAuthSessionStore(),
17955
19313
  codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
19314
+ // Kimi interactive OAuth — the async DEVICE-CODE flow store (no port, no
19315
+ // paste; the app shows the verification URL + user code and polls the
19316
+ // token-free status). Token captured + persisted daemon-side.
19317
+ kimiSessions: new CodexOAuthSessionStore(),
17956
19318
  // Migration pack (app-parity child 6, design D2/D3) — the concrete credential
17957
19319
  // store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
17958
19320
  // the multi-account append (`appendProviderAccount`, import re-encrypts at-
@@ -18011,7 +19373,7 @@ function buildDaemon(config, paths) {
18011
19373
  });
18012
19374
  const webhookDispatcher = new WebhookDispatcher({
18013
19375
  logger,
18014
- fetchImpl: (url, init) => (0, import_upstreamFetch9.fetchUpstream)(url, init)
19376
+ fetchImpl: (url, init) => (0, import_upstreamFetch13.fetchUpstream)(url, init)
18015
19377
  });
18016
19378
  setWebhookRuntime(webhookDispatcher, (0, import_SubscriptionAccountHealth4.getSharedAccountHealth)());
18017
19379
  const auditWriter = new AuditWriter(auditDir, logger);
@@ -18091,9 +19453,9 @@ function resetDaemonSingletonsForTests() {
18091
19453
  (0, import_provider_proxy4.__resetProviderProxyForTests)();
18092
19454
  (0, import_outbound_api10.__resetOutboundApiServerForTests)();
18093
19455
  (0, import_subscriptionRegistryPort.setSubscriptionRegistryForOutbound)(null);
18094
- (0, import_subscriptions6.setSubscriptionProviderRegistry)(null);
18095
- (0, import_subscriptions6.setSubscriptionAccountService)(null);
18096
- (0, import_upstreamFetch9.setUpstreamProxyResolver)(null);
19456
+ (0, import_subscriptions9.setSubscriptionProviderRegistry)(null);
19457
+ (0, import_subscriptions9.setSubscriptionAccountService)(null);
19458
+ (0, import_upstreamFetch13.setUpstreamProxyResolver)(null);
18097
19459
  setServerProxyConfig(void 0);
18098
19460
  (0, import_gemini_code_assist_resolver.setGeminiCodeAssistResolver)(null);
18099
19461
  setSecretBox(null);
@@ -18102,14 +19464,14 @@ function resetDaemonSingletonsForTests() {
18102
19464
  resetAuditRuntimeForTests();
18103
19465
  resetBillingRuntimeForTests();
18104
19466
  (0, import_SubscriptionIdentityStore3.__resetSharedIdentityStoreForTests)();
18105
- (0, import_AccountAllowanceStore4.__resetSharedAccountAllowanceStoreForTests)();
19467
+ (0, import_AccountAllowanceStore7.__resetSharedAccountAllowanceStoreForTests)();
18106
19468
  (0, import_AccountAllowanceScheduling5.__resetSharedAccountAllowanceSchedulingForTests)();
18107
19469
  (0, import_usage2.__resetSharedUsageThroughputTrackerForTests)();
18108
19470
  }
18109
19471
  function isTokensStoreReadable(tokensPath) {
18110
19472
  try {
18111
- if (!(0, import_node_fs34.existsSync)(tokensPath)) return true;
18112
- (0, import_node_fs34.accessSync)(tokensPath, import_node_fs34.constants.R_OK);
19473
+ if (!(0, import_node_fs35.existsSync)(tokensPath)) return true;
19474
+ (0, import_node_fs35.accessSync)(tokensPath, import_node_fs35.constants.R_OK);
18113
19475
  return true;
18114
19476
  } catch {
18115
19477
  return false;