@iislee/opencodex 2.11.0 → 2.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (733) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +117 -22
  3. package/bin/ocx.mjs +245 -88
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-Be1sDpWw.css +1 -0
  6. package/gui/dist/assets/index-D4OKgUV4.js +114 -0
  7. package/gui/dist/index.html +2 -2
  8. package/gui/dist/provider-icons/alibaba-color.svg +1 -1
  9. package/gui/dist/provider-icons/antigravity-color.svg +1 -1
  10. package/gui/dist/provider-icons/claude-color.svg +1 -1
  11. package/gui/dist/provider-icons/cline-color.svg +16 -0
  12. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
  13. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  14. package/gui/dist/provider-icons/copilot-color.svg +1 -1
  15. package/gui/dist/provider-icons/cursor-color.svg +1 -1
  16. package/gui/dist/provider-icons/deepseek-color.svg +1 -1
  17. package/gui/dist/provider-icons/firepass-color.svg +1 -1
  18. package/gui/dist/provider-icons/fireworks-color.svg +1 -1
  19. package/gui/dist/provider-icons/gemini-color.svg +1 -1
  20. package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
  21. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
  22. package/gui/dist/provider-icons/grok.svg +1 -1
  23. package/gui/dist/provider-icons/groq-color.svg +1 -1
  24. package/gui/dist/provider-icons/huggingface-color.svg +1 -1
  25. package/gui/dist/provider-icons/kimi-color.svg +1 -1
  26. package/gui/dist/provider-icons/kiro-color.svg +2 -2
  27. package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
  28. package/gui/dist/provider-icons/mistral-color.svg +1 -1
  29. package/gui/dist/provider-icons/moonshot-color.svg +1 -1
  30. package/gui/dist/provider-icons/nvidia-color.svg +1 -1
  31. package/gui/dist/provider-icons/ollama-color.svg +1 -1
  32. package/gui/dist/provider-icons/openai.svg +1 -1
  33. package/gui/dist/provider-icons/opencode.svg +2 -1
  34. package/gui/dist/provider-icons/openrouter-color.svg +1 -1
  35. package/gui/dist/provider-icons/pi.svg +2 -2
  36. package/gui/dist/provider-icons/qianfan-color.svg +1 -1
  37. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
  38. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
  39. package/gui/dist/provider-icons/vllm-color.svg +1 -1
  40. package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
  41. package/package.json +21 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +475 -62
  44. package/src/adapters/base.ts +82 -7
  45. package/src/adapters/client-fingerprint.ts +18 -12
  46. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  47. package/src/adapters/command-code.ts +637 -0
  48. package/src/adapters/cursor/call-id.ts +44 -0
  49. package/src/adapters/cursor/catalog.ts +541 -0
  50. package/src/adapters/cursor/checkpoint-store.ts +308 -0
  51. package/src/adapters/cursor/cursor-errors.ts +144 -6
  52. package/src/adapters/cursor/discovery.ts +122 -14
  53. package/src/adapters/cursor/effort-map.ts +106 -4
  54. package/src/adapters/cursor/envelope-echo.ts +290 -0
  55. package/src/adapters/cursor/framing.ts +39 -0
  56. package/src/adapters/cursor/h2-pool.ts +123 -0
  57. package/src/adapters/cursor/http1-bidi.ts +361 -0
  58. package/src/adapters/cursor/images.ts +704 -0
  59. package/src/adapters/cursor/live-models.ts +180 -59
  60. package/src/adapters/cursor/live-transport.ts +623 -170
  61. package/src/adapters/cursor/message-mapper.ts +4 -1
  62. package/src/adapters/cursor/native-exec-common.ts +23 -2
  63. package/src/adapters/cursor/native-exec-desktop.ts +23 -0
  64. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  65. package/src/adapters/cursor/native-exec-network.ts +1 -1
  66. package/src/adapters/cursor/native-exec-shell.ts +5 -3
  67. package/src/adapters/cursor/native-exec.ts +121 -14
  68. package/src/adapters/cursor/protobuf-events.ts +829 -11
  69. package/src/adapters/cursor/protobuf-request.ts +447 -75
  70. package/src/adapters/cursor/request-builder.ts +271 -35
  71. package/src/adapters/cursor/tool-definitions.ts +252 -12
  72. package/src/adapters/cursor/tool-result-normalize.ts +115 -0
  73. package/src/adapters/cursor/transport.ts +22 -0
  74. package/src/adapters/cursor/types.ts +48 -1
  75. package/src/adapters/cursor.ts +336 -39
  76. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  77. package/src/adapters/google-antigravity-replay.ts +713 -41
  78. package/src/adapters/google-antigravity-wire.ts +48 -10
  79. package/src/adapters/google-errors.ts +44 -12
  80. package/src/adapters/google-http.ts +38 -10
  81. package/src/adapters/google-tool-schema.ts +80 -15
  82. package/src/adapters/google-truncation.ts +11 -0
  83. package/src/adapters/google.ts +742 -86
  84. package/src/adapters/identity.ts +39 -6
  85. package/src/adapters/image.ts +1 -1
  86. package/src/adapters/kiro-constants.ts +27 -0
  87. package/src/adapters/kiro-errors.ts +11 -0
  88. package/src/adapters/kiro-events.ts +19 -1
  89. package/src/adapters/kiro-thinking.ts +18 -2
  90. package/src/adapters/kiro-tools.ts +65 -17
  91. package/src/adapters/kiro.ts +270 -73
  92. package/src/adapters/mimo-free.ts +17 -0
  93. package/src/adapters/openai-chat-url.ts +11 -0
  94. package/src/adapters/openai-chat.ts +1377 -344
  95. package/src/adapters/openai-responses-url.ts +16 -0
  96. package/src/adapters/openai-responses.ts +1164 -64
  97. package/src/adapters/registry.ts +175 -0
  98. package/src/adapters/responses-tool-schema.ts +67 -0
  99. package/src/adapters/run-turn-queue.ts +36 -1
  100. package/src/adapters/tool-call-id.ts +119 -0
  101. package/src/adapters/tool-catalog-nudge.ts +105 -21
  102. package/src/adapters/xai-tool-schema.ts +436 -0
  103. package/src/adapters/xai-web-search.ts +186 -0
  104. package/src/bridge.ts +512 -92
  105. package/src/chat/inbound.ts +57 -20
  106. package/src/chat/outbound.ts +97 -33
  107. package/src/claude/agents-inject.ts +40 -10
  108. package/src/claude/context-windows.ts +37 -14
  109. package/src/claude/desktop-3p.ts +243 -9
  110. package/src/claude/gateway-cache.ts +41 -4
  111. package/src/claude/inbound.ts +72 -3
  112. package/src/claude/model-info.ts +38 -15
  113. package/src/claude/outbound.ts +88 -21
  114. package/src/cli/access.ts +46 -3
  115. package/src/cli/account-api.ts +150 -19
  116. package/src/cli/account-auth.ts +33 -6
  117. package/src/cli/account-catalog-refresh.ts +14 -0
  118. package/src/cli/account-extended.ts +648 -28
  119. package/src/cli/account-main.ts +317 -0
  120. package/src/cli/account.ts +97 -19
  121. package/src/cli/agent.ts +53 -2
  122. package/src/cli/alias.ts +66 -0
  123. package/src/cli/capabilities-command.ts +94 -0
  124. package/src/cli/capabilities.ts +496 -0
  125. package/src/cli/claude-agent-startup-sync.ts +73 -0
  126. package/src/cli/claude-desktop.ts +60 -15
  127. package/src/cli/claude.ts +129 -35
  128. package/src/cli/codex-log-guard-doctor.ts +103 -0
  129. package/src/cli/codex-shim-autorestore.ts +2 -0
  130. package/src/cli/codex-shim-readiness.ts +76 -0
  131. package/src/cli/combo.ts +8 -0
  132. package/src/cli/config-command.ts +74 -10
  133. package/src/cli/dispatch.ts +821 -0
  134. package/src/cli/doctor.ts +440 -56
  135. package/src/cli/ensure-desired-integrations.ts +152 -0
  136. package/src/cli/export-command.ts +46 -20
  137. package/src/cli/help.ts +34 -274
  138. package/src/cli/index.ts +417 -528
  139. package/src/cli/init.ts +5 -17
  140. package/src/cli/inspect.ts +230 -0
  141. package/src/cli/integrations.ts +120 -2
  142. package/src/cli/lab.ts +607 -0
  143. package/src/cli/launcher-context.ts +77 -0
  144. package/src/cli/minimax.ts +497 -0
  145. package/src/cli/models-runtime.ts +130 -2
  146. package/src/cli/models.ts +118 -16
  147. package/src/cli/observe.ts +123 -11
  148. package/src/cli/opencode.ts +4 -2
  149. package/src/cli/provider-runtime.ts +34 -3
  150. package/src/cli/provider.ts +25 -3
  151. package/src/cli/ready.ts +301 -0
  152. package/src/cli/registry.ts +476 -0
  153. package/src/cli/root.ts +86 -0
  154. package/src/cli/route-policy.ts +92 -0
  155. package/src/cli/runtime-api.ts +57 -10
  156. package/src/cli/star-prompt.ts +71 -15
  157. package/src/cli/status.ts +51 -4
  158. package/src/cli/storage.ts +234 -0
  159. package/src/cli/system-command.ts +16 -0
  160. package/src/cli/system-restart-client.ts +146 -0
  161. package/src/cli/tray-proxy.ts +153 -6
  162. package/src/cli/usage-report.ts +184 -0
  163. package/src/cli/v2.ts +137 -18
  164. package/src/cli/version-skew.ts +46 -0
  165. package/src/cli.ts +1 -1
  166. package/src/clients/config-export.ts +1442 -23
  167. package/src/clients/effective-status.ts +30 -2
  168. package/src/clients/probes/cline.ts +81 -0
  169. package/src/codex/account-label.ts +35 -1
  170. package/src/codex/account-lifecycle.ts +130 -13
  171. package/src/codex/account-namespaces.ts +49 -3
  172. package/src/codex/account-pause.ts +2 -1
  173. package/src/codex/account-priority.ts +84 -0
  174. package/src/codex/account-store.ts +29 -2
  175. package/src/codex/account-usability.ts +25 -2
  176. package/src/codex/admission.ts +256 -0
  177. package/src/codex/affinity-debug.ts +162 -0
  178. package/src/codex/app-server-processes.ts +570 -107
  179. package/src/codex/app-server-restart-service.ts +232 -0
  180. package/src/codex/auth-api.ts +893 -246
  181. package/src/codex/auth-collision.ts +5 -3
  182. package/src/codex/auth-context.ts +348 -32
  183. package/src/codex/autostart-health.ts +24 -1
  184. package/src/codex/catalog/account-models.ts +67 -0
  185. package/src/codex/catalog/aggregation.ts +86 -10
  186. package/src/codex/catalog/bundled.ts +331 -33
  187. package/src/codex/catalog/effort.ts +138 -32
  188. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  189. package/src/codex/catalog/kinds.ts +2 -0
  190. package/src/codex/catalog/metadata.ts +555 -45
  191. package/src/codex/catalog/native-models.ts +75 -0
  192. package/src/codex/catalog/parsing.ts +269 -30
  193. package/src/codex/catalog/provider-fetch.ts +1626 -144
  194. package/src/codex/catalog/sync.ts +1576 -187
  195. package/src/codex/catalog-admission.ts +199 -0
  196. package/src/codex/catalog-refresh-status.ts +105 -0
  197. package/src/codex/catalog-write-serialization.ts +242 -0
  198. package/src/codex/catalog.ts +6 -3
  199. package/src/codex/codex-write-lock.ts +386 -0
  200. package/src/codex/convergence-types.ts +614 -0
  201. package/src/codex/convergence.ts +676 -0
  202. package/src/codex/coordinator-doctor.ts +332 -0
  203. package/src/codex/custom-model-catalog-migration.ts +176 -0
  204. package/src/codex/desired-state.ts +230 -0
  205. package/src/codex/desktop-app-restart.ts +355 -0
  206. package/src/codex/features.ts +636 -39
  207. package/src/codex/generation.ts +202 -0
  208. package/src/codex/history-job.ts +436 -0
  209. package/src/codex/history-lock.ts +242 -0
  210. package/src/codex/history-manifest.ts +112 -0
  211. package/src/codex/history-migration-guardian.ts +30 -24
  212. package/src/codex/history-provider.ts +1016 -235
  213. package/src/codex/history-transition.ts +105 -0
  214. package/src/codex/history-worker.ts +223 -0
  215. package/src/codex/inject-coordination.ts +373 -0
  216. package/src/codex/inject.ts +1114 -152
  217. package/src/codex/injected-marker.ts +37 -3
  218. package/src/codex/integration-record.ts +266 -0
  219. package/src/codex/internal/catalog-writer.ts +203 -0
  220. package/src/codex/internal/history-writer.ts +81 -0
  221. package/src/codex/journal.ts +66 -4
  222. package/src/codex/log-guard/inspect.ts +524 -0
  223. package/src/codex/log-guard/lock.ts +150 -0
  224. package/src/codex/log-guard/maintenance.ts +403 -0
  225. package/src/codex/log-guard/path-safety.ts +88 -0
  226. package/src/codex/log-guard/policy.ts +44 -0
  227. package/src/codex/log-guard/processes.ts +205 -0
  228. package/src/codex/log-guard/protection.ts +489 -0
  229. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  230. package/src/codex/main-account-cache.ts +24 -0
  231. package/src/codex/main-account.ts +29 -1
  232. package/src/codex/management-convergence.ts +167 -0
  233. package/src/codex/model-cache.ts +56 -10
  234. package/src/codex/model-entitlements.ts +360 -0
  235. package/src/codex/native-main-admission.ts +47 -0
  236. package/src/codex/native-main-auth-temp.ts +187 -0
  237. package/src/codex/native-main-claim.ts +178 -0
  238. package/src/codex/native-main-lock-file.ts +162 -0
  239. package/src/codex/native-main-owner.ts +329 -0
  240. package/src/codex/native-profile-api.ts +247 -0
  241. package/src/codex/native-profile-manager.ts +1531 -0
  242. package/src/codex/native-profile-processes.ts +121 -0
  243. package/src/codex/native-profile-recovery.ts +99 -0
  244. package/src/codex/native-profile-stage-store.ts +387 -0
  245. package/src/codex/native-profile-startup.ts +622 -0
  246. package/src/codex/native-profile-store.ts +855 -0
  247. package/src/codex/native-profile-types.ts +120 -0
  248. package/src/codex/native-residue.ts +675 -0
  249. package/src/codex/paths.ts +80 -1
  250. package/src/codex/plan-from-token.ts +140 -0
  251. package/src/codex/plan.ts +40 -0
  252. package/src/codex/plugins-doctor.ts +1 -1
  253. package/src/codex/pool-rotation.ts +74 -4
  254. package/src/codex/project-config-warnings.ts +20 -6
  255. package/src/codex/prompt-journal.ts +352 -0
  256. package/src/codex/prompt-layers.ts +1313 -0
  257. package/src/codex/prompt-lock.ts +143 -0
  258. package/src/codex/prompt-text-probe.ts +238 -0
  259. package/src/codex/quota-rejection.ts +298 -0
  260. package/src/codex/quota.ts +264 -21
  261. package/src/codex/refresh.ts +11 -2
  262. package/src/codex/reset-credit-recovery.ts +1044 -0
  263. package/src/codex/routing.ts +514 -94
  264. package/src/codex/runtime.ts +159 -38
  265. package/src/codex/shim.ts +1065 -31
  266. package/src/codex/subagent-model-fallback.ts +437 -43
  267. package/src/codex/sync.ts +191 -2
  268. package/src/codex/transition-state.ts +720 -0
  269. package/src/codex/upstream-host-health.ts +368 -0
  270. package/src/codex/user-identity.ts +557 -0
  271. package/src/codex/warmup.ts +187 -81
  272. package/src/codex/write-coordination.ts +114 -0
  273. package/src/combos/failover.ts +47 -0
  274. package/src/combos/index.ts +4 -0
  275. package/src/combos/request.ts +32 -0
  276. package/src/combos/types.ts +91 -9
  277. package/src/compatibility/index.ts +26 -0
  278. package/src/compatibility/manifest.ts +253 -0
  279. package/src/compatibility/openai-responses.ts +121 -0
  280. package/src/config/atomic-write.ts +219 -0
  281. package/src/config/paths.ts +40 -0
  282. package/src/config/process-state.ts +309 -0
  283. package/src/config/provider-name.ts +24 -0
  284. package/src/config/provider-validation.ts +177 -0
  285. package/src/config/rebase-provenance.ts +68 -0
  286. package/src/config.ts +1713 -709
  287. package/src/generated/compatibility-version.json +3324 -0
  288. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  289. package/src/grok/inject.ts +16 -5
  290. package/src/grok/inspect.ts +45 -0
  291. package/src/grok/sync.ts +2 -2
  292. package/src/images/loop.ts +157 -33
  293. package/src/images/plan.ts +24 -13
  294. package/src/integrations/config-io.ts +269 -0
  295. package/src/integrations/journal.ts +315 -0
  296. package/src/integrations/merge.ts +135 -0
  297. package/src/integrations/mutation-flight.ts +71 -0
  298. package/src/integrations/native/ownership-preflight.ts +202 -0
  299. package/src/integrations/omp-yaml-source.ts +358 -0
  300. package/src/integrations/owned-refresh.ts +74 -0
  301. package/src/integrations/ownership-policy.ts +160 -0
  302. package/src/integrations/ownership.ts +155 -0
  303. package/src/integrations/registry.ts +166 -0
  304. package/src/integrations/serialize.ts +314 -0
  305. package/src/integrations/state.ts +433 -0
  306. package/src/integrations/store.ts +103 -0
  307. package/src/integrations/writer-lock.ts +98 -0
  308. package/src/integrations/writer.ts +715 -0
  309. package/src/lab/artifacts/sanitize.ts +586 -0
  310. package/src/lab/artifacts/secure-fs.ts +475 -0
  311. package/src/lab/artifacts/store.ts +310 -0
  312. package/src/lab/automation/budgets.ts +78 -0
  313. package/src/lab/automation/config-persistence.ts +256 -0
  314. package/src/lab/automation/constants.ts +39 -0
  315. package/src/lab/automation/cooldown.ts +103 -0
  316. package/src/lab/automation/dispatch.ts +211 -0
  317. package/src/lab/automation/index.ts +13 -0
  318. package/src/lab/automation/orchestrator.ts +499 -0
  319. package/src/lab/automation/persistence.ts +512 -0
  320. package/src/lab/automation/planner.ts +371 -0
  321. package/src/lab/automation/policy.ts +136 -0
  322. package/src/lab/automation/queue.ts +191 -0
  323. package/src/lab/automation/recovery.ts +24 -0
  324. package/src/lab/automation/route-context.ts +21 -0
  325. package/src/lab/automation/run-key.ts +44 -0
  326. package/src/lab/automation/runs-query.ts +34 -0
  327. package/src/lab/automation/types.ts +160 -0
  328. package/src/lab/conformance/assertion.ts +325 -0
  329. package/src/lab/conformance/digest.ts +22 -0
  330. package/src/lab/conformance/executor.ts +741 -0
  331. package/src/lab/conformance/fixture-provider.ts +27 -0
  332. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  333. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  334. package/src/lab/conformance/harness-budget.ts +47 -0
  335. package/src/lab/conformance/index.ts +5 -0
  336. package/src/lab/conformance/jcs.ts +64 -0
  337. package/src/lab/conformance/json-pointer.ts +39 -0
  338. package/src/lab/conformance/manifest.ts +180 -0
  339. package/src/lab/conformance/mcp-stub.ts +179 -0
  340. package/src/lab/conformance/negative-controls.ts +164 -0
  341. package/src/lab/conformance/observation.ts +355 -0
  342. package/src/lab/conformance/runner.ts +68 -0
  343. package/src/lab/conformance/sse-normalize.ts +59 -0
  344. package/src/lab/conformance/suite-manifest.ts +78 -0
  345. package/src/lab/conformance/types.ts +214 -0
  346. package/src/lab/constants.ts +126 -0
  347. package/src/lab/digest.ts +64 -0
  348. package/src/lab/events/errors.ts +9 -0
  349. package/src/lab/events/limits.ts +117 -0
  350. package/src/lab/events/types.ts +229 -0
  351. package/src/lab/events/validate.ts +781 -0
  352. package/src/lab/fabric/constants.ts +40 -0
  353. package/src/lab/fabric/executor.ts +492 -0
  354. package/src/lab/fabric/index.ts +80 -0
  355. package/src/lab/fabric/manifest.ts +222 -0
  356. package/src/lab/fabric/observe.ts +489 -0
  357. package/src/lab/fabric/patch.ts +79 -0
  358. package/src/lab/fabric/producer-child.ts +139 -0
  359. package/src/lab/fabric/producer-isolate.ts +276 -0
  360. package/src/lab/fabric/producer-protocol.ts +61 -0
  361. package/src/lab/fabric/scratch.ts +439 -0
  362. package/src/lab/fabric/subject.ts +106 -0
  363. package/src/lab/fabric/types.ts +134 -0
  364. package/src/lab/fabric/verifier.ts +98 -0
  365. package/src/lab/index.ts +54 -0
  366. package/src/lab/ledger/artifact-refs.ts +127 -0
  367. package/src/lab/ledger/invalidation.ts +136 -0
  368. package/src/lab/ledger/purge.ts +310 -0
  369. package/src/lab/ledger/store.ts +532 -0
  370. package/src/lab/live/credential-lease.ts +53 -0
  371. package/src/lab/live/destination.ts +155 -0
  372. package/src/lab/live/executor.ts +336 -0
  373. package/src/lab/live/inert-tools.ts +56 -0
  374. package/src/lab/live/manifest.ts +85 -0
  375. package/src/lab/live/mcp-loopback.ts +57 -0
  376. package/src/lab/live/runner.ts +19 -0
  377. package/src/lab/live/sandbox.ts +61 -0
  378. package/src/lab/live/suite-manifest.ts +41 -0
  379. package/src/lab/live/transport.ts +118 -0
  380. package/src/lab/live/types.ts +197 -0
  381. package/src/lab/observe/from-conformance.ts +301 -0
  382. package/src/lab/observe/from-live.ts +117 -0
  383. package/src/lab/paths.ts +153 -0
  384. package/src/lab/projection/rebuild.ts +495 -0
  385. package/src/lab/projection/schema.ts +135 -0
  386. package/src/lab/projection/verdicts.ts +474 -0
  387. package/src/lab/projection/verification.ts +412 -0
  388. package/src/lab/public/bundle.ts +217 -0
  389. package/src/lab/public/community-authority.ts +175 -0
  390. package/src/lab/public/community-files.ts +29 -0
  391. package/src/lab/public/community.ts +479 -0
  392. package/src/lab/public/file-safety.ts +155 -0
  393. package/src/lab/public/ids.ts +26 -0
  394. package/src/lab/public/index.ts +16 -0
  395. package/src/lab/public/mutation-lock.ts +424 -0
  396. package/src/lab/public/operator.ts +353 -0
  397. package/src/lab/public/origin-purge.ts +79 -0
  398. package/src/lab/public/origin.ts +203 -0
  399. package/src/lab/public/privacy.ts +143 -0
  400. package/src/lab/public/private-file.ts +261 -0
  401. package/src/lab/public/project.ts +124 -0
  402. package/src/lab/public/purge-test-fault.ts +21 -0
  403. package/src/lab/public/purge.ts +223 -0
  404. package/src/lab/public/registry.ts +44 -0
  405. package/src/lab/public/revocation.ts +252 -0
  406. package/src/lab/public/signature.ts +243 -0
  407. package/src/lab/public/storage.ts +105 -0
  408. package/src/lab/public/strict-json.ts +206 -0
  409. package/src/lab/public/time.ts +26 -0
  410. package/src/lab/public/types.ts +172 -0
  411. package/src/lab/public/validate.ts +391 -0
  412. package/src/lab/query/catalog.ts +101 -0
  413. package/src/lab/query/connection.ts +107 -0
  414. package/src/lab/query/constants.ts +4 -0
  415. package/src/lab/query/cursor.ts +132 -0
  416. package/src/lab/query/dto-map.ts +277 -0
  417. package/src/lab/query/errors.ts +22 -0
  418. package/src/lab/query/freshness.ts +53 -0
  419. package/src/lab/query/index.ts +45 -0
  420. package/src/lab/query/latest-observation.ts +59 -0
  421. package/src/lab/query/passive-production.ts +159 -0
  422. package/src/lab/query/queries.ts +444 -0
  423. package/src/lab/query/types.ts +266 -0
  424. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  425. package/src/lab/subject/installation-salt.ts +112 -0
  426. package/src/lab/subject/protocol-subject.ts +80 -0
  427. package/src/lab/subject/route-subject.ts +74 -0
  428. package/src/lib/admin-secrets.ts +24 -0
  429. package/src/lib/app-owned-memory-stores.ts +22 -0
  430. package/src/lib/bounded-body.ts +167 -11
  431. package/src/lib/bun-runtime.ts +125 -12
  432. package/src/lib/bun-stream-caps.ts +13 -9
  433. package/src/lib/codex-restart-contract.ts +120 -0
  434. package/src/lib/config-ownership.ts +26 -2
  435. package/src/lib/destination-policy.ts +65 -1
  436. package/src/lib/errors.ts +80 -5
  437. package/src/lib/fabric-task-execution-authority.ts +7 -0
  438. package/src/lib/fabric-task-host.ts +29 -0
  439. package/src/lib/lab-activation.ts +223 -0
  440. package/src/lib/lab-live-execution-authority.ts +13 -0
  441. package/src/lib/lab-live-host.ts +30 -0
  442. package/src/lib/lab-live-pinned-sender.ts +56 -0
  443. package/src/lib/lab-live-route-production.ts +130 -0
  444. package/src/lib/lab-passive-linker-registration.ts +26 -0
  445. package/src/lib/local-management-attestation.ts +51 -0
  446. package/src/lib/local-management-capability.ts +100 -0
  447. package/src/lib/local-provider-reload-contract.ts +100 -0
  448. package/src/lib/optional-shutdown-hooks.ts +57 -0
  449. package/src/lib/package-tree-integrity.ts +101 -0
  450. package/src/lib/pinned-http.ts +145 -26
  451. package/src/lib/process-control.ts +6 -2
  452. package/src/lib/provider-outbound.ts +49 -9
  453. package/src/lib/redact.ts +419 -3
  454. package/src/lib/self-launch-argv.ts +15 -0
  455. package/src/lib/server-resource-ownership.ts +71 -0
  456. package/src/lib/service-secrets.ts +15 -0
  457. package/src/lib/shadow-call.ts +35 -4
  458. package/src/lib/sse-decoder.ts +41 -0
  459. package/src/lib/state-store-registrations.ts +10 -2
  460. package/src/lib/system-restart-contract.ts +73 -0
  461. package/src/lib/token-estimate.ts +19 -2
  462. package/src/lib/tool-argument-integers.ts +253 -0
  463. package/src/lib/translator-budget.ts +44 -0
  464. package/src/lib/upstream-http-version.ts +57 -0
  465. package/src/lib/upstream-reachability.ts +95 -0
  466. package/src/lib/upstream-retry.ts +156 -3
  467. package/src/lib/windows-atomic-replace.ts +155 -0
  468. package/src/lib/windows-elevation.ts +70 -2
  469. package/src/lib/windows-secret-acl.ts +409 -69
  470. package/src/lib/windows-service-wrappers.ts +72 -0
  471. package/src/lib/windows-text.ts +106 -0
  472. package/src/lib/windows-user-principal.ts +341 -0
  473. package/src/lib/winsw.ts +33 -5
  474. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  475. package/src/oauth/account-import/index.ts +15 -0
  476. package/src/oauth/account-import/parser.ts +83 -0
  477. package/src/oauth/account-import/registry.ts +18 -0
  478. package/src/oauth/account-import/service.ts +75 -0
  479. package/src/oauth/account-import/types.ts +91 -0
  480. package/src/oauth/anthropic.ts +12 -1
  481. package/src/oauth/callback-server.ts +30 -4
  482. package/src/oauth/chatgpt.ts +12 -1
  483. package/src/oauth/cline.ts +203 -0
  484. package/src/oauth/command-code.ts +239 -0
  485. package/src/oauth/cursor.ts +46 -5
  486. package/src/oauth/generic-account-failover.ts +231 -0
  487. package/src/oauth/google-antigravity.ts +35 -3
  488. package/src/oauth/health.ts +20 -12
  489. package/src/oauth/index.ts +462 -71
  490. package/src/oauth/key-providers.ts +25 -0
  491. package/src/oauth/kimi.ts +25 -3
  492. package/src/oauth/kiro.ts +95 -6
  493. package/src/oauth/local-token-detect.ts +13 -2
  494. package/src/oauth/log.ts +3 -1
  495. package/src/oauth/login-cli.ts +88 -28
  496. package/src/oauth/nous.ts +798 -0
  497. package/src/oauth/open-browser-choice.ts +26 -0
  498. package/src/oauth/store.ts +133 -26
  499. package/src/oauth/token-guardian.ts +9 -3
  500. package/src/oauth/types.ts +15 -0
  501. package/src/pi/models.ts +2 -2
  502. package/src/providers/alibaba-region-migration.ts +1 -1
  503. package/src/providers/antigravity-models.ts +521 -31
  504. package/src/providers/auto-compact-budget.ts +65 -0
  505. package/src/providers/base-url-choices.ts +10 -0
  506. package/src/providers/codex-capacity.ts +292 -0
  507. package/src/providers/command-code-efforts.ts +176 -0
  508. package/src/providers/context-cap.ts +26 -8
  509. package/src/providers/cursor-pool.ts +72 -0
  510. package/src/providers/default-aliases.ts +65 -0
  511. package/src/providers/derive.ts +281 -6
  512. package/src/providers/fastwire.ts +507 -0
  513. package/src/providers/free-directory.ts +10 -7
  514. package/src/providers/google-vertex-location.ts +14 -0
  515. package/src/providers/key-failover.ts +71 -3
  516. package/src/providers/label.ts +35 -2
  517. package/src/providers/model-discovery-limits.ts +16 -0
  518. package/src/providers/model-discovery.ts +115 -22
  519. package/src/providers/model-presets.ts +119 -0
  520. package/src/providers/model-rename-migration.ts +255 -0
  521. package/src/providers/model-rename-startup.ts +28 -0
  522. package/src/providers/new-model-policy.ts +146 -0
  523. package/src/providers/openai-sidecar.ts +72 -4
  524. package/src/providers/openai-tier-startup.ts +31 -2
  525. package/src/providers/openai-tiers.ts +119 -4
  526. package/src/providers/openai-virtual-models.ts +1 -0
  527. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  528. package/src/providers/provider-id-rewrite.ts +30 -0
  529. package/src/providers/quota.ts +1379 -40
  530. package/src/providers/registry.ts +1545 -112
  531. package/src/providers/request-pacing.ts +310 -0
  532. package/src/providers/service-tier.ts +277 -0
  533. package/src/providers/slug-codec.ts +94 -6
  534. package/src/providers/static-model-discovery.ts +86 -0
  535. package/src/providers/xai-responses-opt-in.ts +15 -0
  536. package/src/providers/xai-transport.ts +32 -4
  537. package/src/reasoning-effort.ts +68 -3
  538. package/src/responses/apply-patch-envelope.ts +63 -0
  539. package/src/responses/code-mode-helper-compat.ts +50 -0
  540. package/src/responses/compaction.ts +26 -1
  541. package/src/responses/custom-tool-compat.ts +384 -0
  542. package/src/responses/hosted-tool-policy.ts +9 -0
  543. package/src/responses/namespace-tool-compat.ts +435 -0
  544. package/src/responses/parser.ts +225 -38
  545. package/src/responses/provider-continuation.ts +98 -0
  546. package/src/responses/provider-opaque-metadata.ts +73 -0
  547. package/src/responses/reasoning-envelope.ts +9 -1
  548. package/src/responses/reasoning-replay-cache.ts +504 -0
  549. package/src/responses/schema.ts +12 -2
  550. package/src/responses/spill-store.ts +75 -10
  551. package/src/responses/state.ts +659 -31
  552. package/src/responses/thought-signature-replay.ts +364 -0
  553. package/src/responses/tool-search-compat.ts +301 -0
  554. package/src/responses/truncated-stop-reason.ts +60 -0
  555. package/src/responses/turn-termination.ts +107 -0
  556. package/src/router.ts +405 -31
  557. package/src/routing/analytics.ts +379 -0
  558. package/src/routing/capability.ts +244 -0
  559. package/src/routing/compatibility/assemble.ts +73 -0
  560. package/src/routing/compatibility/behavior.ts +278 -0
  561. package/src/routing/compatibility/catalog.ts +99 -0
  562. package/src/routing/compatibility/endpoint.ts +52 -0
  563. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  564. package/src/routing/compatibility/policy.ts +181 -0
  565. package/src/routing/compatibility/provider-slot.ts +56 -0
  566. package/src/routing/compatibility/reader.ts +110 -0
  567. package/src/routing/compatibility/subject.ts +191 -0
  568. package/src/routing/compatibility/types.ts +64 -0
  569. package/src/routing/compatibility/version.ts +104 -0
  570. package/src/routing/cost.ts +77 -0
  571. package/src/routing/evaluator.ts +495 -0
  572. package/src/routing/health.ts +412 -0
  573. package/src/routing/history/cursor.ts +43 -0
  574. package/src/routing/history/indexer.ts +605 -0
  575. package/src/routing/history/schema.ts +72 -0
  576. package/src/routing/profile-namespace.ts +15 -0
  577. package/src/routing/profile.ts +547 -0
  578. package/src/routing/quota.ts +155 -0
  579. package/src/routing/request-evidence.ts +45 -0
  580. package/src/routing/trace.ts +776 -0
  581. package/src/server/adapter-resolve.ts +2 -29
  582. package/src/server/auth-cors.ts +289 -48
  583. package/src/server/background-lifecycle.ts +182 -0
  584. package/src/server/chat-completions.ts +154 -72
  585. package/src/server/chat-native-sse.ts +331 -0
  586. package/src/server/chat-native.ts +445 -0
  587. package/src/server/claude-messages.ts +160 -44
  588. package/src/server/direct-local-http.ts +347 -0
  589. package/src/server/effort-policy.ts +18 -0
  590. package/src/server/github-copilot-responses-repair.ts +338 -0
  591. package/src/server/gui-static.ts +39 -10
  592. package/src/server/images.ts +94 -12
  593. package/src/server/index.ts +1021 -190
  594. package/src/server/lifecycle.ts +336 -14
  595. package/src/server/live.ts +136 -17
  596. package/src/server/local-management-read-client.ts +90 -0
  597. package/src/server/local-provider-reload-client.ts +137 -0
  598. package/src/server/management/agent-settings-routes.ts +433 -116
  599. package/src/server/management/api-key-usage.ts +31 -5
  600. package/src/server/management/body.ts +6 -0
  601. package/src/server/management/codex-prompt-routes.ts +570 -0
  602. package/src/server/management/combo-routes.ts +64 -25
  603. package/src/server/management/config-routes.ts +511 -51
  604. package/src/server/management/context.ts +89 -2
  605. package/src/server/management/integration-routes.ts +498 -0
  606. package/src/server/management/lab-automation-routes.ts +206 -0
  607. package/src/server/management/lab-routes.ts +563 -0
  608. package/src/server/management/logs-usage-routes.ts +127 -52
  609. package/src/server/management/model-routes.ts +461 -133
  610. package/src/server/management/model-rows.ts +167 -0
  611. package/src/server/management/native-integration-routes.ts +770 -0
  612. package/src/server/management/oauth-account-routes.ts +90 -8
  613. package/src/server/management/provider-capability-config.ts +48 -0
  614. package/src/server/management/provider-routes.ts +836 -159
  615. package/src/server/management/request-history-routes.ts +191 -0
  616. package/src/server/management/route-registry.ts +332 -0
  617. package/src/server/management/routing-analytics-routes.ts +74 -0
  618. package/src/server/management/routing-profile-routes.ts +380 -0
  619. package/src/server/management/shared.ts +27 -11
  620. package/src/server/management/sidebar-routes.ts +47 -31
  621. package/src/server/management/storage-log-guard-routes.ts +186 -0
  622. package/src/server/management/sync-response.ts +69 -0
  623. package/src/server/management/system-restart.ts +289 -33
  624. package/src/server/management/system-routes.ts +77 -0
  625. package/src/server/management/usage-summary-cache.ts +9 -1
  626. package/src/server/management/vision-sidecar-options.ts +167 -0
  627. package/src/server/management/web-search-sidecar-options.ts +120 -0
  628. package/src/server/management-api.ts +126 -23
  629. package/src/server/management-auth.ts +220 -5
  630. package/src/server/passive-route-linker.ts +66 -0
  631. package/src/server/port-reclaim.ts +1 -1
  632. package/src/server/ports.ts +41 -1
  633. package/src/server/proxy-liveness.ts +161 -10
  634. package/src/server/readiness.ts +99 -0
  635. package/src/server/relay-eager.ts +221 -54
  636. package/src/server/relay.ts +479 -87
  637. package/src/server/request-decompress.ts +113 -6
  638. package/src/server/request-log-conversation.ts +51 -0
  639. package/src/server/request-log.ts +298 -31
  640. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  641. package/src/server/responses/agent-task-recovery.ts +465 -0
  642. package/src/server/responses/codex-auth-error.ts +55 -0
  643. package/src/server/responses/collaboration.ts +239 -36
  644. package/src/server/responses/combo-stream-preflight.ts +178 -0
  645. package/src/server/responses/compact.ts +444 -72
  646. package/src/server/responses/core.ts +3579 -436
  647. package/src/server/responses/empty-completion-guard.ts +333 -0
  648. package/src/server/responses/encrypted-payload.ts +62 -39
  649. package/src/server/responses/fetch-helpers.ts +89 -99
  650. package/src/server/responses/input-admission.ts +187 -0
  651. package/src/server/responses/pacing-overload.ts +13 -0
  652. package/src/server/responses/passthrough-error.ts +33 -9
  653. package/src/server/responses/policy-fallback.ts +178 -0
  654. package/src/server/responses/responses-field-backfill.ts +343 -0
  655. package/src/server/responses/terminal-guard.ts +26 -5
  656. package/src/server/responses/upstream-error.ts +5 -0
  657. package/src/server/responses/ws-upstream.ts +381 -0
  658. package/src/server/responses-custom-tool-repair.ts +363 -0
  659. package/src/server/responses-item-id-repair.ts +54 -6
  660. package/src/server/responses-json-events.ts +90 -0
  661. package/src/server/responses-model-rewrite.ts +29 -0
  662. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  663. package/src/server/responses-snapshot-repair.ts +621 -0
  664. package/src/server/responses-terminal-repair.ts +363 -0
  665. package/src/server/responses-tool-search-repair.ts +267 -0
  666. package/src/server/responses-undeclared-tool-guard.ts +376 -0
  667. package/src/server/responses.ts +18 -2
  668. package/src/server/search.ts +78 -13
  669. package/src/server/sse-frame-buffer.ts +319 -0
  670. package/src/server/sse-payload-rewrite.ts +110 -22
  671. package/src/server/startup-action-control.ts +8 -1
  672. package/src/server/startup-health-cache.ts +19 -1
  673. package/src/server/system-env.ts +80 -9
  674. package/src/server/ws-bridge.ts +53 -40
  675. package/src/service-manager-probe.ts +892 -0
  676. package/src/service.ts +1153 -94
  677. package/src/sidecar/auth.ts +92 -0
  678. package/src/sidecar/candidates.ts +83 -0
  679. package/src/storage/cleanup.ts +2 -2
  680. package/src/storage/policy-job.ts +14 -4
  681. package/src/storage/policy.ts +88 -23
  682. package/src/storage/scanner.ts +1 -1
  683. package/src/storage/worker-lifecycle.ts +14 -14
  684. package/src/tray/windows-tray.ps1 +83 -9
  685. package/src/tray/windows.ts +43 -16
  686. package/src/types/accounts.ts +37 -0
  687. package/src/types/config.ts +911 -0
  688. package/src/types/provider.ts +614 -0
  689. package/src/types/request.ts +397 -0
  690. package/src/types/tools.ts +236 -0
  691. package/src/types/wire.ts +80 -0
  692. package/src/types.ts +107 -1237
  693. package/src/update/index.ts +37 -23
  694. package/src/update/job.ts +445 -68
  695. package/src/update/notify.ts +12 -6
  696. package/src/update/npm-cache-preflight.d.mts +47 -0
  697. package/src/update/npm-cache-preflight.mjs +201 -0
  698. package/src/update/transactional-install.d.mts +22 -0
  699. package/src/update/transactional-install.mjs +266 -0
  700. package/src/usage/cost.ts +0 -0
  701. package/src/usage/expected-prices.ts +287 -16
  702. package/src/usage/log.ts +666 -50
  703. package/src/usage/summary.ts +388 -17
  704. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  705. package/src/usage/user-cost-overlays.ts +314 -0
  706. package/src/vision/anthropic-describe.ts +10 -6
  707. package/src/vision/backends.ts +97 -0
  708. package/src/vision/describe.ts +27 -16
  709. package/src/vision/eligibility.ts +250 -0
  710. package/src/vision/index.ts +238 -24
  711. package/src/vision/reasoning.ts +55 -0
  712. package/src/vision/routed-describe.ts +175 -0
  713. package/src/vision/timeout-bounds.ts +9 -0
  714. package/src/web-search/anthropic-executor.ts +13 -7
  715. package/src/web-search/backends.ts +108 -0
  716. package/src/web-search/exa-executor.ts +119 -0
  717. package/src/web-search/executor.ts +21 -6
  718. package/src/web-search/gemini-executor.ts +141 -0
  719. package/src/web-search/index.ts +150 -15
  720. package/src/web-search/loop.ts +284 -54
  721. package/src/web-search/parse.ts +125 -30
  722. package/src/web-search/sources.ts +60 -0
  723. package/src/web-search/xai-executor.ts +219 -0
  724. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  725. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  726. package/gui/dist/provider-icons/antigravity.svg +0 -1
  727. package/gui/dist/provider-icons/claude.svg +0 -1
  728. package/gui/dist/provider-icons/copilot.svg +0 -1
  729. package/gui/dist/provider-icons/cursor.svg +0 -2
  730. package/gui/dist/provider-icons/gemini.svg +0 -1
  731. package/gui/dist/provider-icons/grok-color.svg +0 -1
  732. package/gui/dist/provider-icons/kiro.svg +0 -14
  733. package/src/cli/internal-dispatch.ts +0 -20
package/src/router.ts CHANGED
@@ -1,19 +1,77 @@
1
1
  import type { CodexAccountMode, OcxConfig, OcxProviderConfig } from "./types";
2
- import { preservesPhysicalComboProvider, tryPickComboModel, type ComboPick } from "./combos";
3
- import { hasOwnProvider, resolveEnvValue } from "./config";
2
+ import {
3
+ getCombo,
4
+ isComboTargetInCooldown,
5
+ preservesPhysicalComboProvider,
6
+ targetKey,
7
+ tryPickComboModel,
8
+ type ComboPick,
9
+ } from "./combos";
10
+ import type { NormalizedComboConfig } from "./combos/types";
11
+ import { hasOwnProvider } from "./config/provider-name";
12
+ import { resolveEnvValue } from "./config";
4
13
  import { assertProviderDestinationAllowed } from "./lib/destination-policy";
5
14
  import { redactSecretString, redactUrlForLog } from "./lib/redact";
6
- import { PROVIDER_REGISTRY, providerCodexAccountMode, providerMatchesRegistryTransport } from "./providers/registry";
7
- import { LEGACY_CHATGPT_PROVIDER_ID, LEGACY_OPENAI_MULTI_PROVIDER_ID, OPENAI_API_PROVIDER_ID, OPENAI_CODEX_PROVIDER_ID } from "./providers/openai-tiers";
8
- import { decodeRoutedModelId, encodeRoutedModelId } from "./providers/slug-codec";
15
+ import {
16
+ PROVIDER_REGISTRY,
17
+ mergeRegistryStaticHeaders,
18
+ providerCodexAccountMode,
19
+ registryModelServiceTierCapabilityApplies,
20
+ } from "./providers/registry";
21
+ import { applyDirectReasoningEffortContracts, hasLegacyClinePassReasoningEfforts } from "./providers/derive";
22
+ import { cloneFastWire } from "./providers/fastwire";
23
+ import {
24
+ providerMatchesRegistryTransportWithStaticGuards,
25
+ providerSupportsLiveModelDiscovery,
26
+ } from "./providers/static-model-discovery";
27
+ import {
28
+ isCanonicalOpenAiForwardProvider,
29
+ LEGACY_CHATGPT_PROVIDER_ID,
30
+ LEGACY_OPENAI_MULTI_PROVIDER_ID,
31
+ OPENAI_API_PROVIDER_ID,
32
+ OPENAI_CODEX_PROVIDER_ID,
33
+ } from "./providers/openai-tiers";
34
+ import { decodeRoutedModelIdOrThrow, encodeRoutedModelId } from "./providers/slug-codec";
35
+ import { resolveModelAlias } from "./providers/default-aliases";
9
36
  import { getStaleCached } from "./codex/model-cache";
37
+ import { codexAccountNamespaceEntries } from "./codex/account-namespaces";
38
+ import {
39
+ buildRouteDecisionTrace,
40
+ type RouteDecisionKind,
41
+ type RouteDecisionTraceV1,
42
+ type TraceCandidateInput,
43
+ } from "./routing/trace";
44
+ import { getRoutingProfile, resolvePolicyProfileId } from "./routing/profile";
45
+ import { evaluatePolicyProfile, type PolicyRequestEvidence } from "./routing/evaluator";
46
+ import { assemblePolicyCandidateEvidence } from "./routing/compatibility/assemble";
47
+
48
+ export class NoEligiblePolicyCandidateError extends Error {
49
+ /** Evaluation trace (with per-candidate exclusions) when nothing qualified. */
50
+ readonly trace?: RouteDecisionTraceV1;
51
+
52
+ constructor(readonly profileId: string, trace?: RouteDecisionTraceV1) {
53
+ super(`No eligible candidates for policy profile: ${profileId}`);
54
+ this.name = "NoEligiblePolicyCandidateError";
55
+ this.trace = trace;
56
+ }
57
+ }
10
58
 
11
59
  export interface RouteResult {
12
60
  providerName: string;
13
61
  provider: OcxProviderConfig;
14
62
  modelId: string;
63
+ /** Which deterministic routing path produced this route (RI-01). */
64
+ routeKind: RouteDecisionKind;
65
+ /** Stable wire reason code for the selected route (RI-01). */
66
+ routeReason: string;
15
67
  codexAccountMode?: CodexAccountMode;
68
+ /** Exact account selected by an account-qualified native model. */
69
+ codexAccountId?: string;
70
+ /** Public namespace used by the account-qualified selector. */
71
+ codexAccountNamespace?: string;
16
72
  combo?: ComboPick;
73
+ /** Bounded route-decision trace (RI-01); never contains secrets. */
74
+ routeDecision?: RouteDecisionTraceV1;
17
75
  }
18
76
 
19
77
  const MODEL_PROVIDER_PATTERNS: Array<{ providerNames: string[]; prefixes: string[] }> = [
@@ -37,10 +95,15 @@ const MODEL_PROVIDER_PATTERNS: Array<{ providerNames: string[]; prefixes: string
37
95
  * last-known-good live /models cache (may be empty on a cold start; decode then passes
38
96
  * unknown ids through unchanged for an honest upstream error).
39
97
  */
40
- export function knownModelIdsForProvider(provName: string, prov: OcxProviderConfig): string[] {
98
+ export function knownModelIdsForProvider(
99
+ provName: string,
100
+ prov: OcxProviderConfig,
101
+ config?: Pick<OcxConfig, "customModels">,
102
+ ): string[] {
41
103
  const ids = new Set<string>();
42
104
  for (const id of prov.models ?? []) ids.add(id);
43
- const registry = providerMatchesRegistryTransport(provName, prov)
105
+ if (prov.defaultModel) ids.add(prov.defaultModel);
106
+ const registry = providerMatchesRegistryTransportWithStaticGuards(provName, prov)
44
107
  ? PROVIDER_REGISTRY.find(entry => entry.id === provName)
45
108
  : undefined;
46
109
  for (const id of registry?.models ?? []) ids.add(id);
@@ -53,10 +116,15 @@ export function knownModelIdsForProvider(provName: string, prov: OcxProviderConf
53
116
  registry?.modelDefaultReasoningEfforts,
54
117
  registry?.modelReasoningEffortMap,
55
118
  registry?.modelMaxOutputTokens,
119
+ registry?.modelSupportsServiceTier,
120
+ registry?.modelSupportsVerbosity,
56
121
  ]) {
57
122
  for (const id of Object.keys(map ?? {})) ids.add(id);
58
123
  }
59
124
  for (const cached of getStaleCached(provName) ?? []) ids.add(cached.id);
125
+ for (const model of config?.customModels ?? []) {
126
+ if (model.provider === provName && model.modelId) ids.add(model.modelId);
127
+ }
60
128
  return [...ids];
61
129
  }
62
130
 
@@ -201,22 +269,28 @@ function usableResolvedApiKey(apiKey: string | undefined): string | undefined {
201
269
  return typeof resolved === "string" && resolved.trim().length > 0 ? resolved : undefined;
202
270
  }
203
271
 
204
- function routedProviderConfig(providerName: string, provider: OcxProviderConfig): OcxProviderConfig {
272
+ export function routedProviderConfig(providerName: string, provider: OcxProviderConfig): OcxProviderConfig {
205
273
  const registryEntry = PROVIDER_REGISTRY.find(entry => entry.id === providerName);
206
- if (!registryEntry || !providerMatchesRegistryTransport(providerName, provider)) {
274
+ if (!registryEntry || !providerMatchesRegistryTransportWithStaticGuards(providerName, provider)) {
207
275
  assertProviderDestinationAllowed(providerName, provider);
208
276
  return { ...provider, apiKey: usableResolvedApiKey(provider.apiKey) };
209
277
  }
210
278
  const resolvedApiKey = usableResolvedApiKey(provider.apiKey);
279
+ const staticModelCatalog = !providerSupportsLiveModelDiscovery(providerName, provider);
280
+ const repairLegacyMimoFreeAuth = providerName === "mimo-free"
281
+ && staticModelCatalog
282
+ && (provider.authMode === undefined || provider.authMode === "local");
211
283
  const explicitKeyOverride = registryEntry.authKind === "oauth"
212
284
  && registryEntry.allowKeyAuthOverride === true
213
285
  && provider.authMode === "key"
214
286
  && resolvedApiKey !== undefined;
215
287
  const canonicalAuthMode = explicitKeyOverride
216
288
  ? "key"
217
- : registryEntry.authKind === "forward" || registryEntry.authKind === "oauth"
218
- ? registryEntry.authKind
219
- : provider.authMode === "forward" ? undefined : provider.authMode;
289
+ : repairLegacyMimoFreeAuth
290
+ ? "key"
291
+ : registryEntry.authKind === "forward" || registryEntry.authKind === "oauth"
292
+ ? registryEntry.authKind
293
+ : provider.authMode === "forward" ? undefined : provider.authMode;
220
294
  const reasoningEffortMap = mergeRecord(registryEntry.reasoningEffortMap, provider.reasoningEffortMap);
221
295
  const modelReasoningEffortMap = mergeNestedRecord(registryEntry.modelReasoningEffortMap, provider.modelReasoningEffortMap);
222
296
  const modelReasoningEfforts = mergeStringArrayRecord(registryEntry.modelReasoningEfforts, provider.modelReasoningEfforts);
@@ -225,10 +299,25 @@ function routedProviderConfig(providerName: string, provider: OcxProviderConfig)
225
299
  ? mergePositiveNumberCaps(registryEntry.modelContextWindows, provider.modelContextWindows)
226
300
  : mergeRecordFill(registryEntry.modelContextWindows, provider.modelContextWindows);
227
301
  const modelInputModalities = mergeRecordFill(registryEntry.modelInputModalities, provider.modelInputModalities);
302
+ // Registry static headers are documented as applying to every upstream request, so they are
303
+ // filled at resolve time rather than only at seed time: a config written before a header
304
+ // existed, or one carrying any header of its own, would otherwise never receive it. User
305
+ // headers win, matched case-insensitively so an override replaces rather than duplicates.
306
+ const headers = mergeRegistryStaticHeaders(registryEntry.staticHeaders, provider.headers);
228
307
  const modelMaxInputTokens = providerName === OPENAI_API_PROVIDER_ID
229
308
  ? mergePositiveNumberCaps(registryEntry.modelMaxInputTokens, provider.modelMaxInputTokens)
230
309
  : mergeRecordFill(registryEntry.modelMaxInputTokens, provider.modelMaxInputTokens);
231
310
  const modelMaxOutputTokens = mergeRecordFill(registryEntry.modelMaxOutputTokens, provider.modelMaxOutputTokens);
311
+ const modelSupportsServiceTier = mergeRecordFill(
312
+ registryModelServiceTierCapabilityApplies(registryEntry, provider)
313
+ ? registryEntry.modelSupportsServiceTier
314
+ : undefined,
315
+ provider.modelSupportsServiceTier,
316
+ );
317
+ const modelSupportsVerbosity = mergeRecordFill(
318
+ registryEntry.modelSupportsVerbosity,
319
+ provider.modelSupportsVerbosity,
320
+ );
232
321
  const noVisionModels = mergeStringArray(registryEntry.noVisionModels, provider.noVisionModels);
233
322
  const noReasoningModels = mergeStringArray(registryEntry.noReasoningModels, provider.noReasoningModels);
234
323
  const noTemperatureModels = mergeStringArray(registryEntry.noTemperatureModels, provider.noTemperatureModels);
@@ -236,6 +325,7 @@ function routedProviderConfig(providerName: string, provider: OcxProviderConfig)
236
325
  const noPenaltyModels = mergeStringArray(registryEntry.noPenaltyModels, provider.noPenaltyModels);
237
326
  const autoToolChoiceOnlyModels = mergeStringArray(registryEntry.autoToolChoiceOnlyModels, provider.autoToolChoiceOnlyModels);
238
327
  const preserveReasoningContentModels = mergeStringArray(registryEntry.preserveReasoningContentModels, provider.preserveReasoningContentModels);
328
+ const requiresReasoningPlaceholderModels = mergeStringArray(registryEntry.requiresReasoningPlaceholderModels, provider.requiresReasoningPlaceholderModels);
239
329
  const reasoningSplitModels = mergeStringArray(registryEntry.reasoningSplitModels, provider.reasoningSplitModels);
240
330
  const thinkingToggleModels = mergeStringArray(registryEntry.thinkingToggleModels, provider.thinkingToggleModels);
241
331
  const thinkingBudgetModels = mergeStringArray(registryEntry.thinkingBudgetModels, provider.thinkingBudgetModels);
@@ -252,15 +342,63 @@ function routedProviderConfig(providerName: string, provider: OcxProviderConfig)
252
342
  if (userBaseUrlIsResolved) warnIfBaseUrlDiscarded(providerName, userBaseUrl, baseUrl);
253
343
  assertProviderDestinationAllowed(providerName, { baseUrl, allowPrivateNetwork: provider.allowPrivateNetwork });
254
344
 
255
- return {
345
+ const resolved: OcxProviderConfig = {
256
346
  ...provider,
257
347
  adapter: registryEntry.adapter,
258
348
  baseUrl,
259
349
  ...(provider.responsesPath === undefined && registryEntry.responsesPath !== undefined
260
350
  ? { responsesPath: registryEntry.responsesPath }
261
351
  : {}),
352
+ ...(provider.requiresAdjacentResponsesToolResults === undefined
353
+ && registryEntry.requiresAdjacentResponsesToolResults !== undefined
354
+ ? { requiresAdjacentResponsesToolResults: registryEntry.requiresAdjacentResponsesToolResults }
355
+ : {}),
356
+ ...(provider.fastWire === undefined && registryEntry.fastWire !== undefined
357
+ ? {
358
+ fastWire: cloneFastWire(registryEntry.fastWire),
359
+ }
360
+ : {}),
361
+ ...(provider.supportsServiceTier === undefined && registryEntry.supportsServiceTier !== undefined
362
+ ? { supportsServiceTier: registryEntry.supportsServiceTier }
363
+ : {}),
364
+ // Registry-only web-search capability: without this backfill a saved provider row reaches
365
+ // the Responses adapter with the flag `undefined`, so the capability gate added in #2262
366
+ // reads "unclassified" and forwards Codex's OpenAI-only `web_search` config fields. xAI
367
+ // rejects the whole request before inference ("Argument not supported:
368
+ // external_web_access"), which killed every routed Grok turn on the Responses lane.
369
+ // enrichProviderFromRegistry() already fills this, but the request path resolves through
370
+ // routedProviderConfig() and never called it.
371
+ ...(provider.supportsOpenAiWebSearchToolFields === undefined
372
+ && registryEntry.supportsOpenAiWebSearchToolFields !== undefined
373
+ ? { supportsOpenAiWebSearchToolFields: registryEntry.supportsOpenAiWebSearchToolFields }
374
+ : {}),
375
+ ...(provider.supportsResponsesCustomTools === undefined && registryEntry.supportsResponsesCustomTools !== undefined
376
+ ? { supportsResponsesCustomTools: registryEntry.supportsResponsesCustomTools }
377
+ : {}),
378
+ ...(provider.preserveResponsesReasoningContent === undefined && registryEntry.preserveResponsesReasoningContent !== undefined
379
+ ? { preserveResponsesReasoningContent: registryEntry.preserveResponsesReasoningContent }
380
+ : {}),
381
+ // Registry-only client-facing repair policy (#938): fill only when the
382
+ // saved provider has no explicit policy; clone so runtime never aliases
383
+ // the registry constant.
384
+ ...(provider.responsesItemIdRepair === undefined && registryEntry.responsesItemIdRepair
385
+ ? {
386
+ responsesItemIdRepair: {
387
+ ...(registryEntry.responsesItemIdRepair.message ? { message: [...registryEntry.responsesItemIdRepair.message] } : {}),
388
+ ...(registryEntry.responsesItemIdRepair.reasoning ? { reasoning: [...registryEntry.responsesItemIdRepair.reasoning] } : {}),
389
+ ...(registryEntry.responsesItemIdRepair.repairMissingTerminalIds !== undefined
390
+ ? { repairMissingTerminalIds: registryEntry.responsesItemIdRepair.repairMissingTerminalIds }
391
+ : {}),
392
+ ...(registryEntry.responsesItemIdRepair.repairInvalidIds !== undefined
393
+ ? { repairInvalidIds: registryEntry.responsesItemIdRepair.repairInvalidIds }
394
+ : {}),
395
+ },
396
+ }
397
+ : {}),
262
398
  authMode: canonicalAuthMode,
263
399
  apiKey: resolvedApiKey,
400
+ ...(staticModelCatalog ? { liveModels: false } : {}),
401
+ ...(headers ? { headers } : {}),
264
402
  // Backfill the Google wire mode + Vertex project/location from the registry when the user
265
403
  // config omits them, so a minimal `google-vertex`/`google-antigravity` entry still routes
266
404
  // through the correct branch (CCA/Vertex) instead of falling back to AI Studio.
@@ -268,7 +406,10 @@ function routedProviderConfig(providerName: string, provider: OcxProviderConfig)
268
406
  ...(provider.project === undefined && registryEntry.project !== undefined ? { project: registryEntry.project } : {}),
269
407
  ...(provider.location === undefined && registryEntry.location !== undefined ? { location: registryEntry.location } : {}),
270
408
  ...(provider.contextWindow === undefined && registryEntry.contextWindow !== undefined ? { contextWindow: registryEntry.contextWindow } : {}),
271
- ...(provider.reasoningEfforts === undefined && registryEntry.reasoningEfforts !== undefined ? { reasoningEfforts: registryEntry.reasoningEfforts } : {}),
409
+ ...((provider.reasoningEfforts === undefined || hasLegacyClinePassReasoningEfforts(providerName, provider))
410
+ && registryEntry.reasoningEfforts !== undefined
411
+ ? { reasoningEfforts: [...registryEntry.reasoningEfforts] }
412
+ : {}),
272
413
  ...(provider.escapeBuiltinToolNames === undefined && registryEntry.escapeBuiltinToolNames !== undefined ? { escapeBuiltinToolNames: registryEntry.escapeBuiltinToolNames } : {}),
273
414
  ...(provider.keyOptional === undefined && registryEntry.keyOptional !== undefined ? { keyOptional: registryEntry.keyOptional } : {}),
274
415
  ...(provider.modelSuffixBracketStrip === undefined && registryEntry.modelSuffixBracketStrip !== undefined ? { modelSuffixBracketStrip: registryEntry.modelSuffixBracketStrip } : {}),
@@ -276,6 +417,13 @@ function routedProviderConfig(providerName: string, provider: OcxProviderConfig)
276
417
  // opt-in, while an explicit user `false` keeps overriding registry `true`.
277
418
  ...(provider.parallelToolCalls === undefined && registryEntry.parallelToolCalls !== undefined ? { parallelToolCalls: registryEntry.parallelToolCalls } : {}),
278
419
  ...(provider.promptCacheKey === undefined && registryEntry.promptCacheKey !== undefined ? { promptCacheKey: registryEntry.promptCacheKey } : {}),
420
+ ...(provider.chatServiceTier === undefined && registryEntry.chatServiceTier !== undefined ? { chatServiceTier: registryEntry.chatServiceTier } : {}),
421
+ ...(provider.openaiChatEofTolerance === undefined && registryEntry.openaiChatEofTolerance !== undefined
422
+ ? { openaiChatEofTolerance: registryEntry.openaiChatEofTolerance }
423
+ : {}),
424
+ ...(provider.reasoningWireFormat === undefined && registryEntry.reasoningWireFormat !== undefined
425
+ ? { reasoningWireFormat: registryEntry.reasoningWireFormat }
426
+ : {}),
279
427
  ...(provider.defaultMaxOutputTokens === undefined && registryEntry.defaultMaxOutputTokens !== undefined
280
428
  ? { defaultMaxOutputTokens: registryEntry.defaultMaxOutputTokens }
281
429
  : {}),
@@ -283,6 +431,8 @@ function routedProviderConfig(providerName: string, provider: OcxProviderConfig)
283
431
  ...(modelInputModalities ? { modelInputModalities } : {}),
284
432
  ...(modelMaxInputTokens ? { modelMaxInputTokens } : {}),
285
433
  ...(modelMaxOutputTokens ? { modelMaxOutputTokens } : {}),
434
+ ...(modelSupportsServiceTier ? { modelSupportsServiceTier } : {}),
435
+ ...(modelSupportsVerbosity ? { modelSupportsVerbosity } : {}),
286
436
  ...(modelReasoningEfforts ? { modelReasoningEfforts } : {}),
287
437
  ...(modelDefaultReasoningEfforts ? { modelDefaultReasoningEfforts } : {}),
288
438
  ...(reasoningEffortMap ? { reasoningEffortMap } : {}),
@@ -294,10 +444,13 @@ function routedProviderConfig(providerName: string, provider: OcxProviderConfig)
294
444
  ...(noPenaltyModels ? { noPenaltyModels } : {}),
295
445
  ...(autoToolChoiceOnlyModels ? { autoToolChoiceOnlyModels } : {}),
296
446
  ...(preserveReasoningContentModels ? { preserveReasoningContentModels } : {}),
447
+ ...(requiresReasoningPlaceholderModels ? { requiresReasoningPlaceholderModels } : {}),
297
448
  ...(reasoningSplitModels ? { reasoningSplitModels } : {}),
298
449
  ...(thinkingToggleModels ? { thinkingToggleModels } : {}),
299
450
  ...(thinkingBudgetModels ? { thinkingBudgetModels } : {}),
300
451
  };
452
+ applyDirectReasoningEffortContracts(registryEntry, resolved, provider);
453
+ return resolved;
301
454
  }
302
455
 
303
456
  function activeProviderEntries(config: OcxConfig): [string, OcxProviderConfig][] {
@@ -315,6 +468,34 @@ export class NoEnabledOpenAiProviderError extends Error {
315
468
  }
316
469
  }
317
470
 
471
+ /**
472
+ * One immutable selection trace for a combo request: built once from the
473
+ * initial pick, before any child dispatch. Fallback execution stays in the
474
+ * usage entry's `attempts[]`; the trace never changes after selection.
475
+ */
476
+ export function comboRouteDecisionTrace(
477
+ config: OcxConfig,
478
+ comboId: string,
479
+ pick: ComboPick,
480
+ requestedModel: string,
481
+ ): RouteDecisionTraceV1 {
482
+ const combo = getCombo(config, comboId);
483
+ return buildRouteDecisionTrace({
484
+ requestedModel,
485
+ routeKind: "combo",
486
+ selected: {
487
+ provider: pick.target.provider,
488
+ model: pick.target.model,
489
+ reason: "combo-pick",
490
+ candidateIndex: pick.targetIndex,
491
+ ...(combo
492
+ ? { tieBreak: combo.strategy === "round-robin" ? "round-robin" : "failover" }
493
+ : {}),
494
+ },
495
+ candidates: combo ? comboRouteCandidates(config, pick, combo) : undefined,
496
+ });
497
+ }
498
+
318
499
  // Codex uses a small number of control-plane model ids that are not part of the public GPT/o
319
500
  // naming families. Keep this exact: a broad `codex-*` rule could capture a third-party model.
320
501
  const CODEX_INTERNAL_OPENAI_MODELS = new Set(["codex-auto-review"]);
@@ -324,25 +505,140 @@ function isBareOpenAiFamilyModel(modelId: string): boolean {
324
505
  && (/^(?:gpt-|o1-|o3-|o4-)/.test(modelId) || CODEX_INTERNAL_OPENAI_MODELS.has(modelId));
325
506
  }
326
507
 
327
- function routeResult(providerName: string, provider: OcxProviderConfig, modelId: string): RouteResult {
508
+ function routeResult(
509
+ providerName: string,
510
+ provider: OcxProviderConfig,
511
+ modelId: string,
512
+ routeKind: RouteDecisionKind,
513
+ routeReason: string,
514
+ ): RouteResult {
328
515
  const codexAccountMode = providerCodexAccountMode(providerName, provider);
329
516
  return {
330
517
  providerName,
331
518
  provider: routedProviderConfig(providerName, provider),
332
519
  modelId,
520
+ routeKind,
521
+ routeReason,
333
522
  ...(codexAccountMode ? { codexAccountMode } : {}),
334
523
  };
335
524
  }
336
525
 
337
- function routeModelInternal(config: OcxConfig, modelId: string, bypassCombos: boolean): RouteResult {
526
+ /**
527
+ * Candidate evidence for a combo route: every configured target with its
528
+ * selection-time eligibility and exclusion reasons. Purely observational; the
529
+ * pick already happened and this never re-selects.
530
+ */
531
+ function comboRouteCandidates(
532
+ config: OcxConfig,
533
+ pick: NonNullable<RouteResult["combo"]>,
534
+ combo: NormalizedComboConfig,
535
+ ): TraceCandidateInput[] {
536
+ const now = Date.now();
537
+ return combo.targets.map((target, index) => {
538
+ const key = targetKey(target);
539
+ const provider = config.providers[target.provider];
540
+ const configured = provider !== undefined;
541
+ const enabled = configured && provider.disabled !== true;
542
+ const inCooldown = isComboTargetInCooldown(pick.comboId, target, now);
543
+ const isSelected = index === pick.targetIndex;
544
+ // The pick's `attempted` list includes the winner itself; only non-selected
545
+ // targets can be "already-attempted" (fallback picks exclude earlier tries).
546
+ const alreadyAttempted = !isSelected && pick.attempted.includes(key);
547
+ const exclusions: TraceCandidateInput["exclusions"] = [];
548
+ if (!configured) exclusions.push({ code: "unconfigured" });
549
+ if (configured && !enabled) exclusions.push({ code: "disabled" });
550
+ if (inCooldown) exclusions.push({ code: "cooldown" });
551
+ if (isSelected && inCooldown) exclusions.push({ code: "selected-despite-cooldown" });
552
+ if (!isSelected && alreadyAttempted && exclusions.length === 0) {
553
+ exclusions.push({ code: "already-attempted" });
554
+ }
555
+ if (!isSelected && exclusions.length === 0) exclusions.push({ code: "not-selected" });
556
+ return {
557
+ provider: target.provider,
558
+ model: target.model,
559
+ eligible: enabled && !inCooldown && !alreadyAttempted,
560
+ exclusions,
561
+ };
562
+ });
563
+ }
564
+
565
+ function routeModelInternal(
566
+ config: OcxConfig,
567
+ modelId: string,
568
+ bypassCombos: boolean,
569
+ policyEvidence?: PolicyRequestEvidence,
570
+ ): RouteResult {
571
+ const slash = modelId.indexOf("/");
572
+ // Policy namespace is system-reserved: an explicit `policy/<id>` or a
573
+ // configured profile alias executes the policy evaluator and routes the
574
+ // selected candidate. Only explicit requests reach this branch; concrete
575
+ // recursive targets skip policy resolution entirely (bypassCombos) so an
576
+ // alias matching a selected candidate can never recurse, and a
577
+ // `policy/<id>` without a configured profile falls through to normal
578
+ // provider/default resolution instead of failing.
579
+ const policyId = !bypassCombos ? resolvePolicyProfileId(config, modelId) : null;
580
+ const profile = policyId ? getRoutingProfile(config, policyId) : undefined;
581
+ if (profile && policyId) {
582
+ // One clock read per decision keeps candidate evidence, exclusions, and
583
+ // scores mutually consistent and reproducible.
584
+ const now = Date.now();
585
+ const candidateEvidence = assemblePolicyCandidateEvidence(config, profile, now, {
586
+ routedProviderConfig,
587
+ });
588
+ const evaluation = evaluatePolicyProfile(config, policyId, policyEvidence ?? {}, candidateEvidence, now);
589
+ if (evaluation.selectedIndex === null) {
590
+ throw new NoEligiblePolicyCandidateError(policyId, evaluation.trace);
591
+ }
592
+ const selected = evaluation.candidates[evaluation.selectedIndex]!;
593
+ const concrete = `${selected.provider}/${selected.model}`;
594
+ const routed = routeModelInternal(config, concrete, true);
595
+ return {
596
+ ...routed,
597
+ routeKind: "policy" as const,
598
+ routeReason: "policy-selected",
599
+ routeDecision: evaluation.trace,
600
+ };
601
+ }
602
+ if (slash > 0) {
603
+ const namespace = modelId.slice(0, slash);
604
+ const binding = codexAccountNamespaceEntries(config)
605
+ .find(([candidate]) => candidate === namespace);
606
+ if (binding) {
607
+ const nativeModelId = modelId.slice(slash + 1);
608
+ if (!isBareOpenAiFamilyModel(nativeModelId)) {
609
+ throw new Error(`Codex account namespace ${namespace} only supports native OpenAI model ids`);
610
+ }
611
+ const provider = config.providers[OPENAI_CODEX_PROVIDER_ID];
612
+ if (!provider || provider.disabled === true) {
613
+ throw new NoEnabledOpenAiProviderError(nativeModelId);
614
+ }
615
+ // Registry routing backfills an omitted authMode on the built-in OpenAI row to forward.
616
+ // Mirror only that default here; explicit non-forward modes still fail closed.
617
+ const providerForCanonicalCheck = provider.authMode === undefined
618
+ ? { ...provider, authMode: "forward" as const }
619
+ : provider;
620
+ if (!isCanonicalOpenAiForwardProvider(providerForCanonicalCheck)) {
621
+ throw new NoEnabledOpenAiProviderError(nativeModelId);
622
+ }
623
+ return {
624
+ ...routeResult(OPENAI_CODEX_PROVIDER_ID, provider, nativeModelId, "explicit-account", "account-namespace"),
625
+ // Exact account injection uses the pool credential machinery even when the canonical
626
+ // provider is globally Direct. The fixed id bypasses pool selection entirely.
627
+ codexAccountMode: "pool",
628
+ codexAccountId: binding[1],
629
+ codexAccountNamespace: namespace,
630
+ };
631
+ }
632
+ }
633
+
338
634
  if (!bypassCombos && !preservesPhysicalComboProvider(config)) {
339
635
  const combo = tryPickComboModel(config, modelId);
340
636
  if (combo) {
341
637
  const concrete = `${combo.target.provider}/${combo.target.model}`;
342
638
  // The selected target is already a concrete provider/model reference. Resolve it without
343
639
  // consulting combo aliases again, otherwise an alias that shadows the target can recurse.
344
- const routed = routeModelInternal(config, concrete, true);
345
- return { ...routed, combo };
640
+ const routed = routeModelInternal(config, concrete, true, undefined);
641
+ return { ...routed, combo, routeKind: "combo" as const, routeReason: "combo-pick" };
346
642
  }
347
643
  }
348
644
 
@@ -350,36 +646,58 @@ function routeModelInternal(config: OcxConfig, modelId: string, bypassCombos: bo
350
646
  // Only triggers when the prefix matches a CONFIGURED provider, so genuine
351
647
  // slash-containing model ids (e.g. "anthropic/claude-...") fall through when
352
648
  // no such provider exists.
353
- const slash = modelId.indexOf("/");
354
649
  if (slash > 0) {
355
- const provName = modelId.slice(0, slash);
650
+ const requestedProvider = modelId.slice(0, slash);
651
+ const provName = hasOwnProvider(config.providers, requestedProvider)
652
+ ? requestedProvider
653
+ : Object.entries(config.providers).find(([, provider]) =>
654
+ typeof provider.alias === "string" && provider.alias.toLowerCase() === requestedProvider.toLowerCase())?.[0];
655
+ if (!provName) {
656
+ // A genuine slash-containing native model id still falls through unchanged.
657
+ } else {
356
658
  if (provName === LEGACY_CHATGPT_PROVIDER_ID || provName === LEGACY_OPENAI_MULTI_PROVIDER_ID) {
357
659
  throw new Error(`No provider configured for model: ${modelId}`);
358
660
  }
359
661
  if (hasOwnProvider(config.providers, provName)) {
360
662
  const prov = config.providers[provName];
361
663
  if (prov.disabled === true) throw new Error(`Provider is disabled: ${provName}`);
362
- const known = knownModelIdsForProvider(provName, prov);
664
+ const known = knownModelIdsForProvider(provName, prov, config);
363
665
  // Self-namespaced native id — the vendor segment equals the provider id, so the FULL ref is
364
666
  // itself a known model (e.g. orcarouter/auto). Route it whole instead of stripping to the
365
667
  // remainder, which would send a bare `auto` the upstream cannot resolve.
366
- if (known.includes(modelId)) return routeResult(provName, prov, modelId);
668
+ if (known.includes(modelId)) {
669
+ return routeResult(provName, prov, modelId, "explicit-provider", "explicit-provider-namespace");
670
+ }
367
671
  // Codex-facing alias ids (`provider/vendor-model`) decode back to the native
368
672
  // slash id via an exact known-id lookup; raw full-slash selectors keep working.
369
- return routeResult(provName, prov, decodeRoutedModelId(modelId.slice(slash + 1), known));
673
+ const requestedModel = modelId.slice(slash + 1);
674
+ const decoded = decodeRoutedModelIdOrThrow(requestedModel, known);
675
+ const nativeModel = known.includes(decoded)
676
+ ? decoded
677
+ : resolveModelAlias(config, prov, known, requestedModel) ?? decoded;
678
+ return routeResult(
679
+ provName,
680
+ prov,
681
+ nativeModel,
682
+ "explicit-provider",
683
+ "explicit-provider-namespace",
684
+ );
685
+ }
370
686
  }
371
687
  }
372
688
 
373
689
  if (isBareOpenAiFamilyModel(modelId)) {
374
690
  const provider = config.providers[OPENAI_CODEX_PROVIDER_ID];
375
- if (provider && provider.disabled !== true) return routeResult(OPENAI_CODEX_PROVIDER_ID, provider, modelId);
691
+ if (provider && provider.disabled !== true) {
692
+ return routeResult(OPENAI_CODEX_PROVIDER_ID, provider, modelId, "native", "native-family");
693
+ }
376
694
  throw new NoEnabledOpenAiProviderError(modelId);
377
695
  }
378
696
 
379
697
  for (const [provName, prov] of activeProviderEntries(config)) {
380
698
  if (prov.defaultModel === modelId
381
699
  || (typeof prov.defaultModel === "string" && encodeRoutedModelId(prov.defaultModel) === modelId)) {
382
- return routeResult(provName, prov, prov.defaultModel as string);
700
+ return routeResult(provName, prov, prov.defaultModel as string, "explicit-provider", "configured-default-model");
383
701
  }
384
702
  }
385
703
 
@@ -389,36 +707,92 @@ function routeModelInternal(config: OcxConfig, modelId: string, bypassCombos: bo
389
707
  for (const [provName, prov] of activeProviderEntries(config)) {
390
708
  if (prov.models && Array.isArray(prov.models)) {
391
709
  const hit = (prov.models as string[]).find(id => id === modelId || encodeRoutedModelId(id) === modelId);
392
- if (hit !== undefined) return routeResult(provName, prov, hit);
710
+ if (hit !== undefined) {
711
+ return routeResult(provName, prov, hit, "explicit-provider", "configured-model-list");
712
+ }
393
713
  }
394
714
  }
395
715
 
716
+ const aliasMatches: Array<{ provider: string; model: string; qualified: string }> = [];
717
+ for (const [provName, prov] of activeProviderEntries(config)) {
718
+ const known = knownModelIdsForProvider(provName, prov, config);
719
+ const native = resolveModelAlias(config, prov, known, modelId);
720
+ if (native) aliasMatches.push({
721
+ provider: provName,
722
+ model: native,
723
+ qualified: `${prov.alias || provName}/${modelId}`,
724
+ });
725
+ }
726
+ if (aliasMatches.length > 1) {
727
+ throw new Error(`model alias '${modelId}' is ambiguous: ${aliasMatches.map(match => match.qualified).sort().join(", ")}`);
728
+ }
729
+ if (aliasMatches[0]) {
730
+ const match = aliasMatches[0];
731
+ return routeResult(match.provider, config.providers[match.provider], match.model, "explicit-provider", "model-alias");
732
+ }
733
+
396
734
  if (config.defaultProvider === LEGACY_CHATGPT_PROVIDER_ID) {
397
735
  throw new Error(`No provider configured for model: ${modelId}`);
398
736
  }
399
737
  if (hasOwnProvider(config.providers, config.defaultProvider)) {
400
738
  const defaultProv = config.providers[config.defaultProvider];
401
739
  if (defaultProv.disabled === true) throw new Error(`Default provider is disabled: ${config.defaultProvider}`);
402
- return routeResult(config.defaultProvider, defaultProv, modelId);
740
+ return routeResult(config.defaultProvider, defaultProv, modelId, "default-provider", "default-provider");
403
741
  }
404
742
 
405
743
  throw new Error(`No provider configured for model: ${modelId}`);
406
744
  }
407
745
 
408
- export function routeModel(config: OcxConfig, modelId: string): RouteResult {
409
- return routeModelInternal(config, modelId, false);
746
+ export function routeModel(
747
+ config: OcxConfig,
748
+ modelId: string,
749
+ policyEvidence?: PolicyRequestEvidence,
750
+ ): RouteResult {
751
+ const route = routeModelInternal(config, modelId, false, policyEvidence);
752
+ // Policy routes carry a full evaluation trace already; never rebuild it.
753
+ if (route.routeDecision) return route;
754
+ const accountRef = route.codexAccountNamespace;
755
+ const combo = route.combo ? getCombo(config, route.combo.comboId) : undefined;
756
+ route.routeDecision = buildRouteDecisionTrace({
757
+ requestedModel: modelId,
758
+ routeKind: route.routeKind,
759
+ selected: {
760
+ provider: route.providerName,
761
+ model: route.modelId,
762
+ ...(accountRef ? { accountRef } : {}),
763
+ reason: route.routeReason,
764
+ ...(route.combo ? { candidateIndex: route.combo.targetIndex } : {}),
765
+ ...(combo
766
+ ? { tieBreak: combo.strategy === "round-robin" ? "round-robin" : "failover" }
767
+ : {}),
768
+ },
769
+ candidates: route.routeKind === "combo" && route.combo && combo
770
+ ? comboRouteCandidates(config, route.combo, combo)
771
+ : undefined,
772
+ });
773
+ return route;
774
+ }
775
+
776
+ /** Resolve a combo-selected provider/model target without consulting public combo aliases again. */
777
+ export function routeConcreteModel(config: OcxConfig, modelId: string): RouteResult {
778
+ return routeModelInternal(config, modelId, true, undefined);
410
779
  }
411
780
 
412
781
  function routeByKnownModelPattern(config: OcxConfig, modelId: string): RouteResult | undefined {
413
782
  for (const { providerNames, prefixes } of MODEL_PROVIDER_PATTERNS) {
414
783
  if (prefixes.some(prefix => modelId.startsWith(prefix))) {
415
784
  const matchingProvider = Object.entries(config.providers).find(
416
- ([name]) => providerNames.some(providerName => name === providerName || name.startsWith(`${providerName}-`))
785
+ ([name, prov]) => prov.disabled !== true && providerNames.some(providerName => name === providerName || name.startsWith(`${providerName}-`))
417
786
  );
418
787
  if (matchingProvider) {
419
788
  const [provName, prov] = matchingProvider;
420
- return routeResult(provName, prov, modelId);
789
+ return routeResult(provName, prov, modelId, "explicit-provider", "model-pattern");
421
790
  }
791
+ // Deliberately no "first provider with an Anthropic adapter" fallback here. Picking by
792
+ // object insertion order, without checking `models`, `selectedModels`, `disabledModels` or
793
+ // discovery state, silently moves a request onto a provider the operator never chose, with
794
+ // its own privacy and billing consequences (#1697). A classifier turn that needs a specific
795
+ // target gets it from operator-declared `claudeCode.classifierModel` / `classifierFallbacks`.
422
796
  }
423
797
  }
424
798
  return undefined;