@bitkyc08/opencodex 2.10.2 → 2.11.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.
Files changed (121) hide show
  1. package/README.md +31 -0
  2. package/bin/ocx.mjs +10 -0
  3. package/gui/dist/assets/index-Bk-PN-70.css +1 -0
  4. package/gui/dist/assets/index-BynIEIV-.js +70 -0
  5. package/gui/dist/index.html +2 -2
  6. package/package.json +4 -2
  7. package/src/adapters/cursor/effort-map.ts +11 -0
  8. package/src/adapters/cursor/live-transport.ts +11 -0
  9. package/src/adapters/cursor/native-exec-fs.ts +9 -6
  10. package/src/adapters/cursor/native-exec.ts +4 -2
  11. package/src/adapters/cursor/protobuf-events.ts +176 -4
  12. package/src/adapters/cursor/request-builder.ts +15 -4
  13. package/src/adapters/cursor/tool-definitions.ts +118 -2
  14. package/src/adapters/google.ts +15 -5
  15. package/src/adapters/openai-chat.ts +24 -2
  16. package/src/adapters/openai-responses.ts +2 -1
  17. package/src/bridge.ts +9 -5
  18. package/src/chat/outbound.ts +4 -3
  19. package/src/claude/desktop-3p.ts +222 -2
  20. package/src/claude/outbound.ts +15 -6
  21. package/src/cli/account-api.ts +4 -0
  22. package/src/cli/account-extended.ts +112 -0
  23. package/src/cli/account.ts +23 -6
  24. package/src/cli/claude-desktop.ts +26 -3
  25. package/src/cli/config-command.ts +9 -0
  26. package/src/cli/help.ts +18 -2
  27. package/src/cli/index.ts +277 -55
  28. package/src/cli/models.ts +5 -1
  29. package/src/cli/provider.ts +8 -2
  30. package/src/cli/ready.ts +301 -0
  31. package/src/cli/system-restart-client.ts +146 -0
  32. package/src/cli/tray-proxy.ts +153 -6
  33. package/src/clients/config-export.ts +12 -19
  34. package/src/codex/account-lifecycle.ts +3 -0
  35. package/src/codex/account-namespaces.ts +49 -3
  36. package/src/codex/account-priority.ts +83 -0
  37. package/src/codex/auth-api.ts +83 -0
  38. package/src/codex/auth-context.ts +5 -2
  39. package/src/codex/catalog/provider-fetch.ts +11 -0
  40. package/src/codex/catalog/sync.ts +23 -1
  41. package/src/codex/codex-write-lock.ts +16 -4
  42. package/src/codex/desired-state.ts +37 -4
  43. package/src/codex/history-job.ts +15 -5
  44. package/src/codex/history-provider.ts +31 -14
  45. package/src/codex/history-worker.ts +28 -4
  46. package/src/codex/inject-coordination.ts +13 -1
  47. package/src/codex/inject.ts +360 -66
  48. package/src/codex/internal/history-writer.ts +1 -1
  49. package/src/codex/native-main-lock-file.ts +5 -1
  50. package/src/codex/native-main-owner.ts +17 -3
  51. package/src/codex/native-profile-manager.ts +19 -0
  52. package/src/codex/native-profile-startup.ts +8 -0
  53. package/src/codex/native-residue.ts +140 -27
  54. package/src/codex/pool-rotation.ts +74 -4
  55. package/src/codex/refresh.ts +7 -0
  56. package/src/codex/routing.ts +177 -36
  57. package/src/codex/subagent-model-fallback.ts +34 -4
  58. package/src/codex/sync.ts +61 -0
  59. package/src/codex/upstream-host-health.ts +329 -31
  60. package/src/combos/request.ts +2 -0
  61. package/src/config.ts +221 -2
  62. package/src/images/loop.ts +1 -1
  63. package/src/integrations/native/ownership-preflight.ts +39 -2
  64. package/src/lib/bun-stream-caps.ts +3 -3
  65. package/src/lib/sse-decoder.ts +41 -0
  66. package/src/lib/system-restart-contract.ts +73 -0
  67. package/src/lib/windows-secret-acl.ts +141 -39
  68. package/src/lib/windows-user-principal.ts +283 -0
  69. package/src/lib/winsw.ts +18 -2
  70. package/src/oauth/key-providers.ts +12 -0
  71. package/src/providers/derive.ts +54 -2
  72. package/src/providers/free-directory.ts +6 -5
  73. package/src/providers/model-discovery.ts +9 -3
  74. package/src/providers/quota.ts +592 -0
  75. package/src/providers/registry.ts +316 -13
  76. package/src/responses/parser.ts +26 -10
  77. package/src/responses/reasoning-replay-cache.ts +1 -0
  78. package/src/routing/profile-namespace.ts +15 -0
  79. package/src/routing/profile.ts +2 -1
  80. package/src/server/auth-cors.ts +44 -13
  81. package/src/server/chat-completions.ts +0 -4
  82. package/src/server/claude-messages.ts +73 -15
  83. package/src/server/github-copilot-responses-repair.ts +338 -0
  84. package/src/server/index.ts +328 -111
  85. package/src/server/lifecycle.ts +36 -0
  86. package/src/server/management/agent-settings-routes.ts +147 -56
  87. package/src/server/management/config-routes.ts +7 -2
  88. package/src/server/management/context.ts +4 -0
  89. package/src/server/management/native-integration-routes.ts +199 -20
  90. package/src/server/management/provider-routes.ts +41 -0
  91. package/src/server/management/routing-profile-routes.ts +234 -5
  92. package/src/server/management/system-restart.ts +12 -10
  93. package/src/server/management/system-routes.ts +20 -0
  94. package/src/server/management-auth.ts +51 -3
  95. package/src/server/ports.ts +41 -1
  96. package/src/server/proxy-liveness.ts +129 -4
  97. package/src/server/readiness.ts +99 -0
  98. package/src/server/relay.ts +113 -97
  99. package/src/server/request-log.ts +10 -4
  100. package/src/server/responses/compact.ts +107 -12
  101. package/src/server/responses/core.ts +220 -39
  102. package/src/server/responses-item-id-repair.ts +22 -3
  103. package/src/server/responses-model-rewrite.ts +29 -0
  104. package/src/server/sse-frame-buffer.ts +292 -0
  105. package/src/server/sse-payload-rewrite.ts +25 -14
  106. package/src/server/ws-bridge.ts +27 -22
  107. package/src/service-manager-probe.ts +520 -10
  108. package/src/service.ts +134 -2
  109. package/src/storage/worker-lifecycle.ts +14 -14
  110. package/src/tray/windows-tray.ps1 +74 -9
  111. package/src/types.ts +68 -2
  112. package/src/update/index.ts +12 -0
  113. package/src/update/job.ts +392 -18
  114. package/src/update/npm-cache-preflight.d.mts +47 -0
  115. package/src/update/npm-cache-preflight.mjs +201 -0
  116. package/src/usage/log.ts +1 -1
  117. package/src/vision/index.ts +77 -2
  118. package/src/web-search/loop.ts +1 -1
  119. package/src/web-search/parse.ts +4 -1
  120. package/gui/dist/assets/index-BKVqyYqT.js +0 -70
  121. package/gui/dist/assets/index-Ca_3269W.css +0 -1
@@ -34,6 +34,16 @@ const REQUEST_TIMEOUT_MS = 8_000;
34
34
  const KIMI_CODE_BASE_URL = "https://api.kimi.com/coding/v1";
35
35
  const KIMI_CODE_USAGE_URL = `${KIMI_CODE_BASE_URL}/usages`;
36
36
  const A6API_BASE_URL = "https://api.a6api.com";
37
+ const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
38
+ const DEEPSEEK_BASE_URL = "https://api.deepseek.com";
39
+ const CLINE_BASE_URL = "https://api.cline.bot";
40
+ const ZAI_BASE_URL = "https://api.z.ai";
41
+ const MINIMAX_REMAINS_URL = "https://www.minimax.io/v1/token_plan/remains";
42
+ const MOONSHOT_BASE_URL = "https://api.moonshot.ai/v1";
43
+ const VENICE_BASE_URL = "https://api.venice.ai/api/v1";
44
+ const SYNTHETIC_BASE_URL = "https://api.synthetic.new/v2";
45
+ const DEEPINFRA_BASE_URL = "https://api.deepinfra.com";
46
+ const NEURALWATT_BASE_URL = "https://api.neuralwatt.com/v1";
37
47
  /** Keep a failed probe's previous row at most this long before dropping it. */
38
48
  const LAST_GOOD_MAX_AGE_MS = CODEX_CAPACITY_MAX_QUOTA_AGE_MS;
39
49
  const nativeMainReportGenerations = new WeakMap<ProviderQuotaReport, number>();
@@ -54,6 +64,15 @@ export interface ProviderQuotaWindow {
54
64
  resetAt?: number;
55
65
  }
56
66
 
67
+ export interface ProviderQuotaCreditsUsd {
68
+ used: number;
69
+ limit: number;
70
+ remaining: number;
71
+ percent: number;
72
+ expiresAt?: number;
73
+ unlimited?: boolean;
74
+ }
75
+
57
76
  export interface ProviderQuota {
58
77
  fiveHourPercent?: number;
59
78
  fiveHourResetAt?: number;
@@ -62,6 +81,7 @@ export interface ProviderQuota {
62
81
  monthlyPercent?: number;
63
82
  monthlyResetAt?: number;
64
83
  customWindows?: ProviderQuotaWindow[];
84
+ creditsUsd?: ProviderQuotaCreditsUsd;
65
85
  updatedAt: number;
66
86
  }
67
87
 
@@ -193,6 +213,8 @@ function hasQuotaRows(quota: ProviderQuota | null | undefined): quota is Provide
193
213
  return typeof quota.fiveHourPercent === "number"
194
214
  || typeof quota.weeklyPercent === "number"
195
215
  || typeof quota.monthlyPercent === "number"
216
+ || quota.creditsUsd?.unlimited === true
217
+ || typeof quota.creditsUsd?.percent === "number"
196
218
  || !!quota.customWindows?.some(window => typeof window.percent === "number");
197
219
  }
198
220
 
@@ -243,6 +265,54 @@ function isCanonicalA6apiBaseUrl(baseUrl: string): boolean {
243
265
  return normalized === A6API_BASE_URL || normalized === `${A6API_BASE_URL}/v1`;
244
266
  }
245
267
 
268
+ function isCanonicalOpenRouterBaseUrl(baseUrl: string): boolean {
269
+ const normalized = normalizedBaseUrl(baseUrl);
270
+ return normalized === OPENROUTER_BASE_URL;
271
+ }
272
+
273
+ function isCanonicalDeepSeekBaseUrl(baseUrl: string): boolean {
274
+ const normalized = normalizedBaseUrl(baseUrl);
275
+ return normalized === DEEPSEEK_BASE_URL || normalized === `${DEEPSEEK_BASE_URL}/v1`;
276
+ }
277
+
278
+ function isCanonicalClineBaseUrl(baseUrl: string): boolean {
279
+ const normalized = normalizedBaseUrl(baseUrl);
280
+ return normalized === CLINE_BASE_URL || normalized === `${CLINE_BASE_URL}/api/v1`;
281
+ }
282
+
283
+ function isCanonicalZaiBaseUrl(baseUrl: string): boolean {
284
+ const normalized = normalizedBaseUrl(baseUrl);
285
+ return normalized === ZAI_BASE_URL || normalized === `${ZAI_BASE_URL}/api/coding/paas/v4`;
286
+ }
287
+
288
+ function isCanonicalMinimaxBaseUrl(baseUrl: string): boolean {
289
+ const normalized = normalizedBaseUrl(baseUrl);
290
+ return normalized === "https://api.minimax.io/v1" || normalized === "https://api.minimaxi.com/v1";
291
+ }
292
+
293
+ function isCanonicalMoonshotBaseUrl(baseUrl: string): boolean {
294
+ const normalized = normalizedBaseUrl(baseUrl);
295
+ return normalized === MOONSHOT_BASE_URL || normalized === "https://api.moonshot.cn/v1";
296
+ }
297
+
298
+ function isCanonicalVeniceBaseUrl(baseUrl: string): boolean {
299
+ return normalizedBaseUrl(baseUrl) === VENICE_BASE_URL;
300
+ }
301
+
302
+ function isCanonicalSyntheticBaseUrl(baseUrl: string): boolean {
303
+ const normalized = normalizedBaseUrl(baseUrl);
304
+ return normalized === SYNTHETIC_BASE_URL || normalized === "https://api.synthetic.new/openai/v1";
305
+ }
306
+
307
+ function isCanonicalDeepInfraBaseUrl(baseUrl: string): boolean {
308
+ const normalized = normalizedBaseUrl(baseUrl);
309
+ return normalized === DEEPINFRA_BASE_URL || normalized === `${DEEPINFRA_BASE_URL}/v1/openai`;
310
+ }
311
+
312
+ function isCanonicalNeuralwattBaseUrl(baseUrl: string): boolean {
313
+ return normalizedBaseUrl(baseUrl) === NEURALWATT_BASE_URL;
314
+ }
315
+
246
316
  function a6apiPayload(value: unknown): Record<string, unknown> | null {
247
317
  const body = asRecord(value);
248
318
  return asRecord(body?.data) ?? body;
@@ -282,6 +352,27 @@ async function fetchA6apiQuota(provider: string, config: OcxProviderConfig): Pro
282
352
  }
283
353
  const subscription = a6apiPayload(await subscriptionResponse.json().catch(() => null));
284
354
  const token = a6apiPayload(await tokenResponse.json().catch(() => null));
355
+ const unlimited = token?.unlimited_quota === true
356
+ || token?.unlimited_quota === 1
357
+ || token?.unlimited_quota === "true";
358
+ const normalizedExpiry = normalizeResetAt(token?.expires_at);
359
+ const expiry = normalizedExpiry && normalizedExpiry > 0
360
+ ? { expiresAt: normalizedExpiry }
361
+ : {};
362
+ if (unlimited) {
363
+ return report(provider, "a6api:billing", {
364
+ creditsUsd: {
365
+ used: 0,
366
+ limit: 0,
367
+ remaining: 0,
368
+ percent: 0,
369
+ unlimited: true,
370
+ ...expiry,
371
+ },
372
+ customWindows: [{ label: "Unlimited API credits", percent: 0 }],
373
+ updatedAt: Date.now(),
374
+ });
375
+ }
285
376
  const limitUsd = firstFinite(subscription, ["hard_limit_usd"]);
286
377
  const grantedUnits = firstFinite(token, ["total_granted"]);
287
378
  const usedUnits = firstFinite(token, ["total_used"]);
@@ -304,11 +395,482 @@ async function fetchA6apiQuota(provider: string, config: OcxProviderConfig): Pro
304
395
  if (percent === undefined) return TERMINAL_QUOTA_FAILURE;
305
396
  const label = `API credits ($${remainingUsd.toFixed(2)} of $${limitUsd.toFixed(2)} remaining)`;
306
397
  return report(provider, "a6api:billing", {
398
+ creditsUsd: {
399
+ used: usedUsd,
400
+ limit: limitUsd,
401
+ remaining: remainingUsd,
402
+ percent,
403
+ ...expiry,
404
+ },
405
+ customWindows: [{ label, percent }],
406
+ updatedAt: Date.now(),
407
+ });
408
+ }
409
+
410
+ /**
411
+ * OpenRouter `GET /api/v1/key` — the key's own credit balance and optional
412
+ * per-key spending cap. `limit` is the configured cap (absent = uncapped);
413
+ * `usage` is lifetime spend; `limit_remaining` is what is left of the cap.
414
+ * When no cap is set there is no hard limit to meter against, so no bar is
415
+ * produced — the provider falls back to its documented reference.
416
+ */
417
+ async function fetchOpenRouterQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
418
+ // Never send a configured API key to a lookalike host or through a redirect.
419
+ if (!isCanonicalOpenRouterBaseUrl(config.baseUrl)) return null;
420
+ const apiKey = resolveEnvValue(config.apiKey)?.trim();
421
+ if (!apiKey) return null;
422
+ const response = await fetch(`${OPENROUTER_BASE_URL}/key`, {
423
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
424
+ redirect: "error",
425
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
426
+ });
427
+ if (!response.ok) {
428
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
429
+ ? TERMINAL_QUOTA_FAILURE
430
+ : null;
431
+ }
432
+ const body = asRecord(await response.json().catch(() => null));
433
+ const data = asRecord(body?.data) ?? body;
434
+ if (!data) return null;
435
+ const limit = toFiniteNumber(data.limit);
436
+ const limitRemaining = toFiniteNumber(data.limit_remaining);
437
+ const usage = toFiniteNumber(data.usage);
438
+ // A successful no-cap response is a DELIBERATE change, not a transient
439
+ // failure: the old capped row must be dropped, not preserved as last-good.
440
+ if (limit === undefined || limit <= 0) return TERMINAL_QUOTA_FAILURE;
441
+ // Prefer the authoritative remaining-cap value when present: `usage` is
442
+ // lifetime accumulated spend and overstates a reset or re-capped key.
443
+ const used = limitRemaining !== undefined
444
+ ? Math.max(0, limit - limitRemaining)
445
+ : usage !== undefined && usage >= 0 ? usage : undefined;
446
+ if (used === undefined) return null;
447
+ const percent = normalizePercent((used / limit) * 100);
448
+ if (percent === undefined) return null;
449
+ const remaining = Math.max(0, limit - used);
450
+ const label = `API credits ($${remaining.toFixed(2)} of $${limit.toFixed(2)} remaining)`;
451
+ return report(provider, "openrouter:key-info", {
452
+ customWindows: [{ label, percent }],
453
+ updatedAt: Date.now(),
454
+ });
455
+ }
456
+
457
+ /**
458
+ * DeepSeek `GET /user/balance` — the account's granted + topped-up credit
459
+ * balance. The payload places `total_balance` / `granted_balance` inside
460
+ * entries of `balance_infos` (one row per currency); the row for the account's
461
+ * currency is selected by preference. `granted_balance` is a CURRENT balance
462
+ * component, not the original grant ceiling, so no consumed percentage is
463
+ * fabricated — the balance is reported as a balance-only window.
464
+ */
465
+ async function fetchDeepSeekQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
466
+ if (!isCanonicalDeepSeekBaseUrl(config.baseUrl)) return null;
467
+ const apiKey = resolveEnvValue(config.apiKey)?.trim();
468
+ if (!apiKey) return null;
469
+ const response = await fetch(`${DEEPSEEK_BASE_URL}/user/balance`, {
470
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
471
+ redirect: "error",
472
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
473
+ });
474
+ if (!response.ok) {
475
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
476
+ ? TERMINAL_QUOTA_FAILURE
477
+ : null;
478
+ }
479
+ const body = asRecord(await response.json().catch(() => null));
480
+ // The payload nests balances under `balance_infos` rows keyed by currency;
481
+ // prefer a USD row, then CNY, then the first row that parses.
482
+ const infos = Array.isArray(body?.balance_infos) ? body.balance_infos as unknown[] : null;
483
+ const rows = infos
484
+ ? infos.map((raw): Record<string, unknown> | null => asRecord(raw)).filter((r): r is Record<string, unknown> => r !== null)
485
+ : [];
486
+ const pick = (currency: string): Record<string, unknown> | null =>
487
+ rows.find(row => String(row.currency ?? "").toUpperCase() === currency) ?? null;
488
+ const preferred = pick("USD") ?? pick("CNY") ?? rows[0] ?? null;
489
+ if (!preferred) return null;
490
+ const totalBalance = toFiniteNumber(preferred.total_balance);
491
+ const grantedBalance = toFiniteNumber(preferred.granted_balance);
492
+ const toppedUp = toFiniteNumber(preferred.topped_up_balance);
493
+ const balance = totalBalance ?? grantedBalance ?? toppedUp;
494
+ if (balance === undefined || balance < 0) return null;
495
+ const label = grantedBalance !== undefined && grantedBalance > 0
496
+ ? `API balance ($${balance.toFixed(2)} total, $${grantedBalance.toFixed(2)} granted)`
497
+ : `API balance ($${balance.toFixed(2)})`;
498
+ return report(provider, "deepseek:balance", {
499
+ customWindows: [{ label, percent: 0 }],
500
+ updatedAt: Date.now(),
501
+ });
502
+ }
503
+
504
+ /**
505
+ * ClinePass `GET /api/v1/users/me/plan/usage-limits` — the subscription's
506
+ * rolling five-hour, weekly, and monthly utilization, matching the existing
507
+ * ProviderQuota windows directly. The endpoint 404s (or returns a null plan)
508
+ * for accounts without an active ClinePass, which is a no-report, not an error.
509
+ */
510
+ async function fetchClineQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
511
+ if (!isCanonicalClineBaseUrl(config.baseUrl)) return null;
512
+ const apiKey = resolveEnvValue(config.apiKey)?.trim();
513
+ if (!apiKey) return null;
514
+ const response = await fetch(`${CLINE_BASE_URL}/api/v1/users/me/plan/usage-limits`, {
515
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
516
+ redirect: "error",
517
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
518
+ });
519
+ if (!response.ok) {
520
+ // 404 = no active plan; a plain "no plan" is a no-report, everything else
521
+ // 4xx (except 408/429) is a credential/contract problem.
522
+ if (response.status === 404) return null;
523
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
524
+ ? TERMINAL_QUOTA_FAILURE
525
+ : null;
526
+ }
527
+ const body = asRecord(await response.json().catch(() => null));
528
+ const data = asRecord(body?.data) ?? body;
529
+ const limits = Array.isArray(data?.limits) ? data.limits : null;
530
+ if (!limits) return null;
531
+ const quota: ProviderQuota = { updatedAt: Date.now() };
532
+ let windows = 0;
533
+ for (const raw of limits) {
534
+ const row = asRecord(raw);
535
+ if (!row) continue;
536
+ const percent = normalizePercent(row.percentUsed);
537
+ if (percent === undefined) continue;
538
+ const resetAt = normalizeResetAt(row.resetsAt);
539
+ if (row.type === "five_hour") {
540
+ quota.fiveHourPercent = percent;
541
+ if (resetAt !== undefined) quota.fiveHourResetAt = resetAt;
542
+ windows += 1;
543
+ } else if (row.type === "weekly") {
544
+ quota.weeklyPercent = percent;
545
+ if (resetAt !== undefined) quota.weeklyResetAt = resetAt;
546
+ windows += 1;
547
+ } else if (row.type === "monthly") {
548
+ quota.monthlyPercent = percent;
549
+ if (resetAt !== undefined) quota.monthlyResetAt = resetAt;
550
+ windows += 1;
551
+ }
552
+ }
553
+ return windows > 0 ? report(provider, "cline:plan-usage-limits", quota) : null;
554
+ }
555
+
556
+ /**
557
+ * Z.AI GLM Coding Plan `GET /api/monitor/usage/quota/limit` — the coding-plan
558
+ * subscription's 5-hour token cycle, weekly quota, and monthly MCP usage.
559
+ * Authenticates with the API key as a Bearer token per Z.AI's API reference.
560
+ */
561
+ async function fetchZaiQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
562
+ if (!isCanonicalZaiBaseUrl(config.baseUrl)) return null;
563
+ const apiKey = resolveEnvValue(config.apiKey)?.trim();
564
+ if (!apiKey) return null;
565
+ const response = await fetch(`${ZAI_BASE_URL}/api/monitor/usage/quota/limit`, {
566
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
567
+ redirect: "error",
568
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
569
+ });
570
+ if (!response.ok) {
571
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
572
+ ? TERMINAL_QUOTA_FAILURE
573
+ : null;
574
+ }
575
+ const body = asRecord(await response.json().catch(() => null));
576
+ if (!body || body.success === false) return null;
577
+ const data = asRecord(body.data) ?? body;
578
+ // The plugin renders a 5h token window, a weekly window, and a monthly MCP
579
+ // window. Look for percent fields with window identifiers.
580
+ const quota: ProviderQuota = { updatedAt: Date.now() };
581
+ let windows = 0;
582
+ const percentAt = (key: string): number | undefined => {
583
+ const value = normalizePercent(data?.[key]);
584
+ if (value !== undefined) return value;
585
+ const nested = asRecord(data?.quota);
586
+ return nested ? normalizePercent(nested[key]) : undefined;
587
+ };
588
+ const fiveHour = percentAt("fiveHourPercent") ?? percentAt("fiveHourUsage") ?? percentAt("fiveHourUsed");
589
+ const weekly = percentAt("weeklyPercent") ?? percentAt("weeklyUsage") ?? percentAt("weeklyUsed");
590
+ const monthly = percentAt("monthlyPercent") ?? percentAt("mcpPercent") ?? percentAt("monthlyMCPUsage");
591
+ if (fiveHour !== undefined) {
592
+ quota.fiveHourPercent = fiveHour;
593
+ windows += 1;
594
+ }
595
+ if (weekly !== undefined) {
596
+ quota.weeklyPercent = weekly;
597
+ windows += 1;
598
+ }
599
+ if (monthly !== undefined) {
600
+ quota.monthlyPercent = monthly;
601
+ windows += 1;
602
+ }
603
+ return windows > 0 ? report(provider, "zai:quota-limit", quota) : null;
604
+ }
605
+
606
+ /**
607
+ * MiniMax Token Plan `GET /v1/token_plan/remains` — the subscription's
608
+ * remaining quota as a countdown-time value (ms). The endpoint does not expose
609
+ * the plan's total duration, so no percentage is fabricated from a presumed
610
+ * window: the remaining time is reported as a duration-only window. When the
611
+ * API supplies a total (`total_time` / `plan_duration_ms`), a consumed share
612
+ * is derived from it. Region selects the host: `minimax` → www.minimax.io,
613
+ * `minimax-cn` → api.minimaxi.com.
614
+ */
615
+ async function fetchMinimaxQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
616
+ if (!isCanonicalMinimaxBaseUrl(config.baseUrl)) return null;
617
+ const apiKey = resolveEnvValue(config.apiKey)?.trim();
618
+ if (!apiKey) return null;
619
+ const cnHost = normalizedBaseUrl(config.baseUrl)?.startsWith("https://api.minimaxi.com");
620
+ const remainsUrl = cnHost ? "https://api.minimaxi.com/v1/token_plan/remains" : MINIMAX_REMAINS_URL;
621
+ const response = await fetch(remainsUrl, {
622
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
623
+ redirect: "error",
624
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
625
+ });
626
+ if (!response.ok) {
627
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
628
+ ? TERMINAL_QUOTA_FAILURE
629
+ : null;
630
+ }
631
+ const body = asRecord(await response.json().catch(() => null));
632
+ if (!body || body.success === false) return null;
633
+ const data = asRecord(body.data) ?? body;
634
+ const remainsMs = toFiniteNumber(data.remains_time ?? data.remainsTime);
635
+ if (remainsMs === undefined || remainsMs < 0) return null;
636
+ const hours = Math.floor(remainsMs / 3_600_000);
637
+ const label = `Token Plan remaining (${hours}h)`;
638
+ // Only derive a consumed share when the API actually reports the plan total;
639
+ // a presumed window (e.g. 30 days) would fabricate utilization. A valid
640
+ // response that omits the total after a prior refresh had it is a DELIBERATE
641
+ // contract change — the old row must be dropped (terminal), not preserved as
642
+ // a transient last-good.
643
+ const totalMs = toFiniteNumber(data.total_time ?? data.plan_duration_ms ?? data.total_duration_ms);
644
+ if (totalMs === undefined || totalMs <= 0) return TERMINAL_QUOTA_FAILURE;
645
+ const consumed = Math.max(0, totalMs - remainsMs);
646
+ const percent = normalizePercent((consumed / totalMs) * 100);
647
+ if (percent === undefined) return null;
648
+ return report(provider, "minimax:token-plan-remains", {
307
649
  customWindows: [{ label, percent }],
308
650
  updatedAt: Date.now(),
309
651
  });
310
652
  }
311
653
 
654
+ /**
655
+ * Moonshot/Kimi `GET /v1/users/me/balance` — the account's available balance
656
+ * (voucher + cash). Renders a single balance window against the sum of
657
+ * voucher + cash when positive (there is no per-window rate limit to meter).
658
+ */
659
+ async function fetchMoonshotQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
660
+ if (!isCanonicalMoonshotBaseUrl(config.baseUrl)) return null;
661
+ const apiKey = resolveEnvValue(config.apiKey)?.trim();
662
+ if (!apiKey) return null;
663
+ const host = normalizedBaseUrl(config.baseUrl)?.startsWith("https://api.moonshot.cn") ? "https://api.moonshot.cn/v1" : MOONSHOT_BASE_URL;
664
+ const response = await fetch(`${host}/users/me/balance`, {
665
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
666
+ redirect: "error",
667
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
668
+ });
669
+ if (!response.ok) {
670
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
671
+ ? TERMINAL_QUOTA_FAILURE
672
+ : null;
673
+ }
674
+ const body = asRecord(await response.json().catch(() => null));
675
+ const data = asRecord(body?.data) ?? body;
676
+ if (!data) return null;
677
+ const available = toFiniteNumber(data.available_balance);
678
+ const voucher = toFiniteNumber(data.voucher_balance);
679
+ const cash = toFiniteNumber(data.cash_balance);
680
+ if (available === undefined || available < 0) return null;
681
+ // Moonshot exposes no per-window quota ceiling, only a balance — report it
682
+ // as a balance-only window (percent 0) rather than a fabricated utilization.
683
+ const label = voucher !== undefined && cash !== undefined
684
+ ? `Balance ($${available.toFixed(2)} available, $${voucher.toFixed(2)} voucher)`
685
+ : `Balance ($${available.toFixed(2)} available)`;
686
+ return report(provider, "moonshot:balance", {
687
+ customWindows: [{ label, percent: 0 }],
688
+ updatedAt: Date.now(),
689
+ });
690
+ }
691
+
692
+ /**
693
+ * Venice `GET /api/v1/billing/balance` — DIEM (native credits) or USD balance.
694
+ * Shows the remaining balance; epoch allocation progress when present.
695
+ */
696
+ async function fetchVeniceQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
697
+ if (!isCanonicalVeniceBaseUrl(config.baseUrl)) return null;
698
+ const apiKey = resolveEnvValue(config.apiKey)?.trim();
699
+ if (!apiKey) return null;
700
+ const response = await fetch(`${VENICE_BASE_URL}/billing/balance`, {
701
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
702
+ redirect: "error",
703
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
704
+ });
705
+ if (!response.ok) {
706
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
707
+ ? TERMINAL_QUOTA_FAILURE
708
+ : null;
709
+ }
710
+ const body = asRecord(await response.json().catch(() => null));
711
+ const data = asRecord(body?.data) ?? body;
712
+ if (!data) return null;
713
+ const diemBalance = toFiniteNumber(data.balance);
714
+ const usdBalance = toFiniteNumber(data.balance_usd);
715
+ const epochUsed = toFiniteNumber(data.diem_epoch_used);
716
+ const epochAllocated = toFiniteNumber(data.diem_epoch_allocated);
717
+ if (diemBalance === undefined && usdBalance === undefined) return null;
718
+ const label = diemBalance !== undefined
719
+ ? `DIEM balance (${Math.round(diemBalance)})`
720
+ : `USD balance ($${usdBalance?.toFixed(2) ?? "?"})`;
721
+ if (epochAllocated !== undefined && epochAllocated > 0 && epochUsed !== undefined) {
722
+ const percent = normalizePercent((epochUsed / epochAllocated) * 100);
723
+ if (percent === undefined) return null;
724
+ return report(provider, "venice:billing-balance", {
725
+ customWindows: [{ label, percent }],
726
+ updatedAt: Date.now(),
727
+ });
728
+ }
729
+ return report(provider, "venice:billing-balance", {
730
+ customWindows: [{ label, percent: 0 }],
731
+ updatedAt: Date.now(),
732
+ });
733
+ }
734
+
735
+ /**
736
+ * Synthetic `GET /v2/quotas` — the known quota lanes (rolling 5-hour,
737
+ * weekly token, search-hourly) mapped onto the quota windows.
738
+ */
739
+ async function fetchSyntheticQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
740
+ if (!isCanonicalSyntheticBaseUrl(config.baseUrl)) return null;
741
+ const apiKey = resolveEnvValue(config.apiKey)?.trim();
742
+ if (!apiKey) return null;
743
+ const response = await fetch(`${SYNTHETIC_BASE_URL}/quotas`, {
744
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
745
+ redirect: "error",
746
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
747
+ });
748
+ if (!response.ok) {
749
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
750
+ ? TERMINAL_QUOTA_FAILURE
751
+ : null;
752
+ }
753
+ const body = asRecord(await response.json().catch(() => null));
754
+ const data = asRecord(body?.data) ?? body;
755
+ const quota: ProviderQuota = { updatedAt: Date.now() };
756
+ let windows = 0;
757
+ const percentAt = (key: string): number | undefined => {
758
+ const value = normalizePercent(data?.[key]);
759
+ if (value !== undefined) return value;
760
+ const nested = asRecord(data?.quota) ?? asRecord(data?.quotas);
761
+ return nested ? normalizePercent(nested[key]) : undefined;
762
+ };
763
+ const fiveHour = percentAt("rollingFiveHourLimit");
764
+ const weekly = percentAt("weeklyTokenLimit");
765
+ if (fiveHour !== undefined) {
766
+ quota.fiveHourPercent = fiveHour;
767
+ windows += 1;
768
+ }
769
+ if (weekly !== undefined) {
770
+ quota.weeklyPercent = weekly;
771
+ windows += 1;
772
+ }
773
+ const search = asRecord(data?.search);
774
+ const searchHourly = search ? normalizePercent(search.hourly) : undefined;
775
+ if (searchHourly !== undefined) {
776
+ quota.customWindows = [...(quota.customWindows ?? []), { label: "Search hourly", percent: searchHourly }];
777
+ windows += 1;
778
+ }
779
+ return windows > 0 ? report(provider, "synthetic:quotas", quota) : null;
780
+ }
781
+
782
+ /**
783
+ * DeepInfra `GET /payment/checklist?compute_owed=true` — prepaid balance,
784
+ * recent spend, spending limit, and suspension state. Renders a balance
785
+ * window (prepaid funds are a negative `stripe_balance` → positive available).
786
+ */
787
+ async function fetchDeepInfraQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
788
+ if (!isCanonicalDeepInfraBaseUrl(config.baseUrl)) return null;
789
+ const apiKey = resolveEnvValue(config.apiKey)?.trim();
790
+ if (!apiKey) return null;
791
+ const response = await fetch(`${DEEPINFRA_BASE_URL}/payment/checklist?compute_owed=true`, {
792
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
793
+ redirect: "error",
794
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
795
+ });
796
+ if (!response.ok) {
797
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
798
+ ? TERMINAL_QUOTA_FAILURE
799
+ : null;
800
+ }
801
+ const body = asRecord(await response.json().catch(() => null));
802
+ const data = asRecord(body?.data) ?? body;
803
+ if (!data) return null;
804
+ const stripeBalance = toFiniteNumber(data.stripe_balance);
805
+ const spendLimit = toFiniteNumber(data.spending_limit);
806
+ const total = toFiniteNumber(data.total_amount_due);
807
+ if (stripeBalance === undefined) return null;
808
+ // Prepaid funds are negative; a positive value is money owed.
809
+ const available = stripeBalance < 0 ? -stripeBalance : 0;
810
+ if (spendLimit !== undefined && spendLimit > 0) {
811
+ const spent = total !== undefined && total > 0 ? total : Math.max(0, spendLimit - available);
812
+ const percent = normalizePercent((spent / spendLimit) * 100);
813
+ if (percent === undefined) return null;
814
+ return report(provider, "deepinfra:billing-checklist", {
815
+ customWindows: [{ label: `Billing cycle spend ($${spent.toFixed(2)} of $${spendLimit.toFixed(2)})`, percent }],
816
+ updatedAt: Date.now(),
817
+ });
818
+ }
819
+ return report(provider, "deepinfra:billing-checklist", {
820
+ customWindows: [{ label: `Prepaid balance ($${available.toFixed(2)})`, percent: 0 }],
821
+ updatedAt: Date.now(),
822
+ });
823
+ }
824
+
825
+ /**
826
+ * Neuralwatt `GET /v1/quota` — subscription kWh usage (primary window) and
827
+ * prepaid USD credit balance (secondary).
828
+ */
829
+ async function fetchNeuralwattQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
830
+ if (!isCanonicalNeuralwattBaseUrl(config.baseUrl)) return null;
831
+ const apiKey = resolveEnvValue(config.apiKey)?.trim();
832
+ if (!apiKey) return null;
833
+ const response = await fetch(`${NEURALWATT_BASE_URL}/quota`, {
834
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
835
+ redirect: "error",
836
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
837
+ });
838
+ if (!response.ok) {
839
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
840
+ ? TERMINAL_QUOTA_FAILURE
841
+ : null;
842
+ }
843
+ const body = asRecord(await response.json().catch(() => null));
844
+ const data = asRecord(body?.data) ?? body;
845
+ const quota: ProviderQuota = { updatedAt: Date.now() };
846
+ let windows = 0;
847
+ const subscription = asRecord(data?.subscription);
848
+ const kwhUsed = subscription ? toFiniteNumber(subscription.kwh_used) : undefined;
849
+ const kwhIncluded = subscription ? toFiniteNumber(subscription.kwh_included) : undefined;
850
+ if (kwhUsed !== undefined && kwhIncluded !== undefined && kwhIncluded > 0) {
851
+ const percent = normalizePercent((kwhUsed / kwhIncluded) * 100);
852
+ if (percent !== undefined) {
853
+ quota.fiveHourPercent = percent;
854
+ const periodEnd = subscription ? normalizeResetAt(subscription.current_period_end) : undefined;
855
+ if (periodEnd !== undefined) quota.fiveHourResetAt = periodEnd;
856
+ windows += 1;
857
+ }
858
+ }
859
+ const balance = asRecord(data?.balance);
860
+ const totalCredits = balance ? toFiniteNumber(balance.total_credits_usd) : undefined;
861
+ const remainingCredits = balance ? toFiniteNumber(balance.credits_remaining_usd) : undefined;
862
+ if (totalCredits !== undefined && totalCredits > 0 && remainingCredits !== undefined) {
863
+ // Utilization is CONSUMED credits, not the remaining share.
864
+ const used = Math.max(0, totalCredits - remainingCredits);
865
+ const percent = normalizePercent((used / totalCredits) * 100);
866
+ if (percent !== undefined) {
867
+ quota.customWindows = [...(quota.customWindows ?? []), { label: "Prepaid credits", percent }];
868
+ windows += 1;
869
+ }
870
+ }
871
+ return windows > 0 ? report(provider, "neuralwatt:quota", quota) : null;
872
+ }
873
+
312
874
  function report(
313
875
  provider: string,
314
876
  source: string,
@@ -1179,6 +1741,36 @@ async function maybeFetchProviderQuota(
1179
1741
  if ((provider.authMode ?? "key") === "key" && isCanonicalA6apiBaseUrl(provider.baseUrl)) {
1180
1742
  return fetchA6apiQuota(name, provider);
1181
1743
  }
1744
+ if ((provider.authMode ?? "key") === "key" && name === "openrouter") {
1745
+ return fetchOpenRouterQuota(name, provider);
1746
+ }
1747
+ if ((provider.authMode ?? "key") === "key" && name === "deepseek") {
1748
+ return fetchDeepSeekQuota(name, provider);
1749
+ }
1750
+ if ((provider.authMode ?? "key") === "key" && name === "cline-pass") {
1751
+ return fetchClineQuota(name, provider);
1752
+ }
1753
+ if ((provider.authMode ?? "key") === "key" && name === "zai") {
1754
+ return fetchZaiQuota(name, provider);
1755
+ }
1756
+ if ((provider.authMode ?? "key") === "key" && (name === "minimax" || name === "minimax-cn")) {
1757
+ return fetchMinimaxQuota(name, provider);
1758
+ }
1759
+ if ((provider.authMode ?? "key") === "key" && name === "moonshot") {
1760
+ return fetchMoonshotQuota(name, provider);
1761
+ }
1762
+ if ((provider.authMode ?? "key") === "key" && name === "venice") {
1763
+ return fetchVeniceQuota(name, provider);
1764
+ }
1765
+ if ((provider.authMode ?? "key") === "key" && name === "synthetic") {
1766
+ return fetchSyntheticQuota(name, provider);
1767
+ }
1768
+ if ((provider.authMode ?? "key") === "key" && name === "deepinfra") {
1769
+ return fetchDeepInfraQuota(name, provider);
1770
+ }
1771
+ if ((provider.authMode ?? "key") === "key" && name === "neuralwatt") {
1772
+ return fetchNeuralwattQuota(name, provider);
1773
+ }
1182
1774
  return null;
1183
1775
  } catch {
1184
1776
  return null;