@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
@@ -1,13 +1,20 @@
1
1
  import type { AdapterRequest, IncomingMeta, ProviderAdapter } from "../adapters/base";
2
- import type { AdapterEvent, OcxMessage, OcxParsedRequest, OcxProviderConfig, OcxThinkingContent, OcxUsage } from "../types";
3
- import { namespacedToolName } from "../types";
2
+ import type { AdapterEvent, OcxMessage, OcxParsedRequest, OcxProviderConfig, OcxProviderOpaqueToolCallMetadata, OcxThinkingContent, OcxUsage, RateLimitRetryPolicy } from "../types";
3
+ import { namespacedToolName, toolChoiceToolPredicate } from "../types";
4
+ import { cloneProviderOpaqueToolCallMetadata } from "../responses/provider-opaque-metadata";
5
+ import type { AttemptRecoveryKind } from "../usage/log";
4
6
  import { bridgeToResponsesSSE } from "../bridge";
5
7
  import { runWebSearch, type SidecarOutcome, type SidecarOutcomeRecorder, type SidecarSettings } from "./executor";
6
8
  import { runAnthropicWebSearch } from "./anthropic-executor";
9
+ import { runXaiWebSearch, type XaiSearchOptions } from "./xai-executor";
10
+ import { runGeminiWebSearch } from "./gemini-executor";
11
+ import { runExaWebSearch } from "./exa-executor";
12
+ import type { WebSearchBackendId } from "./index";
7
13
  import { clearableDeadline } from "../lib/abort";
8
14
  import { redactSecretString } from "../lib/redact";
9
15
  import { readBoundedResponseBody } from "../lib/bounded-body";
10
- import { fetchWithResetRetry } from "../lib/upstream-retry";
16
+ import { fetchWithResetRetry, prepareSameTarget429Wait } from "../lib/upstream-retry";
17
+ import { rateLimitRetryDelayMs } from "../providers/key-failover";
11
18
  import {
12
19
  isTranslatorBudgetExceededError,
13
20
  TRANSLATOR_MAX_TURN_BYTES,
@@ -30,6 +37,11 @@ interface WebSearchCall {
30
37
  // empty array means the model called the tool with neither `query` nor `queries` (handled as an
31
38
  // empty-query placeholder).
32
39
  queries: string[];
40
+ /**
41
+ * Provider-opaque metadata from the originating part (issue #1735). Stored PER CALL so a
42
+ * signature can never migrate to a different call when the model batches several.
43
+ */
44
+ providerMetadata?: OcxProviderOpaqueToolCallMetadata;
33
45
  }
34
46
 
35
47
  /**
@@ -61,40 +73,68 @@ export function scanEventsForWebSearch(events: AdapterEvent[]): {
61
73
  calls: WebSearchCall[];
62
74
  passthrough: AdapterEvent[];
63
75
  hasRealToolCall: boolean;
76
+ hasMalformedToolCall: boolean;
64
77
  } {
65
78
  const calls: WebSearchCall[] = [];
66
79
  const passthrough: AdapterEvent[] = [];
67
80
  let hasRealToolCall = false;
68
- let pending: { name: string; id: string; argsBuf: string; events: AdapterEvent[] } | null = null;
81
+ let hasMalformedToolCall = false;
82
+ let pending: { name: string; id: string; argsBuf: string; closed: boolean; events: AdapterEvent[]; providerMetadata?: OcxProviderOpaqueToolCallMetadata } | null = null;
83
+ const isBlank = (value: string): boolean => value.trim().length === 0;
69
84
  const flushPending = (): void => {
85
+ // A pending call that never saw tool_call_end is structurally malformed.
86
+ if (pending && !pending.closed) hasMalformedToolCall = true;
70
87
  if (pending && pending.name !== WEB_SEARCH_TOOL_NAME) {
71
88
  passthrough.push(...pending.events);
72
- hasRealToolCall = true;
89
+ if (pending.closed && !isBlank(pending.id) && !isBlank(pending.name)) hasRealToolCall = true;
73
90
  }
74
91
  pending = null;
75
92
  };
76
93
  for (const e of events) {
77
94
  if (e.type === "tool_call_start") {
78
95
  flushPending();
79
- pending = { name: e.name, id: e.id, argsBuf: "", events: [e] };
80
- } else if (e.type === "tool_call_delta" && pending) {
81
- pending.argsBuf += e.arguments;
82
- pending.events.push(e);
83
- } else if (e.type === "tool_call_end" && pending) {
84
- pending.events.push(e);
85
- if (pending.name === WEB_SEARCH_TOOL_NAME) {
86
- calls.push({ id: pending.id, queries: parseQueries(pending.argsBuf) });
96
+ if (isBlank(e.id) || isBlank(e.name)) hasMalformedToolCall = true;
97
+ pending = { name: e.name, id: e.id, argsBuf: "", closed: false, events: [e], providerMetadata: e.providerMetadata };
98
+ } else if (e.type === "tool_call_delta") {
99
+ // Orphan delta (no open call) is malformed.
100
+ if (!pending) hasMalformedToolCall = true;
101
+ else {
102
+ pending.argsBuf += e.arguments;
103
+ pending.events.push(e);
104
+ }
105
+ } else if (e.type === "tool_call_end") {
106
+ // Orphan end (no open call) is malformed.
107
+ if (!pending) {
108
+ hasMalformedToolCall = true;
87
109
  } else {
88
- passthrough.push(...pending.events);
89
- hasRealToolCall = true;
110
+ pending.events.push(e);
111
+ pending.closed = true;
112
+ if (pending.name === WEB_SEARCH_TOOL_NAME) {
113
+ calls.push({ id: pending.id, queries: parseQueries(pending.argsBuf), providerMetadata: pending.providerMetadata });
114
+ } else {
115
+ passthrough.push(...pending.events);
116
+ if (!isBlank(pending.id) && !isBlank(pending.name)) hasRealToolCall = true;
117
+ }
118
+ pending = null;
90
119
  }
91
- pending = null;
92
120
  } else {
93
121
  passthrough.push(e);
94
122
  }
95
123
  }
96
124
  flushPending();
97
- return { calls, passthrough, hasRealToolCall };
125
+ return { calls, passthrough, hasRealToolCall, hasMalformedToolCall };
126
+ }
127
+
128
+ /**
129
+ * Visible final-answer text: a non-whitespace text_delta that is NOT commentary
130
+ * (#1001). Commentary streams early for progress and must not satisfy the
131
+ * forced-answer output check; thinking alone never counts either.
132
+ */
133
+ export function hasVisibleAssistantText(events: AdapterEvent[]): boolean {
134
+ return events.some(event =>
135
+ event.type === "text_delta"
136
+ && event.phase !== "commentary"
137
+ && event.text.trim().length > 0);
98
138
  }
99
139
 
100
140
  async function* replay(events: AdapterEvent[]): AsyncGenerator<AdapterEvent> {
@@ -206,16 +246,32 @@ class LoopError extends Error {
206
246
  }
207
247
  }
208
248
 
249
+ /**
250
+ * Dependencies for one web-search loop iteration: parsed request, active adapter,
251
+ * incoming metadata, and the configured search executor.
252
+ */
209
253
  export interface WebSearchLoopDeps {
210
254
  parsed: OcxParsedRequest;
211
255
  adapter: ProviderAdapter;
212
256
  incomingMeta: IncomingMeta;
213
- /** Which executor runs searches. Defaults to "openai" so existing callers keep the ChatGPT path (audit F4). */
214
- backend?: "openai" | "anthropic";
257
+ /**
258
+ * Which executor runs searches. Defaults to "openai" so existing callers keep the ChatGPT path
259
+ * (audit F4). The widened ids (xai/gemini/exa) cannot reach the loop yet: planWebSearch returns
260
+ * no plan for them (inert 060 arms), and the dispatch below only branches on "anthropic".
261
+ */
262
+ backend?: WebSearchBackendId;
215
263
  /** Required for the openai backend; unused (and typically undefined) for the anthropic backend. */
216
264
  forwardProvider?: OcxProviderConfig;
217
265
  /** Required for the anthropic backend: the stored-OAuth provider that runs web_search_20250305. */
218
266
  anthropicSidecar?: { providerName: string; provider: OcxProviderConfig };
267
+ /** Required for the xai backend: the stored Grok OAuth provider (L7). */
268
+ xaiSidecar?: { providerName: string; provider: OcxProviderConfig };
269
+ /** Required for the gemini backend: the stored Antigravity CCA provider (L8). */
270
+ geminiSidecar?: { providerName: string; provider: OcxProviderConfig };
271
+ /** Required for the exa backend: the operator key, read from config at plan unpack (L9). */
272
+ exaApiKey?: string;
273
+ /** Opt-in x_search options for the xai backend. */
274
+ xaiSearchOptions?: XaiSearchOptions;
219
275
  hostedTool: Record<string, unknown>;
220
276
  selectedForwardHeaders: Headers;
221
277
  settings: SidecarSettings;
@@ -233,17 +289,30 @@ export interface WebSearchLoopDeps {
233
289
  * sidecar search, so a legitimately slow-but-progressing unit never trips the bridge watchdog.
234
290
  */
235
291
  stallTimeoutSec?: number;
292
+ /**
293
+ * Opt-in: stream the routed model's leading text/thinking deltas live instead of holding the whole
294
+ * iteration back. The live window closes at the first buffer-only event (tool calls above all) so
295
+ * the web_search interception decision stays atomic; everything after replays in order at the end.
296
+ */
297
+ streamRoutedModelOutput?: boolean;
236
298
  /** One-shot TTFT callback: first non-empty model output observed (WP4). */
237
299
  onFirstOutput?: () => void;
238
300
  /** Raw adapter usage at the terminal event, pre wire-normalization (see bridgeToResponsesSSE onUsage). */
239
301
  onUsage?: (usage: OcxUsage | undefined) => void;
240
302
  /** Observe the exact adapter request selected for each routed-model iteration. */
241
303
  onRequestBuilt?: (request: AdapterRequest) => void;
304
+ /** Called before each routed-model dispatch in the loop, for attempt telemetry. Same-target 429 replays pass the `rate-limit-429` recovery kind. */
305
+ onAttemptSend?: (recovery?: AttemptRecoveryKind) => void;
242
306
  /**
243
- * 429 key-failover hook: rotate the provider's active pool key and return a rebuilt adapter,
244
- * or null when the pool is exhausted (same semantics as the normal routed path).
307
+ * 429 failover hook: rotate the provider's active credential and return a rebuilt adapter,
308
+ * or null when the pool is exhausted. Async hooks support OAuth refresh; existing synchronous
309
+ * key-pool hooks remain valid.
245
310
  */
246
- on429?: (retryAfterHeader: string | null) => ProviderAdapter | null;
311
+ on429?: (retryAfterHeader: string | null) => ProviderAdapter | null | Promise<ProviderAdapter | null>;
312
+ /** Opt-in same-target 429 policy (key-auth providers). When present, 429 replays on the SAME key before on429 rotation. */
313
+ retryOn429Policy?: Required<RateLimitRetryPolicy> | null;
314
+ /** Called only when the final bridged Responses stream reaches completed or incomplete. */
315
+ onCompletedResponse?: (response: Record<string, unknown>) => void;
247
316
  }
248
317
 
249
318
  /**
@@ -260,6 +329,12 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
260
329
  // Mutable: 429 key-failover (deps.on429) can swap in a rebuilt adapter mid-loop.
261
330
  let adapter = deps.adapter;
262
331
 
332
+ // Bridge stall budget (seconds of silence before upstream_stall_timeout); the retry backoff
333
+ // heartbeat interval is derived from it so the watchdog is always fed during deliberate waits.
334
+ const stallTimeoutMs = typeof deps.stallTimeoutSec === "number" && Number.isFinite(deps.stallTimeoutSec) && deps.stallTimeoutSec > 0
335
+ ? Math.floor(deps.stallTimeoutSec * 1000)
336
+ : 300_000;
337
+
263
338
  const messages: OcxMessage[] = [...parsed.context.messages];
264
339
  const loopT0 = Date.now();
265
340
  const allTools = parsed.context.tools ?? [];
@@ -292,11 +367,29 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
292
367
  response: Response;
293
368
  responseAdapter: ProviderAdapter;
294
369
  }
295
- type IterationSplit = ReturnType<typeof scanEventsForWebSearch>;
370
+ type IterationSplit = ReturnType<typeof scanEventsForWebSearch> & {
371
+ /**
372
+ * How many leading passthrough events were already delivered live this iteration. They are
373
+ * exactly the first N passthrough entries (live delivery stops before the first event that
374
+ * scanEventsForWebSearch could group or reorder), so the terminal replay skips them by count.
375
+ */
376
+ streamedPassthroughCount: number;
377
+ };
378
+
379
+ // Same-target 429 budget is per REQUEST, not per model iteration: later search rounds inherit
380
+ // what earlier rounds left of `attempts`, so a bounded multi-round turn can never exceed the
381
+ // configured replay count in total (a per-round reset would multiply it by maxSearches).
382
+ const rateLimitRetryPolicy = deps.retryOn429Policy ?? null;
383
+ let rateLimitRetries = 0;
296
384
 
297
385
  // Acquire one iteration's final response headers. The first call is drained eagerly so an initial
298
386
  // connect/header/HTTP failure stays a non-2xx JSON response. Its successful BODY is deliberately
299
387
  // left unread until the downstream Responses SSE bridge exists.
388
+ /**
389
+ * Fetch one web-search iteration's final response headers, applying the response-header
390
+ * deadline and the same-target 429 retry policy (with awaited body release and deadline
391
+ * restart) before the `on429` key rotation.
392
+ */
300
393
  const prepareIterationEvents = async function* (forceAnswer: boolean): AsyncGenerator<AdapterEvent, IterationResponse> {
301
394
  // On the forced-answer pass the synthetic web_search tool is gone, so the model MUST answer
302
395
  // from the results already in `messages`. A weak model can still produce a thin answer that
@@ -313,30 +406,55 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
313
406
  // One cumulative header deadline spans every pool-key 429 rotation in this model iteration.
314
407
  // clear() stops only its timer after final headers; the direct turn signal remains attached to
315
408
  // the returned response body through AbortSignal.any().
316
- const headerDeadline = clearableDeadline(connectTimeoutMs, signal);
409
+ let headerDeadline = clearableDeadline(connectTimeoutMs, signal);
317
410
  try {
318
- const fetchOnce = async (requestAdapter: ProviderAdapter): Promise<IterationResponse> => {
319
- const request = await requestAdapter.buildRequest(iterParsed, {
320
- headers: selectedForwardHeaders,
321
- abortSignal: headerDeadline.signal,
322
- translatorBudget,
323
- });
324
- try {
325
- deps.onRequestBuilt?.(request);
326
- } catch {
327
- // Diagnostics are best-effort and must never abort a web-search iteration.
411
+ /**
412
+ * Build and fetch one web-search iteration on the given adapter, under the iteration
413
+ * header deadline. The caller owns same-target 429 replays and key rotation around it.
414
+ * The outbound request is cached per adapter so a same-target replay reuses the EXACT
415
+ * URL, serialized body, and headers (builder runs once per target sequence).
416
+ */
417
+ let cachedRequest: AdapterRequest | undefined;
418
+ let cachedAdapter: ProviderAdapter | undefined;
419
+ /**
420
+ * Build and fetch one web-search iteration on the given adapter, under the iteration
421
+ * header deadline. The caller owns same-target 429 replays and key rotation around it.
422
+ */
423
+ const fetchOnce = async (requestAdapter: ProviderAdapter, recovery?: AttemptRecoveryKind): Promise<IterationResponse> => {
424
+ let request: AdapterRequest;
425
+ if (cachedRequest !== undefined && cachedAdapter === requestAdapter) {
426
+ request = cachedRequest;
427
+ } else {
428
+ request = await requestAdapter.buildRequest(iterParsed, {
429
+ headers: selectedForwardHeaders,
430
+ abortSignal: headerDeadline.signal,
431
+ translatorBudget,
432
+ });
433
+ try {
434
+ deps.onRequestBuilt?.(request);
435
+ } catch {
436
+ // Diagnostics are best-effort and must never abort a web-search iteration.
437
+ }
438
+ cachedRequest = request;
439
+ cachedAdapter = requestAdapter;
328
440
  }
329
441
  let response: Response;
330
442
  try {
331
- response = requestAdapter.fetchResponse
332
- ? await requestAdapter.fetchResponse(request, {
443
+ if (requestAdapter.fetchResponse) {
444
+ deps.onAttemptSend?.(recovery);
445
+ response = await requestAdapter.fetchResponse(request, {
333
446
  abortSignal: headerDeadline.signal,
334
447
  timeoutMs: connectTimeoutMs,
335
448
  returnRawErrors: true,
336
449
  stream: true,
337
- })
338
- : await fetchWithResetRetry(
339
- () => {
450
+ });
451
+ } else {
452
+ response = await fetchWithResetRetry(
453
+ (retryRecovery) => {
454
+ // Record every helper-driven send (the callback runs for the first attempt and
455
+ // each connection-reset replay); preserve the caller's recovery kind
456
+ // (rate-limit-429 / key-429) when the retry layer supplies none.
457
+ deps.onAttemptSend?.(retryRecovery ?? recovery);
340
458
  const h = new Headers(request.headers);
341
459
  if (!h.has("accept-encoding")) h.set("accept-encoding", "identity");
342
460
  return fetch(request.url, {
@@ -347,7 +465,8 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
347
465
  });
348
466
  },
349
467
  { abortSignal: headerDeadline.signal, label: "web-search-loop" },
350
- );
468
+ );
469
+ }
351
470
  } finally {
352
471
  request.releaseBodyObservation?.();
353
472
  }
@@ -355,10 +474,43 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
355
474
  };
356
475
 
357
476
  let prepared = await fetchOnce(adapter);
477
+ // Same-target 429 wait-and-retry (opt-in `retryOn429`) BEFORE key rotation: a primary-key
478
+ // rate-limit blip replays on the SAME key; rotation only runs after attempts exhaust.
479
+ while (
480
+ prepared.response.status === 429
481
+ && rateLimitRetryPolicy !== null
482
+ && rateLimitRetries < rateLimitRetryPolicy.attempts
483
+ ) {
484
+ rateLimitRetries += 1;
485
+ // Release unread body + heartbeat-fed wait via the shared same-target helper.
486
+ const retryAfterHeader = prepared.response.headers.get("retry-after");
487
+ // The old header deadline must not stay armed across the deliberate wait: clear it
488
+ // before sleeping so a stale expiry can never race the client-cancel path.
489
+ headerDeadline.clear();
490
+ try {
491
+ yield* prepareSameTarget429Wait({
492
+ body: prepared.response.body,
493
+ signal,
494
+ delayMs: rateLimitRetryDelayMs(rateLimitRetryPolicy, retryAfterHeader, Date.now()),
495
+ heartbeatIntervalMs: Math.min(10_000, Math.max(250, stallTimeoutMs / 2)),
496
+ });
497
+ } catch {
498
+ throw new LoopError(499, "client closed request during web-search");
499
+ }
500
+ // Client cancellation wins over any stale-deadline edge: re-check before telemetry/replay.
501
+ if (signal.aborted) throw new LoopError(499, "client closed request during web-search");
502
+ // The deliberate backoff must not consume the cumulative response-header deadline:
503
+ // start a fresh one so the replay gets a new connect budget (504 stays reserved for real
504
+ // upstream latency).
505
+ headerDeadline = clearableDeadline(connectTimeoutMs, signal);
506
+ // Stall-watchdog seam between bounded retry fetches.
507
+ yield { type: "heartbeat" };
508
+ prepared = await fetchOnce(adapter, "rate-limit-429");
509
+ }
358
510
  // 429 key-failover parity with the normal routed path: rotate pool keys until one responds
359
511
  // or the pool is exhausted (deps.on429 returns null — cooldown map guarantees termination).
360
512
  while (prepared.response.status === 429 && deps.on429) {
361
- const rotated = deps.on429(prepared.response.headers.get("retry-after"));
513
+ const rotated = await deps.on429(prepared.response.headers.get("retry-after"));
362
514
  if (!rotated) break;
363
515
  // Never let a broken body's cancel promise outlive the cumulative header deadline. Observe
364
516
  // it, but proceed immediately to the rotated fetch under the SAME deadline signal.
@@ -366,7 +518,7 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
366
518
  adapter = rotated;
367
519
  // Stall-watchdog seam between bounded retry fetches (audit 011 B3).
368
520
  yield { type: "heartbeat" };
369
- prepared = await fetchOnce(adapter);
521
+ prepared = await fetchOnce(adapter, "key-429");
370
522
  }
371
523
 
372
524
  // Final headers have arrived. Clear only the deadline timer before ANY body read.
@@ -417,10 +569,23 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
417
569
  return r.value;
418
570
  };
419
571
 
572
+ // Event types that may leave the live window before the first tool-call boundary: pure
573
+ // text/thinking output the native (sidecar-less) path would deliver identically. Everything
574
+ // else — tool calls above all, and any type scanEventsForWebSearch could group or a future
575
+ // adapter could add — closes the window so live delivery can never reorder against the replay.
576
+ const LIVE_STREAMABLE = new Set<AdapterEvent["type"]>([
577
+ "text_delta", "thinking_delta", "reasoning_raw_delta",
578
+ "thinking_signature", "redacted_thinking", "kiro_redacted_reasoning",
579
+ ]);
580
+
420
581
  // Consume and validate one successful response body under a resettable raw-byte inactivity guard.
421
- // Only invisible heartbeat events escape while semantic output remains buffered for safe scanning.
582
+ // By default only invisible heartbeat events escape while semantic output remains buffered for
583
+ // safe scanning; with `streamRoutedModelOutput` the leading text/thinking deltas stream live and
584
+ // the live window closes permanently at the first buffer-only event (see LIVE_STREAMABLE).
422
585
  const consumeIterationEvents = async function* (prepared: IterationResponse): AsyncGenerator<AdapterEvent, IterationSplit> {
423
586
  const events: AdapterEvent[] = [];
587
+ let liveWindowOpen = deps.streamRoutedModelOutput === true;
588
+ let streamedPassthroughCount = 0;
424
589
  try {
425
590
  const parse = prepared.responseAdapter.parseStream.bind(prepared.responseAdapter);
426
591
  for await (const event of parseStreamWithProgress(prepared.response, parse, {
@@ -436,7 +601,16 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
436
601
  // Tool events remain buffered below, so the decision to invoke the hosted sidecar is still
437
602
  // atomic and no search call can escape before its stream has validated successfully.
438
603
  else if (event.type === "text_delta" && event.phase === "commentary") yield event;
439
- else events.push(event);
604
+ else if (liveWindowOpen && LIVE_STREAMABLE.has(event.type)) {
605
+ // Live events are ALSO buffered: the scanner still needs them for thinking extraction
606
+ // and the forced-answer output check; only the terminal replay skips them (by count).
607
+ yield event;
608
+ streamedPassthroughCount++;
609
+ events.push(event);
610
+ } else {
611
+ liveWindowOpen = false;
612
+ events.push(event);
613
+ }
440
614
  }
441
615
  } catch (error) {
442
616
  if (isTranslatorBudgetExceededError(error)) throw error;
@@ -458,7 +632,7 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
458
632
  }
459
633
  throw new LoopError(502, terminal.message);
460
634
  }
461
- return scanEventsForWebSearch(events);
635
+ return { ...scanEventsForWebSearch(events), streamedPassthroughCount };
462
636
  };
463
637
 
464
638
  // Execute one model-requested web_search call. The call may batch several queries (native
@@ -501,9 +675,30 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
501
675
  // signal.aborted both after the await and in the catch (a fulfilled {error} on an aborted
502
676
  // signal would otherwise look like an ordinary degradable failure).
503
677
  try {
504
- outcome = backend === "anthropic" && anthropicSidecar
505
- ? await runAnthropicWebSearch(query, anthropicSidecar.providerName, anthropicSidecar.provider, settings, signal)
506
- : await runWebSearch(query, hostedTool, forwardProvider!, selectedForwardHeaders, settings, signal, recordSidecarOutcome);
678
+ if (backend === "anthropic" && anthropicSidecar) {
679
+ outcome = await runAnthropicWebSearch(query, anthropicSidecar.providerName, anthropicSidecar.provider, settings, signal);
680
+ } else if (backend === "xai") {
681
+ // L7: stored Grok OAuth to the pinned api.x.ai Responses endpoint; same
682
+ // never-throws contract and no Codex/OpenAI pool outcome recording (F5 parity).
683
+ // A missing xaiSidecar is an invariant violation — fail CLOSED with an error
684
+ // outcome rather than falling through to the forward-header OpenAI executor
685
+ // (review High: that fallthrough would be credential-sensitive).
686
+ outcome = deps.xaiSidecar
687
+ ? await runXaiWebSearch(query, deps.xaiSidecar.providerName, deps.xaiSidecar.provider, settings, deps.xaiSearchOptions ?? {}, signal)
688
+ : { text: "", sources: [], error: "xai backend selected without a resolved Grok OAuth provider" };
689
+ } else if (backend === "gemini") {
690
+ // L8: Antigravity CCA grounding; same fail-closed invariant stance as xai.
691
+ outcome = deps.geminiSidecar
692
+ ? await runGeminiWebSearch(query, deps.geminiSidecar.providerName, deps.geminiSidecar.provider, settings, signal)
693
+ : { text: "", sources: [], error: "gemini backend selected without a resolved Antigravity provider" };
694
+ } else if (backend === "exa") {
695
+ // L9: non-LLM lane; key comes from the loop deps, never the plan. Fail closed.
696
+ outcome = deps.exaApiKey
697
+ ? await runExaWebSearch(query, deps.exaApiKey, settings, signal)
698
+ : { text: "", sources: [], error: "exa backend selected without an exaApiKey" };
699
+ } else {
700
+ outcome = await runWebSearch(query, hostedTool, forwardProvider!, selectedForwardHeaders, settings, signal, recordSidecarOutcome);
701
+ }
507
702
  if (signal.aborted) throw new LoopError(499, "client closed request during web-search");
508
703
  } catch (e) {
509
704
  if (e instanceof LoopError) throw e;
@@ -529,7 +724,17 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
529
724
  // Signed thinking must precede tool_use on replay (Anthropic extended thinking), and
530
725
  // unsigned raw reasoning has to ride along for providers that require it back (#688).
531
726
  ...precedingThinking,
532
- { type: "toolCall" as const, id: call.id, name: WEB_SEARCH_TOOL_NAME, arguments: callArgs },
727
+ {
728
+ type: "toolCall" as const,
729
+ id: call.id,
730
+ name: WEB_SEARCH_TOOL_NAME,
731
+ arguments: callArgs,
732
+ // Re-attach the signature to the rebuilt call so a sidecar turn keeps Gemini
733
+ // reasoning continuity instead of relying on the same-process replay cache.
734
+ ...(cloneProviderOpaqueToolCallMetadata(call.providerMetadata)
735
+ ? { providerMetadata: cloneProviderOpaqueToolCallMetadata(call.providerMetadata) }
736
+ : {}),
737
+ },
533
738
  ],
534
739
  timestamp: now,
535
740
  });
@@ -576,11 +781,20 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
576
781
  throw e;
577
782
  }
578
783
 
579
- const toolNsMap = new Map<string, { namespace: string; name: string }>();
784
+ const toolNsMap = new Map<string, { namespace: string; name: string; freeform?: true }>();
580
785
  const freeform = new Set<string>();
581
786
  const toolSearch = new Set<string>();
582
- for (const t of parsed.context.tools ?? []) {
583
- if (t.namespace) toolNsMap.set(namespacedToolName(t.namespace, t.name), { namespace: t.namespace, name: t.name });
787
+ const requestedTools = parsed.context.tools ?? [];
788
+ const toolAllowed = toolChoiceToolPredicate(parsed.options.toolChoice, requestedTools);
789
+ for (const t of requestedTools) {
790
+ if (!toolAllowed(t)) continue;
791
+ if (t.namespace) {
792
+ toolNsMap.set(namespacedToolName(t.namespace, t.name), {
793
+ namespace: t.namespace,
794
+ name: t.name,
795
+ ...(t.freeform ? { freeform: true } : {}),
796
+ });
797
+ }
584
798
  if (t.freeform) freeform.add(t.name);
585
799
  if (t.toolSearch) toolSearch.add(t.name);
586
800
  }
@@ -607,6 +821,18 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
607
821
  // calls reach Codex. forceAnswer also finalizes.
608
822
  const shouldLoop = split.calls.length > 0 && !split.hasRealToolCall && !forceAnswer;
609
823
  if (!shouldLoop) {
824
+ // #1001: a forced-answer pass that ends `done` must have produced
825
+ // usable output — never a malformed tool call, and never silence.
826
+ if (forceAnswer) {
827
+ // An unterminated call flushes AFTER the terminal event, so find
828
+ // the terminal rather than assuming it is last (#1001).
829
+ const terminalEvent = split.passthrough.find(event => event.type === "done");
830
+ if (terminalEvent?.type === "done"
831
+ && (split.hasMalformedToolCall
832
+ || (!split.hasRealToolCall && !hasVisibleAssistantText(split.passthrough)))) {
833
+ throw new LoopError(502, "forced-answer pass produced no usable assistant output");
834
+ }
835
+ }
610
836
  if (executedSearchCount > 0) {
611
837
  const failedCount = failedQueries.size;
612
838
  console.warn(
@@ -615,7 +841,9 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
615
841
  + `, ${i + 1} iteration${i > 0 ? "s" : ""}, ${Date.now() - loopT0}ms`,
616
842
  );
617
843
  }
618
- yield* replay(split.passthrough);
844
+ // Live-streamed leading events are exactly the first N passthrough entries — replay
845
+ // only the buffered tail so nothing reaches the client twice.
846
+ yield* replay(split.passthrough.slice(split.streamedPassthroughCount));
619
847
  return;
620
848
  }
621
849
  // The thinking that led to the search belongs to the FIRST call's assistant replay turn.
@@ -644,7 +872,7 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
644
872
  }
645
873
 
646
874
  const sse = bridgeToResponsesSSE(
647
- produce(), parsed.modelId, toolNsMap, freeform, toolSearch, () => {
875
+ produce(), parsed._responseModelId ?? parsed.modelId, toolNsMap, freeform, toolSearch, () => {
648
876
  const elapsed = Date.now() - loopT0;
649
877
  if (executedSearchCount > 0 || searchesExecuted > 0) {
650
878
  console.warn(`[web-search-loop] cancelled — ${executedSearchCount} real searches, ${searchesExecuted - executedSearchCount} placeholders, ${elapsed}ms`);
@@ -653,11 +881,13 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
653
881
  }, undefined,
654
882
  {
655
883
  translatorBudget,
884
+ replayCacheScope: parsed._reasoningReplayScope,
656
885
  ...(deps.forceEmptyResponseId ? { responseId: "" } : {}),
657
886
  hideThinkingSummary: parsed.options.hideThinkingSummary,
658
887
  ...(deps.stallTimeoutSec !== undefined ? { stallTimeoutSec: deps.stallTimeoutSec } : {}),
659
888
  ...(deps.onFirstOutput ? { onFirstOutput: deps.onFirstOutput } : {}),
660
889
  ...(deps.onUsage ? { onUsage: deps.onUsage } : {}),
890
+ ...(deps.onCompletedResponse ? { onCompletedResponse: deps.onCompletedResponse } : {}),
661
891
  },
662
892
  );
663
893
  return new Response(sse, { headers: SSE_HEADERS });