@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
@@ -2,7 +2,9 @@ import { markAnthropicFamilyEnumeration } from "./anthropic-family-headers";
2
2
  import { effectiveCodexAuthAccountId, fetchMainAccountInfoSnapshot, listCodexAuthAccountsSnapshot } from "../../codex/auth-api";
3
3
  import { MAIN_CODEX_ACCOUNT_ID } from "../../codex/main-account";
4
4
  import { getValidAccessToken } from "../../oauth";
5
- import { captureOAuthAccountSelection, getAccountCredential, getAccountSet } from "../../oauth/store";
5
+ import { loadConfig } from "../../config";
6
+ import { configuredAnthropicInstance, type AnthropicInstanceId } from "../anthropic-instance";
7
+ import { captureOAuthAccountSelection, credentialGeneration, getAccountCredential, getAccountSet } from "../../oauth/store";
6
8
  import { hydrateKiroAccountState, persistKiroAccountState } from "../kiro-account-state-disk";
7
9
  import { kiroProbeCurrent, kiroProbeIdentity } from "./kiro-account-probe";
8
10
  import { fetchMuseKeyQuotaSnapshot } from "../muse-key-quota";
@@ -33,6 +35,7 @@ import {
33
35
  } from "./report-cache";
34
36
  import {
35
37
  accountCacheKey,
38
+ captureProviderAccountQuotaEpoch,
36
39
  accountQuotaCache,
37
40
  hydrateAccountQuotaCache,
38
41
  mayCommitAccountQuotaKey,
@@ -40,7 +43,7 @@ import {
40
43
  } from "./account-cache";
41
44
  import type { OcxConfig, OcxProviderConfig } from "../../types";
42
45
  import type { ProviderQuota, ProviderQuotaWindow } from "../quota-types";
43
- import { AnthropicQuotaProbeOwnershipError, assertAnthropicQuotaSendAllowed, probeAnthropicQuotaWithRecovery } from "./anthropic-cooldown-recovery";
46
+ import { AnthropicQuotaProbeOwnershipError, anthropicCooldownRecoveryFor } from "./anthropic-cooldown-recovery";
44
47
 
45
48
  const XAI_BILLING_URL = "https://cli-chat-proxy.grok.com/v1/billing";
46
49
  const XAI_CREDITS_URL = `${XAI_BILLING_URL}?format=credits`;
@@ -336,39 +339,68 @@ async function readAnthropicUsageQuota(accessToken: string): Promise<ProviderQuo
336
339
  export async function fetchAnthropicUsageQuota(
337
340
  accessToken: string,
338
341
  requireFreshDispatch = false,
342
+ ): Promise<ProviderQuota | null> {
343
+ return fetchAnthropicUsageQuotaForInstance("anthropic", accessToken, requireFreshDispatch);
344
+ }
345
+
346
+ export async function fetchAnthropicUsageQuotaForInstance(
347
+ instance: AnthropicInstanceId,
348
+ accessToken: string,
349
+ requireFreshDispatch = false,
339
350
  ): Promise<ProviderQuota | null> {
340
351
  // Recovery evidence must be requested after the claimed cooldown. Joining an older request
341
352
  // can return after the 429 while still describing provider state from before that refusal.
342
- const joinable = requireFreshDispatch ? undefined : anthropicUsageInflight.get(accessToken);
353
+ const credentialIdentity = getAccountSet(instance)?.accounts.filter(row => row.credential.access === accessToken)
354
+ .map(row => [row.id, row.loginId, row.addedAt, credentialGeneration(row.credential)]);
355
+ const key = `${instance}\0${captureProviderAccountQuotaEpoch(instance)}\0${JSON.stringify(credentialIdentity)}\0${accessToken}`;
356
+ const joinable = requireFreshDispatch ? undefined : anthropicUsageInflight.get(key);
343
357
  if (joinable) return joinable;
344
358
 
345
359
  const probe = readAnthropicUsageQuota(accessToken).finally(() => {
346
- if (anthropicUsageInflight.get(accessToken) === probe) anthropicUsageInflight.delete(accessToken);
360
+ if (anthropicUsageInflight.get(key) === probe) anthropicUsageInflight.delete(key);
347
361
  });
348
- anthropicUsageInflight.set(accessToken, probe);
362
+ anthropicUsageInflight.set(key, probe);
349
363
  return probe;
350
364
  }
351
365
 
352
- export async function fetchAnthropicQuota(provider: string): Promise<ProviderQuotaReport | null> {
366
+ /** `config` is the caller's live object; currency re-reads it instead of reloading the file. */
367
+ export async function fetchAnthropicQuota(provider: string, config: OcxConfig = loadConfig()): Promise<ProviderQuotaReport | null> {
368
+ const instance = configuredAnthropicInstance(config, provider);
369
+ const target = config.providers[provider];
370
+ if (!instance || target?.disabled || target && target.authMode !== "oauth") return null;
371
+ const targetIdentity = JSON.stringify(target);
372
+ const targetCurrent = () => configuredAnthropicInstance(config, provider) === instance
373
+ && JSON.stringify(config.providers[provider]) === targetIdentity;
374
+ const recovery = anthropicCooldownRecoveryFor(instance);
353
375
  // Capture the account we intend to probe before awaiting — a mid-flight active
354
376
  // switch must not seed the wrong account's cache with this response.
355
- const selection = captureOAuthAccountSelection("anthropic");
377
+ const selection = captureOAuthAccountSelection(instance);
356
378
  const probedAccountId = selection?.accountId;
357
- const probedAccountKey = probedAccountId ? accountCacheKey("anthropic", probedAccountId) : null;
379
+ const initialRow = getAccountSet(instance)?.accounts.find(row => row.id === probedAccountId);
380
+ const epoch = captureProviderAccountQuotaEpoch(instance);
381
+ const incarnation = probedAccountId ? recovery.reserveAnthropicAccountIncarnation(probedAccountId) : undefined;
382
+ const probedAccountKey = probedAccountId ? accountCacheKey(instance, probedAccountId) : null;
358
383
  const writerGeneration = captureConfigGeneration();
359
384
  let accessToken: string;
360
385
  try {
361
- accessToken = await getValidAccessToken("anthropic");
386
+ accessToken = await getValidAccessToken(instance);
362
387
  } catch {
363
388
  return null;
364
389
  }
365
390
  if (!probedAccountId || !probedAccountKey) return null;
391
+ const currentRow = getAccountSet(instance)?.accounts.find(row => row.id === probedAccountId);
392
+ if (!initialRow || !currentRow || initialRow.loginId !== currentRow.loginId || initialRow.addedAt !== currentRow.addedAt
393
+ || epoch !== captureProviderAccountQuotaEpoch(instance) || recovery.anthropicAccountIncarnation(probedAccountId) !== incarnation) return null;
366
394
  let quota: ProviderQuota | null;
367
395
  let anthropicCurrent: (() => boolean) | undefined;
368
396
  try {
369
- const result = await probeAnthropicQuotaWithRecovery(probedAccountId, accessToken,
370
- fresh => { assertAnthropicQuotaSendAllowed(probedAccountId, accessToken); return fetchAnthropicUsageQuota(accessToken, fresh); },
371
- () => mayCommitAccountQuotaKey(probedAccountKey, writerGeneration));
397
+ const result = await recovery.probeAnthropicQuotaWithRecovery(probedAccountId, accessToken,
398
+ fresh => {
399
+ if (!targetCurrent()) throw new AnthropicQuotaProbeOwnershipError("quota target changed");
400
+ recovery.assertAnthropicQuotaSendAllowed(probedAccountId, accessToken);
401
+ return fetchAnthropicUsageQuotaForInstance(instance, accessToken, fresh);
402
+ },
403
+ () => targetCurrent() && mayCommitAccountQuotaKey(probedAccountKey, writerGeneration));
372
404
  if (result && !result.isCurrent()) return null;
373
405
  quota = result?.quota ?? null;
374
406
  anthropicCurrent = result?.isCurrent;
@@ -380,14 +412,14 @@ export async function fetchAnthropicQuota(provider: string): Promise<ProviderQuo
380
412
  // Share the active-account probe with the per-account cache so Providers-page
381
413
  // loads do not double-hit Anthropic's rate-limited usage endpoint.
382
414
  if (probedAccountId && probedAccountKey) {
383
- const stillOwnsToken = getAccountCredential("anthropic", probedAccountId)?.access === accessToken;
415
+ const stillOwnsToken = getAccountCredential(instance, probedAccountId)?.access === accessToken;
384
416
  if (stillOwnsToken && anthropicCurrent?.() && mayCommitAccountQuotaKey(probedAccountKey, writerGeneration)) {
385
417
  accountQuotaCache.set(probedAccountKey, { ts: Date.now(), quota, isCurrent: anthropicCurrent });
386
418
  }
387
419
  }
388
420
  const quotaReport = report(provider, "anthropic:oauth-usage", quota);
389
421
  if (quotaReport && anthropicCurrent) {
390
- accountReportCurrent.set(quotaReport, () => anthropicCurrent() && getAccountSet("anthropic")?.activeAccountId === probedAccountId);
422
+ accountReportCurrent.set(quotaReport, () => anthropicCurrent() && getAccountSet(instance)?.activeAccountId === probedAccountId);
391
423
  }
392
424
  return quotaReport;
393
425
  }
@@ -1,4 +1,6 @@
1
1
  import { listCodexAuthAccountsSnapshot } from "../codex/auth-api";
2
+ import { isAnthropicInstanceId, configuredAnthropicInstance } from "./anthropic-instance";
3
+ import { fetchAnthropicAccountQuota } from "./quota/anthropic-account-quota";
2
4
  import { resolveEnvValue } from "../config";
3
5
  import { getAccountCredential, getAccountSet } from "../oauth/store";
4
6
  import { apiKeyPoolEntryId } from "./api-keys";
@@ -18,6 +20,7 @@ import {
18
20
  accountReportCurrent,
19
21
  AUTHORITATIVE_EMPTY_QUOTA,
20
22
  bumpProviderQuotaInvalidationEpoch,
23
+ captureProviderReportEpochs, providerReportEpochChanged,
21
24
  cacheKeyWithAggregationState,
22
25
  getProviderQuotaReportCache,
23
26
  hasCodexPoolProvider,
@@ -56,7 +59,6 @@ import {
56
59
  } from "./quota/account-cache";
57
60
  import {
58
61
  fetchAnthropicQuota,
59
- fetchAnthropicUsageQuota,
60
62
  fetchChatGptForwardQuota,
61
63
  fetchCursorQuota,
62
64
  fetchKiroQuota,
@@ -68,11 +70,10 @@ import { fetchCommandCodeQuota, fetchKimiQuota, keyQuotaReaderForProvider } from
68
70
  import { antigravityQuotaDiagnosticIdentity, fetchAntigravityQuota, probeAntigravityUsageQuota } from "./quota/antigravity";
69
71
  import { persistKiroAccountState } from "./kiro-account-state-disk";
70
72
  import { kiroProbeCurrent, kiroProbeIdentity } from "./quota/kiro-account-probe";
71
- import { AnthropicQuotaProbeOwnershipError, anthropicCooldownFlightKey, assertAnthropicQuotaSendAllowed, probeAnthropicQuotaWithRecovery } from "./quota/anthropic-cooldown-recovery";
72
73
  export type { ProviderQuota, ProviderQuotaCreditsUsd, ProviderQuotaWindow } from "./quota-types";
73
74
  export { QUOTA_RESPONSE_MAX_BYTES } from "./quota-wire";
74
75
  export {
75
- clearProviderQuotaCache,
76
+ clearProviderQuotaCache, clearProviderQuotaCacheFor,
76
77
  publishKeyReportForTests,
77
78
  readProviderQuotaJsonForTests,
78
79
  setProviderQuotaBeforePublishForTests,
@@ -86,7 +87,7 @@ export {
86
87
  parseAnthropicRateLimitHeaders,
87
88
  providerOAuthAccountQuotaMode,
88
89
  readPassiveProviderAccountQuotas,
89
- recordAnthropicAccountQuotaFromHeaders,
90
+ recordAnthropicAccountQuotaFromHeaders, recordAnthropicAccountQuotaFromHeadersForInstance,
90
91
  recordPassiveAccountQuota,
91
92
  reconcileProviderAccountQuotaRows,
92
93
  resetProviderQuotaReconcileStateForTests,
@@ -96,7 +97,7 @@ export {
96
97
  type ProviderAccountQuota,
97
98
  } from "./quota/account-cache";
98
99
  export { fetchAntigravityUsageQuota, isCanonicalAntigravityQuotaUrl, setAntigravityAccountQuotaTransportForTests } from "./quota/antigravity";
99
- export { parseOllamaCloudQuota, parseZaiQuotaLimits, providerApiKeyQuotaMode } from "./quota/vendor-probes-key";
100
+ export { parseOllamaCloudBalance, parseOllamaCloudQuota, parseZaiQuotaLimits, providerApiKeyQuotaMode } from "./quota/vendor-probes-key";
100
101
  export { parseXaiCreditsResponse } from "./quota/vendor-probes-oauth";
101
102
  export async function fetchProviderApiKeyQuotas(config: OcxConfig, name: string, forceRefresh = false): Promise<ProviderApiKeyQuota[]> {
102
103
  const provider = config.providers[name];
@@ -121,7 +122,7 @@ async function maybeFetchProviderQuota(
121
122
  return fetchChatGptForwardQuota(config, name, provider, forceRefresh, prefetchedCodexSnapshot);
122
123
  }
123
124
  if (provider.authMode === "oauth" && explicitAccountReader(name)) return await fetchExplicitCurrentQuota(name, provider, config);
124
- if (provider.authMode === "oauth" && name === "anthropic") return fetchAnthropicQuota(name);
125
+ if (provider.authMode === "oauth" && configuredAnthropicInstance(config, name)) return fetchAnthropicQuota(name, config);
125
126
  if (provider.authMode === "oauth" && name === "google-antigravity") return await fetchAntigravityQuota(name);
126
127
  if (provider.authMode === "oauth" && name === "kiro") return fetchKiroQuota(name);
127
128
  // meta-muse: a device-logged-in account can be probed at the key endpoint; an
@@ -236,6 +237,7 @@ export async function fetchProviderQuotaReports(config: OcxConfig, forceRefresh
236
237
  const keyCandidate = cacheKeyWithAggregationState(config, prefetchedCodexSnapshot);
237
238
  const key = typeof keyCandidate === "string" ? keyCandidate : await keyCandidate;
238
239
  const writerGeneration = captureConfigGeneration();
240
+ const providerEpochs = captureProviderReportEpochs();
239
241
  const now = Date.now();
240
242
  // The cache fast path must not extend a preserved last-good row past its 30-minute bound:
241
243
  // a row preserved at age 29:59 plus a full 5-minute TTL would otherwise serve until ~35min.
@@ -312,6 +314,8 @@ export async function fetchProviderQuotaReports(config: OcxConfig, forceRefresh
312
314
  byProvider.delete(provider);
313
315
  generationMismatchedProviders.delete(provider);
314
316
  }
317
+ // A provider-scoped clear during this probe retires only that provider's row.
318
+ for (const provider of [...byProvider.keys()]) if (providerReportEpochChanged(providerEpochs, provider)) byProvider.delete(provider);
315
319
 
316
320
  const response = { generatedAt: Date.now(), reports: [...byProvider.values()] };
317
321
  // Commit only when this probe still holds authority (no clear/force superseded it).
@@ -415,27 +419,27 @@ async function fetchAccountQuota(
415
419
  accountId: string,
416
420
  forceRefresh: boolean,
417
421
  providerConfig?: OcxProviderConfig,
422
+ liveConfig?: OcxConfig,
418
423
  ): Promise<AccountQuotaCacheEntry> {
419
424
  if (accountQuotaProbeSkip(provider, accountId)) return accountQuotaProbeSkip(provider, accountId)!;
425
+ if (isAnthropicInstanceId(provider)) return fetchAnthropicAccountQuota(provider, accountId, forceRefresh, liveConfig);
420
426
  if (explicitAccountReader(provider)) return fetchExplicitAccountQuota(provider, accountId, forceRefresh, providerConfig);
421
- if (provider === "anthropic" || provider === "kiro") hydrateAccountQuotaCache();
427
+ if (provider === "kiro") hydrateAccountQuotaCache();
422
428
  const key = accountCacheKey(provider, accountId);
423
429
  const writerGeneration = captureConfigGeneration();
424
430
  const kiroIdentity = provider === "kiro" ? kiroProbeIdentity(accountId) : undefined;
425
431
  const cachedCandidate = accountQuotaCache.get(key);
426
- const cached = (provider !== "kiro" || cachedCandidate?.identity === kiroIdentity)
427
- && (provider !== "anthropic" || cachedCandidate?.isCurrent?.() !== false) ? cachedCandidate : undefined;
432
+ const cached = provider !== "kiro" || cachedCandidate?.identity === kiroIdentity ? cachedCandidate : undefined;
428
433
  if (!forceRefresh && cached && Date.now() - cached.ts < ACCOUNT_QUOTA_TTL_MS) {
429
434
  if (provider === "google-antigravity" && cached.quotaFailure && cached.quotaFailureIsCurrent?.() !== true) return { ...cached, quotaFailure: undefined };
430
- return provider === "anthropic" ? { ...cached, quota: normalizeAnthropicQuota(cached.quota, Date.now()) } : cached;
435
+ return cached;
431
436
  }
432
- const flightKey = provider === "anthropic" ? anthropicCooldownFlightKey(key, accountId) : key;
433
- const flightCurrent = () => provider !== "anthropic" || anthropicCooldownFlightKey(key, accountId) === flightKey;
437
+ const flightKey = key;
434
438
  const joinable = accountQuotaInflight.get(flightKey);
435
439
  if (joinable) {
436
440
  const joined = await joinable;
437
441
  const joinedCurrent = joined.isCurrent?.();
438
- if (joinedCurrent === false || (!flightCurrent() && joinedCurrent !== true)) return { ts: Date.now(), quota: null, unavailable: true };
442
+ if (joinedCurrent === false) return { ts: Date.now(), quota: null, unavailable: true };
439
443
  return provider !== "kiro" || joined.identity === kiroIdentity
440
444
  ? joined : fetchAccountQuota(provider, accountId, true, providerConfig);
441
445
  }
@@ -449,14 +453,10 @@ async function fetchAccountQuota(
449
453
  };
450
454
  const diagnosticFields = () => quotaFailure && quotaFailureIsCurrent() ? { quotaFailure, quotaFailureIsCurrent } : {};
451
455
  const unavailable = (): AccountQuotaCacheEntry => {
452
- if (!flightCurrent()) return { ts: Date.now(), quota: null, unavailable: true };
453
- const previous = accountQuotaCache.get(key);
454
- const retained = provider === "anthropic" && previous?.isCurrent?.() === false ? undefined : previous;
455
456
  const entry: AccountQuotaCacheEntry = {
456
457
  ts: Date.now(),
457
- quota: provider === "anthropic" ? normalizeAnthropicQuota(retained?.quota, Date.now()) : cached?.quota ?? null,
458
+ quota: cached?.quota ?? null,
458
459
  unavailable: true,
459
- ...(provider === "anthropic" && retained?.isCurrent ? { isCurrent: retained.isCurrent } : {}),
460
460
  ...(provider === "kiro" ? { identity: kiroIdentity } : {}),
461
461
  ...diagnosticFields(),
462
462
  };
@@ -470,14 +470,12 @@ async function fetchAccountQuota(
470
470
  try {
471
471
  if (provider === "google-antigravity") diagnosticIdentity = antigravityQuotaDiagnosticIdentity(accountId);
472
472
  let quota: ProviderQuota | null;
473
- let anthropicCurrent: (() => boolean) | undefined;
474
473
  let kiroSnapshot: KiroUsageSnapshot | null = null;
475
474
  if (provider === "kiro") {
476
475
  kiroSnapshot = await fetchKiroUsageSnapshot(await kiroUsageContextForAccount(accountId));
477
476
  quota = kiroSnapshot?.quota ?? null;
478
477
  } else {
479
478
  const token = await getTokenForAccountQuotaProbe(provider, accountId);
480
- if (!flightCurrent()) throw new AnthropicQuotaProbeOwnershipError("anthropic quota flight is stale");
481
479
  if (provider === "google-antigravity") {
482
480
  // Per-account Gem/Cla windows (#1082). The project id is part of the stored
483
481
  // credential; without it the probe cannot be made, and that is "unavailable",
@@ -488,22 +486,14 @@ async function fetchAccountQuota(
488
486
  const result = await probeAntigravityUsageQuota(token, credential.projectId);
489
487
  quota = result.kind === "available" ? result.quota : null;
490
488
  if (result.kind === "unavailable") quotaFailure = result.failure;
491
- } else if (provider === "anthropic") {
492
- const result = await probeAnthropicQuotaWithRecovery(accountId, token,
493
- fresh => { assertAnthropicQuotaSendAllowed(accountId, token); return fetchAnthropicUsageQuota(token, fresh); },
494
- () => mayCommitAccountQuotaKey(key, writerGeneration));
495
- if (result && !result.isCurrent()) throw new AnthropicQuotaProbeOwnershipError("anthropic quota probe lost publication ownership");
496
- quota = result?.quota ?? null;
497
- anthropicCurrent = result?.isCurrent;
498
489
  } else {
499
490
  return { ts: Date.now(), quota: null, unavailable: true };
500
491
  }
501
492
  }
502
493
  if (!quota) return unavailable();
503
494
  const entry: AccountQuotaCacheEntry = {
504
- ts: Date.now(), quota: provider === "anthropic" ? normalizeAnthropicQuota(quota, Date.now()) : quota,
495
+ ts: Date.now(), quota,
505
496
  ...(provider === "kiro" ? { identity: kiroIdentity } : {}),
506
- ...(anthropicCurrent ? { isCurrent: anthropicCurrent } : {}),
507
497
  };
508
498
  if (mayCommitAccountQuotaKey(key, writerGeneration)
509
499
  && (provider !== "kiro" || kiroProbeCurrent(accountId, kiroIdentity))) {
@@ -513,7 +503,6 @@ async function fetchAccountQuota(
513
503
  }
514
504
  return entry;
515
505
  } catch (error) {
516
- if (provider === "anthropic" && error instanceof AnthropicQuotaProbeOwnershipError) return { ts: Date.now(), quota: null, unavailable: true };
517
506
  if (provider === "google-antigravity") quotaFailure = "account_unavailable";
518
507
  return unavailable();
519
508
  }
@@ -531,20 +520,21 @@ export async function fetchProviderAccountQuotas(
531
520
  provider: string,
532
521
  forceRefresh = false,
533
522
  providerConfig?: OcxProviderConfig,
523
+ liveConfig?: OcxConfig,
534
524
  ): Promise<ProviderAccountQuota[]> {
535
525
  if (!supportsPerAccountQuota(provider)) return [];
536
526
  const set = getAccountSet(provider);
537
527
  if (!set) return [];
538
528
  return mapQuotaRoster(set.accounts, async account => {
539
- const entry = await fetchAccountQuota(provider, account.id, forceRefresh, providerConfig);
529
+ const entry = await fetchAccountQuota(provider, account.id, forceRefresh, providerConfig, liveConfig);
540
530
  const result: ProviderAccountQuota = {
541
531
  accountId: account.id,
542
- quota: provider === "anthropic" ? normalizeAnthropicQuota(entry.quota, Date.now()) : entry.quota,
532
+ quota: isAnthropicInstanceId(provider) ? normalizeAnthropicQuota(entry.quota, Date.now()) : entry.quota,
543
533
  ...(entry.unavailable ? { unavailable: true as const } : {}),
544
534
  ...(entry.unavailable && entry.quotaFailure && entry.quotaFailureIsCurrent?.() === true ? { quotaFailure: entry.quotaFailure } : {}),
545
535
  };
546
536
  if (entry.quotaFailureIsCurrent) Object.defineProperty(result, "quotaFailureIsCurrent", { value: entry.quotaFailureIsCurrent });
547
- if (provider === "anthropic" && entry.isCurrent) Object.defineProperty(result, "isCurrent", { value: entry.isCurrent });
537
+ if (isAnthropicInstanceId(provider) && entry.isCurrent) Object.defineProperty(result, "isCurrent", { value: entry.isCurrent });
548
538
  if (!explicitAccountReader(provider)) return result;
549
539
  const identity = entry.identity;
550
540
  Object.defineProperty(result, "isCurrent", { value: () => {
@@ -14,6 +14,7 @@ import { cursorFastCapableBases } from "../../adapters/cursor/catalog";
14
14
  import { COMMAND_CODE_MODEL_REASONING_EFFORTS } from "../command-code-efforts";
15
15
  import { isCanonicalOpenRouterTarget } from "../openrouter-routing";
16
16
  import type { ProviderRegistryEntry } from "./types";
17
+ import type { AnthropicInstanceId } from "../anthropic-instance-id";
17
18
  import { ANTHROPIC_FAST_MODE_BETA } from "../anthropic-fast";
18
19
  import {
19
20
  ANTHROPIC_MODELS,
@@ -109,6 +110,37 @@ const ANTHROPIC_FAST_MODELS: Readonly<Record<string, boolean>> = Object.freeze({
109
110
  const ANTHROPIC_FAST_TIER_DESCRIPTION =
110
111
  "Claude fast mode: faster output at 2x price; needs usage credits (subscription) or fast-mode access (API)";
111
112
 
113
+ /** Each instance owns its entire seed, including nested model lists and Fast wire maps. */
114
+ function anthropicOAuthEntry(id: AnthropicInstanceId, label: string): ProviderRegistryEntry {
115
+ return structuredClone<ProviderRegistryEntry>({
116
+ id,
117
+ label,
118
+ adapter: "anthropic",
119
+ baseUrl: "https://api.anthropic.com",
120
+ authKind: "oauth",
121
+ allowBaseUrlOverride: true,
122
+ featured: true,
123
+ oauthId: id,
124
+ oauthFamily: "anthropic",
125
+ jawcodeBundle: "anthropic",
126
+ note: id === "anthropic" ? "Log in with your Claude account"
127
+ : "Independent Claude account pool — log in with a separate Claude account",
128
+ models: [...ANTHROPIC_MODELS],
129
+ modelContextWindows: { ...ANTHROPIC_MODEL_CONTEXT_WINDOWS },
130
+ modelInputModalities: { ...ANTHROPIC_MODEL_INPUT_MODALITIES },
131
+ modelReasoningEfforts: { ...ANTHROPIC_MODEL_REASONING_EFFORTS },
132
+ // Codex omits max_output_tokens; the fallback 8192 truncates long answers.
133
+ defaultMaxOutputTokens: ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS,
134
+ modelMaxOutputTokens: { ...ANTHROPIC_MODEL_MAX_OUTPUT_TOKENS },
135
+ defaultModel: "claude-sonnet-5",
136
+ // Subscription /fast remains opt-in: it draws usage credits at 2x price.
137
+ fastWire: ANTHROPIC_FAST_WIRE,
138
+ modelSupportsServiceTier: { ...ANTHROPIC_FAST_MODELS },
139
+ fastTierDescription: ANTHROPIC_FAST_TIER_DESCRIPTION,
140
+ fastOptIn: true,
141
+ });
142
+ }
143
+
112
144
  export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
113
145
  {
114
146
  id: "openai",
@@ -209,7 +241,7 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
209
241
  // (it is the current catalog, so its default ordering wins), then the ids
210
242
  // only the old devin entry carried. Degraded-mode seed only either way —
211
243
  // `liveModels` discovers the account's real roster.
212
- models: ["swe-2", "swe-1-7", "gpt-5-6-sol", "gpt-6-astra", "gpt-6-1-sol", "gpt-6-sol", "gpt-6-luna", "claude-opus-5-5", "claude-opus-5", "claude-fable-5-1", "claude-sonnet-5-5", "claude-sonnet-5", "glm-5-3", "kimi-k3", "gemini-3-8-flash", "grok-4-6", "grok-4-7", "swe-1-7-lightning", "gpt-5-6-luna", "gpt-5-6-terra", "claude-opus-4-8", "glm-5-2", "kimi-k2-7", "grok-4-5"],
244
+ models: ["swe-2", "swe-1-7", "gpt-5-6-sol", "gpt-6-astra", "gpt-6-1-sol", "gpt-6-sol", "gpt-6-luna", "claude-opus-5-5", "claude-opus-5", "claude-fable-5-1", "claude-sonnet-5-5", "claude-haiku-5-5", "claude-sonnet-5", "glm-5-3", "kimi-k3", "gemini-3-8-flash", "grok-4-6", "grok-4-7", "swe-1-7-lightning", "gpt-5-6-luna", "gpt-5-6-terra", "claude-opus-4-8", "glm-5-2", "kimi-k2-7", "grok-4-5"],
213
245
  liveModels: true,
214
246
  defaultModel: "swe-2",
215
247
  modelContextWindows: DEVIN_MODEL_CONTEXT_WINDOWS,
@@ -490,35 +522,8 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
490
522
  modelReasoningEfforts: ORCAROUTER_MODEL_REASONING_EFFORTS,
491
523
  note: "Connect your OrcaRouter account with OAuth 2.0 + PKCE; the issued API key is stored in OpenCodex's existing credential store.",
492
524
  },
493
- {
494
- id: "anthropic",
495
- label: "Anthropic Claude",
496
- adapter: "anthropic",
497
- baseUrl: "https://api.anthropic.com",
498
- authKind: "oauth",
499
- allowBaseUrlOverride: true,
500
- featured: true,
501
- oauthId: "anthropic",
502
- jawcodeBundle: "anthropic",
503
- note: "Log in with your Claude account",
504
- models: [...ANTHROPIC_MODELS],
505
- modelContextWindows: { ...ANTHROPIC_MODEL_CONTEXT_WINDOWS },
506
- modelInputModalities: { ...ANTHROPIC_MODEL_INPUT_MODALITIES },
507
- modelReasoningEfforts: { ...ANTHROPIC_MODEL_REASONING_EFFORTS },
508
- // Codex omits max_output_tokens; without a provider budget the Anthropic adapter
509
- // falls back to 8192, which truncates long answers with stop_reason=max_tokens.
510
- defaultMaxOutputTokens: ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS,
511
- modelMaxOutputTokens: { ...ANTHROPIC_MODEL_MAX_OUTPUT_TOKENS },
512
- defaultModel: "claude-sonnet-5",
513
- // Claude fast mode on the subscription lane (Claude Code `/fast`): the OAuth route accepts
514
- // `speed` and gates it on account entitlement (usage credits / org enablement), probed live
515
- // 2026-09-23 (devlog/_plan/260923_anthropic_fast_speed/020_probe-evidence.md).
516
- // Off until the operator opts in: fast mode draws usage credits at 2x price.
517
- fastWire: ANTHROPIC_FAST_WIRE,
518
- modelSupportsServiceTier: { ...ANTHROPIC_FAST_MODELS },
519
- fastTierDescription: ANTHROPIC_FAST_TIER_DESCRIPTION,
520
- fastOptIn: true,
521
- },
525
+ anthropicOAuthEntry("anthropic", "Anthropic Claude"),
526
+ anthropicOAuthEntry("anthropic2", "Anthropic · Pool 2"),
522
527
  {
523
528
  id: "anthropic-apikey",
524
529
  label: "Anthropic (API key)",
@@ -129,6 +129,7 @@ export const REGISTRY_FIELD_MODEL_ID_ROLES = {
129
129
  thinkingBudgetModels: NONE,
130
130
  escapeBuiltinToolNames: NONE,
131
131
  oauthId: NONE,
132
+ oauthFamily: NONE,
132
133
  virtualModels: RECORD_KEYS,
133
134
  modelMaxInputTokens: RECORD_KEYS,
134
135
  jawcodeBundle: NONE,
@@ -12,8 +12,10 @@ import type { ProviderModelDiscoverySpec } from "./types";
12
12
  // 260929 Claude Sonnet 5.5 (`claude-sonnet-5-5`, released 2026-09-28): 1M context / 128K output /
13
13
  // adaptive thinking / effort low..max with a high default, same price as Sonnet 5, per the Sonnet 5.5
14
14
  // overview, migration guide, effort and pricing pages (platform.claude.com).
15
- export const ANTHROPIC_MODELS = ["claude-fable-5-1", "claude-fable-5", "claude-sonnet-5-5", "claude-sonnet-5", "claude-opus-5-5", "claude-opus-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5"];
16
- export const ANTHROPIC_MODEL_CONTEXT_WINDOWS: Record<string, number> = { "claude-fable-5-1": 1_000_000, "claude-sonnet-5-5": 1_000_000, "claude-sonnet-5": 1_000_000, "claude-fable-5": 1_000_000, "claude-opus-5-5": 1_000_000, "claude-opus-5": 1_000_000, "claude-opus-4-8": 1_000_000, "claude-opus-4-7": 1_000_000, "claude-opus-4-6": 1_000_000, "claude-sonnet-4-6": 1_000_000, "claude-haiku-4-5": 200_000 };
15
+ // 261008 Claude Haiku 5.5: 1M / 128K, adaptive thinking, effort low..max (default medium),
16
+ // per platform.claude.com Haiku 5.5 overview, migration guide and pricing page.
17
+ export const ANTHROPIC_MODELS = ["claude-fable-5-1", "claude-fable-5", "claude-sonnet-5-5", "claude-sonnet-5", "claude-opus-5-5", "claude-opus-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-5-5", "claude-haiku-4-5"];
18
+ export const ANTHROPIC_MODEL_CONTEXT_WINDOWS: Record<string, number> = { "claude-fable-5-1": 1_000_000, "claude-sonnet-5-5": 1_000_000, "claude-sonnet-5": 1_000_000, "claude-fable-5": 1_000_000, "claude-opus-5-5": 1_000_000, "claude-opus-5": 1_000_000, "claude-opus-4-8": 1_000_000, "claude-opus-4-7": 1_000_000, "claude-opus-4-6": 1_000_000, "claude-sonnet-4-6": 1_000_000, "claude-haiku-5-5": 1_000_000, "claude-haiku-4-5": 200_000 };
17
19
  // All seeded Claude models support vision: https://platform.claude.com/docs/en/models/overview
18
20
  export const ANTHROPIC_MODEL_INPUT_MODALITIES: Record<string, string[]> = Object.fromEntries(
19
21
  ANTHROPIC_MODELS.map(id => [id, ["text", "image"]]),
@@ -40,6 +42,7 @@ export const ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS = 64_000;
40
42
  * headroom arithmetic wrong.
41
43
  */
42
44
  export const ANTHROPIC_MODEL_MAX_OUTPUT_TOKENS: Record<string, number> = {
45
+ "claude-haiku-5-5": 128_000,
43
46
  "claude-fable-5-1": 128_000,
44
47
  "claude-fable-5": 128_000,
45
48
  "claude-sonnet-5-5": 128_000,
@@ -70,8 +73,8 @@ export const ANTHROPIC_MODEL_MAX_OUTPUT_TOKENS: Record<string, number> = {
70
73
  * do what it says:
71
74
  * - `minimal`: `adaptiveEffort` rewrites it to `low` (the adaptive wire 400s on it), so
72
75
  * it is not a distinct setting.
73
- * - `none`: only sonnet >= 5 accepts an explicit thinking disable
74
- * (`EXPLICIT_THINKING_DISABLE_FAMILY_MINIMUMS`); Fable rejects one outright.
76
+ * - `none`: thinking-off support differs by family (`anthropic-model-contract.ts`);
77
+ * Haiku 5.5 accepts it without effort, while Fable rejects one outright.
75
78
  * - `ultra`: not an Anthropic concept, and it is degraded to `max` at the request
76
79
  * boundary anyway (src/responses/parser.ts).
77
80
  */
@@ -361,6 +361,8 @@ export interface ProviderRegistryEntry {
361
361
  thinkingBudgetModels?: string[];
362
362
  escapeBuiltinToolNames?: boolean;
363
363
  oauthId?: string;
364
+ /** OAuth implementation family; instance identity remains the exact id/oauthId. */
365
+ oauthFamily?: "anthropic";
364
366
  virtualModels?: Record<string, { wireModelId: string; reasoningMode: "pro" }>;
365
367
  modelMaxInputTokens?: Record<string, number>;
366
368
  jawcodeBundle?: string;
@@ -373,6 +375,7 @@ export interface ProviderRegistryEntry {
373
375
 
374
376
  export type ProviderConfigSeed = Pick<
375
377
  OcxProviderConfig,
378
+ "anthropicOAuthInstance" |
376
379
  "adapter" | "baseUrl" | "apiKeyTransport" | "responsesPath" | "chatCompletionsPath" | "authMode" | "keyOptional" | "freeTier" | "modelSuffixBracketStrip" | "defaultModel" | "models"
377
380
  | "liveModels" | "contextWindow" | "modelContextWindows" | "modelInputModalities"
378
381
  | "modelDisplayNames"
@@ -7,7 +7,7 @@ import type {
7
7
  } from "./registry/types";
8
8
  import { PROVIDER_REGISTRY_CORE } from "./registry/entries-core";
9
9
  import { PROVIDER_REGISTRY_EXTENDED } from "./registry/entries-extended";
10
-
10
+ import { anthropicInstanceRowShapeMatches } from "./anthropic-instance-id";
11
11
  export type {
12
12
  ProviderAuthKind,
13
13
  MetadataModelIdNormalize,
@@ -102,10 +102,11 @@ export function normalizedProviderEndpoint(value: string): string {
102
102
  */
103
103
  export function providerMatchesRegistryTransport(
104
104
  id: string,
105
- provider: Pick<OcxProviderConfig, "baseUrl" | "adapter"> & Partial<Pick<OcxProviderConfig, "authMode">>,
105
+ provider: Pick<OcxProviderConfig, "baseUrl" | "adapter"> & Partial<Pick<OcxProviderConfig, "authMode" | "anthropicOAuthInstance">>,
106
106
  ): boolean {
107
107
  const entry = getProviderRegistryEntry(id);
108
108
  if (!entry) return false;
109
+ if (id === "anthropic2") return entry.authKind === "oauth" && entry.oauthId === id && entry.oauthFamily === "anthropic" && anthropicInstanceRowShapeMatches(id, provider);
109
110
  if (entry.authKind !== "key" || entry.preserveCustomDestination !== true) return true;
110
111
  // The opt-in is intentionally limited to fixed key destinations. Fail closed if a future
111
112
  // registry edit combines it with an override/template despite the registry parity tests.
@@ -115,7 +116,6 @@ export function providerMatchesRegistryTransport(
115
116
  if (provider.authMode !== undefined && provider.authMode !== "key") return false;
116
117
  return normalizedProviderEndpoint(provider.baseUrl) === normalizedProviderEndpoint(entry.baseUrl);
117
118
  }
118
-
119
119
  /**
120
120
  * Resolve the registry entry a configured provider actually points at, by TRANSPORT
121
121
  * rather than by name.
@@ -36,6 +36,32 @@ export interface ReasoningEnvelope {
36
36
  * same way a signature does.
37
37
  */
38
38
  krc?: string;
39
+ /**
40
+ * A Responses provider's own `encrypted_content` for one reasoning item, minted while serving
41
+ * `model` (the client-facing model the Claude route was asked for). The Claude route has no
42
+ * other channel to hand a provider its reasoning back: the body is `store: false` and Claude
43
+ * Code replays only the thinking block. `src/claude/inbound.ts` restores it only for a request
44
+ * naming the same model, so a model switch cannot carry it to a provider that cannot decrypt it.
45
+ */
46
+ nat?: NativeReasoning;
47
+ }
48
+
49
+ export interface NativeReasoning {
50
+ /** The provider's opaque `encrypted_content`, verbatim. */
51
+ enc: string;
52
+ /** Client-facing model the item was produced for. */
53
+ model: string;
54
+ /** Route-bound HMAC for this exact ciphertext. */
55
+ tag: string;
56
+ /** The provider's reasoning item id, when it sent one. */
57
+ id?: string;
58
+ }
59
+
60
+ function decodeNativeReasoning(value: unknown): NativeReasoning | undefined {
61
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
62
+ const { enc, model, id, tag } = value as { enc?: unknown; model?: unknown; id?: unknown; tag?: unknown };
63
+ if (typeof enc !== "string" || enc.length === 0 || typeof model !== "string" || model.length === 0 || typeof tag !== "string" || tag.length === 0) return undefined;
64
+ return typeof id === "string" && id.length > 0 ? { enc, model, tag, id } : { enc, model, tag };
39
65
  }
40
66
 
41
67
  export function encodeReasoningEnvelope(envelope: ReasoningEnvelope, budget?: TranslatorBudget): string {
@@ -116,7 +142,9 @@ export function decodeReasoningEnvelope(encryptedContent: string, budget?: Trans
116
142
  if (hasTxt) envelope.txt = txt;
117
143
  const krc = (parsed as { krc?: unknown }).krc;
118
144
  if (typeof krc === "string" && krc.length > 0) envelope.krc = krc;
119
- return envelope.sig || envelope.red || hasTxt || envelope.krc ? envelope : null;
145
+ const nat = decodeNativeReasoning((parsed as { nat?: unknown }).nat);
146
+ if (nat) envelope.nat = nat;
147
+ return envelope.sig || envelope.red || hasTxt || envelope.krc || envelope.nat ? envelope : null;
120
148
  } catch {
121
149
  return null;
122
150
  } finally {
@@ -19,7 +19,8 @@
19
19
  * long-lived proxy cannot grow without limit.
20
20
  */
21
21
 
22
- import { createHash, createHmac, randomBytes } from "node:crypto";
22
+ import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
23
+ import { thoughtSignatureReplaySalt } from "./thought-signature-replay";
23
24
  import type {
24
25
  OcxProviderConfig,
25
26
  OcxReasoningReplayIdentity,
@@ -27,6 +28,21 @@ import type {
27
28
  } from "../types";
28
29
 
29
30
  const MAX_ENTRIES = 64;
31
+ export interface NativeReasoningOwner { destination: string; credential: string }
32
+
33
+ /** Bind exact ciphertext to the durable route that accepted it. */
34
+ export function nativeReasoningTag(owner: NativeReasoningOwner | undefined, blob: string): string | undefined {
35
+ const salt = thoughtSignatureReplaySalt();
36
+ if (!owner || !salt || salt.length < 16 || blob.length === 0) return undefined;
37
+ return createHmac("sha256", salt).update("native-reasoning\0")
38
+ .update(JSON.stringify([owner.destination, owner.credential, createHash("sha256").update(blob).digest("hex")]))
39
+ .digest("hex");
40
+ }
41
+
42
+ export function nativeReasoningTagMatches(expected: string | undefined, presented: string | undefined): boolean {
43
+ if (!expected || !presented || !/^[a-f0-9]{64}$/.test(expected) || !/^[a-f0-9]{64}$/.test(presented)) return false;
44
+ return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(presented, "hex"));
45
+ }
30
46
  const MAX_TOTAL_BYTES = 256 * 1024;
31
47
  const TTL_MS = 60 * 60 * 1000;
32
48
  const OPAQUE_BLOB_REJECTION_TTL_MS = 5 * 60 * 1000;