@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
@@ -0,0 +1,38 @@
1
+ import type { RawEntry } from "./parsing";
2
+
3
+ /** Match the opt-in tier spelling without coercing malformed catalog values to strings. */
4
+ function isUltraFast(value: unknown): boolean {
5
+ return typeof value === "string" && value.trim().toLowerCase() === "ultrafast";
6
+ }
7
+
8
+ /** Recognize an explicitly supplied Ultra Fast descriptor by its string-valued id. */
9
+ function isUltraFastDescriptor(value: unknown): boolean {
10
+ return value !== null && typeof value === "object" && "id" in value && isUltraFast(value.id);
11
+ }
12
+
13
+ /** Restore only exact-row operator metadata, after fresh provider capabilities are applied. */
14
+ export function preserveOperatorUltraFastTiers(entry: RawEntry, previous: RawEntry | undefined, enabled: boolean): void {
15
+ // A native template or stale row is not authority for this opt-in. Keep all other fresh
16
+ // tier policy, including Fast, while replacing only Ultra Fast from the exact persisted row.
17
+ for (const [field, matches] of [
18
+ ["service_tiers", isUltraFastDescriptor],
19
+ ["additional_speed_tiers", isUltraFast],
20
+ ] as const) {
21
+ const current = entry[field];
22
+ const fresh = Array.isArray(current) ? current.filter(value => !matches(value)) : [];
23
+ const old = previous?.[field];
24
+ const supplied = enabled && Array.isArray(old) ? old.find(matches) : undefined;
25
+ if (supplied !== undefined) {
26
+ entry[field] = [...fresh, structuredClone(supplied)];
27
+ } else if (Array.isArray(current) && fresh.length !== current.length) {
28
+ if (fresh.length > 0) entry[field] = fresh;
29
+ else delete entry[field];
30
+ }
31
+ }
32
+ const hasDeclaration = (Array.isArray(entry.service_tiers) && entry.service_tiers.some(isUltraFastDescriptor))
33
+ || (Array.isArray(entry.additional_speed_tiers) && entry.additional_speed_tiers.some(isUltraFast));
34
+ for (const field of ["service_tier", "default_service_tier"] as const) {
35
+ if (enabled && hasDeclaration && previous && isUltraFast(previous[field])) entry[field] = previous[field];
36
+ else if (isUltraFast(entry[field])) delete entry[field];
37
+ }
38
+ }
@@ -1,3 +1,4 @@
1
+ import { isCodexControlPlaneModel } from "../control-plane-models";
1
2
  import { execFileSync } from "node:child_process";
2
3
  import { createHash } from "node:crypto";
3
4
  import { copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync } from "node:fs";
@@ -700,7 +701,7 @@ export function catalogEntryIsNativeChatGpt(entry: RawEntry): boolean {
700
701
  && entry.use_responses_lite === true
701
702
  && hasNativeOpenAiCapabilityMetadata(routedNativeSlug)
702
703
  ) return true;
703
- if (UPSTREAM_NATIVE_ENTRIES.has(slug) || SUPPORTED_NATIVE_OPENAI_SLUGS.has(slug)) return true;
704
+ if (isCodexControlPlaneModel(slug) || UPSTREAM_NATIVE_ENTRIES.has(slug) || SUPPORTED_NATIVE_OPENAI_SLUGS.has(slug)) return true;
704
705
  return false;
705
706
  }
706
707
 
@@ -1,3 +1,4 @@
1
+ import { isAnthropicOAuthInstance } from "../../providers/anthropic-instance";
1
2
  import { effectiveProviderAlias, effectiveProviderAliasDecision } from "../../providers/default-aliases";
2
3
  import { initialModelSelectionPending } from "../../providers/initial-model-selection";
3
4
  import { execFileSync } from "node:child_process";
@@ -28,17 +29,19 @@ import {
28
29
  } from "../model-cache";
29
30
  import {
30
31
  buildModelsRequest,
31
- getValidAccessTokenSnapshot,
32
+ getModelsOAuthAccessSnapshot,
32
33
  observeActiveOAuthAccessToken,
33
34
  resolveModelsAuthToken,
34
35
  type OAuthActiveTokenObservation,
35
36
  } from "../../oauth";
37
+ import { mayResolveModelsOAuth } from "../../oauth/model-discovery-auth";
36
38
  import type { OcxConfig, OcxProviderConfig } from "../../types";
37
39
  import { modelInList } from "../../types";
38
40
  import { CODEX_REASONING_LEVELS, codexEffortRank, configuredReasoningEfforts, modelRecordValue, sanitizeCodexReasoningEfforts } from "../../reasoning-effort";
39
41
  import { isModelVisionSidecarConsumer } from "../../vision/eligibility";
40
42
  import { getModelMetadata, getModelMetadataCaseInsensitive, listModelMetadata, resolveMetadataProvider, type ModelMetadata } from "../../generated/model-metadata";
41
43
  import { enrichProviderFromRegistry, shouldCaseFoldMetadataModelId } from "../../providers/derive";
44
+ import { isAzureModelMetadataDestination, refreshAzureModelMetadata } from "../../providers/azure-model-metadata";
42
45
  import {
43
46
  captureFastPolicyAuthority,
44
47
  fastPolicyForModel,
@@ -145,7 +148,8 @@ export function observedModelsAuthResolver(
145
148
  return { apiKey: resolveProviderApiKey(provider.apiKey), observed: true };
146
149
  }
147
150
 
148
- const observation = observeActiveOAuthAccessToken(name, authStoreBuffer);
151
+ const observation: OAuthActiveTokenObservation = mayResolveModelsOAuth(name, provider)
152
+ ? observeActiveOAuthAccessToken(name, authStoreBuffer) : { kind: "missing" };
149
153
  outcomes.push({ provider: name, state: observation.kind });
150
154
  if (observation.kind !== "available") return { apiKey: undefined, observed: true };
151
155
  return {
@@ -166,7 +170,8 @@ export async function fetchProviderModelsWithAuth(
166
170
  contextCap: number | undefined,
167
171
  resolveAuth: ModelsAuthResolver,
168
172
  ): Promise<ProviderModelsResult> {
169
- const { name, provider: prov, discovery, metadataModelIdCaseFold } = captured;
173
+ const { name, provider: prov, discovery, metadataModelIdCaseFold, metadataConfigDir } = captured;
174
+ const azureMetadata = isAzureModelMetadataDestination(prov.baseUrl);
170
175
  const observed = (
171
176
  models: CatalogModel[],
172
177
  state: CatalogGatherProviderModelOutcome["state"],
@@ -179,9 +184,10 @@ export async function fetchProviderModelsWithAuth(
179
184
  // generation, so a request started with the former account cannot later publish its result.
180
185
  const cacheGeneration = captureModelCacheGeneration(name);
181
186
  const isCurrentCacheGeneration = () => isModelCacheGenerationCurrent(name, cacheGeneration);
182
- const anthropicSelection = name === "anthropic" && prov.authMode === "oauth"
187
+ const anthropicSelection = isAnthropicOAuthInstance(name) && prov.authMode === "oauth" && mayResolveModelsOAuth(name, prov)
183
188
  ? captureOAuthAccountSelection(name) : null;
184
189
  if (prov.authMode === "forward") return observed([], "authoritative"); // ChatGPT backend has no /models
190
+ if (azureMetadata) await refreshAzureModelMetadata(prov.baseUrl, metadataConfigDir);
185
191
  const seedVertexDefault = prov.adapter === "google"
186
192
  && prov.googleMode === "vertex"
187
193
  && (prov.models?.length ?? 0) === 0
@@ -200,7 +206,7 @@ export async function fetchProviderModelsWithAuth(
200
206
  const configured: CatalogModel[] = configuredIds.map(id => ({
201
207
  id,
202
208
  provider: name,
203
- ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
209
+ ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
204
210
  }));
205
211
  const withConfiguredRetention = (
206
212
  models: CatalogModel[],
@@ -216,6 +222,7 @@ export async function fetchProviderModelsWithAuth(
216
222
  seedVertexDefault,
217
223
  retainComboTargets: options?.retainComboTargets,
218
224
  metadataModelIdCaseFold,
225
+ metadataConfigDir,
219
226
  });
220
227
  if (
221
228
  options?.warnDrops === true
@@ -248,7 +255,7 @@ export async function fetchProviderModelsWithAuth(
248
255
  }
249
256
  return observed(ids.map(id => {
250
257
  const hints = catalogHintsFromProviderConfig(name, prov, id, contextCap,
251
- metadataModelIdCaseFold, captured.effectiveAlias);
258
+ metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir);
252
259
  const observedWindow = kiroObservedContextWindow(id);
253
260
  return { id, provider: name, ...hints,
254
261
  ...(observedWindow !== undefined
@@ -257,9 +264,11 @@ export async function fetchProviderModelsWithAuth(
257
264
  }
258
265
  return observed(configured, "authoritative");
259
266
  }
260
- const auth: ModelsAuthResolution = captured.observedAuth ?? (resolveAuth.kind === "refreshing"
267
+ const auth: ModelsAuthResolution = prov.authMode === "oauth" && !mayResolveModelsOAuth(name, prov)
268
+ ? { apiKey: undefined, observed: resolveAuth.kind === "observed" }
269
+ : captured.observedAuth ?? (resolveAuth.kind === "refreshing"
261
270
  ? prov.authMode === "oauth"
262
- ? await getValidAccessTokenSnapshot(name)
271
+ ? await getModelsOAuthAccessSnapshot(name, prov)
263
272
  .then(snapshot => ({
264
273
  apiKey: snapshot.accessToken,
265
274
  observed: false,
@@ -274,7 +283,8 @@ export async function fetchProviderModelsWithAuth(
274
283
  : resolveAuth.resolve(name, prov));
275
284
  const apiKey = auth.apiKey;
276
285
  const maySendAnthropicDiscovery = () => {
277
- if (name !== "anthropic" || prov.authMode !== "oauth") return true;
286
+ if (!isAnthropicOAuthInstance(name) || prov.authMode !== "oauth") return true;
287
+ if (!mayResolveModelsOAuth(name, prov)) return false;
278
288
  const selected = captureOAuthAccountSelection(name);
279
289
  const row = auth.oauthAccountId ? getAccountCredentialWithStatus(name, auth.oauthAccountId) : null;
280
290
  return !!anthropicSelection && !!selected && !!row && !row.paused && !row.needsReauth
@@ -290,7 +300,7 @@ export async function fetchProviderModelsWithAuth(
290
300
  : [{
291
301
  id: prov.defaultModel,
292
302
  provider: name,
293
- ...catalogHintsFromProviderConfig(name, prov, prov.defaultModel, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
303
+ ...catalogHintsFromProviderConfig(name, prov, prov.defaultModel, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
294
304
  }];
295
305
  const vertexDefaultSeed = seedVertexDefault ? configured[0] : undefined;
296
306
  const withVertexDefaultSeed = (models: CatalogModel[]): CatalogModel[] => (
@@ -312,13 +322,13 @@ export async function fetchProviderModelsWithAuth(
312
322
  const fresh = getFreshCached(name, ttlMs, Date.now(), authorityIdentity);
313
323
  if (fresh) {
314
324
  return observed(withConfiguredRetention(
315
- applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
325
+ applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
316
326
  ), "authoritative");
317
327
  }
318
328
  const scopedStale = getStaleCached(name, authorityIdentity);
319
329
  if (isModelsFetchCoolingDown(name, undefined, undefined, authorityIdentity) && scopedStale) {
320
330
  return observed(withConfiguredRetention(
321
- applyConfigHintsToCachedModels(name, prov, scopedStale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
331
+ applyConfigHintsToCachedModels(name, prov, scopedStale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
322
332
  ), "degraded");
323
333
  }
324
334
  const live = await fetchCodeBuddyModels(profile, apiKey);
@@ -326,7 +336,7 @@ export async function fetchProviderModelsWithAuth(
326
336
  const discovered = live.models.map(id => ({
327
337
  id,
328
338
  provider: name,
329
- ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
339
+ ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
330
340
  }));
331
341
  const forCache = withConfiguredRetention(discovered, { retainComboTargets: false });
332
342
  if (!setCached(name, forCache, Date.now(), cacheGeneration, authorityIdentity)) {
@@ -342,7 +352,7 @@ export async function fetchProviderModelsWithAuth(
342
352
  }
343
353
  const stale = getStaleCached(name, authorityIdentity);
344
354
  return observed(withConfiguredRetention(
345
- stale ? applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias) : configured,
355
+ stale ? applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir) : configured,
346
356
  ), "degraded");
347
357
  }
348
358
  if (name === "zed" && prov.adapter === "zed") {
@@ -355,14 +365,14 @@ export async function fetchProviderModelsWithAuth(
355
365
  const cached = getFreshCached(name, ttlMs, Date.now(), authorityIdentity);
356
366
  if (cached) {
357
367
  return observed(
358
- withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cached, contextCap, metadataModelIdCaseFold, captured.effectiveAlias)),
368
+ withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cached, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir)),
359
369
  "authoritative",
360
370
  );
361
371
  }
362
372
  const stale = getStaleCached(name, authorityIdentity);
363
373
  if (isModelsFetchCoolingDown(name, undefined, undefined, authorityIdentity) && stale) {
364
374
  return observed(
365
- withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias)),
375
+ withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir)),
366
376
  "degraded",
367
377
  );
368
378
  }
@@ -382,7 +392,7 @@ export async function fetchProviderModelsWithAuth(
382
392
  ...(model.maxOutputTokens ? { maxOutputTokens: model.maxOutputTokens } : {}),
383
393
  ...(model.supportsImages ? { inputModalities: ["text", "image"] } : {}),
384
394
  ...(reasoningEfforts?.length ? { reasoningEfforts } : {}),
385
- ...catalogHintsFromProviderConfig(name, prov, model.id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
395
+ ...catalogHintsFromProviderConfig(name, prov, model.id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
386
396
  } as CatalogModel;
387
397
  });
388
398
  const forCache = withConfiguredRetention(discovered, { retainComboTargets: false });
@@ -397,7 +407,7 @@ export async function fetchProviderModelsWithAuth(
397
407
  markProviderDiscoveryFailed(name, { reason: "provider" });
398
408
  }
399
409
  return observed(
400
- withConfiguredRetention(stale ? applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias) : configured),
410
+ withConfiguredRetention(stale ? applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir) : configured),
401
411
  "degraded",
402
412
  );
403
413
  }
@@ -413,13 +423,13 @@ export async function fetchProviderModelsWithAuth(
413
423
  const fresh = getFreshCached(name, ttlMs, Date.now(), authorityIdentity);
414
424
  if (fresh) {
415
425
  return observed(withConfiguredRetention(
416
- applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
426
+ applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
417
427
  ), "authoritative");
418
428
  }
419
429
  const scopedStale = getStaleCached(name, authorityIdentity);
420
430
  if (isModelsFetchCoolingDown(name) && scopedStale) {
421
431
  return observed(withConfiguredRetention(
422
- applyConfigHintsToCachedModels(name, prov, scopedStale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
432
+ applyConfigHintsToCachedModels(name, prov, scopedStale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
423
433
  ), "degraded");
424
434
  }
425
435
  const live = await fetchQoderModels(profile, apiKey);
@@ -427,7 +437,7 @@ export async function fetchProviderModelsWithAuth(
427
437
  const discovered = live.models.map(id => ({
428
438
  id,
429
439
  provider: name,
430
- ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
440
+ ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
431
441
  }));
432
442
  const forCache = withConfiguredRetention(discovered, { retainComboTargets: false });
433
443
  if (!setCached(name, forCache, Date.now(), cacheGeneration, authorityIdentity)) {
@@ -443,7 +453,7 @@ export async function fetchProviderModelsWithAuth(
443
453
  }
444
454
  const stale = getStaleCached(name, authorityIdentity);
445
455
  return observed(withConfiguredRetention(
446
- stale ? applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias) : configured,
456
+ stale ? applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir) : configured,
447
457
  ), "degraded");
448
458
  }
449
459
  if (prov.adapter === "devin") {
@@ -458,7 +468,7 @@ export async function fetchProviderModelsWithAuth(
458
468
  const cachedDevin = getFreshCached(name, ttlMs, Date.now(), authorityIdentity);
459
469
  if (cachedDevin) {
460
470
  return observed(
461
- withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cachedDevin)),
471
+ withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cachedDevin, undefined, undefined, undefined, metadataConfigDir)),
462
472
  "authoritative",
463
473
  );
464
474
  }
@@ -466,7 +476,7 @@ export async function fetchProviderModelsWithAuth(
466
476
  const cooling = getStaleCached(name, authorityIdentity);
467
477
  return observed(
468
478
  withConfiguredRetention(
469
- cooling ? applyConfigHintsToCachedModels(name, prov, cooling) : configured,
479
+ cooling ? applyConfigHintsToCachedModels(name, prov, cooling, undefined, undefined, undefined, metadataConfigDir) : configured,
470
480
  ),
471
481
  "degraded",
472
482
  );
@@ -508,7 +518,7 @@ export async function fetchProviderModelsWithAuth(
508
518
  // record and the vision-sidecar rewrite keep winning — the live
509
519
  // value survives only when none of them applies.
510
520
  ...(liveResult.inputModalities[id]?.length ? { inputModalities: liveResult.inputModalities[id] } : {}),
511
- ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
521
+ ...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir),
512
522
  } as CatalogModel;
513
523
  });
514
524
  const forCache = withConfiguredRetention(result, { retainComboTargets: false });
@@ -524,7 +534,7 @@ export async function fetchProviderModelsWithAuth(
524
534
  }
525
535
  const stale = getStaleCached(name, authorityIdentity);
526
536
  return observed(
527
- withConfiguredRetention(stale ? applyConfigHintsToCachedModels(name, prov, stale) : configured),
537
+ withConfiguredRetention(stale ? applyConfigHintsToCachedModels(name, prov, stale, undefined, undefined, undefined, metadataConfigDir) : configured),
528
538
  "degraded",
529
539
  );
530
540
  }
@@ -541,7 +551,7 @@ export async function fetchProviderModelsWithAuth(
541
551
  const cachedCursor = getFreshCached(name, ttlMs, Date.now(), authorityIdentity);
542
552
  if (cachedCursor) {
543
553
  return observed(
544
- withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cachedCursor, undefined, metadataModelIdCaseFold, captured.effectiveAlias)),
554
+ withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cachedCursor, undefined, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir)),
545
555
  "authoritative",
546
556
  );
547
557
  }
@@ -549,7 +559,7 @@ export async function fetchProviderModelsWithAuth(
549
559
  const cooling = getStaleCached(name, authorityIdentity);
550
560
  return observed(
551
561
  withConfiguredRetention(
552
- cooling ? applyConfigHintsToCachedModels(name, prov, cooling, undefined, metadataModelIdCaseFold, captured.effectiveAlias) : configured,
562
+ cooling ? applyConfigHintsToCachedModels(name, prov, cooling, undefined, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir) : configured,
553
563
  ),
554
564
  "degraded",
555
565
  );
@@ -595,7 +605,7 @@ export async function fetchProviderModelsWithAuth(
595
605
  const staleCursor = getStaleCached(name, authorityIdentity);
596
606
  return observed(
597
607
  withConfiguredRetention(
598
- staleCursor ? applyConfigHintsToCachedModels(name, prov, staleCursor, undefined, metadataModelIdCaseFold, captured.effectiveAlias) : configured,
608
+ staleCursor ? applyConfigHintsToCachedModels(name, prov, staleCursor, undefined, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir) : configured,
599
609
  ),
600
610
  "degraded",
601
611
  );
@@ -613,7 +623,7 @@ export async function fetchProviderModelsWithAuth(
613
623
  if (fresh) {
614
624
  return observed(
615
625
  withConfiguredRetention(
616
- withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold, captured.effectiveAlias)),
626
+ withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir)),
617
627
  ),
618
628
  "authoritative",
619
629
  ); // dedups Codex's frequent /v1/models polling within the TTL
@@ -625,7 +635,7 @@ export async function fetchProviderModelsWithAuth(
625
635
  return observed(
626
636
  withConfiguredRetention(
627
637
  stale
628
- ? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias))
638
+ ? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir))
629
639
  : failedDiscoveryConfigured,
630
640
  ),
631
641
  "degraded",
@@ -633,7 +643,7 @@ export async function fetchProviderModelsWithAuth(
633
643
  }
634
644
  // The captured request predates any refresh, so a refreshing gather rebuilds it
635
645
  // from the auth it resolved: the token and its origin, together.
636
- const request = resolveAuth.kind === "refreshing"
646
+ const request = resolveAuth.kind === "refreshing" && name !== "anthropic2"
637
647
  ? captureModelsRequest(name, prov, auth.oauthApiBaseUrl)
638
648
  : captured.request;
639
649
  const url = request.url;
@@ -653,7 +663,7 @@ export async function fetchProviderModelsWithAuth(
653
663
  const failedDiscoveryFallback = (
654
664
  failure: ProviderModelDiscoveryFailure,
655
665
  ): { models: CatalogModel[]; fallback: "stale" | "configured"; shouldLog: boolean } => {
656
- if (!isCurrentCacheGeneration()) {
666
+ if (!isCurrentCacheGeneration() || !maySendAnthropicDiscovery()) {
657
667
  return {
658
668
  models: withConfiguredRetention(failedDiscoveryConfigured),
659
669
  fallback: "configured",
@@ -670,7 +680,7 @@ export async function fetchProviderModelsWithAuth(
670
680
  return {
671
681
  models: withConfiguredRetention(
672
682
  stale
673
- ? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias))
683
+ ? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir))
674
684
  : failedDiscoveryConfigured,
675
685
  ),
676
686
  fallback: stale ? "stale" : "configured",
@@ -748,7 +758,7 @@ export async function fetchProviderModelsWithAuth(
748
758
  return observed(models, "degraded");
749
759
  }
750
760
  if (antigravity) {
751
- const live = antigravity.map(model => applyProviderConfigHints(name, prov, {
761
+ const upstream: CatalogModel[] = antigravity.map(model => ({
752
762
  id: model.id,
753
763
  provider: name,
754
764
  // Only an exact discovered wire map proves a new model's selectable effort ladder.
@@ -760,8 +770,9 @@ export async function fetchProviderModelsWithAuth(
760
770
  } : {}),
761
771
  ...(model.contextWindow ? { contextWindow: model.contextWindow } : {}),
762
772
  ...(model.inputModalities ? { inputModalities: model.inputModalities } : {}),
763
- }, contextCap, metadataModelIdCaseFold, captured.effectiveAlias));
764
- const forCache = withConfiguredRetention(live, { retainComboTargets: false });
773
+ }));
774
+ const live = applyConfigHintsToCachedModels(name, prov, upstream, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir);
775
+ const forCache = azureMetadata ? upstream : withConfiguredRetention(live, { retainComboTargets: false });
765
776
  if (!isCurrentCacheGeneration()) {
766
777
  return observed(withConfiguredRetention(configured), "degraded");
767
778
  }
@@ -775,7 +786,7 @@ export async function fetchProviderModelsWithAuth(
775
786
  return observed(withConfiguredRetention(configured), "degraded");
776
787
  }
777
788
  markProviderDiscoveryOk(name, live.length);
778
- return observed(withConfiguredRetention(forCache, { warnDrops: true }), "authoritative");
789
+ return observed(withConfiguredRetention(azureMetadata ? live : forCache, { warnDrops: true }), "authoritative");
779
790
  }
780
791
  const googleAiStudio = effectiveGoogleMode(name, prov) === "ai-studio"
781
792
  ? extractGoogleAiStudioModelItems(bounded.value, discovery.maxModels)
@@ -814,7 +825,7 @@ export async function fetchProviderModelsWithAuth(
814
825
  provider: prov,
815
826
  }).catch(() => undefined)
816
827
  : undefined;
817
- const live = items.map(m => {
828
+ const upstream: CatalogModel[] = items.map(m => {
818
829
  const ownedBy = boundedOwnedBy(m.owned_by);
819
830
  // Precedence: the authoritative /v1/models row wins; /api/show fills only metadata the
820
831
  // models-API row does not carry. applyProviderConfigHints then applies explicit
@@ -830,22 +841,25 @@ export async function fetchProviderModelsWithAuth(
830
841
  ? { inputModalities: ["text", "image"] as string[] }
831
842
  : {}),
832
843
  };
833
- return applyProviderConfigHints(name, prov, {
844
+ return {
834
845
  id: m.id,
835
846
  provider: name,
836
847
  ...(ownedBy ? { owned_by: ownedBy } : {}),
837
848
  ...discoveredHints,
838
- }, contextCap, metadataModelIdCaseFold, captured.effectiveAlias);
849
+ };
839
850
  })
840
851
  .filter(m => shouldExposeProviderModel(name, m.id));
852
+ const live = applyConfigHintsToCachedModels(name, prov, upstream, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir);
841
853
  // Capture the count BEFORE the alias/configured augmentation below pushes extra rows into
842
854
  // `live`; otherwise configured entries would be reported as discovered ones.
843
855
  const liveModelCount = live.length;
844
856
  // Dated-release aliases + configured retention (compat allow-list, combo targets,
845
857
  // Vertex default). Cache without combo retention so a later gather re-applies the
846
858
  // current capture's retain set on read (warm-cache OCX-111 / #1308).
847
- const forCache = withConfiguredRetention(live, { retainComboTargets: false });
848
- const returned = withConfiguredRetention(forCache, { warnDrops: true });
859
+ // Azure hints depend on the admitted config root. Keep only upstream evidence in the
860
+ // shared TTL/stale cache; every read reapplies metadata and retention for its own root.
861
+ const forCache = azureMetadata ? upstream : withConfiguredRetention(live, { retainComboTargets: false });
862
+ const returned = withConfiguredRetention(azureMetadata ? live : forCache, { warnDrops: true });
849
863
  const droppedConfiguredIds = configured
850
864
  .map(model => model.id)
851
865
  .filter(id => !returned.some(model => model.id === id));
@@ -854,7 +868,7 @@ export async function fetchProviderModelsWithAuth(
854
868
  `[opencodex] Provider model discovery for "${name}" returned an authoritative empty catalog; ${droppedConfiguredIds.length > 0 ? `dropping configured model ids: ${droppedConfiguredIds.join(", ")}` : "no models will be exposed"}.`,
855
869
  );
856
870
  }
857
- if (!setCached(name, forCache, Date.now(), cacheGeneration)) {
871
+ if (!maySendAnthropicDiscovery() || !setCached(name, forCache, Date.now(), cacheGeneration)) {
858
872
  return observed(withConfiguredRetention(configured), "degraded");
859
873
  }
860
874
  markProviderDiscoveryOk(name, liveModelCount);
@@ -2,7 +2,7 @@ import { projectAntigravitySelectedModels } from "../../providers/antigravity-ef
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { initializeConfigOwnership } from "../../lib/config-ownership";
5
- import { getConfigDir, loadConfig, websocketsEnabled, withConfigMutationLockSync } from "../../config";
5
+ import { getConfigDir, loadConfig, ultraFastTierEnabled, websocketsEnabled, withConfigMutationLockSync } from "../../config";
6
6
  import { shouldSyncCodexOnStart } from "../desired-state";
7
7
  import { legacyCustomModelCatalogSlugs } from "../custom-model-catalog-migration";
8
8
  import { getCodexHome } from "../paths";
@@ -54,7 +54,7 @@ import {
54
54
  upstreamNativeEntry,
55
55
  } from "./metadata";
56
56
  import { trustedAccountBoundNativeCatalogSlug } from "./account-models";
57
- import { bundledCatalogCacheState, loadBundledCodexCatalog } from "./bundled";
57
+ import { bundledCatalogCacheState, bundledCodexCatalogSnapshot, loadBundledCodexCatalogAsync } from "./bundled";
58
58
  import { isMultiAgentV2Enabled } from "../features";
59
59
  import { clampCatalogModelsToCodexSupport } from "./effort";
60
60
  import { suppressedSyntheticMaxCatalogSlugs } from "./model-hints";
@@ -155,7 +155,7 @@ function optionalFileBytes(path: string): string | null {
155
155
  }
156
156
 
157
157
  function loadCatalogForRetainedSync(path: string): RawCatalog | null {
158
- const bundled = isDefaultCatalogPath(path) ? loadBundledCodexCatalog() : null;
158
+ const bundled = isDefaultCatalogPath(path) ? bundledCodexCatalogSnapshot() : null;
159
159
  if (bundled) return JSON.parse(JSON.stringify(bundled)) as RawCatalog;
160
160
  const active = readCatalog(path);
161
161
  // A valid configured custom file remains the content authority even when it has no bare native
@@ -508,6 +508,7 @@ function writeRetainedCatalogSync({
508
508
  }).filter(entry => trustedAccountBoundNativeCatalogSlug(entry) !== undefined)
509
509
  : [];
510
510
  catalog.models = mergeCatalogEntriesFromObservedState({
511
+ ultraFastTier: ultraFastTierEnabled(config),
511
512
  modelPickerOrder,
512
513
  accountSelectors,
513
514
  catalogModels: catalogModelsForMerge,
@@ -542,7 +543,7 @@ function writeRetainedCatalogSync({
542
543
  },
543
544
  });
544
545
  applyNativeAccessPrograms(catalog.models, modelEntitlements, accountTargets);
545
- clampCatalogModelsToCodexSupport(catalog.models);
546
+ clampCatalogModelsToCodexSupport(catalog.models, { observedCatalog: bundledCodexCatalogSnapshot() });
546
547
  finalizeAutoReviewModelOverride(catalog.models, catalogModelsForMerge, config);
547
548
  // Last mutation before serialization; see `enforceCatalogSlugUniqueness` for why the ordering
548
549
  // against the effort clamp is load-bearing rather than cosmetic.
@@ -623,6 +624,8 @@ export async function syncCatalogModels(
623
624
  await resolvePendingInitialModelSelection(config);
624
625
  }
625
626
  const owningCodexHome = getCodexHome();
627
+ // Settle before reading content and its matching evidence; no pre-refresh catalog is reused.
628
+ await loadBundledCodexCatalogAsync();
626
629
  const preflightRead = readRetainedCatalogSync(config);
627
630
  if (preflightRead === null) {
628
631
  return {
@@ -643,7 +646,6 @@ export async function syncCatalogModels(
643
646
  // The persisted runtime selection is covered by the filesystem evidence above
644
647
  // rather than by a process epoch; see `retainedCatalogProcessEvidence` for why
645
648
  // the in-memory runtime memo cannot be baselined honestly from this path.
646
- loadBundledCodexCatalog();
647
649
  const prepared: RetainedCatalogSyncRead = {
648
650
  ...preflightRead,
649
651
  evidence: retainedCatalogSyncEvidence(config, preflightRead.catalogPath, preflightRead.catalog),
@@ -347,6 +347,7 @@ async function gatherRoutedModelsUncached(
347
347
  lists.flat(),
348
348
  config,
349
349
  capture.openAiApiPolicy,
350
+ activeProviders.find(provider => provider.name === OPENAI_API_PROVIDER_ID)?.metadataConfigDir,
350
351
  );
351
352
  const apiProvider = activeProviders.find(provider => provider.name === OPENAI_API_PROVIDER_ID);
352
353
  // Trusted reconstruction replaces whole rows, including the earlier Fast hints.
@@ -363,12 +364,16 @@ async function gatherRoutedModelsUncached(
363
364
  const metadataModelIdCaseFoldByProvider = new Map(
364
365
  activeProviders.map(provider => [provider.name, provider.metadataModelIdCaseFold]),
365
366
  );
367
+ const metadataConfigDirByProvider = new Map(
368
+ activeProviders.map(provider => [provider.name, provider.metadataConfigDir]),
369
+ );
366
370
  const all = augmentRoutedModelsWithMetadata(
367
371
  apiAugmented,
368
372
  activeProviders.map(provider => provider.name),
369
373
  config.providers,
370
374
  config,
371
375
  metadataModelIdCaseFoldByProvider,
376
+ metadataConfigDirByProvider,
372
377
  )
373
378
  // Drop image/video generation models (e.g. Grok image/video) by default. Cursor's static catalog
374
379
  // intentionally mirrors Cursor's public model table, including Gemini image preview, so the
@@ -511,6 +516,7 @@ async function gatherRoutedModelsUncached(
511
516
  providerContextCap(config, target.provider),
512
517
  nativeAliasFallback,
513
518
  metadataModelIdCaseFoldByProvider.get(target.provider),
519
+ metadataConfigDirByProvider.get(target.provider),
514
520
  ))
515
521
  .filter((member): member is CatalogModel => member !== undefined);
516
522
  const derived = deriveComboCatalogModel(id, combo, members);
@@ -591,7 +597,7 @@ async function gatherRoutedModelsUncached(
591
597
  id: cm.modelId,
592
598
  provider: cm.provider,
593
599
  ...(nativeAliasMaxOutputTokens !== undefined ? { maxOutputTokens: nativeAliasMaxOutputTokens } : {}),
594
- }, cm.modelId, metadataModelIdCaseFoldByProvider.get(cm.provider))
600
+ }, cm.modelId, metadataModelIdCaseFoldByProvider.get(cm.provider), metadataConfigDirByProvider.get(cm.provider))
595
601
  : nativeAliasMaxOutputTokens;
596
602
  const configuredAutoCompact = configuredAutoCompactTokenLimit(rawProvider, cm.modelId);
597
603
  const customAutoCompactTokenLimit = codexForwardNativeCapabilityAlias
@@ -827,6 +833,7 @@ function augmentRoutedModelsWithCapturedOpenAiApiRows(
827
833
  models: CatalogModel[],
828
834
  config: OcxConfig,
829
835
  policy: CatalogTrustedOpenAiApiPolicySnapshot,
836
+ metadataConfigDir?: string,
830
837
  ): CatalogModel[] {
831
838
  if (policy.state !== "captured" || !policy.models) return models;
832
839
  const configured = config.providers[OPENAI_API_PROVIDER_ID];
@@ -862,6 +869,8 @@ function augmentRoutedModelsWithCapturedOpenAiApiRows(
862
869
  ? { provider: OPENAI_API_PROVIDER_ID, id, maxOutputTokens: policy.modelMaxOutputTokens[id] }
863
870
  : existingById.get(id) ?? { provider: OPENAI_API_PROVIDER_ID, id },
864
871
  policy.virtualModels?.[id]?.wireModelId ?? id,
872
+ undefined,
873
+ metadataConfigDir,
865
874
  );
866
875
  return {
867
876
  provider: OPENAI_API_PROVIDER_ID,
@@ -920,6 +929,7 @@ export function augmentRoutedModelsWithMetadata(
920
929
  providers?: Record<string, OcxProviderConfig>,
921
930
  caps?: Pick<OcxConfig, "providerContextCaps">,
922
931
  metadataModelIdCaseFoldByProvider?: ReadonlyMap<string, boolean>,
932
+ metadataConfigDirByProvider?: ReadonlyMap<string, string>,
923
933
  ): CatalogModel[] {
924
934
  const out = [...models];
925
935
  const indexByKey = new Map(out.map((model, index) => [`${model.provider}/${model.id}`, index]));
@@ -947,6 +957,8 @@ export function augmentRoutedModelsWithMetadata(
947
957
  seeded,
948
958
  contextCap,
949
959
  metadataModelIdCaseFoldByProvider?.get(provider),
960
+ undefined,
961
+ metadataConfigDirByProvider?.get(provider),
950
962
  )
951
963
  : seeded;
952
964
  continue;
@@ -969,6 +981,8 @@ export function augmentRoutedModelsWithMetadata(
969
981
  model,
970
982
  contextCap,
971
983
  metadataModelIdCaseFoldByProvider?.get(provider),
984
+ undefined,
985
+ metadataConfigDirByProvider?.get(provider),
972
986
  )
973
987
  : {}),
974
988
  });
@@ -34,10 +34,9 @@ export async function refreshCatalogAutoRefreshSources(
34
34
  current: () => boolean,
35
35
  ): Promise<void> {
36
36
  // Runtime selection also supplies the roster's trusted client version after an upgrade.
37
- await bestEffortSource(async active => {
38
- const { loadBundledCodexCatalog } = await import("./catalog/bundled");
39
- if (active() && current()) loadBundledCodexCatalog();
40
- // The synchronous loader bounds its selected-runtime subprocess probes itself.
37
+ await bestEffortSource(async (active, signal) => {
38
+ const { loadBundledCodexCatalogAsync } = await import("./catalog/bundled");
39
+ if (active() && current()) await loadBundledCodexCatalogAsync({}, () => active() && current(), signal);
41
40
  });
42
41
  if (!current()) return;
43
42
  await bestEffortSource(async active => {
@@ -50,6 +50,14 @@ let generation = 0;
50
50
  /** setInterval does not skip a firing while the previous callback is still awaiting. */
51
51
  let inFlight = false;
52
52
 
53
+ /** Refreshes already-connected clients after a changed catalog; owned by the server lifecycle. */
54
+ export type CatalogChangedClientFanout =
55
+ (isCurrent: () => boolean) => Promise<ReadonlyArray<{ readonly ok: boolean }>>;
56
+ let clientFanout: CatalogChangedClientFanout | null = null;
57
+ export function setCatalogAutoRefreshClientFanout(fanout: CatalogChangedClientFanout | null): void {
58
+ clientFanout = fanout;
59
+ }
60
+
53
61
  /** Read only a regular file, with a byte limit even if it grows after the stat. */
54
62
  function readBoundedRegularFile(path: string, maxBytes: number): string | null {
55
63
  let fd: number | undefined;
@@ -259,6 +267,17 @@ async function tick(): Promise<void> {
259
267
  ? "[catalog-auto-refresh] served model set changed; running Codex sessions keep the old list until restarted (ocx sync --restart-codex)"
260
268
  : "[catalog-auto-refresh] served model set changed");
261
269
  }
270
+ const fanout = clientFanout;
271
+ if (outcome.changed && fanout) {
272
+ const current = () => entryGeneration === generation;
273
+ try {
274
+ const failed = (await fanout(current)).filter(result => !result.ok).length;
275
+ // Outcomes can contain private paths and reasons; only the failure count leaves this hook.
276
+ if (current() && failed > 0) console.warn(`[catalog-auto-refresh] ${failed} client integration(s) were not refreshed; ocx sync retries them`);
277
+ } catch {
278
+ if (current()) console.warn("[catalog-auto-refresh] client integrations were not refreshed; ocx sync retries them");
279
+ }
280
+ }
262
281
  } catch {
263
282
  // A failed refresh is not an error worth surfacing: the next tick tries again.
264
283
  } finally {