@bitkyc08/opencodex 2.80.0 → 2.82.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 (376) hide show
  1. package/README.md +3 -3
  2. package/bin/ocx.mjs +166 -73
  3. package/gui/dist/assets/App-Ds_qL7o8.js +54 -0
  4. package/gui/dist/assets/Tray-BqHF5Trd.js +1 -0
  5. package/gui/dist/assets/{index-Brk6F3fn.css → index-BAbcj51J.css} +1 -1
  6. package/gui/dist/assets/index-D3FBCNiP.js +94 -0
  7. package/gui/dist/assets/quota-summary-DTBC8n7-.js +1 -0
  8. package/gui/dist/index.html +2 -2
  9. package/gui/dist/provider-icons/claude-green.svg +1 -0
  10. package/package.json +3 -2
  11. package/src/adapters/anthropic-model-contract.ts +8 -2
  12. package/src/adapters/anthropic.ts +8 -3
  13. package/src/adapters/codebuddy/mcp-server.ts +2 -25
  14. package/src/adapters/codebuddy/tool-bridge.ts +1 -7
  15. package/src/adapters/coding-agent/mcp-server.ts +33 -0
  16. package/src/adapters/coding-agent/protocol.ts +122 -17
  17. package/src/adapters/coding-agent/tool-catalog.ts +75 -0
  18. package/src/adapters/coding-agent/turn.ts +101 -44
  19. package/src/adapters/cursor/catalog.ts +8 -0
  20. package/src/adapters/cursor/effort-map.ts +2 -0
  21. package/src/adapters/devin/live-models.ts +2 -0
  22. package/src/adapters/kiro-retry.ts +9 -4
  23. package/src/adapters/openai-chat/messages.ts +7 -6
  24. package/src/adapters/openai-responses/compaction-search-history.ts +109 -0
  25. package/src/adapters/openai-responses/passthrough.ts +9 -4
  26. package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
  27. package/src/adapters/physical-send.ts +8 -5
  28. package/src/adapters/qoder/adapter.ts +41 -6
  29. package/src/adapters/qoder/scaffold-guard.ts +2 -3
  30. package/src/adapters/zed.ts +16 -1
  31. package/src/claude/agents-inject.ts +14 -3
  32. package/src/claude/context-windows.ts +19 -17
  33. package/src/claude/desktop-3p.ts +88 -12
  34. package/src/claude/desktop-profile.ts +2 -1
  35. package/src/claude/gateway-cache.ts +18 -1
  36. package/src/claude/inbound-cache-stabilize.ts +4 -9
  37. package/src/claude/inbound.ts +78 -3
  38. package/src/claude/intercept/cli-catalog.ts +3 -0
  39. package/src/claude/intercept/model-bindings.ts +1 -0
  40. package/src/claude/intercept/picker-ca-persistence.ts +7 -6
  41. package/src/claude/intercept/picker-listener.ts +71 -4
  42. package/src/claude/intercept/picker-models.ts +14 -6
  43. package/src/claude/intercept/settings.ts +3 -0
  44. package/src/claude/long-context.ts +66 -0
  45. package/src/claude/model-info.ts +19 -16
  46. package/src/claude/outbound.ts +94 -9
  47. package/src/claude/subagent-model.ts +40 -11
  48. package/src/cli/account-anthropic-threshold.ts +14 -5
  49. package/src/cli/account-api.ts +3 -1
  50. package/src/cli/account-extended.ts +13 -9
  51. package/src/cli/account-policy-dto.ts +1 -1
  52. package/src/cli/account-policy.ts +13 -4
  53. package/src/cli/account.ts +3 -3
  54. package/src/cli/agent.ts +9 -5
  55. package/src/cli/aside-profile-recovery.ts +33 -0
  56. package/src/cli/capabilities-accounts.ts +2 -2
  57. package/src/cli/capabilities-agents-routing.ts +24 -0
  58. package/src/cli/capabilities-base.ts +5 -5
  59. package/src/cli/capabilities-integrations.ts +18 -2
  60. package/src/cli/cli-path-diagnostics.ts +132 -0
  61. package/src/cli/codex-shim-autorestore.ts +3 -0
  62. package/src/cli/desktop-runtime-guidance.ts +65 -0
  63. package/src/cli/dispatch.ts +15 -1
  64. package/src/cli/doctor.ts +16 -3
  65. package/src/cli/help.ts +4 -1
  66. package/src/cli/index.ts +25 -14
  67. package/src/cli/integration-aside-sync.ts +2 -1
  68. package/src/cli/integration-plan-dto.ts +1 -0
  69. package/src/cli/integration-preview.ts +8 -1
  70. package/src/cli/integrations.ts +48 -6
  71. package/src/cli/message-args.ts +34 -0
  72. package/src/cli/message-command.ts +43 -0
  73. package/src/cli/message-runtime.ts +6 -0
  74. package/src/cli/observe.ts +3 -1
  75. package/src/cli/provider.ts +29 -6
  76. package/src/cli/registry.ts +24 -2
  77. package/src/cli/resolve.ts +25 -2
  78. package/src/cli/root.ts +4 -2
  79. package/src/cli/status.ts +64 -12
  80. package/src/cli/update-restart-child.ts +4 -2
  81. package/src/cli/update-restart-home.ts +28 -10
  82. package/src/cli/update-restart-service-record.ts +116 -0
  83. package/src/cli/update-restart-supervision.ts +96 -0
  84. package/src/cli/update-restart.ts +59 -13
  85. package/src/cli/v2.ts +27 -20
  86. package/src/cli/version-skew-notice.ts +77 -0
  87. package/src/clients/config-export/commandcode.ts +131 -0
  88. package/src/clients/config-export/contracts.ts +12 -0
  89. package/src/clients/config-export.ts +64 -1
  90. package/src/clients/lazycodex.ts +1 -1
  91. package/src/clients/omo-role-models.ts +32 -7
  92. package/src/codex/auth-api/account-list.ts +11 -3
  93. package/src/codex/auth-api/account-selection.ts +107 -0
  94. package/src/codex/auth-api/main-account-probe.ts +25 -8
  95. package/src/codex/auth-api/pool-mode-gate.ts +33 -9
  96. package/src/codex/auth-api/pool-quota-probe.ts +15 -1
  97. package/src/codex/auth-api/routes.ts +11 -49
  98. package/src/codex/auth-context.ts +20 -1
  99. package/src/codex/autostart-health.ts +12 -3
  100. package/src/codex/catalog/auto-review.ts +13 -6
  101. package/src/codex/catalog/build-entries.ts +33 -7
  102. package/src/codex/catalog/bundled.ts +155 -26
  103. package/src/codex/catalog/combo-member.ts +3 -2
  104. package/src/codex/catalog/control-plane.ts +45 -0
  105. package/src/codex/catalog/effort.ts +37 -6
  106. package/src/codex/catalog/gather-capture.ts +10 -3
  107. package/src/codex/catalog/model-hints.ts +15 -8
  108. package/src/codex/catalog/model-visibility.ts +3 -1
  109. package/src/codex/catalog/operator-tiers.ts +38 -0
  110. package/src/codex/catalog/parsing.ts +2 -1
  111. package/src/codex/catalog/provider-models.ts +58 -44
  112. package/src/codex/catalog/retained-sync.ts +7 -5
  113. package/src/codex/catalog/routed-gather.ts +15 -1
  114. package/src/codex/catalog-auto-refresh-sources.ts +3 -4
  115. package/src/codex/catalog-auto-refresh.ts +19 -0
  116. package/src/codex/catalog.ts +1 -1
  117. package/src/codex/codex-home-owner.ts +3 -0
  118. package/src/codex/config-write-lock.ts +333 -0
  119. package/src/codex/control-plane-models.ts +6 -0
  120. package/src/codex/convergence.ts +2 -1
  121. package/src/codex/features.ts +257 -200
  122. package/src/codex/inject/config-write-section.ts +34 -0
  123. package/src/codex/inject/multi-agent-v2.ts +14 -6
  124. package/src/codex/inject/remove.ts +36 -11
  125. package/src/codex/inject/restore.ts +85 -20
  126. package/src/codex/inject-coordination.ts +15 -6
  127. package/src/codex/inject.ts +62 -20
  128. package/src/codex/journal.ts +62 -17
  129. package/src/codex/main-account-cache.ts +43 -0
  130. package/src/codex/paths.ts +2 -0
  131. package/src/codex/prepared-home.ts +18 -0
  132. package/src/codex/prompt-journal.ts +25 -12
  133. package/src/codex/prompt-layers.ts +49 -26
  134. package/src/codex/prompt-lock-claim.ts +134 -0
  135. package/src/codex/prompt-lock-evidence.ts +26 -0
  136. package/src/codex/prompt-lock-io.ts +16 -0
  137. package/src/codex/prompt-lock-owner.ts +130 -0
  138. package/src/codex/prompt-lock.ts +84 -53
  139. package/src/codex/routing/failure-window.ts +146 -0
  140. package/src/codex/routing/selection.ts +4 -1
  141. package/src/codex/routing.ts +13 -17
  142. package/src/codex/runtime.ts +106 -18
  143. package/src/codex/shim.ts +45 -19
  144. package/src/combos/jev-decision-contract.ts +25 -2
  145. package/src/combos/jev-service-exchange.ts +281 -0
  146. package/src/combos/jev.ts +34 -219
  147. package/src/combos/types.ts +1 -1
  148. package/src/config/atomic-write.ts +1 -1
  149. package/src/config/codex-ws-reuse-setting.ts +10 -0
  150. package/src/config/diagnostics.ts +27 -16
  151. package/src/config/live-reconcile.ts +17 -0
  152. package/src/config/load-degrade.ts +42 -0
  153. package/src/config/rebase-provenance.ts +11 -0
  154. package/src/config/schema/anthropic-account-pool.ts +143 -0
  155. package/src/config/schema/config-schema.ts +11 -1
  156. package/src/config/schema/leaf-validators.ts +4 -0
  157. package/src/config.ts +2 -0
  158. package/src/generated/compatibility-version.json +566 -298
  159. package/src/generated/model-metadata.ts +7 -6
  160. package/src/grok/inject.ts +50 -4
  161. package/src/grok/sync.ts +2 -1
  162. package/src/images/loop.ts +30 -11
  163. package/src/integrations/aside-profiles.ts +10 -3
  164. package/src/integrations/catalog-refresh.ts +19 -3
  165. package/src/integrations/mutation-plan.ts +7 -0
  166. package/src/integrations/registry.ts +8 -0
  167. package/src/integrations/state.ts +9 -2
  168. package/src/integrations/writer.ts +17 -5
  169. package/src/lib/bun-path-runtime.d.mts +32 -0
  170. package/src/lib/bun-path-runtime.mjs +95 -0
  171. package/src/lib/bun-runtime-preflight.ts +140 -0
  172. package/src/lib/desktop-cli-handoff.d.mts +20 -0
  173. package/src/lib/desktop-cli-handoff.mjs +92 -0
  174. package/src/lib/desktop-cli-record.d.mts +29 -0
  175. package/src/lib/desktop-cli-record.mjs +102 -0
  176. package/src/lib/lab-live-route-production.ts +9 -1
  177. package/src/lib/request-execution-budget.ts +244 -58
  178. package/src/lib/spend-pool-alias-validation.ts +79 -0
  179. package/src/lib/spend-pool-continuity.ts +34 -0
  180. package/src/lib/spend-reservation-ledger.ts +412 -50
  181. package/src/lib/state-store-registrations.ts +20 -3
  182. package/src/lib/synced-state-location.ts +46 -6
  183. package/src/lib/test-home-guard.ts +87 -1
  184. package/src/lib/translator-budget.ts +17 -4
  185. package/src/lib/upstream-retry.ts +17 -5
  186. package/src/lib/windows-atomic-replace.ts +38 -16
  187. package/src/lib/windows-elevation.ts +6 -0
  188. package/src/lib/windows-secret-acl.ts +92 -25
  189. package/src/lib/winsw.ts +2 -3
  190. package/src/lib/workflow-budget.ts +53 -11
  191. package/src/messaging/budget.ts +45 -0
  192. package/src/messaging/discovery.ts +57 -0
  193. package/src/messaging/envelope.ts +49 -0
  194. package/src/messaging/input.ts +37 -0
  195. package/src/messaging/rpc.ts +196 -0
  196. package/src/messaging/send.ts +58 -0
  197. package/src/messaging/socket.ts +72 -0
  198. package/src/messaging/types.ts +29 -0
  199. package/src/oauth/anthropic-account-refusal.ts +90 -18
  200. package/src/oauth/anthropic-account-threshold.ts +8 -2
  201. package/src/oauth/anthropic-continuity.ts +15 -1
  202. package/src/oauth/anthropic-model-quota.ts +120 -48
  203. package/src/oauth/anthropic-model-routes.ts +11 -3
  204. package/src/oauth/anthropic-oauth-definitions.ts +98 -0
  205. package/src/oauth/anthropic-pool-config.ts +30 -0
  206. package/src/oauth/anthropic-rate-limit-policy.ts +64 -9
  207. package/src/oauth/anthropic-routing.ts +1202 -866
  208. package/src/oauth/anthropic-send-ownership.ts +41 -0
  209. package/src/oauth/anthropic.ts +4 -1
  210. package/src/oauth/chatgpt.ts +113 -41
  211. package/src/oauth/generic-account-failover.ts +3 -1
  212. package/src/oauth/health.ts +8 -6
  213. package/src/oauth/index.ts +96 -175
  214. package/src/oauth/kiro.ts +22 -9
  215. package/src/oauth/login-cli.ts +2 -0
  216. package/src/oauth/model-discovery-auth.ts +43 -0
  217. package/src/oauth/pool-kernel.ts +11 -5
  218. package/src/oauth/pool-settings-capability.ts +5 -3
  219. package/src/oauth/store-anthropic-instance.ts +87 -0
  220. package/src/oauth/store.ts +68 -10
  221. package/src/oauth/token-guardian.ts +2 -0
  222. package/src/oauth/types.ts +2 -0
  223. package/src/protocols/plan-snapshot.ts +5 -0
  224. package/src/protocols/settings.ts +20 -5
  225. package/src/providers/anthropic-instance-id.ts +47 -0
  226. package/src/providers/anthropic-instance.ts +48 -0
  227. package/src/providers/anthropic-reset-grant-ledger.ts +11 -1
  228. package/src/providers/azure-model-metadata.ts +115 -0
  229. package/src/providers/derive.ts +23 -0
  230. package/src/providers/kiro-models.ts +3 -0
  231. package/src/providers/label.ts +1 -1
  232. package/src/providers/model-rename-fields.ts +2 -0
  233. package/src/providers/quota/account-cache.ts +37 -11
  234. package/src/providers/quota/anthropic-account-quota.ts +93 -0
  235. package/src/providers/quota/anthropic-cooldown-recovery.ts +222 -111
  236. package/src/providers/quota/report-cache.ts +24 -1
  237. package/src/providers/quota/vendor-probes-key.ts +127 -17
  238. package/src/providers/quota/vendor-probes-oauth.ts +46 -14
  239. package/src/providers/quota.ts +23 -33
  240. package/src/providers/registry/entries-core.ts +35 -30
  241. package/src/providers/registry/model-ids.ts +1 -0
  242. package/src/providers/registry/model-seeds.ts +7 -4
  243. package/src/providers/registry/types.ts +3 -0
  244. package/src/providers/registry.ts +3 -3
  245. package/src/responses/reasoning-envelope.ts +29 -1
  246. package/src/responses/reasoning-replay-cache.ts +17 -1
  247. package/src/responses/spill-store.ts +20 -3
  248. package/src/responses/state/metrics.ts +95 -0
  249. package/src/responses/state/snapshot-codec.ts +6 -0
  250. package/src/responses/state/spill-failure.ts +12 -0
  251. package/src/responses/state/spill-queue.ts +13 -5
  252. package/src/responses/state/unforced-store-false.ts +55 -0
  253. package/src/responses/state.ts +68 -76
  254. package/src/responses/task-input.ts +17 -0
  255. package/src/router.ts +28 -4
  256. package/src/routing/compatibility/assemble.ts +1 -1
  257. package/src/routing/quota.ts +7 -3
  258. package/src/server/auth-cors.ts +5 -0
  259. package/src/server/background-lifecycle.ts +27 -0
  260. package/src/server/chat-completions.ts +7 -1
  261. package/src/server/chat-native.ts +64 -28
  262. package/src/server/claude-messages.ts +59 -8
  263. package/src/server/gui-static.ts +38 -4
  264. package/src/server/inbound-body-admission.ts +1 -0
  265. package/src/server/index/claude-intercept-lifecycle.ts +3 -1
  266. package/src/server/index/serve-options.ts +10 -2
  267. package/src/server/index/spend-ledger-lifecycle.ts +12 -5
  268. package/src/server/index/startup-warnings.ts +19 -0
  269. package/src/server/index.ts +5 -2
  270. package/src/server/inference/context.ts +1 -1
  271. package/src/server/lifecycle.ts +1 -1
  272. package/src/server/management/agent-settings-routes.ts +123 -38
  273. package/src/server/management/anthropic-account-threshold.ts +12 -9
  274. package/src/server/management/anthropic-pool-settings.ts +72 -0
  275. package/src/server/management/anthropic-reset-grant-routes.ts +98 -39
  276. package/src/server/management/codex-agent-role-routes.ts +4 -2
  277. package/src/server/management/codex-prompt-routes.ts +1 -0
  278. package/src/server/management/codex-role-auto-assign.ts +13 -2
  279. package/src/server/management/config-routes.ts +85 -14
  280. package/src/server/management/context.ts +2 -2
  281. package/src/server/management/decision-routes.ts +2 -1
  282. package/src/server/management/integration-routes.ts +25 -2
  283. package/src/server/management/oauth-account-routes.ts +79 -98
  284. package/src/server/management/provider-instance-ownership.ts +44 -0
  285. package/src/server/management/provider-routes.ts +21 -28
  286. package/src/server/management/route-registry.ts +3 -1
  287. package/src/server/management/routing-profile-routes.ts +4 -4
  288. package/src/server/management/shared.ts +3 -3
  289. package/src/server/management/system-restart.ts +44 -14
  290. package/src/server/management/system-routes.ts +24 -2
  291. package/src/server/management/vision-sidecar-options.ts +9 -6
  292. package/src/server/management/web-search-sidecar-options.ts +51 -2
  293. package/src/server/management-auth.ts +6 -0
  294. package/src/server/messages-native-eligibility.ts +3 -2
  295. package/src/server/messages-native-oauth.ts +91 -45
  296. package/src/server/messages-native-selector.ts +39 -0
  297. package/src/server/messages-native.ts +133 -38
  298. package/src/server/messages-response-headers.ts +14 -0
  299. package/src/server/request-log-conversation.ts +48 -5
  300. package/src/server/request-log.ts +36 -2
  301. package/src/server/responses/adapter-continuation.ts +31 -19
  302. package/src/server/responses/adapter-dispatch.ts +75 -24
  303. package/src/server/responses/antigravity-validation-refusal.ts +20 -6
  304. package/src/server/responses/codex-ws-exchange.ts +17 -2
  305. package/src/server/responses/codex-ws-pool.ts +100 -13
  306. package/src/server/responses/codex-ws-wire.ts +9 -0
  307. package/src/server/responses/combo-requested-effort.ts +3 -1
  308. package/src/server/responses/compact.ts +25 -8
  309. package/src/server/responses/compaction-recovery.ts +1 -1
  310. package/src/server/responses/core-codex-account.ts +22 -1
  311. package/src/server/responses/core-combo-native.ts +24 -4
  312. package/src/server/responses/core-combo.ts +38 -25
  313. package/src/server/responses/core-lifetime.ts +3 -36
  314. package/src/server/responses/core-normalize.ts +1 -0
  315. package/src/server/responses/core-options.ts +8 -1
  316. package/src/server/responses/core-replay.ts +78 -0
  317. package/src/server/responses/core.ts +3 -3
  318. package/src/server/responses/fetch-helpers.ts +26 -3
  319. package/src/server/responses/passthrough-delivery.ts +30 -4
  320. package/src/server/responses/passthrough-dispatch.ts +39 -9
  321. package/src/server/responses/request-prepare.ts +41 -5
  322. package/src/server/responses/request-send-budget.ts +150 -50
  323. package/src/server/responses/request-spend.ts +325 -33
  324. package/src/server/responses/request-transport.ts +132 -52
  325. package/src/server/responses/response-effects.ts +1 -0
  326. package/src/server/responses/run-turn-execution.ts +13 -3
  327. package/src/server/responses/sidecar-execution.ts +113 -22
  328. package/src/server/responses/sidecar-send-budget.ts +58 -0
  329. package/src/server/responses/ws-upstream.ts +19 -6
  330. package/src/server/responses-undeclared-tool-guard.ts +5 -1
  331. package/src/server/startup-health-cache.ts +2 -1
  332. package/src/server/workflow-refusal.ts +33 -0
  333. package/src/service/cli.ts +20 -4
  334. package/src/service/desktop-command-guard.ts +32 -0
  335. package/src/service/desktop-startup.ts +66 -40
  336. package/src/service/desktop-supervision.d.mts +26 -0
  337. package/src/service/desktop-supervision.mjs +140 -0
  338. package/src/service/managing-cli.ts +1 -0
  339. package/src/service/orchestration.ts +46 -20
  340. package/src/service/ownership-mutation-lease.d.mts +1 -1
  341. package/src/service/ownership-mutation-lease.mjs +5 -7
  342. package/src/service/repair.ts +25 -9
  343. package/src/service/state.ts +4 -3
  344. package/src/service/systemd.ts +17 -8
  345. package/src/service/windows-ops.ts +61 -22
  346. package/src/sidecar/anthropic-binding.ts +79 -0
  347. package/src/sidecar/auth.ts +76 -2
  348. package/src/storage/policy-job.ts +5 -1
  349. package/src/storage/policy-worker.ts +3 -1
  350. package/src/storage/policy.ts +9 -0
  351. package/src/types/anthropic-account-pool.ts +33 -0
  352. package/src/types/config.ts +33 -28
  353. package/src/types/provider.ts +5 -0
  354. package/src/types/request.ts +5 -0
  355. package/src/types/tools.ts +7 -0
  356. package/src/types/wire.ts +1 -0
  357. package/src/types.ts +4 -0
  358. package/src/update/index.ts +57 -9
  359. package/src/update/restart-ownership.ts +10 -3
  360. package/src/update/runtime-ownership.d.mts +6 -0
  361. package/src/update/runtime-ownership.mjs +23 -7
  362. package/src/usage/cost.ts +3 -2
  363. package/src/usage/expected-prices.ts +66 -14
  364. package/src/vision/anthropic-describe.ts +16 -3
  365. package/src/vision/index.ts +3 -0
  366. package/src/vision/plan.ts +19 -6
  367. package/src/web-search/alpha-search.ts +7 -1
  368. package/src/web-search/anthropic-executor.ts +16 -4
  369. package/src/web-search/index.ts +7 -3
  370. package/src/web-search/loop.ts +32 -7
  371. package/src/web-search/passthrough-bridge.ts +4 -1
  372. package/src/web-search/sidecar-providers.ts +4 -3
  373. package/gui/dist/assets/App-MbDVfUeC.js +0 -54
  374. package/gui/dist/assets/Tray-CzkxK82A.js +0 -1
  375. package/gui/dist/assets/index-UJt6npXk.js +0 -94
  376. package/gui/dist/assets/quota-summary-DU3sD6Z-.js +0 -1
@@ -21,6 +21,7 @@ import { existsSync, mkdirSync, readFileSync, writeSync } from "node:fs";
21
21
  import { dirname, join } from "node:path";
22
22
  import { atomicWriteFileStreamed } from "../config/atomic-write";
23
23
  import { getConfigDir } from "../config/paths";
24
+ import type { AnthropicInstanceId } from "./anthropic-instance-id";
24
25
 
25
26
  export const ANTHROPIC_RESET_LEASE_MS = 90_000;
26
27
  export const ANTHROPIC_RESET_RETRY_WINDOW_MS = 600_000;
@@ -83,7 +84,16 @@ export function anthropicOrgDigest(organizationUuid: string): string {
83
84
  }
84
85
 
85
86
  export function anthropicResetJournalPath(customDir?: string): string {
86
- return join(customDir ?? getConfigDir(), "anthropic-reset-grant-ledger.json");
87
+ return anthropicResetJournalPathForInstance("anthropic", customDir);
88
+ }
89
+
90
+ export function anthropicResetJournalPathForInstance(instance: AnthropicInstanceId, customDir?: string): string {
91
+ return join(customDir ?? getConfigDir(), `${instance}-reset-grant-ledger.json`);
92
+ }
93
+
94
+ /** Namespace is in the path; A's version-1 journal bytes and record schema stay unchanged. */
95
+ export function anthropicResetLedgerOptionsForInstance(instance: AnthropicInstanceId, customDir?: string): AnthropicResetLedgerOptions {
96
+ return { journalPath: anthropicResetJournalPathForInstance(instance, customDir) };
87
97
  }
88
98
 
89
99
  function isRecordShape(value: unknown): value is OperationRecord {
@@ -0,0 +1,115 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { atomicWriteFile } from "../config/atomic-write";
4
+ import { getConfigDir } from "../config/paths";
5
+ import { readBoundedResponseBytes } from "../lib/bounded-body";
6
+
7
+ const SOURCE_URL = "https://models.dev/api.json";
8
+ const FILENAME = "azure-model-metadata-cache.json";
9
+ const TTL_MS = 24 * 60 * 60 * 1000;
10
+
11
+ export type AzureModelMetadata = { input?: string[]; contextWindow?: number; maxTokens?: number };
12
+ type Snapshot = { version: 1; fetchedAt: number; models: Record<string, AzureModelMetadata> };
13
+ type CacheState = { snapshot?: Snapshot | null; inFlight?: Promise<void>; retryAfter: number };
14
+ const cacheByConfigDir = new Map<string, CacheState>();
15
+
16
+ function cacheState(configDir: string): CacheState {
17
+ let state = cacheByConfigDir.get(configDir);
18
+ if (!state) {
19
+ state = { retryAfter: 0 };
20
+ cacheByConfigDir.set(configDir, state);
21
+ }
22
+ return state;
23
+ }
24
+
25
+ export function isAzureModelMetadataDestination(baseUrl: string | undefined): boolean {
26
+ try {
27
+ return !!baseUrl && new URL(baseUrl).hostname.endsWith(".openai.azure.com");
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+
33
+ function positiveInteger(value: unknown): number | undefined {
34
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined;
35
+ }
36
+
37
+ function record(value: unknown): Record<string, unknown> | undefined {
38
+ return value !== null && typeof value === "object" && !Array.isArray(value)
39
+ ? value as Record<string, unknown> : undefined;
40
+ }
41
+
42
+ function parseModels(models: unknown, published: boolean): Record<string, AzureModelMetadata> {
43
+ const out: Record<string, AzureModelMetadata> = Object.create(null);
44
+ for (const [id, value] of Object.entries(record(models) ?? {}).slice(0, 2000)) {
45
+ const row = record(value);
46
+ if (!row || id.length > 256) continue;
47
+ const limit = published ? record(row.limit) : row;
48
+ const input = published ? record(row.modalities)?.input : row.input;
49
+ const modalities = Array.isArray(input)
50
+ ? [...new Set(input.filter((item): item is string => item === "text" || item === "image" || item === "audio"))]
51
+ : [];
52
+ const contextWindow = positiveInteger(published ? limit?.context : limit?.contextWindow);
53
+ const maxTokens = positiveInteger(published ? limit?.output : limit?.maxTokens);
54
+ if (!modalities.length && contextWindow === undefined && maxTokens === undefined) continue;
55
+ out[id.toLowerCase()] = {
56
+ ...(modalities.length ? { input: modalities } : {}),
57
+ ...(contextWindow !== undefined ? { contextWindow } : {}),
58
+ ...(maxTokens !== undefined ? { maxTokens } : {}),
59
+ };
60
+ }
61
+ return out;
62
+ }
63
+
64
+ function loadSnapshot(configDir: string, state: CacheState): Snapshot | null {
65
+ if (state.snapshot !== undefined) return state.snapshot;
66
+ state.snapshot = null;
67
+ try {
68
+ const raw = record(JSON.parse(readFileSync(join(configDir, FILENAME), "utf8")));
69
+ if (raw?.version === 1 && positiveInteger(raw.fetchedAt) !== undefined && record(raw.models)) {
70
+ state.snapshot = { version: 1, fetchedAt: raw.fetchedAt as number, models: parseModels(raw.models, false) };
71
+ }
72
+ } catch { /* Missing or corrupt optional metadata leaves bundled hints available. */ }
73
+ return state.snapshot;
74
+ }
75
+
76
+ /** Synchronous catalog fallback. Generation requests never fetch this snapshot. */
77
+ export function publishedAzureModelMetadata(modelId: string, configDir = getConfigDir()): AzureModelMetadata | undefined {
78
+ return loadSnapshot(configDir, cacheState(configDir))?.models[modelId.toLowerCase()];
79
+ }
80
+
81
+ /** Refresh once per day during discovery, coalesced and bounded; retain stale data offline. */
82
+ export async function refreshAzureModelMetadata(baseUrl: string | undefined, configDir = getConfigDir()): Promise<void> {
83
+ if (!isAzureModelMetadataDestination(baseUrl)) return;
84
+ // Capture state and destination together: tests and embedders can change config roots.
85
+ const state = cacheState(configDir);
86
+ const current = loadSnapshot(configDir, state);
87
+ if ((current && Date.now() - current.fetchedAt < TTL_MS) || Date.now() < state.retryAfter) return;
88
+ if (state.inFlight) return state.inFlight;
89
+ const path = join(configDir, FILENAME);
90
+ state.inFlight = (async () => {
91
+ try {
92
+ const signal = AbortSignal.timeout(2000);
93
+ const response = await fetch(SOURCE_URL, {
94
+ headers: { accept: "application/json" },
95
+ redirect: "error",
96
+ signal,
97
+ });
98
+ if (!response.ok) return;
99
+ const body = await readBoundedResponseBytes(response, { maxBytes: 16 * 1024 * 1024, signal });
100
+ if (body.oversized) return;
101
+ const raw = record(JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(body.bytes)));
102
+ // Provider-specific rows matter: Azure may expose text only where the direct vendor has vision.
103
+ const models = parseModels(record(raw?.azure)?.models, true);
104
+ if (!Object.keys(models).length) return;
105
+ state.snapshot = { version: 1, fetchedAt: Date.now(), models };
106
+ try { atomicWriteFile(path, JSON.stringify(state.snapshot) + "\n"); } catch { /* Memory still serves discovery. */ }
107
+ } catch { /* A public metadata outage must not fail provider discovery. */ }
108
+ finally { state.retryAfter = Date.now() + 60_000; }
109
+ })().finally(() => { state.inFlight = undefined; });
110
+ return state.inFlight;
111
+ }
112
+
113
+ export function resetAzureModelMetadataForTests(): void {
114
+ cacheByConfigDir.clear();
115
+ }
@@ -1,5 +1,7 @@
1
1
  import type { CodexAccountMode, OcxProviderConfig } from "../types";
2
+ import { COST_VENDOR_PRIORITY, getModelMetadata, getModelMetadataCaseInsensitive } from "../generated/model-metadata";
2
3
  import { cloneFastWire } from "./fastwire";
4
+ import { isAzureModelMetadataDestination, publishedAzureModelMetadata, type AzureModelMetadata } from "./azure-model-metadata";
3
5
  import { resolveModelPolicy } from "./resolved-model-policy";
4
6
  import {
5
7
  PROVIDER_REGISTRY,
@@ -245,6 +247,8 @@ export function providerConfigSeed(entry: ProviderRegistryEntry): OcxProviderCon
245
247
  // Preserve the registry auth kind verbatim (including "local") so fail-closed gates that
246
248
  // distinguish local runtimes from API-key providers keep working after the seed round-trip.
247
249
  authMode: entry.authKind,
250
+ ...(entry.id === "anthropic2" && entry.authKind === "oauth" && entry.oauthFamily === "anthropic"
251
+ ? { anthropicOAuthInstance: "anthropic2" as const } : {}),
248
252
  ...(entry.codexAccountMode ? { codexAccountMode: entry.codexAccountMode } : {}),
249
253
  ...(entry.keyOptional !== undefined ? { keyOptional: entry.keyOptional } : {}),
250
254
  ...(entry.freeTier !== undefined ? { freeTier: entry.freeTier } : {}),
@@ -649,6 +653,25 @@ export function shouldCaseFoldMetadataModelId(providerId: string): boolean {
649
653
  return entry?.metadataModelIdNormalize === "case-insensitive";
650
654
  }
651
655
 
656
+ const AZURE_VENDOR_METADATA_PRIORITY = [
657
+ "azure-openai",
658
+ ...COST_VENDOR_PRIORITY.filter(provider => provider !== "azure-openai"),
659
+ ];
660
+
661
+ /**
662
+ * Fill missing Azure model metadata by destination and known model id. Custom provider
663
+ * names miss PROVIDER_ALIASES; arbitrary deployment aliases still need explicit metadata.
664
+ */
665
+ export function azureVendorModelMetadata(baseUrl: string | undefined, modelId: string, metadataConfigDir?: string): AzureModelMetadata | undefined {
666
+ if (!isAzureModelMetadataDestination(baseUrl)) return undefined;
667
+ const published = publishedAzureModelMetadata(modelId, metadataConfigDir);
668
+ for (const provider of AZURE_VENDOR_METADATA_PRIORITY) {
669
+ const meta = getModelMetadata(provider, modelId) ?? getModelMetadataCaseInsensitive(provider, modelId);
670
+ if (meta) return { ...meta, ...published };
671
+ }
672
+ return published;
673
+ }
674
+
652
675
  function entryToPreset(entry: ProviderRegistryEntry): DerivedProviderPreset {
653
676
  return {
654
677
  id: entry.id,
@@ -25,6 +25,8 @@ export const KIRO_MODELS = [
25
25
  "claude-sonnet-4.6",
26
26
  "claude-sonnet-4.5",
27
27
  "claude-sonnet-4.0",
28
+ // 261008 preemptive: Haiku 5.5; calls fail upstream until Kiro ships it.
29
+ "claude-haiku-5.5",
28
30
  "claude-haiku-4.5",
29
31
  "deepseek-3.2",
30
32
  "minimax-m2.5",
@@ -55,6 +57,7 @@ export const KIRO_MODEL_CONTEXT_WINDOWS: Record<string, number> = {
55
57
  "claude-sonnet-4.6": 1_000_000,
56
58
  "claude-sonnet-4.5": 200_000,
57
59
  "claude-sonnet-4.0": 200_000,
60
+ "claude-haiku-5.5": 1_000_000,
58
61
  "claude-haiku-4.5": 200_000,
59
62
  "deepseek-3.2": 128_000,
60
63
  "minimax-m2.5": 200_000,
@@ -89,6 +89,6 @@ export function stampOAuthAccountLabel(
89
89
  if (!accountId) return;
90
90
  if (provider.authMode !== "oauth") return;
91
91
  const base = baseProviderLabel(providerName);
92
- if (base === "openai" || base === "anthropic") return;
92
+ if (base === "openai" || (base === "anthropic" || base === "anthropic2")) return;
93
93
  logCtx.accountLogLabel = oauthAccountLogLabel(accountId, base);
94
94
  }
@@ -78,6 +78,8 @@ export const PROVIDER_MODEL_RENAME_ROLES = {
78
78
  modelVercelGatewayRouting: "record",
79
79
  authMode: "none",
80
80
  oauthAccountFailover: "none",
81
+ anthropicAccountPool: "none",
82
+ anthropicOAuthInstance: "none",
81
83
  keyOptional: "none",
82
84
  freeTier: "none",
83
85
  note: "none",
@@ -1,8 +1,9 @@
1
1
  import { parseAnthropicFamilyHeaders, mergeAnthropicFamilyWindows } from "./anthropic-family-headers";
2
- import { observeAnthropicFamilyQuota, clearAnthropicRequestedFamilyQuota, ANTHROPIC_PASSIVE_FAMILY_MAX_AGE_MS } from "../../oauth/anthropic-model-quota";
2
+ import { anthropicModelQuotaFor, ANTHROPIC_PASSIVE_FAMILY_MAX_AGE_MS } from "../../oauth/anthropic-model-quota";
3
3
  import { createHash } from "node:crypto";
4
4
  import { getValidAccessTokenForAccount } from "../../oauth";
5
5
  import { credentialGeneration, getAccountCredential, getAccountCredentialWithStatus, getAccountSet } from "../../oauth/store";
6
+ import { isAnthropicInstanceId, type AnthropicInstanceId } from "../anthropic-instance-id";
6
7
  import type { GenerationContext } from "../../lib/state-store-sweeper";
7
8
  import { ACCOUNT_QUOTA_TTL_MS, toFiniteNumber } from "../quota-wire";
8
9
  import { clearKiroAccountUsageState, hydrateKiroUsageVerdict, kiroPersistableVerdicts, reconcileKiroAccountUsageState } from "../kiro-usage";
@@ -96,8 +97,19 @@ export function normalizeAnthropicQuota(quota: ProviderQuota | null | undefined,
96
97
  return hasQuotaRows(result) ? result : null;
97
98
  }
98
99
 
100
+ function isAnthropicAccountCacheKey(key: string): boolean {
101
+ const separator = key.indexOf("\0");
102
+ return separator > 0 && isAnthropicInstanceId(key.slice(0, separator));
103
+ }
104
+
99
105
  export const accountQuotaCache = new Map<string, AccountQuotaCacheEntry>();
100
106
  export let explicitAccountEpoch = 0;
107
+ let allAccountQuotaEpoch = 0;
108
+ const providerAccountQuotaEpochs = new Map<string, number>();
109
+ /** Instance-owned cache clears do not revoke an unrelated account's probe. */
110
+ export function captureProviderAccountQuotaEpoch(provider: string): number {
111
+ return allAccountQuotaEpoch + (providerAccountQuotaEpochs.get(provider) ?? 0);
112
+ }
101
113
 
102
114
  /**
103
115
  * Seed the cache from the last run, once.
@@ -127,7 +139,7 @@ export function hydrateAccountQuotaCache(): void {
127
139
  }
128
140
  // Disk stores observation time, not the Anthropic usage probe's clock.
129
141
  if (!accountQuotaCache.has(key)) {
130
- const anthropic = key.startsWith("anthropic\u0000");
142
+ const anthropic = isAnthropicAccountCacheKey(key);
131
143
  accountQuotaCache.set(key, {
132
144
  ts: anthropic ? 0 : quota.updatedAt,
133
145
  quota: anthropic ? normalizeAnthropicQuota(quota, now)
@@ -148,7 +160,7 @@ export function persistAccountQuotaCache(): void {
148
160
  const liveKiro = new Map<string, ProviderAccount>(getAccountSet("kiro")?.accounts.map(account =>
149
161
  [accountCacheKey("kiro", account.id), account]) ?? []);
150
162
  for (const [key, entry] of accountQuotaCache) {
151
- const quota = key.startsWith("anthropic\u0000") ? normalizeAnthropicQuota(entry.quota, now) : entry.quota;
163
+ const quota = isAnthropicAccountCacheKey(key) ? normalizeAnthropicQuota(entry.quota, now) : entry.quota;
152
164
  if (quota && key.startsWith("kiro\0")) {
153
165
  const account = liveKiro.get(key);
154
166
  if (account && entry.identity === kiroEvidenceIdentity(account))
@@ -182,7 +194,7 @@ export interface ProviderAccountQuota {
182
194
 
183
195
  /** Providers whose per-account quota can be probed. Extend as other OAuth APIs are covered. */
184
196
  export function supportsPerAccountQuota(provider: string): boolean {
185
- return provider === "anthropic" || provider === "kiro" || provider === "google-antigravity"
197
+ return isAnthropicInstanceId(provider) || provider === "kiro" || provider === "google-antigravity"
186
198
  || explicitAccountReader(provider);
187
199
  }
188
200
 
@@ -209,12 +221,12 @@ export function getCachedProviderAccountQuota(provider: string, accountId: strin
209
221
  const account = getAccountSet("kiro")?.accounts.find(row => row.id === accountId);
210
222
  if (!account || entry?.identity !== kiroEvidenceIdentity(account)) return null;
211
223
  }
212
- if (provider === "anthropic" && entry?.anthropicCredentialGeneration) {
224
+ if (isAnthropicInstanceId(provider) && entry?.anthropicCredentialGeneration) {
213
225
  const credential = getAccountCredential(provider, accountId);
214
226
  if (!credential || credentialGeneration(credential) !== entry.anthropicCredentialGeneration) return null;
215
227
  }
216
228
  if (entry?.isCurrent && !entry.isCurrent()) return null;
217
- return provider === "anthropic" ? normalizeAnthropicQuota(entry?.quota, Date.now()) : entry?.quota ?? null;
229
+ return isAnthropicInstanceId(provider) ? normalizeAnthropicQuota(entry?.quota, Date.now()) : entry?.quota ?? null;
218
230
  }
219
231
 
220
232
  /** Test-only: seed or clear the per-account quota cache without probing upstream. */
@@ -270,7 +282,8 @@ function normalizeUtilizationFraction(value: string | null): number | undefined
270
282
  * erasing model-specific windows. The caller owns credential attribution; this guard
271
283
  * prevents a retired account key from being revived by an older config generation.
272
284
  */
273
- export function recordAnthropicAccountQuotaFromHeaders(
285
+ export function recordAnthropicAccountQuotaFromHeadersForInstance(
286
+ instance: AnthropicInstanceId,
274
287
  accountId: string,
275
288
  headers: Headers,
276
289
  writerGeneration: number,
@@ -278,7 +291,8 @@ export function recordAnthropicAccountQuotaFromHeaders(
278
291
  model?: string,
279
292
  ): void {
280
293
  if (!accountId) return;
281
- const key = accountCacheKey("anthropic", accountId);
294
+ const key = accountCacheKey(instance, accountId);
295
+ const { observeAnthropicFamilyQuota, clearAnthropicRequestedFamilyQuota } = anthropicModelQuotaFor(instance);
282
296
  if (!mayCommitAccountQuotaKey(key, writerGeneration)) return;
283
297
  if (status !== undefined && status >= 200 && status < 300) clearAnthropicRequestedFamilyQuota(accountId, model);
284
298
  const observed = parseAnthropicRateLimitHeaders(headers, status);
@@ -289,7 +303,7 @@ export function recordAnthropicAccountQuotaFromHeaders(
289
303
  // every other provider's saved row.
290
304
  hydrateAccountQuotaCache();
291
305
  const candidate = accountQuotaCache.get(key);
292
- const credential = getAccountCredential("anthropic", accountId);
306
+ const credential = getAccountCredential(instance, accountId);
293
307
  const generation = credential && credentialGeneration(credential);
294
308
  const previous = candidate?.isCurrent?.() === false
295
309
  || candidate?.anthropicCredentialGeneration && candidate.anthropicCredentialGeneration !== generation ? undefined : candidate;
@@ -307,6 +321,13 @@ export function recordAnthropicAccountQuotaFromHeaders(
307
321
  persistAccountQuotaCache();
308
322
  }
309
323
 
324
+ /** Legacy recorder: config/roster generation is numeric and distinct from bearer ownership. */
325
+ export function recordAnthropicAccountQuotaFromHeaders(
326
+ accountId: string, headers: Headers, writerGeneration: number, status?: number, model?: string,
327
+ ): void {
328
+ recordAnthropicAccountQuotaFromHeadersForInstance("anthropic", accountId, headers, writerGeneration, status, model);
329
+ }
330
+
310
331
  /**
311
332
  * Providers whose per-account quota is OBSERVED in-band, never probed.
312
333
  *
@@ -380,7 +401,7 @@ export function sweepExpiredProviderAccountQuotaRows(now = Date.now()): number {
380
401
  let removed = 0;
381
402
  for (const [key, entry] of accountQuotaCache) {
382
403
  // Anthropic observations extend retention, never the usage probe's eligibility clock.
383
- const retainedAt = key.startsWith("anthropic\u0000")
404
+ const retainedAt = isAnthropicAccountCacheKey(key)
384
405
  ? Math.max(entry.ts, entry.quota?.updatedAt ?? 0)
385
406
  : entry.ts;
386
407
  if (retainedAt + ACCOUNT_QUOTA_TTL_MS > now) continue;
@@ -425,8 +446,10 @@ export function resetProviderQuotaReconcileStateForTests(): void {
425
446
 
426
447
  /** Drop cached per-account rows (all, or just one provider's). */
427
448
  export function clearAccountQuotaCache(provider?: string): void {
428
- explicitAccountEpoch += 1;
449
+ // Anthropic readers use provider-owned epochs; B cannot revoke an A flight.
450
+ if (provider !== "anthropic2") explicitAccountEpoch += 1;
429
451
  if (!provider) {
452
+ allAccountQuotaEpoch += 1;
430
453
  accountQuotaCache.clear();
431
454
  accountQuotaInflight.clear();
432
455
  clearKiroAccountUsageState();
@@ -436,6 +459,7 @@ export function clearAccountQuotaCache(provider?: string): void {
436
459
  cancelPendingAccountQuotaPersist();
437
460
  return;
438
461
  }
462
+ providerAccountQuotaEpochs.set(provider, (providerAccountQuotaEpochs.get(provider) ?? 0) + 1);
439
463
  hydrateAccountQuotaCache();
440
464
  const prefix = `${provider}\u0000`;
441
465
  for (const key of [...accountQuotaCache.keys()]) {
@@ -464,7 +488,9 @@ export async function getTokenForAccountQuotaProbe(provider: string, accountId:
464
488
  const row = getAccountCredentialWithStatus(provider, accountId);
465
489
  if (!row) throw new Error("account credential missing");
466
490
  // An operator-paused account is excluded from every automatic upstream use, quota reads included.
491
+ // Anthropic instances also skip a needs-reauth row; other providers keep their probe behavior.
467
492
  if (row.paused) throw new Error("account is paused; quota probe skipped");
493
+ if (row.needsReauth && isAnthropicInstanceId(provider)) throw new Error("account needs sign-in; quota probe skipped");
468
494
  const stored = row.credential;
469
495
  if (stored.expires > Date.now() + ACCOUNT_TOKEN_SKEW_MS) return stored.access;
470
496
  const activeId = getAccountSet(provider)?.activeAccountId;
@@ -0,0 +1,93 @@
1
+ import { loadConfig } from "../../config";
2
+ import type { OcxConfig } from "../../types";
3
+ import { getAccountSet } from "../../oauth/store";
4
+ import { captureConfigGeneration, sweepExpiredOnWrite } from "../../lib/state-store-sweeper";
5
+ import { configuredAnthropicInstance, type AnthropicInstanceId } from "../anthropic-instance";
6
+ import { ACCOUNT_QUOTA_TTL_MS } from "../quota-wire";
7
+ import { fetchAnthropicUsageQuotaForInstance } from "./vendor-probes-oauth";
8
+ import { AnthropicQuotaProbeOwnershipError, anthropicCooldownRecoveryFor, type AnthropicQuotaRecoveryResult } from "./anthropic-cooldown-recovery";
9
+ import {
10
+ accountCacheKey, accountQuotaCache, accountQuotaInflight, captureProviderAccountQuotaEpoch,
11
+ getTokenForAccountQuotaProbe, hydrateAccountQuotaCache, mayCommitAccountQuotaKey,
12
+ normalizeAnthropicQuota, type AccountQuotaCacheEntry,
13
+ } from "./account-cache";
14
+
15
+ /**
16
+ * Resolve renewal first: a flight always belongs to the credential actually dispatched.
17
+ * `config` is the caller's live object. Currency re-reads that one object, never a fresh file
18
+ * load, so memory/disk drift cannot refuse every probe; commit authority stays with the
19
+ * captured writer generation.
20
+ */
21
+ export async function fetchAnthropicAccountQuota(
22
+ instance: AnthropicInstanceId, accountId: string, force: boolean, config: OcxConfig = loadConfig(),
23
+ ): Promise<AccountQuotaCacheEntry> {
24
+ const unavailable = (): AccountQuotaCacheEntry => ({ ts: Date.now(), quota: null, unavailable: true });
25
+ const target = config.providers[instance];
26
+ if (target?.disabled || target && target.authMode !== "oauth" || configuredAnthropicInstance(config, instance) !== instance) return unavailable();
27
+ const targetIdentity = JSON.stringify(target);
28
+ const epoch = captureProviderAccountQuotaEpoch(instance);
29
+ const initialRow = getAccountSet(instance)?.accounts.find(row => row.id === accountId);
30
+ if (!initialRow) return unavailable();
31
+ const recovery = anthropicCooldownRecoveryFor(instance);
32
+ const incarnation = recovery.reserveAnthropicAccountIncarnation(accountId);
33
+ const ownerCurrent = () => {
34
+ const row = getAccountSet(instance)?.accounts.find(row => row.id === accountId);
35
+ return epoch === captureProviderAccountQuotaEpoch(instance)
36
+ && configuredAnthropicInstance(config, instance) === instance
37
+ && JSON.stringify(config.providers[instance]) === targetIdentity
38
+ && recovery.anthropicAccountIncarnation(accountId) === incarnation
39
+ && !!row && row.loginId === initialRow.loginId && row.addedAt === initialRow.addedAt;
40
+ };
41
+ hydrateAccountQuotaCache();
42
+ const key = accountCacheKey(instance, accountId);
43
+ const candidate = accountQuotaCache.get(key);
44
+ const cached = candidate?.isCurrent?.() === false ? undefined : candidate;
45
+ if (!force && cached && Date.now() - cached.ts < ACCOUNT_QUOTA_TTL_MS) {
46
+ return { ...cached, quota: normalizeAnthropicQuota(cached.quota, Date.now()) };
47
+ }
48
+ let token: string;
49
+ try { token = await getTokenForAccountQuotaProbe(instance, accountId); }
50
+ catch { return unavailable(); }
51
+ if (!ownerCurrent()) return unavailable();
52
+ const flightKey = recovery.anthropicCredentialQuotaFlightKey(key, accountId);
53
+ const flightCurrent = () => ownerCurrent()
54
+ && recovery.anthropicCredentialQuotaFlightKey(key, accountId) === flightKey;
55
+ const joinable = accountQuotaInflight.get(flightKey);
56
+ if (joinable) {
57
+ const joined = await joinable;
58
+ return ownerCurrent() && (joined.isCurrent?.() ?? flightCurrent()) ? joined : unavailable();
59
+ }
60
+ const writerGeneration = captureConfigGeneration();
61
+ // One publication path for a result and for a failed dispatch. A null result (HTTP refusal)
62
+ // and a thrown read (network failure) both record the unavailable row over the retained
63
+ // in-flight observations, as the pre-instance A path did, so joined callers and later cached
64
+ // reads agree and windows that expired during the shared probe are normalized away.
65
+ const publish = (result: AnthropicQuotaRecoveryResult | null): AccountQuotaCacheEntry => {
66
+ const previous = accountQuotaCache.get(key);
67
+ const retained = previous?.isCurrent?.() === false ? undefined : previous;
68
+ const isCurrent = result?.isCurrent ?? flightCurrent;
69
+ const entry: AccountQuotaCacheEntry = {
70
+ ts: Date.now(), quota: normalizeAnthropicQuota(result?.quota ?? retained?.quota, Date.now()),
71
+ ...(!result ? { unavailable: true as const } : {}), isCurrent,
72
+ };
73
+ if (isCurrent() && mayCommitAccountQuotaKey(key, writerGeneration)) { accountQuotaCache.set(key, entry); sweepExpiredOnWrite(entry.ts); }
74
+ return entry;
75
+ };
76
+ const probe = (async (): Promise<AccountQuotaCacheEntry> => {
77
+ try {
78
+ const result = await recovery.probeAnthropicQuotaWithRecovery(accountId, token, fresh => {
79
+ if (!flightCurrent()) throw new AnthropicQuotaProbeOwnershipError("quota flight changed");
80
+ recovery.assertAnthropicQuotaSendAllowed(accountId, token);
81
+ return fetchAnthropicUsageQuotaForInstance(instance, token, fresh);
82
+ }, () => ownerCurrent() && mayCommitAccountQuotaKey(key, writerGeneration));
83
+ if (result && !result.isCurrent()) return unavailable();
84
+ if (!result && !flightCurrent()) return unavailable();
85
+ return publish(result);
86
+ } catch (error) {
87
+ if (error instanceof AnthropicQuotaProbeOwnershipError || !flightCurrent()) return unavailable();
88
+ return publish(null);
89
+ }
90
+ })().finally(() => { if (accountQuotaInflight.get(flightKey) === probe) accountQuotaInflight.delete(flightKey); });
91
+ accountQuotaInflight.set(flightKey, probe);
92
+ return probe;
93
+ }