@iislee/opencodex 2.11.0 → 2.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (733) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +117 -22
  3. package/bin/ocx.mjs +245 -88
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-Be1sDpWw.css +1 -0
  6. package/gui/dist/assets/index-D4OKgUV4.js +114 -0
  7. package/gui/dist/index.html +2 -2
  8. package/gui/dist/provider-icons/alibaba-color.svg +1 -1
  9. package/gui/dist/provider-icons/antigravity-color.svg +1 -1
  10. package/gui/dist/provider-icons/claude-color.svg +1 -1
  11. package/gui/dist/provider-icons/cline-color.svg +16 -0
  12. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
  13. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  14. package/gui/dist/provider-icons/copilot-color.svg +1 -1
  15. package/gui/dist/provider-icons/cursor-color.svg +1 -1
  16. package/gui/dist/provider-icons/deepseek-color.svg +1 -1
  17. package/gui/dist/provider-icons/firepass-color.svg +1 -1
  18. package/gui/dist/provider-icons/fireworks-color.svg +1 -1
  19. package/gui/dist/provider-icons/gemini-color.svg +1 -1
  20. package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
  21. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
  22. package/gui/dist/provider-icons/grok.svg +1 -1
  23. package/gui/dist/provider-icons/groq-color.svg +1 -1
  24. package/gui/dist/provider-icons/huggingface-color.svg +1 -1
  25. package/gui/dist/provider-icons/kimi-color.svg +1 -1
  26. package/gui/dist/provider-icons/kiro-color.svg +2 -2
  27. package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
  28. package/gui/dist/provider-icons/mistral-color.svg +1 -1
  29. package/gui/dist/provider-icons/moonshot-color.svg +1 -1
  30. package/gui/dist/provider-icons/nvidia-color.svg +1 -1
  31. package/gui/dist/provider-icons/ollama-color.svg +1 -1
  32. package/gui/dist/provider-icons/openai.svg +1 -1
  33. package/gui/dist/provider-icons/opencode.svg +2 -1
  34. package/gui/dist/provider-icons/openrouter-color.svg +1 -1
  35. package/gui/dist/provider-icons/pi.svg +2 -2
  36. package/gui/dist/provider-icons/qianfan-color.svg +1 -1
  37. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
  38. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
  39. package/gui/dist/provider-icons/vllm-color.svg +1 -1
  40. package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
  41. package/package.json +21 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +475 -62
  44. package/src/adapters/base.ts +82 -7
  45. package/src/adapters/client-fingerprint.ts +18 -12
  46. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  47. package/src/adapters/command-code.ts +637 -0
  48. package/src/adapters/cursor/call-id.ts +44 -0
  49. package/src/adapters/cursor/catalog.ts +541 -0
  50. package/src/adapters/cursor/checkpoint-store.ts +308 -0
  51. package/src/adapters/cursor/cursor-errors.ts +144 -6
  52. package/src/adapters/cursor/discovery.ts +122 -14
  53. package/src/adapters/cursor/effort-map.ts +106 -4
  54. package/src/adapters/cursor/envelope-echo.ts +290 -0
  55. package/src/adapters/cursor/framing.ts +39 -0
  56. package/src/adapters/cursor/h2-pool.ts +123 -0
  57. package/src/adapters/cursor/http1-bidi.ts +361 -0
  58. package/src/adapters/cursor/images.ts +704 -0
  59. package/src/adapters/cursor/live-models.ts +180 -59
  60. package/src/adapters/cursor/live-transport.ts +623 -170
  61. package/src/adapters/cursor/message-mapper.ts +4 -1
  62. package/src/adapters/cursor/native-exec-common.ts +23 -2
  63. package/src/adapters/cursor/native-exec-desktop.ts +23 -0
  64. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  65. package/src/adapters/cursor/native-exec-network.ts +1 -1
  66. package/src/adapters/cursor/native-exec-shell.ts +5 -3
  67. package/src/adapters/cursor/native-exec.ts +121 -14
  68. package/src/adapters/cursor/protobuf-events.ts +829 -11
  69. package/src/adapters/cursor/protobuf-request.ts +447 -75
  70. package/src/adapters/cursor/request-builder.ts +271 -35
  71. package/src/adapters/cursor/tool-definitions.ts +252 -12
  72. package/src/adapters/cursor/tool-result-normalize.ts +115 -0
  73. package/src/adapters/cursor/transport.ts +22 -0
  74. package/src/adapters/cursor/types.ts +48 -1
  75. package/src/adapters/cursor.ts +336 -39
  76. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  77. package/src/adapters/google-antigravity-replay.ts +713 -41
  78. package/src/adapters/google-antigravity-wire.ts +48 -10
  79. package/src/adapters/google-errors.ts +44 -12
  80. package/src/adapters/google-http.ts +38 -10
  81. package/src/adapters/google-tool-schema.ts +80 -15
  82. package/src/adapters/google-truncation.ts +11 -0
  83. package/src/adapters/google.ts +742 -86
  84. package/src/adapters/identity.ts +39 -6
  85. package/src/adapters/image.ts +1 -1
  86. package/src/adapters/kiro-constants.ts +27 -0
  87. package/src/adapters/kiro-errors.ts +11 -0
  88. package/src/adapters/kiro-events.ts +19 -1
  89. package/src/adapters/kiro-thinking.ts +18 -2
  90. package/src/adapters/kiro-tools.ts +65 -17
  91. package/src/adapters/kiro.ts +270 -73
  92. package/src/adapters/mimo-free.ts +17 -0
  93. package/src/adapters/openai-chat-url.ts +11 -0
  94. package/src/adapters/openai-chat.ts +1377 -344
  95. package/src/adapters/openai-responses-url.ts +16 -0
  96. package/src/adapters/openai-responses.ts +1164 -64
  97. package/src/adapters/registry.ts +175 -0
  98. package/src/adapters/responses-tool-schema.ts +67 -0
  99. package/src/adapters/run-turn-queue.ts +36 -1
  100. package/src/adapters/tool-call-id.ts +119 -0
  101. package/src/adapters/tool-catalog-nudge.ts +105 -21
  102. package/src/adapters/xai-tool-schema.ts +436 -0
  103. package/src/adapters/xai-web-search.ts +186 -0
  104. package/src/bridge.ts +512 -92
  105. package/src/chat/inbound.ts +57 -20
  106. package/src/chat/outbound.ts +97 -33
  107. package/src/claude/agents-inject.ts +40 -10
  108. package/src/claude/context-windows.ts +37 -14
  109. package/src/claude/desktop-3p.ts +243 -9
  110. package/src/claude/gateway-cache.ts +41 -4
  111. package/src/claude/inbound.ts +72 -3
  112. package/src/claude/model-info.ts +38 -15
  113. package/src/claude/outbound.ts +88 -21
  114. package/src/cli/access.ts +46 -3
  115. package/src/cli/account-api.ts +150 -19
  116. package/src/cli/account-auth.ts +33 -6
  117. package/src/cli/account-catalog-refresh.ts +14 -0
  118. package/src/cli/account-extended.ts +648 -28
  119. package/src/cli/account-main.ts +317 -0
  120. package/src/cli/account.ts +97 -19
  121. package/src/cli/agent.ts +53 -2
  122. package/src/cli/alias.ts +66 -0
  123. package/src/cli/capabilities-command.ts +94 -0
  124. package/src/cli/capabilities.ts +496 -0
  125. package/src/cli/claude-agent-startup-sync.ts +73 -0
  126. package/src/cli/claude-desktop.ts +60 -15
  127. package/src/cli/claude.ts +129 -35
  128. package/src/cli/codex-log-guard-doctor.ts +103 -0
  129. package/src/cli/codex-shim-autorestore.ts +2 -0
  130. package/src/cli/codex-shim-readiness.ts +76 -0
  131. package/src/cli/combo.ts +8 -0
  132. package/src/cli/config-command.ts +74 -10
  133. package/src/cli/dispatch.ts +821 -0
  134. package/src/cli/doctor.ts +440 -56
  135. package/src/cli/ensure-desired-integrations.ts +152 -0
  136. package/src/cli/export-command.ts +46 -20
  137. package/src/cli/help.ts +34 -274
  138. package/src/cli/index.ts +417 -528
  139. package/src/cli/init.ts +5 -17
  140. package/src/cli/inspect.ts +230 -0
  141. package/src/cli/integrations.ts +120 -2
  142. package/src/cli/lab.ts +607 -0
  143. package/src/cli/launcher-context.ts +77 -0
  144. package/src/cli/minimax.ts +497 -0
  145. package/src/cli/models-runtime.ts +130 -2
  146. package/src/cli/models.ts +118 -16
  147. package/src/cli/observe.ts +123 -11
  148. package/src/cli/opencode.ts +4 -2
  149. package/src/cli/provider-runtime.ts +34 -3
  150. package/src/cli/provider.ts +25 -3
  151. package/src/cli/ready.ts +301 -0
  152. package/src/cli/registry.ts +476 -0
  153. package/src/cli/root.ts +86 -0
  154. package/src/cli/route-policy.ts +92 -0
  155. package/src/cli/runtime-api.ts +57 -10
  156. package/src/cli/star-prompt.ts +71 -15
  157. package/src/cli/status.ts +51 -4
  158. package/src/cli/storage.ts +234 -0
  159. package/src/cli/system-command.ts +16 -0
  160. package/src/cli/system-restart-client.ts +146 -0
  161. package/src/cli/tray-proxy.ts +153 -6
  162. package/src/cli/usage-report.ts +184 -0
  163. package/src/cli/v2.ts +137 -18
  164. package/src/cli/version-skew.ts +46 -0
  165. package/src/cli.ts +1 -1
  166. package/src/clients/config-export.ts +1442 -23
  167. package/src/clients/effective-status.ts +30 -2
  168. package/src/clients/probes/cline.ts +81 -0
  169. package/src/codex/account-label.ts +35 -1
  170. package/src/codex/account-lifecycle.ts +130 -13
  171. package/src/codex/account-namespaces.ts +49 -3
  172. package/src/codex/account-pause.ts +2 -1
  173. package/src/codex/account-priority.ts +84 -0
  174. package/src/codex/account-store.ts +29 -2
  175. package/src/codex/account-usability.ts +25 -2
  176. package/src/codex/admission.ts +256 -0
  177. package/src/codex/affinity-debug.ts +162 -0
  178. package/src/codex/app-server-processes.ts +570 -107
  179. package/src/codex/app-server-restart-service.ts +232 -0
  180. package/src/codex/auth-api.ts +893 -246
  181. package/src/codex/auth-collision.ts +5 -3
  182. package/src/codex/auth-context.ts +348 -32
  183. package/src/codex/autostart-health.ts +24 -1
  184. package/src/codex/catalog/account-models.ts +67 -0
  185. package/src/codex/catalog/aggregation.ts +86 -10
  186. package/src/codex/catalog/bundled.ts +331 -33
  187. package/src/codex/catalog/effort.ts +138 -32
  188. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  189. package/src/codex/catalog/kinds.ts +2 -0
  190. package/src/codex/catalog/metadata.ts +555 -45
  191. package/src/codex/catalog/native-models.ts +75 -0
  192. package/src/codex/catalog/parsing.ts +269 -30
  193. package/src/codex/catalog/provider-fetch.ts +1626 -144
  194. package/src/codex/catalog/sync.ts +1576 -187
  195. package/src/codex/catalog-admission.ts +199 -0
  196. package/src/codex/catalog-refresh-status.ts +105 -0
  197. package/src/codex/catalog-write-serialization.ts +242 -0
  198. package/src/codex/catalog.ts +6 -3
  199. package/src/codex/codex-write-lock.ts +386 -0
  200. package/src/codex/convergence-types.ts +614 -0
  201. package/src/codex/convergence.ts +676 -0
  202. package/src/codex/coordinator-doctor.ts +332 -0
  203. package/src/codex/custom-model-catalog-migration.ts +176 -0
  204. package/src/codex/desired-state.ts +230 -0
  205. package/src/codex/desktop-app-restart.ts +355 -0
  206. package/src/codex/features.ts +636 -39
  207. package/src/codex/generation.ts +202 -0
  208. package/src/codex/history-job.ts +436 -0
  209. package/src/codex/history-lock.ts +242 -0
  210. package/src/codex/history-manifest.ts +112 -0
  211. package/src/codex/history-migration-guardian.ts +30 -24
  212. package/src/codex/history-provider.ts +1016 -235
  213. package/src/codex/history-transition.ts +105 -0
  214. package/src/codex/history-worker.ts +223 -0
  215. package/src/codex/inject-coordination.ts +373 -0
  216. package/src/codex/inject.ts +1114 -152
  217. package/src/codex/injected-marker.ts +37 -3
  218. package/src/codex/integration-record.ts +266 -0
  219. package/src/codex/internal/catalog-writer.ts +203 -0
  220. package/src/codex/internal/history-writer.ts +81 -0
  221. package/src/codex/journal.ts +66 -4
  222. package/src/codex/log-guard/inspect.ts +524 -0
  223. package/src/codex/log-guard/lock.ts +150 -0
  224. package/src/codex/log-guard/maintenance.ts +403 -0
  225. package/src/codex/log-guard/path-safety.ts +88 -0
  226. package/src/codex/log-guard/policy.ts +44 -0
  227. package/src/codex/log-guard/processes.ts +205 -0
  228. package/src/codex/log-guard/protection.ts +489 -0
  229. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  230. package/src/codex/main-account-cache.ts +24 -0
  231. package/src/codex/main-account.ts +29 -1
  232. package/src/codex/management-convergence.ts +167 -0
  233. package/src/codex/model-cache.ts +56 -10
  234. package/src/codex/model-entitlements.ts +360 -0
  235. package/src/codex/native-main-admission.ts +47 -0
  236. package/src/codex/native-main-auth-temp.ts +187 -0
  237. package/src/codex/native-main-claim.ts +178 -0
  238. package/src/codex/native-main-lock-file.ts +162 -0
  239. package/src/codex/native-main-owner.ts +329 -0
  240. package/src/codex/native-profile-api.ts +247 -0
  241. package/src/codex/native-profile-manager.ts +1531 -0
  242. package/src/codex/native-profile-processes.ts +121 -0
  243. package/src/codex/native-profile-recovery.ts +99 -0
  244. package/src/codex/native-profile-stage-store.ts +387 -0
  245. package/src/codex/native-profile-startup.ts +622 -0
  246. package/src/codex/native-profile-store.ts +855 -0
  247. package/src/codex/native-profile-types.ts +120 -0
  248. package/src/codex/native-residue.ts +675 -0
  249. package/src/codex/paths.ts +80 -1
  250. package/src/codex/plan-from-token.ts +140 -0
  251. package/src/codex/plan.ts +40 -0
  252. package/src/codex/plugins-doctor.ts +1 -1
  253. package/src/codex/pool-rotation.ts +74 -4
  254. package/src/codex/project-config-warnings.ts +20 -6
  255. package/src/codex/prompt-journal.ts +352 -0
  256. package/src/codex/prompt-layers.ts +1313 -0
  257. package/src/codex/prompt-lock.ts +143 -0
  258. package/src/codex/prompt-text-probe.ts +238 -0
  259. package/src/codex/quota-rejection.ts +298 -0
  260. package/src/codex/quota.ts +264 -21
  261. package/src/codex/refresh.ts +11 -2
  262. package/src/codex/reset-credit-recovery.ts +1044 -0
  263. package/src/codex/routing.ts +514 -94
  264. package/src/codex/runtime.ts +159 -38
  265. package/src/codex/shim.ts +1065 -31
  266. package/src/codex/subagent-model-fallback.ts +437 -43
  267. package/src/codex/sync.ts +191 -2
  268. package/src/codex/transition-state.ts +720 -0
  269. package/src/codex/upstream-host-health.ts +368 -0
  270. package/src/codex/user-identity.ts +557 -0
  271. package/src/codex/warmup.ts +187 -81
  272. package/src/codex/write-coordination.ts +114 -0
  273. package/src/combos/failover.ts +47 -0
  274. package/src/combos/index.ts +4 -0
  275. package/src/combos/request.ts +32 -0
  276. package/src/combos/types.ts +91 -9
  277. package/src/compatibility/index.ts +26 -0
  278. package/src/compatibility/manifest.ts +253 -0
  279. package/src/compatibility/openai-responses.ts +121 -0
  280. package/src/config/atomic-write.ts +219 -0
  281. package/src/config/paths.ts +40 -0
  282. package/src/config/process-state.ts +309 -0
  283. package/src/config/provider-name.ts +24 -0
  284. package/src/config/provider-validation.ts +177 -0
  285. package/src/config/rebase-provenance.ts +68 -0
  286. package/src/config.ts +1713 -709
  287. package/src/generated/compatibility-version.json +3324 -0
  288. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  289. package/src/grok/inject.ts +16 -5
  290. package/src/grok/inspect.ts +45 -0
  291. package/src/grok/sync.ts +2 -2
  292. package/src/images/loop.ts +157 -33
  293. package/src/images/plan.ts +24 -13
  294. package/src/integrations/config-io.ts +269 -0
  295. package/src/integrations/journal.ts +315 -0
  296. package/src/integrations/merge.ts +135 -0
  297. package/src/integrations/mutation-flight.ts +71 -0
  298. package/src/integrations/native/ownership-preflight.ts +202 -0
  299. package/src/integrations/omp-yaml-source.ts +358 -0
  300. package/src/integrations/owned-refresh.ts +74 -0
  301. package/src/integrations/ownership-policy.ts +160 -0
  302. package/src/integrations/ownership.ts +155 -0
  303. package/src/integrations/registry.ts +166 -0
  304. package/src/integrations/serialize.ts +314 -0
  305. package/src/integrations/state.ts +433 -0
  306. package/src/integrations/store.ts +103 -0
  307. package/src/integrations/writer-lock.ts +98 -0
  308. package/src/integrations/writer.ts +715 -0
  309. package/src/lab/artifacts/sanitize.ts +586 -0
  310. package/src/lab/artifacts/secure-fs.ts +475 -0
  311. package/src/lab/artifacts/store.ts +310 -0
  312. package/src/lab/automation/budgets.ts +78 -0
  313. package/src/lab/automation/config-persistence.ts +256 -0
  314. package/src/lab/automation/constants.ts +39 -0
  315. package/src/lab/automation/cooldown.ts +103 -0
  316. package/src/lab/automation/dispatch.ts +211 -0
  317. package/src/lab/automation/index.ts +13 -0
  318. package/src/lab/automation/orchestrator.ts +499 -0
  319. package/src/lab/automation/persistence.ts +512 -0
  320. package/src/lab/automation/planner.ts +371 -0
  321. package/src/lab/automation/policy.ts +136 -0
  322. package/src/lab/automation/queue.ts +191 -0
  323. package/src/lab/automation/recovery.ts +24 -0
  324. package/src/lab/automation/route-context.ts +21 -0
  325. package/src/lab/automation/run-key.ts +44 -0
  326. package/src/lab/automation/runs-query.ts +34 -0
  327. package/src/lab/automation/types.ts +160 -0
  328. package/src/lab/conformance/assertion.ts +325 -0
  329. package/src/lab/conformance/digest.ts +22 -0
  330. package/src/lab/conformance/executor.ts +741 -0
  331. package/src/lab/conformance/fixture-provider.ts +27 -0
  332. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  333. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  334. package/src/lab/conformance/harness-budget.ts +47 -0
  335. package/src/lab/conformance/index.ts +5 -0
  336. package/src/lab/conformance/jcs.ts +64 -0
  337. package/src/lab/conformance/json-pointer.ts +39 -0
  338. package/src/lab/conformance/manifest.ts +180 -0
  339. package/src/lab/conformance/mcp-stub.ts +179 -0
  340. package/src/lab/conformance/negative-controls.ts +164 -0
  341. package/src/lab/conformance/observation.ts +355 -0
  342. package/src/lab/conformance/runner.ts +68 -0
  343. package/src/lab/conformance/sse-normalize.ts +59 -0
  344. package/src/lab/conformance/suite-manifest.ts +78 -0
  345. package/src/lab/conformance/types.ts +214 -0
  346. package/src/lab/constants.ts +126 -0
  347. package/src/lab/digest.ts +64 -0
  348. package/src/lab/events/errors.ts +9 -0
  349. package/src/lab/events/limits.ts +117 -0
  350. package/src/lab/events/types.ts +229 -0
  351. package/src/lab/events/validate.ts +781 -0
  352. package/src/lab/fabric/constants.ts +40 -0
  353. package/src/lab/fabric/executor.ts +492 -0
  354. package/src/lab/fabric/index.ts +80 -0
  355. package/src/lab/fabric/manifest.ts +222 -0
  356. package/src/lab/fabric/observe.ts +489 -0
  357. package/src/lab/fabric/patch.ts +79 -0
  358. package/src/lab/fabric/producer-child.ts +139 -0
  359. package/src/lab/fabric/producer-isolate.ts +276 -0
  360. package/src/lab/fabric/producer-protocol.ts +61 -0
  361. package/src/lab/fabric/scratch.ts +439 -0
  362. package/src/lab/fabric/subject.ts +106 -0
  363. package/src/lab/fabric/types.ts +134 -0
  364. package/src/lab/fabric/verifier.ts +98 -0
  365. package/src/lab/index.ts +54 -0
  366. package/src/lab/ledger/artifact-refs.ts +127 -0
  367. package/src/lab/ledger/invalidation.ts +136 -0
  368. package/src/lab/ledger/purge.ts +310 -0
  369. package/src/lab/ledger/store.ts +532 -0
  370. package/src/lab/live/credential-lease.ts +53 -0
  371. package/src/lab/live/destination.ts +155 -0
  372. package/src/lab/live/executor.ts +336 -0
  373. package/src/lab/live/inert-tools.ts +56 -0
  374. package/src/lab/live/manifest.ts +85 -0
  375. package/src/lab/live/mcp-loopback.ts +57 -0
  376. package/src/lab/live/runner.ts +19 -0
  377. package/src/lab/live/sandbox.ts +61 -0
  378. package/src/lab/live/suite-manifest.ts +41 -0
  379. package/src/lab/live/transport.ts +118 -0
  380. package/src/lab/live/types.ts +197 -0
  381. package/src/lab/observe/from-conformance.ts +301 -0
  382. package/src/lab/observe/from-live.ts +117 -0
  383. package/src/lab/paths.ts +153 -0
  384. package/src/lab/projection/rebuild.ts +495 -0
  385. package/src/lab/projection/schema.ts +135 -0
  386. package/src/lab/projection/verdicts.ts +474 -0
  387. package/src/lab/projection/verification.ts +412 -0
  388. package/src/lab/public/bundle.ts +217 -0
  389. package/src/lab/public/community-authority.ts +175 -0
  390. package/src/lab/public/community-files.ts +29 -0
  391. package/src/lab/public/community.ts +479 -0
  392. package/src/lab/public/file-safety.ts +155 -0
  393. package/src/lab/public/ids.ts +26 -0
  394. package/src/lab/public/index.ts +16 -0
  395. package/src/lab/public/mutation-lock.ts +424 -0
  396. package/src/lab/public/operator.ts +353 -0
  397. package/src/lab/public/origin-purge.ts +79 -0
  398. package/src/lab/public/origin.ts +203 -0
  399. package/src/lab/public/privacy.ts +143 -0
  400. package/src/lab/public/private-file.ts +261 -0
  401. package/src/lab/public/project.ts +124 -0
  402. package/src/lab/public/purge-test-fault.ts +21 -0
  403. package/src/lab/public/purge.ts +223 -0
  404. package/src/lab/public/registry.ts +44 -0
  405. package/src/lab/public/revocation.ts +252 -0
  406. package/src/lab/public/signature.ts +243 -0
  407. package/src/lab/public/storage.ts +105 -0
  408. package/src/lab/public/strict-json.ts +206 -0
  409. package/src/lab/public/time.ts +26 -0
  410. package/src/lab/public/types.ts +172 -0
  411. package/src/lab/public/validate.ts +391 -0
  412. package/src/lab/query/catalog.ts +101 -0
  413. package/src/lab/query/connection.ts +107 -0
  414. package/src/lab/query/constants.ts +4 -0
  415. package/src/lab/query/cursor.ts +132 -0
  416. package/src/lab/query/dto-map.ts +277 -0
  417. package/src/lab/query/errors.ts +22 -0
  418. package/src/lab/query/freshness.ts +53 -0
  419. package/src/lab/query/index.ts +45 -0
  420. package/src/lab/query/latest-observation.ts +59 -0
  421. package/src/lab/query/passive-production.ts +159 -0
  422. package/src/lab/query/queries.ts +444 -0
  423. package/src/lab/query/types.ts +266 -0
  424. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  425. package/src/lab/subject/installation-salt.ts +112 -0
  426. package/src/lab/subject/protocol-subject.ts +80 -0
  427. package/src/lab/subject/route-subject.ts +74 -0
  428. package/src/lib/admin-secrets.ts +24 -0
  429. package/src/lib/app-owned-memory-stores.ts +22 -0
  430. package/src/lib/bounded-body.ts +167 -11
  431. package/src/lib/bun-runtime.ts +125 -12
  432. package/src/lib/bun-stream-caps.ts +13 -9
  433. package/src/lib/codex-restart-contract.ts +120 -0
  434. package/src/lib/config-ownership.ts +26 -2
  435. package/src/lib/destination-policy.ts +65 -1
  436. package/src/lib/errors.ts +80 -5
  437. package/src/lib/fabric-task-execution-authority.ts +7 -0
  438. package/src/lib/fabric-task-host.ts +29 -0
  439. package/src/lib/lab-activation.ts +223 -0
  440. package/src/lib/lab-live-execution-authority.ts +13 -0
  441. package/src/lib/lab-live-host.ts +30 -0
  442. package/src/lib/lab-live-pinned-sender.ts +56 -0
  443. package/src/lib/lab-live-route-production.ts +130 -0
  444. package/src/lib/lab-passive-linker-registration.ts +26 -0
  445. package/src/lib/local-management-attestation.ts +51 -0
  446. package/src/lib/local-management-capability.ts +100 -0
  447. package/src/lib/local-provider-reload-contract.ts +100 -0
  448. package/src/lib/optional-shutdown-hooks.ts +57 -0
  449. package/src/lib/package-tree-integrity.ts +101 -0
  450. package/src/lib/pinned-http.ts +145 -26
  451. package/src/lib/process-control.ts +6 -2
  452. package/src/lib/provider-outbound.ts +49 -9
  453. package/src/lib/redact.ts +419 -3
  454. package/src/lib/self-launch-argv.ts +15 -0
  455. package/src/lib/server-resource-ownership.ts +71 -0
  456. package/src/lib/service-secrets.ts +15 -0
  457. package/src/lib/shadow-call.ts +35 -4
  458. package/src/lib/sse-decoder.ts +41 -0
  459. package/src/lib/state-store-registrations.ts +10 -2
  460. package/src/lib/system-restart-contract.ts +73 -0
  461. package/src/lib/token-estimate.ts +19 -2
  462. package/src/lib/tool-argument-integers.ts +253 -0
  463. package/src/lib/translator-budget.ts +44 -0
  464. package/src/lib/upstream-http-version.ts +57 -0
  465. package/src/lib/upstream-reachability.ts +95 -0
  466. package/src/lib/upstream-retry.ts +156 -3
  467. package/src/lib/windows-atomic-replace.ts +155 -0
  468. package/src/lib/windows-elevation.ts +70 -2
  469. package/src/lib/windows-secret-acl.ts +409 -69
  470. package/src/lib/windows-service-wrappers.ts +72 -0
  471. package/src/lib/windows-text.ts +106 -0
  472. package/src/lib/windows-user-principal.ts +341 -0
  473. package/src/lib/winsw.ts +33 -5
  474. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  475. package/src/oauth/account-import/index.ts +15 -0
  476. package/src/oauth/account-import/parser.ts +83 -0
  477. package/src/oauth/account-import/registry.ts +18 -0
  478. package/src/oauth/account-import/service.ts +75 -0
  479. package/src/oauth/account-import/types.ts +91 -0
  480. package/src/oauth/anthropic.ts +12 -1
  481. package/src/oauth/callback-server.ts +30 -4
  482. package/src/oauth/chatgpt.ts +12 -1
  483. package/src/oauth/cline.ts +203 -0
  484. package/src/oauth/command-code.ts +239 -0
  485. package/src/oauth/cursor.ts +46 -5
  486. package/src/oauth/generic-account-failover.ts +231 -0
  487. package/src/oauth/google-antigravity.ts +35 -3
  488. package/src/oauth/health.ts +20 -12
  489. package/src/oauth/index.ts +462 -71
  490. package/src/oauth/key-providers.ts +25 -0
  491. package/src/oauth/kimi.ts +25 -3
  492. package/src/oauth/kiro.ts +95 -6
  493. package/src/oauth/local-token-detect.ts +13 -2
  494. package/src/oauth/log.ts +3 -1
  495. package/src/oauth/login-cli.ts +88 -28
  496. package/src/oauth/nous.ts +798 -0
  497. package/src/oauth/open-browser-choice.ts +26 -0
  498. package/src/oauth/store.ts +133 -26
  499. package/src/oauth/token-guardian.ts +9 -3
  500. package/src/oauth/types.ts +15 -0
  501. package/src/pi/models.ts +2 -2
  502. package/src/providers/alibaba-region-migration.ts +1 -1
  503. package/src/providers/antigravity-models.ts +521 -31
  504. package/src/providers/auto-compact-budget.ts +65 -0
  505. package/src/providers/base-url-choices.ts +10 -0
  506. package/src/providers/codex-capacity.ts +292 -0
  507. package/src/providers/command-code-efforts.ts +176 -0
  508. package/src/providers/context-cap.ts +26 -8
  509. package/src/providers/cursor-pool.ts +72 -0
  510. package/src/providers/default-aliases.ts +65 -0
  511. package/src/providers/derive.ts +281 -6
  512. package/src/providers/fastwire.ts +507 -0
  513. package/src/providers/free-directory.ts +10 -7
  514. package/src/providers/google-vertex-location.ts +14 -0
  515. package/src/providers/key-failover.ts +71 -3
  516. package/src/providers/label.ts +35 -2
  517. package/src/providers/model-discovery-limits.ts +16 -0
  518. package/src/providers/model-discovery.ts +115 -22
  519. package/src/providers/model-presets.ts +119 -0
  520. package/src/providers/model-rename-migration.ts +255 -0
  521. package/src/providers/model-rename-startup.ts +28 -0
  522. package/src/providers/new-model-policy.ts +146 -0
  523. package/src/providers/openai-sidecar.ts +72 -4
  524. package/src/providers/openai-tier-startup.ts +31 -2
  525. package/src/providers/openai-tiers.ts +119 -4
  526. package/src/providers/openai-virtual-models.ts +1 -0
  527. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  528. package/src/providers/provider-id-rewrite.ts +30 -0
  529. package/src/providers/quota.ts +1379 -40
  530. package/src/providers/registry.ts +1545 -112
  531. package/src/providers/request-pacing.ts +310 -0
  532. package/src/providers/service-tier.ts +277 -0
  533. package/src/providers/slug-codec.ts +94 -6
  534. package/src/providers/static-model-discovery.ts +86 -0
  535. package/src/providers/xai-responses-opt-in.ts +15 -0
  536. package/src/providers/xai-transport.ts +32 -4
  537. package/src/reasoning-effort.ts +68 -3
  538. package/src/responses/apply-patch-envelope.ts +63 -0
  539. package/src/responses/code-mode-helper-compat.ts +50 -0
  540. package/src/responses/compaction.ts +26 -1
  541. package/src/responses/custom-tool-compat.ts +384 -0
  542. package/src/responses/hosted-tool-policy.ts +9 -0
  543. package/src/responses/namespace-tool-compat.ts +435 -0
  544. package/src/responses/parser.ts +225 -38
  545. package/src/responses/provider-continuation.ts +98 -0
  546. package/src/responses/provider-opaque-metadata.ts +73 -0
  547. package/src/responses/reasoning-envelope.ts +9 -1
  548. package/src/responses/reasoning-replay-cache.ts +504 -0
  549. package/src/responses/schema.ts +12 -2
  550. package/src/responses/spill-store.ts +75 -10
  551. package/src/responses/state.ts +659 -31
  552. package/src/responses/thought-signature-replay.ts +364 -0
  553. package/src/responses/tool-search-compat.ts +301 -0
  554. package/src/responses/truncated-stop-reason.ts +60 -0
  555. package/src/responses/turn-termination.ts +107 -0
  556. package/src/router.ts +405 -31
  557. package/src/routing/analytics.ts +379 -0
  558. package/src/routing/capability.ts +244 -0
  559. package/src/routing/compatibility/assemble.ts +73 -0
  560. package/src/routing/compatibility/behavior.ts +278 -0
  561. package/src/routing/compatibility/catalog.ts +99 -0
  562. package/src/routing/compatibility/endpoint.ts +52 -0
  563. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  564. package/src/routing/compatibility/policy.ts +181 -0
  565. package/src/routing/compatibility/provider-slot.ts +56 -0
  566. package/src/routing/compatibility/reader.ts +110 -0
  567. package/src/routing/compatibility/subject.ts +191 -0
  568. package/src/routing/compatibility/types.ts +64 -0
  569. package/src/routing/compatibility/version.ts +104 -0
  570. package/src/routing/cost.ts +77 -0
  571. package/src/routing/evaluator.ts +495 -0
  572. package/src/routing/health.ts +412 -0
  573. package/src/routing/history/cursor.ts +43 -0
  574. package/src/routing/history/indexer.ts +605 -0
  575. package/src/routing/history/schema.ts +72 -0
  576. package/src/routing/profile-namespace.ts +15 -0
  577. package/src/routing/profile.ts +547 -0
  578. package/src/routing/quota.ts +155 -0
  579. package/src/routing/request-evidence.ts +45 -0
  580. package/src/routing/trace.ts +776 -0
  581. package/src/server/adapter-resolve.ts +2 -29
  582. package/src/server/auth-cors.ts +289 -48
  583. package/src/server/background-lifecycle.ts +182 -0
  584. package/src/server/chat-completions.ts +154 -72
  585. package/src/server/chat-native-sse.ts +331 -0
  586. package/src/server/chat-native.ts +445 -0
  587. package/src/server/claude-messages.ts +160 -44
  588. package/src/server/direct-local-http.ts +347 -0
  589. package/src/server/effort-policy.ts +18 -0
  590. package/src/server/github-copilot-responses-repair.ts +338 -0
  591. package/src/server/gui-static.ts +39 -10
  592. package/src/server/images.ts +94 -12
  593. package/src/server/index.ts +1021 -190
  594. package/src/server/lifecycle.ts +336 -14
  595. package/src/server/live.ts +136 -17
  596. package/src/server/local-management-read-client.ts +90 -0
  597. package/src/server/local-provider-reload-client.ts +137 -0
  598. package/src/server/management/agent-settings-routes.ts +433 -116
  599. package/src/server/management/api-key-usage.ts +31 -5
  600. package/src/server/management/body.ts +6 -0
  601. package/src/server/management/codex-prompt-routes.ts +570 -0
  602. package/src/server/management/combo-routes.ts +64 -25
  603. package/src/server/management/config-routes.ts +511 -51
  604. package/src/server/management/context.ts +89 -2
  605. package/src/server/management/integration-routes.ts +498 -0
  606. package/src/server/management/lab-automation-routes.ts +206 -0
  607. package/src/server/management/lab-routes.ts +563 -0
  608. package/src/server/management/logs-usage-routes.ts +127 -52
  609. package/src/server/management/model-routes.ts +461 -133
  610. package/src/server/management/model-rows.ts +167 -0
  611. package/src/server/management/native-integration-routes.ts +770 -0
  612. package/src/server/management/oauth-account-routes.ts +90 -8
  613. package/src/server/management/provider-capability-config.ts +48 -0
  614. package/src/server/management/provider-routes.ts +836 -159
  615. package/src/server/management/request-history-routes.ts +191 -0
  616. package/src/server/management/route-registry.ts +332 -0
  617. package/src/server/management/routing-analytics-routes.ts +74 -0
  618. package/src/server/management/routing-profile-routes.ts +380 -0
  619. package/src/server/management/shared.ts +27 -11
  620. package/src/server/management/sidebar-routes.ts +47 -31
  621. package/src/server/management/storage-log-guard-routes.ts +186 -0
  622. package/src/server/management/sync-response.ts +69 -0
  623. package/src/server/management/system-restart.ts +289 -33
  624. package/src/server/management/system-routes.ts +77 -0
  625. package/src/server/management/usage-summary-cache.ts +9 -1
  626. package/src/server/management/vision-sidecar-options.ts +167 -0
  627. package/src/server/management/web-search-sidecar-options.ts +120 -0
  628. package/src/server/management-api.ts +126 -23
  629. package/src/server/management-auth.ts +220 -5
  630. package/src/server/passive-route-linker.ts +66 -0
  631. package/src/server/port-reclaim.ts +1 -1
  632. package/src/server/ports.ts +41 -1
  633. package/src/server/proxy-liveness.ts +161 -10
  634. package/src/server/readiness.ts +99 -0
  635. package/src/server/relay-eager.ts +221 -54
  636. package/src/server/relay.ts +479 -87
  637. package/src/server/request-decompress.ts +113 -6
  638. package/src/server/request-log-conversation.ts +51 -0
  639. package/src/server/request-log.ts +298 -31
  640. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  641. package/src/server/responses/agent-task-recovery.ts +465 -0
  642. package/src/server/responses/codex-auth-error.ts +55 -0
  643. package/src/server/responses/collaboration.ts +239 -36
  644. package/src/server/responses/combo-stream-preflight.ts +178 -0
  645. package/src/server/responses/compact.ts +444 -72
  646. package/src/server/responses/core.ts +3579 -436
  647. package/src/server/responses/empty-completion-guard.ts +333 -0
  648. package/src/server/responses/encrypted-payload.ts +62 -39
  649. package/src/server/responses/fetch-helpers.ts +89 -99
  650. package/src/server/responses/input-admission.ts +187 -0
  651. package/src/server/responses/pacing-overload.ts +13 -0
  652. package/src/server/responses/passthrough-error.ts +33 -9
  653. package/src/server/responses/policy-fallback.ts +178 -0
  654. package/src/server/responses/responses-field-backfill.ts +343 -0
  655. package/src/server/responses/terminal-guard.ts +26 -5
  656. package/src/server/responses/upstream-error.ts +5 -0
  657. package/src/server/responses/ws-upstream.ts +381 -0
  658. package/src/server/responses-custom-tool-repair.ts +363 -0
  659. package/src/server/responses-item-id-repair.ts +54 -6
  660. package/src/server/responses-json-events.ts +90 -0
  661. package/src/server/responses-model-rewrite.ts +29 -0
  662. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  663. package/src/server/responses-snapshot-repair.ts +621 -0
  664. package/src/server/responses-terminal-repair.ts +363 -0
  665. package/src/server/responses-tool-search-repair.ts +267 -0
  666. package/src/server/responses-undeclared-tool-guard.ts +376 -0
  667. package/src/server/responses.ts +18 -2
  668. package/src/server/search.ts +78 -13
  669. package/src/server/sse-frame-buffer.ts +319 -0
  670. package/src/server/sse-payload-rewrite.ts +110 -22
  671. package/src/server/startup-action-control.ts +8 -1
  672. package/src/server/startup-health-cache.ts +19 -1
  673. package/src/server/system-env.ts +80 -9
  674. package/src/server/ws-bridge.ts +53 -40
  675. package/src/service-manager-probe.ts +892 -0
  676. package/src/service.ts +1153 -94
  677. package/src/sidecar/auth.ts +92 -0
  678. package/src/sidecar/candidates.ts +83 -0
  679. package/src/storage/cleanup.ts +2 -2
  680. package/src/storage/policy-job.ts +14 -4
  681. package/src/storage/policy.ts +88 -23
  682. package/src/storage/scanner.ts +1 -1
  683. package/src/storage/worker-lifecycle.ts +14 -14
  684. package/src/tray/windows-tray.ps1 +83 -9
  685. package/src/tray/windows.ts +43 -16
  686. package/src/types/accounts.ts +37 -0
  687. package/src/types/config.ts +911 -0
  688. package/src/types/provider.ts +614 -0
  689. package/src/types/request.ts +397 -0
  690. package/src/types/tools.ts +236 -0
  691. package/src/types/wire.ts +80 -0
  692. package/src/types.ts +107 -1237
  693. package/src/update/index.ts +37 -23
  694. package/src/update/job.ts +445 -68
  695. package/src/update/notify.ts +12 -6
  696. package/src/update/npm-cache-preflight.d.mts +47 -0
  697. package/src/update/npm-cache-preflight.mjs +201 -0
  698. package/src/update/transactional-install.d.mts +22 -0
  699. package/src/update/transactional-install.mjs +266 -0
  700. package/src/usage/cost.ts +0 -0
  701. package/src/usage/expected-prices.ts +287 -16
  702. package/src/usage/log.ts +666 -50
  703. package/src/usage/summary.ts +388 -17
  704. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  705. package/src/usage/user-cost-overlays.ts +314 -0
  706. package/src/vision/anthropic-describe.ts +10 -6
  707. package/src/vision/backends.ts +97 -0
  708. package/src/vision/describe.ts +27 -16
  709. package/src/vision/eligibility.ts +250 -0
  710. package/src/vision/index.ts +238 -24
  711. package/src/vision/reasoning.ts +55 -0
  712. package/src/vision/routed-describe.ts +175 -0
  713. package/src/vision/timeout-bounds.ts +9 -0
  714. package/src/web-search/anthropic-executor.ts +13 -7
  715. package/src/web-search/backends.ts +108 -0
  716. package/src/web-search/exa-executor.ts +119 -0
  717. package/src/web-search/executor.ts +21 -6
  718. package/src/web-search/gemini-executor.ts +141 -0
  719. package/src/web-search/index.ts +150 -15
  720. package/src/web-search/loop.ts +284 -54
  721. package/src/web-search/parse.ts +125 -30
  722. package/src/web-search/sources.ts +60 -0
  723. package/src/web-search/xai-executor.ts +219 -0
  724. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  725. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  726. package/gui/dist/provider-icons/antigravity.svg +0 -1
  727. package/gui/dist/provider-icons/claude.svg +0 -1
  728. package/gui/dist/provider-icons/copilot.svg +0 -1
  729. package/gui/dist/provider-icons/cursor.svg +0 -2
  730. package/gui/dist/provider-icons/gemini.svg +0 -1
  731. package/gui/dist/provider-icons/grok-color.svg +0 -1
  732. package/gui/dist/provider-icons/kiro.svg +0 -14
  733. package/src/cli/internal-dispatch.ts +0 -20
@@ -0,0 +1,436 @@
1
+ import type { OcxProviderConfig } from "../types";
2
+
3
+ function isSchemaObject(value: unknown): value is Record<string, unknown> {
4
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
5
+ }
6
+
7
+ export function isXaiSchemaTarget(provider: Pick<OcxProviderConfig, "baseUrl">): boolean {
8
+ try {
9
+ // Public api.x.ai accepts native root object unions. Only the Grok CLI proxy
10
+ // 400s on a root oneOf/anyOf, so flattening/omitting is scoped to that host.
11
+ return new URL(provider.baseUrl).hostname === "cli-chat-proxy.grok.com";
12
+ } catch {
13
+ return false;
14
+ }
15
+ }
16
+
17
+ /** A tool this proxy had to omit is still named by `tool_choice`; the caller maps this to a 400. */
18
+ export class XaiToolSchemaCompatibilityError extends Error {}
19
+
20
+ function stringRequiredFields(value: unknown): string[] {
21
+ return Array.isArray(value)
22
+ ? value.filter((item): item is string => typeof item === "string")
23
+ : [];
24
+ }
25
+
26
+ /** Variant keys the merger can keep. Anything else is refused, not silently dropped. */
27
+ const XAI_VARIANT_MERGE_KEYS = new Set([
28
+ "type",
29
+ "properties",
30
+ "required",
31
+ "additionalProperties",
32
+ "description",
33
+ "title",
34
+ "$comment",
35
+ "$defs",
36
+ "definitions",
37
+ ]);
38
+
39
+ /**
40
+ * A root union expands combinatorially: n nested binary unions yield 2^n variants, and a
41
+ * diamond of `$ref`s amplifies node count the same way without ever cycling. Either one can
42
+ * exhaust memory before the schema is judged unflattenable, so depth, node, and variant
43
+ * budgets bound the walk. Exceeding any of them omits that one function — the same fallback
44
+ * an unflattenable schema already takes. Mirrors the node budgets in openai-chat.ts.
45
+ */
46
+ const XAI_MAX_SCHEMA_DEPTH = 64;
47
+ const XAI_MAX_SCHEMA_NODES = 4_096;
48
+ const XAI_MAX_ROOT_VARIANTS = 256;
49
+
50
+ /** Mutable walk budget, shared across ref resolution and root-union expansion for one tool. */
51
+ interface XaiSchemaBudget {
52
+ remainingNodes: number;
53
+ remainingVariants: number;
54
+ }
55
+
56
+ /** One budget per tool: a large catalog must not let one schema spend another's allowance. */
57
+ function createXaiSchemaBudget(): XaiSchemaBudget {
58
+ return { remainingNodes: XAI_MAX_SCHEMA_NODES, remainingVariants: XAI_MAX_ROOT_VARIANTS };
59
+ }
60
+
61
+ function decodeJsonPointerToken(token: string): string {
62
+ return token.replace(/~1/g, "/").replace(/~0/g, "~");
63
+ }
64
+
65
+ /** Resolve a local `#/`-rooted JSON Pointer against `root`; undefined when it does not resolve. */
66
+ export function lookupLocalJsonPointer(root: unknown, ref: string): unknown {
67
+ if (ref === "#" || ref === "#/") return root;
68
+ if (!ref.startsWith("#/")) return undefined;
69
+ let current: unknown = root;
70
+ for (const token of ref.slice(2).split("/").map(decodeJsonPointerToken)) {
71
+ if (!isSchemaObject(current) || !Object.hasOwn(current, token)) return undefined;
72
+ current = current[token];
73
+ }
74
+ return current;
75
+ }
76
+
77
+ /** Resolve local `#/` `$ref`s. Unresolvable, cyclic, or over-budget refs return undefined. */
78
+ function resolveXaiSchemaRefs(
79
+ schema: unknown,
80
+ root: Record<string, unknown>,
81
+ budget: XaiSchemaBudget,
82
+ stack: Set<string> = new Set(),
83
+ depth = 0,
84
+ ): unknown | undefined {
85
+ if (!isSchemaObject(schema)) return schema;
86
+ if (depth >= XAI_MAX_SCHEMA_DEPTH || budget.remainingNodes <= 0) return undefined;
87
+ budget.remainingNodes -= 1;
88
+ if (typeof schema.$ref === "string") {
89
+ const ref = schema.$ref;
90
+ if (stack.has(ref)) return undefined;
91
+ const target = lookupLocalJsonPointer(root, ref);
92
+ if (target === undefined) return undefined;
93
+ stack.add(ref);
94
+ const resolvedTarget = resolveXaiSchemaRefs(target, root, budget, stack, depth + 1);
95
+ stack.delete(ref);
96
+ if (resolvedTarget === undefined) return undefined;
97
+ const rest: Record<string, unknown> = { ...schema };
98
+ delete rest.$ref;
99
+ if (Object.keys(rest).length === 0) return resolvedTarget;
100
+ const resolvedRest = resolveXaiSchemaRefs(rest, root, budget, stack, depth + 1);
101
+ if (resolvedRest === undefined || !isSchemaObject(resolvedTarget) || !isSchemaObject(resolvedRest)) {
102
+ return undefined;
103
+ }
104
+ return composeXaiObjectSchemas(resolvedTarget, resolvedRest);
105
+ }
106
+
107
+ const resolved: Record<string, unknown> = {};
108
+ for (const [key, value] of Object.entries(schema)) {
109
+ if ((key === "oneOf" || key === "anyOf") && Array.isArray(value)) {
110
+ const items: unknown[] = [];
111
+ for (const item of value) {
112
+ const next = resolveXaiSchemaRefs(item, root, budget, stack, depth + 1);
113
+ if (next === undefined) return undefined;
114
+ items.push(next);
115
+ }
116
+ resolved[key] = items;
117
+ continue;
118
+ }
119
+ if (key === "properties" && isSchemaObject(value)) {
120
+ const properties: Record<string, unknown> = {};
121
+ for (const [name, property] of Object.entries(value)) {
122
+ const next = resolveXaiSchemaRefs(property, root, budget, stack, depth + 1);
123
+ if (next === undefined) return undefined;
124
+ properties[name] = next;
125
+ }
126
+ resolved[key] = properties;
127
+ continue;
128
+ }
129
+ resolved[key] = value;
130
+ }
131
+ return resolved;
132
+ }
133
+
134
+ /** A variant is mergeable only when it is an object whose every key the merger preserves. */
135
+ function xaiVariantIsConcreteObject(variant: Record<string, unknown>): boolean {
136
+ if (variant.type !== undefined && variant.type !== "object") return false;
137
+ return Object.keys(variant).every(key => XAI_VARIANT_MERGE_KEYS.has(key));
138
+ }
139
+
140
+ function variantProperties(variant: Record<string, unknown>): Record<string, unknown> {
141
+ return isSchemaObject(variant.properties) ? variant.properties : {};
142
+ }
143
+
144
+ /**
145
+ * Independent per-property anyOf is lossless only when every property name exists
146
+ * on every variant (absence is meaningful under xAI's default additionalProperties:
147
+ * false, and promoting a branch-local key also tightens explicit-true variants)
148
+ * and at most one property schema differs.
149
+ */
150
+ function xaiPropertyMergeIsLossless(variants: Record<string, unknown>[]): boolean {
151
+ const names = new Set<string>();
152
+ const props = variants.map(variant => {
153
+ const properties = variantProperties(variant);
154
+ for (const name of Object.keys(properties)) names.add(name);
155
+ return properties;
156
+ });
157
+ let schemaConflicts = 0;
158
+ for (const name of names) {
159
+ const values = props.map(property => property[name]);
160
+ if (values.some(value => value === undefined)) return false;
161
+ if (values.some(value => JSON.stringify(value) !== JSON.stringify(values[0]))) schemaConflicts += 1;
162
+ }
163
+ return schemaConflicts <= 1;
164
+ }
165
+
166
+ function xaiRequiredSetsMatch(variants: Record<string, unknown>[]): boolean {
167
+ const serialized = variants.map(variant => [...stringRequiredFields(variant.required)].sort().join("\0"));
168
+ return serialized.every(value => value === serialized[0]);
169
+ }
170
+
171
+ /** Values a schema pins through `const`/`enum`, or undefined when it pins none. */
172
+ function xaiLiteralValues(schema: unknown): unknown[] | undefined {
173
+ if (!isSchemaObject(schema)) return undefined;
174
+ if (Object.hasOwn(schema, "const")) return [schema.const];
175
+ if (Array.isArray(schema.enum)) return schema.enum;
176
+ return undefined;
177
+ }
178
+
179
+ /** JSON type name for a literal, so it can be compared against a `type` keyword. */
180
+ function xaiJsonTypeOf(value: unknown): string {
181
+ if (value === null) return "null";
182
+ if (Array.isArray(value)) return "array";
183
+ if (typeof value === "string") return "string";
184
+ if (typeof value === "boolean") return "boolean";
185
+ if (typeof value === "number") return Number.isInteger(value) ? "integer" : "number";
186
+ return "object";
187
+ }
188
+
189
+ /** Types a schema declares, or undefined when it constrains none. */
190
+ function xaiDeclaredTypes(schema: unknown): Set<string> | undefined {
191
+ if (!isSchemaObject(schema)) return undefined;
192
+ const type = schema.type;
193
+ if (typeof type === "string") return new Set([type]);
194
+ if (Array.isArray(type) && type.every(item => typeof item === "string")) return new Set(type as string[]);
195
+ return undefined;
196
+ }
197
+
198
+ /** `integer` is a subset of `number`, so those two names overlap rather than exclude. */
199
+ function xaiTypesOverlap(left: string, right: string): boolean {
200
+ if (left === right) return true;
201
+ return (left === "integer" && right === "number") || (left === "number" && right === "integer");
202
+ }
203
+
204
+ /**
205
+ * Conservative mutual-exclusion test: true only when no instance can satisfy both schemas.
206
+ * Proof comes from disjoint literal sets or disjoint declared types; anything it cannot prove
207
+ * is reported as overlapping so the caller refuses the merge instead of widening the schema.
208
+ */
209
+ function xaiSchemasAreProvablyDisjoint(left: unknown, right: unknown): boolean {
210
+ const leftValues = xaiLiteralValues(left);
211
+ const rightValues = xaiLiteralValues(right);
212
+ if (leftValues && rightValues) {
213
+ const seen = new Set(rightValues.map(value => JSON.stringify(value)));
214
+ return leftValues.every(value => !seen.has(JSON.stringify(value)));
215
+ }
216
+ const leftTypes = xaiDeclaredTypes(left);
217
+ const rightTypes = xaiDeclaredTypes(right);
218
+ const literalsExcludedByTypes = (values: unknown[], types: Set<string>): boolean =>
219
+ values.every(value => ![...types].some(type => xaiTypesOverlap(xaiJsonTypeOf(value), type)));
220
+ if (leftValues && rightTypes) return literalsExcludedByTypes(leftValues, rightTypes);
221
+ if (rightValues && leftTypes) return literalsExcludedByTypes(rightValues, leftTypes);
222
+ if (leftTypes && rightTypes) {
223
+ return ![...leftTypes].some(leftType => [...rightTypes].some(rightType => xaiTypesOverlap(leftType, rightType)));
224
+ }
225
+ return false;
226
+ }
227
+
228
+ /** Every pair provably disjoint, so a union over them accepts each instance exactly once. */
229
+ function xaiSchemasArePairwiseDisjoint(schemas: unknown[]): boolean {
230
+ for (let i = 0; i < schemas.length; i += 1) {
231
+ for (let j = i + 1; j < schemas.length; j += 1) {
232
+ if (!xaiSchemasAreProvablyDisjoint(schemas[i], schemas[j])) return false;
233
+ }
234
+ }
235
+ return true;
236
+ }
237
+
238
+ /** Deduplicate schemas by serialized shape, preserving first-seen order. */
239
+ function uniqueXaiSchemas(values: unknown[]): unknown[] {
240
+ const unique: unknown[] = [];
241
+ const serialized = new Set<string>();
242
+ for (const value of values) {
243
+ const key = JSON.stringify(value);
244
+ if (serialized.has(key)) continue;
245
+ serialized.add(key);
246
+ unique.push(value);
247
+ }
248
+ return unique;
249
+ }
250
+
251
+ function mergeXaiAdditionalProperties(
252
+ variants: Record<string, unknown>[],
253
+ ): { ok: true; value?: unknown } | { ok: false } {
254
+ const values = variants.map(variant => variant.additionalProperties);
255
+ const explicit = values.filter(value => value !== undefined);
256
+ if (explicit.length === 0) return { ok: true };
257
+ if (explicit.length !== values.length) return { ok: false };
258
+ const hasFalse = explicit.some(value => value === false);
259
+ const permissive = explicit.filter(value => value !== false);
260
+ if (hasFalse && permissive.length > 0) return { ok: false };
261
+ if (hasFalse) return { ok: true, value: false };
262
+ const unique = uniqueXaiSchemas(permissive);
263
+ if (unique.length !== 1) return { ok: false };
264
+ return { ok: true, value: unique[0] };
265
+ }
266
+
267
+ /** Compose root siblings into a branch so properties/required are not overwritten. */
268
+ function composeXaiObjectSchemas(
269
+ inherited: Record<string, unknown>,
270
+ branch: Record<string, unknown>,
271
+ ): Record<string, unknown> {
272
+ const composed: Record<string, unknown> = { ...inherited, ...branch };
273
+ const inheritedProps = isSchemaObject(inherited.properties) ? inherited.properties : undefined;
274
+ const branchProps = isSchemaObject(branch.properties) ? branch.properties : undefined;
275
+ if (inheritedProps || branchProps) {
276
+ const properties: Record<string, unknown> = { ...(inheritedProps ?? {}) };
277
+ for (const [name, value] of Object.entries(branchProps ?? {})) {
278
+ const inheritedValue = inheritedProps?.[name];
279
+ properties[name] = inheritedValue !== undefined && JSON.stringify(inheritedValue) !== JSON.stringify(value)
280
+ ? { allOf: [inheritedValue, value] }
281
+ : value;
282
+ }
283
+ composed.properties = properties;
284
+ }
285
+ const required = [...new Set([
286
+ ...stringRequiredFields(inherited.required),
287
+ ...stringRequiredFields(branch.required),
288
+ ])];
289
+ if (required.length > 0) composed.required = required;
290
+ else delete composed.required;
291
+ return composed;
292
+ }
293
+
294
+ /** Flattened root variants, plus the shape of the union tree they came from. */
295
+ interface XaiRootExpansion {
296
+ variants: Record<string, unknown>[];
297
+ /** This node was itself a union, as opposed to a plain object leaf. */
298
+ isUnion: boolean;
299
+ /**
300
+ * True when a `oneOf` was expanded anywhere in the tree. `oneOf` rejects an instance that
301
+ * matches more than one branch, so its variants must stay mutually exclusive after merging;
302
+ * `anyOf` carries no such obligation.
303
+ */
304
+ exclusive: boolean;
305
+ /**
306
+ * True when a branch was itself a union. Nested `anyOf` flattens associatively and stays
307
+ * exact, but a `oneOf` mixed into a nest no longer maps onto one flat variant list: its
308
+ * exclusivity binds only its own branch group, which a merged root cannot express.
309
+ */
310
+ nestedUnion: boolean;
311
+ }
312
+
313
+ function expandXaiRootObjectSchemas(
314
+ schema: unknown,
315
+ budget: XaiSchemaBudget,
316
+ depth = 0,
317
+ ): XaiRootExpansion | undefined {
318
+ if (!isSchemaObject(schema) || depth >= XAI_MAX_SCHEMA_DEPTH) return undefined;
319
+ const compositionKey = ["oneOf", "anyOf"].find(key => Array.isArray(schema[key]));
320
+ if (!compositionKey) {
321
+ if (schema.type !== undefined && schema.type !== "object") return undefined;
322
+ if (budget.remainingVariants <= 0) return undefined;
323
+ budget.remainingVariants -= 1;
324
+ return { variants: [{ ...schema, type: "object" }], isUnion: false, exclusive: false, nestedUnion: false };
325
+ }
326
+
327
+ const siblings = Object.fromEntries(Object.entries(schema).filter(([key]) => key !== compositionKey));
328
+ const branches = schema[compositionKey];
329
+ if (!Array.isArray(branches)) return undefined;
330
+ const expanded: Record<string, unknown>[] = [];
331
+ let exclusive = compositionKey === "oneOf";
332
+ let nestedUnion = false;
333
+ for (const branch of branches) {
334
+ const nested = expandXaiRootObjectSchemas(branch, budget, depth + 1);
335
+ if (!nested) return undefined;
336
+ exclusive ||= nested.exclusive;
337
+ nestedUnion ||= nested.isUnion || nested.nestedUnion;
338
+ for (const variant of nested.variants) expanded.push(composeXaiObjectSchemas(siblings, variant));
339
+ }
340
+ return expanded.length > 0 ? { variants: expanded, isUnion: true, exclusive, nestedUnion } : undefined;
341
+ }
342
+
343
+ /**
344
+ * The Grok CLI proxy rejects a function parameter schema whose root remains oneOf/anyOf.
345
+ * Flatten only when the merge is lossless: local $refs resolve inside the walk budget, every
346
+ * variant is a concrete object whose keys we can preserve, required sets match,
347
+ * additionalProperties does not change meaning, every property name exists on every variant,
348
+ * and at most one property schema differs.
349
+ *
350
+ * A `oneOf` carries one more obligation than `anyOf`: it rejects an instance matching several
351
+ * branches. Only the destination's ROOT rejects a union, so exclusivity survives by moving the
352
+ * union down onto the one differing property rather than by widening it — with every other
353
+ * property, the required set, and additionalProperties already identical across branches, a
354
+ * property-level `oneOf` accepts exactly what the root `oneOf` accepted. Branches that are
355
+ * provably disjoint keep emitting `anyOf`, since there the two keywords describe the same set.
356
+ * The remaining hole is an optional discriminator: absent, it matches every branch, which the
357
+ * root `oneOf` rejects and a per-property union would accept, so that property is promoted into
358
+ * `required`.
359
+ *
360
+ * Two deliberate widenings remain, and both widen only from the EMPTY set. A `oneOf` listing the
361
+ * same branch twice accepts nothing at all. Neither does a root `additionalProperties: false`
362
+ * placed over branches that declare properties the root itself does not: the restriction cannot
363
+ * see into an applicator, so it forbids the very keys those branches require (same for a `$ref`
364
+ * target's restriction against a sibling's properties). No author intends either, no root object
365
+ * schema can express "accepts nothing", and the emitted schema still carries the restriction — so
366
+ * the tool stays usable instead of disappearing over a source-schema bug. A union whose branch
367
+ * properties ARE declared on the root is satisfiable and is normalized losslessly, which is why
368
+ * the presence of `additionalProperties` alone is not grounds to refuse.
369
+ */
370
+ export function normalizeXaiToolParameters(parameters: unknown): Record<string, unknown> | undefined {
371
+ if (!isSchemaObject(parameters)) return undefined;
372
+ const budget = createXaiSchemaBudget();
373
+ const resolved = resolveXaiSchemaRefs(parameters, parameters, budget);
374
+ if (!isSchemaObject(resolved)) return undefined;
375
+
376
+ const normalizedRoot = { ...resolved };
377
+ delete normalizedRoot.$schema;
378
+
379
+ const expansion = expandXaiRootObjectSchemas(normalizedRoot, budget);
380
+ if (!expansion) return undefined;
381
+ const { variants, exclusive, nestedUnion } = expansion;
382
+ if (variants.length === 1) {
383
+ return xaiVariantIsConcreteObject(variants[0]) ? variants[0] : undefined;
384
+ }
385
+ if (!variants.every(xaiVariantIsConcreteObject) || !xaiRequiredSetsMatch(variants)) return undefined;
386
+ const additionalProperties = mergeXaiAdditionalProperties(variants);
387
+ if (!additionalProperties.ok) return undefined;
388
+ if (!xaiPropertyMergeIsLossless(variants)) return undefined;
389
+
390
+ const metadata = Object.fromEntries(Object.entries(normalizedRoot).filter(([key]) => key !== "oneOf" && key !== "anyOf" && key !== "type"));
391
+ delete metadata.properties;
392
+ delete metadata.required;
393
+ delete metadata.additionalProperties;
394
+
395
+ const propertyValues = new Map<string, unknown[]>();
396
+ for (const variant of variants) {
397
+ if (!isSchemaObject(variant.properties)) continue;
398
+ for (const [name, value] of Object.entries(variant.properties)) {
399
+ const values = propertyValues.get(name) ?? [];
400
+ values.push(value);
401
+ propertyValues.set(name, values);
402
+ }
403
+ }
404
+
405
+ const properties: Record<string, unknown> = {};
406
+ const differingNames: string[] = [];
407
+ for (const [name, values] of propertyValues) {
408
+ const unique = uniqueXaiSchemas(values);
409
+ if (unique.length === 1) {
410
+ properties[name] = unique[0];
411
+ continue;
412
+ }
413
+ // A `oneOf` nested among other unions binds exclusivity to its own branch group only, so a
414
+ // single flat variant list cannot say what the original said — in either direction. Refuse.
415
+ if (exclusive && nestedUnion) return undefined;
416
+ differingNames.push(name);
417
+ // Disjoint branches make `anyOf` and `oneOf` describe the same set, so prefer the keyword
418
+ // already proven on this wire; overlapping branches need the exclusivity kept verbatim.
419
+ properties[name] = exclusive && !xaiSchemasArePairwiseDisjoint(unique)
420
+ ? { oneOf: unique }
421
+ : { anyOf: unique };
422
+ }
423
+
424
+ let required = stringRequiredFields(variants[0]?.required);
425
+ if (exclusive && differingNames.length > 0) {
426
+ required = [...new Set([...required, ...differingNames])];
427
+ }
428
+
429
+ return {
430
+ ...metadata,
431
+ type: "object",
432
+ properties,
433
+ ...(required.length > 0 ? { required } : {}),
434
+ ...("value" in additionalProperties ? { additionalProperties: additionalProperties.value } : {}),
435
+ };
436
+ }
@@ -0,0 +1,186 @@
1
+ import type { OcxProviderConfig } from "../types";
2
+ import { isXaiResponsesDestination } from "../providers/xai-transport";
3
+
4
+ const CODEX_WEB_SEARCH_TOOL = "web_search";
5
+ const CODEX_WEB_SEARCH_PREVIEW_TOOL = "web_search_preview";
6
+
7
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
8
+ return !!value && typeof value === "object" && !Array.isArray(value);
9
+ }
10
+
11
+ function isCodexWebSearchToolType(value: unknown): boolean {
12
+ return value === CODEX_WEB_SEARCH_TOOL || value === CODEX_WEB_SEARCH_PREVIEW_TOOL;
13
+ }
14
+
15
+ type ToolGroupRewrite = {
16
+ tools: unknown[];
17
+ changed: boolean;
18
+ };
19
+
20
+ /**
21
+ * Translate Codex-private hosted-search fields to xAI's public Responses schema.
22
+ *
23
+ * xAI web search is live-only. A Codex cached/index-only declaration carries
24
+ * `external_web_access: false`; dropping that flag while keeping the tool would silently widen
25
+ * network access, so the whole tool is omitted instead. `true` maps to xAI's ordinary live
26
+ * `{type:"web_search"}` declaration. Requests that omit the private flag are already public-API
27
+ * shaped and retain their live-search behavior.
28
+ */
29
+ function normalizeToolGroup(tools: unknown[]): ToolGroupRewrite {
30
+ const normalized: unknown[] = [];
31
+ let changed = false;
32
+
33
+ for (const tool of tools) {
34
+ if (!isPlainObject(tool) || !isCodexWebSearchToolType(tool.type)) {
35
+ normalized.push(tool);
36
+ continue;
37
+ }
38
+
39
+ const hasExternalAccess = Object.hasOwn(tool, "external_web_access");
40
+ if (hasExternalAccess && tool.external_web_access !== true) {
41
+ // xAI has no cached/index-only equivalent. Fail closed instead of turning it into live search.
42
+ changed = true;
43
+ continue;
44
+ }
45
+
46
+ const searchContentTypes = Array.isArray(tool.search_content_types)
47
+ ? tool.search_content_types
48
+ : undefined;
49
+ const enableImageSearch = searchContentTypes?.includes("image") === true;
50
+ const next: Record<string, unknown> = { ...tool, type: CODEX_WEB_SEARCH_TOOL };
51
+ // Only the two fields xAI actually refuses are removed. Probed 2026-08-22, one field per
52
+ // request, against BOTH xAI destinations (api.x.ai and cli-chat-proxy.grok.com): they behave
53
+ // identically — `external_web_access` 400s on every value including `true`, and
54
+ // `search_context_size` 400s, while `user_location`, `search_content_types`, `filters` and
55
+ // `enable_image_search` are all accepted. Deleting the accepted ones was a silent capability
56
+ // loss, and it contradicted the sibling layer, whose own probe note already records
57
+ // user_location/filters as accepted (tests/responses-routed-web-search-fields.test.ts).
58
+ delete next.external_web_access;
59
+ delete next.search_context_size;
60
+ if (enableImageSearch && !Object.hasOwn(next, "enable_image_search")) {
61
+ next.enable_image_search = true;
62
+ }
63
+
64
+ const toolChanged = Object.keys(next).length !== Object.keys(tool).length
65
+ || Object.entries(next).some(([key, value]) => tool[key] !== value);
66
+ changed ||= toolChanged;
67
+ normalized.push(toolChanged ? next : tool);
68
+ }
69
+
70
+ return { tools: changed ? normalized : tools, changed };
71
+ }
72
+
73
+ function hasWebSearchTool(body: Record<string, unknown>): boolean {
74
+ if (Array.isArray(body.tools) && body.tools.some(tool =>
75
+ isPlainObject(tool) && isCodexWebSearchToolType(tool.type)
76
+ )) return true;
77
+ return Array.isArray(body.input) && body.input.some(item =>
78
+ isPlainObject(item)
79
+ && item.type === "additional_tools"
80
+ && Array.isArray(item.tools)
81
+ && item.tools.some(tool => isPlainObject(tool) && isCodexWebSearchToolType(tool.type))
82
+ );
83
+ }
84
+
85
+ function hasAnyDeclaredTool(body: Record<string, unknown>): boolean {
86
+ if (Array.isArray(body.tools) && body.tools.length > 0) return true;
87
+ return Array.isArray(body.input) && body.input.some(item =>
88
+ isPlainObject(item)
89
+ && item.type === "additional_tools"
90
+ && Array.isArray(item.tools)
91
+ && item.tools.length > 0
92
+ );
93
+ }
94
+
95
+ /** Remove selectors that would still force a cached-only tool omitted above. */
96
+ function normalizeToolChoice(body: Record<string, unknown>): Record<string, unknown> {
97
+ const choice = body.tool_choice;
98
+ if (choice === undefined) return body;
99
+ const hasSearch = hasWebSearchTool(body);
100
+
101
+ if (isPlainObject(choice) && isCodexWebSearchToolType(choice.type)) {
102
+ if (!hasSearch) return { ...body, tool_choice: "none" };
103
+ return choice.type === CODEX_WEB_SEARCH_TOOL
104
+ ? body
105
+ : { ...body, tool_choice: { ...choice, type: CODEX_WEB_SEARCH_TOOL } };
106
+ }
107
+ if (isPlainObject(choice) && choice.type === "allowed_tools" && Array.isArray(choice.tools)) {
108
+ let changed = false;
109
+ const tools: unknown[] = [];
110
+ for (const tool of choice.tools) {
111
+ if (!isPlainObject(tool) || !isCodexWebSearchToolType(tool.type)) {
112
+ tools.push(tool);
113
+ continue;
114
+ }
115
+ if (!hasSearch) {
116
+ changed = true;
117
+ continue;
118
+ }
119
+ if (tool.type === CODEX_WEB_SEARCH_PREVIEW_TOOL) {
120
+ tools.push({ ...tool, type: CODEX_WEB_SEARCH_TOOL });
121
+ changed = true;
122
+ } else {
123
+ tools.push(tool);
124
+ }
125
+ }
126
+ if (!changed) return body;
127
+ return {
128
+ ...body,
129
+ tool_choice: tools.length > 0 ? { ...choice, tools } : "none",
130
+ };
131
+ }
132
+ if (choice === "required" && !hasAnyDeclaredTool(body)) {
133
+ return { ...body, tool_choice: "none" };
134
+ }
135
+ return body;
136
+ }
137
+
138
+ /**
139
+ * Make Codex's hosted web-search declaration acceptable to xAI Responses without changing other
140
+ * providers or mutating the caller-owned request body.
141
+ *
142
+ * Scoped to BOTH xAI Responses hosts, not just the public API. The 2026-08-22 probe recorded in
143
+ * `normalizeToolGroup` and in `isXaiResponsesDestination` already found the two hosts to be one
144
+ * dialect, but this gate stayed on `api.x.ai` alone, so the Grok CLI proxy — the OAuth lane — was
145
+ * left unnormalized. Re-probed 2026-08-27 against `cli-chat-proxy.grok.com`:
146
+ * `web_search_preview` -> 422 `unknown variant`, `external_web_access` -> 400 on every value,
147
+ * `search_context_size` -> 400, while `user_location` and `search_content_types` -> 200. Identical
148
+ * to the public API, which is what makes one shared gate correct.
149
+ */
150
+ export function normalizeXaiResponsesWebSearch(
151
+ body: unknown,
152
+ provider: Pick<OcxProviderConfig, "baseUrl">,
153
+ ): unknown {
154
+ if (!isXaiResponsesDestination(provider) || !isPlainObject(body)) return body;
155
+
156
+ let next: Record<string, unknown> = body;
157
+ if (Array.isArray(body.tools)) {
158
+ const rewritten = normalizeToolGroup(body.tools);
159
+ if (rewritten.changed) {
160
+ next = { ...next };
161
+ if (rewritten.tools.length > 0) next.tools = rewritten.tools;
162
+ else delete next.tools;
163
+ }
164
+ }
165
+
166
+ if (Array.isArray(next.input)) {
167
+ let inputChanged = false;
168
+ const input: unknown[] = [];
169
+ for (const item of next.input) {
170
+ if (!isPlainObject(item) || item.type !== "additional_tools" || !Array.isArray(item.tools)) {
171
+ input.push(item);
172
+ continue;
173
+ }
174
+ const rewritten = normalizeToolGroup(item.tools);
175
+ if (!rewritten.changed) {
176
+ input.push(item);
177
+ continue;
178
+ }
179
+ inputChanged = true;
180
+ if (rewritten.tools.length > 0) input.push({ ...item, tools: rewritten.tools });
181
+ }
182
+ if (inputChanged) next = { ...next, input };
183
+ }
184
+
185
+ return normalizeToolChoice(next);
186
+ }