@omnicross/daemon 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/bootstrap.ts
2
- import { accessSync, constants as fsConstants, existsSync as existsSync29, mkdirSync as mkdirSync9 } from "fs";
2
+ import { accessSync, constants as fsConstants, existsSync as existsSync30, mkdirSync as mkdirSync9 } from "fs";
3
3
  import { dirname as dirname17 } from "path";
4
4
  import { DEFAULT_AUDIT_CONFIG } from "@omnicross/contracts/audit-types";
5
5
  import { DEFAULT_BILLING_CONFIG } from "@omnicross/contracts/billing-types";
@@ -18,14 +18,14 @@ import { setSubscriptionRegistryForOutbound } from "@omnicross/core/outbound-api
18
18
  import { getSharedAccountHealth as getSharedAccountHealth4 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
19
19
  import {
20
20
  __resetSharedAccountAllowanceStoreForTests,
21
- AccountAllowanceStore as AccountAllowanceStore3,
21
+ AccountAllowanceStore as AccountAllowanceStore6,
22
22
  setSharedAccountAllowanceStore
23
23
  } from "@omnicross/core/pipeline/AccountAllowanceStore";
24
24
  import {
25
25
  __resetSharedAccountAllowanceSchedulingForTests,
26
26
  getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling5
27
27
  } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
28
- import { fetchUpstream as fetchUpstream7, setUpstreamProxyResolver } from "@omnicross/core/pipeline/upstreamFetch";
28
+ import { fetchUpstream as fetchUpstream11, setUpstreamProxyResolver } from "@omnicross/core/pipeline/upstreamFetch";
29
29
  import { __resetSharedIdentityStoreForTests } from "@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore";
30
30
  import { setGeminiCodeAssistResolver } from "@omnicross/core/ports/gemini-code-assist-resolver";
31
31
  import {
@@ -155,9 +155,84 @@ function handleCodexOAuthStatus(sessionId, deps) {
155
155
  return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
156
156
  }
157
157
 
158
+ // src/admin/accountsKimiOAuth.ts
159
+ import { kimiOAuth } from "@omnicross/subscriptions";
160
+ function err2(status, message) {
161
+ return { status, body: { error: { type: "admin_api_error", message } } };
162
+ }
163
+ var DEFAULT_KIMI_OAUTH_TTL_MS = 15 * 6e4;
164
+ async function handleKimiOAuthStart(deps) {
165
+ if (deps.kimiSessions.isBusy()) {
166
+ return err2(409, "a kimi sign-in is already in progress \u2014 finish it in the browser or cancel it");
167
+ }
168
+ const fetchImpl = deps.oauthExchangeFetch("kimi");
169
+ const deviceId = kimiOAuth.generateKimiDeviceId();
170
+ const fingerprint = kimiOAuth.kimiFingerprintHeaders(deviceId);
171
+ let authorization;
172
+ try {
173
+ authorization = await kimiOAuth.requestDeviceAuthorization(fetchImpl, fingerprint);
174
+ } catch (e) {
175
+ const reason = e instanceof Error ? e.message : "device authorization failed";
176
+ return err2(502, `kimi device authorization failed: ${reason}`);
177
+ }
178
+ const { sessionId, signal } = deps.kimiSessions.begin();
179
+ void runKimiDevicePoll(sessionId, authorization.deviceCode, deviceId, fingerprint, signal, deps).catch(() => deps.kimiSessions.settle(sessionId, "error", "kimi sign-in failed"));
180
+ return {
181
+ status: 200,
182
+ body: {
183
+ authUrl: authorization.verificationUriComplete ?? authorization.verificationUri,
184
+ userCode: authorization.userCode,
185
+ sessionId
186
+ }
187
+ };
188
+ }
189
+ async function runKimiDevicePoll(sessionId, deviceCode, deviceId, fingerprint, signal, deps) {
190
+ const fetchImpl = deps.oauthExchangeFetch("kimi");
191
+ const result = await kimiOAuth.awaitDeviceToken(
192
+ { userCode: "", deviceCode, verificationUri: "" },
193
+ fetchImpl,
194
+ {
195
+ fingerprint,
196
+ deadlineMs: DEFAULT_KIMI_OAUTH_TTL_MS,
197
+ sleep: (ms) => new Promise((resolve10, reject) => {
198
+ const onAbort = () => {
199
+ clearTimeout(timer);
200
+ reject(new Error("login: cancelled"));
201
+ };
202
+ const timer = setTimeout(() => {
203
+ signal.removeEventListener("abort", onAbort);
204
+ resolve10();
205
+ }, ms);
206
+ signal.addEventListener("abort", onAbort, { once: true });
207
+ })
208
+ }
209
+ );
210
+ const block = {
211
+ authMethod: "oauth",
212
+ status: "authorized",
213
+ accessToken: result.accessToken,
214
+ refreshToken: result.refreshToken,
215
+ expiresAt: new Date(Date.now() + result.expiresIn * 1e3).toISOString(),
216
+ accountId: kimiOAuth.kimiAccountIdFromAccessToken(result.accessToken),
217
+ deviceId,
218
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
219
+ };
220
+ await deps.subscriptionAccountAppender.appendProviderAccount("kimi", block);
221
+ deps.kimiSessions.settle(sessionId, "done");
222
+ }
223
+ function handleKimiOAuthCancel(sessionId, deps) {
224
+ if (!deps.kimiSessions.cancel(sessionId)) return err2(404, "unknown or expired kimi sign-in session");
225
+ return { status: 200, body: { ok: true } };
226
+ }
227
+ function handleKimiOAuthStatus(sessionId, deps) {
228
+ const s = deps.kimiSessions.get(sessionId);
229
+ if (!s) return err2(404, "unknown or expired kimi sign-in session");
230
+ return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
231
+ }
232
+
158
233
  // src/allowance/AccountAllowanceService.ts
159
234
  import {
160
- getSharedAccountAllowanceStore as getSharedAccountAllowanceStore2
235
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore5
161
236
  } from "@omnicross/core/pipeline/AccountAllowanceStore";
162
237
  import {
163
238
  getSharedAccountAllowanceScheduling
@@ -191,13 +266,11 @@ function secondsUntil(instant, now) {
191
266
  function windowFromPayload(id, payload, now) {
192
267
  const usedPercent = finitePercent(payload?.utilization);
193
268
  const resetsAt = isoInstant(payload?.resets_at);
194
- const isSonnet = id === "seven-day-sonnet";
195
269
  const isFiveHour = id === "five-hour";
196
270
  return {
197
271
  id,
198
- label: isFiveHour ? "5 hours" : isSonnet ? "7 days \xB7 Sonnet" : "7 days",
199
- scope: isSonnet ? "model-family" : "all",
200
- modelFamily: isSonnet ? "sonnet" : void 0,
272
+ label: isFiveHour ? "5 hours" : "7 days",
273
+ scope: "all",
201
274
  usedPercent,
202
275
  windowMinutes: isFiveHour ? 5 * 60 : 7 * 24 * 60,
203
276
  resetsAt,
@@ -205,6 +278,44 @@ function windowFromPayload(id, payload, now) {
205
278
  state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
206
279
  };
207
280
  }
281
+ function limitEntryWindow(entries, kind) {
282
+ const entry = entries.find((candidate) => candidate.kind === kind);
283
+ if (!entry) return void 0;
284
+ return { utilization: entry.percent, resets_at: entry.resets_at };
285
+ }
286
+ function slugifyDisplayName(name) {
287
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
288
+ }
289
+ function scopedWeeklyWindows(entries, now) {
290
+ const seen = /* @__PURE__ */ new Set();
291
+ const windows = [];
292
+ for (const entry of entries) {
293
+ if (entry.kind !== "weekly_scoped") continue;
294
+ const displayName = typeof entry.scope?.model?.display_name === "string" && entry.scope.model.display_name.trim() ? entry.scope.model.display_name.trim() : void 0;
295
+ if (!displayName) continue;
296
+ const slug = slugifyDisplayName(displayName);
297
+ if (!slug || seen.has(slug)) continue;
298
+ seen.add(slug);
299
+ const usedPercent = finitePercent(entry.percent);
300
+ const resetsAt = isoInstant(entry.resets_at);
301
+ windows.push({
302
+ id: `seven-day-${slug}`,
303
+ label: `7 days \xB7 ${displayName}`,
304
+ scope: "model-family",
305
+ modelFamily: slug,
306
+ usedPercent,
307
+ windowMinutes: 7 * 24 * 60,
308
+ resetsAt,
309
+ remainingSeconds: secondsUntil(resetsAt, now),
310
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
311
+ });
312
+ }
313
+ return windows;
314
+ }
315
+ function parseLimitEntries(raw) {
316
+ if (!Array.isArray(raw)) return [];
317
+ return raw.filter((entry) => !!entry && typeof entry === "object");
318
+ }
208
319
  function emptyClaudeWindows(state) {
209
320
  return [
210
321
  {
@@ -222,15 +333,6 @@ function emptyClaudeWindows(state) {
222
333
  usedPercent: null,
223
334
  windowMinutes: 7 * 24 * 60,
224
335
  state
225
- },
226
- {
227
- id: "seven-day-sonnet",
228
- label: "7 days \xB7 Sonnet",
229
- scope: "model-family",
230
- modelFamily: "sonnet",
231
- usedPercent: null,
232
- windowMinutes: 7 * 24 * 60,
233
- state
234
336
  }
235
337
  ];
236
338
  }
@@ -311,6 +413,9 @@ var ClaudeAllowanceCollector = class {
311
413
  }
312
414
  const now = this.now();
313
415
  const usage = payload;
416
+ const limitEntries = parseLimitEntries(usage.limits);
417
+ const fiveHour = usage.five_hour ?? limitEntryWindow(limitEntries, "session");
418
+ const sevenDay = usage.seven_day ?? limitEntryWindow(limitEntries, "weekly_all");
314
419
  const snapshot = {
315
420
  providerId: "claude",
316
421
  accountId,
@@ -318,10 +423,10 @@ var ClaudeAllowanceCollector = class {
318
423
  observedAt: new Date(now).toISOString(),
319
424
  expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
320
425
  windows: [
321
- windowFromPayload("five-hour", usage.five_hour, now),
322
- windowFromPayload("seven-day", usage.seven_day, now),
323
- windowFromPayload("seven-day-sonnet", usage.seven_day_sonnet, now)
324
- ]
426
+ windowFromPayload("five-hour", fiveHour, now),
427
+ windowFromPayload("seven-day", sevenDay, now),
428
+ ...scopedWeeklyWindows(limitEntries, now)
429
+ ].slice(0, 8)
325
430
  };
326
431
  this.store.set(snapshot);
327
432
  return snapshot;
@@ -348,34 +453,635 @@ var ClaudeAllowanceCollector = class {
348
453
  const existing = this.store.get("claude", accountId, now);
349
454
  const snapshot = existing ? {
350
455
  ...existing,
351
- expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
456
+ expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
457
+ windows: existing.windows.map((window) => ({
458
+ ...window,
459
+ state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
460
+ })),
461
+ lastErrorCode: code
462
+ } : {
463
+ providerId: "claude",
464
+ accountId,
465
+ source: "oauth-usage-api",
466
+ observedAt: new Date(now).toISOString(),
467
+ expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
468
+ windows: emptyClaudeWindows("unavailable"),
469
+ lastErrorCode: code
470
+ };
471
+ this.store.set(snapshot);
472
+ return snapshot;
473
+ }
474
+ unsupportedSnapshot(accountId, now, code = "claude_usage_unsupported_auth") {
475
+ return {
476
+ providerId: "claude",
477
+ accountId,
478
+ source: "oauth-usage-api",
479
+ observedAt: new Date(now).toISOString(),
480
+ windows: emptyClaudeWindows("unsupported"),
481
+ lastErrorCode: code
482
+ };
483
+ }
484
+ };
485
+
486
+ // src/allowance/CodexAllowanceCollector.ts
487
+ import {
488
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore2
489
+ } from "@omnicross/core/pipeline/AccountAllowanceStore";
490
+ import { fetchUpstream as fetchUpstream2 } from "@omnicross/core/pipeline/upstreamFetch";
491
+ var CODEX_ALLOWANCE_CACHE_MS = 5 * 6e4;
492
+ var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
493
+ var CODEX_CLI_USER_AGENT = "codex_cli_rs/0.144.5";
494
+ function finiteNumber(value) {
495
+ if (value === null || value === void 0 || value === "") return null;
496
+ const parsed = typeof value === "number" ? value : Number(value);
497
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
498
+ }
499
+ function finitePercent2(value) {
500
+ const parsed = finiteNumber(value);
501
+ return parsed !== null && parsed <= 100 ? parsed : null;
502
+ }
503
+ function epochMs(value) {
504
+ return value > 1e11 ? value : value * 1e3;
505
+ }
506
+ function secondsUntil2(instant, now) {
507
+ if (!instant) return void 0;
508
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
509
+ }
510
+ function decodeJwtClaims(token) {
511
+ const parts = token.split(".");
512
+ if (parts.length !== 3) return void 0;
513
+ try {
514
+ const json2 = Buffer.from(parts[1], "base64url").toString("utf8");
515
+ const parsed = JSON.parse(json2);
516
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
517
+ } catch {
518
+ return void 0;
519
+ }
520
+ }
521
+ function chatgptAccountIdFromClaims(claims) {
522
+ const auth = claims?.["https://api.openai.com/auth"];
523
+ if (!auth || typeof auth !== "object") return void 0;
524
+ const accountId = auth.chatgpt_account_id;
525
+ return typeof accountId === "string" && accountId.trim() ? accountId.trim() : void 0;
526
+ }
527
+ function resolveCodexChatGptAccountId(tokens) {
528
+ if (tokens.accountId?.trim()) return tokens.accountId.trim();
529
+ if (tokens.idToken) {
530
+ const fromIdToken = chatgptAccountIdFromClaims(decodeJwtClaims(tokens.idToken));
531
+ if (fromIdToken) return fromIdToken;
532
+ }
533
+ if (tokens.accessToken) {
534
+ return chatgptAccountIdFromClaims(decodeJwtClaims(tokens.accessToken));
535
+ }
536
+ return void 0;
537
+ }
538
+ function windowFromPayload2(id, payload, now) {
539
+ const usedPercent = finitePercent2(payload?.used_percent);
540
+ const resetAtSeconds = finiteNumber(payload?.reset_at);
541
+ const resetAfterSeconds = finiteNumber(payload?.reset_after_seconds);
542
+ const windowSeconds = finiteNumber(payload?.limit_window_seconds);
543
+ const resetsAt = resetAtSeconds !== null && resetAtSeconds > 0 ? new Date(epochMs(resetAtSeconds)).toISOString() : resetAfterSeconds !== null && resetAfterSeconds > 0 ? new Date(now + resetAfterSeconds * 1e3).toISOString() : void 0;
544
+ const windowMinutes = windowSeconds !== null && windowSeconds > 0 ? Math.round(windowSeconds / 60) : void 0;
545
+ return {
546
+ id,
547
+ label: id === "primary" ? "Primary" : "Secondary",
548
+ scope: "all",
549
+ usedPercent,
550
+ ...windowMinutes !== void 0 ? { windowMinutes } : {},
551
+ ...resetsAt !== void 0 ? { resetsAt } : {},
552
+ remainingSeconds: secondsUntil2(resetsAt, now),
553
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
554
+ };
555
+ }
556
+ var CodexAllowanceCollector = class {
557
+ constructor(credentials, store = getSharedAccountAllowanceStore2(), fetchImpl = (url, init, accountId) => fetchUpstream2(url, init, { providerId: "codex", accountId, redactBodies: true }), now = Date.now) {
558
+ this.credentials = credentials;
559
+ this.store = store;
560
+ this.fetchImpl = fetchImpl;
561
+ this.now = now;
562
+ }
563
+ credentials;
564
+ store;
565
+ fetchImpl;
566
+ now;
567
+ inFlight = /* @__PURE__ */ new Map();
568
+ async collectMany(accounts, options = {}) {
569
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
570
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
571
+ }
572
+ collect(account, options = {}) {
573
+ const now = this.now();
574
+ const unsupported = account.tokens.authMethod !== "oauth";
575
+ if (unsupported) {
576
+ const existing = this.store.get("codex", account.id, now);
577
+ if (existing?.windows.every((window) => window.state === "unsupported")) {
578
+ return Promise.resolve(existing);
579
+ }
580
+ const snapshot = this.unsupportedSnapshot(account.id, now);
581
+ this.store.set(snapshot);
582
+ return Promise.resolve(snapshot);
583
+ }
584
+ const cached = this.store.get("codex", account.id, now);
585
+ if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
586
+ return Promise.resolve(cached);
587
+ }
588
+ const running = this.inFlight.get(account.id);
589
+ if (running) return running;
590
+ const promise = this.fetchAccount(account.id, account.tokens).catch(() => this.failureSnapshot(account.id, "codex_usage_request_failed", this.now())).finally(() => this.inFlight.delete(account.id));
591
+ this.inFlight.set(account.id, promise);
592
+ return promise;
593
+ }
594
+ /**
595
+ * A response-header snapshot stays a valid cache hit only while fresh; an
596
+ * active oauth-usage snapshot is honored on the same 5-minute cadence as
597
+ * Claude's (the poll is cheap and quota is the scheduling input).
598
+ */
599
+ isCacheValid(snapshot, now, refreshAheadMs) {
600
+ if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
601
+ const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
602
+ const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
603
+ return Number.isFinite(expiresAt) && expiresAt > now + ahead;
604
+ }
605
+ async fetchAccount(accountId, tokens) {
606
+ let accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
607
+ if (!accessToken) {
608
+ return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
609
+ }
610
+ let response = await this.request(accountId, accessToken, tokens);
611
+ if (response.status === 401) {
612
+ const refreshed = await this.credentials.refreshAccountToken("codex", accountId);
613
+ if (!refreshed) {
614
+ return this.failureSnapshot(accountId, "codex_usage_unauthorized", this.now());
615
+ }
616
+ accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
617
+ if (!accessToken) {
618
+ return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
619
+ }
620
+ response = await this.request(accountId, accessToken, tokens);
621
+ }
622
+ if (response.status === 403) {
623
+ const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "codex_usage_unsupported");
624
+ this.store.set(snapshot2);
625
+ return snapshot2;
626
+ }
627
+ if (!response.ok) {
628
+ return this.failureSnapshot(accountId, "codex_usage_http_error", this.now());
629
+ }
630
+ let payload;
631
+ try {
632
+ payload = await response.json();
633
+ } catch {
634
+ return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
635
+ }
636
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
637
+ return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
638
+ }
639
+ const now = this.now();
640
+ const usage = payload.rate_limit;
641
+ const previous = this.store.get("codex", accountId, now);
642
+ const snapshot = {
643
+ providerId: "codex",
644
+ accountId,
645
+ source: "oauth-usage-api",
646
+ observedAt: new Date(now).toISOString(),
647
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
648
+ windows: [
649
+ windowFromPayload2("primary", usage?.primary_window ?? void 0, now),
650
+ windowFromPayload2("secondary", usage?.secondary_window ?? void 0, now)
651
+ ],
652
+ // The wham payload has no ratio field; keep the passively-observed value.
653
+ ...previous?.primaryOverSecondaryLimitPercent !== void 0 ? { primaryOverSecondaryLimitPercent: previous.primaryOverSecondaryLimitPercent } : {}
654
+ };
655
+ this.store.set(snapshot);
656
+ return snapshot;
657
+ }
658
+ request(accountId, accessToken, tokens) {
659
+ const headers = {
660
+ Authorization: `Bearer ${accessToken}`,
661
+ Accept: "application/json",
662
+ "User-Agent": CODEX_CLI_USER_AGENT
663
+ };
664
+ const chatgptAccountId = resolveCodexChatGptAccountId(tokens);
665
+ if (chatgptAccountId) headers["ChatGPT-Account-Id"] = chatgptAccountId;
666
+ return this.fetchImpl(CODEX_USAGE_URL, {
667
+ method: "GET",
668
+ headers,
669
+ signal: AbortSignal.timeout(15e3)
670
+ }, accountId);
671
+ }
672
+ failureSnapshot(accountId, code, now) {
673
+ const existing = this.store.get("codex", accountId, now);
674
+ const snapshot = existing ? {
675
+ ...existing,
676
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
677
+ windows: existing.windows.map((window) => ({
678
+ ...window,
679
+ state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
680
+ })),
681
+ lastErrorCode: code
682
+ } : {
683
+ providerId: "codex",
684
+ accountId,
685
+ source: "oauth-usage-api",
686
+ observedAt: new Date(now).toISOString(),
687
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
688
+ windows: [
689
+ { id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unavailable" },
690
+ { id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unavailable" }
691
+ ],
692
+ lastErrorCode: code
693
+ };
694
+ this.store.set(snapshot);
695
+ return snapshot;
696
+ }
697
+ unsupportedSnapshot(accountId, now, code = "codex_usage_unsupported_auth") {
698
+ return {
699
+ providerId: "codex",
700
+ accountId,
701
+ source: "oauth-usage-api",
702
+ observedAt: new Date(now).toISOString(),
703
+ windows: [
704
+ { id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unsupported" },
705
+ { id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unsupported" }
706
+ ],
707
+ lastErrorCode: code
708
+ };
709
+ }
710
+ };
711
+
712
+ // src/allowance/KimiAllowanceCollector.ts
713
+ import {
714
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore3
715
+ } from "@omnicross/core/pipeline/AccountAllowanceStore";
716
+ import { fetchUpstream as fetchUpstream3 } from "@omnicross/core/pipeline/upstreamFetch";
717
+ import { kimiFingerprintHeaders } from "@omnicross/subscriptions";
718
+ var KIMI_ALLOWANCE_CACHE_MS = 5 * 6e4;
719
+ var KIMI_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
720
+ function finiteNumber2(value) {
721
+ if (value === null || value === void 0 || value === "") return void 0;
722
+ const parsed = typeof value === "number" ? value : Number(value);
723
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
724
+ }
725
+ function isRecord(value) {
726
+ return !!value && typeof value === "object" && !Array.isArray(value);
727
+ }
728
+ function parseResetMs(row, nowMs) {
729
+ for (const key of ["reset_at", "resetAt", "reset_time", "resetTime"]) {
730
+ const value = row[key];
731
+ if (typeof value === "string" && value.trim()) {
732
+ const parsed = Date.parse(value);
733
+ if (Number.isFinite(parsed)) return parsed;
734
+ }
735
+ const numeric = finiteNumber2(value);
736
+ if (numeric !== void 0 && numeric > 1e9) {
737
+ return numeric > 1e12 ? numeric : numeric * 1e3;
738
+ }
739
+ }
740
+ for (const key of ["reset_in", "resetIn", "ttl", "window"]) {
741
+ const seconds = finiteNumber2(row[key]);
742
+ if (seconds !== void 0) return nowMs + seconds * 1e3;
743
+ }
744
+ return void 0;
745
+ }
746
+ var MINUTE_MS = 6e4;
747
+ var HOUR_MS = 36e5;
748
+ var DAY_MS = 864e5;
749
+ function canonicalWindow(durationMs) {
750
+ if (durationMs === 5 * HOUR_MS) return { id: "five-hour", label: "5 hours", minutes: 300 };
751
+ if (durationMs === 7 * DAY_MS) return { id: "seven-day", label: "7 days", minutes: 10080 };
752
+ if (durationMs > 0 && durationMs % DAY_MS === 0) {
753
+ const days = durationMs / DAY_MS;
754
+ return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
755
+ }
756
+ if (durationMs > 0 && durationMs % HOUR_MS === 0) {
757
+ const hours = durationMs / HOUR_MS;
758
+ return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
759
+ }
760
+ return void 0;
761
+ }
762
+ function secondsUntil3(instant, now) {
763
+ if (!instant) return void 0;
764
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
765
+ }
766
+ function windowFromRow(row, fallback, now) {
767
+ const usedPercent = row?.limit !== void 0 && row.limit > 0 && row.used !== void 0 ? Math.round(Math.min(100, row.used / row.limit * 100) * 10) / 10 : null;
768
+ const resetsAt = row?.resetsAtMs !== void 0 ? new Date(row.resetsAtMs).toISOString() : void 0;
769
+ return {
770
+ id: fallback.id,
771
+ label: fallback.label,
772
+ scope: "all",
773
+ usedPercent,
774
+ windowMinutes: fallback.minutes,
775
+ ...resetsAt !== void 0 ? { resetsAt } : {},
776
+ remainingSeconds: secondsUntil3(resetsAt, now),
777
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
778
+ };
779
+ }
780
+ function parseKimiUsagePayload(payload, now) {
781
+ if (!isRecord(payload)) return [];
782
+ const byId = /* @__PURE__ */ new Map();
783
+ const rowFrom = (data) => {
784
+ const limit = finiteNumber2(data["limit"]);
785
+ let used = finiteNumber2(data["used"]);
786
+ const remaining = finiteNumber2(data["remaining"]);
787
+ if (used === void 0 && remaining !== void 0 && limit !== void 0) {
788
+ used = limit - remaining;
789
+ }
790
+ let windowDurationMs;
791
+ const windowData = isRecord(data["window"]) ? data["window"] : void 0;
792
+ const duration = finiteNumber2(windowData?.["duration"]);
793
+ const timeUnit = typeof windowData?.["timeUnit"] === "string" ? windowData["timeUnit"].toUpperCase() : "";
794
+ if (duration !== void 0) {
795
+ if (timeUnit.includes("MINUTE")) windowDurationMs = duration * MINUTE_MS;
796
+ else if (timeUnit.includes("HOUR")) windowDurationMs = duration * HOUR_MS;
797
+ else if (timeUnit.includes("DAY")) windowDurationMs = duration * DAY_MS;
798
+ else if (timeUnit.includes("WEEK")) windowDurationMs = duration * 7 * DAY_MS;
799
+ else if (timeUnit.includes("SECOND")) windowDurationMs = duration * 1e3;
800
+ }
801
+ const resetsAtMs = parseResetMs(windowData && parseResetMs(windowData, now) !== void 0 ? windowData : data, now);
802
+ return { used, limit, remaining, ...resetsAtMs !== void 0 ? { resetsAtMs } : {}, ...windowDurationMs !== void 0 ? { windowDurationMs } : {} };
803
+ };
804
+ if (isRecord(payload["usage"])) {
805
+ const row = rowFrom(payload["usage"]);
806
+ const window = windowFromRow({ ...row, resetsAtMs: row.resetsAtMs }, { id: "seven-day", label: "7 days", minutes: 10080 }, now);
807
+ byId.set("seven-day", window);
808
+ }
809
+ if (Array.isArray(payload["limits"])) {
810
+ for (const item of payload["limits"]) {
811
+ if (!isRecord(item)) continue;
812
+ const detail = isRecord(item["detail"]) ? item["detail"] : item;
813
+ const row = rowFrom(detail);
814
+ const canonical = row.windowDurationMs !== void 0 ? canonicalWindow(row.windowDurationMs) : void 0;
815
+ if (!canonical) continue;
816
+ const window = windowFromRow(row, canonical, now);
817
+ const existing = byId.get(canonical.id);
818
+ if (!existing || (window.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
819
+ byId.set(canonical.id, window);
820
+ }
821
+ }
822
+ }
823
+ return [...byId.values()].sort((a, b) => (a.windowMinutes ?? Infinity) - (b.windowMinutes ?? Infinity)).slice(0, 4);
824
+ }
825
+ var KimiAllowanceCollector = class {
826
+ constructor(credentials, store = getSharedAccountAllowanceStore3(), fetchImpl = (url, init, accountId) => fetchUpstream3(url, init, { providerId: "kimi", accountId, redactBodies: true }), now = Date.now) {
827
+ this.credentials = credentials;
828
+ this.store = store;
829
+ this.fetchImpl = fetchImpl;
830
+ this.now = now;
831
+ }
832
+ credentials;
833
+ store;
834
+ fetchImpl;
835
+ now;
836
+ inFlight = /* @__PURE__ */ new Map();
837
+ async collectMany(accounts, options = {}) {
838
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
839
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
840
+ }
841
+ collect(account, options = {}) {
842
+ const now = this.now();
843
+ if (account.tokens.authMethod !== "oauth") {
844
+ const existing = this.store.get("kimi", account.id, now);
845
+ if (existing?.windows.every((window) => window.state === "unsupported")) {
846
+ return Promise.resolve(existing);
847
+ }
848
+ const snapshot = this.unsupportedSnapshot(account.id, now);
849
+ this.store.set(snapshot);
850
+ return Promise.resolve(snapshot);
851
+ }
852
+ const cached = this.store.get("kimi", account.id, now);
853
+ if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
854
+ return Promise.resolve(cached);
855
+ }
856
+ const running = this.inFlight.get(account.id);
857
+ if (running) return running;
858
+ const promise = this.fetchAccount(account.id, account.tokens).catch(() => this.failureSnapshot(account.id, "kimi_usage_request_failed", this.now())).finally(() => this.inFlight.delete(account.id));
859
+ this.inFlight.set(account.id, promise);
860
+ return promise;
861
+ }
862
+ isCacheValid(snapshot, now, refreshAheadMs) {
863
+ if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
864
+ const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
865
+ const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
866
+ return Number.isFinite(expiresAt) && expiresAt > now + ahead;
867
+ }
868
+ async fetchAccount(accountId, tokens) {
869
+ let accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
870
+ if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
871
+ let response = await this.request(accountId, accessToken, tokens);
872
+ if (response.status === 401) {
873
+ const refreshed = await this.credentials.refreshAccountToken("kimi", accountId);
874
+ if (!refreshed) return this.failureSnapshot(accountId, "kimi_usage_unauthorized", this.now());
875
+ accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
876
+ if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
877
+ response = await this.request(accountId, accessToken, tokens);
878
+ }
879
+ if (response.status === 403) {
880
+ const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "kimi_usage_unsupported");
881
+ this.store.set(snapshot2);
882
+ return snapshot2;
883
+ }
884
+ if (!response.ok) return this.failureSnapshot(accountId, "kimi_usage_http_error", this.now());
885
+ let payload;
886
+ try {
887
+ payload = await response.json();
888
+ } catch {
889
+ return this.failureSnapshot(accountId, "kimi_usage_invalid_response", this.now());
890
+ }
891
+ const now = this.now();
892
+ const windows = parseKimiUsagePayload(payload, now);
893
+ const snapshot = {
894
+ providerId: "kimi",
895
+ accountId,
896
+ source: "oauth-usage-api",
897
+ observedAt: new Date(now).toISOString(),
898
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
899
+ windows: windows.length > 0 ? windows : [
900
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
901
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
902
+ ],
903
+ ...windows.length > 0 ? {} : { lastErrorCode: "kimi_usage_invalid_response" }
904
+ };
905
+ this.store.set(snapshot);
906
+ return snapshot;
907
+ }
908
+ request(accountId, accessToken, tokens) {
909
+ return this.fetchImpl(KIMI_USAGE_URL, {
910
+ method: "GET",
911
+ headers: {
912
+ Authorization: `Bearer ${accessToken}`,
913
+ Accept: "application/json",
914
+ ...kimiFingerprintHeaders(tokens.deviceId)
915
+ },
916
+ signal: AbortSignal.timeout(15e3)
917
+ }, accountId);
918
+ }
919
+ failureSnapshot(accountId, code, now) {
920
+ const existing = this.store.get("kimi", accountId, now);
921
+ const snapshot = existing ? {
922
+ ...existing,
923
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
924
+ windows: existing.windows.map((window) => ({
925
+ ...window,
926
+ state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
927
+ })),
928
+ lastErrorCode: code
929
+ } : {
930
+ providerId: "kimi",
931
+ accountId,
932
+ source: "oauth-usage-api",
933
+ observedAt: new Date(now).toISOString(),
934
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
935
+ windows: [
936
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
937
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
938
+ ],
939
+ lastErrorCode: code
940
+ };
941
+ this.store.set(snapshot);
942
+ return snapshot;
943
+ }
944
+ unsupportedSnapshot(accountId, now, code = "kimi_usage_unsupported_auth") {
945
+ return {
946
+ providerId: "kimi",
947
+ accountId,
948
+ source: "oauth-usage-api",
949
+ observedAt: new Date(now).toISOString(),
950
+ windows: [
951
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unsupported" },
952
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unsupported" }
953
+ ],
954
+ lastErrorCode: code
955
+ };
956
+ }
957
+ };
958
+
959
+ // src/allowance/OpenCodeGoAllowanceCollector.ts
960
+ import {
961
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore4
962
+ } from "@omnicross/core/pipeline/AccountAllowanceStore";
963
+ import { fetchUpstream as fetchUpstream4 } from "@omnicross/core/pipeline/upstreamFetch";
964
+ import { normalizeOpenCodeGoBaseUrl } from "@omnicross/subscriptions";
965
+ var OPENCODEGO_ALLOWANCE_CACHE_MS = 5 * 6e4;
966
+ var OPENCODEGO_DEFAULT_GO_BASE = "https://opencode.ai/zen/go";
967
+ function finitePercent3(value) {
968
+ if (value === null || value === void 0 || value === "") return null;
969
+ const parsed = typeof value === "number" ? value : Number(value);
970
+ return Number.isFinite(parsed) && parsed >= 0 && parsed <= 100 ? parsed : null;
971
+ }
972
+ function isoInstant2(value) {
973
+ if (typeof value !== "string" || !value.trim()) return void 0;
974
+ const time = Date.parse(value);
975
+ return Number.isFinite(time) ? new Date(time).toISOString() : void 0;
976
+ }
977
+ function secondsUntil4(instant, now) {
978
+ if (!instant) return void 0;
979
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
980
+ }
981
+ function windowFromPayload3(id, label, minutes, payload, now) {
982
+ const statusRateLimited = payload?.status === "rate-limited";
983
+ const usedPercent = statusRateLimited ? 100 : finitePercent3(payload?.percent);
984
+ const resetsAt = isoInstant2(payload?.resetsAt);
985
+ return {
986
+ id,
987
+ label,
988
+ scope: "all",
989
+ usedPercent,
990
+ windowMinutes: minutes,
991
+ ...resetsAt !== void 0 ? { resetsAt } : {},
992
+ remainingSeconds: secondsUntil4(resetsAt, now),
993
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
994
+ };
995
+ }
996
+ var OpenCodeGoAllowanceCollector = class {
997
+ constructor(credentials, store = getSharedAccountAllowanceStore4(), fetchImpl = (url, init, accountId) => fetchUpstream4(url, init, { providerId: "opencodego", accountId, redactBodies: true }), now = Date.now) {
998
+ this.credentials = credentials;
999
+ this.store = store;
1000
+ this.fetchImpl = fetchImpl;
1001
+ this.now = now;
1002
+ }
1003
+ credentials;
1004
+ store;
1005
+ fetchImpl;
1006
+ now;
1007
+ inFlight = /* @__PURE__ */ new Map();
1008
+ async collectMany(accounts, options = {}) {
1009
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
1010
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
1011
+ }
1012
+ collect(account, options = {}) {
1013
+ const now = this.now();
1014
+ const cached = this.store.get("opencodego", account.id, now);
1015
+ if (!options.force && cached && (cached.windows.every((window) => window.state === "unsupported") || cached.expiresAt && Date.parse(cached.expiresAt) > now + (options.refreshAheadMs ?? 0))) {
1016
+ return Promise.resolve(cached);
1017
+ }
1018
+ const running = this.inFlight.get(account.id);
1019
+ if (running) return running;
1020
+ const promise = this.fetchAccount(account).catch(() => this.failureSnapshot(account.id, this.now())).finally(() => this.inFlight.delete(account.id));
1021
+ this.inFlight.set(account.id, promise);
1022
+ return promise;
1023
+ }
1024
+ async fetchAccount(account) {
1025
+ const apiKey = await this.credentials.getAccessTokenForAccount("opencodego", account.id);
1026
+ if (!apiKey) return this.failureSnapshot(account.id, this.now());
1027
+ const base = account.tokens.baseUrl ? normalizeOpenCodeGoBaseUrl(account.tokens.baseUrl) : OPENCODEGO_DEFAULT_GO_BASE;
1028
+ const response = await this.fetchImpl(`${base}/v1/usage`, {
1029
+ method: "GET",
1030
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
1031
+ signal: AbortSignal.timeout(15e3)
1032
+ }, account.id);
1033
+ if (response.status === 401 || response.status === 403) {
1034
+ return this.failureSnapshot(account.id, this.now(), "opencodego_usage_unauthorized");
1035
+ }
1036
+ if (!response.ok) return this.failureSnapshot(account.id, this.now());
1037
+ let payload;
1038
+ try {
1039
+ payload = await response.json();
1040
+ } catch {
1041
+ return this.failureSnapshot(account.id, this.now());
1042
+ }
1043
+ const usage = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.usage : void 0;
1044
+ const now = this.now();
1045
+ const snapshot = {
1046
+ providerId: "opencodego",
1047
+ accountId: account.id,
1048
+ source: "oauth-usage-api",
1049
+ observedAt: new Date(now).toISOString(),
1050
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
1051
+ // Monthly deliberately omitted (module doc).
1052
+ windows: [
1053
+ windowFromPayload3("five-hour", "5 hours", 5 * 60, usage?.rolling ?? void 0, now),
1054
+ windowFromPayload3("seven-day", "7 days", 7 * 24 * 60, usage?.weekly ?? void 0, now)
1055
+ ]
1056
+ };
1057
+ this.store.set(snapshot);
1058
+ return snapshot;
1059
+ }
1060
+ failureSnapshot(accountId, now, code = "opencodego_usage_request_failed") {
1061
+ const existing = this.store.get("opencodego", accountId, now);
1062
+ const snapshot = existing ? {
1063
+ ...existing,
1064
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
352
1065
  windows: existing.windows.map((window) => ({
353
1066
  ...window,
354
- state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
1067
+ state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
355
1068
  })),
356
1069
  lastErrorCode: code
357
1070
  } : {
358
- providerId: "claude",
1071
+ providerId: "opencodego",
359
1072
  accountId,
360
1073
  source: "oauth-usage-api",
361
1074
  observedAt: new Date(now).toISOString(),
362
- expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
363
- windows: emptyClaudeWindows("unavailable"),
1075
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
1076
+ windows: [
1077
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
1078
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
1079
+ ],
364
1080
  lastErrorCode: code
365
1081
  };
366
1082
  this.store.set(snapshot);
367
1083
  return snapshot;
368
1084
  }
369
- unsupportedSnapshot(accountId, now, code = "claude_usage_unsupported_auth") {
370
- return {
371
- providerId: "claude",
372
- accountId,
373
- source: "oauth-usage-api",
374
- observedAt: new Date(now).toISOString(),
375
- windows: emptyClaudeWindows("unsupported"),
376
- lastErrorCode: code
377
- };
378
- }
379
1085
  };
380
1086
 
381
1087
  // src/allowance/AccountAllowanceService.ts
@@ -393,26 +1099,30 @@ function codexUnavailable(accountId, now) {
393
1099
  };
394
1100
  }
395
1101
  var AccountAllowanceService = class {
396
- constructor(credentials, store = getSharedAccountAllowanceStore2(), collector, now = Date.now) {
1102
+ constructor(credentials, store = getSharedAccountAllowanceStore5(), collector, codexCollector, kimiCollector, opencodegoCollector, now = Date.now) {
397
1103
  this.credentials = credentials;
398
1104
  this.store = store;
399
1105
  this.now = now;
400
1106
  this.claudeCollector = collector ?? new ClaudeAllowanceCollector(credentials, store);
1107
+ this.codexCollector = codexCollector ?? new CodexAllowanceCollector(credentials, store);
1108
+ this.kimiCollector = kimiCollector ?? new KimiAllowanceCollector(credentials, store);
1109
+ this.opencodegoCollector = opencodegoCollector ?? new OpenCodeGoAllowanceCollector(credentials, store);
401
1110
  }
402
1111
  credentials;
403
1112
  store;
404
1113
  now;
405
1114
  claudeCollector;
1115
+ codexCollector;
1116
+ kimiCollector;
1117
+ opencodegoCollector;
406
1118
  /**
407
- * Read all/filtered snapshots. Claude's five-minute cache is refreshed lazily;
408
- * Codex remains passive and reports not-observed until a real model response.
1119
+ * Read all/filtered snapshots. Claude's and Codex's five-minute caches are
1120
+ * refreshed lazily on read (Codex polls `/backend-api/wham/usage`; the
1121
+ * passive `x-codex-*` header tap still feeds mid-flight updates).
409
1122
  */
410
1123
  async list(filter = {}) {
411
1124
  const config = await this.credentials.getFullConfig();
412
- this.store.pruneToKnownAccounts([
413
- ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
414
- ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
415
- ]);
1125
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
416
1126
  const wantsClaude = !filter.providerId || filter.providerId === "claude";
417
1127
  const claudeAccounts = (config.claudeAccounts ?? []).filter(
418
1128
  (account) => !filter.accountId || account.id === filter.accountId
@@ -423,39 +1133,90 @@ var AccountAllowanceService = class {
423
1133
  (account) => !filter.accountId || account.id === filter.accountId
424
1134
  );
425
1135
  if (wantsCodex) {
1136
+ await this.codexCollector.collectMany(codexAccounts);
426
1137
  for (const account of codexAccounts) {
427
1138
  if (!this.store.get("codex", account.id)) this.store.set(codexUnavailable(account.id, this.now()));
428
1139
  }
429
1140
  }
1141
+ const wantsKimi = !filter.providerId || filter.providerId === "kimi";
1142
+ const kimiAccounts = (config.kimiAccounts ?? []).filter(
1143
+ (account) => !filter.accountId || account.id === filter.accountId
1144
+ );
1145
+ if (wantsKimi) await this.kimiCollector.collectMany(kimiAccounts);
1146
+ const wantsOpenCodeGo = !filter.providerId || filter.providerId === "opencodego";
1147
+ const opencodegoAccounts = (config.opencodegoAccounts ?? []).filter(
1148
+ (account) => !filter.accountId || account.id === filter.accountId
1149
+ );
1150
+ if (wantsOpenCodeGo) await this.opencodegoCollector.collectMany(opencodegoAccounts);
430
1151
  const known = /* @__PURE__ */ new Set();
431
1152
  if (wantsClaude) for (const account of claudeAccounts) known.add(`claude\0${account.id}`);
432
1153
  if (wantsCodex) for (const account of codexAccounts) known.add(`codex\0${account.id}`);
1154
+ if (wantsKimi) for (const account of kimiAccounts) known.add(`kimi\0${account.id}`);
1155
+ if (wantsOpenCodeGo) for (const account of opencodegoAccounts) known.add(`opencodego\0${account.id}`);
433
1156
  return this.store.list(filter).filter((snapshot) => known.has(`${snapshot.providerId}\0${snapshot.accountId}`));
434
1157
  }
1158
+ knownAccounts(config) {
1159
+ return [
1160
+ ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
1161
+ ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id })),
1162
+ ...(config.kimiAccounts ?? []).map((account) => ({ providerId: "kimi", accountId: account.id })),
1163
+ ...(config.opencodegoAccounts ?? []).map((account) => ({ providerId: "opencodego", accountId: account.id }))
1164
+ ];
1165
+ }
435
1166
  /** Force-refresh Claude usage for one account or every stored Claude account. */
436
1167
  async refreshClaude(accountId) {
437
1168
  const config = await this.credentials.getFullConfig();
438
- this.store.pruneToKnownAccounts([
439
- ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
440
- ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
441
- ]);
1169
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
442
1170
  const accounts = (config.claudeAccounts ?? []).filter(
443
1171
  (account) => !accountId || account.id === accountId
444
1172
  );
445
1173
  return this.claudeCollector.collectMany(accounts, { force: true });
446
1174
  }
447
1175
  /**
448
- * Keep Claude snapshots warm for allowance-aware routing. This deliberately
449
- * excludes Codex (whose quota is learned from real response headers) and
450
- * preserves the collector's cache + per-account in-flight coalescing.
1176
+ * Force-refresh Codex usage (`/backend-api/wham/usage`) for one account or
1177
+ * every stored Codex account. Replaces the old probe-request workaround
1178
+ * no quota is spent reading the usage endpoint.
1179
+ */
1180
+ async refreshCodex(accountId) {
1181
+ const config = await this.credentials.getFullConfig();
1182
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1183
+ const accounts = (config.codexAccounts ?? []).filter(
1184
+ (account) => !accountId || account.id === accountId
1185
+ );
1186
+ return this.codexCollector.collectMany(accounts, { force: true });
1187
+ }
1188
+ /** Force-refresh OpenCodeGo usage (`{go}/v1/usage`) for one/all accounts. */
1189
+ async refreshOpenCodeGo(accountId) {
1190
+ const config = await this.credentials.getFullConfig();
1191
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1192
+ const accounts = (config.opencodegoAccounts ?? []).filter(
1193
+ (account) => !accountId || account.id === accountId
1194
+ );
1195
+ return this.opencodegoCollector.collectMany(accounts, { force: true });
1196
+ }
1197
+ /** Force-refresh Kimi usage (`/coding/v1/usages`) for one/all accounts. */
1198
+ async refreshKimi(accountId) {
1199
+ const config = await this.credentials.getFullConfig();
1200
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1201
+ const accounts = (config.kimiAccounts ?? []).filter(
1202
+ (account) => !accountId || account.id === accountId
1203
+ );
1204
+ return this.kimiCollector.collectMany(accounts, { force: true });
1205
+ }
1206
+ /**
1207
+ * Keep Claude + Codex + Kimi snapshots warm for allowance-aware routing. All
1208
+ * collectors preserve their cache + per-account in-flight coalescing; a tick
1209
+ * normally performs no network I/O. (Codex joined the warm path when it
1210
+ * gained an active `/wham/usage` collector — the passive `x-codex-*` header
1211
+ * tap alone could not keep the policy fed while idle.)
451
1212
  */
452
1213
  async maintainClaudeCache(refreshAheadMs) {
453
1214
  const config = await this.credentials.getFullConfig();
454
- this.store.pruneToKnownAccounts([
455
- ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
456
- ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
457
- ]);
1215
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
458
1216
  await this.claudeCollector.collectMany(config.claudeAccounts ?? [], { refreshAheadMs });
1217
+ await this.codexCollector.collectMany(config.codexAccounts ?? [], { refreshAheadMs });
1218
+ await this.kimiCollector.collectMany(config.kimiAccounts ?? [], { refreshAheadMs });
1219
+ await this.opencodegoCollector.collectMany(config.opencodegoAccounts ?? [], { refreshAheadMs });
459
1220
  }
460
1221
  /** Remove a cache row as soon as an account is deleted by the admin path. */
461
1222
  removeAccountSnapshot(providerId, accountId) {
@@ -892,7 +1653,7 @@ import {
892
1653
  } from "@omnicross/contracts/image-generation-types";
893
1654
  import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling2 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
894
1655
  import { getSharedAccountHealth } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
895
- import { fetchUpstream as fetchUpstream2 } from "@omnicross/core/pipeline/upstreamFetch";
1656
+ import { fetchUpstream as fetchUpstream5 } from "@omnicross/core/pipeline/upstreamFetch";
896
1657
 
897
1658
  // src/image-generation/imagesConfigValidation.ts
898
1659
  import { validateImagesServerConfig } from "@omnicross/core/outbound-api";
@@ -2969,7 +3730,8 @@ var VALID_PROVIDER_IDS = [
2969
3730
  "claude",
2970
3731
  "codex",
2971
3732
  "gemini",
2972
- "opencodego"
3733
+ "opencodego",
3734
+ "kimi"
2973
3735
  ];
2974
3736
  function asSubscriptionProviderId(id) {
2975
3737
  return VALID_PROVIDER_IDS.includes(id) ? id : null;
@@ -3105,6 +3867,18 @@ function validateGemini(body) {
3105
3867
  copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "lastRefreshedAt", "errorMessage"]);
3106
3868
  return out;
3107
3869
  }
3870
+ function validateKimi(body) {
3871
+ const authMethod = str(body["authMethod"]);
3872
+ const status = str(body["status"]);
3873
+ if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
3874
+ if (!status || !TOKEN_STATUSES.has(status)) return null;
3875
+ const out = {
3876
+ authMethod,
3877
+ status
3878
+ };
3879
+ copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "accountId", "deviceId", "lastRefreshedAt", "errorMessage"]);
3880
+ return out;
3881
+ }
3108
3882
  function validateOpenCodeGo(body) {
3109
3883
  const authMethod = str(body["authMethod"]);
3110
3884
  const status = str(body["status"]);
@@ -3140,6 +3914,8 @@ function validateTokenBody(providerId, body) {
3140
3914
  return validateGemini(body);
3141
3915
  case "opencodego":
3142
3916
  return validateOpenCodeGo(body);
3917
+ case "kimi":
3918
+ return validateKimi(body);
3143
3919
  default:
3144
3920
  return null;
3145
3921
  }
@@ -3169,12 +3945,12 @@ async function statusEntryFor(reader, providerId) {
3169
3945
 
3170
3946
  // src/admin/accountsOAuth.ts
3171
3947
  var OAUTH_HTTP_PROVIDERS = /* @__PURE__ */ new Set(["claude", "gemini"]);
3172
- function err2(status, message) {
3948
+ function err3(status, message) {
3173
3949
  return { status, body: { error: { type: "admin_api_error", message } } };
3174
3950
  }
3175
3951
  function handleOAuthStart(providerId, deps) {
3176
3952
  if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
3177
- return err2(400, `oauth not available for provider '${providerId}'`);
3953
+ return err3(400, `oauth not available for provider '${providerId}'`);
3178
3954
  }
3179
3955
  const flow = providerId === "claude" ? claudeOAuth : geminiOAuth;
3180
3956
  const { authUrl, codeVerifier, state } = flow.generateAuthParams();
@@ -3183,23 +3959,23 @@ function handleOAuthStart(providerId, deps) {
3183
3959
  }
3184
3960
  async function handleOAuthComplete(providerId, body, deps) {
3185
3961
  if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
3186
- return err2(400, `oauth not available for provider '${providerId}'`);
3962
+ return err3(400, `oauth not available for provider '${providerId}'`);
3187
3963
  }
3188
3964
  const sessionId = typeof body["sessionId"] === "string" ? body["sessionId"] : "";
3189
3965
  const rawCode = typeof body["code"] === "string" ? body["code"] : "";
3190
- if (!sessionId) return err2(400, "oauth complete requires { sessionId }");
3191
- if (!rawCode) return err2(400, "oauth complete requires { code }");
3966
+ if (!sessionId) return err3(400, "oauth complete requires { sessionId }");
3967
+ if (!rawCode) return err3(400, "oauth complete requires { code }");
3192
3968
  const session = deps.oauthSessions.peek(sessionId);
3193
- if (!session) return err2(410, "oauth session is unknown, expired, or already used");
3969
+ if (!session) return err3(410, "oauth session is unknown, expired, or already used");
3194
3970
  if (session.providerId !== providerId) {
3195
- return err2(400, `oauth session does not match provider '${providerId}'`);
3971
+ return err3(400, `oauth session does not match provider '${providerId}'`);
3196
3972
  }
3197
3973
  let code = rawCode.trim();
3198
3974
  if (providerId === "claude") {
3199
3975
  const [splitCode, pastedState] = code.split("#");
3200
- if (!splitCode) return err2(400, "no authorization code was provided");
3976
+ if (!splitCode) return err3(400, "no authorization code was provided");
3201
3977
  if (pastedState && pastedState !== session.state) {
3202
- return err2(400, "oauth state did not match (possible CSRF) \u2014 aborting");
3978
+ return err3(400, "oauth state did not match (possible CSRF) \u2014 aborting");
3203
3979
  }
3204
3980
  code = splitCode;
3205
3981
  }
@@ -3209,7 +3985,7 @@ async function handleOAuthComplete(providerId, body, deps) {
3209
3985
  block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state, exchangeFetch) : await exchangeGemini(code, session.codeVerifier, exchangeFetch);
3210
3986
  } catch (exchangeError) {
3211
3987
  const reason = exchangeError instanceof Error ? exchangeError.message : "token exchange failed";
3212
- return err2(502, `oauth token exchange failed for '${providerId}': ${reason}`);
3988
+ return err3(502, `oauth token exchange failed for '${providerId}': ${reason}`);
3213
3989
  }
3214
3990
  deps.oauthSessions.consume(sessionId);
3215
3991
  const label = typeof body["label"] === "string" && body["label"].trim() ? body["label"].trim() : void 0;
@@ -3547,8 +4323,8 @@ function errBody(message) {
3547
4323
  return { error: { type: "admin_api_error", message } };
3548
4324
  }
3549
4325
  var defaultCommandRunner = (command) => new Promise((resolve10) => {
3550
- exec(command, { timeout: 18e4 }, (err5, _stdout, stderr) => {
3551
- if (err5) resolve10({ ok: false, error: stderr.trim() || err5.message });
4326
+ exec(command, { timeout: 18e4 }, (err6, _stdout, stderr) => {
4327
+ if (err6) resolve10({ ok: false, error: stderr.trim() || err6.message });
3552
4328
  else resolve10({ ok: true });
3553
4329
  });
3554
4330
  });
@@ -3594,8 +4370,8 @@ async function handleCliLaunch(cli, body, ctx) {
3594
4370
  providerId: typeof body["providerId"] === "string" ? body["providerId"] : void 0,
3595
4371
  model: typeof body["model"] === "string" ? body["model"] : void 0
3596
4372
  });
3597
- } catch (err5) {
3598
- return { status: 400, body: errBody(err5 instanceof Error ? err5.message : "no launch target") };
4373
+ } catch (err6) {
4374
+ return { status: 400, body: errBody(err6 instanceof Error ? err6.message : "no launch target") };
3599
4375
  }
3600
4376
  const id = randomUUID2();
3601
4377
  let leaseId2;
@@ -3623,9 +4399,9 @@ async function handleCliLaunch(cli, body, ctx) {
3623
4399
  } else {
3624
4400
  launch = await buildLaunchEnv(cli, ctx.llmConfig, target);
3625
4401
  }
3626
- } catch (err5) {
3627
- const status = err5 instanceof RouteLeaseError2 ? err5.status : 400;
3628
- return { status, body: errBody(err5 instanceof Error ? err5.message : "failed to build launch env") };
4402
+ } catch (err6) {
4403
+ const status = err6 instanceof RouteLeaseError2 ? err6.status : 400;
4404
+ return { status, body: errBody(err6 instanceof Error ? err6.message : "failed to build launch env") };
3629
4405
  }
3630
4406
  const cwd = typeof body["cwd"] === "string" && body["cwd"].trim() ? body["cwd"].trim() : void 0;
3631
4407
  const opener = ctx.opener ?? defaultTerminalOpener;
@@ -3653,9 +4429,9 @@ async function handleCliLaunch(cli, body, ctx) {
3653
4429
  onFailure: onSessionEnd
3654
4430
  });
3655
4431
  if (cleanup) openerCleanup = cleanup;
3656
- } catch (err5) {
4432
+ } catch (err6) {
3657
4433
  onSessionEnd();
3658
- return { status: 500, body: errBody(err5 instanceof Error ? err5.message : "failed to open terminal") };
4434
+ return { status: 500, body: errBody(err6 instanceof Error ? err6.message : "failed to open terminal") };
3659
4435
  }
3660
4436
  if (ended) {
3661
4437
  openerCleanup?.();
@@ -4204,7 +4980,7 @@ async function handleSearchQuery(req, res, deps) {
4204
4980
  // src/admin/searchAdminView.ts
4205
4981
  var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
4206
4982
  var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
4207
- function isRecord(value) {
4983
+ function isRecord2(value) {
4208
4984
  return value !== null && typeof value === "object" && !Array.isArray(value);
4209
4985
  }
4210
4986
  function redactSearchServerConfig(search) {
@@ -4254,13 +5030,13 @@ function resolveSecretField(entry, field, stored) {
4254
5030
  else delete entry[field];
4255
5031
  }
4256
5032
  function preserveSearchSecrets(incoming, current) {
4257
- if (!isRecord(incoming)) return incoming;
5033
+ if (!isRecord2(incoming)) return incoming;
4258
5034
  const section = { ...incoming };
4259
5035
  const providersValue = section["providers"];
4260
- if (!isRecord(providersValue)) return section;
5036
+ if (!isRecord2(providersValue)) return section;
4261
5037
  const providers = {};
4262
5038
  for (const [id, entryValue] of Object.entries(providersValue)) {
4263
- if (!isRecord(entryValue)) {
5039
+ if (!isRecord2(entryValue)) {
4264
5040
  providers[id] = entryValue;
4265
5041
  continue;
4266
5042
  }
@@ -4338,7 +5114,7 @@ function parseKeyPolicyBody(body) {
4338
5114
  var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
4339
5115
  var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
4340
5116
  var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
4341
- function isRecord2(value) {
5117
+ function isRecord3(value) {
4342
5118
  return !!value && typeof value === "object" && !Array.isArray(value);
4343
5119
  }
4344
5120
  function nonBlank(value) {
@@ -4358,7 +5134,7 @@ function validateGatewayBindingsSegment(patch) {
4358
5134
  const ids = /* @__PURE__ */ new Set();
4359
5135
  raw.forEach((entry, index) => {
4360
5136
  const path2 = `bindings[${index}]`;
4361
- if (!isRecord2(entry)) {
5137
+ if (!isRecord3(entry)) {
4362
5138
  errors.push(`${path2} must be an object`);
4363
5139
  return;
4364
5140
  }
@@ -4387,12 +5163,12 @@ function validateGatewayBindingsSegment(patch) {
4387
5163
  } else if (entry.modelMappings.length > 100) {
4388
5164
  errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
4389
5165
  } else if (entry.modelMappings.some(
4390
- (mapping) => !isRecord2(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
5166
+ (mapping) => !isRecord3(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
4391
5167
  )) {
4392
5168
  errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
4393
5169
  }
4394
5170
  }
4395
- if (!isRecord2(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
5171
+ if (!isRecord3(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
4396
5172
  errors.push(`${path2}.target is invalid`);
4397
5173
  } else {
4398
5174
  if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
@@ -4407,7 +5183,7 @@ function validateGatewayBindingsSegment(patch) {
4407
5183
  }
4408
5184
  }
4409
5185
  if (entry.modelMap !== void 0) {
4410
- if (!isRecord2(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
5186
+ if (!isRecord3(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
4411
5187
  errors.push(`${path2}.modelMap must contain string values`);
4412
5188
  }
4413
5189
  }
@@ -4717,7 +5493,8 @@ var PROVIDER_KEYS = {
4717
5493
  block: "opencodego",
4718
5494
  accounts: "opencodegoAccounts",
4719
5495
  active: "activeOpencodegoAccountId"
4720
- }
5496
+ },
5497
+ kimi: { block: "kimi", accounts: "kimiAccounts", active: "activeKimiAccountId" }
4721
5498
  };
4722
5499
  function clone(value) {
4723
5500
  return JSON.parse(JSON.stringify(value));
@@ -5239,7 +6016,7 @@ async function applyImport(packString, passphrase, mode, deps, parseProviderInpu
5239
6016
  }
5240
6017
 
5241
6018
  // src/admin/adminMigration.ts
5242
- function err3(status, message) {
6019
+ function err4(status, message) {
5243
6020
  return { status, body: { error: { type: "admin_api_error", message } } };
5244
6021
  }
5245
6022
  async function handleExport(body, deps) {
@@ -5249,30 +6026,30 @@ async function handleExport(body, deps) {
5249
6026
  return { status: 200, body: { pack, version: BUNDLE_VERSION } };
5250
6027
  } catch (error) {
5251
6028
  if (error instanceof WeakPassphraseError) {
5252
- return err3(400, error.message);
6029
+ return err4(400, error.message);
5253
6030
  }
5254
- return err3(500, "failed to build the migration pack");
6031
+ return err4(500, "failed to build the migration pack");
5255
6032
  }
5256
6033
  }
5257
6034
  async function handleImport(body, deps) {
5258
6035
  const blob = typeof body["blob"] === "string" ? body["blob"] : "";
5259
6036
  const passphrase = typeof body["passphrase"] === "string" ? body["passphrase"] : "";
5260
6037
  const mode = body["mode"] === "overwrite" ? "overwrite" : "merge";
5261
- if (!blob) return err3(400, "import requires { blob }");
6038
+ if (!blob) return err4(400, "import requires { blob }");
5262
6039
  try {
5263
6040
  const counts = await applyImport(blob, passphrase, mode, deps, deps.parseProviderInput);
5264
6041
  return { status: 200, body: counts };
5265
6042
  } catch (error) {
5266
6043
  if (error instanceof WeakPassphraseError) {
5267
- return err3(400, error.message);
6044
+ return err4(400, error.message);
5268
6045
  }
5269
- return err3(400, error instanceof Error ? error.message : "import failed");
6046
+ return err4(400, error instanceof Error ? error.message : "import failed");
5270
6047
  }
5271
6048
  }
5272
6049
 
5273
6050
  // src/admin/usagePricing.ts
5274
6051
  import { getSharedUsageThroughputTracker } from "@omnicross/core/usage";
5275
- var err4 = (status, message) => ({
6052
+ var err5 = (status, message) => ({
5276
6053
  status,
5277
6054
  body: { error: { type: "admin_api_error", message } }
5278
6055
  });
@@ -5285,7 +6062,7 @@ function parseRange(query2) {
5285
6062
  const startTs = parseFiniteInt(query2.get("startTs"));
5286
6063
  const endTs = parseFiniteInt(query2.get("endTs"));
5287
6064
  if (startTs === null || endTs === null) {
5288
- return err4(400, "startTs and endTs are required finite-integer unix-millis query params");
6065
+ return err5(400, "startTs and endTs are required finite-integer unix-millis query params");
5289
6066
  }
5290
6067
  return { startTs, endTs };
5291
6068
  }
@@ -5310,14 +6087,14 @@ async function handleUsageGet(view, query2, deps) {
5310
6087
  case "timeseries": {
5311
6088
  const bucket = query2.get("bucket");
5312
6089
  if (bucket !== "hour" && bucket !== "day" && bucket !== "month") {
5313
- return err4(400, "bucket must be one of 'hour', 'day', 'month'");
6090
+ return err5(400, "bucket must be one of 'hour', 'day', 'month'");
5314
6091
  }
5315
6092
  const now = Date.now();
5316
6093
  const clamped = { startTs: range.startTs, endTs: Math.min(range.endTs, now) };
5317
6094
  if (clamped.startTs < clamped.endTs) {
5318
6095
  const projected = Math.ceil((clamped.endTs - clamped.startTs) / BUCKET_SPAN_MS[bucket]) + 1;
5319
6096
  if (projected > MAX_TIMESERIES_BUCKETS) {
5320
- return err4(
6097
+ return err5(
5321
6098
  400,
5322
6099
  `requested range projects ~${projected} '${bucket}' buckets (max ${MAX_TIMESERIES_BUCKETS}); narrow the range or use a coarser bucket`
5323
6100
  );
@@ -5340,7 +6117,7 @@ async function handleUsageGet(view, query2, deps) {
5340
6117
  };
5341
6118
  }
5342
6119
  default:
5343
- return err4(404, `unknown usage view '${view ?? ""}'`);
6120
+ return err5(404, `unknown usage view '${view ?? ""}'`);
5344
6121
  }
5345
6122
  }
5346
6123
  function poolKeyLabels(cfg) {
@@ -5389,7 +6166,7 @@ async function handlePricingList(deps) {
5389
6166
  async function handlePricingUpsert(body, deps) {
5390
6167
  const input = parsePricingEntryInput(body);
5391
6168
  if (!input) {
5392
- return err4(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
6169
+ return err5(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
5393
6170
  }
5394
6171
  const entry = await deps.pricingEngine.upsertManual(input);
5395
6172
  return { status: 200, body: { entry } };
@@ -5398,7 +6175,7 @@ async function handlePricingDelete(query2, deps) {
5398
6175
  const providerId = query2.get("providerId")?.trim() ?? "";
5399
6176
  const modelId = query2.get("modelId")?.trim() ?? "";
5400
6177
  if (!providerId || !modelId) {
5401
- return err4(400, "delete requires providerId and modelId query params");
6178
+ return err5(400, "delete requires providerId and modelId query params");
5402
6179
  }
5403
6180
  const deleted = await deps.pricingStore.delete(providerId, modelId);
5404
6181
  if (deleted) await deps.pricingEngine.invalidateCache();
@@ -5418,13 +6195,13 @@ async function handlePricingFetchLatest(deps) {
5418
6195
  }
5419
6196
  };
5420
6197
  } catch (e) {
5421
- return err4(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
6198
+ return err5(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
5422
6199
  }
5423
6200
  }
5424
6201
  async function handlePricingResolveConflicts(body, deps) {
5425
6202
  const raw = body["resolutions"];
5426
6203
  if (!Array.isArray(raw)) {
5427
- return err4(400, "resolve-conflicts requires { resolutions: [...] }");
6204
+ return err5(400, "resolve-conflicts requires { resolutions: [...] }");
5428
6205
  }
5429
6206
  const currentRows = await deps.pricingStore.getAll();
5430
6207
  const userEditedKeys = new Set(
@@ -5434,21 +6211,21 @@ async function handlePricingResolveConflicts(body, deps) {
5434
6211
  const pendingIncoming = /* @__PURE__ */ new Map();
5435
6212
  let staleCount = 0;
5436
6213
  for (const item of raw) {
5437
- if (!item || typeof item !== "object") return err4(400, "invalid resolution entry");
6214
+ if (!item || typeof item !== "object") return err5(400, "invalid resolution entry");
5438
6215
  const r = item;
5439
6216
  const action = r["action"];
5440
6217
  if (action !== "overwrite" && action !== "skip") {
5441
- return err4(400, "resolution action must be 'overwrite' or 'skip'");
6218
+ return err5(400, "resolution action must be 'overwrite' or 'skip'");
5442
6219
  }
5443
6220
  const providerId = typeof r["providerId"] === "string" && r["providerId"].trim() ? r["providerId"].trim() : "";
5444
6221
  const modelId = typeof r["modelId"] === "string" && r["modelId"].trim() ? r["modelId"].trim() : "";
5445
6222
  if (!providerId || !modelId) {
5446
- return err4(400, "each resolution requires top-level providerId and modelId");
6223
+ return err5(400, "each resolution requires top-level providerId and modelId");
5447
6224
  }
5448
6225
  const incoming = parsePricingEntryInput(r["incoming"]);
5449
- if (!incoming) return err4(400, "each resolution must echo a valid incoming pricing entry");
6226
+ if (!incoming) return err5(400, "each resolution must echo a valid incoming pricing entry");
5450
6227
  if (incoming.providerId !== providerId || incoming.modelId !== modelId) {
5451
- return err4(400, "resolution providerId/modelId must match the echoed incoming entry");
6228
+ return err5(400, "resolution providerId/modelId must match the echoed incoming entry");
5452
6229
  }
5453
6230
  const key = `${providerId}::${modelId}`;
5454
6231
  if (action === "overwrite" && !userEditedKeys.has(key)) {
@@ -5493,7 +6270,7 @@ function query(req) {
5493
6270
  }
5494
6271
  function allowanceProvider(value) {
5495
6272
  if (!value) return void 0;
5496
- return value === "claude" || value === "codex" ? value : null;
6273
+ return value === "claude" || value === "codex" || value === "kimi" || value === "opencodego" ? value : null;
5497
6274
  }
5498
6275
  async function handleAccountAllowanceApi(req, res, method, rest, service) {
5499
6276
  if (!service) return writeError2(res, 501, "account allowance service is not available");
@@ -5507,7 +6284,9 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
5507
6284
  const params = query(req);
5508
6285
  const pathProvider = rest.length >= 2 ? rest[0] : null;
5509
6286
  const providerId = allowanceProvider(pathProvider ?? params.get("providerId") ?? params.get("provider"));
5510
- if (providerId === null) return writeError2(res, 400, "providerId must be claude or codex");
6287
+ if (providerId === null) {
6288
+ return writeError2(res, 400, "providerId must be claude, codex, kimi, or opencodego");
6289
+ }
5511
6290
  const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
5512
6291
  const allowances = await service.list({ providerId, accountId });
5513
6292
  return writeJson3(res, 200, { allowances });
@@ -5517,10 +6296,37 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
5517
6296
  const requestedProvider = allowanceProvider(
5518
6297
  typeof body["providerId"] === "string" ? body["providerId"] : "claude"
5519
6298
  );
5520
- if (requestedProvider !== "claude") {
5521
- return writeError2(res, 400, "only Claude allowances support explicit refresh");
5522
- }
5523
6299
  const accountId = typeof body["accountId"] === "string" && body["accountId"].trim() ? body["accountId"].trim() : void 0;
6300
+ if (requestedProvider === "codex") {
6301
+ if (!service.refreshCodex) {
6302
+ return writeError2(res, 501, "codex allowance refresh is not available");
6303
+ }
6304
+ const allowances2 = await service.refreshCodex(accountId);
6305
+ if (accountId && allowances2.length === 0) {
6306
+ return writeError2(res, 404, `Codex account '${accountId}' not found`);
6307
+ }
6308
+ return writeJson3(res, 200, { allowances: allowances2 });
6309
+ }
6310
+ if (requestedProvider === "kimi") {
6311
+ if (!service.refreshKimi) {
6312
+ return writeError2(res, 501, "kimi allowance refresh is not available");
6313
+ }
6314
+ const allowances2 = await service.refreshKimi(accountId);
6315
+ if (accountId && allowances2.length === 0) {
6316
+ return writeError2(res, 404, `Kimi account '${accountId}' not found`);
6317
+ }
6318
+ return writeJson3(res, 200, { allowances: allowances2 });
6319
+ }
6320
+ if (requestedProvider === "opencodego") {
6321
+ if (!service.refreshOpenCodeGo) {
6322
+ return writeError2(res, 501, "opencodego allowance refresh is not available");
6323
+ }
6324
+ const allowances2 = await service.refreshOpenCodeGo(accountId);
6325
+ if (accountId && allowances2.length === 0) {
6326
+ return writeError2(res, 404, `OpenCodeGo account '${accountId}' not found`);
6327
+ }
6328
+ return writeJson3(res, 200, { allowances: allowances2 });
6329
+ }
5524
6330
  const allowances = await service.refreshClaude(accountId);
5525
6331
  if (accountId && allowances.length === 0) {
5526
6332
  return writeError2(res, 404, `Claude account '${accountId}' not found`);
@@ -5691,8 +6497,8 @@ async function handleAdminApi(req, res, path2, deps) {
5691
6497
  default:
5692
6498
  return writeJsonError(res, 404, `unknown admin resource '${resource}'`);
5693
6499
  }
5694
- } catch (err5) {
5695
- writeJsonError(res, 500, err5 instanceof Error ? err5.message : String(err5));
6500
+ } catch (err6) {
6501
+ writeJsonError(res, 500, err6 instanceof Error ? err6.message : String(err6));
5696
6502
  }
5697
6503
  }
5698
6504
  function requestQuery(req) {
@@ -5762,6 +6568,9 @@ async function handleProviders(req, res, method, rest, deps) {
5762
6568
  if (method === "POST" && rest.length === 4 && rest[1] === "keys" && rest[3] === "enabled") {
5763
6569
  return await handleToggleProviderKey(req, res, rest[0], rest[2], cfg, deps);
5764
6570
  }
6571
+ if (method === "POST" && rest.length === 5 && rest[1] === "keys" && rest[3] === "quota" && rest[4] === "refresh") {
6572
+ return await handleProviderKeyQuotaRefresh(res, rest[0], rest[2], cfg, deps);
6573
+ }
5765
6574
  if (method === "PUT" && rest.length === 3 && rest[1] === "keys") {
5766
6575
  return await handleUpdateProviderKey(req, res, rest[0], rest[2], cfg, deps);
5767
6576
  }
@@ -5860,7 +6669,7 @@ async function handleDiscoverModels(res, id, cfg) {
5860
6669
  try {
5861
6670
  const headers = { Accept: "application/json" };
5862
6671
  if (resolvedKey) headers["Authorization"] = `Bearer ${resolvedKey}`;
5863
- const response = await fetchUpstream2(url, { method: "GET", headers }, { providerId: "byo" });
6672
+ const response = await fetchUpstream5(url, { method: "GET", headers }, { providerId: "byo" });
5864
6673
  if (!response.ok) {
5865
6674
  const text = await response.text().catch(() => "");
5866
6675
  let message = text.slice(0, 300);
@@ -5877,8 +6686,8 @@ async function handleDiscoverModels(res, id, cfg) {
5877
6686
  const data = await response.json();
5878
6687
  const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
5879
6688
  return writeJson4(res, 200, { models });
5880
- } catch (err5) {
5881
- const message = err5 instanceof Error ? err5.message : String(err5);
6689
+ } catch (err6) {
6690
+ const message = err6 instanceof Error ? err6.message : String(err6);
5882
6691
  return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
5883
6692
  }
5884
6693
  }
@@ -5919,7 +6728,7 @@ async function handleTestModel(req, res, id, cfg) {
5919
6728
  }
5920
6729
  const startedAt = Date.now();
5921
6730
  try {
5922
- const response = await fetchUpstream2(
6731
+ const response = await fetchUpstream5(
5923
6732
  url,
5924
6733
  { method: "POST", headers, body: JSON.stringify(payload) },
5925
6734
  { providerId: "byo" }
@@ -5941,8 +6750,8 @@ async function handleTestModel(req, res, id, cfg) {
5941
6750
  latencyMs,
5942
6751
  sample: extractSampleText(text, row.apiFormat)
5943
6752
  });
5944
- } catch (err5) {
5945
- const message = err5 instanceof Error ? err5.message : String(err5);
6753
+ } catch (err6) {
6754
+ const message = err6 instanceof Error ? err6.message : String(err6);
5946
6755
  return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
5947
6756
  }
5948
6757
  }
@@ -5984,7 +6793,30 @@ async function handleProviderKeys(res, id, cfg, deps) {
5984
6793
  const row = cfg.providers.find((p) => p.id === id);
5985
6794
  if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
5986
6795
  const cooldown = await deps.apiKeyPool.getKeyHealth(id);
5987
- return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
6796
+ const views = toPoolKeyView(row, cooldown, deps);
6797
+ if (deps.providerKeyQuota) {
6798
+ const quotas = await Promise.allSettled(
6799
+ views.map((view) => deps.providerKeyQuota.quotaFor(row, view.id))
6800
+ );
6801
+ views.forEach((view, index) => {
6802
+ const settled = quotas[index];
6803
+ if (settled.status === "fulfilled" && settled.value) view.quota = settled.value;
6804
+ });
6805
+ }
6806
+ return writeJson4(res, 200, { keys: views });
6807
+ }
6808
+ async function handleProviderKeyQuotaRefresh(res, id, keyId, cfg, deps) {
6809
+ if (!deps.providerKeyQuota) return writeJsonError(res, 501, "provider key quota is not available");
6810
+ if (!id || !keyId) return writeJsonError(res, 400, "provider id and key id required in path");
6811
+ const row = cfg.providers.find((p) => p.id === id);
6812
+ if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
6813
+ try {
6814
+ const quota = await deps.providerKeyQuota.quotaFor(row, keyId, { force: true });
6815
+ if (!quota) return writeJsonError(res, 404, `no quota endpoint for key '${keyId}'`);
6816
+ return writeJson4(res, 200, { quota });
6817
+ } catch {
6818
+ return writeJsonError(res, 502, "quota refresh failed");
6819
+ }
5988
6820
  }
5989
6821
  function parsePoolKeyInput(body, existing) {
5990
6822
  const out = {};
@@ -6729,12 +7561,12 @@ async function handleAccounts(req, res, method, rest, deps) {
6729
7561
  }
6730
7562
  return writeJson4(res, 200, { ok: true, affected: result.affected });
6731
7563
  }
6732
- if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
6733
- const result = handleCodexOAuthStatus(rest[2], deps);
7564
+ if (method === "GET" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[3] === "status") {
7565
+ const result = rest[0] === "codex" ? handleCodexOAuthStatus(rest[2], deps) : handleKimiOAuthStatus(rest[2], deps);
6734
7566
  return writeJson4(res, result.status, result.body);
6735
7567
  }
6736
- if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
6737
- const result = handleCodexOAuthCancel(rest[2], deps);
7568
+ if (method === "DELETE" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[2]) {
7569
+ const result = rest[0] === "codex" ? handleCodexOAuthCancel(rest[2], deps) : handleKimiOAuthCancel(rest[2], deps);
6738
7570
  return writeJson4(res, result.status, result.body);
6739
7571
  }
6740
7572
  if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
@@ -6787,7 +7619,15 @@ async function handleAccounts(req, res, method, rest, deps) {
6787
7619
  return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
6788
7620
  }
6789
7621
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
6790
- const result = providerId === "codex" ? handleCodexOAuthStart(deps) : handleOAuthStart(providerId, deps);
7622
+ if (providerId === "codex") {
7623
+ const result2 = handleCodexOAuthStart(deps);
7624
+ return writeJson4(res, result2.status, result2.body);
7625
+ }
7626
+ if (providerId === "kimi") {
7627
+ const result2 = await handleKimiOAuthStart(deps);
7628
+ return writeJson4(res, result2.status, result2.body);
7629
+ }
7630
+ const result = handleOAuthStart(providerId, deps);
6791
7631
  return writeJson4(res, result.status, result.body);
6792
7632
  }
6793
7633
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
@@ -7281,12 +8121,12 @@ async function handlePlayground(req, res, method, deps) {
7281
8121
  const payload = body["body"];
7282
8122
  const status = deps.outboundApiServer.getStatus();
7283
8123
  if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
7284
- const path2 = resolvePlaygroundPath(endpoint, isRecord3(payload) ? payload : {});
8124
+ const path2 = resolvePlaygroundPath(endpoint, isRecord4(payload) ? payload : {});
7285
8125
  if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
7286
8126
  const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
7287
8127
  await proxyToOutbound(res, status.port, path2, key, upstreamBody);
7288
8128
  }
7289
- function isRecord3(v) {
8129
+ function isRecord4(v) {
7290
8130
  return !!v && typeof v === "object" && !Array.isArray(v);
7291
8131
  }
7292
8132
  function proxyToOutbound(res, outboundPort, path2, key, body) {
@@ -7315,8 +8155,8 @@ function proxyToOutbound(res, outboundPort, path2, key, body) {
7315
8155
  });
7316
8156
  }
7317
8157
  );
7318
- upstream.on("error", (err5) => {
7319
- if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err5.message}`);
8158
+ upstream.on("error", (err6) => {
8159
+ if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err6.message}`);
7320
8160
  else res.end();
7321
8161
  resolve10();
7322
8162
  });
@@ -7421,7 +8261,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
7421
8261
  }
7422
8262
 
7423
8263
  // src/admin/version.ts
7424
- var DAEMON_VERSION = true ? "0.3.0" : "0.0.0-dev";
8264
+ var DAEMON_VERSION = true ? "0.3.1" : "0.0.0-dev";
7425
8265
 
7426
8266
  // src/admin/AdminServer.ts
7427
8267
  var LOOPBACK_ADDR = "127.0.0.1";
@@ -7464,13 +8304,13 @@ var AdminServer = class {
7464
8304
  const server = http2.createServer((req, res) => {
7465
8305
  this.onRequest(req, res);
7466
8306
  });
7467
- const onError = (err5) => {
7468
- if (err5.code === "EADDRINUSE" && port !== 0) {
8307
+ const onError = (err6) => {
8308
+ if (err6.code === "EADDRINUSE" && port !== 0) {
7469
8309
  server.removeListener("error", onError);
7470
8310
  this.listen(bindAddr, 0).then(resolve10, reject);
7471
8311
  return;
7472
8312
  }
7473
- reject(err5);
8313
+ reject(err6);
7474
8314
  };
7475
8315
  server.on("error", onError);
7476
8316
  server.listen(port, bindAddr, () => {
@@ -7488,8 +8328,8 @@ var AdminServer = class {
7488
8328
  }
7489
8329
  /** Per-request handler: auth gate (when a token is set) → routing. */
7490
8330
  onRequest(req, res) {
7491
- void this.dispatch(req, res).catch((err5) => {
7492
- const message = err5 instanceof Error ? err5.message : String(err5);
8331
+ void this.dispatch(req, res).catch((err6) => {
8332
+ const message = err6 instanceof Error ? err6.message : String(err6);
7493
8333
  this.deps.logger.error("[AdminServer] unhandled error:", message);
7494
8334
  if (!res.headersSent) {
7495
8335
  res.writeHead(500, { "Content-Type": "application/json" });
@@ -7753,18 +8593,18 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
7753
8593
  return;
7754
8594
  }
7755
8595
  signal?.addEventListener("abort", abort, { once: true });
7756
- server.on("error", (err5) => {
8596
+ server.on("error", (err6) => {
7757
8597
  if (settled) return;
7758
8598
  settled = true;
7759
8599
  clearTimeout(timer);
7760
- if (err5.code === "EADDRINUSE") {
8600
+ if (err6.code === "EADDRINUSE") {
7761
8601
  reject(
7762
8602
  new Error(
7763
8603
  `login: cannot bind ${LOOPBACK_HOST}:${LOOPBACK_PORT} (address in use) \u2014 another codex login or process is holding the port`
7764
8604
  )
7765
8605
  );
7766
8606
  } else {
7767
- reject(err5);
8607
+ reject(err6);
7768
8608
  }
7769
8609
  });
7770
8610
  const timer = setTimeout(() => {
@@ -7839,6 +8679,411 @@ function createPoolKeysLoader(getProviderRow, autoDisabled) {
7839
8679
  };
7840
8680
  }
7841
8681
 
8682
+ // src/allowance/ProviderKeyQuotaService.ts
8683
+ import { fetchUpstream as fetchUpstream6 } from "@omnicross/core/pipeline/upstreamFetch";
8684
+
8685
+ // src/allowance/ProviderKeyQuota.ts
8686
+ var MINUTE_MS2 = 6e4;
8687
+ var HOUR_MS2 = 60 * MINUTE_MS2;
8688
+ var DAY_MS2 = 24 * HOUR_MS2;
8689
+ var WEEK_MS = 7 * DAY_MS2;
8690
+ var MONTH_MS = 30 * DAY_MS2;
8691
+ function finiteNumber3(value) {
8692
+ if (value === null || value === void 0 || value === "") return void 0;
8693
+ const parsed = typeof value === "number" ? value : Number(value);
8694
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
8695
+ }
8696
+ function finitePercent4(value) {
8697
+ const parsed = finiteNumber3(value);
8698
+ return parsed !== void 0 && parsed <= 100 ? parsed : null;
8699
+ }
8700
+ function isoInstant3(value) {
8701
+ if (typeof value === "string" && value.trim()) {
8702
+ const time = Date.parse(value);
8703
+ if (Number.isFinite(time)) return new Date(time).toISOString();
8704
+ }
8705
+ const numeric = finiteNumber3(value);
8706
+ if (numeric !== void 0 && numeric > 1e9) {
8707
+ const ms = numeric > 1e12 ? numeric : numeric * 1e3;
8708
+ return new Date(ms).toISOString();
8709
+ }
8710
+ return void 0;
8711
+ }
8712
+ function secondsUntil5(instant, now) {
8713
+ if (!instant) return void 0;
8714
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
8715
+ }
8716
+ function isRecord5(value) {
8717
+ return !!value && typeof value === "object" && !Array.isArray(value);
8718
+ }
8719
+ function detectProviderKeyQuotaAdapter(baseUrl) {
8720
+ if (!baseUrl) return null;
8721
+ let url;
8722
+ try {
8723
+ url = new URL(baseUrl);
8724
+ } catch {
8725
+ return null;
8726
+ }
8727
+ const host = url.hostname.toLowerCase();
8728
+ const path2 = url.pathname.toLowerCase();
8729
+ if ((host === "api.z.ai" || host === "open.bigmodel.cn") && path2.includes("/coding")) {
8730
+ return "zai";
8731
+ }
8732
+ if ((host === "api.minimax.io" || host === "api.minimaxi.com") && // Token Plan rides the plain openai `/v1` (chat completions) surface; the
8733
+ // anthropic `/anthropic` rows are excluded (their usage impl is unverified).
8734
+ (path2 === "/v1" || path2 === "/v1/" || path2 === "" || path2 === "/")) {
8735
+ return "minimax-token-plan";
8736
+ }
8737
+ if (host === "api.code.umans.ai") return "umans";
8738
+ if (host === "api.synthetic.new") return "synthetic";
8739
+ return null;
8740
+ }
8741
+ function providerKeyQuotaUrl(adapter, baseUrl) {
8742
+ const origin = new URL(baseUrl).origin;
8743
+ if (adapter === "zai") return `${origin}/api/monitor/usage/quota/limit`;
8744
+ if (adapter === "minimax-token-plan") return `${origin}/v1/token_plan/remains`;
8745
+ if (adapter === "umans") return `${origin}/v1/usage`;
8746
+ return `${origin}/v2/quotas`;
8747
+ }
8748
+ function providerKeyQuotaAuthHeader(adapter, key) {
8749
+ return adapter === "zai" ? key : `Bearer ${key}`;
8750
+ }
8751
+ function zaiWindowDurationMs(item) {
8752
+ const count = item.number !== void 0 && item.number > 0 ? item.number : 1;
8753
+ switch (item.unit) {
8754
+ case 3:
8755
+ return count * HOUR_MS2;
8756
+ case 4:
8757
+ return count * DAY_MS2;
8758
+ case 5:
8759
+ return count * MONTH_MS;
8760
+ case 6:
8761
+ return WEEK_MS;
8762
+ default:
8763
+ return void 0;
8764
+ }
8765
+ }
8766
+ function zaiWindowIdLabel(durationMs) {
8767
+ if (durationMs === WEEK_MS) return { id: "seven-day", label: "7 days" };
8768
+ if (durationMs === 5 * HOUR_MS2) return { id: "five-hour", label: "5 hours" };
8769
+ if (durationMs === MONTH_MS) return { id: "thirty-day", label: "30 days" };
8770
+ if (durationMs !== void 0 && durationMs % DAY_MS2 === 0) {
8771
+ const days = durationMs / DAY_MS2;
8772
+ return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}` };
8773
+ }
8774
+ if (durationMs !== void 0 && durationMs % HOUR_MS2 === 0) {
8775
+ const hours = durationMs / HOUR_MS2;
8776
+ return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}` };
8777
+ }
8778
+ return { id: "quota", label: "Quota" };
8779
+ }
8780
+ function parseZaiQuotaPayload(payload, now) {
8781
+ if (!isRecord5(payload)) return null;
8782
+ const data = isRecord5(payload["data"]) ? payload["data"] : payload;
8783
+ if (payload["success"] === false) return null;
8784
+ const limits = Array.isArray(data["limits"]) ? data["limits"] : [];
8785
+ const byWindow = /* @__PURE__ */ new Map();
8786
+ for (const raw of limits) {
8787
+ if (!isRecord5(raw)) continue;
8788
+ const item = raw;
8789
+ if (item.type === void 0) continue;
8790
+ const details = raw["usageDetails"];
8791
+ if (Array.isArray(details) && details.some((d) => isRecord5(d) && d["modelCode"] === "zread")) {
8792
+ continue;
8793
+ }
8794
+ const durationMs = zaiWindowDurationMs(item);
8795
+ const { id, label } = zaiWindowIdLabel(durationMs);
8796
+ const limit = finiteNumber3(item.usage);
8797
+ const used = finiteNumber3(item.currentValue);
8798
+ const fromAbsolute = limit !== void 0 && used !== void 0 && limit > 0 ? Math.min(100, used / limit * 100) : void 0;
8799
+ const fromPercentage = finitePercent4(item.percentage) ?? void 0;
8800
+ const usedPercent = fromAbsolute !== void 0 ? Math.round(fromAbsolute * 10) / 10 : fromPercentage;
8801
+ if (usedPercent === void 0) continue;
8802
+ const resetsAt = isoInstant3(item.nextResetTime);
8803
+ const candidate = {
8804
+ id,
8805
+ label,
8806
+ scope: "all",
8807
+ usedPercent,
8808
+ ...durationMs !== void 0 ? { windowMinutes: Math.round(durationMs / MINUTE_MS2) } : {},
8809
+ ...resetsAt !== void 0 ? { resetsAt } : {},
8810
+ remainingSeconds: secondsUntil5(resetsAt, now),
8811
+ state: "fresh"
8812
+ };
8813
+ const existing = byWindow.get(id);
8814
+ if (!existing || (candidate.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
8815
+ byWindow.set(id, candidate);
8816
+ }
8817
+ }
8818
+ const windows = [...byWindow.values()].sort((a, b) => (a.windowMinutes ?? Number.POSITIVE_INFINITY) - (b.windowMinutes ?? Number.POSITIVE_INFINITY));
8819
+ return windows.length > 0 ? windows.slice(0, 4) : null;
8820
+ }
8821
+ var MINIMAX_STATUS_EXHAUSTED = 2;
8822
+ var MINIMAX_SHARED_BUCKET = "general";
8823
+ function parseMiniMaxBucket(value) {
8824
+ if (!isRecord5(value)) return null;
8825
+ const modelName = typeof value["model_name"] === "string" ? value["model_name"].trim() : "";
8826
+ if (!modelName) return null;
8827
+ const instant = (v) => {
8828
+ const n = finiteNumber3(v);
8829
+ return n !== void 0 && n > 1e9 ? n > 1e12 ? n : n * 1e3 : void 0;
8830
+ };
8831
+ return {
8832
+ modelName,
8833
+ intervalEnd: instant(value["end_time"]),
8834
+ intervalRemainingPercent: finiteNumber3(value["current_interval_remaining_percent"]),
8835
+ intervalStatus: finiteNumber3(value["current_interval_status"]),
8836
+ weeklyEnd: instant(value["weekly_end_time"]),
8837
+ weeklyRemainingPercent: finiteNumber3(value["current_weekly_remaining_percent"]),
8838
+ weeklyStatus: finiteNumber3(value["current_weekly_status"])
8839
+ };
8840
+ }
8841
+ function minimaxWindow(id, label, windowMinutes, resetsAtMs, remainingPercent, status, now) {
8842
+ const usedPercent = status === MINIMAX_STATUS_EXHAUSTED ? 100 : remainingPercent !== void 0 ? Math.round((100 - remainingPercent) * 10) / 10 : null;
8843
+ const resetsAt = resetsAtMs !== void 0 ? new Date(resetsAtMs).toISOString() : void 0;
8844
+ return {
8845
+ id,
8846
+ label,
8847
+ scope: "all",
8848
+ usedPercent,
8849
+ ...windowMinutes !== void 0 ? { windowMinutes } : {},
8850
+ ...resetsAt !== void 0 ? { resetsAt } : {},
8851
+ remainingSeconds: secondsUntil5(resetsAt, now),
8852
+ state: usedPercent !== null ? "fresh" : "unavailable"
8853
+ };
8854
+ }
8855
+ function parseMiniMaxTokenPlanPayload(payload, now) {
8856
+ if (!isRecord5(payload)) return null;
8857
+ const baseResp = payload["base_resp"];
8858
+ if (!isRecord5(baseResp) || baseResp["status_code"] !== 0) return null;
8859
+ const buckets = Array.isArray(payload["model_remains"]) ? payload["model_remains"] : [];
8860
+ let general = null;
8861
+ for (const raw of buckets) {
8862
+ const bucket = parseMiniMaxBucket(raw);
8863
+ if (bucket?.modelName === MINIMAX_SHARED_BUCKET) {
8864
+ general = bucket;
8865
+ break;
8866
+ }
8867
+ }
8868
+ if (!general) return null;
8869
+ return [
8870
+ minimaxWindow(
8871
+ "five-hour",
8872
+ "5 hours",
8873
+ 5 * 60,
8874
+ general.intervalEnd,
8875
+ general.intervalRemainingPercent,
8876
+ general.intervalStatus,
8877
+ now
8878
+ ),
8879
+ minimaxWindow(
8880
+ "seven-day",
8881
+ "7 days",
8882
+ Math.round(WEEK_MS / MINUTE_MS2),
8883
+ general.weeklyEnd,
8884
+ general.weeklyRemainingPercent,
8885
+ general.weeklyStatus,
8886
+ now
8887
+ )
8888
+ ];
8889
+ }
8890
+ function parseUmansUsagePayload(payload, now) {
8891
+ if (!isRecord5(payload)) return null;
8892
+ const limits = isRecord5(payload["limits"]) ? payload["limits"] : void 0;
8893
+ const requests = limits && isRecord5(limits["requests"]) ? limits["requests"] : void 0;
8894
+ const usage = isRecord5(payload["usage"]) ? payload["usage"] : void 0;
8895
+ const window = isRecord5(payload["window"]) ? payload["window"] : void 0;
8896
+ const hardCap = finiteNumber3(requests?.["hard_cap"]);
8897
+ const softLimit = finiteNumber3(requests?.["limit"]);
8898
+ const requestsInWindow = finiteNumber3(usage?.["requests_in_window"]);
8899
+ const weightedInWindow = finiteNumber3(usage?.["weighted_in_window"]);
8900
+ const resetsAt = isoInstant3(window?.["resets_at"]);
8901
+ let usedPercent = null;
8902
+ if (hardCap !== void 0 && hardCap > 0 && requestsInWindow !== void 0) {
8903
+ usedPercent = Math.round(Math.min(100, requestsInWindow / hardCap * 100) * 10) / 10;
8904
+ } else if (softLimit !== void 0 && softLimit > 0 && weightedInWindow !== void 0) {
8905
+ usedPercent = Math.round(Math.min(100, weightedInWindow / softLimit * 100) * 10) / 10;
8906
+ }
8907
+ if (usedPercent === null && resetsAt === void 0) return null;
8908
+ return [
8909
+ {
8910
+ id: "five-hour",
8911
+ label: "5 hours",
8912
+ scope: "all",
8913
+ usedPercent,
8914
+ windowMinutes: 5 * 60,
8915
+ ...resetsAt !== void 0 ? { resetsAt } : {},
8916
+ remainingSeconds: secondsUntil5(resetsAt, now),
8917
+ state: "fresh"
8918
+ }
8919
+ ];
8920
+ }
8921
+ function parseSyntheticQuotasPayload(payload, now) {
8922
+ if (!isRecord5(payload)) return null;
8923
+ const fiveHour = isRecord5(payload["rollingFiveHourLimit"]) ? payload["rollingFiveHourLimit"] : void 0;
8924
+ const weekly = isRecord5(payload["weeklyTokenLimit"]) ? payload["weeklyTokenLimit"] : void 0;
8925
+ const windows = [];
8926
+ if (fiveHour) {
8927
+ const max = finiteNumber3(fiveHour["max"]);
8928
+ const remaining = finiteNumber3(fiveHour["remaining"]);
8929
+ const usedPercent = max !== void 0 && max > 0 && remaining !== void 0 ? Math.round(Math.min(100, (max - remaining) / max * 100) * 10) / 10 : null;
8930
+ const resetsAt = isoInstant3(fiveHour["nextTickAt"]);
8931
+ windows.push({
8932
+ id: "five-hour",
8933
+ label: "5 hours",
8934
+ scope: "all",
8935
+ usedPercent,
8936
+ windowMinutes: 5 * 60,
8937
+ ...resetsAt !== void 0 ? { resetsAt } : {},
8938
+ remainingSeconds: secondsUntil5(resetsAt, now),
8939
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
8940
+ });
8941
+ }
8942
+ if (weekly) {
8943
+ const percentRemaining = finiteNumber3(weekly["percentRemaining"]);
8944
+ const usedPercent = percentRemaining !== void 0 ? Math.round(Math.min(100, Math.max(0, 100 - percentRemaining)) * 10) / 10 : null;
8945
+ const resetsAt = isoInstant3(weekly["nextRegenAt"]);
8946
+ windows.push({
8947
+ id: "seven-day",
8948
+ label: "7 days",
8949
+ scope: "all",
8950
+ usedPercent,
8951
+ windowMinutes: 7 * 24 * 60,
8952
+ ...resetsAt !== void 0 ? { resetsAt } : {},
8953
+ remainingSeconds: secondsUntil5(resetsAt, now),
8954
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
8955
+ });
8956
+ }
8957
+ return windows.length > 0 ? windows : null;
8958
+ }
8959
+
8960
+ // src/allowance/ProviderKeyQuotaService.ts
8961
+ function parseQuotaPayload(adapter, payload, now) {
8962
+ switch (adapter) {
8963
+ case "zai":
8964
+ return parseZaiQuotaPayload(payload, now);
8965
+ case "minimax-token-plan":
8966
+ return parseMiniMaxTokenPlanPayload(payload, now);
8967
+ case "umans":
8968
+ return parseUmansUsagePayload(payload, now);
8969
+ case "synthetic":
8970
+ return parseSyntheticQuotasPayload(payload, now);
8971
+ }
8972
+ }
8973
+ var PROVIDER_KEY_QUOTA_CACHE_MS = 5 * 6e4;
8974
+ function resolvedBaseUrl(row) {
8975
+ const modes = row.apiModes ?? [];
8976
+ const selected = row.selectedApiModeId ? modes.find((mode) => mode.id === row.selectedApiModeId) : void 0;
8977
+ const fallback = modes[0];
8978
+ const modeBase = selected?.baseUrl ?? fallback?.baseUrl;
8979
+ return modeBase ?? row.codingPlan?.baseUrl ?? row.baseUrl;
8980
+ }
8981
+ function rowKeyEntries(row) {
8982
+ const pool = (row.apiKeys ?? []).filter((entry) => entry.apiKey.length > 0);
8983
+ if (pool.length > 0) return pool.map((entry) => ({ id: entry.id, apiKey: entry.apiKey }));
8984
+ if (row.apiKey.length > 0) {
8985
+ return [{ id: `${row.id}:default`, apiKey: row.apiKey }];
8986
+ }
8987
+ return [];
8988
+ }
8989
+ var ProviderKeyQuotaService = class {
8990
+ constructor(box, fetchImpl = (url, init) => fetchUpstream6(url, init, { redactBodies: true }), now = Date.now) {
8991
+ this.box = box;
8992
+ this.fetchImpl = fetchImpl;
8993
+ this.now = now;
8994
+ }
8995
+ box;
8996
+ fetchImpl;
8997
+ now;
8998
+ cache = /* @__PURE__ */ new Map();
8999
+ inFlight = /* @__PURE__ */ new Map();
9000
+ /**
9001
+ * Quota for one key of a provider row, or `null` when the row has no quota
9002
+ * adapter / no such key. Cache-first; concurrent reads share one flight.
9003
+ */
9004
+ async quotaFor(row, keyId, options = {}) {
9005
+ const adapter = detectProviderKeyQuotaAdapter(resolvedBaseUrl(row));
9006
+ if (!adapter) return null;
9007
+ const entry = rowKeyEntries(row).find((candidate) => candidate.id === keyId);
9008
+ if (!entry) return null;
9009
+ const cacheKey = `${row.id}\0${keyId}`;
9010
+ const now = this.now();
9011
+ const cached = this.cache.get(cacheKey);
9012
+ if (!options.force && cached && Date.parse(cached.expiresAt) > now) return cached;
9013
+ const running = this.inFlight.get(cacheKey);
9014
+ if (running) return running;
9015
+ const promise = this.fetchQuota(adapter, row, entry.apiKey, cacheKey).catch((error) => {
9016
+ void error;
9017
+ const previous = this.cache.get(cacheKey);
9018
+ if (previous) {
9019
+ const degraded = {
9020
+ ...previous,
9021
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
9022
+ windows: previous.windows.map((window) => ({
9023
+ ...window,
9024
+ state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
9025
+ })),
9026
+ errorCode: "quota_request_failed"
9027
+ };
9028
+ this.cache.set(cacheKey, degraded);
9029
+ return degraded;
9030
+ }
9031
+ return null;
9032
+ }).finally(() => this.inFlight.delete(cacheKey));
9033
+ this.inFlight.set(cacheKey, promise);
9034
+ return promise;
9035
+ }
9036
+ /** Drop cached rows for a provider (key added/removed/rotated). */
9037
+ invalidateProvider(providerRowId) {
9038
+ for (const key of this.cache.keys()) {
9039
+ if (key.startsWith(`${providerRowId}\0`)) this.cache.delete(key);
9040
+ }
9041
+ }
9042
+ async fetchQuota(adapter, row, rawKey, cacheKey) {
9043
+ const baseUrl = resolvedBaseUrl(row);
9044
+ const url = providerKeyQuotaUrl(adapter, baseUrl);
9045
+ const key = this.box.decryptMaybe(rawKey);
9046
+ const now = this.now();
9047
+ const response = await this.fetchImpl(url, {
9048
+ method: "GET",
9049
+ headers: {
9050
+ Authorization: providerKeyQuotaAuthHeader(adapter, key),
9051
+ Accept: "application/json",
9052
+ "Content-Type": "application/json"
9053
+ },
9054
+ signal: AbortSignal.timeout(15e3)
9055
+ });
9056
+ if (response.status === 401 || response.status === 403) {
9057
+ const snapshot2 = {
9058
+ adapter,
9059
+ observedAt: new Date(now).toISOString(),
9060
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
9061
+ windows: [],
9062
+ errorCode: "quota_unauthorized"
9063
+ };
9064
+ this.cache.set(cacheKey, snapshot2);
9065
+ return snapshot2;
9066
+ }
9067
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
9068
+ let payload;
9069
+ try {
9070
+ payload = await response.json();
9071
+ } catch {
9072
+ throw new Error("invalid JSON");
9073
+ }
9074
+ const windows = parseQuotaPayload(adapter, payload, now);
9075
+ const snapshot = {
9076
+ adapter,
9077
+ observedAt: new Date(now).toISOString(),
9078
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
9079
+ windows: windows ?? [],
9080
+ ...windows ? {} : { errorCode: "quota_unavailable" }
9081
+ };
9082
+ this.cache.set(cacheKey, snapshot);
9083
+ return snapshot;
9084
+ }
9085
+ };
9086
+
7842
9087
  // src/commands/paths.ts
7843
9088
  import { dirname as dirname5, join as join5 } from "path";
7844
9089
  function defaultVouchersPath(configPath) {
@@ -13988,21 +15233,23 @@ function bucketLabel(bucketStartTs, bucket) {
13988
15233
  }
13989
15234
 
13990
15235
  // src/ports/JsonOutboundKeyDb.ts
15236
+ import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
15237
+ import {
15238
+ validateOutboundPermissions as validateOutboundPermissions3
15239
+ } from "@omnicross/core";
15240
+
15241
+ // src/ports/atomicFile.ts
13991
15242
  import { randomBytes as randomBytes11 } from "crypto";
13992
15243
  import {
13993
15244
  closeSync as closeSync7,
13994
15245
  existsSync as existsSync16,
13995
15246
  fsyncSync as fsyncSync7,
13996
15247
  openSync as openSync7,
13997
- readFileSync as readFileSync13,
13998
15248
  renameSync as renameSync9,
13999
15249
  unlinkSync as unlinkSync11,
14000
15250
  writeFileSync as writeFileSync12
14001
15251
  } from "fs";
14002
15252
  import { basename as basename8, dirname as dirname14, join as join16 } from "path";
14003
- import {
14004
- validateOutboundPermissions as validateOutboundPermissions3
14005
- } from "@omnicross/core";
14006
15253
  function atomicReplaceUtf8(targetPath, contents) {
14007
15254
  const tempPath = join16(
14008
15255
  dirname14(targetPath),
@@ -14032,6 +15279,8 @@ function atomicReplaceUtf8(targetPath, contents) {
14032
15279
  throw error;
14033
15280
  }
14034
15281
  }
15282
+
15283
+ // src/ports/JsonOutboundKeyDb.ts
14035
15284
  var JsonOutboundKeyDb = class {
14036
15285
  /**
14037
15286
  * @param secretBox OPTIONAL reversible-secret codec. When present, a created
@@ -14174,7 +15423,7 @@ var JsonOutboundKeyDb = class {
14174
15423
  }
14175
15424
  /** Read the key rows, tolerating a missing/corrupt file (→ empty list). */
14176
15425
  readRows() {
14177
- if (!existsSync16(this.keysPath)) return [];
15426
+ if (!existsSync17(this.keysPath)) return [];
14178
15427
  try {
14179
15428
  const parsed = JSON.parse(readFileSync13(this.keysPath, "utf8"));
14180
15429
  return Array.isArray(parsed) ? parsed : [];
@@ -14193,7 +15442,7 @@ function applyPolicyField(row, field, value) {
14193
15442
  }
14194
15443
 
14195
15444
  // src/ports/JsonPricingStore.ts
14196
- import { existsSync as existsSync17, readFileSync as readFileSync14, renameSync as renameSync10, rmSync as rmSync3, writeFileSync as writeFileSync13 } from "fs";
15445
+ import { existsSync as existsSync18, readFileSync as readFileSync14, renameSync as renameSync10, rmSync as rmSync3, writeFileSync as writeFileSync13 } from "fs";
14197
15446
  import { randomUUID as randomUUID5 } from "crypto";
14198
15447
  var JsonPricingStore = class {
14199
15448
  constructor(pricingPath) {
@@ -14208,7 +15457,7 @@ var JsonPricingStore = class {
14208
15457
  * otherwise unusable pricing table after a crash or manual file edit.
14209
15458
  */
14210
15459
  hasUsableSnapshot() {
14211
- if (!existsSync17(this.pricingPath)) return false;
15460
+ if (!existsSync18(this.pricingPath)) return false;
14212
15461
  try {
14213
15462
  const parsed = JSON.parse(readFileSync14(this.pricingPath, "utf8"));
14214
15463
  return Array.isArray(parsed) && parsed.some(isUsablePricingRow);
@@ -14323,7 +15572,7 @@ var JsonPricingStore = class {
14323
15572
  }
14324
15573
  /** Read the pricing rows, tolerating a missing/corrupt file (→ empty list). */
14325
15574
  readRows() {
14326
- if (!existsSync17(this.pricingPath)) return [];
15575
+ if (!existsSync18(this.pricingPath)) return [];
14327
15576
  try {
14328
15577
  const parsed = JSON.parse(readFileSync14(this.pricingPath, "utf8"));
14329
15578
  return Array.isArray(parsed) ? parsed : [];
@@ -14355,7 +15604,7 @@ function isUsablePricingRow(value) {
14355
15604
  }
14356
15605
 
14357
15606
  // src/pricing/PricingRefreshScheduler.ts
14358
- import { existsSync as existsSync18, readFileSync as readFileSync15, renameSync as renameSync11, writeFileSync as writeFileSync14 } from "fs";
15607
+ import { existsSync as existsSync19, readFileSync as readFileSync15, renameSync as renameSync11, writeFileSync as writeFileSync14 } from "fs";
14359
15608
  var EMPTY_STATE2 = {
14360
15609
  lastAttemptAt: null,
14361
15610
  lastSuccessAt: null,
@@ -14393,7 +15642,7 @@ var PricingRefreshScheduler = class {
14393
15642
  this.timer = null;
14394
15643
  }
14395
15644
  getState() {
14396
- if (!existsSync18(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
15645
+ if (!existsSync19(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
14397
15646
  try {
14398
15647
  const value = JSON.parse(readFileSync15(this.statePath, "utf8"));
14399
15648
  return {
@@ -14458,7 +15707,7 @@ function finiteOrNull(value) {
14458
15707
  }
14459
15708
 
14460
15709
  // src/ports/JsonVoucherDb.ts
14461
- import { existsSync as existsSync19, readFileSync as readFileSync16, writeFileSync as writeFileSync15 } from "fs";
15710
+ import { existsSync as existsSync20, readFileSync as readFileSync16, writeFileSync as writeFileSync15 } from "fs";
14462
15711
  var JsonVoucherDb = class {
14463
15712
  constructor(vouchersPath) {
14464
15713
  this.vouchersPath = vouchersPath;
@@ -14536,7 +15785,7 @@ var JsonVoucherDb = class {
14536
15785
  }
14537
15786
  /** Read the voucher rows, tolerating a missing/corrupt file (→ empty list). */
14538
15787
  readRows() {
14539
- if (!existsSync19(this.vouchersPath)) return [];
15788
+ if (!existsSync20(this.vouchersPath)) return [];
14540
15789
  try {
14541
15790
  const parsed = JSON.parse(readFileSync16(this.vouchersPath, "utf8"));
14542
15791
  return Array.isArray(parsed) ? parsed : [];
@@ -14550,16 +15799,17 @@ var JsonVoucherDb = class {
14550
15799
  };
14551
15800
 
14552
15801
  // src/ports/JsonSubscriptionCredentialStore.ts
14553
- import { existsSync as existsSync21, mkdirSync as mkdirSync6, readFileSync as readFileSync18, writeFileSync as writeFileSync16 } from "fs";
15802
+ import { existsSync as existsSync22, mkdirSync as mkdirSync6, readFileSync as readFileSync18, renameSync as renameSync12 } from "fs";
14554
15803
  import { dirname as dirname15 } from "path";
14555
15804
  import { getSharedAccountHealth as getSharedAccountHealth2 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
14556
15805
  import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling3 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
14557
- import { fetchUpstream as fetchUpstream3 } from "@omnicross/core/pipeline/upstreamFetch";
15806
+ import { fetchUpstream as fetchUpstream7 } from "@omnicross/core/pipeline/upstreamFetch";
14558
15807
  import { getSharedIdentityStore as getSharedIdentityStore2 } from "@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore";
14559
15808
  import {
14560
15809
  claudeOAuth as claudeOAuth2,
14561
15810
  codexOAuth as codexOAuth2,
14562
- geminiOAuth as geminiOAuth2
15811
+ geminiOAuth as geminiOAuth2,
15812
+ kimiOAuth as kimiOAuth2
14563
15813
  } from "@omnicross/subscriptions";
14564
15814
 
14565
15815
  // src/ports/account-sync.ts
@@ -14604,7 +15854,7 @@ function findDuplicateCredentialIds(accounts) {
14604
15854
  }
14605
15855
 
14606
15856
  // src/ports/external-cli-credentials.ts
14607
- import { existsSync as existsSync20, readFileSync as readFileSync17 } from "fs";
15857
+ import { existsSync as existsSync21, readFileSync as readFileSync17 } from "fs";
14608
15858
  import { homedir as homedir4 } from "os";
14609
15859
  import { join as join17 } from "path";
14610
15860
  function externalStorePath(provider, home = homedir4()) {
@@ -14657,7 +15907,7 @@ function parseCodexTokensEnvelope(raw) {
14657
15907
  }
14658
15908
  function readExternalCliCredentials(provider, home = homedir4()) {
14659
15909
  const path2 = externalStorePath(provider, home);
14660
- if (!existsSync20(path2)) return null;
15910
+ if (!existsSync21(path2)) return null;
14661
15911
  let raw;
14662
15912
  try {
14663
15913
  const parsed = JSON.parse(readFileSync17(path2, "utf8"));
@@ -14683,16 +15933,18 @@ var JsonSubscriptionCredentialStore = class {
14683
15933
  * as on relay refresh egresses from the SAME proxy IP as the
14684
15934
  * account's traffic. NOT used by any read/write path.
14685
15935
  */
14686
- constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials) {
15936
+ constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials, atomicReplace = atomicReplaceUtf8) {
14687
15937
  this.tokensPath = tokensPath;
14688
15938
  this.box = box;
14689
15939
  this.fetchImpl = fetchImpl;
14690
15940
  this.externalCliReader = externalCliReader;
15941
+ this.atomicReplace = atomicReplace;
14691
15942
  }
14692
15943
  tokensPath;
14693
15944
  box;
14694
15945
  fetchImpl;
14695
15946
  externalCliReader;
15947
+ atomicReplace;
14696
15948
  /**
14697
15949
  * The proxy-aware `FetchLike` for one refresh round-trip (upstream-proxy M1). A
14698
15950
  * TEST-injected `fetchImpl` is returned verbatim; otherwise the refresh routes
@@ -14706,7 +15958,7 @@ var JsonSubscriptionCredentialStore = class {
14706
15958
  * a plaintext token pair into `upstream-trace.jsonl`.
14707
15959
  */
14708
15960
  buildRefreshFetch(providerId, accountId) {
14709
- return this.fetchImpl ?? ((url, init) => fetchUpstream3(url, init, { providerId, accountId, redactBodies: true }));
15961
+ return this.fetchImpl ?? ((url, init) => fetchUpstream7(url, init, { providerId, accountId, redactBodies: true }));
14710
15962
  }
14711
15963
  /**
14712
15964
  * In-flight refresh coalescing. OAuth refresh tokens are
@@ -14747,7 +15999,7 @@ var JsonSubscriptionCredentialStore = class {
14747
15999
  * other hot reads. Never returns token material.
14748
16000
  */
14749
16001
  getAccountProxy(providerId, accountId) {
14750
- if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego") {
16002
+ if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego" && providerId !== "kimi") {
14751
16003
  return void 0;
14752
16004
  }
14753
16005
  return getAccountProxy(this.readConfig(), providerId, accountId);
@@ -14766,7 +16018,7 @@ var JsonSubscriptionCredentialStore = class {
14766
16018
  const fingerprintOn = identityStore.isEnabled();
14767
16019
  const now = Date.now();
14768
16020
  const out = {};
14769
- for (const provider of ["claude", "codex", "gemini", "opencodego"]) {
16021
+ for (const provider of ["claude", "codex", "gemini", "opencodego", "kimi"]) {
14770
16022
  const sanitized = sanitizeAccounts(config, provider);
14771
16023
  if (sanitized.length === 0) continue;
14772
16024
  for (const account of sanitized) {
@@ -14924,6 +16176,47 @@ var JsonSubscriptionCredentialStore = class {
14924
16176
  }
14925
16177
  });
14926
16178
  }
16179
+ /**
16180
+ * Refresh the Kimi Code (Moonshot) OAuth access token (device-flow grant).
16181
+ * Kimi ROTATES the refresh token, so the response's pair is written back
16182
+ * whole; the account's stable `deviceId` (fingerprint header input) is
16183
+ * preserved. The refresh call carries the CLI fingerprint headers. HONEST
16184
+ * `false` when no refresh_token.
16185
+ */
16186
+ async refreshKimiToken() {
16187
+ return this.coalesce("kimi:active", async () => {
16188
+ const config = this.readConfig();
16189
+ const active = getActiveAccount(config, "kimi");
16190
+ const kimi = active?.tokens;
16191
+ if (!active || !kimi?.refreshToken) return false;
16192
+ const capturedId = active.id;
16193
+ this.materializeMigration(config);
16194
+ const refreshFetch = this.buildRefreshFetch("kimi", capturedId);
16195
+ try {
16196
+ const result = await kimiOAuth2.refreshAccessToken(
16197
+ kimi.refreshToken,
16198
+ refreshFetch,
16199
+ kimiOAuth2.kimiFingerprintHeaders(kimi.deviceId)
16200
+ );
16201
+ const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
16202
+ const next = {
16203
+ ...kimi,
16204
+ accessToken: result.accessToken,
16205
+ refreshToken: result.refreshToken,
16206
+ expiresAt,
16207
+ status: "authorized",
16208
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
16209
+ errorMessage: void 0,
16210
+ syncWarning: void 0
16211
+ };
16212
+ this.writeBackById("kimi", capturedId, next);
16213
+ return true;
16214
+ } catch (error) {
16215
+ this.markExpiredById("kimi", capturedId, kimi, error);
16216
+ return false;
16217
+ }
16218
+ });
16219
+ }
14927
16220
  /**
14928
16221
  * Refresh a SPECIFIC managed account by id (background scheduler sweep and
14929
16222
  * account-pool resolution). It uses only that account's stored refresh
@@ -14976,7 +16269,7 @@ var JsonSubscriptionCredentialStore = class {
14976
16269
  }
14977
16270
  const oauth = account.tokens;
14978
16271
  if (!oauth.accessToken) return null;
14979
- if (providerId === "codex" || providerId === "gemini") {
16272
+ if (providerId === "codex" || providerId === "gemini" || providerId === "kimi") {
14980
16273
  const expiresAtMs = oauth.expiresAt ? Date.parse(oauth.expiresAt) : 0;
14981
16274
  const expiringSoon = expiresAtMs > 0 && Date.now() >= expiresAtMs - ACCOUNT_REFRESH_LEAD_MS;
14982
16275
  if (expiringSoon && oauth.refreshToken) {
@@ -15065,8 +16358,23 @@ var JsonSubscriptionCredentialStore = class {
15065
16358
  }
15066
16359
  /** Dispatch one OAuth refresh round-trip to the provider's shared flow. */
15067
16360
  async refreshUpstream(provider, refreshToken, accountId) {
16361
+ const refreshFetch = this.buildRefreshFetch(provider, accountId);
16362
+ if (provider === "kimi") {
16363
+ const account = accountId ? getAccountById(this.readConfig(), "kimi", accountId) : void 0;
16364
+ const deviceId = account?.tokens?.deviceId;
16365
+ const r2 = await kimiOAuth2.refreshAccessToken(
16366
+ refreshToken,
16367
+ refreshFetch,
16368
+ kimiOAuth2.kimiFingerprintHeaders(deviceId)
16369
+ );
16370
+ return {
16371
+ accessToken: r2.accessToken,
16372
+ refreshToken: r2.refreshToken,
16373
+ expiresAt: new Date(Date.now() + r2.expiresIn * 1e3).toISOString()
16374
+ };
16375
+ }
15068
16376
  const flow = provider === "claude" ? claudeOAuth2 : provider === "codex" ? codexOAuth2 : geminiOAuth2;
15069
- const r = await flow.refreshAccessToken(refreshToken, this.buildRefreshFetch(provider, accountId));
16377
+ const r = await flow.refreshAccessToken(refreshToken, refreshFetch);
15070
16378
  return {
15071
16379
  accessToken: r.accessToken,
15072
16380
  refreshToken: r.refreshToken,
@@ -15229,42 +16537,86 @@ var JsonSubscriptionCredentialStore = class {
15229
16537
  /** Write the merged config to disk as pretty JSON (mkdir parent if needed).
15230
16538
  * Encrypt-on-write: the token-material fields are encrypted (legacy plaintext
15231
16539
  * `enc:v1:`; already-`enc:`/`$ENV` untouched) before serializing, so any
15232
- * write incl. child 4's future refresh writes lands encrypted. */
16540
+ * write incl. child 4's future refresh writes lands encrypted.
16541
+ * ATOMIC: temp + fsync + rename (`atomicReplaceUtf8`) — a failed or
16542
+ * interrupted write discards only the temp file; the prior `tokens.json`
16543
+ * survives byte-equal (bare `writeFileSync` truncate-writes lost every
16544
+ * account on a mid-write failure, 2026-09-06). */
15233
16545
  persist(config) {
15234
16546
  mkdirSync6(dirname15(this.tokensPath), { recursive: true });
15235
16547
  const encrypted = encryptTokens(config, this.box);
15236
- writeFileSync16(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n", "utf8");
16548
+ this.atomicReplace(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
15237
16549
  }
15238
16550
  /**
15239
- * Read + parse `tokens.json`, tolerating a missing/corrupt file, then DECRYPT
15240
- * the token-material fields so every getter returns plaintext (the
15241
- * subscription bearer path is byte-identical).
16551
+ * Read + parse `tokens.json`, then DECRYPT the token-material fields so every
16552
+ * getter returns plaintext (the subscription bearer path is byte-identical).
16553
+ *
16554
+ * A MISSING file is a legitimate first-boot state → minimal `{ updatedAt: '' }`.
16555
+ * A file that EXISTS but cannot be parsed as a JSON object is CORRUPT →
16556
+ * `quarantineCorrupt` moves it aside (once) before the empty config is
16557
+ * returned, so the unreadable accounts survive for manual recovery.
15242
16558
  *
15243
- * The fs-read + JSON-parse tolerance is INSIDE the try (a missing or corrupt
15244
- * file empty `{ updatedAt: '' }`). The DECRYPT runs OUTSIDE the try, so a
15245
- * wrong/missing master key or a tampered `enc:` envelope FAILS FAST with the
15246
- * box's clear, secret-free error (secrets spec "/ UX":
15247
- * SHALL fail-fast, SHALL NOT a swallowed decrypt would report "no
15248
- * tokens" and silently send the WRONG bearer upstream 401). Mirrors
15249
- * `config.ts loadConfig`, which decrypts outside its parse try.
16559
+ * The DECRYPT runs OUTSIDE any try, so a wrong/missing master key or a
16560
+ * tampered `enc:` envelope FAILS FAST with the box's clear, secret-free
16561
+ * error (secrets spec "/ UX": SHALL fail-fast, SHALL NOT a swallowed
16562
+ * decrypt would report "no tokens" and silently send the WRONG bearer
16563
+ * upstream 401). Mirrors `config.ts loadConfig`, which decrypts outside
16564
+ * its parse try.
15250
16565
  */
15251
16566
  readConfig() {
15252
- if (!existsSync21(this.tokensPath)) return { updatedAt: "" };
16567
+ if (!existsSync22(this.tokensPath)) return { updatedAt: "" };
15253
16568
  let parsed;
15254
16569
  try {
15255
16570
  const raw = JSON.parse(readFileSync18(this.tokensPath, "utf8"));
15256
- parsed = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
16571
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
16572
+ return this.quarantineCorrupt("parsed JSON is not an object");
16573
+ }
16574
+ parsed = raw;
15257
16575
  } catch {
15258
- parsed = null;
16576
+ return this.quarantineCorrupt("unparseable JSON");
15259
16577
  }
15260
- if (!parsed) return { updatedAt: "" };
15261
16578
  const decrypted = decryptTokens(parsed, this.box);
15262
16579
  return migrateLazily(decrypted);
15263
16580
  }
16581
+ /** One-shot latch: a corrupt file is quarantined (or found unmovable) at
16582
+ * most once per process, so the hot read path never re-attempts or re-logs. */
16583
+ corruptQuarantined = false;
16584
+ /**
16585
+ * Quarantine a present-but-corrupt `tokens.json`, then treat it as empty.
16586
+ *
16587
+ * Renames the file to a sibling `tokens.json.corrupt-<stamp>` backup and
16588
+ * logs loudly (the daemon's stderr log; secret-free — reason + paths only).
16589
+ * The daemon KEEPS SERVING (API-key routing is unaffected; subscription
16590
+ * routing reports no credential, same as an absent file) while the corrupt
16591
+ * bytes survive for manual recovery — and, critically, the NEXT persist
16592
+ * (e.g. the user re-logging in) can no longer overwrite the only copy of
16593
+ * the old accounts, which is exactly how the 2026-09-06 incident turned a
16594
+ * recoverable truncated file into permanent account loss.
16595
+ *
16596
+ * Best-effort: if the rename fails (file locked, permissions), the corrupt
16597
+ * file is left in place and every later read still tolerates it as empty;
16598
+ * the latch still trips so the attempt + log happen exactly once.
16599
+ */
16600
+ quarantineCorrupt(reason) {
16601
+ if (!this.corruptQuarantined) {
16602
+ this.corruptQuarantined = true;
16603
+ const backup = `${this.tokensPath}.corrupt-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
16604
+ let moved = false;
16605
+ try {
16606
+ renameSync12(this.tokensPath, backup);
16607
+ moved = true;
16608
+ } catch {
16609
+ }
16610
+ console.error(
16611
+ `[JsonSubscriptionCredentialStore] tokens.json is corrupt (${reason}); ` + (moved ? `moved to '${backup}' and treated as empty \u2014 recover accounts from that backup before re-adding them` : `could not move '${this.tokensPath}' \u2014 treated as empty`)
16612
+ );
16613
+ }
16614
+ return { updatedAt: "" };
16615
+ }
15264
16616
  };
15265
16617
 
15266
16618
  // src/AccountHealthProbeScheduler.ts
15267
- import { fetchUpstream as fetchUpstream4 } from "@omnicross/core/pipeline/upstreamFetch";
16619
+ import { fetchUpstream as fetchUpstream8 } from "@omnicross/core/pipeline/upstreamFetch";
15268
16620
 
15269
16621
  // src/probe/CodexGenerationProbe.ts
15270
16622
  import {
@@ -15406,7 +16758,11 @@ var PROVIDER_PROBE_PLANS = {
15406
16758
  // billable/wrong endpoint). Upgrade to `{ kind:'upstream' }` once verified.
15407
16759
  codex: { kind: "local" },
15408
16760
  gemini: { kind: "local" },
15409
- opencodego: { kind: "local" }
16761
+ opencodego: { kind: "local" },
16762
+ // Kimi's `GET /coding/v1/usages` is a verified FREE authed GET (the allowance
16763
+ // collector uses it), but the probe path also needs the fingerprint headers —
16764
+ // keep the probe local until the collector covers the health surface.
16765
+ kimi: { kind: "local" }
15410
16766
  };
15411
16767
  function probePlanFor(providerId) {
15412
16768
  return PROVIDER_PROBE_PLANS[providerId] ?? { kind: "local" };
@@ -15428,7 +16784,7 @@ var AccountHealthProbeScheduler = class {
15428
16784
  this.logger = logger;
15429
16785
  this.config = config;
15430
16786
  this.now = opts.now ?? Date.now;
15431
- this.fetchImpl = opts.fetchImpl ?? fetchUpstream4;
16787
+ this.fetchImpl = opts.fetchImpl ?? fetchUpstream8;
15432
16788
  this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
15433
16789
  this.planFor = opts.planFor ?? probePlanFor;
15434
16790
  }
@@ -15772,13 +17128,13 @@ var AccountHealthSweeper = class {
15772
17128
  };
15773
17129
 
15774
17130
  // src/audit/AuditPruneSweeper.ts
15775
- import { createReadStream as createReadStream3, createWriteStream as createWriteStream2, existsSync as existsSync24, readdirSync as readdirSync6, rmSync as rmSync4, unlinkSync as unlinkSync13 } from "fs";
17131
+ import { createReadStream as createReadStream3, createWriteStream as createWriteStream2, existsSync as existsSync25, readdirSync as readdirSync6, rmSync as rmSync4, unlinkSync as unlinkSync13 } from "fs";
15776
17132
  import { join as join20 } from "path";
15777
17133
  import { pipeline } from "stream/promises";
15778
17134
  import { createGzip } from "zlib";
15779
17135
 
15780
17136
  // src/audit/auditDictionary.ts
15781
- import { existsSync as existsSync22, readdirSync as readdirSync4, readFileSync as readFileSync19, renameSync as renameSync12, unlinkSync as unlinkSync12, writeFileSync as writeFileSync17 } from "fs";
17137
+ import { existsSync as existsSync23, readdirSync as readdirSync4, readFileSync as readFileSync19, renameSync as renameSync13, unlinkSync as unlinkSync12, writeFileSync as writeFileSync16 } from "fs";
15782
17138
  import { join as join18 } from "path";
15783
17139
 
15784
17140
  // src/audit/auditBodyStore.ts
@@ -16036,9 +17392,9 @@ function chooseDictionary(anchors) {
16036
17392
  var EMPTY = { shards: 0, anchors: 0, savedBytes: 0 };
16037
17393
  function compactAuditDay(dayPath) {
16038
17394
  const bodiesPath = join18(dayPath, AUDIT_BODIES_DIR);
16039
- if (!existsSync22(bodiesPath)) return EMPTY;
17395
+ if (!existsSync23(bodiesPath)) return EMPTY;
16040
17396
  const dictPath = join18(bodiesPath, AUDIT_DICT_FILE);
16041
- if (existsSync22(dictPath) || existsSync22(`${dictPath}.gz`)) return EMPTY;
17397
+ if (existsSync23(dictPath) || existsSync23(`${dictPath}.gz`)) return EMPTY;
16042
17398
  const shardFiles = plainShards(bodiesPath);
16043
17399
  if (shardFiles.length < 2) return EMPTY;
16044
17400
  const loaded = /* @__PURE__ */ new Map();
@@ -16063,7 +17419,7 @@ function compactAuditDay(dayPath) {
16063
17419
  ts: 0,
16064
17420
  req: { base: null, anchor: "dict", pre: 0, suf: 0, ins: dictionary }
16065
17421
  };
16066
- writeFileSync17(dictPath, JSON.stringify(dictEntry) + "\n", "utf8");
17422
+ writeFileSync16(dictPath, JSON.stringify(dictEntry) + "\n", "utf8");
16067
17423
  const result = { shards: 0, anchors: 0, savedBytes: 0 };
16068
17424
  for (const [file, entries] of loaded) {
16069
17425
  let changed = false;
@@ -16083,11 +17439,11 @@ function compactAuditDay(dayPath) {
16083
17439
  const target = join18(bodiesPath, file);
16084
17440
  const temp = `${target}.compacting`;
16085
17441
  try {
16086
- writeFileSync17(temp, rewritten.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
16087
- renameSync12(temp, target);
17442
+ writeFileSync16(temp, rewritten.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
17443
+ renameSync13(temp, target);
16088
17444
  } catch {
16089
17445
  try {
16090
- if (existsSync22(temp)) unlinkSync12(temp);
17446
+ if (existsSync23(temp)) unlinkSync12(temp);
16091
17447
  } catch {
16092
17448
  }
16093
17449
  continue;
@@ -16106,7 +17462,7 @@ function compactAuditDay(dayPath) {
16106
17462
  }
16107
17463
  function compactAllClosedAuditDays(auditDir, now = Date.now) {
16108
17464
  const run = { days: 0, shards: 0, savedBytes: 0 };
16109
- if (!existsSync22(auditDir)) return run;
17465
+ if (!existsSync23(auditDir)) return run;
16110
17466
  const today = auditDayDirName(now());
16111
17467
  let names;
16112
17468
  try {
@@ -16131,11 +17487,11 @@ function compactAllClosedAuditDays(auditDir, now = Date.now) {
16131
17487
  // src/audit/auditStats.ts
16132
17488
  import {
16133
17489
  createReadStream as createReadStream2,
16134
- existsSync as existsSync23,
17490
+ existsSync as existsSync24,
16135
17491
  readFileSync as readFileSync20,
16136
17492
  readdirSync as readdirSync5,
16137
17493
  statSync as statSync5,
16138
- writeFileSync as writeFileSync18
17494
+ writeFileSync as writeFileSync17
16139
17495
  } from "fs";
16140
17496
  import { basename as basename9, dirname as dirname16, join as join19 } from "path";
16141
17497
  var SIDECAR_VERSION = 1;
@@ -16145,7 +17501,7 @@ function auditStatsFileName(auditFile) {
16145
17501
  return auditFile.replace(/\.jsonl$/, ".stats.json");
16146
17502
  }
16147
17503
  function readPersisted(path2) {
16148
- if (!existsSync23(path2)) return null;
17504
+ if (!existsSync24(path2)) return null;
16149
17505
  try {
16150
17506
  const value = JSON.parse(readFileSync20(path2, "utf8"));
16151
17507
  if (value.version !== SIDECAR_VERSION || !Number.isSafeInteger(value.auditBytes) || (value.auditBytes ?? -1) < 0 || !Number.isSafeInteger(value.requestCount) || (value.requestCount ?? -1) < 0 || !Number.isSafeInteger(value.errorCount) || (value.errorCount ?? -1) < 0 || (value.errorCount ?? 0) > (value.requestCount ?? -1) || typeof value.complete !== "boolean" || value.minTs !== null && !Number.isFinite(value.minTs) || value.maxTs !== null && !Number.isFinite(value.maxTs)) {
@@ -16177,7 +17533,7 @@ function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfte
16177
17533
  minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
16178
17534
  maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
16179
17535
  };
16180
- writeFileSync18(statsPath, JSON.stringify(next), "utf8");
17536
+ writeFileSync17(statsPath, JSON.stringify(next), "utf8");
16181
17537
  }
16182
17538
  function queryCovers(stats, from, to) {
16183
17539
  return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
@@ -16288,7 +17644,7 @@ function mergePersistedStats(previous, appended) {
16288
17644
  };
16289
17645
  }
16290
17646
  async function readAuditStats(auditDir, query2 = {}) {
16291
- if (!existsSync23(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
17647
+ if (!existsSync24(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
16292
17648
  const from = typeof query2.from === "number" ? query2.from : -Infinity;
16293
17649
  const to = typeof query2.to === "number" ? query2.to : Infinity;
16294
17650
  let sources;
@@ -16301,7 +17657,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16301
17657
  auditPath: join19(auditDir, name),
16302
17658
  statsPath: join19(auditDir, auditStatsFileName(name))
16303
17659
  }
16304
- ).filter((source) => existsSync23(source.auditPath));
17660
+ ).filter((source) => existsSync24(source.auditPath));
16305
17661
  } catch {
16306
17662
  return { requestCount: 0, errorCount: 0, complete: false };
16307
17663
  }
@@ -16327,7 +17683,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16327
17683
  total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
16328
17684
  total.complete = total.complete && scanned.filtered.complete;
16329
17685
  const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
16330
- if (current.complete) writeFileSync18(statsPath, JSON.stringify(current), "utf8");
17686
+ if (current.complete) writeFileSync17(statsPath, JSON.stringify(current), "utf8");
16331
17687
  } catch {
16332
17688
  total.complete = false;
16333
17689
  }
@@ -16336,7 +17692,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16336
17692
  }
16337
17693
 
16338
17694
  // src/audit/AuditPruneSweeper.ts
16339
- var DAY_MS = 24 * 60 * 6e4;
17695
+ var DAY_MS3 = 24 * 60 * 6e4;
16340
17696
  var SWEEP_INTERVAL_MS2 = 60 * 6e4;
16341
17697
  var ARCHIVE_BATCH = 64;
16342
17698
  var AuditPruneSweeper = class {
@@ -16399,8 +17755,8 @@ var AuditPruneSweeper = class {
16399
17755
  if (!this.config.enabled || this.sweeping) return 0;
16400
17756
  this.sweeping = true;
16401
17757
  try {
16402
- if (!existsSync24(this.auditDir)) return 0;
16403
- const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS;
17758
+ if (!existsSync25(this.auditDir)) return 0;
17759
+ const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS3;
16404
17760
  let removed = 0;
16405
17761
  for (const name of readdirSync6(this.auditDir)) {
16406
17762
  const dateMs = auditFileDateMs(name);
@@ -16411,7 +17767,7 @@ var AuditPruneSweeper = class {
16411
17767
  } else {
16412
17768
  unlinkSync13(join20(this.auditDir, name));
16413
17769
  const statsPath = join20(this.auditDir, auditStatsFileName(name));
16414
- if (existsSync24(statsPath)) unlinkSync13(statsPath);
17770
+ if (existsSync25(statsPath)) unlinkSync13(statsPath);
16415
17771
  }
16416
17772
  removed += 1;
16417
17773
  } catch (error) {
@@ -16441,7 +17797,7 @@ var AuditPruneSweeper = class {
16441
17797
  if (!this.config.enabled || this.archiving) return 0;
16442
17798
  this.archiving = true;
16443
17799
  try {
16444
- if (!existsSync24(this.auditDir)) return 0;
17800
+ if (!existsSync25(this.auditDir)) return 0;
16445
17801
  const today = this.todayMidnight();
16446
17802
  let compressed = 0;
16447
17803
  for (const name of readdirSync6(this.auditDir)) {
@@ -16495,7 +17851,7 @@ var AuditPruneSweeper = class {
16495
17851
  const source = join20(bodiesPath, shard);
16496
17852
  const target = `${source}.gz`;
16497
17853
  try {
16498
- if (existsSync24(target)) {
17854
+ if (existsSync25(target)) {
16499
17855
  unlinkSync13(source);
16500
17856
  continue;
16501
17857
  }
@@ -16504,7 +17860,7 @@ var AuditPruneSweeper = class {
16504
17860
  compressed += 1;
16505
17861
  } catch (error) {
16506
17862
  try {
16507
- if (existsSync24(target)) unlinkSync13(target);
17863
+ if (existsSync25(target)) unlinkSync13(target);
16508
17864
  } catch {
16509
17865
  }
16510
17866
  this.logger.warn("[AuditPruneSweeper] failed to archive audit body shard", {
@@ -16657,7 +18013,7 @@ async function closeAll(writers) {
16657
18013
  // src/usage/UsagePruneSweeper.ts
16658
18014
  import { unlink as unlink3 } from "fs/promises";
16659
18015
  import { join as join22 } from "path";
16660
- var DAY_MS2 = 24 * 60 * 6e4;
18016
+ var DAY_MS4 = 24 * 60 * 6e4;
16661
18017
  var SWEEP_INTERVAL_MS3 = 60 * 6e4;
16662
18018
  var DEFAULT_USAGE_RETENTION_DAYS = 90;
16663
18019
  var UsagePruneSweeper = class {
@@ -16714,7 +18070,7 @@ var UsagePruneSweeper = class {
16714
18070
  this.sweeping = true;
16715
18071
  try {
16716
18072
  const retentionDays = this.config.retentionDays ?? DEFAULT_USAGE_RETENTION_DAYS;
16717
- const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS2;
18073
+ const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS4;
16718
18074
  let removed = 0;
16719
18075
  for (const entry of await listUsageDays(this.usageDir)) {
16720
18076
  if (!entry.hasShard) continue;
@@ -16772,7 +18128,7 @@ var UsagePruneSweeper = class {
16772
18128
  };
16773
18129
 
16774
18130
  // src/audit/auditBodyReader.ts
16775
- import { existsSync as existsSync25, readdirSync as readdirSync7, readFileSync as readFileSync21, statSync as statSync7 } from "fs";
18131
+ import { existsSync as existsSync26, readdirSync as readdirSync7, readFileSync as readFileSync21, statSync as statSync7 } from "fs";
16776
18132
  import { join as join23 } from "path";
16777
18133
  import { gunzipSync } from "zlib";
16778
18134
 
@@ -16836,7 +18192,7 @@ function forEachLineFromTail(path2, onLine) {
16836
18192
  function candidateDays(auditDir, ts) {
16837
18193
  if (typeof ts === "number" && Number.isFinite(ts)) {
16838
18194
  const named = auditDayDirName(ts);
16839
- if (existsSync25(join23(auditDir, named))) return [named];
18195
+ if (existsSync26(join23(auditDir, named))) return [named];
16840
18196
  }
16841
18197
  try {
16842
18198
  return readdirSync7(auditDir).filter(isAuditDayDir).sort().reverse();
@@ -16847,9 +18203,9 @@ function candidateDays(auditDir, ts) {
16847
18203
  function readShard(auditDir, day, sessionKey) {
16848
18204
  const base = join23(auditDir, day, AUDIT_BODIES_DIR, auditBodyFileName(sessionKey));
16849
18205
  try {
16850
- if (existsSync25(base)) return readFileSync21(base, "utf8");
18206
+ if (existsSync26(base)) return readFileSync21(base, "utf8");
16851
18207
  const gz = `${base}.gz`;
16852
- if (existsSync25(gz)) return gunzipSync(readFileSync21(gz)).toString("utf8");
18208
+ if (existsSync26(gz)) return gunzipSync(readFileSync21(gz)).toString("utf8");
16853
18209
  } catch {
16854
18210
  return null;
16855
18211
  }
@@ -16882,8 +18238,8 @@ function withDictionary(auditDir, day, entries) {
16882
18238
  const base = join23(auditDir, day, AUDIT_BODIES_DIR, AUDIT_DICT_FILE);
16883
18239
  let raw = null;
16884
18240
  try {
16885
- if (existsSync25(base)) raw = readFileSync21(base, "utf8");
16886
- else if (existsSync25(`${base}.gz`)) raw = gunzipSync(readFileSync21(`${base}.gz`)).toString("utf8");
18241
+ if (existsSync26(base)) raw = readFileSync21(base, "utf8");
18242
+ else if (existsSync26(`${base}.gz`)) raw = gunzipSync(readFileSync21(`${base}.gz`)).toString("utf8");
16887
18243
  } catch {
16888
18244
  return entries;
16889
18245
  }
@@ -16916,7 +18272,7 @@ function reconstructRequest(entries, entry) {
16916
18272
  }
16917
18273
  function readAuditBody(auditDir, query2) {
16918
18274
  if (!isSafeSessionKey(query2.sessionKey) || !query2.id) return {};
16919
- if (!existsSync25(auditDir)) return {};
18275
+ if (!existsSync26(auditDir)) return {};
16920
18276
  for (const day of candidateDays(auditDir, query2.ts)) {
16921
18277
  const raw = readShard(auditDir, day, query2.sessionKey);
16922
18278
  if (raw === null) continue;
@@ -16963,7 +18319,7 @@ function readLegacyInlineBody(auditDir, id) {
16963
18319
  }
16964
18320
 
16965
18321
  // src/audit/auditReader.ts
16966
- import { existsSync as existsSync26, readdirSync as readdirSync8 } from "fs";
18322
+ import { existsSync as existsSync27, readdirSync as readdirSync8 } from "fs";
16967
18323
  import { join as join24 } from "path";
16968
18324
  var DEFAULT_LIMIT = 200;
16969
18325
  var MAX_LIMIT = 2e3;
@@ -16981,7 +18337,7 @@ function daySources(auditDir) {
16981
18337
  if (dateMs === null) continue;
16982
18338
  if (AUDIT_DAY_DIR_RE.test(name)) {
16983
18339
  const path2 = join24(auditDir, name, AUDIT_META_FILE);
16984
- if (existsSync26(path2)) sources.push({ path: path2, dateMs });
18340
+ if (existsSync27(path2)) sources.push({ path: path2, dateMs });
16985
18341
  } else if (AUDIT_FILE_RE.test(name)) {
16986
18342
  sources.push({ path: join24(auditDir, name), dateMs });
16987
18343
  }
@@ -16999,7 +18355,7 @@ function toMetaRecord(record) {
16999
18355
  return { ...meta, hasBody: true };
17000
18356
  }
17001
18357
  function readAuditRecords(auditDir, query2 = {}) {
17002
- if (!existsSync26(auditDir)) return [];
18358
+ if (!existsSync27(auditDir)) return [];
17003
18359
  const from = typeof query2.from === "number" ? query2.from : -Infinity;
17004
18360
  const to = typeof query2.to === "number" ? query2.to : Infinity;
17005
18361
  const limit = Math.min(MAX_LIMIT, Math.max(1, Math.trunc(query2.limit ?? DEFAULT_LIMIT)));
@@ -17027,7 +18383,7 @@ function readAuditRecords(auditDir, query2 = {}) {
17027
18383
  }
17028
18384
 
17029
18385
  // src/audit/AuditWriter.ts
17030
- import { appendFileSync as appendFileSync2, existsSync as existsSync27, mkdirSync as mkdirSync7, statSync as statSync8 } from "fs";
18386
+ import { appendFileSync as appendFileSync2, existsSync as existsSync28, mkdirSync as mkdirSync7, statSync as statSync8 } from "fs";
17031
18387
  import { join as join25 } from "path";
17032
18388
  var AuditWriter = class {
17033
18389
  constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
@@ -17085,7 +18441,7 @@ var AuditWriter = class {
17085
18441
  const { requestBody: _req, responseBody: _res, ...meta } = record;
17086
18442
  const file = join25(dayPath, AUDIT_META_FILE);
17087
18443
  const line = JSON.stringify(meta) + "\n";
17088
- const bytesBefore = existsSync27(file) ? statSync8(file).size : 0;
18444
+ const bytesBefore = existsSync28(file) ? statSync8(file).size : 0;
17089
18445
  appendFileSync2(file, line, "utf8");
17090
18446
  try {
17091
18447
  updateAuditStatsAfterAppend(
@@ -17133,7 +18489,7 @@ var AuditWriter = class {
17133
18489
  import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync8 } from "fs";
17134
18490
  import { createHmac as createHmac5 } from "crypto";
17135
18491
  import { join as join26 } from "path";
17136
- import { fetchUpstream as fetchUpstream5 } from "@omnicross/core/pipeline/upstreamFetch";
18492
+ import { fetchUpstream as fetchUpstream9 } from "@omnicross/core/pipeline/upstreamFetch";
17137
18493
 
17138
18494
  // src/billing/billingFiles.ts
17139
18495
  var BILLING_FILE_RE = /^billing-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
@@ -17156,7 +18512,7 @@ var BillingPublisher = class {
17156
18512
  constructor(billingDir, logger, opts = {}) {
17157
18513
  this.billingDir = billingDir;
17158
18514
  this.logger = logger;
17159
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream5(url, init));
18515
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream9(url, init));
17160
18516
  this.defer = opts.defer ?? ((fn) => setTimeout(fn, 0));
17161
18517
  this.timeoutMs = opts.timeoutMs ?? BILLING_POST_TIMEOUT_MS;
17162
18518
  this.now = opts.now ?? Date.now;
@@ -17269,11 +18625,11 @@ var BillingPublisher = class {
17269
18625
  };
17270
18626
 
17271
18627
  // src/billing/billingReader.ts
17272
- import { existsSync as existsSync28, readdirSync as readdirSync9, readFileSync as readFileSync22 } from "fs";
18628
+ import { existsSync as existsSync29, readdirSync as readdirSync9, readFileSync as readFileSync22 } from "fs";
17273
18629
  import { join as join27 } from "path";
17274
18630
  function readBillingLedger(billingDir) {
17275
18631
  const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
17276
- if (!existsSync28(billingDir)) return view;
18632
+ if (!existsSync29(billingDir)) return view;
17277
18633
  let files;
17278
18634
  try {
17279
18635
  files = readdirSync9(billingDir);
@@ -17406,7 +18762,7 @@ var BillingRetrySweeper = class {
17406
18762
  // src/TokenRefreshScheduler.ts
17407
18763
  var REFRESH_LEAD_MS2 = 5 * 6e4;
17408
18764
  var SWEEP_INTERVAL_MS5 = 6e4;
17409
- var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini"];
18765
+ var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini", "kimi"];
17410
18766
  var TokenRefreshScheduler = class {
17411
18767
  constructor(store, logger, intervalMs = SWEEP_INTERVAL_MS5, leadMs = REFRESH_LEAD_MS2) {
17412
18768
  this.store = store;
@@ -17489,6 +18845,8 @@ var TokenRefreshScheduler = class {
17489
18845
  return this.store.refreshCodexToken();
17490
18846
  case "gemini":
17491
18847
  return this.store.refreshGeminiToken();
18848
+ case "kimi":
18849
+ return this.store.refreshKimiToken();
17492
18850
  }
17493
18851
  }
17494
18852
  };
@@ -17567,7 +18925,7 @@ function createRouteLeaseSubscriptionPreflight(credentials) {
17567
18925
 
17568
18926
  // src/webhook/WebhookDispatcher.ts
17569
18927
  import { createHmac as createHmac6 } from "crypto";
17570
- import { fetchUpstream as fetchUpstream6 } from "@omnicross/core/pipeline/upstreamFetch";
18928
+ import { fetchUpstream as fetchUpstream10 } from "@omnicross/core/pipeline/upstreamFetch";
17571
18929
  var WEBHOOK_MAX_ATTEMPTS = 3;
17572
18930
  var WEBHOOK_QUEUE_MAX = 1e3;
17573
18931
  var WEBHOOK_SEND_TIMEOUT_MS = 1e4;
@@ -17587,7 +18945,7 @@ var WebhookDispatcher = class {
17587
18945
  sleep;
17588
18946
  now;
17589
18947
  constructor(opts = {}) {
17590
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream6(url, init));
18948
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream10(url, init));
17591
18949
  this.logger = opts.logger;
17592
18950
  this.maxAttempts = opts.maxAttempts ?? WEBHOOK_MAX_ATTEMPTS;
17593
18951
  this.queueMax = opts.queueMax ?? WEBHOOK_QUEUE_MAX;
@@ -17673,8 +19031,8 @@ var WebhookDispatcher = class {
17673
19031
  signal: AbortSignal.timeout(this.timeoutMs)
17674
19032
  });
17675
19033
  return res.ok ? { ok: true, status: res.status } : { ok: false, status: res.status };
17676
- } catch (err5) {
17677
- return { ok: false, error: err5 instanceof Error ? err5.message : String(err5) };
19034
+ } catch (err6) {
19035
+ return { ok: false, error: err6 instanceof Error ? err6.message : String(err6) };
17678
19036
  }
17679
19037
  }
17680
19038
  /**
@@ -17816,7 +19174,7 @@ function buildDaemon(config, paths) {
17816
19174
  setSecretBox(secretBox3);
17817
19175
  setSecretBox2(secretBox3);
17818
19176
  const decryptedConfig = decryptConfigSecrets(config, secretBox3);
17819
- const accountAllowanceStore = new AccountAllowanceStore3(
19177
+ const accountAllowanceStore = new AccountAllowanceStore6(
17820
19178
  Date.now,
17821
19179
  void 0,
17822
19180
  new JsonAccountAllowancePersistence(defaultAccountAllowancePath(paths.configPath))
@@ -17861,6 +19219,7 @@ function buildDaemon(config, paths) {
17861
19219
  );
17862
19220
  setGeminiCodeAssistResolver(getGeminiCodeAssistProjectResolver());
17863
19221
  const autoDisableStore = new AutoDisableStore();
19222
+ const providerKeyQuotaService = new ProviderKeyQuotaService(secretBox3);
17864
19223
  const apiKeyPool = new ApiKeyPoolService(
17865
19224
  createPoolKeysLoader((id) => llmConfig.getProviderRow(id), autoDisableStore),
17866
19225
  resolveEnvKey,
@@ -17877,7 +19236,7 @@ function buildDaemon(config, paths) {
17877
19236
  const pricingEngine = new PricingEngine(pricingStore, logger, {
17878
19237
  // Catalog egress follows the same global/env proxy policy as every other
17879
19238
  // daemon upstream call; no provider/account override applies here.
17880
- fetchImpl: ((input, init) => fetchUpstream7(String(input), init ?? {}))
19239
+ fetchImpl: ((input, init) => fetchUpstream11(String(input), init ?? {}))
17881
19240
  });
17882
19241
  const pricingRefreshScheduler = new PricingRefreshScheduler(
17883
19242
  pricingEngine,
@@ -18141,6 +19500,11 @@ function buildDaemon(config, paths) {
18141
19500
  // values themselves NEVER leave (masked via `maskProviderApiKey`).
18142
19501
  apiKeyPool,
18143
19502
  autoDisableStore,
19503
+ // BYO provider-key quota (Z.AI coding plan, MiniMax Token Plan, …): a
19504
+ // read-through cached same-key usage probe surfaced on the keys view. The
19505
+ // key plaintext is resolved + decrypted inside the service and never
19506
+ // crosses back out.
19507
+ providerKeyQuota: providerKeyQuotaService,
18144
19508
  // Interactive OAuth login over admin HTTP (app-parity child 4, design
18145
19509
  // D1/D2-a). The in-memory pending-session store (NEVER serialized), the
18146
19510
  // injected token-exchange fetch (global `fetch` here; mocked in tests), and a
@@ -18157,7 +19521,7 @@ function buildDaemon(config, paths) {
18157
19521
  // — `server.proxy.byProvider[...]` was silently skipped — and the call was
18158
19522
  // excluded from the upstream trace, so a failing login left no evidence.
18159
19523
  // `redactBodies` keeps the code/verifier + minted token out of that trace.
18160
- oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => fetchUpstream7(url, init, { providerId, redactBodies: true }),
19524
+ oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => fetchUpstream11(url, init, { providerId, redactBodies: true }),
18161
19525
  subscriptionAccountAppender: credentialStore,
18162
19526
  // Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
18163
19527
  // + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
@@ -18165,6 +19529,10 @@ function buildDaemon(config, paths) {
18165
19529
  // can inject a mock so no real port is bound.
18166
19530
  codexSessions: new CodexOAuthSessionStore(),
18167
19531
  codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
19532
+ // Kimi interactive OAuth — the async DEVICE-CODE flow store (no port, no
19533
+ // paste; the app shows the verification URL + user code and polls the
19534
+ // token-free status). Token captured + persisted daemon-side.
19535
+ kimiSessions: new CodexOAuthSessionStore(),
18168
19536
  // Migration pack (app-parity child 6, design D2/D3) — the concrete credential
18169
19537
  // store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
18170
19538
  // the multi-account append (`appendProviderAccount`, import re-encrypts at-
@@ -18223,7 +19591,7 @@ function buildDaemon(config, paths) {
18223
19591
  });
18224
19592
  const webhookDispatcher = new WebhookDispatcher({
18225
19593
  logger,
18226
- fetchImpl: (url, init) => fetchUpstream7(url, init)
19594
+ fetchImpl: (url, init) => fetchUpstream11(url, init)
18227
19595
  });
18228
19596
  setWebhookRuntime(webhookDispatcher, getSharedAccountHealth4());
18229
19597
  const auditWriter = new AuditWriter(auditDir, logger);
@@ -18320,7 +19688,7 @@ function resetDaemonSingletonsForTests() {
18320
19688
  }
18321
19689
  function isTokensStoreReadable(tokensPath) {
18322
19690
  try {
18323
- if (!existsSync29(tokensPath)) return true;
19691
+ if (!existsSync30(tokensPath)) return true;
18324
19692
  accessSync(tokensPath, fsConstants.R_OK);
18325
19693
  return true;
18326
19694
  } catch {