@omnicross/daemon 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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_upstreamFetch8 = 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
- ]);
1184
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
463
1185
  const accounts = (config.claudeAccounts ?? []).filter(
464
1186
  (account) => !accountId || account.id === accountId
465
1187
  );
466
1188
  return this.claudeCollector.collectMany(accounts, { force: true });
467
1189
  }
468
1190
  /**
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.
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(
1217
+ (account) => !accountId || account.id === accountId
1218
+ );
1219
+ return this.kimiCollector.collectMany(accounts, { force: true });
1220
+ }
1221
+ /**
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_upstreamFetch3 = 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;
@@ -3054,7 +3810,19 @@ function validateCodex(body) {
3054
3810
  ]);
3055
3811
  return out;
3056
3812
  }
3057
- function validateGemini(body) {
3813
+ function validateGemini(body) {
3814
+ const authMethod = str(body["authMethod"]);
3815
+ const status = str(body["status"]);
3816
+ if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
3817
+ if (!status || !TOKEN_STATUSES.has(status)) return null;
3818
+ const out = {
3819
+ authMethod,
3820
+ status
3821
+ };
3822
+ copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "lastRefreshedAt", "errorMessage"]);
3823
+ return out;
3824
+ }
3825
+ function validateKimi(body) {
3058
3826
  const authMethod = str(body["authMethod"]);
3059
3827
  const status = str(body["status"]);
3060
3828
  if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
@@ -3063,7 +3831,7 @@ function validateGemini(body) {
3063
3831
  authMethod,
3064
3832
  status
3065
3833
  };
3066
- copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "lastRefreshedAt", "errorMessage"]);
3834
+ copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "accountId", "deviceId", "lastRefreshedAt", "errorMessage"]);
3067
3835
  return out;
3068
3836
  }
3069
3837
  function validateOpenCodeGo(body) {
@@ -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?.();
@@ -3743,6 +4513,7 @@ async function handleDashboard(deps) {
3743
4513
  var import_outbound_api2 = require("@omnicross/core/outbound-api");
3744
4514
  var import_api3 = require("@omnicross/core/search/api");
3745
4515
  var import_http3 = require("@omnicross/core/search/http");
4516
+ var import_search2 = require("@omnicross/core/search");
3746
4517
 
3747
4518
  // src/search/searchDoctorProjection.ts
3748
4519
  var import_search_types = require("@omnicross/contracts/search-types");
@@ -3803,7 +4574,7 @@ function buildSearchDoctorSnapshot(contributions = (0, import_http.builtinHttpSe
3803
4574
  }
3804
4575
  return rows;
3805
4576
  }
3806
- var SEARCH_DOCTOR_QUERY = "mozilla developer network http headers";
4577
+ var SEARCH_DOCTOR_QUERY = "MDN HTTP headers documentation";
3807
4578
  function classifyLiveSearchOutcome(providerId, outcome, checkedAt) {
3808
4579
  if (outcome.kind === "results") {
3809
4580
  if (outcome.count > 0) return { providerId, status: "healthy", checkedAt };
@@ -3848,6 +4619,7 @@ function classifySearchFailure(stage, code) {
3848
4619
  }
3849
4620
 
3850
4621
  // src/search/SearchAssembly.ts
4622
+ var import_upstreamFetch6 = require("@omnicross/core/pipeline/upstreamFetch");
3851
4623
  var import_search = require("@omnicross/core/search");
3852
4624
  var import_api2 = require("@omnicross/core/search/api");
3853
4625
  var import_http2 = require("@omnicross/core/search/http");
@@ -3864,11 +4636,24 @@ function searchPolicyFrom(config) {
3864
4636
  ...maxAttempts !== void 0 ? { maxAttempts } : {}
3865
4637
  };
3866
4638
  }
4639
+ function resolveSearchUpstreamDispatcher(url) {
4640
+ return (0, import_upstreamFetch6.resolveUpstreamDispatcher)({ url });
4641
+ }
4642
+ var searchUpstreamProxyConfig = createUpstreamProxyResolver();
4643
+ function resolveSearchUpstreamProxyConfig(url) {
4644
+ return searchUpstreamProxyConfig({ url });
4645
+ }
3867
4646
  function searchContributionsFrom(config) {
3868
4647
  return [
3869
- ...(0, import_http2.builtinHttpSearchContributions)(),
4648
+ ...(0, import_http2.builtinHttpSearchContributions)(
4649
+ (0, import_http2.createSearchHttpTransport)({
4650
+ resolveProxyDispatcher: resolveSearchUpstreamDispatcher,
4651
+ resolveProxyConfig: resolveSearchUpstreamProxyConfig
4652
+ })
4653
+ ),
3870
4654
  ...(0, import_api2.apiSearchContributions)(config.providers, {
3871
- egressPolicy: searchEgressPolicyFrom(config)
4655
+ egressPolicy: searchEgressPolicyFrom(config),
4656
+ resolveProxyDispatcher: resolveSearchUpstreamDispatcher
3872
4657
  })
3873
4658
  ];
3874
4659
  }
@@ -4012,6 +4797,18 @@ async function handleSearchDiagnostics(res, deps) {
4012
4797
  };
4013
4798
  return writeJson(res, 200, { diagnostics: snapshot });
4014
4799
  }
4800
+ function persistedSearchContributions(search, fetchImpl) {
4801
+ if (fetchImpl) {
4802
+ const egressPolicy = searchEgressPolicyFrom(search);
4803
+ return [
4804
+ ...(0, import_http3.builtinHttpSearchContributions)(
4805
+ (0, import_http3.createSearchHttpTransport)({ fetch: fetchImpl, egressPolicy })
4806
+ ),
4807
+ ...(0, import_api3.apiSearchContributions)(search.providers, { egressPolicy, fetchImpl })
4808
+ ];
4809
+ }
4810
+ return searchContributionsFrom(search);
4811
+ }
4015
4812
  async function handleSearchTest(req, res, deps) {
4016
4813
  const status = deps.searchStatus;
4017
4814
  const body = await readBodyOrReject(req, res);
@@ -4029,16 +4826,8 @@ async function handleSearchTest(req, res, deps) {
4029
4826
  if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
4030
4827
  return writeErr(res, 400, `search provider '${providerId}' is not configured`);
4031
4828
  }
4032
- const egressPolicy = searchEgressPolicyFrom(search);
4033
4829
  const fetchImpl = status.testFetch;
4034
- const transport = fetchImpl ? (0, import_http3.createSearchHttpTransport)({ fetch: fetchImpl, egressPolicy }) : void 0;
4035
- const contributions = [
4036
- ...(0, import_http3.builtinHttpSearchContributions)(transport),
4037
- ...(0, import_api3.apiSearchContributions)(search.providers, {
4038
- egressPolicy,
4039
- ...fetchImpl ? { fetchImpl } : {}
4040
- })
4041
- ];
4830
+ const contributions = persistedSearchContributions(search, fetchImpl);
4042
4831
  const contribution = contributions.find((c) => c.id === providerId);
4043
4832
  if (!contribution) {
4044
4833
  return writeErr(res, 400, `search provider '${providerId}' is not configured`);
@@ -4086,41 +4875,42 @@ async function handleSearchQuery(req, res, deps) {
4086
4875
  if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
4087
4876
  return writeErr(res, 400, `search provider '${providerId}' is not configured`);
4088
4877
  }
4089
- const egressPolicy = searchEgressPolicyFrom(search);
4090
4878
  const fetchImpl = status.testFetch;
4091
- const transport = fetchImpl ? (0, import_http3.createSearchHttpTransport)({ fetch: fetchImpl, egressPolicy }) : void 0;
4092
- const contributions = [
4093
- ...(0, import_http3.builtinHttpSearchContributions)(transport),
4094
- ...(0, import_api3.apiSearchContributions)(search.providers, {
4095
- egressPolicy,
4096
- ...fetchImpl ? { fetchImpl } : {}
4097
- })
4098
- ];
4099
- const contribution = contributions.find((c) => c.id === providerId);
4100
- if (!contribution) {
4101
- return writeErr(res, 400, `search provider '${providerId}' is not configured`);
4102
- }
4879
+ const runtime = (0, import_search2.createSearchRuntime)({
4880
+ contributions: persistedSearchContributions(search, fetchImpl),
4881
+ policy: {
4882
+ ...searchPolicyFrom(search),
4883
+ // The panel always walks: it answers "does a search WORK for this
4884
+ // operator", not "does this one provider behave" — that is `/test`'s
4885
+ // job. The persisted policy's allowlist still bounds the walk.
4886
+ fallbackEnabled: true,
4887
+ preferred: providerId
4888
+ }
4889
+ });
4103
4890
  const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
4104
4891
  try {
4105
- const results = await contribution.provider.search(query2, { maxResults: 5 });
4892
+ const orchestrated = await runtime.search({ query: query2, options: { maxResults: 5 } });
4893
+ const results = orchestrated.results;
4106
4894
  const sanitized = results.slice(0, SEARCH_QUERY_MAX_RESULTS).map((result) => ({
4107
4895
  title: sanitizeResultField(result.title, SEARCH_RESULT_FIELD_CAPS.title),
4108
4896
  url: sanitizeResultField(result.url, SEARCH_RESULT_FIELD_CAPS.url),
4109
4897
  content: sanitizeResultField(result.content, SEARCH_RESULT_FIELD_CAPS.content)
4110
4898
  }));
4111
- const diagnostic = sanitized.length === 0 ? { providerId: contribution.id, status: "healthy", checkedAt } : classifyLiveSearchOutcome(
4112
- contribution.id,
4899
+ const diagnostic = sanitized.length === 0 ? { providerId: orchestrated.providerId, status: "healthy", checkedAt } : classifyLiveSearchOutcome(
4900
+ orchestrated.providerId,
4113
4901
  { kind: "results", count: sanitized.length },
4114
4902
  checkedAt
4115
4903
  );
4116
4904
  const response = {
4117
4905
  diagnostic,
4906
+ providerUsed: orchestrated.providerId,
4907
+ fallbackCount: orchestrated.fallbackCount,
4118
4908
  resultCount: sanitized.length,
4119
4909
  results: sanitized
4120
4910
  };
4121
4911
  return writeJson(res, 200, { result: response });
4122
4912
  } catch (error) {
4123
- const diagnostic = classifyLiveSearchOutcome(contribution.id, { kind: "failure", error }, checkedAt);
4913
+ const diagnostic = classifyLiveSearchOutcome(providerId, { kind: "failure", error }, checkedAt);
4124
4914
  const response = { diagnostic };
4125
4915
  return writeJson(res, 200, { result: response });
4126
4916
  }
@@ -4129,7 +4919,7 @@ async function handleSearchQuery(req, res, deps) {
4129
4919
  // src/admin/searchAdminView.ts
4130
4920
  var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
4131
4921
  var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
4132
- function isRecord(value) {
4922
+ function isRecord2(value) {
4133
4923
  return value !== null && typeof value === "object" && !Array.isArray(value);
4134
4924
  }
4135
4925
  function redactSearchServerConfig(search) {
@@ -4179,13 +4969,13 @@ function resolveSecretField(entry, field, stored) {
4179
4969
  else delete entry[field];
4180
4970
  }
4181
4971
  function preserveSearchSecrets(incoming, current) {
4182
- if (!isRecord(incoming)) return incoming;
4972
+ if (!isRecord2(incoming)) return incoming;
4183
4973
  const section = { ...incoming };
4184
4974
  const providersValue = section["providers"];
4185
- if (!isRecord(providersValue)) return section;
4975
+ if (!isRecord2(providersValue)) return section;
4186
4976
  const providers = {};
4187
4977
  for (const [id, entryValue] of Object.entries(providersValue)) {
4188
- if (!isRecord(entryValue)) {
4978
+ if (!isRecord2(entryValue)) {
4189
4979
  providers[id] = entryValue;
4190
4980
  continue;
4191
4981
  }
@@ -4263,7 +5053,7 @@ function parseKeyPolicyBody(body) {
4263
5053
  var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
4264
5054
  var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
4265
5055
  var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
4266
- function isRecord2(value) {
5056
+ function isRecord3(value) {
4267
5057
  return !!value && typeof value === "object" && !Array.isArray(value);
4268
5058
  }
4269
5059
  function nonBlank(value) {
@@ -4283,7 +5073,7 @@ function validateGatewayBindingsSegment(patch) {
4283
5073
  const ids = /* @__PURE__ */ new Set();
4284
5074
  raw.forEach((entry, index) => {
4285
5075
  const path2 = `bindings[${index}]`;
4286
- if (!isRecord2(entry)) {
5076
+ if (!isRecord3(entry)) {
4287
5077
  errors.push(`${path2} must be an object`);
4288
5078
  return;
4289
5079
  }
@@ -4312,12 +5102,12 @@ function validateGatewayBindingsSegment(patch) {
4312
5102
  } else if (entry.modelMappings.length > 100) {
4313
5103
  errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
4314
5104
  } else if (entry.modelMappings.some(
4315
- (mapping) => !isRecord2(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
5105
+ (mapping) => !isRecord3(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
4316
5106
  )) {
4317
5107
  errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
4318
5108
  }
4319
5109
  }
4320
- if (!isRecord2(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
5110
+ if (!isRecord3(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
4321
5111
  errors.push(`${path2}.target is invalid`);
4322
5112
  } else {
4323
5113
  if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
@@ -4332,7 +5122,7 @@ function validateGatewayBindingsSegment(patch) {
4332
5122
  }
4333
5123
  }
4334
5124
  if (entry.modelMap !== void 0) {
4335
- 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")) {
4336
5126
  errors.push(`${path2}.modelMap must contain string values`);
4337
5127
  }
4338
5128
  }
@@ -4630,7 +5420,8 @@ var PROVIDER_KEYS = {
4630
5420
  block: "opencodego",
4631
5421
  accounts: "opencodegoAccounts",
4632
5422
  active: "activeOpencodegoAccountId"
4633
- }
5423
+ },
5424
+ kimi: { block: "kimi", accounts: "kimiAccounts", active: "activeKimiAccountId" }
4634
5425
  };
4635
5426
  function clone(value) {
4636
5427
  return JSON.parse(JSON.stringify(value));
@@ -5152,7 +5943,7 @@ async function applyImport(packString, passphrase, mode, deps, parseProviderInpu
5152
5943
  }
5153
5944
 
5154
5945
  // src/admin/adminMigration.ts
5155
- function err3(status, message) {
5946
+ function err4(status, message) {
5156
5947
  return { status, body: { error: { type: "admin_api_error", message } } };
5157
5948
  }
5158
5949
  async function handleExport(body, deps) {
@@ -5162,30 +5953,30 @@ async function handleExport(body, deps) {
5162
5953
  return { status: 200, body: { pack, version: BUNDLE_VERSION } };
5163
5954
  } catch (error) {
5164
5955
  if (error instanceof WeakPassphraseError) {
5165
- return err3(400, error.message);
5956
+ return err4(400, error.message);
5166
5957
  }
5167
- return err3(500, "failed to build the migration pack");
5958
+ return err4(500, "failed to build the migration pack");
5168
5959
  }
5169
5960
  }
5170
5961
  async function handleImport(body, deps) {
5171
5962
  const blob = typeof body["blob"] === "string" ? body["blob"] : "";
5172
5963
  const passphrase = typeof body["passphrase"] === "string" ? body["passphrase"] : "";
5173
5964
  const mode = body["mode"] === "overwrite" ? "overwrite" : "merge";
5174
- if (!blob) return err3(400, "import requires { blob }");
5965
+ if (!blob) return err4(400, "import requires { blob }");
5175
5966
  try {
5176
5967
  const counts = await applyImport(blob, passphrase, mode, deps, deps.parseProviderInput);
5177
5968
  return { status: 200, body: counts };
5178
5969
  } catch (error) {
5179
5970
  if (error instanceof WeakPassphraseError) {
5180
- return err3(400, error.message);
5971
+ return err4(400, error.message);
5181
5972
  }
5182
- return err3(400, error instanceof Error ? error.message : "import failed");
5973
+ return err4(400, error instanceof Error ? error.message : "import failed");
5183
5974
  }
5184
5975
  }
5185
5976
 
5186
5977
  // src/admin/usagePricing.ts
5187
5978
  var import_usage = require("@omnicross/core/usage");
5188
- var err4 = (status, message) => ({
5979
+ var err5 = (status, message) => ({
5189
5980
  status,
5190
5981
  body: { error: { type: "admin_api_error", message } }
5191
5982
  });
@@ -5198,7 +5989,7 @@ function parseRange(query2) {
5198
5989
  const startTs = parseFiniteInt(query2.get("startTs"));
5199
5990
  const endTs = parseFiniteInt(query2.get("endTs"));
5200
5991
  if (startTs === null || endTs === null) {
5201
- 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");
5202
5993
  }
5203
5994
  return { startTs, endTs };
5204
5995
  }
@@ -5223,14 +6014,14 @@ async function handleUsageGet(view, query2, deps) {
5223
6014
  case "timeseries": {
5224
6015
  const bucket = query2.get("bucket");
5225
6016
  if (bucket !== "hour" && bucket !== "day" && bucket !== "month") {
5226
- return err4(400, "bucket must be one of 'hour', 'day', 'month'");
6017
+ return err5(400, "bucket must be one of 'hour', 'day', 'month'");
5227
6018
  }
5228
6019
  const now = Date.now();
5229
6020
  const clamped = { startTs: range.startTs, endTs: Math.min(range.endTs, now) };
5230
6021
  if (clamped.startTs < clamped.endTs) {
5231
6022
  const projected = Math.ceil((clamped.endTs - clamped.startTs) / BUCKET_SPAN_MS[bucket]) + 1;
5232
6023
  if (projected > MAX_TIMESERIES_BUCKETS) {
5233
- return err4(
6024
+ return err5(
5234
6025
  400,
5235
6026
  `requested range projects ~${projected} '${bucket}' buckets (max ${MAX_TIMESERIES_BUCKETS}); narrow the range or use a coarser bucket`
5236
6027
  );
@@ -5253,7 +6044,7 @@ async function handleUsageGet(view, query2, deps) {
5253
6044
  };
5254
6045
  }
5255
6046
  default:
5256
- return err4(404, `unknown usage view '${view ?? ""}'`);
6047
+ return err5(404, `unknown usage view '${view ?? ""}'`);
5257
6048
  }
5258
6049
  }
5259
6050
  function poolKeyLabels(cfg) {
@@ -5302,7 +6093,7 @@ async function handlePricingList(deps) {
5302
6093
  async function handlePricingUpsert(body, deps) {
5303
6094
  const input = parsePricingEntryInput(body);
5304
6095
  if (!input) {
5305
- 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)");
5306
6097
  }
5307
6098
  const entry = await deps.pricingEngine.upsertManual(input);
5308
6099
  return { status: 200, body: { entry } };
@@ -5311,7 +6102,7 @@ async function handlePricingDelete(query2, deps) {
5311
6102
  const providerId = query2.get("providerId")?.trim() ?? "";
5312
6103
  const modelId = query2.get("modelId")?.trim() ?? "";
5313
6104
  if (!providerId || !modelId) {
5314
- return err4(400, "delete requires providerId and modelId query params");
6105
+ return err5(400, "delete requires providerId and modelId query params");
5315
6106
  }
5316
6107
  const deleted = await deps.pricingStore.delete(providerId, modelId);
5317
6108
  if (deleted) await deps.pricingEngine.invalidateCache();
@@ -5331,13 +6122,13 @@ async function handlePricingFetchLatest(deps) {
5331
6122
  }
5332
6123
  };
5333
6124
  } catch (e) {
5334
- 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)}`);
5335
6126
  }
5336
6127
  }
5337
6128
  async function handlePricingResolveConflicts(body, deps) {
5338
6129
  const raw = body["resolutions"];
5339
6130
  if (!Array.isArray(raw)) {
5340
- return err4(400, "resolve-conflicts requires { resolutions: [...] }");
6131
+ return err5(400, "resolve-conflicts requires { resolutions: [...] }");
5341
6132
  }
5342
6133
  const currentRows = await deps.pricingStore.getAll();
5343
6134
  const userEditedKeys = new Set(
@@ -5347,21 +6138,21 @@ async function handlePricingResolveConflicts(body, deps) {
5347
6138
  const pendingIncoming = /* @__PURE__ */ new Map();
5348
6139
  let staleCount = 0;
5349
6140
  for (const item of raw) {
5350
- if (!item || typeof item !== "object") return err4(400, "invalid resolution entry");
6141
+ if (!item || typeof item !== "object") return err5(400, "invalid resolution entry");
5351
6142
  const r = item;
5352
6143
  const action = r["action"];
5353
6144
  if (action !== "overwrite" && action !== "skip") {
5354
- return err4(400, "resolution action must be 'overwrite' or 'skip'");
6145
+ return err5(400, "resolution action must be 'overwrite' or 'skip'");
5355
6146
  }
5356
6147
  const providerId = typeof r["providerId"] === "string" && r["providerId"].trim() ? r["providerId"].trim() : "";
5357
6148
  const modelId = typeof r["modelId"] === "string" && r["modelId"].trim() ? r["modelId"].trim() : "";
5358
6149
  if (!providerId || !modelId) {
5359
- return err4(400, "each resolution requires top-level providerId and modelId");
6150
+ return err5(400, "each resolution requires top-level providerId and modelId");
5360
6151
  }
5361
6152
  const incoming = parsePricingEntryInput(r["incoming"]);
5362
- 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");
5363
6154
  if (incoming.providerId !== providerId || incoming.modelId !== modelId) {
5364
- 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");
5365
6156
  }
5366
6157
  const key = `${providerId}::${modelId}`;
5367
6158
  if (action === "overwrite" && !userEditedKeys.has(key)) {
@@ -5406,7 +6197,7 @@ function query(req) {
5406
6197
  }
5407
6198
  function allowanceProvider(value) {
5408
6199
  if (!value) return void 0;
5409
- return value === "claude" || value === "codex" ? value : null;
6200
+ return value === "claude" || value === "codex" || value === "kimi" || value === "opencodego" ? value : null;
5410
6201
  }
5411
6202
  async function handleAccountAllowanceApi(req, res, method, rest, service) {
5412
6203
  if (!service) return writeError2(res, 501, "account allowance service is not available");
@@ -5420,7 +6211,9 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
5420
6211
  const params = query(req);
5421
6212
  const pathProvider = rest.length >= 2 ? rest[0] : null;
5422
6213
  const providerId = allowanceProvider(pathProvider ?? params.get("providerId") ?? params.get("provider"));
5423
- 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
+ }
5424
6217
  const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
5425
6218
  const allowances = await service.list({ providerId, accountId });
5426
6219
  return writeJson3(res, 200, { allowances });
@@ -5430,10 +6223,37 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
5430
6223
  const requestedProvider = allowanceProvider(
5431
6224
  typeof body["providerId"] === "string" ? body["providerId"] : "claude"
5432
6225
  );
5433
- if (requestedProvider !== "claude") {
5434
- return writeError2(res, 400, "only Claude allowances support explicit refresh");
5435
- }
5436
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
+ }
5437
6257
  const allowances = await service.refreshClaude(accountId);
5438
6258
  if (accountId && allowances.length === 0) {
5439
6259
  return writeError2(res, 404, `Claude account '${accountId}' not found`);
@@ -5601,8 +6421,8 @@ async function handleAdminApi(req, res, path2, deps) {
5601
6421
  default:
5602
6422
  return writeJsonError(res, 404, `unknown admin resource '${resource}'`);
5603
6423
  }
5604
- } catch (err5) {
5605
- writeJsonError(res, 500, err5 instanceof Error ? err5.message : String(err5));
6424
+ } catch (err6) {
6425
+ writeJsonError(res, 500, err6 instanceof Error ? err6.message : String(err6));
5606
6426
  }
5607
6427
  }
5608
6428
  function requestQuery(req) {
@@ -5672,6 +6492,9 @@ async function handleProviders(req, res, method, rest, deps) {
5672
6492
  if (method === "POST" && rest.length === 4 && rest[1] === "keys" && rest[3] === "enabled") {
5673
6493
  return await handleToggleProviderKey(req, res, rest[0], rest[2], cfg, deps);
5674
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
+ }
5675
6498
  if (method === "PUT" && rest.length === 3 && rest[1] === "keys") {
5676
6499
  return await handleUpdateProviderKey(req, res, rest[0], rest[2], cfg, deps);
5677
6500
  }
@@ -5770,7 +6593,7 @@ async function handleDiscoverModels(res, id, cfg) {
5770
6593
  try {
5771
6594
  const headers = { Accept: "application/json" };
5772
6595
  if (resolvedKey) headers["Authorization"] = `Bearer ${resolvedKey}`;
5773
- const response = await (0, import_upstreamFetch3.fetchUpstream)(url, { method: "GET", headers }, { providerId: "byo" });
6596
+ const response = await (0, import_upstreamFetch7.fetchUpstream)(url, { method: "GET", headers }, { providerId: "byo" });
5774
6597
  if (!response.ok) {
5775
6598
  const text = await response.text().catch(() => "");
5776
6599
  let message = text.slice(0, 300);
@@ -5787,8 +6610,8 @@ async function handleDiscoverModels(res, id, cfg) {
5787
6610
  const data = await response.json();
5788
6611
  const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
5789
6612
  return writeJson4(res, 200, { models });
5790
- } catch (err5) {
5791
- const message = err5 instanceof Error ? err5.message : String(err5);
6613
+ } catch (err6) {
6614
+ const message = err6 instanceof Error ? err6.message : String(err6);
5792
6615
  return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
5793
6616
  }
5794
6617
  }
@@ -5829,7 +6652,7 @@ async function handleTestModel(req, res, id, cfg) {
5829
6652
  }
5830
6653
  const startedAt = Date.now();
5831
6654
  try {
5832
- const response = await (0, import_upstreamFetch3.fetchUpstream)(
6655
+ const response = await (0, import_upstreamFetch7.fetchUpstream)(
5833
6656
  url,
5834
6657
  { method: "POST", headers, body: JSON.stringify(payload) },
5835
6658
  { providerId: "byo" }
@@ -5851,8 +6674,8 @@ async function handleTestModel(req, res, id, cfg) {
5851
6674
  latencyMs,
5852
6675
  sample: extractSampleText(text, row.apiFormat)
5853
6676
  });
5854
- } catch (err5) {
5855
- const message = err5 instanceof Error ? err5.message : String(err5);
6677
+ } catch (err6) {
6678
+ const message = err6 instanceof Error ? err6.message : String(err6);
5856
6679
  return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
5857
6680
  }
5858
6681
  }
@@ -5894,7 +6717,30 @@ async function handleProviderKeys(res, id, cfg, deps) {
5894
6717
  const row = cfg.providers.find((p) => p.id === id);
5895
6718
  if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
5896
6719
  const cooldown = await deps.apiKeyPool.getKeyHealth(id);
5897
- 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
+ }
5898
6744
  }
5899
6745
  function parsePoolKeyInput(body, existing) {
5900
6746
  const out = {};
@@ -6639,12 +7485,12 @@ async function handleAccounts(req, res, method, rest, deps) {
6639
7485
  }
6640
7486
  return writeJson4(res, 200, { ok: true, affected: result.affected });
6641
7487
  }
6642
- if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
6643
- 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);
6644
7490
  return writeJson4(res, result.status, result.body);
6645
7491
  }
6646
- if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
6647
- 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);
6648
7494
  return writeJson4(res, result.status, result.body);
6649
7495
  }
6650
7496
  if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
@@ -6697,7 +7543,15 @@ async function handleAccounts(req, res, method, rest, deps) {
6697
7543
  return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
6698
7544
  }
6699
7545
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
6700
- 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);
6701
7555
  return writeJson4(res, result.status, result.body);
6702
7556
  }
6703
7557
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
@@ -7191,12 +8045,12 @@ async function handlePlayground(req, res, method, deps) {
7191
8045
  const payload = body["body"];
7192
8046
  const status = deps.outboundApiServer.getStatus();
7193
8047
  if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
7194
- const path2 = resolvePlaygroundPath(endpoint, isRecord3(payload) ? payload : {});
8048
+ const path2 = resolvePlaygroundPath(endpoint, isRecord4(payload) ? payload : {});
7195
8049
  if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
7196
8050
  const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
7197
8051
  await proxyToOutbound(res, status.port, path2, key, upstreamBody);
7198
8052
  }
7199
- function isRecord3(v) {
8053
+ function isRecord4(v) {
7200
8054
  return !!v && typeof v === "object" && !Array.isArray(v);
7201
8055
  }
7202
8056
  function proxyToOutbound(res, outboundPort, path2, key, body) {
@@ -7225,8 +8079,8 @@ function proxyToOutbound(res, outboundPort, path2, key, body) {
7225
8079
  });
7226
8080
  }
7227
8081
  );
7228
- upstream.on("error", (err5) => {
7229
- 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}`);
7230
8084
  else res.end();
7231
8085
  resolve10();
7232
8086
  });
@@ -7332,7 +8186,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
7332
8186
  }
7333
8187
 
7334
8188
  // src/admin/version.ts
7335
- var DAEMON_VERSION = true ? "0.2.1" : "0.0.0-dev";
8189
+ var DAEMON_VERSION = true ? "0.3.1" : "0.0.0-dev";
7336
8190
 
7337
8191
  // src/admin/AdminServer.ts
7338
8192
  var LOOPBACK_ADDR = "127.0.0.1";
@@ -7375,13 +8229,13 @@ var AdminServer = class {
7375
8229
  const server = import_node_http2.default.createServer((req, res) => {
7376
8230
  this.onRequest(req, res);
7377
8231
  });
7378
- const onError = (err5) => {
7379
- if (err5.code === "EADDRINUSE" && port !== 0) {
8232
+ const onError = (err6) => {
8233
+ if (err6.code === "EADDRINUSE" && port !== 0) {
7380
8234
  server.removeListener("error", onError);
7381
8235
  this.listen(bindAddr, 0).then(resolve10, reject);
7382
8236
  return;
7383
8237
  }
7384
- reject(err5);
8238
+ reject(err6);
7385
8239
  };
7386
8240
  server.on("error", onError);
7387
8241
  server.listen(port, bindAddr, () => {
@@ -7399,8 +8253,8 @@ var AdminServer = class {
7399
8253
  }
7400
8254
  /** Per-request handler: auth gate (when a token is set) → routing. */
7401
8255
  onRequest(req, res) {
7402
- void this.dispatch(req, res).catch((err5) => {
7403
- 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);
7404
8258
  this.deps.logger.error("[AdminServer] unhandled error:", message);
7405
8259
  if (!res.headersSent) {
7406
8260
  res.writeHead(500, { "Content-Type": "application/json" });
@@ -7664,18 +8518,18 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
7664
8518
  return;
7665
8519
  }
7666
8520
  signal?.addEventListener("abort", abort, { once: true });
7667
- server.on("error", (err5) => {
8521
+ server.on("error", (err6) => {
7668
8522
  if (settled) return;
7669
8523
  settled = true;
7670
8524
  clearTimeout(timer);
7671
- if (err5.code === "EADDRINUSE") {
8525
+ if (err6.code === "EADDRINUSE") {
7672
8526
  reject(
7673
8527
  new Error(
7674
8528
  `login: cannot bind ${LOOPBACK_HOST}:${LOOPBACK_PORT} (address in use) \u2014 another codex login or process is holding the port`
7675
8529
  )
7676
8530
  );
7677
8531
  } else {
7678
- reject(err5);
8532
+ reject(err6);
7679
8533
  }
7680
8534
  });
7681
8535
  const timer = setTimeout(() => {
@@ -7750,6 +8604,411 @@ function createPoolKeysLoader(getProviderRow, autoDisabled) {
7750
8604
  };
7751
8605
  }
7752
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
+
7753
9012
  // src/commands/paths.ts
7754
9013
  var import_node_path9 = require("path");
7755
9014
  function defaultVouchersPath(configPath) {
@@ -7786,7 +9045,7 @@ function defaultBillingDir(configPath) {
7786
9045
  // src/image-generation/ImageDoctorService.ts
7787
9046
  var import_image_generation = require("@omnicross/core/image-generation");
7788
9047
  var import_outbound_api7 = require("@omnicross/core/outbound-api");
7789
- var import_subscriptions3 = require("@omnicross/subscriptions");
9048
+ var import_subscriptions6 = require("@omnicross/subscriptions");
7790
9049
 
7791
9050
  // src/image-generation/FileCodexImageCapabilityEvidenceSource.ts
7792
9051
  var import_node_crypto13 = require("crypto");
@@ -8224,7 +9483,7 @@ function createImageDoctorService(options) {
8224
9483
  paths,
8225
9484
  ttlMs: config.evidenceTtlMs
8226
9485
  }));
8227
- const createLiveVerifier = options.createLiveVerifier ?? ((strategy, config) => (0, import_subscriptions3.createCodexImageLiveVerifier)({
9486
+ const createLiveVerifier = options.createLiveVerifier ?? ((strategy, config) => (0, import_subscriptions6.createCodexImageLiveVerifier)({
8228
9487
  authStrategy: strategy,
8229
9488
  generationTimeoutMs: config.queue.generationTimeoutMs
8230
9489
  }));
@@ -8586,7 +9845,7 @@ var ImageCleanupService = class {
8586
9845
  var import_node_crypto16 = require("crypto");
8587
9846
  var import_image_generation5 = require("@omnicross/core/image-generation");
8588
9847
  var import_outbound_api8 = require("@omnicross/core/outbound-api");
8589
- var import_subscriptions4 = require("@omnicross/subscriptions");
9848
+ var import_subscriptions7 = require("@omnicross/subscriptions");
8590
9849
 
8591
9850
  // src/image-generation/ImageApiRuntimeResolver.ts
8592
9851
  var import_node_crypto14 = require("crypto");
@@ -9117,7 +10376,7 @@ function createImageRuntimeGeneration(options) {
9117
10376
  now: options.now ?? Date.now,
9118
10377
  referenceStore: options.storage.referenceStore,
9119
10378
  stateStore: options.storage.stateStore
9120
- }) : (0, import_subscriptions4.createCodexSubscriptionImageProvider)({
10379
+ }) : (0, import_subscriptions7.createCodexSubscriptionImageProvider)({
9121
10380
  authStrategy,
9122
10381
  evidenceSource: generationEvidenceSource,
9123
10382
  executionScheduler: scheduler,
@@ -13783,10 +15042,13 @@ function bucketLabel(bucketStartTs, bucket) {
13783
15042
  }
13784
15043
 
13785
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
13786
15049
  var import_node_crypto22 = require("crypto");
13787
15050
  var import_node_fs18 = require("fs");
13788
15051
  var import_node_path22 = require("path");
13789
- var import_core3 = require("@omnicross/core");
13790
15052
  function atomicReplaceUtf8(targetPath, contents) {
13791
15053
  const tempPath = (0, import_node_path22.join)(
13792
15054
  (0, import_node_path22.dirname)(targetPath),
@@ -13816,6 +15078,8 @@ function atomicReplaceUtf8(targetPath, contents) {
13816
15078
  throw error;
13817
15079
  }
13818
15080
  }
15081
+
15082
+ // src/ports/JsonOutboundKeyDb.ts
13819
15083
  var JsonOutboundKeyDb = class {
13820
15084
  /**
13821
15085
  * @param secretBox OPTIONAL reversible-secret codec. When present, a created
@@ -13958,9 +15222,9 @@ var JsonOutboundKeyDb = class {
13958
15222
  }
13959
15223
  /** Read the key rows, tolerating a missing/corrupt file (→ empty list). */
13960
15224
  readRows() {
13961
- if (!(0, import_node_fs18.existsSync)(this.keysPath)) return [];
15225
+ if (!(0, import_node_fs19.existsSync)(this.keysPath)) return [];
13962
15226
  try {
13963
- 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"));
13964
15228
  return Array.isArray(parsed) ? parsed : [];
13965
15229
  } catch {
13966
15230
  return [];
@@ -13977,7 +15241,7 @@ function applyPolicyField(row, field, value) {
13977
15241
  }
13978
15242
 
13979
15243
  // src/ports/JsonPricingStore.ts
13980
- var import_node_fs19 = require("fs");
15244
+ var import_node_fs20 = require("fs");
13981
15245
  var import_node_crypto23 = require("crypto");
13982
15246
  var JsonPricingStore = class {
13983
15247
  constructor(pricingPath) {
@@ -13992,9 +15256,9 @@ var JsonPricingStore = class {
13992
15256
  * otherwise unusable pricing table after a crash or manual file edit.
13993
15257
  */
13994
15258
  hasUsableSnapshot() {
13995
- if (!(0, import_node_fs19.existsSync)(this.pricingPath)) return false;
15259
+ if (!(0, import_node_fs20.existsSync)(this.pricingPath)) return false;
13996
15260
  try {
13997
- 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"));
13998
15262
  return Array.isArray(parsed) && parsed.some(isUsablePricingRow);
13999
15263
  } catch {
14000
15264
  return false;
@@ -14107,9 +15371,9 @@ var JsonPricingStore = class {
14107
15371
  }
14108
15372
  /** Read the pricing rows, tolerating a missing/corrupt file (→ empty list). */
14109
15373
  readRows() {
14110
- if (!(0, import_node_fs19.existsSync)(this.pricingPath)) return [];
15374
+ if (!(0, import_node_fs20.existsSync)(this.pricingPath)) return [];
14111
15375
  try {
14112
- 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"));
14113
15377
  return Array.isArray(parsed) ? parsed : [];
14114
15378
  } catch {
14115
15379
  return [];
@@ -14118,18 +15382,18 @@ var JsonPricingStore = class {
14118
15382
  writeRows(rows) {
14119
15383
  const temporaryPath = `${this.pricingPath}.${process.pid}.${(0, import_node_crypto23.randomUUID)()}.tmp`;
14120
15384
  try {
14121
- (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", {
14122
15386
  encoding: "utf8",
14123
15387
  flag: "wx"
14124
15388
  });
14125
15389
  this.replaceFile(temporaryPath);
14126
15390
  } finally {
14127
- (0, import_node_fs19.rmSync)(temporaryPath, { force: true });
15391
+ (0, import_node_fs20.rmSync)(temporaryPath, { force: true });
14128
15392
  }
14129
15393
  }
14130
15394
  /** Isolated for deterministic failure testing; never removes the target. */
14131
15395
  replaceFile(temporaryPath) {
14132
- (0, import_node_fs19.renameSync)(temporaryPath, this.pricingPath);
15396
+ (0, import_node_fs20.renameSync)(temporaryPath, this.pricingPath);
14133
15397
  }
14134
15398
  };
14135
15399
  function isUsablePricingRow(value) {
@@ -14139,7 +15403,7 @@ function isUsablePricingRow(value) {
14139
15403
  }
14140
15404
 
14141
15405
  // src/pricing/PricingRefreshScheduler.ts
14142
- var import_node_fs20 = require("fs");
15406
+ var import_node_fs21 = require("fs");
14143
15407
  var EMPTY_STATE2 = {
14144
15408
  lastAttemptAt: null,
14145
15409
  lastSuccessAt: null,
@@ -14177,9 +15441,9 @@ var PricingRefreshScheduler = class {
14177
15441
  this.timer = null;
14178
15442
  }
14179
15443
  getState() {
14180
- 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: [] };
14181
15445
  try {
14182
- 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"));
14183
15447
  return {
14184
15448
  lastAttemptAt: finiteOrNull(value.lastAttemptAt),
14185
15449
  lastSuccessAt: finiteOrNull(value.lastSuccessAt),
@@ -14232,9 +15496,9 @@ var PricingRefreshScheduler = class {
14232
15496
  }
14233
15497
  writeState(state) {
14234
15498
  const temporaryPath = `${this.statePath}.tmp`;
14235
- (0, import_node_fs20.writeFileSync)(temporaryPath, `${JSON.stringify(state, null, 2)}
15499
+ (0, import_node_fs21.writeFileSync)(temporaryPath, `${JSON.stringify(state, null, 2)}
14236
15500
  `, "utf8");
14237
- (0, import_node_fs20.renameSync)(temporaryPath, this.statePath);
15501
+ (0, import_node_fs21.renameSync)(temporaryPath, this.statePath);
14238
15502
  }
14239
15503
  };
14240
15504
  function finiteOrNull(value) {
@@ -14242,7 +15506,7 @@ function finiteOrNull(value) {
14242
15506
  }
14243
15507
 
14244
15508
  // src/ports/JsonVoucherDb.ts
14245
- var import_node_fs21 = require("fs");
15509
+ var import_node_fs22 = require("fs");
14246
15510
  var JsonVoucherDb = class {
14247
15511
  constructor(vouchersPath) {
14248
15512
  this.vouchersPath = vouchersPath;
@@ -14320,27 +15584,27 @@ var JsonVoucherDb = class {
14320
15584
  }
14321
15585
  /** Read the voucher rows, tolerating a missing/corrupt file (→ empty list). */
14322
15586
  readRows() {
14323
- if (!(0, import_node_fs21.existsSync)(this.vouchersPath)) return [];
15587
+ if (!(0, import_node_fs22.existsSync)(this.vouchersPath)) return [];
14324
15588
  try {
14325
- 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"));
14326
15590
  return Array.isArray(parsed) ? parsed : [];
14327
15591
  } catch {
14328
15592
  return [];
14329
15593
  }
14330
15594
  }
14331
15595
  writeRows(rows) {
14332
- (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");
14333
15597
  }
14334
15598
  };
14335
15599
 
14336
15600
  // src/ports/JsonSubscriptionCredentialStore.ts
14337
- var import_node_fs23 = require("fs");
15601
+ var import_node_fs24 = require("fs");
14338
15602
  var import_node_path24 = require("path");
14339
15603
  var import_SubscriptionAccountHealth2 = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
14340
15604
  var import_AccountAllowanceScheduling3 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
14341
- var import_upstreamFetch4 = require("@omnicross/core/pipeline/upstreamFetch");
15605
+ var import_upstreamFetch9 = require("@omnicross/core/pipeline/upstreamFetch");
14342
15606
  var import_SubscriptionIdentityStore2 = require("@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore");
14343
- var import_subscriptions5 = require("@omnicross/subscriptions");
15607
+ var import_subscriptions8 = require("@omnicross/subscriptions");
14344
15608
 
14345
15609
  // src/ports/account-sync.ts
14346
15610
  function viewOf(tokens) {
@@ -14384,7 +15648,7 @@ function findDuplicateCredentialIds(accounts) {
14384
15648
  }
14385
15649
 
14386
15650
  // src/ports/external-cli-credentials.ts
14387
- var import_node_fs22 = require("fs");
15651
+ var import_node_fs23 = require("fs");
14388
15652
  var import_node_os5 = require("os");
14389
15653
  var import_node_path23 = require("path");
14390
15654
  function externalStorePath(provider, home = (0, import_node_os5.homedir)()) {
@@ -14437,10 +15701,10 @@ function parseCodexTokensEnvelope(raw) {
14437
15701
  }
14438
15702
  function readExternalCliCredentials(provider, home = (0, import_node_os5.homedir)()) {
14439
15703
  const path2 = externalStorePath(provider, home);
14440
- if (!(0, import_node_fs22.existsSync)(path2)) return null;
15704
+ if (!(0, import_node_fs23.existsSync)(path2)) return null;
14441
15705
  let raw;
14442
15706
  try {
14443
- const parsed = JSON.parse((0, import_node_fs22.readFileSync)(path2, "utf8"));
15707
+ const parsed = JSON.parse((0, import_node_fs23.readFileSync)(path2, "utf8"));
14444
15708
  raw = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
14445
15709
  } catch {
14446
15710
  return null;
@@ -14463,16 +15727,18 @@ var JsonSubscriptionCredentialStore = class {
14463
15727
  * as on relay refresh egresses from the SAME proxy IP as the
14464
15728
  * account's traffic. NOT used by any read/write path.
14465
15729
  */
14466
- constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials) {
15730
+ constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials, atomicReplace = atomicReplaceUtf8) {
14467
15731
  this.tokensPath = tokensPath;
14468
15732
  this.box = box;
14469
15733
  this.fetchImpl = fetchImpl;
14470
15734
  this.externalCliReader = externalCliReader;
15735
+ this.atomicReplace = atomicReplace;
14471
15736
  }
14472
15737
  tokensPath;
14473
15738
  box;
14474
15739
  fetchImpl;
14475
15740
  externalCliReader;
15741
+ atomicReplace;
14476
15742
  /**
14477
15743
  * The proxy-aware `FetchLike` for one refresh round-trip (upstream-proxy M1). A
14478
15744
  * TEST-injected `fetchImpl` is returned verbatim; otherwise the refresh routes
@@ -14486,7 +15752,7 @@ var JsonSubscriptionCredentialStore = class {
14486
15752
  * a plaintext token pair into `upstream-trace.jsonl`.
14487
15753
  */
14488
15754
  buildRefreshFetch(providerId, accountId) {
14489
- return this.fetchImpl ?? ((url, init) => (0, import_upstreamFetch4.fetchUpstream)(url, init, { providerId, accountId, redactBodies: true }));
15755
+ return this.fetchImpl ?? ((url, init) => (0, import_upstreamFetch9.fetchUpstream)(url, init, { providerId, accountId, redactBodies: true }));
14490
15756
  }
14491
15757
  /**
14492
15758
  * In-flight refresh coalescing. OAuth refresh tokens are
@@ -14527,7 +15793,7 @@ var JsonSubscriptionCredentialStore = class {
14527
15793
  * other hot reads. Never returns token material.
14528
15794
  */
14529
15795
  getAccountProxy(providerId, accountId) {
14530
- if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego") {
15796
+ if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego" && providerId !== "kimi") {
14531
15797
  return void 0;
14532
15798
  }
14533
15799
  return getAccountProxy(this.readConfig(), providerId, accountId);
@@ -14546,7 +15812,7 @@ var JsonSubscriptionCredentialStore = class {
14546
15812
  const fingerprintOn = identityStore.isEnabled();
14547
15813
  const now = Date.now();
14548
15814
  const out = {};
14549
- for (const provider of ["claude", "codex", "gemini", "opencodego"]) {
15815
+ for (const provider of ["claude", "codex", "gemini", "opencodego", "kimi"]) {
14550
15816
  const sanitized = sanitizeAccounts(config, provider);
14551
15817
  if (sanitized.length === 0) continue;
14552
15818
  for (const account of sanitized) {
@@ -14612,7 +15878,7 @@ var JsonSubscriptionCredentialStore = class {
14612
15878
  this.materializeMigration(config);
14613
15879
  const refreshFetch = this.buildRefreshFetch("claude", capturedId);
14614
15880
  try {
14615
- const result = await import_subscriptions5.claudeOAuth.refreshAccessToken(claude.refreshToken, refreshFetch);
15881
+ const result = await import_subscriptions8.claudeOAuth.refreshAccessToken(claude.refreshToken, refreshFetch);
14616
15882
  const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
14617
15883
  const next = {
14618
15884
  ...claude,
@@ -14647,7 +15913,7 @@ var JsonSubscriptionCredentialStore = class {
14647
15913
  this.materializeMigration(config);
14648
15914
  const refreshFetch = this.buildRefreshFetch("codex", capturedId);
14649
15915
  try {
14650
- const result = await import_subscriptions5.codexOAuth.refreshAccessToken(codex.refreshToken, refreshFetch);
15916
+ const result = await import_subscriptions8.codexOAuth.refreshAccessToken(codex.refreshToken, refreshFetch);
14651
15917
  const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
14652
15918
  const next = {
14653
15919
  ...codex,
@@ -14685,7 +15951,7 @@ var JsonSubscriptionCredentialStore = class {
14685
15951
  this.materializeMigration(config);
14686
15952
  const refreshFetch = this.buildRefreshFetch("gemini", capturedId);
14687
15953
  try {
14688
- const result = await import_subscriptions5.geminiOAuth.refreshAccessToken(gemini.refreshToken, refreshFetch);
15954
+ const result = await import_subscriptions8.geminiOAuth.refreshAccessToken(gemini.refreshToken, refreshFetch);
14689
15955
  const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
14690
15956
  const next = {
14691
15957
  ...gemini,
@@ -14704,6 +15970,47 @@ var JsonSubscriptionCredentialStore = class {
14704
15970
  }
14705
15971
  });
14706
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
+ }
14707
16014
  /**
14708
16015
  * Refresh a SPECIFIC managed account by id (background scheduler sweep and
14709
16016
  * account-pool resolution). It uses only that account's stored refresh
@@ -14756,7 +16063,7 @@ var JsonSubscriptionCredentialStore = class {
14756
16063
  }
14757
16064
  const oauth = account.tokens;
14758
16065
  if (!oauth.accessToken) return null;
14759
- if (providerId === "codex" || providerId === "gemini") {
16066
+ if (providerId === "codex" || providerId === "gemini" || providerId === "kimi") {
14760
16067
  const expiresAtMs = oauth.expiresAt ? Date.parse(oauth.expiresAt) : 0;
14761
16068
  const expiringSoon = expiresAtMs > 0 && Date.now() >= expiresAtMs - ACCOUNT_REFRESH_LEAD_MS;
14762
16069
  if (expiringSoon && oauth.refreshToken) {
@@ -14845,8 +16152,23 @@ var JsonSubscriptionCredentialStore = class {
14845
16152
  }
14846
16153
  /** Dispatch one OAuth refresh round-trip to the provider's shared flow. */
14847
16154
  async refreshUpstream(provider, refreshToken, accountId) {
14848
- const flow = provider === "claude" ? import_subscriptions5.claudeOAuth : provider === "codex" ? import_subscriptions5.codexOAuth : import_subscriptions5.geminiOAuth;
14849
- 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);
14850
16172
  return {
14851
16173
  accessToken: r.accessToken,
14852
16174
  refreshToken: r.refreshToken,
@@ -15009,42 +16331,86 @@ var JsonSubscriptionCredentialStore = class {
15009
16331
  /** Write the merged config to disk as pretty JSON (mkdir parent if needed).
15010
16332
  * Encrypt-on-write: the token-material fields are encrypted (legacy plaintext
15011
16333
  * `enc:v1:`; already-`enc:`/`$ENV` untouched) before serializing, so any
15012
- * 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). */
15013
16339
  persist(config) {
15014
- (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 });
15015
16341
  const encrypted = encryptTokens(config, this.box);
15016
- (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");
15017
16343
  }
15018
16344
  /**
15019
- * Read + parse `tokens.json`, tolerating a missing/corrupt file, then DECRYPT
15020
- * the token-material fields so every getter returns plaintext (the
15021
- * 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.
15022
16352
  *
15023
- * The fs-read + JSON-parse tolerance is INSIDE the try (a missing or corrupt
15024
- * file empty `{ updatedAt: '' }`). The DECRYPT runs OUTSIDE the try, so a
15025
- * wrong/missing master key or a tampered `enc:` envelope FAILS FAST with the
15026
- * box's clear, secret-free error (secrets spec "/ UX":
15027
- * SHALL fail-fast, SHALL NOT a swallowed decrypt would report "no
15028
- * tokens" and silently send the WRONG bearer upstream 401). Mirrors
15029
- * `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.
15030
16359
  */
15031
16360
  readConfig() {
15032
- if (!(0, import_node_fs23.existsSync)(this.tokensPath)) return { updatedAt: "" };
16361
+ if (!(0, import_node_fs24.existsSync)(this.tokensPath)) return { updatedAt: "" };
15033
16362
  let parsed;
15034
16363
  try {
15035
- const raw = JSON.parse((0, import_node_fs23.readFileSync)(this.tokensPath, "utf8"));
15036
- 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;
15037
16369
  } catch {
15038
- parsed = null;
16370
+ return this.quarantineCorrupt("unparseable JSON");
15039
16371
  }
15040
- if (!parsed) return { updatedAt: "" };
15041
16372
  const decrypted = decryptTokens(parsed, this.box);
15042
16373
  return migrateLazily(decrypted);
15043
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
+ }
15044
16410
  };
15045
16411
 
15046
16412
  // src/AccountHealthProbeScheduler.ts
15047
- var import_upstreamFetch5 = require("@omnicross/core/pipeline/upstreamFetch");
16413
+ var import_upstreamFetch10 = require("@omnicross/core/pipeline/upstreamFetch");
15048
16414
 
15049
16415
  // src/probe/CodexGenerationProbe.ts
15050
16416
  var import_codexCliHeaders = require("@omnicross/core/provider-proxy/identity/codexCliHeaders");
@@ -15183,7 +16549,11 @@ var PROVIDER_PROBE_PLANS = {
15183
16549
  // billable/wrong endpoint). Upgrade to `{ kind:'upstream' }` once verified.
15184
16550
  codex: { kind: "local" },
15185
16551
  gemini: { kind: "local" },
15186
- 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" }
15187
16557
  };
15188
16558
  function probePlanFor(providerId) {
15189
16559
  return PROVIDER_PROBE_PLANS[providerId] ?? { kind: "local" };
@@ -15205,7 +16575,7 @@ var AccountHealthProbeScheduler = class {
15205
16575
  this.logger = logger;
15206
16576
  this.config = config;
15207
16577
  this.now = opts.now ?? Date.now;
15208
- this.fetchImpl = opts.fetchImpl ?? import_upstreamFetch5.fetchUpstream;
16578
+ this.fetchImpl = opts.fetchImpl ?? import_upstreamFetch10.fetchUpstream;
15209
16579
  this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
15210
16580
  this.planFor = opts.planFor ?? probePlanFor;
15211
16581
  }
@@ -15549,13 +16919,13 @@ var AccountHealthSweeper = class {
15549
16919
  };
15550
16920
 
15551
16921
  // src/audit/AuditPruneSweeper.ts
15552
- var import_node_fs26 = require("fs");
16922
+ var import_node_fs27 = require("fs");
15553
16923
  var import_node_path27 = require("path");
15554
16924
  var import_promises6 = require("stream/promises");
15555
16925
  var import_node_zlib = require("zlib");
15556
16926
 
15557
16927
  // src/audit/auditDictionary.ts
15558
- var import_node_fs24 = require("fs");
16928
+ var import_node_fs25 = require("fs");
15559
16929
  var import_node_path25 = require("path");
15560
16930
 
15561
16931
  // src/audit/auditBodyStore.ts
@@ -15788,7 +17158,7 @@ function parseEntries(raw) {
15788
17158
  }
15789
17159
  function plainShards(bodiesPath) {
15790
17160
  try {
15791
- return (0, import_node_fs24.readdirSync)(bodiesPath).filter(
17161
+ return (0, import_node_fs25.readdirSync)(bodiesPath).filter(
15792
17162
  (file) => file.endsWith(".jsonl") && isSafeSessionKey(file.slice(0, -".jsonl".length))
15793
17163
  );
15794
17164
  } catch {
@@ -15813,9 +17183,9 @@ function chooseDictionary(anchors) {
15813
17183
  var EMPTY = { shards: 0, anchors: 0, savedBytes: 0 };
15814
17184
  function compactAuditDay(dayPath) {
15815
17185
  const bodiesPath = (0, import_node_path25.join)(dayPath, AUDIT_BODIES_DIR);
15816
- if (!(0, import_node_fs24.existsSync)(bodiesPath)) return EMPTY;
17186
+ if (!(0, import_node_fs25.existsSync)(bodiesPath)) return EMPTY;
15817
17187
  const dictPath = (0, import_node_path25.join)(bodiesPath, AUDIT_DICT_FILE);
15818
- 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;
15819
17189
  const shardFiles = plainShards(bodiesPath);
15820
17190
  if (shardFiles.length < 2) return EMPTY;
15821
17191
  const loaded = /* @__PURE__ */ new Map();
@@ -15823,7 +17193,7 @@ function compactAuditDay(dayPath) {
15823
17193
  for (const file of shardFiles) {
15824
17194
  let entries;
15825
17195
  try {
15826
- 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"));
15827
17197
  } catch {
15828
17198
  continue;
15829
17199
  }
@@ -15840,7 +17210,7 @@ function compactAuditDay(dayPath) {
15840
17210
  ts: 0,
15841
17211
  req: { base: null, anchor: "dict", pre: 0, suf: 0, ins: dictionary }
15842
17212
  };
15843
- (0, import_node_fs24.writeFileSync)(dictPath, JSON.stringify(dictEntry) + "\n", "utf8");
17213
+ (0, import_node_fs25.writeFileSync)(dictPath, JSON.stringify(dictEntry) + "\n", "utf8");
15844
17214
  const result = { shards: 0, anchors: 0, savedBytes: 0 };
15845
17215
  for (const [file, entries] of loaded) {
15846
17216
  let changed = false;
@@ -15860,11 +17230,11 @@ function compactAuditDay(dayPath) {
15860
17230
  const target = (0, import_node_path25.join)(bodiesPath, file);
15861
17231
  const temp = `${target}.compacting`;
15862
17232
  try {
15863
- (0, import_node_fs24.writeFileSync)(temp, rewritten.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
15864
- (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);
15865
17235
  } catch {
15866
17236
  try {
15867
- 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);
15868
17238
  } catch {
15869
17239
  }
15870
17240
  continue;
@@ -15875,7 +17245,7 @@ function compactAuditDay(dayPath) {
15875
17245
  }
15876
17246
  if (result.shards === 0) {
15877
17247
  try {
15878
- (0, import_node_fs24.unlinkSync)(dictPath);
17248
+ (0, import_node_fs25.unlinkSync)(dictPath);
15879
17249
  } catch {
15880
17250
  }
15881
17251
  }
@@ -15883,11 +17253,11 @@ function compactAuditDay(dayPath) {
15883
17253
  }
15884
17254
  function compactAllClosedAuditDays(auditDir, now = Date.now) {
15885
17255
  const run = { days: 0, shards: 0, savedBytes: 0 };
15886
- if (!(0, import_node_fs24.existsSync)(auditDir)) return run;
17256
+ if (!(0, import_node_fs25.existsSync)(auditDir)) return run;
15887
17257
  const today = auditDayDirName(now());
15888
17258
  let names;
15889
17259
  try {
15890
- names = (0, import_node_fs24.readdirSync)(auditDir).filter(isAuditDayDir).sort();
17260
+ names = (0, import_node_fs25.readdirSync)(auditDir).filter(isAuditDayDir).sort();
15891
17261
  } catch {
15892
17262
  return run;
15893
17263
  }
@@ -15906,7 +17276,7 @@ function compactAllClosedAuditDays(auditDir, now = Date.now) {
15906
17276
  }
15907
17277
 
15908
17278
  // src/audit/auditStats.ts
15909
- var import_node_fs25 = require("fs");
17279
+ var import_node_fs26 = require("fs");
15910
17280
  var import_node_path26 = require("path");
15911
17281
  var SIDECAR_VERSION = 1;
15912
17282
  var META_PREFIX_BYTES = 64 * 1024;
@@ -15915,9 +17285,9 @@ function auditStatsFileName(auditFile) {
15915
17285
  return auditFile.replace(/\.jsonl$/, ".stats.json");
15916
17286
  }
15917
17287
  function readPersisted(path2) {
15918
- if (!(0, import_node_fs25.existsSync)(path2)) return null;
17288
+ if (!(0, import_node_fs26.existsSync)(path2)) return null;
15919
17289
  try {
15920
- const value = JSON.parse((0, import_node_fs25.readFileSync)(path2, "utf8"));
17290
+ const value = JSON.parse((0, import_node_fs26.readFileSync)(path2, "utf8"));
15921
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)) {
15922
17292
  return null;
15923
17293
  }
@@ -15947,7 +17317,7 @@ function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfte
15947
17317
  minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
15948
17318
  maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
15949
17319
  };
15950
- (0, import_node_fs25.writeFileSync)(statsPath, JSON.stringify(next), "utf8");
17320
+ (0, import_node_fs26.writeFileSync)(statsPath, JSON.stringify(next), "utf8");
15951
17321
  }
15952
17322
  function queryCovers(stats, from, to) {
15953
17323
  return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
@@ -16005,7 +17375,7 @@ async function scanAuditFile(auditPath, startByte, auditBytes, from, to) {
16005
17375
  prefixTruncated = false;
16006
17376
  };
16007
17377
  if (auditBytes > startByte) {
16008
- const stream = (0, import_node_fs25.createReadStream)(auditPath, {
17378
+ const stream = (0, import_node_fs26.createReadStream)(auditPath, {
16009
17379
  start: startByte,
16010
17380
  end: auditBytes - 1,
16011
17381
  highWaterMark: READ_CHUNK_BYTES2
@@ -16058,12 +17428,12 @@ function mergePersistedStats(previous, appended) {
16058
17428
  };
16059
17429
  }
16060
17430
  async function readAuditStats(auditDir, query2 = {}) {
16061
- 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 };
16062
17432
  const from = typeof query2.from === "number" ? query2.from : -Infinity;
16063
17433
  const to = typeof query2.to === "number" ? query2.to : Infinity;
16064
17434
  let sources;
16065
17435
  try {
16066
- 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(
16067
17437
  (name) => AUDIT_DAY_DIR_RE.test(name) ? {
16068
17438
  auditPath: (0, import_node_path26.join)(auditDir, name, AUDIT_META_FILE),
16069
17439
  statsPath: (0, import_node_path26.join)(auditDir, name, auditStatsFileName(AUDIT_META_FILE))
@@ -16071,14 +17441,14 @@ async function readAuditStats(auditDir, query2 = {}) {
16071
17441
  auditPath: (0, import_node_path26.join)(auditDir, name),
16072
17442
  statsPath: (0, import_node_path26.join)(auditDir, auditStatsFileName(name))
16073
17443
  }
16074
- ).filter((source) => (0, import_node_fs25.existsSync)(source.auditPath));
17444
+ ).filter((source) => (0, import_node_fs26.existsSync)(source.auditPath));
16075
17445
  } catch {
16076
17446
  return { requestCount: 0, errorCount: 0, complete: false };
16077
17447
  }
16078
17448
  const total = { requestCount: 0, errorCount: 0, complete: true };
16079
17449
  for (const { auditPath, statsPath } of sources) {
16080
17450
  try {
16081
- const auditBytes = (0, import_node_fs25.statSync)(auditPath).size;
17451
+ const auditBytes = (0, import_node_fs26.statSync)(auditPath).size;
16082
17452
  const persisted = readPersisted(statsPath);
16083
17453
  if (persisted && persisted.complete && persisted.auditBytes === auditBytes && queryCovers(persisted, from, to)) {
16084
17454
  total.requestCount += persisted.requestCount;
@@ -16097,7 +17467,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16097
17467
  total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
16098
17468
  total.complete = total.complete && scanned.filtered.complete;
16099
17469
  const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
16100
- 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");
16101
17471
  } catch {
16102
17472
  total.complete = false;
16103
17473
  }
@@ -16106,7 +17476,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16106
17476
  }
16107
17477
 
16108
17478
  // src/audit/AuditPruneSweeper.ts
16109
- var DAY_MS = 24 * 60 * 6e4;
17479
+ var DAY_MS3 = 24 * 60 * 6e4;
16110
17480
  var SWEEP_INTERVAL_MS2 = 60 * 6e4;
16111
17481
  var ARCHIVE_BATCH = 64;
16112
17482
  var AuditPruneSweeper = class {
@@ -16169,19 +17539,19 @@ var AuditPruneSweeper = class {
16169
17539
  if (!this.config.enabled || this.sweeping) return 0;
16170
17540
  this.sweeping = true;
16171
17541
  try {
16172
- if (!(0, import_node_fs26.existsSync)(this.auditDir)) return 0;
16173
- 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;
16174
17544
  let removed = 0;
16175
- for (const name of (0, import_node_fs26.readdirSync)(this.auditDir)) {
17545
+ for (const name of (0, import_node_fs27.readdirSync)(this.auditDir)) {
16176
17546
  const dateMs = auditFileDateMs(name);
16177
17547
  if (dateMs === null || dateMs >= cutoff) continue;
16178
17548
  try {
16179
17549
  if (isAuditDayDir(name)) {
16180
- (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 });
16181
17551
  } else {
16182
- (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));
16183
17553
  const statsPath = (0, import_node_path27.join)(this.auditDir, auditStatsFileName(name));
16184
- 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);
16185
17555
  }
16186
17556
  removed += 1;
16187
17557
  } catch (error) {
@@ -16211,10 +17581,10 @@ var AuditPruneSweeper = class {
16211
17581
  if (!this.config.enabled || this.archiving) return 0;
16212
17582
  this.archiving = true;
16213
17583
  try {
16214
- if (!(0, import_node_fs26.existsSync)(this.auditDir)) return 0;
17584
+ if (!(0, import_node_fs27.existsSync)(this.auditDir)) return 0;
16215
17585
  const today = this.todayMidnight();
16216
17586
  let compressed = 0;
16217
- for (const name of (0, import_node_fs26.readdirSync)(this.auditDir)) {
17587
+ for (const name of (0, import_node_fs27.readdirSync)(this.auditDir)) {
16218
17588
  if (compressed >= ARCHIVE_BATCH) break;
16219
17589
  const dateMs = auditFileDateMs(name);
16220
17590
  if (dateMs === null || dateMs >= today || !isAuditDayDir(name)) continue;
@@ -16255,7 +17625,7 @@ var AuditPruneSweeper = class {
16255
17625
  async archiveDay(bodiesPath, budget) {
16256
17626
  let shards;
16257
17627
  try {
16258
- 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"));
16259
17629
  } catch {
16260
17630
  return 0;
16261
17631
  }
@@ -16265,16 +17635,16 @@ var AuditPruneSweeper = class {
16265
17635
  const source = (0, import_node_path27.join)(bodiesPath, shard);
16266
17636
  const target = `${source}.gz`;
16267
17637
  try {
16268
- if ((0, import_node_fs26.existsSync)(target)) {
16269
- (0, import_node_fs26.unlinkSync)(source);
17638
+ if ((0, import_node_fs27.existsSync)(target)) {
17639
+ (0, import_node_fs27.unlinkSync)(source);
16270
17640
  continue;
16271
17641
  }
16272
- await (0, import_promises6.pipeline)((0, import_node_fs26.createReadStream)(source), (0, import_node_zlib.createGzip)(), (0, import_node_fs26.createWriteStream)(target));
16273
- (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);
16274
17644
  compressed += 1;
16275
17645
  } catch (error) {
16276
17646
  try {
16277
- 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);
16278
17648
  } catch {
16279
17649
  }
16280
17650
  this.logger.warn("[AuditPruneSweeper] failed to archive audit body shard", {
@@ -16288,7 +17658,7 @@ var AuditPruneSweeper = class {
16288
17658
  };
16289
17659
 
16290
17660
  // src/usage/usageMigrate.ts
16291
- var import_node_fs27 = require("fs");
17661
+ var import_node_fs28 = require("fs");
16292
17662
  var import_promises7 = require("fs/promises");
16293
17663
  var import_node_path28 = require("path");
16294
17664
  var import_node_readline = require("readline");
@@ -16335,7 +17705,7 @@ async function migrateLegacyUsageEvents(opts) {
16335
17705
  let skipped = 0;
16336
17706
  try {
16337
17707
  const reader = (0, import_node_readline.createInterface)({
16338
- input: (0, import_node_fs27.createReadStream)(eventsPath, { encoding: "utf8" }),
17708
+ input: (0, import_node_fs28.createReadStream)(eventsPath, { encoding: "utf8" }),
16339
17709
  crlfDelay: Number.POSITIVE_INFINITY
16340
17710
  });
16341
17711
  for await (const line of reader) {
@@ -16427,7 +17797,7 @@ async function closeAll(writers) {
16427
17797
  // src/usage/UsagePruneSweeper.ts
16428
17798
  var import_promises8 = require("fs/promises");
16429
17799
  var import_node_path29 = require("path");
16430
- var DAY_MS2 = 24 * 60 * 6e4;
17800
+ var DAY_MS4 = 24 * 60 * 6e4;
16431
17801
  var SWEEP_INTERVAL_MS3 = 60 * 6e4;
16432
17802
  var DEFAULT_USAGE_RETENTION_DAYS = 90;
16433
17803
  var UsagePruneSweeper = class {
@@ -16484,7 +17854,7 @@ var UsagePruneSweeper = class {
16484
17854
  this.sweeping = true;
16485
17855
  try {
16486
17856
  const retentionDays = this.config.retentionDays ?? DEFAULT_USAGE_RETENTION_DAYS;
16487
- const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS2;
17857
+ const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS4;
16488
17858
  let removed = 0;
16489
17859
  for (const entry of await listUsageDays(this.usageDir)) {
16490
17860
  if (!entry.hasShard) continue;
@@ -16542,12 +17912,12 @@ var UsagePruneSweeper = class {
16542
17912
  };
16543
17913
 
16544
17914
  // src/audit/auditBodyReader.ts
16545
- var import_node_fs29 = require("fs");
17915
+ var import_node_fs30 = require("fs");
16546
17916
  var import_node_path30 = require("path");
16547
17917
  var import_node_zlib2 = require("zlib");
16548
17918
 
16549
17919
  // src/audit/auditJsonl.ts
16550
- var import_node_fs28 = require("fs");
17920
+ var import_node_fs29 = require("fs");
16551
17921
  var WINDOW_BYTES = 1 << 20;
16552
17922
  var MAX_LINE_BYTES = 32 * 1024 * 1024;
16553
17923
  var NEWLINE2 = 10;
@@ -16555,9 +17925,9 @@ function forEachLineFromTail(path2, onLine) {
16555
17925
  let fd;
16556
17926
  let end;
16557
17927
  try {
16558
- end = (0, import_node_fs28.statSync)(path2).size;
17928
+ end = (0, import_node_fs29.statSync)(path2).size;
16559
17929
  if (end === 0) return;
16560
- fd = (0, import_node_fs28.openSync)(path2, "r");
17930
+ fd = (0, import_node_fs29.openSync)(path2, "r");
16561
17931
  } catch {
16562
17932
  return;
16563
17933
  }
@@ -16568,7 +17938,7 @@ function forEachLineFromTail(path2, onLine) {
16568
17938
  const window = Buffer.allocUnsafe(end - start);
16569
17939
  let read;
16570
17940
  try {
16571
- 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);
16572
17942
  } catch {
16573
17943
  return;
16574
17944
  }
@@ -16596,7 +17966,7 @@ function forEachLineFromTail(path2, onLine) {
16596
17966
  }
16597
17967
  } finally {
16598
17968
  try {
16599
- (0, import_node_fs28.closeSync)(fd);
17969
+ (0, import_node_fs29.closeSync)(fd);
16600
17970
  } catch {
16601
17971
  }
16602
17972
  }
@@ -16606,10 +17976,10 @@ function forEachLineFromTail(path2, onLine) {
16606
17976
  function candidateDays(auditDir, ts) {
16607
17977
  if (typeof ts === "number" && Number.isFinite(ts)) {
16608
17978
  const named = auditDayDirName(ts);
16609
- 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];
16610
17980
  }
16611
17981
  try {
16612
- return (0, import_node_fs29.readdirSync)(auditDir).filter(isAuditDayDir).sort().reverse();
17982
+ return (0, import_node_fs30.readdirSync)(auditDir).filter(isAuditDayDir).sort().reverse();
16613
17983
  } catch {
16614
17984
  return [];
16615
17985
  }
@@ -16617,9 +17987,9 @@ function candidateDays(auditDir, ts) {
16617
17987
  function readShard(auditDir, day, sessionKey) {
16618
17988
  const base = (0, import_node_path30.join)(auditDir, day, AUDIT_BODIES_DIR, auditBodyFileName(sessionKey));
16619
17989
  try {
16620
- 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");
16621
17991
  const gz = `${base}.gz`;
16622
- 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");
16623
17993
  } catch {
16624
17994
  return null;
16625
17995
  }
@@ -16652,8 +18022,8 @@ function withDictionary(auditDir, day, entries) {
16652
18022
  const base = (0, import_node_path30.join)(auditDir, day, AUDIT_BODIES_DIR, AUDIT_DICT_FILE);
16653
18023
  let raw = null;
16654
18024
  try {
16655
- if ((0, import_node_fs29.existsSync)(base)) raw = (0, import_node_fs29.readFileSync)(base, "utf8");
16656
- 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");
16657
18027
  } catch {
16658
18028
  return entries;
16659
18029
  }
@@ -16686,7 +18056,7 @@ function reconstructRequest(entries, entry) {
16686
18056
  }
16687
18057
  function readAuditBody(auditDir, query2) {
16688
18058
  if (!isSafeSessionKey(query2.sessionKey) || !query2.id) return {};
16689
- if (!(0, import_node_fs29.existsSync)(auditDir)) return {};
18059
+ if (!(0, import_node_fs30.existsSync)(auditDir)) return {};
16690
18060
  for (const day of candidateDays(auditDir, query2.ts)) {
16691
18061
  const raw = readShard(auditDir, day, query2.sessionKey);
16692
18062
  if (raw === null) continue;
@@ -16704,7 +18074,7 @@ function readAuditBody(auditDir, query2) {
16704
18074
  function readLegacyInlineBody(auditDir, id) {
16705
18075
  let names;
16706
18076
  try {
16707
- 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();
16708
18078
  } catch {
16709
18079
  return {};
16710
18080
  }
@@ -16733,7 +18103,7 @@ function readLegacyInlineBody(auditDir, id) {
16733
18103
  }
16734
18104
 
16735
18105
  // src/audit/auditReader.ts
16736
- var import_node_fs30 = require("fs");
18106
+ var import_node_fs31 = require("fs");
16737
18107
  var import_node_path31 = require("path");
16738
18108
  var DEFAULT_LIMIT = 200;
16739
18109
  var MAX_LIMIT = 2e3;
@@ -16741,7 +18111,7 @@ var OVERSCAN = 256;
16741
18111
  function daySources(auditDir) {
16742
18112
  let names;
16743
18113
  try {
16744
- names = (0, import_node_fs30.readdirSync)(auditDir);
18114
+ names = (0, import_node_fs31.readdirSync)(auditDir);
16745
18115
  } catch {
16746
18116
  return [];
16747
18117
  }
@@ -16751,7 +18121,7 @@ function daySources(auditDir) {
16751
18121
  if (dateMs === null) continue;
16752
18122
  if (AUDIT_DAY_DIR_RE.test(name)) {
16753
18123
  const path2 = (0, import_node_path31.join)(auditDir, name, AUDIT_META_FILE);
16754
- 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 });
16755
18125
  } else if (AUDIT_FILE_RE.test(name)) {
16756
18126
  sources.push({ path: (0, import_node_path31.join)(auditDir, name), dateMs });
16757
18127
  }
@@ -16769,7 +18139,7 @@ function toMetaRecord(record) {
16769
18139
  return { ...meta, hasBody: true };
16770
18140
  }
16771
18141
  function readAuditRecords(auditDir, query2 = {}) {
16772
- if (!(0, import_node_fs30.existsSync)(auditDir)) return [];
18142
+ if (!(0, import_node_fs31.existsSync)(auditDir)) return [];
16773
18143
  const from = typeof query2.from === "number" ? query2.from : -Infinity;
16774
18144
  const to = typeof query2.to === "number" ? query2.to : Infinity;
16775
18145
  const limit = Math.min(MAX_LIMIT, Math.max(1, Math.trunc(query2.limit ?? DEFAULT_LIMIT)));
@@ -16797,7 +18167,7 @@ function readAuditRecords(auditDir, query2 = {}) {
16797
18167
  }
16798
18168
 
16799
18169
  // src/audit/AuditWriter.ts
16800
- var import_node_fs31 = require("fs");
18170
+ var import_node_fs32 = require("fs");
16801
18171
  var import_node_path32 = require("path");
16802
18172
  var AuditWriter = class {
16803
18173
  constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
@@ -16845,7 +18215,7 @@ var AuditWriter = class {
16845
18215
  /** Create a directory once per process and remember it. */
16846
18216
  ensureDir(path2) {
16847
18217
  if (!this.ensuredDirs.has(path2)) {
16848
- (0, import_node_fs31.mkdirSync)(path2, { recursive: true });
18218
+ (0, import_node_fs32.mkdirSync)(path2, { recursive: true });
16849
18219
  this.ensuredDirs.add(path2);
16850
18220
  }
16851
18221
  return path2;
@@ -16855,8 +18225,8 @@ var AuditWriter = class {
16855
18225
  const { requestBody: _req, responseBody: _res, ...meta } = record;
16856
18226
  const file = (0, import_node_path32.join)(dayPath, AUDIT_META_FILE);
16857
18227
  const line = JSON.stringify(meta) + "\n";
16858
- const bytesBefore = (0, import_node_fs31.existsSync)(file) ? (0, import_node_fs31.statSync)(file).size : 0;
16859
- (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");
16860
18230
  try {
16861
18231
  updateAuditStatsAfterAppend(
16862
18232
  file,
@@ -16888,7 +18258,7 @@ var AuditWriter = class {
16888
18258
  const line = encodeBodyEntry(record, sessionKey, dayDir, this.bases);
16889
18259
  if (line === null) return;
16890
18260
  const bodiesPath = this.ensureDir((0, import_node_path32.join)(dayPath, AUDIT_BODIES_DIR));
16891
- (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");
16892
18262
  } catch (error) {
16893
18263
  this.bases.forget(sessionKey);
16894
18264
  this.logger.warn("[AuditWriter] failed to append audit body shard", {
@@ -16900,10 +18270,10 @@ var AuditWriter = class {
16900
18270
  };
16901
18271
 
16902
18272
  // src/billing/BillingPublisher.ts
16903
- var import_node_fs32 = require("fs");
18273
+ var import_node_fs33 = require("fs");
16904
18274
  var import_node_crypto24 = require("crypto");
16905
18275
  var import_node_path33 = require("path");
16906
- var import_upstreamFetch6 = require("@omnicross/core/pipeline/upstreamFetch");
18276
+ var import_upstreamFetch11 = require("@omnicross/core/pipeline/upstreamFetch");
16907
18277
 
16908
18278
  // src/billing/billingFiles.ts
16909
18279
  var BILLING_FILE_RE = /^billing-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
@@ -16926,7 +18296,7 @@ var BillingPublisher = class {
16926
18296
  constructor(billingDir, logger, opts = {}) {
16927
18297
  this.billingDir = billingDir;
16928
18298
  this.logger = logger;
16929
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch6.fetchUpstream)(url, init));
18299
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch11.fetchUpstream)(url, init));
16930
18300
  this.defer = opts.defer ?? ((fn) => setTimeout(fn, 0));
16931
18301
  this.timeoutMs = opts.timeoutMs ?? BILLING_POST_TIMEOUT_MS;
16932
18302
  this.now = opts.now ?? Date.now;
@@ -16974,7 +18344,7 @@ var BillingPublisher = class {
16974
18344
  appendNow(event) {
16975
18345
  this.ensureDir();
16976
18346
  const file = (0, import_node_path33.join)(this.billingDir, billingFileName(event.ts));
16977
- (0, import_node_fs32.appendFileSync)(file, JSON.stringify(event) + "\n", "utf8");
18347
+ (0, import_node_fs33.appendFileSync)(file, JSON.stringify(event) + "\n", "utf8");
16978
18348
  }
16979
18349
  /**
16980
18350
  * One best-effort delivery attempt for an event ALREADY in the ledger. POSTs the
@@ -17024,7 +18394,7 @@ var BillingPublisher = class {
17024
18394
  try {
17025
18395
  this.ensureDir();
17026
18396
  const file = (0, import_node_path33.join)(this.billingDir, deliveredFileName(event.ts));
17027
- (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");
17028
18398
  } catch (error) {
17029
18399
  this.logger.warn("[BillingPublisher] failed to append delivery marker", {
17030
18400
  error: error instanceof Error ? error.message : String(error)
@@ -17033,20 +18403,20 @@ var BillingPublisher = class {
17033
18403
  }
17034
18404
  ensureDir() {
17035
18405
  if (this.dirEnsured) return;
17036
- (0, import_node_fs32.mkdirSync)(this.billingDir, { recursive: true });
18406
+ (0, import_node_fs33.mkdirSync)(this.billingDir, { recursive: true });
17037
18407
  this.dirEnsured = true;
17038
18408
  }
17039
18409
  };
17040
18410
 
17041
18411
  // src/billing/billingReader.ts
17042
- var import_node_fs33 = require("fs");
18412
+ var import_node_fs34 = require("fs");
17043
18413
  var import_node_path34 = require("path");
17044
18414
  function readBillingLedger(billingDir) {
17045
18415
  const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
17046
- if (!(0, import_node_fs33.existsSync)(billingDir)) return view;
18416
+ if (!(0, import_node_fs34.existsSync)(billingDir)) return view;
17047
18417
  let files;
17048
18418
  try {
17049
- files = (0, import_node_fs33.readdirSync)(billingDir);
18419
+ files = (0, import_node_fs34.readdirSync)(billingDir);
17050
18420
  } catch {
17051
18421
  return view;
17052
18422
  }
@@ -17077,7 +18447,7 @@ function readBillingStatus(billingDir) {
17077
18447
  function parseLines(dir, file) {
17078
18448
  let raw;
17079
18449
  try {
17080
- 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");
17081
18451
  } catch {
17082
18452
  return [];
17083
18453
  }
@@ -17176,7 +18546,7 @@ var BillingRetrySweeper = class {
17176
18546
  // src/TokenRefreshScheduler.ts
17177
18547
  var REFRESH_LEAD_MS2 = 5 * 6e4;
17178
18548
  var SWEEP_INTERVAL_MS5 = 6e4;
17179
- var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini"];
18549
+ var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini", "kimi"];
17180
18550
  var TokenRefreshScheduler = class {
17181
18551
  constructor(store, logger, intervalMs = SWEEP_INTERVAL_MS5, leadMs = REFRESH_LEAD_MS2) {
17182
18552
  this.store = store;
@@ -17259,6 +18629,8 @@ var TokenRefreshScheduler = class {
17259
18629
  return this.store.refreshCodexToken();
17260
18630
  case "gemini":
17261
18631
  return this.store.refreshGeminiToken();
18632
+ case "kimi":
18633
+ return this.store.refreshKimiToken();
17262
18634
  }
17263
18635
  }
17264
18636
  };
@@ -17335,7 +18707,7 @@ function createRouteLeaseSubscriptionPreflight(credentials) {
17335
18707
 
17336
18708
  // src/webhook/WebhookDispatcher.ts
17337
18709
  var import_node_crypto25 = require("crypto");
17338
- var import_upstreamFetch7 = require("@omnicross/core/pipeline/upstreamFetch");
18710
+ var import_upstreamFetch12 = require("@omnicross/core/pipeline/upstreamFetch");
17339
18711
  var WEBHOOK_MAX_ATTEMPTS = 3;
17340
18712
  var WEBHOOK_QUEUE_MAX = 1e3;
17341
18713
  var WEBHOOK_SEND_TIMEOUT_MS = 1e4;
@@ -17355,7 +18727,7 @@ var WebhookDispatcher = class {
17355
18727
  sleep;
17356
18728
  now;
17357
18729
  constructor(opts = {}) {
17358
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch7.fetchUpstream)(url, init));
18730
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch12.fetchUpstream)(url, init));
17359
18731
  this.logger = opts.logger;
17360
18732
  this.maxAttempts = opts.maxAttempts ?? WEBHOOK_MAX_ATTEMPTS;
17361
18733
  this.queueMax = opts.queueMax ?? WEBHOOK_QUEUE_MAX;
@@ -17441,8 +18813,8 @@ var WebhookDispatcher = class {
17441
18813
  signal: AbortSignal.timeout(this.timeoutMs)
17442
18814
  });
17443
18815
  return res.ok ? { ok: true, status: res.status } : { ok: false, status: res.status };
17444
- } catch (err5) {
17445
- 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) };
17446
18818
  }
17447
18819
  }
17448
18820
  /**
@@ -17566,7 +18938,7 @@ function resetImageRuntimeBootstrapSession() {
17566
18938
  function resolveLoggingConfig(configured, configPath) {
17567
18939
  const file = configured?.file ?? defaultDaemonLogPath(configPath);
17568
18940
  try {
17569
- (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), {
17570
18942
  recursive: true
17571
18943
  });
17572
18944
  } catch {
@@ -17584,12 +18956,12 @@ function buildDaemon(config, paths) {
17584
18956
  setSecretBox(secretBox3);
17585
18957
  setSecretBox2(secretBox3);
17586
18958
  const decryptedConfig = decryptConfigSecrets(config, secretBox3);
17587
- const accountAllowanceStore = new import_AccountAllowanceStore4.AccountAllowanceStore(
18959
+ const accountAllowanceStore = new import_AccountAllowanceStore7.AccountAllowanceStore(
17588
18960
  Date.now,
17589
18961
  void 0,
17590
18962
  new JsonAccountAllowancePersistence(defaultAccountAllowancePath(paths.configPath))
17591
18963
  );
17592
- (0, import_AccountAllowanceStore4.setSharedAccountAllowanceStore)(accountAllowanceStore);
18964
+ (0, import_AccountAllowanceStore7.setSharedAccountAllowanceStore)(accountAllowanceStore);
17593
18965
  (0, import_AccountAllowanceScheduling5.getSharedAccountAllowanceScheduling)().configure(
17594
18966
  (0, import_outbound_api10.normalizeServerConfig)(decryptedConfig.server).allowanceScheduling
17595
18967
  );
@@ -17614,21 +18986,22 @@ function buildDaemon(config, paths) {
17614
18986
  claudeAllowanceRefreshScheduler.configure(
17615
18987
  (0, import_outbound_api10.normalizeServerConfig)(decryptedConfig.server).allowanceScheduling
17616
18988
  );
17617
- const subscriptionAccounts = new import_subscriptions6.SubscriptionAccountService(credentialStore);
17618
- (0, import_subscriptions6.setSubscriptionAccountService)(subscriptionAccounts);
17619
- 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(
17620
18992
  subscriptionAccounts,
17621
18993
  credentialStore
17622
18994
  );
17623
- (0, import_subscriptions6.setSubscriptionProviderRegistry)(subscriptionRegistry);
18995
+ (0, import_subscriptions9.setSubscriptionProviderRegistry)(subscriptionRegistry);
17624
18996
  setServerProxyConfig(decryptedConfig.server?.proxy);
17625
- (0, import_upstreamFetch8.setUpstreamProxyResolver)(
18997
+ (0, import_upstreamFetch13.setUpstreamProxyResolver)(
17626
18998
  createUpstreamProxyResolver({
17627
18999
  getAccountProxy: (providerId, accountId) => credentialStore.getAccountProxy(providerId, accountId)
17628
19000
  })
17629
19001
  );
17630
19002
  (0, import_gemini_code_assist_resolver.setGeminiCodeAssistResolver)((0, import_GeminiCodeAssistProjectResolver.getGeminiCodeAssistProjectResolver)());
17631
19003
  const autoDisableStore = new AutoDisableStore();
19004
+ const providerKeyQuotaService = new ProviderKeyQuotaService(secretBox3);
17632
19005
  const apiKeyPool = new import_ApiKeyPoolService.ApiKeyPoolService(
17633
19006
  createPoolKeysLoader((id) => llmConfig.getProviderRow(id), autoDisableStore),
17634
19007
  resolveEnvKey,
@@ -17645,7 +19018,7 @@ function buildDaemon(config, paths) {
17645
19018
  const pricingEngine = new import_usage2.PricingEngine(pricingStore, logger, {
17646
19019
  // Catalog egress follows the same global/env proxy policy as every other
17647
19020
  // daemon upstream call; no provider/account override applies here.
17648
- fetchImpl: ((input, init) => (0, import_upstreamFetch8.fetchUpstream)(String(input), init ?? {}))
19021
+ fetchImpl: ((input, init) => (0, import_upstreamFetch13.fetchUpstream)(String(input), init ?? {}))
17649
19022
  });
17650
19023
  const pricingRefreshScheduler = new PricingRefreshScheduler(
17651
19024
  pricingEngine,
@@ -17909,6 +19282,11 @@ function buildDaemon(config, paths) {
17909
19282
  // values themselves NEVER leave (masked via `maskProviderApiKey`).
17910
19283
  apiKeyPool,
17911
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,
17912
19290
  // Interactive OAuth login over admin HTTP (app-parity child 4, design
17913
19291
  // D1/D2-a). The in-memory pending-session store (NEVER serialized), the
17914
19292
  // injected token-exchange fetch (global `fetch` here; mocked in tests), and a
@@ -17925,7 +19303,7 @@ function buildDaemon(config, paths) {
17925
19303
  // — `server.proxy.byProvider[...]` was silently skipped — and the call was
17926
19304
  // excluded from the upstream trace, so a failing login left no evidence.
17927
19305
  // `redactBodies` keeps the code/verifier + minted token out of that trace.
17928
- oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => (0, import_upstreamFetch8.fetchUpstream)(url, init, { providerId, redactBodies: true }),
19306
+ oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => (0, import_upstreamFetch13.fetchUpstream)(url, init, { providerId, redactBodies: true }),
17929
19307
  subscriptionAccountAppender: credentialStore,
17930
19308
  // Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
17931
19309
  // + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
@@ -17933,6 +19311,10 @@ function buildDaemon(config, paths) {
17933
19311
  // can inject a mock so no real port is bound.
17934
19312
  codexSessions: new CodexOAuthSessionStore(),
17935
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(),
17936
19318
  // Migration pack (app-parity child 6, design D2/D3) — the concrete credential
17937
19319
  // store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
17938
19320
  // the multi-account append (`appendProviderAccount`, import re-encrypts at-
@@ -17991,7 +19373,7 @@ function buildDaemon(config, paths) {
17991
19373
  });
17992
19374
  const webhookDispatcher = new WebhookDispatcher({
17993
19375
  logger,
17994
- fetchImpl: (url, init) => (0, import_upstreamFetch8.fetchUpstream)(url, init)
19376
+ fetchImpl: (url, init) => (0, import_upstreamFetch13.fetchUpstream)(url, init)
17995
19377
  });
17996
19378
  setWebhookRuntime(webhookDispatcher, (0, import_SubscriptionAccountHealth4.getSharedAccountHealth)());
17997
19379
  const auditWriter = new AuditWriter(auditDir, logger);
@@ -18071,9 +19453,9 @@ function resetDaemonSingletonsForTests() {
18071
19453
  (0, import_provider_proxy4.__resetProviderProxyForTests)();
18072
19454
  (0, import_outbound_api10.__resetOutboundApiServerForTests)();
18073
19455
  (0, import_subscriptionRegistryPort.setSubscriptionRegistryForOutbound)(null);
18074
- (0, import_subscriptions6.setSubscriptionProviderRegistry)(null);
18075
- (0, import_subscriptions6.setSubscriptionAccountService)(null);
18076
- (0, import_upstreamFetch8.setUpstreamProxyResolver)(null);
19456
+ (0, import_subscriptions9.setSubscriptionProviderRegistry)(null);
19457
+ (0, import_subscriptions9.setSubscriptionAccountService)(null);
19458
+ (0, import_upstreamFetch13.setUpstreamProxyResolver)(null);
18077
19459
  setServerProxyConfig(void 0);
18078
19460
  (0, import_gemini_code_assist_resolver.setGeminiCodeAssistResolver)(null);
18079
19461
  setSecretBox(null);
@@ -18082,14 +19464,14 @@ function resetDaemonSingletonsForTests() {
18082
19464
  resetAuditRuntimeForTests();
18083
19465
  resetBillingRuntimeForTests();
18084
19466
  (0, import_SubscriptionIdentityStore3.__resetSharedIdentityStoreForTests)();
18085
- (0, import_AccountAllowanceStore4.__resetSharedAccountAllowanceStoreForTests)();
19467
+ (0, import_AccountAllowanceStore7.__resetSharedAccountAllowanceStoreForTests)();
18086
19468
  (0, import_AccountAllowanceScheduling5.__resetSharedAccountAllowanceSchedulingForTests)();
18087
19469
  (0, import_usage2.__resetSharedUsageThroughputTrackerForTests)();
18088
19470
  }
18089
19471
  function isTokensStoreReadable(tokensPath) {
18090
19472
  try {
18091
- if (!(0, import_node_fs34.existsSync)(tokensPath)) return true;
18092
- (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);
18093
19475
  return true;
18094
19476
  } catch {
18095
19477
  return false;