@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
@@ -1,11 +1,4 @@
1
- import { createAnthropicAdapter } from "../adapters/anthropic";
2
- import { createAzureAdapter } from "../adapters/azure";
3
- import { createCursorAdapter } from "../adapters/cursor";
4
- import { createGoogleAdapter } from "../adapters/google";
5
- import { createKiroAdapter } from "../adapters/kiro";
6
- import { createMimoFreeAdapter } from "../adapters/mimo-free";
7
- import { createOpenAIChatAdapter } from "../adapters/openai-chat";
8
- import { createResponsesPassthroughAdapter } from "../adapters/openai-responses";
1
+ import { createRegisteredAdapter } from "../adapters/registry";
9
2
  import type { OcxProviderConfig } from "../types";
10
3
  import { isWirePinnedModel, MODEL_ADAPTER_OVERRIDE_ALLOWED, pinnedWireAdapter } from "../types";
11
4
  import { isCanonicalOpenAiForwardProvider } from "../providers/openai-tiers";
@@ -56,25 +49,5 @@ export function resolveWireProtocolOverride(
56
49
 
57
50
  /** Build the provider adapter for a resolved provider config. */
58
51
  export function resolveAdapter(providerConfig: OcxProviderConfig, cacheRetention?: "none" | "short" | "long") {
59
- switch (providerConfig.adapter) {
60
- case "openai-chat":
61
- return createOpenAIChatAdapter(providerConfig);
62
- case "anthropic":
63
- return createAnthropicAdapter(providerConfig, cacheRetention);
64
- case "openai-responses":
65
- return createResponsesPassthroughAdapter(providerConfig);
66
- case "google":
67
- return createGoogleAdapter(providerConfig);
68
- case "kiro":
69
- return createKiroAdapter(providerConfig);
70
- case "azure":
71
- case "azure-openai":
72
- return createAzureAdapter(providerConfig);
73
- case "cursor":
74
- return createCursorAdapter(providerConfig);
75
- case "mimo-free":
76
- return createMimoFreeAdapter(providerConfig);
77
- default:
78
- throw new Error(`Unknown adapter: ${providerConfig.adapter}`);
79
- }
52
+ return createRegisteredAdapter(providerConfig, { cacheRetention });
80
53
  }
@@ -4,18 +4,28 @@ import {
4
4
  apiKeyTransportConfigError,
5
5
  booleanRecordConfigError,
6
6
  modelAdapterRecordConfigError,
7
+ modelPreferHostedToolsConfigError,
7
8
  codexAutoStartEnabled,
9
+ nonBlankStringArrayConfigError,
8
10
  positiveIntegerConfigError,
9
11
  positiveIntegerRecordConfigError,
10
12
  providerBaseUrlConfigError,
11
13
  providerHeadersConfigError,
14
+ providerModelCostsConfigError,
12
15
  reasoningSummaryDeliveryRecordConfigError,
16
+ retryOn429PolicyConfigError,
17
+ requestPacingConfigError,
18
+ sanitizeModelCostsForDisplay,
19
+ upstreamHttpVersionConfigError,
13
20
  } from "../config";
14
21
  import { providerDestinationConfigError } from "../lib/destination-policy";
15
- import { getProviderRegistryEntry, providerCodexAccountMode, providerMatchesRegistryTransport, registryEntryForProviderDestination } from "../providers/registry";
22
+ import { redactSecretString } from "../lib/redact";
23
+ import { effectiveGoogleMode, getProviderRegistryEntry, providerCodexAccountMode, providerMatchesRegistryTransport, registryEntryForProviderDestination } from "../providers/registry";
16
24
  import { providerConfigSeed } from "../providers/derive";
17
25
  import type { OcxConfig, OcxProviderConfig } from "../types";
18
26
  import { openRouterRoutingConfigError } from "../providers/openrouter-routing";
27
+ import { googleVertexLocationConfigError } from "../providers/google-vertex-location";
28
+ import { xaiResponsesOptInState } from "../providers/xai-responses-opt-in";
19
29
 
20
30
  let _corsOrigin = "http://localhost:10100";
21
31
  export function setCorsOrigin(port: number): void { _corsOrigin = `http://localhost:${port}`; }
@@ -69,7 +79,7 @@ export function isSameOriginAsRequest(req: Request, origin: string): boolean {
69
79
  }
70
80
  }
71
81
 
72
- export function isAllowedRequestOrigin(req: Request, config: OcxConfig): boolean {
82
+ export function isAllowedRequestOrigin(req: Request, config: RequestPolicyView): boolean {
73
83
  const origin = req.headers.get("Origin");
74
84
  if (!isApiAuthRequired(config)) {
75
85
  if (!isLoopbackRequestHost(req.headers.get("Host"))) return false;
@@ -78,17 +88,30 @@ export function isAllowedRequestOrigin(req: Request, config: OcxConfig): boolean
78
88
  return !origin || isLoopbackOriginValue(origin) || isSameOriginAsRequest(req, origin) || isExtraAllowedOrigin(origin, config);
79
89
  }
80
90
 
81
- function isExtraAllowedOrigin(origin: string, cfg: OcxConfig): boolean {
91
+ function isExtraAllowedOrigin(origin: string, cfg: RequestPolicyView): boolean {
82
92
  if (!cfg.corsAllowOrigins?.length) return false;
93
+ const parsedOrigin = comparableOrigin(origin);
83
94
  return cfg.corsAllowOrigins.some(allowed => {
84
- try {
85
- return new URL(allowed).origin === new URL(origin).origin;
86
- } catch {
87
- return allowed === origin;
88
- }
95
+ const parsedAllowed = comparableOrigin(allowed);
96
+ return parsedOrigin !== null && parsedAllowed !== null
97
+ ? parsedAllowed === parsedOrigin
98
+ : allowed === origin;
89
99
  });
90
100
  }
91
101
 
102
+ function comparableOrigin(value: string): string | null {
103
+ try {
104
+ const parsed = new URL(value);
105
+ if (parsed.origin !== "null") return parsed.origin;
106
+ // WHATWG URL exposes authority-based custom schemes (for example browser
107
+ // extensions) as opaque `null` origins. Compare their scheme + authority so
108
+ // one allowlisted extension cannot admit every other opaque origin.
109
+ return parsed.host ? `${parsed.protocol}//${parsed.host}` : null;
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+
92
115
  export function managementRequestOrigin(req: Request, config: OcxConfig): string | null {
93
116
  const host = req.headers.get("Host");
94
117
  const parsedHost = parseHttpHost(host);
@@ -119,17 +142,53 @@ export function browserSecurityHeaders(): Record<string, string> {
119
142
  };
120
143
  }
121
144
 
122
- export function corsHeaders(req?: Request, config?: OcxConfig): Record<string, string> {
145
+ /**
146
+ * Baseline data-plane request headers. ChatGPT-Account-Id is required for browser/Electron
147
+ * ChatGPT & Codex App voice preflights (direct forward auth matches the bearer to this account
148
+ * id). The OpenAI-Alpha .. X-OAI-Attestation block covers GPT-Live voice protocol headers
149
+ * relayed by the /v1/live call-create path.
150
+ */
151
+ const STATIC_ALLOWED_REQUEST_HEADERS =
152
+ "Content-Type, Authorization, X-OpenCodex-API-Key, X-Api-Key, Anthropic-Version, Anthropic-Beta, ChatGPT-Account-Id, OpenAI-Alpha, X-Session-Id, Session-Id, Thread-Id, Originator, X-OAI-Attestation";
153
+
154
+ /**
155
+ * A fixed allow-list cannot enumerate vendor telemetry headers: the OpenAI and Anthropic
156
+ * browser SDKs send `X-Stainless-*` describing runtime and retry state, and the browser blocks
157
+ * the real request when the preflight omits even one of them (#1773).
158
+ *
159
+ * Echo what an already-allowed origin asked for, and fall back to the static list otherwise.
160
+ * The echo is deliberately gated on the origin check that ran first: this widens which headers
161
+ * an admitted caller may send, never which origins are admitted, and it grants nothing to an
162
+ * origin that would have been rejected anyway. Authentication is unchanged — the preflight
163
+ * itself carries no credential and produces no auth or account-pool side effect.
164
+ */
165
+ function allowedRequestHeaders(req?: Request): string {
166
+ const requested = req?.headers.get("Access-Control-Request-Headers")?.trim();
167
+ if (!requested) return STATIC_ALLOWED_REQUEST_HEADERS;
168
+ const seen = new Set(STATIC_ALLOWED_REQUEST_HEADERS.split(",").map(h => h.trim().toLowerCase()));
169
+ const extra: string[] = [];
170
+ for (const raw of requested.split(",")) {
171
+ const name = raw.trim();
172
+ // Header names are case-insensitive on the wire, so normalize before de-duplicating;
173
+ // echo the caller's spelling for the ones we add.
174
+ if (!name || seen.has(name.toLowerCase())) continue;
175
+ seen.add(name.toLowerCase());
176
+ extra.push(name);
177
+ }
178
+ return extra.length === 0 ? STATIC_ALLOWED_REQUEST_HEADERS : `${STATIC_ALLOWED_REQUEST_HEADERS}, ${extra.join(", ")}`;
179
+ }
180
+
181
+ export function corsHeaders(req?: Request, config?: RequestPolicyView): Record<string, string> {
123
182
  const origin = req?.headers.get("Origin");
124
- const allowOrigin = origin && req && config && isAllowedRequestOrigin(req, config) ? origin : _corsOrigin;
183
+ const originAllowed = Boolean(origin && req && config && isAllowedRequestOrigin(req, config));
184
+ const allowOrigin = originAllowed && origin ? origin : _corsOrigin;
125
185
  return {
126
186
  "Access-Control-Allow-Origin": allowOrigin,
127
187
  "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
128
- // ChatGPT-Account-Id is required for browser/Electron ChatGPT & Codex App voice preflights
129
- // (direct forward auth matches the bearer to this account id). The OpenAI-Alpha .. X-OAI-Attestation
130
- // block covers GPT-Live voice protocol headers relayed by the /v1/live call-create path.
131
- "Access-Control-Allow-Headers": "Content-Type, Authorization, X-OpenCodex-API-Key, X-Api-Key, Anthropic-Version, Anthropic-Beta, ChatGPT-Account-Id, OpenAI-Alpha, X-Session-Id, Session-Id, Thread-Id, Originator, X-OAI-Attestation",
132
- "Vary": "Origin",
188
+ "Access-Control-Allow-Headers": allowedRequestHeaders(originAllowed ? req : undefined),
189
+ // A response that varies by the request's headers must say so, or a shared cache can
190
+ // replay one client's allow-list to a client that asked for different headers.
191
+ "Vary": "Origin, Access-Control-Request-Headers",
133
192
  ...browserSecurityHeaders(),
134
193
  };
135
194
  }
@@ -143,7 +202,7 @@ export function managementCorsHeaders(req?: Request, config?: OcxConfig): Record
143
202
  return headers;
144
203
  }
145
204
 
146
- export function withCors(response: Response, req: Request, config: OcxConfig): Response {
205
+ export function withCors(response: Response, req: Request, config: RequestPolicyView): Response {
147
206
  const headers = new Headers(response.headers);
148
207
  for (const [name, value] of Object.entries(corsHeaders(req, config))) {
149
208
  headers.set(name, value);
@@ -167,14 +226,18 @@ export function withManagementCors(response: Response, req: Request, config: Ocx
167
226
  });
168
227
  }
169
228
 
170
- export function jsonResponse(data: unknown, status = 200, req?: Request, config?: OcxConfig): Response {
229
+ export function jsonResponse(data: unknown, status = 200, req?: Request, config?: RequestPolicyView): Response {
171
230
  return new Response(JSON.stringify(data), {
172
231
  status,
173
232
  headers: { "Content-Type": "application/json", ...corsHeaders(req, config) },
174
233
  });
175
234
  }
176
235
 
177
- export function configuredApiAuthToken(_config: OcxConfig): string | undefined {
236
+ // The parameter is vestigial the token has always come from the environment — but callers
237
+ // pass a config, so keep accepting one. Typed as `unknown` rather than `OcxConfig` so a narrow
238
+ // policy view can reach it too (#1102); widening to OcxConfig here would force every caller in
239
+ // the admission path back to the full config.
240
+ export function configuredApiAuthToken(_config?: unknown): string | undefined {
178
241
  const token = process.env.OPENCODEX_API_AUTH_TOKEN?.trim();
179
242
  return token || undefined;
180
243
  }
@@ -191,10 +254,37 @@ export function isLoopbackHostname(hostname: string | undefined): boolean {
191
254
  return normalized === "" || normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1" || normalized === "[::1]";
192
255
  }
193
256
 
194
- export function isApiAuthRequired(config: OcxConfig): boolean {
257
+ export function isApiAuthRequired(config: Pick<OcxConfig, "hostname">): boolean {
195
258
  return !isLoopbackHostname(config.hostname);
196
259
  }
197
260
 
261
+ /**
262
+ * The slice of config that decides admission and CORS, and nothing else (#1102).
263
+ *
264
+ * The unauthenticated loopback listener shares this process with the public one: same routing,
265
+ * same account pool, same drain. The only thing it must see differently is its own bind
266
+ * address, because `isApiAuthRequired` reads `hostname` and the shared config says "0.0.0.0".
267
+ *
268
+ * Two ways to express that were rejected. Passing the whole config with `hostname` rewritten
269
+ * and holding it for the listener's lifetime would go stale the moment the management API
270
+ * changes a setting. Adding an `allowUnauthenticated` parameter to the resolvers would create a
271
+ * callable admission bypass that the PUBLIC listener could also reach — the switch would exist
272
+ * on the wrong side of the boundary.
273
+ *
274
+ * So this type is deliberately narrow: it cannot masquerade as a business config, and a policy
275
+ * view that leaks into a routing path fails to typecheck rather than silently taking effect.
276
+ */
277
+ export type RequestPolicyView = Pick<OcxConfig, "hostname" | "corsAllowOrigins" | "apiKeys">;
278
+
279
+ /** Derive the per-request policy view for a listener. Cheap enough to build per request. */
280
+ export function requestPolicyView(config: OcxConfig, bindHostname: string): RequestPolicyView {
281
+ return {
282
+ hostname: bindHostname,
283
+ ...(config.corsAllowOrigins ? { corsAllowOrigins: config.corsAllowOrigins } : {}),
284
+ ...(config.apiKeys ? { apiKeys: config.apiKeys } : {}),
285
+ };
286
+ }
287
+
198
288
  export function assertServerAuthConfig(config: OcxConfig): void {
199
289
  const hasConfiguredDataCredential = !!configuredApiAuthToken(config)
200
290
  || (config.apiKeys ?? []).some(entry => !!entry.key.trim());
@@ -222,10 +312,20 @@ function secretEquals(actual: string, expected: string | undefined): boolean {
222
312
  * point at, and a sentinel string in the id would collide with a hand-edited
223
313
  * entry that happens to be named `loopback`.
224
314
  */
315
+ /**
316
+ * HOW an admission credential was presented.
317
+ *
318
+ * The credential IDENTITY (which key matched) and its PRESENTATION (which header carried it)
319
+ * are different facts, and #1686 needs both: a proxy secret arriving as a bearer on the
320
+ * Responses transport is admissible, but only if the upstream credential is then guaranteed to
321
+ * be substituted. Collapsing the two is what made that flow unexpressible.
322
+ */
323
+ export type DataPlaneAdmissionSource = "loopback" | "dedicated" | "bearer" | "x-api-key";
324
+
225
325
  export type DataPlaneAdmission =
226
- | { kind: "configured"; keyId: string }
227
- | { kind: "environment" }
228
- | { kind: "loopback" };
326
+ | { kind: "configured"; keyId: string; source: DataPlaneAdmissionSource }
327
+ | { kind: "environment"; source: DataPlaneAdmissionSource }
328
+ | { kind: "loopback"; source: "loopback" };
229
329
 
230
330
  /**
231
331
  * Which admission secret `token` is, or null when it is none of them.
@@ -236,12 +336,16 @@ export type DataPlaneAdmission =
236
336
  * discarded, which is what makes per-key attribution possible without touching
237
337
  * the admission decision itself.
238
338
  */
239
- export function resolveDataPlaneAdmissionSecret(token: string, config: OcxConfig): DataPlaneAdmission | null {
339
+ export function resolveDataPlaneAdmissionSecret(
340
+ token: string,
341
+ config: Pick<OcxConfig, "apiKeys">,
342
+ source: DataPlaneAdmissionSource = "dedicated",
343
+ ): DataPlaneAdmission | null {
240
344
  const actual = token.trim();
241
345
  if (!actual) return null;
242
- if (secretEquals(actual, configuredApiAuthToken(config))) return { kind: "environment" };
346
+ if (secretEquals(actual, configuredApiAuthToken(config))) return { kind: "environment", source };
243
347
  for (const k of config.apiKeys ?? []) {
244
- if (secretEquals(actual, k.key)) return { kind: "configured", keyId: k.id };
348
+ if (secretEquals(actual, k.key)) return { kind: "configured", keyId: k.id, source };
245
349
  }
246
350
  return null;
247
351
  }
@@ -288,8 +392,12 @@ export interface ApiAuthMatrixRow {
288
392
  * against every cell rather than reading the table back to itself.
289
393
  */
290
394
  export const AUTH_MATRIX: readonly ApiAuthMatrixRow[] = [
291
- { endpoint: "/v1/responses", bearer: "rejected", dedicated: "required", xApiKey: "rejected" },
292
- { endpoint: "/v1/chat/completions", bearer: "rejected", dedicated: "required", xApiKey: "rejected" },
395
+ // #1686: a bearer that is one of OUR admission secrets is now accepted here. It is safe
396
+ // because materializeCodexUpstreamAuth substitutes the stored main credential rather than
397
+ // forwarding it; a bearer that is NOT our secret stays unadmitted and remains Codex Direct
398
+ // passthrough, so the two bearer domains still never mix. `x-api-key` is still rejected.
399
+ { endpoint: "/v1/responses", bearer: "accepted", dedicated: "accepted", xApiKey: "rejected" },
400
+ { endpoint: "/v1/chat/completions", bearer: "accepted", dedicated: "accepted", xApiKey: "rejected" },
293
401
  { endpoint: "/v1/messages", bearer: "accepted", dedicated: "accepted", xApiKey: "accepted" },
294
402
  { endpoint: "/v1/models", bearer: "accepted", dedicated: "accepted", xApiKey: "accepted" },
295
403
  ];
@@ -324,22 +432,24 @@ export function validateForwardAdmissionCredential(headers: Headers, config: Ocx
324
432
  * Resolving form of `hasValidApiAuth`: identical header precedence, identical
325
433
  * decision, but it names the admission instead of collapsing it to a boolean.
326
434
  */
327
- export function resolveApiAuth(req: Request, config: OcxConfig): DataPlaneAdmission | null {
435
+ export function resolveApiAuth(req: Request, config: RequestPolicyView): DataPlaneAdmission | null {
328
436
  // A loopback bind never reads a token at all, so there is no key to name.
329
- if (!isApiAuthRequired(config)) return { kind: "loopback" };
330
- const actual = req.headers.get("x-opencodex-api-key")?.trim()
331
- || req.headers.get("authorization")?.replace(/^Bearer\s+/i, "").trim()
332
- // Anthropic-SDK clients (Claude Code with ANTHROPIC_API_KEY) authenticate via x-api-key.
333
- || req.headers.get("x-api-key")?.trim();
334
- if (!actual) return null;
335
- return resolveDataPlaneAdmissionSecret(actual, config);
437
+ if (!isApiAuthRequired(config)) return { kind: "loopback", source: "loopback" };
438
+ const dedicated = req.headers.get("x-opencodex-api-key")?.trim();
439
+ if (dedicated) return resolveDataPlaneAdmissionSecret(dedicated, config, "dedicated");
440
+ const bearer = req.headers.get("authorization")?.replace(/^Bearer\s+/i, "").trim();
441
+ if (bearer) return resolveDataPlaneAdmissionSecret(bearer, config, "bearer");
442
+ // Anthropic-SDK clients (Claude Code with ANTHROPIC_API_KEY) authenticate via x-api-key.
443
+ const apiKey = req.headers.get("x-api-key")?.trim();
444
+ if (apiKey) return resolveDataPlaneAdmissionSecret(apiKey, config, "x-api-key");
445
+ return null;
336
446
  }
337
447
 
338
- export function hasValidApiAuth(req: Request, config: OcxConfig): boolean {
448
+ export function hasValidApiAuth(req: Request, config: RequestPolicyView): boolean {
339
449
  return resolveApiAuth(req, config) !== null;
340
450
  }
341
451
 
342
- export function requireApiAuth(req: Request, config: OcxConfig, _kind: "data-plane"): Response | null {
452
+ export function requireApiAuth(req: Request, config: RequestPolicyView, _kind: "data-plane"): Response | null {
343
453
  if (hasValidApiAuth(req, config)) return null;
344
454
  return formatErrorResponse(401, "authentication_error", "opencodex API key required");
345
455
  }
@@ -349,16 +459,25 @@ export function requireApiAuth(req: Request, config: OcxConfig, _kind: "data-pla
349
459
  * Codex Direct. Remote binds must use the dedicated proxy header so the two bearer
350
460
  * domains can never be confused.
351
461
  */
352
- export function resolveResponsesApiAuth(req: Request, config: OcxConfig): DataPlaneAdmission | null {
353
- if (!isApiAuthRequired(config)) return { kind: "loopback" };
354
- // Dedicated header ONLY. `Authorization` on these transports may belong to
355
- // Codex Direct passthrough, and the two bearer domains must stay unconfusable.
356
- const actual = req.headers.get("x-opencodex-api-key")?.trim();
357
- if (!actual) return null;
358
- return resolveDataPlaneAdmissionSecret(actual, config);
462
+ export function resolveResponsesApiAuth(req: Request, config: RequestPolicyView): DataPlaneAdmission | null {
463
+ if (!isApiAuthRequired(config)) return { kind: "loopback", source: "loopback" };
464
+ // The dedicated header still WINS, because it is unambiguous.
465
+ const dedicated = req.headers.get("x-opencodex-api-key")?.trim();
466
+ if (dedicated) return resolveDataPlaneAdmissionSecret(dedicated, config, "dedicated");
467
+ // #1686: a bearer may also be one of OUR admission secrets. Rejecting it outright meant a
468
+ // Codex client configured with `env_key` could not reach Direct at all. Admitting it is only
469
+ // safe because the upstream credential is then SUBSTITUTED rather than forwarded -- see
470
+ // materializeCodexUpstreamAuth. A bearer that is NOT our secret stays unadmitted here and
471
+ // remains Codex Direct passthrough, so the two bearer domains still never mix.
472
+ const bearer = req.headers.get("authorization")?.replace(/^Bearer\s+/i, "").trim();
473
+ if (bearer) return resolveDataPlaneAdmissionSecret(bearer, config, "bearer");
474
+ // `x-api-key` is deliberately NOT accepted on this transport.
475
+ return null;
359
476
  }
360
477
 
361
- export function requireResponsesApiAuth(req: Request, config: OcxConfig): Response | null {
478
+
479
+
480
+ export function requireResponsesApiAuth(req: Request, config: RequestPolicyView): Response | null {
362
481
  if (resolveResponsesApiAuth(req, config)) return null;
363
482
  return formatErrorResponse(401, "authentication_error", "opencodex API key required");
364
483
  }
@@ -375,6 +494,42 @@ function sameCanonicalProviderSeed(actual: Record<string, unknown>, expected: Oc
375
494
  return actualKeys.every(key => JSON.stringify(actual[key]) === JSON.stringify((expected as unknown as Record<string, unknown>)[key]));
376
495
  }
377
496
 
497
+ function positiveWindowValue(value: unknown): boolean {
498
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
499
+ }
500
+
501
+ /**
502
+ * Shape-check the two context overlays before the canonical seed comparison drops them.
503
+ *
504
+ * `null` means "clear this" and is normalized by the PATCH field mask, but this validator
505
+ * also runs for POST and reload, where a whole provider object lands on disk verbatim. A
506
+ * `null` surviving there would be a value no reader expects, so full objects must carry a
507
+ * real number or omit the field.
508
+ */
509
+ function nativeContextOverlayError(raw: Record<string, unknown>): string | null {
510
+ if (Object.hasOwn(raw, "contextWindow") && !positiveWindowValue(raw.contextWindow)) {
511
+ return "provider openai contextWindow must be a positive safe integer";
512
+ }
513
+ if (Object.hasOwn(raw, "modelContextWindows")) {
514
+ const windows = raw.modelContextWindows;
515
+ if (typeof windows !== "object" || windows === null || Array.isArray(windows)) {
516
+ return "provider openai modelContextWindows must be a plain object";
517
+ }
518
+ for (const [model, value] of Object.entries(windows as Record<string, unknown>)) {
519
+ if (model.trim() === "") return "provider openai modelContextWindows keys must be nonblank model ids";
520
+ if (!positiveWindowValue(value)) {
521
+ return "provider openai modelContextWindows values must be positive safe integers";
522
+ }
523
+ }
524
+ }
525
+ return null;
526
+ }
527
+
528
+ /**
529
+ * Validate a provider object arriving at the management write boundary. Returns an error
530
+ * string, or null when the provider may be persisted. Caller-controlled names/fields are
531
+ * redacted and JSON-escaped so secrets never reach the response.
532
+ */
378
533
  export function providerManagementConfigError(name: unknown, provider: unknown): string | null {
379
534
  if (typeof name !== "string" || !provider || typeof provider !== "object" || Array.isArray(provider)) {
380
535
  return "provider must be a plain object";
@@ -392,7 +547,23 @@ export function providerManagementConfigError(name: unknown, provider: unknown):
392
547
  return "provider openai codexAccountMode must be pool or direct";
393
548
  }
394
549
  if (seed) seed.codexAccountMode = raw.codexAccountMode;
395
- const canonical = seed && sameCanonicalProviderSeed(raw, seed);
550
+ const canonicalCandidate = { ...raw };
551
+ delete canonicalCandidate.responsesSnapshotRepair;
552
+ // modelCosts is a user-owned display overlay, not part of the canonical
553
+ // forward seed; it is validated separately below (providerModelCostsConfigError).
554
+ delete canonicalCandidate.modelCosts;
555
+ // requestPacing is a user-owned transport overlay, not part of the canonical seed.
556
+ delete canonicalCandidate.requestPacing;
557
+ // Context windows are the same kind of user-owned overlay as requestPacing: the operator
558
+ // narrowing what their own native rows advertise. They can only ever LOWER the measured
559
+ // window (see nativeOpenAiContextWindow), so admitting them cannot widen what the proxy
560
+ // claims. Validated first — this function also guards POST/reload, where nothing
561
+ // normalizes the shape afterwards, so a bad value would reach disk.
562
+ const contextOverlayError = nativeContextOverlayError(raw);
563
+ if (contextOverlayError) return contextOverlayError;
564
+ delete canonicalCandidate.contextWindow;
565
+ delete canonicalCandidate.modelContextWindows;
566
+ const canonical = seed && sameCanonicalProviderSeed(canonicalCandidate, seed);
396
567
  if (!canonical) {
397
568
  return `provider ${name} must equal the canonical built-in provider seed`;
398
569
  }
@@ -402,10 +573,34 @@ export function providerManagementConfigError(name: unknown, provider: unknown):
402
573
  const typed = provider as unknown as OcxProviderConfig;
403
574
  const baseUrlError = providerBaseUrlConfigError(typed.baseUrl);
404
575
  if (baseUrlError) return `provider ${name} ${baseUrlError}`;
576
+ if (effectiveGoogleMode(name, typed) === "vertex" && typed.location !== undefined) {
577
+ const locationError = googleVertexLocationConfigError(typed.location);
578
+ if (locationError) return `provider ${name} ${locationError}`;
579
+ }
405
580
  const destinationError = providerDestinationConfigError(name, typed);
406
581
  if (destinationError) return `provider ${name} ${destinationError}`;
407
582
  const headersError = providerHeadersConfigError(typed.headers);
408
583
  if (headersError) return `provider ${name} ${headersError}`;
584
+ const retryOn429Error = retryOn429PolicyConfigError(raw.retryOn429);
585
+ if (retryOn429Error) {
586
+ // The provider name is caller-controlled and can be token-shaped; redact and JSON-escape
587
+ // it before it reaches the management API response.
588
+ return `provider ${JSON.stringify(redactSecretString(name))} ${retryOn429Error}`;
589
+ }
590
+ const requestPacingError = requestPacingConfigError(raw.requestPacing);
591
+ if (requestPacingError) {
592
+ return `provider ${JSON.stringify(redactSecretString(name))} ${requestPacingError}`;
593
+ }
594
+ const upstreamHttpVersionError = upstreamHttpVersionConfigError(raw.upstreamHttpVersion);
595
+ if (upstreamHttpVersionError) {
596
+ return `provider ${JSON.stringify(redactSecretString(name))} ${upstreamHttpVersionError}`;
597
+ }
598
+ const modelCostsError = providerModelCostsConfigError(raw.modelCosts);
599
+ if (modelCostsError) {
600
+ // The provider name is caller-controlled and can be token-shaped; redact and JSON-escape
601
+ // it before it reaches the management API response (same rule as retryOn429 above).
602
+ return `provider ${JSON.stringify(redactSecretString(name))} ${modelCostsError}`;
603
+ }
409
604
  const apiKeyTransportError = apiKeyTransportConfigError(typed);
410
605
  if (apiKeyTransportError) return `provider ${name} ${apiKeyTransportError}`;
411
606
  const maxInputError = positiveIntegerRecordConfigError(raw.modelMaxInputTokens, "modelMaxInputTokens");
@@ -419,10 +614,25 @@ export function providerManagementConfigError(name: unknown, provider: unknown):
419
614
  if (reasoningSummaryDeliveryError) return `provider ${name} ${reasoningSummaryDeliveryError}`;
420
615
  const modelAdaptersError = modelAdapterRecordConfigError(raw.modelAdapters, "modelAdapters", name, typed);
421
616
  if (modelAdaptersError) return `provider ${name} ${modelAdaptersError}`;
617
+ const preferHostedToolsError = modelPreferHostedToolsConfigError(
618
+ raw.modelPreferHostedTools,
619
+ "modelPreferHostedTools",
620
+ name,
621
+ typed,
622
+ );
623
+ if (preferHostedToolsError) return `provider ${name} ${preferHostedToolsError}`;
624
+ if (raw.responsesSnapshotRepair !== undefined && typeof raw.responsesSnapshotRepair !== "boolean") {
625
+ return `provider ${name} responsesSnapshotRepair must be a boolean`;
626
+ }
422
627
  const defaultMaxOutputError = positiveIntegerConfigError(raw.defaultMaxOutputTokens, "defaultMaxOutputTokens");
423
628
  if (defaultMaxOutputError) return `provider ${name} ${defaultMaxOutputError}`;
424
629
  const maxOutputError = positiveIntegerRecordConfigError(raw.modelMaxOutputTokens, "modelMaxOutputTokens");
425
630
  if (maxOutputError) return `provider ${name} ${maxOutputError}`;
631
+ const structuredOutputOptOutError = nonBlankStringArrayConfigError(
632
+ raw.noStructuredOutputModels,
633
+ "noStructuredOutputModels",
634
+ );
635
+ if (structuredOutputOptOutError) return `provider ${name} ${structuredOutputOptOutError}`;
426
636
  const openRouterError = openRouterRoutingConfigError(typed);
427
637
  if (openRouterError) return `provider ${name} ${openRouterError}`;
428
638
  if (typed.authMode === "local") {
@@ -469,6 +679,7 @@ export function copyIfDefined<K extends keyof OcxProviderConfig>(
469
679
  if (value !== undefined) out[key as string] = value as unknown;
470
680
  }
471
681
 
682
+ /** Public dashboard DTO for config.json: provider entries with secrets stripped and documented fields exposed (including `modelCosts`). */
472
683
  export function safeConfigDTO(config: OcxConfig): unknown {
473
684
  const providers: Record<string, Record<string, unknown>> = {};
474
685
  for (const [name, provider] of Object.entries(config.providers)) {
@@ -478,6 +689,9 @@ export function safeConfigDTO(config: OcxConfig): unknown {
478
689
  hasApiKey: !!provider.apiKey,
479
690
  hasHeaders: !!provider.headers && Object.keys(provider.headers).length > 0,
480
691
  };
692
+ if (name === "xai") {
693
+ dto.xaiResponsesOptInState = xaiResponsesOptInState(provider);
694
+ }
481
695
  for (const key of [
482
696
  "defaultModel",
483
697
  "disabled",
@@ -487,6 +701,7 @@ export function safeConfigDTO(config: OcxConfig): unknown {
487
701
  "keyOptional",
488
702
  "freeTier",
489
703
  "liveModels",
704
+ "requestPacing",
490
705
  "models",
491
706
  "contextWindow",
492
707
  "modelContextWindows",
@@ -496,17 +711,23 @@ export function safeConfigDTO(config: OcxConfig): unknown {
496
711
  "modelOpenRouterRouting",
497
712
  "reasoningEfforts",
498
713
  "modelReasoningEfforts",
714
+ "reasoningWireFormat",
499
715
  "noVisionModels",
500
716
  "noReasoningModels",
501
717
  "noTemperatureModels",
502
718
  "noTopPModels",
503
719
  "noPenaltyModels",
720
+ "noStructuredOutputModels",
721
+ "upstreamHttpVersion",
504
722
  "autoToolChoiceOnlyModels",
505
723
  "preserveReasoningContentModels",
724
+ "requiresReasoningPlaceholderModels",
506
725
  "escapeBuiltinToolNames",
507
726
  ] as const) {
508
727
  copyIfDefined(dto, provider, key);
509
728
  }
729
+ const modelCosts = sanitizeModelCostsForDisplay(provider.modelCosts);
730
+ if (modelCosts) dto.modelCosts = modelCosts;
510
731
  // Resolve the note by DESTINATION, not by name. A preset saved under a custom name is
511
732
  // still pointed at the same vendor route, and a usage restriction the user needs to see
512
733
  // must not disappear because the row was renamed. Prefer the same-name entry so an