@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,955 @@
1
+ /**
2
+ * Media bridge agentic loop — supports both image and video generation sidecars.
3
+ *
4
+ * The routed (non-OpenAI) model runs in a bounded loop. Each iteration is streamed and fully
5
+ * buffered internally. If the model calls an image-generation or video-generation tool, the
6
+ * bridge fulfills it via the xAI sidecar, injects the result as a tool_result, and loops
7
+ * (bounded by maxRounds). When the model produces a real tool call or the budget is exhausted,
8
+ * the passthrough events are replayed to the bridge for final SSE output.
9
+ *
10
+ * Removed vs web-search: no sidecar backend selection, no forced-answer nudge, no failed-query
11
+ * dedup, no describeImages/structuredOutput, no recordSidecarOutcome.
12
+ */
13
+ import type { AdapterRequest, IncomingMeta, ProviderAdapter } from "../adapters/base";
14
+ import { existsSync } from "node:fs";
15
+ import { pathToFileURL } from "node:url";
16
+ import { createAdapterEventQueue } from "../adapters/run-turn-queue";
17
+ import type { AdapterEvent, OcxMessage, OcxParsedRequest, OcxProviderContinuationState, OcxProviderOpaqueToolCallMetadata, OcxRequestOptions, OcxThinkingContent, OcxUsage, RateLimitRetryPolicy } from "../types";
18
+ import { namespacedToolName, toolChoiceToolPredicate } from "../types";
19
+ import { cloneProviderOpaqueToolCallMetadata } from "../responses/provider-opaque-metadata";
20
+ import type { AttemptRecoveryKind } from "../usage/log";
21
+ import { bridgeToResponsesSSE } from "../bridge";
22
+ import { clearableDeadline, idleDeadline } from "../lib/abort";
23
+ import { readBoundedResponseBody } from "../lib/bounded-body";
24
+ import { fetchWithResetRetry, prepareSameTarget429Wait } from "../lib/upstream-retry";
25
+ import { rateLimitRetryDelayMs } from "../providers/key-failover";
26
+ import {
27
+ isTranslatorBudgetExceededError,
28
+ TRANSLATOR_MAX_TURN_BYTES,
29
+ TranslatorBudgetExceededError,
30
+ } from "../lib/translator-budget";
31
+ import { parseStreamWithProgress, RoutedModelInactivityError, WebSearchStreamProtocolError } from "../web-search/progress-stream";
32
+ import { fulfillImageCall } from "./fulfill";
33
+ import { parseVideoCallArgs, pollVideoWithHeartbeats, buildVideoResult, createVideoBudget } from "./fulfill-video";
34
+ import { submitVideoJob } from "./xai-video-client";
35
+ import { downloadVideoToArtifact, createImageBudget, pruneArtifacts } from "./artifacts";
36
+ import { IMAGE_GEN_TOOL_NAME, VIDEO_GEN_TOOL_NAME } from "./synthetic-tool";
37
+ import type { ImageBridgePlan, VideoBridgePlan } from "./types";
38
+
39
+ const SSE_HEADERS = {
40
+ "Content-Type": "text/event-stream",
41
+ "Cache-Control": "no-cache",
42
+ "Connection": "keep-alive",
43
+ "X-Accel-Buffering": "no",
44
+ };
45
+
46
+ const CONNECT_TIMEOUT_MS = 200_000;
47
+ const STALL_TIMEOUT_MS = 200_000;
48
+ export const DEFAULT_MAX_ROUNDS = 3;
49
+ /** Absolute ceiling so a hand-edited `images.maxRounds: 10000` cannot unbound paid xAI calls. */
50
+ export const MAX_ROUNDS_HARD_LIMIT = 10;
51
+ /** Cap paid xAI image fulfillments per turn (parallel calls in one round count separately). */
52
+ export const MAX_IMAGE_CALLS_PER_TURN = 10;
53
+ /** Cap paid xAI video fulfillments per turn (video is slower/costlier than image). */
54
+ export const MAX_VIDEO_CALLS_PER_TURN = 3;
55
+
56
+ /**
57
+ * Clamp a configured maxRounds value to a safe integer in [0, MAX_ROUNDS_HARD_LIMIT].
58
+ * Non-finite / non-number inputs fall back to DEFAULT_MAX_ROUNDS.
59
+ */
60
+ export function clampImageMaxRounds(value: unknown): number {
61
+ if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_MAX_ROUNDS;
62
+ return Math.max(0, Math.min(MAX_ROUNDS_HARD_LIMIT, Math.floor(value)));
63
+ }
64
+
65
+ /** Drop image/video-specific tool_choice when media tools are stripped for a forced-final pass. */
66
+ function stripMediaToolChoice(
67
+ options: OcxRequestOptions,
68
+ plan?: ImageBridgePlan,
69
+ videoPlan?: VideoBridgePlan,
70
+ ): OcxRequestOptions {
71
+ const tc = options.toolChoice;
72
+ if (!tc || typeof tc !== "object") return options;
73
+ const isMediaTool = (name: string): boolean =>
74
+ name === IMAGE_GEN_TOOL_NAME ||
75
+ name === VIDEO_GEN_TOOL_NAME ||
76
+ (plan?.toolNames.has(name) ?? false) ||
77
+ (videoPlan?.toolNames.has(name) ?? false);
78
+ if ("name" in tc && typeof tc.name === "string") {
79
+ if (isMediaTool(tc.name)) {
80
+ return { ...options, toolChoice: "auto" };
81
+ }
82
+ return options;
83
+ }
84
+ if ("allowedTools" in tc && Array.isArray(tc.allowedTools)) {
85
+ const filtered = tc.allowedTools.filter(name => !isMediaTool(name));
86
+ if (filtered.length === tc.allowedTools.length) return options;
87
+ if (filtered.length === 0) return { ...options, toolChoice: "auto" };
88
+ return { ...options, toolChoice: { ...tc, allowedTools: filtered } };
89
+ }
90
+ return options;
91
+ }
92
+
93
+ interface ImageCall {
94
+ id: string;
95
+ name: string;
96
+ args: string;
97
+ /**
98
+ * Provider-opaque metadata from the originating part (issue #1735). Stored PER CALL: a
99
+ * signature belongs to one specific part, so parallel calls must not share one value.
100
+ */
101
+ providerMetadata?: OcxProviderOpaqueToolCallMetadata;
102
+ }
103
+
104
+ /**
105
+ * Split an iteration's adapter events into (a) the image-generation tool calls to intercept and
106
+ * (b) the events to pass through to Codex. An image tool-call's own start/delta/end events are
107
+ * dropped (Codex never sees the synthetic tool); every other event — text, thinking, real tool
108
+ * calls, done — is preserved in order.
109
+ */
110
+ function scanEventsForImageCall(events: AdapterEvent[], toolNames: Set<string>): {
111
+ calls: ImageCall[];
112
+ passthrough: AdapterEvent[];
113
+ hasRealToolCall: boolean;
114
+ } {
115
+ const calls: ImageCall[] = [];
116
+ const passthrough: AdapterEvent[] = [];
117
+ let hasRealToolCall = false;
118
+ let pending: { name: string; id: string; argsBuf: string; events: AdapterEvent[]; providerMetadata?: OcxProviderOpaqueToolCallMetadata } | null = null;
119
+ const flushPending = (): void => {
120
+ if (!pending) return;
121
+ if (toolNames.has(pending.name)) {
122
+ // Unterminated image call still carries buffered args — fulfill so malformed JSON
123
+ // becomes a normal tool_result error instead of silently vanishing.
124
+ calls.push({ id: pending.id, name: pending.name, args: pending.argsBuf, providerMetadata: pending.providerMetadata });
125
+ } else {
126
+ passthrough.push(...pending.events);
127
+ hasRealToolCall = true;
128
+ }
129
+ pending = null;
130
+ };
131
+ for (const e of events) {
132
+ if (e.type === "tool_call_start") {
133
+ flushPending();
134
+ pending = { name: e.name, id: e.id, argsBuf: "", events: [e], providerMetadata: e.providerMetadata };
135
+ } else if (e.type === "tool_call_delta" && pending) {
136
+ pending.argsBuf += e.arguments;
137
+ pending.events.push(e);
138
+ } else if (e.type === "tool_call_end" && pending) {
139
+ pending.events.push(e);
140
+ if (toolNames.has(pending.name)) {
141
+ calls.push({ id: pending.id, name: pending.name, args: pending.argsBuf, providerMetadata: pending.providerMetadata });
142
+ } else {
143
+ passthrough.push(...pending.events);
144
+ hasRealToolCall = true;
145
+ }
146
+ pending = null;
147
+ } else {
148
+ flushPending();
149
+ passthrough.push(e);
150
+ }
151
+ }
152
+ flushPending();
153
+ return { calls, passthrough, hasRealToolCall };
154
+ }
155
+
156
+ async function* replay(events: AdapterEvent[]): AsyncGenerator<AdapterEvent> {
157
+ for (const e of events) yield e;
158
+ }
159
+
160
+ /**
161
+ * Collect thinking / redacted_thinking blocks that preceded an image tool call, preserving
162
+ * stream order and per-block signatures. Anthropic extended thinking REQUIRES the assistant
163
+ * message containing tool_use to start with its signed thinking blocks — flattening multiple
164
+ * blocks into one signature 400s on replay.
165
+ */
166
+ function extractIterationThinking(events: AdapterEvent[]): OcxThinkingContent[] {
167
+ const parts: OcxThinkingContent[] = [];
168
+ let thinking = "";
169
+ let signature: string | undefined;
170
+ let rawReasoning = "";
171
+
172
+ const flushVisible = () => {
173
+ if (!thinking && !signature) return;
174
+ parts.push({
175
+ type: "thinking",
176
+ thinking,
177
+ ...(signature ? { signature } : {}),
178
+ });
179
+ thinking = "";
180
+ signature = undefined;
181
+ };
182
+ const flushRaw = () => {
183
+ if (!rawReasoning) return;
184
+ parts.push({ type: "thinking", thinking: rawReasoning });
185
+ rawReasoning = "";
186
+ };
187
+
188
+ for (const e of events) {
189
+ if (e.type === "thinking_delta") {
190
+ flushRaw();
191
+ thinking += e.thinking;
192
+ } else if (e.type === "reasoning_raw_delta") {
193
+ // OpenAI-compatible providers emit raw reasoning instead of signed
194
+ // thinking; DeepSeek thinking mode requires it back alongside replayed
195
+ // tool_calls (mirrors src/web-search/loop.ts, issue #950).
196
+ flushVisible();
197
+ rawReasoning += e.text;
198
+ } else if (e.type === "thinking_signature") {
199
+ signature = e.signature;
200
+ flushVisible();
201
+ } else if (e.type === "redacted_thinking") {
202
+ flushVisible();
203
+ flushRaw();
204
+ parts.push({ type: "thinking", thinking: "", redacted: [e.data] });
205
+ }
206
+ }
207
+ flushVisible();
208
+ flushRaw();
209
+ return parts;
210
+ }
211
+
212
+ function jsonError(status: number, message: string): Response {
213
+ return new Response(JSON.stringify({ error: { message, type: "upstream_error", code: null } }), {
214
+ status,
215
+ headers: { "Content-Type": "application/json" },
216
+ });
217
+ }
218
+
219
+ /** Hard provider/parse failure inside an iteration. The eager first iteration converts it to a
220
+ * non-2xx jsonError; later (already-streaming) iterations surface it as an in-stream error event. */
221
+ class LoopError extends Error {
222
+ constructor(readonly status: number, message: string) {
223
+ super(message);
224
+ this.name = "LoopError";
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Dependencies for one image-bridge iteration: parsed request, active adapter, incoming
230
+ * metadata, and the optional image/video bridge plans.
231
+ */
232
+ export interface ImageBridgeDeps {
233
+ parsed: OcxParsedRequest;
234
+ adapter: ProviderAdapter;
235
+ incomingMeta: IncomingMeta;
236
+ plan?: ImageBridgePlan;
237
+ videoPlan?: VideoBridgePlan;
238
+ /** Per-video generation timeout (ms) including polling. */
239
+ videoTimeoutMs?: number;
240
+ /** OAuth account identity forwarded to AdapterFetchContext for provider-local cooldown bookkeeping. */
241
+ accountId?: string;
242
+ /** Headers forwarded from the original request (e.g. Codex auth). Cloned per iteration. */
243
+ forwardHeaders?: Headers;
244
+ /** Called before each routed-model dispatch in the bridge loop, for attempt telemetry. Same-target 429 replays pass the `rate-limit-429` recovery kind. */
245
+ onAttemptSend?: (recovery?: AttemptRecoveryKind) => void;
246
+ /** Called after each upstream request is built (parity with web-search / normal path). */
247
+ onRequestBuilt?: (request: AdapterRequest) => void;
248
+ abortSignal?: AbortSignal;
249
+ onFirstOutput?: () => void;
250
+ /** Max image-generation rounds before forcing a final answer. Defaults to 3; clamped to [0, 10]. */
251
+ maxRounds?: number;
252
+ /** Connect / response-header budget for non-runTurn iterations. */
253
+ connectTimeoutMs?: number;
254
+ /** Stall budget (seconds) forwarded to bridgeToResponsesSSE; also bounds runTurn collect. */
255
+ stallTimeoutSec?: number;
256
+ /** Provider-specific fetch (e.g. xAI transport wrapper). Falls back to global fetch. */
257
+ fetchImpl?: typeof globalThis.fetch;
258
+ /** Reserve the routed provider's next request-start slot before each adapter dispatch. */
259
+ waitForRequestSlot?: (signal?: AbortSignal) => Promise<void>;
260
+ /** Raw adapter usage at the terminal event, pre wire-normalization (see bridgeToResponsesSSE onUsage). */
261
+ onUsage?: (usage: OcxUsage | undefined) => void;
262
+ /**
263
+ * Optional 429 key-failover for the routed (non-xAI) model. Return a rebuilt adapter for the
264
+ * rotated key, or null when the pool is exhausted.
265
+ */
266
+ on429?: (retryAfterHeader: string | null) => ProviderAdapter | null;
267
+ /** Opt-in same-target 429 policy (key-auth providers). When present, 429 replays on the SAME key before on429 rotation. */
268
+ retryOn429Policy?: Required<RateLimitRetryPolicy> | null;
269
+ /** Called when the bridged Responses stream completes (parity with runTurn / routed paths). */
270
+ onCompletedResponse?: (response: Record<string, unknown>, providerState?: OcxProviderContinuationState) => void;
271
+ /** WebSocket Responses path only — leave response id empty for protocol compatibility. */
272
+ forceEmptyResponseId?: boolean;
273
+ }
274
+
275
+ /**
276
+ * Run the main (non-OpenAI) model in a small agentic loop. Each upstream iteration is streamed and
277
+ * fully buffered internally so raw byte progress is observable without leaking the synthetic tool or
278
+ * preliminary assistant output. If the model invokes image generation, run it via the xAI sidecar,
279
+ * inject the answer as a tool_result, and loop (bounded by `maxRounds`).
280
+ */
281
+ export async function runWithImageBridge(deps: ImageBridgeDeps): Promise<Response> {
282
+ const translatorBudget = deps.incomingMeta.translatorBudget;
283
+ const { parsed, plan, videoPlan, videoTimeoutMs, abortSignal } = deps;
284
+ let adapter = deps.adapter;
285
+ const maxRounds = clampImageMaxRounds(deps.maxRounds ?? DEFAULT_MAX_ROUNDS);
286
+ const HARD_CAP = maxRounds + 1;
287
+ const connectTimeoutMs = typeof deps.connectTimeoutMs === "number" && Number.isFinite(deps.connectTimeoutMs) && deps.connectTimeoutMs > 0
288
+ ? Math.floor(deps.connectTimeoutMs)
289
+ : CONNECT_TIMEOUT_MS;
290
+ const stallTimeoutMs = typeof deps.stallTimeoutSec === "number" && Number.isFinite(deps.stallTimeoutSec) && deps.stallTimeoutSec > 0
291
+ ? Math.floor(deps.stallTimeoutSec * 1000)
292
+ : STALL_TIMEOUT_MS;
293
+ const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
294
+ let paidImageCalls = 0;
295
+ let paidVideoCalls = 0;
296
+ let hiddenUsage: OcxUsage | undefined;
297
+
298
+ const addUsage = (a: OcxUsage | undefined, b: OcxUsage | undefined): OcxUsage | undefined => {
299
+ if (!a) return b;
300
+ if (!b) return a;
301
+ return {
302
+ inputTokens: a.inputTokens + b.inputTokens,
303
+ outputTokens: a.outputTokens + b.outputTokens,
304
+ ...(a.contextTotalTokens !== undefined || b.contextTotalTokens !== undefined
305
+ ? { contextTotalTokens: Math.max(a.contextTotalTokens ?? 0, b.contextTotalTokens ?? 0) }
306
+ : {}),
307
+ ...(a.cachedInputTokens !== undefined || b.cachedInputTokens !== undefined
308
+ ? { cachedInputTokens: (a.cachedInputTokens ?? 0) + (b.cachedInputTokens ?? 0) }
309
+ : {}),
310
+ ...(a.cacheReadInputTokens !== undefined || b.cacheReadInputTokens !== undefined
311
+ ? { cacheReadInputTokens: (a.cacheReadInputTokens ?? 0) + (b.cacheReadInputTokens ?? 0) }
312
+ : {}),
313
+ ...(a.cacheCreationInputTokens !== undefined || b.cacheCreationInputTokens !== undefined
314
+ ? { cacheCreationInputTokens: (a.cacheCreationInputTokens ?? 0) + (b.cacheCreationInputTokens ?? 0) }
315
+ : {}),
316
+ ...(a.reasoningOutputTokens !== undefined || b.reasoningOutputTokens !== undefined
317
+ ? { reasoningOutputTokens: (a.reasoningOutputTokens ?? 0) + (b.reasoningOutputTokens ?? 0) }
318
+ : {}),
319
+ ...(a.estimated || b.estimated ? { estimated: true } : {}),
320
+ };
321
+ };
322
+ const takeUsageFrom = (events: AdapterEvent[]): void => {
323
+ for (const e of events) {
324
+ if ((e.type === "done" || e.type === "incomplete") && e.usage) {
325
+ hiddenUsage = addUsage(hiddenUsage, e.usage);
326
+ }
327
+ }
328
+ };
329
+
330
+ const messages: OcxMessage[] = [...parsed.context.messages];
331
+ const allTools = parsed.context.tools ?? [];
332
+ // Merge tool names from both plans for event scanning.
333
+ const mediaToolNames = new Set<string>();
334
+ if (plan) for (const n of plan.toolNames) mediaToolNames.add(n);
335
+ if (videoPlan) for (const n of videoPlan.toolNames) mediaToolNames.add(n);
336
+ // Forced-final must strip every image/video-generation alias the plans know about — not only
337
+ // tools flagged `imageGeneration:true` or `videoGeneration:true`. Hosted `image_generation` /
338
+ // function aliases would otherwise remain callable; scanEventsForImageCall would strip the
339
+ // call while forceFinal blocks fulfillment, leaving the client an empty completion.
340
+ const toolsNoMedia = allTools.filter(t => {
341
+ if (t.imageGeneration) return false;
342
+ if (t.videoGeneration) return false;
343
+ if (plan && plan.toolNames.has(t.name)) return false;
344
+ if (plan && t.namespace && plan.toolNames.has(namespacedToolName(t.namespace, t.name))) return false;
345
+ if (videoPlan && videoPlan.toolNames.has(t.name)) return false;
346
+ return true;
347
+ });
348
+ const budget = createImageBudget();
349
+ const vBudget = createVideoBudget();
350
+
351
+ // Link an internal AbortController to the turn signal so a client cancel of the SSE body aborts
352
+ // in-flight model fetches AND the sidecar.
353
+ const internalAbort = new AbortController();
354
+ const linkAbort = (): void => internalAbort.abort(abortSignal?.reason);
355
+ if (abortSignal) {
356
+ if (abortSignal.aborted) linkAbort();
357
+ else abortSignal.addEventListener("abort", linkAbort, { once: true });
358
+ }
359
+ const signal = internalAbort.signal;
360
+
361
+ interface IterationResponse {
362
+ response: Response;
363
+ responseAdapter: ProviderAdapter;
364
+ }
365
+ type IterationSplit = ReturnType<typeof scanEventsForImageCall>;
366
+
367
+ // Same-target 429 budget is per REQUEST, not per model iteration: later image rounds inherit
368
+ // what earlier rounds left of `attempts`, so a bounded multi-round turn can never exceed the
369
+ // configured replay count in total (a per-round reset would multiply it by maxRounds).
370
+ const rateLimitRetryPolicy = deps.retryOn429Policy ?? null;
371
+ let rateLimitRetries = 0;
372
+
373
+ // Acquire one iteration's final response headers. The first call is drained eagerly so an initial
374
+ // connect/header/HTTP failure stays a non-2xx JSON response — except for runTurn adapters, which
375
+ // have no HTTP status surface and must not block SSE headers behind queue.collect().
376
+ /**
377
+ * Fetch one image-bridge iteration's final response headers, applying the response-header
378
+ * deadline and the same-target 429 retry policy (with awaited body release and deadline
379
+ * restart) before the `on429` key rotation.
380
+ */
381
+ const prepareIterationEvents = async function* (forceFinal: boolean): AsyncGenerator<AdapterEvent, IterationResponse> {
382
+ const iterParsed: OcxParsedRequest = {
383
+ ...parsed,
384
+ stream: true,
385
+ context: { ...parsed.context, messages, tools: forceFinal ? toolsNoMedia : allTools },
386
+ options: forceFinal ? stripMediaToolChoice(parsed.options, plan, videoPlan) : parsed.options,
387
+ };
388
+
389
+ // runTurn adapters (Cursor) own all upstream communication via an emit callback. They don't
390
+ // expose buildRequest/fetchResponse/parseStream to the bridge, so collect their events through
391
+ // an AdapterEventQueue and wrap them in a pseudo-response whose parseStream replays them.
392
+ if (adapter.runTurn) {
393
+ await deps.waitForRequestSlot?.(signal);
394
+ const queue = createAdapterEventQueue({
395
+ onBacklogExceeded: () => internalAbort.abort("runTurn backlog exceeded"),
396
+ });
397
+ // Attempt telemetry must fire at dispatch time (parity with fetchOnce), not after collect.
398
+ deps.onAttemptSend?.();
399
+ void adapter
400
+ .runTurn(
401
+ iterParsed,
402
+ {
403
+ headers: deps.forwardHeaders ? new Headers(deps.forwardHeaders) : new Headers(),
404
+ abortSignal: signal,
405
+ translatorBudget,
406
+ },
407
+ queue.push,
408
+ )
409
+ .then(() => queue.close())
410
+ .catch(err => {
411
+ queue.push({ type: "error", message: err instanceof Error ? err.message : String(err) });
412
+ queue.close();
413
+ });
414
+
415
+ // Bound collect with a real *idle* deadline that resets on each emitted event.
416
+ // A fixed wall-clock race would abort legitimate long Cursor turns that keep
417
+ // producing tokens. Do NOT manufacture adapter heartbeats here — bridgeToResponsesSSE
418
+ // treats those as upstream activity and would defeat the stall guard. SSE keepalives
419
+ // come from the bridge heartbeat interval instead.
420
+ //
421
+ // On idle expiry: abort the runTurn signal AND close the queue so the consumer
422
+ // unblocks even when adapter.runTurn ignores cancellation and never settles.
423
+ let timedOut = false;
424
+ const idle = idleDeadline(stallTimeoutMs, () => {
425
+ timedOut = true;
426
+ // Cancel the fire-and-forget runTurn so a well-behaved adapter can stop.
427
+ internalAbort.abort(`runTurn inactivity timeout after ${stallTimeoutMs}ms`);
428
+ // Independently unblock queue.stream() — do not wait for runTurn to observe abort.
429
+ queue.close();
430
+ });
431
+ const events: AdapterEvent[] = [];
432
+ try {
433
+ idle.reset();
434
+ for await (const event of queue.stream()) {
435
+ if (timedOut) break;
436
+ idle.reset();
437
+ events.push(event);
438
+ }
439
+ } finally {
440
+ idle.cancel();
441
+ }
442
+ if (timedOut) {
443
+ throw new LoopError(504, `runTurn inactivity timeout after ${stallTimeoutMs}ms during image-bridge`);
444
+ }
445
+
446
+ // Preserve Cursor conversation continuity across image-loop iterations. runTurn mutates
447
+ // iterParsed (shallow copy); copy the id back onto the shared parsed request.
448
+ if (iterParsed._cursorConversationId) {
449
+ parsed._cursorConversationId = iterParsed._cursorConversationId;
450
+ }
451
+
452
+ // runTurn adapters signal errors via {type:"error"} events, not HTTP status codes.
453
+ const errorEvent = events.find(e => e.type === "error");
454
+ if (errorEvent && errorEvent.type === "error") {
455
+ if (errorEvent.code === "translation_buffer_limit") {
456
+ throw new TranslatorBudgetExceededError("retained_collectors", TRANSLATOR_MAX_TURN_BYTES);
457
+ }
458
+ throw new LoopError(502, errorEvent.message);
459
+ }
460
+
461
+ const wrappedAdapter: ProviderAdapter = {
462
+ ...adapter,
463
+ async *parseStream() {
464
+ for (const e of events) yield e;
465
+ },
466
+ };
467
+ return { response: new Response(new Uint8Array(0), { status: 200 }), responseAdapter: wrappedAdapter };
468
+ }
469
+
470
+ let headerDeadline = clearableDeadline(connectTimeoutMs, signal);
471
+ const paceThenResetHeaderDeadline = async (): Promise<void> => {
472
+ headerDeadline.clear();
473
+ await deps.waitForRequestSlot?.(signal);
474
+ headerDeadline = clearableDeadline(connectTimeoutMs, signal);
475
+ };
476
+ try {
477
+ /**
478
+ * Build and fetch one image-bridge iteration on the given adapter, under the iteration
479
+ * header deadline. The caller owns same-target 429 replays and key rotation around it.
480
+ * The outbound request is cached per adapter so a same-target replay reuses the EXACT
481
+ * URL, serialized body, and headers (builder runs once per target sequence).
482
+ */
483
+ let cachedRequest: AdapterRequest | undefined;
484
+ let cachedAdapter: ProviderAdapter | undefined;
485
+ /**
486
+ * Build and fetch one image-bridge iteration on the given adapter, under the iteration
487
+ * header deadline. The caller owns same-target 429 replays and key rotation around it.
488
+ */
489
+ const fetchOnce = async (requestAdapter: ProviderAdapter, recovery?: AttemptRecoveryKind): Promise<IterationResponse> => {
490
+ let request: AdapterRequest;
491
+ if (cachedRequest !== undefined && cachedAdapter === requestAdapter) {
492
+ request = cachedRequest;
493
+ } else {
494
+ request = await requestAdapter.buildRequest(iterParsed, {
495
+ headers: deps.forwardHeaders ? new Headers(deps.forwardHeaders) : new Headers(),
496
+ abortSignal: headerDeadline.signal,
497
+ translatorBudget,
498
+ });
499
+ try { deps.onRequestBuilt?.(request); } catch { /* diagnostics are best-effort */ }
500
+ cachedRequest = request;
501
+ cachedAdapter = requestAdapter;
502
+ }
503
+ let response: Response;
504
+ try {
505
+ if (requestAdapter.fetchResponse) {
506
+ await paceThenResetHeaderDeadline();
507
+ deps.onAttemptSend?.(recovery);
508
+ response = await requestAdapter.fetchResponse(request, {
509
+ abortSignal: headerDeadline.signal,
510
+ timeoutMs: connectTimeoutMs,
511
+ returnRawErrors: true,
512
+ stream: true,
513
+ executor: fetchImpl,
514
+ ...(deps.accountId ? { accountId: deps.accountId } : {}),
515
+ });
516
+ } else {
517
+ response = await fetchWithResetRetry(
518
+ async (retryRecovery) => {
519
+ await paceThenResetHeaderDeadline();
520
+ // Record every helper-driven send (the callback runs for the first attempt and
521
+ // each connection-reset replay); preserve the caller's recovery kind
522
+ // (rate-limit-429 / key-429) when the retry layer supplies none.
523
+ deps.onAttemptSend?.(retryRecovery ?? recovery);
524
+ const h = new Headers(request.headers);
525
+ if (!h.has("accept-encoding")) h.set("accept-encoding", "identity");
526
+ return fetchImpl(request.url, {
527
+ method: request.method,
528
+ headers: h,
529
+ body: request.body,
530
+ signal: headerDeadline.signal,
531
+ });
532
+ },
533
+ { abortSignal: headerDeadline.signal, label: "image-bridge-loop" },
534
+ );
535
+ }
536
+ } finally {
537
+ request.releaseBodyObservation?.();
538
+ }
539
+ return { response, responseAdapter: requestAdapter };
540
+ };
541
+
542
+ let prepared = await fetchOnce(adapter);
543
+ // Same-target 429 wait-and-retry (opt-in `retryOn429`) BEFORE key rotation: a primary-key
544
+ // rate-limit blip replays on the SAME key; rotation only runs after attempts exhaust.
545
+ while (
546
+ prepared.response.status === 429
547
+ && rateLimitRetryPolicy !== null
548
+ && rateLimitRetries < rateLimitRetryPolicy.attempts
549
+ ) {
550
+ rateLimitRetries += 1;
551
+ // Release unread body + heartbeat-fed wait via the shared same-target helper.
552
+ const retryAfterHeader = prepared.response.headers.get("retry-after");
553
+ // The old header deadline must not stay armed across the deliberate wait: clear it
554
+ // before sleeping so a stale expiry can never race the client-cancel path.
555
+ headerDeadline.clear();
556
+ try {
557
+ yield* prepareSameTarget429Wait({
558
+ body: prepared.response.body,
559
+ signal,
560
+ delayMs: rateLimitRetryDelayMs(rateLimitRetryPolicy, retryAfterHeader, Date.now()),
561
+ heartbeatIntervalMs: Math.min(10_000, Math.max(250, stallTimeoutMs / 2)),
562
+ });
563
+ } catch {
564
+ throw new LoopError(499, "client closed request during image-bridge");
565
+ }
566
+ // Client cancellation wins over any stale-deadline edge: re-check before telemetry/replay.
567
+ if (signal.aborted) throw new LoopError(499, "client closed request during image-bridge");
568
+ // The deliberate backoff must not consume the cumulative response-header deadline:
569
+ // start a fresh one so the replay gets a new connect budget (504 stays reserved for real
570
+ // upstream latency).
571
+ headerDeadline = clearableDeadline(connectTimeoutMs, signal);
572
+ // Stall-watchdog seam between bounded retry fetches.
573
+ yield { type: "heartbeat" };
574
+ prepared = await fetchOnce(adapter, "rate-limit-429");
575
+ }
576
+ // 429 key-failover parity with web-search / normal routed path.
577
+ while (prepared.response.status === 429 && deps.on429) {
578
+ const rotated = deps.on429(prepared.response.headers.get("retry-after"));
579
+ if (!rotated) break;
580
+ try { void prepared.response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
581
+ adapter = rotated;
582
+ yield { type: "heartbeat" };
583
+ prepared = await fetchOnce(adapter, "key-429");
584
+ }
585
+
586
+ // Final headers have arrived. Clear only the deadline timer before ANY body read.
587
+ headerDeadline.clear();
588
+ if (!prepared.response.ok) {
589
+ let body: Awaited<ReturnType<typeof readBoundedResponseBody>>;
590
+ try {
591
+ body = await readBoundedResponseBody(prepared.response, { signal });
592
+ } catch {
593
+ if (signal.aborted) throw new LoopError(499, "client closed request during image-bridge");
594
+ throw new LoopError(prepared.response.status, `Provider error ${prepared.response.status}`);
595
+ }
596
+ let formatted = "";
597
+ if (body.displaySafe && !body.truncated && body.text.trim() && prepared.responseAdapter.formatErrorBody) {
598
+ try {
599
+ formatted = prepared.responseAdapter.formatErrorBody(
600
+ prepared.response.status,
601
+ prepared.response.headers,
602
+ body.text,
603
+ ).trim();
604
+ } catch { /* formatter hooks are best-effort */ }
605
+ }
606
+ const suffix = formatted ? `: ${formatted.slice(0, 400)}` : "";
607
+ throw new LoopError(prepared.response.status, `Provider error ${prepared.response.status}${suffix}`);
608
+ }
609
+ return prepared;
610
+ } catch (error) {
611
+ if (isTranslatorBudgetExceededError(error)) throw error;
612
+ if (headerDeadline.didExpire()) {
613
+ throw new LoopError(504, `Provider response-header timeout after ${connectTimeoutMs}ms during image-bridge`);
614
+ }
615
+ if (signal.aborted) throw new LoopError(499, "client closed request during image-bridge");
616
+ if (error instanceof LoopError) throw error;
617
+ throw new LoopError(502, `Provider unreachable: ${error instanceof Error ? error.message : String(error)}`);
618
+ } finally {
619
+ headerDeadline.clear();
620
+ }
621
+ };
622
+
623
+ const prepareIterationDrained = async (forceFinal: boolean): Promise<IterationResponse> => {
624
+ const it = prepareIterationEvents(forceFinal);
625
+ let r = await it.next();
626
+ while (!r.done) r = await it.next();
627
+ return r.value;
628
+ };
629
+
630
+ // Consume and validate one successful response body. Only invisible heartbeat events escape while
631
+ // semantic output remains buffered for safe scanning.
632
+ const consumeIterationEvents = async function* (prepared: IterationResponse): AsyncGenerator<AdapterEvent, IterationSplit> {
633
+ const events: AdapterEvent[] = [];
634
+ try {
635
+ const parse = prepared.responseAdapter.parseStream.bind(prepared.responseAdapter);
636
+ for await (const event of parseStreamWithProgress(prepared.response, parse, {
637
+ signal,
638
+ inactivityTimeoutMs: stallTimeoutMs,
639
+ translatorBudget,
640
+ })) {
641
+ if (event.type === "heartbeat") yield event;
642
+ else events.push(event);
643
+ }
644
+ } catch (error) {
645
+ if (isTranslatorBudgetExceededError(error)) throw error;
646
+ if (signal.aborted) throw new LoopError(499, "client closed request during image-bridge");
647
+ if (error instanceof RoutedModelInactivityError) throw new LoopError(504, error.message);
648
+ if (error instanceof WebSearchStreamProtocolError) throw new LoopError(502, error.message);
649
+ throw new LoopError(502, `Provider stream error: ${error instanceof Error ? error.message : String(error)}`);
650
+ }
651
+
652
+ const terminalIndexes = events.flatMap((event, index) =>
653
+ event.type === "done" || event.type === "incomplete" || event.type === "error" ? [index] : []);
654
+ if (terminalIndexes.length !== 1 || terminalIndexes[0] !== events.length - 1) {
655
+ throw new LoopError(502, `Image-bridge adapter stream protocol error: expected one final terminal event, received ${terminalIndexes.length}`);
656
+ }
657
+ const terminal = events[terminalIndexes[0]!];
658
+ if (terminal.type === "error") {
659
+ if (terminal.code === "translation_buffer_limit") {
660
+ throw new TranslatorBudgetExceededError("retained_collectors", TRANSLATOR_MAX_TURN_BYTES);
661
+ }
662
+ throw new LoopError(502, terminal.message);
663
+ }
664
+ return scanEventsForImageCall(events, mediaToolNames);
665
+ };
666
+
667
+ // Eagerly acquire only the FIRST iteration's final headers so connect/header/HTTP failures remain
668
+ // non-2xx JSON. Skip for runTurn adapters: their "headers" are synthetic, and awaiting
669
+ // queue.collect() before returning SSE starves clients of headers/heartbeats on slow first turns.
670
+ const skipEagerDrain = !!adapter.runTurn;
671
+ let firstPrepared: IterationResponse | undefined;
672
+ if (!skipEagerDrain) {
673
+ try {
674
+ firstPrepared = await prepareIterationDrained(maxRounds <= 0);
675
+ } catch (e) {
676
+ if (abortSignal) abortSignal.removeEventListener("abort", linkAbort);
677
+ if (e instanceof LoopError) return jsonError(e.status, e.message);
678
+ throw e;
679
+ }
680
+ }
681
+
682
+ const toolNsMap = new Map<string, { namespace: string; name: string; freeform?: true }>();
683
+ const freeform = new Set<string>();
684
+ const toolSearch = new Set<string>();
685
+ const requestedTools = parsed.context.tools ?? [];
686
+ const toolAllowed = toolChoiceToolPredicate(parsed.options.toolChoice, requestedTools);
687
+ for (const t of requestedTools) {
688
+ if (!toolAllowed(t)) continue;
689
+ if (t.namespace) {
690
+ toolNsMap.set(namespacedToolName(t.namespace, t.name), {
691
+ namespace: t.namespace,
692
+ name: t.name,
693
+ ...(t.freeform ? { freeform: true } : {}),
694
+ });
695
+ }
696
+ if (t.freeform) freeform.add(t.name);
697
+ if (t.toolSearch) toolSearch.add(t.name);
698
+ }
699
+
700
+ // Drive the remaining iterations live. Image generation runs interleaved with the real sidecar
701
+ // timing; the final answer's passthrough events come last.
702
+ async function* produce(): AsyncGenerator<AdapterEvent> {
703
+ let prepared = firstPrepared;
704
+ try {
705
+ for (let i = 0; i < HARD_CAP; i++) {
706
+ const forceFinal = i >= maxRounds;
707
+ try {
708
+ // First loop turn reuses the eager HEADERS when present. runTurn (and later iterations)
709
+ // acquire headers inside the live SSE stream so clients already have the response open.
710
+ if (!prepared || i > 0) {
711
+ yield { type: "heartbeat" };
712
+ prepared = yield* prepareIterationEvents(forceFinal);
713
+ }
714
+ // Raw-byte progress heartbeats reach the bridge; semantic events remain buffered.
715
+ const split = yield* consumeIterationEvents(prepared);
716
+ prepared = undefined;
717
+
718
+ // Loop (fulfill + re-ask) ONLY when the model's actionable output is purely image_gen. A
719
+ // real tool call means this turn is terminal for Codex — finalize so those calls reach
720
+ // Codex. forceFinal also finalizes.
721
+ const shouldLoop = split.calls.length > 0 && !split.hasRealToolCall && !forceFinal;
722
+ if (!shouldLoop) {
723
+ if (hiddenUsage) {
724
+ for (let i = split.passthrough.length - 1; i >= 0; i--) {
725
+ const e = split.passthrough[i];
726
+ if (e?.type === "done" || e?.type === "incomplete") {
727
+ split.passthrough[i] = { ...e, usage: addUsage(hiddenUsage, e.usage) };
728
+ break;
729
+ }
730
+ }
731
+ }
732
+ yield* replay(split.passthrough);
733
+ return;
734
+ }
735
+
736
+ // Discarded iteration still contributed tokens — accumulate for the final onUsage.
737
+ takeUsageFrom(split.passthrough);
738
+
739
+ // Fulfill each image/video call, then inject ONE assistant turn (thinking once + all tool
740
+ // calls) so Anthropic extended-thinking continuations stay valid across parallel calls.
741
+ const iterationThinking = extractIterationThinking(split.passthrough);
742
+ const fulfilled: Array<{ call: ImageCall; result: Awaited<ReturnType<typeof fulfillImageCall>>; args: Record<string, unknown> }> = [];
743
+ for (const call of split.calls) {
744
+ const isVideoCall = videoPlan?.toolNames.has(call.name) === true;
745
+ if (isVideoCall) {
746
+ yield { type: "heartbeat" };
747
+ if (paidVideoCalls >= MAX_VIDEO_CALLS_PER_TURN) {
748
+ const vResult = {
749
+ ok: false, model: videoPlan!.model, prompt: "", files: [], count: 0,
750
+ error: `video call budget exhausted (max ${MAX_VIDEO_CALLS_PER_TURN} per turn)`,
751
+ };
752
+ let pArgs: Record<string, unknown> = {};
753
+ try { const raw: unknown = JSON.parse(call.args || "{}"); if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) pArgs = raw as Record<string, unknown>; } catch { /* malformed args */ }
754
+ fulfilled.push({ call, result: vResult, args: pArgs });
755
+ continue;
756
+ }
757
+ const vArgs = parseVideoCallArgs(call.args);
758
+ let vResult;
759
+ if (!vArgs.ok) {
760
+ vResult = { ok: false, model: videoPlan!.model, prompt: "", files: [], count: 0, error: vArgs.error };
761
+ } else {
762
+ paidVideoCalls += 1;
763
+ const videoTimeout = videoTimeoutMs ?? 300_000;
764
+ const deadlineSignal = AbortSignal.timeout(videoTimeout);
765
+ try {
766
+ const videoDeadline = Date.now() + videoTimeout;
767
+ // Bind submit to the shared deadline so submit + poll fit one budget.
768
+ const linkedDeadline = signal
769
+ ? AbortSignal.any([signal, deadlineSignal])
770
+ : deadlineSignal;
771
+ const { requestId } = await submitVideoJob(
772
+ {
773
+ prompt: vArgs.prompt, model: videoPlan!.model,
774
+ ...(vArgs.duration != null ? { duration: vArgs.duration } : {}),
775
+ ...(vArgs.resolution != null ? { resolution: vArgs.resolution } : {}),
776
+ ...(vArgs.aspectRatio != null ? { aspectRatio: vArgs.aspectRatio } : {}),
777
+ },
778
+ videoPlan!.auth, linkedDeadline,
779
+ );
780
+ const remainingMs = videoDeadline - Date.now();
781
+ if (remainingMs <= 0) {
782
+ vResult = { ok: false, model: videoPlan!.model, prompt: vArgs.prompt, files: [], count: 0, error: `video generation timed out after ${Math.floor(videoTimeout / 1000)}s` };
783
+ } else {
784
+ const pollGen = pollVideoWithHeartbeats(requestId, videoPlan!.auth, linkedDeadline, remainingMs);
785
+ let pollResult: { ok: true; videoUrl: string } | { ok: false; error: string };
786
+ try {
787
+ for (;;) {
788
+ const { value, done } = await pollGen.next();
789
+ if (done) { pollResult = value; break; }
790
+ yield { type: "heartbeat" };
791
+ }
792
+ } finally {
793
+ await pollGen.return({ ok: false, error: "cancelled" }).catch(() => {});
794
+ }
795
+ if (signal.aborted) throw new LoopError(499, "client closed request during video-bridge");
796
+ if (pollResult.ok) {
797
+ const dlPath = await downloadVideoToArtifact(pollResult.videoUrl, vBudget, signal);
798
+ vResult = buildVideoResult(dlPath, vArgs.prompt, videoPlan!.model);
799
+ } else {
800
+ vResult = { ok: false, model: videoPlan!.model, prompt: vArgs.prompt, files: [], count: 0, error: pollResult.error };
801
+ }
802
+ }
803
+ } catch (e) {
804
+ if (deadlineSignal.aborted && !signal.aborted) {
805
+ vResult = { ok: false, model: videoPlan!.model, prompt: vArgs.prompt ?? "", files: [], count: 0, error: `video generation timed out after ${Math.floor(videoTimeout / 1000)}s` };
806
+ } else if (signal.aborted) {
807
+ throw new LoopError(499, "client closed request during video-bridge");
808
+ } else {
809
+ const error = e instanceof Error ? e.message : String(e);
810
+ vResult = { ok: false, model: videoPlan!.model, prompt: vArgs.prompt ?? "", files: [], count: 0, error };
811
+ }
812
+ }
813
+ }
814
+ if (signal.aborted) throw new LoopError(499, "client closed request during video-bridge");
815
+ let vParsedArgs: Record<string, unknown> = {};
816
+ try { const raw: unknown = JSON.parse(call.args || "{}"); if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) vParsedArgs = raw as Record<string, unknown>; } catch { /* malformed args */ }
817
+ fulfilled.push({ call, result: vResult, args: vParsedArgs });
818
+ } else {
819
+ yield { type: "heartbeat" };
820
+ if (!plan) {
821
+ fulfilled.push({ call, result: { ok: false, model: "", prompt: "", files: [], count: 0, error: "image bridge not configured" }, args: {} });
822
+ continue;
823
+ }
824
+ let result: Awaited<ReturnType<typeof fulfillImageCall>>;
825
+ if (paidImageCalls >= MAX_IMAGE_CALLS_PER_TURN) {
826
+ result = {
827
+ ok: false,
828
+ model: plan.model,
829
+ prompt: "",
830
+ files: [],
831
+ count: 0,
832
+ error: `image call budget exhausted (max ${MAX_IMAGE_CALLS_PER_TURN} per turn)`,
833
+ };
834
+ } else {
835
+ paidImageCalls += 1;
836
+ result = await fulfillImageCall(
837
+ { id: call.id, name: call.name, arguments: call.args },
838
+ plan, budget, signal,
839
+ );
840
+ }
841
+ if (signal.aborted) throw new LoopError(499, "client closed request during image-bridge");
842
+ let parsedArgs: Record<string, unknown> = {};
843
+ try {
844
+ const raw: unknown = JSON.parse(call.args || "{}");
845
+ if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
846
+ parsedArgs = raw as Record<string, unknown>;
847
+ }
848
+ } catch { /* malformed args */ }
849
+ fulfilled.push({ call, result, args: parsedArgs });
850
+ }
851
+ }
852
+ // Prune artifacts once after the entire batch so a tight keepCount
853
+ // cannot delete a video from an earlier call in this same iteration.
854
+ pruneArtifacts(videoPlan?.artifactsKeepCount ?? plan?.artifactsKeepCount);
855
+ // Drop results whose artifact files were pruned — never hand the model a dead path.
856
+ for (const f of fulfilled) {
857
+ if (!f.result.ok || !f.result.files || f.result.files.length === 0) continue;
858
+ const survivors = f.result.files.filter(p => existsSync(p));
859
+ if (survivors.length === f.result.files.length) continue; // nothing pruned
860
+ if (survivors.length === 0) {
861
+ f.result = {
862
+ ok: false, model: f.result.model, prompt: f.result.prompt ?? "",
863
+ files: [], count: 0,
864
+ error: "artifact was pruned before delivery (increase artifactsKeepCount)",
865
+ } as typeof f.result;
866
+ } else {
867
+ // Some files survived — refresh from survivors
868
+ f.result = { ...f.result, files: survivors, count: survivors.length };
869
+ const primary = survivors[0]!;
870
+ (f.result as { path?: string }).path = primary;
871
+ if ("markdown" in f.result && f.result.markdown) {
872
+ // Image markdown references the primary path; video uses pathToFileURL
873
+ if (f.result.markdown.startsWith("![")) {
874
+ (f.result as { markdown: string }).markdown = `![image](${pathToFileURL(primary).href})`;
875
+ } else {
876
+ (f.result as { markdown: string }).markdown = `[video](${pathToFileURL(primary).href})`;
877
+ }
878
+ }
879
+ }
880
+ }
881
+ const now = Date.now();
882
+ messages.push({
883
+ role: "assistant",
884
+ content: [
885
+ ...iterationThinking,
886
+ ...fulfilled.map(({ call, args }) => ({
887
+ type: "toolCall" as const,
888
+ id: call.id,
889
+ name: call.name,
890
+ arguments: args,
891
+ // Clone per call: parallel media calls each keep their own signature.
892
+ ...(cloneProviderOpaqueToolCallMetadata(call.providerMetadata)
893
+ ? { providerMetadata: cloneProviderOpaqueToolCallMetadata(call.providerMetadata) }
894
+ : {}),
895
+ })),
896
+ ],
897
+ timestamp: now,
898
+ });
899
+ for (const { call, result } of fulfilled) {
900
+ messages.push({
901
+ role: "toolResult",
902
+ toolCallId: call.id,
903
+ toolName: call.name,
904
+ content: JSON.stringify(result),
905
+ isError: !result.ok,
906
+ timestamp: now,
907
+ });
908
+ }
909
+ } catch (e) {
910
+ if (isTranslatorBudgetExceededError(e)) {
911
+ yield {
912
+ type: "error",
913
+ status: 502,
914
+ errorType: "upstream_error",
915
+ code: e.code,
916
+ message: "upstream translation buffer exceeded the safe limit",
917
+ ...(hiddenUsage ? { usage: hiddenUsage } : {}),
918
+ };
919
+ } else {
920
+ yield {
921
+ type: "error",
922
+ message: e instanceof LoopError ? e.message : (e instanceof Error ? e.message : String(e)),
923
+ ...(e instanceof LoopError ? { status: e.status } : {}),
924
+ ...(hiddenUsage ? { usage: hiddenUsage } : {}),
925
+ };
926
+ }
927
+ return;
928
+ }
929
+ }
930
+ } finally {
931
+ if (abortSignal) abortSignal.removeEventListener("abort", linkAbort);
932
+ }
933
+ }
934
+
935
+ const sse = bridgeToResponsesSSE(
936
+ produce(), parsed._responseModelId ?? parsed.modelId, toolNsMap, freeform, toolSearch, () => {
937
+ internalAbort.abort("client closed responses stream");
938
+ }, 2_000,
939
+ {
940
+ translatorBudget,
941
+ replayCacheScope: parsed._reasoningReplayScope,
942
+ ...(deps.forceEmptyResponseId ? { responseId: "" } : {}),
943
+ hideThinkingSummary: parsed.options.hideThinkingSummary,
944
+ stallTimeoutSec: deps.stallTimeoutSec,
945
+ ...(deps.onFirstOutput ? { onFirstOutput: deps.onFirstOutput } : {}),
946
+ ...(deps.onUsage ? {
947
+ // Terminal done/incomplete already includes hiddenUsage (merged above). Do not
948
+ // add it again here or request logs double-count multi-iteration image turns.
949
+ onUsage: (usage: OcxUsage | undefined) => deps.onUsage?.(usage),
950
+ } : {}),
951
+ ...(deps.onCompletedResponse ? { onCompletedResponse: deps.onCompletedResponse } : {}),
952
+ },
953
+ );
954
+ return new Response(sse, { headers: SSE_HEADERS });
955
+ }