@omnicross/daemon 0.3.0 → 0.4.0

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 AccountAllowanceStore8,
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 fetchUpstream13, 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,263 @@ 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
+
233
+ // src/admin/accountsGrokOAuth.ts
234
+ import { grokOAuth } from "@omnicross/subscriptions";
235
+ function err3(status, message) {
236
+ return { status, body: { error: { type: "admin_api_error", message } } };
237
+ }
238
+ var DEFAULT_GROK_OAUTH_TTL_MS = 15 * 6e4;
239
+ async function handleGrokOAuthStart(deps) {
240
+ if (deps.grokSessions.isBusy()) {
241
+ return err3(409, "a grok sign-in is already in progress \u2014 finish it in the browser or cancel it");
242
+ }
243
+ const fetchImpl = deps.oauthExchangeFetch("grok");
244
+ let tokenEndpoint;
245
+ try {
246
+ tokenEndpoint = await grokOAuth.resolveGrokTokenEndpoint(fetchImpl);
247
+ } catch (e) {
248
+ const reason = e instanceof Error ? e.message : "OIDC discovery failed";
249
+ return err3(502, `grok token-endpoint discovery failed: ${reason}`);
250
+ }
251
+ let authorization;
252
+ try {
253
+ authorization = await grokOAuth.requestGrokDeviceAuthorization(fetchImpl);
254
+ } catch (e) {
255
+ const reason = e instanceof Error ? e.message : "device authorization failed";
256
+ return err3(502, `grok device authorization failed: ${reason}`);
257
+ }
258
+ const { sessionId, signal } = deps.grokSessions.begin();
259
+ void runGrokDevicePoll(sessionId, tokenEndpoint, authorization.deviceCode, signal, deps).catch((e) => {
260
+ const reason = e instanceof Error ? e.message : "grok sign-in failed";
261
+ deps.grokSessions.settle(sessionId, "error", reason);
262
+ });
263
+ return {
264
+ status: 200,
265
+ body: {
266
+ authUrl: authorization.verificationUriComplete ?? authorization.verificationUri,
267
+ userCode: authorization.userCode,
268
+ sessionId
269
+ }
270
+ };
271
+ }
272
+ async function runGrokDevicePoll(sessionId, tokenEndpoint, deviceCode, signal, deps) {
273
+ const fetchImpl = deps.oauthExchangeFetch("grok");
274
+ const result = await grokOAuth.awaitGrokDeviceToken(
275
+ { userCode: "", deviceCode, verificationUri: "" },
276
+ tokenEndpoint,
277
+ fetchImpl,
278
+ {
279
+ deadlineMs: DEFAULT_GROK_OAUTH_TTL_MS,
280
+ sleep: (ms) => new Promise((resolve10, reject) => {
281
+ const onAbort = () => {
282
+ clearTimeout(timer);
283
+ reject(new Error("login: cancelled"));
284
+ };
285
+ const timer = setTimeout(() => {
286
+ signal.removeEventListener("abort", onAbort);
287
+ resolve10();
288
+ }, ms);
289
+ signal.addEventListener("abort", onAbort, { once: true });
290
+ })
291
+ }
292
+ );
293
+ const block = {
294
+ authMethod: "oauth",
295
+ status: "authorized",
296
+ accessToken: result.accessToken,
297
+ refreshToken: result.refreshToken,
298
+ expiresAt: new Date(Date.now() + result.expiresIn * 1e3).toISOString(),
299
+ accountId: grokOAuth.grokAccountIdFromAccessToken(result.accessToken),
300
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
301
+ };
302
+ await deps.subscriptionAccountAppender.appendProviderAccount("grok", block);
303
+ deps.grokSessions.settle(sessionId, "done");
304
+ }
305
+ function handleGrokOAuthCancel(sessionId, deps) {
306
+ if (!deps.grokSessions.cancel(sessionId)) return err3(404, "unknown or expired grok sign-in session");
307
+ return { status: 200, body: { ok: true } };
308
+ }
309
+ function handleGrokOAuthStatus(sessionId, deps) {
310
+ const s = deps.grokSessions.get(sessionId);
311
+ if (!s) return err3(404, "unknown or expired grok sign-in session");
312
+ return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
313
+ }
314
+
315
+ // src/admin/accountsCopilotOAuth.ts
316
+ import { copilotOAuth } from "@omnicross/subscriptions";
317
+ function err4(status, message) {
318
+ return { status, body: { error: { type: "admin_api_error", message } } };
319
+ }
320
+ var DEFAULT_COPILOT_OAUTH_TTL_MS = 15 * 6e4;
321
+ async function handleCopilotOAuthStart(deps, enterpriseUrlInput) {
322
+ if (deps.copilotSessions.isBusy()) {
323
+ return err4(409, "a copilot sign-in is already in progress \u2014 finish it in the browser or cancel it");
324
+ }
325
+ let enterpriseUrl;
326
+ if (typeof enterpriseUrlInput === "string" && enterpriseUrlInput.trim()) {
327
+ try {
328
+ enterpriseUrl = copilotOAuth.normalizeCopilotEnterpriseDomain(enterpriseUrlInput);
329
+ } catch (e) {
330
+ const reason = e instanceof Error ? e.message : "invalid GitHub Enterprise domain";
331
+ return err4(400, `copilot ${reason}`);
332
+ }
333
+ }
334
+ const fetchImpl = deps.oauthExchangeFetch("copilot");
335
+ let authorization;
336
+ try {
337
+ authorization = await copilotOAuth.requestCopilotDeviceAuthorization(fetchImpl, enterpriseUrl);
338
+ } catch (e) {
339
+ const reason = e instanceof Error ? e.message : "device authorization failed";
340
+ return err4(502, `copilot device authorization failed: ${reason}`);
341
+ }
342
+ const { sessionId, signal } = deps.copilotSessions.begin();
343
+ void runCopilotDevicePoll(sessionId, authorization.deviceCode, signal, deps, enterpriseUrl).catch((e) => {
344
+ const reason = e instanceof Error ? e.message : "copilot sign-in failed";
345
+ deps.copilotSessions.settle(sessionId, "error", reason);
346
+ });
347
+ return {
348
+ status: 200,
349
+ body: {
350
+ authUrl: authorization.verificationUri,
351
+ userCode: authorization.userCode,
352
+ sessionId,
353
+ ...enterpriseUrl ? { enterpriseUrl } : {}
354
+ }
355
+ };
356
+ }
357
+ async function runCopilotDevicePoll(sessionId, deviceCode, signal, deps, enterpriseUrl) {
358
+ const fetchImpl = deps.oauthExchangeFetch("copilot");
359
+ const result = await copilotOAuth.awaitCopilotDeviceToken(
360
+ { userCode: "", deviceCode, verificationUri: "", interval: 5, expiresIn: 900 },
361
+ fetchImpl,
362
+ {
363
+ deadlineMs: DEFAULT_COPILOT_OAUTH_TTL_MS,
364
+ ...enterpriseUrl ? { enterpriseUrl } : {},
365
+ sleep: (ms) => new Promise((resolve10, reject) => {
366
+ const onAbort = () => {
367
+ clearTimeout(timer);
368
+ reject(new Error("login: cancelled"));
369
+ };
370
+ const timer = setTimeout(() => {
371
+ signal.removeEventListener("abort", onAbort);
372
+ resolve10();
373
+ }, ms);
374
+ signal.addEventListener("abort", onAbort, { once: true });
375
+ })
376
+ }
377
+ );
378
+ const identity = await copilotOAuth.fetchCopilotIdentity(result.accessToken, fetchImpl, enterpriseUrl);
379
+ const apiEndpoint = await copilotOAuth.discoverCopilotApiEndpoint(result.accessToken, fetchImpl, enterpriseUrl);
380
+ await copilotOAuth.enableAllCopilotModels(
381
+ result.accessToken,
382
+ { apiEndpoint, ...enterpriseUrl ? { enterpriseUrl } : {} },
383
+ fetchImpl
384
+ );
385
+ const block = {
386
+ authMethod: "oauth",
387
+ status: "authorized",
388
+ accessToken: result.accessToken,
389
+ refreshToken: result.accessToken,
390
+ expiresAt: new Date(Date.now() + copilotOAuth.COPILOT_FAR_FUTURE_MS).toISOString(),
391
+ ...identity.accountId ? { accountId: identity.accountId } : {},
392
+ ...identity.email ? { email: identity.email } : {},
393
+ ...apiEndpoint ? { apiEndpoint } : {},
394
+ ...enterpriseUrl ? { enterpriseUrl } : {},
395
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
396
+ };
397
+ await deps.subscriptionAccountAppender.appendProviderAccount("copilot", block);
398
+ deps.copilotSessions.settle(sessionId, "done");
399
+ }
400
+ function handleCopilotOAuthCancel(sessionId, deps) {
401
+ if (!deps.copilotSessions.cancel(sessionId)) {
402
+ return err4(404, "unknown or expired copilot sign-in session");
403
+ }
404
+ return { status: 200, body: { ok: true } };
405
+ }
406
+ function handleCopilotOAuthStatus(sessionId, deps) {
407
+ const s = deps.copilotSessions.get(sessionId);
408
+ if (!s) return err4(404, "unknown or expired copilot sign-in session");
409
+ return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
410
+ }
411
+
158
412
  // src/allowance/AccountAllowanceService.ts
159
413
  import {
160
- getSharedAccountAllowanceStore as getSharedAccountAllowanceStore2
414
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore7
161
415
  } from "@omnicross/core/pipeline/AccountAllowanceStore";
162
416
  import {
163
417
  getSharedAccountAllowanceScheduling
@@ -191,13 +445,11 @@ function secondsUntil(instant, now) {
191
445
  function windowFromPayload(id, payload, now) {
192
446
  const usedPercent = finitePercent(payload?.utilization);
193
447
  const resetsAt = isoInstant(payload?.resets_at);
194
- const isSonnet = id === "seven-day-sonnet";
195
448
  const isFiveHour = id === "five-hour";
196
449
  return {
197
450
  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,
451
+ label: isFiveHour ? "5 hours" : "7 days",
452
+ scope: "all",
201
453
  usedPercent,
202
454
  windowMinutes: isFiveHour ? 5 * 60 : 7 * 24 * 60,
203
455
  resetsAt,
@@ -205,6 +457,44 @@ function windowFromPayload(id, payload, now) {
205
457
  state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
206
458
  };
207
459
  }
460
+ function limitEntryWindow(entries, kind) {
461
+ const entry = entries.find((candidate) => candidate.kind === kind);
462
+ if (!entry) return void 0;
463
+ return { utilization: entry.percent, resets_at: entry.resets_at };
464
+ }
465
+ function slugifyDisplayName(name) {
466
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
467
+ }
468
+ function scopedWeeklyWindows(entries, now) {
469
+ const seen = /* @__PURE__ */ new Set();
470
+ const windows = [];
471
+ for (const entry of entries) {
472
+ if (entry.kind !== "weekly_scoped") continue;
473
+ const displayName = typeof entry.scope?.model?.display_name === "string" && entry.scope.model.display_name.trim() ? entry.scope.model.display_name.trim() : void 0;
474
+ if (!displayName) continue;
475
+ const slug = slugifyDisplayName(displayName);
476
+ if (!slug || seen.has(slug)) continue;
477
+ seen.add(slug);
478
+ const usedPercent = finitePercent(entry.percent);
479
+ const resetsAt = isoInstant(entry.resets_at);
480
+ windows.push({
481
+ id: `seven-day-${slug}`,
482
+ label: `7 days \xB7 ${displayName}`,
483
+ scope: "model-family",
484
+ modelFamily: slug,
485
+ usedPercent,
486
+ windowMinutes: 7 * 24 * 60,
487
+ resetsAt,
488
+ remainingSeconds: secondsUntil(resetsAt, now),
489
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
490
+ });
491
+ }
492
+ return windows;
493
+ }
494
+ function parseLimitEntries(raw) {
495
+ if (!Array.isArray(raw)) return [];
496
+ return raw.filter((entry) => !!entry && typeof entry === "object");
497
+ }
208
498
  function emptyClaudeWindows(state) {
209
499
  return [
210
500
  {
@@ -222,159 +512,1233 @@ function emptyClaudeWindows(state) {
222
512
  usedPercent: null,
223
513
  windowMinutes: 7 * 24 * 60,
224
514
  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
515
  }
235
- ];
516
+ ];
517
+ }
518
+ function hasHeader(headers, name) {
519
+ const wanted = name.toLowerCase();
520
+ return Object.keys(headers).some((key) => key.toLowerCase() === wanted);
521
+ }
522
+ var ClaudeAllowanceCollector = class {
523
+ constructor(credentials, store = getSharedAccountAllowanceStore(), fetchImpl = (url, init, accountId) => fetchUpstream(url, init, { providerId: "claude", accountId }), identityStore = getSharedIdentityStore(), now = Date.now) {
524
+ this.credentials = credentials;
525
+ this.store = store;
526
+ this.fetchImpl = fetchImpl;
527
+ this.identityStore = identityStore;
528
+ this.now = now;
529
+ }
530
+ credentials;
531
+ store;
532
+ fetchImpl;
533
+ identityStore;
534
+ now;
535
+ inFlight = /* @__PURE__ */ new Map();
536
+ async collectMany(accounts, options = {}) {
537
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
538
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
539
+ }
540
+ collect(account, options = {}) {
541
+ const now = this.now();
542
+ const unsupported = account.tokens.isSetupToken || account.tokens.authMethod !== "oauth";
543
+ if (unsupported) {
544
+ const existing = this.store.get("claude", account.id, now);
545
+ if (existing?.windows.every((window) => window.state === "unsupported")) return Promise.resolve(existing);
546
+ const snapshot = this.unsupportedSnapshot(account.id, now);
547
+ this.store.set(snapshot);
548
+ return Promise.resolve(snapshot);
549
+ }
550
+ const cached = this.store.get("claude", account.id, now);
551
+ if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) return Promise.resolve(cached);
552
+ const running = this.inFlight.get(account.id);
553
+ if (running) return running;
554
+ const promise = this.fetchAccount(account.id).catch(() => this.failureSnapshot(account.id, "claude_usage_request_failed", this.now())).finally(() => this.inFlight.delete(account.id));
555
+ this.inFlight.set(account.id, promise);
556
+ return promise;
557
+ }
558
+ isCacheValid(snapshot, now, refreshAheadMs) {
559
+ if (snapshot.source !== "oauth-usage-api") return false;
560
+ if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
561
+ const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
562
+ const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
563
+ return Number.isFinite(expiresAt) && expiresAt > now + ahead;
564
+ }
565
+ async fetchAccount(accountId) {
566
+ let token = await this.credentials.getAccessTokenForAccount("claude", accountId);
567
+ if (!token) return this.failureSnapshot(accountId, "claude_usage_token_unavailable", this.now());
568
+ let response = await this.request(accountId, token);
569
+ if (response.status === 401) {
570
+ const refreshed = await this.credentials.refreshAccountToken("claude", accountId);
571
+ if (!refreshed) return this.failureSnapshot(accountId, "claude_usage_unauthorized", this.now());
572
+ token = await this.credentials.getAccessTokenForAccount("claude", accountId);
573
+ if (!token) return this.failureSnapshot(accountId, "claude_usage_token_unavailable", this.now());
574
+ response = await this.request(accountId, token);
575
+ }
576
+ if (response.status === 403) {
577
+ const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "claude_usage_unsupported");
578
+ this.store.set(snapshot2);
579
+ return snapshot2;
580
+ }
581
+ if (!response.ok) {
582
+ return this.failureSnapshot(accountId, "claude_usage_http_error", this.now());
583
+ }
584
+ let payload;
585
+ try {
586
+ payload = await response.json();
587
+ } catch {
588
+ return this.failureSnapshot(accountId, "claude_usage_invalid_response", this.now());
589
+ }
590
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
591
+ return this.failureSnapshot(accountId, "claude_usage_invalid_response", this.now());
592
+ }
593
+ const now = this.now();
594
+ const usage = payload;
595
+ const limitEntries = parseLimitEntries(usage.limits);
596
+ const fiveHour = usage.five_hour ?? limitEntryWindow(limitEntries, "session");
597
+ const sevenDay = usage.seven_day ?? limitEntryWindow(limitEntries, "weekly_all");
598
+ const snapshot = {
599
+ providerId: "claude",
600
+ accountId,
601
+ source: "oauth-usage-api",
602
+ observedAt: new Date(now).toISOString(),
603
+ expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
604
+ windows: [
605
+ windowFromPayload("five-hour", fiveHour, now),
606
+ windowFromPayload("seven-day", sevenDay, now),
607
+ ...scopedWeeklyWindows(limitEntries, now)
608
+ ].slice(0, 8)
609
+ };
610
+ this.store.set(snapshot);
611
+ return snapshot;
612
+ }
613
+ request(accountId, token) {
614
+ const headers = {
615
+ Authorization: `Bearer ${token}`,
616
+ Accept: "application/json",
617
+ "Content-Type": "application/json",
618
+ "anthropic-beta": "oauth-2025-04-20",
619
+ "Accept-Language": "en-US,en;q=0.9"
620
+ };
621
+ applyFingerprint(this.identityStore, headers, "claude", accountId, void 0);
622
+ if (!hasHeader(headers, "user-agent")) {
623
+ headers["User-Agent"] = "claude-cli/2.0.53 (external, cli)";
624
+ }
625
+ return this.fetchImpl(CLAUDE_USAGE_URL, {
626
+ method: "GET",
627
+ headers,
628
+ signal: AbortSignal.timeout(15e3)
629
+ }, accountId);
630
+ }
631
+ failureSnapshot(accountId, code, now) {
632
+ const existing = this.store.get("claude", accountId, now);
633
+ const snapshot = existing ? {
634
+ ...existing,
635
+ expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
636
+ windows: existing.windows.map((window) => ({
637
+ ...window,
638
+ state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
639
+ })),
640
+ lastErrorCode: code
641
+ } : {
642
+ providerId: "claude",
643
+ accountId,
644
+ source: "oauth-usage-api",
645
+ observedAt: new Date(now).toISOString(),
646
+ expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
647
+ windows: emptyClaudeWindows("unavailable"),
648
+ lastErrorCode: code
649
+ };
650
+ this.store.set(snapshot);
651
+ return snapshot;
652
+ }
653
+ unsupportedSnapshot(accountId, now, code = "claude_usage_unsupported_auth") {
654
+ return {
655
+ providerId: "claude",
656
+ accountId,
657
+ source: "oauth-usage-api",
658
+ observedAt: new Date(now).toISOString(),
659
+ windows: emptyClaudeWindows("unsupported"),
660
+ lastErrorCode: code
661
+ };
662
+ }
663
+ };
664
+
665
+ // src/allowance/CodexAllowanceCollector.ts
666
+ import {
667
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore2
668
+ } from "@omnicross/core/pipeline/AccountAllowanceStore";
669
+ import { fetchUpstream as fetchUpstream2 } from "@omnicross/core/pipeline/upstreamFetch";
670
+ var CODEX_ALLOWANCE_CACHE_MS = 5 * 6e4;
671
+ var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
672
+ var CODEX_CLI_USER_AGENT = "codex_cli_rs/0.144.5";
673
+ function finiteNumber(value) {
674
+ if (value === null || value === void 0 || value === "") return null;
675
+ const parsed = typeof value === "number" ? value : Number(value);
676
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
677
+ }
678
+ function finitePercent2(value) {
679
+ const parsed = finiteNumber(value);
680
+ return parsed !== null && parsed <= 100 ? parsed : null;
681
+ }
682
+ function epochMs(value) {
683
+ return value > 1e11 ? value : value * 1e3;
684
+ }
685
+ function secondsUntil2(instant, now) {
686
+ if (!instant) return void 0;
687
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
688
+ }
689
+ function decodeJwtClaims(token) {
690
+ const parts = token.split(".");
691
+ if (parts.length !== 3) return void 0;
692
+ try {
693
+ const json2 = Buffer.from(parts[1], "base64url").toString("utf8");
694
+ const parsed = JSON.parse(json2);
695
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
696
+ } catch {
697
+ return void 0;
698
+ }
699
+ }
700
+ function chatgptAccountIdFromClaims(claims) {
701
+ const auth = claims?.["https://api.openai.com/auth"];
702
+ if (!auth || typeof auth !== "object") return void 0;
703
+ const accountId = auth.chatgpt_account_id;
704
+ return typeof accountId === "string" && accountId.trim() ? accountId.trim() : void 0;
705
+ }
706
+ function resolveCodexChatGptAccountId(tokens) {
707
+ if (tokens.accountId?.trim()) return tokens.accountId.trim();
708
+ if (tokens.idToken) {
709
+ const fromIdToken = chatgptAccountIdFromClaims(decodeJwtClaims(tokens.idToken));
710
+ if (fromIdToken) return fromIdToken;
711
+ }
712
+ if (tokens.accessToken) {
713
+ return chatgptAccountIdFromClaims(decodeJwtClaims(tokens.accessToken));
714
+ }
715
+ return void 0;
716
+ }
717
+ function windowFromPayload2(id, payload, now) {
718
+ const usedPercent = finitePercent2(payload?.used_percent);
719
+ const resetAtSeconds = finiteNumber(payload?.reset_at);
720
+ const resetAfterSeconds = finiteNumber(payload?.reset_after_seconds);
721
+ const windowSeconds = finiteNumber(payload?.limit_window_seconds);
722
+ const resetsAt = resetAtSeconds !== null && resetAtSeconds > 0 ? new Date(epochMs(resetAtSeconds)).toISOString() : resetAfterSeconds !== null && resetAfterSeconds > 0 ? new Date(now + resetAfterSeconds * 1e3).toISOString() : void 0;
723
+ const windowMinutes = windowSeconds !== null && windowSeconds > 0 ? Math.round(windowSeconds / 60) : void 0;
724
+ return {
725
+ id,
726
+ label: id === "primary" ? "Primary" : "Secondary",
727
+ scope: "all",
728
+ usedPercent,
729
+ ...windowMinutes !== void 0 ? { windowMinutes } : {},
730
+ ...resetsAt !== void 0 ? { resetsAt } : {},
731
+ remainingSeconds: secondsUntil2(resetsAt, now),
732
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
733
+ };
734
+ }
735
+ var CodexAllowanceCollector = class {
736
+ constructor(credentials, store = getSharedAccountAllowanceStore2(), fetchImpl = (url, init, accountId) => fetchUpstream2(url, init, { providerId: "codex", accountId, redactBodies: true }), now = Date.now) {
737
+ this.credentials = credentials;
738
+ this.store = store;
739
+ this.fetchImpl = fetchImpl;
740
+ this.now = now;
741
+ }
742
+ credentials;
743
+ store;
744
+ fetchImpl;
745
+ now;
746
+ inFlight = /* @__PURE__ */ new Map();
747
+ async collectMany(accounts, options = {}) {
748
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
749
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
750
+ }
751
+ collect(account, options = {}) {
752
+ const now = this.now();
753
+ const unsupported = account.tokens.authMethod !== "oauth";
754
+ if (unsupported) {
755
+ const existing = this.store.get("codex", account.id, now);
756
+ if (existing?.windows.every((window) => window.state === "unsupported")) {
757
+ return Promise.resolve(existing);
758
+ }
759
+ const snapshot = this.unsupportedSnapshot(account.id, now);
760
+ this.store.set(snapshot);
761
+ return Promise.resolve(snapshot);
762
+ }
763
+ const cached = this.store.get("codex", account.id, now);
764
+ if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
765
+ return Promise.resolve(cached);
766
+ }
767
+ const running = this.inFlight.get(account.id);
768
+ if (running) return running;
769
+ 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));
770
+ this.inFlight.set(account.id, promise);
771
+ return promise;
772
+ }
773
+ /**
774
+ * A response-header snapshot stays a valid cache hit only while fresh; an
775
+ * active oauth-usage snapshot is honored on the same 5-minute cadence as
776
+ * Claude's (the poll is cheap and quota is the scheduling input).
777
+ */
778
+ isCacheValid(snapshot, now, refreshAheadMs) {
779
+ if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
780
+ const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
781
+ const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
782
+ return Number.isFinite(expiresAt) && expiresAt > now + ahead;
783
+ }
784
+ async fetchAccount(accountId, tokens) {
785
+ let accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
786
+ if (!accessToken) {
787
+ return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
788
+ }
789
+ let response = await this.request(accountId, accessToken, tokens);
790
+ if (response.status === 401) {
791
+ const refreshed = await this.credentials.refreshAccountToken("codex", accountId);
792
+ if (!refreshed) {
793
+ return this.failureSnapshot(accountId, "codex_usage_unauthorized", this.now());
794
+ }
795
+ accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
796
+ if (!accessToken) {
797
+ return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
798
+ }
799
+ response = await this.request(accountId, accessToken, tokens);
800
+ }
801
+ if (response.status === 403) {
802
+ const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "codex_usage_unsupported");
803
+ this.store.set(snapshot2);
804
+ return snapshot2;
805
+ }
806
+ if (!response.ok) {
807
+ return this.failureSnapshot(accountId, "codex_usage_http_error", this.now());
808
+ }
809
+ let payload;
810
+ try {
811
+ payload = await response.json();
812
+ } catch {
813
+ return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
814
+ }
815
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
816
+ return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
817
+ }
818
+ const now = this.now();
819
+ const usage = payload.rate_limit;
820
+ const previous = this.store.get("codex", accountId, now);
821
+ const snapshot = {
822
+ providerId: "codex",
823
+ accountId,
824
+ source: "oauth-usage-api",
825
+ observedAt: new Date(now).toISOString(),
826
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
827
+ windows: [
828
+ windowFromPayload2("primary", usage?.primary_window ?? void 0, now),
829
+ windowFromPayload2("secondary", usage?.secondary_window ?? void 0, now)
830
+ ],
831
+ // The wham payload has no ratio field; keep the passively-observed value.
832
+ ...previous?.primaryOverSecondaryLimitPercent !== void 0 ? { primaryOverSecondaryLimitPercent: previous.primaryOverSecondaryLimitPercent } : {}
833
+ };
834
+ this.store.set(snapshot);
835
+ return snapshot;
836
+ }
837
+ request(accountId, accessToken, tokens) {
838
+ const headers = {
839
+ Authorization: `Bearer ${accessToken}`,
840
+ Accept: "application/json",
841
+ "User-Agent": CODEX_CLI_USER_AGENT
842
+ };
843
+ const chatgptAccountId = resolveCodexChatGptAccountId(tokens);
844
+ if (chatgptAccountId) headers["ChatGPT-Account-Id"] = chatgptAccountId;
845
+ return this.fetchImpl(CODEX_USAGE_URL, {
846
+ method: "GET",
847
+ headers,
848
+ signal: AbortSignal.timeout(15e3)
849
+ }, accountId);
850
+ }
851
+ failureSnapshot(accountId, code, now) {
852
+ const existing = this.store.get("codex", accountId, now);
853
+ const snapshot = existing ? {
854
+ ...existing,
855
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
856
+ windows: existing.windows.map((window) => ({
857
+ ...window,
858
+ state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
859
+ })),
860
+ lastErrorCode: code
861
+ } : {
862
+ providerId: "codex",
863
+ accountId,
864
+ source: "oauth-usage-api",
865
+ observedAt: new Date(now).toISOString(),
866
+ expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
867
+ windows: [
868
+ { id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unavailable" },
869
+ { id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unavailable" }
870
+ ],
871
+ lastErrorCode: code
872
+ };
873
+ this.store.set(snapshot);
874
+ return snapshot;
875
+ }
876
+ unsupportedSnapshot(accountId, now, code = "codex_usage_unsupported_auth") {
877
+ return {
878
+ providerId: "codex",
879
+ accountId,
880
+ source: "oauth-usage-api",
881
+ observedAt: new Date(now).toISOString(),
882
+ windows: [
883
+ { id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unsupported" },
884
+ { id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unsupported" }
885
+ ],
886
+ lastErrorCode: code
887
+ };
888
+ }
889
+ };
890
+
891
+ // src/allowance/KimiAllowanceCollector.ts
892
+ import {
893
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore3
894
+ } from "@omnicross/core/pipeline/AccountAllowanceStore";
895
+ import { fetchUpstream as fetchUpstream3 } from "@omnicross/core/pipeline/upstreamFetch";
896
+ import { kimiFingerprintHeaders } from "@omnicross/subscriptions";
897
+ var KIMI_ALLOWANCE_CACHE_MS = 5 * 6e4;
898
+ var KIMI_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
899
+ function finiteNumber2(value) {
900
+ if (value === null || value === void 0 || value === "") return void 0;
901
+ const parsed = typeof value === "number" ? value : Number(value);
902
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
903
+ }
904
+ function isRecord(value) {
905
+ return !!value && typeof value === "object" && !Array.isArray(value);
906
+ }
907
+ function parseResetMs(row, nowMs) {
908
+ for (const key of ["reset_at", "resetAt", "reset_time", "resetTime"]) {
909
+ const value = row[key];
910
+ if (typeof value === "string" && value.trim()) {
911
+ const parsed = Date.parse(value);
912
+ if (Number.isFinite(parsed)) return parsed;
913
+ }
914
+ const numeric = finiteNumber2(value);
915
+ if (numeric !== void 0 && numeric > 1e9) {
916
+ return numeric > 1e12 ? numeric : numeric * 1e3;
917
+ }
918
+ }
919
+ for (const key of ["reset_in", "resetIn", "ttl", "window"]) {
920
+ const seconds = finiteNumber2(row[key]);
921
+ if (seconds !== void 0) return nowMs + seconds * 1e3;
922
+ }
923
+ return void 0;
924
+ }
925
+ var MINUTE_MS = 6e4;
926
+ var HOUR_MS = 36e5;
927
+ var DAY_MS = 864e5;
928
+ function canonicalWindow(durationMs) {
929
+ if (durationMs === 5 * HOUR_MS) return { id: "five-hour", label: "5 hours", minutes: 300 };
930
+ if (durationMs === 7 * DAY_MS) return { id: "seven-day", label: "7 days", minutes: 10080 };
931
+ if (durationMs > 0 && durationMs % DAY_MS === 0) {
932
+ const days = durationMs / DAY_MS;
933
+ return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
934
+ }
935
+ if (durationMs > 0 && durationMs % HOUR_MS === 0) {
936
+ const hours = durationMs / HOUR_MS;
937
+ return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
938
+ }
939
+ return void 0;
940
+ }
941
+ function secondsUntil3(instant, now) {
942
+ if (!instant) return void 0;
943
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
944
+ }
945
+ function windowFromRow(row, fallback, now) {
946
+ 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;
947
+ const resetsAt = row?.resetsAtMs !== void 0 ? new Date(row.resetsAtMs).toISOString() : void 0;
948
+ return {
949
+ id: fallback.id,
950
+ label: fallback.label,
951
+ scope: "all",
952
+ usedPercent,
953
+ windowMinutes: fallback.minutes,
954
+ ...resetsAt !== void 0 ? { resetsAt } : {},
955
+ remainingSeconds: secondsUntil3(resetsAt, now),
956
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
957
+ };
958
+ }
959
+ function parseKimiUsagePayload(payload, now) {
960
+ if (!isRecord(payload)) return [];
961
+ const byId = /* @__PURE__ */ new Map();
962
+ const rowFrom = (data) => {
963
+ const limit = finiteNumber2(data["limit"]);
964
+ let used = finiteNumber2(data["used"]);
965
+ const remaining = finiteNumber2(data["remaining"]);
966
+ if (used === void 0 && remaining !== void 0 && limit !== void 0) {
967
+ used = limit - remaining;
968
+ }
969
+ let windowDurationMs;
970
+ const windowData = isRecord(data["window"]) ? data["window"] : void 0;
971
+ const duration = finiteNumber2(windowData?.["duration"]);
972
+ const timeUnit = typeof windowData?.["timeUnit"] === "string" ? windowData["timeUnit"].toUpperCase() : "";
973
+ if (duration !== void 0) {
974
+ if (timeUnit.includes("MINUTE")) windowDurationMs = duration * MINUTE_MS;
975
+ else if (timeUnit.includes("HOUR")) windowDurationMs = duration * HOUR_MS;
976
+ else if (timeUnit.includes("DAY")) windowDurationMs = duration * DAY_MS;
977
+ else if (timeUnit.includes("WEEK")) windowDurationMs = duration * 7 * DAY_MS;
978
+ else if (timeUnit.includes("SECOND")) windowDurationMs = duration * 1e3;
979
+ }
980
+ const resetsAtMs = parseResetMs(windowData && parseResetMs(windowData, now) !== void 0 ? windowData : data, now);
981
+ return { used, limit, remaining, ...resetsAtMs !== void 0 ? { resetsAtMs } : {}, ...windowDurationMs !== void 0 ? { windowDurationMs } : {} };
982
+ };
983
+ if (isRecord(payload["usage"])) {
984
+ const row = rowFrom(payload["usage"]);
985
+ const window = windowFromRow({ ...row, resetsAtMs: row.resetsAtMs }, { id: "seven-day", label: "7 days", minutes: 10080 }, now);
986
+ byId.set("seven-day", window);
987
+ }
988
+ if (Array.isArray(payload["limits"])) {
989
+ for (const item of payload["limits"]) {
990
+ if (!isRecord(item)) continue;
991
+ const detail = isRecord(item["detail"]) ? item["detail"] : item;
992
+ const row = rowFrom(detail);
993
+ const canonical = row.windowDurationMs !== void 0 ? canonicalWindow(row.windowDurationMs) : void 0;
994
+ if (!canonical) continue;
995
+ const window = windowFromRow(row, canonical, now);
996
+ const existing = byId.get(canonical.id);
997
+ if (!existing || (window.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
998
+ byId.set(canonical.id, window);
999
+ }
1000
+ }
1001
+ }
1002
+ return [...byId.values()].sort((a, b) => (a.windowMinutes ?? Infinity) - (b.windowMinutes ?? Infinity)).slice(0, 4);
1003
+ }
1004
+ var KimiAllowanceCollector = class {
1005
+ constructor(credentials, store = getSharedAccountAllowanceStore3(), fetchImpl = (url, init, accountId) => fetchUpstream3(url, init, { providerId: "kimi", accountId, redactBodies: true }), now = Date.now) {
1006
+ this.credentials = credentials;
1007
+ this.store = store;
1008
+ this.fetchImpl = fetchImpl;
1009
+ this.now = now;
1010
+ }
1011
+ credentials;
1012
+ store;
1013
+ fetchImpl;
1014
+ now;
1015
+ inFlight = /* @__PURE__ */ new Map();
1016
+ async collectMany(accounts, options = {}) {
1017
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
1018
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
1019
+ }
1020
+ collect(account, options = {}) {
1021
+ const now = this.now();
1022
+ if (account.tokens.authMethod !== "oauth") {
1023
+ const existing = this.store.get("kimi", account.id, now);
1024
+ if (existing?.windows.every((window) => window.state === "unsupported")) {
1025
+ return Promise.resolve(existing);
1026
+ }
1027
+ const snapshot = this.unsupportedSnapshot(account.id, now);
1028
+ this.store.set(snapshot);
1029
+ return Promise.resolve(snapshot);
1030
+ }
1031
+ const cached = this.store.get("kimi", account.id, now);
1032
+ if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
1033
+ return Promise.resolve(cached);
1034
+ }
1035
+ const running = this.inFlight.get(account.id);
1036
+ if (running) return running;
1037
+ 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));
1038
+ this.inFlight.set(account.id, promise);
1039
+ return promise;
1040
+ }
1041
+ isCacheValid(snapshot, now, refreshAheadMs) {
1042
+ if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
1043
+ const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
1044
+ const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
1045
+ return Number.isFinite(expiresAt) && expiresAt > now + ahead;
1046
+ }
1047
+ async fetchAccount(accountId, tokens) {
1048
+ let accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
1049
+ if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
1050
+ let response = await this.request(accountId, accessToken, tokens);
1051
+ if (response.status === 401) {
1052
+ const refreshed = await this.credentials.refreshAccountToken("kimi", accountId);
1053
+ if (!refreshed) return this.failureSnapshot(accountId, "kimi_usage_unauthorized", this.now());
1054
+ accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
1055
+ if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
1056
+ response = await this.request(accountId, accessToken, tokens);
1057
+ }
1058
+ if (response.status === 403) {
1059
+ const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "kimi_usage_unsupported");
1060
+ this.store.set(snapshot2);
1061
+ return snapshot2;
1062
+ }
1063
+ if (!response.ok) return this.failureSnapshot(accountId, "kimi_usage_http_error", this.now());
1064
+ let payload;
1065
+ try {
1066
+ payload = await response.json();
1067
+ } catch {
1068
+ return this.failureSnapshot(accountId, "kimi_usage_invalid_response", this.now());
1069
+ }
1070
+ const now = this.now();
1071
+ const windows = parseKimiUsagePayload(payload, now);
1072
+ const snapshot = {
1073
+ providerId: "kimi",
1074
+ accountId,
1075
+ source: "oauth-usage-api",
1076
+ observedAt: new Date(now).toISOString(),
1077
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
1078
+ windows: windows.length > 0 ? windows : [
1079
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
1080
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
1081
+ ],
1082
+ ...windows.length > 0 ? {} : { lastErrorCode: "kimi_usage_invalid_response" }
1083
+ };
1084
+ this.store.set(snapshot);
1085
+ return snapshot;
1086
+ }
1087
+ request(accountId, accessToken, tokens) {
1088
+ return this.fetchImpl(KIMI_USAGE_URL, {
1089
+ method: "GET",
1090
+ headers: {
1091
+ Authorization: `Bearer ${accessToken}`,
1092
+ Accept: "application/json",
1093
+ ...kimiFingerprintHeaders(tokens.deviceId)
1094
+ },
1095
+ signal: AbortSignal.timeout(15e3)
1096
+ }, accountId);
1097
+ }
1098
+ failureSnapshot(accountId, code, now) {
1099
+ const existing = this.store.get("kimi", accountId, now);
1100
+ const snapshot = existing ? {
1101
+ ...existing,
1102
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
1103
+ windows: existing.windows.map((window) => ({
1104
+ ...window,
1105
+ state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
1106
+ })),
1107
+ lastErrorCode: code
1108
+ } : {
1109
+ providerId: "kimi",
1110
+ accountId,
1111
+ source: "oauth-usage-api",
1112
+ observedAt: new Date(now).toISOString(),
1113
+ expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
1114
+ windows: [
1115
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
1116
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
1117
+ ],
1118
+ lastErrorCode: code
1119
+ };
1120
+ this.store.set(snapshot);
1121
+ return snapshot;
1122
+ }
1123
+ unsupportedSnapshot(accountId, now, code = "kimi_usage_unsupported_auth") {
1124
+ return {
1125
+ providerId: "kimi",
1126
+ accountId,
1127
+ source: "oauth-usage-api",
1128
+ observedAt: new Date(now).toISOString(),
1129
+ windows: [
1130
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unsupported" },
1131
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unsupported" }
1132
+ ],
1133
+ lastErrorCode: code
1134
+ };
1135
+ }
1136
+ };
1137
+
1138
+ // src/allowance/GrokAllowanceCollector.ts
1139
+ import {
1140
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore4
1141
+ } from "@omnicross/core/pipeline/AccountAllowanceStore";
1142
+ import { fetchUpstream as fetchUpstream4 } from "@omnicross/core/pipeline/upstreamFetch";
1143
+ var GROK_ALLOWANCE_CACHE_MS = 5 * 6e4;
1144
+ var GROK_BILLING_BASE = "https://cli-chat-proxy.grok.com";
1145
+ var GROK_BILLING_CREDITS_URL = `${GROK_BILLING_BASE}/v1/billing?format=credits`;
1146
+ var GROK_BILLING_MONTHLY_URL = `${GROK_BILLING_BASE}/v1/billing`;
1147
+ function isRecord2(value) {
1148
+ return !!value && typeof value === "object" && !Array.isArray(value);
1149
+ }
1150
+ function finiteNumber3(value) {
1151
+ if (value === null || value === void 0 || value === "") return void 0;
1152
+ const parsed = typeof value === "number" ? value : Number(value);
1153
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
1154
+ }
1155
+ function percent(value) {
1156
+ const parsed = finiteNumber3(value);
1157
+ return parsed !== void 0 && parsed <= 100 ? parsed : void 0;
1158
+ }
1159
+ function onDemandAmount(value) {
1160
+ return isRecord2(value) ? finiteNumber3(value["val"]) : void 0;
1161
+ }
1162
+ function confirmsNoMonthlyQuota(raw) {
1163
+ const limit = onDemandAmount(raw["monthlyLimit"]);
1164
+ if (limit !== void 0) return limit === 0;
1165
+ return parseWeeklyConfig(raw)?.inferredPercent === true;
1166
+ }
1167
+ function parseWeeklyConfig(raw) {
1168
+ const period = isRecord2(raw["currentPeriod"]) ? raw["currentPeriod"] : void 0;
1169
+ if (!period) return null;
1170
+ const start = typeof period["start"] === "string" ? Date.parse(period["start"]) : Number.NaN;
1171
+ const end = typeof period["end"] === "string" ? Date.parse(period["end"]) : Number.NaN;
1172
+ const type = typeof period["type"] === "string" ? period["type"] : "";
1173
+ if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) return null;
1174
+ if (!type.toUpperCase().includes("WEEK")) return null;
1175
+ const inferred = raw["creditUsagePercent"] === void 0 || raw["creditUsagePercent"] === null;
1176
+ let creditUsagePercent;
1177
+ if (inferred) {
1178
+ creditUsagePercent = end > Date.now() ? 0 : void 0;
1179
+ } else {
1180
+ creditUsagePercent = percent(raw["creditUsagePercent"]);
1181
+ }
1182
+ if (creditUsagePercent === void 0) return null;
1183
+ return {
1184
+ creditUsagePercent,
1185
+ inferredPercent: inferred,
1186
+ resetsAtMs: end,
1187
+ unified: raw["isUnifiedBillingUser"] === true
1188
+ };
1189
+ }
1190
+ function parseMonthlyConfig(raw) {
1191
+ const start = typeof raw["billingPeriodStart"] === "string" ? Date.parse(raw["billingPeriodStart"]) : Number.NaN;
1192
+ const end = typeof raw["billingPeriodEnd"] === "string" ? Date.parse(raw["billingPeriodEnd"]) : Number.NaN;
1193
+ if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) return null;
1194
+ const limit = onDemandAmount(raw["monthlyLimit"]);
1195
+ const used = onDemandAmount(raw["used"]);
1196
+ if (limit === void 0 || limit <= 0 || used === void 0) return null;
1197
+ return { used, limit, periodStartMs: start, periodEndMs: end };
1198
+ }
1199
+ function secondsUntil4(instant, now) {
1200
+ if (!instant) return void 0;
1201
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
1202
+ }
1203
+ var MINUTE_MS2 = 6e4;
1204
+ var DAY_MS2 = 864e5;
1205
+ var WEEK_MINUTES = 7 * 24 * 60;
1206
+ function weeklyWindow(config, now) {
1207
+ const resetsAt = new Date(config.resetsAtMs).toISOString();
1208
+ return {
1209
+ id: "seven-day",
1210
+ label: "7 days",
1211
+ scope: "all",
1212
+ usedPercent: config.creditUsagePercent,
1213
+ windowMinutes: WEEK_MINUTES,
1214
+ resetsAt,
1215
+ remainingSeconds: secondsUntil4(resetsAt, now),
1216
+ state: "fresh"
1217
+ };
1218
+ }
1219
+ function monthlyWindow(config, now) {
1220
+ const resetsAt = new Date(config.periodEndMs).toISOString();
1221
+ const days = Math.max(1, Math.round((config.periodEndMs - config.periodStartMs) / DAY_MS2));
1222
+ return {
1223
+ id: "thirty-day",
1224
+ label: days === 30 || days === 31 ? "30 days" : `${days} days`,
1225
+ scope: "all",
1226
+ usedPercent: Math.round(Math.min(100, config.used / config.limit * 100) * 10) / 10,
1227
+ windowMinutes: Math.round((config.periodEndMs - config.periodStartMs) / MINUTE_MS2),
1228
+ resetsAt,
1229
+ remainingSeconds: secondsUntil4(resetsAt, now),
1230
+ state: "fresh"
1231
+ };
1232
+ }
1233
+ function onDemandWindow(raw) {
1234
+ const cap = onDemandAmount(raw["onDemandCap"]);
1235
+ const used = onDemandAmount(raw["onDemandUsed"]);
1236
+ if (cap === void 0 || cap <= 0 || used === void 0) return null;
1237
+ return {
1238
+ id: "on-demand",
1239
+ label: "On-demand",
1240
+ scope: "all",
1241
+ usedPercent: Math.round(Math.min(100, used / cap * 100) * 10) / 10,
1242
+ state: "fresh"
1243
+ };
1244
+ }
1245
+ async function probeBilling(url, accessToken, accountId, fetchImpl) {
1246
+ try {
1247
+ const response = await fetchImpl(url, {
1248
+ method: "GET",
1249
+ headers: {
1250
+ Authorization: `Bearer ${accessToken}`,
1251
+ Accept: "application/json",
1252
+ "X-XAI-Token-Auth": "xai-grok-cli"
1253
+ },
1254
+ redirect: "error",
1255
+ signal: AbortSignal.timeout(15e3)
1256
+ }, accountId);
1257
+ if (!response.ok) return { status: response.status, payload: null };
1258
+ const payload = await response.json();
1259
+ return { status: response.status, payload: isRecord2(payload) ? payload : null };
1260
+ } catch {
1261
+ return { status: 0, payload: null };
1262
+ }
1263
+ }
1264
+ function parseGrokBillingPayloads(creditsPayload, monthlyPayload, now) {
1265
+ const creditsConfig = isRecord2(creditsPayload?.["config"]) ? creditsPayload["config"] : null;
1266
+ const monthlyConfig = isRecord2(monthlyPayload?.["config"]) ? monthlyPayload["config"] : null;
1267
+ let weekly = creditsConfig ? parseWeeklyConfig(creditsConfig) : null;
1268
+ const unifiedFlag = creditsConfig?.["isUnifiedBillingUser"] === true;
1269
+ let monthly = monthlyConfig ? parseMonthlyConfig(monthlyConfig) : null;
1270
+ if (weekly?.inferredPercent && unifiedFlag) {
1271
+ if (monthly) {
1272
+ weekly = null;
1273
+ } else if (!monthlyConfig || !confirmsNoMonthlyQuota(monthlyConfig)) {
1274
+ weekly = null;
1275
+ }
1276
+ }
1277
+ const windows = [];
1278
+ if (weekly) windows.push(weeklyWindow(weekly, now));
1279
+ if (monthly) windows.push(monthlyWindow(monthly, now));
1280
+ const onDemandSource = monthly && monthlyConfig ? monthlyConfig : creditsConfig;
1281
+ const onDemand = onDemandSource ? onDemandWindow(onDemandSource) : null;
1282
+ if (onDemand) windows.push(onDemand);
1283
+ return windows.length > 0 ? windows : null;
1284
+ }
1285
+ var GrokAllowanceCollector = class {
1286
+ constructor(credentials, store = getSharedAccountAllowanceStore4(), fetchImpl = (url, init, accountId) => fetchUpstream4(url, init, { providerId: "grok", accountId, redactBodies: true }), now = Date.now) {
1287
+ this.credentials = credentials;
1288
+ this.store = store;
1289
+ this.fetchImpl = fetchImpl;
1290
+ this.now = now;
1291
+ }
1292
+ credentials;
1293
+ store;
1294
+ fetchImpl;
1295
+ now;
1296
+ inFlight = /* @__PURE__ */ new Map();
1297
+ async collectMany(accounts, options = {}) {
1298
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
1299
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
1300
+ }
1301
+ collect(account, options = {}) {
1302
+ const now = this.now();
1303
+ if (account.tokens.authMethod !== "oauth") {
1304
+ const existing = this.store.get("grok", account.id, now);
1305
+ if (existing?.windows.every((window) => window.state === "unsupported")) {
1306
+ return Promise.resolve(existing);
1307
+ }
1308
+ const snapshot = this.unsupportedSnapshot(account.id, now);
1309
+ this.store.set(snapshot);
1310
+ return Promise.resolve(snapshot);
1311
+ }
1312
+ const cached = this.store.get("grok", account.id, now);
1313
+ if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
1314
+ return Promise.resolve(cached);
1315
+ }
1316
+ const running = this.inFlight.get(account.id);
1317
+ if (running) return running;
1318
+ const promise = this.fetchAccount(account.id).catch(() => this.failureSnapshot(account.id, "grok_usage_request_failed", this.now())).finally(() => this.inFlight.delete(account.id));
1319
+ this.inFlight.set(account.id, promise);
1320
+ return promise;
1321
+ }
1322
+ isCacheValid(snapshot, now, refreshAheadMs) {
1323
+ if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
1324
+ const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
1325
+ const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
1326
+ return Number.isFinite(expiresAt) && expiresAt > now + ahead;
1327
+ }
1328
+ async fetchAccount(accountId) {
1329
+ const probe = async () => {
1330
+ const accessToken = await this.credentials.getAccessTokenForAccount("grok", accountId);
1331
+ if (!accessToken) return { unauthorized: true, windows: null };
1332
+ const credits = await probeBilling(GROK_BILLING_CREDITS_URL, accessToken, accountId, this.fetchImpl);
1333
+ if (credits.status === 401 || credits.status === 403) return { unauthorized: true, windows: null };
1334
+ const creditsConfig = isRecord2(credits.payload?.["config"]) ? credits.payload["config"] : null;
1335
+ const weekly = creditsConfig ? parseWeeklyConfig(creditsConfig) : null;
1336
+ const monthly = !weekly || creditsConfig?.["isUnifiedBillingUser"] === true ? await probeBilling(GROK_BILLING_MONTHLY_URL, accessToken, accountId, this.fetchImpl) : { status: 200, payload: null };
1337
+ if (monthly.status === 401 || monthly.status === 403) return { unauthorized: true, windows: null };
1338
+ return {
1339
+ unauthorized: false,
1340
+ windows: parseGrokBillingPayloads(credits.payload, monthly.payload, this.now())
1341
+ };
1342
+ };
1343
+ let result = await probe();
1344
+ if (result.unauthorized) {
1345
+ const refreshed = await this.credentials.refreshAccountToken("grok", accountId);
1346
+ if (!refreshed) return this.failureSnapshot(accountId, "grok_usage_unauthorized", this.now());
1347
+ result = await probe();
1348
+ if (result.unauthorized) {
1349
+ return this.failureSnapshot(accountId, "grok_usage_unauthorized", this.now());
1350
+ }
1351
+ }
1352
+ const now = this.now();
1353
+ if (result.windows && result.windows.length > 0) {
1354
+ const snapshot = {
1355
+ providerId: "grok",
1356
+ accountId,
1357
+ source: "oauth-usage-api",
1358
+ observedAt: new Date(now).toISOString(),
1359
+ expiresAt: new Date(now + GROK_ALLOWANCE_CACHE_MS).toISOString(),
1360
+ windows: result.windows
1361
+ };
1362
+ this.store.set(snapshot);
1363
+ return snapshot;
1364
+ }
1365
+ return this.failureSnapshot(accountId, "grok_usage_invalid_response", now);
1366
+ }
1367
+ failureSnapshot(accountId, code, now) {
1368
+ const existing = this.store.get("grok", accountId, now);
1369
+ const snapshot = existing ? {
1370
+ ...existing,
1371
+ expiresAt: new Date(now + GROK_ALLOWANCE_CACHE_MS).toISOString(),
1372
+ windows: existing.windows.map((window) => ({
1373
+ ...window,
1374
+ state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
1375
+ })),
1376
+ lastErrorCode: code
1377
+ } : {
1378
+ providerId: "grok",
1379
+ accountId,
1380
+ source: "oauth-usage-api",
1381
+ observedAt: new Date(now).toISOString(),
1382
+ expiresAt: new Date(now + GROK_ALLOWANCE_CACHE_MS).toISOString(),
1383
+ windows: [
1384
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" },
1385
+ { id: "thirty-day", label: "30 days", scope: "all", usedPercent: null, state: "unavailable" }
1386
+ ],
1387
+ lastErrorCode: code
1388
+ };
1389
+ this.store.set(snapshot);
1390
+ return snapshot;
1391
+ }
1392
+ unsupportedSnapshot(accountId, now) {
1393
+ return {
1394
+ providerId: "grok",
1395
+ accountId,
1396
+ source: "oauth-usage-api",
1397
+ observedAt: new Date(now).toISOString(),
1398
+ windows: [
1399
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unsupported" },
1400
+ { id: "thirty-day", label: "30 days", scope: "all", usedPercent: null, state: "unsupported" }
1401
+ ],
1402
+ lastErrorCode: "grok_usage_unsupported_auth"
1403
+ };
1404
+ }
1405
+ };
1406
+
1407
+ // src/allowance/CopilotAllowanceCollector.ts
1408
+ import {
1409
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore5
1410
+ } from "@omnicross/core/pipeline/AccountAllowanceStore";
1411
+ import { fetchUpstream as fetchUpstream5 } from "@omnicross/core/pipeline/upstreamFetch";
1412
+ import { COPILOT_GITHUB_HEADERS, copilotGitHubApiBase } from "@omnicross/subscriptions";
1413
+ var COPILOT_ALLOWANCE_CACHE_MS = 5 * 6e4;
1414
+ function isRecord3(value) {
1415
+ return !!value && typeof value === "object" && !Array.isArray(value);
1416
+ }
1417
+ function finiteNumber4(value) {
1418
+ if (value === null || value === void 0 || value === "") return void 0;
1419
+ const parsed = typeof value === "number" ? value : Number(value);
1420
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
1421
+ }
1422
+ function booleanValue(value) {
1423
+ if (typeof value === "boolean") return value;
1424
+ if (value === "true") return true;
1425
+ if (value === "false") return false;
1426
+ return void 0;
1427
+ }
1428
+ function parseQuotaDetail(value) {
1429
+ if (!isRecord3(value)) return null;
1430
+ const entitlement = finiteNumber4(value["entitlement"]);
1431
+ const remaining = finiteNumber4(value["remaining"]);
1432
+ const percentRemaining = finiteNumber4(value["percent_remaining"]);
1433
+ const unlimited = booleanValue(value["unlimited"]);
1434
+ if (entitlement === void 0 || remaining === void 0 || percentRemaining === void 0 || unlimited === void 0) {
1435
+ return null;
1436
+ }
1437
+ return { entitlement, remaining, percentRemaining, unlimited };
1438
+ }
1439
+ function secondsUntil5(instant, now) {
1440
+ if (!instant) return void 0;
1441
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
1442
+ }
1443
+ function parseCopilotUserPayload(payload, now) {
1444
+ if (!isRecord3(payload)) return null;
1445
+ const snapshots = isRecord3(payload["quota_snapshots"]) ? payload["quota_snapshots"] : void 0;
1446
+ if (!snapshots) return null;
1447
+ const resetRaw = payload["quota_reset_date"];
1448
+ const resetsAt = typeof resetRaw === "string" && resetRaw.trim() && Number.isFinite(Date.parse(resetRaw)) ? new Date(Date.parse(resetRaw)).toISOString() : void 0;
1449
+ const windows = [];
1450
+ const premium = parseQuotaDetail(snapshots["premium_interactions"]);
1451
+ if (premium) {
1452
+ const usedPercent = premium.unlimited ? 0 : premium.entitlement > 0 ? Math.round(Math.min(100, (premium.entitlement - premium.remaining) / premium.entitlement * 100) * 10) / 10 : finiteNumber4(premium.percentRemaining) !== void 0 ? Math.round(Math.min(100, Math.max(0, 100 - premium.percentRemaining)) * 10) / 10 : null;
1453
+ if (usedPercent !== null) {
1454
+ windows.push({
1455
+ id: "thirty-day",
1456
+ label: "Monthly",
1457
+ scope: "all",
1458
+ usedPercent,
1459
+ windowMinutes: 30 * 24 * 60,
1460
+ ...resetsAt !== void 0 ? { resetsAt } : {},
1461
+ remainingSeconds: secondsUntil5(resetsAt, now),
1462
+ state: "fresh"
1463
+ });
1464
+ }
1465
+ }
1466
+ const chat = parseQuotaDetail(snapshots["chat"]);
1467
+ if (chat && !chat.unlimited && chat.entitlement > 0) {
1468
+ const usedPercent = Math.round(Math.min(100, (chat.entitlement - chat.remaining) / chat.entitlement * 100) * 10) / 10;
1469
+ windows.push({
1470
+ id: "chat-monthly",
1471
+ label: "Chat (monthly)",
1472
+ scope: "all",
1473
+ usedPercent,
1474
+ windowMinutes: 30 * 24 * 60,
1475
+ ...resetsAt !== void 0 ? { resetsAt } : {},
1476
+ remainingSeconds: secondsUntil5(resetsAt, now),
1477
+ state: "fresh"
1478
+ });
1479
+ }
1480
+ return windows.length > 0 ? windows : null;
236
1481
  }
237
- function hasHeader(headers, name) {
238
- const wanted = name.toLowerCase();
239
- return Object.keys(headers).some((key) => key.toLowerCase() === wanted);
1482
+ function githubApiBase(tokens) {
1483
+ return copilotGitHubApiBase(tokens.enterpriseUrl);
240
1484
  }
241
- var ClaudeAllowanceCollector = class {
242
- constructor(credentials, store = getSharedAccountAllowanceStore(), fetchImpl = (url, init, accountId) => fetchUpstream(url, init, { providerId: "claude", accountId }), identityStore = getSharedIdentityStore(), now = Date.now) {
1485
+ var CopilotAllowanceCollector = class {
1486
+ constructor(credentials, store = getSharedAccountAllowanceStore5(), fetchImpl = (url, init, accountId) => fetchUpstream5(url, init, { providerId: "copilot", accountId, redactBodies: true }), now = Date.now) {
243
1487
  this.credentials = credentials;
244
1488
  this.store = store;
245
1489
  this.fetchImpl = fetchImpl;
246
- this.identityStore = identityStore;
247
1490
  this.now = now;
248
1491
  }
249
1492
  credentials;
250
1493
  store;
251
1494
  fetchImpl;
252
- identityStore;
253
1495
  now;
254
1496
  inFlight = /* @__PURE__ */ new Map();
255
1497
  async collectMany(accounts, options = {}) {
256
- const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
1498
+ const settled = await Promise.allSettled(
1499
+ accounts.map((account) => this.collect(account, options))
1500
+ );
257
1501
  return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
258
1502
  }
259
1503
  collect(account, options = {}) {
260
1504
  const now = this.now();
261
- const unsupported = account.tokens.isSetupToken || account.tokens.authMethod !== "oauth";
262
- if (unsupported) {
263
- const existing = this.store.get("claude", account.id, now);
264
- if (existing?.windows.every((window) => window.state === "unsupported")) return Promise.resolve(existing);
1505
+ if (account.tokens.authMethod !== "oauth") {
1506
+ const existing = this.store.get("copilot", account.id, now);
1507
+ if (existing?.windows.every((window) => window.state === "unsupported")) {
1508
+ return Promise.resolve(existing);
1509
+ }
265
1510
  const snapshot = this.unsupportedSnapshot(account.id, now);
266
1511
  this.store.set(snapshot);
267
1512
  return Promise.resolve(snapshot);
268
1513
  }
269
- const cached = this.store.get("claude", account.id, now);
270
- if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) return Promise.resolve(cached);
1514
+ const cached = this.store.get("copilot", account.id, now);
1515
+ if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
1516
+ return Promise.resolve(cached);
1517
+ }
271
1518
  const running = this.inFlight.get(account.id);
272
1519
  if (running) return running;
273
- const promise = this.fetchAccount(account.id).catch(() => this.failureSnapshot(account.id, "claude_usage_request_failed", this.now())).finally(() => this.inFlight.delete(account.id));
1520
+ const promise = this.fetchAccount(account.id, account.tokens).catch(() => this.failureSnapshot(account.id, "copilot_usage_request_failed", this.now())).finally(() => this.inFlight.delete(account.id));
274
1521
  this.inFlight.set(account.id, promise);
275
1522
  return promise;
276
1523
  }
277
1524
  isCacheValid(snapshot, now, refreshAheadMs) {
278
- if (snapshot.source !== "oauth-usage-api") return false;
279
1525
  if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
280
1526
  const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
281
1527
  const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
282
1528
  return Number.isFinite(expiresAt) && expiresAt > now + ahead;
283
1529
  }
284
- async fetchAccount(accountId) {
285
- let token = await this.credentials.getAccessTokenForAccount("claude", accountId);
286
- if (!token) return this.failureSnapshot(accountId, "claude_usage_token_unavailable", this.now());
287
- let response = await this.request(accountId, token);
288
- if (response.status === 401) {
289
- const refreshed = await this.credentials.refreshAccountToken("claude", accountId);
290
- if (!refreshed) return this.failureSnapshot(accountId, "claude_usage_unauthorized", this.now());
291
- token = await this.credentials.getAccessTokenForAccount("claude", accountId);
292
- if (!token) return this.failureSnapshot(accountId, "claude_usage_token_unavailable", this.now());
293
- response = await this.request(accountId, token);
294
- }
295
- if (response.status === 403) {
296
- const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "claude_usage_unsupported");
297
- this.store.set(snapshot2);
298
- return snapshot2;
299
- }
300
- if (!response.ok) {
301
- return this.failureSnapshot(accountId, "claude_usage_http_error", this.now());
1530
+ async fetchAccount(accountId, tokens) {
1531
+ let accessToken = await this.credentials.getAccessTokenForAccount("copilot", accountId);
1532
+ if (!accessToken) return this.failureSnapshot(accountId, "copilot_usage_token_unavailable", this.now());
1533
+ let response = await this.request(accountId, accessToken, tokens);
1534
+ if (response.status === 401 || response.status === 403) {
1535
+ const refreshed = await this.credentials.refreshAccountToken("copilot", accountId);
1536
+ if (!refreshed) return this.failureSnapshot(accountId, "copilot_usage_unauthorized", this.now());
1537
+ accessToken = await this.credentials.getAccessTokenForAccount("copilot", accountId);
1538
+ if (!accessToken) return this.failureSnapshot(accountId, "copilot_usage_token_unavailable", this.now());
1539
+ response = await this.request(accountId, accessToken, tokens);
1540
+ if (response.status === 401 || response.status === 403) {
1541
+ return this.failureSnapshot(accountId, "copilot_usage_unauthorized", this.now());
1542
+ }
302
1543
  }
1544
+ if (!response.ok) return this.failureSnapshot(accountId, "copilot_usage_http_error", this.now());
303
1545
  let payload;
304
1546
  try {
305
1547
  payload = await response.json();
306
1548
  } catch {
307
- return this.failureSnapshot(accountId, "claude_usage_invalid_response", this.now());
308
- }
309
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
310
- return this.failureSnapshot(accountId, "claude_usage_invalid_response", this.now());
1549
+ return this.failureSnapshot(accountId, "copilot_usage_invalid_response", this.now());
311
1550
  }
312
1551
  const now = this.now();
313
- const usage = payload;
1552
+ const windows = parseCopilotUserPayload(payload, now);
314
1553
  const snapshot = {
315
- providerId: "claude",
1554
+ providerId: "copilot",
316
1555
  accountId,
317
1556
  source: "oauth-usage-api",
318
1557
  observedAt: new Date(now).toISOString(),
319
- expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
320
- 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
- ]
1558
+ expiresAt: new Date(now + COPILOT_ALLOWANCE_CACHE_MS).toISOString(),
1559
+ windows: windows ?? [
1560
+ { id: "thirty-day", label: "Monthly", scope: "all", usedPercent: null, state: "unavailable" }
1561
+ ],
1562
+ ...windows ? {} : { lastErrorCode: "copilot_usage_invalid_response" }
325
1563
  };
326
1564
  this.store.set(snapshot);
327
1565
  return snapshot;
328
1566
  }
329
- request(accountId, token) {
330
- const headers = {
331
- Authorization: `Bearer ${token}`,
332
- Accept: "application/json",
333
- "Content-Type": "application/json",
334
- "anthropic-beta": "oauth-2025-04-20",
335
- "Accept-Language": "en-US,en;q=0.9"
336
- };
337
- applyFingerprint(this.identityStore, headers, "claude", accountId, void 0);
338
- if (!hasHeader(headers, "user-agent")) {
339
- headers["User-Agent"] = "claude-cli/2.0.53 (external, cli)";
340
- }
341
- return this.fetchImpl(CLAUDE_USAGE_URL, {
1567
+ request(accountId, accessToken, tokens) {
1568
+ return this.fetchImpl(`${githubApiBase(tokens)}/copilot_internal/user`, {
342
1569
  method: "GET",
343
- headers,
1570
+ headers: {
1571
+ Authorization: `Bearer ${accessToken}`,
1572
+ Accept: "application/json",
1573
+ "Content-Type": "application/json",
1574
+ ...COPILOT_GITHUB_HEADERS
1575
+ },
344
1576
  signal: AbortSignal.timeout(15e3)
345
1577
  }, accountId);
346
1578
  }
347
1579
  failureSnapshot(accountId, code, now) {
348
- const existing = this.store.get("claude", accountId, now);
1580
+ const existing = this.store.get("copilot", accountId, now);
349
1581
  const snapshot = existing ? {
350
1582
  ...existing,
351
- expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
1583
+ expiresAt: new Date(now + COPILOT_ALLOWANCE_CACHE_MS).toISOString(),
352
1584
  windows: existing.windows.map((window) => ({
353
1585
  ...window,
354
1586
  state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
355
1587
  })),
356
1588
  lastErrorCode: code
357
1589
  } : {
358
- providerId: "claude",
1590
+ providerId: "copilot",
359
1591
  accountId,
360
1592
  source: "oauth-usage-api",
361
1593
  observedAt: new Date(now).toISOString(),
362
- expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
363
- windows: emptyClaudeWindows("unavailable"),
1594
+ expiresAt: new Date(now + COPILOT_ALLOWANCE_CACHE_MS).toISOString(),
1595
+ windows: [
1596
+ { id: "thirty-day", label: "Monthly", scope: "all", usedPercent: null, state: "unavailable" }
1597
+ ],
364
1598
  lastErrorCode: code
365
1599
  };
366
1600
  this.store.set(snapshot);
367
1601
  return snapshot;
368
1602
  }
369
- unsupportedSnapshot(accountId, now, code = "claude_usage_unsupported_auth") {
1603
+ unsupportedSnapshot(accountId, now) {
370
1604
  return {
371
- providerId: "claude",
1605
+ providerId: "copilot",
372
1606
  accountId,
373
1607
  source: "oauth-usage-api",
374
1608
  observedAt: new Date(now).toISOString(),
375
- windows: emptyClaudeWindows("unsupported"),
1609
+ windows: [
1610
+ { id: "thirty-day", label: "Monthly", scope: "all", usedPercent: null, state: "unsupported" }
1611
+ ],
1612
+ lastErrorCode: "copilot_usage_unsupported_auth"
1613
+ };
1614
+ }
1615
+ };
1616
+
1617
+ // src/allowance/OpenCodeGoAllowanceCollector.ts
1618
+ import {
1619
+ getSharedAccountAllowanceStore as getSharedAccountAllowanceStore6
1620
+ } from "@omnicross/core/pipeline/AccountAllowanceStore";
1621
+ import { fetchUpstream as fetchUpstream6 } from "@omnicross/core/pipeline/upstreamFetch";
1622
+ import { normalizeOpenCodeGoBaseUrl } from "@omnicross/subscriptions";
1623
+ var OPENCODEGO_ALLOWANCE_CACHE_MS = 5 * 6e4;
1624
+ var OPENCODEGO_DEFAULT_GO_BASE = "https://opencode.ai/zen/go";
1625
+ function finitePercent3(value) {
1626
+ if (value === null || value === void 0 || value === "") return null;
1627
+ const parsed = typeof value === "number" ? value : Number(value);
1628
+ return Number.isFinite(parsed) && parsed >= 0 && parsed <= 100 ? parsed : null;
1629
+ }
1630
+ function isoInstant2(value) {
1631
+ if (typeof value !== "string" || !value.trim()) return void 0;
1632
+ const time = Date.parse(value);
1633
+ return Number.isFinite(time) ? new Date(time).toISOString() : void 0;
1634
+ }
1635
+ function secondsUntil6(instant, now) {
1636
+ if (!instant) return void 0;
1637
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
1638
+ }
1639
+ function windowFromPayload3(id, label, minutes, payload, now) {
1640
+ const statusRateLimited = payload?.status === "rate-limited";
1641
+ const usedPercent = statusRateLimited ? 100 : finitePercent3(payload?.percent);
1642
+ const resetsAt = isoInstant2(payload?.resetsAt);
1643
+ return {
1644
+ id,
1645
+ label,
1646
+ scope: "all",
1647
+ usedPercent,
1648
+ windowMinutes: minutes,
1649
+ ...resetsAt !== void 0 ? { resetsAt } : {},
1650
+ remainingSeconds: secondsUntil6(resetsAt, now),
1651
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
1652
+ };
1653
+ }
1654
+ var OpenCodeGoAllowanceCollector = class {
1655
+ constructor(credentials, store = getSharedAccountAllowanceStore6(), fetchImpl = (url, init, accountId) => fetchUpstream6(url, init, { providerId: "opencodego", accountId, redactBodies: true }), now = Date.now) {
1656
+ this.credentials = credentials;
1657
+ this.store = store;
1658
+ this.fetchImpl = fetchImpl;
1659
+ this.now = now;
1660
+ }
1661
+ credentials;
1662
+ store;
1663
+ fetchImpl;
1664
+ now;
1665
+ inFlight = /* @__PURE__ */ new Map();
1666
+ async collectMany(accounts, options = {}) {
1667
+ const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
1668
+ return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
1669
+ }
1670
+ collect(account, options = {}) {
1671
+ const now = this.now();
1672
+ const cached = this.store.get("opencodego", account.id, now);
1673
+ if (!options.force && cached && (cached.windows.every((window) => window.state === "unsupported") || cached.expiresAt && Date.parse(cached.expiresAt) > now + (options.refreshAheadMs ?? 0))) {
1674
+ return Promise.resolve(cached);
1675
+ }
1676
+ const running = this.inFlight.get(account.id);
1677
+ if (running) return running;
1678
+ const promise = this.fetchAccount(account).catch(() => this.failureSnapshot(account.id, this.now())).finally(() => this.inFlight.delete(account.id));
1679
+ this.inFlight.set(account.id, promise);
1680
+ return promise;
1681
+ }
1682
+ async fetchAccount(account) {
1683
+ const apiKey = await this.credentials.getAccessTokenForAccount("opencodego", account.id);
1684
+ if (!apiKey) return this.failureSnapshot(account.id, this.now());
1685
+ const base = account.tokens.baseUrl ? normalizeOpenCodeGoBaseUrl(account.tokens.baseUrl) : OPENCODEGO_DEFAULT_GO_BASE;
1686
+ const response = await this.fetchImpl(`${base}/v1/usage`, {
1687
+ method: "GET",
1688
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
1689
+ signal: AbortSignal.timeout(15e3)
1690
+ }, account.id);
1691
+ if (response.status === 401 || response.status === 403) {
1692
+ return this.failureSnapshot(account.id, this.now(), "opencodego_usage_unauthorized");
1693
+ }
1694
+ if (!response.ok) return this.failureSnapshot(account.id, this.now());
1695
+ let payload;
1696
+ try {
1697
+ payload = await response.json();
1698
+ } catch {
1699
+ return this.failureSnapshot(account.id, this.now());
1700
+ }
1701
+ const usage = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.usage : void 0;
1702
+ const now = this.now();
1703
+ const snapshot = {
1704
+ providerId: "opencodego",
1705
+ accountId: account.id,
1706
+ source: "oauth-usage-api",
1707
+ observedAt: new Date(now).toISOString(),
1708
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
1709
+ // Monthly deliberately omitted (module doc).
1710
+ windows: [
1711
+ windowFromPayload3("five-hour", "5 hours", 5 * 60, usage?.rolling ?? void 0, now),
1712
+ windowFromPayload3("seven-day", "7 days", 7 * 24 * 60, usage?.weekly ?? void 0, now)
1713
+ ]
1714
+ };
1715
+ this.store.set(snapshot);
1716
+ return snapshot;
1717
+ }
1718
+ failureSnapshot(accountId, now, code = "opencodego_usage_request_failed") {
1719
+ const existing = this.store.get("opencodego", accountId, now);
1720
+ const snapshot = existing ? {
1721
+ ...existing,
1722
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
1723
+ windows: existing.windows.map((window) => ({
1724
+ ...window,
1725
+ state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
1726
+ })),
1727
+ lastErrorCode: code
1728
+ } : {
1729
+ providerId: "opencodego",
1730
+ accountId,
1731
+ source: "oauth-usage-api",
1732
+ observedAt: new Date(now).toISOString(),
1733
+ expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
1734
+ windows: [
1735
+ { id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
1736
+ { id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
1737
+ ],
376
1738
  lastErrorCode: code
377
1739
  };
1740
+ this.store.set(snapshot);
1741
+ return snapshot;
378
1742
  }
379
1743
  };
380
1744
 
@@ -393,26 +1757,34 @@ function codexUnavailable(accountId, now) {
393
1757
  };
394
1758
  }
395
1759
  var AccountAllowanceService = class {
396
- constructor(credentials, store = getSharedAccountAllowanceStore2(), collector, now = Date.now) {
1760
+ constructor(credentials, store = getSharedAccountAllowanceStore7(), collector, codexCollector, kimiCollector, opencodegoCollector, grokCollector, copilotCollector, now = Date.now) {
397
1761
  this.credentials = credentials;
398
1762
  this.store = store;
399
1763
  this.now = now;
400
1764
  this.claudeCollector = collector ?? new ClaudeAllowanceCollector(credentials, store);
1765
+ this.codexCollector = codexCollector ?? new CodexAllowanceCollector(credentials, store);
1766
+ this.kimiCollector = kimiCollector ?? new KimiAllowanceCollector(credentials, store);
1767
+ this.opencodegoCollector = opencodegoCollector ?? new OpenCodeGoAllowanceCollector(credentials, store);
1768
+ this.grokCollector = grokCollector ?? new GrokAllowanceCollector(credentials, store);
1769
+ this.copilotCollector = copilotCollector ?? new CopilotAllowanceCollector(credentials, store);
401
1770
  }
402
1771
  credentials;
403
1772
  store;
404
1773
  now;
405
1774
  claudeCollector;
1775
+ codexCollector;
1776
+ kimiCollector;
1777
+ grokCollector;
1778
+ copilotCollector;
1779
+ opencodegoCollector;
406
1780
  /**
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.
1781
+ * Read all/filtered snapshots. Claude's and Codex's five-minute caches are
1782
+ * refreshed lazily on read (Codex polls `/backend-api/wham/usage`; the
1783
+ * passive `x-codex-*` header tap still feeds mid-flight updates).
409
1784
  */
410
1785
  async list(filter = {}) {
411
1786
  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
- ]);
1787
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
416
1788
  const wantsClaude = !filter.providerId || filter.providerId === "claude";
417
1789
  const claudeAccounts = (config.claudeAccounts ?? []).filter(
418
1790
  (account) => !filter.accountId || account.id === filter.accountId
@@ -423,39 +1795,124 @@ var AccountAllowanceService = class {
423
1795
  (account) => !filter.accountId || account.id === filter.accountId
424
1796
  );
425
1797
  if (wantsCodex) {
1798
+ await this.codexCollector.collectMany(codexAccounts);
426
1799
  for (const account of codexAccounts) {
427
1800
  if (!this.store.get("codex", account.id)) this.store.set(codexUnavailable(account.id, this.now()));
428
1801
  }
429
1802
  }
1803
+ const wantsKimi = !filter.providerId || filter.providerId === "kimi";
1804
+ const kimiAccounts = (config.kimiAccounts ?? []).filter(
1805
+ (account) => !filter.accountId || account.id === filter.accountId
1806
+ );
1807
+ if (wantsKimi) await this.kimiCollector.collectMany(kimiAccounts);
1808
+ const wantsOpenCodeGo = !filter.providerId || filter.providerId === "opencodego";
1809
+ const opencodegoAccounts = (config.opencodegoAccounts ?? []).filter(
1810
+ (account) => !filter.accountId || account.id === filter.accountId
1811
+ );
1812
+ if (wantsOpenCodeGo) await this.opencodegoCollector.collectMany(opencodegoAccounts);
1813
+ const wantsGrok = !filter.providerId || filter.providerId === "grok";
1814
+ const grokAccounts = (config.grokAccounts ?? []).filter(
1815
+ (account) => !filter.accountId || account.id === filter.accountId
1816
+ );
1817
+ if (wantsGrok) await this.grokCollector.collectMany(grokAccounts);
1818
+ const wantsCopilot = !filter.providerId || filter.providerId === "copilot";
1819
+ const copilotAccounts = (config.copilotAccounts ?? []).filter(
1820
+ (account) => !filter.accountId || account.id === filter.accountId
1821
+ );
1822
+ if (wantsCopilot) await this.copilotCollector.collectMany(copilotAccounts);
430
1823
  const known = /* @__PURE__ */ new Set();
431
1824
  if (wantsClaude) for (const account of claudeAccounts) known.add(`claude\0${account.id}`);
432
1825
  if (wantsCodex) for (const account of codexAccounts) known.add(`codex\0${account.id}`);
1826
+ if (wantsKimi) for (const account of kimiAccounts) known.add(`kimi\0${account.id}`);
1827
+ if (wantsOpenCodeGo) for (const account of opencodegoAccounts) known.add(`opencodego\0${account.id}`);
1828
+ if (wantsGrok) for (const account of grokAccounts) known.add(`grok\0${account.id}`);
1829
+ if (wantsCopilot) for (const account of copilotAccounts) known.add(`copilot\0${account.id}`);
433
1830
  return this.store.list(filter).filter((snapshot) => known.has(`${snapshot.providerId}\0${snapshot.accountId}`));
434
1831
  }
1832
+ knownAccounts(config) {
1833
+ return [
1834
+ ...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
1835
+ ...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id })),
1836
+ ...(config.kimiAccounts ?? []).map((account) => ({ providerId: "kimi", accountId: account.id })),
1837
+ ...(config.opencodegoAccounts ?? []).map((account) => ({ providerId: "opencodego", accountId: account.id })),
1838
+ ...(config.grokAccounts ?? []).map((account) => ({ providerId: "grok", accountId: account.id })),
1839
+ ...(config.copilotAccounts ?? []).map((account) => ({ providerId: "copilot", accountId: account.id }))
1840
+ ];
1841
+ }
435
1842
  /** Force-refresh Claude usage for one account or every stored Claude account. */
436
1843
  async refreshClaude(accountId) {
437
1844
  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
- ]);
1845
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
442
1846
  const accounts = (config.claudeAccounts ?? []).filter(
443
1847
  (account) => !accountId || account.id === accountId
444
1848
  );
445
1849
  return this.claudeCollector.collectMany(accounts, { force: true });
446
1850
  }
447
1851
  /**
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.
1852
+ * Force-refresh Codex usage (`/backend-api/wham/usage`) for one account or
1853
+ * every stored Codex account. Replaces the old probe-request workaround
1854
+ * no quota is spent reading the usage endpoint.
1855
+ */
1856
+ async refreshCodex(accountId) {
1857
+ const config = await this.credentials.getFullConfig();
1858
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1859
+ const accounts = (config.codexAccounts ?? []).filter(
1860
+ (account) => !accountId || account.id === accountId
1861
+ );
1862
+ return this.codexCollector.collectMany(accounts, { force: true });
1863
+ }
1864
+ /** Force-refresh OpenCodeGo usage (`{go}/v1/usage`) for one/all accounts. */
1865
+ async refreshOpenCodeGo(accountId) {
1866
+ const config = await this.credentials.getFullConfig();
1867
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1868
+ const accounts = (config.opencodegoAccounts ?? []).filter(
1869
+ (account) => !accountId || account.id === accountId
1870
+ );
1871
+ return this.opencodegoCollector.collectMany(accounts, { force: true });
1872
+ }
1873
+ /** Force-refresh Kimi usage (`/coding/v1/usages`) for one/all accounts. */
1874
+ async refreshKimi(accountId) {
1875
+ const config = await this.credentials.getFullConfig();
1876
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1877
+ const accounts = (config.kimiAccounts ?? []).filter(
1878
+ (account) => !accountId || account.id === accountId
1879
+ );
1880
+ return this.kimiCollector.collectMany(accounts, { force: true });
1881
+ }
1882
+ /** Force-refresh Copilot usage (copilot_internal/user) for one/all accounts. */
1883
+ async refreshCopilot(accountId) {
1884
+ const config = await this.credentials.getFullConfig();
1885
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1886
+ const accounts = (config.copilotAccounts ?? []).filter(
1887
+ (account) => !accountId || account.id === accountId
1888
+ );
1889
+ return this.copilotCollector.collectMany(accounts, { force: true });
1890
+ }
1891
+ /** Force-refresh Grok usage (CLI billing proxy) for one/all accounts. */
1892
+ async refreshGrok(accountId) {
1893
+ const config = await this.credentials.getFullConfig();
1894
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
1895
+ const accounts = (config.grokAccounts ?? []).filter(
1896
+ (account) => !accountId || account.id === accountId
1897
+ );
1898
+ return this.grokCollector.collectMany(accounts, { force: true });
1899
+ }
1900
+ /**
1901
+ * Keep Claude + Codex + Kimi snapshots warm for allowance-aware routing. All
1902
+ * collectors preserve their cache + per-account in-flight coalescing; a tick
1903
+ * normally performs no network I/O. (Codex joined the warm path when it
1904
+ * gained an active `/wham/usage` collector — the passive `x-codex-*` header
1905
+ * tap alone could not keep the policy fed while idle.)
451
1906
  */
452
1907
  async maintainClaudeCache(refreshAheadMs) {
453
1908
  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
- ]);
1909
+ this.store.pruneToKnownAccounts(this.knownAccounts(config));
458
1910
  await this.claudeCollector.collectMany(config.claudeAccounts ?? [], { refreshAheadMs });
1911
+ await this.codexCollector.collectMany(config.codexAccounts ?? [], { refreshAheadMs });
1912
+ await this.kimiCollector.collectMany(config.kimiAccounts ?? [], { refreshAheadMs });
1913
+ await this.opencodegoCollector.collectMany(config.opencodegoAccounts ?? [], { refreshAheadMs });
1914
+ await this.grokCollector.collectMany(config.grokAccounts ?? [], { refreshAheadMs });
1915
+ await this.copilotCollector.collectMany(config.copilotAccounts ?? [], { refreshAheadMs });
459
1916
  }
460
1917
  /** Remove a cache row as soon as an account is deleted by the admin path. */
461
1918
  removeAccountSnapshot(providerId, accountId) {
@@ -892,7 +2349,8 @@ import {
892
2349
  } from "@omnicross/contracts/image-generation-types";
893
2350
  import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling2 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
894
2351
  import { getSharedAccountHealth } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
895
- import { fetchUpstream as fetchUpstream2 } from "@omnicross/core/pipeline/upstreamFetch";
2352
+ import { fetchUpstream as fetchUpstream7 } from "@omnicross/core/pipeline/upstreamFetch";
2353
+ import { mergeExtraHeaders } from "@omnicross/core";
896
2354
 
897
2355
  // src/image-generation/imagesConfigValidation.ts
898
2356
  import { validateImagesServerConfig } from "@omnicross/core/outbound-api";
@@ -1205,6 +2663,7 @@ async function applyServerConfigTransaction(current, next, deps) {
1205
2663
 
1206
2664
  // src/config.ts
1207
2665
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
2666
+ import { EXTRA_HEADER_RESERVED_NAMES } from "@omnicross/core";
1208
2667
 
1209
2668
  // src/secrets/envelope.ts
1210
2669
  import { createCipheriv, createDecipheriv, randomBytes as randomBytes2 } from "crypto";
@@ -1616,6 +3075,18 @@ var FORMAT_AXIS_TRANSFORMERS = [
1616
3075
  "openai-response",
1617
3076
  "gemini-code-assist"
1618
3077
  ];
3078
+ function validateExtraHeaders(raw) {
3079
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
3080
+ const reserved = EXTRA_HEADER_RESERVED_NAMES;
3081
+ const out = {};
3082
+ for (const [name, value] of Object.entries(raw)) {
3083
+ if (!name.trim()) continue;
3084
+ if (typeof value !== "string") continue;
3085
+ if (reserved.has(name.toLowerCase())) continue;
3086
+ out[name] = value;
3087
+ }
3088
+ return Object.keys(out).length > 0 ? out : void 0;
3089
+ }
1619
3090
  function validateApiKeys(raw) {
1620
3091
  if (!Array.isArray(raw)) return void 0;
1621
3092
  const out = [];
@@ -1829,6 +3300,9 @@ function validateProvider(raw, index) {
1829
3300
  apiVersion,
1830
3301
  maxConcurrency,
1831
3302
  modelsEndpoint,
3303
+ // Static extra headers: load-guard (reserved names dropped), collapse-to-
3304
+ // undefined; enforced by the outbound header funnel + admin probes.
3305
+ extraHeaders: validateExtraHeaders(p["extraHeaders"]),
1832
3306
  // Provider transformer config (app-parity child 5): load-guard, collapse-to-
1833
3307
  // undefined; non-secret; ENFORCED via resolveTransformerChain (parity-2 child 2).
1834
3308
  // Format-axis entries are stripped by `migrateFormatAxis` — `use[]` is the
@@ -2798,7 +4272,8 @@ function listMappablePresets() {
2798
4272
  description: preset.description,
2799
4273
  features: preset.features,
2800
4274
  website: preset.website,
2801
- modelsEndpoint: preset.modelsEndpoint
4275
+ modelsEndpoint: preset.modelsEndpoint,
4276
+ extraHeaders: preset.extraHeaders
2802
4277
  });
2803
4278
  }
2804
4279
  return { mappable, excluded };
@@ -2969,7 +4444,10 @@ var VALID_PROVIDER_IDS = [
2969
4444
  "claude",
2970
4445
  "codex",
2971
4446
  "gemini",
2972
- "opencodego"
4447
+ "opencodego",
4448
+ "kimi",
4449
+ "grok",
4450
+ "copilot"
2973
4451
  ];
2974
4452
  function asSubscriptionProviderId(id) {
2975
4453
  return VALID_PROVIDER_IDS.includes(id) ? id : null;
@@ -3093,7 +4571,43 @@ function validateCodex(body) {
3093
4571
  ]);
3094
4572
  return out;
3095
4573
  }
3096
- function validateGemini(body) {
4574
+ function validateGemini(body) {
4575
+ const authMethod = str(body["authMethod"]);
4576
+ const status = str(body["status"]);
4577
+ if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
4578
+ if (!status || !TOKEN_STATUSES.has(status)) return null;
4579
+ const out = {
4580
+ authMethod,
4581
+ status
4582
+ };
4583
+ copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "lastRefreshedAt", "errorMessage"]);
4584
+ return out;
4585
+ }
4586
+ function validateKimi(body) {
4587
+ const authMethod = str(body["authMethod"]);
4588
+ const status = str(body["status"]);
4589
+ if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
4590
+ if (!status || !TOKEN_STATUSES.has(status)) return null;
4591
+ const out = {
4592
+ authMethod,
4593
+ status
4594
+ };
4595
+ copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "accountId", "deviceId", "lastRefreshedAt", "errorMessage"]);
4596
+ return out;
4597
+ }
4598
+ function validateGrok(body) {
4599
+ const authMethod = str(body["authMethod"]);
4600
+ const status = str(body["status"]);
4601
+ if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
4602
+ if (!status || !TOKEN_STATUSES.has(status)) return null;
4603
+ const out = {
4604
+ authMethod,
4605
+ status
4606
+ };
4607
+ copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "accountId", "lastRefreshedAt", "errorMessage"]);
4608
+ return out;
4609
+ }
4610
+ function validateCopilot(body) {
3097
4611
  const authMethod = str(body["authMethod"]);
3098
4612
  const status = str(body["status"]);
3099
4613
  if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
@@ -3102,7 +4616,17 @@ function validateGemini(body) {
3102
4616
  authMethod,
3103
4617
  status
3104
4618
  };
3105
- copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "lastRefreshedAt", "errorMessage"]);
4619
+ copyOptional(out, body, [
4620
+ "accessToken",
4621
+ "refreshToken",
4622
+ "expiresAt",
4623
+ "accountId",
4624
+ "email",
4625
+ "apiEndpoint",
4626
+ "enterpriseUrl",
4627
+ "lastRefreshedAt",
4628
+ "errorMessage"
4629
+ ]);
3106
4630
  return out;
3107
4631
  }
3108
4632
  function validateOpenCodeGo(body) {
@@ -3140,6 +4664,12 @@ function validateTokenBody(providerId, body) {
3140
4664
  return validateGemini(body);
3141
4665
  case "opencodego":
3142
4666
  return validateOpenCodeGo(body);
4667
+ case "kimi":
4668
+ return validateKimi(body);
4669
+ case "grok":
4670
+ return validateGrok(body);
4671
+ case "copilot":
4672
+ return validateCopilot(body);
3143
4673
  default:
3144
4674
  return null;
3145
4675
  }
@@ -3169,12 +4699,12 @@ async function statusEntryFor(reader, providerId) {
3169
4699
 
3170
4700
  // src/admin/accountsOAuth.ts
3171
4701
  var OAUTH_HTTP_PROVIDERS = /* @__PURE__ */ new Set(["claude", "gemini"]);
3172
- function err2(status, message) {
4702
+ function err5(status, message) {
3173
4703
  return { status, body: { error: { type: "admin_api_error", message } } };
3174
4704
  }
3175
4705
  function handleOAuthStart(providerId, deps) {
3176
4706
  if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
3177
- return err2(400, `oauth not available for provider '${providerId}'`);
4707
+ return err5(400, `oauth not available for provider '${providerId}'`);
3178
4708
  }
3179
4709
  const flow = providerId === "claude" ? claudeOAuth : geminiOAuth;
3180
4710
  const { authUrl, codeVerifier, state } = flow.generateAuthParams();
@@ -3183,23 +4713,23 @@ function handleOAuthStart(providerId, deps) {
3183
4713
  }
3184
4714
  async function handleOAuthComplete(providerId, body, deps) {
3185
4715
  if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
3186
- return err2(400, `oauth not available for provider '${providerId}'`);
4716
+ return err5(400, `oauth not available for provider '${providerId}'`);
3187
4717
  }
3188
4718
  const sessionId = typeof body["sessionId"] === "string" ? body["sessionId"] : "";
3189
4719
  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 }");
4720
+ if (!sessionId) return err5(400, "oauth complete requires { sessionId }");
4721
+ if (!rawCode) return err5(400, "oauth complete requires { code }");
3192
4722
  const session = deps.oauthSessions.peek(sessionId);
3193
- if (!session) return err2(410, "oauth session is unknown, expired, or already used");
4723
+ if (!session) return err5(410, "oauth session is unknown, expired, or already used");
3194
4724
  if (session.providerId !== providerId) {
3195
- return err2(400, `oauth session does not match provider '${providerId}'`);
4725
+ return err5(400, `oauth session does not match provider '${providerId}'`);
3196
4726
  }
3197
4727
  let code = rawCode.trim();
3198
4728
  if (providerId === "claude") {
3199
4729
  const [splitCode, pastedState] = code.split("#");
3200
- if (!splitCode) return err2(400, "no authorization code was provided");
4730
+ if (!splitCode) return err5(400, "no authorization code was provided");
3201
4731
  if (pastedState && pastedState !== session.state) {
3202
- return err2(400, "oauth state did not match (possible CSRF) \u2014 aborting");
4732
+ return err5(400, "oauth state did not match (possible CSRF) \u2014 aborting");
3203
4733
  }
3204
4734
  code = splitCode;
3205
4735
  }
@@ -3209,7 +4739,7 @@ async function handleOAuthComplete(providerId, body, deps) {
3209
4739
  block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state, exchangeFetch) : await exchangeGemini(code, session.codeVerifier, exchangeFetch);
3210
4740
  } catch (exchangeError) {
3211
4741
  const reason = exchangeError instanceof Error ? exchangeError.message : "token exchange failed";
3212
- return err2(502, `oauth token exchange failed for '${providerId}': ${reason}`);
4742
+ return err5(502, `oauth token exchange failed for '${providerId}': ${reason}`);
3213
4743
  }
3214
4744
  deps.oauthSessions.consume(sessionId);
3215
4745
  const label = typeof body["label"] === "string" && body["label"].trim() ? body["label"].trim() : void 0;
@@ -3547,8 +5077,8 @@ function errBody(message) {
3547
5077
  return { error: { type: "admin_api_error", message } };
3548
5078
  }
3549
5079
  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 });
5080
+ exec(command, { timeout: 18e4 }, (err8, _stdout, stderr) => {
5081
+ if (err8) resolve10({ ok: false, error: stderr.trim() || err8.message });
3552
5082
  else resolve10({ ok: true });
3553
5083
  });
3554
5084
  });
@@ -3594,8 +5124,8 @@ async function handleCliLaunch(cli, body, ctx) {
3594
5124
  providerId: typeof body["providerId"] === "string" ? body["providerId"] : void 0,
3595
5125
  model: typeof body["model"] === "string" ? body["model"] : void 0
3596
5126
  });
3597
- } catch (err5) {
3598
- return { status: 400, body: errBody(err5 instanceof Error ? err5.message : "no launch target") };
5127
+ } catch (err8) {
5128
+ return { status: 400, body: errBody(err8 instanceof Error ? err8.message : "no launch target") };
3599
5129
  }
3600
5130
  const id = randomUUID2();
3601
5131
  let leaseId2;
@@ -3623,9 +5153,9 @@ async function handleCliLaunch(cli, body, ctx) {
3623
5153
  } else {
3624
5154
  launch = await buildLaunchEnv(cli, ctx.llmConfig, target);
3625
5155
  }
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") };
5156
+ } catch (err8) {
5157
+ const status = err8 instanceof RouteLeaseError2 ? err8.status : 400;
5158
+ return { status, body: errBody(err8 instanceof Error ? err8.message : "failed to build launch env") };
3629
5159
  }
3630
5160
  const cwd = typeof body["cwd"] === "string" && body["cwd"].trim() ? body["cwd"].trim() : void 0;
3631
5161
  const opener = ctx.opener ?? defaultTerminalOpener;
@@ -3653,9 +5183,9 @@ async function handleCliLaunch(cli, body, ctx) {
3653
5183
  onFailure: onSessionEnd
3654
5184
  });
3655
5185
  if (cleanup) openerCleanup = cleanup;
3656
- } catch (err5) {
5186
+ } catch (err8) {
3657
5187
  onSessionEnd();
3658
- return { status: 500, body: errBody(err5 instanceof Error ? err5.message : "failed to open terminal") };
5188
+ return { status: 500, body: errBody(err8 instanceof Error ? err8.message : "failed to open terminal") };
3659
5189
  }
3660
5190
  if (ended) {
3661
5191
  openerCleanup?.();
@@ -4204,7 +5734,7 @@ async function handleSearchQuery(req, res, deps) {
4204
5734
  // src/admin/searchAdminView.ts
4205
5735
  var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
4206
5736
  var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
4207
- function isRecord(value) {
5737
+ function isRecord4(value) {
4208
5738
  return value !== null && typeof value === "object" && !Array.isArray(value);
4209
5739
  }
4210
5740
  function redactSearchServerConfig(search) {
@@ -4254,13 +5784,13 @@ function resolveSecretField(entry, field, stored) {
4254
5784
  else delete entry[field];
4255
5785
  }
4256
5786
  function preserveSearchSecrets(incoming, current) {
4257
- if (!isRecord(incoming)) return incoming;
5787
+ if (!isRecord4(incoming)) return incoming;
4258
5788
  const section = { ...incoming };
4259
5789
  const providersValue = section["providers"];
4260
- if (!isRecord(providersValue)) return section;
5790
+ if (!isRecord4(providersValue)) return section;
4261
5791
  const providers = {};
4262
5792
  for (const [id, entryValue] of Object.entries(providersValue)) {
4263
- if (!isRecord(entryValue)) {
5793
+ if (!isRecord4(entryValue)) {
4264
5794
  providers[id] = entryValue;
4265
5795
  continue;
4266
5796
  }
@@ -4338,7 +5868,7 @@ function parseKeyPolicyBody(body) {
4338
5868
  var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
4339
5869
  var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
4340
5870
  var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
4341
- function isRecord2(value) {
5871
+ function isRecord5(value) {
4342
5872
  return !!value && typeof value === "object" && !Array.isArray(value);
4343
5873
  }
4344
5874
  function nonBlank(value) {
@@ -4358,7 +5888,7 @@ function validateGatewayBindingsSegment(patch) {
4358
5888
  const ids = /* @__PURE__ */ new Set();
4359
5889
  raw.forEach((entry, index) => {
4360
5890
  const path2 = `bindings[${index}]`;
4361
- if (!isRecord2(entry)) {
5891
+ if (!isRecord5(entry)) {
4362
5892
  errors.push(`${path2} must be an object`);
4363
5893
  return;
4364
5894
  }
@@ -4387,12 +5917,12 @@ function validateGatewayBindingsSegment(patch) {
4387
5917
  } else if (entry.modelMappings.length > 100) {
4388
5918
  errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
4389
5919
  } else if (entry.modelMappings.some(
4390
- (mapping) => !isRecord2(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
5920
+ (mapping) => !isRecord5(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
4391
5921
  )) {
4392
5922
  errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
4393
5923
  }
4394
5924
  }
4395
- if (!isRecord2(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
5925
+ if (!isRecord5(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
4396
5926
  errors.push(`${path2}.target is invalid`);
4397
5927
  } else {
4398
5928
  if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
@@ -4407,7 +5937,7 @@ function validateGatewayBindingsSegment(patch) {
4407
5937
  }
4408
5938
  }
4409
5939
  if (entry.modelMap !== void 0) {
4410
- if (!isRecord2(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
5940
+ if (!isRecord5(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
4411
5941
  errors.push(`${path2}.modelMap must contain string values`);
4412
5942
  }
4413
5943
  }
@@ -4717,7 +6247,10 @@ var PROVIDER_KEYS = {
4717
6247
  block: "opencodego",
4718
6248
  accounts: "opencodegoAccounts",
4719
6249
  active: "activeOpencodegoAccountId"
4720
- }
6250
+ },
6251
+ kimi: { block: "kimi", accounts: "kimiAccounts", active: "activeKimiAccountId" },
6252
+ grok: { block: "grok", accounts: "grokAccounts", active: "activeGrokAccountId" },
6253
+ copilot: { block: "copilot", accounts: "copilotAccounts", active: "activeCopilotAccountId" }
4721
6254
  };
4722
6255
  function clone(value) {
4723
6256
  return JSON.parse(JSON.stringify(value));
@@ -5239,7 +6772,7 @@ async function applyImport(packString, passphrase, mode, deps, parseProviderInpu
5239
6772
  }
5240
6773
 
5241
6774
  // src/admin/adminMigration.ts
5242
- function err3(status, message) {
6775
+ function err6(status, message) {
5243
6776
  return { status, body: { error: { type: "admin_api_error", message } } };
5244
6777
  }
5245
6778
  async function handleExport(body, deps) {
@@ -5249,30 +6782,30 @@ async function handleExport(body, deps) {
5249
6782
  return { status: 200, body: { pack, version: BUNDLE_VERSION } };
5250
6783
  } catch (error) {
5251
6784
  if (error instanceof WeakPassphraseError) {
5252
- return err3(400, error.message);
6785
+ return err6(400, error.message);
5253
6786
  }
5254
- return err3(500, "failed to build the migration pack");
6787
+ return err6(500, "failed to build the migration pack");
5255
6788
  }
5256
6789
  }
5257
6790
  async function handleImport(body, deps) {
5258
6791
  const blob = typeof body["blob"] === "string" ? body["blob"] : "";
5259
6792
  const passphrase = typeof body["passphrase"] === "string" ? body["passphrase"] : "";
5260
6793
  const mode = body["mode"] === "overwrite" ? "overwrite" : "merge";
5261
- if (!blob) return err3(400, "import requires { blob }");
6794
+ if (!blob) return err6(400, "import requires { blob }");
5262
6795
  try {
5263
6796
  const counts = await applyImport(blob, passphrase, mode, deps, deps.parseProviderInput);
5264
6797
  return { status: 200, body: counts };
5265
6798
  } catch (error) {
5266
6799
  if (error instanceof WeakPassphraseError) {
5267
- return err3(400, error.message);
6800
+ return err6(400, error.message);
5268
6801
  }
5269
- return err3(400, error instanceof Error ? error.message : "import failed");
6802
+ return err6(400, error instanceof Error ? error.message : "import failed");
5270
6803
  }
5271
6804
  }
5272
6805
 
5273
6806
  // src/admin/usagePricing.ts
5274
6807
  import { getSharedUsageThroughputTracker } from "@omnicross/core/usage";
5275
- var err4 = (status, message) => ({
6808
+ var err7 = (status, message) => ({
5276
6809
  status,
5277
6810
  body: { error: { type: "admin_api_error", message } }
5278
6811
  });
@@ -5285,7 +6818,7 @@ function parseRange(query2) {
5285
6818
  const startTs = parseFiniteInt(query2.get("startTs"));
5286
6819
  const endTs = parseFiniteInt(query2.get("endTs"));
5287
6820
  if (startTs === null || endTs === null) {
5288
- return err4(400, "startTs and endTs are required finite-integer unix-millis query params");
6821
+ return err7(400, "startTs and endTs are required finite-integer unix-millis query params");
5289
6822
  }
5290
6823
  return { startTs, endTs };
5291
6824
  }
@@ -5310,14 +6843,14 @@ async function handleUsageGet(view, query2, deps) {
5310
6843
  case "timeseries": {
5311
6844
  const bucket = query2.get("bucket");
5312
6845
  if (bucket !== "hour" && bucket !== "day" && bucket !== "month") {
5313
- return err4(400, "bucket must be one of 'hour', 'day', 'month'");
6846
+ return err7(400, "bucket must be one of 'hour', 'day', 'month'");
5314
6847
  }
5315
6848
  const now = Date.now();
5316
6849
  const clamped = { startTs: range.startTs, endTs: Math.min(range.endTs, now) };
5317
6850
  if (clamped.startTs < clamped.endTs) {
5318
6851
  const projected = Math.ceil((clamped.endTs - clamped.startTs) / BUCKET_SPAN_MS[bucket]) + 1;
5319
6852
  if (projected > MAX_TIMESERIES_BUCKETS) {
5320
- return err4(
6853
+ return err7(
5321
6854
  400,
5322
6855
  `requested range projects ~${projected} '${bucket}' buckets (max ${MAX_TIMESERIES_BUCKETS}); narrow the range or use a coarser bucket`
5323
6856
  );
@@ -5340,7 +6873,7 @@ async function handleUsageGet(view, query2, deps) {
5340
6873
  };
5341
6874
  }
5342
6875
  default:
5343
- return err4(404, `unknown usage view '${view ?? ""}'`);
6876
+ return err7(404, `unknown usage view '${view ?? ""}'`);
5344
6877
  }
5345
6878
  }
5346
6879
  function poolKeyLabels(cfg) {
@@ -5389,7 +6922,7 @@ async function handlePricingList(deps) {
5389
6922
  async function handlePricingUpsert(body, deps) {
5390
6923
  const input = parsePricingEntryInput(body);
5391
6924
  if (!input) {
5392
- return err4(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
6925
+ return err7(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
5393
6926
  }
5394
6927
  const entry = await deps.pricingEngine.upsertManual(input);
5395
6928
  return { status: 200, body: { entry } };
@@ -5398,7 +6931,7 @@ async function handlePricingDelete(query2, deps) {
5398
6931
  const providerId = query2.get("providerId")?.trim() ?? "";
5399
6932
  const modelId = query2.get("modelId")?.trim() ?? "";
5400
6933
  if (!providerId || !modelId) {
5401
- return err4(400, "delete requires providerId and modelId query params");
6934
+ return err7(400, "delete requires providerId and modelId query params");
5402
6935
  }
5403
6936
  const deleted = await deps.pricingStore.delete(providerId, modelId);
5404
6937
  if (deleted) await deps.pricingEngine.invalidateCache();
@@ -5418,13 +6951,13 @@ async function handlePricingFetchLatest(deps) {
5418
6951
  }
5419
6952
  };
5420
6953
  } catch (e) {
5421
- return err4(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
6954
+ return err7(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
5422
6955
  }
5423
6956
  }
5424
6957
  async function handlePricingResolveConflicts(body, deps) {
5425
6958
  const raw = body["resolutions"];
5426
6959
  if (!Array.isArray(raw)) {
5427
- return err4(400, "resolve-conflicts requires { resolutions: [...] }");
6960
+ return err7(400, "resolve-conflicts requires { resolutions: [...] }");
5428
6961
  }
5429
6962
  const currentRows = await deps.pricingStore.getAll();
5430
6963
  const userEditedKeys = new Set(
@@ -5434,21 +6967,21 @@ async function handlePricingResolveConflicts(body, deps) {
5434
6967
  const pendingIncoming = /* @__PURE__ */ new Map();
5435
6968
  let staleCount = 0;
5436
6969
  for (const item of raw) {
5437
- if (!item || typeof item !== "object") return err4(400, "invalid resolution entry");
6970
+ if (!item || typeof item !== "object") return err7(400, "invalid resolution entry");
5438
6971
  const r = item;
5439
6972
  const action = r["action"];
5440
6973
  if (action !== "overwrite" && action !== "skip") {
5441
- return err4(400, "resolution action must be 'overwrite' or 'skip'");
6974
+ return err7(400, "resolution action must be 'overwrite' or 'skip'");
5442
6975
  }
5443
6976
  const providerId = typeof r["providerId"] === "string" && r["providerId"].trim() ? r["providerId"].trim() : "";
5444
6977
  const modelId = typeof r["modelId"] === "string" && r["modelId"].trim() ? r["modelId"].trim() : "";
5445
6978
  if (!providerId || !modelId) {
5446
- return err4(400, "each resolution requires top-level providerId and modelId");
6979
+ return err7(400, "each resolution requires top-level providerId and modelId");
5447
6980
  }
5448
6981
  const incoming = parsePricingEntryInput(r["incoming"]);
5449
- if (!incoming) return err4(400, "each resolution must echo a valid incoming pricing entry");
6982
+ if (!incoming) return err7(400, "each resolution must echo a valid incoming pricing entry");
5450
6983
  if (incoming.providerId !== providerId || incoming.modelId !== modelId) {
5451
- return err4(400, "resolution providerId/modelId must match the echoed incoming entry");
6984
+ return err7(400, "resolution providerId/modelId must match the echoed incoming entry");
5452
6985
  }
5453
6986
  const key = `${providerId}::${modelId}`;
5454
6987
  if (action === "overwrite" && !userEditedKeys.has(key)) {
@@ -5493,7 +7026,7 @@ function query(req) {
5493
7026
  }
5494
7027
  function allowanceProvider(value) {
5495
7028
  if (!value) return void 0;
5496
- return value === "claude" || value === "codex" ? value : null;
7029
+ return value === "claude" || value === "codex" || value === "kimi" || value === "opencodego" || value === "grok" || value === "copilot" ? value : null;
5497
7030
  }
5498
7031
  async function handleAccountAllowanceApi(req, res, method, rest, service) {
5499
7032
  if (!service) return writeError2(res, 501, "account allowance service is not available");
@@ -5507,7 +7040,9 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
5507
7040
  const params = query(req);
5508
7041
  const pathProvider = rest.length >= 2 ? rest[0] : null;
5509
7042
  const providerId = allowanceProvider(pathProvider ?? params.get("providerId") ?? params.get("provider"));
5510
- if (providerId === null) return writeError2(res, 400, "providerId must be claude or codex");
7043
+ if (providerId === null) {
7044
+ return writeError2(res, 400, "providerId must be claude, codex, kimi, opencodego, grok, or copilot");
7045
+ }
5511
7046
  const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
5512
7047
  const allowances = await service.list({ providerId, accountId });
5513
7048
  return writeJson3(res, 200, { allowances });
@@ -5517,10 +7052,57 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
5517
7052
  const requestedProvider = allowanceProvider(
5518
7053
  typeof body["providerId"] === "string" ? body["providerId"] : "claude"
5519
7054
  );
5520
- if (requestedProvider !== "claude") {
5521
- return writeError2(res, 400, "only Claude allowances support explicit refresh");
5522
- }
5523
7055
  const accountId = typeof body["accountId"] === "string" && body["accountId"].trim() ? body["accountId"].trim() : void 0;
7056
+ if (requestedProvider === "codex") {
7057
+ if (!service.refreshCodex) {
7058
+ return writeError2(res, 501, "codex allowance refresh is not available");
7059
+ }
7060
+ const allowances2 = await service.refreshCodex(accountId);
7061
+ if (accountId && allowances2.length === 0) {
7062
+ return writeError2(res, 404, `Codex account '${accountId}' not found`);
7063
+ }
7064
+ return writeJson3(res, 200, { allowances: allowances2 });
7065
+ }
7066
+ if (requestedProvider === "kimi") {
7067
+ if (!service.refreshKimi) {
7068
+ return writeError2(res, 501, "kimi allowance refresh is not available");
7069
+ }
7070
+ const allowances2 = await service.refreshKimi(accountId);
7071
+ if (accountId && allowances2.length === 0) {
7072
+ return writeError2(res, 404, `Kimi account '${accountId}' not found`);
7073
+ }
7074
+ return writeJson3(res, 200, { allowances: allowances2 });
7075
+ }
7076
+ if (requestedProvider === "opencodego") {
7077
+ if (!service.refreshOpenCodeGo) {
7078
+ return writeError2(res, 501, "opencodego allowance refresh is not available");
7079
+ }
7080
+ const allowances2 = await service.refreshOpenCodeGo(accountId);
7081
+ if (accountId && allowances2.length === 0) {
7082
+ return writeError2(res, 404, `OpenCodeGo account '${accountId}' not found`);
7083
+ }
7084
+ return writeJson3(res, 200, { allowances: allowances2 });
7085
+ }
7086
+ if (requestedProvider === "copilot") {
7087
+ if (!service.refreshCopilot) {
7088
+ return writeError2(res, 501, "copilot allowance refresh is not available");
7089
+ }
7090
+ const allowances2 = await service.refreshCopilot(accountId);
7091
+ if (accountId && allowances2.length === 0) {
7092
+ return writeError2(res, 404, `Copilot account '${accountId}' not found`);
7093
+ }
7094
+ return writeJson3(res, 200, { allowances: allowances2 });
7095
+ }
7096
+ if (requestedProvider === "grok") {
7097
+ if (!service.refreshGrok) {
7098
+ return writeError2(res, 501, "grok allowance refresh is not available");
7099
+ }
7100
+ const allowances2 = await service.refreshGrok(accountId);
7101
+ if (accountId && allowances2.length === 0) {
7102
+ return writeError2(res, 404, `Grok account '${accountId}' not found`);
7103
+ }
7104
+ return writeJson3(res, 200, { allowances: allowances2 });
7105
+ }
5524
7106
  const allowances = await service.refreshClaude(accountId);
5525
7107
  if (accountId && allowances.length === 0) {
5526
7108
  return writeError2(res, 404, `Claude account '${accountId}' not found`);
@@ -5622,6 +7204,9 @@ function toProviderView(row) {
5622
7204
  apiVersion: row.apiVersion,
5623
7205
  maxConcurrency: row.maxConcurrency,
5624
7206
  modelsEndpoint: row.modelsEndpoint,
7207
+ // Static extra headers round-trip VERBATIM (non-secret identity values;
7208
+ // auth/content names were already dropped at the write/load gate).
7209
+ extraHeaders: row.extraHeaders,
5625
7210
  // app-parity child 5: transformer config round-trips VERBATIM (non-secret —
5626
7211
  // transform-rule names + options, no key material; absent stays absent).
5627
7212
  transformer: row.transformer,
@@ -5691,8 +7276,8 @@ async function handleAdminApi(req, res, path2, deps) {
5691
7276
  default:
5692
7277
  return writeJsonError(res, 404, `unknown admin resource '${resource}'`);
5693
7278
  }
5694
- } catch (err5) {
5695
- writeJsonError(res, 500, err5 instanceof Error ? err5.message : String(err5));
7279
+ } catch (err8) {
7280
+ writeJsonError(res, 500, err8 instanceof Error ? err8.message : String(err8));
5696
7281
  }
5697
7282
  }
5698
7283
  function requestQuery(req) {
@@ -5762,6 +7347,9 @@ async function handleProviders(req, res, method, rest, deps) {
5762
7347
  if (method === "POST" && rest.length === 4 && rest[1] === "keys" && rest[3] === "enabled") {
5763
7348
  return await handleToggleProviderKey(req, res, rest[0], rest[2], cfg, deps);
5764
7349
  }
7350
+ if (method === "POST" && rest.length === 5 && rest[1] === "keys" && rest[3] === "quota" && rest[4] === "refresh") {
7351
+ return await handleProviderKeyQuotaRefresh(res, rest[0], rest[2], cfg, deps);
7352
+ }
5765
7353
  if (method === "PUT" && rest.length === 3 && rest[1] === "keys") {
5766
7354
  return await handleUpdateProviderKey(req, res, rest[0], rest[2], cfg, deps);
5767
7355
  }
@@ -5847,6 +7435,9 @@ async function handleProviderReorder(req, res, cfg, deps) {
5847
7435
  persistProviders(cfg, deps);
5848
7436
  return writeJson4(res, 200, { ok: true, providers: cfg.providers.map(toProviderView) });
5849
7437
  }
7438
+ function expandRowExtraHeaders(row) {
7439
+ return mergeExtraHeaders({}, row.extraHeaders);
7440
+ }
5850
7441
  async function handleDiscoverModels(res, id, cfg) {
5851
7442
  if (!id) return writeJsonError(res, 400, "provider id required in path");
5852
7443
  const row = cfg.providers.find((p) => p.id === id);
@@ -5860,7 +7451,8 @@ async function handleDiscoverModels(res, id, cfg) {
5860
7451
  try {
5861
7452
  const headers = { Accept: "application/json" };
5862
7453
  if (resolvedKey) headers["Authorization"] = `Bearer ${resolvedKey}`;
5863
- const response = await fetchUpstream2(url, { method: "GET", headers }, { providerId: "byo" });
7454
+ Object.assign(headers, expandRowExtraHeaders(row));
7455
+ const response = await fetchUpstream7(url, { method: "GET", headers }, { providerId: "byo" });
5864
7456
  if (!response.ok) {
5865
7457
  const text = await response.text().catch(() => "");
5866
7458
  let message = text.slice(0, 300);
@@ -5877,8 +7469,8 @@ async function handleDiscoverModels(res, id, cfg) {
5877
7469
  const data = await response.json();
5878
7470
  const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
5879
7471
  return writeJson4(res, 200, { models });
5880
- } catch (err5) {
5881
- const message = err5 instanceof Error ? err5.message : String(err5);
7472
+ } catch (err8) {
7473
+ const message = err8 instanceof Error ? err8.message : String(err8);
5882
7474
  return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
5883
7475
  }
5884
7476
  }
@@ -5917,9 +7509,10 @@ async function handleTestModel(req, res, id, cfg) {
5917
7509
  messages: [{ role: "user", content: prompt }]
5918
7510
  };
5919
7511
  }
7512
+ Object.assign(headers, expandRowExtraHeaders(row));
5920
7513
  const startedAt = Date.now();
5921
7514
  try {
5922
- const response = await fetchUpstream2(
7515
+ const response = await fetchUpstream7(
5923
7516
  url,
5924
7517
  { method: "POST", headers, body: JSON.stringify(payload) },
5925
7518
  { providerId: "byo" }
@@ -5941,8 +7534,8 @@ async function handleTestModel(req, res, id, cfg) {
5941
7534
  latencyMs,
5942
7535
  sample: extractSampleText(text, row.apiFormat)
5943
7536
  });
5944
- } catch (err5) {
5945
- const message = err5 instanceof Error ? err5.message : String(err5);
7537
+ } catch (err8) {
7538
+ const message = err8 instanceof Error ? err8.message : String(err8);
5946
7539
  return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
5947
7540
  }
5948
7541
  }
@@ -5984,7 +7577,30 @@ async function handleProviderKeys(res, id, cfg, deps) {
5984
7577
  const row = cfg.providers.find((p) => p.id === id);
5985
7578
  if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
5986
7579
  const cooldown = await deps.apiKeyPool.getKeyHealth(id);
5987
- return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
7580
+ const views = toPoolKeyView(row, cooldown, deps);
7581
+ if (deps.providerKeyQuota) {
7582
+ const quotas = await Promise.allSettled(
7583
+ views.map((view) => deps.providerKeyQuota.quotaFor(row, view.id))
7584
+ );
7585
+ views.forEach((view, index) => {
7586
+ const settled = quotas[index];
7587
+ if (settled.status === "fulfilled" && settled.value) view.quota = settled.value;
7588
+ });
7589
+ }
7590
+ return writeJson4(res, 200, { keys: views });
7591
+ }
7592
+ async function handleProviderKeyQuotaRefresh(res, id, keyId, cfg, deps) {
7593
+ if (!deps.providerKeyQuota) return writeJsonError(res, 501, "provider key quota is not available");
7594
+ if (!id || !keyId) return writeJsonError(res, 400, "provider id and key id required in path");
7595
+ const row = cfg.providers.find((p) => p.id === id);
7596
+ if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
7597
+ try {
7598
+ const quota = await deps.providerKeyQuota.quotaFor(row, keyId, { force: true });
7599
+ if (!quota) return writeJsonError(res, 404, `no quota endpoint for key '${keyId}'`);
7600
+ return writeJson4(res, 200, { quota });
7601
+ } catch {
7602
+ return writeJsonError(res, 502, "quota refresh failed");
7603
+ }
5988
7604
  }
5989
7605
  function parsePoolKeyInput(body, existing) {
5990
7606
  const out = {};
@@ -6201,6 +7817,7 @@ function parseProviderInput(body, existing) {
6201
7817
  const apiVersion = typeof body["apiVersion"] === "string" && body["apiVersion"].length > 0 ? body["apiVersion"] : body["apiVersion"] === null ? void 0 : existing?.apiVersion;
6202
7818
  const modelsEndpoint = typeof body["modelsEndpoint"] === "string" && body["modelsEndpoint"].length > 0 ? body["modelsEndpoint"] : body["modelsEndpoint"] === null ? void 0 : existing?.modelsEndpoint;
6203
7819
  const maxConcurrency = typeof body["maxConcurrency"] === "number" && Number.isFinite(body["maxConcurrency"]) ? body["maxConcurrency"] : body["maxConcurrency"] === null ? void 0 : existing?.maxConcurrency;
7820
+ const extraHeaders = body["extraHeaders"] === null ? void 0 : body["extraHeaders"] === void 0 ? existing?.extraHeaders : validateExtraHeaders(body["extraHeaders"]);
6204
7821
  const transformer = body["transformer"] === null ? void 0 : parseTransformerInput(body["transformer"], existing?.transformer);
6205
7822
  const codingPlan = body["codingPlan"] === null ? void 0 : body["codingPlan"] === void 0 ? existing?.codingPlan : body["codingPlan"] && typeof body["codingPlan"] === "object" && !Array.isArray(body["codingPlan"]) ? parseCodingPlanInput(body["codingPlan"], existing?.codingPlan) : existing?.codingPlan;
6206
7823
  const apiModes = body["apiModes"] === null ? void 0 : parseApiModesInput(body["apiModes"], existing?.apiModes);
@@ -6226,6 +7843,7 @@ function parseProviderInput(body, existing) {
6226
7843
  apiVersion,
6227
7844
  maxConcurrency,
6228
7845
  modelsEndpoint,
7846
+ extraHeaders,
6229
7847
  transformer: migrated.transformer,
6230
7848
  codingPlan,
6231
7849
  apiModes,
@@ -6247,7 +7865,10 @@ function handlePresets(res, method) {
6247
7865
  description: p.description,
6248
7866
  features: p.features,
6249
7867
  website: p.website,
6250
- modelsEndpoint: p.modelsEndpoint
7868
+ modelsEndpoint: p.modelsEndpoint,
7869
+ // Static extra headers ride along so `addFromPreset` can seed them onto the
7870
+ // row (the write gateway re-validates via the shared allowlist).
7871
+ extraHeaders: p.extraHeaders
6251
7872
  }));
6252
7873
  return writeJson4(res, 200, { presets, excluded });
6253
7874
  }
@@ -6729,12 +8350,12 @@ async function handleAccounts(req, res, method, rest, deps) {
6729
8350
  }
6730
8351
  return writeJson4(res, 200, { ok: true, affected: result.affected });
6731
8352
  }
6732
- if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
6733
- const result = handleCodexOAuthStatus(rest[2], deps);
8353
+ if (method === "GET" && (rest[0] === "codex" || rest[0] === "kimi" || rest[0] === "grok" || rest[0] === "copilot") && rest[1] === "oauth" && rest[3] === "status") {
8354
+ const result = rest[0] === "codex" ? handleCodexOAuthStatus(rest[2], deps) : rest[0] === "kimi" ? handleKimiOAuthStatus(rest[2], deps) : rest[0] === "grok" ? handleGrokOAuthStatus(rest[2], deps) : handleCopilotOAuthStatus(rest[2], deps);
6734
8355
  return writeJson4(res, result.status, result.body);
6735
8356
  }
6736
- if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
6737
- const result = handleCodexOAuthCancel(rest[2], deps);
8357
+ if (method === "DELETE" && (rest[0] === "codex" || rest[0] === "kimi" || rest[0] === "grok" || rest[0] === "copilot") && rest[1] === "oauth" && rest[2]) {
8358
+ const result = rest[0] === "codex" ? handleCodexOAuthCancel(rest[2], deps) : rest[0] === "kimi" ? handleKimiOAuthCancel(rest[2], deps) : rest[0] === "grok" ? handleGrokOAuthCancel(rest[2], deps) : handleCopilotOAuthCancel(rest[2], deps);
6738
8359
  return writeJson4(res, result.status, result.body);
6739
8360
  }
6740
8361
  if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
@@ -6787,7 +8408,24 @@ async function handleAccounts(req, res, method, rest, deps) {
6787
8408
  return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
6788
8409
  }
6789
8410
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
6790
- const result = providerId === "codex" ? handleCodexOAuthStart(deps) : handleOAuthStart(providerId, deps);
8411
+ if (providerId === "codex") {
8412
+ const result2 = handleCodexOAuthStart(deps);
8413
+ return writeJson4(res, result2.status, result2.body);
8414
+ }
8415
+ if (providerId === "kimi") {
8416
+ const result2 = await handleKimiOAuthStart(deps);
8417
+ return writeJson4(res, result2.status, result2.body);
8418
+ }
8419
+ if (providerId === "grok") {
8420
+ const result2 = await handleGrokOAuthStart(deps);
8421
+ return writeJson4(res, result2.status, result2.body);
8422
+ }
8423
+ if (providerId === "copilot") {
8424
+ const body2 = await readJsonBody4(req);
8425
+ const result2 = await handleCopilotOAuthStart(deps, body2["enterpriseUrl"]);
8426
+ return writeJson4(res, result2.status, result2.body);
8427
+ }
8428
+ const result = handleOAuthStart(providerId, deps);
6791
8429
  return writeJson4(res, result.status, result.body);
6792
8430
  }
6793
8431
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
@@ -7281,12 +8919,12 @@ async function handlePlayground(req, res, method, deps) {
7281
8919
  const payload = body["body"];
7282
8920
  const status = deps.outboundApiServer.getStatus();
7283
8921
  if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
7284
- const path2 = resolvePlaygroundPath(endpoint, isRecord3(payload) ? payload : {});
8922
+ const path2 = resolvePlaygroundPath(endpoint, isRecord6(payload) ? payload : {});
7285
8923
  if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
7286
8924
  const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
7287
8925
  await proxyToOutbound(res, status.port, path2, key, upstreamBody);
7288
8926
  }
7289
- function isRecord3(v) {
8927
+ function isRecord6(v) {
7290
8928
  return !!v && typeof v === "object" && !Array.isArray(v);
7291
8929
  }
7292
8930
  function proxyToOutbound(res, outboundPort, path2, key, body) {
@@ -7315,8 +8953,8 @@ function proxyToOutbound(res, outboundPort, path2, key, body) {
7315
8953
  });
7316
8954
  }
7317
8955
  );
7318
- upstream.on("error", (err5) => {
7319
- if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err5.message}`);
8956
+ upstream.on("error", (err8) => {
8957
+ if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err8.message}`);
7320
8958
  else res.end();
7321
8959
  resolve10();
7322
8960
  });
@@ -7421,7 +9059,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
7421
9059
  }
7422
9060
 
7423
9061
  // src/admin/version.ts
7424
- var DAEMON_VERSION = true ? "0.3.0" : "0.0.0-dev";
9062
+ var DAEMON_VERSION = true ? "0.4.0" : "0.0.0-dev";
7425
9063
 
7426
9064
  // src/admin/AdminServer.ts
7427
9065
  var LOOPBACK_ADDR = "127.0.0.1";
@@ -7464,13 +9102,13 @@ var AdminServer = class {
7464
9102
  const server = http2.createServer((req, res) => {
7465
9103
  this.onRequest(req, res);
7466
9104
  });
7467
- const onError = (err5) => {
7468
- if (err5.code === "EADDRINUSE" && port !== 0) {
9105
+ const onError = (err8) => {
9106
+ if (err8.code === "EADDRINUSE" && port !== 0) {
7469
9107
  server.removeListener("error", onError);
7470
9108
  this.listen(bindAddr, 0).then(resolve10, reject);
7471
9109
  return;
7472
9110
  }
7473
- reject(err5);
9111
+ reject(err8);
7474
9112
  };
7475
9113
  server.on("error", onError);
7476
9114
  server.listen(port, bindAddr, () => {
@@ -7488,8 +9126,8 @@ var AdminServer = class {
7488
9126
  }
7489
9127
  /** Per-request handler: auth gate (when a token is set) → routing. */
7490
9128
  onRequest(req, res) {
7491
- void this.dispatch(req, res).catch((err5) => {
7492
- const message = err5 instanceof Error ? err5.message : String(err5);
9129
+ void this.dispatch(req, res).catch((err8) => {
9130
+ const message = err8 instanceof Error ? err8.message : String(err8);
7493
9131
  this.deps.logger.error("[AdminServer] unhandled error:", message);
7494
9132
  if (!res.headersSent) {
7495
9133
  res.writeHead(500, { "Content-Type": "application/json" });
@@ -7753,18 +9391,18 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
7753
9391
  return;
7754
9392
  }
7755
9393
  signal?.addEventListener("abort", abort, { once: true });
7756
- server.on("error", (err5) => {
9394
+ server.on("error", (err8) => {
7757
9395
  if (settled) return;
7758
9396
  settled = true;
7759
9397
  clearTimeout(timer);
7760
- if (err5.code === "EADDRINUSE") {
9398
+ if (err8.code === "EADDRINUSE") {
7761
9399
  reject(
7762
9400
  new Error(
7763
9401
  `login: cannot bind ${LOOPBACK_HOST}:${LOOPBACK_PORT} (address in use) \u2014 another codex login or process is holding the port`
7764
9402
  )
7765
9403
  );
7766
9404
  } else {
7767
- reject(err5);
9405
+ reject(err8);
7768
9406
  }
7769
9407
  });
7770
9408
  const timer = setTimeout(() => {
@@ -7839,6 +9477,449 @@ function createPoolKeysLoader(getProviderRow, autoDisabled) {
7839
9477
  };
7840
9478
  }
7841
9479
 
9480
+ // src/allowance/ProviderKeyQuotaService.ts
9481
+ import { mergeExtraHeaders as mergeExtraHeaders2 } from "@omnicross/core";
9482
+ import { fetchUpstream as fetchUpstream8 } from "@omnicross/core/pipeline/upstreamFetch";
9483
+
9484
+ // src/allowance/ProviderKeyQuota.ts
9485
+ var MINUTE_MS3 = 6e4;
9486
+ var HOUR_MS2 = 60 * MINUTE_MS3;
9487
+ var DAY_MS3 = 24 * HOUR_MS2;
9488
+ var WEEK_MS = 7 * DAY_MS3;
9489
+ var MONTH_MS = 30 * DAY_MS3;
9490
+ function finiteNumber5(value) {
9491
+ if (value === null || value === void 0 || value === "") return void 0;
9492
+ const parsed = typeof value === "number" ? value : Number(value);
9493
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
9494
+ }
9495
+ function finitePercent4(value) {
9496
+ const parsed = finiteNumber5(value);
9497
+ return parsed !== void 0 && parsed <= 100 ? parsed : null;
9498
+ }
9499
+ function isoInstant3(value) {
9500
+ if (typeof value === "string" && value.trim()) {
9501
+ const time = Date.parse(value);
9502
+ if (Number.isFinite(time)) return new Date(time).toISOString();
9503
+ }
9504
+ const numeric = finiteNumber5(value);
9505
+ if (numeric !== void 0 && numeric > 1e9) {
9506
+ const ms = numeric > 1e12 ? numeric : numeric * 1e3;
9507
+ return new Date(ms).toISOString();
9508
+ }
9509
+ return void 0;
9510
+ }
9511
+ function secondsUntil7(instant, now) {
9512
+ if (!instant) return void 0;
9513
+ return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
9514
+ }
9515
+ function isRecord7(value) {
9516
+ return !!value && typeof value === "object" && !Array.isArray(value);
9517
+ }
9518
+ function detectProviderKeyQuotaAdapter(baseUrl) {
9519
+ if (!baseUrl) return null;
9520
+ let url;
9521
+ try {
9522
+ url = new URL(baseUrl);
9523
+ } catch {
9524
+ return null;
9525
+ }
9526
+ const host = url.hostname.toLowerCase();
9527
+ const path2 = url.pathname.toLowerCase();
9528
+ if ((host === "api.z.ai" || host === "open.bigmodel.cn") && path2.includes("/coding")) {
9529
+ return "zai";
9530
+ }
9531
+ if ((host === "api.minimax.io" || host === "api.minimaxi.com") && // Token Plan rides the plain openai `/v1` (chat completions) surface; the
9532
+ // anthropic `/anthropic` rows are excluded (their usage impl is unverified).
9533
+ (path2 === "/v1" || path2 === "/v1/" || path2 === "" || path2 === "/")) {
9534
+ return "minimax-token-plan";
9535
+ }
9536
+ if (host === "api.code.umans.ai") return "umans";
9537
+ if (host === "api.synthetic.new") return "synthetic";
9538
+ if (host === "api.cline.bot") return "cline-pass";
9539
+ return null;
9540
+ }
9541
+ function providerKeyQuotaUrl(adapter, baseUrl) {
9542
+ const origin = new URL(baseUrl).origin;
9543
+ if (adapter === "zai") return `${origin}/api/monitor/usage/quota/limit`;
9544
+ if (adapter === "minimax-token-plan") return `${origin}/v1/token_plan/remains`;
9545
+ if (adapter === "umans") return `${origin}/v1/usage`;
9546
+ if (adapter === "cline-pass") return `${origin}/api/v1/users/me/plan/usage-limits`;
9547
+ return `${origin}/v2/quotas`;
9548
+ }
9549
+ function providerKeyQuotaAuthHeader(adapter, key) {
9550
+ return adapter === "zai" ? key : `Bearer ${key}`;
9551
+ }
9552
+ function zaiWindowDurationMs(item) {
9553
+ const count = item.number !== void 0 && item.number > 0 ? item.number : 1;
9554
+ switch (item.unit) {
9555
+ case 3:
9556
+ return count * HOUR_MS2;
9557
+ case 4:
9558
+ return count * DAY_MS3;
9559
+ case 5:
9560
+ return count * MONTH_MS;
9561
+ case 6:
9562
+ return WEEK_MS;
9563
+ default:
9564
+ return void 0;
9565
+ }
9566
+ }
9567
+ function zaiWindowIdLabel(durationMs) {
9568
+ if (durationMs === WEEK_MS) return { id: "seven-day", label: "7 days" };
9569
+ if (durationMs === 5 * HOUR_MS2) return { id: "five-hour", label: "5 hours" };
9570
+ if (durationMs === MONTH_MS) return { id: "thirty-day", label: "30 days" };
9571
+ if (durationMs !== void 0 && durationMs % DAY_MS3 === 0) {
9572
+ const days = durationMs / DAY_MS3;
9573
+ return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}` };
9574
+ }
9575
+ if (durationMs !== void 0 && durationMs % HOUR_MS2 === 0) {
9576
+ const hours = durationMs / HOUR_MS2;
9577
+ return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}` };
9578
+ }
9579
+ return { id: "quota", label: "Quota" };
9580
+ }
9581
+ function parseZaiQuotaPayload(payload, now) {
9582
+ if (!isRecord7(payload)) return null;
9583
+ const data = isRecord7(payload["data"]) ? payload["data"] : payload;
9584
+ if (payload["success"] === false) return null;
9585
+ const limits = Array.isArray(data["limits"]) ? data["limits"] : [];
9586
+ const byWindow = /* @__PURE__ */ new Map();
9587
+ for (const raw of limits) {
9588
+ if (!isRecord7(raw)) continue;
9589
+ const item = raw;
9590
+ if (item.type === void 0) continue;
9591
+ const details = raw["usageDetails"];
9592
+ if (Array.isArray(details) && details.some((d) => isRecord7(d) && d["modelCode"] === "zread")) {
9593
+ continue;
9594
+ }
9595
+ const durationMs = zaiWindowDurationMs(item);
9596
+ const { id, label } = zaiWindowIdLabel(durationMs);
9597
+ const limit = finiteNumber5(item.usage);
9598
+ const used = finiteNumber5(item.currentValue);
9599
+ const fromAbsolute = limit !== void 0 && used !== void 0 && limit > 0 ? Math.min(100, used / limit * 100) : void 0;
9600
+ const fromPercentage = finitePercent4(item.percentage) ?? void 0;
9601
+ const usedPercent = fromAbsolute !== void 0 ? Math.round(fromAbsolute * 10) / 10 : fromPercentage;
9602
+ if (usedPercent === void 0) continue;
9603
+ const resetsAt = isoInstant3(item.nextResetTime);
9604
+ const candidate = {
9605
+ id,
9606
+ label,
9607
+ scope: "all",
9608
+ usedPercent,
9609
+ ...durationMs !== void 0 ? { windowMinutes: Math.round(durationMs / MINUTE_MS3) } : {},
9610
+ ...resetsAt !== void 0 ? { resetsAt } : {},
9611
+ remainingSeconds: secondsUntil7(resetsAt, now),
9612
+ state: "fresh"
9613
+ };
9614
+ const existing = byWindow.get(id);
9615
+ if (!existing || (candidate.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
9616
+ byWindow.set(id, candidate);
9617
+ }
9618
+ }
9619
+ const windows = [...byWindow.values()].sort((a, b) => (a.windowMinutes ?? Number.POSITIVE_INFINITY) - (b.windowMinutes ?? Number.POSITIVE_INFINITY));
9620
+ return windows.length > 0 ? windows.slice(0, 4) : null;
9621
+ }
9622
+ var MINIMAX_STATUS_EXHAUSTED = 2;
9623
+ var MINIMAX_SHARED_BUCKET = "general";
9624
+ function parseMiniMaxBucket(value) {
9625
+ if (!isRecord7(value)) return null;
9626
+ const modelName = typeof value["model_name"] === "string" ? value["model_name"].trim() : "";
9627
+ if (!modelName) return null;
9628
+ const instant = (v) => {
9629
+ const n = finiteNumber5(v);
9630
+ return n !== void 0 && n > 1e9 ? n > 1e12 ? n : n * 1e3 : void 0;
9631
+ };
9632
+ return {
9633
+ modelName,
9634
+ intervalEnd: instant(value["end_time"]),
9635
+ intervalRemainingPercent: finiteNumber5(value["current_interval_remaining_percent"]),
9636
+ intervalStatus: finiteNumber5(value["current_interval_status"]),
9637
+ weeklyEnd: instant(value["weekly_end_time"]),
9638
+ weeklyRemainingPercent: finiteNumber5(value["current_weekly_remaining_percent"]),
9639
+ weeklyStatus: finiteNumber5(value["current_weekly_status"])
9640
+ };
9641
+ }
9642
+ function minimaxWindow(id, label, windowMinutes, resetsAtMs, remainingPercent, status, now) {
9643
+ const usedPercent = status === MINIMAX_STATUS_EXHAUSTED ? 100 : remainingPercent !== void 0 ? Math.round((100 - remainingPercent) * 10) / 10 : null;
9644
+ const resetsAt = resetsAtMs !== void 0 ? new Date(resetsAtMs).toISOString() : void 0;
9645
+ return {
9646
+ id,
9647
+ label,
9648
+ scope: "all",
9649
+ usedPercent,
9650
+ ...windowMinutes !== void 0 ? { windowMinutes } : {},
9651
+ ...resetsAt !== void 0 ? { resetsAt } : {},
9652
+ remainingSeconds: secondsUntil7(resetsAt, now),
9653
+ state: usedPercent !== null ? "fresh" : "unavailable"
9654
+ };
9655
+ }
9656
+ function parseMiniMaxTokenPlanPayload(payload, now) {
9657
+ if (!isRecord7(payload)) return null;
9658
+ const baseResp = payload["base_resp"];
9659
+ if (!isRecord7(baseResp) || baseResp["status_code"] !== 0) return null;
9660
+ const buckets = Array.isArray(payload["model_remains"]) ? payload["model_remains"] : [];
9661
+ let general = null;
9662
+ for (const raw of buckets) {
9663
+ const bucket = parseMiniMaxBucket(raw);
9664
+ if (bucket?.modelName === MINIMAX_SHARED_BUCKET) {
9665
+ general = bucket;
9666
+ break;
9667
+ }
9668
+ }
9669
+ if (!general) return null;
9670
+ return [
9671
+ minimaxWindow(
9672
+ "five-hour",
9673
+ "5 hours",
9674
+ 5 * 60,
9675
+ general.intervalEnd,
9676
+ general.intervalRemainingPercent,
9677
+ general.intervalStatus,
9678
+ now
9679
+ ),
9680
+ minimaxWindow(
9681
+ "seven-day",
9682
+ "7 days",
9683
+ Math.round(WEEK_MS / MINUTE_MS3),
9684
+ general.weeklyEnd,
9685
+ general.weeklyRemainingPercent,
9686
+ general.weeklyStatus,
9687
+ now
9688
+ )
9689
+ ];
9690
+ }
9691
+ function parseUmansUsagePayload(payload, now) {
9692
+ if (!isRecord7(payload)) return null;
9693
+ const limits = isRecord7(payload["limits"]) ? payload["limits"] : void 0;
9694
+ const requests = limits && isRecord7(limits["requests"]) ? limits["requests"] : void 0;
9695
+ const usage = isRecord7(payload["usage"]) ? payload["usage"] : void 0;
9696
+ const window = isRecord7(payload["window"]) ? payload["window"] : void 0;
9697
+ const hardCap = finiteNumber5(requests?.["hard_cap"]);
9698
+ const softLimit = finiteNumber5(requests?.["limit"]);
9699
+ const requestsInWindow = finiteNumber5(usage?.["requests_in_window"]);
9700
+ const weightedInWindow = finiteNumber5(usage?.["weighted_in_window"]);
9701
+ const resetsAt = isoInstant3(window?.["resets_at"]);
9702
+ let usedPercent = null;
9703
+ if (hardCap !== void 0 && hardCap > 0 && requestsInWindow !== void 0) {
9704
+ usedPercent = Math.round(Math.min(100, requestsInWindow / hardCap * 100) * 10) / 10;
9705
+ } else if (softLimit !== void 0 && softLimit > 0 && weightedInWindow !== void 0) {
9706
+ usedPercent = Math.round(Math.min(100, weightedInWindow / softLimit * 100) * 10) / 10;
9707
+ }
9708
+ if (usedPercent === null && resetsAt === void 0) return null;
9709
+ return [
9710
+ {
9711
+ id: "five-hour",
9712
+ label: "5 hours",
9713
+ scope: "all",
9714
+ usedPercent,
9715
+ windowMinutes: 5 * 60,
9716
+ ...resetsAt !== void 0 ? { resetsAt } : {},
9717
+ remainingSeconds: secondsUntil7(resetsAt, now),
9718
+ state: "fresh"
9719
+ }
9720
+ ];
9721
+ }
9722
+ function parseSyntheticQuotasPayload(payload, now) {
9723
+ if (!isRecord7(payload)) return null;
9724
+ const fiveHour = isRecord7(payload["rollingFiveHourLimit"]) ? payload["rollingFiveHourLimit"] : void 0;
9725
+ const weekly = isRecord7(payload["weeklyTokenLimit"]) ? payload["weeklyTokenLimit"] : void 0;
9726
+ const windows = [];
9727
+ if (fiveHour) {
9728
+ const max = finiteNumber5(fiveHour["max"]);
9729
+ const remaining = finiteNumber5(fiveHour["remaining"]);
9730
+ const usedPercent = max !== void 0 && max > 0 && remaining !== void 0 ? Math.round(Math.min(100, (max - remaining) / max * 100) * 10) / 10 : null;
9731
+ const resetsAt = isoInstant3(fiveHour["nextTickAt"]);
9732
+ windows.push({
9733
+ id: "five-hour",
9734
+ label: "5 hours",
9735
+ scope: "all",
9736
+ usedPercent,
9737
+ windowMinutes: 5 * 60,
9738
+ ...resetsAt !== void 0 ? { resetsAt } : {},
9739
+ remainingSeconds: secondsUntil7(resetsAt, now),
9740
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
9741
+ });
9742
+ }
9743
+ if (weekly) {
9744
+ const percentRemaining = finiteNumber5(weekly["percentRemaining"]);
9745
+ const usedPercent = percentRemaining !== void 0 ? Math.round(Math.min(100, Math.max(0, 100 - percentRemaining)) * 10) / 10 : null;
9746
+ const resetsAt = isoInstant3(weekly["nextRegenAt"]);
9747
+ windows.push({
9748
+ id: "seven-day",
9749
+ label: "7 days",
9750
+ scope: "all",
9751
+ usedPercent,
9752
+ windowMinutes: 7 * 24 * 60,
9753
+ ...resetsAt !== void 0 ? { resetsAt } : {},
9754
+ remainingSeconds: secondsUntil7(resetsAt, now),
9755
+ state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
9756
+ });
9757
+ }
9758
+ return windows.length > 0 ? windows : null;
9759
+ }
9760
+ var CLINE_WINDOW_CONFIG = {
9761
+ five_hour: { id: "five-hour", label: "5 hours", minutes: 5 * 60 },
9762
+ weekly: { id: "seven-day", label: "7 days", minutes: 7 * 24 * 60 },
9763
+ monthly: { id: "thirty-day", label: "30 days", minutes: 30 * 24 * 60 }
9764
+ };
9765
+ function parseClinePassUsageLimitsPayload(payload, now) {
9766
+ if (!isRecord7(payload)) return null;
9767
+ const data = isRecord7(payload["data"]) ? payload["data"] : payload;
9768
+ const limits = Array.isArray(data["limits"]) ? data["limits"] : [];
9769
+ const windows = [];
9770
+ for (const raw of limits) {
9771
+ if (!isRecord7(raw)) continue;
9772
+ const config = CLINE_WINDOW_CONFIG[typeof raw["type"] === "string" ? raw["type"] : ""];
9773
+ if (!config) continue;
9774
+ const usedPercent = finitePercent4(raw["percentUsed"]);
9775
+ if (usedPercent === null) continue;
9776
+ const resetsAt = isoInstant3(raw["resetsAt"]);
9777
+ windows.push({
9778
+ id: config.id,
9779
+ label: config.label,
9780
+ scope: "all",
9781
+ usedPercent,
9782
+ windowMinutes: config.minutes,
9783
+ ...resetsAt !== void 0 ? { resetsAt } : {},
9784
+ remainingSeconds: secondsUntil7(resetsAt, now),
9785
+ state: "fresh"
9786
+ });
9787
+ }
9788
+ return windows.length > 0 ? windows : null;
9789
+ }
9790
+
9791
+ // src/allowance/ProviderKeyQuotaService.ts
9792
+ function parseQuotaPayload(adapter, payload, now) {
9793
+ switch (adapter) {
9794
+ case "zai":
9795
+ return parseZaiQuotaPayload(payload, now);
9796
+ case "minimax-token-plan":
9797
+ return parseMiniMaxTokenPlanPayload(payload, now);
9798
+ case "umans":
9799
+ return parseUmansUsagePayload(payload, now);
9800
+ case "synthetic":
9801
+ return parseSyntheticQuotasPayload(payload, now);
9802
+ case "cline-pass":
9803
+ return parseClinePassUsageLimitsPayload(payload, now);
9804
+ }
9805
+ }
9806
+ var PROVIDER_KEY_QUOTA_CACHE_MS = 5 * 6e4;
9807
+ function resolvedBaseUrl(row) {
9808
+ const modes = row.apiModes ?? [];
9809
+ const selected = row.selectedApiModeId ? modes.find((mode) => mode.id === row.selectedApiModeId) : void 0;
9810
+ const fallback = modes[0];
9811
+ const modeBase = selected?.baseUrl ?? fallback?.baseUrl;
9812
+ return modeBase ?? row.codingPlan?.baseUrl ?? row.baseUrl;
9813
+ }
9814
+ function rowKeyEntries(row) {
9815
+ const pool = (row.apiKeys ?? []).filter((entry) => entry.apiKey.length > 0);
9816
+ if (pool.length > 0) return pool.map((entry) => ({ id: entry.id, apiKey: entry.apiKey }));
9817
+ if (row.apiKey.length > 0) {
9818
+ return [{ id: `${row.id}:default`, apiKey: row.apiKey }];
9819
+ }
9820
+ return [];
9821
+ }
9822
+ var ProviderKeyQuotaService = class {
9823
+ constructor(box, fetchImpl = (url, init) => fetchUpstream8(url, init, { redactBodies: true }), now = Date.now) {
9824
+ this.box = box;
9825
+ this.fetchImpl = fetchImpl;
9826
+ this.now = now;
9827
+ }
9828
+ box;
9829
+ fetchImpl;
9830
+ now;
9831
+ cache = /* @__PURE__ */ new Map();
9832
+ inFlight = /* @__PURE__ */ new Map();
9833
+ /**
9834
+ * Quota for one key of a provider row, or `null` when the row has no quota
9835
+ * adapter / no such key. Cache-first; concurrent reads share one flight.
9836
+ */
9837
+ async quotaFor(row, keyId, options = {}) {
9838
+ const adapter = detectProviderKeyQuotaAdapter(resolvedBaseUrl(row));
9839
+ if (!adapter) return null;
9840
+ const entry = rowKeyEntries(row).find((candidate) => candidate.id === keyId);
9841
+ if (!entry) return null;
9842
+ const cacheKey = `${row.id}\0${keyId}`;
9843
+ const now = this.now();
9844
+ const cached = this.cache.get(cacheKey);
9845
+ if (!options.force && cached && Date.parse(cached.expiresAt) > now) return cached;
9846
+ const running = this.inFlight.get(cacheKey);
9847
+ if (running) return running;
9848
+ const promise = this.fetchQuota(adapter, row, entry.apiKey, cacheKey).catch((error) => {
9849
+ void error;
9850
+ const previous = this.cache.get(cacheKey);
9851
+ if (previous) {
9852
+ const degraded = {
9853
+ ...previous,
9854
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
9855
+ windows: previous.windows.map((window) => ({
9856
+ ...window,
9857
+ state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
9858
+ })),
9859
+ errorCode: "quota_request_failed"
9860
+ };
9861
+ this.cache.set(cacheKey, degraded);
9862
+ return degraded;
9863
+ }
9864
+ return null;
9865
+ }).finally(() => this.inFlight.delete(cacheKey));
9866
+ this.inFlight.set(cacheKey, promise);
9867
+ return promise;
9868
+ }
9869
+ /** Drop cached rows for a provider (key added/removed/rotated). */
9870
+ invalidateProvider(providerRowId) {
9871
+ for (const key of this.cache.keys()) {
9872
+ if (key.startsWith(`${providerRowId}\0`)) this.cache.delete(key);
9873
+ }
9874
+ }
9875
+ async fetchQuota(adapter, row, rawKey, cacheKey) {
9876
+ const baseUrl = resolvedBaseUrl(row);
9877
+ const url = providerKeyQuotaUrl(adapter, baseUrl);
9878
+ const key = this.box.decryptMaybe(rawKey);
9879
+ const now = this.now();
9880
+ const response = await this.fetchImpl(url, {
9881
+ method: "GET",
9882
+ headers: {
9883
+ Authorization: providerKeyQuotaAuthHeader(adapter, key),
9884
+ Accept: "application/json",
9885
+ "Content-Type": "application/json",
9886
+ // The row's static identity headers ride along — the Cline usage
9887
+ // endpoint sits behind the SAME client-identity 403 gate as inference.
9888
+ ...mergeExtraHeaders2({}, row.extraHeaders)
9889
+ },
9890
+ signal: AbortSignal.timeout(15e3)
9891
+ });
9892
+ if (response.status === 401 || response.status === 403) {
9893
+ const snapshot2 = {
9894
+ adapter,
9895
+ observedAt: new Date(now).toISOString(),
9896
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
9897
+ windows: [],
9898
+ errorCode: "quota_unauthorized"
9899
+ };
9900
+ this.cache.set(cacheKey, snapshot2);
9901
+ return snapshot2;
9902
+ }
9903
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
9904
+ let payload;
9905
+ try {
9906
+ payload = await response.json();
9907
+ } catch {
9908
+ throw new Error("invalid JSON");
9909
+ }
9910
+ const windows = parseQuotaPayload(adapter, payload, now);
9911
+ const snapshot = {
9912
+ adapter,
9913
+ observedAt: new Date(now).toISOString(),
9914
+ expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
9915
+ windows: windows ?? [],
9916
+ ...windows ? {} : { errorCode: "quota_unavailable" }
9917
+ };
9918
+ this.cache.set(cacheKey, snapshot);
9919
+ return snapshot;
9920
+ }
9921
+ };
9922
+
7842
9923
  // src/commands/paths.ts
7843
9924
  import { dirname as dirname5, join as join5 } from "path";
7844
9925
  function defaultVouchersPath(configPath) {
@@ -12609,6 +14690,10 @@ function toLLMProvider(row) {
12609
14690
  // `parseProviderInput`), so customizations are preserved (the row value wins).
12610
14691
  apiModes: row.apiModes,
12611
14692
  selectedApiModeId: row.selectedApiModeId,
14693
+ // Static extra request headers ride along verbatim (load-guarded — no
14694
+ // auth/content names); core's `getProviderHeaders` merges them into every
14695
+ // BYO request, and the same-format relay path inherits that funnel.
14696
+ extraHeaders: row.extraHeaders,
12612
14697
  // Official-Anthropic signature handling only matters for the Anthropic
12613
14698
  // ingress (deferred → 502); leave it off for the BYO transform path.
12614
14699
  isOfficial: false
@@ -13988,21 +16073,23 @@ function bucketLabel(bucketStartTs, bucket) {
13988
16073
  }
13989
16074
 
13990
16075
  // src/ports/JsonOutboundKeyDb.ts
16076
+ import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
16077
+ import {
16078
+ validateOutboundPermissions as validateOutboundPermissions3
16079
+ } from "@omnicross/core";
16080
+
16081
+ // src/ports/atomicFile.ts
13991
16082
  import { randomBytes as randomBytes11 } from "crypto";
13992
16083
  import {
13993
16084
  closeSync as closeSync7,
13994
16085
  existsSync as existsSync16,
13995
16086
  fsyncSync as fsyncSync7,
13996
16087
  openSync as openSync7,
13997
- readFileSync as readFileSync13,
13998
16088
  renameSync as renameSync9,
13999
16089
  unlinkSync as unlinkSync11,
14000
16090
  writeFileSync as writeFileSync12
14001
16091
  } from "fs";
14002
16092
  import { basename as basename8, dirname as dirname14, join as join16 } from "path";
14003
- import {
14004
- validateOutboundPermissions as validateOutboundPermissions3
14005
- } from "@omnicross/core";
14006
16093
  function atomicReplaceUtf8(targetPath, contents) {
14007
16094
  const tempPath = join16(
14008
16095
  dirname14(targetPath),
@@ -14032,6 +16119,8 @@ function atomicReplaceUtf8(targetPath, contents) {
14032
16119
  throw error;
14033
16120
  }
14034
16121
  }
16122
+
16123
+ // src/ports/JsonOutboundKeyDb.ts
14035
16124
  var JsonOutboundKeyDb = class {
14036
16125
  /**
14037
16126
  * @param secretBox OPTIONAL reversible-secret codec. When present, a created
@@ -14174,7 +16263,7 @@ var JsonOutboundKeyDb = class {
14174
16263
  }
14175
16264
  /** Read the key rows, tolerating a missing/corrupt file (→ empty list). */
14176
16265
  readRows() {
14177
- if (!existsSync16(this.keysPath)) return [];
16266
+ if (!existsSync17(this.keysPath)) return [];
14178
16267
  try {
14179
16268
  const parsed = JSON.parse(readFileSync13(this.keysPath, "utf8"));
14180
16269
  return Array.isArray(parsed) ? parsed : [];
@@ -14193,7 +16282,7 @@ function applyPolicyField(row, field, value) {
14193
16282
  }
14194
16283
 
14195
16284
  // src/ports/JsonPricingStore.ts
14196
- import { existsSync as existsSync17, readFileSync as readFileSync14, renameSync as renameSync10, rmSync as rmSync3, writeFileSync as writeFileSync13 } from "fs";
16285
+ import { existsSync as existsSync18, readFileSync as readFileSync14, renameSync as renameSync10, rmSync as rmSync3, writeFileSync as writeFileSync13 } from "fs";
14197
16286
  import { randomUUID as randomUUID5 } from "crypto";
14198
16287
  var JsonPricingStore = class {
14199
16288
  constructor(pricingPath) {
@@ -14208,7 +16297,7 @@ var JsonPricingStore = class {
14208
16297
  * otherwise unusable pricing table after a crash or manual file edit.
14209
16298
  */
14210
16299
  hasUsableSnapshot() {
14211
- if (!existsSync17(this.pricingPath)) return false;
16300
+ if (!existsSync18(this.pricingPath)) return false;
14212
16301
  try {
14213
16302
  const parsed = JSON.parse(readFileSync14(this.pricingPath, "utf8"));
14214
16303
  return Array.isArray(parsed) && parsed.some(isUsablePricingRow);
@@ -14323,7 +16412,7 @@ var JsonPricingStore = class {
14323
16412
  }
14324
16413
  /** Read the pricing rows, tolerating a missing/corrupt file (→ empty list). */
14325
16414
  readRows() {
14326
- if (!existsSync17(this.pricingPath)) return [];
16415
+ if (!existsSync18(this.pricingPath)) return [];
14327
16416
  try {
14328
16417
  const parsed = JSON.parse(readFileSync14(this.pricingPath, "utf8"));
14329
16418
  return Array.isArray(parsed) ? parsed : [];
@@ -14355,7 +16444,7 @@ function isUsablePricingRow(value) {
14355
16444
  }
14356
16445
 
14357
16446
  // src/pricing/PricingRefreshScheduler.ts
14358
- import { existsSync as existsSync18, readFileSync as readFileSync15, renameSync as renameSync11, writeFileSync as writeFileSync14 } from "fs";
16447
+ import { existsSync as existsSync19, readFileSync as readFileSync15, renameSync as renameSync11, writeFileSync as writeFileSync14 } from "fs";
14359
16448
  var EMPTY_STATE2 = {
14360
16449
  lastAttemptAt: null,
14361
16450
  lastSuccessAt: null,
@@ -14393,7 +16482,7 @@ var PricingRefreshScheduler = class {
14393
16482
  this.timer = null;
14394
16483
  }
14395
16484
  getState() {
14396
- if (!existsSync18(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
16485
+ if (!existsSync19(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
14397
16486
  try {
14398
16487
  const value = JSON.parse(readFileSync15(this.statePath, "utf8"));
14399
16488
  return {
@@ -14458,7 +16547,7 @@ function finiteOrNull(value) {
14458
16547
  }
14459
16548
 
14460
16549
  // src/ports/JsonVoucherDb.ts
14461
- import { existsSync as existsSync19, readFileSync as readFileSync16, writeFileSync as writeFileSync15 } from "fs";
16550
+ import { existsSync as existsSync20, readFileSync as readFileSync16, writeFileSync as writeFileSync15 } from "fs";
14462
16551
  var JsonVoucherDb = class {
14463
16552
  constructor(vouchersPath) {
14464
16553
  this.vouchersPath = vouchersPath;
@@ -14536,7 +16625,7 @@ var JsonVoucherDb = class {
14536
16625
  }
14537
16626
  /** Read the voucher rows, tolerating a missing/corrupt file (→ empty list). */
14538
16627
  readRows() {
14539
- if (!existsSync19(this.vouchersPath)) return [];
16628
+ if (!existsSync20(this.vouchersPath)) return [];
14540
16629
  try {
14541
16630
  const parsed = JSON.parse(readFileSync16(this.vouchersPath, "utf8"));
14542
16631
  return Array.isArray(parsed) ? parsed : [];
@@ -14550,16 +16639,18 @@ var JsonVoucherDb = class {
14550
16639
  };
14551
16640
 
14552
16641
  // src/ports/JsonSubscriptionCredentialStore.ts
14553
- import { existsSync as existsSync21, mkdirSync as mkdirSync6, readFileSync as readFileSync18, writeFileSync as writeFileSync16 } from "fs";
16642
+ import { existsSync as existsSync22, mkdirSync as mkdirSync6, readFileSync as readFileSync18, renameSync as renameSync12 } from "fs";
14554
16643
  import { dirname as dirname15 } from "path";
14555
16644
  import { getSharedAccountHealth as getSharedAccountHealth2 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
14556
16645
  import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling3 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
14557
- import { fetchUpstream as fetchUpstream3 } from "@omnicross/core/pipeline/upstreamFetch";
16646
+ import { fetchUpstream as fetchUpstream9 } from "@omnicross/core/pipeline/upstreamFetch";
14558
16647
  import { getSharedIdentityStore as getSharedIdentityStore2 } from "@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore";
14559
16648
  import {
14560
16649
  claudeOAuth as claudeOAuth2,
14561
16650
  codexOAuth as codexOAuth2,
14562
- geminiOAuth as geminiOAuth2
16651
+ geminiOAuth as geminiOAuth2,
16652
+ grokOAuth as grokOAuth2,
16653
+ kimiOAuth as kimiOAuth2
14563
16654
  } from "@omnicross/subscriptions";
14564
16655
 
14565
16656
  // src/ports/account-sync.ts
@@ -14604,7 +16695,7 @@ function findDuplicateCredentialIds(accounts) {
14604
16695
  }
14605
16696
 
14606
16697
  // src/ports/external-cli-credentials.ts
14607
- import { existsSync as existsSync20, readFileSync as readFileSync17 } from "fs";
16698
+ import { existsSync as existsSync21, readFileSync as readFileSync17 } from "fs";
14608
16699
  import { homedir as homedir4 } from "os";
14609
16700
  import { join as join17 } from "path";
14610
16701
  function externalStorePath(provider, home = homedir4()) {
@@ -14657,7 +16748,7 @@ function parseCodexTokensEnvelope(raw) {
14657
16748
  }
14658
16749
  function readExternalCliCredentials(provider, home = homedir4()) {
14659
16750
  const path2 = externalStorePath(provider, home);
14660
- if (!existsSync20(path2)) return null;
16751
+ if (!existsSync21(path2)) return null;
14661
16752
  let raw;
14662
16753
  try {
14663
16754
  const parsed = JSON.parse(readFileSync17(path2, "utf8"));
@@ -14683,16 +16774,18 @@ var JsonSubscriptionCredentialStore = class {
14683
16774
  * as on relay refresh egresses from the SAME proxy IP as the
14684
16775
  * account's traffic. NOT used by any read/write path.
14685
16776
  */
14686
- constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials) {
16777
+ constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials, atomicReplace = atomicReplaceUtf8) {
14687
16778
  this.tokensPath = tokensPath;
14688
16779
  this.box = box;
14689
16780
  this.fetchImpl = fetchImpl;
14690
16781
  this.externalCliReader = externalCliReader;
16782
+ this.atomicReplace = atomicReplace;
14691
16783
  }
14692
16784
  tokensPath;
14693
16785
  box;
14694
16786
  fetchImpl;
14695
16787
  externalCliReader;
16788
+ atomicReplace;
14696
16789
  /**
14697
16790
  * The proxy-aware `FetchLike` for one refresh round-trip (upstream-proxy M1). A
14698
16791
  * TEST-injected `fetchImpl` is returned verbatim; otherwise the refresh routes
@@ -14706,7 +16799,7 @@ var JsonSubscriptionCredentialStore = class {
14706
16799
  * a plaintext token pair into `upstream-trace.jsonl`.
14707
16800
  */
14708
16801
  buildRefreshFetch(providerId, accountId) {
14709
- return this.fetchImpl ?? ((url, init) => fetchUpstream3(url, init, { providerId, accountId, redactBodies: true }));
16802
+ return this.fetchImpl ?? ((url, init) => fetchUpstream9(url, init, { providerId, accountId, redactBodies: true }));
14710
16803
  }
14711
16804
  /**
14712
16805
  * In-flight refresh coalescing. OAuth refresh tokens are
@@ -14747,7 +16840,7 @@ var JsonSubscriptionCredentialStore = class {
14747
16840
  * other hot reads. Never returns token material.
14748
16841
  */
14749
16842
  getAccountProxy(providerId, accountId) {
14750
- if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego") {
16843
+ if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego" && providerId !== "kimi" && providerId !== "grok" && providerId !== "copilot") {
14751
16844
  return void 0;
14752
16845
  }
14753
16846
  return getAccountProxy(this.readConfig(), providerId, accountId);
@@ -14766,7 +16859,7 @@ var JsonSubscriptionCredentialStore = class {
14766
16859
  const fingerprintOn = identityStore.isEnabled();
14767
16860
  const now = Date.now();
14768
16861
  const out = {};
14769
- for (const provider of ["claude", "codex", "gemini", "opencodego"]) {
16862
+ for (const provider of ["claude", "codex", "gemini", "opencodego", "kimi", "grok", "copilot"]) {
14770
16863
  const sanitized = sanitizeAccounts(config, provider);
14771
16864
  if (sanitized.length === 0) continue;
14772
16865
  for (const account of sanitized) {
@@ -14924,6 +17017,107 @@ var JsonSubscriptionCredentialStore = class {
14924
17017
  }
14925
17018
  });
14926
17019
  }
17020
+ /**
17021
+ * Refresh the Kimi Code (Moonshot) OAuth access token (device-flow grant).
17022
+ * Kimi ROTATES the refresh token, so the response's pair is written back
17023
+ * whole; the account's stable `deviceId` (fingerprint header input) is
17024
+ * preserved. The refresh call carries the CLI fingerprint headers. HONEST
17025
+ * `false` when no refresh_token.
17026
+ */
17027
+ async refreshKimiToken() {
17028
+ return this.coalesce("kimi:active", async () => {
17029
+ const config = this.readConfig();
17030
+ const active = getActiveAccount(config, "kimi");
17031
+ const kimi = active?.tokens;
17032
+ if (!active || !kimi?.refreshToken) return false;
17033
+ const capturedId = active.id;
17034
+ this.materializeMigration(config);
17035
+ const refreshFetch = this.buildRefreshFetch("kimi", capturedId);
17036
+ try {
17037
+ const result = await kimiOAuth2.refreshAccessToken(
17038
+ kimi.refreshToken,
17039
+ refreshFetch,
17040
+ kimiOAuth2.kimiFingerprintHeaders(kimi.deviceId)
17041
+ );
17042
+ const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
17043
+ const next = {
17044
+ ...kimi,
17045
+ accessToken: result.accessToken,
17046
+ refreshToken: result.refreshToken,
17047
+ expiresAt,
17048
+ status: "authorized",
17049
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
17050
+ errorMessage: void 0,
17051
+ syncWarning: void 0
17052
+ };
17053
+ this.writeBackById("kimi", capturedId, next);
17054
+ return true;
17055
+ } catch (error) {
17056
+ this.markExpiredById("kimi", capturedId, kimi, error);
17057
+ return false;
17058
+ }
17059
+ });
17060
+ }
17061
+ /**
17062
+ * Refresh the Grok (xAI SuperGrok) OAuth access token. The token endpoint is
17063
+ * resolved through OIDC discovery on every refresh (process-cached 1h by the
17064
+ * flow module) so a rotated endpoint document is picked up without a daemon
17065
+ * restart. HONEST `false` when no refresh_token.
17066
+ */
17067
+ async refreshGrokToken() {
17068
+ return this.coalesce("grok:active", async () => {
17069
+ const config = this.readConfig();
17070
+ const active = getActiveAccount(config, "grok");
17071
+ const grok = active?.tokens;
17072
+ if (!active || !grok?.refreshToken) return false;
17073
+ const capturedId = active.id;
17074
+ this.materializeMigration(config);
17075
+ const refreshFetch = this.buildRefreshFetch("grok", capturedId);
17076
+ try {
17077
+ const tokenEndpoint = await grokOAuth2.resolveGrokTokenEndpoint(refreshFetch);
17078
+ const result = await grokOAuth2.refreshGrokAccessToken(grok.refreshToken, tokenEndpoint, refreshFetch);
17079
+ const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
17080
+ const next = {
17081
+ ...grok,
17082
+ accessToken: result.accessToken,
17083
+ refreshToken: result.refreshToken,
17084
+ expiresAt,
17085
+ status: "authorized",
17086
+ lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
17087
+ errorMessage: void 0,
17088
+ syncWarning: void 0
17089
+ };
17090
+ this.writeBackById("grok", capturedId, next);
17091
+ return true;
17092
+ } catch (error) {
17093
+ this.markExpiredById("grok", capturedId, grok, error);
17094
+ return false;
17095
+ }
17096
+ });
17097
+ }
17098
+ /**
17099
+ * "Refresh" a GitHub Copilot token — there is nothing to refresh (ghu_
17100
+ * tokens are long-lived with no exchange endpoint). A call here means the
17101
+ * strategy saw a 401 (the token was revoked); mark the account `expired`
17102
+ * with a re-authenticate message and return `false` (the proxy then declines
17103
+ * the retry instead of looping on a dead token).
17104
+ */
17105
+ async refreshCopilotToken() {
17106
+ return this.coalesce("copilot:active", async () => {
17107
+ const config = this.readConfig();
17108
+ const active = getActiveAccount(config, "copilot");
17109
+ const copilot = active?.tokens;
17110
+ if (!active || !copilot?.accessToken) return false;
17111
+ this.materializeMigration(config);
17112
+ this.markExpiredById(
17113
+ "copilot",
17114
+ active.id,
17115
+ copilot,
17116
+ new Error("GitHub Copilot tokens cannot be refreshed \u2014 re-authenticate the account")
17117
+ );
17118
+ return false;
17119
+ });
17120
+ }
14927
17121
  /**
14928
17122
  * Refresh a SPECIFIC managed account by id (background scheduler sweep and
14929
17123
  * account-pool resolution). It uses only that account's stored refresh
@@ -14976,7 +17170,7 @@ var JsonSubscriptionCredentialStore = class {
14976
17170
  }
14977
17171
  const oauth = account.tokens;
14978
17172
  if (!oauth.accessToken) return null;
14979
- if (providerId === "codex" || providerId === "gemini") {
17173
+ if (providerId === "codex" || providerId === "gemini" || providerId === "kimi" || providerId === "grok" || providerId === "copilot") {
14980
17174
  const expiresAtMs = oauth.expiresAt ? Date.parse(oauth.expiresAt) : 0;
14981
17175
  const expiringSoon = expiresAtMs > 0 && Date.now() >= expiresAtMs - ACCOUNT_REFRESH_LEAD_MS;
14982
17176
  if (expiringSoon && oauth.refreshToken) {
@@ -15065,8 +17259,35 @@ var JsonSubscriptionCredentialStore = class {
15065
17259
  }
15066
17260
  /** Dispatch one OAuth refresh round-trip to the provider's shared flow. */
15067
17261
  async refreshUpstream(provider, refreshToken, accountId) {
17262
+ const refreshFetch = this.buildRefreshFetch(provider, accountId);
17263
+ if (provider === "kimi") {
17264
+ const account = accountId ? getAccountById(this.readConfig(), "kimi", accountId) : void 0;
17265
+ const deviceId = account?.tokens?.deviceId;
17266
+ const r2 = await kimiOAuth2.refreshAccessToken(
17267
+ refreshToken,
17268
+ refreshFetch,
17269
+ kimiOAuth2.kimiFingerprintHeaders(deviceId)
17270
+ );
17271
+ return {
17272
+ accessToken: r2.accessToken,
17273
+ refreshToken: r2.refreshToken,
17274
+ expiresAt: new Date(Date.now() + r2.expiresIn * 1e3).toISOString()
17275
+ };
17276
+ }
17277
+ if (provider === "grok") {
17278
+ const tokenEndpoint = await grokOAuth2.resolveGrokTokenEndpoint(refreshFetch);
17279
+ const r2 = await grokOAuth2.refreshGrokAccessToken(refreshToken, tokenEndpoint, refreshFetch);
17280
+ return {
17281
+ accessToken: r2.accessToken,
17282
+ refreshToken: r2.refreshToken,
17283
+ expiresAt: new Date(Date.now() + r2.expiresIn * 1e3).toISOString()
17284
+ };
17285
+ }
17286
+ if (provider === "copilot") {
17287
+ throw new Error("GitHub Copilot tokens cannot be refreshed \u2014 re-authenticate the account");
17288
+ }
15068
17289
  const flow = provider === "claude" ? claudeOAuth2 : provider === "codex" ? codexOAuth2 : geminiOAuth2;
15069
- const r = await flow.refreshAccessToken(refreshToken, this.buildRefreshFetch(provider, accountId));
17290
+ const r = await flow.refreshAccessToken(refreshToken, refreshFetch);
15070
17291
  return {
15071
17292
  accessToken: r.accessToken,
15072
17293
  refreshToken: r.refreshToken,
@@ -15229,42 +17450,86 @@ var JsonSubscriptionCredentialStore = class {
15229
17450
  /** Write the merged config to disk as pretty JSON (mkdir parent if needed).
15230
17451
  * Encrypt-on-write: the token-material fields are encrypted (legacy plaintext
15231
17452
  * `enc:v1:`; already-`enc:`/`$ENV` untouched) before serializing, so any
15232
- * write incl. child 4's future refresh writes lands encrypted. */
17453
+ * write incl. child 4's future refresh writes lands encrypted.
17454
+ * ATOMIC: temp + fsync + rename (`atomicReplaceUtf8`) — a failed or
17455
+ * interrupted write discards only the temp file; the prior `tokens.json`
17456
+ * survives byte-equal (bare `writeFileSync` truncate-writes lost every
17457
+ * account on a mid-write failure, 2026-09-06). */
15233
17458
  persist(config) {
15234
17459
  mkdirSync6(dirname15(this.tokensPath), { recursive: true });
15235
17460
  const encrypted = encryptTokens(config, this.box);
15236
- writeFileSync16(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n", "utf8");
17461
+ this.atomicReplace(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
15237
17462
  }
15238
17463
  /**
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).
17464
+ * Read + parse `tokens.json`, then DECRYPT the token-material fields so every
17465
+ * getter returns plaintext (the subscription bearer path is byte-identical).
17466
+ *
17467
+ * A MISSING file is a legitimate first-boot state → minimal `{ updatedAt: '' }`.
17468
+ * A file that EXISTS but cannot be parsed as a JSON object is CORRUPT →
17469
+ * `quarantineCorrupt` moves it aside (once) before the empty config is
17470
+ * returned, so the unreadable accounts survive for manual recovery.
15242
17471
  *
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.
17472
+ * The DECRYPT runs OUTSIDE any try, so a wrong/missing master key or a
17473
+ * tampered `enc:` envelope FAILS FAST with the box's clear, secret-free
17474
+ * error (secrets spec "/ UX": SHALL fail-fast, SHALL NOT a swallowed
17475
+ * decrypt would report "no tokens" and silently send the WRONG bearer
17476
+ * upstream 401). Mirrors `config.ts loadConfig`, which decrypts outside
17477
+ * its parse try.
15250
17478
  */
15251
17479
  readConfig() {
15252
- if (!existsSync21(this.tokensPath)) return { updatedAt: "" };
17480
+ if (!existsSync22(this.tokensPath)) return { updatedAt: "" };
15253
17481
  let parsed;
15254
17482
  try {
15255
17483
  const raw = JSON.parse(readFileSync18(this.tokensPath, "utf8"));
15256
- parsed = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
17484
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
17485
+ return this.quarantineCorrupt("parsed JSON is not an object");
17486
+ }
17487
+ parsed = raw;
15257
17488
  } catch {
15258
- parsed = null;
17489
+ return this.quarantineCorrupt("unparseable JSON");
15259
17490
  }
15260
- if (!parsed) return { updatedAt: "" };
15261
17491
  const decrypted = decryptTokens(parsed, this.box);
15262
17492
  return migrateLazily(decrypted);
15263
17493
  }
17494
+ /** One-shot latch: a corrupt file is quarantined (or found unmovable) at
17495
+ * most once per process, so the hot read path never re-attempts or re-logs. */
17496
+ corruptQuarantined = false;
17497
+ /**
17498
+ * Quarantine a present-but-corrupt `tokens.json`, then treat it as empty.
17499
+ *
17500
+ * Renames the file to a sibling `tokens.json.corrupt-<stamp>` backup and
17501
+ * logs loudly (the daemon's stderr log; secret-free — reason + paths only).
17502
+ * The daemon KEEPS SERVING (API-key routing is unaffected; subscription
17503
+ * routing reports no credential, same as an absent file) while the corrupt
17504
+ * bytes survive for manual recovery — and, critically, the NEXT persist
17505
+ * (e.g. the user re-logging in) can no longer overwrite the only copy of
17506
+ * the old accounts, which is exactly how the 2026-09-06 incident turned a
17507
+ * recoverable truncated file into permanent account loss.
17508
+ *
17509
+ * Best-effort: if the rename fails (file locked, permissions), the corrupt
17510
+ * file is left in place and every later read still tolerates it as empty;
17511
+ * the latch still trips so the attempt + log happen exactly once.
17512
+ */
17513
+ quarantineCorrupt(reason) {
17514
+ if (!this.corruptQuarantined) {
17515
+ this.corruptQuarantined = true;
17516
+ const backup = `${this.tokensPath}.corrupt-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
17517
+ let moved = false;
17518
+ try {
17519
+ renameSync12(this.tokensPath, backup);
17520
+ moved = true;
17521
+ } catch {
17522
+ }
17523
+ console.error(
17524
+ `[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`)
17525
+ );
17526
+ }
17527
+ return { updatedAt: "" };
17528
+ }
15264
17529
  };
15265
17530
 
15266
17531
  // src/AccountHealthProbeScheduler.ts
15267
- import { fetchUpstream as fetchUpstream4 } from "@omnicross/core/pipeline/upstreamFetch";
17532
+ import { fetchUpstream as fetchUpstream10 } from "@omnicross/core/pipeline/upstreamFetch";
15268
17533
 
15269
17534
  // src/probe/CodexGenerationProbe.ts
15270
17535
  import {
@@ -15406,7 +17671,20 @@ var PROVIDER_PROBE_PLANS = {
15406
17671
  // billable/wrong endpoint). Upgrade to `{ kind:'upstream' }` once verified.
15407
17672
  codex: { kind: "local" },
15408
17673
  gemini: { kind: "local" },
15409
- opencodego: { kind: "local" }
17674
+ opencodego: { kind: "local" },
17675
+ // Kimi's `GET /coding/v1/usages` is a verified FREE authed GET (the allowance
17676
+ // collector uses it), but the probe path also needs the fingerprint headers —
17677
+ // keep the probe local until the collector covers the health surface.
17678
+ kimi: { kind: "local" },
17679
+ // Grok's billing proxy is a verified FREE authed GET (the allowance collector
17680
+ // uses it) but it REJECTS non-OAuth credentials and sits on a separate host
17681
+ // with its own product-gate header — keep the probe local, the collector
17682
+ // owns the health surface.
17683
+ grok: { kind: "local" },
17684
+ // The Copilot quota endpoint (copilot_internal/user) is a verified FREE
17685
+ // authed GET but lives on api.github.com with its own auth dialect and a
17686
+ // monthly-only window — the allowance collector owns the health surface.
17687
+ copilot: { kind: "local" }
15410
17688
  };
15411
17689
  function probePlanFor(providerId) {
15412
17690
  return PROVIDER_PROBE_PLANS[providerId] ?? { kind: "local" };
@@ -15428,7 +17706,7 @@ var AccountHealthProbeScheduler = class {
15428
17706
  this.logger = logger;
15429
17707
  this.config = config;
15430
17708
  this.now = opts.now ?? Date.now;
15431
- this.fetchImpl = opts.fetchImpl ?? fetchUpstream4;
17709
+ this.fetchImpl = opts.fetchImpl ?? fetchUpstream10;
15432
17710
  this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
15433
17711
  this.planFor = opts.planFor ?? probePlanFor;
15434
17712
  }
@@ -15772,13 +18050,13 @@ var AccountHealthSweeper = class {
15772
18050
  };
15773
18051
 
15774
18052
  // 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";
18053
+ import { createReadStream as createReadStream3, createWriteStream as createWriteStream2, existsSync as existsSync25, readdirSync as readdirSync6, rmSync as rmSync4, unlinkSync as unlinkSync13 } from "fs";
15776
18054
  import { join as join20 } from "path";
15777
18055
  import { pipeline } from "stream/promises";
15778
18056
  import { createGzip } from "zlib";
15779
18057
 
15780
18058
  // 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";
18059
+ import { existsSync as existsSync23, readdirSync as readdirSync4, readFileSync as readFileSync19, renameSync as renameSync13, unlinkSync as unlinkSync12, writeFileSync as writeFileSync16 } from "fs";
15782
18060
  import { join as join18 } from "path";
15783
18061
 
15784
18062
  // src/audit/auditBodyStore.ts
@@ -16036,9 +18314,9 @@ function chooseDictionary(anchors) {
16036
18314
  var EMPTY = { shards: 0, anchors: 0, savedBytes: 0 };
16037
18315
  function compactAuditDay(dayPath) {
16038
18316
  const bodiesPath = join18(dayPath, AUDIT_BODIES_DIR);
16039
- if (!existsSync22(bodiesPath)) return EMPTY;
18317
+ if (!existsSync23(bodiesPath)) return EMPTY;
16040
18318
  const dictPath = join18(bodiesPath, AUDIT_DICT_FILE);
16041
- if (existsSync22(dictPath) || existsSync22(`${dictPath}.gz`)) return EMPTY;
18319
+ if (existsSync23(dictPath) || existsSync23(`${dictPath}.gz`)) return EMPTY;
16042
18320
  const shardFiles = plainShards(bodiesPath);
16043
18321
  if (shardFiles.length < 2) return EMPTY;
16044
18322
  const loaded = /* @__PURE__ */ new Map();
@@ -16063,7 +18341,7 @@ function compactAuditDay(dayPath) {
16063
18341
  ts: 0,
16064
18342
  req: { base: null, anchor: "dict", pre: 0, suf: 0, ins: dictionary }
16065
18343
  };
16066
- writeFileSync17(dictPath, JSON.stringify(dictEntry) + "\n", "utf8");
18344
+ writeFileSync16(dictPath, JSON.stringify(dictEntry) + "\n", "utf8");
16067
18345
  const result = { shards: 0, anchors: 0, savedBytes: 0 };
16068
18346
  for (const [file, entries] of loaded) {
16069
18347
  let changed = false;
@@ -16083,11 +18361,11 @@ function compactAuditDay(dayPath) {
16083
18361
  const target = join18(bodiesPath, file);
16084
18362
  const temp = `${target}.compacting`;
16085
18363
  try {
16086
- writeFileSync17(temp, rewritten.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
16087
- renameSync12(temp, target);
18364
+ writeFileSync16(temp, rewritten.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
18365
+ renameSync13(temp, target);
16088
18366
  } catch {
16089
18367
  try {
16090
- if (existsSync22(temp)) unlinkSync12(temp);
18368
+ if (existsSync23(temp)) unlinkSync12(temp);
16091
18369
  } catch {
16092
18370
  }
16093
18371
  continue;
@@ -16106,7 +18384,7 @@ function compactAuditDay(dayPath) {
16106
18384
  }
16107
18385
  function compactAllClosedAuditDays(auditDir, now = Date.now) {
16108
18386
  const run = { days: 0, shards: 0, savedBytes: 0 };
16109
- if (!existsSync22(auditDir)) return run;
18387
+ if (!existsSync23(auditDir)) return run;
16110
18388
  const today = auditDayDirName(now());
16111
18389
  let names;
16112
18390
  try {
@@ -16131,11 +18409,11 @@ function compactAllClosedAuditDays(auditDir, now = Date.now) {
16131
18409
  // src/audit/auditStats.ts
16132
18410
  import {
16133
18411
  createReadStream as createReadStream2,
16134
- existsSync as existsSync23,
18412
+ existsSync as existsSync24,
16135
18413
  readFileSync as readFileSync20,
16136
18414
  readdirSync as readdirSync5,
16137
18415
  statSync as statSync5,
16138
- writeFileSync as writeFileSync18
18416
+ writeFileSync as writeFileSync17
16139
18417
  } from "fs";
16140
18418
  import { basename as basename9, dirname as dirname16, join as join19 } from "path";
16141
18419
  var SIDECAR_VERSION = 1;
@@ -16145,7 +18423,7 @@ function auditStatsFileName(auditFile) {
16145
18423
  return auditFile.replace(/\.jsonl$/, ".stats.json");
16146
18424
  }
16147
18425
  function readPersisted(path2) {
16148
- if (!existsSync23(path2)) return null;
18426
+ if (!existsSync24(path2)) return null;
16149
18427
  try {
16150
18428
  const value = JSON.parse(readFileSync20(path2, "utf8"));
16151
18429
  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 +18455,7 @@ function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfte
16177
18455
  minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
16178
18456
  maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
16179
18457
  };
16180
- writeFileSync18(statsPath, JSON.stringify(next), "utf8");
18458
+ writeFileSync17(statsPath, JSON.stringify(next), "utf8");
16181
18459
  }
16182
18460
  function queryCovers(stats, from, to) {
16183
18461
  return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
@@ -16288,7 +18566,7 @@ function mergePersistedStats(previous, appended) {
16288
18566
  };
16289
18567
  }
16290
18568
  async function readAuditStats(auditDir, query2 = {}) {
16291
- if (!existsSync23(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
18569
+ if (!existsSync24(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
16292
18570
  const from = typeof query2.from === "number" ? query2.from : -Infinity;
16293
18571
  const to = typeof query2.to === "number" ? query2.to : Infinity;
16294
18572
  let sources;
@@ -16301,7 +18579,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16301
18579
  auditPath: join19(auditDir, name),
16302
18580
  statsPath: join19(auditDir, auditStatsFileName(name))
16303
18581
  }
16304
- ).filter((source) => existsSync23(source.auditPath));
18582
+ ).filter((source) => existsSync24(source.auditPath));
16305
18583
  } catch {
16306
18584
  return { requestCount: 0, errorCount: 0, complete: false };
16307
18585
  }
@@ -16327,7 +18605,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16327
18605
  total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
16328
18606
  total.complete = total.complete && scanned.filtered.complete;
16329
18607
  const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
16330
- if (current.complete) writeFileSync18(statsPath, JSON.stringify(current), "utf8");
18608
+ if (current.complete) writeFileSync17(statsPath, JSON.stringify(current), "utf8");
16331
18609
  } catch {
16332
18610
  total.complete = false;
16333
18611
  }
@@ -16336,7 +18614,7 @@ async function readAuditStats(auditDir, query2 = {}) {
16336
18614
  }
16337
18615
 
16338
18616
  // src/audit/AuditPruneSweeper.ts
16339
- var DAY_MS = 24 * 60 * 6e4;
18617
+ var DAY_MS4 = 24 * 60 * 6e4;
16340
18618
  var SWEEP_INTERVAL_MS2 = 60 * 6e4;
16341
18619
  var ARCHIVE_BATCH = 64;
16342
18620
  var AuditPruneSweeper = class {
@@ -16399,8 +18677,8 @@ var AuditPruneSweeper = class {
16399
18677
  if (!this.config.enabled || this.sweeping) return 0;
16400
18678
  this.sweeping = true;
16401
18679
  try {
16402
- if (!existsSync24(this.auditDir)) return 0;
16403
- const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS;
18680
+ if (!existsSync25(this.auditDir)) return 0;
18681
+ const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS4;
16404
18682
  let removed = 0;
16405
18683
  for (const name of readdirSync6(this.auditDir)) {
16406
18684
  const dateMs = auditFileDateMs(name);
@@ -16411,7 +18689,7 @@ var AuditPruneSweeper = class {
16411
18689
  } else {
16412
18690
  unlinkSync13(join20(this.auditDir, name));
16413
18691
  const statsPath = join20(this.auditDir, auditStatsFileName(name));
16414
- if (existsSync24(statsPath)) unlinkSync13(statsPath);
18692
+ if (existsSync25(statsPath)) unlinkSync13(statsPath);
16415
18693
  }
16416
18694
  removed += 1;
16417
18695
  } catch (error) {
@@ -16441,7 +18719,7 @@ var AuditPruneSweeper = class {
16441
18719
  if (!this.config.enabled || this.archiving) return 0;
16442
18720
  this.archiving = true;
16443
18721
  try {
16444
- if (!existsSync24(this.auditDir)) return 0;
18722
+ if (!existsSync25(this.auditDir)) return 0;
16445
18723
  const today = this.todayMidnight();
16446
18724
  let compressed = 0;
16447
18725
  for (const name of readdirSync6(this.auditDir)) {
@@ -16495,7 +18773,7 @@ var AuditPruneSweeper = class {
16495
18773
  const source = join20(bodiesPath, shard);
16496
18774
  const target = `${source}.gz`;
16497
18775
  try {
16498
- if (existsSync24(target)) {
18776
+ if (existsSync25(target)) {
16499
18777
  unlinkSync13(source);
16500
18778
  continue;
16501
18779
  }
@@ -16504,7 +18782,7 @@ var AuditPruneSweeper = class {
16504
18782
  compressed += 1;
16505
18783
  } catch (error) {
16506
18784
  try {
16507
- if (existsSync24(target)) unlinkSync13(target);
18785
+ if (existsSync25(target)) unlinkSync13(target);
16508
18786
  } catch {
16509
18787
  }
16510
18788
  this.logger.warn("[AuditPruneSweeper] failed to archive audit body shard", {
@@ -16657,7 +18935,7 @@ async function closeAll(writers) {
16657
18935
  // src/usage/UsagePruneSweeper.ts
16658
18936
  import { unlink as unlink3 } from "fs/promises";
16659
18937
  import { join as join22 } from "path";
16660
- var DAY_MS2 = 24 * 60 * 6e4;
18938
+ var DAY_MS5 = 24 * 60 * 6e4;
16661
18939
  var SWEEP_INTERVAL_MS3 = 60 * 6e4;
16662
18940
  var DEFAULT_USAGE_RETENTION_DAYS = 90;
16663
18941
  var UsagePruneSweeper = class {
@@ -16714,7 +18992,7 @@ var UsagePruneSweeper = class {
16714
18992
  this.sweeping = true;
16715
18993
  try {
16716
18994
  const retentionDays = this.config.retentionDays ?? DEFAULT_USAGE_RETENTION_DAYS;
16717
- const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS2;
18995
+ const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS5;
16718
18996
  let removed = 0;
16719
18997
  for (const entry of await listUsageDays(this.usageDir)) {
16720
18998
  if (!entry.hasShard) continue;
@@ -16772,7 +19050,7 @@ var UsagePruneSweeper = class {
16772
19050
  };
16773
19051
 
16774
19052
  // src/audit/auditBodyReader.ts
16775
- import { existsSync as existsSync25, readdirSync as readdirSync7, readFileSync as readFileSync21, statSync as statSync7 } from "fs";
19053
+ import { existsSync as existsSync26, readdirSync as readdirSync7, readFileSync as readFileSync21, statSync as statSync7 } from "fs";
16776
19054
  import { join as join23 } from "path";
16777
19055
  import { gunzipSync } from "zlib";
16778
19056
 
@@ -16836,7 +19114,7 @@ function forEachLineFromTail(path2, onLine) {
16836
19114
  function candidateDays(auditDir, ts) {
16837
19115
  if (typeof ts === "number" && Number.isFinite(ts)) {
16838
19116
  const named = auditDayDirName(ts);
16839
- if (existsSync25(join23(auditDir, named))) return [named];
19117
+ if (existsSync26(join23(auditDir, named))) return [named];
16840
19118
  }
16841
19119
  try {
16842
19120
  return readdirSync7(auditDir).filter(isAuditDayDir).sort().reverse();
@@ -16847,9 +19125,9 @@ function candidateDays(auditDir, ts) {
16847
19125
  function readShard(auditDir, day, sessionKey) {
16848
19126
  const base = join23(auditDir, day, AUDIT_BODIES_DIR, auditBodyFileName(sessionKey));
16849
19127
  try {
16850
- if (existsSync25(base)) return readFileSync21(base, "utf8");
19128
+ if (existsSync26(base)) return readFileSync21(base, "utf8");
16851
19129
  const gz = `${base}.gz`;
16852
- if (existsSync25(gz)) return gunzipSync(readFileSync21(gz)).toString("utf8");
19130
+ if (existsSync26(gz)) return gunzipSync(readFileSync21(gz)).toString("utf8");
16853
19131
  } catch {
16854
19132
  return null;
16855
19133
  }
@@ -16882,8 +19160,8 @@ function withDictionary(auditDir, day, entries) {
16882
19160
  const base = join23(auditDir, day, AUDIT_BODIES_DIR, AUDIT_DICT_FILE);
16883
19161
  let raw = null;
16884
19162
  try {
16885
- if (existsSync25(base)) raw = readFileSync21(base, "utf8");
16886
- else if (existsSync25(`${base}.gz`)) raw = gunzipSync(readFileSync21(`${base}.gz`)).toString("utf8");
19163
+ if (existsSync26(base)) raw = readFileSync21(base, "utf8");
19164
+ else if (existsSync26(`${base}.gz`)) raw = gunzipSync(readFileSync21(`${base}.gz`)).toString("utf8");
16887
19165
  } catch {
16888
19166
  return entries;
16889
19167
  }
@@ -16916,7 +19194,7 @@ function reconstructRequest(entries, entry) {
16916
19194
  }
16917
19195
  function readAuditBody(auditDir, query2) {
16918
19196
  if (!isSafeSessionKey(query2.sessionKey) || !query2.id) return {};
16919
- if (!existsSync25(auditDir)) return {};
19197
+ if (!existsSync26(auditDir)) return {};
16920
19198
  for (const day of candidateDays(auditDir, query2.ts)) {
16921
19199
  const raw = readShard(auditDir, day, query2.sessionKey);
16922
19200
  if (raw === null) continue;
@@ -16963,7 +19241,7 @@ function readLegacyInlineBody(auditDir, id) {
16963
19241
  }
16964
19242
 
16965
19243
  // src/audit/auditReader.ts
16966
- import { existsSync as existsSync26, readdirSync as readdirSync8 } from "fs";
19244
+ import { existsSync as existsSync27, readdirSync as readdirSync8 } from "fs";
16967
19245
  import { join as join24 } from "path";
16968
19246
  var DEFAULT_LIMIT = 200;
16969
19247
  var MAX_LIMIT = 2e3;
@@ -16981,7 +19259,7 @@ function daySources(auditDir) {
16981
19259
  if (dateMs === null) continue;
16982
19260
  if (AUDIT_DAY_DIR_RE.test(name)) {
16983
19261
  const path2 = join24(auditDir, name, AUDIT_META_FILE);
16984
- if (existsSync26(path2)) sources.push({ path: path2, dateMs });
19262
+ if (existsSync27(path2)) sources.push({ path: path2, dateMs });
16985
19263
  } else if (AUDIT_FILE_RE.test(name)) {
16986
19264
  sources.push({ path: join24(auditDir, name), dateMs });
16987
19265
  }
@@ -16999,7 +19277,7 @@ function toMetaRecord(record) {
16999
19277
  return { ...meta, hasBody: true };
17000
19278
  }
17001
19279
  function readAuditRecords(auditDir, query2 = {}) {
17002
- if (!existsSync26(auditDir)) return [];
19280
+ if (!existsSync27(auditDir)) return [];
17003
19281
  const from = typeof query2.from === "number" ? query2.from : -Infinity;
17004
19282
  const to = typeof query2.to === "number" ? query2.to : Infinity;
17005
19283
  const limit = Math.min(MAX_LIMIT, Math.max(1, Math.trunc(query2.limit ?? DEFAULT_LIMIT)));
@@ -17027,7 +19305,7 @@ function readAuditRecords(auditDir, query2 = {}) {
17027
19305
  }
17028
19306
 
17029
19307
  // src/audit/AuditWriter.ts
17030
- import { appendFileSync as appendFileSync2, existsSync as existsSync27, mkdirSync as mkdirSync7, statSync as statSync8 } from "fs";
19308
+ import { appendFileSync as appendFileSync2, existsSync as existsSync28, mkdirSync as mkdirSync7, statSync as statSync8 } from "fs";
17031
19309
  import { join as join25 } from "path";
17032
19310
  var AuditWriter = class {
17033
19311
  constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
@@ -17085,7 +19363,7 @@ var AuditWriter = class {
17085
19363
  const { requestBody: _req, responseBody: _res, ...meta } = record;
17086
19364
  const file = join25(dayPath, AUDIT_META_FILE);
17087
19365
  const line = JSON.stringify(meta) + "\n";
17088
- const bytesBefore = existsSync27(file) ? statSync8(file).size : 0;
19366
+ const bytesBefore = existsSync28(file) ? statSync8(file).size : 0;
17089
19367
  appendFileSync2(file, line, "utf8");
17090
19368
  try {
17091
19369
  updateAuditStatsAfterAppend(
@@ -17133,7 +19411,7 @@ var AuditWriter = class {
17133
19411
  import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync8 } from "fs";
17134
19412
  import { createHmac as createHmac5 } from "crypto";
17135
19413
  import { join as join26 } from "path";
17136
- import { fetchUpstream as fetchUpstream5 } from "@omnicross/core/pipeline/upstreamFetch";
19414
+ import { fetchUpstream as fetchUpstream11 } from "@omnicross/core/pipeline/upstreamFetch";
17137
19415
 
17138
19416
  // src/billing/billingFiles.ts
17139
19417
  var BILLING_FILE_RE = /^billing-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
@@ -17156,7 +19434,7 @@ var BillingPublisher = class {
17156
19434
  constructor(billingDir, logger, opts = {}) {
17157
19435
  this.billingDir = billingDir;
17158
19436
  this.logger = logger;
17159
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream5(url, init));
19437
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream11(url, init));
17160
19438
  this.defer = opts.defer ?? ((fn) => setTimeout(fn, 0));
17161
19439
  this.timeoutMs = opts.timeoutMs ?? BILLING_POST_TIMEOUT_MS;
17162
19440
  this.now = opts.now ?? Date.now;
@@ -17269,11 +19547,11 @@ var BillingPublisher = class {
17269
19547
  };
17270
19548
 
17271
19549
  // src/billing/billingReader.ts
17272
- import { existsSync as existsSync28, readdirSync as readdirSync9, readFileSync as readFileSync22 } from "fs";
19550
+ import { existsSync as existsSync29, readdirSync as readdirSync9, readFileSync as readFileSync22 } from "fs";
17273
19551
  import { join as join27 } from "path";
17274
19552
  function readBillingLedger(billingDir) {
17275
19553
  const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
17276
- if (!existsSync28(billingDir)) return view;
19554
+ if (!existsSync29(billingDir)) return view;
17277
19555
  let files;
17278
19556
  try {
17279
19557
  files = readdirSync9(billingDir);
@@ -17406,7 +19684,7 @@ var BillingRetrySweeper = class {
17406
19684
  // src/TokenRefreshScheduler.ts
17407
19685
  var REFRESH_LEAD_MS2 = 5 * 6e4;
17408
19686
  var SWEEP_INTERVAL_MS5 = 6e4;
17409
- var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini"];
19687
+ var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini", "kimi", "grok", "copilot"];
17410
19688
  var TokenRefreshScheduler = class {
17411
19689
  constructor(store, logger, intervalMs = SWEEP_INTERVAL_MS5, leadMs = REFRESH_LEAD_MS2) {
17412
19690
  this.store = store;
@@ -17489,6 +19767,14 @@ var TokenRefreshScheduler = class {
17489
19767
  return this.store.refreshCodexToken();
17490
19768
  case "gemini":
17491
19769
  return this.store.refreshGeminiToken();
19770
+ case "kimi":
19771
+ return this.store.refreshKimiToken();
19772
+ case "grok":
19773
+ return this.store.refreshGrokToken();
19774
+ // ghu_ tokens never near-expire (far-future expiresAt), so the sweep
19775
+ // never reaches this — the branch exists for union totality.
19776
+ case "copilot":
19777
+ return this.store.refreshCopilotToken();
17492
19778
  }
17493
19779
  }
17494
19780
  };
@@ -17567,7 +19853,7 @@ function createRouteLeaseSubscriptionPreflight(credentials) {
17567
19853
 
17568
19854
  // src/webhook/WebhookDispatcher.ts
17569
19855
  import { createHmac as createHmac6 } from "crypto";
17570
- import { fetchUpstream as fetchUpstream6 } from "@omnicross/core/pipeline/upstreamFetch";
19856
+ import { fetchUpstream as fetchUpstream12 } from "@omnicross/core/pipeline/upstreamFetch";
17571
19857
  var WEBHOOK_MAX_ATTEMPTS = 3;
17572
19858
  var WEBHOOK_QUEUE_MAX = 1e3;
17573
19859
  var WEBHOOK_SEND_TIMEOUT_MS = 1e4;
@@ -17587,7 +19873,7 @@ var WebhookDispatcher = class {
17587
19873
  sleep;
17588
19874
  now;
17589
19875
  constructor(opts = {}) {
17590
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream6(url, init));
19876
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream12(url, init));
17591
19877
  this.logger = opts.logger;
17592
19878
  this.maxAttempts = opts.maxAttempts ?? WEBHOOK_MAX_ATTEMPTS;
17593
19879
  this.queueMax = opts.queueMax ?? WEBHOOK_QUEUE_MAX;
@@ -17673,8 +19959,8 @@ var WebhookDispatcher = class {
17673
19959
  signal: AbortSignal.timeout(this.timeoutMs)
17674
19960
  });
17675
19961
  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) };
19962
+ } catch (err8) {
19963
+ return { ok: false, error: err8 instanceof Error ? err8.message : String(err8) };
17678
19964
  }
17679
19965
  }
17680
19966
  /**
@@ -17816,7 +20102,7 @@ function buildDaemon(config, paths) {
17816
20102
  setSecretBox(secretBox3);
17817
20103
  setSecretBox2(secretBox3);
17818
20104
  const decryptedConfig = decryptConfigSecrets(config, secretBox3);
17819
- const accountAllowanceStore = new AccountAllowanceStore3(
20105
+ const accountAllowanceStore = new AccountAllowanceStore8(
17820
20106
  Date.now,
17821
20107
  void 0,
17822
20108
  new JsonAccountAllowancePersistence(defaultAccountAllowancePath(paths.configPath))
@@ -17861,6 +20147,7 @@ function buildDaemon(config, paths) {
17861
20147
  );
17862
20148
  setGeminiCodeAssistResolver(getGeminiCodeAssistProjectResolver());
17863
20149
  const autoDisableStore = new AutoDisableStore();
20150
+ const providerKeyQuotaService = new ProviderKeyQuotaService(secretBox3);
17864
20151
  const apiKeyPool = new ApiKeyPoolService(
17865
20152
  createPoolKeysLoader((id) => llmConfig.getProviderRow(id), autoDisableStore),
17866
20153
  resolveEnvKey,
@@ -17877,7 +20164,7 @@ function buildDaemon(config, paths) {
17877
20164
  const pricingEngine = new PricingEngine(pricingStore, logger, {
17878
20165
  // Catalog egress follows the same global/env proxy policy as every other
17879
20166
  // daemon upstream call; no provider/account override applies here.
17880
- fetchImpl: ((input, init) => fetchUpstream7(String(input), init ?? {}))
20167
+ fetchImpl: ((input, init) => fetchUpstream13(String(input), init ?? {}))
17881
20168
  });
17882
20169
  const pricingRefreshScheduler = new PricingRefreshScheduler(
17883
20170
  pricingEngine,
@@ -18141,6 +20428,11 @@ function buildDaemon(config, paths) {
18141
20428
  // values themselves NEVER leave (masked via `maskProviderApiKey`).
18142
20429
  apiKeyPool,
18143
20430
  autoDisableStore,
20431
+ // BYO provider-key quota (Z.AI coding plan, MiniMax Token Plan, …): a
20432
+ // read-through cached same-key usage probe surfaced on the keys view. The
20433
+ // key plaintext is resolved + decrypted inside the service and never
20434
+ // crosses back out.
20435
+ providerKeyQuota: providerKeyQuotaService,
18144
20436
  // Interactive OAuth login over admin HTTP (app-parity child 4, design
18145
20437
  // D1/D2-a). The in-memory pending-session store (NEVER serialized), the
18146
20438
  // injected token-exchange fetch (global `fetch` here; mocked in tests), and a
@@ -18157,7 +20449,7 @@ function buildDaemon(config, paths) {
18157
20449
  // — `server.proxy.byProvider[...]` was silently skipped — and the call was
18158
20450
  // excluded from the upstream trace, so a failing login left no evidence.
18159
20451
  // `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 }),
20452
+ oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => fetchUpstream13(url, init, { providerId, redactBodies: true }),
18161
20453
  subscriptionAccountAppender: credentialStore,
18162
20454
  // Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
18163
20455
  // + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
@@ -18165,6 +20457,13 @@ function buildDaemon(config, paths) {
18165
20457
  // can inject a mock so no real port is bound.
18166
20458
  codexSessions: new CodexOAuthSessionStore(),
18167
20459
  codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
20460
+ // Kimi interactive OAuth — the async DEVICE-CODE flow store (no port, no
20461
+ // paste; the app shows the verification URL + user code and polls the
20462
+ // token-free status). Token captured + persisted daemon-side.
20463
+ kimiSessions: new CodexOAuthSessionStore(),
20464
+ // Grok interactive OAuth — the same async DEVICE-CODE shape as kimi.
20465
+ grokSessions: new CodexOAuthSessionStore(),
20466
+ copilotSessions: new CodexOAuthSessionStore(),
18168
20467
  // Migration pack (app-parity child 6, design D2/D3) — the concrete credential
18169
20468
  // store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
18170
20469
  // the multi-account append (`appendProviderAccount`, import re-encrypts at-
@@ -18223,7 +20522,7 @@ function buildDaemon(config, paths) {
18223
20522
  });
18224
20523
  const webhookDispatcher = new WebhookDispatcher({
18225
20524
  logger,
18226
- fetchImpl: (url, init) => fetchUpstream7(url, init)
20525
+ fetchImpl: (url, init) => fetchUpstream13(url, init)
18227
20526
  });
18228
20527
  setWebhookRuntime(webhookDispatcher, getSharedAccountHealth4());
18229
20528
  const auditWriter = new AuditWriter(auditDir, logger);
@@ -18320,7 +20619,7 @@ function resetDaemonSingletonsForTests() {
18320
20619
  }
18321
20620
  function isTokensStoreReadable(tokensPath) {
18322
20621
  try {
18323
- if (!existsSync29(tokensPath)) return true;
20622
+ if (!existsSync30(tokensPath)) return true;
18324
20623
  accessSync(tokensPath, fsConstants.R_OK);
18325
20624
  return true;
18326
20625
  } catch {