@yansigit/opencodex 2.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (826) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/LICENSE +21 -0
  3. package/README.md +303 -0
  4. package/assets/architecture.png +0 -0
  5. package/assets/banner.png +0 -0
  6. package/assets/claude-code-models.gif +0 -0
  7. package/assets/codex-app-picker.png +0 -0
  8. package/bin/ocx.mjs +587 -0
  9. package/bin/package-main.mjs +9 -0
  10. package/gui/dist/assets/index-BNJ7r4Gd.js +102 -0
  11. package/gui/dist/assets/index-CGoDO3uO.css +1 -0
  12. package/gui/dist/favicon.png +0 -0
  13. package/gui/dist/icons.svg +24 -0
  14. package/gui/dist/index.html +25 -0
  15. package/gui/dist/logo.png +0 -0
  16. package/gui/dist/provider-icons/alibaba-color.svg +1 -0
  17. package/gui/dist/provider-icons/antigravity-color.svg +1 -0
  18. package/gui/dist/provider-icons/claude-color.svg +1 -0
  19. package/gui/dist/provider-icons/cline-color.svg +16 -0
  20. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
  21. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  22. package/gui/dist/provider-icons/copilot-color.svg +1 -0
  23. package/gui/dist/provider-icons/cursor-color.svg +2 -0
  24. package/gui/dist/provider-icons/deepseek-color.svg +1 -0
  25. package/gui/dist/provider-icons/discord.svg +1 -0
  26. package/gui/dist/provider-icons/firepass-color.svg +1 -0
  27. package/gui/dist/provider-icons/fireworks-color.svg +1 -0
  28. package/gui/dist/provider-icons/gemini-color.svg +1 -0
  29. package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
  30. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
  31. package/gui/dist/provider-icons/grok.svg +1 -0
  32. package/gui/dist/provider-icons/groq-color.svg +1 -0
  33. package/gui/dist/provider-icons/huggingface-color.svg +1 -0
  34. package/gui/dist/provider-icons/kimi-color.svg +1 -0
  35. package/gui/dist/provider-icons/kiro-color.svg +15 -0
  36. package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
  37. package/gui/dist/provider-icons/mistral-color.svg +1 -0
  38. package/gui/dist/provider-icons/moonshot-color.svg +1 -0
  39. package/gui/dist/provider-icons/nvidia-color.svg +1 -0
  40. package/gui/dist/provider-icons/ollama-color.svg +1 -0
  41. package/gui/dist/provider-icons/openai.svg +1 -0
  42. package/gui/dist/provider-icons/opencode.svg +2 -0
  43. package/gui/dist/provider-icons/openrouter-color.svg +1 -0
  44. package/gui/dist/provider-icons/pi.svg +21 -0
  45. package/gui/dist/provider-icons/qianfan-color.svg +1 -0
  46. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
  47. package/gui/dist/provider-icons/telegram.svg +1 -0
  48. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
  49. package/gui/dist/provider-icons/vllm-color.svg +1 -0
  50. package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
  51. package/package.json +108 -0
  52. package/src/AGENTS.md +28 -0
  53. package/src/adapters/anthropic-image-guard.ts +251 -0
  54. package/src/adapters/anthropic-image-normalize.ts +518 -0
  55. package/src/adapters/anthropic-output-schema.ts +137 -0
  56. package/src/adapters/anthropic.ts +1327 -0
  57. package/src/adapters/azure.ts +36 -0
  58. package/src/adapters/base.ts +121 -0
  59. package/src/adapters/client-fingerprint.ts +65 -0
  60. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  61. package/src/adapters/command-code.ts +601 -0
  62. package/src/adapters/cursor/arg-codec.ts +38 -0
  63. package/src/adapters/cursor/arg-normalize.ts +104 -0
  64. package/src/adapters/cursor/checkpoint-store.ts +303 -0
  65. package/src/adapters/cursor/cursor-errors.ts +288 -0
  66. package/src/adapters/cursor/discovery.ts +333 -0
  67. package/src/adapters/cursor/effort-map.ts +151 -0
  68. package/src/adapters/cursor/exec-policy.ts +88 -0
  69. package/src/adapters/cursor/framing.ts +250 -0
  70. package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
  71. package/src/adapters/cursor/h2-pool.ts +123 -0
  72. package/src/adapters/cursor/http1-bidi.ts +361 -0
  73. package/src/adapters/cursor/images.ts +704 -0
  74. package/src/adapters/cursor/kv-store.ts +52 -0
  75. package/src/adapters/cursor/live-models.ts +269 -0
  76. package/src/adapters/cursor/live-smoke-gate.ts +41 -0
  77. package/src/adapters/cursor/live-transport.ts +1653 -0
  78. package/src/adapters/cursor/mcp-config.ts +42 -0
  79. package/src/adapters/cursor/mcp-manager.ts +333 -0
  80. package/src/adapters/cursor/message-mapper.ts +49 -0
  81. package/src/adapters/cursor/native-exec-common.ts +76 -0
  82. package/src/adapters/cursor/native-exec-desktop.ts +184 -0
  83. package/src/adapters/cursor/native-exec-fs.ts +332 -0
  84. package/src/adapters/cursor/native-exec-mcp.ts +153 -0
  85. package/src/adapters/cursor/native-exec-network.ts +43 -0
  86. package/src/adapters/cursor/native-exec-shell.ts +547 -0
  87. package/src/adapters/cursor/native-exec-tools.ts +118 -0
  88. package/src/adapters/cursor/native-exec.ts +663 -0
  89. package/src/adapters/cursor/protobuf-events.ts +1381 -0
  90. package/src/adapters/cursor/protobuf-request.ts +1032 -0
  91. package/src/adapters/cursor/request-builder.ts +461 -0
  92. package/src/adapters/cursor/thread-continuity.ts +67 -0
  93. package/src/adapters/cursor/tool-definitions.ts +735 -0
  94. package/src/adapters/cursor/tool-result-normalize.ts +92 -0
  95. package/src/adapters/cursor/transport-retry.ts +132 -0
  96. package/src/adapters/cursor/transport.ts +79 -0
  97. package/src/adapters/cursor/types.ts +79 -0
  98. package/src/adapters/cursor.ts +322 -0
  99. package/src/adapters/google-antigravity-hosts.ts +48 -0
  100. package/src/adapters/google-antigravity-replay.ts +827 -0
  101. package/src/adapters/google-antigravity-tools.ts +105 -0
  102. package/src/adapters/google-antigravity-wire.ts +141 -0
  103. package/src/adapters/google-errors.ts +92 -0
  104. package/src/adapters/google-http.ts +460 -0
  105. package/src/adapters/google-tool-schema.ts +238 -0
  106. package/src/adapters/google-truncation.ts +24 -0
  107. package/src/adapters/google-wire-compiler.ts +232 -0
  108. package/src/adapters/google.ts +1377 -0
  109. package/src/adapters/identity.ts +77 -0
  110. package/src/adapters/image.ts +23 -0
  111. package/src/adapters/kiro-constants.ts +16 -0
  112. package/src/adapters/kiro-errors.ts +208 -0
  113. package/src/adapters/kiro-events.ts +197 -0
  114. package/src/adapters/kiro-images.ts +129 -0
  115. package/src/adapters/kiro-retry.ts +312 -0
  116. package/src/adapters/kiro-thinking.ts +112 -0
  117. package/src/adapters/kiro-tool-fallback.ts +36 -0
  118. package/src/adapters/kiro-tools.ts +224 -0
  119. package/src/adapters/kiro-truncation.ts +33 -0
  120. package/src/adapters/kiro-wire.ts +129 -0
  121. package/src/adapters/kiro.ts +1936 -0
  122. package/src/adapters/mimo-free.ts +280 -0
  123. package/src/adapters/openai-chat-url.ts +11 -0
  124. package/src/adapters/openai-chat.ts +1980 -0
  125. package/src/adapters/openai-responses-url.ts +16 -0
  126. package/src/adapters/openai-responses.ts +1911 -0
  127. package/src/adapters/registry.ts +175 -0
  128. package/src/adapters/responses-tool-schema.ts +67 -0
  129. package/src/adapters/run-turn-queue.ts +114 -0
  130. package/src/adapters/tool-call-id.ts +119 -0
  131. package/src/adapters/tool-catalog-nudge.ts +154 -0
  132. package/src/adapters/upstream-http-error.ts +48 -0
  133. package/src/adapters/xai-web-search.ts +185 -0
  134. package/src/bridge.ts +1986 -0
  135. package/src/chat/inbound.ts +319 -0
  136. package/src/chat/outbound.ts +821 -0
  137. package/src/claude/agents-inject.ts +266 -0
  138. package/src/claude/alias.ts +149 -0
  139. package/src/claude/auth-detect.ts +229 -0
  140. package/src/claude/auth-mode-migration.ts +32 -0
  141. package/src/claude/auth-mode.ts +62 -0
  142. package/src/claude/context-windows.ts +205 -0
  143. package/src/claude/desktop-3p-guard.ts +35 -0
  144. package/src/claude/desktop-3p-paths.ts +84 -0
  145. package/src/claude/desktop-3p.ts +615 -0
  146. package/src/claude/desktop-health.ts +26 -0
  147. package/src/claude/desktop-profile.ts +263 -0
  148. package/src/claude/gateway-cache.ts +107 -0
  149. package/src/claude/inbound-debug.ts +163 -0
  150. package/src/claude/inbound.ts +578 -0
  151. package/src/claude/model-info.ts +174 -0
  152. package/src/claude/outbound.ts +926 -0
  153. package/src/cli/access.ts +108 -0
  154. package/src/cli/account-api.ts +302 -0
  155. package/src/cli/account-auth.ts +250 -0
  156. package/src/cli/account-catalog-refresh.ts +14 -0
  157. package/src/cli/account-extended.ts +737 -0
  158. package/src/cli/account-main.ts +317 -0
  159. package/src/cli/account.ts +299 -0
  160. package/src/cli/agent-driven.ts +70 -0
  161. package/src/cli/agent.ts +290 -0
  162. package/src/cli/catalog-prewarm.ts +27 -0
  163. package/src/cli/claude-agent-startup-sync.ts +73 -0
  164. package/src/cli/claude-desktop.ts +213 -0
  165. package/src/cli/claude.ts +355 -0
  166. package/src/cli/codex-log-guard-doctor.ts +103 -0
  167. package/src/cli/codex-shim-autorestore.ts +47 -0
  168. package/src/cli/codex-shim-readiness.ts +76 -0
  169. package/src/cli/combo.ts +127 -0
  170. package/src/cli/config-command.ts +209 -0
  171. package/src/cli/debug.ts +228 -0
  172. package/src/cli/dispatch.ts +585 -0
  173. package/src/cli/doctor.ts +1202 -0
  174. package/src/cli/ensure-desired-integrations.ts +152 -0
  175. package/src/cli/export-command.ts +213 -0
  176. package/src/cli/help.ts +101 -0
  177. package/src/cli/index.ts +973 -0
  178. package/src/cli/init.ts +211 -0
  179. package/src/cli/integrations.ts +260 -0
  180. package/src/cli/interactive-confirm.ts +133 -0
  181. package/src/cli/lab.ts +607 -0
  182. package/src/cli/launcher-context.ts +77 -0
  183. package/src/cli/minimax.ts +497 -0
  184. package/src/cli/models-runtime.ts +245 -0
  185. package/src/cli/models.ts +422 -0
  186. package/src/cli/observe.ts +206 -0
  187. package/src/cli/opencode.ts +588 -0
  188. package/src/cli/provider-replit.ts +232 -0
  189. package/src/cli/provider-runtime.ts +179 -0
  190. package/src/cli/provider.ts +492 -0
  191. package/src/cli/ready.ts +301 -0
  192. package/src/cli/registry.ts +422 -0
  193. package/src/cli/replit-gateway-key-input.ts +138 -0
  194. package/src/cli/root.ts +86 -0
  195. package/src/cli/route-policy.ts +92 -0
  196. package/src/cli/runtime-api.ts +328 -0
  197. package/src/cli/star-prompt.ts +211 -0
  198. package/src/cli/status-oauth.ts +78 -0
  199. package/src/cli/status.ts +328 -0
  200. package/src/cli/system-command.ts +112 -0
  201. package/src/cli/system-restart-client.ts +146 -0
  202. package/src/cli/tray-proxy.ts +199 -0
  203. package/src/cli/v2.ts +268 -0
  204. package/src/cli.ts +10 -0
  205. package/src/clients/config-export.ts +1704 -0
  206. package/src/codex/account-id.ts +34 -0
  207. package/src/codex/account-label.ts +47 -0
  208. package/src/codex/account-lifecycle.ts +172 -0
  209. package/src/codex/account-namespace-match.ts +63 -0
  210. package/src/codex/account-namespaces.ts +195 -0
  211. package/src/codex/account-pause.ts +20 -0
  212. package/src/codex/account-priority.ts +83 -0
  213. package/src/codex/account-runtime-state.ts +31 -0
  214. package/src/codex/account-store.ts +544 -0
  215. package/src/codex/account-usability.ts +43 -0
  216. package/src/codex/admission.ts +256 -0
  217. package/src/codex/affinity-debug.ts +162 -0
  218. package/src/codex/agent-roles-sync.ts +225 -0
  219. package/src/codex/agent-roles.ts +238 -0
  220. package/src/codex/app-server-processes.ts +1143 -0
  221. package/src/codex/app-server-restart-service.ts +232 -0
  222. package/src/codex/auth-api.ts +2147 -0
  223. package/src/codex/auth-collision.ts +109 -0
  224. package/src/codex/auth-context.ts +665 -0
  225. package/src/codex/autostart-health.ts +156 -0
  226. package/src/codex/catalog/account-models.ts +67 -0
  227. package/src/codex/catalog/aggregation.ts +436 -0
  228. package/src/codex/catalog/bundled.ts +549 -0
  229. package/src/codex/catalog/effort.ts +446 -0
  230. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  231. package/src/codex/catalog/kinds.ts +2 -0
  232. package/src/codex/catalog/metadata.ts +664 -0
  233. package/src/codex/catalog/native-models.ts +72 -0
  234. package/src/codex/catalog/parsing.ts +650 -0
  235. package/src/codex/catalog/provider-fetch.ts +2064 -0
  236. package/src/codex/catalog/sync.ts +1883 -0
  237. package/src/codex/catalog-admission.ts +199 -0
  238. package/src/codex/catalog-refresh-status.ts +105 -0
  239. package/src/codex/catalog-write-serialization.ts +242 -0
  240. package/src/codex/catalog.ts +14 -0
  241. package/src/codex/codex-write-lock.ts +384 -0
  242. package/src/codex/convergence-types.ts +614 -0
  243. package/src/codex/convergence.ts +651 -0
  244. package/src/codex/coordinator-doctor.ts +332 -0
  245. package/src/codex/custom-model-catalog-migration.ts +176 -0
  246. package/src/codex/data/upstream-models.json +830 -0
  247. package/src/codex/desired-state.ts +230 -0
  248. package/src/codex/exec-invocation.ts +22 -0
  249. package/src/codex/features.ts +1566 -0
  250. package/src/codex/generation.ts +202 -0
  251. package/src/codex/history-job.ts +407 -0
  252. package/src/codex/history-lock.ts +242 -0
  253. package/src/codex/history-migration-guardian.ts +108 -0
  254. package/src/codex/history-provider.ts +979 -0
  255. package/src/codex/history-transition.ts +105 -0
  256. package/src/codex/history-worker.ts +220 -0
  257. package/src/codex/home.ts +206 -0
  258. package/src/codex/inject-coordination.ts +290 -0
  259. package/src/codex/inject.ts +1733 -0
  260. package/src/codex/injected-marker.ts +106 -0
  261. package/src/codex/integration-record.ts +266 -0
  262. package/src/codex/internal/catalog-writer.ts +203 -0
  263. package/src/codex/internal/history-writer.ts +80 -0
  264. package/src/codex/journal.ts +225 -0
  265. package/src/codex/log-guard/inspect.ts +506 -0
  266. package/src/codex/log-guard/lock.ts +150 -0
  267. package/src/codex/log-guard/maintenance.ts +403 -0
  268. package/src/codex/log-guard/path-safety.ts +88 -0
  269. package/src/codex/log-guard/policy.ts +44 -0
  270. package/src/codex/log-guard/processes.ts +205 -0
  271. package/src/codex/log-guard/protection.ts +489 -0
  272. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  273. package/src/codex/main-account-cache.ts +56 -0
  274. package/src/codex/main-account.ts +68 -0
  275. package/src/codex/management-convergence.ts +167 -0
  276. package/src/codex/model-cache.ts +273 -0
  277. package/src/codex/model-entitlements.ts +353 -0
  278. package/src/codex/native-main-admission.ts +47 -0
  279. package/src/codex/native-main-auth-temp.ts +187 -0
  280. package/src/codex/native-main-claim.ts +178 -0
  281. package/src/codex/native-main-lock-file.ts +162 -0
  282. package/src/codex/native-main-owner.ts +329 -0
  283. package/src/codex/native-profile-api.ts +247 -0
  284. package/src/codex/native-profile-manager.ts +1531 -0
  285. package/src/codex/native-profile-processes.ts +121 -0
  286. package/src/codex/native-profile-recovery.ts +99 -0
  287. package/src/codex/native-profile-stage-store.ts +387 -0
  288. package/src/codex/native-profile-startup.ts +492 -0
  289. package/src/codex/native-profile-store.ts +855 -0
  290. package/src/codex/native-profile-types.ts +120 -0
  291. package/src/codex/native-residue.ts +682 -0
  292. package/src/codex/paths.ts +144 -0
  293. package/src/codex/plan-from-token.ts +140 -0
  294. package/src/codex/plan.ts +40 -0
  295. package/src/codex/plugins-doctor.ts +242 -0
  296. package/src/codex/pool-rotation.ts +295 -0
  297. package/src/codex/project-config-warnings.ts +425 -0
  298. package/src/codex/prompt-journal.ts +352 -0
  299. package/src/codex/prompt-layers.ts +967 -0
  300. package/src/codex/prompt-lock.ts +143 -0
  301. package/src/codex/quota-rejection.ts +298 -0
  302. package/src/codex/quota.ts +573 -0
  303. package/src/codex/refresh.ts +62 -0
  304. package/src/codex/reset-credit-recovery.ts +1044 -0
  305. package/src/codex/routing.ts +1888 -0
  306. package/src/codex/runtime.ts +659 -0
  307. package/src/codex/shim.ts +2170 -0
  308. package/src/codex/subagent-defaults.ts +550 -0
  309. package/src/codex/subagent-model-fallback.ts +784 -0
  310. package/src/codex/sync.ts +319 -0
  311. package/src/codex/transition-state.ts +612 -0
  312. package/src/codex/upstream-host-health.ts +368 -0
  313. package/src/codex/user-identity.ts +557 -0
  314. package/src/codex/warmup.ts +298 -0
  315. package/src/codex/websocket-registry.ts +100 -0
  316. package/src/codex/write-coordination.ts +114 -0
  317. package/src/combos/failover.ts +160 -0
  318. package/src/combos/index.ts +45 -0
  319. package/src/combos/request.ts +94 -0
  320. package/src/combos/resolve.ts +232 -0
  321. package/src/combos/types.ts +398 -0
  322. package/src/config/provider-name.ts +24 -0
  323. package/src/config.ts +4041 -0
  324. package/src/fork/register.ts +3 -0
  325. package/src/generated/compatibility-version.json +3116 -0
  326. package/src/generated/model-metadata.ts +106 -0
  327. package/src/github/star-state.ts +203 -0
  328. package/src/grok/inject.ts +530 -0
  329. package/src/grok/inspect.ts +45 -0
  330. package/src/grok/status.ts +121 -0
  331. package/src/grok/sync.ts +66 -0
  332. package/src/images/artifacts.ts +516 -0
  333. package/src/images/fulfill-video.ts +163 -0
  334. package/src/images/fulfill.ts +149 -0
  335. package/src/images/index.ts +4 -0
  336. package/src/images/loop.ts +955 -0
  337. package/src/images/plan.ts +143 -0
  338. package/src/images/synthetic-tool.ts +133 -0
  339. package/src/images/types.ts +41 -0
  340. package/src/images/xai-client.ts +141 -0
  341. package/src/images/xai-video-client.ts +163 -0
  342. package/src/index.ts +22 -0
  343. package/src/integrations/config-io.ts +269 -0
  344. package/src/integrations/journal.ts +315 -0
  345. package/src/integrations/merge.ts +135 -0
  346. package/src/integrations/mutation-flight.ts +71 -0
  347. package/src/integrations/native/ownership-preflight.ts +202 -0
  348. package/src/integrations/omp-yaml-source.ts +358 -0
  349. package/src/integrations/owned-refresh.ts +74 -0
  350. package/src/integrations/ownership.ts +111 -0
  351. package/src/integrations/registry.ts +159 -0
  352. package/src/integrations/serialize.ts +314 -0
  353. package/src/integrations/state.ts +361 -0
  354. package/src/integrations/store.ts +103 -0
  355. package/src/integrations/writer-lock.ts +98 -0
  356. package/src/integrations/writer.ts +691 -0
  357. package/src/lab/artifacts/sanitize.ts +586 -0
  358. package/src/lab/artifacts/secure-fs.ts +475 -0
  359. package/src/lab/artifacts/store.ts +310 -0
  360. package/src/lab/automation/budgets.ts +78 -0
  361. package/src/lab/automation/config-persistence.ts +256 -0
  362. package/src/lab/automation/constants.ts +39 -0
  363. package/src/lab/automation/cooldown.ts +103 -0
  364. package/src/lab/automation/dispatch.ts +211 -0
  365. package/src/lab/automation/index.ts +13 -0
  366. package/src/lab/automation/orchestrator.ts +499 -0
  367. package/src/lab/automation/persistence.ts +512 -0
  368. package/src/lab/automation/planner.ts +371 -0
  369. package/src/lab/automation/policy.ts +136 -0
  370. package/src/lab/automation/queue.ts +191 -0
  371. package/src/lab/automation/recovery.ts +24 -0
  372. package/src/lab/automation/route-context.ts +21 -0
  373. package/src/lab/automation/run-key.ts +44 -0
  374. package/src/lab/automation/runs-query.ts +34 -0
  375. package/src/lab/automation/types.ts +160 -0
  376. package/src/lab/conformance/assertion.ts +325 -0
  377. package/src/lab/conformance/digest.ts +22 -0
  378. package/src/lab/conformance/executor.ts +741 -0
  379. package/src/lab/conformance/fixture-provider.ts +27 -0
  380. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  381. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  382. package/src/lab/conformance/harness-budget.ts +47 -0
  383. package/src/lab/conformance/index.ts +5 -0
  384. package/src/lab/conformance/jcs.ts +64 -0
  385. package/src/lab/conformance/json-pointer.ts +39 -0
  386. package/src/lab/conformance/manifest.ts +180 -0
  387. package/src/lab/conformance/mcp-stub.ts +179 -0
  388. package/src/lab/conformance/negative-controls.ts +164 -0
  389. package/src/lab/conformance/observation.ts +355 -0
  390. package/src/lab/conformance/runner.ts +68 -0
  391. package/src/lab/conformance/sse-normalize.ts +59 -0
  392. package/src/lab/conformance/suite-manifest.ts +78 -0
  393. package/src/lab/conformance/types.ts +214 -0
  394. package/src/lab/constants.ts +126 -0
  395. package/src/lab/digest.ts +64 -0
  396. package/src/lab/events/errors.ts +9 -0
  397. package/src/lab/events/limits.ts +117 -0
  398. package/src/lab/events/types.ts +229 -0
  399. package/src/lab/events/validate.ts +781 -0
  400. package/src/lab/fabric/constants.ts +40 -0
  401. package/src/lab/fabric/executor.ts +492 -0
  402. package/src/lab/fabric/index.ts +80 -0
  403. package/src/lab/fabric/manifest.ts +222 -0
  404. package/src/lab/fabric/observe.ts +489 -0
  405. package/src/lab/fabric/patch.ts +79 -0
  406. package/src/lab/fabric/producer-child.ts +139 -0
  407. package/src/lab/fabric/producer-isolate.ts +276 -0
  408. package/src/lab/fabric/producer-protocol.ts +61 -0
  409. package/src/lab/fabric/scratch.ts +439 -0
  410. package/src/lab/fabric/subject.ts +106 -0
  411. package/src/lab/fabric/types.ts +134 -0
  412. package/src/lab/fabric/verifier.ts +98 -0
  413. package/src/lab/index.ts +54 -0
  414. package/src/lab/ledger/artifact-refs.ts +127 -0
  415. package/src/lab/ledger/invalidation.ts +136 -0
  416. package/src/lab/ledger/purge.ts +310 -0
  417. package/src/lab/ledger/store.ts +532 -0
  418. package/src/lab/live/credential-lease.ts +53 -0
  419. package/src/lab/live/destination.ts +155 -0
  420. package/src/lab/live/executor.ts +336 -0
  421. package/src/lab/live/inert-tools.ts +56 -0
  422. package/src/lab/live/manifest.ts +85 -0
  423. package/src/lab/live/mcp-loopback.ts +57 -0
  424. package/src/lab/live/runner.ts +19 -0
  425. package/src/lab/live/sandbox.ts +61 -0
  426. package/src/lab/live/suite-manifest.ts +41 -0
  427. package/src/lab/live/transport.ts +118 -0
  428. package/src/lab/live/types.ts +197 -0
  429. package/src/lab/observe/from-conformance.ts +301 -0
  430. package/src/lab/observe/from-live.ts +117 -0
  431. package/src/lab/paths.ts +153 -0
  432. package/src/lab/projection/rebuild.ts +495 -0
  433. package/src/lab/projection/schema.ts +135 -0
  434. package/src/lab/projection/verdicts.ts +474 -0
  435. package/src/lab/projection/verification.ts +412 -0
  436. package/src/lab/public/bundle.ts +217 -0
  437. package/src/lab/public/community-authority.ts +175 -0
  438. package/src/lab/public/community-files.ts +29 -0
  439. package/src/lab/public/community.ts +479 -0
  440. package/src/lab/public/file-safety.ts +155 -0
  441. package/src/lab/public/ids.ts +26 -0
  442. package/src/lab/public/index.ts +16 -0
  443. package/src/lab/public/mutation-lock.ts +424 -0
  444. package/src/lab/public/operator.ts +353 -0
  445. package/src/lab/public/origin-purge.ts +79 -0
  446. package/src/lab/public/origin.ts +203 -0
  447. package/src/lab/public/privacy.ts +143 -0
  448. package/src/lab/public/private-file.ts +261 -0
  449. package/src/lab/public/project.ts +124 -0
  450. package/src/lab/public/purge-test-fault.ts +21 -0
  451. package/src/lab/public/purge.ts +223 -0
  452. package/src/lab/public/registry.ts +44 -0
  453. package/src/lab/public/revocation.ts +252 -0
  454. package/src/lab/public/signature.ts +243 -0
  455. package/src/lab/public/storage.ts +105 -0
  456. package/src/lab/public/strict-json.ts +206 -0
  457. package/src/lab/public/time.ts +26 -0
  458. package/src/lab/public/types.ts +172 -0
  459. package/src/lab/public/validate.ts +391 -0
  460. package/src/lab/query/catalog.ts +101 -0
  461. package/src/lab/query/connection.ts +107 -0
  462. package/src/lab/query/constants.ts +4 -0
  463. package/src/lab/query/cursor.ts +132 -0
  464. package/src/lab/query/dto-map.ts +277 -0
  465. package/src/lab/query/errors.ts +22 -0
  466. package/src/lab/query/freshness.ts +53 -0
  467. package/src/lab/query/index.ts +45 -0
  468. package/src/lab/query/latest-observation.ts +59 -0
  469. package/src/lab/query/passive-production.ts +159 -0
  470. package/src/lab/query/queries.ts +444 -0
  471. package/src/lab/query/types.ts +266 -0
  472. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  473. package/src/lab/subject/installation-salt.ts +112 -0
  474. package/src/lab/subject/protocol-subject.ts +80 -0
  475. package/src/lab/subject/route-subject.ts +74 -0
  476. package/src/lib/abort.ts +146 -0
  477. package/src/lib/admin-secrets.ts +25 -0
  478. package/src/lib/admission.ts +83 -0
  479. package/src/lib/app-owned-memory-stores.ts +195 -0
  480. package/src/lib/app-owned-memory.ts +265 -0
  481. package/src/lib/bounded-body.ts +346 -0
  482. package/src/lib/bun-binary-validator.d.mts +3 -0
  483. package/src/lib/bun-binary-validator.mjs +18 -0
  484. package/src/lib/bun-runtime.ts +184 -0
  485. package/src/lib/bun-stream-caps.ts +130 -0
  486. package/src/lib/codex-restart-contract.ts +120 -0
  487. package/src/lib/config-ownership.ts +364 -0
  488. package/src/lib/crash-guard.ts +344 -0
  489. package/src/lib/debug-log-buffer.ts +83 -0
  490. package/src/lib/debug-settings.ts +108 -0
  491. package/src/lib/debug.ts +31 -0
  492. package/src/lib/destination-policy.ts +380 -0
  493. package/src/lib/errors.ts +406 -0
  494. package/src/lib/eventstream-decoder.ts +253 -0
  495. package/src/lib/fabric-task-execution-authority.ts +7 -0
  496. package/src/lib/fabric-task-host.ts +29 -0
  497. package/src/lib/gcp-adc.ts +341 -0
  498. package/src/lib/injection-debug-log.ts +58 -0
  499. package/src/lib/lab-activation.ts +223 -0
  500. package/src/lib/lab-live-execution-authority.ts +13 -0
  501. package/src/lib/lab-live-host.ts +30 -0
  502. package/src/lib/lab-live-pinned-sender.ts +56 -0
  503. package/src/lib/lab-live-route-production.ts +130 -0
  504. package/src/lib/lab-passive-linker-registration.ts +26 -0
  505. package/src/lib/local-management-attestation.ts +51 -0
  506. package/src/lib/local-management-capability.ts +100 -0
  507. package/src/lib/local-provider-reload-contract.ts +100 -0
  508. package/src/lib/open-url.ts +25 -0
  509. package/src/lib/optional-shutdown-hooks.ts +57 -0
  510. package/src/lib/pinned-http.ts +270 -0
  511. package/src/lib/privacy.ts +20 -0
  512. package/src/lib/process-control.ts +168 -0
  513. package/src/lib/provider-outbound.ts +210 -0
  514. package/src/lib/provider-url.ts +14 -0
  515. package/src/lib/proxy-env.ts +18 -0
  516. package/src/lib/redact.ts +521 -0
  517. package/src/lib/retry-after.ts +55 -0
  518. package/src/lib/self-launch-argv.ts +15 -0
  519. package/src/lib/server-resource-ownership.ts +71 -0
  520. package/src/lib/service-secrets.ts +25 -0
  521. package/src/lib/shadow-call.ts +61 -0
  522. package/src/lib/sidecar-tracker.ts +52 -0
  523. package/src/lib/sse-decoder.ts +364 -0
  524. package/src/lib/state-store-registrations.ts +119 -0
  525. package/src/lib/state-store-sweeper.ts +184 -0
  526. package/src/lib/system-restart-contract.ts +73 -0
  527. package/src/lib/test-home-guard.ts +90 -0
  528. package/src/lib/token-estimate.ts +86 -0
  529. package/src/lib/tool-argument-integers.ts +202 -0
  530. package/src/lib/translator-budget.ts +400 -0
  531. package/src/lib/upstream-http-version.ts +57 -0
  532. package/src/lib/upstream-reachability.ts +95 -0
  533. package/src/lib/upstream-retry.ts +392 -0
  534. package/src/lib/win-exec.ts +115 -0
  535. package/src/lib/win-paths.ts +68 -0
  536. package/src/lib/windows-atomic-replace.ts +156 -0
  537. package/src/lib/windows-elevation.ts +773 -0
  538. package/src/lib/windows-secret-acl.ts +854 -0
  539. package/src/lib/windows-service-wrappers.ts +72 -0
  540. package/src/lib/windows-text.ts +106 -0
  541. package/src/lib/windows-user-principal.ts +341 -0
  542. package/src/lib/winsw.ts +403 -0
  543. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  544. package/src/oauth/account-import/index.ts +15 -0
  545. package/src/oauth/account-import/parser.ts +83 -0
  546. package/src/oauth/account-import/registry.ts +18 -0
  547. package/src/oauth/account-import/service.ts +75 -0
  548. package/src/oauth/account-import/types.ts +91 -0
  549. package/src/oauth/anthropic-routing.ts +594 -0
  550. package/src/oauth/anthropic.ts +188 -0
  551. package/src/oauth/antigravity-routing.ts +151 -0
  552. package/src/oauth/callback-server.ts +300 -0
  553. package/src/oauth/chatgpt.ts +161 -0
  554. package/src/oauth/command-code.ts +239 -0
  555. package/src/oauth/cursor.ts +252 -0
  556. package/src/oauth/github-copilot.ts +428 -0
  557. package/src/oauth/google-antigravity.ts +262 -0
  558. package/src/oauth/health.ts +407 -0
  559. package/src/oauth/index.ts +1504 -0
  560. package/src/oauth/key-providers.ts +124 -0
  561. package/src/oauth/kimi.ts +227 -0
  562. package/src/oauth/kiro-credentials.ts +726 -0
  563. package/src/oauth/kiro.ts +621 -0
  564. package/src/oauth/local-token-detect.ts +130 -0
  565. package/src/oauth/log.ts +50 -0
  566. package/src/oauth/login-cli.ts +223 -0
  567. package/src/oauth/nous.ts +798 -0
  568. package/src/oauth/pkce.ts +15 -0
  569. package/src/oauth/store.ts +728 -0
  570. package/src/oauth/token-guardian.ts +309 -0
  571. package/src/oauth/types.ts +62 -0
  572. package/src/oauth/xai.ts +241 -0
  573. package/src/providers/alibaba-region-backup.ts +75 -0
  574. package/src/providers/alibaba-region-migration.ts +156 -0
  575. package/src/providers/alibaba-region-startup.ts +36 -0
  576. package/src/providers/antigravity-models.ts +695 -0
  577. package/src/providers/antigravity-quota.ts +216 -0
  578. package/src/providers/api-keys.ts +140 -0
  579. package/src/providers/base-url-choices.ts +74 -0
  580. package/src/providers/codex-capacity.ts +292 -0
  581. package/src/providers/command-code-efforts.ts +144 -0
  582. package/src/providers/context-cap.ts +82 -0
  583. package/src/providers/cursor-pool.ts +72 -0
  584. package/src/providers/derive.ts +586 -0
  585. package/src/providers/fastwire.ts +501 -0
  586. package/src/providers/free-directory.ts +187 -0
  587. package/src/providers/github-copilot-transport.ts +56 -0
  588. package/src/providers/google-vertex-location.ts +14 -0
  589. package/src/providers/key-failover.ts +271 -0
  590. package/src/providers/kiro-models.ts +67 -0
  591. package/src/providers/label.ts +19 -0
  592. package/src/providers/model-discovery-limits.ts +16 -0
  593. package/src/providers/model-discovery.ts +449 -0
  594. package/src/providers/model-rename-migration.ts +255 -0
  595. package/src/providers/model-rename-startup.ts +28 -0
  596. package/src/providers/openai-sidecar.ts +243 -0
  597. package/src/providers/openai-tier-startup.ts +56 -0
  598. package/src/providers/openai-tiers.ts +423 -0
  599. package/src/providers/openai-virtual-models.ts +83 -0
  600. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  601. package/src/providers/openrouter-routing.ts +102 -0
  602. package/src/providers/provider-id-rewrite.ts +185 -0
  603. package/src/providers/quota.ts +2345 -0
  604. package/src/providers/registry.ts +2918 -0
  605. package/src/providers/replit/constants.ts +27 -0
  606. package/src/providers/replit/derive.ts +85 -0
  607. package/src/providers/replit/headers.ts +28 -0
  608. package/src/providers/replit/origin.ts +55 -0
  609. package/src/providers/replit/pair-install-response.ts +72 -0
  610. package/src/providers/replit/probe.ts +199 -0
  611. package/src/providers/replit/setup.ts +350 -0
  612. package/src/providers/request-pacing.ts +310 -0
  613. package/src/providers/service-tier.ts +277 -0
  614. package/src/providers/slug-codec.ts +103 -0
  615. package/src/providers/static-model-discovery.ts +86 -0
  616. package/src/providers/xai-responses-opt-in.ts +15 -0
  617. package/src/providers/xai-transport.ts +148 -0
  618. package/src/reasoning-effort.ts +183 -0
  619. package/src/responses/compaction.ts +142 -0
  620. package/src/responses/custom-tool-compat.ts +266 -0
  621. package/src/responses/hosted-tool-policy.ts +9 -0
  622. package/src/responses/namespace-tool-compat.ts +355 -0
  623. package/src/responses/parser.ts +838 -0
  624. package/src/responses/provider-continuation.ts +98 -0
  625. package/src/responses/provider-opaque-metadata.ts +73 -0
  626. package/src/responses/reasoning-envelope.ts +60 -0
  627. package/src/responses/reasoning-replay-cache.ts +426 -0
  628. package/src/responses/schema.ts +165 -0
  629. package/src/responses/spill-store.ts +459 -0
  630. package/src/responses/state.ts +1433 -0
  631. package/src/responses/thought-signature-replay.ts +347 -0
  632. package/src/responses/tool-groups.ts +19 -0
  633. package/src/responses/tool-search-compat.ts +301 -0
  634. package/src/responses/truncated-stop-reason.ts +60 -0
  635. package/src/router.ts +761 -0
  636. package/src/routing/analytics.ts +378 -0
  637. package/src/routing/capability.ts +244 -0
  638. package/src/routing/compatibility/assemble.ts +73 -0
  639. package/src/routing/compatibility/behavior.ts +278 -0
  640. package/src/routing/compatibility/catalog.ts +99 -0
  641. package/src/routing/compatibility/endpoint.ts +52 -0
  642. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  643. package/src/routing/compatibility/policy.ts +181 -0
  644. package/src/routing/compatibility/provider-slot.ts +56 -0
  645. package/src/routing/compatibility/reader.ts +110 -0
  646. package/src/routing/compatibility/subject.ts +191 -0
  647. package/src/routing/compatibility/types.ts +64 -0
  648. package/src/routing/compatibility/version.ts +104 -0
  649. package/src/routing/cost.ts +77 -0
  650. package/src/routing/evaluator.ts +495 -0
  651. package/src/routing/health.ts +412 -0
  652. package/src/routing/history/cursor.ts +43 -0
  653. package/src/routing/history/indexer.ts +605 -0
  654. package/src/routing/history/schema.ts +72 -0
  655. package/src/routing/profile-namespace.ts +15 -0
  656. package/src/routing/profile.ts +547 -0
  657. package/src/routing/quota.ts +145 -0
  658. package/src/routing/request-evidence.ts +45 -0
  659. package/src/routing/trace.ts +776 -0
  660. package/src/server/adapter-resolve.ts +53 -0
  661. package/src/server/auth-cors.ts +751 -0
  662. package/src/server/background-lifecycle.ts +182 -0
  663. package/src/server/chat-completions.ts +442 -0
  664. package/src/server/chat-native-sse.ts +331 -0
  665. package/src/server/chat-native.ts +426 -0
  666. package/src/server/claude-messages.ts +1030 -0
  667. package/src/server/direct-local-http.ts +347 -0
  668. package/src/server/effort-policy.ts +190 -0
  669. package/src/server/github-copilot-responses-repair.ts +338 -0
  670. package/src/server/gui-static.ts +152 -0
  671. package/src/server/image-retry.ts +42 -0
  672. package/src/server/images.ts +568 -0
  673. package/src/server/index.ts +1813 -0
  674. package/src/server/lifecycle.ts +498 -0
  675. package/src/server/live.ts +717 -0
  676. package/src/server/local-management-read-client.ts +90 -0
  677. package/src/server/local-provider-reload-client.ts +137 -0
  678. package/src/server/management/agent-settings-routes.ts +1433 -0
  679. package/src/server/management/api-access.ts +141 -0
  680. package/src/server/management/api-key-usage.ts +193 -0
  681. package/src/server/management/body.ts +41 -0
  682. package/src/server/management/combo-routes.ts +263 -0
  683. package/src/server/management/config-routes.ts +835 -0
  684. package/src/server/management/context.ts +113 -0
  685. package/src/server/management/integration-routes.ts +498 -0
  686. package/src/server/management/lab-automation-routes.ts +206 -0
  687. package/src/server/management/lab-routes.ts +563 -0
  688. package/src/server/management/logs-usage-routes.ts +586 -0
  689. package/src/server/management/model-routes.ts +560 -0
  690. package/src/server/management/model-rows.ts +163 -0
  691. package/src/server/management/native-integration-routes.ts +769 -0
  692. package/src/server/management/oauth-account-routes.ts +637 -0
  693. package/src/server/management/provider-capability-config.ts +48 -0
  694. package/src/server/management/provider-routes.ts +1033 -0
  695. package/src/server/management/replit-provider-routes.ts +86 -0
  696. package/src/server/management/request-history-routes.ts +191 -0
  697. package/src/server/management/routing-analytics-routes.ts +74 -0
  698. package/src/server/management/routing-profile-routes.ts +385 -0
  699. package/src/server/management/shared.ts +286 -0
  700. package/src/server/management/sidebar-routes.ts +106 -0
  701. package/src/server/management/storage-log-guard-routes.ts +186 -0
  702. package/src/server/management/sync-response.ts +69 -0
  703. package/src/server/management/system-restart.ts +435 -0
  704. package/src/server/management/system-routes.ts +194 -0
  705. package/src/server/management/usage-summary-cache.ts +94 -0
  706. package/src/server/management/vision-sidecar-options.ts +167 -0
  707. package/src/server/management/web-search-sidecar-options.ts +120 -0
  708. package/src/server/management-api.ts +314 -0
  709. package/src/server/management-auth.ts +482 -0
  710. package/src/server/memory-watchdog.ts +156 -0
  711. package/src/server/passive-route-linker.ts +66 -0
  712. package/src/server/port-reclaim.ts +307 -0
  713. package/src/server/ports.ts +156 -0
  714. package/src/server/proxy-liveness.ts +328 -0
  715. package/src/server/readiness.ts +99 -0
  716. package/src/server/relay-eager.ts +353 -0
  717. package/src/server/relay.ts +1209 -0
  718. package/src/server/request-decompress.ts +239 -0
  719. package/src/server/request-log-conversation.ts +168 -0
  720. package/src/server/request-log.ts +1259 -0
  721. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  722. package/src/server/responses/agent-task-recovery.ts +465 -0
  723. package/src/server/responses/collaboration.ts +551 -0
  724. package/src/server/responses/compact.ts +771 -0
  725. package/src/server/responses/core.ts +5389 -0
  726. package/src/server/responses/empty-completion-guard.ts +276 -0
  727. package/src/server/responses/encrypted-payload.ts +331 -0
  728. package/src/server/responses/fetch-helpers.ts +232 -0
  729. package/src/server/responses/input-admission.ts +185 -0
  730. package/src/server/responses/pacing-overload.ts +13 -0
  731. package/src/server/responses/passthrough-error.ts +78 -0
  732. package/src/server/responses/policy-fallback.ts +178 -0
  733. package/src/server/responses/responses-field-backfill.ts +251 -0
  734. package/src/server/responses/terminal-guard.ts +251 -0
  735. package/src/server/responses/upstream-error.ts +53 -0
  736. package/src/server/responses/ws-upstream.ts +308 -0
  737. package/src/server/responses-custom-tool-repair.ts +282 -0
  738. package/src/server/responses-image-gen-repair.ts +132 -0
  739. package/src/server/responses-item-id-repair.ts +272 -0
  740. package/src/server/responses-json-events.ts +90 -0
  741. package/src/server/responses-model-rewrite.ts +29 -0
  742. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  743. package/src/server/responses-snapshot-repair.ts +621 -0
  744. package/src/server/responses-terminal-repair.ts +342 -0
  745. package/src/server/responses-tool-search-repair.ts +267 -0
  746. package/src/server/responses-undeclared-tool-guard.ts +153 -0
  747. package/src/server/responses.ts +25 -0
  748. package/src/server/search.ts +201 -0
  749. package/src/server/sse-frame-buffer.ts +292 -0
  750. package/src/server/sse-payload-rewrite.ts +263 -0
  751. package/src/server/startup-action-control.ts +315 -0
  752. package/src/server/startup-health-cache.ts +131 -0
  753. package/src/server/system-env.ts +484 -0
  754. package/src/server/windows-tcp-drop.ts +184 -0
  755. package/src/server/windows-tray-control.ts +41 -0
  756. package/src/server/ws-bridge.ts +472 -0
  757. package/src/service-manager-probe.ts +892 -0
  758. package/src/service.ts +3575 -0
  759. package/src/sidecar/auth.ts +92 -0
  760. package/src/sidecar/candidates.ts +83 -0
  761. package/src/stall-timeout.ts +20 -0
  762. package/src/storage/cleanup-job.ts +57 -0
  763. package/src/storage/cleanup.ts +3085 -0
  764. package/src/storage/policy-job.ts +457 -0
  765. package/src/storage/policy-scheduler.ts +40 -0
  766. package/src/storage/policy-worker.ts +59 -0
  767. package/src/storage/policy.ts +527 -0
  768. package/src/storage/restore-job.ts +299 -0
  769. package/src/storage/restore-worker.ts +58 -0
  770. package/src/storage/scanner.ts +238 -0
  771. package/src/storage/storage-mutation-coordinator.ts +139 -0
  772. package/src/storage/worker-lifecycle.ts +215 -0
  773. package/src/tray/assets/opencodex-tray-offline.ico +0 -0
  774. package/src/tray/assets/opencodex-tray-online.ico +0 -0
  775. package/src/tray/assets/opencodex-tray-warning.ico +0 -0
  776. package/src/tray/assets/opencodex-tray.png +0 -0
  777. package/src/tray/windows-tray.ps1 +364 -0
  778. package/src/tray/windows.ts +757 -0
  779. package/src/types/accounts.ts +37 -0
  780. package/src/types/config.ts +876 -0
  781. package/src/types/provider.ts +545 -0
  782. package/src/types/request.ts +384 -0
  783. package/src/types/tools.ts +131 -0
  784. package/src/types/wire.ts +80 -0
  785. package/src/types.ts +106 -0
  786. package/src/update/badge.ts +72 -0
  787. package/src/update/index.ts +415 -0
  788. package/src/update/job.ts +1887 -0
  789. package/src/update/notify.ts +263 -0
  790. package/src/update/npm-cache-preflight.d.mts +47 -0
  791. package/src/update/npm-cache-preflight.mjs +201 -0
  792. package/src/update/npm-invocation.d.mts +23 -0
  793. package/src/update/npm-invocation.mjs +94 -0
  794. package/src/update/transactional-install.d.mts +22 -0
  795. package/src/update/transactional-install.mjs +259 -0
  796. package/src/update/tray-update-plan.d.mts +18 -0
  797. package/src/update/tray-update-plan.mjs +38 -0
  798. package/src/usage/cost.ts +625 -0
  799. package/src/usage/debug.ts +97 -0
  800. package/src/usage/expected-prices.ts +416 -0
  801. package/src/usage/log.ts +1223 -0
  802. package/src/usage/summary.ts +753 -0
  803. package/src/usage/totals.ts +14 -0
  804. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  805. package/src/usage/user-cost-overlays.ts +314 -0
  806. package/src/vision/anthropic-describe.ts +189 -0
  807. package/src/vision/backends.ts +97 -0
  808. package/src/vision/describe.ts +131 -0
  809. package/src/vision/eligibility.ts +250 -0
  810. package/src/vision/index.ts +681 -0
  811. package/src/vision/reasoning.ts +55 -0
  812. package/src/vision/routed-describe.ts +175 -0
  813. package/src/vision/timeout-bounds.ts +9 -0
  814. package/src/web-search/anthropic-executor.ts +195 -0
  815. package/src/web-search/backends.ts +108 -0
  816. package/src/web-search/exa-executor.ts +88 -0
  817. package/src/web-search/executor.ts +113 -0
  818. package/src/web-search/format-result.ts +89 -0
  819. package/src/web-search/gemini-executor.ts +141 -0
  820. package/src/web-search/index.ts +331 -0
  821. package/src/web-search/loop.ts +896 -0
  822. package/src/web-search/parse.ts +315 -0
  823. package/src/web-search/progress-stream.ts +342 -0
  824. package/src/web-search/sources.ts +60 -0
  825. package/src/web-search/synthetic-tool.ts +47 -0
  826. package/src/web-search/xai-executor.ts +219 -0
@@ -0,0 +1,896 @@
1
+ import type { AdapterRequest, IncomingMeta, ProviderAdapter } from "../adapters/base";
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";
6
+ import { bridgeToResponsesSSE } from "../bridge";
7
+ import { runWebSearch, type SidecarOutcome, type SidecarOutcomeRecorder, type SidecarSettings } from "./executor";
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";
13
+ import { clearableDeadline } from "../lib/abort";
14
+ import { redactSecretString } from "../lib/redact";
15
+ import { readBoundedResponseBody } from "../lib/bounded-body";
16
+ import { fetchWithResetRetry, prepareSameTarget429Wait } from "../lib/upstream-retry";
17
+ import { rateLimitRetryDelayMs } from "../providers/key-failover";
18
+ import {
19
+ isTranslatorBudgetExceededError,
20
+ TRANSLATOR_MAX_TURN_BYTES,
21
+ TranslatorBudgetExceededError,
22
+ } from "../lib/translator-budget";
23
+ import { formatWebSearchResults } from "./format-result";
24
+ import { parseStreamWithProgress, RoutedModelInactivityError, WebSearchStreamProtocolError } from "./progress-stream";
25
+ import { WEB_SEARCH_TOOL_NAME } from "./synthetic-tool";
26
+
27
+ const SSE_HEADERS = {
28
+ "Content-Type": "text/event-stream",
29
+ "Cache-Control": "no-cache",
30
+ "Connection": "keep-alive",
31
+ "X-Accel-Buffering": "no",
32
+ };
33
+
34
+ interface WebSearchCall {
35
+ id: string;
36
+ // One or more queries the model batched into a single web_search call. Always length >= 0; an
37
+ // empty array means the model called the tool with neither `query` nor `queries` (handled as an
38
+ // empty-query placeholder).
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;
45
+ }
46
+
47
+ /**
48
+ * Normalize a web_search tool call's raw JSON args into a canonical `queries[]`. Accepts native
49
+ * plural `queries: string[]` or singular `query: string` (the model may send either). Non-string /
50
+ * empty entries are dropped; malformed JSON yields `[]` (handled downstream as an empty-query call).
51
+ */
52
+ function parseQueries(argsBuf: string): string[] {
53
+ try {
54
+ const o: unknown = JSON.parse(argsBuf || "{}");
55
+ if (!o || typeof o !== "object") return [];
56
+ const obj = o as { query?: unknown; queries?: unknown };
57
+ if (Array.isArray(obj.queries)) {
58
+ const qs = obj.queries.filter((q): q is string => typeof q === "string" && q.trim() !== "");
59
+ if (qs.length > 0) return qs;
60
+ }
61
+ if (typeof obj.query === "string" && obj.query.trim() !== "") return [obj.query];
62
+ } catch { /* malformed args → empty */ }
63
+ return [];
64
+ }
65
+
66
+ /**
67
+ * Split a non-streaming turn's adapter events into (a) the web_search calls to intercept and (b) the
68
+ * events to pass through to Codex. A web_search tool-call's own start/delta/end events are dropped
69
+ * (Codex never sees the synthetic tool); every other event — text, thinking, real tool calls, done —
70
+ * is preserved in order.
71
+ */
72
+ export function scanEventsForWebSearch(events: AdapterEvent[]): {
73
+ calls: WebSearchCall[];
74
+ passthrough: AdapterEvent[];
75
+ hasRealToolCall: boolean;
76
+ hasMalformedToolCall: boolean;
77
+ } {
78
+ const calls: WebSearchCall[] = [];
79
+ const passthrough: AdapterEvent[] = [];
80
+ let hasRealToolCall = false;
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;
84
+ const flushPending = (): void => {
85
+ // A pending call that never saw tool_call_end is structurally malformed.
86
+ if (pending && !pending.closed) hasMalformedToolCall = true;
87
+ if (pending && pending.name !== WEB_SEARCH_TOOL_NAME) {
88
+ passthrough.push(...pending.events);
89
+ if (pending.closed && !isBlank(pending.id) && !isBlank(pending.name)) hasRealToolCall = true;
90
+ }
91
+ pending = null;
92
+ };
93
+ for (const e of events) {
94
+ if (e.type === "tool_call_start") {
95
+ flushPending();
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;
109
+ } else {
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;
119
+ }
120
+ } else {
121
+ passthrough.push(e);
122
+ }
123
+ }
124
+ flushPending();
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);
138
+ }
139
+
140
+ async function* replay(events: AdapterEvent[]): AsyncGenerator<AdapterEvent> {
141
+ for (const e of events) yield e;
142
+ }
143
+
144
+ /**
145
+ * Collect the thinking block that preceded a web_search call in this iteration's events, so the
146
+ * replayed assistant turn can carry it. Anthropic extended thinking REQUIRES the assistant
147
+ * message that contains tool_use to start with its signed thinking/redacted_thinking blocks —
148
+ * replaying a bare toolCall 400s ("Expected `thinking` or `redacted_thinking`, but found
149
+ * `tool_use`"). The signature validity gate stays in the anthropic adapter; other adapters
150
+ * ignore or serialize the part harmlessly.
151
+ *
152
+ * Each signed block keeps its OWN signature and text, mirroring src/images/loop.ts: a signature
153
+ * authenticates the exact block it closed, so flattening two blocks under the last signature
154
+ * 400s on replay just as it does there.
155
+ *
156
+ * Raw reasoning (`reasoning_raw_delta`, what OpenAI-compatible providers emit instead of signed
157
+ * thinking) accumulates into a SEPARATE UNSIGNED part. It must never join a signed block: the
158
+ * anthropic serializer skips signature-less parts, while openai-chat serializes their text as
159
+ * `reasoning_content` — which DeepSeek V4 thinking mode requires back alongside the replayed
160
+ * tool_calls, and whose absence ended the turn as a provider 400 (issue #688).
161
+ *
162
+ * This assumes raw reasoning never interleaves INSIDE an unfinished signed block: Anthropic-family
163
+ * adapters emit thinking_delta/signature and OpenAI-compatible ones emit reasoning_raw_delta, and
164
+ * the two never share a stream. Honoring a genuinely mixed stream would need per-segment state,
165
+ * not another accumulator.
166
+ */
167
+ function extractIterationThinking(events: AdapterEvent[]): OcxThinkingContent[] {
168
+ const parts: OcxThinkingContent[] = [];
169
+ let thinking = "";
170
+ let signature: string | undefined;
171
+ let rawReasoning = "";
172
+
173
+ const flushVisible = () => {
174
+ if (!thinking && !signature) return;
175
+ parts.push({
176
+ type: "thinking",
177
+ thinking,
178
+ ...(signature ? { signature } : {}),
179
+ });
180
+ thinking = "";
181
+ signature = undefined;
182
+ };
183
+ const flushRaw = () => {
184
+ if (!rawReasoning) return;
185
+ parts.push({ type: "thinking", thinking: rawReasoning });
186
+ rawReasoning = "";
187
+ };
188
+
189
+ for (const e of events) {
190
+ if (e.type === "thinking_delta") {
191
+ flushRaw();
192
+ thinking += e.thinking;
193
+ } else if (e.type === "reasoning_raw_delta") {
194
+ flushVisible();
195
+ rawReasoning += e.text;
196
+ } else if (e.type === "thinking_signature") {
197
+ signature = e.signature;
198
+ flushVisible();
199
+ } else if (e.type === "redacted_thinking") {
200
+ flushVisible();
201
+ flushRaw();
202
+ parts.push({ type: "thinking", thinking: "", redacted: [e.data] });
203
+ }
204
+ }
205
+ flushVisible();
206
+ flushRaw();
207
+ return parts;
208
+ }
209
+
210
+ /** Normalize a query for failed-query de-duplication (case/whitespace-insensitive). */
211
+ function normalizeQuery(q: string): string {
212
+ return q.trim().toLowerCase().replace(/\s+/g, " ");
213
+ }
214
+
215
+ /**
216
+ * Transient developer-role nudge appended ONLY to the forced-answer pass's request (never the
217
+ * persisted `messages`). It tells the model to ground its final answer in the web results already
218
+ * gathered this turn. Citation wording is conditional — a failed/empty search still wants an answer,
219
+ * just without fabricated sources.
220
+ */
221
+ function forcedAnswerNudge(): OcxMessage {
222
+ return {
223
+ role: "developer",
224
+ content:
225
+ "Answer the user's question now using the web search results already gathered above. " +
226
+ "Ground your answer in what those results actually say, and reference the relevant sources " +
227
+ "when they are available. Do not claim you lack information that the results contain, and do " +
228
+ "not invent sources that were not returned.",
229
+ timestamp: Date.now(),
230
+ };
231
+ }
232
+
233
+ function jsonError(status: number, message: string): Response {
234
+ return new Response(JSON.stringify({ error: { message, type: "upstream_error", code: null } }), {
235
+ status,
236
+ headers: { "Content-Type": "application/json" },
237
+ });
238
+ }
239
+
240
+ /** Hard provider/parse failure inside an iteration. The eager first iteration converts it to a
241
+ * non-200 jsonError; later (already-streaming) iterations surface it as an in-stream error event. */
242
+ class LoopError extends Error {
243
+ constructor(readonly status: number, message: string) {
244
+ super(message);
245
+ this.name = "LoopError";
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Dependencies for one web-search loop iteration: parsed request, active adapter,
251
+ * incoming metadata, and the configured search executor.
252
+ */
253
+ export interface WebSearchLoopDeps {
254
+ parsed: OcxParsedRequest;
255
+ adapter: ProviderAdapter;
256
+ incomingMeta: IncomingMeta;
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;
263
+ /** Required for the openai backend; unused (and typically undefined) for the anthropic backend. */
264
+ forwardProvider?: OcxProviderConfig;
265
+ /** Required for the anthropic backend: the stored-OAuth provider that runs web_search_20250305. */
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;
275
+ hostedTool: Record<string, unknown>;
276
+ selectedForwardHeaders: Headers;
277
+ settings: SidecarSettings;
278
+ maxSearches: number;
279
+ forceEmptyResponseId?: boolean;
280
+ abortSignal?: AbortSignal;
281
+ recordSidecarOutcome?: SidecarOutcomeRecorder;
282
+ /** Cumulative per-iteration deadline for DNS/TCP/TLS and final response headers only. */
283
+ connectTimeoutMs?: number;
284
+ /** Continuous routed-model response-body raw-byte inactivity deadline. Default 200000ms. */
285
+ routedModelStallTimeoutMs?: number;
286
+ /**
287
+ * Effective bridge stall deadline for this turn (seconds). Computed by planWebSearch
288
+ * (webSearchStallTimeoutSec) to cover response-header wait, routed-model body inactivity, and one
289
+ * sidecar search, so a legitimately slow-but-progressing unit never trips the bridge watchdog.
290
+ */
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;
298
+ /** One-shot TTFT callback: first non-empty model output observed (WP4). */
299
+ onFirstOutput?: () => void;
300
+ /** Raw adapter usage at the terminal event, pre wire-normalization (see bridgeToResponsesSSE onUsage). */
301
+ onUsage?: (usage: OcxUsage | undefined) => void;
302
+ /** Observe the exact adapter request selected for each routed-model iteration. */
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;
306
+ /**
307
+ * 429 key-failover hook: rotate the provider's active pool key and return a rebuilt adapter,
308
+ * or null when the pool is exhausted (same semantics as the normal routed path).
309
+ */
310
+ on429?: (retryAfterHeader: string | null) => ProviderAdapter | null;
311
+ /** Opt-in same-target 429 policy (key-auth providers). When present, 429 replays on the SAME key before on429 rotation. */
312
+ retryOn429Policy?: Required<RateLimitRetryPolicy> | null;
313
+ /** Called only when the final bridged Responses stream reaches completed or incomplete. */
314
+ onCompletedResponse?: (response: Record<string, unknown>) => void;
315
+ /** OAuth account identity forwarded to AdapterFetchContext for provider-local cooldown bookkeeping. */
316
+ accountId?: string;
317
+ }
318
+
319
+ /**
320
+ * Run the main (non-OpenAI) model in a small agentic loop. Each upstream iteration is streamed and
321
+ * fully buffered internally so raw byte progress is observable without leaking a synthetic tool or
322
+ * preliminary assistant output. If the model invokes web_search, run it via the hosted sidecar,
323
+ * inject the answer as a tool_result, and loop (bounded by `maxSearches`).
324
+ */
325
+ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Response> {
326
+ const translatorBudget = deps.incomingMeta.translatorBudget;
327
+ const { parsed, selectedForwardHeaders, forwardProvider, hostedTool, settings, maxSearches, abortSignal, recordSidecarOutcome } = deps;
328
+ const backend = deps.backend ?? "openai";
329
+ const anthropicSidecar = deps.anthropicSidecar;
330
+ // Mutable: 429 key-failover (deps.on429) can swap in a rebuilt adapter mid-loop.
331
+ let adapter = deps.adapter;
332
+
333
+ // Bridge stall budget (seconds of silence before upstream_stall_timeout); the retry backoff
334
+ // heartbeat interval is derived from it so the watchdog is always fed during deliberate waits.
335
+ const stallTimeoutMs = typeof deps.stallTimeoutSec === "number" && Number.isFinite(deps.stallTimeoutSec) && deps.stallTimeoutSec > 0
336
+ ? Math.floor(deps.stallTimeoutSec * 1000)
337
+ : 300_000;
338
+
339
+ const messages: OcxMessage[] = [...parsed.context.messages];
340
+ const loopT0 = Date.now();
341
+ const allTools = parsed.context.tools ?? [];
342
+ // For the forced-answer pass we drop the synthetic web_search tool so the model MUST answer from the
343
+ // results already in `messages` (can't search again) — this guarantees a non-empty final answer.
344
+ const toolsNoWebSearch = allTools.filter(t => !t.webSearch);
345
+ let searchesExecuted = 0;
346
+ let executedSearchCount = 0;
347
+ // Queries whose search already failed this turn — repeats are short-circuited so a model that keeps
348
+ // re-asking the same failing query doesn't burn the whole search budget on it.
349
+ const failedQueries = new Set<string>();
350
+
351
+ // Link an internal AbortController to the turn signal so a client cancel of the SSE body (bridge
352
+ // `onCancel`) aborts in-flight model fetches AND the sidecar — the work now runs INSIDE the stream,
353
+ // so without this a cancelled turn would leak fetches and keep draining tokens.
354
+ const internalAbort = new AbortController();
355
+ const linkAbort = (): void => internalAbort.abort(abortSignal?.reason);
356
+ if (abortSignal) {
357
+ if (abortSignal.aborted) linkAbort();
358
+ else abortSignal.addEventListener("abort", linkAbort, { once: true });
359
+ }
360
+ const signal = internalAbort.signal;
361
+
362
+ // Hard iteration bound (termination safety net); forceAnswer normally ends the loop sooner.
363
+ const HARD_CAP = maxSearches + 2;
364
+ const connectTimeoutMs = deps.connectTimeoutMs ?? 200_000;
365
+ const routedModelStallTimeoutMs = deps.routedModelStallTimeoutMs ?? 200_000;
366
+
367
+ interface IterationResponse {
368
+ response: Response;
369
+ responseAdapter: ProviderAdapter;
370
+ }
371
+ type IterationSplit = ReturnType<typeof scanEventsForWebSearch> & {
372
+ /**
373
+ * How many leading passthrough events were already delivered live this iteration. They are
374
+ * exactly the first N passthrough entries (live delivery stops before the first event that
375
+ * scanEventsForWebSearch could group or reorder), so the terminal replay skips them by count.
376
+ */
377
+ streamedPassthroughCount: number;
378
+ };
379
+
380
+ // Same-target 429 budget is per REQUEST, not per model iteration: later search rounds inherit
381
+ // what earlier rounds left of `attempts`, so a bounded multi-round turn can never exceed the
382
+ // configured replay count in total (a per-round reset would multiply it by maxSearches).
383
+ const rateLimitRetryPolicy = deps.retryOn429Policy ?? null;
384
+ let rateLimitRetries = 0;
385
+
386
+ // Acquire one iteration's final response headers. The first call is drained eagerly so an initial
387
+ // connect/header/HTTP failure stays a non-2xx JSON response. Its successful BODY is deliberately
388
+ // left unread until the downstream Responses SSE bridge exists.
389
+ /**
390
+ * Fetch one web-search iteration's final response headers, applying the response-header
391
+ * deadline and the same-target 429 retry policy (with awaited body release and deadline
392
+ * restart) before the `on429` key rotation.
393
+ */
394
+ const prepareIterationEvents = async function* (forceAnswer: boolean): AsyncGenerator<AdapterEvent, IterationResponse> {
395
+ // On the forced-answer pass the synthetic web_search tool is gone, so the model MUST answer
396
+ // from the results already in `messages`. A weak model can still produce a thin answer that
397
+ // ignores what the search found, which reads to the user as "the search did nothing". Nudge it
398
+ // (iteration-locally — never mutate the shared `messages`) to actually use the gathered results.
399
+ // Only when a REAL search ran (executedSearchCount, not empty-query/limit/repeat placeholders).
400
+ const iterMessages: OcxMessage[] = forceAnswer && executedSearchCount > 0
401
+ ? [...messages, forcedAnswerNudge()]
402
+ : messages;
403
+ const iterParsed: OcxParsedRequest = {
404
+ ...parsed, stream: true,
405
+ context: { ...parsed.context, messages: iterMessages, tools: forceAnswer ? toolsNoWebSearch : allTools },
406
+ };
407
+ // One cumulative header deadline spans every pool-key 429 rotation in this model iteration.
408
+ // clear() stops only its timer after final headers; the direct turn signal remains attached to
409
+ // the returned response body through AbortSignal.any().
410
+ let headerDeadline = clearableDeadline(connectTimeoutMs, signal);
411
+ try {
412
+ /**
413
+ * Build and fetch one web-search iteration on the given adapter, under the iteration
414
+ * header deadline. The caller owns same-target 429 replays and key rotation around it.
415
+ * The outbound request is cached per adapter so a same-target replay reuses the EXACT
416
+ * URL, serialized body, and headers (builder runs once per target sequence).
417
+ */
418
+ let cachedRequest: AdapterRequest | undefined;
419
+ let cachedAdapter: ProviderAdapter | undefined;
420
+ /**
421
+ * Build and fetch one web-search iteration on the given adapter, under the iteration
422
+ * header deadline. The caller owns same-target 429 replays and key rotation around it.
423
+ */
424
+ const fetchOnce = async (requestAdapter: ProviderAdapter, recovery?: AttemptRecoveryKind): Promise<IterationResponse> => {
425
+ let request: AdapterRequest;
426
+ if (cachedRequest !== undefined && cachedAdapter === requestAdapter) {
427
+ request = cachedRequest;
428
+ } else {
429
+ request = await requestAdapter.buildRequest(iterParsed, {
430
+ headers: selectedForwardHeaders,
431
+ abortSignal: headerDeadline.signal,
432
+ translatorBudget,
433
+ });
434
+ try {
435
+ deps.onRequestBuilt?.(request);
436
+ } catch {
437
+ // Diagnostics are best-effort and must never abort a web-search iteration.
438
+ }
439
+ cachedRequest = request;
440
+ cachedAdapter = requestAdapter;
441
+ }
442
+ let response: Response;
443
+ try {
444
+ if (requestAdapter.fetchResponse) {
445
+ deps.onAttemptSend?.(recovery);
446
+ response = await requestAdapter.fetchResponse(request, {
447
+ abortSignal: headerDeadline.signal,
448
+ timeoutMs: connectTimeoutMs,
449
+ returnRawErrors: true,
450
+ stream: true,
451
+ ...(deps.accountId ? { accountId: deps.accountId } : {}),
452
+ });
453
+ } else {
454
+ response = await fetchWithResetRetry(
455
+ (retryRecovery) => {
456
+ // Record every helper-driven send (the callback runs for the first attempt and
457
+ // each connection-reset replay); preserve the caller's recovery kind
458
+ // (rate-limit-429 / key-429) when the retry layer supplies none.
459
+ deps.onAttemptSend?.(retryRecovery ?? recovery);
460
+ const h = new Headers(request.headers);
461
+ if (!h.has("accept-encoding")) h.set("accept-encoding", "identity");
462
+ return fetch(request.url, {
463
+ method: request.method,
464
+ headers: h,
465
+ body: request.body,
466
+ signal: headerDeadline.signal,
467
+ });
468
+ },
469
+ { abortSignal: headerDeadline.signal, label: "web-search-loop" },
470
+ );
471
+ }
472
+ } finally {
473
+ request.releaseBodyObservation?.();
474
+ }
475
+ return { response, responseAdapter: requestAdapter };
476
+ };
477
+
478
+ let prepared = await fetchOnce(adapter);
479
+ // Same-target 429 wait-and-retry (opt-in `retryOn429`) BEFORE key rotation: a primary-key
480
+ // rate-limit blip replays on the SAME key; rotation only runs after attempts exhaust.
481
+ while (
482
+ prepared.response.status === 429
483
+ && rateLimitRetryPolicy !== null
484
+ && rateLimitRetries < rateLimitRetryPolicy.attempts
485
+ ) {
486
+ rateLimitRetries += 1;
487
+ // Release unread body + heartbeat-fed wait via the shared same-target helper.
488
+ const retryAfterHeader = prepared.response.headers.get("retry-after");
489
+ // The old header deadline must not stay armed across the deliberate wait: clear it
490
+ // before sleeping so a stale expiry can never race the client-cancel path.
491
+ headerDeadline.clear();
492
+ try {
493
+ yield* prepareSameTarget429Wait({
494
+ body: prepared.response.body,
495
+ signal,
496
+ delayMs: rateLimitRetryDelayMs(rateLimitRetryPolicy, retryAfterHeader, Date.now()),
497
+ heartbeatIntervalMs: Math.min(10_000, Math.max(250, stallTimeoutMs / 2)),
498
+ });
499
+ } catch {
500
+ throw new LoopError(499, "client closed request during web-search");
501
+ }
502
+ // Client cancellation wins over any stale-deadline edge: re-check before telemetry/replay.
503
+ if (signal.aborted) throw new LoopError(499, "client closed request during web-search");
504
+ // The deliberate backoff must not consume the cumulative response-header deadline:
505
+ // start a fresh one so the replay gets a new connect budget (504 stays reserved for real
506
+ // upstream latency).
507
+ headerDeadline = clearableDeadline(connectTimeoutMs, signal);
508
+ // Stall-watchdog seam between bounded retry fetches.
509
+ yield { type: "heartbeat" };
510
+ prepared = await fetchOnce(adapter, "rate-limit-429");
511
+ }
512
+ // 429 key-failover parity with the normal routed path: rotate pool keys until one responds
513
+ // or the pool is exhausted (deps.on429 returns null — cooldown map guarantees termination).
514
+ while (prepared.response.status === 429 && deps.on429) {
515
+ const rotated = deps.on429(prepared.response.headers.get("retry-after"));
516
+ if (!rotated) break;
517
+ // Never let a broken body's cancel promise outlive the cumulative header deadline. Observe
518
+ // it, but proceed immediately to the rotated fetch under the SAME deadline signal.
519
+ try { void prepared.response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
520
+ adapter = rotated;
521
+ // Stall-watchdog seam between bounded retry fetches (audit 011 B3).
522
+ yield { type: "heartbeat" };
523
+ prepared = await fetchOnce(adapter, "key-429");
524
+ }
525
+
526
+ // Final headers have arrived. Clear only the deadline timer before ANY body read.
527
+ headerDeadline.clear();
528
+ if (!prepared.response.ok) {
529
+ let body: Awaited<ReturnType<typeof readBoundedResponseBody>>;
530
+ try {
531
+ body = await readBoundedResponseBody(prepared.response, { signal });
532
+ } catch {
533
+ // The response status is authoritative even when its untrusted error body fails while
534
+ // being read (including a synchronous getReader() failure). Never route that failure
535
+ // through the adapter formatter or the generic transport error, which could expose its
536
+ // raw message. A parent/client cancellation still owns the request lifecycle as 499.
537
+ if (signal.aborted) throw new LoopError(499, "client closed request during web-search");
538
+ throw new LoopError(prepared.response.status, `Provider error ${prepared.response.status}`);
539
+ }
540
+ let formatted = "";
541
+ if (body.displaySafe && !body.truncated && body.text.trim() && prepared.responseAdapter.formatErrorBody) {
542
+ try {
543
+ formatted = prepared.responseAdapter.formatErrorBody(
544
+ prepared.response.status,
545
+ prepared.response.headers,
546
+ body.text,
547
+ ).trim();
548
+ } catch { /* formatter hooks are best-effort; unsafe raw text is never the fallback */ }
549
+ }
550
+ const suffix = formatted ? `: ${formatted.slice(0, 400)}` : "";
551
+ throw new LoopError(prepared.response.status, `Provider error ${prepared.response.status}${suffix}`);
552
+ }
553
+ return prepared;
554
+ } catch (error) {
555
+ if (isTranslatorBudgetExceededError(error)) throw error;
556
+ if (headerDeadline.didExpire()) {
557
+ throw new LoopError(504, `Provider response-header timeout after ${connectTimeoutMs}ms during web-search`);
558
+ }
559
+ if (signal.aborted) throw new LoopError(499, "client closed request during web-search");
560
+ if (error instanceof LoopError) throw error;
561
+ throw new LoopError(502, `Provider unreachable: ${error instanceof Error ? error.message : String(error)}`);
562
+ } finally {
563
+ headerDeadline.clear();
564
+ }
565
+ };
566
+
567
+ const prepareIterationDrained = async (forceAnswer: boolean): Promise<IterationResponse> => {
568
+ const it = prepareIterationEvents(forceAnswer);
569
+ let r = await it.next();
570
+ while (!r.done) r = await it.next();
571
+ return r.value;
572
+ };
573
+
574
+ // Event types that may leave the live window before the first tool-call boundary: pure
575
+ // text/thinking output the native (sidecar-less) path would deliver identically. Everything
576
+ // else — tool calls above all, and any type scanEventsForWebSearch could group or a future
577
+ // adapter could add — closes the window so live delivery can never reorder against the replay.
578
+ const LIVE_STREAMABLE = new Set<AdapterEvent["type"]>([
579
+ "text_delta", "thinking_delta", "reasoning_raw_delta",
580
+ "thinking_signature", "redacted_thinking", "kiro_redacted_reasoning",
581
+ ]);
582
+
583
+ // Consume and validate one successful response body under a resettable raw-byte inactivity guard.
584
+ // By default only invisible heartbeat events escape while semantic output remains buffered for
585
+ // safe scanning; with `streamRoutedModelOutput` the leading text/thinking deltas stream live and
586
+ // the live window closes permanently at the first buffer-only event (see LIVE_STREAMABLE).
587
+ const consumeIterationEvents = async function* (prepared: IterationResponse): AsyncGenerator<AdapterEvent, IterationSplit> {
588
+ const events: AdapterEvent[] = [];
589
+ let liveWindowOpen = deps.streamRoutedModelOutput === true;
590
+ let streamedPassthroughCount = 0;
591
+ try {
592
+ const parse = prepared.responseAdapter.parseStream.bind(prepared.responseAdapter);
593
+ for await (const event of parseStreamWithProgress(prepared.response, parse, {
594
+ signal,
595
+ inactivityTimeoutMs: routedModelStallTimeoutMs,
596
+ translatorBudget,
597
+ })) {
598
+ if (event.type === "heartbeat") yield event;
599
+ // Kiro's explicit-completion protocol marks ordinary assistant text as commentary while
600
+ // it performs a bounded final-answer retry. That text is safe to surface immediately and
601
+ // is exactly what the native Kiro transport streams. Keeping it in the search scanner made
602
+ // Codex show only `Working` until both Kiro attempts had finished (often 30-40 seconds).
603
+ // Tool events remain buffered below, so the decision to invoke the hosted sidecar is still
604
+ // atomic and no search call can escape before its stream has validated successfully.
605
+ else if (event.type === "text_delta" && event.phase === "commentary") yield event;
606
+ else if (liveWindowOpen && LIVE_STREAMABLE.has(event.type)) {
607
+ // Live events are ALSO buffered: the scanner still needs them for thinking extraction
608
+ // and the forced-answer output check; only the terminal replay skips them (by count).
609
+ yield event;
610
+ streamedPassthroughCount++;
611
+ events.push(event);
612
+ } else {
613
+ liveWindowOpen = false;
614
+ events.push(event);
615
+ }
616
+ }
617
+ } catch (error) {
618
+ if (isTranslatorBudgetExceededError(error)) throw error;
619
+ if (signal.aborted) throw new LoopError(499, "client closed request during web-search");
620
+ if (error instanceof RoutedModelInactivityError) throw new LoopError(504, error.message);
621
+ if (error instanceof WebSearchStreamProtocolError) throw new LoopError(502, error.message);
622
+ throw new LoopError(502, `Provider stream error: ${error instanceof Error ? error.message : String(error)}`);
623
+ }
624
+
625
+ const terminalIndexes = events.flatMap((event, index) =>
626
+ event.type === "done" || event.type === "incomplete" || event.type === "error" ? [index] : []);
627
+ if (terminalIndexes.length !== 1 || terminalIndexes[0] !== events.length - 1) {
628
+ throw new LoopError(502, `Web-search adapter stream protocol error: expected one final terminal event, received ${terminalIndexes.length}`);
629
+ }
630
+ const terminal = events[terminalIndexes[0]!];
631
+ if (terminal.type === "error") {
632
+ if (terminal.code === "translation_buffer_limit") {
633
+ throw new TranslatorBudgetExceededError("retained_collectors", TRANSLATOR_MAX_TURN_BYTES);
634
+ }
635
+ throw new LoopError(502, terminal.message);
636
+ }
637
+ return { ...scanEventsForWebSearch(events), streamedPassthroughCount };
638
+ };
639
+
640
+ // Execute one model-requested web_search call. The call may batch several queries (native
641
+ // `action.search.queries`); each query runs as its own sidecar search (budget-aware), but they are
642
+ // paired as ONE assistant toolCall + ONE aggregated toolResult so function-call pairing stays
643
+ // valid, and surface as ONE search cell carrying every attempted query. A real search (one that
644
+ // hits the sidecar) shows the spinner WHILE the batch runs. Empty/limit/repeat placeholders never
645
+ // emit a cell (matching the prior single-query behavior).
646
+ async function* runSearchCall(call: WebSearchCall, precedingThinking: OcxThinkingContent[] = []): AsyncGenerator<AdapterEvent> {
647
+ const results: { query: string; outcome: SidecarOutcome }[] = [];
648
+ let beganCell = false;
649
+ if (call.queries.length === 0) {
650
+ // The model called web_search with neither query nor queries — count it against the budget
651
+ // (loop-bounding) exactly as the old empty-query placeholder did, but emit no cell.
652
+ searchesExecuted++;
653
+ results.push({ query: "", outcome: { text: "", sources: [], error: "the model called web_search with an empty query" } });
654
+ }
655
+ for (const query of call.queries) {
656
+ // Stall-watchdog seam: batched queries run sequentially inside ONE begin/end cell, and
657
+ // placeholder outcomes (repeat/limit) emit no cell at all — without this, consecutive
658
+ // bounded units chain into one silent span past the stall deadline (audit 011 B1).
659
+ yield { type: "heartbeat" };
660
+ let outcome: SidecarOutcome;
661
+ if (failedQueries.has(normalizeQuery(query))) {
662
+ // Already failed this turn — don't spend another real search on it.
663
+ outcome = { text: "", sources: [], error: "this query already failed earlier in the turn — do not call web_search again for it; answer from existing context" };
664
+ } else if (searchesExecuted >= maxSearches) {
665
+ outcome = { text: "", sources: [], error: "web search limit reached for this turn — answer from results already gathered" };
666
+ } else {
667
+ // Real sidecar search. Open the cell once, before the first real query runs.
668
+ if (!beganCell) {
669
+ beganCell = true;
670
+ yield { type: "web_search_call_begin", id: call.id };
671
+ }
672
+ // F5: the anthropic sidecar authenticates with its own stored OAuth — it never touches the
673
+ // ChatGPT forward headers and must NOT record a Codex/OpenAI pool outcome.
674
+ // #398: the executors are "never throws", but enforce the contract defensively so a future
675
+ // throw degrades to a failed tool result instead of aborting the whole turn. A genuine
676
+ // parent abort MUST stay 499 — the executors catch abort and RETURN {error}, so check
677
+ // signal.aborted both after the await and in the catch (a fulfilled {error} on an aborted
678
+ // signal would otherwise look like an ordinary degradable failure).
679
+ try {
680
+ if (backend === "anthropic" && anthropicSidecar) {
681
+ outcome = await runAnthropicWebSearch(query, anthropicSidecar.providerName, anthropicSidecar.provider, settings, signal);
682
+ } else if (backend === "xai") {
683
+ // L7: stored Grok OAuth to the pinned api.x.ai Responses endpoint; same
684
+ // never-throws contract and no Codex/OpenAI pool outcome recording (F5 parity).
685
+ // A missing xaiSidecar is an invariant violation — fail CLOSED with an error
686
+ // outcome rather than falling through to the forward-header OpenAI executor
687
+ // (review High: that fallthrough would be credential-sensitive).
688
+ outcome = deps.xaiSidecar
689
+ ? await runXaiWebSearch(query, deps.xaiSidecar.providerName, deps.xaiSidecar.provider, settings, deps.xaiSearchOptions ?? {}, signal)
690
+ : { text: "", sources: [], error: "xai backend selected without a resolved Grok OAuth provider" };
691
+ } else if (backend === "gemini") {
692
+ // L8: Antigravity CCA grounding; same fail-closed invariant stance as xai.
693
+ outcome = deps.geminiSidecar
694
+ ? await runGeminiWebSearch(query, deps.geminiSidecar.providerName, deps.geminiSidecar.provider, settings, signal)
695
+ : { text: "", sources: [], error: "gemini backend selected without a resolved Antigravity provider" };
696
+ } else if (backend === "exa") {
697
+ // L9: non-LLM lane; key comes from the loop deps, never the plan. Fail closed.
698
+ outcome = deps.exaApiKey
699
+ ? await runExaWebSearch(query, deps.exaApiKey, settings, signal)
700
+ : { text: "", sources: [], error: "exa backend selected without an exaApiKey" };
701
+ } else {
702
+ outcome = await runWebSearch(query, hostedTool, forwardProvider!, selectedForwardHeaders, settings, signal, recordSidecarOutcome);
703
+ }
704
+ if (signal.aborted) throw new LoopError(499, "client closed request during web-search");
705
+ } catch (e) {
706
+ if (e instanceof LoopError) throw e;
707
+ if (signal.aborted) throw new LoopError(499, "client closed request during web-search");
708
+ // Unexpected executor throw: degrade this query to a failed tool result (redacted).
709
+ outcome = { text: "", sources: [], error: `sidecar failed: ${redactSecretString(e instanceof Error ? e.message : String(e))}` };
710
+ }
711
+ searchesExecuted++;
712
+ executedSearchCount++;
713
+ if (outcome.error) failedQueries.add(normalizeQuery(query));
714
+ }
715
+ results.push({ query, outcome });
716
+ }
717
+ const now = Date.now();
718
+ // Preserve the singular `{query}` arg shape for a single-query call (avoids prompt-history drift);
719
+ // use `{queries}` only when the model actually batched several.
720
+ const callArgs: Record<string, unknown> = call.queries.length > 1
721
+ ? { queries: call.queries }
722
+ : { query: call.queries[0] ?? "" };
723
+ messages.push({
724
+ role: "assistant",
725
+ content: [
726
+ // Signed thinking must precede tool_use on replay (Anthropic extended thinking), and
727
+ // unsigned raw reasoning has to ride along for providers that require it back (#688).
728
+ ...precedingThinking,
729
+ {
730
+ type: "toolCall" as const,
731
+ id: call.id,
732
+ name: WEB_SEARCH_TOOL_NAME,
733
+ arguments: callArgs,
734
+ // Re-attach the signature to the rebuilt call so a sidecar turn keeps Gemini
735
+ // reasoning continuity instead of relying on the same-process replay cache.
736
+ ...(cloneProviderOpaqueToolCallMetadata(call.providerMetadata)
737
+ ? { providerMetadata: cloneProviderOpaqueToolCallMetadata(call.providerMetadata) }
738
+ : {}),
739
+ },
740
+ ],
741
+ timestamp: now,
742
+ });
743
+ // One aggregated tool result. isError only when EVERY query failed (a partial success is usable).
744
+ const allFailed = results.every(r => !!r.outcome.error);
745
+ messages.push({
746
+ role: "toolResult", toolCallId: call.id, toolName: WEB_SEARCH_TOOL_NAME,
747
+ content: formatWebSearchResults(results, !!parsed._structuredOutput),
748
+ isError: allFailed, timestamp: now,
749
+ });
750
+ if (beganCell) {
751
+ // The cell is "completed" if any query produced a usable result, else "failed". `queries`
752
+ // carries every attempted query so Codex renders the native plural label.
753
+ const anySuccess = results.some(r => !r.outcome.error);
754
+ // Collect the citations backing this batch (dedup by URL), so the bridge can attach them as
755
+ // url_citation annotations on the following assistant message → the app's Sources chip.
756
+ const sources: { url: string; title?: string }[] = [];
757
+ const seenSrc = new Set<string>();
758
+ for (const r of results) {
759
+ for (const s of r.outcome.sources) {
760
+ if (seenSrc.has(s.url)) continue;
761
+ seenSrc.add(s.url);
762
+ sources.push(s.title ? { url: s.url, title: s.title } : { url: s.url });
763
+ }
764
+ }
765
+ yield {
766
+ type: "web_search_call_end", id: call.id,
767
+ queries: call.queries,
768
+ status: anySuccess ? "completed" : "failed",
769
+ ...(sources.length > 0 ? { sources } : {}),
770
+ };
771
+ }
772
+ }
773
+
774
+ // Eagerly acquire only the FIRST iteration's final headers so connect/header/HTTP failures remain
775
+ // non-2xx JSON. A successful body is consumed inside the bridge, where byte progress can keep the
776
+ // downstream turn alive and body failures are correctly in-stream.
777
+ let firstPrepared: IterationResponse;
778
+ try {
779
+ firstPrepared = await prepareIterationDrained(false);
780
+ } catch (e) {
781
+ if (abortSignal) abortSignal.removeEventListener("abort", linkAbort);
782
+ if (e instanceof LoopError) return jsonError(e.status, e.message);
783
+ throw e;
784
+ }
785
+
786
+ const toolNsMap = new Map<string, { namespace: string; name: string; freeform?: true }>();
787
+ const freeform = new Set<string>();
788
+ const toolSearch = new Set<string>();
789
+ const requestedTools = parsed.context.tools ?? [];
790
+ const toolAllowed = toolChoiceToolPredicate(parsed.options.toolChoice, requestedTools);
791
+ for (const t of requestedTools) {
792
+ if (!toolAllowed(t)) continue;
793
+ if (t.namespace) {
794
+ toolNsMap.set(namespacedToolName(t.namespace, t.name), {
795
+ namespace: t.namespace,
796
+ name: t.name,
797
+ ...(t.freeform ? { freeform: true } : {}),
798
+ });
799
+ }
800
+ if (t.freeform) freeform.add(t.name);
801
+ if (t.toolSearch) toolSearch.add(t.name);
802
+ }
803
+
804
+ // Drive the remaining iterations live. Search cells (begin/end) are yielded interleaved with the
805
+ // real sidecar timing, the final answer's passthrough events come last — matching native ordering
806
+ // (search cell BEFORE the assistant message). Iteration 2+ failures surface as an in-stream error.
807
+ async function* produce(): AsyncGenerator<AdapterEvent> {
808
+ let prepared = firstPrepared;
809
+ try {
810
+ for (let i = 0; i < HARD_CAP; i++) {
811
+ const forceAnswer = searchesExecuted >= maxSearches;
812
+ try {
813
+ // First loop turn reuses the eager HEADERS. Subsequent header acquisitions run here.
814
+ if (i > 0) {
815
+ yield { type: "heartbeat" };
816
+ prepared = yield* prepareIterationEvents(forceAnswer);
817
+ }
818
+ // Raw-byte progress heartbeats reach the bridge; semantic events remain buffered.
819
+ const split = yield* consumeIterationEvents(prepared);
820
+
821
+ // Loop (search + re-ask) ONLY when the model's actionable output is purely web_search. A real
822
+ // tool call (e.g. shell/apply_patch) means this turn is terminal for Codex — finalize so those
823
+ // calls reach Codex. forceAnswer also finalizes.
824
+ const shouldLoop = split.calls.length > 0 && !split.hasRealToolCall && !forceAnswer;
825
+ if (!shouldLoop) {
826
+ // #1001: a forced-answer pass that ends `done` must have produced
827
+ // usable output — never a malformed tool call, and never silence.
828
+ if (forceAnswer) {
829
+ // An unterminated call flushes AFTER the terminal event, so find
830
+ // the terminal rather than assuming it is last (#1001).
831
+ const terminalEvent = split.passthrough.find(event => event.type === "done");
832
+ if (terminalEvent?.type === "done"
833
+ && (split.hasMalformedToolCall
834
+ || (!split.hasRealToolCall && !hasVisibleAssistantText(split.passthrough)))) {
835
+ throw new LoopError(502, "forced-answer pass produced no usable assistant output");
836
+ }
837
+ }
838
+ if (executedSearchCount > 0) {
839
+ const failedCount = failedQueries.size;
840
+ console.warn(
841
+ `[web-search-loop] done — ${executedSearchCount} search${executedSearchCount > 1 ? "es" : ""}`
842
+ + (failedCount > 0 ? ` (${failedCount} failed)` : "")
843
+ + `, ${i + 1} iteration${i > 0 ? "s" : ""}, ${Date.now() - loopT0}ms`,
844
+ );
845
+ }
846
+ // Live-streamed leading events are exactly the first N passthrough entries — replay
847
+ // only the buffered tail so nothing reaches the client twice.
848
+ yield* replay(split.passthrough.slice(split.streamedPassthroughCount));
849
+ return;
850
+ }
851
+ // The thinking that led to the search belongs to the FIRST call's assistant replay turn.
852
+ const iterationThinking = extractIterationThinking(split.passthrough);
853
+ for (const [callIndex, call] of split.calls.entries()) {
854
+ yield* runSearchCall(call, callIndex === 0 ? iterationThinking : []);
855
+ }
856
+ } catch (e) {
857
+ if (isTranslatorBudgetExceededError(e)) {
858
+ yield {
859
+ type: "error",
860
+ status: 502,
861
+ errorType: "upstream_error",
862
+ code: e.code,
863
+ message: "upstream translation buffer exceeded the safe limit",
864
+ };
865
+ } else {
866
+ yield { type: "error", message: e instanceof LoopError ? e.message : (e instanceof Error ? e.message : String(e)) };
867
+ }
868
+ return;
869
+ }
870
+ }
871
+ } finally {
872
+ if (abortSignal) abortSignal.removeEventListener("abort", linkAbort);
873
+ }
874
+ }
875
+
876
+ const sse = bridgeToResponsesSSE(
877
+ produce(), parsed._responseModelId ?? parsed.modelId, toolNsMap, freeform, toolSearch, () => {
878
+ const elapsed = Date.now() - loopT0;
879
+ if (executedSearchCount > 0 || searchesExecuted > 0) {
880
+ console.warn(`[web-search-loop] cancelled — ${executedSearchCount} real searches, ${searchesExecuted - executedSearchCount} placeholders, ${elapsed}ms`);
881
+ }
882
+ internalAbort.abort("client closed responses stream");
883
+ }, undefined,
884
+ {
885
+ translatorBudget,
886
+ replayCacheScope: parsed._reasoningReplayScope,
887
+ ...(deps.forceEmptyResponseId ? { responseId: "" } : {}),
888
+ hideThinkingSummary: parsed.options.hideThinkingSummary,
889
+ ...(deps.stallTimeoutSec !== undefined ? { stallTimeoutSec: deps.stallTimeoutSec } : {}),
890
+ ...(deps.onFirstOutput ? { onFirstOutput: deps.onFirstOutput } : {}),
891
+ ...(deps.onUsage ? { onUsage: deps.onUsage } : {}),
892
+ ...(deps.onCompletedResponse ? { onCompletedResponse: deps.onCompletedResponse } : {}),
893
+ },
894
+ );
895
+ return new Response(sse, { headers: SSE_HEADERS });
896
+ }