@iislee/opencodex 2.11.0 → 2.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (733) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +117 -22
  3. package/bin/ocx.mjs +245 -88
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-Be1sDpWw.css +1 -0
  6. package/gui/dist/assets/index-D4OKgUV4.js +114 -0
  7. package/gui/dist/index.html +2 -2
  8. package/gui/dist/provider-icons/alibaba-color.svg +1 -1
  9. package/gui/dist/provider-icons/antigravity-color.svg +1 -1
  10. package/gui/dist/provider-icons/claude-color.svg +1 -1
  11. package/gui/dist/provider-icons/cline-color.svg +16 -0
  12. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
  13. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  14. package/gui/dist/provider-icons/copilot-color.svg +1 -1
  15. package/gui/dist/provider-icons/cursor-color.svg +1 -1
  16. package/gui/dist/provider-icons/deepseek-color.svg +1 -1
  17. package/gui/dist/provider-icons/firepass-color.svg +1 -1
  18. package/gui/dist/provider-icons/fireworks-color.svg +1 -1
  19. package/gui/dist/provider-icons/gemini-color.svg +1 -1
  20. package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
  21. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
  22. package/gui/dist/provider-icons/grok.svg +1 -1
  23. package/gui/dist/provider-icons/groq-color.svg +1 -1
  24. package/gui/dist/provider-icons/huggingface-color.svg +1 -1
  25. package/gui/dist/provider-icons/kimi-color.svg +1 -1
  26. package/gui/dist/provider-icons/kiro-color.svg +2 -2
  27. package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
  28. package/gui/dist/provider-icons/mistral-color.svg +1 -1
  29. package/gui/dist/provider-icons/moonshot-color.svg +1 -1
  30. package/gui/dist/provider-icons/nvidia-color.svg +1 -1
  31. package/gui/dist/provider-icons/ollama-color.svg +1 -1
  32. package/gui/dist/provider-icons/openai.svg +1 -1
  33. package/gui/dist/provider-icons/opencode.svg +2 -1
  34. package/gui/dist/provider-icons/openrouter-color.svg +1 -1
  35. package/gui/dist/provider-icons/pi.svg +2 -2
  36. package/gui/dist/provider-icons/qianfan-color.svg +1 -1
  37. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
  38. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
  39. package/gui/dist/provider-icons/vllm-color.svg +1 -1
  40. package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
  41. package/package.json +21 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +475 -62
  44. package/src/adapters/base.ts +82 -7
  45. package/src/adapters/client-fingerprint.ts +18 -12
  46. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  47. package/src/adapters/command-code.ts +637 -0
  48. package/src/adapters/cursor/call-id.ts +44 -0
  49. package/src/adapters/cursor/catalog.ts +541 -0
  50. package/src/adapters/cursor/checkpoint-store.ts +308 -0
  51. package/src/adapters/cursor/cursor-errors.ts +144 -6
  52. package/src/adapters/cursor/discovery.ts +122 -14
  53. package/src/adapters/cursor/effort-map.ts +106 -4
  54. package/src/adapters/cursor/envelope-echo.ts +290 -0
  55. package/src/adapters/cursor/framing.ts +39 -0
  56. package/src/adapters/cursor/h2-pool.ts +123 -0
  57. package/src/adapters/cursor/http1-bidi.ts +361 -0
  58. package/src/adapters/cursor/images.ts +704 -0
  59. package/src/adapters/cursor/live-models.ts +180 -59
  60. package/src/adapters/cursor/live-transport.ts +623 -170
  61. package/src/adapters/cursor/message-mapper.ts +4 -1
  62. package/src/adapters/cursor/native-exec-common.ts +23 -2
  63. package/src/adapters/cursor/native-exec-desktop.ts +23 -0
  64. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  65. package/src/adapters/cursor/native-exec-network.ts +1 -1
  66. package/src/adapters/cursor/native-exec-shell.ts +5 -3
  67. package/src/adapters/cursor/native-exec.ts +121 -14
  68. package/src/adapters/cursor/protobuf-events.ts +829 -11
  69. package/src/adapters/cursor/protobuf-request.ts +447 -75
  70. package/src/adapters/cursor/request-builder.ts +271 -35
  71. package/src/adapters/cursor/tool-definitions.ts +252 -12
  72. package/src/adapters/cursor/tool-result-normalize.ts +115 -0
  73. package/src/adapters/cursor/transport.ts +22 -0
  74. package/src/adapters/cursor/types.ts +48 -1
  75. package/src/adapters/cursor.ts +336 -39
  76. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  77. package/src/adapters/google-antigravity-replay.ts +713 -41
  78. package/src/adapters/google-antigravity-wire.ts +48 -10
  79. package/src/adapters/google-errors.ts +44 -12
  80. package/src/adapters/google-http.ts +38 -10
  81. package/src/adapters/google-tool-schema.ts +80 -15
  82. package/src/adapters/google-truncation.ts +11 -0
  83. package/src/adapters/google.ts +742 -86
  84. package/src/adapters/identity.ts +39 -6
  85. package/src/adapters/image.ts +1 -1
  86. package/src/adapters/kiro-constants.ts +27 -0
  87. package/src/adapters/kiro-errors.ts +11 -0
  88. package/src/adapters/kiro-events.ts +19 -1
  89. package/src/adapters/kiro-thinking.ts +18 -2
  90. package/src/adapters/kiro-tools.ts +65 -17
  91. package/src/adapters/kiro.ts +270 -73
  92. package/src/adapters/mimo-free.ts +17 -0
  93. package/src/adapters/openai-chat-url.ts +11 -0
  94. package/src/adapters/openai-chat.ts +1377 -344
  95. package/src/adapters/openai-responses-url.ts +16 -0
  96. package/src/adapters/openai-responses.ts +1164 -64
  97. package/src/adapters/registry.ts +175 -0
  98. package/src/adapters/responses-tool-schema.ts +67 -0
  99. package/src/adapters/run-turn-queue.ts +36 -1
  100. package/src/adapters/tool-call-id.ts +119 -0
  101. package/src/adapters/tool-catalog-nudge.ts +105 -21
  102. package/src/adapters/xai-tool-schema.ts +436 -0
  103. package/src/adapters/xai-web-search.ts +186 -0
  104. package/src/bridge.ts +512 -92
  105. package/src/chat/inbound.ts +57 -20
  106. package/src/chat/outbound.ts +97 -33
  107. package/src/claude/agents-inject.ts +40 -10
  108. package/src/claude/context-windows.ts +37 -14
  109. package/src/claude/desktop-3p.ts +243 -9
  110. package/src/claude/gateway-cache.ts +41 -4
  111. package/src/claude/inbound.ts +72 -3
  112. package/src/claude/model-info.ts +38 -15
  113. package/src/claude/outbound.ts +88 -21
  114. package/src/cli/access.ts +46 -3
  115. package/src/cli/account-api.ts +150 -19
  116. package/src/cli/account-auth.ts +33 -6
  117. package/src/cli/account-catalog-refresh.ts +14 -0
  118. package/src/cli/account-extended.ts +648 -28
  119. package/src/cli/account-main.ts +317 -0
  120. package/src/cli/account.ts +97 -19
  121. package/src/cli/agent.ts +53 -2
  122. package/src/cli/alias.ts +66 -0
  123. package/src/cli/capabilities-command.ts +94 -0
  124. package/src/cli/capabilities.ts +496 -0
  125. package/src/cli/claude-agent-startup-sync.ts +73 -0
  126. package/src/cli/claude-desktop.ts +60 -15
  127. package/src/cli/claude.ts +129 -35
  128. package/src/cli/codex-log-guard-doctor.ts +103 -0
  129. package/src/cli/codex-shim-autorestore.ts +2 -0
  130. package/src/cli/codex-shim-readiness.ts +76 -0
  131. package/src/cli/combo.ts +8 -0
  132. package/src/cli/config-command.ts +74 -10
  133. package/src/cli/dispatch.ts +821 -0
  134. package/src/cli/doctor.ts +440 -56
  135. package/src/cli/ensure-desired-integrations.ts +152 -0
  136. package/src/cli/export-command.ts +46 -20
  137. package/src/cli/help.ts +34 -274
  138. package/src/cli/index.ts +417 -528
  139. package/src/cli/init.ts +5 -17
  140. package/src/cli/inspect.ts +230 -0
  141. package/src/cli/integrations.ts +120 -2
  142. package/src/cli/lab.ts +607 -0
  143. package/src/cli/launcher-context.ts +77 -0
  144. package/src/cli/minimax.ts +497 -0
  145. package/src/cli/models-runtime.ts +130 -2
  146. package/src/cli/models.ts +118 -16
  147. package/src/cli/observe.ts +123 -11
  148. package/src/cli/opencode.ts +4 -2
  149. package/src/cli/provider-runtime.ts +34 -3
  150. package/src/cli/provider.ts +25 -3
  151. package/src/cli/ready.ts +301 -0
  152. package/src/cli/registry.ts +476 -0
  153. package/src/cli/root.ts +86 -0
  154. package/src/cli/route-policy.ts +92 -0
  155. package/src/cli/runtime-api.ts +57 -10
  156. package/src/cli/star-prompt.ts +71 -15
  157. package/src/cli/status.ts +51 -4
  158. package/src/cli/storage.ts +234 -0
  159. package/src/cli/system-command.ts +16 -0
  160. package/src/cli/system-restart-client.ts +146 -0
  161. package/src/cli/tray-proxy.ts +153 -6
  162. package/src/cli/usage-report.ts +184 -0
  163. package/src/cli/v2.ts +137 -18
  164. package/src/cli/version-skew.ts +46 -0
  165. package/src/cli.ts +1 -1
  166. package/src/clients/config-export.ts +1442 -23
  167. package/src/clients/effective-status.ts +30 -2
  168. package/src/clients/probes/cline.ts +81 -0
  169. package/src/codex/account-label.ts +35 -1
  170. package/src/codex/account-lifecycle.ts +130 -13
  171. package/src/codex/account-namespaces.ts +49 -3
  172. package/src/codex/account-pause.ts +2 -1
  173. package/src/codex/account-priority.ts +84 -0
  174. package/src/codex/account-store.ts +29 -2
  175. package/src/codex/account-usability.ts +25 -2
  176. package/src/codex/admission.ts +256 -0
  177. package/src/codex/affinity-debug.ts +162 -0
  178. package/src/codex/app-server-processes.ts +570 -107
  179. package/src/codex/app-server-restart-service.ts +232 -0
  180. package/src/codex/auth-api.ts +893 -246
  181. package/src/codex/auth-collision.ts +5 -3
  182. package/src/codex/auth-context.ts +348 -32
  183. package/src/codex/autostart-health.ts +24 -1
  184. package/src/codex/catalog/account-models.ts +67 -0
  185. package/src/codex/catalog/aggregation.ts +86 -10
  186. package/src/codex/catalog/bundled.ts +331 -33
  187. package/src/codex/catalog/effort.ts +138 -32
  188. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  189. package/src/codex/catalog/kinds.ts +2 -0
  190. package/src/codex/catalog/metadata.ts +555 -45
  191. package/src/codex/catalog/native-models.ts +75 -0
  192. package/src/codex/catalog/parsing.ts +269 -30
  193. package/src/codex/catalog/provider-fetch.ts +1626 -144
  194. package/src/codex/catalog/sync.ts +1576 -187
  195. package/src/codex/catalog-admission.ts +199 -0
  196. package/src/codex/catalog-refresh-status.ts +105 -0
  197. package/src/codex/catalog-write-serialization.ts +242 -0
  198. package/src/codex/catalog.ts +6 -3
  199. package/src/codex/codex-write-lock.ts +386 -0
  200. package/src/codex/convergence-types.ts +614 -0
  201. package/src/codex/convergence.ts +676 -0
  202. package/src/codex/coordinator-doctor.ts +332 -0
  203. package/src/codex/custom-model-catalog-migration.ts +176 -0
  204. package/src/codex/desired-state.ts +230 -0
  205. package/src/codex/desktop-app-restart.ts +355 -0
  206. package/src/codex/features.ts +636 -39
  207. package/src/codex/generation.ts +202 -0
  208. package/src/codex/history-job.ts +436 -0
  209. package/src/codex/history-lock.ts +242 -0
  210. package/src/codex/history-manifest.ts +112 -0
  211. package/src/codex/history-migration-guardian.ts +30 -24
  212. package/src/codex/history-provider.ts +1016 -235
  213. package/src/codex/history-transition.ts +105 -0
  214. package/src/codex/history-worker.ts +223 -0
  215. package/src/codex/inject-coordination.ts +373 -0
  216. package/src/codex/inject.ts +1114 -152
  217. package/src/codex/injected-marker.ts +37 -3
  218. package/src/codex/integration-record.ts +266 -0
  219. package/src/codex/internal/catalog-writer.ts +203 -0
  220. package/src/codex/internal/history-writer.ts +81 -0
  221. package/src/codex/journal.ts +66 -4
  222. package/src/codex/log-guard/inspect.ts +524 -0
  223. package/src/codex/log-guard/lock.ts +150 -0
  224. package/src/codex/log-guard/maintenance.ts +403 -0
  225. package/src/codex/log-guard/path-safety.ts +88 -0
  226. package/src/codex/log-guard/policy.ts +44 -0
  227. package/src/codex/log-guard/processes.ts +205 -0
  228. package/src/codex/log-guard/protection.ts +489 -0
  229. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  230. package/src/codex/main-account-cache.ts +24 -0
  231. package/src/codex/main-account.ts +29 -1
  232. package/src/codex/management-convergence.ts +167 -0
  233. package/src/codex/model-cache.ts +56 -10
  234. package/src/codex/model-entitlements.ts +360 -0
  235. package/src/codex/native-main-admission.ts +47 -0
  236. package/src/codex/native-main-auth-temp.ts +187 -0
  237. package/src/codex/native-main-claim.ts +178 -0
  238. package/src/codex/native-main-lock-file.ts +162 -0
  239. package/src/codex/native-main-owner.ts +329 -0
  240. package/src/codex/native-profile-api.ts +247 -0
  241. package/src/codex/native-profile-manager.ts +1531 -0
  242. package/src/codex/native-profile-processes.ts +121 -0
  243. package/src/codex/native-profile-recovery.ts +99 -0
  244. package/src/codex/native-profile-stage-store.ts +387 -0
  245. package/src/codex/native-profile-startup.ts +622 -0
  246. package/src/codex/native-profile-store.ts +855 -0
  247. package/src/codex/native-profile-types.ts +120 -0
  248. package/src/codex/native-residue.ts +675 -0
  249. package/src/codex/paths.ts +80 -1
  250. package/src/codex/plan-from-token.ts +140 -0
  251. package/src/codex/plan.ts +40 -0
  252. package/src/codex/plugins-doctor.ts +1 -1
  253. package/src/codex/pool-rotation.ts +74 -4
  254. package/src/codex/project-config-warnings.ts +20 -6
  255. package/src/codex/prompt-journal.ts +352 -0
  256. package/src/codex/prompt-layers.ts +1313 -0
  257. package/src/codex/prompt-lock.ts +143 -0
  258. package/src/codex/prompt-text-probe.ts +238 -0
  259. package/src/codex/quota-rejection.ts +298 -0
  260. package/src/codex/quota.ts +264 -21
  261. package/src/codex/refresh.ts +11 -2
  262. package/src/codex/reset-credit-recovery.ts +1044 -0
  263. package/src/codex/routing.ts +514 -94
  264. package/src/codex/runtime.ts +159 -38
  265. package/src/codex/shim.ts +1065 -31
  266. package/src/codex/subagent-model-fallback.ts +437 -43
  267. package/src/codex/sync.ts +191 -2
  268. package/src/codex/transition-state.ts +720 -0
  269. package/src/codex/upstream-host-health.ts +368 -0
  270. package/src/codex/user-identity.ts +557 -0
  271. package/src/codex/warmup.ts +187 -81
  272. package/src/codex/write-coordination.ts +114 -0
  273. package/src/combos/failover.ts +47 -0
  274. package/src/combos/index.ts +4 -0
  275. package/src/combos/request.ts +32 -0
  276. package/src/combos/types.ts +91 -9
  277. package/src/compatibility/index.ts +26 -0
  278. package/src/compatibility/manifest.ts +253 -0
  279. package/src/compatibility/openai-responses.ts +121 -0
  280. package/src/config/atomic-write.ts +219 -0
  281. package/src/config/paths.ts +40 -0
  282. package/src/config/process-state.ts +309 -0
  283. package/src/config/provider-name.ts +24 -0
  284. package/src/config/provider-validation.ts +177 -0
  285. package/src/config/rebase-provenance.ts +68 -0
  286. package/src/config.ts +1713 -709
  287. package/src/generated/compatibility-version.json +3324 -0
  288. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  289. package/src/grok/inject.ts +16 -5
  290. package/src/grok/inspect.ts +45 -0
  291. package/src/grok/sync.ts +2 -2
  292. package/src/images/loop.ts +157 -33
  293. package/src/images/plan.ts +24 -13
  294. package/src/integrations/config-io.ts +269 -0
  295. package/src/integrations/journal.ts +315 -0
  296. package/src/integrations/merge.ts +135 -0
  297. package/src/integrations/mutation-flight.ts +71 -0
  298. package/src/integrations/native/ownership-preflight.ts +202 -0
  299. package/src/integrations/omp-yaml-source.ts +358 -0
  300. package/src/integrations/owned-refresh.ts +74 -0
  301. package/src/integrations/ownership-policy.ts +160 -0
  302. package/src/integrations/ownership.ts +155 -0
  303. package/src/integrations/registry.ts +166 -0
  304. package/src/integrations/serialize.ts +314 -0
  305. package/src/integrations/state.ts +433 -0
  306. package/src/integrations/store.ts +103 -0
  307. package/src/integrations/writer-lock.ts +98 -0
  308. package/src/integrations/writer.ts +715 -0
  309. package/src/lab/artifacts/sanitize.ts +586 -0
  310. package/src/lab/artifacts/secure-fs.ts +475 -0
  311. package/src/lab/artifacts/store.ts +310 -0
  312. package/src/lab/automation/budgets.ts +78 -0
  313. package/src/lab/automation/config-persistence.ts +256 -0
  314. package/src/lab/automation/constants.ts +39 -0
  315. package/src/lab/automation/cooldown.ts +103 -0
  316. package/src/lab/automation/dispatch.ts +211 -0
  317. package/src/lab/automation/index.ts +13 -0
  318. package/src/lab/automation/orchestrator.ts +499 -0
  319. package/src/lab/automation/persistence.ts +512 -0
  320. package/src/lab/automation/planner.ts +371 -0
  321. package/src/lab/automation/policy.ts +136 -0
  322. package/src/lab/automation/queue.ts +191 -0
  323. package/src/lab/automation/recovery.ts +24 -0
  324. package/src/lab/automation/route-context.ts +21 -0
  325. package/src/lab/automation/run-key.ts +44 -0
  326. package/src/lab/automation/runs-query.ts +34 -0
  327. package/src/lab/automation/types.ts +160 -0
  328. package/src/lab/conformance/assertion.ts +325 -0
  329. package/src/lab/conformance/digest.ts +22 -0
  330. package/src/lab/conformance/executor.ts +741 -0
  331. package/src/lab/conformance/fixture-provider.ts +27 -0
  332. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  333. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  334. package/src/lab/conformance/harness-budget.ts +47 -0
  335. package/src/lab/conformance/index.ts +5 -0
  336. package/src/lab/conformance/jcs.ts +64 -0
  337. package/src/lab/conformance/json-pointer.ts +39 -0
  338. package/src/lab/conformance/manifest.ts +180 -0
  339. package/src/lab/conformance/mcp-stub.ts +179 -0
  340. package/src/lab/conformance/negative-controls.ts +164 -0
  341. package/src/lab/conformance/observation.ts +355 -0
  342. package/src/lab/conformance/runner.ts +68 -0
  343. package/src/lab/conformance/sse-normalize.ts +59 -0
  344. package/src/lab/conformance/suite-manifest.ts +78 -0
  345. package/src/lab/conformance/types.ts +214 -0
  346. package/src/lab/constants.ts +126 -0
  347. package/src/lab/digest.ts +64 -0
  348. package/src/lab/events/errors.ts +9 -0
  349. package/src/lab/events/limits.ts +117 -0
  350. package/src/lab/events/types.ts +229 -0
  351. package/src/lab/events/validate.ts +781 -0
  352. package/src/lab/fabric/constants.ts +40 -0
  353. package/src/lab/fabric/executor.ts +492 -0
  354. package/src/lab/fabric/index.ts +80 -0
  355. package/src/lab/fabric/manifest.ts +222 -0
  356. package/src/lab/fabric/observe.ts +489 -0
  357. package/src/lab/fabric/patch.ts +79 -0
  358. package/src/lab/fabric/producer-child.ts +139 -0
  359. package/src/lab/fabric/producer-isolate.ts +276 -0
  360. package/src/lab/fabric/producer-protocol.ts +61 -0
  361. package/src/lab/fabric/scratch.ts +439 -0
  362. package/src/lab/fabric/subject.ts +106 -0
  363. package/src/lab/fabric/types.ts +134 -0
  364. package/src/lab/fabric/verifier.ts +98 -0
  365. package/src/lab/index.ts +54 -0
  366. package/src/lab/ledger/artifact-refs.ts +127 -0
  367. package/src/lab/ledger/invalidation.ts +136 -0
  368. package/src/lab/ledger/purge.ts +310 -0
  369. package/src/lab/ledger/store.ts +532 -0
  370. package/src/lab/live/credential-lease.ts +53 -0
  371. package/src/lab/live/destination.ts +155 -0
  372. package/src/lab/live/executor.ts +336 -0
  373. package/src/lab/live/inert-tools.ts +56 -0
  374. package/src/lab/live/manifest.ts +85 -0
  375. package/src/lab/live/mcp-loopback.ts +57 -0
  376. package/src/lab/live/runner.ts +19 -0
  377. package/src/lab/live/sandbox.ts +61 -0
  378. package/src/lab/live/suite-manifest.ts +41 -0
  379. package/src/lab/live/transport.ts +118 -0
  380. package/src/lab/live/types.ts +197 -0
  381. package/src/lab/observe/from-conformance.ts +301 -0
  382. package/src/lab/observe/from-live.ts +117 -0
  383. package/src/lab/paths.ts +153 -0
  384. package/src/lab/projection/rebuild.ts +495 -0
  385. package/src/lab/projection/schema.ts +135 -0
  386. package/src/lab/projection/verdicts.ts +474 -0
  387. package/src/lab/projection/verification.ts +412 -0
  388. package/src/lab/public/bundle.ts +217 -0
  389. package/src/lab/public/community-authority.ts +175 -0
  390. package/src/lab/public/community-files.ts +29 -0
  391. package/src/lab/public/community.ts +479 -0
  392. package/src/lab/public/file-safety.ts +155 -0
  393. package/src/lab/public/ids.ts +26 -0
  394. package/src/lab/public/index.ts +16 -0
  395. package/src/lab/public/mutation-lock.ts +424 -0
  396. package/src/lab/public/operator.ts +353 -0
  397. package/src/lab/public/origin-purge.ts +79 -0
  398. package/src/lab/public/origin.ts +203 -0
  399. package/src/lab/public/privacy.ts +143 -0
  400. package/src/lab/public/private-file.ts +261 -0
  401. package/src/lab/public/project.ts +124 -0
  402. package/src/lab/public/purge-test-fault.ts +21 -0
  403. package/src/lab/public/purge.ts +223 -0
  404. package/src/lab/public/registry.ts +44 -0
  405. package/src/lab/public/revocation.ts +252 -0
  406. package/src/lab/public/signature.ts +243 -0
  407. package/src/lab/public/storage.ts +105 -0
  408. package/src/lab/public/strict-json.ts +206 -0
  409. package/src/lab/public/time.ts +26 -0
  410. package/src/lab/public/types.ts +172 -0
  411. package/src/lab/public/validate.ts +391 -0
  412. package/src/lab/query/catalog.ts +101 -0
  413. package/src/lab/query/connection.ts +107 -0
  414. package/src/lab/query/constants.ts +4 -0
  415. package/src/lab/query/cursor.ts +132 -0
  416. package/src/lab/query/dto-map.ts +277 -0
  417. package/src/lab/query/errors.ts +22 -0
  418. package/src/lab/query/freshness.ts +53 -0
  419. package/src/lab/query/index.ts +45 -0
  420. package/src/lab/query/latest-observation.ts +59 -0
  421. package/src/lab/query/passive-production.ts +159 -0
  422. package/src/lab/query/queries.ts +444 -0
  423. package/src/lab/query/types.ts +266 -0
  424. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  425. package/src/lab/subject/installation-salt.ts +112 -0
  426. package/src/lab/subject/protocol-subject.ts +80 -0
  427. package/src/lab/subject/route-subject.ts +74 -0
  428. package/src/lib/admin-secrets.ts +24 -0
  429. package/src/lib/app-owned-memory-stores.ts +22 -0
  430. package/src/lib/bounded-body.ts +167 -11
  431. package/src/lib/bun-runtime.ts +125 -12
  432. package/src/lib/bun-stream-caps.ts +13 -9
  433. package/src/lib/codex-restart-contract.ts +120 -0
  434. package/src/lib/config-ownership.ts +26 -2
  435. package/src/lib/destination-policy.ts +65 -1
  436. package/src/lib/errors.ts +80 -5
  437. package/src/lib/fabric-task-execution-authority.ts +7 -0
  438. package/src/lib/fabric-task-host.ts +29 -0
  439. package/src/lib/lab-activation.ts +223 -0
  440. package/src/lib/lab-live-execution-authority.ts +13 -0
  441. package/src/lib/lab-live-host.ts +30 -0
  442. package/src/lib/lab-live-pinned-sender.ts +56 -0
  443. package/src/lib/lab-live-route-production.ts +130 -0
  444. package/src/lib/lab-passive-linker-registration.ts +26 -0
  445. package/src/lib/local-management-attestation.ts +51 -0
  446. package/src/lib/local-management-capability.ts +100 -0
  447. package/src/lib/local-provider-reload-contract.ts +100 -0
  448. package/src/lib/optional-shutdown-hooks.ts +57 -0
  449. package/src/lib/package-tree-integrity.ts +101 -0
  450. package/src/lib/pinned-http.ts +145 -26
  451. package/src/lib/process-control.ts +6 -2
  452. package/src/lib/provider-outbound.ts +49 -9
  453. package/src/lib/redact.ts +419 -3
  454. package/src/lib/self-launch-argv.ts +15 -0
  455. package/src/lib/server-resource-ownership.ts +71 -0
  456. package/src/lib/service-secrets.ts +15 -0
  457. package/src/lib/shadow-call.ts +35 -4
  458. package/src/lib/sse-decoder.ts +41 -0
  459. package/src/lib/state-store-registrations.ts +10 -2
  460. package/src/lib/system-restart-contract.ts +73 -0
  461. package/src/lib/token-estimate.ts +19 -2
  462. package/src/lib/tool-argument-integers.ts +253 -0
  463. package/src/lib/translator-budget.ts +44 -0
  464. package/src/lib/upstream-http-version.ts +57 -0
  465. package/src/lib/upstream-reachability.ts +95 -0
  466. package/src/lib/upstream-retry.ts +156 -3
  467. package/src/lib/windows-atomic-replace.ts +155 -0
  468. package/src/lib/windows-elevation.ts +70 -2
  469. package/src/lib/windows-secret-acl.ts +409 -69
  470. package/src/lib/windows-service-wrappers.ts +72 -0
  471. package/src/lib/windows-text.ts +106 -0
  472. package/src/lib/windows-user-principal.ts +341 -0
  473. package/src/lib/winsw.ts +33 -5
  474. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  475. package/src/oauth/account-import/index.ts +15 -0
  476. package/src/oauth/account-import/parser.ts +83 -0
  477. package/src/oauth/account-import/registry.ts +18 -0
  478. package/src/oauth/account-import/service.ts +75 -0
  479. package/src/oauth/account-import/types.ts +91 -0
  480. package/src/oauth/anthropic.ts +12 -1
  481. package/src/oauth/callback-server.ts +30 -4
  482. package/src/oauth/chatgpt.ts +12 -1
  483. package/src/oauth/cline.ts +203 -0
  484. package/src/oauth/command-code.ts +239 -0
  485. package/src/oauth/cursor.ts +46 -5
  486. package/src/oauth/generic-account-failover.ts +231 -0
  487. package/src/oauth/google-antigravity.ts +35 -3
  488. package/src/oauth/health.ts +20 -12
  489. package/src/oauth/index.ts +462 -71
  490. package/src/oauth/key-providers.ts +25 -0
  491. package/src/oauth/kimi.ts +25 -3
  492. package/src/oauth/kiro.ts +95 -6
  493. package/src/oauth/local-token-detect.ts +13 -2
  494. package/src/oauth/log.ts +3 -1
  495. package/src/oauth/login-cli.ts +88 -28
  496. package/src/oauth/nous.ts +798 -0
  497. package/src/oauth/open-browser-choice.ts +26 -0
  498. package/src/oauth/store.ts +133 -26
  499. package/src/oauth/token-guardian.ts +9 -3
  500. package/src/oauth/types.ts +15 -0
  501. package/src/pi/models.ts +2 -2
  502. package/src/providers/alibaba-region-migration.ts +1 -1
  503. package/src/providers/antigravity-models.ts +521 -31
  504. package/src/providers/auto-compact-budget.ts +65 -0
  505. package/src/providers/base-url-choices.ts +10 -0
  506. package/src/providers/codex-capacity.ts +292 -0
  507. package/src/providers/command-code-efforts.ts +176 -0
  508. package/src/providers/context-cap.ts +26 -8
  509. package/src/providers/cursor-pool.ts +72 -0
  510. package/src/providers/default-aliases.ts +65 -0
  511. package/src/providers/derive.ts +281 -6
  512. package/src/providers/fastwire.ts +507 -0
  513. package/src/providers/free-directory.ts +10 -7
  514. package/src/providers/google-vertex-location.ts +14 -0
  515. package/src/providers/key-failover.ts +71 -3
  516. package/src/providers/label.ts +35 -2
  517. package/src/providers/model-discovery-limits.ts +16 -0
  518. package/src/providers/model-discovery.ts +115 -22
  519. package/src/providers/model-presets.ts +119 -0
  520. package/src/providers/model-rename-migration.ts +255 -0
  521. package/src/providers/model-rename-startup.ts +28 -0
  522. package/src/providers/new-model-policy.ts +146 -0
  523. package/src/providers/openai-sidecar.ts +72 -4
  524. package/src/providers/openai-tier-startup.ts +31 -2
  525. package/src/providers/openai-tiers.ts +119 -4
  526. package/src/providers/openai-virtual-models.ts +1 -0
  527. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  528. package/src/providers/provider-id-rewrite.ts +30 -0
  529. package/src/providers/quota.ts +1379 -40
  530. package/src/providers/registry.ts +1545 -112
  531. package/src/providers/request-pacing.ts +310 -0
  532. package/src/providers/service-tier.ts +277 -0
  533. package/src/providers/slug-codec.ts +94 -6
  534. package/src/providers/static-model-discovery.ts +86 -0
  535. package/src/providers/xai-responses-opt-in.ts +15 -0
  536. package/src/providers/xai-transport.ts +32 -4
  537. package/src/reasoning-effort.ts +68 -3
  538. package/src/responses/apply-patch-envelope.ts +63 -0
  539. package/src/responses/code-mode-helper-compat.ts +50 -0
  540. package/src/responses/compaction.ts +26 -1
  541. package/src/responses/custom-tool-compat.ts +384 -0
  542. package/src/responses/hosted-tool-policy.ts +9 -0
  543. package/src/responses/namespace-tool-compat.ts +435 -0
  544. package/src/responses/parser.ts +225 -38
  545. package/src/responses/provider-continuation.ts +98 -0
  546. package/src/responses/provider-opaque-metadata.ts +73 -0
  547. package/src/responses/reasoning-envelope.ts +9 -1
  548. package/src/responses/reasoning-replay-cache.ts +504 -0
  549. package/src/responses/schema.ts +12 -2
  550. package/src/responses/spill-store.ts +75 -10
  551. package/src/responses/state.ts +659 -31
  552. package/src/responses/thought-signature-replay.ts +364 -0
  553. package/src/responses/tool-search-compat.ts +301 -0
  554. package/src/responses/truncated-stop-reason.ts +60 -0
  555. package/src/responses/turn-termination.ts +107 -0
  556. package/src/router.ts +405 -31
  557. package/src/routing/analytics.ts +379 -0
  558. package/src/routing/capability.ts +244 -0
  559. package/src/routing/compatibility/assemble.ts +73 -0
  560. package/src/routing/compatibility/behavior.ts +278 -0
  561. package/src/routing/compatibility/catalog.ts +99 -0
  562. package/src/routing/compatibility/endpoint.ts +52 -0
  563. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  564. package/src/routing/compatibility/policy.ts +181 -0
  565. package/src/routing/compatibility/provider-slot.ts +56 -0
  566. package/src/routing/compatibility/reader.ts +110 -0
  567. package/src/routing/compatibility/subject.ts +191 -0
  568. package/src/routing/compatibility/types.ts +64 -0
  569. package/src/routing/compatibility/version.ts +104 -0
  570. package/src/routing/cost.ts +77 -0
  571. package/src/routing/evaluator.ts +495 -0
  572. package/src/routing/health.ts +412 -0
  573. package/src/routing/history/cursor.ts +43 -0
  574. package/src/routing/history/indexer.ts +605 -0
  575. package/src/routing/history/schema.ts +72 -0
  576. package/src/routing/profile-namespace.ts +15 -0
  577. package/src/routing/profile.ts +547 -0
  578. package/src/routing/quota.ts +155 -0
  579. package/src/routing/request-evidence.ts +45 -0
  580. package/src/routing/trace.ts +776 -0
  581. package/src/server/adapter-resolve.ts +2 -29
  582. package/src/server/auth-cors.ts +289 -48
  583. package/src/server/background-lifecycle.ts +182 -0
  584. package/src/server/chat-completions.ts +154 -72
  585. package/src/server/chat-native-sse.ts +331 -0
  586. package/src/server/chat-native.ts +445 -0
  587. package/src/server/claude-messages.ts +160 -44
  588. package/src/server/direct-local-http.ts +347 -0
  589. package/src/server/effort-policy.ts +18 -0
  590. package/src/server/github-copilot-responses-repair.ts +338 -0
  591. package/src/server/gui-static.ts +39 -10
  592. package/src/server/images.ts +94 -12
  593. package/src/server/index.ts +1021 -190
  594. package/src/server/lifecycle.ts +336 -14
  595. package/src/server/live.ts +136 -17
  596. package/src/server/local-management-read-client.ts +90 -0
  597. package/src/server/local-provider-reload-client.ts +137 -0
  598. package/src/server/management/agent-settings-routes.ts +433 -116
  599. package/src/server/management/api-key-usage.ts +31 -5
  600. package/src/server/management/body.ts +6 -0
  601. package/src/server/management/codex-prompt-routes.ts +570 -0
  602. package/src/server/management/combo-routes.ts +64 -25
  603. package/src/server/management/config-routes.ts +511 -51
  604. package/src/server/management/context.ts +89 -2
  605. package/src/server/management/integration-routes.ts +498 -0
  606. package/src/server/management/lab-automation-routes.ts +206 -0
  607. package/src/server/management/lab-routes.ts +563 -0
  608. package/src/server/management/logs-usage-routes.ts +127 -52
  609. package/src/server/management/model-routes.ts +461 -133
  610. package/src/server/management/model-rows.ts +167 -0
  611. package/src/server/management/native-integration-routes.ts +770 -0
  612. package/src/server/management/oauth-account-routes.ts +90 -8
  613. package/src/server/management/provider-capability-config.ts +48 -0
  614. package/src/server/management/provider-routes.ts +836 -159
  615. package/src/server/management/request-history-routes.ts +191 -0
  616. package/src/server/management/route-registry.ts +332 -0
  617. package/src/server/management/routing-analytics-routes.ts +74 -0
  618. package/src/server/management/routing-profile-routes.ts +380 -0
  619. package/src/server/management/shared.ts +27 -11
  620. package/src/server/management/sidebar-routes.ts +47 -31
  621. package/src/server/management/storage-log-guard-routes.ts +186 -0
  622. package/src/server/management/sync-response.ts +69 -0
  623. package/src/server/management/system-restart.ts +289 -33
  624. package/src/server/management/system-routes.ts +77 -0
  625. package/src/server/management/usage-summary-cache.ts +9 -1
  626. package/src/server/management/vision-sidecar-options.ts +167 -0
  627. package/src/server/management/web-search-sidecar-options.ts +120 -0
  628. package/src/server/management-api.ts +126 -23
  629. package/src/server/management-auth.ts +220 -5
  630. package/src/server/passive-route-linker.ts +66 -0
  631. package/src/server/port-reclaim.ts +1 -1
  632. package/src/server/ports.ts +41 -1
  633. package/src/server/proxy-liveness.ts +161 -10
  634. package/src/server/readiness.ts +99 -0
  635. package/src/server/relay-eager.ts +221 -54
  636. package/src/server/relay.ts +479 -87
  637. package/src/server/request-decompress.ts +113 -6
  638. package/src/server/request-log-conversation.ts +51 -0
  639. package/src/server/request-log.ts +298 -31
  640. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  641. package/src/server/responses/agent-task-recovery.ts +465 -0
  642. package/src/server/responses/codex-auth-error.ts +55 -0
  643. package/src/server/responses/collaboration.ts +239 -36
  644. package/src/server/responses/combo-stream-preflight.ts +178 -0
  645. package/src/server/responses/compact.ts +444 -72
  646. package/src/server/responses/core.ts +3579 -436
  647. package/src/server/responses/empty-completion-guard.ts +333 -0
  648. package/src/server/responses/encrypted-payload.ts +62 -39
  649. package/src/server/responses/fetch-helpers.ts +89 -99
  650. package/src/server/responses/input-admission.ts +187 -0
  651. package/src/server/responses/pacing-overload.ts +13 -0
  652. package/src/server/responses/passthrough-error.ts +33 -9
  653. package/src/server/responses/policy-fallback.ts +178 -0
  654. package/src/server/responses/responses-field-backfill.ts +343 -0
  655. package/src/server/responses/terminal-guard.ts +26 -5
  656. package/src/server/responses/upstream-error.ts +5 -0
  657. package/src/server/responses/ws-upstream.ts +381 -0
  658. package/src/server/responses-custom-tool-repair.ts +363 -0
  659. package/src/server/responses-item-id-repair.ts +54 -6
  660. package/src/server/responses-json-events.ts +90 -0
  661. package/src/server/responses-model-rewrite.ts +29 -0
  662. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  663. package/src/server/responses-snapshot-repair.ts +621 -0
  664. package/src/server/responses-terminal-repair.ts +363 -0
  665. package/src/server/responses-tool-search-repair.ts +267 -0
  666. package/src/server/responses-undeclared-tool-guard.ts +376 -0
  667. package/src/server/responses.ts +18 -2
  668. package/src/server/search.ts +78 -13
  669. package/src/server/sse-frame-buffer.ts +319 -0
  670. package/src/server/sse-payload-rewrite.ts +110 -22
  671. package/src/server/startup-action-control.ts +8 -1
  672. package/src/server/startup-health-cache.ts +19 -1
  673. package/src/server/system-env.ts +80 -9
  674. package/src/server/ws-bridge.ts +53 -40
  675. package/src/service-manager-probe.ts +892 -0
  676. package/src/service.ts +1153 -94
  677. package/src/sidecar/auth.ts +92 -0
  678. package/src/sidecar/candidates.ts +83 -0
  679. package/src/storage/cleanup.ts +2 -2
  680. package/src/storage/policy-job.ts +14 -4
  681. package/src/storage/policy.ts +88 -23
  682. package/src/storage/scanner.ts +1 -1
  683. package/src/storage/worker-lifecycle.ts +14 -14
  684. package/src/tray/windows-tray.ps1 +83 -9
  685. package/src/tray/windows.ts +43 -16
  686. package/src/types/accounts.ts +37 -0
  687. package/src/types/config.ts +911 -0
  688. package/src/types/provider.ts +614 -0
  689. package/src/types/request.ts +397 -0
  690. package/src/types/tools.ts +236 -0
  691. package/src/types/wire.ts +80 -0
  692. package/src/types.ts +107 -1237
  693. package/src/update/index.ts +37 -23
  694. package/src/update/job.ts +445 -68
  695. package/src/update/notify.ts +12 -6
  696. package/src/update/npm-cache-preflight.d.mts +47 -0
  697. package/src/update/npm-cache-preflight.mjs +201 -0
  698. package/src/update/transactional-install.d.mts +22 -0
  699. package/src/update/transactional-install.mjs +266 -0
  700. package/src/usage/cost.ts +0 -0
  701. package/src/usage/expected-prices.ts +287 -16
  702. package/src/usage/log.ts +666 -50
  703. package/src/usage/summary.ts +388 -17
  704. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  705. package/src/usage/user-cost-overlays.ts +314 -0
  706. package/src/vision/anthropic-describe.ts +10 -6
  707. package/src/vision/backends.ts +97 -0
  708. package/src/vision/describe.ts +27 -16
  709. package/src/vision/eligibility.ts +250 -0
  710. package/src/vision/index.ts +238 -24
  711. package/src/vision/reasoning.ts +55 -0
  712. package/src/vision/routed-describe.ts +175 -0
  713. package/src/vision/timeout-bounds.ts +9 -0
  714. package/src/web-search/anthropic-executor.ts +13 -7
  715. package/src/web-search/backends.ts +108 -0
  716. package/src/web-search/exa-executor.ts +119 -0
  717. package/src/web-search/executor.ts +21 -6
  718. package/src/web-search/gemini-executor.ts +141 -0
  719. package/src/web-search/index.ts +150 -15
  720. package/src/web-search/loop.ts +284 -54
  721. package/src/web-search/parse.ts +125 -30
  722. package/src/web-search/sources.ts +60 -0
  723. package/src/web-search/xai-executor.ts +219 -0
  724. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  725. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  726. package/gui/dist/provider-icons/antigravity.svg +0 -1
  727. package/gui/dist/provider-icons/claude.svg +0 -1
  728. package/gui/dist/provider-icons/copilot.svg +0 -1
  729. package/gui/dist/provider-icons/cursor.svg +0 -2
  730. package/gui/dist/provider-icons/gemini.svg +0 -1
  731. package/gui/dist/provider-icons/grok-color.svg +0 -1
  732. package/gui/dist/provider-icons/kiro.svg +0 -14
  733. package/src/cli/internal-dispatch.ts +0 -20
@@ -1,4 +1,12 @@
1
1
  import type { ResponsesTerminalStatus } from "../bridge";
2
+ import {
3
+ cyberPolicyErrorType,
4
+ CYBER_POLICY_ERROR_CODE,
5
+ CYBER_POLICY_FALLBACK_MESSAGE,
6
+ isCyberPolicyCode,
7
+ isCyberPolicyMessage,
8
+ } from "../lib/errors";
9
+ import { redactSecretString } from "../lib/redact";
2
10
  import { isTranslatorBudgetExceededError } from "../lib/translator-budget";
3
11
  import { isUsageDebugEnabled } from "../usage/debug";
4
12
  import {
@@ -6,20 +14,49 @@ import {
6
14
  addFinalRequestLog,
7
15
  httpStatusForRequestLogTerminal,
8
16
  inspectResponseLogJson,
9
- inspectResponseLogSsePayload,
10
17
  inspectResponseLogSsePayloadParsed,
11
18
  recordFirstOutput,
12
19
  type RequestLogContext,
13
20
  type RequestLogEntry,
14
21
  } from "./request-log";
22
+ import {
23
+ BoundedSseFrameBuffer,
24
+ joinSseFrameBytes,
25
+ MAX_CLIENT_SSE_FRAME_BYTES,
26
+ } from "./sse-frame-buffer";
27
+ import { replaceSseDataPayload } from "./sse-payload-rewrite";
15
28
 
16
29
  const nativePassthroughSseResponses = new WeakSet<Response>();
17
30
  const eagerRelaySseResponses = new WeakSet<Response>();
18
31
 
19
- export const MAX_INSPECTION_SSE_FRAME_BYTES = 4 * 1024 * 1024;
32
+ export const MAX_INSPECTION_SSE_FRAME_BYTES = MAX_CLIENT_SSE_FRAME_BYTES;
20
33
  export const MAX_COMPLETED_OUTPUT_ITEMS = 256;
21
34
  export const MAX_COMPLETED_OUTPUT_ITEM_SOURCE_BYTES = 8 * 1024 * 1024;
22
35
  export const MAX_TAIL_ERROR_MESSAGE_CHARS = 512;
36
+ const ADAPTER_EOF_INCOMPLETE_PAYLOAD = JSON.stringify({
37
+ type: "response.incomplete",
38
+ response: {
39
+ status: "incomplete",
40
+ incomplete_details: { reason: "adapter_eof" },
41
+ },
42
+ });
43
+ const DONE_SSE_FRAME_TEXT = "data: [DONE]\n\n";
44
+ const FAILED_TAIL_FALLBACK_PAYLOAD = JSON.stringify({
45
+ type: "response.failed",
46
+ response: {
47
+ status: "failed",
48
+ error: {
49
+ type: "upstream_error",
50
+ code: "upstream_reset",
51
+ message: "Upstream stream terminated unexpectedly",
52
+ },
53
+ last_error: {
54
+ type: "upstream_error",
55
+ code: "upstream_reset",
56
+ message: "Upstream stream terminated unexpectedly",
57
+ },
58
+ },
59
+ });
23
60
 
24
61
  export type InspectionCounters = {
25
62
  frameBufferHighWaterBytes: number;
@@ -95,6 +132,122 @@ export function buildFailedTailPayload(err: unknown): string {
95
132
  });
96
133
  }
97
134
 
135
+ function buildFailedTailPayloadOrFallback(err: unknown): string {
136
+ try {
137
+ return buildFailedTailPayload(err);
138
+ } catch {
139
+ // Error.message and String(error) may execute hostile accessors. Preserve a
140
+ // bounded protocol terminal even when diagnostic serialization is unsafe.
141
+ return FAILED_TAIL_FALLBACK_PAYLOAD;
142
+ }
143
+ }
144
+
145
+ export function failedTailFrame(encoder: TextEncoder, err: unknown): Uint8Array {
146
+ const payload = buildFailedTailPayloadOrFallback(err);
147
+ return encoder.encode(`\n\nevent: response.failed\ndata: ${payload}\n\n${DONE_SSE_FRAME_TEXT}`);
148
+ }
149
+
150
+ export type SseTerminalOutputBoundary = {
151
+ feed(chunk: Uint8Array): Uint8Array;
152
+ finish(): Uint8Array;
153
+ terminalSeen(): boolean;
154
+ doneSeen(): boolean;
155
+ dispose(): void;
156
+ };
157
+
158
+ /**
159
+ * Frame-aware client output boundary shared by both native Responses relays.
160
+ * It buffers only the current incomplete SSE block under the same hard byte
161
+ * cap as inspection, forwards complete blocks through the first Responses
162
+ * terminal, and drops every later block/byte. A premature [DONE] is held until
163
+ * a terminal arrives so clean EOF can synthesize one terminal and one sentinel.
164
+ */
165
+ export function createSseTerminalOutputBoundary(): SseTerminalOutputBoundary {
166
+ const decoder = new TextDecoder();
167
+ const encoder = new TextEncoder();
168
+ const framer = new BoundedSseFrameBuffer(MAX_INSPECTION_SSE_FRAME_BYTES);
169
+ let terminal = false;
170
+ let done = false;
171
+ let pendingDone: { block: Uint8Array; delimiter: Uint8Array } | null = null;
172
+ let disposed = false;
173
+
174
+ const processFrames = (
175
+ frames: ReturnType<BoundedSseFrameBuffer["feed"]>,
176
+ ): Uint8Array => {
177
+ if (disposed || terminal || frames.length === 0) return new Uint8Array(0);
178
+ const output: Uint8Array[] = [];
179
+ let responsesTerminal = false;
180
+ for (const frame of frames) {
181
+ const payload = sseDataPayload(decoder.decode(frame.block));
182
+ const isDone = payload === "[DONE]";
183
+ const parsed = payload === null ? undefined : parseSsePayload(payload);
184
+ const policyError = parsed !== undefined && isPolicyRewriteType(parsed)
185
+ ? cyberPolicyTerminalError(parsed)
186
+ : undefined;
187
+ const outboundBlock = policyError
188
+ ? encoder.encode(rewritePolicyTerminalBlock(
189
+ decoder.decode(frame.block),
190
+ policyFailurePayload(policyError, parsed),
191
+ ))
192
+ : frame.block;
193
+ if (isDone) {
194
+ done = true;
195
+ if (responsesTerminal) {
196
+ output.push(outboundBlock, frame.delimiter);
197
+ } else if (!pendingDone) {
198
+ // Do not expose a sentinel before a Responses terminal. If EOF
199
+ // follows, the synthetic incomplete path owns the one sentinel;
200
+ // if a terminal arrives later, this pending frame is emitted then.
201
+ pendingDone = { block: outboundBlock, delimiter: frame.delimiter };
202
+ }
203
+ continue;
204
+ }
205
+ // Preserve every frame through the first Responses terminal. Every
206
+ // later non-DONE frame is dropped.
207
+ if (!responsesTerminal) output.push(outboundBlock, frame.delimiter);
208
+ if (!responsesTerminal && payload && terminalStatusFromParsed(parsed)) {
209
+ responsesTerminal = true;
210
+ if (pendingDone) {
211
+ output.push(pendingDone.block, pendingDone.delimiter);
212
+ pendingDone = null;
213
+ }
214
+ }
215
+ }
216
+ if (responsesTerminal) {
217
+ terminal = true;
218
+ framer.dispose();
219
+ }
220
+ return joinSseFrameBytes(output);
221
+ };
222
+
223
+ return {
224
+ feed(chunk) {
225
+ if (disposed || terminal) return new Uint8Array(0);
226
+ return processFrames(framer.feed(chunk));
227
+ },
228
+ finish() {
229
+ if (disposed || terminal) return new Uint8Array(0);
230
+ const tail = framer.finish();
231
+ if (tail.byteLength === 0) return new Uint8Array(0);
232
+ // EOF may cut off the final SSE block before its blank-line delimiter.
233
+ // Feed it through the exact same parser/rewrite/terminal path as a
234
+ // complete frame, using a synthetic delimiter so the client receives a
235
+ // dispatchable event rather than an unterminated tail.
236
+ const tailText = decoder.decode(tail);
237
+ const delimiter = encoder.encode(tailText.includes("\r\n") ? "\r\n\r\n" : "\n\n");
238
+ return processFrames([{ block: tail, delimiter }]);
239
+ },
240
+ terminalSeen: () => terminal,
241
+ doneSeen: () => done,
242
+ dispose() {
243
+ if (disposed) return;
244
+ disposed = true;
245
+ pendingDone = null;
246
+ framer.dispose();
247
+ },
248
+ };
249
+ }
250
+
98
251
  /**
99
252
  * Relay a passthrough SSE body like relayWithAbort, but convert a MID-STREAM failure (upstream
100
253
  * reset after headers) into a clean terminal: any partial block is closed off, then a synthetic
@@ -110,26 +263,88 @@ export function relaySseWithFailedTail(
110
263
  ): ReadableStream<Uint8Array> {
111
264
  const reader = body.getReader();
112
265
  const encoder = new TextEncoder();
266
+ const terminalBoundary = createSseTerminalOutputBoundary();
267
+ let closed = false;
268
+ const relayChunk = (
269
+ controller: ReadableStreamDefaultController<Uint8Array>,
270
+ value: Uint8Array,
271
+ ): "terminal" | "output" | "buffered" => {
272
+ const outbound = terminalBoundary.feed(value);
273
+ if (outbound.byteLength > 0) controller.enqueue(outbound);
274
+ if (!terminalBoundary.terminalSeen()) return outbound.byteLength > 0 ? "output" : "buffered";
275
+
276
+ // A Responses terminal frame is the protocol boundary. Some compatible
277
+ // gateways leave the HTTP connection open after response.completed, which
278
+ // otherwise leaves Codex waiting forever even though the model turn is done.
279
+ // Preserve through the terminal block only, add the conventional sentinel
280
+ // when there was no real [DONE] data event, then stop reading upstream.
281
+ if (!terminalBoundary.doneSeen()) {
282
+ controller.enqueue(doneFrame(encoder));
283
+ }
284
+ closed = true;
285
+ controller.close();
286
+ const reason = "Responses terminal event received";
287
+ // Notify the tee inspection branch as well. It has already received the
288
+ // same terminal-bearing upstream chunk, so its bounded drain records the
289
+ // real terminal and then releases the turn/upstream keep-alive connection.
290
+ onClientGone?.(reason);
291
+ reader.cancel(reason).catch(() => {});
292
+ terminalBoundary.dispose();
293
+ return "terminal";
294
+ };
113
295
  return new ReadableStream<Uint8Array>({
114
296
  async pull(controller) {
115
297
  try {
116
- const { done, value } = await reader.read();
117
- if (done) {
118
- controller.close();
119
- return;
298
+ for (;;) {
299
+ const { done, value } = await reader.read();
300
+ if (done) {
301
+ const tail = terminalBoundary.finish();
302
+ if (tail.byteLength > 0) controller.enqueue(tail);
303
+ if (terminalBoundary.terminalSeen()) {
304
+ if (!terminalBoundary.doneSeen()) controller.enqueue(doneFrame(encoder));
305
+ } else {
306
+ // A clean upstream EOF is still a failed Responses turn when no
307
+ // protocol terminal arrived. Make that state explicit so Codex
308
+ // does not treat HTTP 200 + bare EOF as a retryable disconnect.
309
+ const incomplete = adapterEofIncompleteFrame(encoder);
310
+ controller.enqueue(incomplete);
311
+ controller.enqueue(doneFrame(encoder));
312
+ }
313
+ terminalBoundary.dispose();
314
+ controller.close();
315
+ return;
316
+ }
317
+ const result = relayChunk(controller, value);
318
+ if (result !== "buffered") return;
120
319
  }
121
- controller.enqueue(value);
122
320
  } catch (err) {
123
- const payload = buildFailedTailPayload(err);
321
+ let partial: Uint8Array = new Uint8Array(0);
322
+ let tailTerminal = false;
323
+ try {
324
+ partial = terminalBoundary.finish();
325
+ tailTerminal = terminalBoundary.terminalSeen();
326
+ } catch {
327
+ // A near-cap ambiguous delimiter tail may itself overflow at EOF.
328
+ // Preserve the original read/framing failure and continue emitting
329
+ // the bounded failed tail instead of letting cleanup throw again.
330
+ }
331
+ terminalBoundary.dispose();
332
+ if (closed) return;
124
333
  try {
125
- // Leading blank line terminates a partial SSE block so the failed frame parses cleanly.
126
- controller.enqueue(encoder.encode(`\n\nevent: response.failed\ndata: ${payload}\n\ndata: [DONE]\n\n`));
334
+ if (partial.byteLength > 0) controller.enqueue(partial);
335
+ if (tailTerminal) {
336
+ if (!terminalBoundary.doneSeen()) controller.enqueue(doneFrame(encoder));
337
+ } else {
338
+ // Leading blank line terminates a partial SSE block so the failed frame parses cleanly.
339
+ controller.enqueue(failedTailFrame(encoder, err));
340
+ }
127
341
  controller.close();
128
342
  } catch { /* client already torn down */ }
129
343
  upstream.abort();
130
344
  }
131
345
  },
132
346
  cancel(reason) {
347
+ terminalBoundary.dispose();
133
348
  if (onClientGone) onClientGone(reason);
134
349
  else upstream.abort(reason);
135
350
  reader.cancel(reason).catch(() => {});
@@ -137,11 +352,12 @@ export function relaySseWithFailedTail(
137
352
  });
138
353
  }
139
354
 
140
- export function nextSseBlock(buffer: string): { block: string; rest: string } | null {
355
+ export function nextSseBlock(buffer: string): { block: string; delimiter: string; rest: string } | null {
141
356
  const match = buffer.match(/\r?\n\r?\n/);
142
357
  if (!match || match.index === undefined) return null;
143
358
  return {
144
359
  block: buffer.slice(0, match.index),
360
+ delimiter: match[0],
145
361
  rest: buffer.slice(match.index + match[0].length),
146
362
  };
147
363
  }
@@ -156,15 +372,139 @@ export function sseDataPayload(block: string): string | null {
156
372
  return data.length > 0 ? data.join("\n") : null;
157
373
  }
158
374
 
159
- export function terminalStatusFromSsePayload(payload: string): ResponsesTerminalStatus | null {
160
- if (payload === "[DONE]") return null;
375
+ type JsonRecord = Record<string, unknown>;
376
+
377
+ function asJsonRecord(value: unknown): JsonRecord | null {
378
+ return value !== null && typeof value === "object" && !Array.isArray(value)
379
+ ? value as JsonRecord
380
+ : null;
381
+ }
382
+
383
+ function stringField(record: JsonRecord | null, key: string): string | undefined {
384
+ const value = record?.[key];
385
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
386
+ }
387
+
388
+ /**
389
+ * Return a high-confidence policy error carried by an upstream terminal shape.
390
+ * Deliberately inspect structured error fields and known refusal copy only — a
391
+ * bare `cyber_policy` token in an unrelated payload is not sufficient.
392
+ */
393
+ function cyberPolicyTerminalError(parsed: unknown): { message: string; type?: string } | undefined {
394
+ const root = asJsonRecord(parsed);
395
+ if (!root) return undefined;
396
+ const response = asJsonRecord(root.response);
397
+ const candidates = [
398
+ asJsonRecord(root.error),
399
+ asJsonRecord(root.last_error),
400
+ asJsonRecord(response?.error),
401
+ asJsonRecord(response?.incomplete_details),
402
+ root,
403
+ response,
404
+ ];
405
+ for (const candidate of candidates) {
406
+ if (!candidate) continue;
407
+ if (isCyberPolicyCode(stringField(candidate, "code"))) {
408
+ return {
409
+ message: stringField(candidate, "message")
410
+ ?? CYBER_POLICY_FALLBACK_MESSAGE,
411
+ ...(stringField(candidate, "type") ? { type: stringField(candidate, "type") } : {}),
412
+ };
413
+ }
414
+ }
415
+ for (const candidate of candidates) {
416
+ const message = stringField(candidate, "message");
417
+ if (message && isCyberPolicyMessage(message)) {
418
+ return {
419
+ message,
420
+ ...(stringField(candidate, "type") ? { type: stringField(candidate, "type") } : {}),
421
+ };
422
+ }
423
+ }
424
+ return undefined;
425
+ }
426
+
427
+ function parseSsePayload(payload: string): unknown | undefined {
428
+ if (payload === "[DONE]") return undefined;
161
429
  try {
162
- return terminalStatusFromParsed(JSON.parse(payload));
430
+ return JSON.parse(payload);
163
431
  } catch {
164
- return null;
432
+ return undefined;
165
433
  }
166
434
  }
167
435
 
436
+ function isPolicyRewriteType(parsed: unknown): boolean {
437
+ const type = asJsonRecord(parsed)?.type;
438
+ return type === "response.failed" || type === "response.incomplete" || type === "error";
439
+ }
440
+
441
+ function rewritePolicyTerminalBlock(block: string, payload: string): string {
442
+ const newline = block.includes("\r\n") ? "\r\n" : "\n";
443
+ const rewritten = replaceSseDataPayload(block, payload);
444
+ const lines = rewritten.split(/\r?\n/);
445
+ let eventRewritten = false;
446
+ const withEvent = lines.map(line => {
447
+ if (!eventRewritten && line.startsWith("event:")) {
448
+ eventRewritten = true;
449
+ return "event: response.failed";
450
+ }
451
+ return line;
452
+ });
453
+ if (!eventRewritten) withEvent.unshift("event: response.failed");
454
+ return withEvent.join(newline);
455
+ }
456
+
457
+ function policyFailurePayload(policyError: { message: string; type?: string }, parsed: unknown): string {
458
+ const error = {
459
+ type: cyberPolicyErrorType(policyError.type),
460
+ code: CYBER_POLICY_ERROR_CODE,
461
+ message: redactSecretString(policyError.message).slice(0, MAX_TAIL_ERROR_MESSAGE_CHARS),
462
+ };
463
+ const root = asJsonRecord(parsed);
464
+ const originalResponse = asJsonRecord(root?.response);
465
+ const preservedResponse = Object.fromEntries(
466
+ Object.entries(originalResponse ?? {}).filter(([key]) => key !== "incomplete_details"),
467
+ );
468
+ const response = {
469
+ ...preservedResponse,
470
+ status: "failed",
471
+ error,
472
+ last_error: error,
473
+ retryable: false,
474
+ };
475
+ // Responses event metadata such as sequence_number is normally top-level.
476
+ // Keep it (and any other non-error, non-response fields) while replacing only
477
+ // the protocol type and error envelope.
478
+ const preservedRoot = root
479
+ ? Object.fromEntries(Object.entries(root).filter(([key]) => (
480
+ key !== "type"
481
+ && key !== "response"
482
+ && key !== "error"
483
+ && key !== "last_error"
484
+ && key !== "retryable"
485
+ )))
486
+ : {};
487
+ return JSON.stringify({
488
+ ...preservedRoot,
489
+ type: "response.failed",
490
+ retryable: false,
491
+ response,
492
+ });
493
+ }
494
+
495
+ export function adapterEofIncompleteFrame(encoder: TextEncoder): Uint8Array {
496
+ return encoder.encode(`event: response.incomplete\ndata: ${ADAPTER_EOF_INCOMPLETE_PAYLOAD}\n\n`);
497
+ }
498
+
499
+ export function doneFrame(encoder: TextEncoder): Uint8Array {
500
+ return encoder.encode(DONE_SSE_FRAME_TEXT);
501
+ }
502
+
503
+ export function terminalStatusFromSsePayload(payload: string): ResponsesTerminalStatus | null {
504
+ if (payload === "[DONE]") return null;
505
+ return terminalStatusFromParsed(parseSsePayload(payload));
506
+ }
507
+
168
508
  /** True when a native Responses SSE payload carries the FIRST kind of non-empty model output. */
169
509
  export function isFirstOutputSsePayload(payload: string | null): boolean {
170
510
  if (!payload || payload === "[DONE]") return false;
@@ -202,14 +542,16 @@ function createFirstOutputReporter(onFirstOutput?: () => void): {
202
542
  }
203
543
 
204
544
  export function terminalStatusFromParsed(parsed: unknown): ResponsesTerminalStatus | null {
205
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
206
- switch ((parsed as { type?: unknown }).type) {
545
+ const type = asJsonRecord(parsed)?.type;
546
+ switch (type) {
207
547
  case "response.completed":
208
548
  return "completed";
209
549
  case "response.failed":
210
550
  return "failed";
211
551
  case "response.incomplete":
212
- return "incomplete";
552
+ return cyberPolicyTerminalError(parsed) ? "failed" : "incomplete";
553
+ case "error":
554
+ return cyberPolicyTerminalError(parsed) ? "failed" : null;
213
555
  default:
214
556
  return null;
215
557
  }
@@ -245,53 +587,52 @@ export function trackSseForRequestLog(
245
587
  onFirstOutput?: () => void,
246
588
  ): ReadableStream<Uint8Array> {
247
589
  const reader = body.getReader();
248
- const decoder = new TextDecoder();
249
- let buffer = "";
250
590
  let terminalReported = false;
251
- const reportFirstOutput = createFirstOutputReporter(onFirstOutput);
591
+ let cancelled = false;
252
592
 
253
593
  const reportTerminal = (status: ResponsesTerminalStatus) => {
254
594
  if (terminalReported) return;
255
595
  terminalReported = true;
256
596
  onTerminal(status);
257
597
  };
258
-
259
- const inspectPayload = (payload: string | null) => {
260
- if (!payload) return;
261
- if (logCtx) inspectResponseLogSsePayload(logCtx, payload);
262
- reportFirstOutput.payload(payload);
263
- const status = terminalStatusFromSsePayload(payload);
264
- if (status) reportTerminal(status);
265
- };
266
-
267
- const inspectChunk = (value: Uint8Array) => {
268
- buffer += decoder.decode(value, { stream: true });
269
- let next: { block: string; rest: string } | null;
270
- while ((next = nextSseBlock(buffer))) {
271
- buffer = next.rest;
272
- inspectPayload(sseDataPayload(next.block));
273
- }
274
- };
598
+ // Reuse the byte-bounded inspector so translated responses cannot retain an
599
+ // unterminated upstream frame or parse the same event once per observer.
600
+ const inspector = createSseInspector({
601
+ onTerminal: reportTerminal,
602
+ logCtx,
603
+ onFirstOutput,
604
+ });
275
605
 
276
606
  return new ReadableStream<Uint8Array>({
277
607
  async pull(controller) {
278
608
  try {
279
609
  const { done, value } = await reader.read();
280
610
  if (done) {
281
- buffer += decoder.decode();
282
- if (buffer.trim()) inspectPayload(sseDataPayload(buffer));
283
- if (!terminalReported) reportTerminal("incomplete");
611
+ if (!cancelled) {
612
+ inspector.finish();
613
+ if (!terminalReported) reportTerminal("incomplete");
614
+ }
615
+ inspector.dispose();
284
616
  controller.close();
285
617
  return;
286
618
  }
287
- inspectChunk(value);
619
+ inspector.feed(value);
288
620
  controller.enqueue(value);
289
621
  } catch (err) {
290
- if (!terminalReported) reportTerminal("incomplete");
622
+ // The upstream read rejected: the 200 body died mid-flight. Client
623
+ // cancellation is the caller's separate 499 path, so a cancel-drained
624
+ // pending read (cancelled=true) must not carry the truncation marker.
625
+ if (!cancelled && !terminalReported && logCtx?.activeAttempt) {
626
+ logCtx.activeAttempt.streamAborted = true;
627
+ }
628
+ if (!cancelled && !terminalReported) reportTerminal("incomplete");
629
+ inspector.dispose();
291
630
  try { controller.error(err); } catch { /* already torn down */ }
292
631
  }
293
632
  },
294
633
  cancel(reason) {
634
+ cancelled = true;
635
+ inspector.dispose();
295
636
  onCancel();
296
637
  reader.cancel(reason).catch(() => {});
297
638
  },
@@ -402,38 +743,23 @@ export function relaySseWithHeartbeat(
402
743
  ): ReadableStream<Uint8Array> | null {
403
744
  if (!body) return null;
404
745
  const reader = body.getReader();
405
- const decoder = new TextDecoder();
406
746
  const heartbeat = new TextEncoder().encode(": opencodex keepalive\n\n");
407
747
  let timer: ReturnType<typeof setInterval> | undefined;
408
748
  let closed = false;
409
749
  let clientCancelled = false;
410
750
  let terminalReported = false;
411
- let buffer = "";
412
751
 
413
752
  const reportTerminal = (status: ResponsesTerminalStatus) => {
414
753
  if (terminalReported || clientCancelled || closed) return;
415
754
  terminalReported = true;
416
755
  onTerminal?.(status);
417
756
  };
418
-
419
- const inspectPayload = (payload: string | null) => {
420
- if (!payload) return;
421
- const status = terminalStatusFromSsePayload(payload);
422
- if (status) reportTerminal(status);
423
- };
424
-
425
- const inspectChunk = (value: Uint8Array) => {
426
- buffer += decoder.decode(value, { stream: true });
427
- let next: { block: string; rest: string } | null;
428
- while ((next = nextSseBlock(buffer))) {
429
- buffer = next.rest;
430
- inspectPayload(sseDataPayload(next.block));
431
- }
432
- };
757
+ const inspector = createSseInspector({ onTerminal: reportTerminal });
433
758
 
434
759
  const cleanup = () => {
435
760
  if (closed) return;
436
761
  closed = true;
762
+ inspector.dispose();
437
763
  if (timer) clearInterval(timer);
438
764
  timer = undefined;
439
765
  options?.onDone?.();
@@ -455,14 +781,13 @@ export function relaySseWithHeartbeat(
455
781
  try {
456
782
  const { done, value } = await reader.read();
457
783
  if (done) {
458
- buffer += decoder.decode();
459
- if (buffer.trim()) inspectPayload(sseDataPayload(buffer));
784
+ inspector.finish();
460
785
  if (!terminalReported && !clientCancelled) reportTerminal("incomplete");
461
786
  cleanup();
462
787
  controller.close();
463
788
  return;
464
789
  }
465
- inspectChunk(value);
790
+ inspector.feed(value);
466
791
  controller.enqueue(value);
467
792
  } catch (err) {
468
793
  if (!clientCancelled) reportTerminal("incomplete");
@@ -500,7 +825,20 @@ export type SseInspectorHandlers = {
500
825
  onTerminal?: (status: ResponsesTerminalStatus, httpStatusOverride?: number) => void;
501
826
  logCtx?: RequestLogContext;
502
827
  onCompletedResponse?: (response: { id?: unknown; output?: unknown; status?: unknown }) => void;
828
+ /**
829
+ * Every parsed SSE payload, delivered BEFORE any onCompletedResponse derived from that same
830
+ * payload. A caller that must decide on the whole turn -- not just its terminal snapshot --
831
+ * needs to see the incremental events, because a stream can announce an item and then close
832
+ * with an empty `output`.
833
+ */
834
+ onParsedPayload?: (payload: unknown) => void;
503
835
  onFirstOutput?: () => void;
836
+ /**
837
+ * Provider-scoped compatibility: persist the completed snapshot under the
838
+ * first response id exposed to the client when an upstream changes ids
839
+ * between `response.created` and `response.completed`.
840
+ */
841
+ pinCompletedResponseIdToFirstSeen?: boolean;
504
842
  };
505
843
 
506
844
  type CompletedOutputItem = { item: unknown; sourceBytes: number };
@@ -530,17 +868,6 @@ function delimiterLengthAt(
530
868
  return byteAt(index + 3) === 10 ? 4 : 0;
531
869
  }
532
870
 
533
- function joinedBytes(slices: readonly Uint8Array[], byteLength: number): Uint8Array {
534
- if (slices.length === 1 && slices[0]!.byteLength === byteLength) return slices[0]!;
535
- const joined = new Uint8Array(byteLength);
536
- let offset = 0;
537
- for (const slice of slices) {
538
- joined.set(slice, offset);
539
- offset += slice.byteLength;
540
- }
541
- return joined;
542
- }
543
-
544
871
  /**
545
872
  * Per-chunk SSE inspection state machine shared by consumeForInspection,
546
873
  * consumeForResponseLogMetadata, and the eager bounded relay (relay-eager.ts).
@@ -561,8 +888,8 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
561
888
  let reported = false;
562
889
  let sawTerminal = false;
563
890
  let disposed = false;
564
- let delimiterTail = new Uint8Array(0);
565
- let candidateSlices: Uint8Array[] = [];
891
+ let delimiterTail: Uint8Array = new Uint8Array(0);
892
+ let candidate: Uint8Array = new Uint8Array(0);
566
893
  let candidateBytes = 0;
567
894
  let discardingOversizedFrame = false;
568
895
  const reportFirstOutput = createFirstOutputReporter(handlers.onFirstOutput);
@@ -572,10 +899,11 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
572
899
  : null;
573
900
  let aggregateItemBytes = 0;
574
901
  let reconstructionTainted = false;
902
+ let firstResponseId: string | undefined;
575
903
 
576
904
  const clearFrameState = (): void => {
577
905
  delimiterTail = new Uint8Array(0);
578
- candidateSlices = [];
906
+ candidate = new Uint8Array(0);
579
907
  candidateBytes = 0;
580
908
  discardingOversizedFrame = false;
581
909
  };
@@ -592,6 +920,31 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
592
920
  decoder = null;
593
921
  clearFrameState();
594
922
  clearCompletedItems();
923
+ firstResponseId = undefined;
924
+ };
925
+
926
+ const ensureCandidateCapacity = (requiredBytes: number): void => {
927
+ if (candidate.byteLength >= requiredBytes) return;
928
+ let capacity = candidate.byteLength === 0
929
+ ? Math.min(MAX_INSPECTION_SSE_FRAME_BYTES, Math.max(requiredBytes, 4096))
930
+ : candidate.byteLength;
931
+ while (capacity < requiredBytes) {
932
+ capacity = Math.min(
933
+ MAX_INSPECTION_SSE_FRAME_BYTES,
934
+ Math.max(requiredBytes, capacity * 2),
935
+ );
936
+ }
937
+ const grown = new Uint8Array(capacity);
938
+ if (candidateBytes > 0) grown.set(candidate.subarray(0, candidateBytes));
939
+ candidate = grown;
940
+ };
941
+
942
+ const takeCandidate = (): Uint8Array => {
943
+ if (candidateBytes === 0) return new Uint8Array(0);
944
+ const frame = candidate.slice(0, candidateBytes);
945
+ candidate = new Uint8Array(0);
946
+ candidateBytes = 0;
947
+ return frame;
595
948
  };
596
949
 
597
950
  const retainCandidateSlice = (slice: Uint8Array): void => {
@@ -602,7 +955,7 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
602
955
  Math.min(nextBytes, MAX_INSPECTION_SSE_FRAME_BYTES),
603
956
  );
604
957
  if (nextBytes > MAX_INSPECTION_SSE_FRAME_BYTES) {
605
- candidateSlices = [];
958
+ candidate = new Uint8Array(0);
606
959
  candidateBytes = 0;
607
960
  discardingOversizedFrame = true;
608
961
  inspectionCounters.frameCapOverflows += 1;
@@ -612,10 +965,8 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
612
965
  reconstructionTainted = true;
613
966
  return;
614
967
  }
615
- // `subarray()` aliases the upstream chunk's backing buffer. Copy only the
616
- // live candidate bytes so a tiny trailing frame cannot pin a multi-MiB
617
- // chunk whose preceding frames have already been consumed.
618
- candidateSlices.push(slice.slice());
968
+ ensureCandidateCapacity(nextBytes);
969
+ candidate.set(slice, candidateBytes);
619
970
  candidateBytes = nextBytes;
620
971
  };
621
972
 
@@ -664,8 +1015,16 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
664
1015
  if (!reported && handlers.logCtx) {
665
1016
  inspectResponseLogSsePayloadParsed(handlers.logCtx, payload, parsed);
666
1017
  }
1018
+ // Before any terminal handling: a consumer deciding on the whole turn must observe this
1019
+ // payload even when the terminal snapshot that follows no longer mentions it.
1020
+ if (handlers.onParsedPayload && parsed !== undefined) {
1021
+ try { handlers.onParsedPayload(parsed); } catch { /* inspection must never throw into the pump */ }
1022
+ }
667
1023
  reportFirstOutput.parsed(parsed);
668
1024
  const status = terminalStatusFromParsed(parsed);
1025
+ const policyTerminal = status === "failed"
1026
+ && isPolicyRewriteType(parsed)
1027
+ && cyberPolicyTerminalError(parsed) !== undefined;
669
1028
  if (status) sawTerminal = true;
670
1029
  if (!reported && handlers.onTerminal && status) {
671
1030
  try {
@@ -674,7 +1033,7 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
674
1033
  handlers.logCtx.transportPhase = "terminal_sse";
675
1034
  handlers.logCtx.terminalSource = "upstream";
676
1035
  }
677
- handlers.onTerminal(status);
1036
+ handlers.onTerminal(status, policyTerminal ? 400 : undefined);
678
1037
  } finally {
679
1038
  if (status === "failed" || status === "incomplete") clearCompletedItems();
680
1039
  }
@@ -686,6 +1045,17 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
686
1045
  const parsedEvent = parsed && typeof parsed === "object" && !Array.isArray(parsed)
687
1046
  ? parsed as ParsedSseEvent
688
1047
  : null;
1048
+ const responseRecord = parsedEvent
1049
+ && typeof parsedEvent.response === "object"
1050
+ && parsedEvent.response !== null
1051
+ && !Array.isArray(parsedEvent.response)
1052
+ ? parsedEvent.response as { id?: unknown }
1053
+ : null;
1054
+ if (handlers.pinCompletedResponseIdToFirstSeen
1055
+ && responseRecord
1056
+ && typeof responseRecord.id === "string") {
1057
+ firstResponseId ??= responseRecord.id;
1058
+ }
689
1059
  const doneItem = parsedEvent?.type === "response.output_item.done" ? parsedEvent.item : undefined;
690
1060
  if (parsedEvent
691
1061
  && doneItem !== undefined
@@ -700,6 +1070,11 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
700
1070
 
701
1071
  let response = completedResponseFromParsedEvent(parsedEvent);
702
1072
  if (response) {
1073
+ if (handlers.pinCompletedResponseIdToFirstSeen
1074
+ && firstResponseId !== undefined
1075
+ && response.id !== firstResponseId) {
1076
+ response = { ...response, id: firstResponseId };
1077
+ }
703
1078
  // Authoritative output is a NON-EMPTY ARRAY only. Anything else
704
1079
  // (missing, null, scalar, object) keeps the historical backfill
705
1080
  // behavior so a malformed terminal cannot reach rememberResponseState
@@ -735,9 +1110,7 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
735
1110
  return;
736
1111
  }
737
1112
  const sourceBytes = candidateBytes;
738
- const frame = joinedBytes(candidateSlices, sourceBytes);
739
- candidateSlices = [];
740
- candidateBytes = 0;
1113
+ const frame = takeCandidate();
741
1114
  if (reported && !handlers.onCompletedResponse) return;
742
1115
  const decoded = decoder!.decode(frame);
743
1116
  scanPayload(sseDataPayload(decoded), sourceBytes);
@@ -794,7 +1167,7 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
794
1167
  delimiterTail = new Uint8Array(0);
795
1168
  if (!discardingOversizedFrame && candidateBytes > 0 && !reported) {
796
1169
  const sourceBytes = candidateBytes;
797
- const decoded = decoder!.decode(joinedBytes(candidateSlices, sourceBytes));
1170
+ const decoded = decoder!.decode(takeCandidate());
798
1171
  scanPayload(decoded.trim() ? sseDataPayload(decoded) : null, sourceBytes);
799
1172
  }
800
1173
  } finally {
@@ -815,6 +1188,10 @@ export type InspectionConsumerOptions = {
815
1188
  drainBounds?: Partial<InspectionDrainBounds>;
816
1189
  upstream?: AbortController;
817
1190
  now?: () => number;
1191
+ /** Forward provider-scoped response-id pinning to the owned inspector. */
1192
+ pinCompletedResponseIdToFirstSeen?: boolean;
1193
+ /** Observe every parsed SSE payload on the inspection side; see SseInspectorHandlers. */
1194
+ onParsedPayload?: (payload: unknown) => void;
818
1195
  /** Test seam for proving both public consumers dispose their owned inspector. */
819
1196
  inspectorFactory?: (handlers: SseInspectorHandlers) => SseInspector;
820
1197
  };
@@ -935,6 +1312,13 @@ function startBoundedInspectionPump(options: InspectionPumpOptions): void {
935
1312
  }
936
1313
  }
937
1314
  } catch {
1315
+ // A read error can follow a final SSE block without its blank-line
1316
+ // delimiter. Flush that candidate before classifying the transport as a
1317
+ // synthetic reset; otherwise a real completed/failed/policy terminal is
1318
+ // downgraded to 502 on the inspection branch.
1319
+ if (!cancelled) {
1320
+ try { inspector.finish(); } catch { /* preserve the original read error */ }
1321
+ }
938
1322
  if (clientGone) clientGoneWithoutTerminal = !inspector.terminalSeen();
939
1323
  else if (!cancelled) options.onReadError?.();
940
1324
  } finally {
@@ -970,7 +1354,9 @@ export function consumeForInspection(
970
1354
  onTerminal,
971
1355
  logCtx,
972
1356
  onCompletedResponse,
1357
+ onParsedPayload: options?.onParsedPayload,
973
1358
  onFirstOutput,
1359
+ pinCompletedResponseIdToFirstSeen: options?.pinCompletedResponseIdToFirstSeen,
974
1360
  });
975
1361
  startBoundedInspectionPump({
976
1362
  ...options,
@@ -993,6 +1379,10 @@ export function consumeForInspection(
993
1379
  if (logCtx) {
994
1380
  logCtx.transportPhase = "mid_stream";
995
1381
  logCtx.terminalSource = "synthetic";
1382
+ // A truncated 200 body must not meter as a success the client never
1383
+ // received; the router's equivalent turn carries 502 + streamAborted
1384
+ // (codex-router #139).
1385
+ if (logCtx.activeAttempt) logCtx.activeAttempt.streamAborted = true;
996
1386
  }
997
1387
  onTerminal("failed", 502);
998
1388
  }
@@ -1015,7 +1405,9 @@ export function consumeForResponseLogMetadata(
1015
1405
  const inspector = (options?.inspectorFactory ?? createSseInspector)({
1016
1406
  logCtx,
1017
1407
  onCompletedResponse,
1408
+ onParsedPayload: options?.onParsedPayload,
1018
1409
  onFirstOutput,
1410
+ pinCompletedResponseIdToFirstSeen: options?.pinCompletedResponseIdToFirstSeen,
1019
1411
  });
1020
1412
  startBoundedInspectionPump({ ...options, reader, inspector, signal, onDone });
1021
1413
  }