@bitkyc08/opencodex 2.67.0 → 2.68.0-preview.20260927

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 (257) hide show
  1. package/bin/ocx.mjs +3 -0
  2. package/gui/dist/assets/App-B5-cAEd2.js +51 -0
  3. package/gui/dist/assets/App-DCBismRi.css +1 -0
  4. package/gui/dist/assets/Tray-DFnMiyD0.js +1 -0
  5. package/gui/dist/assets/{index-DtNmX7hW.css → index-BDUBS8PW.css} +1 -1
  6. package/gui/dist/assets/index-BcXblvem.js +86 -0
  7. package/gui/dist/assets/quota-summary-i84eOU3o.js +1 -0
  8. package/gui/dist/index.html +21 -2
  9. package/package.json +1 -1
  10. package/src/adapters/anthropic-image-guard.ts +13 -1
  11. package/src/adapters/anthropic-image-normalize.ts +3 -2
  12. package/src/adapters/anthropic-output-schema.ts +36 -0
  13. package/src/adapters/anthropic.ts +10 -2
  14. package/src/adapters/base.ts +7 -0
  15. package/src/adapters/codebuddy/live-models.ts +180 -0
  16. package/src/adapters/coding-agent/protocol.ts +91 -12
  17. package/src/adapters/coding-agent/turn.ts +32 -27
  18. package/src/adapters/devin/cloud-direct/index.ts +1 -0
  19. package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +18 -4
  20. package/src/adapters/devin.ts +9 -6
  21. package/src/adapters/google-http.ts +4 -4
  22. package/src/adapters/google.ts +93 -3
  23. package/src/adapters/kiro/adapter.ts +6 -2
  24. package/src/adapters/kiro/stream.ts +12 -2
  25. package/src/adapters/kiro/usage.ts +3 -1
  26. package/src/adapters/kiro-errors.ts +11 -1
  27. package/src/adapters/kiro-events.ts +27 -1
  28. package/src/adapters/kiro-refusal.ts +30 -0
  29. package/src/adapters/kiro-retry.ts +71 -31
  30. package/src/adapters/openai-chat/deepseek-artifact-schema.ts +45 -0
  31. package/src/adapters/openai-chat/messages.ts +31 -4
  32. package/src/adapters/openai-chat/serialized-tool-call-content.ts +52 -7
  33. package/src/adapters/openai-chat/tool-call-id-remint.ts +65 -0
  34. package/src/adapters/openai-chat/tool-schema.ts +6 -2
  35. package/src/adapters/openai-chat.ts +2 -2
  36. package/src/adapters/openai-responses/muse-tool-choice.ts +31 -0
  37. package/src/adapters/openai-responses/passthrough.ts +9 -3
  38. package/src/adapters/opencode-go-additional-tools.ts +19 -10
  39. package/src/adapters/physical-send.ts +10 -3
  40. package/src/adapters/registry.ts +2 -1
  41. package/src/adapters/responses-tool-schema.ts +31 -3
  42. package/src/adapters/run-turn-queue.ts +63 -23
  43. package/src/adapters/unique-tool-call-ids.ts +63 -0
  44. package/src/adapters/xai-web-search.ts +32 -2
  45. package/src/bridge/sse.ts +14 -2
  46. package/src/chat/inbound.ts +20 -6
  47. package/src/claude/agents-inject.ts +4 -0
  48. package/src/claude/desktop-3p.ts +53 -13
  49. package/src/claude/desktop-profile.ts +41 -4
  50. package/src/claude/inbound-model-options.ts +14 -2
  51. package/src/claude/inbound.ts +1 -1
  52. package/src/claude/intercept/connect-proxy.ts +17 -1
  53. package/src/claude/intercept/local-ca.ts +7 -1
  54. package/src/claude/message-threads.ts +28 -0
  55. package/src/cli/account-api.ts +13 -0
  56. package/src/cli/account-auth.ts +49 -2
  57. package/src/cli/account-extended.ts +4 -2
  58. package/src/cli/account.ts +4 -2
  59. package/src/cli/capabilities.ts +17 -0
  60. package/src/cli/claude.ts +3 -1
  61. package/src/cli/dispatch.ts +62 -5
  62. package/src/cli/index.ts +84 -79
  63. package/src/cli/minimax.ts +4 -2
  64. package/src/cli/opencode.ts +6 -1
  65. package/src/cli/restart-handoff.ts +110 -0
  66. package/src/cli/status.ts +51 -0
  67. package/src/client/connect.ts +34 -15
  68. package/src/client/link-ingress.ts +102 -0
  69. package/src/client/link-join.ts +37 -16
  70. package/src/client/link-relay.ts +228 -45
  71. package/src/client/link-state.ts +54 -0
  72. package/src/client/link-status.ts +39 -0
  73. package/src/client/link-teardown.ts +2 -2
  74. package/src/client/link-tunnel.ts +615 -86
  75. package/src/client/machine-api.ts +10 -4
  76. package/src/client/machine-listener.ts +57 -10
  77. package/src/client/runtime.ts +210 -35
  78. package/src/clients/config-export/constants.ts +1 -12
  79. package/src/clients/config-export/contracts.ts +2 -0
  80. package/src/clients/config-export/model-metadata.ts +9 -3
  81. package/src/clients/config-export/omp.ts +1 -1
  82. package/src/clients/config-export/zcode-store.ts +2 -2
  83. package/src/clients/config-export.ts +36 -3
  84. package/src/codex/app-server-processes.ts +32 -0
  85. package/src/codex/app-server-restart-service.ts +20 -3
  86. package/src/codex/catalog/access-programs.ts +50 -0
  87. package/src/codex/catalog/build-entries.ts +13 -0
  88. package/src/codex/catalog/derive-entry.ts +1 -0
  89. package/src/codex/catalog/model-hints.ts +11 -0
  90. package/src/codex/catalog/parsing.ts +6 -2
  91. package/src/codex/catalog/provider-models.ts +81 -0
  92. package/src/codex/catalog/retained-sync.ts +3 -0
  93. package/src/codex/codex-write-lock.ts +2 -2
  94. package/src/codex/convergence.ts +2 -0
  95. package/src/codex/desired-state.ts +14 -4
  96. package/src/codex/home.ts +21 -3
  97. package/src/codex/inject/restore.ts +17 -0
  98. package/src/codex/inject/routing-classify.ts +3 -2
  99. package/src/codex/inject-coordination.ts +4 -1
  100. package/src/codex/inject.ts +8 -1
  101. package/src/codex/journal.ts +6 -2
  102. package/src/codex/management-convergence.ts +9 -0
  103. package/src/codex/model-entitlements.ts +28 -5
  104. package/src/codex/routing/idle-window.ts +58 -0
  105. package/src/codex/routing-drift.ts +134 -0
  106. package/src/codex/routing-healer.ts +419 -0
  107. package/src/codex/routing.ts +10 -0
  108. package/src/codex/runtime.ts +188 -57
  109. package/src/codex/sibling-handoff.ts +71 -0
  110. package/src/codex/sibling-start.ts +139 -0
  111. package/src/codex/sync.ts +5 -3
  112. package/src/combos/failover.ts +32 -5
  113. package/src/combos/request.ts +11 -3
  114. package/src/combos/reset-window.ts +10 -5
  115. package/src/combos/resolve.ts +36 -6
  116. package/src/config/diagnostics.ts +2 -1
  117. package/src/config/load-degrade.ts +2 -0
  118. package/src/config/paths.ts +7 -1
  119. package/src/config/process-state.ts +10 -1
  120. package/src/config/schema/compaction-recovery.ts +15 -0
  121. package/src/config/schema/config-schema.ts +2 -0
  122. package/src/config/schema/leaf-validators.ts +9 -3
  123. package/src/generated/compatibility-version.json +360 -204
  124. package/src/images/loop.ts +47 -32
  125. package/src/integrations/catalog-refresh.ts +4 -0
  126. package/src/integrations/omp-yaml-source.ts +1 -0
  127. package/src/lib/claude-request-projection.ts +101 -0
  128. package/src/lib/local-account-switch-capability.ts +48 -0
  129. package/src/lib/local-upstream.ts +146 -0
  130. package/src/lib/package-tree-integrity.ts +25 -3
  131. package/src/lib/package-tree-retarget.ts +130 -0
  132. package/src/lib/process-control.ts +4 -1
  133. package/src/lib/system-restart-contract.ts +106 -0
  134. package/src/lib/upstream-retry.ts +57 -1
  135. package/src/link/ports.ts +9 -0
  136. package/src/link/ssh-argv.ts +16 -0
  137. package/src/link/ssh-runner.ts +70 -2
  138. package/src/link/tunnel-state.ts +58 -9
  139. package/src/oauth/account-quota-rank.ts +29 -18
  140. package/src/oauth/generic-account-failover.ts +179 -22
  141. package/src/oauth/index.ts +17 -4
  142. package/src/oauth/kiro-account-load.ts +107 -0
  143. package/src/oauth/kiro-device-login.ts +312 -0
  144. package/src/oauth/kiro-terminal-failover.ts +28 -0
  145. package/src/oauth/login-flow-state.ts +7 -1
  146. package/src/oauth/pool-settings-capability.ts +23 -14
  147. package/src/oauth/store.ts +56 -3
  148. package/src/oauth/types.ts +4 -0
  149. package/src/plugins/loader.ts +350 -0
  150. package/src/plugins/upstream-hooks.ts +119 -0
  151. package/src/protocols/encoders/adapter-events.ts +9 -2
  152. package/src/providers/account-quota-disk.ts +42 -4
  153. package/src/providers/codebuddy-models.ts +6 -2
  154. package/src/providers/command-code-efforts.ts +33 -10
  155. package/src/providers/kiro-account-state-disk.ts +44 -0
  156. package/src/providers/kiro-model-catalog.ts +162 -0
  157. package/src/providers/kiro-models.ts +5 -4
  158. package/src/providers/kiro-quota-metrics.ts +35 -0
  159. package/src/providers/kiro-usage.ts +118 -14
  160. package/src/providers/quota/account-cache.ts +43 -7
  161. package/src/providers/quota/antigravity.ts +8 -3
  162. package/src/providers/quota/kiro-account-probe.ts +12 -0
  163. package/src/providers/quota/vendor-probes-key.ts +31 -26
  164. package/src/providers/quota/vendor-probes-oauth.ts +16 -7
  165. package/src/providers/quota-types.ts +14 -0
  166. package/src/providers/quota.ts +21 -21
  167. package/src/providers/registry/entries-extended.ts +5 -3
  168. package/src/providers/request-pacing.ts +410 -12
  169. package/src/remote-control/workspace-codex-runtime.ts +4 -3
  170. package/src/responses/citation-markers.ts +132 -65
  171. package/src/responses/hosted-tool-policy.ts +14 -3
  172. package/src/responses/parser-content.ts +3 -2
  173. package/src/responses/parser.ts +4 -1
  174. package/src/responses/schema.ts +4 -0
  175. package/src/responses/visualization-directives.ts +182 -0
  176. package/src/server/chat-native-sse.ts +24 -12
  177. package/src/server/chat-native.ts +137 -68
  178. package/src/server/claude-messages.ts +147 -12
  179. package/src/server/index/link-listener.ts +7 -0
  180. package/src/server/index/optional-listeners.ts +4 -1
  181. package/src/server/index/package-tree-guard.ts +35 -16
  182. package/src/server/index/serve-options.ts +4 -2
  183. package/src/server/index/startup-warnings.ts +6 -0
  184. package/src/server/index.ts +7 -7
  185. package/src/server/inference/client-encoder-delivery.ts +3 -0
  186. package/src/server/inference/context.ts +30 -2
  187. package/src/server/local-account-switch-auth.ts +79 -0
  188. package/src/server/management/agent-settings-routes.ts +11 -3
  189. package/src/server/management/config-routes.ts +28 -11
  190. package/src/server/management/context.ts +2 -0
  191. package/src/server/management/link-routes.ts +123 -39
  192. package/src/server/management/logs-usage-routes.ts +9 -42
  193. package/src/server/management/model-rows.ts +1 -0
  194. package/src/server/management/oauth-account-routes.ts +67 -12
  195. package/src/server/management/route-registry.ts +5 -5
  196. package/src/server/management/sibling-guard.ts +60 -0
  197. package/src/server/management/storage-log-guard-routes.ts +72 -6
  198. package/src/server/management/system-restart.ts +92 -135
  199. package/src/server/management/system-routes.ts +1 -1
  200. package/src/server/management-api.ts +31 -3
  201. package/src/server/management-auth.ts +3 -0
  202. package/src/server/port-reclaim.ts +111 -1
  203. package/src/server/proxy-liveness.ts +16 -0
  204. package/src/server/relay.ts +16 -15
  205. package/src/server/request-log-filter.ts +28 -27
  206. package/src/server/request-log.ts +7 -3
  207. package/src/server/request-metrics.ts +28 -0
  208. package/src/server/responses/adapter-continuation.ts +151 -21
  209. package/src/server/responses/adapter-delivery.ts +21 -7
  210. package/src/server/responses/adapter-dispatch.ts +239 -51
  211. package/src/server/responses/codex-ws-pool.ts +5 -4
  212. package/src/server/responses/codex-ws-request.ts +4 -1
  213. package/src/server/responses/compact.ts +8 -4
  214. package/src/server/responses/compaction-recovery-policy.ts +116 -0
  215. package/src/server/responses/compaction-recovery.ts +330 -0
  216. package/src/server/responses/core-combo-failure.ts +1 -0
  217. package/src/server/responses/core-combo.ts +5 -1
  218. package/src/server/responses/core-lifetime.ts +22 -0
  219. package/src/server/responses/core-options.ts +16 -1
  220. package/src/server/responses/core.ts +14 -4
  221. package/src/server/responses/empty-completion-guard.ts +2 -0
  222. package/src/server/responses/fetch-helpers.ts +82 -31
  223. package/src/server/responses/passthrough-delivery.ts +3 -2
  224. package/src/server/responses/passthrough-dispatch.ts +12 -2
  225. package/src/server/responses/passthrough-execution.ts +2 -2
  226. package/src/server/responses/request-send-budget.ts +21 -5
  227. package/src/server/responses/request-transport.ts +116 -18
  228. package/src/server/responses/reset-replay.ts +32 -0
  229. package/src/server/responses/run-turn-execution.ts +136 -19
  230. package/src/server/responses/sidecar-execution.ts +41 -8
  231. package/src/server/responses/terminal-guard.ts +2 -0
  232. package/src/server/responses/ws-upstream.ts +44 -4
  233. package/src/server/restart-replacement.ts +382 -0
  234. package/src/server/startup-health-cache.ts +23 -4
  235. package/src/server/stop-teardown.ts +35 -1
  236. package/src/server/system-env.ts +3 -0
  237. package/src/service/windows-taskxml.ts +11 -7
  238. package/src/service/windows-wrapper-exit.ts +8 -0
  239. package/src/stall-timeout.ts +35 -10
  240. package/src/storage/policy-job.ts +4 -0
  241. package/src/storage/scanner.ts +177 -29
  242. package/src/storage/storage-mutation-coordinator.ts +17 -0
  243. package/src/tray/windows-tray.ps1 +50 -23
  244. package/src/types/config.ts +4 -0
  245. package/src/types/provider.ts +5 -1
  246. package/src/types/request.ts +13 -2
  247. package/src/update/index.ts +3 -1
  248. package/src/update/job.ts +6 -2
  249. package/src/update/mise-launcher-target.ts +100 -0
  250. package/src/update/worker-launch.ts +46 -0
  251. package/src/usage/log.ts +2 -0
  252. package/src/web-search/loop.ts +5 -2
  253. package/gui/dist/assets/App-BJsT8Icc.css +0 -1
  254. package/gui/dist/assets/App-D3pNGiN4.js +0 -51
  255. package/gui/dist/assets/Tray-yb05wCjb.js +0 -1
  256. package/gui/dist/assets/index-CouvvtMV.js +0 -86
  257. package/gui/dist/assets/tray-data-CYGdjCJ7.js +0 -1
@@ -3,8 +3,10 @@ import { getValidAccessTokenForAccount } from "../../oauth";
3
3
  import { getAccountCredential, getAccountSet } from "../../oauth/store";
4
4
  import type { GenerationContext } from "../../lib/state-store-sweeper";
5
5
  import { ACCOUNT_QUOTA_TTL_MS, toFiniteNumber } from "../quota-wire";
6
- import { clearKiroAccountUsageState, reconcileKiroAccountUsageState } from "../kiro-usage";
7
- import { cancelPendingAccountQuotaPersist, readPersistedAccountQuotas, schedulePersistAccountQuotas } from "../account-quota-disk";
6
+ import { clearKiroAccountUsageState, hydrateKiroUsageVerdict, kiroPersistableVerdicts, reconcileKiroAccountUsageState } from "../kiro-usage";
7
+ import { kiroEvidenceIdentity, sanitizeKiroQuota, type KiroPersistedQuota } from "../kiro-account-state-disk";
8
+ import { cancelPendingAccountQuotaPersist, readPersistedAccountQuotas, readPersistedKiroVerdicts, schedulePersistAccountQuotas } from "../account-quota-disk";
9
+ import type { ProviderAccount } from "../../oauth/types";
8
10
  import { replaceCachedProviderQuotas } from "../quota-routing-cache";
9
11
  import { getProviderRegistryEntry } from "../registry";
10
12
  import { getProviderQuotaReportCache, hasQuotaRows, routingEvidence, setProviderQuotaReportCache } from "./report-cache";
@@ -102,26 +104,51 @@ let diskHydrated = false;
102
104
  export function hydrateAccountQuotaCache(): void {
103
105
  if (diskHydrated) return;
104
106
  diskHydrated = true;
105
- for (const [key, quota] of readPersistedAccountQuotas()) {
107
+ const now = Date.now();
108
+ const liveKiro = new Map<string, ProviderAccount>(getAccountSet("kiro")?.accounts.map(account =>
109
+ [accountCacheKey("kiro", account.id), account]) ?? []);
110
+ for (const [key, quota] of readPersistedAccountQuotas(now)) {
111
+ if (key.startsWith("kiro\0")) {
112
+ const account = liveKiro.get(key);
113
+ if (!account || (quota as KiroPersistedQuota).identity !== kiroEvidenceIdentity(account)
114
+ || typeof quota.monthlyPercent !== "number" || !Number.isFinite(quota.monthlyPercent)
115
+ || quota.monthlyPercent < 0 || quota.monthlyPercent > 100
116
+ || now - quota.updatedAt >= ACCOUNT_QUOTA_TTL_MS
117
+ || (quota.monthlyResetAt !== undefined && (typeof quota.monthlyResetAt !== "number"
118
+ || !Number.isFinite(quota.monthlyResetAt) || quota.monthlyResetAt <= now
119
+ || !Number.isFinite(new Date(quota.monthlyResetAt).getTime())))) continue;
120
+ }
106
121
  // Disk stores observation time, not the Anthropic usage probe's clock.
107
122
  if (!accountQuotaCache.has(key)) {
108
123
  const anthropic = key.startsWith("anthropic\u0000");
109
124
  accountQuotaCache.set(key, {
110
125
  ts: anthropic ? 0 : quota.updatedAt,
111
- quota: anthropic ? normalizeAnthropicQuota(quota, Date.now()) : quota,
126
+ quota: anthropic ? normalizeAnthropicQuota(quota, now)
127
+ : key.startsWith("kiro\0") ? sanitizeKiroQuota(quota) : quota,
128
+ ...(key.startsWith("kiro\0") ? { identity: (quota as KiroPersistedQuota).identity } : {}),
112
129
  });
113
130
  }
114
131
  }
132
+ for (const [key, verdict] of readPersistedKiroVerdicts(now)) {
133
+ const account = liveKiro.get(key);
134
+ if (account && verdict.identity === kiroEvidenceIdentity(account)) hydrateKiroUsageVerdict(key, verdict, account);
135
+ }
115
136
  }
116
137
 
117
138
  export function persistAccountQuotaCache(): void {
118
139
  schedulePersistAccountQuotas(function* () {
119
140
  const now = Date.now();
141
+ const liveKiro = new Map<string, ProviderAccount>(getAccountSet("kiro")?.accounts.map(account =>
142
+ [accountCacheKey("kiro", account.id), account]) ?? []);
120
143
  for (const [key, entry] of accountQuotaCache) {
121
144
  const quota = key.startsWith("anthropic\u0000") ? normalizeAnthropicQuota(entry.quota, now) : entry.quota;
122
- if (quota) yield [key, quota] as [string, ProviderQuota];
145
+ if (quota && key.startsWith("kiro\0")) {
146
+ const account = liveKiro.get(key);
147
+ if (account && entry.identity === kiroEvidenceIdentity(account))
148
+ yield [key, { ...sanitizeKiroQuota(quota), identity: entry.identity } as KiroPersistedQuota] as [string, KiroPersistedQuota];
149
+ } else if (quota) yield [key, quota] as [string, ProviderQuota];
123
150
  }
124
- });
151
+ }, () => kiroPersistableVerdicts());
125
152
  }
126
153
  export const accountQuotaInflight = new Map<string, Promise<AccountQuotaCacheEntry>>();
127
154
  let lastReconciledGeneration = 0;
@@ -170,6 +197,10 @@ export function accountCacheKey(provider: string, accountId: string): string {
170
197
  */
171
198
  export function getCachedProviderAccountQuota(provider: string, accountId: string): ProviderQuota | null {
172
199
  const entry = accountQuotaCache.get(accountCacheKey(provider, accountId));
200
+ if (provider === "kiro") {
201
+ const account = getAccountSet("kiro")?.accounts.find(row => row.id === accountId);
202
+ if (!account || entry?.identity !== kiroEvidenceIdentity(account)) return null;
203
+ }
173
204
  if (entry?.isCurrent && !entry.isCurrent()) return null;
174
205
  return provider === "anthropic" ? normalizeAnthropicQuota(entry?.quota, Date.now()) : entry?.quota ?? null;
175
206
  }
@@ -185,7 +216,9 @@ export function setCachedProviderAccountQuotaForTests(
185
216
  accountQuotaCache.delete(key);
186
217
  return;
187
218
  }
188
- accountQuotaCache.set(key, { ts: Date.now(), quota });
219
+ const account = provider === "kiro" ? getAccountSet("kiro")?.accounts.find(row => row.id === accountId) : undefined;
220
+ accountQuotaCache.set(key, { ts: Date.now(), quota,
221
+ ...(account ? { identity: kiroEvidenceIdentity(account) } : {}) });
189
222
  }
190
223
 
191
224
  /** Unified headers report utilization fractions and epoch-second reset times. */
@@ -335,6 +368,7 @@ export function sweepExpiredProviderAccountQuotaRows(now = Date.now()): number {
335
368
 
336
369
  export function reconcileProviderAccountQuotaRows(context: GenerationContext): number {
337
370
  if (context.generation <= lastReconciledGeneration) return 0;
371
+ hydrateAccountQuotaCache();
338
372
  let removed = 0;
339
373
  for (const key of accountQuotaCache.keys()) {
340
374
  if (context.oauthAccountKeys.has(key)) continue;
@@ -354,6 +388,7 @@ export function reconcileProviderAccountQuotaRows(context: GenerationContext): n
354
388
  liveAccountQuotaKeys = new Set(context.oauthAccountKeys);
355
389
  liveProviderQuotaKeys = new Set(context.providerNames);
356
390
  lastReconciledGeneration = context.generation;
391
+ if (removed > 0) persistAccountQuotaCache();
357
392
  return removed;
358
393
  }
359
394
 
@@ -377,6 +412,7 @@ export function clearAccountQuotaCache(provider?: string): void {
377
412
  cancelPendingAccountQuotaPersist();
378
413
  return;
379
414
  }
415
+ hydrateAccountQuotaCache();
380
416
  const prefix = `${provider}\u0000`;
381
417
  for (const key of [...accountQuotaCache.keys()]) {
382
418
  if (key.startsWith(prefix)) accountQuotaCache.delete(key);
@@ -253,16 +253,21 @@ function antigravityUnavailableFailure(
253
253
  }
254
254
 
255
255
  export async function probeAntigravityUsageQuota(accessToken: string, projectId: string): Promise<AntigravityQuotaProbeResult> {
256
- const fetchQuota = (url: string) => providerOutboundPost("google-antigravity", { baseUrl: ANTIGRAVITY_ACCOUNT_QUOTA_BASE }, url, {
256
+ const fetchQuota = (url: string, userAgent = antigravityUserAgent()) => providerOutboundPost("google-antigravity", { baseUrl: ANTIGRAVITY_ACCOUNT_QUOTA_BASE }, url, {
257
257
  headers: {
258
258
  Accept: "application/json", "Content-Type": "application/json",
259
- "User-Agent": antigravityUserAgent(), Authorization: `Bearer ${accessToken}`,
259
+ "User-Agent": userAgent, Authorization: `Bearer ${accessToken}`,
260
260
  },
261
261
  body: JSON.stringify({ project: projectId }), signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
262
262
  }, antigravityOutboundDependencies);
263
263
  let summaryFailure: QuotaFailureCode | undefined;
264
264
  try {
265
- const response = await fetchQuota(ANTIGRAVITY_QUOTA_SUMMARY_URL);
265
+ let response = await fetchQuota(ANTIGRAVITY_QUOTA_SUMMARY_URL);
266
+ if (response.status === 403) {
267
+ // Some valid accounts reject the IDE fingerprint only for quota accounting.
268
+ try { await response.body?.cancel(); } catch { /* Best-effort release before retry. */ }
269
+ response = await fetchQuota(ANTIGRAVITY_QUOTA_SUMMARY_URL, "antigravity/1.0");
270
+ }
266
271
  if (await providerRedirectError(response, ANTIGRAVITY_QUOTA_SUMMARY_URL)) return unavailableAntigravityQuota("redirect_blocked");
267
272
  if (response.status === 401 || response.status === 403) return unavailableAntigravityQuota("access_denied");
268
273
  if (response.ok) {
@@ -0,0 +1,12 @@
1
+ import { getAccountSet } from "../../oauth/store";
2
+ import { kiroEvidenceIdentity } from "../kiro-account-state-disk";
3
+
4
+ /** Capture login identity before an await and recheck it before publishing evidence. */
5
+ export function kiroProbeIdentity(accountId: string): string | undefined {
6
+ const account = getAccountSet("kiro")?.accounts.find(row => row.id === accountId);
7
+ return account ? kiroEvidenceIdentity(account) : undefined;
8
+ }
9
+
10
+ export function kiroProbeCurrent(accountId: string, identity: string | undefined): boolean {
11
+ return identity !== undefined && kiroProbeIdentity(accountId) === identity;
12
+ }
@@ -40,7 +40,7 @@ const OLLAMA_CLOUD_BASE_URL = "https://ollama.com";
40
40
  const OLLAMA_CLOUD_USAGE_URL = `${OLLAMA_CLOUD_BASE_URL}/api/usage`;
41
41
  const ZAI_BASE_URL = "https://api.z.ai";
42
42
  const ZAI_CN_BASE_URL = "https://open.bigmodel.cn";
43
- const MINIMAX_REMAINS_URL = "https://www.minimax.io/v1/token_plan/remains";
43
+ const MINIMAX_REMAINS_PATH = "/v1/api/openplatform/coding_plan/remains";
44
44
  const MOONSHOT_BASE_URL = "https://api.moonshot.ai/v1";
45
45
  const VENICE_BASE_URL = "https://api.venice.ai/api/v1";
46
46
  const SYNTHETIC_BASE_URL = "https://api.synthetic.new/v2";
@@ -652,20 +652,17 @@ async function fetchZaiQuota(provider: string, config: OcxProviderConfig): Promi
652
652
  }
653
653
 
654
654
  /**
655
- * MiniMax Token Plan `GET /v1/token_plan/remains` — the subscription's
656
- * remaining quota as a countdown-time value (ms). The endpoint does not expose
657
- * the plan's total duration, so no percentage is fabricated from a presumed
658
- * window: the remaining time is reported as a duration-only window. When the
659
- * API supplies a total (`total_time` / `plan_duration_ms`), a consumed share
660
- * is derived from it. Region selects the host: `minimax` → www.minimax.io,
661
- * `minimax-cn` → api.minimaxi.com.
655
+ * MiniMax Coding Plan `GET /v1/api/openplatform/coding_plan/remains` reports
656
+ * remaining percentages per model/window. Only the `general` model is the
657
+ * Coding Plan quota; video remains are unrelated. Region selects the host.
662
658
  */
663
659
  async function fetchMinimaxQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
664
660
  if (!isCanonicalMinimaxBaseUrl(config.baseUrl)) return null;
665
661
  const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
666
662
  if (!apiKey) return null;
667
663
  const cnHost = normalizedBaseUrl(config.baseUrl)?.startsWith("https://api.minimaxi.com");
668
- const remainsUrl = cnHost ? "https://api.minimaxi.com/v1/token_plan/remains" : MINIMAX_REMAINS_URL;
664
+ const canonicalHost = cnHost ? "https://api.minimaxi.com" : "https://api.minimax.io";
665
+ const remainsUrl = `${canonicalHost}${MINIMAX_REMAINS_PATH}`;
669
666
  const response = await quotaFetch(provider, config, remainsUrl, {
670
667
  headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
671
668
  redirect: "error",
@@ -677,24 +674,32 @@ async function fetchMinimaxQuota(provider: string, config: OcxProviderConfig): P
677
674
  : null;
678
675
  }
679
676
  const body = asRecord(await readQuotaJson(response));
680
- if (!body || body.success === false) return null;
681
- const data = asRecord(body.data) ?? body;
682
- const remainsMs = toFiniteNumber(data.remains_time ?? data.remainsTime);
683
- if (remainsMs === undefined || remainsMs < 0) return null;
684
- const hours = Math.floor(remainsMs / 3_600_000);
685
- const label = `Token Plan remaining (${hours}h)`;
686
- // Only derive a consumed share when the API actually reports the plan total;
687
- // a presumed window (e.g. 30 days) would fabricate utilization. A valid
688
- // response that omits the total after a prior refresh had it is a DELIBERATE
689
- // contract change — the old row must be dropped (terminal), not preserved as
690
- // a transient last-good.
691
- const totalMs = toFiniteNumber(data.total_time ?? data.plan_duration_ms ?? data.total_duration_ms);
692
- if (totalMs === undefined || totalMs <= 0) return TERMINAL_QUOTA_FAILURE;
693
- const consumed = Math.max(0, totalMs - remainsMs);
694
- const percent = normalizePercent((consumed / totalMs) * 100);
695
- if (percent === undefined) return null;
677
+ if (!body || asRecord(body.base_resp)?.status_code !== 0) return null;
678
+ const rows = Array.isArray(body.model_remains) ? body.model_remains : [];
679
+ const general = rows.map(asRecord).find(row => row?.model_name === "general");
680
+ if (!general) return null;
681
+ const customWindows: NonNullable<ProviderQuota["customWindows"]> = [];
682
+ const fiveHourRemaining = toFiniteNumber(general.current_interval_remaining_percent);
683
+ if (fiveHourRemaining !== undefined) {
684
+ const percent = normalizePercent(100 - fiveHourRemaining);
685
+ if (percent !== undefined) {
686
+ const resetAt = normalizeResetAt(general.end_time);
687
+ customWindows.push({ label: "Coding Plan 5-hour", percent, ...(resetAt ? { resetAt } : {}) });
688
+ }
689
+ }
690
+ if (general.current_weekly_status === 1) {
691
+ const weeklyRemaining = toFiniteNumber(general.current_weekly_remaining_percent);
692
+ if (weeklyRemaining !== undefined) {
693
+ const percent = normalizePercent(100 - weeklyRemaining);
694
+ if (percent !== undefined) {
695
+ const resetAt = normalizeResetAt(general.weekly_end_time);
696
+ customWindows.push({ label: "Coding Plan weekly", percent, ...(resetAt ? { resetAt } : {}) });
697
+ }
698
+ }
699
+ }
700
+ if (customWindows.length === 0) return null;
696
701
  return report(provider, "minimax:token-plan-remains", {
697
- customWindows: [{ label, percent }],
702
+ customWindows,
698
703
  updatedAt: Date.now(),
699
704
  });
700
705
  }
@@ -2,6 +2,8 @@ import { effectiveCodexAuthAccountId, fetchMainAccountInfoSnapshot, listCodexAut
2
2
  import { MAIN_CODEX_ACCOUNT_ID } from "../../codex/main-account";
3
3
  import { getValidAccessToken } from "../../oauth";
4
4
  import { getAccountCredential, getAccountSet } from "../../oauth/store";
5
+ import { hydrateKiroAccountState, persistKiroAccountState } from "../kiro-account-state-disk";
6
+ import { kiroProbeCurrent, kiroProbeIdentity } from "./kiro-account-probe";
5
7
  import { fetchMuseKeyQuotaSnapshot } from "../muse-key-quota";
6
8
  import { CLAUDE_CLI_USER_AGENT } from "../claude-cli-identity";
7
9
  import { XAI_GROK_CLIENT_VERSION, XAI_GROK_COMPATIBILITY } from "../xai-transport";
@@ -227,7 +229,7 @@ function parseClaudeBucket(value: unknown): { percent?: number; resetAt?: number
227
229
 
228
230
  const TERMINAL_CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/gu;
229
231
 
230
- function parseClaudeLimit(value: unknown): { label: string; percent: number; resetAt?: number } | null {
232
+ function parseClaudeLimit(value: unknown): ProviderQuotaWindow | null {
231
233
  const rec = asRecord(value);
232
234
  if (!rec) return null;
233
235
  const percent = normalizePercent(rec.percent);
@@ -247,7 +249,10 @@ function parseClaudeLimit(value: unknown): { label: string; percent: number; res
247
249
  // control characters still leaves attacker-chosen residue on the quota line.
248
250
  if (label === null) return null;
249
251
  const resetAt = normalizeResetAt(rec.resets_at);
250
- return { label, percent, ...(resetAt !== undefined ? { resetAt } : {}) };
252
+ // Model scope is proven structurally here, not guessed from text: the caller admits only
253
+ // `kind: "weekly_scoped"`, and a limit without a recognized `scope.model.display_name` has
254
+ // already returned null above. Routing keys on `scope`, never on `label`.
255
+ return { label, scope: "model", percent, ...(resetAt !== undefined ? { resetAt } : {}) };
251
256
  }
252
257
 
253
258
  /** Claude's OAuth usage endpoint, probed with ONE account's own bearer token. */
@@ -292,9 +297,9 @@ export async function fetchAnthropicUsageQuota(accessToken: string): Promise<Pro
292
297
  const opus = parseClaudeBucket(body.seven_day_opus);
293
298
  const sonnet = parseClaudeBucket(body.seven_day_sonnet);
294
299
  const customWindows: ProviderQuotaWindow[] = [];
295
- if (fable?.percent !== undefined) customWindows.push({ label: "Fable", percent: fable.percent, ...(fable.resetAt !== undefined ? { resetAt: fable.resetAt } : {}) });
296
- if (opus?.percent !== undefined) customWindows.push({ label: "Opus", percent: opus.percent, ...(opus.resetAt !== undefined ? { resetAt: opus.resetAt } : {}) });
297
- if (sonnet?.percent !== undefined) customWindows.push({ label: "Sonnet", percent: sonnet.percent, ...(sonnet.resetAt !== undefined ? { resetAt: sonnet.resetAt } : {}) });
300
+ if (fable?.percent !== undefined) customWindows.push({ label: "Fable", scope: "model", percent: fable.percent, ...(fable.resetAt !== undefined ? { resetAt: fable.resetAt } : {}) });
301
+ if (opus?.percent !== undefined) customWindows.push({ label: "Opus", scope: "model", percent: opus.percent, ...(opus.resetAt !== undefined ? { resetAt: opus.resetAt } : {}) });
302
+ if (sonnet?.percent !== undefined) customWindows.push({ label: "Sonnet", scope: "model", percent: sonnet.percent, ...(sonnet.resetAt !== undefined ? { resetAt: sonnet.resetAt } : {}) });
298
303
  const knownLabels = new Set(customWindows.map(window => window.label.toLowerCase()));
299
304
  const limits = Array.isArray(body.limits) ? body.limits : [];
300
305
  for (const rawLimit of limits) {
@@ -360,8 +365,10 @@ export async function fetchAnthropicQuota(provider: string): Promise<ProviderQuo
360
365
  * concurrent account switch cannot file this answer under the wrong account.
361
366
  */
362
367
  export async function fetchKiroQuota(provider: string): Promise<ProviderQuotaReport | null> {
368
+ hydrateKiroAccountState();
363
369
  const probedAccountId = getAccountSet("kiro")?.activeAccountId;
364
370
  if (!probedAccountId) return null;
371
+ const identity = kiroProbeIdentity(probedAccountId);
365
372
  const probedAccountKey = accountCacheKey("kiro", probedAccountId);
366
373
  const writerGeneration = captureConfigGeneration();
367
374
  let snapshot: KiroUsageSnapshot | null;
@@ -371,9 +378,11 @@ export async function fetchKiroQuota(provider: string): Promise<ProviderQuotaRep
371
378
  return null;
372
379
  }
373
380
  if (!snapshot) return null;
381
+ if (!kiroProbeCurrent(probedAccountId, identity)) return null;
374
382
  if (mayCommitAccountQuotaKey(probedAccountKey, writerGeneration)) {
375
- accountQuotaCache.set(probedAccountKey, { ts: Date.now(), quota: snapshot.quota });
376
- commitKiroAccountUsageState(probedAccountKey, snapshot);
383
+ accountQuotaCache.set(probedAccountKey, { ts: Date.now(), quota: snapshot.quota, identity });
384
+ commitKiroAccountUsageState(probedAccountKey, snapshot, identity);
385
+ persistKiroAccountState();
377
386
  }
378
387
  return report(provider, "kiro:usage-limits", snapshot.quota);
379
388
  }
@@ -19,6 +19,17 @@ export interface ProviderQuotaWindow {
19
19
  label: string;
20
20
  percent: number;
21
21
  resetAt?: number;
22
+ /**
23
+ * Set only when the PRODUCER proved this window covers one model family, structurally rather
24
+ * than by reading its label: an Anthropic `seven_day_<family>` body key, or a limit with
25
+ * `kind: "weekly_scoped"` and a recognized `scope.model.display_name`.
26
+ *
27
+ * Absent means provider-wide, which gates every model. That is the fail-closed direction and
28
+ * the behaviour every other producer keeps. Routing must key on THIS, never on the label text:
29
+ * `quota/antigravity.ts` passes an upstream `group.displayName` straight through, so a
30
+ * provider-wide group named "Opus" there would otherwise be mistaken for a per-model window.
31
+ */
32
+ scope?: "model";
22
33
  }
23
34
 
24
35
  export interface ProviderQuotaCreditsUsd {
@@ -37,6 +48,9 @@ export interface ProviderQuota {
37
48
  weeklyResetAt?: number;
38
49
  monthlyPercent?: number;
39
50
  monthlyResetAt?: number;
51
+ /** Observed Kiro plan credits, independent of token and currency estimates. */
52
+ kiroCreditsUsed?: number;
53
+ kiroCreditsLimit?: number;
40
54
  customWindows?: ProviderQuotaWindow[];
41
55
  creditsUsd?: ProviderQuotaCreditsUsd;
42
56
  updatedAt: number;
@@ -67,6 +67,8 @@ import {
67
67
  } from "./quota/vendor-probes-oauth";
68
68
  import { fetchCommandCodeQuota, fetchKimiQuota, keyQuotaReaderForProvider } from "./quota/vendor-probes-key";
69
69
  import { antigravityQuotaDiagnosticIdentity, fetchAntigravityQuota, probeAntigravityUsageQuota } from "./quota/antigravity";
70
+ import { persistKiroAccountState } from "./kiro-account-state-disk";
71
+ import { kiroProbeCurrent, kiroProbeIdentity } from "./quota/kiro-account-probe";
70
72
 
71
73
  export type { ProviderQuota, ProviderQuotaCreditsUsd, ProviderQuotaWindow } from "./quota-types";
72
74
  export { QUOTA_RESPONSE_MAX_BYTES } from "./quota-wire";
@@ -413,8 +415,6 @@ async function fetchExplicitCurrentQuota(provider: string, config: OcxProviderCo
413
415
  if (read.result && typeof read.result !== "symbol") accountReportCurrent.set(read.result, isCurrent);
414
416
  return read.result;
415
417
  }
416
-
417
-
418
418
  async function fetchAccountQuota(
419
419
  provider: string,
420
420
  accountId: string,
@@ -423,16 +423,22 @@ async function fetchAccountQuota(
423
423
  ): Promise<AccountQuotaCacheEntry> {
424
424
  if (!supportsPerAccountQuota(provider)) return { ts: Date.now(), quota: null, unavailable: true };
425
425
  if (explicitAccountReader(provider)) return fetchExplicitAccountQuota(provider, accountId, forceRefresh, providerConfig);
426
- if (provider === "anthropic") hydrateAccountQuotaCache();
426
+ if (provider === "anthropic" || provider === "kiro") hydrateAccountQuotaCache();
427
427
  const key = accountCacheKey(provider, accountId);
428
428
  const writerGeneration = captureConfigGeneration();
429
- const cached = accountQuotaCache.get(key);
429
+ const kiroIdentity = provider === "kiro" ? kiroProbeIdentity(accountId) : undefined;
430
+ const cachedCandidate = accountQuotaCache.get(key);
431
+ const cached = provider !== "kiro" || cachedCandidate?.identity === kiroIdentity ? cachedCandidate : undefined;
430
432
  if (!forceRefresh && cached && Date.now() - cached.ts < ACCOUNT_QUOTA_TTL_MS) {
431
433
  if (provider === "google-antigravity" && cached.quotaFailure && cached.quotaFailureIsCurrent?.() !== true) return { ...cached, quotaFailure: undefined };
432
434
  return provider === "anthropic" ? { ...cached, quota: normalizeAnthropicQuota(cached.quota, Date.now()) } : cached;
433
435
  }
434
436
  const joinable = accountQuotaInflight.get(key);
435
- if (joinable) return joinable;
437
+ if (joinable) {
438
+ const joined = await joinable;
439
+ return provider !== "kiro" || joined.identity === kiroIdentity
440
+ ? joined : fetchAccountQuota(provider, accountId, true, providerConfig);
441
+ }
436
442
 
437
443
  const epoch = explicitAccountEpoch;
438
444
  const probe = (async (): Promise<AccountQuotaCacheEntry> => {
@@ -448,12 +454,6 @@ async function fetchAccountQuota(
448
454
  let quota: ProviderQuota | null;
449
455
  let kiroSnapshot: KiroUsageSnapshot | null = null;
450
456
  if (provider === "kiro") {
451
- // Kiro resolves the bearer and its routing metadata from ONE account-scoped
452
- // snapshot. It deliberately does not use getTokenForAccountQuotaProbe: that
453
- // helper refuses to refresh a background `local-cli` slot because Anthropic's
454
- // lock can adopt a mismatched Claude CLI identity, but Kiro marks every
455
- // CLI-imported credential `local-cli`, so the same rule would blank the quota of
456
- // every inactive pool account the moment its token expired.
457
457
  kiroSnapshot = await fetchKiroUsageSnapshot(await kiroUsageContextForAccount(accountId));
458
458
  quota = kiroSnapshot?.quota ?? null;
459
459
  } else {
@@ -475,31 +475,29 @@ async function fetchAccountQuota(
475
475
  }
476
476
  }
477
477
  if (!quota) {
478
- // Preserve last-good bars and mark unavailable; advance TTL so failures
479
- // negative-cache instead of re-probing on every GUI poll.
478
+ // Preserve last-good bars while negative-caching the failed probe.
480
479
  const entry: AccountQuotaCacheEntry = {
481
480
  ts: Date.now(),
482
- // Settle once for all joiners against observations committed during the probe.
483
481
  quota: provider === "anthropic"
484
482
  ? normalizeAnthropicQuota(accountQuotaCache.get(key)?.quota, Date.now()) : cached?.quota ?? null,
485
483
  unavailable: true,
484
+ ...(provider === "kiro" ? { identity: kiroIdentity } : {}),
486
485
  ...diagnosticFields(),
487
486
  };
488
- if (mayCommitAccountQuotaKey(key, writerGeneration)) {
487
+ if (mayCommitAccountQuotaKey(key, writerGeneration) && (provider !== "kiro" || kiroProbeCurrent(accountId, kiroIdentity))) {
489
488
  accountQuotaCache.set(key, entry);
490
- if (provider === "kiro") commitKiroAccountUsageState(key, null);
489
+ if (provider === "kiro") persistKiroAccountState();
491
490
  sweepExpiredOnWrite(entry.ts);
492
491
  }
493
492
  return entry;
494
493
  }
495
494
  const entry: AccountQuotaCacheEntry = {
496
495
  ts: Date.now(), quota: provider === "anthropic" ? normalizeAnthropicQuota(quota, Date.now()) : quota,
496
+ ...(provider === "kiro" ? { identity: kiroIdentity } : {}),
497
497
  };
498
- if (mayCommitAccountQuotaKey(key, writerGeneration)) {
498
+ if (mayCommitAccountQuotaKey(key, writerGeneration) && (provider !== "kiro" || kiroProbeCurrent(accountId, kiroIdentity))) {
499
499
  accountQuotaCache.set(key, entry);
500
- // Exhaustion state rides the SAME commit guard as the quota row: a probe from a
501
- // superseded config generation must not publish either half.
502
- if (provider === "kiro") commitKiroAccountUsageState(key, kiroSnapshot);
500
+ if (provider === "kiro") { commitKiroAccountUsageState(key, kiroSnapshot, kiroIdentity); persistKiroAccountState(); }
503
501
  sweepExpiredOnWrite(entry.ts);
504
502
  }
505
503
  return entry;
@@ -510,10 +508,12 @@ async function fetchAccountQuota(
510
508
  quota: provider === "anthropic"
511
509
  ? normalizeAnthropicQuota(accountQuotaCache.get(key)?.quota, Date.now()) : cached?.quota ?? null,
512
510
  unavailable: true,
511
+ ...(provider === "kiro" ? { identity: kiroIdentity } : {}),
513
512
  ...diagnosticFields(),
514
513
  };
515
- if (mayCommitAccountQuotaKey(key, writerGeneration)) {
514
+ if (mayCommitAccountQuotaKey(key, writerGeneration) && (provider !== "kiro" || kiroProbeCurrent(accountId, kiroIdentity))) {
516
515
  accountQuotaCache.set(key, entry);
516
+ if (provider === "kiro") persistKiroAccountState();
517
517
  sweepExpiredOnWrite(entry.ts);
518
518
  }
519
519
  return entry;
@@ -1402,7 +1402,7 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
1402
1402
  dashboardUrl: "https://www.codebuddy.ai/profile/keys",
1403
1403
  defaultModel: "default-model",
1404
1404
  models: CODEBUDDY_GLOBAL_MODELS,
1405
- liveModels: false,
1405
+ liveModels: true,
1406
1406
  modelContextWindows: CODEBUDDY_GLOBAL_MODEL_CONTEXT_WINDOWS,
1407
1407
  modelMaxOutputTokens: CODEBUDDY_GLOBAL_MODEL_MAX_OUTPUT_TOKENS,
1408
1408
  defaultMaxOutputTokens: 32_000,
@@ -1415,7 +1415,9 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
1415
1415
  // Official CodeBuddy Code CLI provider, CHINA / `internal` environment. Identical adapter and
1416
1416
  // binary as `codebuddy`; the region is fixed by the profile's CODEBUDDY_INTERNET_ENVIRONMENT
1417
1417
  // and this canonical baseUrl. CN key: https://copilot.tencent.com/profile/keys. The CN model
1418
- // roster differs from Global (see codebuddy-models.ts) and is seeded separately (§八).
1418
+ // roster differs from Global and is discovered live from the key-authenticated product
1419
+ // configuration roster; the seeded list in codebuddy-models.ts is only the degraded
1420
+ // fallback (§八).
1419
1421
  id: "codebuddy-cn",
1420
1422
  label: "CodeBuddy (CN)",
1421
1423
  adapter: "codebuddy",
@@ -1426,7 +1428,7 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
1426
1428
  dashboardUrl: "https://copilot.tencent.com/profile/keys",
1427
1429
  defaultModel: "default",
1428
1430
  models: CODEBUDDY_CN_MODELS,
1429
- liveModels: false,
1431
+ liveModels: true,
1430
1432
  modelContextWindows: CODEBUDDY_CN_MODEL_CONTEXT_WINDOWS,
1431
1433
  modelMaxOutputTokens: CODEBUDDY_CN_MODEL_MAX_OUTPUT_TOKENS,
1432
1434
  defaultMaxOutputTokens: 32_000,