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