@bitkyc08/opencodex 2.55.0 → 2.57.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 (256) hide show
  1. package/bin/ocx.mjs +10 -0
  2. package/gui/dist/assets/{index-BBOZWGB6.css → index-C5-RdDmD.css} +1 -1
  3. package/gui/dist/assets/{index-VuoiWj9J.js → index-Cz7CLdif.js} +21 -21
  4. package/gui/dist/index.html +2 -2
  5. package/package.json +4 -3
  6. package/src/adapters/base.ts +21 -0
  7. package/src/adapters/codebuddy/adapter.ts +2 -1
  8. package/src/adapters/codebuddy/scaffold-guard.ts +248 -0
  9. package/src/adapters/command-code.ts +1 -1
  10. package/src/adapters/cursor/envelope-echo.ts +8 -2
  11. package/src/adapters/cursor/transport-retry.ts +46 -1
  12. package/src/adapters/cursor.ts +4 -0
  13. package/src/adapters/google.ts +7 -7
  14. package/src/adapters/kiro/adapter.ts +42 -1
  15. package/src/adapters/kiro/payload.ts +17 -3
  16. package/src/adapters/kiro/reasoning.ts +70 -7
  17. package/src/adapters/kiro/stream.ts +8 -2
  18. package/src/adapters/kiro/wire.ts +2 -1
  19. package/src/adapters/kiro-events.ts +21 -13
  20. package/src/adapters/kiro-retry.ts +23 -4
  21. package/src/adapters/openai-chat/errors.ts +116 -0
  22. package/src/adapters/openai-chat/messages.ts +346 -0
  23. package/src/adapters/openai-chat/passthrough.ts +146 -0
  24. package/src/adapters/openai-chat/response-events.ts +117 -0
  25. package/src/adapters/openai-chat/tool-call-validation.ts +200 -0
  26. package/src/adapters/openai-chat/tool-name-registry.ts +166 -0
  27. package/src/adapters/openai-chat/tool-schema.ts +495 -0
  28. package/src/adapters/openai-chat/wire.ts +50 -0
  29. package/src/adapters/openai-chat.ts +40 -1452
  30. package/src/adapters/openai-responses/canonical-forward.ts +202 -0
  31. package/src/adapters/openai-responses/image-gen.ts +406 -0
  32. package/src/adapters/openai-responses/internal.ts +3 -0
  33. package/src/adapters/openai-responses/passthrough.ts +642 -0
  34. package/src/adapters/openai-responses/prompt-cache.ts +83 -0
  35. package/src/adapters/openai-responses/reasoning.ts +220 -0
  36. package/src/adapters/openai-responses/request-strips.ts +185 -0
  37. package/src/adapters/openai-responses/tool-output-recovery.ts +509 -0
  38. package/src/adapters/openai-responses/tool-schema.ts +293 -0
  39. package/src/adapters/openai-responses/web-search.ts +156 -0
  40. package/src/adapters/openai-responses.ts +4 -2625
  41. package/src/bridge/errors.ts +58 -0
  42. package/src/bridge/internal.ts +174 -0
  43. package/src/bridge/response-json.ts +630 -0
  44. package/src/bridge/sse.ts +1462 -0
  45. package/src/bridge.ts +5 -2204
  46. package/src/chat/inbound.ts +12 -1
  47. package/src/claude/desktop-profile.ts +66 -9
  48. package/src/claude/outbound.ts +18 -0
  49. package/src/cli/account-main.ts +1 -1
  50. package/src/cli/capabilities.ts +2 -2
  51. package/src/cli/combo.ts +10 -1
  52. package/src/cli/index.ts +48 -5
  53. package/src/cli/registry.ts +2 -1
  54. package/src/cli/system-command.ts +4 -4
  55. package/src/clients/config-export.ts +7 -3
  56. package/src/codex/account-label.ts +14 -3
  57. package/src/codex/account-lifecycle.ts +3 -0
  58. package/src/codex/account-store.ts +184 -35
  59. package/src/codex/account-usability.ts +21 -0
  60. package/src/codex/auth-api/account-list.ts +507 -0
  61. package/src/codex/auth-api/http.ts +32 -0
  62. package/src/codex/auth-api/login-flow.ts +566 -0
  63. package/src/codex/auth-api/login-state.ts +64 -0
  64. package/src/codex/auth-api/main-account-probe.ts +331 -0
  65. package/src/codex/auth-api/pool-mode-gate.ts +274 -0
  66. package/src/codex/auth-api/pool-quota-probe.ts +512 -0
  67. package/src/codex/auth-api/reset-credit-service.ts +431 -0
  68. package/src/codex/auth-api/routes.ts +425 -0
  69. package/src/codex/auth-api/runtime-config.ts +48 -0
  70. package/src/codex/auth-api.ts +27 -3118
  71. package/src/codex/auth-context.ts +252 -35
  72. package/src/codex/catalog/aggregation.ts +80 -1
  73. package/src/codex/catalog/auto-review.ts +507 -0
  74. package/src/codex/catalog/build-entries.ts +981 -0
  75. package/src/codex/catalog/combo-member.ts +375 -0
  76. package/src/codex/catalog/derive-entry.ts +229 -0
  77. package/src/codex/catalog/effort.ts +0 -1
  78. package/src/codex/catalog/gated-native-warn.ts +63 -0
  79. package/src/codex/catalog/gather-capture.ts +533 -0
  80. package/src/codex/catalog/model-hints.ts +691 -0
  81. package/src/codex/catalog/model-visibility.ts +305 -0
  82. package/src/codex/catalog/provider-fetch.ts +52 -2942
  83. package/src/codex/catalog/provider-models.ts +685 -0
  84. package/src/codex/catalog/remote.ts +30 -0
  85. package/src/codex/catalog/restore.ts +132 -0
  86. package/src/codex/catalog/retained-sync.ts +714 -0
  87. package/src/codex/catalog/routed-gather.ts +895 -0
  88. package/src/codex/catalog/subagent-roster.ts +176 -0
  89. package/src/codex/catalog/sync.ts +52 -2698
  90. package/src/codex/cli-install-provenance.ts +7 -1
  91. package/src/codex/convergence.ts +7 -2
  92. package/src/codex/desktop-app/types.ts +11 -2
  93. package/src/codex/desktop-app/windows.ts +5 -5
  94. package/src/codex/inject/config-toml.ts +563 -0
  95. package/src/codex/inject/remove.ts +192 -0
  96. package/src/codex/inject/restore.ts +567 -0
  97. package/src/codex/inject/routing-classify.ts +109 -0
  98. package/src/codex/inject/routing-target.ts +125 -0
  99. package/src/codex/inject.ts +89 -1444
  100. package/src/codex/lineage.ts +458 -0
  101. package/src/codex/model-entitlements.ts +152 -15
  102. package/src/codex/pool-refresh-backoff.ts +161 -0
  103. package/src/codex/quota-rejection.ts +104 -15
  104. package/src/codex/routing/active-account.ts +194 -0
  105. package/src/codex/routing/cache-affinity.ts +70 -0
  106. package/src/codex/routing/cooldown-math.ts +285 -0
  107. package/src/codex/routing/health-store.ts +402 -0
  108. package/src/codex/routing/probe-lease.ts +358 -0
  109. package/src/codex/routing/selection.ts +780 -0
  110. package/src/codex/routing/thread-affinity.ts +586 -0
  111. package/src/codex/routing/transient-hold-dispatch.ts +141 -0
  112. package/src/codex/routing.ts +370 -2271
  113. package/src/codex/shim-fingerprint.ts +223 -0
  114. package/src/codex/shim-inspect.ts +175 -0
  115. package/src/codex/shim-probe.ts +367 -0
  116. package/src/codex/shim-restore-lock.ts +169 -0
  117. package/src/codex/shim-state-file.ts +151 -0
  118. package/src/codex/shim-templates.ts +265 -0
  119. package/src/codex/shim.ts +48 -1268
  120. package/src/codex/warmup.ts +1 -1
  121. package/src/combos/failover.ts +85 -0
  122. package/src/combos/request.ts +17 -10
  123. package/src/combos/types.ts +23 -2
  124. package/src/config/diagnostics.ts +705 -0
  125. package/src/config/feature-flags.ts +55 -0
  126. package/src/config/live-reconcile.ts +403 -0
  127. package/src/config/load-degrade.ts +880 -0
  128. package/src/config/mutation-lock.ts +244 -0
  129. package/src/config/openai-tier-backup.ts +268 -0
  130. package/src/config/pending-teardown.ts +31 -0
  131. package/src/config/persist-unlocked.ts +92 -0
  132. package/src/config/proxy-env.ts +188 -0
  133. package/src/config/salvage.ts +244 -0
  134. package/src/config/schema/config-schema.ts +640 -0
  135. package/src/config/schema/leaf-validators.ts +855 -0
  136. package/src/config/warn-memo.ts +28 -0
  137. package/src/config.ts +234 -4481
  138. package/src/generated/compatibility-version.json +649 -121
  139. package/src/images/loop.ts +1 -1
  140. package/src/lib/errors.ts +17 -0
  141. package/src/lib/request-execution-budget.ts +198 -23
  142. package/src/lib/spend-reservation-ledger.ts +958 -0
  143. package/src/lib/state-store-registrations.ts +6 -2
  144. package/src/lib/test-home-guard.ts +85 -1
  145. package/src/lib/upstream-retry.ts +132 -21
  146. package/src/lib/windows-elevation.ts +76 -14
  147. package/src/lib/workflow-budget.ts +553 -30
  148. package/src/oauth/index.ts +2 -2
  149. package/src/oauth/key-providers.ts +2 -2
  150. package/src/providers/kiro-models.ts +4 -3
  151. package/src/providers/label.ts +19 -1
  152. package/src/providers/model-discovery.ts +16 -0
  153. package/src/providers/quota/account-cache.ts +441 -0
  154. package/src/providers/quota/antigravity.ts +295 -0
  155. package/src/providers/quota/report-cache.ts +320 -0
  156. package/src/providers/quota/vendor-probes-key.ts +1243 -0
  157. package/src/providers/quota/vendor-probes-oauth.ts +590 -0
  158. package/src/providers/quota.ts +324 -3079
  159. package/src/providers/registry/entries-core.ts +1228 -0
  160. package/src/providers/registry/entries-extended.ts +1213 -0
  161. package/src/providers/registry/model-seeds.ts +912 -0
  162. package/src/providers/registry/types.ts +352 -0
  163. package/src/providers/registry.ts +24 -3536
  164. package/src/responses/continuation-ownership.ts +29 -0
  165. package/src/responses/reasoning-envelope.ts +6 -3
  166. package/src/responses/state/replay-fingerprint.ts +80 -0
  167. package/src/responses/state/snapshot-codec.ts +104 -0
  168. package/src/responses/state/spill-failure.ts +118 -0
  169. package/src/responses/state/spill-queue.ts +665 -0
  170. package/src/responses/state/temp-recovery.ts +257 -0
  171. package/src/responses/state.ts +82 -1143
  172. package/src/routing/identity-domains.ts +456 -0
  173. package/src/routing/probe-lease.ts +613 -0
  174. package/src/server/chat-completions.ts +3 -1
  175. package/src/server/chat-native.ts +37 -9
  176. package/src/server/index/bounded-request.ts +88 -0
  177. package/src/server/index/live-sideband.ts +601 -0
  178. package/src/server/index/serve-options.ts +1766 -0
  179. package/src/server/index/startup-warnings.ts +213 -0
  180. package/src/server/index/websocket-handler.ts +339 -0
  181. package/src/server/index.ts +45 -2552
  182. package/src/server/inspection-tee.ts +107 -0
  183. package/src/server/live.ts +46 -1
  184. package/src/server/management/combo-routes.ts +10 -1
  185. package/src/server/management/route-registry.ts +26 -23
  186. package/src/server/management/shared.ts +8 -5
  187. package/src/server/management/workflow-budget-routes.ts +133 -0
  188. package/src/server/management-api.ts +12 -0
  189. package/src/server/relay-eager.ts +2 -0
  190. package/src/server/relay.ts +14 -19
  191. package/src/server/request-log-conversation.ts +9 -7
  192. package/src/server/request-log.ts +372 -4
  193. package/src/server/response-log-body.ts +153 -0
  194. package/src/server/responses/account-change-state.ts +307 -0
  195. package/src/server/responses/adapter-continuation.ts +540 -0
  196. package/src/server/responses/adapter-delivery.ts +208 -0
  197. package/src/server/responses/adapter-dispatch.ts +1042 -0
  198. package/src/server/responses/codex-ws-wire.ts +5 -0
  199. package/src/server/responses/collaboration.ts +74 -4
  200. package/src/server/responses/combo-session-recall.ts +68 -8
  201. package/src/server/responses/compact.ts +113 -17
  202. package/src/server/responses/completion-policy.ts +33 -0
  203. package/src/server/responses/core-auth.ts +529 -0
  204. package/src/server/responses/core-codex-account.ts +907 -0
  205. package/src/server/responses/core-combo-failure.ts +210 -0
  206. package/src/server/responses/core-combo.ts +787 -0
  207. package/src/server/responses/core-errors.ts +170 -0
  208. package/src/server/responses/core-lifetime.ts +95 -0
  209. package/src/server/responses/core-normalize.ts +350 -0
  210. package/src/server/responses/core-opaque-recovery.ts +380 -0
  211. package/src/server/responses/core-options.ts +159 -0
  212. package/src/server/responses/core-replay.ts +298 -0
  213. package/src/server/responses/core.ts +192 -8893
  214. package/src/server/responses/encrypted-payload.ts +0 -1
  215. package/src/server/responses/input-admission.ts +126 -6
  216. package/src/server/responses/passthrough-delivery.ts +869 -0
  217. package/src/server/responses/passthrough-dispatch.ts +1494 -0
  218. package/src/server/responses/passthrough-error.ts +38 -2
  219. package/src/server/responses/passthrough-execution.ts +54 -0
  220. package/src/server/responses/request-prepare.ts +1080 -0
  221. package/src/server/responses/request-send-budget.ts +259 -0
  222. package/src/server/responses/request-sidecar-auth.ts +149 -0
  223. package/src/server/responses/request-spend.ts +147 -0
  224. package/src/server/responses/request-transport.ts +803 -0
  225. package/src/server/responses/response-effects.ts +157 -0
  226. package/src/server/responses/run-turn-execution.ts +476 -0
  227. package/src/server/responses/sidecar-execution.ts +463 -0
  228. package/src/server/responses/terminal-guard.ts +65 -4
  229. package/src/server/responses-image-gen-repair.ts +1 -1
  230. package/src/server/responses-undeclared-tool-guard.ts +9 -5
  231. package/src/server/workflow-refusal.ts +84 -0
  232. package/src/service/windows-ops.ts +210 -16
  233. package/src/service/windows-scheduler.ts +28 -21
  234. package/src/service.ts +1 -1
  235. package/src/types/config.ts +34 -1
  236. package/src/types/request.ts +8 -5
  237. package/src/types/tools.ts +24 -0
  238. package/src/types.ts +2 -0
  239. package/src/update/index.ts +10 -0
  240. package/src/update/stop-contract.d.mts +1 -0
  241. package/src/update/stop-contract.mjs +19 -0
  242. package/src/update/stop-decision.d.mts +1 -1
  243. package/src/update/stop-decision.mjs +12 -3
  244. package/src/usage/log.ts +147 -1
  245. package/src/usage/summary.ts +171 -21
  246. package/src/vision/anthropic-describe.ts +1 -1
  247. package/src/vision/describe.ts +5 -5
  248. package/src/web-search/anthropic-executor.ts +1 -1
  249. package/src/web-search/exa-executor.ts +1 -1
  250. package/src/web-search/executor.ts +1 -1
  251. package/src/web-search/gemini-executor.ts +1 -1
  252. package/src/web-search/loop.ts +1 -1
  253. package/src/web-search/ollama-executor.ts +1 -1
  254. package/src/web-search/parse.ts +67 -14
  255. package/src/web-search/passthrough-bridge.ts +64 -31
  256. package/src/web-search/xai-executor.ts +1 -1
@@ -0,0 +1,1243 @@
1
+ import { resolveProviderApiKey } from "../key-store";
2
+ import { getProviderRegistryEntry, registryEntryForProviderDestination } from "../registry";
3
+ import { isCanonicalOllamaCloudUrl } from "../../adapters/ollama-native-url";
4
+ import { QUOTA_JSON_READ_FAILURE, asRecord, normalizePercent, normalizeResetAt, readQuotaJson, REQUEST_TIMEOUT_MS, toFiniteNumber } from "../quota-wire";
5
+ import {
6
+ AUTHORITATIVE_EMPTY_QUOTA,
7
+ hasQuotaRows,
8
+ keyReport,
9
+ report,
10
+ TERMINAL_QUOTA_FAILURE,
11
+ type ProviderQuotaProbeResult,
12
+ type ProviderQuotaReport,
13
+ } from "./report-cache";
14
+ import { getTokenForAccountQuotaProbe } from "./account-cache";
15
+ import type { AccountQuotaMode, ProviderQuota, ProviderQuotaCreditsUsd } from "../quota-types";
16
+ import type { OcxProviderConfig } from "../../types";
17
+
18
+ const KIMI_CODE_BASE_URL = "https://api.kimi.com/coding/v1";
19
+ const KIMI_CODE_USAGE_URL = `${KIMI_CODE_BASE_URL}/usages`;
20
+ const COMMAND_CODE_BASE_URL = "https://api.commandcode.ai";
21
+ const COMMAND_CODE_WHOAMI_URL = `${COMMAND_CODE_BASE_URL}/alpha/whoami`;
22
+ const COMMAND_CODE_CREDITS_URL = `${COMMAND_CODE_BASE_URL}/alpha/billing/credits`;
23
+ const COMMAND_CODE_SUBSCRIPTIONS_URL = `${COMMAND_CODE_BASE_URL}/alpha/billing/subscriptions`;
24
+ const COMMAND_CODE_USAGE_URL = `${COMMAND_CODE_BASE_URL}/alpha/usage/summary`;
25
+ const A6API_BASE_URL = "https://api.a6api.com";
26
+ const OPENCODE_GO_BASE_URL = "https://opencode.ai/zen/go/v1";
27
+ const OPENCODE_GO_USAGE_URL = `${OPENCODE_GO_BASE_URL}/usage`;
28
+ const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
29
+ const DEEPSEEK_BASE_URL = "https://api.deepseek.com";
30
+ const CLINE_BASE_URL = "https://api.cline.bot";
31
+ const OLLAMA_CLOUD_BASE_URL = "https://ollama.com";
32
+ const OLLAMA_CLOUD_USAGE_URL = `${OLLAMA_CLOUD_BASE_URL}/api/usage`;
33
+ const ZAI_BASE_URL = "https://api.z.ai";
34
+ const ZAI_CN_BASE_URL = "https://open.bigmodel.cn";
35
+ const MINIMAX_REMAINS_URL = "https://www.minimax.io/v1/token_plan/remains";
36
+ const MOONSHOT_BASE_URL = "https://api.moonshot.ai/v1";
37
+ const VENICE_BASE_URL = "https://api.venice.ai/api/v1";
38
+ const SYNTHETIC_BASE_URL = "https://api.synthetic.new/v2";
39
+ const DEEPINFRA_BASE_URL = "https://api.deepinfra.com";
40
+ const NEURALWATT_BASE_URL = "https://api.neuralwatt.com/v1";
41
+
42
+
43
+ function isCanonicalA6apiBaseUrl(baseUrl: string): boolean {
44
+ const normalized = normalizedBaseUrl(baseUrl);
45
+ return normalized === A6API_BASE_URL || normalized === `${A6API_BASE_URL}/v1`;
46
+ }
47
+
48
+ function isCanonicalOpenCodeGoBaseUrl(baseUrl: string): boolean {
49
+ return normalizedBaseUrl(baseUrl) === OPENCODE_GO_BASE_URL;
50
+ }
51
+
52
+ function isCanonicalOpenRouterBaseUrl(baseUrl: string): boolean {
53
+ const normalized = normalizedBaseUrl(baseUrl);
54
+ return normalized === OPENROUTER_BASE_URL;
55
+ }
56
+
57
+ function isCanonicalDeepSeekBaseUrl(baseUrl: string): boolean {
58
+ const normalized = normalizedBaseUrl(baseUrl);
59
+ return normalized === DEEPSEEK_BASE_URL || normalized === `${DEEPSEEK_BASE_URL}/v1`;
60
+ }
61
+
62
+ function isCanonicalClineBaseUrl(baseUrl: string): boolean {
63
+ const normalized = normalizedBaseUrl(baseUrl);
64
+ return normalized === CLINE_BASE_URL || normalized === `${CLINE_BASE_URL}/api/v1`;
65
+ }
66
+
67
+ function isCanonicalOllamaCloudBaseUrl(baseUrl?: string): boolean {
68
+ if (!baseUrl) return false;
69
+ try {
70
+ return isCanonicalOllamaCloudUrl(baseUrl);
71
+ } catch {
72
+ return false;
73
+ }
74
+ }
75
+
76
+ function zaiQuotaMonitorHost(baseUrl: string): string | null {
77
+ // Admission and destination selection must share one mapping: admitting a new
78
+ // international wire must never fall through to the CN host/authentication scheme.
79
+ switch (normalizedBaseUrl(baseUrl)) {
80
+ case ZAI_BASE_URL:
81
+ case `${ZAI_BASE_URL}/api/coding/paas/v4`:
82
+ case `${ZAI_BASE_URL}/api/anthropic`:
83
+ case `${ZAI_BASE_URL}/api/v1`:
84
+ return ZAI_BASE_URL;
85
+ case ZAI_CN_BASE_URL:
86
+ case `${ZAI_CN_BASE_URL}/api/coding/paas/v4`:
87
+ case `${ZAI_CN_BASE_URL}/api/v1`:
88
+ return ZAI_CN_BASE_URL;
89
+ default:
90
+ return null;
91
+ }
92
+ }
93
+
94
+ function isCanonicalZaiBaseUrl(baseUrl: string): boolean {
95
+ return zaiQuotaMonitorHost(baseUrl) !== null;
96
+ }
97
+
98
+ function isCanonicalMinimaxBaseUrl(baseUrl: string): boolean {
99
+ const normalized = normalizedBaseUrl(baseUrl);
100
+ return normalized === "https://api.minimax.io/v1" || normalized === "https://api.minimaxi.com/v1";
101
+ }
102
+
103
+ function isCanonicalMoonshotBaseUrl(baseUrl: string): boolean {
104
+ const normalized = normalizedBaseUrl(baseUrl);
105
+ return normalized === MOONSHOT_BASE_URL || normalized === "https://api.moonshot.cn/v1";
106
+ }
107
+
108
+ function isCanonicalVeniceBaseUrl(baseUrl: string): boolean {
109
+ return normalizedBaseUrl(baseUrl) === VENICE_BASE_URL;
110
+ }
111
+
112
+ function isCanonicalSyntheticBaseUrl(baseUrl: string): boolean {
113
+ const normalized = normalizedBaseUrl(baseUrl);
114
+ return normalized === SYNTHETIC_BASE_URL || normalized === "https://api.synthetic.new/openai/v1";
115
+ }
116
+
117
+ function isCanonicalDeepInfraBaseUrl(baseUrl: string): boolean {
118
+ const normalized = normalizedBaseUrl(baseUrl);
119
+ return normalized === DEEPINFRA_BASE_URL || normalized === `${DEEPINFRA_BASE_URL}/v1/openai`;
120
+ }
121
+
122
+ function isCanonicalNeuralwattBaseUrl(baseUrl: string): boolean {
123
+ return normalizedBaseUrl(baseUrl) === NEURALWATT_BASE_URL;
124
+ }
125
+
126
+ function a6apiPayload(value: unknown): Record<string, unknown> | null {
127
+ const body = asRecord(value);
128
+ return asRecord(body?.data) ?? body;
129
+ }
130
+
131
+ function firstFinite(record: Record<string, unknown> | null, names: string[]): number | undefined {
132
+ if (!record) return undefined;
133
+ for (const name of names) {
134
+ const value = toFiniteNumber(record[name]);
135
+ if (value !== undefined) return value;
136
+ }
137
+ return undefined;
138
+ }
139
+
140
+ async function fetchA6apiQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
141
+ // Never send a configured API key to a lookalike host or through a redirect.
142
+ if (!isCanonicalA6apiBaseUrl(config.baseUrl)) return null;
143
+ const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
144
+ if (!apiKey) return null;
145
+ const headers = { Accept: "application/json", Authorization: `Bearer ${apiKey}` } as const;
146
+ const [subscriptionResponse, tokenResponse] = await Promise.all([
147
+ fetch(`${A6API_BASE_URL}/dashboard/billing/subscription`, {
148
+ headers, redirect: "error", signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
149
+ }),
150
+ fetch(`${A6API_BASE_URL}/api/usage/token/`, {
151
+ headers, redirect: "error", signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
152
+ }),
153
+ ]);
154
+ if (!subscriptionResponse.ok || !tokenResponse.ok) {
155
+ const statuses = [subscriptionResponse.status, tokenResponse.status];
156
+ // 408/429 are transient (timeout/throttle), not invalid-account signals: keep the
157
+ // last-good row like 5xx/network failures. 401/403 (bad key) and 404 (contract change)
158
+ // stay terminal.
159
+ return statuses.some(status => status >= 400 && status < 500 && status !== 429 && status !== 408)
160
+ ? TERMINAL_QUOTA_FAILURE
161
+ : null;
162
+ }
163
+ const [subscriptionBody, tokenBody] = await Promise.all([
164
+ readQuotaJson(subscriptionResponse),
165
+ readQuotaJson(tokenResponse),
166
+ ]);
167
+ if (subscriptionBody === QUOTA_JSON_READ_FAILURE || tokenBody === QUOTA_JSON_READ_FAILURE) return null;
168
+ const subscription = a6apiPayload(subscriptionBody);
169
+ const token = a6apiPayload(tokenBody);
170
+ const unlimited = token?.unlimited_quota === true
171
+ || token?.unlimited_quota === 1
172
+ || token?.unlimited_quota === "true";
173
+ const normalizedExpiry = normalizeResetAt(token?.expires_at);
174
+ const expiry = normalizedExpiry && normalizedExpiry > 0
175
+ ? { expiresAt: normalizedExpiry }
176
+ : {};
177
+ if (unlimited) {
178
+ // Every row is an API-credit constraint on inference, so the display quota is also
179
+ // the routing projection. Passing it explicitly is the opt-in.
180
+ const quota: ProviderQuota = {
181
+ creditsUsd: {
182
+ used: 0,
183
+ limit: 0,
184
+ remaining: 0,
185
+ percent: 0,
186
+ unlimited: true,
187
+ ...expiry,
188
+ },
189
+ customWindows: [{ label: "Unlimited API credits", percent: 0 }],
190
+ updatedAt: Date.now(),
191
+ };
192
+ return keyReport(provider, "a6api:billing", quota, config, apiKey, quota);
193
+ }
194
+ const limitUsd = firstFinite(subscription, ["hard_limit_usd"]);
195
+ const grantedUnits = firstFinite(token, ["total_granted"]);
196
+ const usedUnits = firstFinite(token, ["total_used"]);
197
+ const availableUnits = firstFinite(token, ["total_available"]);
198
+ const reconciledUnits = usedUnits !== undefined && availableUnits !== undefined
199
+ ? usedUnits + availableUnits
200
+ : undefined;
201
+ const reconciliationTolerance = grantedUnits !== undefined
202
+ ? Math.abs(grantedUnits) * 1e-9
203
+ : 0;
204
+ if (limitUsd === undefined || grantedUnits === undefined || usedUnits === undefined
205
+ || availableUnits === undefined || limitUsd <= 0 || grantedUnits <= 0
206
+ || usedUnits < 0 || availableUnits < 0
207
+ || reconciledUnits === undefined
208
+ || Math.abs(reconciledUnits - grantedUnits) > reconciliationTolerance) return TERMINAL_QUOTA_FAILURE;
209
+ const usdPerUnit = limitUsd / grantedUnits;
210
+ const usedUsd = usedUnits * usdPerUnit;
211
+ const remainingUsd = Math.max(0, availableUnits * usdPerUnit);
212
+ const percent = normalizePercent((usedUsd / limitUsd) * 100);
213
+ if (percent === undefined) return TERMINAL_QUOTA_FAILURE;
214
+ const label = `API credits ($${remainingUsd.toFixed(2)} of $${limitUsd.toFixed(2)} remaining)`;
215
+ const quota: ProviderQuota = {
216
+ creditsUsd: {
217
+ used: usedUsd,
218
+ limit: limitUsd,
219
+ remaining: remainingUsd,
220
+ percent,
221
+ ...expiry,
222
+ },
223
+ customWindows: [{ label, percent }],
224
+ updatedAt: Date.now(),
225
+ };
226
+ // The credit balance funds inference itself, so display and routing scope agree.
227
+ return keyReport(provider, "a6api:billing", quota, config, apiKey, quota);
228
+ }
229
+
230
+ function parseOpenCodeGoUsageWindow(value: unknown): { percent: number; resetAt?: number } | null {
231
+ const row = asRecord(value);
232
+ if (!row) return null;
233
+ const percent = normalizePercent(row.percent);
234
+ if (percent === undefined) return null;
235
+ const resetAt = normalizeResetAt(row.resetsAt);
236
+ return { percent, ...(resetAt !== undefined ? { resetAt } : {}) };
237
+ }
238
+
239
+ async function fetchOpenCodeGoQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
240
+ // Never send a configured API key when the provider destination is not the built-in Go endpoint.
241
+ if (!isCanonicalOpenCodeGoBaseUrl(config.baseUrl)) return null;
242
+ const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
243
+ if (!apiKey) return null;
244
+ const response = await fetch(OPENCODE_GO_USAGE_URL, {
245
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
246
+ redirect: "error",
247
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
248
+ });
249
+ if (!response.ok) {
250
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
251
+ ? TERMINAL_QUOTA_FAILURE
252
+ : null;
253
+ }
254
+ const body = asRecord(await readQuotaJson(response));
255
+ const usage = asRecord(body?.usage);
256
+ if (!usage) return null;
257
+ const rolling = parseOpenCodeGoUsageWindow(usage.rolling);
258
+ const weekly = parseOpenCodeGoUsageWindow(usage.weekly);
259
+ const monthly = parseOpenCodeGoUsageWindow(usage.monthly);
260
+ const quota: ProviderQuota = {
261
+ ...(rolling ? {
262
+ fiveHourPercent: rolling.percent,
263
+ ...(rolling.resetAt !== undefined ? { fiveHourResetAt: rolling.resetAt } : {}),
264
+ } : {}),
265
+ ...(weekly ? {
266
+ weeklyPercent: weekly.percent,
267
+ ...(weekly.resetAt !== undefined ? { weeklyResetAt: weekly.resetAt } : {}),
268
+ } : {}),
269
+ ...(monthly ? {
270
+ monthlyPercent: monthly.percent,
271
+ ...(monthly.resetAt !== undefined ? { monthlyResetAt: monthly.resetAt } : {}),
272
+ } : {}),
273
+ updatedAt: Date.now(),
274
+ };
275
+ return keyReport(provider, "opencode-go:usage", quota, config, apiKey, quota);
276
+ }
277
+
278
+ /**
279
+ * OpenRouter `GET /api/v1/key` — the key's own credit balance and optional
280
+ * per-key spending cap. `limit` is the configured cap (absent = uncapped);
281
+ * `usage` is lifetime spend; `limit_remaining` is what is left of the cap.
282
+ * When no cap is set there is no hard limit to meter against, so no bar is
283
+ * produced — the provider falls back to its documented reference.
284
+ */
285
+ async function fetchOpenRouterQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
286
+ // Never send a configured API key to a lookalike host or through a redirect.
287
+ if (!isCanonicalOpenRouterBaseUrl(config.baseUrl)) return null;
288
+ const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
289
+ if (!apiKey) return null;
290
+ const response = await fetch(`${OPENROUTER_BASE_URL}/key`, {
291
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
292
+ redirect: "error",
293
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
294
+ });
295
+ if (!response.ok) {
296
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
297
+ ? TERMINAL_QUOTA_FAILURE
298
+ : null;
299
+ }
300
+ const body = asRecord(await readQuotaJson(response));
301
+ const data = asRecord(body?.data) ?? body;
302
+ if (!data) return null;
303
+ const limit = toFiniteNumber(data.limit);
304
+ const limitRemaining = toFiniteNumber(data.limit_remaining);
305
+ const usage = toFiniteNumber(data.usage);
306
+ // A successful no-cap response is a DELIBERATE change, not a transient
307
+ // failure: the old capped row must be dropped, not preserved as last-good.
308
+ if (limit === undefined || limit <= 0) return TERMINAL_QUOTA_FAILURE;
309
+ // Prefer the authoritative remaining-cap value when present: `usage` is
310
+ // lifetime accumulated spend and overstates a reset or re-capped key.
311
+ const used = limitRemaining !== undefined
312
+ ? Math.max(0, limit - limitRemaining)
313
+ : usage !== undefined && usage >= 0 ? usage : undefined;
314
+ if (used === undefined) return null;
315
+ const percent = normalizePercent((used / limit) * 100);
316
+ if (percent === undefined) return null;
317
+ const remaining = Math.max(0, limit - used);
318
+ const label = `API credits ($${remaining.toFixed(2)} of $${limit.toFixed(2)} remaining)`;
319
+ // The per-key spending cap stops every request this credential can make, so the
320
+ // whole report is inference-wide routing evidence.
321
+ const quota: ProviderQuota = {
322
+ customWindows: [{ label, percent }],
323
+ updatedAt: Date.now(),
324
+ };
325
+ return keyReport(provider, "openrouter:key-info", quota, config, apiKey, quota);
326
+ }
327
+
328
+ /**
329
+ * DeepSeek `GET /user/balance` — the account's granted + topped-up credit
330
+ * balance. The payload places `total_balance` / `granted_balance` inside
331
+ * entries of `balance_infos` (one row per currency); the row for the account's
332
+ * currency is selected by preference. `granted_balance` is a CURRENT balance
333
+ * component, not the original grant ceiling, so no consumed percentage is
334
+ * fabricated — the balance is reported as a balance-only window.
335
+ */
336
+ async function fetchDeepSeekQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
337
+ if (!isCanonicalDeepSeekBaseUrl(config.baseUrl)) return null;
338
+ const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
339
+ if (!apiKey) return null;
340
+ const response = await fetch(`${DEEPSEEK_BASE_URL}/user/balance`, {
341
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
342
+ redirect: "error",
343
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
344
+ });
345
+ if (!response.ok) {
346
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
347
+ ? TERMINAL_QUOTA_FAILURE
348
+ : null;
349
+ }
350
+ const body = asRecord(await readQuotaJson(response));
351
+ // The payload nests balances under `balance_infos` rows keyed by currency;
352
+ // prefer a USD row, then CNY, then the first row that parses.
353
+ const infos = Array.isArray(body?.balance_infos) ? body.balance_infos as unknown[] : null;
354
+ const rows = infos
355
+ ? infos.map((raw): Record<string, unknown> | null => asRecord(raw)).filter((r): r is Record<string, unknown> => r !== null)
356
+ : [];
357
+ const pick = (currency: string): Record<string, unknown> | null =>
358
+ rows.find(row => String(row.currency ?? "").toUpperCase() === currency) ?? null;
359
+ const preferred = pick("USD") ?? pick("CNY") ?? rows[0] ?? null;
360
+ if (!preferred) return null;
361
+ const totalBalance = toFiniteNumber(preferred.total_balance);
362
+ const grantedBalance = toFiniteNumber(preferred.granted_balance);
363
+ const toppedUp = toFiniteNumber(preferred.topped_up_balance);
364
+ const balance = totalBalance ?? grantedBalance ?? toppedUp;
365
+ if (balance === undefined || balance < 0) return null;
366
+ const label = grantedBalance !== undefined && grantedBalance > 0
367
+ ? `API balance ($${balance.toFixed(2)} total, $${grantedBalance.toFixed(2)} granted)`
368
+ : `API balance ($${balance.toFixed(2)})`;
369
+ return report(provider, "deepseek:balance", {
370
+ customWindows: [{ label, percent: 0 }],
371
+ updatedAt: Date.now(),
372
+ });
373
+ }
374
+
375
+ /**
376
+ * ClinePass `GET /api/v1/users/me/plan/usage-limits` — the subscription's
377
+ * rolling five-hour, weekly, and monthly utilization, matching the existing
378
+ * ProviderQuota windows directly. The endpoint 404s (or returns a null plan)
379
+ * for accounts without an active ClinePass, which is a no-report, not an error.
380
+ */
381
+ async function fetchClineQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
382
+ if (!isCanonicalClineBaseUrl(config.baseUrl)) return null;
383
+ const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
384
+ if (!apiKey) return null;
385
+ const response = await fetch(`${CLINE_BASE_URL}/api/v1/users/me/plan/usage-limits`, {
386
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
387
+ redirect: "error",
388
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
389
+ });
390
+ if (!response.ok) {
391
+ // 404 = no active plan; a plain "no plan" is a no-report, everything else
392
+ // 4xx (except 408/429) is a credential/contract problem.
393
+ if (response.status === 404) return null;
394
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
395
+ ? TERMINAL_QUOTA_FAILURE
396
+ : null;
397
+ }
398
+ const body = asRecord(await readQuotaJson(response));
399
+ const data = asRecord(body?.data) ?? body;
400
+ const limits = Array.isArray(data?.limits) ? data.limits : null;
401
+ if (!limits) return null;
402
+ const quota: ProviderQuota = { updatedAt: Date.now() };
403
+ let windows = 0;
404
+ for (const raw of limits) {
405
+ const row = asRecord(raw);
406
+ if (!row) continue;
407
+ const percent = normalizePercent(row.percentUsed);
408
+ if (percent === undefined) continue;
409
+ const resetAt = normalizeResetAt(row.resetsAt);
410
+ if (row.type === "five_hour") {
411
+ quota.fiveHourPercent = percent;
412
+ if (resetAt !== undefined) quota.fiveHourResetAt = resetAt;
413
+ windows += 1;
414
+ } else if (row.type === "weekly") {
415
+ quota.weeklyPercent = percent;
416
+ if (resetAt !== undefined) quota.weeklyResetAt = resetAt;
417
+ windows += 1;
418
+ } else if (row.type === "monthly") {
419
+ quota.monthlyPercent = percent;
420
+ if (resetAt !== undefined) quota.monthlyResetAt = resetAt;
421
+ windows += 1;
422
+ }
423
+ }
424
+ return windows > 0 ? keyReport(provider, "cline:plan-usage-limits", quota, config, apiKey, quota) : null;
425
+ }
426
+
427
+ /**
428
+ * Ollama Cloud `GET https://ollama.com/api/usage` — returns account usage.
429
+ * Legacy plans report rolling 5-hour `limits.session.usage` and 7-day
430
+ * `limits.weekly.usage`. Migrated monthly-credit plans report
431
+ * `limits.monthly.usage`. `usage` values are normalized fractions (0..1).
432
+ */
433
+ function parseOllamaPercent(usageValue: unknown): number | undefined {
434
+ const usage = toFiniteNumber(usageValue);
435
+ if (usage === undefined || usage < 0) return undefined;
436
+ const percent = Math.round(usage * 10000) / 100;
437
+ return normalizePercent(percent);
438
+ }
439
+
440
+ export function parseOllamaCloudQuota(body: Record<string, unknown> | null): ProviderQuota | null {
441
+ if (!body) return null;
442
+ const limits = asRecord(body.limits);
443
+ if (!limits) return null;
444
+
445
+ const quota: ProviderQuota = { updatedAt: Date.now() };
446
+ let windows = 0;
447
+
448
+ const session = asRecord(limits.session);
449
+ if (session) {
450
+ const percent = parseOllamaPercent(session.usage);
451
+ if (percent !== undefined) {
452
+ quota.fiveHourPercent = percent;
453
+ windows += 1;
454
+ }
455
+ }
456
+
457
+ const weekly = asRecord(limits.weekly);
458
+ if (weekly) {
459
+ const percent = parseOllamaPercent(weekly.usage);
460
+ if (percent !== undefined) {
461
+ quota.weeklyPercent = percent;
462
+ windows += 1;
463
+ }
464
+ }
465
+
466
+ const monthly = asRecord(limits.monthly);
467
+ if (monthly) {
468
+ const percent = parseOllamaPercent(monthly.usage);
469
+ if (percent !== undefined) {
470
+ quota.monthlyPercent = percent;
471
+ windows += 1;
472
+ }
473
+ }
474
+
475
+ return windows > 0 ? quota : null;
476
+ }
477
+
478
+ async function fetchOllamaCloudQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
479
+ const effectiveBaseUrl = config.baseUrl ?? getProviderRegistryEntry(provider)?.baseUrl ?? "";
480
+ if (!isCanonicalOllamaCloudBaseUrl(effectiveBaseUrl)) return null;
481
+ const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
482
+ if (!apiKey) return null;
483
+ const response = await fetch(OLLAMA_CLOUD_USAGE_URL, {
484
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
485
+ redirect: "error",
486
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
487
+ });
488
+ if (!response.ok) {
489
+ if (response.status === 404) return null;
490
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
491
+ ? TERMINAL_QUOTA_FAILURE
492
+ : null;
493
+ }
494
+ const body = asRecord(await readQuotaJson(response));
495
+ const quota = parseOllamaCloudQuota(body);
496
+ return quota ? keyReport(provider, "ollama-cloud:usage", quota, config, apiKey, quota) : null;
497
+ }
498
+
499
+ /**
500
+ * Z.AI GLM Coding Plan `GET /api/monitor/usage/quota/limit` — the coding-plan
501
+ * limits arrive as a `limits` array of `TOKENS_LIMIT` (newer plans call the
502
+ * same rows `CREDIT_LIMIT`) and `TIME_LIMIT` rows. `TOKENS_LIMIT`/`CREDIT_LIMIT`
503
+ * rows carry the window length as `unit`/`number`: unit 3 is hours (number 5 →
504
+ * the rolling five-hour window), unit 6 is weeks (number 1 → the weekly
505
+ * window). Every row's `percentage` is the consumed share (falling
506
+ * back to `currentValue`/`usage` when absent) and `nextResetTime` (unix ms)
507
+ * the window reset.
508
+ *
509
+ * `TIME_LIMIT` rows are deliberately ignored (issue #1168). They are the shared
510
+ * monthly MCP *call* allowance for Web Search / Web Reader / Zread — not a
511
+ * model-token budget — and `ProviderQuota.monthlyPercent` is consumed as a
512
+ * model-capacity signal: `headroomOf()` in `src/oauth/account-quota-rank.ts`
513
+ * takes the MAX across every window, so a user who spent their MCP search
514
+ * allowance would be ranked as having no model capacity left, and the dashboard
515
+ * would draw a full monthly bar for a plan whose model tokens are untouched.
516
+ * A payload carrying only `TIME_LIMIT` rows therefore reports no quota at all,
517
+ * which is the honest answer rather than a fabricated one.
518
+ */
519
+ export function parseZaiQuotaLimits(data: Record<string, unknown> | null): ProviderQuota | null {
520
+ const limits = Array.isArray(data?.limits) ? data.limits as unknown[] : null;
521
+ if (!limits) return null;
522
+ const quota: ProviderQuota = { updatedAt: Date.now() };
523
+ let windows = 0;
524
+ for (const raw of limits) {
525
+ const row = asRecord(raw);
526
+ if (!row) continue;
527
+ // Gate on row type before deriving a percentage: an MCP row must not even
528
+ // contribute a parsed value to a model-quota report.
529
+ if (row.type !== "TOKENS_LIMIT" && row.type !== "CREDIT_LIMIT") continue;
530
+ const resetAt = normalizeResetAt(row.nextResetTime);
531
+ let percent = normalizePercent(row.percentage);
532
+ if (percent === undefined) {
533
+ const used = toFiniteNumber(row.currentValue);
534
+ const total = toFiniteNumber(row.usage);
535
+ if (used !== undefined && total !== undefined && total > 0) {
536
+ percent = normalizePercent((used / total) * 100);
537
+ }
538
+ }
539
+ if (percent === undefined) continue;
540
+ const unit = toFiniteNumber(row.unit);
541
+ const number = toFiniteNumber(row.number);
542
+ if (unit === 3 && number === 5) {
543
+ quota.fiveHourPercent = percent;
544
+ if (resetAt !== undefined) quota.fiveHourResetAt = resetAt;
545
+ windows += 1;
546
+ } else if (unit === 6 && number === 1) {
547
+ quota.weeklyPercent = percent;
548
+ if (resetAt !== undefined) quota.weeklyResetAt = resetAt;
549
+ windows += 1;
550
+ }
551
+ }
552
+ return windows > 0 ? quota : null;
553
+ }
554
+
555
+ /**
556
+ * Legacy Z.AI payload shape: percent fields with window identifiers directly on
557
+ * the data object (optionally nested under `quota`). Kept as a fallback so
558
+ * older responses keep rendering when the `limits` array is absent.
559
+ */
560
+ function parseZaiQuotaLegacyFields(data: Record<string, unknown> | null): ProviderQuota | null {
561
+ if (!data) return null;
562
+ const quota: ProviderQuota = { updatedAt: Date.now() };
563
+ let windows = 0;
564
+ const percentAt = (key: string): number | undefined => {
565
+ const value = normalizePercent(data[key]);
566
+ if (value !== undefined) return value;
567
+ const nested = asRecord(data.quota);
568
+ return nested ? normalizePercent(nested[key]) : undefined;
569
+ };
570
+ const fiveHour = percentAt("fiveHourPercent") ?? percentAt("fiveHourUsage") ?? percentAt("fiveHourUsed");
571
+ const weekly = percentAt("weeklyPercent") ?? percentAt("weeklyUsage") ?? percentAt("weeklyUsed");
572
+ const monthly = percentAt("monthlyPercent") ?? percentAt("mcpPercent") ?? percentAt("monthlyMCPUsage");
573
+ if (fiveHour !== undefined) {
574
+ quota.fiveHourPercent = fiveHour;
575
+ windows += 1;
576
+ }
577
+ if (weekly !== undefined) {
578
+ quota.weeklyPercent = weekly;
579
+ windows += 1;
580
+ }
581
+ if (monthly !== undefined) {
582
+ quota.monthlyPercent = monthly;
583
+ windows += 1;
584
+ }
585
+ return windows > 0 ? quota : null;
586
+ }
587
+
588
+ /**
589
+ * Fetches the Z.AI GLM Coding Plan quota — on whichever region the provider
590
+ * points at (api.z.ai or open.bigmodel.cn). The `limits` array shape is
591
+ * preferred; older field-name payloads fall back to the legacy parser.
592
+ *
593
+ * Authentication differs by host (issue #1168). `api.z.ai` takes the API key as
594
+ * a Bearer token per Z.AI's API reference; `open.bigmodel.cn` expects the key
595
+ * directly in `Authorization` with no scheme prefix and answers a Bearer header
596
+ * with an auth error, which is why BigModel Coding Plan quota never rendered.
597
+ * The host is already canonicalized by `isCanonicalZaiBaseUrl` above and
598
+ * `redirect: "error"` stays set, so the bare key cannot travel to a lookalike
599
+ * host or follow a redirect off-origin.
600
+ */
601
+ async function fetchZaiQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
602
+ const monitorHost = zaiQuotaMonitorHost(config.baseUrl);
603
+ if (!monitorHost) return null;
604
+ const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
605
+ if (!apiKey) return null;
606
+ const authorization = monitorHost === ZAI_CN_BASE_URL ? apiKey : `Bearer ${apiKey}`;
607
+ const response = await fetch(`${monitorHost}/api/monitor/usage/quota/limit`, {
608
+ headers: { Accept: "application/json", Authorization: authorization },
609
+ redirect: "error",
610
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
611
+ });
612
+ if (!response.ok) {
613
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
614
+ ? TERMINAL_QUOTA_FAILURE
615
+ : null;
616
+ }
617
+ const body = asRecord(await readQuotaJson(response));
618
+ if (!body || body.success === false) return null;
619
+ const data = asRecord(body.data) ?? body;
620
+ if (Array.isArray(data?.limits)) {
621
+ const quota = parseZaiQuotaLimits(data);
622
+ // A well-formed `limits[]` we fully understood is authoritative even when it yields no
623
+ // model window — for example a plan reporting only the monthly MCP `TIME_LIMIT` row.
624
+ // Returning `null` here would preserve the previous token windows for up to 30 minutes
625
+ // and keep quota-aware routing acting on a report the provider has already superseded.
626
+ return quota
627
+ ? keyReport(provider, "zai:quota-limit", quota, config, apiKey, quota)
628
+ : AUTHORITATIVE_EMPTY_QUOTA;
629
+ }
630
+ const legacy = parseZaiQuotaLegacyFields(data);
631
+ if (!legacy) return null;
632
+ // The legacy monthly figure also carries MCP usage; it is display evidence, not
633
+ // proof that model inference is unavailable. Modern TOKEN_LIMIT rows above are scoped.
634
+ const inferenceQuota = { ...legacy };
635
+ delete inferenceQuota.monthlyPercent;
636
+ delete inferenceQuota.monthlyResetAt;
637
+ return keyReport(provider, "zai:quota-limit", legacy, config, apiKey, inferenceQuota);
638
+ }
639
+
640
+ /**
641
+ * MiniMax Token Plan `GET /v1/token_plan/remains` — the subscription's
642
+ * remaining quota as a countdown-time value (ms). The endpoint does not expose
643
+ * the plan's total duration, so no percentage is fabricated from a presumed
644
+ * window: the remaining time is reported as a duration-only window. When the
645
+ * API supplies a total (`total_time` / `plan_duration_ms`), a consumed share
646
+ * is derived from it. Region selects the host: `minimax` → www.minimax.io,
647
+ * `minimax-cn` → api.minimaxi.com.
648
+ */
649
+ async function fetchMinimaxQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
650
+ if (!isCanonicalMinimaxBaseUrl(config.baseUrl)) return null;
651
+ const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
652
+ if (!apiKey) return null;
653
+ const cnHost = normalizedBaseUrl(config.baseUrl)?.startsWith("https://api.minimaxi.com");
654
+ const remainsUrl = cnHost ? "https://api.minimaxi.com/v1/token_plan/remains" : MINIMAX_REMAINS_URL;
655
+ const response = await fetch(remainsUrl, {
656
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
657
+ redirect: "error",
658
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
659
+ });
660
+ if (!response.ok) {
661
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
662
+ ? TERMINAL_QUOTA_FAILURE
663
+ : null;
664
+ }
665
+ const body = asRecord(await readQuotaJson(response));
666
+ if (!body || body.success === false) return null;
667
+ const data = asRecord(body.data) ?? body;
668
+ const remainsMs = toFiniteNumber(data.remains_time ?? data.remainsTime);
669
+ if (remainsMs === undefined || remainsMs < 0) return null;
670
+ const hours = Math.floor(remainsMs / 3_600_000);
671
+ const label = `Token Plan remaining (${hours}h)`;
672
+ // Only derive a consumed share when the API actually reports the plan total;
673
+ // a presumed window (e.g. 30 days) would fabricate utilization. A valid
674
+ // response that omits the total after a prior refresh had it is a DELIBERATE
675
+ // contract change — the old row must be dropped (terminal), not preserved as
676
+ // a transient last-good.
677
+ const totalMs = toFiniteNumber(data.total_time ?? data.plan_duration_ms ?? data.total_duration_ms);
678
+ if (totalMs === undefined || totalMs <= 0) return TERMINAL_QUOTA_FAILURE;
679
+ const consumed = Math.max(0, totalMs - remainsMs);
680
+ const percent = normalizePercent((consumed / totalMs) * 100);
681
+ if (percent === undefined) return null;
682
+ return report(provider, "minimax:token-plan-remains", {
683
+ customWindows: [{ label, percent }],
684
+ updatedAt: Date.now(),
685
+ });
686
+ }
687
+
688
+ /**
689
+ * Moonshot/Kimi `GET /v1/users/me/balance` — the account's available balance
690
+ * (voucher + cash). Renders a single balance window against the sum of
691
+ * voucher + cash when positive (there is no per-window rate limit to meter).
692
+ */
693
+ async function fetchMoonshotQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
694
+ if (!isCanonicalMoonshotBaseUrl(config.baseUrl)) return null;
695
+ const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
696
+ if (!apiKey) return null;
697
+ const host = normalizedBaseUrl(config.baseUrl)?.startsWith("https://api.moonshot.cn") ? "https://api.moonshot.cn/v1" : MOONSHOT_BASE_URL;
698
+ const response = await fetch(`${host}/users/me/balance`, {
699
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
700
+ redirect: "error",
701
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
702
+ });
703
+ if (!response.ok) {
704
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
705
+ ? TERMINAL_QUOTA_FAILURE
706
+ : null;
707
+ }
708
+ const body = asRecord(await readQuotaJson(response));
709
+ const data = asRecord(body?.data) ?? body;
710
+ if (!data) return null;
711
+ const available = toFiniteNumber(data.available_balance);
712
+ const voucher = toFiniteNumber(data.voucher_balance);
713
+ const cash = toFiniteNumber(data.cash_balance);
714
+ if (available === undefined || available < 0) return null;
715
+ // Moonshot exposes no per-window quota ceiling, only a balance — report it
716
+ // as a balance-only window (percent 0) rather than a fabricated utilization.
717
+ // Currency is host-scoped: China platform (api.moonshot.cn) bills in CNY;
718
+ // the international platform (api.moonshot.ai) bills in USD. Do not force
719
+ // either side into the other unit — the number is correct, only the unit
720
+ // must match the host.
721
+ const isChinaHost = host.startsWith("https://api.moonshot.cn");
722
+ const money = (n: number) => isChinaHost ? `¥${n.toFixed(2)}` : `$${n.toFixed(2)}`;
723
+ const unit = isChinaHost ? "CNY" : "USD";
724
+ const label = voucher !== undefined && cash !== undefined
725
+ ? `Balance (${money(available)} ${unit} available, ${money(voucher)} voucher)`
726
+ : `Balance (${money(available)} ${unit} available)`;
727
+ return report(provider, "moonshot:balance", {
728
+ customWindows: [{ label, percent: 0 }],
729
+ updatedAt: Date.now(),
730
+ });
731
+ }
732
+
733
+ /**
734
+ * Venice `GET /api/v1/billing/balance` — DIEM (native credits) or USD balance.
735
+ * Shows the remaining balance; epoch allocation progress when present.
736
+ */
737
+ async function fetchVeniceQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
738
+ if (!isCanonicalVeniceBaseUrl(config.baseUrl)) return null;
739
+ const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
740
+ if (!apiKey) return null;
741
+ const response = await fetch(`${VENICE_BASE_URL}/billing/balance`, {
742
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
743
+ redirect: "error",
744
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
745
+ });
746
+ if (!response.ok) {
747
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
748
+ ? TERMINAL_QUOTA_FAILURE
749
+ : null;
750
+ }
751
+ const body = asRecord(await readQuotaJson(response));
752
+ const data = asRecord(body?.data) ?? body;
753
+ if (!data) return null;
754
+ const diemBalance = toFiniteNumber(data.balance);
755
+ const usdBalance = toFiniteNumber(data.balance_usd);
756
+ const epochUsed = toFiniteNumber(data.diem_epoch_used);
757
+ const epochAllocated = toFiniteNumber(data.diem_epoch_allocated);
758
+ if (diemBalance === undefined && usdBalance === undefined) return null;
759
+ const label = diemBalance !== undefined
760
+ ? `DIEM balance (${Math.round(diemBalance)})`
761
+ : `USD balance ($${usdBalance?.toFixed(2) ?? "?"})`;
762
+ if (epochAllocated !== undefined && epochAllocated > 0 && epochUsed !== undefined) {
763
+ const percent = normalizePercent((epochUsed / epochAllocated) * 100);
764
+ if (percent === undefined) return null;
765
+ return report(provider, "venice:billing-balance", {
766
+ customWindows: [{ label, percent }],
767
+ updatedAt: Date.now(),
768
+ });
769
+ }
770
+ return report(provider, "venice:billing-balance", {
771
+ customWindows: [{ label, percent: 0 }],
772
+ updatedAt: Date.now(),
773
+ });
774
+ }
775
+
776
+ /**
777
+ * Synthetic `GET /v2/quotas` — the known quota lanes (rolling 5-hour,
778
+ * weekly token, search-hourly) mapped onto the quota windows.
779
+ */
780
+ async function fetchSyntheticQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
781
+ if (!isCanonicalSyntheticBaseUrl(config.baseUrl)) return null;
782
+ const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
783
+ if (!apiKey) return null;
784
+ const response = await fetch(`${SYNTHETIC_BASE_URL}/quotas`, {
785
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
786
+ redirect: "error",
787
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
788
+ });
789
+ if (!response.ok) {
790
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
791
+ ? TERMINAL_QUOTA_FAILURE
792
+ : null;
793
+ }
794
+ const body = asRecord(await readQuotaJson(response));
795
+ const data = asRecord(body?.data) ?? body;
796
+ const quota: ProviderQuota = { updatedAt: Date.now() };
797
+ let windows = 0;
798
+ const percentAt = (key: string): number | undefined => {
799
+ const value = normalizePercent(data?.[key]);
800
+ if (value !== undefined) return value;
801
+ const nested = asRecord(data?.quota) ?? asRecord(data?.quotas);
802
+ return nested ? normalizePercent(nested[key]) : undefined;
803
+ };
804
+ const fiveHour = percentAt("rollingFiveHourLimit");
805
+ const weekly = percentAt("weeklyTokenLimit");
806
+ if (fiveHour !== undefined) {
807
+ quota.fiveHourPercent = fiveHour;
808
+ windows += 1;
809
+ }
810
+ if (weekly !== undefined) {
811
+ quota.weeklyPercent = weekly;
812
+ windows += 1;
813
+ }
814
+ const search = asRecord(data?.search);
815
+ const searchHourly = search ? normalizePercent(search.hourly) : undefined;
816
+ if (searchHourly !== undefined) {
817
+ quota.customWindows = [...(quota.customWindows ?? []), { label: "Search hourly", percent: searchHourly }];
818
+ windows += 1;
819
+ }
820
+ const inferenceQuota = { ...quota };
821
+ delete inferenceQuota.customWindows; // search.hourly does not constrain model inference.
822
+ return windows > 0 ? keyReport(provider, "synthetic:quotas", quota, config, apiKey, inferenceQuota) : null;
823
+ }
824
+
825
+ /**
826
+ * DeepInfra `GET /payment/checklist?compute_owed=true` — prepaid balance,
827
+ * recent spend, spending limit, and suspension state. Renders a balance
828
+ * window (prepaid funds are a negative `stripe_balance` → positive available).
829
+ */
830
+ async function fetchDeepInfraQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
831
+ if (!isCanonicalDeepInfraBaseUrl(config.baseUrl)) return null;
832
+ const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
833
+ if (!apiKey) return null;
834
+ const response = await fetch(`${DEEPINFRA_BASE_URL}/payment/checklist?compute_owed=true`, {
835
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
836
+ redirect: "error",
837
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
838
+ });
839
+ if (!response.ok) {
840
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
841
+ ? TERMINAL_QUOTA_FAILURE
842
+ : null;
843
+ }
844
+ const body = asRecord(await readQuotaJson(response));
845
+ const data = asRecord(body?.data) ?? body;
846
+ if (!data) return null;
847
+ const stripeBalance = toFiniteNumber(data.stripe_balance);
848
+ const spendLimit = toFiniteNumber(data.spending_limit);
849
+ const total = toFiniteNumber(data.total_amount_due);
850
+ if (stripeBalance === undefined) return null;
851
+ // Prepaid funds are negative; a positive value is money owed.
852
+ const available = stripeBalance < 0 ? -stripeBalance : 0;
853
+ if (spendLimit !== undefined && spendLimit > 0) {
854
+ const spent = total !== undefined && total > 0 ? total : Math.max(0, spendLimit - available);
855
+ const percent = normalizePercent((spent / spendLimit) * 100);
856
+ if (percent === undefined) return null;
857
+ return report(provider, "deepinfra:billing-checklist", {
858
+ customWindows: [{ label: `Billing cycle spend ($${spent.toFixed(2)} of $${spendLimit.toFixed(2)})`, percent }],
859
+ updatedAt: Date.now(),
860
+ });
861
+ }
862
+ return report(provider, "deepinfra:billing-checklist", {
863
+ customWindows: [{ label: `Prepaid balance ($${available.toFixed(2)})`, percent: 0 }],
864
+ updatedAt: Date.now(),
865
+ });
866
+ }
867
+
868
+ /**
869
+ * Neuralwatt `GET /v1/quota` — subscription kWh usage (primary window) and
870
+ * prepaid USD credit balance (secondary).
871
+ */
872
+ async function fetchNeuralwattQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
873
+ if (!isCanonicalNeuralwattBaseUrl(config.baseUrl)) return null;
874
+ const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
875
+ if (!apiKey) return null;
876
+ const response = await fetch(`${NEURALWATT_BASE_URL}/quota`, {
877
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
878
+ redirect: "error",
879
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
880
+ });
881
+ if (!response.ok) {
882
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
883
+ ? TERMINAL_QUOTA_FAILURE
884
+ : null;
885
+ }
886
+ const body = asRecord(await readQuotaJson(response));
887
+ const data = asRecord(body?.data) ?? body;
888
+ const quota: ProviderQuota = { updatedAt: Date.now() };
889
+ let windows = 0;
890
+ const subscription = asRecord(data?.subscription);
891
+ const kwhUsed = subscription ? toFiniteNumber(subscription.kwh_used) : undefined;
892
+ const kwhIncluded = subscription ? toFiniteNumber(subscription.kwh_included) : undefined;
893
+ if (kwhUsed !== undefined && kwhIncluded !== undefined && kwhIncluded > 0) {
894
+ const percent = normalizePercent((kwhUsed / kwhIncluded) * 100);
895
+ if (percent !== undefined) {
896
+ quota.fiveHourPercent = percent;
897
+ const periodEnd = subscription ? normalizeResetAt(subscription.current_period_end) : undefined;
898
+ if (periodEnd !== undefined) quota.fiveHourResetAt = periodEnd;
899
+ windows += 1;
900
+ }
901
+ }
902
+ const balance = asRecord(data?.balance);
903
+ const totalCredits = balance ? toFiniteNumber(balance.total_credits_usd) : undefined;
904
+ const remainingCredits = balance ? toFiniteNumber(balance.credits_remaining_usd) : undefined;
905
+ if (totalCredits !== undefined && totalCredits > 0 && remainingCredits !== undefined) {
906
+ // Utilization is CONSUMED credits, not the remaining share.
907
+ const used = Math.max(0, totalCredits - remainingCredits);
908
+ const percent = normalizePercent((used / totalCredits) * 100);
909
+ if (percent !== undefined) {
910
+ quota.customWindows = [...(quota.customWindows ?? []), { label: "Prepaid credits", percent }];
911
+ windows += 1;
912
+ }
913
+ }
914
+ return windows > 0 ? report(provider, "neuralwatt:quota", quota) : null;
915
+ }
916
+
917
+
918
+ function normalizedBaseUrl(value: string): string | null {
919
+ try {
920
+ const url = new URL(value);
921
+ if (url.username || url.password || url.search || url.hash) return null;
922
+ return `${url.origin.toLowerCase()}${url.pathname.replace(/\/+$/, "")}`;
923
+ } catch {
924
+ return null;
925
+ }
926
+ }
927
+
928
+ function quotaResetAt(row: Record<string, unknown>): number | undefined {
929
+ return normalizeResetAt(row.resetTime ?? row.resetAt ?? row.reset_time ?? row.reset_at);
930
+ }
931
+
932
+ export function isCanonicalKimiCodeBaseUrl(baseUrl: string): boolean {
933
+ return normalizedBaseUrl(baseUrl) === KIMI_CODE_BASE_URL;
934
+ }
935
+
936
+ export function isCanonicalCommandCodeBaseUrl(baseUrl: string): boolean {
937
+ const normalized = normalizedBaseUrl(baseUrl);
938
+ // OAuth preset points at the API root; the Provider-API preset at /provider/v1.
939
+ return normalized === COMMAND_CODE_BASE_URL || normalized === `${COMMAND_CODE_BASE_URL}/provider/v1`;
940
+ }
941
+
942
+ /** Prefer the nested `data` shell when the outer object is only an envelope. */
943
+ function unwrapKimiQuotaPayload(value: unknown): Record<string, unknown> | null {
944
+ const body = asRecord(value);
945
+ if (!body) return null;
946
+ const nested = asRecord(body.data);
947
+ if (!nested) return body;
948
+ // A null/non-usable outer field is a placeholder, not data — an envelope like
949
+ // { usage: null, data: { usage: {...} } } must still unwrap to the nested payload.
950
+ const usable = (field: unknown): boolean => field !== undefined && field !== null;
951
+ const outerHasUsage = usable(body.usage) || usable(body.limits) || usable(body.totalQuota);
952
+ const nestedHasUsage = usable(nested.usage) || usable(nested.limits) || usable(nested.totalQuota);
953
+ return !outerHasUsage && nestedHasUsage ? nested : body;
954
+ }
955
+
956
+ function kimiLimitLabel(item: Record<string, unknown>, detail: Record<string, unknown>): string {
957
+ return [item.name, item.title, item.scope, detail.name, detail.title]
958
+ .filter((value): value is string => typeof value === "string")
959
+ .join(" ")
960
+ .toLowerCase();
961
+ }
962
+
963
+ function parseKimiQuotaRow(value: unknown, resetFallback?: Record<string, unknown>): { percent: number; resetAt?: number } | null {
964
+ const row = asRecord(value);
965
+ if (!row) return null;
966
+ const resetAt = quotaResetAt(row) ?? (resetFallback ? quotaResetAt(resetFallback) : undefined);
967
+ const limit = toFiniteNumber(row.limit);
968
+ if (limit !== undefined && limit > 0) {
969
+ let used = toFiniteNumber(row.used);
970
+ if (used === undefined) {
971
+ const remaining = toFiniteNumber(row.remaining);
972
+ if (remaining !== undefined) used = limit - remaining;
973
+ }
974
+ if (used !== undefined) {
975
+ const percent = normalizePercent((used / limit) * 100);
976
+ if (percent !== undefined) return { percent, ...(resetAt !== undefined ? { resetAt } : {}) };
977
+ }
978
+ }
979
+ // Some payloads expose utilisation directly when limit/used arithmetic is absent.
980
+ const direct = normalizePercent(row.utilization ?? row.percent ?? row.usedPercent ?? row.used_percent);
981
+ return direct === undefined ? null : { percent: direct, ...(resetAt !== undefined ? { resetAt } : {}) };
982
+ }
983
+
984
+ function isKimiFiveHourLimit(item: Record<string, unknown>, detail: Record<string, unknown>, window: Record<string, unknown>): boolean {
985
+ const duration = toFiniteNumber(window.duration ?? item.duration ?? detail.duration);
986
+ const unit = String(window.timeUnit ?? item.timeUnit ?? detail.timeUnit ?? "").toUpperCase();
987
+ if ((unit.includes("MINUTE") && duration === 300) || (unit.includes("HOUR") && duration === 5)) return true;
988
+ return /(^|\b)5\s*(?:h|hour)/.test(kimiLimitLabel(item, detail));
989
+ }
990
+
991
+ function isKimiWeeklyLimit(item: Record<string, unknown>, detail: Record<string, unknown>, window: Record<string, unknown>): boolean {
992
+ const duration = toFiniteNumber(window.duration ?? item.duration ?? detail.duration);
993
+ const unit = String(window.timeUnit ?? item.timeUnit ?? detail.timeUnit ?? "").toUpperCase();
994
+ if ((unit.includes("DAY") && duration === 7) || (unit.includes("HOUR") && duration === 168)) return true;
995
+ return /weekly|7\s*(?:d|day)/.test(kimiLimitLabel(item, detail));
996
+ }
997
+
998
+ function parseKimiQuotaPayload(value: unknown): ProviderQuota | null {
999
+ const body = unwrapKimiQuotaPayload(value);
1000
+ if (!body) return null;
1001
+ let weekly = parseKimiQuotaRow(body.usage);
1002
+ const total = parseKimiQuotaRow(body.totalQuota);
1003
+ let fiveHour: { percent: number; resetAt?: number } | null = null;
1004
+ if (Array.isArray(body.limits)) {
1005
+ for (const rawItem of body.limits) {
1006
+ const item = asRecord(rawItem);
1007
+ if (!item) continue;
1008
+ const detail = asRecord(item.detail) ?? item;
1009
+ const window = asRecord(item.window) ?? {};
1010
+ if (!fiveHour && isKimiFiveHourLimit(item, detail, window)) {
1011
+ fiveHour = parseKimiQuotaRow(detail, window);
1012
+ }
1013
+ if (!weekly && isKimiWeeklyLimit(item, detail, window)) {
1014
+ weekly = parseKimiQuotaRow(detail, window);
1015
+ }
1016
+ if (fiveHour && weekly) break;
1017
+ }
1018
+ }
1019
+ const quota: ProviderQuota = {
1020
+ ...(fiveHour ? {
1021
+ fiveHourPercent: fiveHour.percent,
1022
+ ...(fiveHour.resetAt !== undefined ? { fiveHourResetAt: fiveHour.resetAt } : {}),
1023
+ } : {}),
1024
+ ...(weekly ? {
1025
+ weeklyPercent: weekly.percent,
1026
+ ...(weekly.resetAt !== undefined ? { weeklyResetAt: weekly.resetAt } : {}),
1027
+ } : {}),
1028
+ ...(total ? { customWindows: [{ label: "Total subscription credits", percent: total.percent, ...(total.resetAt !== undefined ? { resetAt: total.resetAt } : {}) }] } : {}),
1029
+ updatedAt: Date.now(),
1030
+ };
1031
+ return hasQuotaRows(quota) ? quota : null;
1032
+ }
1033
+
1034
+ async function resolveKimiQuotaBearer(config: OcxProviderConfig, accountId?: string): Promise<string | null> {
1035
+ if (config.authMode === "oauth") {
1036
+ try {
1037
+ return accountId ? await getTokenForAccountQuotaProbe("kimi", accountId) : null;
1038
+ } catch {
1039
+ return null;
1040
+ }
1041
+ }
1042
+ // ACTIVE key only: silently walking apiKeyPool when the primary env reference is
1043
+ // unresolved would render a quota bar for a DIFFERENT account than the one routing
1044
+ // requests — a wrong meter is worse than no meter.
1045
+ const primary = resolveProviderApiKey(config.apiKey)?.trim();
1046
+ return primary || null;
1047
+ }
1048
+
1049
+ export async function fetchKimiQuota(provider: string, config: OcxProviderConfig, accessToken: string): Promise<ProviderQuotaReport | null> {
1050
+ // Never release credentials to a user-edited or lookalike provider host.
1051
+ if (!isCanonicalKimiCodeBaseUrl(config.baseUrl)) return null;
1052
+ if (!accessToken) return null;
1053
+ const response = await fetch(KIMI_CODE_USAGE_URL, {
1054
+ headers: { Accept: "application/json", Authorization: `Bearer ${accessToken}` },
1055
+ redirect: "error",
1056
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
1057
+ });
1058
+ if (!response.ok) return null;
1059
+ const quota = parseKimiQuotaPayload(await readQuotaJson(response));
1060
+ return quota ? keyReport(provider, "kimi:usages", quota, config, accessToken, quota) : null;
1061
+ }
1062
+
1063
+ /**
1064
+ * Command Code rolling window: `{ cap, used, resetAt }` off /alpha/billing/credits,
1065
+ * normalized to a percent with an optional reset timestamp.
1066
+ */
1067
+ function parseCommandCodeWindow(value: unknown): { percent: number; resetAt?: number } | null {
1068
+ const row = asRecord(value);
1069
+ if (!row) return null;
1070
+ const cap = toFiniteNumber(row.cap);
1071
+ const used = toFiniteNumber(row.used);
1072
+ if (cap === undefined || used === undefined || cap <= 0 || used < 0) return null;
1073
+ const percent = normalizePercent((used / cap) * 100);
1074
+ if (percent === undefined) return null;
1075
+ const resetAt = quotaResetAt(row);
1076
+ return { percent, ...(resetAt !== undefined ? { resetAt } : {}) };
1077
+ }
1078
+
1079
+ /** Soft-fail GET returning a parsed record, or null when unavailable. */
1080
+ async function fetchCommandCodeJson(url: string, bearer: string): Promise<Record<string, unknown> | null> {
1081
+ try {
1082
+ const response = await fetch(url, {
1083
+ headers: { Accept: "application/json", Authorization: `Bearer ${bearer}` },
1084
+ redirect: "error",
1085
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
1086
+ });
1087
+ if (!response.ok) return null;
1088
+ return asRecord(await readQuotaJson(response));
1089
+ } catch {
1090
+ return null;
1091
+ }
1092
+ }
1093
+
1094
+ /**
1095
+ * Soft-fail period spend (used) against the remaining credit pools → creditsUsd.
1096
+ * Period scoping: `since=<currentPeriodStart>` keeps spend aligned with the
1097
+ * pools' billing cycle, and `currentPeriodEnd` becomes expiresAt.
1098
+ */
1099
+ async function fetchCommandCodeSpend(
1100
+ bearer: string,
1101
+ credits: Record<string, unknown> | null,
1102
+ orgQuery: string,
1103
+ ): Promise<ProviderQuotaCreditsUsd | undefined> {
1104
+ if (!credits) return undefined;
1105
+ const subscriptionBody = await fetchCommandCodeJson(`${COMMAND_CODE_SUBSCRIPTIONS_URL}${orgQuery}`, bearer);
1106
+ const subscription = asRecord(subscriptionBody?.data) ?? subscriptionBody;
1107
+ const periodStart = typeof subscription?.currentPeriodStart === "string" ? subscription.currentPeriodStart.trim() : "";
1108
+ // Unscoped /usage/summary is lifetime spend; mixing it with current-cycle
1109
+ // remaining pools produces a wrong percent. Omit creditsUsd until a period exists.
1110
+ if (!periodStart) return undefined;
1111
+ const sinceQuery = `${orgQuery ? "&" : "?"}since=${encodeURIComponent(periodStart)}`;
1112
+ const expiresAt = normalizeResetAt(subscription?.currentPeriodEnd);
1113
+ const summaryBody = await fetchCommandCodeJson(`${COMMAND_CODE_USAGE_URL}${orgQuery}${sinceQuery}`, bearer);
1114
+ const summary = asRecord(summaryBody?.data) ?? summaryBody;
1115
+ const used = toFiniteNumber(summary?.totalCost) ?? toFiniteNumber(summary?.totalMonthlyCredits);
1116
+ if (used === undefined || used < 0) return undefined;
1117
+ const pools = [credits.monthlyCredits, credits.purchasedCredits, credits.freeCredits]
1118
+ .map(value => toFiniteNumber(value))
1119
+ .filter((value): value is number => value !== undefined);
1120
+ // Field presence is what separates a real balance from absent data: an exhausted
1121
+ // all-zero account still reports remaining=0, while no remaining-credit field at
1122
+ // all means there is nothing to meter.
1123
+ if (pools.length === 0) return undefined;
1124
+ const remaining = pools.reduce((sum, value) => sum + Math.max(0, value ?? 0), 0);
1125
+ const limit = used + remaining;
1126
+ const percent = normalizePercent(limit > 0 ? (used / limit) * 100 : 0);
1127
+ // Purchased credits roll over past the subscription period end, so an expiry is
1128
+ // only truthful when the aggregate contains no non-expiring purchased pool.
1129
+ const purchased = toFiniteNumber(credits.purchasedCredits) ?? 0;
1130
+ return percent === undefined
1131
+ ? undefined
1132
+ : {
1133
+ used,
1134
+ limit,
1135
+ remaining,
1136
+ percent,
1137
+ ...(expiresAt !== undefined && purchased <= 0 ? { expiresAt } : {}),
1138
+ };
1139
+ }
1140
+
1141
+ /** OAuth access token or ACTIVE Provider-API key for the Command Code quota probe. */
1142
+ async function resolveCommandCodeQuotaBearer(config: OcxProviderConfig, accountId?: string): Promise<string | null> {
1143
+ if (config.authMode === "oauth") {
1144
+ try {
1145
+ return accountId ? await getTokenForAccountQuotaProbe("command-code", accountId) : null;
1146
+ } catch {
1147
+ return null;
1148
+ }
1149
+ }
1150
+ // ACTIVE key only: a quota bar for a different account than the one routing
1151
+ // requests is a wrong meter, not a helpful one.
1152
+ return resolveProviderApiKey(config.apiKey)?.trim() || null;
1153
+ }
1154
+
1155
+ /**
1156
+ * Command Code `GET /alpha/billing/credits` — the same Bearer surface the CLI's
1157
+ * usage view uses (windowLimits.fiveHour / windowLimits.weekly), plus soft
1158
+ * whoami (team orgId scoping) and subscription-scoped spend for creditsUsd.
1159
+ */
1160
+ export async function fetchCommandCodeQuota(provider: string, config: OcxProviderConfig, bearer: string): Promise<ProviderQuotaProbeResult> {
1161
+ // Never release credentials to a user-edited or lookalike provider host.
1162
+ if (!isCanonicalCommandCodeBaseUrl(config.baseUrl)) return null;
1163
+ if (!bearer) return null;
1164
+ const whoamiBody = await fetchCommandCodeJson(COMMAND_CODE_WHOAMI_URL, bearer);
1165
+ const whoami = asRecord(whoamiBody?.data) ?? whoamiBody;
1166
+ const org = asRecord(whoami?.org);
1167
+ const orgId = typeof org?.id === "string" && org.id.trim() ? org.id.trim() : null;
1168
+ const orgQuery = orgId ? `?orgId=${encodeURIComponent(orgId)}` : "";
1169
+ const response = await fetch(`${COMMAND_CODE_CREDITS_URL}${orgQuery}`, {
1170
+ headers: { Accept: "application/json", Authorization: `Bearer ${bearer}` },
1171
+ redirect: "error",
1172
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
1173
+ });
1174
+ if (!response.ok) {
1175
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
1176
+ ? TERMINAL_QUOTA_FAILURE
1177
+ : null;
1178
+ }
1179
+ const raw = asRecord(await readQuotaJson(response));
1180
+ const body = asRecord(raw?.data) ?? raw;
1181
+ const credits = asRecord(body?.credits);
1182
+ const limits = asRecord(body?.windowLimits);
1183
+ if (!credits && !limits) return null;
1184
+ const fiveHour = parseCommandCodeWindow(limits?.fiveHour);
1185
+ const weekly = parseCommandCodeWindow(limits?.weekly);
1186
+ const creditsUsd = await fetchCommandCodeSpend(bearer, credits, orgQuery);
1187
+ const quota: ProviderQuota = {
1188
+ ...(fiveHour ? {
1189
+ fiveHourPercent: fiveHour.percent,
1190
+ ...(fiveHour.resetAt !== undefined ? { fiveHourResetAt: fiveHour.resetAt } : {}),
1191
+ } : {}),
1192
+ ...(weekly ? {
1193
+ weeklyPercent: weekly.percent,
1194
+ ...(weekly.resetAt !== undefined ? { weeklyResetAt: weekly.resetAt } : {}),
1195
+ } : {}),
1196
+ ...(creditsUsd ? { creditsUsd } : {}),
1197
+ updatedAt: Date.now(),
1198
+ };
1199
+ // Rolling windows and the credit balance both gate inference on this bearer.
1200
+ return keyReport(provider, "command-code:credits", quota, config, bearer, quota);
1201
+ }
1202
+
1203
+
1204
+ type KeyQuotaReader = (name: string, provider: OcxProviderConfig) => Promise<ProviderQuotaProbeResult>;
1205
+
1206
+ /** Same selector drives cheap capabilities and uncached reads; never resolves credentials. */
1207
+ export function keyQuotaReaderForProvider(name: string, provider: OcxProviderConfig): KeyQuotaReader | null {
1208
+ if (provider.disabled === true || (provider.authMode ?? "key") !== "key") return null;
1209
+ if (isCanonicalKimiCodeBaseUrl(provider.baseUrl)) {
1210
+ return async (id, config) => {
1211
+ const bearer = await resolveKimiQuotaBearer(config);
1212
+ return bearer ? fetchKimiQuota(id, config, bearer) : null;
1213
+ };
1214
+ }
1215
+ if (name === "commandcode" && isCanonicalCommandCodeBaseUrl(provider.baseUrl)) {
1216
+ return async (id, config) => {
1217
+ const bearer = await resolveCommandCodeQuotaBearer(config);
1218
+ return bearer ? fetchCommandCodeQuota(id, config, bearer) : null;
1219
+ };
1220
+ }
1221
+ if (registryEntryForProviderDestination(provider)?.id === "opencode-go") return fetchOpenCodeGoQuota;
1222
+ if (isCanonicalA6apiBaseUrl(provider.baseUrl)) return fetchA6apiQuota;
1223
+ if (name === "openrouter" && isCanonicalOpenRouterBaseUrl(provider.baseUrl)) return fetchOpenRouterQuota;
1224
+ if (name === "deepseek" && isCanonicalDeepSeekBaseUrl(provider.baseUrl)) return fetchDeepSeekQuota;
1225
+ if (name === "cline-pass" && isCanonicalClineBaseUrl(provider.baseUrl)) return fetchClineQuota;
1226
+ if (isCanonicalOllamaCloudBaseUrl(provider.baseUrl ?? getProviderRegistryEntry(name)?.baseUrl)) return fetchOllamaCloudQuota;
1227
+ // #4201: the Responses preset is the same domestic GLM Coding Plan subscription on the OpenAI
1228
+ // Responses wire, so it reads the same monitor endpoint. Eligibility stays a name list AND the
1229
+ // canonical-URL guard: the guard is what keeps BigModel's bare-key Authorization from reaching a
1230
+ // lookalike host, so a same-named custom destination still dispatches nothing.
1231
+ if (["zai", "glm", "glm-cn", "zhipu-bigmodel-coding", "zhipu-bigmodel-responses"].includes(name) && isCanonicalZaiBaseUrl(provider.baseUrl)) return fetchZaiQuota;
1232
+ if (["minimax", "minimax-cn"].includes(name) && isCanonicalMinimaxBaseUrl(provider.baseUrl)) return fetchMinimaxQuota;
1233
+ if (name === "moonshot" && isCanonicalMoonshotBaseUrl(provider.baseUrl)) return fetchMoonshotQuota;
1234
+ if (name === "venice" && isCanonicalVeniceBaseUrl(provider.baseUrl)) return fetchVeniceQuota;
1235
+ if (name === "synthetic" && isCanonicalSyntheticBaseUrl(provider.baseUrl)) return fetchSyntheticQuota;
1236
+ if (name === "deepinfra" && isCanonicalDeepInfraBaseUrl(provider.baseUrl)) return fetchDeepInfraQuota;
1237
+ if (name === "neuralwatt" && isCanonicalNeuralwattBaseUrl(provider.baseUrl)) return fetchNeuralwattQuota;
1238
+ return null;
1239
+ }
1240
+
1241
+ export function providerApiKeyQuotaMode(name: string, provider: OcxProviderConfig): AccountQuotaMode {
1242
+ return keyQuotaReaderForProvider(name, provider) ? "probe" : "unsupported";
1243
+ }