@yansigit/opencodex 2.33.0 → 2.35.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 (196) hide show
  1. package/README.md +3 -3
  2. package/gui/dist/assets/index-BjCaHxdz.js +112 -0
  3. package/gui/dist/assets/index-DLkXOXLC.css +1 -0
  4. package/gui/dist/index.html +2 -2
  5. package/package.json +1 -1
  6. package/src/adapters/anthropic.ts +79 -2
  7. package/src/adapters/command-code.ts +141 -23
  8. package/src/adapters/cursor/call-id.ts +44 -0
  9. package/src/adapters/cursor/checkpoint-store.ts +15 -10
  10. package/src/adapters/cursor/discovery.ts +60 -2
  11. package/src/adapters/cursor/effort-map.ts +79 -1
  12. package/src/adapters/cursor/envelope-echo.ts +162 -0
  13. package/src/adapters/cursor/live-models.ts +7 -2
  14. package/src/adapters/cursor/live-transport.ts +17 -1
  15. package/src/adapters/cursor/message-mapper.ts +4 -1
  16. package/src/adapters/cursor/native-exec-fs.ts +13 -12
  17. package/src/adapters/cursor/native-exec-network.ts +3 -5
  18. package/src/adapters/cursor/native-exec-policy.ts +47 -0
  19. package/src/adapters/cursor/native-exec-shell.ts +116 -31
  20. package/src/adapters/cursor/native-exec.ts +38 -10
  21. package/src/adapters/cursor/protobuf-events.ts +28 -2
  22. package/src/adapters/cursor/protobuf-request.ts +93 -41
  23. package/src/adapters/cursor/request-builder.ts +39 -10
  24. package/src/adapters/cursor/tool-definitions.ts +27 -3
  25. package/src/adapters/cursor/tool-result-normalize.ts +51 -6
  26. package/src/adapters/cursor/types.ts +23 -4
  27. package/src/adapters/cursor.ts +170 -29
  28. package/src/adapters/google-aistudio-parser.ts +49 -0
  29. package/src/adapters/google-antigravity-replay.ts +105 -25
  30. package/src/adapters/google-antigravity-wire.ts +5 -0
  31. package/src/adapters/google-errors.ts +41 -12
  32. package/src/adapters/google-http.ts +12 -11
  33. package/src/adapters/google.ts +219 -36
  34. package/src/adapters/image.ts +1 -1
  35. package/src/adapters/kiro-constants.ts +15 -0
  36. package/src/adapters/kiro-tools.ts +43 -15
  37. package/src/adapters/kiro.ts +54 -9
  38. package/src/adapters/openai-chat.ts +286 -242
  39. package/src/adapters/openai-responses.ts +335 -24
  40. package/src/adapters/run-turn-queue.ts +36 -1
  41. package/src/adapters/tool-catalog-nudge.ts +2 -2
  42. package/src/adapters/xai-tool-schema.ts +436 -0
  43. package/src/bridge.ts +67 -26
  44. package/src/chat/inbound.ts +29 -1
  45. package/src/chat/outbound.ts +15 -7
  46. package/src/claude/agents-inject.ts +8 -1
  47. package/src/claude/outbound.ts +10 -8
  48. package/src/cli/account-api.ts +27 -7
  49. package/src/cli/account-extended.ts +10 -3
  50. package/src/cli/account.ts +29 -5
  51. package/src/cli/alias.ts +66 -0
  52. package/src/cli/claude.ts +26 -1
  53. package/src/cli/dispatch.ts +13 -1
  54. package/src/cli/help.ts +1 -0
  55. package/src/cli/index.ts +6 -1
  56. package/src/cli/init.ts +1 -0
  57. package/src/cli/models-runtime.ts +95 -0
  58. package/src/cli/models.ts +13 -7
  59. package/src/cli/provider-runtime.ts +16 -2
  60. package/src/cli/registry.ts +6 -1
  61. package/src/cli/telemetry-commands.ts +25 -0
  62. package/src/cli/v2.ts +34 -10
  63. package/src/codex/account-pause.ts +2 -1
  64. package/src/codex/account-priority.ts +3 -2
  65. package/src/codex/app-server-processes.ts +80 -6
  66. package/src/codex/auth-api.ts +48 -8
  67. package/src/codex/auth-context.ts +21 -18
  68. package/src/codex/catalog/aggregation.ts +6 -0
  69. package/src/codex/catalog/model-metadata.ts +13 -1
  70. package/src/codex/catalog/native-models.ts +5 -2
  71. package/src/codex/catalog/parsing.ts +16 -0
  72. package/src/codex/catalog/provider-fetch.ts +20 -3
  73. package/src/codex/catalog/sync.ts +127 -2
  74. package/src/codex/catalog.ts +1 -1
  75. package/src/codex/codex-write-lock.ts +3 -1
  76. package/src/codex/convergence-types.ts +1 -1
  77. package/src/codex/convergence.ts +22 -2
  78. package/src/codex/desired-state.ts +2 -2
  79. package/src/codex/desktop-app-restart.ts +18 -5
  80. package/src/codex/inject-coordination.ts +83 -0
  81. package/src/codex/inject.ts +14 -1
  82. package/src/codex/log-guard/inspect.ts +22 -4
  83. package/src/codex/model-entitlements.ts +9 -2
  84. package/src/codex/prompt-layers.ts +371 -25
  85. package/src/codex/prompt-text-probe.ts +238 -0
  86. package/src/codex/quota.ts +123 -18
  87. package/src/codex/routing.ts +9 -0
  88. package/src/codex/subagent-model-fallback.ts +198 -27
  89. package/src/codex/transition-state.ts +107 -8
  90. package/src/combos/types.ts +10 -0
  91. package/src/compatibility/openai-responses.ts +33 -1
  92. package/src/config/autonomous-remediation.ts +21 -0
  93. package/src/config/provider-validation.ts +14 -0
  94. package/src/config/rebase-provenance.ts +68 -0
  95. package/src/config.ts +191 -17
  96. package/src/generated/compatibility-version.json +279 -159
  97. package/src/generated/model-metadata.ts +3 -0
  98. package/src/images/loop.ts +5 -4
  99. package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
  100. package/src/lab/fabric/producer-child.ts +1 -1
  101. package/src/lib/config-ownership.ts +20 -0
  102. package/src/lib/errors.ts +11 -2
  103. package/src/lib/package-tree-integrity.ts +101 -0
  104. package/src/oauth/aistudio-credentials.ts +65 -0
  105. package/src/oauth/aistudio-native-daemon.ts +116 -0
  106. package/src/oauth/aistudio-session-sync.ts +95 -0
  107. package/src/oauth/generic-account-failover.ts +231 -0
  108. package/src/oauth/google-aistudio-auth.ts +98 -0
  109. package/src/oauth/index.ts +57 -5
  110. package/src/oauth/key-providers.ts +18 -1
  111. package/src/oauth/kiro.ts +45 -0
  112. package/src/oauth/login-cli.ts +65 -1
  113. package/src/oauth/types.ts +15 -0
  114. package/src/providers/codex-capacity.ts +5 -2
  115. package/src/providers/command-code-efforts.ts +38 -6
  116. package/src/providers/context-cap.ts +4 -3
  117. package/src/providers/default-aliases.ts +65 -0
  118. package/src/providers/derive.ts +29 -1
  119. package/src/providers/fastwire.ts +7 -1
  120. package/src/providers/model-presets.ts +119 -0
  121. package/src/providers/new-model-policy.ts +146 -0
  122. package/src/providers/provider-id-rewrite.ts +2 -1
  123. package/src/providers/quota.ts +157 -46
  124. package/src/providers/registry.ts +184 -71
  125. package/src/providers/slug-codec.ts +52 -0
  126. package/src/responses/code-mode-helper-compat.ts +50 -0
  127. package/src/responses/custom-tool-compat.ts +34 -10
  128. package/src/responses/parser.ts +4 -0
  129. package/src/responses/schema.ts +5 -1
  130. package/src/responses/thought-signature-replay.ts +17 -0
  131. package/src/router.ts +43 -2
  132. package/src/routing/account-pool/cooldown.ts +8 -0
  133. package/src/routing/account-pool/index.ts +1 -0
  134. package/src/routing/analytics.ts +1 -0
  135. package/src/routing/quota.ts +10 -0
  136. package/src/server/auth-cors.ts +24 -0
  137. package/src/server/chat-completions.ts +26 -16
  138. package/src/server/chat-native-sse.ts +3 -3
  139. package/src/server/chat-native.ts +30 -11
  140. package/src/server/claude-messages.ts +1 -1
  141. package/src/server/effort-policy.ts +16 -0
  142. package/src/server/index.ts +180 -14
  143. package/src/server/lifecycle.ts +52 -1
  144. package/src/server/management/agent-settings-routes.ts +31 -15
  145. package/src/server/management/codex-prompt-routes.ts +570 -0
  146. package/src/server/management/combo-routes.ts +2 -1
  147. package/src/server/management/config-routes.ts +27 -9
  148. package/src/server/management/context.ts +9 -0
  149. package/src/server/management/logs-usage-routes.ts +11 -5
  150. package/src/server/management/model-routes.ts +266 -0
  151. package/src/server/management/oauth-account-routes.ts +13 -3
  152. package/src/server/management/provider-routes.ts +137 -3
  153. package/src/server/management/routing-profile-routes.ts +2 -2
  154. package/src/server/management-api.ts +2 -0
  155. package/src/server/port-reclaim.ts +19 -1
  156. package/src/server/relay-eager.ts +147 -20
  157. package/src/server/relay.ts +251 -19
  158. package/src/server/request-log-conversation.ts +33 -0
  159. package/src/server/request-log.ts +48 -21
  160. package/src/server/responses/collaboration.ts +42 -5
  161. package/src/server/responses/combo-stream-preflight.ts +10 -3
  162. package/src/server/responses/core.ts +575 -140
  163. package/src/server/responses/empty-completion-guard.ts +35 -0
  164. package/src/server/responses/fetch-helpers.ts +14 -6
  165. package/src/server/responses/input-admission.ts +3 -1
  166. package/src/server/responses/passthrough-error.ts +33 -9
  167. package/src/server/responses/policy-fallback.ts +1 -1
  168. package/src/server/responses/responses-field-backfill.ts +105 -13
  169. package/src/server/responses/ws-upstream.ts +35 -5
  170. package/src/server/responses-custom-tool-repair.ts +52 -7
  171. package/src/server/responses-terminal-repair.ts +25 -4
  172. package/src/server/sse-frame-buffer.ts +31 -4
  173. package/src/server/ws-bridge.ts +14 -2
  174. package/src/smoke/fingerprint-cache.ts +133 -0
  175. package/src/smoke/live-scenarios.ts +33 -0
  176. package/src/smoke/runner.ts +119 -0
  177. package/src/telemetry/dispatcher.ts +44 -0
  178. package/src/telemetry/fingerprint.ts +24 -0
  179. package/src/telemetry/hook.ts +43 -0
  180. package/src/telemetry/ledger.ts +54 -0
  181. package/src/telemetry/types.ts +23 -0
  182. package/src/types/config.ts +66 -14
  183. package/src/types/provider.ts +79 -1
  184. package/src/types/request.ts +18 -10
  185. package/src/types/tools.ts +30 -11
  186. package/src/types.ts +1 -0
  187. package/src/usage/command-code-manifest.ts +116 -0
  188. package/src/usage/cost.ts +2 -2
  189. package/src/usage/expected-prices.ts +126 -24
  190. package/src/usage/log.ts +18 -8
  191. package/src/usage/summary.ts +34 -12
  192. package/src/web-search/exa-executor.ts +40 -9
  193. package/src/web-search/index.ts +16 -8
  194. package/src/web-search/loop.ts +5 -4
  195. package/gui/dist/assets/index-DKLr4LTE.js +0 -102
  196. package/gui/dist/assets/index-DrSQdTRd.css +0 -1
@@ -3,7 +3,9 @@ import {
3
3
  effectiveCodexAuthAccountId,
4
4
  fetchMainAccountInfoSnapshot,
5
5
  listCodexAuthAccountsSnapshot,
6
+ withSparkVisibility,
6
7
  } from "../codex/auth-api";
8
+ import type { StoredAccountQuota } from "../codex/quota";
7
9
  import { isMainAccountIdentityGenerationLive } from "../codex/main-account-cache";
8
10
  import { MAIN_CODEX_ACCOUNT_ID } from "../codex/main-account";
9
11
  import { codexPlanKey } from "../codex/plan";
@@ -173,6 +175,27 @@ function quotaSignatureValue(quota: CodexCapacityQuota | null): unknown {
173
175
  };
174
176
  }
175
177
 
178
+ function providerQuotaFromCodexQuota(
179
+ quota: StoredAccountQuota | Omit<StoredAccountQuota, "updatedAt"> | null | undefined,
180
+ ): CodexCapacityQuota | null {
181
+ if (!quota) return null;
182
+ // Every Codex-sourced provider report funnels through here — the pooled path via
183
+ // listCodexAuthAccountsSnapshot and the `direct` path via fetchMainAccountInfoSnapshot, which
184
+ // never touches the Codex Auth DTO. Applying the Spark preference at this one point is what
185
+ // stops the row surviving on /api/provider-quotas after the operator switched it off.
186
+ quota = withSparkVisibility(quota ?? null) ?? quota;
187
+ return {
188
+ ...(quota.shortPercent !== undefined ? { fiveHourPercent: quota.shortPercent } : {}),
189
+ ...(quota.shortResetAt !== undefined ? { fiveHourResetAt: quota.shortResetAt } : {}),
190
+ ...(quota.weeklyPercent !== undefined ? { weeklyPercent: quota.weeklyPercent } : {}),
191
+ ...(quota.weeklyResetAt !== undefined ? { weeklyResetAt: quota.weeklyResetAt } : {}),
192
+ ...(quota.monthlyPercent !== undefined ? { monthlyPercent: quota.monthlyPercent } : {}),
193
+ ...(quota.monthlyResetAt !== undefined ? { monthlyResetAt: quota.monthlyResetAt } : {}),
194
+ ...(quota.customWindows !== undefined ? { customWindows: quota.customWindows } : {}),
195
+ updatedAt: "updatedAt" in quota ? quota.updatedAt : Date.now(),
196
+ };
197
+ }
198
+
176
199
  /** Hash only presentation-relevant state; account ids and email addresses never enter the key. */
177
200
  function cacheKeyWithAggregationState(
178
201
  config: OcxConfig,
@@ -190,7 +213,7 @@ function cacheKeyWithAggregationState(
190
213
  plan: codexPlanKey(account.plan) ?? null,
191
214
  paused: account.paused,
192
215
  needsReauth: account.needsReauth === true,
193
- quota: quotaSignatureValue(account.quota as CodexCapacityQuota | null),
216
+ quota: quotaSignatureValue(providerQuotaFromCodexQuota(account.quota)),
194
217
  }));
195
218
  const canonicalRows = rows.map(row => JSON.stringify(row)).sort();
196
219
  const digest = createHash("sha256").update(JSON.stringify(canonicalRows)).digest("hex").slice(0, 24);
@@ -1110,9 +1133,8 @@ async function fetchChatGptForwardQuota(
1110
1133
  ): Promise<ProviderQuotaReport | null> {
1111
1134
  if (providerCodexAccountMode(provider, providerConfig) === "direct") {
1112
1135
  const snapshot = await fetchMainAccountInfoSnapshot(forceRefresh);
1113
- const quota = snapshot.info.quota
1114
- ? { ...snapshot.info.quota, updatedAt: Date.now() } as ProviderQuota
1115
- : null;
1136
+ const quota = providerQuotaFromCodexQuota(snapshot.info.quota);
1137
+ if (quota) quota.updatedAt = Date.now();
1116
1138
  return quota
1117
1139
  ? tagNativeMainReport(report(provider, "chatgpt:wham", quota), snapshot.mainIdentityGeneration)
1118
1140
  : null;
@@ -1120,10 +1142,14 @@ async function fetchChatGptForwardQuota(
1120
1142
  const snapshot = await (prefetchedSnapshot ?? listCodexAuthAccountsSnapshot(config, forceRefresh));
1121
1143
  const accounts = snapshot.accounts;
1122
1144
  const activeId = effectiveCodexAuthAccountId(config);
1123
- const capacityAccounts = accounts.map(account => ({ ...account, active: account.id === activeId }));
1145
+ const capacityAccounts = accounts.map(account => ({
1146
+ ...account,
1147
+ active: account.id === activeId,
1148
+ quota: providerQuotaFromCodexQuota(account.quota),
1149
+ }));
1124
1150
  const active = capacityAccounts.find(account => account.active)
1125
- ?? accounts.find(account => account.id === MAIN_CODEX_ACCOUNT_ID)
1126
- ?? accounts[0];
1151
+ ?? capacityAccounts.find(account => account.id === MAIN_CODEX_ACCOUNT_ID)
1152
+ ?? capacityAccounts[0];
1127
1153
  const now = Date.now();
1128
1154
  const capacity = aggregateCodexPoolCapacity(capacityAccounts, now);
1129
1155
  if (capacity.aggregation && capacity.quota) {
@@ -1139,7 +1165,12 @@ async function fetchChatGptForwardQuota(
1139
1165
  }
1140
1166
  const activeUsable = !!active && !active.paused && active.needsReauth !== true;
1141
1167
  const quota = activeUsable && active?.quota
1142
- ? { ...active.quota, updatedAt: active.quota.updatedAt ?? Date.now() } as CodexCapacityQuota
1168
+ ? {
1169
+ ...active.quota,
1170
+ fiveHourPercent: active.quota.fiveHourPercent ?? active.quota.shortPercent,
1171
+ fiveHourResetAt: active.quota.fiveHourResetAt ?? active.quota.shortResetAt,
1172
+ updatedAt: active.quota.updatedAt ?? Date.now(),
1173
+ } as CodexCapacityQuota
1143
1174
  : null;
1144
1175
  const quotaFresh = !!quota
1145
1176
  && Number.isFinite(quota.updatedAt)
@@ -1285,6 +1316,24 @@ function parseClaudeBucket(value: unknown): { percent?: number; resetAt?: number
1285
1316
  return { percent, resetAt };
1286
1317
  }
1287
1318
 
1319
+ function parseClaudeLimit(value: unknown): { label: string; percent: number; resetAt?: number } | null {
1320
+ const rec = asRecord(value);
1321
+ if (!rec) return null;
1322
+ const percent = normalizePercent(rec.percent);
1323
+ if (percent === undefined) return null;
1324
+ const scope = asRecord(rec.scope);
1325
+ const model = asRecord(scope?.model);
1326
+ const rawLabel = String(model?.display_name ?? "").trim();
1327
+ if (!rawLabel) return null;
1328
+ const lowerLabel = rawLabel.toLowerCase();
1329
+ const label = lowerLabel.includes("fable") ? "Fable"
1330
+ : lowerLabel.includes("opus") ? "Opus"
1331
+ : lowerLabel.includes("sonnet") ? "Sonnet"
1332
+ : rawLabel;
1333
+ const resetAt = normalizeResetAt(rec.resets_at);
1334
+ return { label, percent, ...(resetAt !== undefined ? { resetAt } : {}) };
1335
+ }
1336
+
1288
1337
  /** Claude's OAuth usage endpoint, probed with ONE account's own bearer token. */
1289
1338
  const anthropicUsageInflight = new Map<string, Promise<ProviderQuota | null>>();
1290
1339
 
@@ -1308,11 +1357,25 @@ async function fetchAnthropicUsageQuota(accessToken: string): Promise<ProviderQu
1308
1357
  if (!body) return null;
1309
1358
  const fiveHour = parseClaudeBucket(body.five_hour);
1310
1359
  const sevenDay = parseClaudeBucket(body.seven_day);
1360
+ const fable = parseClaudeBucket(body.seven_day_fable);
1311
1361
  const opus = parseClaudeBucket(body.seven_day_opus);
1312
1362
  const sonnet = parseClaudeBucket(body.seven_day_sonnet);
1313
1363
  const customWindows: ProviderQuotaWindow[] = [];
1364
+ if (fable?.percent !== undefined) customWindows.push({ label: "Fable", percent: fable.percent, ...(fable.resetAt !== undefined ? { resetAt: fable.resetAt } : {}) });
1314
1365
  if (opus?.percent !== undefined) customWindows.push({ label: "Opus", percent: opus.percent, ...(opus.resetAt !== undefined ? { resetAt: opus.resetAt } : {}) });
1315
1366
  if (sonnet?.percent !== undefined) customWindows.push({ label: "Sonnet", percent: sonnet.percent, ...(sonnet.resetAt !== undefined ? { resetAt: sonnet.resetAt } : {}) });
1367
+ const knownLabels = new Set(customWindows.map(window => window.label.toLowerCase()));
1368
+ const limits = Array.isArray(body.limits) ? body.limits : [];
1369
+ for (const rawLimit of limits) {
1370
+ const limitRecord = asRecord(rawLimit);
1371
+ // `session` and `weekly_all` mirror the canonical five-hour and weekly
1372
+ // buckets above; only model-scoped weekly limits add a third window.
1373
+ if (String(limitRecord?.kind ?? "").trim().toLowerCase() !== "weekly_scoped") continue;
1374
+ const limit = parseClaudeLimit(rawLimit);
1375
+ if (!limit || knownLabels.has(limit.label.toLowerCase())) continue;
1376
+ knownLabels.add(limit.label.toLowerCase());
1377
+ customWindows.push(limit);
1378
+ }
1316
1379
  const quota: ProviderQuota = {
1317
1380
  // Claude's 5-hour window is a first-class rate limit, same as the Codex login 5h/weekly
1318
1381
  // rows: report it in the canonical fields so the dashboard renders it with the standard
@@ -1401,7 +1464,12 @@ export interface ProviderAccountQuota {
1401
1464
 
1402
1465
  /** Providers whose per-account quota can be probed. Extend as other OAuth APIs are covered. */
1403
1466
  export function supportsPerAccountQuota(provider: string): boolean {
1404
- return provider === "anthropic" || provider === "google-antigravity";
1467
+ return (
1468
+ provider === "anthropic" ||
1469
+ provider === "google-antigravity" ||
1470
+ provider === "command-code" ||
1471
+ provider === "cursor"
1472
+ );
1405
1473
  }
1406
1474
 
1407
1475
  function accountCacheKey(provider: string, accountId: string): string {
@@ -1526,6 +1594,14 @@ async function fetchAccountQuota(
1526
1594
  quota = await fetchAnthropicUsageQuota(token);
1527
1595
  } else if (provider === "google-antigravity") {
1528
1596
  quota = await fetchAntigravityAccountQuota(accountId);
1597
+ } else if (provider === "command-code") {
1598
+ const token = await getTokenForAccountQuotaProbe(provider, accountId);
1599
+ const res = await fetchCommandCodeUsageQuota(token);
1600
+ quota = res === TERMINAL_QUOTA_FAILURE ? null : res;
1601
+ } else if (provider === "cursor") {
1602
+ const token = await getTokenForAccountQuotaProbe(provider, accountId);
1603
+ const res = await fetchCursorUsageQuota(token);
1604
+ quota = res?.quota ?? null;
1529
1605
  }
1530
1606
  if (!quota) {
1531
1607
  // Preserve last-good bars and mark unavailable; advance TTL so failures
@@ -1741,8 +1817,9 @@ function parseCommandCodeWindow(value: unknown): { percent: number; resetAt?: nu
1741
1817
  if (!row) return null;
1742
1818
  const cap = toFiniteNumber(row.cap);
1743
1819
  const used = toFiniteNumber(row.used);
1744
- if (cap === undefined || used === undefined || cap <= 0 || used < 0) return null;
1745
- const percent = normalizePercent((used / cap) * 100);
1820
+ const percent = cap !== undefined && used !== undefined && cap > 0 && used >= 0
1821
+ ? normalizePercent((used / cap) * 100)
1822
+ : normalizePercent(row.percent);
1746
1823
  if (percent === undefined) return null;
1747
1824
  const resetAt = quotaResetAt(row);
1748
1825
  return { percent, ...(resetAt !== undefined ? { resetAt } : {}) };
@@ -1829,11 +1906,7 @@ async function resolveCommandCodeQuotaBearer(config: OcxProviderConfig): Promise
1829
1906
  * usage view uses (windowLimits.fiveHour / windowLimits.weekly), plus soft
1830
1907
  * whoami (team orgId scoping) and subscription-scoped spend for creditsUsd.
1831
1908
  */
1832
- async function fetchCommandCodeQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
1833
- // Never release credentials to a user-edited or lookalike provider host.
1834
- if (!isCanonicalCommandCodeBaseUrl(config.baseUrl)) return null;
1835
- const bearer = await resolveCommandCodeQuotaBearer(config);
1836
- if (!bearer) return null;
1909
+ async function fetchCommandCodeUsageQuota(bearer: string): Promise<ProviderQuota | null | typeof TERMINAL_QUOTA_FAILURE> {
1837
1910
  const whoamiBody = await fetchCommandCodeJson(COMMAND_CODE_WHOAMI_URL, bearer);
1838
1911
  const whoami = asRecord(whoamiBody?.data) ?? whoamiBody;
1839
1912
  const org = asRecord(whoami?.org);
@@ -1857,7 +1930,7 @@ async function fetchCommandCodeQuota(provider: string, config: OcxProviderConfig
1857
1930
  const fiveHour = parseCommandCodeWindow(limits?.fiveHour);
1858
1931
  const weekly = parseCommandCodeWindow(limits?.weekly);
1859
1932
  const creditsUsd = await fetchCommandCodeSpend(bearer, credits, orgQuery);
1860
- return report(provider, "command-code:credits", {
1933
+ return {
1861
1934
  ...(fiveHour ? {
1862
1935
  fiveHourPercent: fiveHour.percent,
1863
1936
  ...(fiveHour.resetAt !== undefined ? { fiveHourResetAt: fiveHour.resetAt } : {}),
@@ -1868,18 +1941,21 @@ async function fetchCommandCodeQuota(provider: string, config: OcxProviderConfig
1868
1941
  } : {}),
1869
1942
  ...(creditsUsd ? { creditsUsd } : {}),
1870
1943
  updatedAt: Date.now(),
1871
- });
1944
+ };
1872
1945
  }
1873
1946
 
1874
- /** Cursor included usage via api2.cursor.sh (Bearer from OAuth) unofficial, may change. */
1875
- async function fetchCursorQuota(provider: string): Promise<ProviderQuotaReport | null> {
1876
- let accessToken: string;
1877
- try {
1878
- accessToken = await getValidAccessToken("cursor");
1879
- } catch {
1880
- return null;
1881
- }
1947
+ async function fetchCommandCodeQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
1948
+ // Never release credentials to a user-edited or lookalike provider host.
1949
+ if (!isCanonicalCommandCodeBaseUrl(config.baseUrl)) return null;
1950
+ const bearer = await resolveCommandCodeQuotaBearer(config);
1951
+ if (!bearer) return null;
1952
+ const result = await fetchCommandCodeUsageQuota(bearer);
1953
+ if (!result || result === TERMINAL_QUOTA_FAILURE) return result;
1954
+ return report(provider, "command-code:credits", result);
1955
+ }
1882
1956
 
1957
+ /** Cursor included usage via api2.cursor.sh (Bearer from OAuth) — unofficial, may change. */
1958
+ async function fetchCursorUsageQuota(accessToken: string): Promise<{ quota: ProviderQuota; source: string } | null> {
1883
1959
  const authHeaders = {
1884
1960
  Accept: "application/json",
1885
1961
  Authorization: `Bearer ${accessToken}`,
@@ -1939,15 +2015,17 @@ async function fetchCursorQuota(provider: string): Promise<ProviderQuotaReport |
1939
2015
  }
1940
2016
 
1941
2017
  if (totalPercent !== undefined || customWindows.length > 0) {
1942
- const built = report(provider, "cursor:period-usage", {
1943
- ...(totalPercent !== undefined ? {
1944
- monthlyPercent: totalPercent,
1945
- ...(resetAt !== undefined ? { monthlyResetAt: resetAt } : {}),
1946
- } : {}),
1947
- ...(customWindows.length > 0 ? { customWindows } : {}),
1948
- updatedAt: Date.now(),
1949
- });
1950
- if (built) return { ...built, reverseEngineered: true };
2018
+ return {
2019
+ source: "cursor:period-usage",
2020
+ quota: {
2021
+ ...(totalPercent !== undefined ? {
2022
+ monthlyPercent: totalPercent,
2023
+ ...(resetAt !== undefined ? { monthlyResetAt: resetAt } : {}),
2024
+ } : {}),
2025
+ ...(customWindows.length > 0 ? { customWindows } : {}),
2026
+ updatedAt: Date.now(),
2027
+ },
2028
+ };
1951
2029
  }
1952
2030
  }
1953
2031
  }
@@ -1973,12 +2051,14 @@ async function fetchCursorQuota(provider: string): Promise<ProviderQuotaReport |
1973
2051
  ? normalizePercent((used / limit) * 100)
1974
2052
  : undefined);
1975
2053
  if (percent !== undefined) {
1976
- const built = report(provider, "cursor:usage-summary", {
1977
- monthlyPercent: percent,
1978
- monthlyResetAt: normalizeResetAt(body?.billingCycleEnd),
1979
- updatedAt: Date.now(),
1980
- });
1981
- if (built) return { ...built, reverseEngineered: true };
2054
+ return {
2055
+ source: "cursor:usage-summary",
2056
+ quota: {
2057
+ monthlyPercent: percent,
2058
+ monthlyResetAt: normalizeResetAt(body?.billingCycleEnd),
2059
+ updatedAt: Date.now(),
2060
+ },
2061
+ };
1982
2062
  }
1983
2063
  }
1984
2064
  }
@@ -2027,11 +2107,26 @@ async function fetchCursorQuota(provider: string): Promise<ProviderQuotaReport |
2027
2107
  return Date.UTC(start.getUTCFullYear(), start.getUTCMonth() + 1, start.getUTCDate());
2028
2108
  })()
2029
2109
  : undefined;
2030
- const built = report(provider, "cursor:auth-usage", {
2031
- monthlyPercent: percent,
2032
- ...(monthlyResetAt !== undefined ? { monthlyResetAt } : {}),
2033
- updatedAt: Date.now(),
2034
- });
2110
+ return {
2111
+ source: "cursor:auth-usage",
2112
+ quota: {
2113
+ monthlyPercent: percent,
2114
+ ...(monthlyResetAt !== undefined ? { monthlyResetAt } : {}),
2115
+ updatedAt: Date.now(),
2116
+ },
2117
+ };
2118
+ }
2119
+
2120
+ async function fetchCursorQuota(provider: string): Promise<ProviderQuotaReport | null> {
2121
+ let accessToken: string;
2122
+ try {
2123
+ accessToken = await getValidAccessToken("cursor");
2124
+ } catch {
2125
+ return null;
2126
+ }
2127
+ const result = await fetchCursorUsageQuota(accessToken);
2128
+ if (!result) return null;
2129
+ const built = report(provider, result.source, result.quota);
2035
2130
  return built ? { ...built, reverseEngineered: true } : null;
2036
2131
  }
2037
2132
 
@@ -2219,6 +2314,19 @@ async function fetchAntigravityQuota(provider: string, config: OcxProviderConfig
2219
2314
  });
2220
2315
  }
2221
2316
 
2317
+ async function fetchAiStudioQuota(name: string, _provider: OcxProviderConfig): Promise<ProviderQuotaReport | null> {
2318
+ const now = Date.now();
2319
+ return {
2320
+ provider: name,
2321
+ label: "Google AI Studio (Web)",
2322
+ source: "Direct Session",
2323
+ updatedAt: now,
2324
+ quota: {
2325
+ updatedAt: now,
2326
+ },
2327
+ };
2328
+ }
2329
+
2222
2330
  async function maybeFetchProviderQuota(
2223
2331
  name: string,
2224
2332
  provider: OcxProviderConfig,
@@ -2294,6 +2402,9 @@ async function maybeFetchProviderQuota(
2294
2402
  if ((provider.authMode ?? "key") === "key" && name === "neuralwatt") {
2295
2403
  return fetchNeuralwattQuota(name, provider);
2296
2404
  }
2405
+ if (provider.googleMode === "ai-studio-web" || name === "google-aistudio") {
2406
+ return fetchAiStudioQuota(name, provider);
2407
+ }
2297
2408
  return null;
2298
2409
  } catch {
2299
2410
  return null;