@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,1209 @@
1
+ import type { ResponsesTerminalStatus } from "../bridge";
2
+ import { isTranslatorBudgetExceededError } from "../lib/translator-budget";
3
+ import { isUsageDebugEnabled } from "../usage/debug";
4
+ import {
5
+ addRequestLog,
6
+ addFinalRequestLog,
7
+ httpStatusForRequestLogTerminal,
8
+ inspectResponseLogJson,
9
+ inspectResponseLogSsePayloadParsed,
10
+ recordFirstOutput,
11
+ type RequestLogContext,
12
+ type RequestLogEntry,
13
+ } from "./request-log";
14
+ import {
15
+ BoundedSseFrameBuffer,
16
+ joinSseFrameBytes,
17
+ MAX_CLIENT_SSE_FRAME_BYTES,
18
+ } from "./sse-frame-buffer";
19
+
20
+ const nativePassthroughSseResponses = new WeakSet<Response>();
21
+ const eagerRelaySseResponses = new WeakSet<Response>();
22
+
23
+ export const MAX_INSPECTION_SSE_FRAME_BYTES = MAX_CLIENT_SSE_FRAME_BYTES;
24
+ export const MAX_COMPLETED_OUTPUT_ITEMS = 256;
25
+ export const MAX_COMPLETED_OUTPUT_ITEM_SOURCE_BYTES = 8 * 1024 * 1024;
26
+ export const MAX_TAIL_ERROR_MESSAGE_CHARS = 512;
27
+
28
+ export type InspectionCounters = {
29
+ frameBufferHighWaterBytes: number;
30
+ completedItemsMaxCount: number;
31
+ frameCapOverflows: number;
32
+ itemCapEvictions: number;
33
+ postCancelDrainStops: number;
34
+ };
35
+
36
+ const inspectionCounters: InspectionCounters = {
37
+ frameBufferHighWaterBytes: 0,
38
+ completedItemsMaxCount: 0,
39
+ frameCapOverflows: 0,
40
+ itemCapEvictions: 0,
41
+ postCancelDrainStops: 0,
42
+ };
43
+
44
+ export function getInspectionCounters(): InspectionCounters {
45
+ return { ...inspectionCounters };
46
+ }
47
+
48
+ export function resetInspectionCountersForTest(): void {
49
+ inspectionCounters.frameBufferHighWaterBytes = 0;
50
+ inspectionCounters.completedItemsMaxCount = 0;
51
+ inspectionCounters.frameCapOverflows = 0;
52
+ inspectionCounters.itemCapEvictions = 0;
53
+ inspectionCounters.postCancelDrainStops = 0;
54
+ }
55
+
56
+ export function relayWithAbort(
57
+ body: ReadableStream<Uint8Array> | null,
58
+ upstream: AbortController,
59
+ onClientGone?: (reason?: unknown) => void,
60
+ ): ReadableStream<Uint8Array> | null {
61
+ if (!body) return null;
62
+ const reader = body.getReader();
63
+ return new ReadableStream<Uint8Array>({
64
+ async pull(controller) {
65
+ try {
66
+ const { done, value } = await reader.read();
67
+ if (done) {
68
+ controller.close();
69
+ return;
70
+ }
71
+ controller.enqueue(value);
72
+ } catch (err) {
73
+ try { controller.error(err); } catch { /* already torn down */ }
74
+ }
75
+ },
76
+ cancel(reason) {
77
+ // A tee caller may transfer abort ownership to its bounded inspection pump.
78
+ if (onClientGone) onClientGone(reason);
79
+ else upstream.abort(reason);
80
+ reader.cancel(reason).catch(() => {});
81
+ },
82
+ });
83
+ }
84
+
85
+ export function buildFailedTailPayload(err: unknown): string {
86
+ const translatorOverflow = isTranslatorBudgetExceededError(err);
87
+ const message = (translatorOverflow
88
+ ? "upstream translation buffer exceeded the safe limit"
89
+ : `Upstream stream terminated unexpectedly: ${err instanceof Error ? err.message : String(err)}`)
90
+ .slice(0, MAX_TAIL_ERROR_MESSAGE_CHARS);
91
+ const failure = {
92
+ type: "upstream_error",
93
+ code: translatorOverflow ? "translation_buffer_limit" : "upstream_reset",
94
+ message,
95
+ };
96
+ return JSON.stringify({
97
+ type: "response.failed",
98
+ response: { status: "failed", error: failure, last_error: failure },
99
+ });
100
+ }
101
+
102
+ export type SseTerminalOutputBoundary = {
103
+ feed(chunk: Uint8Array): Uint8Array;
104
+ finish(): Uint8Array;
105
+ terminalSeen(): boolean;
106
+ doneSeen(): boolean;
107
+ dispose(): void;
108
+ };
109
+
110
+ /**
111
+ * Frame-aware client output boundary shared by both native Responses relays.
112
+ * It buffers only the current incomplete SSE block under the same hard byte
113
+ * cap as inspection, forwards complete blocks through the first Responses
114
+ * terminal, and drops every later block/byte.
115
+ */
116
+ export function createSseTerminalOutputBoundary(): SseTerminalOutputBoundary {
117
+ const decoder = new TextDecoder();
118
+ const framer = new BoundedSseFrameBuffer(MAX_INSPECTION_SSE_FRAME_BYTES);
119
+ let terminal = false;
120
+ let done = false;
121
+ let disposed = false;
122
+
123
+ const processFrames = (
124
+ frames: ReturnType<BoundedSseFrameBuffer["feed"]>,
125
+ ): Uint8Array => {
126
+ if (disposed || terminal || frames.length === 0) return new Uint8Array(0);
127
+ const output: Uint8Array[] = [];
128
+ let responsesTerminal = false;
129
+ for (const frame of frames) {
130
+ const payload = sseDataPayload(decoder.decode(frame.block));
131
+ const isDone = payload === "[DONE]";
132
+ // Preserve every frame through the first Responses terminal. A [DONE]
133
+ // frame is also preserved when it immediately follows that terminal in
134
+ // the same upstream chunk; every later non-DONE frame is dropped.
135
+ if (!responsesTerminal || isDone) output.push(frame.block, frame.delimiter);
136
+ if (isDone) {
137
+ done = true;
138
+ continue;
139
+ }
140
+ if (!responsesTerminal && payload && terminalStatusFromSsePayload(payload)) {
141
+ responsesTerminal = true;
142
+ }
143
+ }
144
+ if (responsesTerminal) {
145
+ terminal = true;
146
+ framer.dispose();
147
+ }
148
+ return joinSseFrameBytes(output);
149
+ };
150
+
151
+ return {
152
+ feed(chunk) {
153
+ if (disposed || terminal) return new Uint8Array(0);
154
+ return processFrames(framer.feed(chunk));
155
+ },
156
+ finish() {
157
+ if (disposed || terminal) return new Uint8Array(0);
158
+ return framer.finish();
159
+ },
160
+ terminalSeen: () => terminal,
161
+ doneSeen: () => done,
162
+ dispose() {
163
+ if (disposed) return;
164
+ disposed = true;
165
+ framer.dispose();
166
+ },
167
+ };
168
+ }
169
+
170
+ /**
171
+ * Relay a passthrough SSE body like relayWithAbort, but convert a MID-STREAM failure (upstream
172
+ * reset after headers) into a clean terminal: any partial block is closed off, then a synthetic
173
+ * `response.failed` event and `data: [DONE]` are emitted and the stream closes. Without this the
174
+ * client sees a raw socket teardown with no terminal SSE event. Deliberately NOT a resend: the
175
+ * upstream already committed the request (duplicate-completion risk — same policy as cursor's
176
+ * committed=non-replayable transport retry).
177
+ */
178
+ export function relaySseWithFailedTail(
179
+ body: ReadableStream<Uint8Array>,
180
+ upstream: AbortController,
181
+ onClientGone?: (reason?: unknown) => void,
182
+ ): ReadableStream<Uint8Array> {
183
+ const reader = body.getReader();
184
+ const encoder = new TextEncoder();
185
+ const terminalBoundary = createSseTerminalOutputBoundary();
186
+ let closed = false;
187
+ const relayChunk = (
188
+ controller: ReadableStreamDefaultController<Uint8Array>,
189
+ value: Uint8Array,
190
+ ): "terminal" | "output" | "buffered" => {
191
+ const outbound = terminalBoundary.feed(value);
192
+ if (outbound.byteLength > 0) controller.enqueue(outbound);
193
+ if (!terminalBoundary.terminalSeen()) return outbound.byteLength > 0 ? "output" : "buffered";
194
+
195
+ // A Responses terminal frame is the protocol boundary. Some compatible
196
+ // gateways leave the HTTP connection open after response.completed, which
197
+ // otherwise leaves Codex waiting forever even though the model turn is done.
198
+ // Preserve through the terminal block only, add the conventional sentinel
199
+ // when there was no real [DONE] data event, then stop reading upstream.
200
+ if (!terminalBoundary.doneSeen()) {
201
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"));
202
+ }
203
+ closed = true;
204
+ controller.close();
205
+ const reason = "Responses terminal event received";
206
+ // Notify the tee inspection branch as well. It has already received the
207
+ // same terminal-bearing upstream chunk, so its bounded drain records the
208
+ // real terminal and then releases the turn/upstream keep-alive connection.
209
+ onClientGone?.(reason);
210
+ reader.cancel(reason).catch(() => {});
211
+ terminalBoundary.dispose();
212
+ return "terminal";
213
+ };
214
+ return new ReadableStream<Uint8Array>({
215
+ async pull(controller) {
216
+ try {
217
+ for (;;) {
218
+ const { done, value } = await reader.read();
219
+ if (done) {
220
+ const tail = terminalBoundary.finish();
221
+ if (tail.byteLength > 0) controller.enqueue(tail);
222
+ terminalBoundary.dispose();
223
+ controller.close();
224
+ return;
225
+ }
226
+ const result = relayChunk(controller, value);
227
+ if (result !== "buffered") return;
228
+ }
229
+ } catch (err) {
230
+ let partial: Uint8Array = new Uint8Array(0);
231
+ try {
232
+ partial = terminalBoundary.finish();
233
+ } catch {
234
+ // A near-cap ambiguous delimiter tail may itself overflow at EOF.
235
+ // Preserve the original read/framing failure and continue emitting
236
+ // the bounded failed tail instead of letting cleanup throw again.
237
+ }
238
+ terminalBoundary.dispose();
239
+ if (closed) return;
240
+ const payload = buildFailedTailPayload(err);
241
+ try {
242
+ if (partial.byteLength > 0) controller.enqueue(partial);
243
+ // Leading blank line terminates a partial SSE block so the failed frame parses cleanly.
244
+ controller.enqueue(encoder.encode(`\n\nevent: response.failed\ndata: ${payload}\n\ndata: [DONE]\n\n`));
245
+ controller.close();
246
+ } catch { /* client already torn down */ }
247
+ upstream.abort();
248
+ }
249
+ },
250
+ cancel(reason) {
251
+ terminalBoundary.dispose();
252
+ if (onClientGone) onClientGone(reason);
253
+ else upstream.abort(reason);
254
+ reader.cancel(reason).catch(() => {});
255
+ },
256
+ });
257
+ }
258
+
259
+ export function nextSseBlock(buffer: string): { block: string; delimiter: string; rest: string } | null {
260
+ const match = buffer.match(/\r?\n\r?\n/);
261
+ if (!match || match.index === undefined) return null;
262
+ return {
263
+ block: buffer.slice(0, match.index),
264
+ delimiter: match[0],
265
+ rest: buffer.slice(match.index + match[0].length),
266
+ };
267
+ }
268
+
269
+ export function sseDataPayload(block: string): string | null {
270
+ const data: string[] = [];
271
+ for (const line of block.split(/\r?\n/)) {
272
+ if (!line.startsWith("data:")) continue;
273
+ const value = line.slice(5);
274
+ data.push(value.startsWith(" ") ? value.slice(1) : value);
275
+ }
276
+ return data.length > 0 ? data.join("\n") : null;
277
+ }
278
+
279
+ export function terminalStatusFromSsePayload(payload: string): ResponsesTerminalStatus | null {
280
+ if (payload === "[DONE]") return null;
281
+ try {
282
+ return terminalStatusFromParsed(JSON.parse(payload));
283
+ } catch {
284
+ return null;
285
+ }
286
+ }
287
+
288
+ /** True when a native Responses SSE payload carries the FIRST kind of non-empty model output. */
289
+ export function isFirstOutputSsePayload(payload: string | null): boolean {
290
+ if (!payload || payload === "[DONE]") return false;
291
+ try {
292
+ return firstOutputFromParsed(JSON.parse(payload));
293
+ } catch {
294
+ return false;
295
+ }
296
+ }
297
+
298
+ export function firstOutputFromParsed(parsed: unknown): boolean {
299
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
300
+ const event = parsed as { type?: unknown; delta?: unknown };
301
+ return (event.type === "response.output_text.delta"
302
+ || event.type === "response.reasoning_summary_text.delta"
303
+ || event.type === "response.reasoning_text.delta")
304
+ && typeof event.delta === "string"
305
+ && event.delta.length > 0;
306
+ }
307
+
308
+ function createFirstOutputReporter(onFirstOutput?: () => void): {
309
+ payload: (payload: string | null) => void;
310
+ parsed: (parsed: unknown) => void;
311
+ } {
312
+ let reported = false;
313
+ const report = (isFirst: boolean) => {
314
+ if (reported || !isFirst) return;
315
+ reported = true;
316
+ try { onFirstOutput?.(); } catch { /* metrics must not break the stream */ }
317
+ };
318
+ return {
319
+ payload: payload => report(isFirstOutputSsePayload(payload)),
320
+ parsed: parsed => report(firstOutputFromParsed(parsed)),
321
+ };
322
+ }
323
+
324
+ export function terminalStatusFromParsed(parsed: unknown): ResponsesTerminalStatus | null {
325
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
326
+ switch ((parsed as { type?: unknown }).type) {
327
+ case "response.completed":
328
+ return "completed";
329
+ case "response.failed":
330
+ return "failed";
331
+ case "response.incomplete":
332
+ return "incomplete";
333
+ default:
334
+ return null;
335
+ }
336
+ }
337
+
338
+ /** Extract the response object from a `response.completed` SSE payload, or null. */
339
+ export function completedResponseFromSsePayload(payload: string): { id?: unknown; output?: unknown; status?: unknown } | null {
340
+ if (payload === "[DONE]") return null;
341
+ try {
342
+ const json = JSON.parse(payload) as { type?: unknown; response?: unknown };
343
+ return completedResponseFromParsedEvent(json);
344
+ } catch {
345
+ return null;
346
+ }
347
+ }
348
+
349
+ /** Extract the response object from an already-parsed `response.completed` event, or null. */
350
+ export function completedResponseFromParsedEvent(
351
+ json: unknown,
352
+ ): { id?: unknown; output?: unknown; status?: unknown } | null {
353
+ if (!json || typeof json !== "object" || Array.isArray(json)
354
+ || (json as { type?: unknown }).type !== "response.completed") return null;
355
+ const response = (json as { response?: unknown }).response;
356
+ if (!response || typeof response !== "object" || Array.isArray(response)) return null;
357
+ return response as { id?: unknown; output?: unknown; status?: unknown };
358
+ }
359
+
360
+ export function trackSseForRequestLog(
361
+ body: ReadableStream<Uint8Array>,
362
+ onTerminal: (status: ResponsesTerminalStatus) => void,
363
+ onCancel: () => void,
364
+ logCtx?: RequestLogContext,
365
+ onFirstOutput?: () => void,
366
+ ): ReadableStream<Uint8Array> {
367
+ const reader = body.getReader();
368
+ let terminalReported = false;
369
+ let cancelled = false;
370
+
371
+ const reportTerminal = (status: ResponsesTerminalStatus) => {
372
+ if (terminalReported) return;
373
+ terminalReported = true;
374
+ onTerminal(status);
375
+ };
376
+ // Reuse the byte-bounded inspector so translated responses cannot retain an
377
+ // unterminated upstream frame or parse the same event once per observer.
378
+ const inspector = createSseInspector({
379
+ onTerminal: reportTerminal,
380
+ logCtx,
381
+ onFirstOutput,
382
+ });
383
+
384
+ return new ReadableStream<Uint8Array>({
385
+ async pull(controller) {
386
+ try {
387
+ const { done, value } = await reader.read();
388
+ if (done) {
389
+ if (!cancelled) {
390
+ inspector.finish();
391
+ if (!terminalReported) reportTerminal("incomplete");
392
+ }
393
+ inspector.dispose();
394
+ controller.close();
395
+ return;
396
+ }
397
+ inspector.feed(value);
398
+ controller.enqueue(value);
399
+ } catch (err) {
400
+ // The upstream read rejected: the 200 body died mid-flight. Client
401
+ // cancellation is the caller's separate 499 path, so a cancel-drained
402
+ // pending read (cancelled=true) must not carry the truncation marker.
403
+ if (!cancelled && !terminalReported && logCtx?.activeAttempt) {
404
+ logCtx.activeAttempt.streamAborted = true;
405
+ }
406
+ if (!cancelled && !terminalReported) reportTerminal("incomplete");
407
+ inspector.dispose();
408
+ try { controller.error(err); } catch { /* already torn down */ }
409
+ }
410
+ },
411
+ cancel(reason) {
412
+ cancelled = true;
413
+ inspector.dispose();
414
+ onCancel();
415
+ reader.cancel(reason).catch(() => {});
416
+ },
417
+ });
418
+ }
419
+
420
+ export function responseWithDeferredRequestLog(
421
+ response: Response,
422
+ requestId: string,
423
+ start: number,
424
+ logCtx: RequestLogContext,
425
+ addLog: (entry: RequestLogEntry) => void = addRequestLog,
426
+ ): Response {
427
+ const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
428
+ if (isUsageDebugEnabled() && !logCtx.usageDebugContentType && contentType) {
429
+ logCtx.usageDebugContentType = contentType;
430
+ }
431
+ if (isNativePassthroughSseResponse(response)) {
432
+ return response;
433
+ }
434
+ if (!response.body || !contentType.includes("text/event-stream")) {
435
+ if (response.body && (contentType.includes("application/json") || response.status >= 400)) {
436
+ const finalizeJsonLog = async () => {
437
+ const text = await response.text();
438
+ // Non-JSON error bodies: inspect/log only a bounded prefix (the stored
439
+ // upstreamError is 500 chars anyway); the FULL text is still forwarded to the
440
+ // client below, unchanged. JSON bodies keep full inspection (usage parsing).
441
+ const isJson = contentType.includes("application/json");
442
+ inspectResponseLogJson(logCtx, isJson ? text : text.slice(0, 8192));
443
+ addFinalRequestLog(requestId, start, logCtx, response.status, { closeReason: "non_stream" }, addLog);
444
+ return text;
445
+ };
446
+ const body = new ReadableStream<Uint8Array>({
447
+ async start(controller) {
448
+ try {
449
+ controller.enqueue(new TextEncoder().encode(await finalizeJsonLog()));
450
+ controller.close();
451
+ } catch (err) {
452
+ addFinalRequestLog(requestId, start, logCtx, 502, { closeReason: "non_stream" }, addLog);
453
+ try { controller.error(err); } catch { /* already torn down */ }
454
+ }
455
+ },
456
+ });
457
+ return new Response(body, {
458
+ status: response.status,
459
+ statusText: response.statusText,
460
+ headers: response.headers,
461
+ });
462
+ }
463
+ if (isUsageDebugEnabled() && logCtx.usageDebugBodyKind === undefined) {
464
+ logCtx.usageDebugBodyKind = response.body ? "other" : "none";
465
+ }
466
+ addFinalRequestLog(requestId, start, logCtx, response.status, { closeReason: "non_stream" }, addLog);
467
+ return response;
468
+ }
469
+
470
+ let logged = false;
471
+ const body = trackSseForRequestLog(
472
+ response.body,
473
+ status => {
474
+ if (logged) return;
475
+ logged = true;
476
+ addFinalRequestLog(requestId, start, logCtx, httpStatusForRequestLogTerminal(status, logCtx), {
477
+ terminalStatus: status,
478
+ closeReason: "terminal",
479
+ }, addLog);
480
+ },
481
+ () => {
482
+ if (logged) return;
483
+ logged = true;
484
+ addFinalRequestLog(requestId, start, logCtx, 499, { closeReason: "client_cancel" }, addLog);
485
+ },
486
+ logCtx,
487
+ () => recordFirstOutput(logCtx, start),
488
+ );
489
+ return new Response(body, {
490
+ status: response.status,
491
+ statusText: response.statusText,
492
+ headers: response.headers,
493
+ });
494
+ }
495
+
496
+ export function markNativePassthroughSseResponse(response: Response): Response {
497
+ nativePassthroughSseResponses.add(response);
498
+ return response;
499
+ }
500
+
501
+ export function isNativePassthroughSseResponse(response: Response): boolean {
502
+ return nativePassthroughSseResponses.has(response);
503
+ }
504
+
505
+ export function markEagerRelaySseResponse(response: Response): Response {
506
+ eagerRelaySseResponses.add(response);
507
+ return response;
508
+ }
509
+
510
+ /** Test-only path identity seam; runtime behavior must not branch on this marker. */
511
+ export function isEagerRelaySseResponse(response: Response): boolean {
512
+ return eagerRelaySseResponses.has(response);
513
+ }
514
+
515
+ export function relaySseWithHeartbeat(
516
+ body: ReadableStream<Uint8Array> | null,
517
+ upstream: AbortController,
518
+ heartbeatMs = 15_000,
519
+ onTerminal?: (status: ResponsesTerminalStatus) => void,
520
+ options?: { onStart?: () => void; onDone?: () => void },
521
+ ): ReadableStream<Uint8Array> | null {
522
+ if (!body) return null;
523
+ const reader = body.getReader();
524
+ const heartbeat = new TextEncoder().encode(": opencodex keepalive\n\n");
525
+ let timer: ReturnType<typeof setInterval> | undefined;
526
+ let closed = false;
527
+ let clientCancelled = false;
528
+ let terminalReported = false;
529
+
530
+ const reportTerminal = (status: ResponsesTerminalStatus) => {
531
+ if (terminalReported || clientCancelled || closed) return;
532
+ terminalReported = true;
533
+ onTerminal?.(status);
534
+ };
535
+ const inspector = createSseInspector({ onTerminal: reportTerminal });
536
+
537
+ const cleanup = () => {
538
+ if (closed) return;
539
+ closed = true;
540
+ inspector.dispose();
541
+ if (timer) clearInterval(timer);
542
+ timer = undefined;
543
+ options?.onDone?.();
544
+ };
545
+
546
+ return new ReadableStream<Uint8Array>({
547
+ start(controller) {
548
+ options?.onStart?.();
549
+ timer = setInterval(() => {
550
+ if (closed) return;
551
+ try {
552
+ controller.enqueue(heartbeat);
553
+ } catch {
554
+ cleanup();
555
+ }
556
+ }, heartbeatMs);
557
+ },
558
+ async pull(controller) {
559
+ try {
560
+ const { done, value } = await reader.read();
561
+ if (done) {
562
+ inspector.finish();
563
+ if (!terminalReported && !clientCancelled) reportTerminal("incomplete");
564
+ cleanup();
565
+ controller.close();
566
+ return;
567
+ }
568
+ inspector.feed(value);
569
+ controller.enqueue(value);
570
+ } catch (err) {
571
+ if (!clientCancelled) reportTerminal("incomplete");
572
+ cleanup();
573
+ try { controller.error(err); } catch { /* already torn down */ }
574
+ }
575
+ },
576
+ cancel(reason) {
577
+ clientCancelled = true;
578
+ cleanup();
579
+ upstream.abort(reason);
580
+ reader.cancel(reason).catch(() => {});
581
+ },
582
+ });
583
+ }
584
+
585
+ /**
586
+ * Background-consume an SSE stream purely for terminal-outcome inspection (quota tracking).
587
+ * Does not produce output; safe to ignore errors (the client-facing stream is separate).
588
+ */
589
+ export type SseInspector = {
590
+ /** Feed one upstream chunk through the SSE scanning state machine. */
591
+ feed(chunk: Uint8Array): void;
592
+ /** Flush the decoder + trailing unterminated buffer (upstream cleanly done). */
593
+ finish(): void;
594
+ /** Drop every retained frame/item reference without parsing. Idempotent. */
595
+ dispose(): void;
596
+ /** True once a protocol terminal was detected and reported. */
597
+ reported(): boolean;
598
+ /** True once any protocol terminal was parsed, including metadata-only inspectors. */
599
+ terminalSeen(): boolean;
600
+ };
601
+
602
+ export type SseInspectorHandlers = {
603
+ onTerminal?: (status: ResponsesTerminalStatus, httpStatusOverride?: number) => void;
604
+ logCtx?: RequestLogContext;
605
+ onCompletedResponse?: (response: { id?: unknown; output?: unknown; status?: unknown }) => void;
606
+ /**
607
+ * Every parsed SSE payload, delivered BEFORE any onCompletedResponse derived from that same
608
+ * payload. A caller that must decide on the whole turn -- not just its terminal snapshot --
609
+ * needs to see the incremental events, because a stream can announce an item and then close
610
+ * with an empty `output`.
611
+ */
612
+ onParsedPayload?: (payload: unknown) => void;
613
+ onFirstOutput?: () => void;
614
+ /**
615
+ * Provider-scoped compatibility: persist the completed snapshot under the
616
+ * first response id exposed to the client when an upstream changes ids
617
+ * between `response.created` and `response.completed`.
618
+ */
619
+ pinCompletedResponseIdToFirstSeen?: boolean;
620
+ };
621
+
622
+ type CompletedOutputItem = { item: unknown; sourceBytes: number };
623
+
624
+ function delimiterLengthAt(
625
+ index: number,
626
+ length: number,
627
+ byteAt: (index: number) => number,
628
+ ): number | 0 | undefined {
629
+ const first = byteAt(index);
630
+ if (first === 10) {
631
+ if (index + 1 >= length) return undefined;
632
+ const second = byteAt(index + 1);
633
+ if (second === 10) return 2;
634
+ if (second !== 13) return 0;
635
+ if (index + 2 >= length) return undefined;
636
+ return byteAt(index + 2) === 10 ? 3 : 0;
637
+ }
638
+ if (first !== 13) return 0;
639
+ if (index + 1 >= length) return undefined;
640
+ if (byteAt(index + 1) !== 10) return 0;
641
+ if (index + 2 >= length) return undefined;
642
+ const third = byteAt(index + 2);
643
+ if (third === 10) return 3;
644
+ if (third !== 13) return 0;
645
+ if (index + 3 >= length) return undefined;
646
+ return byteAt(index + 3) === 10 ? 4 : 0;
647
+ }
648
+
649
+ /**
650
+ * Per-chunk SSE inspection state machine shared by consumeForInspection,
651
+ * consumeForResponseLogMetadata, and the eager bounded relay (relay-eager.ts).
652
+ *
653
+ * Extraction-fidelity invariants (devlog/_plan/260723_win_mem_safestream/020):
654
+ * - logCtx SSE inspection is gated on !reported; in the metadata configuration
655
+ * (no onTerminal) `reported` stays permanently false, which reproduces the
656
+ * metadata consumer's unconditional inspection through the same gate.
657
+ * - finish() skips the trailing-buffer scan once reported, while per-block
658
+ * onCompletedResponse continues firing after reported — an intentional
659
+ * asymmetry inherited from consumeForInspection.
660
+ * - logCtx.transportPhase/terminalSource are mutated BEFORE onTerminal fires.
661
+ * - Synthetic terminals (incomplete / failed-502) are the CALLER's decision:
662
+ * the caller owns `cancelled` state and reads `reported()` to decide.
663
+ */
664
+ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector {
665
+ let decoder: TextDecoder | null = new TextDecoder();
666
+ let reported = false;
667
+ let sawTerminal = false;
668
+ let disposed = false;
669
+ let delimiterTail: Uint8Array = new Uint8Array(0);
670
+ let candidate: Uint8Array = new Uint8Array(0);
671
+ let candidateBytes = 0;
672
+ let discardingOversizedFrame = false;
673
+ const reportFirstOutput = createFirstOutputReporter(handlers.onFirstOutput);
674
+ // Allocate reconstruction state only for persistence-capable inspectors.
675
+ const completedItemsByOutputIndex = handlers.onCompletedResponse
676
+ ? new Map<number, CompletedOutputItem>()
677
+ : null;
678
+ let aggregateItemBytes = 0;
679
+ let reconstructionTainted = false;
680
+ let firstResponseId: string | undefined;
681
+
682
+ const clearFrameState = (): void => {
683
+ delimiterTail = new Uint8Array(0);
684
+ candidate = new Uint8Array(0);
685
+ candidateBytes = 0;
686
+ discardingOversizedFrame = false;
687
+ };
688
+
689
+ const clearCompletedItems = (): void => {
690
+ completedItemsByOutputIndex?.clear();
691
+ aggregateItemBytes = 0;
692
+ reconstructionTainted = false;
693
+ };
694
+
695
+ const dispose = (): void => {
696
+ if (disposed) return;
697
+ disposed = true;
698
+ decoder = null;
699
+ clearFrameState();
700
+ clearCompletedItems();
701
+ firstResponseId = undefined;
702
+ };
703
+
704
+ const ensureCandidateCapacity = (requiredBytes: number): void => {
705
+ if (candidate.byteLength >= requiredBytes) return;
706
+ let capacity = candidate.byteLength === 0
707
+ ? Math.min(MAX_INSPECTION_SSE_FRAME_BYTES, Math.max(requiredBytes, 4096))
708
+ : candidate.byteLength;
709
+ while (capacity < requiredBytes) {
710
+ capacity = Math.min(
711
+ MAX_INSPECTION_SSE_FRAME_BYTES,
712
+ Math.max(requiredBytes, capacity * 2),
713
+ );
714
+ }
715
+ const grown = new Uint8Array(capacity);
716
+ if (candidateBytes > 0) grown.set(candidate.subarray(0, candidateBytes));
717
+ candidate = grown;
718
+ };
719
+
720
+ const takeCandidate = (): Uint8Array => {
721
+ if (candidateBytes === 0) return new Uint8Array(0);
722
+ const frame = candidate.slice(0, candidateBytes);
723
+ candidate = new Uint8Array(0);
724
+ candidateBytes = 0;
725
+ return frame;
726
+ };
727
+
728
+ const retainCandidateSlice = (slice: Uint8Array): void => {
729
+ if (slice.byteLength === 0 || discardingOversizedFrame) return;
730
+ const nextBytes = candidateBytes + slice.byteLength;
731
+ inspectionCounters.frameBufferHighWaterBytes = Math.max(
732
+ inspectionCounters.frameBufferHighWaterBytes,
733
+ Math.min(nextBytes, MAX_INSPECTION_SSE_FRAME_BYTES),
734
+ );
735
+ if (nextBytes > MAX_INSPECTION_SSE_FRAME_BYTES) {
736
+ candidate = new Uint8Array(0);
737
+ candidateBytes = 0;
738
+ discardingOversizedFrame = true;
739
+ inspectionCounters.frameCapOverflows += 1;
740
+ // The rejected frame may have carried an output item we will never see;
741
+ // any later empty-output terminal must not synthesize a partial replay
742
+ // from the surviving map entries (same taint rule as item eviction).
743
+ reconstructionTainted = true;
744
+ return;
745
+ }
746
+ ensureCandidateCapacity(nextBytes);
747
+ candidate.set(slice, candidateBytes);
748
+ candidateBytes = nextBytes;
749
+ };
750
+
751
+ const retainCompletedItem = (index: number, item: unknown, sourceBytes: number): void => {
752
+ const previous = completedItemsByOutputIndex!.get(index);
753
+ if (previous) {
754
+ aggregateItemBytes -= previous.sourceBytes;
755
+ completedItemsByOutputIndex!.delete(index);
756
+ }
757
+ if (sourceBytes > MAX_COMPLETED_OUTPUT_ITEM_SOURCE_BYTES) {
758
+ reconstructionTainted = true;
759
+ inspectionCounters.itemCapEvictions += 1;
760
+ return;
761
+ }
762
+ completedItemsByOutputIndex!.set(index, { item, sourceBytes });
763
+ aggregateItemBytes += sourceBytes;
764
+ while (completedItemsByOutputIndex!.size > MAX_COMPLETED_OUTPUT_ITEMS
765
+ || aggregateItemBytes > MAX_COMPLETED_OUTPUT_ITEM_SOURCE_BYTES) {
766
+ let highestIndex = -1;
767
+ for (const retainedIndex of completedItemsByOutputIndex!.keys()) {
768
+ if (retainedIndex > highestIndex) highestIndex = retainedIndex;
769
+ }
770
+ const evicted = completedItemsByOutputIndex!.get(highestIndex);
771
+ if (!evicted) break;
772
+ completedItemsByOutputIndex!.delete(highestIndex);
773
+ aggregateItemBytes -= evicted.sourceBytes;
774
+ reconstructionTainted = true;
775
+ inspectionCounters.itemCapEvictions += 1;
776
+ }
777
+ inspectionCounters.completedItemsMaxCount = Math.max(
778
+ inspectionCounters.completedItemsMaxCount,
779
+ completedItemsByOutputIndex!.size,
780
+ );
781
+ };
782
+
783
+ const scanPayload = (payload: string | null, sourceBytes: number): void => {
784
+ if (!payload) return;
785
+ let parsed: unknown | undefined;
786
+ if (payload !== "[DONE]") {
787
+ try {
788
+ parsed = JSON.parse(payload);
789
+ } catch {
790
+ /* malformed SSE payloads remain best-effort/no-throw */
791
+ }
792
+ }
793
+ if (!reported && handlers.logCtx) {
794
+ inspectResponseLogSsePayloadParsed(handlers.logCtx, payload, parsed);
795
+ }
796
+ // Before any terminal handling: a consumer deciding on the whole turn must observe this
797
+ // payload even when the terminal snapshot that follows no longer mentions it.
798
+ if (handlers.onParsedPayload && parsed !== undefined) {
799
+ try { handlers.onParsedPayload(parsed); } catch { /* inspection must never throw into the pump */ }
800
+ }
801
+ reportFirstOutput.parsed(parsed);
802
+ const status = terminalStatusFromParsed(parsed);
803
+ if (status) sawTerminal = true;
804
+ if (!reported && handlers.onTerminal && status) {
805
+ try {
806
+ reported = true;
807
+ if (handlers.logCtx) {
808
+ handlers.logCtx.transportPhase = "terminal_sse";
809
+ handlers.logCtx.terminalSource = "upstream";
810
+ }
811
+ handlers.onTerminal(status);
812
+ } finally {
813
+ if (status === "failed" || status === "incomplete") clearCompletedItems();
814
+ }
815
+ } else if (status === "failed" || status === "incomplete") {
816
+ clearCompletedItems();
817
+ }
818
+ if (handlers.onCompletedResponse) {
819
+ type ParsedSseEvent = { type?: unknown; output_index?: unknown; item?: unknown; response?: unknown };
820
+ const parsedEvent = parsed && typeof parsed === "object" && !Array.isArray(parsed)
821
+ ? parsed as ParsedSseEvent
822
+ : null;
823
+ const responseRecord = parsedEvent
824
+ && typeof parsedEvent.response === "object"
825
+ && parsedEvent.response !== null
826
+ && !Array.isArray(parsedEvent.response)
827
+ ? parsedEvent.response as { id?: unknown }
828
+ : null;
829
+ if (handlers.pinCompletedResponseIdToFirstSeen
830
+ && responseRecord
831
+ && typeof responseRecord.id === "string") {
832
+ firstResponseId ??= responseRecord.id;
833
+ }
834
+ const doneItem = parsedEvent?.type === "response.output_item.done" ? parsedEvent.item : undefined;
835
+ if (parsedEvent
836
+ && doneItem !== undefined
837
+ && Number.isInteger(parsedEvent.output_index)
838
+ && (parsedEvent.output_index as number) >= 0
839
+ && typeof doneItem === "object"
840
+ && doneItem !== null
841
+ && !Array.isArray(doneItem)
842
+ && typeof (doneItem as { type?: unknown }).type === "string") {
843
+ retainCompletedItem(parsedEvent.output_index as number, doneItem, sourceBytes);
844
+ }
845
+
846
+ let response = completedResponseFromParsedEvent(parsedEvent);
847
+ if (response) {
848
+ if (handlers.pinCompletedResponseIdToFirstSeen
849
+ && firstResponseId !== undefined
850
+ && response.id !== firstResponseId) {
851
+ response = { ...response, id: firstResponseId };
852
+ }
853
+ // Authoritative output is a NON-EMPTY ARRAY only. Anything else
854
+ // (missing, null, scalar, object) keeps the historical backfill
855
+ // behavior so a malformed terminal cannot reach rememberResponseState
856
+ // and destroy continuation state (review C1-2).
857
+ const hasAuthoritativeOutput = Array.isArray(response.output)
858
+ && response.output.length > 0;
859
+ if (!hasAuthoritativeOutput && reconstructionTainted) {
860
+ clearCompletedItems();
861
+ return;
862
+ }
863
+ if (!hasAuthoritativeOutput && completedItemsByOutputIndex!.size > 0) {
864
+ response = {
865
+ ...response,
866
+ output: [...completedItemsByOutputIndex!.entries()]
867
+ .sort(([left], [right]) => left - right)
868
+ .map(([, retained]) => retained.item),
869
+ };
870
+ }
871
+ try {
872
+ handlers.onCompletedResponse(response);
873
+ } finally {
874
+ clearCompletedItems();
875
+ }
876
+ } else if (parsedEvent?.type === "response.completed") {
877
+ clearCompletedItems();
878
+ }
879
+ }
880
+ };
881
+
882
+ const completeCandidate = (): void => {
883
+ if (discardingOversizedFrame) {
884
+ discardingOversizedFrame = false;
885
+ return;
886
+ }
887
+ const sourceBytes = candidateBytes;
888
+ const frame = takeCandidate();
889
+ if (reported && !handlers.onCompletedResponse) return;
890
+ const decoded = decoder!.decode(frame);
891
+ scanPayload(sseDataPayload(decoded), sourceBytes);
892
+ };
893
+
894
+ const scanChunk = (chunk: Uint8Array): void => {
895
+ const previousTail = delimiterTail;
896
+ delimiterTail = new Uint8Array(0);
897
+ const tailLength = previousTail.byteLength;
898
+ const totalLength = tailLength + chunk.byteLength;
899
+ const byteAt = (index: number): number => index < tailLength
900
+ ? previousTail[index]!
901
+ : chunk[index - tailLength]!;
902
+ const retainRange = (start: number, end: number): void => {
903
+ if (end <= start || discardingOversizedFrame) return;
904
+ if (start < tailLength) {
905
+ retainCandidateSlice(previousTail.subarray(start, Math.min(end, tailLength)));
906
+ }
907
+ if (end > tailLength) {
908
+ retainCandidateSlice(chunk.subarray(Math.max(0, start - tailLength), end - tailLength));
909
+ }
910
+ };
911
+ let index = 0;
912
+ let retainedThrough = 0;
913
+ while (index < totalLength) {
914
+ const delimiterLength = delimiterLengthAt(index, totalLength, byteAt);
915
+ if (delimiterLength === undefined) break;
916
+ if (delimiterLength > 0) {
917
+ retainRange(retainedThrough, index);
918
+ completeCandidate();
919
+ index += delimiterLength;
920
+ retainedThrough = index;
921
+ continue;
922
+ }
923
+ index += 1;
924
+ }
925
+ retainRange(retainedThrough, index);
926
+ if (index < totalLength) {
927
+ delimiterTail = new Uint8Array(totalLength - index);
928
+ for (let offset = 0; offset < delimiterTail.byteLength; offset += 1) {
929
+ delimiterTail[offset] = byteAt(index + offset);
930
+ }
931
+ }
932
+ };
933
+
934
+ return {
935
+ feed(chunk) {
936
+ if (!disposed) scanChunk(chunk);
937
+ },
938
+ finish() {
939
+ if (disposed) return;
940
+ try {
941
+ retainCandidateSlice(delimiterTail);
942
+ delimiterTail = new Uint8Array(0);
943
+ if (!discardingOversizedFrame && candidateBytes > 0 && !reported) {
944
+ const sourceBytes = candidateBytes;
945
+ const decoded = decoder!.decode(takeCandidate());
946
+ scanPayload(decoded.trim() ? sseDataPayload(decoded) : null, sourceBytes);
947
+ }
948
+ } finally {
949
+ clearFrameState();
950
+ clearCompletedItems();
951
+ }
952
+ },
953
+ dispose,
954
+ reported: () => reported,
955
+ terminalSeen: () => sawTerminal,
956
+ };
957
+ }
958
+
959
+ export type InspectionDrainBounds = { ms: number; bytes: number };
960
+
961
+ export type InspectionConsumerOptions = {
962
+ clientGoneSignal?: AbortSignal;
963
+ drainBounds?: Partial<InspectionDrainBounds>;
964
+ upstream?: AbortController;
965
+ now?: () => number;
966
+ /** Forward provider-scoped response-id pinning to the owned inspector. */
967
+ pinCompletedResponseIdToFirstSeen?: boolean;
968
+ /** Observe every parsed SSE payload on the inspection side; see SseInspectorHandlers. */
969
+ onParsedPayload?: (payload: unknown) => void;
970
+ /** Test seam for proving both public consumers dispose their owned inspector. */
971
+ inspectorFactory?: (handlers: SseInspectorHandlers) => SseInspector;
972
+ };
973
+
974
+ const DEFAULT_INSPECTION_DRAIN_MS = 15_000;
975
+ const DEFAULT_INSPECTION_DRAIN_BYTES = 32 * 1024 * 1024;
976
+ type InspectionPumpOptions = InspectionConsumerOptions & {
977
+ reader: ReadableStreamDefaultReader<Uint8Array>;
978
+ inspector: SseInspector;
979
+ signal?: AbortSignal;
980
+ onDone?: () => void;
981
+ onCancel?: () => void;
982
+ onCleanEof?: () => void;
983
+ onReadError?: () => void;
984
+ };
985
+
986
+ function startBoundedInspectionPump(options: InspectionPumpOptions): void {
987
+ const { reader, inspector, signal, clientGoneSignal } = options;
988
+ let cancelled = false;
989
+ let clientGone = false;
990
+ let clientGoneReason: unknown;
991
+ let drainedBytes = 0;
992
+ let drainDeadline = Number.POSITIVE_INFINITY;
993
+ let drainTimer: ReturnType<typeof setTimeout> | undefined;
994
+ let drainStopped = false;
995
+ const drainMs = options.drainBounds?.ms ?? DEFAULT_INSPECTION_DRAIN_MS;
996
+ const drainBytes = options.drainBounds?.bytes ?? DEFAULT_INSPECTION_DRAIN_BYTES;
997
+ const now = options.now ?? Date.now;
998
+ let cancelFired = false;
999
+ const fireCancel = () => {
1000
+ if (cancelFired) return;
1001
+ cancelFired = true;
1002
+ options.onCancel?.();
1003
+ };
1004
+ const markClientGone = () => {
1005
+ if (clientGone || cancelled) return;
1006
+ clientGone = true;
1007
+ clientGoneReason = clientGoneSignal?.reason;
1008
+ drainDeadline = now() + drainMs;
1009
+ if (inspector.terminalSeen() || drainMs <= 0 || drainBytes <= 0) {
1010
+ stopDrain();
1011
+ return;
1012
+ }
1013
+ // Do not unref: on Bun/Windows a pending `reader.read()` can be the only
1014
+ // wake source; an unref'd timer may never run, so a silent post-cancel
1015
+ // drain (time bound, no bytes) hangs the suite until the job timeout.
1016
+ drainTimer = setTimeout(stopDrain, drainMs);
1017
+ };
1018
+ // Ends the bounded drain by cancelling the reader: the pending read settles
1019
+ // and the pump loop observes `drainStopped`. Deliberately NOT a shared
1020
+ // Promise.race companion — racing every read against one pending promise
1021
+ // retains O(chunk-count) reactions on long streams (review C1-1), the exact
1022
+ // retention class this phase removes.
1023
+ const stopDrain = () => {
1024
+ if (drainStopped || cancelled) return;
1025
+ drainStopped = true;
1026
+ reader.cancel(clientGoneReason).catch(() => {});
1027
+ };
1028
+ const abortImmediately = () => {
1029
+ if (cancelled) return;
1030
+ cancelled = true;
1031
+ reader.cancel(signal?.reason).catch(() => {});
1032
+ fireCancel();
1033
+ };
1034
+
1035
+ if (signal?.aborted) {
1036
+ cancelled = true;
1037
+ reader.cancel(signal.reason).catch(() => {});
1038
+ inspector.dispose();
1039
+ fireCancel();
1040
+ options.onDone?.();
1041
+ return;
1042
+ }
1043
+ signal?.addEventListener("abort", abortImmediately, { once: true });
1044
+ clientGoneSignal?.addEventListener("abort", markClientGone, { once: true });
1045
+ if (clientGoneSignal?.aborted) markClientGone();
1046
+
1047
+ const pump = async () => {
1048
+ let clientGoneWithoutTerminal = false;
1049
+ let boundEndedDrain = false;
1050
+ try {
1051
+ for (;;) {
1052
+ const { done, value } = await reader.read();
1053
+ if (drainStopped) {
1054
+ // stopDrain() cancelled the reader; the settled read is the wake-up.
1055
+ clientGoneWithoutTerminal = !inspector.terminalSeen();
1056
+ boundEndedDrain = clientGoneWithoutTerminal;
1057
+ break;
1058
+ }
1059
+ if (done) {
1060
+ inspector.finish();
1061
+ if (clientGone) clientGoneWithoutTerminal = !inspector.terminalSeen();
1062
+ else if (!cancelled) options.onCleanEof?.();
1063
+ break;
1064
+ }
1065
+ if (!clientGone) {
1066
+ inspector.feed(value);
1067
+ continue;
1068
+ }
1069
+ if (now() >= drainDeadline) {
1070
+ clientGoneWithoutTerminal = true;
1071
+ boundEndedDrain = true;
1072
+ break;
1073
+ }
1074
+ const remainingBytes = Math.max(0, drainBytes - drainedBytes);
1075
+ const inspectedValue = value.byteLength > remainingBytes
1076
+ ? value.subarray(0, remainingBytes)
1077
+ : value;
1078
+ if (inspectedValue.byteLength > 0) inspector.feed(inspectedValue);
1079
+ drainedBytes += inspectedValue.byteLength;
1080
+ if (inspector.terminalSeen()) break;
1081
+ if (value.byteLength > remainingBytes
1082
+ || drainedBytes >= drainBytes
1083
+ || now() >= drainDeadline) {
1084
+ clientGoneWithoutTerminal = true;
1085
+ boundEndedDrain = true;
1086
+ break;
1087
+ }
1088
+ }
1089
+ } catch {
1090
+ if (clientGone) clientGoneWithoutTerminal = !inspector.terminalSeen();
1091
+ else if (!cancelled) options.onReadError?.();
1092
+ } finally {
1093
+ if (drainTimer) clearTimeout(drainTimer);
1094
+ signal?.removeEventListener("abort", abortImmediately);
1095
+ clientGoneSignal?.removeEventListener("abort", markClientGone);
1096
+ if (clientGone) {
1097
+ if (boundEndedDrain) inspectionCounters.postCancelDrainStops += 1;
1098
+ if (clientGoneWithoutTerminal) fireCancel();
1099
+ options.upstream?.abort(clientGoneReason);
1100
+ reader.cancel(clientGoneReason).catch(() => {});
1101
+ }
1102
+ inspector.dispose();
1103
+ options.onDone?.();
1104
+ }
1105
+ };
1106
+ void pump();
1107
+ }
1108
+
1109
+ export function consumeForInspection(
1110
+ body: ReadableStream<Uint8Array>,
1111
+ onTerminal: (status: ResponsesTerminalStatus, httpStatusOverride?: number) => void,
1112
+ signal?: AbortSignal,
1113
+ onDone?: () => void,
1114
+ logCtx?: RequestLogContext,
1115
+ onCancel?: () => void,
1116
+ onCompletedResponse?: (response: { id?: unknown; output?: unknown; status?: unknown }) => void,
1117
+ onFirstOutput?: () => void,
1118
+ options?: InspectionConsumerOptions,
1119
+ ): void {
1120
+ const reader = body.getReader();
1121
+ const inspector = (options?.inspectorFactory ?? createSseInspector)({
1122
+ onTerminal,
1123
+ logCtx,
1124
+ onCompletedResponse,
1125
+ onParsedPayload: options?.onParsedPayload,
1126
+ onFirstOutput,
1127
+ pinCompletedResponseIdToFirstSeen: options?.pinCompletedResponseIdToFirstSeen,
1128
+ });
1129
+ startBoundedInspectionPump({
1130
+ ...options,
1131
+ reader,
1132
+ inspector,
1133
+ signal,
1134
+ onDone,
1135
+ onCancel,
1136
+ onCleanEof: () => {
1137
+ if (!inspector.reported()) {
1138
+ if (logCtx) logCtx.terminalSource = "synthetic";
1139
+ onTerminal("incomplete");
1140
+ }
1141
+ },
1142
+ onReadError: () => {
1143
+ // Upstream read failure after HTTP 200 (mid-stream socket reset) is not a
1144
+ // protocol `response.incomplete` terminal. Report a synthetic 502 so account
1145
+ // health treats it as transient; abort-driven client cancellation still wins.
1146
+ if (!inspector.reported()) {
1147
+ if (logCtx) {
1148
+ logCtx.transportPhase = "mid_stream";
1149
+ logCtx.terminalSource = "synthetic";
1150
+ // A truncated 200 body must not meter as a success the client never
1151
+ // received; the router's equivalent turn carries 502 + streamAborted
1152
+ // (codex-router #139).
1153
+ if (logCtx.activeAttempt) logCtx.activeAttempt.streamAborted = true;
1154
+ }
1155
+ onTerminal("failed", 502);
1156
+ }
1157
+ },
1158
+ });
1159
+ }
1160
+
1161
+ export function consumeForResponseLogMetadata(
1162
+ body: ReadableStream<Uint8Array>,
1163
+ logCtx: RequestLogContext,
1164
+ signal?: AbortSignal,
1165
+ onDone?: () => void,
1166
+ onCompletedResponse?: (response: { id?: unknown; output?: unknown; status?: unknown }) => void,
1167
+ onFirstOutput?: () => void,
1168
+ options?: InspectionConsumerOptions,
1169
+ ): void {
1170
+ const reader = body.getReader();
1171
+ // No onTerminal → the inspector's `reported` gate stays permanently false,
1172
+ // reproducing this consumer's unconditional logCtx inspection.
1173
+ const inspector = (options?.inspectorFactory ?? createSseInspector)({
1174
+ logCtx,
1175
+ onCompletedResponse,
1176
+ onParsedPayload: options?.onParsedPayload,
1177
+ onFirstOutput,
1178
+ pinCompletedResponseIdToFirstSeen: options?.pinCompletedResponseIdToFirstSeen,
1179
+ });
1180
+ startBoundedInspectionPump({ ...options, reader, inspector, signal, onDone });
1181
+ }
1182
+
1183
+ /**
1184
+ * Bun's fetch auto-decompresses the response body but leaves the upstream `content-encoding`
1185
+ * (and a now-stale `content-length`) on `response.headers`. Relaying those with the already-decoded
1186
+ * body makes the caller (Codex) double-decode / truncate → "stream error" on every gpt passthrough.
1187
+ * Drop encoding + hop-by-hop headers; relay everything else (content-type, etc.) verbatim.
1188
+ */
1189
+ export function sanitizePassthroughHeaders(upstream: Headers): Headers {
1190
+ const DROP = new Set([
1191
+ "content-encoding",
1192
+ "content-length",
1193
+ "transfer-encoding",
1194
+ "connection",
1195
+ "keep-alive",
1196
+ "proxy-authenticate",
1197
+ "proxy-authorization",
1198
+ "set-cookie",
1199
+ "set-cookie2",
1200
+ "te",
1201
+ "trailer",
1202
+ "upgrade",
1203
+ ]);
1204
+ const out = new Headers();
1205
+ upstream.forEach((value, key) => {
1206
+ if (!DROP.has(key.toLowerCase())) out.set(key, value);
1207
+ });
1208
+ return out;
1209
+ }