@bitkyc08/opencodex 2.41.0 → 2.43.0-preview.20260906

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 (260) hide show
  1. package/AGENTS_INSTALL.md +2 -2
  2. package/README.md +31 -0
  3. package/bin/ocx.mjs +10 -1
  4. package/gui/dist/assets/index-DS1NE4Jn.css +1 -0
  5. package/gui/dist/assets/index-VGeQEZ_v.js +112 -0
  6. package/gui/dist/index.html +2 -2
  7. package/package.json +1 -1
  8. package/src/adapters/anthropic-image-codec.ts +304 -0
  9. package/src/adapters/anthropic-image-normalize.ts +8 -298
  10. package/src/adapters/anthropic.ts +30 -7
  11. package/src/adapters/command-code.ts +7 -5
  12. package/src/adapters/cursor/desktop-executor-contract.ts +15 -0
  13. package/src/adapters/cursor/images.ts +36 -6
  14. package/src/adapters/cursor/live-transport.ts +7 -2
  15. package/src/adapters/cursor/native-exec-desktop.ts +2 -15
  16. package/src/adapters/cursor/protobuf-request.ts +54 -24
  17. package/src/adapters/cursor/tool-definitions.ts +5 -670
  18. package/src/adapters/cursor/tool-guidance.ts +236 -0
  19. package/src/adapters/cursor/tool-naming.ts +252 -0
  20. package/src/adapters/cursor/tool-schemas.ts +195 -0
  21. package/src/adapters/cursor/types.ts +6 -3
  22. package/src/adapters/exec-tool-result-normalize.ts +1 -1
  23. package/src/adapters/google-errors.ts +9 -1
  24. package/src/adapters/google.ts +1 -0
  25. package/src/adapters/identity.ts +8 -2
  26. package/src/adapters/kiro-calibration.ts +181 -0
  27. package/src/adapters/kiro.ts +135 -3
  28. package/src/adapters/openai-responses.ts +259 -29
  29. package/src/adapters/responses-code-mode.ts +59 -0
  30. package/src/adapters/tool-catalog-nudge.ts +1 -1
  31. package/src/adapters/xai-schema-analysis.ts +86 -0
  32. package/src/adapters/xai-tool-schema.ts +2 -87
  33. package/src/adapters/xai-web-search.ts +1 -1
  34. package/src/bridge.ts +47 -13
  35. package/src/chat/inbound.ts +11 -3
  36. package/src/claude/inbound-content-options.ts +60 -0
  37. package/src/claude/inbound-model-options.ts +142 -0
  38. package/src/claude/inbound-records.ts +7 -0
  39. package/src/claude/inbound.ts +10 -202
  40. package/src/claude/model-info.ts +45 -0
  41. package/src/cli/account-auth.ts +49 -9
  42. package/src/cli/account-extended.ts +7 -1
  43. package/src/cli/capabilities.ts +15 -4
  44. package/src/cli/claude.ts +232 -39
  45. package/src/cli/config-command.ts +9 -1
  46. package/src/cli/dispatch.ts +5 -1
  47. package/src/cli/doctor.ts +10 -0
  48. package/src/cli/effort.ts +372 -0
  49. package/src/cli/export-command.ts +3 -9
  50. package/src/cli/help.ts +1 -0
  51. package/src/cli/index.ts +13 -0
  52. package/src/cli/init.ts +4 -0
  53. package/src/cli/model-selection-guidance.ts +30 -0
  54. package/src/cli/models-runtime.ts +3 -2
  55. package/src/cli/models.ts +8 -3
  56. package/src/cli/observe.ts +3 -1
  57. package/src/cli/opencode.ts +4 -1
  58. package/src/cli/provider-runtime.ts +65 -0
  59. package/src/cli/provider.ts +8 -0
  60. package/src/cli/registry.ts +16 -2
  61. package/src/cli/runtime-api.ts +3 -1
  62. package/src/cli/star-prompt.ts +22 -6
  63. package/src/cli/status-probes.ts +168 -0
  64. package/src/cli/status.ts +5 -168
  65. package/src/clients/config-export/constants.ts +69 -0
  66. package/src/clients/config-export/contracts.ts +154 -0
  67. package/src/clients/config-export/dsh.ts +132 -0
  68. package/src/clients/config-export/fast-models.ts +29 -0
  69. package/src/clients/config-export/mcode.ts +83 -0
  70. package/src/clients/config-export/model-metadata.ts +108 -0
  71. package/src/clients/config-export/omp.ts +104 -0
  72. package/src/clients/config-export/zcode.ts +92 -0
  73. package/src/clients/config-export.ts +18 -710
  74. package/src/codex/account-lifecycle.ts +20 -3
  75. package/src/codex/account-usability.ts +2 -0
  76. package/src/codex/auth-api.ts +272 -32
  77. package/src/codex/auth-context.ts +328 -24
  78. package/src/codex/catalog/effort.ts +44 -5
  79. package/src/codex/catalog/metadata.ts +149 -14
  80. package/src/codex/catalog/native-models.ts +116 -4
  81. package/src/codex/catalog/parsing.ts +122 -8
  82. package/src/codex/catalog/provider-fetch.ts +154 -23
  83. package/src/codex/catalog/reserve.ts +52 -0
  84. package/src/codex/catalog/sync.ts +89 -16
  85. package/src/codex/catalog.ts +1 -1
  86. package/src/codex/convergence-types.ts +1 -0
  87. package/src/codex/convergence.ts +2 -0
  88. package/src/codex/data/upstream-models.json +169 -0
  89. package/src/codex/forward-transport-headers.ts +25 -0
  90. package/src/codex/inject.ts +99 -34
  91. package/src/codex/injected-marker.ts +30 -4
  92. package/src/codex/journal.ts +14 -0
  93. package/src/codex/legacy-config-keys.ts +68 -0
  94. package/src/codex/log-guard/inspect-schema.ts +137 -0
  95. package/src/codex/log-guard/inspect.ts +2 -134
  96. package/src/codex/loopback-target.ts +54 -0
  97. package/src/codex/main-account-cache.ts +63 -1
  98. package/src/codex/main-account-hard-lock.ts +52 -0
  99. package/src/codex/main-account.ts +3 -1
  100. package/src/codex/management-convergence.ts +3 -0
  101. package/src/codex/model-entitlements.ts +54 -4
  102. package/src/codex/project-config-warnings.ts +92 -2
  103. package/src/codex/prompt-layers/encoding.ts +80 -0
  104. package/src/codex/prompt-layers/paths.ts +54 -0
  105. package/src/codex/prompt-layers/revision.ts +55 -0
  106. package/src/codex/prompt-layers/toml-edit.ts +163 -0
  107. package/src/codex/prompt-layers/toml-read.ts +181 -0
  108. package/src/codex/prompt-layers.ts +14 -520
  109. package/src/codex/quota-auto-refresh-state.ts +16 -0
  110. package/src/codex/quota-auto-refresh.ts +219 -0
  111. package/src/codex/quota-types.ts +51 -0
  112. package/src/codex/quota.ts +252 -93
  113. package/src/codex/reserve-availability.ts +177 -0
  114. package/src/codex/routing.ts +28 -9
  115. package/src/codex/shim.ts +53 -11
  116. package/src/codex/subagent-model-fallback.ts +23 -3
  117. package/src/combos/failover.ts +125 -7
  118. package/src/combos/identifiers.ts +89 -0
  119. package/src/combos/index.ts +4 -0
  120. package/src/combos/resolve.ts +80 -9
  121. package/src/combos/types.ts +20 -93
  122. package/src/config/subagent-models.ts +24 -0
  123. package/src/config.ts +156 -13
  124. package/src/generated/compatibility-version.json +474 -178
  125. package/src/generated/model-metadata.ts +1 -1
  126. package/src/integrations/journal.ts +65 -4
  127. package/src/integrations/store.ts +5 -0
  128. package/src/lab/events/limits.ts +4 -0
  129. package/src/lib/destination-policy.ts +31 -2
  130. package/src/lib/errors.ts +39 -0
  131. package/src/lib/provider-outbound.ts +69 -3
  132. package/src/lib/proxy-env.ts +22 -0
  133. package/src/lib/redact-folding.ts +176 -0
  134. package/src/lib/redact.ts +2 -175
  135. package/src/lib/state-store-sweeper.ts +20 -6
  136. package/src/lib/token-estimate.ts +94 -27
  137. package/src/lib/windows-user-principal.ts +53 -5
  138. package/src/oauth/account-quota-rank.ts +40 -1
  139. package/src/oauth/anthropic-routing.ts +99 -3
  140. package/src/oauth/chatgpt-device.ts +187 -0
  141. package/src/oauth/chatgpt.ts +31 -4
  142. package/src/oauth/generic-account-failover.ts +36 -13
  143. package/src/oauth/index.ts +140 -29
  144. package/src/oauth/log.ts +3 -0
  145. package/src/oauth/login-cli.ts +5 -0
  146. package/src/oauth/meta-muse.ts +117 -15
  147. package/src/oauth/pool-settings-capability.ts +15 -4
  148. package/src/providers/api-keys.ts +8 -10
  149. package/src/providers/default-aliases.ts +39 -0
  150. package/src/providers/derive.ts +10 -2
  151. package/src/providers/fastwire.ts +36 -7
  152. package/src/providers/initial-model-selection-runtime.ts +90 -0
  153. package/src/providers/initial-model-selection.ts +120 -0
  154. package/src/providers/key-failover.ts +134 -54
  155. package/src/providers/key-store.ts +11 -1
  156. package/src/providers/label.ts +1 -1
  157. package/src/providers/model-discovery.ts +76 -0
  158. package/src/providers/model-rename-startup.ts +72 -8
  159. package/src/providers/muse-subscription-usage.ts +95 -0
  160. package/src/providers/openai-sidecar.ts +17 -5
  161. package/src/providers/openai-tiers-destination.ts +102 -0
  162. package/src/providers/openai-tiers.ts +2 -99
  163. package/src/providers/opencode-go-transport.ts +41 -0
  164. package/src/providers/quota-key-accounts.ts +141 -0
  165. package/src/providers/quota-types.ts +9 -0
  166. package/src/providers/quota.ts +625 -98
  167. package/src/providers/registry.ts +60 -17
  168. package/src/providers/xai-responses-opt-in.ts +31 -5
  169. package/src/quota/reset-activation.ts +81 -0
  170. package/src/quota/reset-detector.ts +305 -0
  171. package/src/quota/reset-notify-config.ts +162 -0
  172. package/src/quota/reset-observer.ts +125 -0
  173. package/src/quota/reset-poller.ts +160 -0
  174. package/src/quota/reset-seen-store.ts +385 -0
  175. package/src/quota/reset-sinks.ts +199 -0
  176. package/src/quota/window-mapping.ts +106 -0
  177. package/src/responses/apply-patch-envelope.ts +46 -0
  178. package/src/responses/code-mode-helper-compat.ts +39 -1
  179. package/src/responses/custom-tool-compat.ts +10 -4
  180. package/src/responses/hosted-tool-policy.ts +12 -4
  181. package/src/responses/parser-content.ts +133 -0
  182. package/src/responses/parser-text-format.ts +24 -0
  183. package/src/responses/parser-tools.ts +188 -0
  184. package/src/responses/parser.ts +3 -326
  185. package/src/responses/state.ts +124 -28
  186. package/src/router.ts +48 -13
  187. package/src/routing/analytics.ts +1 -0
  188. package/src/routing/capability.ts +17 -4
  189. package/src/server/auth-cors.ts +7 -1
  190. package/src/server/background-lifecycle.ts +23 -1
  191. package/src/server/chat-completions.ts +25 -3
  192. package/src/server/claude-messages.ts +62 -5
  193. package/src/server/effort-row.ts +1 -1
  194. package/src/server/fast-row.ts +295 -0
  195. package/src/server/gui-static.ts +30 -4
  196. package/src/server/index.ts +122 -28
  197. package/src/server/live.ts +18 -4
  198. package/src/server/management/agent-settings-routes.ts +2 -2
  199. package/src/server/management/combo-routes.ts +37 -9
  200. package/src/server/management/config-routes.ts +93 -2
  201. package/src/server/management/integration-routes.ts +108 -0
  202. package/src/server/management/model-routes.ts +13 -3
  203. package/src/server/management/model-rows.ts +20 -1
  204. package/src/server/management/native-integration-routes.ts +4 -1
  205. package/src/server/management/oauth-account-routes.ts +45 -10
  206. package/src/server/management/provider-routes.ts +34 -3
  207. package/src/server/management/quota-reset-routes.ts +57 -0
  208. package/src/server/management/route-registry.ts +7 -4
  209. package/src/server/management/shared.ts +19 -5
  210. package/src/server/management/system-routes.ts +3 -2
  211. package/src/server/management-api.ts +14 -2
  212. package/src/server/ports.ts +12 -2
  213. package/src/server/relay-eager.ts +38 -23
  214. package/src/server/relay.ts +4 -0
  215. package/src/server/request-log.ts +6 -0
  216. package/src/server/responses/codex-ws-correlation.ts +65 -0
  217. package/src/server/responses/codex-ws-exchange.ts +261 -0
  218. package/src/server/responses/codex-ws-metadata.ts +134 -0
  219. package/src/server/responses/codex-ws-pool.ts +162 -0
  220. package/src/server/responses/codex-ws-request.ts +87 -0
  221. package/src/server/responses/codex-ws-session.ts +93 -0
  222. package/src/server/responses/codex-ws-wire.ts +144 -0
  223. package/src/server/responses/collaboration.ts +41 -1
  224. package/src/server/responses/compact.ts +105 -12
  225. package/src/server/responses/core.ts +510 -57
  226. package/src/server/responses/empty-completion-guard.ts +4 -0
  227. package/src/server/responses/fetch-helpers.ts +10 -3
  228. package/src/server/responses/input-admission.ts +16 -9
  229. package/src/server/responses/responses-field-backfill.ts +1 -1
  230. package/src/server/responses/ws-upstream.ts +34 -318
  231. package/src/server/responses-custom-tool-repair.ts +20 -4
  232. package/src/server/responses-undeclared-tool-guard.ts +100 -8
  233. package/src/server/safe-response-headers.ts +23 -0
  234. package/src/server/search.ts +9 -0
  235. package/src/server/subagent-models-startup.ts +27 -0
  236. package/src/server/system-env-shell.ts +238 -0
  237. package/src/server/system-env.ts +7 -234
  238. package/src/server/ws-bridge.ts +3 -25
  239. package/src/server/xai-responses-startup.ts +21 -0
  240. package/src/service-manager-probe.ts +1 -1
  241. package/src/service.ts +55 -16
  242. package/src/types/config.ts +108 -12
  243. package/src/types/provider.ts +36 -7
  244. package/src/types/request.ts +8 -0
  245. package/src/types/tools.ts +26 -1
  246. package/src/types.ts +2 -0
  247. package/src/update/notify.ts +8 -2
  248. package/src/usage/cost.ts +38 -28
  249. package/src/usage/expected-prices.ts +34 -15
  250. package/src/usage/log.ts +2 -0
  251. package/src/usage/model-identity.ts +26 -0
  252. package/src/usage/summary.ts +15 -1
  253. package/src/vision/describe.ts +6 -0
  254. package/src/vision/image-rewrite.ts +108 -0
  255. package/src/vision/index.ts +19 -306
  256. package/src/vision/plan.ts +205 -0
  257. package/src/web-search/executor.ts +6 -0
  258. package/src/web-search/index.ts +8 -1
  259. package/gui/dist/assets/index-B2YjLA-i.css +0 -1
  260. package/gui/dist/assets/index-aPup8CKb.js +0 -112
@@ -9,10 +9,12 @@ import type { StoredAccountQuota } from "../codex/quota";
9
9
  import { isMainAccountIdentityGenerationLive } from "../codex/main-account-cache";
10
10
  import { MAIN_CODEX_ACCOUNT_ID } from "../codex/main-account";
11
11
  import { codexPlanKey } from "../codex/plan";
12
+ import { resolveEnvValue } from "../config";
12
13
  import { resolveProviderApiKey } from "./key-store";
13
14
  import { getValidAccessToken, getValidAccessTokenForAccount } from "../oauth";
14
15
  import { getAccountCredential, getAccountSet, getCredential } from "../oauth/store";
15
16
  import { antigravityUserAgent } from "../adapters/client-fingerprint";
17
+ import { isCanonicalOllamaCloudUrl } from "../adapters/ollama-native-url";
16
18
  import { providerOutboundPost, providerRedirectError, type ProviderOutboundDependencies } from "../lib/provider-outbound";
17
19
  import { apiKeyPoolEntryId } from "./api-keys";
18
20
  import { XAI_GROK_CLIENT_VERSION, XAI_GROK_COMPATIBILITY } from "./xai-transport";
@@ -46,6 +48,7 @@ import {
46
48
  type CodexCapacityQuota,
47
49
  } from "./codex-capacity";
48
50
  import type {
51
+ AccountQuotaMode,
49
52
  ProviderQuota,
50
53
  ProviderQuotaCreditsUsd,
51
54
  ProviderQuotaWindow,
@@ -63,6 +66,7 @@ import {
63
66
  readPersistedAccountQuotas,
64
67
  schedulePersistAccountQuotas,
65
68
  } from "./account-quota-disk";
69
+ import { clearProviderApiKeyQuotaCache, mapQuotaRoster, readProviderApiKeyQuotas, type ProviderApiKeyQuota } from "./quota-key-accounts";
66
70
 
67
71
  export type { ProviderQuota, ProviderQuotaCreditsUsd, ProviderQuotaWindow } from "./quota-types";
68
72
 
@@ -83,6 +87,8 @@ const OPENCODE_GO_USAGE_URL = `${OPENCODE_GO_BASE_URL}/usage`;
83
87
  const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
84
88
  const DEEPSEEK_BASE_URL = "https://api.deepseek.com";
85
89
  const CLINE_BASE_URL = "https://api.cline.bot";
90
+ const OLLAMA_CLOUD_BASE_URL = "https://ollama.com";
91
+ const OLLAMA_CLOUD_USAGE_URL = `${OLLAMA_CLOUD_BASE_URL}/api/usage`;
86
92
  const ZAI_BASE_URL = "https://api.z.ai";
87
93
  const ZAI_CN_BASE_URL = "https://open.bigmodel.cn";
88
94
  const MINIMAX_REMAINS_URL = "https://www.minimax.io/v1/token_plan/remains";
@@ -96,6 +102,7 @@ const XAI_CREDITS_URL = `${XAI_BILLING_URL}?format=credits`;
96
102
  /** Keep a failed probe's previous row at most this long before dropping it. */
97
103
  const LAST_GOOD_MAX_AGE_MS = CODEX_CAPACITY_MAX_QUOTA_AGE_MS;
98
104
  const nativeMainReportGenerations = new WeakMap<ProviderQuotaReport, number>();
105
+ const accountReportCurrent = new WeakMap<ProviderQuotaReport, () => boolean>();
99
106
  let providerQuotaBeforePublishForTests: (() => void | Promise<void>) | null = null;
100
107
 
101
108
  /** Test-only seam for identity/config invalidation after probes but before publication. */
@@ -130,6 +137,18 @@ export interface ProviderQuotaReport {
130
137
  quota: ProviderQuota;
131
138
  updatedAt: number;
132
139
  reverseEngineered?: boolean;
140
+ /**
141
+ * The row was OBSERVED in-band on a streaming turn rather than probed.
142
+ *
143
+ * Age means something different for these. A probed provider re-reads on its own TTL,
144
+ * so a row older than the last-good bound means the probe is failing and showing it
145
+ * would misrepresent a live number. A passive provider publishes no endpoint at all
146
+ * (`hasPassiveAccountQuota`), so its last observation is not a stale reading of
147
+ * something fresher — it is the only measurement that exists, and dropping it leaves
148
+ * the operator with nothing. Consumers that enforce a freshness bound must exempt
149
+ * these and state the observation age instead.
150
+ */
151
+ observed?: boolean;
133
152
  aggregation?: CodexCapacityAggregation;
134
153
  }
135
154
 
@@ -147,6 +166,7 @@ let invalidationEpoch = 0;
147
166
  export function clearProviderQuotaCache(): void {
148
167
  cache = null;
149
168
  clearCachedProviderQuotas();
169
+ clearProviderApiKeyQuotaCache();
150
170
  invalidationEpoch += 1;
151
171
  }
152
172
 
@@ -316,6 +336,15 @@ function isCanonicalClineBaseUrl(baseUrl: string): boolean {
316
336
  return normalized === CLINE_BASE_URL || normalized === `${CLINE_BASE_URL}/api/v1`;
317
337
  }
318
338
 
339
+ function isCanonicalOllamaCloudBaseUrl(baseUrl?: string): boolean {
340
+ if (!baseUrl) return false;
341
+ try {
342
+ return isCanonicalOllamaCloudUrl(baseUrl);
343
+ } catch {
344
+ return false;
345
+ }
346
+ }
347
+
319
348
  function isCanonicalZaiBaseUrl(baseUrl: string): boolean {
320
349
  const normalized = normalizedBaseUrl(baseUrl);
321
350
  return normalized === ZAI_BASE_URL
@@ -647,6 +676,78 @@ async function fetchClineQuota(provider: string, config: OcxProviderConfig): Pro
647
676
  return windows > 0 ? report(provider, "cline:plan-usage-limits", quota) : null;
648
677
  }
649
678
 
679
+ /**
680
+ * Ollama Cloud `GET https://ollama.com/api/usage` — returns account usage.
681
+ * Legacy plans report rolling 5-hour `limits.session.usage` and 7-day
682
+ * `limits.weekly.usage`. Migrated monthly-credit plans report
683
+ * `limits.monthly.usage`. `usage` values are normalized fractions (0..1).
684
+ */
685
+ function parseOllamaPercent(usageValue: unknown): number | undefined {
686
+ const usage = toFiniteNumber(usageValue);
687
+ if (usage === undefined || usage < 0) return undefined;
688
+ const percent = Math.round(usage * 10000) / 100;
689
+ return normalizePercent(percent);
690
+ }
691
+
692
+ export function parseOllamaCloudQuota(body: Record<string, unknown> | null): ProviderQuota | null {
693
+ if (!body) return null;
694
+ const limits = asRecord(body.limits);
695
+ if (!limits) return null;
696
+
697
+ const quota: ProviderQuota = { updatedAt: Date.now() };
698
+ let windows = 0;
699
+
700
+ const session = asRecord(limits.session);
701
+ if (session) {
702
+ const percent = parseOllamaPercent(session.usage);
703
+ if (percent !== undefined) {
704
+ quota.fiveHourPercent = percent;
705
+ windows += 1;
706
+ }
707
+ }
708
+
709
+ const weekly = asRecord(limits.weekly);
710
+ if (weekly) {
711
+ const percent = parseOllamaPercent(weekly.usage);
712
+ if (percent !== undefined) {
713
+ quota.weeklyPercent = percent;
714
+ windows += 1;
715
+ }
716
+ }
717
+
718
+ const monthly = asRecord(limits.monthly);
719
+ if (monthly) {
720
+ const percent = parseOllamaPercent(monthly.usage);
721
+ if (percent !== undefined) {
722
+ quota.monthlyPercent = percent;
723
+ windows += 1;
724
+ }
725
+ }
726
+
727
+ return windows > 0 ? quota : null;
728
+ }
729
+
730
+ async function fetchOllamaCloudQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
731
+ const effectiveBaseUrl = config.baseUrl ?? getProviderRegistryEntry(provider)?.baseUrl ?? "";
732
+ if (!isCanonicalOllamaCloudBaseUrl(effectiveBaseUrl)) return null;
733
+ const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
734
+ if (!apiKey) return null;
735
+ const response = await fetch(OLLAMA_CLOUD_USAGE_URL, {
736
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
737
+ redirect: "error",
738
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
739
+ });
740
+ if (!response.ok) {
741
+ if (response.status === 404) return null;
742
+ return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
743
+ ? TERMINAL_QUOTA_FAILURE
744
+ : null;
745
+ }
746
+ const body = asRecord(await readQuotaJson(response));
747
+ const quota = parseOllamaCloudQuota(body);
748
+ return quota ? report(provider, "ollama-cloud:usage", quota) : null;
749
+ }
750
+
650
751
  /**
651
752
  * Z.AI GLM Coding Plan `GET /api/monitor/usage/quota/limit` — the coding-plan
652
753
  * limits arrive as a `limits` array of `TOKENS_LIMIT` (newer plans call the
@@ -1085,7 +1186,8 @@ function tagNativeMainReport(
1085
1186
 
1086
1187
  function isProviderQuotaReportCurrent(value: ProviderQuotaReport): boolean {
1087
1188
  const generation = nativeMainReportGenerations.get(value);
1088
- return generation === undefined || isMainAccountIdentityGenerationLive(generation);
1189
+ return (generation === undefined || isMainAccountIdentityGenerationLive(generation))
1190
+ && (accountReportCurrent.get(value)?.() ?? true);
1089
1191
  }
1090
1192
 
1091
1193
  async function fetchChatGptForwardQuota(
@@ -1206,6 +1308,7 @@ export function parseXaiCreditsResponse(value: unknown): { percent: number; rese
1206
1308
  async function fetchXaiWeeklyCredits(accessToken: string, userId: string): Promise<ProviderQuota | null> {
1207
1309
  try {
1208
1310
  const response = await fetch(XAI_CREDITS_URL, {
1311
+ redirect: "error",
1209
1312
  headers: {
1210
1313
  Accept: "application/json",
1211
1314
  Authorization: `Bearer ${accessToken}`,
@@ -1229,16 +1332,11 @@ async function fetchXaiWeeklyCredits(accessToken: string, userId: string): Promi
1229
1332
  }
1230
1333
  }
1231
1334
 
1232
- async function fetchXaiQuota(provider: string): Promise<ProviderQuotaReport | null> {
1233
- let accessToken: string;
1234
- try {
1235
- accessToken = await getValidAccessToken("xai");
1236
- } catch {
1237
- return null;
1238
- }
1335
+ async function fetchXaiQuota(provider: string, context: { accessToken: string; upstreamAccountId?: string }): Promise<ProviderQuotaReport | null> {
1336
+ const { accessToken } = context;
1239
1337
 
1240
1338
  // Prefer the SuperGrok weekly credits window that actually gates prompting (#1283).
1241
- const userId = getCredential("xai")?.accountId?.trim() || xaiUserIdFromAccessToken(accessToken);
1339
+ const userId = context.upstreamAccountId?.trim() || xaiUserIdFromAccessToken(accessToken);
1242
1340
  if (userId) {
1243
1341
  const weekly = await fetchXaiWeeklyCredits(accessToken, userId);
1244
1342
  if (weekly) return report(provider, "xai:grok-billing-credits", weekly);
@@ -1247,6 +1345,7 @@ async function fetchXaiQuota(provider: string): Promise<ProviderQuotaReport | nu
1247
1345
  // Legacy monthly dollar pool — retained when weekly is unavailable.
1248
1346
  try {
1249
1347
  const response = await fetch(XAI_BILLING_URL, {
1348
+ redirect: "error",
1250
1349
  headers: { Accept: "application/json", Authorization: `Bearer ${accessToken}` },
1251
1350
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
1252
1351
  });
@@ -1409,6 +1508,29 @@ async function fetchKiroQuota(provider: string): Promise<ProviderQuotaReport | n
1409
1508
  return report(provider, "kiro:usage-limits", snapshot.quota);
1410
1509
  }
1411
1510
 
1511
+ /**
1512
+ * Provider-level row for a passive provider: the ACTIVE account's last observed
1513
+ * subscription windows, the same shape `fetchAnthropicQuota` and `fetchKiroQuota`
1514
+ * return.
1515
+ *
1516
+ * Cache-only. A dashboard load or `ocx account refresh` must never spend an inference
1517
+ * turn, so `forceRefresh` does not exist on this path — there is nothing to refresh.
1518
+ * `report.updatedAt` is the observation time, which is what both GUI surfaces render
1519
+ * as the relative age of the row.
1520
+ */
1521
+ async function fetchPassiveProviderQuota(provider: string): Promise<ProviderQuotaReport | null> {
1522
+ const activeId = getAccountSet(provider)?.activeAccountId;
1523
+ if (!activeId) return null;
1524
+ // Idempotent; without it a proxy restart shows nothing until the next streaming turn
1525
+ // even though the last observation is on disk.
1526
+ hydrateAccountQuotaCache();
1527
+ const entry = accountQuotaCache.get(accountCacheKey(provider, activeId));
1528
+ if (!entry?.quota) return null;
1529
+ const built = report(provider, `${provider}:subscription-observation`, entry.quota);
1530
+ // Tagged here rather than inside report(), which every probed path shares.
1531
+ return built ? { ...built, observed: true } : null;
1532
+ }
1533
+
1412
1534
  // ---------------------------------------------------------------------------
1413
1535
  // Per-account quota (multiauth)
1414
1536
  // ---------------------------------------------------------------------------
@@ -1426,8 +1548,12 @@ type AccountQuotaCacheEntry = {
1426
1548
  quota: ProviderQuota | null;
1427
1549
  /** Last probe failed (429 / network / expired login); still may hold last-good quota. */
1428
1550
  unavailable?: true;
1551
+ /** Private new-reader identity; never persisted or serialized. */
1552
+ identity?: string;
1553
+ isCurrent?: () => boolean;
1429
1554
  };
1430
1555
  const accountQuotaCache = new Map<string, AccountQuotaCacheEntry>();
1556
+ let explicitAccountEpoch = 0;
1431
1557
 
1432
1558
  /**
1433
1559
  * Seed the cache from the last run, once.
@@ -1471,11 +1597,21 @@ export interface ProviderAccountQuota {
1471
1597
  quota: ProviderQuota | null;
1472
1598
  /** Set when the probe could not reach upstream (expired login, 429, network). */
1473
1599
  unavailable?: true;
1600
+ isCurrent?: () => boolean;
1474
1601
  }
1475
1602
 
1476
1603
  /** Providers whose per-account quota can be probed. Extend as other OAuth APIs are covered. */
1477
1604
  export function supportsPerAccountQuota(provider: string): boolean {
1478
- return provider === "anthropic" || provider === "kiro" || provider === "google-antigravity";
1605
+ return provider === "anthropic" || provider === "kiro" || provider === "google-antigravity"
1606
+ || explicitAccountReader(provider);
1607
+ }
1608
+
1609
+ function explicitAccountReader(provider: string): boolean {
1610
+ return provider === "xai" || provider === "cursor" || provider === "kimi" || provider === "command-code";
1611
+ }
1612
+
1613
+ export function providerOAuthAccountQuotaMode(provider: string): AccountQuotaMode {
1614
+ return hasPassiveAccountQuota(provider) ? "passive" : supportsPerAccountQuota(provider) ? "probe" : "unsupported";
1479
1615
  }
1480
1616
 
1481
1617
  function accountCacheKey(provider: string, accountId: string): string {
@@ -1488,6 +1624,7 @@ function accountCacheKey(provider: string, accountId: string): string {
1488
1624
  */
1489
1625
  export function getCachedProviderAccountQuota(provider: string, accountId: string): ProviderQuota | null {
1490
1626
  const entry = accountQuotaCache.get(accountCacheKey(provider, accountId));
1627
+ if (entry?.isCurrent && !entry.isCurrent()) return null;
1491
1628
  return entry?.quota ?? null;
1492
1629
  }
1493
1630
 
@@ -1505,6 +1642,75 @@ export function setCachedProviderAccountQuotaForTests(
1505
1642
  accountQuotaCache.set(key, { ts: Date.now(), quota });
1506
1643
  }
1507
1644
 
1645
+ /**
1646
+ * Providers whose per-account quota is OBSERVED in-band, never probed.
1647
+ *
1648
+ * Deliberately separate from `supportsPerAccountQuota` rather than folded into it. That
1649
+ * predicate gates explicit upstream readers. Meta publishes no quota endpoint, so it
1650
+ * remains a cache-only observation even when every probe reader is account-scoped.
1651
+ */
1652
+ export function hasPassiveAccountQuota(provider: string): boolean {
1653
+ return provider === "meta-muse";
1654
+ }
1655
+
1656
+ /**
1657
+ * Record a quota observed in-band on a streaming turn.
1658
+ *
1659
+ * The CALLER captures `writerGeneration` when it resolves the serving credential, not
1660
+ * this function at write time. A streaming turn is a long await, and a generation
1661
+ * captured immediately before the write cannot see a config or account change that
1662
+ * happened EARLIER in the same turn — which is exactly the case the fence exists for.
1663
+ */
1664
+ export function recordPassiveAccountQuota(
1665
+ provider: string,
1666
+ accountId: string,
1667
+ quota: ProviderQuota,
1668
+ writerGeneration: number,
1669
+ ): void {
1670
+ if (!hasPassiveAccountQuota(provider) || !accountId) return;
1671
+ const key = accountCacheKey(provider, accountId);
1672
+ if (!mayCommitAccountQuotaKey(key, writerGeneration)) return;
1673
+ // Hydrate BEFORE writing, not only on the read path. `persistAccountQuotaCache`
1674
+ // serializes the whole in-memory map, so a passive write that lands before anything
1675
+ // has read the cache would persist this one row and erase every other provider's
1676
+ // saved row -- and `diskHydrated` would then stop any later reader from recovering
1677
+ // them. A probe writer cannot hit this because its own read hydrates first; an
1678
+ // observation arrives unprompted, so it must hydrate itself.
1679
+ hydrateAccountQuotaCache();
1680
+ accountQuotaCache.set(key, { ts: Date.now(), quota });
1681
+ // Persisted so a restart keeps the last observation: with no probe to re-establish it,
1682
+ // a forgotten row stays forgotten until the user happens to run another streaming turn.
1683
+ persistAccountQuotaCache();
1684
+ // sweepExpiredOnWrite is deliberately NOT called. Existing probe writers call it
1685
+ // because they run on a poll; this runs on the request path, where a state sweep does
1686
+ // not belong. Passive rows are still reclaimed by generation reconciliation
1687
+ // (reconcileProviderAccountQuotaRows) and by the disk reader's age bound.
1688
+ }
1689
+
1690
+ /**
1691
+ * Cache-only per-account rows for a passive provider. Never probes, never refreshes.
1692
+ *
1693
+ * An account with no observation is OMITTED rather than returned with `quota: null` and
1694
+ * `unavailable`: that pair means "a probe was attempted and failed", and no probe was
1695
+ * ever attempted here. A user who has not yet run a streaming turn simply has no
1696
+ * measurement, which is not an error state.
1697
+ */
1698
+ export function readPassiveProviderAccountQuotas(provider: string): ProviderAccountQuota[] {
1699
+ if (!hasPassiveAccountQuota(provider)) return [];
1700
+ // Idempotent, and otherwise only reached from probe paths a passive provider never
1701
+ // enters — without it a restart shows nothing until the next streaming turn, even
1702
+ // though the row is sitting on disk.
1703
+ hydrateAccountQuotaCache();
1704
+ const set = getAccountSet(provider);
1705
+ if (!set) return [];
1706
+ const rows: ProviderAccountQuota[] = [];
1707
+ for (const account of set.accounts) {
1708
+ const entry = accountQuotaCache.get(accountCacheKey(provider, account.id));
1709
+ if (entry?.quota) rows.push({ accountId: account.id, quota: entry.quota });
1710
+ }
1711
+ return rows;
1712
+ }
1713
+
1508
1714
  export function sweepExpiredProviderAccountQuotaRows(now = Date.now()): number {
1509
1715
  let removed = 0;
1510
1716
  for (const [key, entry] of accountQuotaCache) {
@@ -1547,6 +1753,7 @@ export function resetProviderQuotaReconcileStateForTests(): void {
1547
1753
 
1548
1754
  /** Drop cached per-account rows (all, or just one provider's). */
1549
1755
  export function clearAccountQuotaCache(provider?: string): void {
1756
+ explicitAccountEpoch += 1;
1550
1757
  if (!provider) {
1551
1758
  accountQuotaCache.clear();
1552
1759
  accountQuotaInflight.clear();
@@ -1591,11 +1798,115 @@ async function getTokenForAccountQuotaProbe(provider: string, accountId: string)
1591
1798
  return getValidAccessTokenForAccount(provider, accountId);
1592
1799
  }
1593
1800
 
1801
+ function explicitQuotaConfig(provider: string, configured?: OcxProviderConfig): OcxProviderConfig | undefined {
1802
+ if (configured) return configured;
1803
+ const entry = getProviderRegistryEntry(provider);
1804
+ return entry ? { adapter: entry.adapter, baseUrl: entry.baseUrl, authMode: "oauth" } : undefined;
1805
+ }
1806
+
1807
+ function explicitQuotaIdentity(provider: string, accountId: string, configured?: OcxProviderConfig): string | undefined {
1808
+ const credential = getAccountCredential(provider, accountId);
1809
+ const target = explicitQuotaConfig(provider, configured);
1810
+ if (!credential || !target) return undefined;
1811
+ return createHash("sha256").update(JSON.stringify([
1812
+ provider, accountId, credential.access, credential.refresh, credential.expires,
1813
+ credential.accountId, credential.projectId, credential.source,
1814
+ target.adapter, target.baseUrl, target.authMode, target.disabled === true,
1815
+ ])).digest("hex");
1816
+ }
1817
+
1818
+ function explicitQuotaDestination(provider: string, config: OcxProviderConfig): boolean {
1819
+ if (config.disabled === true || config.authMode !== "oauth") return false;
1820
+ if (provider === "kimi") return isCanonicalKimiCodeBaseUrl(config.baseUrl);
1821
+ if (provider === "command-code") return isCanonicalCommandCodeBaseUrl(config.baseUrl);
1822
+ // These readers use fixed canonical billing origins, never config.baseUrl.
1823
+ return provider === "xai" || provider === "cursor";
1824
+ }
1825
+
1826
+ async function readExplicitAccountQuota(provider: string, accountId: string, configured?: OcxProviderConfig): Promise<{
1827
+ result: ProviderQuotaProbeResult;
1828
+ identity: string | undefined;
1829
+ isCurrent: () => boolean;
1830
+ } | null> {
1831
+ const target = explicitQuotaConfig(provider, configured);
1832
+ if (!target || !explicitQuotaDestination(provider, target)) return null;
1833
+ const config = { ...target };
1834
+ const epoch = explicitAccountEpoch;
1835
+ const accessToken = await getTokenForAccountQuotaProbe(provider, accountId);
1836
+ const credential = getAccountCredential(provider, accountId);
1837
+ if (!credential || credential.access !== accessToken) return null;
1838
+ // Pair the post-renewal credential with the destination captured before renewal.
1839
+ const identity = explicitQuotaIdentity(provider, accountId, config);
1840
+ const isCurrent = () => epoch === explicitAccountEpoch
1841
+ && identity === explicitQuotaIdentity(provider, accountId, configured);
1842
+ if (!isCurrent()) return null;
1843
+ let result: ProviderQuotaProbeResult;
1844
+ switch (provider) {
1845
+ case "xai": result = await fetchXaiQuota(provider, { accessToken, upstreamAccountId: credential.accountId }); break;
1846
+ case "cursor": result = await fetchCursorQuota(provider, accessToken); break;
1847
+ case "kimi": result = await fetchKimiQuota(provider, config, accessToken); break;
1848
+ case "command-code": result = await fetchCommandCodeQuota(provider, config, accessToken); break;
1849
+ default: return null;
1850
+ }
1851
+ return { result, identity, isCurrent };
1852
+ }
1853
+
1854
+ async function fetchExplicitAccountQuota(provider: string, accountId: string, force: boolean, configured?: OcxProviderConfig): Promise<AccountQuotaCacheEntry> {
1855
+ const key = accountCacheKey(provider, accountId);
1856
+ const identity = explicitQuotaIdentity(provider, accountId, configured);
1857
+ const previous = accountQuotaCache.get(key);
1858
+ const cached = identity && previous?.identity === identity && previous.isCurrent?.() ? previous : undefined;
1859
+ if (!force && cached && Date.now() - cached.ts < ACCOUNT_QUOTA_TTL_MS
1860
+ && (!cached.quota || Date.now() - cached.quota.updatedAt < LAST_GOOD_MAX_AGE_MS)) return cached;
1861
+ const flightKey = `${key}\u0000${identity ?? "missing"}`;
1862
+ const running = accountQuotaInflight.get(flightKey);
1863
+ if (running) return running;
1864
+ const epoch = explicitAccountEpoch;
1865
+ const lastGood = cached?.quota && Date.now() - cached.quota.updatedAt < LAST_GOOD_MAX_AGE_MS ? cached.quota : null;
1866
+ const flight = (async (): Promise<AccountQuotaCacheEntry> => {
1867
+ let read: Awaited<ReturnType<typeof readExplicitAccountQuota>> = null;
1868
+ try { read = await readExplicitAccountQuota(provider, accountId, configured); } catch { /* unavailable */ }
1869
+ const isCurrent = read?.isCurrent ?? (() => epoch === explicitAccountEpoch && !!identity
1870
+ && identity === explicitQuotaIdentity(provider, accountId, configured));
1871
+ const result = read?.result;
1872
+ const current = epoch === explicitAccountEpoch && isCurrent();
1873
+ const quota = current && result && typeof result !== "symbol" ? result.quota : null;
1874
+ const empty = result === AUTHORITATIVE_EMPTY_QUOTA;
1875
+ const entry: AccountQuotaCacheEntry = {
1876
+ ts: Date.now(),
1877
+ quota: quota ?? (current && result !== TERMINAL_QUOTA_FAILURE && !empty
1878
+ && lastGood && Date.now() - lastGood.updatedAt < LAST_GOOD_MAX_AGE_MS ? lastGood : null),
1879
+ ...(!current || (!quota && !empty) ? { unavailable: true as const } : {}),
1880
+ identity: read?.identity ?? identity,
1881
+ isCurrent: () => epoch === explicitAccountEpoch && isCurrent(),
1882
+ };
1883
+ if (entry.isCurrent?.()) accountQuotaCache.set(key, entry);
1884
+ return entry;
1885
+ })().finally(() => { if (accountQuotaInflight.get(flightKey) === flight) accountQuotaInflight.delete(flightKey); });
1886
+ accountQuotaInflight.set(flightKey, flight);
1887
+ return flight;
1888
+ }
1889
+
1890
+ async function fetchExplicitCurrentQuota(provider: string, config: OcxProviderConfig, liveConfig: OcxConfig): Promise<ProviderQuotaProbeResult> {
1891
+ const id = getAccountSet(provider)?.activeAccountId;
1892
+ if (!id) return null;
1893
+ const read = await readExplicitAccountQuota(provider, id, config);
1894
+ if (!read) return null;
1895
+ const isCurrent = () => liveConfig.providers[provider] === config
1896
+ && read.isCurrent() && getAccountSet(provider)?.activeAccountId === id;
1897
+ if (!isCurrent()) return TERMINAL_QUOTA_FAILURE;
1898
+ if (read.result && typeof read.result !== "symbol") accountReportCurrent.set(read.result, isCurrent);
1899
+ return read.result;
1900
+ }
1901
+
1594
1902
  async function fetchAccountQuota(
1595
1903
  provider: string,
1596
1904
  accountId: string,
1597
1905
  forceRefresh: boolean,
1906
+ providerConfig?: OcxProviderConfig,
1598
1907
  ): Promise<AccountQuotaCacheEntry> {
1908
+ if (!supportsPerAccountQuota(provider)) return { ts: Date.now(), quota: null, unavailable: true };
1909
+ if (explicitAccountReader(provider)) return fetchExplicitAccountQuota(provider, accountId, forceRefresh, providerConfig);
1599
1910
  const key = accountCacheKey(provider, accountId);
1600
1911
  const writerGeneration = captureConfigGeneration();
1601
1912
  const cached = accountQuotaCache.get(key);
@@ -1625,8 +1936,10 @@ async function fetchAccountQuota(
1625
1936
  const projectId = getAccountCredential(provider, accountId)?.projectId;
1626
1937
  if (!projectId) throw new Error("antigravity account has no project id");
1627
1938
  quota = await fetchAntigravityUsageQuota(token, projectId);
1628
- } else {
1939
+ } else if (provider === "anthropic") {
1629
1940
  quota = await fetchAnthropicUsageQuota(token);
1941
+ } else {
1942
+ return { ts: Date.now(), quota: null, unavailable: true };
1630
1943
  }
1631
1944
  }
1632
1945
  if (!quota) {
@@ -1679,18 +1992,27 @@ async function fetchAccountQuota(
1679
1992
  export async function fetchProviderAccountQuotas(
1680
1993
  provider: string,
1681
1994
  forceRefresh = false,
1995
+ providerConfig?: OcxProviderConfig,
1682
1996
  ): Promise<ProviderAccountQuota[]> {
1683
1997
  if (!supportsPerAccountQuota(provider)) return [];
1684
1998
  const set = getAccountSet(provider);
1685
1999
  if (!set) return [];
1686
- return await Promise.all(set.accounts.map(async account => {
1687
- const entry = await fetchAccountQuota(provider, account.id, forceRefresh);
1688
- return {
2000
+ return mapQuotaRoster(set.accounts, async account => {
2001
+ const entry = await fetchAccountQuota(provider, account.id, forceRefresh, providerConfig);
2002
+ const result: ProviderAccountQuota = {
1689
2003
  accountId: account.id,
1690
2004
  quota: entry.quota,
1691
2005
  ...(entry.unavailable ? { unavailable: true as const } : {}),
1692
2006
  };
1693
- }));
2007
+ if (!explicitAccountReader(provider)) return result;
2008
+ const identity = entry.identity;
2009
+ Object.defineProperty(result, "isCurrent", { value: () => {
2010
+ if (entry.isCurrent) return entry.isCurrent();
2011
+ const credential = getAccountCredential(provider, account.id);
2012
+ return !!credential && (!identity || explicitQuotaIdentity(provider, account.id, providerConfig) === identity);
2013
+ } });
2014
+ return result;
2015
+ });
1694
2016
  }
1695
2017
 
1696
2018
  function normalizedBaseUrl(value: string): string | null {
@@ -1809,10 +2131,10 @@ function parseKimiQuotaPayload(value: unknown): ProviderQuota | null {
1809
2131
  return hasQuotaRows(quota) ? quota : null;
1810
2132
  }
1811
2133
 
1812
- async function resolveKimiQuotaBearer(config: OcxProviderConfig): Promise<string | null> {
2134
+ async function resolveKimiQuotaBearer(config: OcxProviderConfig, accountId?: string): Promise<string | null> {
1813
2135
  if (config.authMode === "oauth") {
1814
2136
  try {
1815
- return await getValidAccessToken("kimi");
2137
+ return accountId ? await getTokenForAccountQuotaProbe("kimi", accountId) : null;
1816
2138
  } catch {
1817
2139
  return null;
1818
2140
  }
@@ -1824,13 +2146,13 @@ async function resolveKimiQuotaBearer(config: OcxProviderConfig): Promise<string
1824
2146
  return primary || null;
1825
2147
  }
1826
2148
 
1827
- async function fetchKimiQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaReport | null> {
2149
+ async function fetchKimiQuota(provider: string, config: OcxProviderConfig, accessToken: string): Promise<ProviderQuotaReport | null> {
1828
2150
  // Never release credentials to a user-edited or lookalike provider host.
1829
2151
  if (!isCanonicalKimiCodeBaseUrl(config.baseUrl)) return null;
1830
- const accessToken = await resolveKimiQuotaBearer(config);
1831
2152
  if (!accessToken) return null;
1832
2153
  const response = await fetch(KIMI_CODE_USAGE_URL, {
1833
2154
  headers: { Accept: "application/json", Authorization: `Bearer ${accessToken}` },
2155
+ redirect: "error",
1834
2156
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
1835
2157
  });
1836
2158
  if (!response.ok) return null;
@@ -1917,10 +2239,10 @@ async function fetchCommandCodeSpend(
1917
2239
  }
1918
2240
 
1919
2241
  /** OAuth access token or ACTIVE Provider-API key for the Command Code quota probe. */
1920
- async function resolveCommandCodeQuotaBearer(config: OcxProviderConfig): Promise<string | null> {
2242
+ async function resolveCommandCodeQuotaBearer(config: OcxProviderConfig, accountId?: string): Promise<string | null> {
1921
2243
  if (config.authMode === "oauth") {
1922
2244
  try {
1923
- return await getValidAccessToken("command-code");
2245
+ return accountId ? await getTokenForAccountQuotaProbe("command-code", accountId) : null;
1924
2246
  } catch {
1925
2247
  return null;
1926
2248
  }
@@ -1935,10 +2257,9 @@ async function resolveCommandCodeQuotaBearer(config: OcxProviderConfig): Promise
1935
2257
  * usage view uses (windowLimits.fiveHour / windowLimits.weekly), plus soft
1936
2258
  * whoami (team orgId scoping) and subscription-scoped spend for creditsUsd.
1937
2259
  */
1938
- async function fetchCommandCodeQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
2260
+ async function fetchCommandCodeQuota(provider: string, config: OcxProviderConfig, bearer: string): Promise<ProviderQuotaProbeResult> {
1939
2261
  // Never release credentials to a user-edited or lookalike provider host.
1940
2262
  if (!isCanonicalCommandCodeBaseUrl(config.baseUrl)) return null;
1941
- const bearer = await resolveCommandCodeQuotaBearer(config);
1942
2263
  if (!bearer) return null;
1943
2264
  const whoamiBody = await fetchCommandCodeJson(COMMAND_CODE_WHOAMI_URL, bearer);
1944
2265
  const whoami = asRecord(whoamiBody?.data) ?? whoamiBody;
@@ -1978,13 +2299,7 @@ async function fetchCommandCodeQuota(provider: string, config: OcxProviderConfig
1978
2299
  }
1979
2300
 
1980
2301
  /** Cursor included usage via api2.cursor.sh (Bearer from OAuth) — unofficial, may change. */
1981
- async function fetchCursorQuota(provider: string): Promise<ProviderQuotaReport | null> {
1982
- let accessToken: string;
1983
- try {
1984
- accessToken = await getValidAccessToken("cursor");
1985
- } catch {
1986
- return null;
1987
- }
2302
+ async function fetchCursorQuota(provider: string, accessToken: string): Promise<ProviderQuotaReport | null> {
1988
2303
 
1989
2304
  const authHeaders = {
1990
2305
  Accept: "application/json",
@@ -1997,6 +2312,7 @@ async function fetchCursorQuota(provider: string): Promise<ProviderQuotaReport |
1997
2312
  try {
1998
2313
  const periodRes = await fetch("https://api2.cursor.sh/aiserver.v1.DashboardService/GetCurrentPeriodUsage", {
1999
2314
  method: "POST",
2315
+ redirect: "error",
2000
2316
  headers: {
2001
2317
  ...authHeaders,
2002
2318
  "Content-Type": "application/json",
@@ -2065,6 +2381,7 @@ async function fetchCursorQuota(provider: string): Promise<ProviderQuotaReport |
2065
2381
  try {
2066
2382
  const summaryRes = await fetch("https://api2.cursor.sh/api/usage/summary", {
2067
2383
  headers: authHeaders,
2384
+ redirect: "error",
2068
2385
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
2069
2386
  });
2070
2387
  if (summaryRes.ok) {
@@ -2094,6 +2411,7 @@ async function fetchCursorQuota(provider: string): Promise<ProviderQuotaReport |
2094
2411
 
2095
2412
  const response = await fetch("https://api2.cursor.sh/auth/usage", {
2096
2413
  headers: authHeaders,
2414
+ redirect: "error",
2097
2415
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
2098
2416
  });
2099
2417
  if (!response.ok) return null;
@@ -2180,10 +2498,11 @@ function classifyAntigravityFamily(modelId: string, modelInfo: Record<string, un
2180
2498
  }
2181
2499
 
2182
2500
  function antigravityUsedPercent(quotaInfo: Record<string, unknown>): number | undefined {
2183
- const remaining = normalizePercent(toFiniteNumber(quotaInfo.remainingFraction) !== undefined
2184
- ? toFiniteNumber(quotaInfo.remainingFraction)! * 100
2185
- : toFiniteNumber(quotaInfo.remainingPercentage) !== undefined
2186
- ? toFiniteNumber(quotaInfo.remainingPercentage)! * 100
2501
+ const target = asRecord(quotaInfo.remaining) ?? quotaInfo;
2502
+ const remaining = normalizePercent(toFiniteNumber(target.remainingFraction) !== undefined
2503
+ ? toFiniteNumber(target.remainingFraction)! * 100
2504
+ : toFiniteNumber(target.remainingPercentage) !== undefined
2505
+ ? toFiniteNumber(target.remainingPercentage)! * 100
2187
2506
  : undefined);
2188
2507
  if (remaining === undefined) return undefined;
2189
2508
  return normalizePercent(100 - remaining);
@@ -2218,6 +2537,75 @@ function antigravityWindowsFromModels(body: Record<string, unknown> | null): Pro
2218
2537
  return customWindows;
2219
2538
  }
2220
2539
 
2540
+ /**
2541
+ * Parse Google Antigravity quota from `v1internal:retrieveUserQuotaSummary`.
2542
+ * Groups contain Gemini models and Claude/3P models, each with 5h and weekly limit buckets.
2543
+ */
2544
+ function parseAntigravityQuotaSummary(body: Record<string, unknown> | null): ProviderQuota | null {
2545
+ const groups = Array.isArray(body?.groups) ? (body.groups as unknown[]) : [];
2546
+ if (groups.length === 0) return null;
2547
+
2548
+ const customWindowsMap = new Map<string, ProviderQuotaWindow>();
2549
+
2550
+ for (const rawGroup of groups) {
2551
+ const group = asRecord(rawGroup);
2552
+ if (!group) continue;
2553
+ const groupName = `${typeof group.displayName === "string" ? group.displayName : ""} ${typeof group.description === "string" ? group.description : ""}`.toLowerCase();
2554
+ const isGemini = groupName.includes("gemini");
2555
+ const isClaude = groupName.includes("claude") || groupName.includes("3p") || groupName.includes("gpt");
2556
+
2557
+ const buckets = Array.isArray(group.buckets) ? (group.buckets as unknown[]) : [];
2558
+ for (const rawBucket of buckets) {
2559
+ const bucket = asRecord(rawBucket);
2560
+ if (!bucket) continue;
2561
+ const windowStr = `${typeof bucket.window === "string" ? bucket.window : ""} ${typeof bucket.bucketId === "string" ? bucket.bucketId : ""} ${typeof bucket.displayName === "string" ? bucket.displayName : ""}`.toLowerCase();
2562
+ const percent = antigravityUsedPercent(bucket);
2563
+ if (percent === undefined) continue;
2564
+ const resetAt = normalizeResetAt(bucket.resetTime);
2565
+
2566
+ const isWeekly = windowStr.includes("week");
2567
+ const is5h = windowStr.includes("5h") || windowStr.includes("five");
2568
+
2569
+ if (isGemini) {
2570
+ const label = is5h ? "Gem" : isWeekly ? "Gem (Weekly)" : "";
2571
+ if (label && !customWindowsMap.has(label)) {
2572
+ customWindowsMap.set(label, { label, percent, ...(resetAt !== undefined ? { resetAt } : {}) });
2573
+ }
2574
+ } else if (isClaude) {
2575
+ const label = is5h ? "Cla" : isWeekly ? "Cla (Weekly)" : "";
2576
+ if (label && !customWindowsMap.has(label)) {
2577
+ customWindowsMap.set(label, { label, percent, ...(resetAt !== undefined ? { resetAt } : {}) });
2578
+ }
2579
+ } else {
2580
+ const baseLabel = typeof group.displayName === "string" ? group.displayName : "Other";
2581
+ const label = isWeekly ? `${baseLabel} (Weekly)` : baseLabel;
2582
+ if (!customWindowsMap.has(label)) {
2583
+ customWindowsMap.set(label, { label, percent, ...(resetAt !== undefined ? { resetAt } : {}) });
2584
+ }
2585
+ }
2586
+ }
2587
+ }
2588
+
2589
+ const PREFERRED_ORDER = ["Gem", "Gem (Weekly)", "Cla", "Cla (Weekly)"];
2590
+ const customWindows = Array.from(customWindowsMap.values()).sort((a, b) => {
2591
+ const ia = PREFERRED_ORDER.indexOf(a.label);
2592
+ const ib = PREFERRED_ORDER.indexOf(b.label);
2593
+ if (ia !== -1 && ib !== -1) return ia - ib;
2594
+ if (ia !== -1) return -1;
2595
+ if (ib !== -1) return 1;
2596
+ return a.label.localeCompare(b.label);
2597
+ });
2598
+
2599
+ if (customWindows.length === 0) {
2600
+ return null;
2601
+ }
2602
+
2603
+ return {
2604
+ customWindows,
2605
+ updatedAt: Date.now(),
2606
+ };
2607
+ }
2608
+
2221
2609
  const ANTIGRAVITY_ACCOUNT_QUOTA_BASE = "https://daily-cloudcode-pa.googleapis.com";
2222
2610
  let antigravityOutboundDependencies: ProviderOutboundDependencies = {};
2223
2611
 
@@ -2234,6 +2622,28 @@ export function setAntigravityAccountQuotaTransportForTests(dependencies: Provid
2234
2622
  * A redirect or non-2xx yields null (unavailable), never a partial row.
2235
2623
  */
2236
2624
  export async function fetchAntigravityUsageQuota(accessToken: string, projectId: string): Promise<ProviderQuota | null> {
2625
+ const summaryUrl = `${ANTIGRAVITY_ACCOUNT_QUOTA_BASE}/v1internal:retrieveUserQuotaSummary`;
2626
+ try {
2627
+ const summaryResponse = await providerOutboundPost("google-antigravity", { baseUrl: ANTIGRAVITY_ACCOUNT_QUOTA_BASE }, summaryUrl, {
2628
+ headers: {
2629
+ Accept: "application/json",
2630
+ "Content-Type": "application/json",
2631
+ "User-Agent": antigravityUserAgent(),
2632
+ Authorization: `Bearer ${accessToken}`,
2633
+ },
2634
+ body: JSON.stringify({ project: projectId }),
2635
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
2636
+ }, antigravityOutboundDependencies);
2637
+ if (await providerRedirectError(summaryResponse, summaryUrl)) return null;
2638
+ if (summaryResponse.status === 401 || summaryResponse.status === 403) return null;
2639
+ if (summaryResponse.ok) {
2640
+ const quota = parseAntigravityQuotaSummary(asRecord(await readQuotaJson(summaryResponse)));
2641
+ if (quota) return quota;
2642
+ }
2643
+ } catch {
2644
+ // Fallback to fetchAvailableModels on error
2645
+ }
2646
+
2237
2647
  const url = `${ANTIGRAVITY_ACCOUNT_QUOTA_BASE}/v1internal:fetchAvailableModels`;
2238
2648
  const response = await providerOutboundPost("google-antigravity", { baseUrl: ANTIGRAVITY_ACCOUNT_QUOTA_BASE }, url, {
2239
2649
  headers: {
@@ -2262,6 +2672,35 @@ async function fetchAntigravityQuota(provider: string, config: OcxProviderConfig
2262
2672
  return null;
2263
2673
  }
2264
2674
  const baseUrl = (config.baseUrl || ANTIGRAVITY_ACCOUNT_QUOTA_BASE).replace(/\/+$/, "");
2675
+
2676
+ // The summary probe is pinned to Google's own host through the provider-outbound
2677
+ // transport, mirroring `fetchAntigravityUsageQuota` above: a configured `baseUrl` is a
2678
+ // routing choice for requests, not a second source of Google's accounting, and this
2679
+ // request carries the account bearer.
2680
+ const summaryUrl = `${ANTIGRAVITY_ACCOUNT_QUOTA_BASE}/v1internal:retrieveUserQuotaSummary`;
2681
+ try {
2682
+ const summaryResponse = await providerOutboundPost("google-antigravity", { baseUrl: ANTIGRAVITY_ACCOUNT_QUOTA_BASE }, summaryUrl, {
2683
+ headers: {
2684
+ Accept: "application/json",
2685
+ "Content-Type": "application/json",
2686
+ "User-Agent": antigravityUserAgent(),
2687
+ Authorization: `Bearer ${accessToken}`,
2688
+ },
2689
+ body: JSON.stringify({ project: credential.projectId }),
2690
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
2691
+ }, antigravityOutboundDependencies);
2692
+ if (await providerRedirectError(summaryResponse, summaryUrl)) return null;
2693
+ if (summaryResponse.status === 401 || summaryResponse.status === 403) return null;
2694
+ if (summaryResponse.ok) {
2695
+ const quota = parseAntigravityQuotaSummary(asRecord(await readQuotaJson(summaryResponse)));
2696
+ if (quota) {
2697
+ return report(provider, "google-antigravity:retrieveUserQuotaSummary", quota);
2698
+ }
2699
+ }
2700
+ } catch {
2701
+ // Fallback on network/fetch error
2702
+ }
2703
+
2265
2704
  const response = await fetch(`${baseUrl}/v1internal:fetchAvailableModels`, {
2266
2705
  method: "POST",
2267
2706
  headers: {
@@ -2282,6 +2721,54 @@ async function fetchAntigravityQuota(provider: string, config: OcxProviderConfig
2282
2721
  });
2283
2722
  }
2284
2723
 
2724
+ type KeyQuotaReader = (name: string, provider: OcxProviderConfig) => Promise<ProviderQuotaProbeResult>;
2725
+
2726
+ /** Same selector drives cheap capabilities and uncached reads; never resolves credentials. */
2727
+ function keyQuotaReaderForProvider(name: string, provider: OcxProviderConfig): KeyQuotaReader | null {
2728
+ if (provider.disabled === true || (provider.authMode ?? "key") !== "key") return null;
2729
+ if (isCanonicalKimiCodeBaseUrl(provider.baseUrl)) {
2730
+ return async (id, config) => {
2731
+ const bearer = await resolveKimiQuotaBearer(config);
2732
+ return bearer ? fetchKimiQuota(id, config, bearer) : null;
2733
+ };
2734
+ }
2735
+ if (name === "commandcode" && isCanonicalCommandCodeBaseUrl(provider.baseUrl)) {
2736
+ return async (id, config) => {
2737
+ const bearer = await resolveCommandCodeQuotaBearer(config);
2738
+ return bearer ? fetchCommandCodeQuota(id, config, bearer) : null;
2739
+ };
2740
+ }
2741
+ if (registryEntryForProviderDestination(provider)?.id === "opencode-go") return fetchOpenCodeGoQuota;
2742
+ if (isCanonicalA6apiBaseUrl(provider.baseUrl)) return fetchA6apiQuota;
2743
+ if (name === "openrouter" && isCanonicalOpenRouterBaseUrl(provider.baseUrl)) return fetchOpenRouterQuota;
2744
+ if (name === "deepseek" && isCanonicalDeepSeekBaseUrl(provider.baseUrl)) return fetchDeepSeekQuota;
2745
+ if (name === "cline-pass" && isCanonicalClineBaseUrl(provider.baseUrl)) return fetchClineQuota;
2746
+ if (isCanonicalOllamaCloudBaseUrl(provider.baseUrl ?? getProviderRegistryEntry(name)?.baseUrl)) return fetchOllamaCloudQuota;
2747
+ if (["zai", "glm", "glm-cn", "zhipu-bigmodel-coding"].includes(name) && isCanonicalZaiBaseUrl(provider.baseUrl)) return fetchZaiQuota;
2748
+ if (["minimax", "minimax-cn"].includes(name) && isCanonicalMinimaxBaseUrl(provider.baseUrl)) return fetchMinimaxQuota;
2749
+ if (name === "moonshot" && isCanonicalMoonshotBaseUrl(provider.baseUrl)) return fetchMoonshotQuota;
2750
+ if (name === "venice" && isCanonicalVeniceBaseUrl(provider.baseUrl)) return fetchVeniceQuota;
2751
+ if (name === "synthetic" && isCanonicalSyntheticBaseUrl(provider.baseUrl)) return fetchSyntheticQuota;
2752
+ if (name === "deepinfra" && isCanonicalDeepInfraBaseUrl(provider.baseUrl)) return fetchDeepInfraQuota;
2753
+ if (name === "neuralwatt" && isCanonicalNeuralwattBaseUrl(provider.baseUrl)) return fetchNeuralwattQuota;
2754
+ return null;
2755
+ }
2756
+
2757
+ export function providerApiKeyQuotaMode(name: string, provider: OcxProviderConfig): AccountQuotaMode {
2758
+ return keyQuotaReaderForProvider(name, provider) ? "probe" : "unsupported";
2759
+ }
2760
+
2761
+ export async function fetchProviderApiKeyQuotas(config: OcxConfig, name: string, forceRefresh = false): Promise<ProviderApiKeyQuota[]> {
2762
+ const provider = config.providers[name];
2763
+ if (!provider || !keyQuotaReaderForProvider(name, provider)) return [];
2764
+ return readProviderApiKeyQuotas(config, name, forceRefresh, async (isolatedProvider, isolatedConfig) => {
2765
+ const result = await maybeFetchProviderQuota(name, isolatedProvider, isolatedConfig, false);
2766
+ if (result === TERMINAL_QUOTA_FAILURE) return { kind: "terminal" };
2767
+ if (result === AUTHORITATIVE_EMPTY_QUOTA) return { kind: "empty" };
2768
+ return result ? { kind: "quota", quota: result.quota } : { kind: "unavailable" };
2769
+ });
2770
+ }
2771
+
2285
2772
  async function maybeFetchProviderQuota(
2286
2773
  name: string,
2287
2774
  provider: OcxProviderConfig,
@@ -2294,76 +2781,108 @@ async function maybeFetchProviderQuota(
2294
2781
  if (isBuiltInChatGptForwardProvider(name, provider)) {
2295
2782
  return fetchChatGptForwardQuota(config, name, provider, forceRefresh, prefetchedCodexSnapshot);
2296
2783
  }
2297
- if (provider.authMode === "oauth" && name === "xai") return fetchXaiQuota(name);
2784
+ if (provider.authMode === "oauth" && explicitAccountReader(name)) return await fetchExplicitCurrentQuota(name, provider, config);
2298
2785
  if (provider.authMode === "oauth" && name === "anthropic") return fetchAnthropicQuota(name);
2299
- if (provider.authMode === "oauth" && name === "cursor") return fetchCursorQuota(name);
2300
2786
  if (provider.authMode === "oauth" && name === "google-antigravity") return fetchAntigravityQuota(name, provider);
2301
2787
  if (provider.authMode === "oauth" && name === "kiro") return fetchKiroQuota(name);
2302
- // Kimi Code `/usages` accepts OAuth or coding-plan API keys, but only on the canonical
2303
- // host and only for real key auth forward/local modes carry no credential of ours.
2304
- if (provider.authMode === "oauth" && name === "kimi") return fetchKimiQuota(name, provider);
2305
- if (provider.authMode === "key" && isCanonicalKimiCodeBaseUrl(provider.baseUrl)) {
2306
- return fetchKimiQuota(name, provider);
2307
- }
2308
- // OAuth account login or Provider-API key only; forward/local modes carry no
2309
- // credential of ours on the canonical host.
2310
- if (provider.authMode === "oauth" && name === "command-code") {
2311
- return fetchCommandCodeQuota(name, provider);
2312
- }
2313
- if ((provider.authMode ?? "key") === "key" && name === "commandcode"
2314
- && isCanonicalCommandCodeBaseUrl(provider.baseUrl)) {
2315
- return fetchCommandCodeQuota(name, provider);
2316
- }
2317
- // Identify OpenCode Go by where it routes, not by what the row is called. Multi-account
2318
- // setups keep the same destination under names like `opencode-go-2` (#1924), and those rows
2319
- // silently had no quota panel and no `ocx provider quota --json` report while the literal
2320
- // name was the gate. `registryEntryForProviderDestination` is the existing predicate for
2321
- // exactly this question: normalized endpoint + adapter + key auth, so a canonical URL behind
2322
- // a different adapter is still not OpenCode Go. The defensive URL check inside
2323
- // `fetchOpenCodeGoQuota` stays — sending a key anywhere must not depend on this gate.
2324
- if ((provider.authMode ?? "key") === "key" && registryEntryForProviderDestination(provider)?.id === "opencode-go") {
2325
- return fetchOpenCodeGoQuota(name, provider);
2326
- }
2327
- if ((provider.authMode ?? "key") === "key" && isCanonicalA6apiBaseUrl(provider.baseUrl)) {
2328
- return fetchA6apiQuota(name, provider);
2329
- }
2330
- if ((provider.authMode ?? "key") === "key" && name === "openrouter") {
2331
- return fetchOpenRouterQuota(name, provider);
2332
- }
2333
- if ((provider.authMode ?? "key") === "key" && name === "deepseek") {
2334
- return fetchDeepSeekQuota(name, provider);
2335
- }
2336
- if ((provider.authMode ?? "key") === "key" && name === "cline-pass") {
2337
- return fetchClineQuota(name, provider);
2338
- }
2339
- if ((provider.authMode ?? "key") === "key"
2340
- && (name === "zai" || name === "glm" || name === "glm-cn" || name === "zhipu-bigmodel-coding")) {
2341
- return fetchZaiQuota(name, provider);
2342
- }
2343
- if ((provider.authMode ?? "key") === "key" && (name === "minimax" || name === "minimax-cn")) {
2344
- return fetchMinimaxQuota(name, provider);
2345
- }
2346
- if ((provider.authMode ?? "key") === "key" && name === "moonshot") {
2347
- return fetchMoonshotQuota(name, provider);
2348
- }
2349
- if ((provider.authMode ?? "key") === "key" && name === "venice") {
2350
- return fetchVeniceQuota(name, provider);
2351
- }
2352
- if ((provider.authMode ?? "key") === "key" && name === "synthetic") {
2353
- return fetchSyntheticQuota(name, provider);
2354
- }
2355
- if ((provider.authMode ?? "key") === "key" && name === "deepinfra") {
2356
- return fetchDeepInfraQuota(name, provider);
2357
- }
2358
- if ((provider.authMode ?? "key") === "key" && name === "neuralwatt") {
2359
- return fetchNeuralwattQuota(name, provider);
2360
- }
2361
- return null;
2788
+ // Passive providers (meta-muse): Meta publishes no quota endpoint, so there is no
2789
+ // probe to run the row is the active account's last in-band observation.
2790
+ if (provider.authMode === "oauth" && hasPassiveAccountQuota(name)) return fetchPassiveProviderQuota(name);
2791
+ const reader = keyQuotaReaderForProvider(name, provider);
2792
+ return reader ? reader(name, provider) : null;
2362
2793
  } catch {
2363
2794
  return null;
2364
2795
  }
2365
2796
  }
2366
2797
 
2798
+ /**
2799
+ * Hand freshly committed provider reports to the optional quota-reset observer.
2800
+ *
2801
+ * Lazy import on purpose: this module is statically reachable from
2802
+ * src/server/responses/core.ts (via oauth/anthropic-routing.ts), so a static edge would put
2803
+ * the observer and its sink registry on every install's request path.
2804
+ *
2805
+ * No previous snapshot is passed. The observer keeps its own persisted last-seen map,
2806
+ * because `previous` here is bound only when `cache.key === key` and the key digest at
2807
+ * cacheKeyWithAggregationState includes quota values and updatedAt — so it is empty exactly
2808
+ * when a reset happened.
2809
+ *
2810
+ * Account identity is resolved SYNCHRONOUSLY, before any await. Two reasons, both observed:
2811
+ * pool failover rewrites `activeAccountId` during request routing
2812
+ * (promoteAnthropicActiveAccount -> setActiveAccount), so a 429 between this commit and a
2813
+ * later async read would attribute this report to a different account and overwrite that
2814
+ * account's baseline with these numbers. fetchAnthropicQuota already captures
2815
+ * `probedAccountId` before awaiting for exactly this reason; the observer must not be
2816
+ * sloppier than the cache it observes.
2817
+ *
2818
+ * Observations are serialized through a module-level promise chain for the same reason as
2819
+ * the codex seam: Bun does not resolve concurrent import() calls in call order, and an
2820
+ * out-of-order baseline swap manufactures false resets that then burn the durable
2821
+ * idempotence key.
2822
+ */
2823
+ let pendingProviderObservation: Promise<void> = Promise.resolve();
2824
+
2825
+ /**
2826
+ * Stable per-account observation key for one provider report.
2827
+ *
2828
+ * OAuth providers key by active account id. Key-auth providers have NO account set, so
2829
+ * every key in `apiKeyPool` would collapse onto "default" and rotating from a spent key to
2830
+ * a fresh one would read as a reset (measured: 97% -> 12% fired a false surprise). The
2831
+ * cache key already discriminates these at cacheKey() via apiKeyPoolEntryId, so this
2832
+ * mirrors that discriminator instead of inventing a second notion of identity.
2833
+ */
2834
+ function providerObservationAccountKey(provider: string, config: OcxConfig): string {
2835
+ const oauthAccountId = getAccountSet(provider)?.activeAccountId;
2836
+ if (oauthAccountId !== undefined) return `${provider}\u0000${oauthAccountId}`;
2837
+ const providerConfig = config.providers[provider];
2838
+ const resolvedKey = typeof providerConfig?.apiKey === "string"
2839
+ ? resolveEnvValue(providerConfig.apiKey)?.trim()
2840
+ : undefined;
2841
+ const keyId = resolvedKey ? apiKeyPoolEntryId(resolvedKey) : "default";
2842
+ return `${provider}\u0000key:${keyId}`;
2843
+ }
2844
+
2845
+ /** Test-only view of the observation account key. */
2846
+ export function providerObservationAccountKeyForTests(provider: string, config: OcxConfig): string {
2847
+ return providerObservationAccountKey(provider, config);
2848
+ }
2849
+
2850
+ function notifyProviderQuotaSnapshot(
2851
+ reports: ReadonlyArray<ProviderQuotaReport>,
2852
+ config: OcxConfig,
2853
+ ): void {
2854
+ if (reports.length === 0) return;
2855
+ // Resolved here, synchronously, while the identity is still the one that produced these
2856
+ // reports.
2857
+ const observations = reports.map(report => ({
2858
+ scope: report.provider,
2859
+ accountKey: providerObservationAccountKey(report.provider, config),
2860
+ quota: report.quota,
2861
+ }));
2862
+ pendingProviderObservation = pendingProviderObservation
2863
+ .then(async () => {
2864
+ const observer = await import("../quota/reset-observer");
2865
+ if (!observer.hasQuotaResetSink()) return;
2866
+ const { providerWindowObservations } = await import("../quota/window-mapping");
2867
+ for (const observation of observations) {
2868
+ observer.observeQuotaSnapshot({
2869
+ scope: observation.scope,
2870
+ accountKey: observation.accountKey,
2871
+ windows: providerWindowObservations(observation.quota),
2872
+ });
2873
+ }
2874
+ })
2875
+ .catch(() => {
2876
+ // Detection is best-effort: a quota refresh must never fail because of it. Swallowing
2877
+ // here also keeps the chain alive — a rejected link would poison every later one.
2878
+ });
2879
+ }
2880
+
2881
+ /** Await the observation chain. Tests only: production never needs to join it. */
2882
+ export function flushProviderQuotaObservationsForTests(): Promise<void> {
2883
+ return pendingProviderObservation;
2884
+ }
2885
+
2367
2886
  export async function fetchProviderQuotaReports(config: OcxConfig, forceRefresh = false): Promise<ProviderQuotaResponse> {
2368
2887
  // A Pool report's cache signature and provider fetch must share one account snapshot.
2369
2888
  // Preserve force semantics when deciding whether that snapshot refreshes upstream data.
@@ -2376,9 +2895,14 @@ export async function fetchProviderQuotaReports(config: OcxConfig, forceRefresh
2376
2895
  const now = Date.now();
2377
2896
  // The cache fast path must not extend a preserved last-good row past its 30-minute bound:
2378
2897
  // a row preserved at age 29:59 plus a full 5-minute TTL would otherwise serve until ~35min.
2898
+ // An OBSERVED row is exempt: it carries the observation time, which is older than the bound
2899
+ // by construction and never becomes fresher on its own. Without the exemption a single
2900
+ // configured passive provider makes this predicate permanently false, so every dashboard
2901
+ // poll re-probes every OTHER provider upstream instead of serving the 5-minute cache.
2379
2902
  const cacheFresh = cache && cache.key === key && now - cache.ts < CACHE_TTL_MS
2380
2903
  && cache.response.reports.every(item =>
2381
- now - item.updatedAt < LAST_GOOD_MAX_AGE_MS && isProviderQuotaReportCurrent(item));
2904
+ (item.observed === true || now - item.updatedAt < LAST_GOOD_MAX_AGE_MS)
2905
+ && isProviderQuotaReportCurrent(item));
2382
2906
  if (!forceRefresh && cacheFresh) return cache!.response;
2383
2907
  const joinable = inflight.get(key);
2384
2908
  if (!forceRefresh && joinable && joinable.epoch === invalidationEpoch) return joinable.promise;
@@ -2422,7 +2946,9 @@ export async function fetchProviderQuotaReports(config: OcxConfig, forceRefresh
2422
2946
  const byProvider = new Map<string, ProviderQuotaReport>();
2423
2947
  const generationMismatchedProviders = new Set<string>();
2424
2948
  for (const item of previous) {
2425
- if (item.updatedAt < cutoff) continue;
2949
+ // Same exemption as the fast path. A passive row reaching `previous` is not a probe
2950
+ // that went quiet — there is no probe — so age cannot condemn it.
2951
+ if (item.observed !== true && item.updatedAt < cutoff) continue;
2426
2952
  if (isProviderQuotaReportCurrent(item)) byProvider.set(item.provider, item);
2427
2953
  else generationMismatchedProviders.add(item.provider);
2428
2954
  }
@@ -2451,6 +2977,7 @@ export async function fetchProviderQuotaReports(config: OcxConfig, forceRefresh
2451
2977
  const reports = response.reports.filter(item => mayCommitProviderQuotaKey(item.provider, writerGeneration));
2452
2978
  cache = { key, ts: Date.now(), response: { ...response, reports } };
2453
2979
  replaceCachedProviderQuotas(reports);
2980
+ notifyProviderQuotaSnapshot(reports, config);
2454
2981
  }
2455
2982
  return response;
2456
2983
  })();