@iislee/opencodex 2.11.0 → 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 (675) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +114 -19
  3. package/bin/ocx.mjs +164 -36
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-BF38heuV.js +104 -0
  6. package/gui/dist/assets/index-DMiI18Kv.css +1 -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 +19 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +376 -52
  44. package/src/adapters/base.ts +54 -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 +601 -0
  48. package/src/adapters/cursor/checkpoint-store.ts +303 -0
  49. package/src/adapters/cursor/cursor-errors.ts +129 -6
  50. package/src/adapters/cursor/discovery.ts +61 -4
  51. package/src/adapters/cursor/effort-map.ts +27 -3
  52. package/src/adapters/cursor/framing.ts +39 -0
  53. package/src/adapters/cursor/h2-pool.ts +123 -0
  54. package/src/adapters/cursor/http1-bidi.ts +361 -0
  55. package/src/adapters/cursor/images.ts +704 -0
  56. package/src/adapters/cursor/live-models.ts +174 -58
  57. package/src/adapters/cursor/live-transport.ts +609 -170
  58. package/src/adapters/cursor/native-exec-common.ts +23 -2
  59. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  60. package/src/adapters/cursor/native-exec-network.ts +1 -1
  61. package/src/adapters/cursor/native-exec-shell.ts +0 -1
  62. package/src/adapters/cursor/native-exec.ts +101 -14
  63. package/src/adapters/cursor/protobuf-events.ts +829 -11
  64. package/src/adapters/cursor/protobuf-request.ts +383 -65
  65. package/src/adapters/cursor/request-builder.ts +237 -31
  66. package/src/adapters/cursor/tool-definitions.ts +242 -12
  67. package/src/adapters/cursor/tool-result-normalize.ts +92 -0
  68. package/src/adapters/cursor/transport.ts +22 -0
  69. package/src/adapters/cursor/types.ts +28 -1
  70. package/src/adapters/cursor.ts +133 -7
  71. package/src/adapters/google-antigravity-replay.ts +558 -34
  72. package/src/adapters/google-antigravity-wire.ts +43 -10
  73. package/src/adapters/google-http.ts +38 -10
  74. package/src/adapters/google-tool-schema.ts +80 -15
  75. package/src/adapters/google-truncation.ts +11 -0
  76. package/src/adapters/google.ts +618 -74
  77. package/src/adapters/identity.ts +39 -6
  78. package/src/adapters/kiro-errors.ts +11 -0
  79. package/src/adapters/kiro-events.ts +19 -1
  80. package/src/adapters/kiro-thinking.ts +18 -2
  81. package/src/adapters/kiro-tools.ts +10 -1
  82. package/src/adapters/kiro.ts +92 -54
  83. package/src/adapters/mimo-free.ts +17 -0
  84. package/src/adapters/openai-chat-url.ts +11 -0
  85. package/src/adapters/openai-chat.ts +1311 -336
  86. package/src/adapters/openai-responses-url.ts +16 -0
  87. package/src/adapters/openai-responses.ts +830 -56
  88. package/src/adapters/registry.ts +175 -0
  89. package/src/adapters/responses-tool-schema.ts +67 -0
  90. package/src/adapters/tool-call-id.ts +119 -0
  91. package/src/adapters/tool-catalog-nudge.ts +104 -21
  92. package/src/adapters/xai-web-search.ts +185 -0
  93. package/src/bridge.ts +428 -61
  94. package/src/chat/inbound.ts +43 -19
  95. package/src/chat/outbound.ts +82 -26
  96. package/src/claude/agents-inject.ts +32 -9
  97. package/src/claude/context-windows.ts +21 -5
  98. package/src/claude/desktop-3p.ts +243 -9
  99. package/src/claude/gateway-cache.ts +41 -4
  100. package/src/claude/inbound.ts +72 -3
  101. package/src/claude/model-info.ts +38 -15
  102. package/src/claude/outbound.ts +70 -16
  103. package/src/cli/account-api.ts +35 -1
  104. package/src/cli/account-auth.ts +33 -6
  105. package/src/cli/account-catalog-refresh.ts +14 -0
  106. package/src/cli/account-extended.ts +389 -2
  107. package/src/cli/account-main.ts +317 -0
  108. package/src/cli/account.ts +30 -6
  109. package/src/cli/agent.ts +45 -1
  110. package/src/cli/claude-agent-startup-sync.ts +73 -0
  111. package/src/cli/claude-desktop.ts +29 -4
  112. package/src/cli/claude.ts +103 -34
  113. package/src/cli/codex-log-guard-doctor.ts +103 -0
  114. package/src/cli/codex-shim-autorestore.ts +2 -0
  115. package/src/cli/codex-shim-readiness.ts +76 -0
  116. package/src/cli/combo.ts +8 -0
  117. package/src/cli/config-command.ts +74 -10
  118. package/src/cli/dispatch.ts +593 -0
  119. package/src/cli/doctor.ts +315 -43
  120. package/src/cli/ensure-desired-integrations.ts +152 -0
  121. package/src/cli/export-command.ts +46 -20
  122. package/src/cli/help.ts +20 -271
  123. package/src/cli/index.ts +378 -519
  124. package/src/cli/init.ts +4 -17
  125. package/src/cli/integrations.ts +120 -2
  126. package/src/cli/lab.ts +607 -0
  127. package/src/cli/launcher-context.ts +77 -0
  128. package/src/cli/minimax.ts +497 -0
  129. package/src/cli/models-runtime.ts +35 -2
  130. package/src/cli/models.ts +100 -14
  131. package/src/cli/observe.ts +92 -3
  132. package/src/cli/opencode.ts +4 -2
  133. package/src/cli/provider-runtime.ts +18 -1
  134. package/src/cli/provider.ts +24 -3
  135. package/src/cli/ready.ts +301 -0
  136. package/src/cli/registry.ts +437 -0
  137. package/src/cli/root.ts +86 -0
  138. package/src/cli/route-policy.ts +92 -0
  139. package/src/cli/runtime-api.ts +6 -3
  140. package/src/cli/star-prompt.ts +71 -15
  141. package/src/cli/status.ts +10 -3
  142. package/src/cli/system-restart-client.ts +146 -0
  143. package/src/cli/tray-proxy.ts +153 -6
  144. package/src/cli/v2.ts +105 -10
  145. package/src/cli.ts +1 -1
  146. package/src/clients/config-export.ts +1358 -21
  147. package/src/codex/account-label.ts +14 -1
  148. package/src/codex/account-lifecycle.ts +130 -13
  149. package/src/codex/account-namespaces.ts +49 -3
  150. package/src/codex/account-priority.ts +83 -0
  151. package/src/codex/account-store.ts +29 -2
  152. package/src/codex/account-usability.ts +25 -2
  153. package/src/codex/admission.ts +256 -0
  154. package/src/codex/affinity-debug.ts +162 -0
  155. package/src/codex/app-server-processes.ts +493 -106
  156. package/src/codex/app-server-restart-service.ts +232 -0
  157. package/src/codex/auth-api.ts +849 -242
  158. package/src/codex/auth-collision.ts +5 -3
  159. package/src/codex/auth-context.ts +345 -32
  160. package/src/codex/autostart-health.ts +8 -1
  161. package/src/codex/catalog/account-models.ts +67 -0
  162. package/src/codex/catalog/aggregation.ts +68 -10
  163. package/src/codex/catalog/bundled.ts +331 -33
  164. package/src/codex/catalog/effort.ts +121 -30
  165. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  166. package/src/codex/catalog/kinds.ts +2 -0
  167. package/src/codex/catalog/metadata.ts +529 -45
  168. package/src/codex/catalog/native-models.ts +72 -0
  169. package/src/codex/catalog/parsing.ts +224 -30
  170. package/src/codex/catalog/provider-fetch.ts +1460 -134
  171. package/src/codex/catalog/sync.ts +1449 -186
  172. package/src/codex/catalog-admission.ts +199 -0
  173. package/src/codex/catalog-refresh-status.ts +105 -0
  174. package/src/codex/catalog-write-serialization.ts +242 -0
  175. package/src/codex/catalog.ts +6 -3
  176. package/src/codex/codex-write-lock.ts +384 -0
  177. package/src/codex/convergence-types.ts +614 -0
  178. package/src/codex/convergence.ts +651 -0
  179. package/src/codex/coordinator-doctor.ts +332 -0
  180. package/src/codex/custom-model-catalog-migration.ts +176 -0
  181. package/src/codex/desired-state.ts +230 -0
  182. package/src/codex/features.ts +636 -39
  183. package/src/codex/generation.ts +202 -0
  184. package/src/codex/history-job.ts +407 -0
  185. package/src/codex/history-lock.ts +242 -0
  186. package/src/codex/history-migration-guardian.ts +26 -20
  187. package/src/codex/history-provider.ts +231 -28
  188. package/src/codex/history-transition.ts +105 -0
  189. package/src/codex/history-worker.ts +220 -0
  190. package/src/codex/inject-coordination.ts +290 -0
  191. package/src/codex/inject.ts +1073 -152
  192. package/src/codex/injected-marker.ts +37 -3
  193. package/src/codex/integration-record.ts +266 -0
  194. package/src/codex/internal/catalog-writer.ts +203 -0
  195. package/src/codex/internal/history-writer.ts +80 -0
  196. package/src/codex/journal.ts +66 -4
  197. package/src/codex/log-guard/inspect.ts +506 -0
  198. package/src/codex/log-guard/lock.ts +150 -0
  199. package/src/codex/log-guard/maintenance.ts +403 -0
  200. package/src/codex/log-guard/path-safety.ts +88 -0
  201. package/src/codex/log-guard/policy.ts +44 -0
  202. package/src/codex/log-guard/processes.ts +205 -0
  203. package/src/codex/log-guard/protection.ts +489 -0
  204. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  205. package/src/codex/main-account-cache.ts +24 -0
  206. package/src/codex/main-account.ts +29 -1
  207. package/src/codex/management-convergence.ts +167 -0
  208. package/src/codex/model-cache.ts +56 -10
  209. package/src/codex/model-entitlements.ts +353 -0
  210. package/src/codex/native-main-admission.ts +47 -0
  211. package/src/codex/native-main-auth-temp.ts +187 -0
  212. package/src/codex/native-main-claim.ts +178 -0
  213. package/src/codex/native-main-lock-file.ts +162 -0
  214. package/src/codex/native-main-owner.ts +329 -0
  215. package/src/codex/native-profile-api.ts +247 -0
  216. package/src/codex/native-profile-manager.ts +1531 -0
  217. package/src/codex/native-profile-processes.ts +121 -0
  218. package/src/codex/native-profile-recovery.ts +99 -0
  219. package/src/codex/native-profile-stage-store.ts +387 -0
  220. package/src/codex/native-profile-startup.ts +492 -0
  221. package/src/codex/native-profile-store.ts +855 -0
  222. package/src/codex/native-profile-types.ts +120 -0
  223. package/src/codex/native-residue.ts +682 -0
  224. package/src/codex/paths.ts +80 -1
  225. package/src/codex/plan-from-token.ts +140 -0
  226. package/src/codex/plan.ts +40 -0
  227. package/src/codex/plugins-doctor.ts +1 -1
  228. package/src/codex/pool-rotation.ts +74 -4
  229. package/src/codex/project-config-warnings.ts +20 -6
  230. package/src/codex/prompt-journal.ts +352 -0
  231. package/src/codex/prompt-layers.ts +967 -0
  232. package/src/codex/prompt-lock.ts +143 -0
  233. package/src/codex/quota-rejection.ts +298 -0
  234. package/src/codex/quota.ts +175 -13
  235. package/src/codex/refresh.ts +11 -2
  236. package/src/codex/reset-credit-recovery.ts +1044 -0
  237. package/src/codex/routing.ts +505 -94
  238. package/src/codex/runtime.ts +159 -38
  239. package/src/codex/shim.ts +1009 -28
  240. package/src/codex/subagent-model-fallback.ts +350 -35
  241. package/src/codex/sync.ts +191 -2
  242. package/src/codex/transition-state.ts +612 -0
  243. package/src/codex/upstream-host-health.ts +368 -0
  244. package/src/codex/user-identity.ts +557 -0
  245. package/src/codex/warmup.ts +187 -81
  246. package/src/codex/write-coordination.ts +114 -0
  247. package/src/combos/failover.ts +20 -0
  248. package/src/combos/index.ts +4 -0
  249. package/src/combos/request.ts +32 -0
  250. package/src/combos/types.ts +81 -9
  251. package/src/config/provider-name.ts +24 -0
  252. package/src/config.ts +1762 -140
  253. package/src/generated/compatibility-version.json +3164 -0
  254. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  255. package/src/grok/inject.ts +16 -5
  256. package/src/grok/inspect.ts +45 -0
  257. package/src/grok/sync.ts +2 -2
  258. package/src/images/loop.ts +152 -29
  259. package/src/images/plan.ts +23 -13
  260. package/src/integrations/config-io.ts +269 -0
  261. package/src/integrations/journal.ts +315 -0
  262. package/src/integrations/merge.ts +135 -0
  263. package/src/integrations/mutation-flight.ts +71 -0
  264. package/src/integrations/native/ownership-preflight.ts +202 -0
  265. package/src/integrations/omp-yaml-source.ts +358 -0
  266. package/src/integrations/owned-refresh.ts +74 -0
  267. package/src/integrations/ownership.ts +111 -0
  268. package/src/integrations/registry.ts +159 -0
  269. package/src/integrations/serialize.ts +314 -0
  270. package/src/integrations/state.ts +361 -0
  271. package/src/integrations/store.ts +103 -0
  272. package/src/integrations/writer-lock.ts +98 -0
  273. package/src/integrations/writer.ts +691 -0
  274. package/src/lab/artifacts/sanitize.ts +586 -0
  275. package/src/lab/artifacts/secure-fs.ts +475 -0
  276. package/src/lab/artifacts/store.ts +310 -0
  277. package/src/lab/automation/budgets.ts +78 -0
  278. package/src/lab/automation/config-persistence.ts +256 -0
  279. package/src/lab/automation/constants.ts +39 -0
  280. package/src/lab/automation/cooldown.ts +103 -0
  281. package/src/lab/automation/dispatch.ts +211 -0
  282. package/src/lab/automation/index.ts +13 -0
  283. package/src/lab/automation/orchestrator.ts +499 -0
  284. package/src/lab/automation/persistence.ts +512 -0
  285. package/src/lab/automation/planner.ts +371 -0
  286. package/src/lab/automation/policy.ts +136 -0
  287. package/src/lab/automation/queue.ts +191 -0
  288. package/src/lab/automation/recovery.ts +24 -0
  289. package/src/lab/automation/route-context.ts +21 -0
  290. package/src/lab/automation/run-key.ts +44 -0
  291. package/src/lab/automation/runs-query.ts +34 -0
  292. package/src/lab/automation/types.ts +160 -0
  293. package/src/lab/conformance/assertion.ts +325 -0
  294. package/src/lab/conformance/digest.ts +22 -0
  295. package/src/lab/conformance/executor.ts +741 -0
  296. package/src/lab/conformance/fixture-provider.ts +27 -0
  297. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  298. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  299. package/src/lab/conformance/harness-budget.ts +47 -0
  300. package/src/lab/conformance/index.ts +5 -0
  301. package/src/lab/conformance/jcs.ts +64 -0
  302. package/src/lab/conformance/json-pointer.ts +39 -0
  303. package/src/lab/conformance/manifest.ts +180 -0
  304. package/src/lab/conformance/mcp-stub.ts +179 -0
  305. package/src/lab/conformance/negative-controls.ts +164 -0
  306. package/src/lab/conformance/observation.ts +355 -0
  307. package/src/lab/conformance/runner.ts +68 -0
  308. package/src/lab/conformance/sse-normalize.ts +59 -0
  309. package/src/lab/conformance/suite-manifest.ts +78 -0
  310. package/src/lab/conformance/types.ts +214 -0
  311. package/src/lab/constants.ts +126 -0
  312. package/src/lab/digest.ts +64 -0
  313. package/src/lab/events/errors.ts +9 -0
  314. package/src/lab/events/limits.ts +117 -0
  315. package/src/lab/events/types.ts +229 -0
  316. package/src/lab/events/validate.ts +781 -0
  317. package/src/lab/fabric/constants.ts +40 -0
  318. package/src/lab/fabric/executor.ts +492 -0
  319. package/src/lab/fabric/index.ts +80 -0
  320. package/src/lab/fabric/manifest.ts +222 -0
  321. package/src/lab/fabric/observe.ts +489 -0
  322. package/src/lab/fabric/patch.ts +79 -0
  323. package/src/lab/fabric/producer-child.ts +139 -0
  324. package/src/lab/fabric/producer-isolate.ts +276 -0
  325. package/src/lab/fabric/producer-protocol.ts +61 -0
  326. package/src/lab/fabric/scratch.ts +439 -0
  327. package/src/lab/fabric/subject.ts +106 -0
  328. package/src/lab/fabric/types.ts +134 -0
  329. package/src/lab/fabric/verifier.ts +98 -0
  330. package/src/lab/index.ts +54 -0
  331. package/src/lab/ledger/artifact-refs.ts +127 -0
  332. package/src/lab/ledger/invalidation.ts +136 -0
  333. package/src/lab/ledger/purge.ts +310 -0
  334. package/src/lab/ledger/store.ts +532 -0
  335. package/src/lab/live/credential-lease.ts +53 -0
  336. package/src/lab/live/destination.ts +155 -0
  337. package/src/lab/live/executor.ts +336 -0
  338. package/src/lab/live/inert-tools.ts +56 -0
  339. package/src/lab/live/manifest.ts +85 -0
  340. package/src/lab/live/mcp-loopback.ts +57 -0
  341. package/src/lab/live/runner.ts +19 -0
  342. package/src/lab/live/sandbox.ts +61 -0
  343. package/src/lab/live/suite-manifest.ts +41 -0
  344. package/src/lab/live/transport.ts +118 -0
  345. package/src/lab/live/types.ts +197 -0
  346. package/src/lab/observe/from-conformance.ts +301 -0
  347. package/src/lab/observe/from-live.ts +117 -0
  348. package/src/lab/paths.ts +153 -0
  349. package/src/lab/projection/rebuild.ts +495 -0
  350. package/src/lab/projection/schema.ts +135 -0
  351. package/src/lab/projection/verdicts.ts +474 -0
  352. package/src/lab/projection/verification.ts +412 -0
  353. package/src/lab/public/bundle.ts +217 -0
  354. package/src/lab/public/community-authority.ts +175 -0
  355. package/src/lab/public/community-files.ts +29 -0
  356. package/src/lab/public/community.ts +479 -0
  357. package/src/lab/public/file-safety.ts +155 -0
  358. package/src/lab/public/ids.ts +26 -0
  359. package/src/lab/public/index.ts +16 -0
  360. package/src/lab/public/mutation-lock.ts +424 -0
  361. package/src/lab/public/operator.ts +353 -0
  362. package/src/lab/public/origin-purge.ts +79 -0
  363. package/src/lab/public/origin.ts +203 -0
  364. package/src/lab/public/privacy.ts +143 -0
  365. package/src/lab/public/private-file.ts +261 -0
  366. package/src/lab/public/project.ts +124 -0
  367. package/src/lab/public/purge-test-fault.ts +21 -0
  368. package/src/lab/public/purge.ts +223 -0
  369. package/src/lab/public/registry.ts +44 -0
  370. package/src/lab/public/revocation.ts +252 -0
  371. package/src/lab/public/signature.ts +243 -0
  372. package/src/lab/public/storage.ts +105 -0
  373. package/src/lab/public/strict-json.ts +206 -0
  374. package/src/lab/public/time.ts +26 -0
  375. package/src/lab/public/types.ts +172 -0
  376. package/src/lab/public/validate.ts +391 -0
  377. package/src/lab/query/catalog.ts +101 -0
  378. package/src/lab/query/connection.ts +107 -0
  379. package/src/lab/query/constants.ts +4 -0
  380. package/src/lab/query/cursor.ts +132 -0
  381. package/src/lab/query/dto-map.ts +277 -0
  382. package/src/lab/query/errors.ts +22 -0
  383. package/src/lab/query/freshness.ts +53 -0
  384. package/src/lab/query/index.ts +45 -0
  385. package/src/lab/query/latest-observation.ts +59 -0
  386. package/src/lab/query/passive-production.ts +159 -0
  387. package/src/lab/query/queries.ts +444 -0
  388. package/src/lab/query/types.ts +266 -0
  389. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  390. package/src/lab/subject/installation-salt.ts +112 -0
  391. package/src/lab/subject/protocol-subject.ts +80 -0
  392. package/src/lab/subject/route-subject.ts +74 -0
  393. package/src/lib/app-owned-memory-stores.ts +22 -0
  394. package/src/lib/bounded-body.ts +153 -9
  395. package/src/lib/bun-runtime.ts +125 -12
  396. package/src/lib/bun-stream-caps.ts +13 -9
  397. package/src/lib/codex-restart-contract.ts +120 -0
  398. package/src/lib/config-ownership.ts +6 -2
  399. package/src/lib/destination-policy.ts +65 -1
  400. package/src/lib/errors.ts +44 -2
  401. package/src/lib/fabric-task-execution-authority.ts +7 -0
  402. package/src/lib/fabric-task-host.ts +29 -0
  403. package/src/lib/lab-activation.ts +223 -0
  404. package/src/lib/lab-live-execution-authority.ts +13 -0
  405. package/src/lib/lab-live-host.ts +30 -0
  406. package/src/lib/lab-live-pinned-sender.ts +56 -0
  407. package/src/lib/lab-live-route-production.ts +130 -0
  408. package/src/lib/lab-passive-linker-registration.ts +26 -0
  409. package/src/lib/local-management-attestation.ts +51 -0
  410. package/src/lib/local-management-capability.ts +100 -0
  411. package/src/lib/local-provider-reload-contract.ts +100 -0
  412. package/src/lib/optional-shutdown-hooks.ts +57 -0
  413. package/src/lib/pinned-http.ts +145 -26
  414. package/src/lib/process-control.ts +4 -1
  415. package/src/lib/provider-outbound.ts +49 -9
  416. package/src/lib/redact.ts +419 -3
  417. package/src/lib/self-launch-argv.ts +15 -0
  418. package/src/lib/server-resource-ownership.ts +71 -0
  419. package/src/lib/shadow-call.ts +35 -4
  420. package/src/lib/sse-decoder.ts +41 -0
  421. package/src/lib/state-store-registrations.ts +10 -2
  422. package/src/lib/system-restart-contract.ts +73 -0
  423. package/src/lib/token-estimate.ts +19 -2
  424. package/src/lib/tool-argument-integers.ts +202 -0
  425. package/src/lib/translator-budget.ts +44 -0
  426. package/src/lib/upstream-http-version.ts +57 -0
  427. package/src/lib/upstream-reachability.ts +95 -0
  428. package/src/lib/upstream-retry.ts +156 -3
  429. package/src/lib/windows-atomic-replace.ts +155 -0
  430. package/src/lib/windows-elevation.ts +70 -2
  431. package/src/lib/windows-secret-acl.ts +409 -69
  432. package/src/lib/windows-service-wrappers.ts +72 -0
  433. package/src/lib/windows-text.ts +106 -0
  434. package/src/lib/windows-user-principal.ts +341 -0
  435. package/src/lib/winsw.ts +33 -5
  436. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  437. package/src/oauth/account-import/index.ts +15 -0
  438. package/src/oauth/account-import/parser.ts +83 -0
  439. package/src/oauth/account-import/registry.ts +18 -0
  440. package/src/oauth/account-import/service.ts +75 -0
  441. package/src/oauth/account-import/types.ts +91 -0
  442. package/src/oauth/anthropic.ts +12 -1
  443. package/src/oauth/callback-server.ts +8 -2
  444. package/src/oauth/chatgpt.ts +12 -1
  445. package/src/oauth/command-code.ts +239 -0
  446. package/src/oauth/cursor.ts +46 -5
  447. package/src/oauth/google-antigravity.ts +35 -3
  448. package/src/oauth/health.ts +20 -12
  449. package/src/oauth/index.ts +398 -66
  450. package/src/oauth/key-providers.ts +16 -0
  451. package/src/oauth/kimi.ts +16 -2
  452. package/src/oauth/kiro.ts +50 -6
  453. package/src/oauth/local-token-detect.ts +11 -2
  454. package/src/oauth/log.ts +3 -1
  455. package/src/oauth/login-cli.ts +88 -28
  456. package/src/oauth/nous.ts +798 -0
  457. package/src/oauth/store.ts +119 -21
  458. package/src/oauth/token-guardian.ts +9 -3
  459. package/src/pi/models.ts +2 -2
  460. package/src/providers/alibaba-region-migration.ts +1 -1
  461. package/src/providers/antigravity-models.ts +521 -31
  462. package/src/providers/base-url-choices.ts +10 -0
  463. package/src/providers/codex-capacity.ts +292 -0
  464. package/src/providers/command-code-efforts.ts +144 -0
  465. package/src/providers/context-cap.ts +22 -5
  466. package/src/providers/cursor-pool.ts +72 -0
  467. package/src/providers/derive.ts +253 -6
  468. package/src/providers/fastwire.ts +501 -0
  469. package/src/providers/free-directory.ts +10 -7
  470. package/src/providers/google-vertex-location.ts +14 -0
  471. package/src/providers/key-failover.ts +71 -3
  472. package/src/providers/label.ts +1 -1
  473. package/src/providers/model-discovery-limits.ts +16 -0
  474. package/src/providers/model-discovery.ts +115 -22
  475. package/src/providers/model-rename-migration.ts +255 -0
  476. package/src/providers/model-rename-startup.ts +28 -0
  477. package/src/providers/openai-sidecar.ts +72 -4
  478. package/src/providers/openai-tier-startup.ts +31 -2
  479. package/src/providers/openai-tiers.ts +119 -4
  480. package/src/providers/openai-virtual-models.ts +1 -0
  481. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  482. package/src/providers/provider-id-rewrite.ts +29 -0
  483. package/src/providers/quota.ts +1319 -38
  484. package/src/providers/registry.ts +1429 -111
  485. package/src/providers/request-pacing.ts +310 -0
  486. package/src/providers/service-tier.ts +277 -0
  487. package/src/providers/slug-codec.ts +42 -6
  488. package/src/providers/static-model-discovery.ts +86 -0
  489. package/src/providers/xai-responses-opt-in.ts +15 -0
  490. package/src/providers/xai-transport.ts +11 -4
  491. package/src/reasoning-effort.ts +49 -1
  492. package/src/responses/compaction.ts +26 -1
  493. package/src/responses/custom-tool-compat.ts +266 -0
  494. package/src/responses/hosted-tool-policy.ts +9 -0
  495. package/src/responses/namespace-tool-compat.ts +355 -0
  496. package/src/responses/parser.ts +220 -38
  497. package/src/responses/provider-continuation.ts +98 -0
  498. package/src/responses/provider-opaque-metadata.ts +73 -0
  499. package/src/responses/reasoning-envelope.ts +9 -1
  500. package/src/responses/reasoning-replay-cache.ts +426 -0
  501. package/src/responses/schema.ts +7 -1
  502. package/src/responses/spill-store.ts +75 -10
  503. package/src/responses/state.ts +565 -27
  504. package/src/responses/thought-signature-replay.ts +347 -0
  505. package/src/responses/tool-search-compat.ts +301 -0
  506. package/src/responses/truncated-stop-reason.ts +60 -0
  507. package/src/router.ts +366 -30
  508. package/src/routing/analytics.ts +378 -0
  509. package/src/routing/capability.ts +244 -0
  510. package/src/routing/compatibility/assemble.ts +73 -0
  511. package/src/routing/compatibility/behavior.ts +278 -0
  512. package/src/routing/compatibility/catalog.ts +99 -0
  513. package/src/routing/compatibility/endpoint.ts +52 -0
  514. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  515. package/src/routing/compatibility/policy.ts +181 -0
  516. package/src/routing/compatibility/provider-slot.ts +56 -0
  517. package/src/routing/compatibility/reader.ts +110 -0
  518. package/src/routing/compatibility/subject.ts +191 -0
  519. package/src/routing/compatibility/types.ts +64 -0
  520. package/src/routing/compatibility/version.ts +104 -0
  521. package/src/routing/cost.ts +77 -0
  522. package/src/routing/evaluator.ts +495 -0
  523. package/src/routing/health.ts +412 -0
  524. package/src/routing/history/cursor.ts +43 -0
  525. package/src/routing/history/indexer.ts +605 -0
  526. package/src/routing/history/schema.ts +72 -0
  527. package/src/routing/profile-namespace.ts +15 -0
  528. package/src/routing/profile.ts +547 -0
  529. package/src/routing/quota.ts +145 -0
  530. package/src/routing/request-evidence.ts +45 -0
  531. package/src/routing/trace.ts +776 -0
  532. package/src/server/adapter-resolve.ts +2 -29
  533. package/src/server/auth-cors.ts +267 -46
  534. package/src/server/background-lifecycle.ts +182 -0
  535. package/src/server/chat-completions.ts +130 -56
  536. package/src/server/chat-native-sse.ts +331 -0
  537. package/src/server/chat-native.ts +426 -0
  538. package/src/server/claude-messages.ts +159 -43
  539. package/src/server/direct-local-http.ts +347 -0
  540. package/src/server/effort-policy.ts +18 -0
  541. package/src/server/github-copilot-responses-repair.ts +338 -0
  542. package/src/server/gui-static.ts +39 -10
  543. package/src/server/images.ts +94 -12
  544. package/src/server/index.ts +865 -181
  545. package/src/server/lifecycle.ts +284 -13
  546. package/src/server/live.ts +136 -17
  547. package/src/server/local-management-read-client.ts +90 -0
  548. package/src/server/local-provider-reload-client.ts +137 -0
  549. package/src/server/management/agent-settings-routes.ts +398 -103
  550. package/src/server/management/api-key-usage.ts +31 -5
  551. package/src/server/management/body.ts +6 -0
  552. package/src/server/management/combo-routes.ts +62 -24
  553. package/src/server/management/config-routes.ts +464 -51
  554. package/src/server/management/context.ts +80 -2
  555. package/src/server/management/integration-routes.ts +498 -0
  556. package/src/server/management/lab-automation-routes.ts +206 -0
  557. package/src/server/management/lab-routes.ts +563 -0
  558. package/src/server/management/logs-usage-routes.ts +101 -32
  559. package/src/server/management/model-routes.ts +189 -131
  560. package/src/server/management/model-rows.ts +163 -0
  561. package/src/server/management/native-integration-routes.ts +769 -0
  562. package/src/server/management/oauth-account-routes.ts +80 -4
  563. package/src/server/management/provider-capability-config.ts +48 -0
  564. package/src/server/management/provider-routes.ts +764 -157
  565. package/src/server/management/request-history-routes.ts +191 -0
  566. package/src/server/management/routing-analytics-routes.ts +74 -0
  567. package/src/server/management/routing-profile-routes.ts +380 -0
  568. package/src/server/management/shared.ts +27 -11
  569. package/src/server/management/sidebar-routes.ts +47 -31
  570. package/src/server/management/storage-log-guard-routes.ts +186 -0
  571. package/src/server/management/sync-response.ts +69 -0
  572. package/src/server/management/system-restart.ts +288 -32
  573. package/src/server/management/system-routes.ts +77 -0
  574. package/src/server/management/usage-summary-cache.ts +9 -1
  575. package/src/server/management/vision-sidecar-options.ts +167 -0
  576. package/src/server/management/web-search-sidecar-options.ts +120 -0
  577. package/src/server/management-api.ts +115 -14
  578. package/src/server/management-auth.ts +220 -5
  579. package/src/server/passive-route-linker.ts +66 -0
  580. package/src/server/ports.ts +41 -1
  581. package/src/server/proxy-liveness.ts +132 -5
  582. package/src/server/readiness.ts +99 -0
  583. package/src/server/relay-eager.ts +82 -42
  584. package/src/server/relay.ts +236 -76
  585. package/src/server/request-decompress.ts +113 -6
  586. package/src/server/request-log.ts +235 -22
  587. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  588. package/src/server/responses/agent-task-recovery.ts +465 -0
  589. package/src/server/responses/collaboration.ts +204 -35
  590. package/src/server/responses/compact.ts +442 -55
  591. package/src/server/responses/core.ts +2872 -331
  592. package/src/server/responses/empty-completion-guard.ts +276 -0
  593. package/src/server/responses/encrypted-payload.ts +62 -39
  594. package/src/server/responses/fetch-helpers.ts +79 -4
  595. package/src/server/responses/input-admission.ts +185 -0
  596. package/src/server/responses/pacing-overload.ts +13 -0
  597. package/src/server/responses/policy-fallback.ts +178 -0
  598. package/src/server/responses/responses-field-backfill.ts +251 -0
  599. package/src/server/responses/terminal-guard.ts +26 -5
  600. package/src/server/responses/upstream-error.ts +5 -0
  601. package/src/server/responses/ws-upstream.ts +308 -0
  602. package/src/server/responses-custom-tool-repair.ts +282 -0
  603. package/src/server/responses-item-id-repair.ts +54 -6
  604. package/src/server/responses-json-events.ts +90 -0
  605. package/src/server/responses-model-rewrite.ts +29 -0
  606. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  607. package/src/server/responses-snapshot-repair.ts +621 -0
  608. package/src/server/responses-terminal-repair.ts +342 -0
  609. package/src/server/responses-tool-search-repair.ts +267 -0
  610. package/src/server/responses-undeclared-tool-guard.ts +153 -0
  611. package/src/server/responses.ts +18 -2
  612. package/src/server/search.ts +78 -13
  613. package/src/server/sse-frame-buffer.ts +292 -0
  614. package/src/server/sse-payload-rewrite.ts +110 -22
  615. package/src/server/startup-action-control.ts +8 -1
  616. package/src/server/startup-health-cache.ts +19 -1
  617. package/src/server/system-env.ts +80 -9
  618. package/src/server/ws-bridge.ts +39 -38
  619. package/src/service-manager-probe.ts +892 -0
  620. package/src/service.ts +1111 -90
  621. package/src/sidecar/auth.ts +92 -0
  622. package/src/sidecar/candidates.ts +83 -0
  623. package/src/storage/cleanup.ts +2 -2
  624. package/src/storage/scanner.ts +1 -1
  625. package/src/storage/worker-lifecycle.ts +14 -14
  626. package/src/tray/windows-tray.ps1 +83 -9
  627. package/src/tray/windows.ts +43 -16
  628. package/src/types/accounts.ts +37 -0
  629. package/src/types/config.ts +845 -0
  630. package/src/types/provider.ts +545 -0
  631. package/src/types/request.ts +384 -0
  632. package/src/types/tools.ts +131 -0
  633. package/src/types/wire.ts +80 -0
  634. package/src/types.ts +104 -1236
  635. package/src/update/index.ts +32 -19
  636. package/src/update/job.ts +442 -67
  637. package/src/update/notify.ts +12 -6
  638. package/src/update/npm-cache-preflight.d.mts +47 -0
  639. package/src/update/npm-cache-preflight.mjs +201 -0
  640. package/src/update/transactional-install.d.mts +22 -0
  641. package/src/update/transactional-install.mjs +259 -0
  642. package/src/usage/cost.ts +0 -0
  643. package/src/usage/expected-prices.ts +268 -16
  644. package/src/usage/log.ts +606 -41
  645. package/src/usage/summary.ts +177 -9
  646. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  647. package/src/usage/user-cost-overlays.ts +314 -0
  648. package/src/vision/anthropic-describe.ts +10 -6
  649. package/src/vision/backends.ts +97 -0
  650. package/src/vision/describe.ts +9 -3
  651. package/src/vision/eligibility.ts +250 -0
  652. package/src/vision/index.ts +238 -24
  653. package/src/vision/reasoning.ts +55 -0
  654. package/src/vision/routed-describe.ts +175 -0
  655. package/src/vision/timeout-bounds.ts +9 -0
  656. package/src/web-search/anthropic-executor.ts +13 -7
  657. package/src/web-search/backends.ts +108 -0
  658. package/src/web-search/exa-executor.ts +88 -0
  659. package/src/web-search/executor.ts +11 -3
  660. package/src/web-search/gemini-executor.ts +141 -0
  661. package/src/web-search/index.ts +150 -15
  662. package/src/web-search/loop.ts +279 -50
  663. package/src/web-search/parse.ts +125 -30
  664. package/src/web-search/sources.ts +60 -0
  665. package/src/web-search/xai-executor.ts +219 -0
  666. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  667. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  668. package/gui/dist/provider-icons/antigravity.svg +0 -1
  669. package/gui/dist/provider-icons/claude.svg +0 -1
  670. package/gui/dist/provider-icons/copilot.svg +0 -1
  671. package/gui/dist/provider-icons/cursor.svg +0 -2
  672. package/gui/dist/provider-icons/gemini.svg +0 -1
  673. package/gui/dist/provider-icons/grok-color.svg +0 -1
  674. package/gui/dist/provider-icons/kiro.svg +0 -14
  675. package/src/cli/internal-dispatch.ts +0 -20
@@ -117,7 +117,7 @@ export function analyzeTerminalTurn(parsed: OcxParsedRequest, events: readonly A
117
117
  return { decision: "continue", reason: "suspicious_no_tool", assistantText: text, userText, hasToolCall };
118
118
  }
119
119
 
120
- function assistantMessageFromEvents(events: readonly AdapterEvent[]): OcxAssistantMessage | undefined {
120
+ function assistantMessageFromEvents(events: readonly AdapterEvent[], timestamp: number): OcxAssistantMessage | undefined {
121
121
  let text = "";
122
122
  let thinking = "";
123
123
  let signature: string | undefined;
@@ -134,14 +134,18 @@ function assistantMessageFromEvents(events: readonly AdapterEvent[]): OcxAssista
134
134
  }
135
135
  if (text) content.push({ type: "text", text });
136
136
  if (content.length === 0) return undefined;
137
- return { role: "assistant", content, timestamp: Date.now() };
137
+ return { role: "assistant", content, timestamp };
138
138
  }
139
139
 
140
140
  export function buildContinuationRequest(parsed: OcxParsedRequest, events: readonly AdapterEvent[]): OcxParsedRequest {
141
141
  const messages = [...parsed.context.messages];
142
- const assistant = assistantMessageFromEvents(events);
142
+ // One clock read for the whole rebuild. Two Date.now() calls could straddle a millisecond
143
+ // boundary, which made the retained-heartbeat contract test fail intermittently in CI on a
144
+ // 1ms skew between the assistant message and the nudge that follows it.
145
+ const timestamp = Date.now();
146
+ const assistant = assistantMessageFromEvents(events, timestamp);
143
147
  if (assistant) messages.push(assistant);
144
- messages.push({ role: "developer", content: TERMINAL_GUARD_NUDGE, timestamp: Date.now() });
148
+ messages.push({ role: "developer", content: TERMINAL_GUARD_NUDGE, timestamp });
145
149
  return { ...parsed, context: { ...parsed.context, messages } };
146
150
  }
147
151
 
@@ -153,6 +157,16 @@ export interface GuardedEventStreamOptions {
153
157
  maxAutoContinuations?: number;
154
158
  }
155
159
 
160
+ /**
161
+ * Events that are useful to the downstream stream consumer but carry no state used by the
162
+ * terminal-continuation decision or its rebuilt request. Keep them out of the retained event
163
+ * list so adapter liveness markers and arbitrarily large tool-argument fragments cannot make
164
+ * the guard's per-turn memory grow without adding any continuation semantics.
165
+ */
166
+ export function isTerminalGuardPassthroughOnly(event: AdapterEvent): boolean {
167
+ return event.type === "heartbeat" || event.type === "tool_call_delta";
168
+ }
169
+
156
170
  function mergeUsage(first: OcxUsage | undefined, second: OcxUsage | undefined): OcxUsage | undefined {
157
171
  if (!first) return second;
158
172
  if (!second) return first;
@@ -193,9 +207,16 @@ export async function* guardTerminalEventStream(options: GuardedEventStreamOptio
193
207
  const seen: AdapterEvent[] = [];
194
208
  let terminalSeen = false;
195
209
  for await (const event of source) {
210
+ // Liveness markers and tool argument fragments are passed through to the bridge, but
211
+ // neither analyzeTerminalTurn nor buildContinuationRequest consumes them. Retaining the
212
+ // fragments would duplicate arbitrarily large argument payloads in `seen` for no effect.
213
+ if (isTerminalGuardPassthroughOnly(event)) {
214
+ yield event;
215
+ continue;
216
+ }
196
217
  if (event.type === "done") {
197
218
  terminalSeen = true;
198
- const analysis = options.adapterName === "anthropic"
219
+ const analysis = (options.adapterName === "anthropic" || options.adapterName === "openai-chat")
199
220
  ? analyzeTerminalTurn(parsed, seen)
200
221
  : { decision: "pass" as const };
201
222
  const normalStop = event.stopReason !== "max_tokens" && event.stopReason !== "content_filter";
@@ -5,7 +5,12 @@
5
5
  * #553 looking for an adapter URL bug that does not exist. Name the likely cause and the
6
6
  * command that settles it.
7
7
  */
8
+ import { RequestPacingQueueOverloadError } from "../../providers/request-pacing";
9
+
8
10
  export function describeUpstreamConnectFailure(err: unknown, connectMs: number): string {
11
+ // Local pacing admission is not a transport failure. Let the outer response boundary
12
+ // preserve its retryable 429 identity instead of laundering it into a 502.
13
+ if (err instanceof RequestPacingQueueOverloadError) throw err;
9
14
  if (err instanceof Error && err.name === "TimeoutError") {
10
15
  return `Provider connect timeout after ${connectMs}ms`;
11
16
  }
@@ -0,0 +1,308 @@
1
+ // Upstream WebSocket transport for the ChatGPT Codex backend.
2
+ //
3
+ // Why this exists: the Codex backend serves the responses_websockets path from
4
+ // a measurably faster queue than the plain SSE POST path. Measured 2026-08-12
5
+ // KST (same account, same payload, strictly sequential): gpt-5.6-luna TTFT p50
6
+ // ~1.0s over WS vs ~3.9s over SSE. Codex CLI itself defaults to the WS
7
+ // transport; opencodex previously always POSTed SSE, which is where its extra
8
+ // 2-3s of TTFT came from.
9
+ //
10
+ // The wrapper only swaps the transport. It dials wss:// with the same headers,
11
+ // sends the JSON body as a single `response.create` frame, and re-encodes the
12
+ // returned event frames as an SSE byte stream, so every downstream consumer
13
+ // (passthrough relay, adapter parsers, usage sniffing) is unchanged.
14
+
15
+ import { MAX_CLIENT_SSE_FRAME_BYTES } from "../sse-frame-buffer";
16
+ import { compareBunVersions } from "../../lib/bun-stream-caps";
17
+
18
+ const CODEX_RESPONSES_HTTP_URL = "https://chatgpt.com/backend-api/codex/responses";
19
+ const CODEX_RESPONSES_WS_URL = "wss://chatgpt.com/backend-api/codex/responses";
20
+ const WS_BETA = "responses_websockets=2026-02-06";
21
+ // If the 101 never arrives (network black hole), give SSE a chance well before
22
+ // the caller's connect timeout (default 200s) would fire.
23
+ const UPGRADE_DEADLINE_MS = 10_000;
24
+ // Keep the push-based WS transport inside the same memory envelope as the
25
+ // bounded SSE relays that consume this response. Unlike fetch response bodies,
26
+ // a WebSocket cannot be paused when a ReadableStream applies backpressure, so
27
+ // an upstream that outruns the consumer must be disconnected.
28
+ export const MAX_CODEX_WS_FRAME_BYTES = MAX_CLIENT_SSE_FRAME_BYTES;
29
+ export const MAX_CODEX_WS_QUEUE_BYTES = 8 * 1024 * 1024;
30
+ export const MIN_BOUNDED_CODEX_WS_BUN_VERSION = "1.4.0";
31
+
32
+ export type BunRuntimeIdentity = {
33
+ version: string;
34
+ versionWithSha: string;
35
+ };
36
+
37
+ export type BunRuntimeGateInput = string | BunRuntimeIdentity;
38
+
39
+ const codexWsUpstreamResponses = new WeakSet<Response>();
40
+
41
+ /** True only for a successful Codex WebSocket upgrade, never an HTTP fallback. */
42
+ export function isCodexWsUpstreamResponse(response: Response): boolean {
43
+ return codexWsUpstreamResponses.has(response);
44
+ }
45
+
46
+ export function currentBunRuntimeIdentity(): BunRuntimeIdentity {
47
+ return {
48
+ version: Bun.version,
49
+ versionWithSha: Bun.version_with_sha,
50
+ };
51
+ }
52
+
53
+ function boundedRelayVersion(input: BunRuntimeGateInput): string | null {
54
+ if (typeof input === "string") return input.trim() || null;
55
+ const numericVersion = input.version.trim();
56
+ const numericMatch = /^(\d+\.\d+\.\d+)$/.exec(numericVersion);
57
+ const detailedMatch = /^v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\s+\([0-9a-fA-F]+\)$/.exec(
58
+ input.versionWithSha.trim(),
59
+ );
60
+ if (!numericMatch || !detailedMatch) return null;
61
+ const detailedNumeric = /^(\d+\.\d+\.\d+)/.exec(detailedMatch[1])?.[1];
62
+ return detailedNumeric === numericMatch[1] ? detailedMatch[1] : null;
63
+ }
64
+
65
+ /**
66
+ * Bun 1.3.14 does not propagate a stalled HTTP response socket back to a JS
67
+ * ReadableStream producer on Windows. A real raw-TCP slow-client probe drained
68
+ * the entire upstream despite the eager relay queue; Bun 1.4.0-canary.1 stopped
69
+ * below one MiB. Prereleases still fail closed; release builds before 1.4.0
70
+ * fall back to HTTP SSE.
71
+ */
72
+ export function bunSupportsBoundedCodexWsRelay(
73
+ runtime: BunRuntimeGateInput = currentBunRuntimeIdentity(),
74
+ ): boolean {
75
+ const version = boundedRelayVersion(runtime);
76
+ if (!version) return false;
77
+ if (/^\d+\.\d+\.\d+-/.test(version.trim())) return false;
78
+ const comparison = compareBunVersions(version, MIN_BOUNDED_CODEX_WS_BUN_VERSION);
79
+ return comparison !== null && comparison >= 0;
80
+ }
81
+
82
+ export function shouldUseCodexWsUpstream(
83
+ url: string,
84
+ init?: RequestInit,
85
+ runtime: BunRuntimeGateInput = currentBunRuntimeIdentity(),
86
+ ): boolean {
87
+ if (!bunSupportsBoundedCodexWsRelay(runtime)) return false;
88
+ if (url !== CODEX_RESPONSES_HTTP_URL) return false;
89
+ if ((init?.method ?? "GET").toUpperCase() !== "POST") return false;
90
+ const body = init?.body;
91
+ if (typeof body !== "string") return false;
92
+ // Only root-level stream:true selects WS: JSON-mode calls keep the HTTP path
93
+ // because the WS path only speaks the event protocol, and a nested
94
+ // {"metadata":{"stream":true}} must not flip the transport. Parsing (not
95
+ // substring matching) also keeps whitespace-formatted bodies routable.
96
+ try {
97
+ const parsed = JSON.parse(body) as unknown;
98
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
99
+ && (parsed as Record<string, unknown>).stream === true;
100
+ } catch {
101
+ return false;
102
+ }
103
+ }
104
+
105
+ export function codexWsUpstreamFetch(
106
+ url: string,
107
+ init: RequestInit,
108
+ sseFallback: typeof globalThis.fetch,
109
+ runtime: BunRuntimeGateInput = currentBunRuntimeIdentity(),
110
+ ): Promise<Response> {
111
+ if (!bunSupportsBoundedCodexWsRelay(runtime)) {
112
+ return sseFallback(url, init);
113
+ }
114
+ const signal = init.signal ?? undefined;
115
+ if (signal?.aborted) {
116
+ return Promise.reject(signal.reason ?? new DOMException("The operation was aborted.", "AbortError"));
117
+ }
118
+
119
+ let frameText: string;
120
+ try {
121
+ const body = JSON.parse(init.body as string) as Record<string, unknown>;
122
+ // The WS create frame is implicitly streaming; the backend rejects the
123
+ // HTTP-only `stream` flag inside a frame.
124
+ delete body.stream;
125
+ frameText = JSON.stringify({ ...body, type: "response.create" });
126
+ } catch {
127
+ return sseFallback(url, init);
128
+ }
129
+
130
+ const headers: Record<string, string> = {};
131
+ new Headers(init.headers ?? {}).forEach((value, key) => {
132
+ // HTTP-body framing headers do not apply to a WS handshake.
133
+ if (key === "content-type" || key === "content-length" || key === "accept" || key === "accept-encoding") return;
134
+ headers[key] = value;
135
+ });
136
+ headers["openai-beta"] = headers["openai-beta"]
137
+ ? headers["openai-beta"].includes("responses_websockets")
138
+ ? headers["openai-beta"]
139
+ : `${headers["openai-beta"]}, ${WS_BETA}`
140
+ : WS_BETA;
141
+ // A genuine caller `originator` is already in these headers via the forward
142
+ // set. Never fabricate one here: pool/forward traffic must not impersonate
143
+ // Codex CLI, per the metadata-integrity contract. (The backend's fast lane
144
+ // keys on WS + originator, so callers without the tag simply keep their own
145
+ // provenance and scheduling.)
146
+
147
+ return new Promise<Response>((resolve, reject) => {
148
+ let ws: WebSocket;
149
+ try {
150
+ // Bun accepts per-handshake headers; the DOM lib types only list protocol arrays.
151
+ ws = new WebSocket(CODEX_RESPONSES_WS_URL, { headers } as unknown as string[]);
152
+ } catch {
153
+ resolve(sseFallback(url, init));
154
+ return;
155
+ }
156
+
157
+ let opened = false;
158
+ let settledPreOpen = false;
159
+ let terminal = false;
160
+ let controller: ReadableStreamDefaultController<Uint8Array> | null = null;
161
+ const encoder = new TextEncoder();
162
+
163
+ const failStream = (message: string) => {
164
+ if (terminal) return;
165
+ terminal = true;
166
+ try { controller?.error(new Error(message)); } catch { /* stream already done */ }
167
+ try { ws.close(); } catch { /* already closing */ }
168
+ };
169
+
170
+ const upgradeTimer = setTimeout(() => {
171
+ if (opened || settledPreOpen) return;
172
+ settledPreOpen = true;
173
+ try { ws.close(); } catch { /* already closing */ }
174
+ resolve(sseFallback(url, init));
175
+ }, UPGRADE_DEADLINE_MS);
176
+
177
+ const onAbort = () => {
178
+ if (!opened) {
179
+ if (settledPreOpen) return;
180
+ // Settle BEFORE close(): the close handler treats a pre-open close as
181
+ // an upgrade rejection and would dial the SSE fallback for a request
182
+ // the caller just cancelled.
183
+ settledPreOpen = true;
184
+ clearTimeout(upgradeTimer);
185
+ try { ws.close(); } catch { /* already closing */ }
186
+ reject(signal?.reason ?? new DOMException("The operation was aborted.", "AbortError"));
187
+ return;
188
+ }
189
+ if (controller && !terminal) {
190
+ terminal = true;
191
+ // Mirror an aborted fetch: the body read rejects with the abort reason.
192
+ try { controller.error(signal?.reason ?? new DOMException("The operation was aborted.", "AbortError")); } catch { /* stream already done */ }
193
+ }
194
+ // Error the body before close(): test doubles and some runtimes dispatch
195
+ // close synchronously, and the caller's abort reason must stay authoritative.
196
+ try { ws.close(); } catch { /* already closing */ }
197
+ };
198
+ signal?.addEventListener("abort", onAbort, { once: true });
199
+
200
+ ws.addEventListener("open", () => {
201
+ if (settledPreOpen) return;
202
+ clearTimeout(upgradeTimer);
203
+ try {
204
+ ws.send(frameText);
205
+ } catch {
206
+ // send() throwing means the frame never left, so no upstream turn
207
+ // started and the SSE resend cannot double-generate. Falling back
208
+ // (instead of erroring a synthetic 200 body) keeps the pre-stream
209
+ // HTTP error/refresh/failover machinery in charge.
210
+ settledPreOpen = true;
211
+ try { ws.close(); } catch { /* already closing */ }
212
+ resolve(sseFallback(url, init));
213
+ return;
214
+ }
215
+ opened = true;
216
+ const stream = new ReadableStream<Uint8Array>({
217
+ start(c) { controller = c; },
218
+ cancel() { try { ws.close(); } catch { /* already closing */ } },
219
+ }, new ByteLengthQueuingStrategy({ highWaterMark: MAX_CODEX_WS_QUEUE_BYTES }));
220
+ const response = new Response(stream, {
221
+ status: 200,
222
+ // The 101 response headers (x-codex-*-reset-at quota hints) are not
223
+ // exposed by Bun's WebSocket; the periodic quota poller covers those.
224
+ headers: { "content-type": "text/event-stream; charset=utf-8" },
225
+ });
226
+ codexWsUpstreamResponses.add(response);
227
+ resolve(response);
228
+ });
229
+
230
+ ws.addEventListener("message", (event) => {
231
+ if (!controller || terminal) return;
232
+ const text = typeof event.data === "string" ? event.data : "";
233
+ if (!text) return;
234
+ // UTF-8 byte length is always at least the JS string length. Reject this
235
+ // cheap lower bound before parsing so an obviously oversized frame does
236
+ // not create another large object graph.
237
+ if (text.length > MAX_CODEX_WS_FRAME_BYTES) {
238
+ failStream("codex websocket frame exceeds the response size limit");
239
+ return;
240
+ }
241
+ const encodedText = encoder.encode(text);
242
+ if (encodedText.byteLength > MAX_CODEX_WS_FRAME_BYTES) {
243
+ failStream("codex websocket frame exceeds the response size limit");
244
+ return;
245
+ }
246
+ let type: unknown;
247
+ try { type = (JSON.parse(text) as { type?: unknown }).type; } catch { return; }
248
+ if (typeof type !== "string") return;
249
+ // Relay only the event surface the SSE path produces today. WS-only
250
+ // frames (codex.rate_limits, responsesapi.websocket_timing) are dropped
251
+ // so downstream clients see exactly the stream shape they always got.
252
+ if (!type.startsWith("response.") && type !== "error") return;
253
+ const prefix = encoder.encode(`event: ${type}\ndata: `);
254
+ const suffix = encoder.encode("\n\n");
255
+ const frameBytes = prefix.byteLength + encodedText.byteLength + suffix.byteLength;
256
+ if (frameBytes > MAX_CLIENT_SSE_FRAME_BYTES) {
257
+ failStream("codex websocket frame exceeds the response size limit");
258
+ return;
259
+ }
260
+ const availableBytes = controller.desiredSize ?? 0;
261
+ if (frameBytes > availableBytes) {
262
+ failStream("codex websocket response exceeded the buffered queue limit");
263
+ return;
264
+ }
265
+ const sseFrame = new Uint8Array(frameBytes);
266
+ sseFrame.set(prefix);
267
+ sseFrame.set(encodedText, prefix.byteLength);
268
+ sseFrame.set(suffix, prefix.byteLength + encodedText.byteLength);
269
+ try {
270
+ controller.enqueue(sseFrame);
271
+ } catch {
272
+ failStream("codex websocket response stream closed while enqueueing");
273
+ return;
274
+ }
275
+ if (type === "response.completed" || type === "response.failed" || type === "response.incomplete" || type === "error") {
276
+ terminal = true;
277
+ try { controller.close(); } catch { /* already closed */ }
278
+ try { ws.close(); } catch { /* already closing */ }
279
+ }
280
+ });
281
+
282
+ ws.addEventListener("close", () => {
283
+ signal?.removeEventListener("abort", onAbort);
284
+ if (!opened) {
285
+ if (settledPreOpen) return;
286
+ settledPreOpen = true;
287
+ clearTimeout(upgradeTimer);
288
+ // Upgrade rejected (401/403/429/5xx). Retry over plain SSE so the real
289
+ // HTTP status reaches the existing refresh/rotation handlers. No turn
290
+ // started upstream, so the resend cannot double-generate.
291
+ resolve(sseFallback(url, init));
292
+ return;
293
+ }
294
+ if (controller && !terminal) {
295
+ terminal = true;
296
+ // Connection dropped before a Responses terminal event. A clean EOF
297
+ // here would reach clients with no response.completed/failed at all —
298
+ // relaySseWithFailedTail() only synthesizes a failed terminal when the
299
+ // body read THROWS. Error the stream like a reset TCP socket.
300
+ try { controller.error(new Error("codex websocket closed before a Responses terminal event")); } catch { /* stream already done */ }
301
+ }
302
+ });
303
+
304
+ ws.addEventListener("error", () => {
305
+ /* Bun always follows error with close; the close handler settles. */
306
+ });
307
+ });
308
+ }
@@ -0,0 +1,282 @@
1
+ import type { TranslatorBudget } from "../lib/translator-budget";
2
+ import {
3
+ customToolItemId,
4
+ restoreRoutedCustomCalls,
5
+ routedCustomToolWireName,
6
+ unwrapRoutedCustomToolArguments,
7
+ } from "../responses/custom-tool-compat";
8
+ import {
9
+ replaceSseDataPayload,
10
+ sseDataPayload,
11
+ type SseBlockRewrite,
12
+ } from "./sse-payload-rewrite";
13
+
14
+ /** Exact compact prefix used by our upstream rewriter; progressive matching also
15
+ * tolerates insignificant JSON whitespace via FREEFORM_WRAP_PREFIX_RE. */
16
+ const FREEFORM_WRAP_PREFIX = '{"input":"';
17
+ const FREEFORM_WRAP_PREFIX_RE = /^\s*\{\s*"input"\s*:\s*"/;
18
+
19
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
20
+ return !!value && typeof value === "object" && !Array.isArray(value);
21
+ }
22
+
23
+ /**
24
+ * Progressive decode of the freeform `{ "input": "…" }` wrapper.
25
+ * Returns null when the accumulated text does not (yet) match that wrapper so
26
+ * callers suppress deltas and rely on `response.custom_tool_call_input.done`.
27
+ */
28
+ function partialCustomToolInput(argumentsText: string): string | null {
29
+ const match = FREEFORM_WRAP_PREFIX_RE.exec(argumentsText);
30
+ if (!match) return null;
31
+ const body = argumentsText.slice(match[0]!.length);
32
+ let output = "";
33
+ for (let index = 0; index < body.length; index++) {
34
+ const char = body[index];
35
+ if (char === '"') break;
36
+ if (char !== "\\") {
37
+ output += char;
38
+ continue;
39
+ }
40
+ const escaped = body[index + 1];
41
+ if (escaped === undefined) break;
42
+ index += 1;
43
+ if (escaped === "n") output += "\n";
44
+ else if (escaped === "t") output += "\t";
45
+ else if (escaped === "r") output += "\r";
46
+ else if (escaped === "b") output += "\b";
47
+ else if (escaped === "f") output += "\f";
48
+ else if (escaped === "u") {
49
+ const hex = body.slice(index + 1, index + 5);
50
+ if (hex.length !== 4 || !/^[0-9a-fA-F]{4}$/.test(hex)) break;
51
+ output += String.fromCharCode(Number.parseInt(hex, 16));
52
+ index += 4;
53
+ } else output += escaped;
54
+ }
55
+ return output;
56
+ }
57
+
58
+ function replaceSseEventName(block: string, type: string): string {
59
+ const newline = block.includes("\r\n") ? "\r\n" : "\n";
60
+ const lines = block.split(/\r?\n/);
61
+ let replaced = false;
62
+ const next = lines.map(line => {
63
+ if (!replaced && line.startsWith("event:")) {
64
+ replaced = true;
65
+ return `event: ${type}`;
66
+ }
67
+ return line;
68
+ });
69
+ return next.join(newline);
70
+ }
71
+
72
+ type OpenCustomCall = {
73
+ argumentsText: string;
74
+ emittedInput: string;
75
+ retainedBytes: number;
76
+ };
77
+
78
+ type PendingArgumentBlock = {
79
+ block: string;
80
+ itemId?: string;
81
+ outputIndex?: number;
82
+ retainedBytes: number;
83
+ };
84
+
85
+ export function createRoutedCustomToolRestoreBlockRewrite(
86
+ names: ReadonlySet<string>,
87
+ budget?: TranslatorBudget,
88
+ ): SseBlockRewrite {
89
+ const itemNames = new Map<string, string>();
90
+ const ordinaryItemIds = new Set<string>();
91
+ const openCalls = new Map<string, OpenCustomCall>();
92
+ let pendingArguments: PendingArgumentBlock[] = [];
93
+ let disposed = false;
94
+
95
+ const releaseCall = (itemId: string): void => {
96
+ const open = openCalls.get(itemId);
97
+ if (!open) return;
98
+ if (open.retainedBytes > 0) {
99
+ budget?.releaseRetained(open.retainedBytes, { kind: "retained_collectors" });
100
+ }
101
+ openCalls.delete(itemId);
102
+ };
103
+
104
+ const releaseAll = (): void => {
105
+ if (disposed) return;
106
+ disposed = true;
107
+ for (const itemId of openCalls.keys()) releaseCall(itemId);
108
+ const pendingBytes = pendingArguments.reduce((total, pending) => total + pending.retainedBytes, 0);
109
+ if (pendingBytes > 0) {
110
+ budget?.releaseRetained(pendingBytes, { kind: "retained_collectors" });
111
+ }
112
+ pendingArguments = [];
113
+ itemNames.clear();
114
+ ordinaryItemIds.clear();
115
+ };
116
+
117
+ const retainPendingArgument = (
118
+ block: string,
119
+ itemId: string | undefined,
120
+ outputIndex: number | undefined,
121
+ ): void => {
122
+ const retainedBytes = Buffer.byteLength(block, "utf8");
123
+ if (retainedBytes > 0) budget?.chargeRetained(retainedBytes, { kind: "retained_collectors" });
124
+ pendingArguments.push({ block, itemId, outputIndex, retainedBytes });
125
+ };
126
+
127
+ const takePendingArguments = (
128
+ itemId: string | undefined,
129
+ outputIndex: number | undefined,
130
+ ): string[] => {
131
+ const matched: PendingArgumentBlock[] = [];
132
+ const remaining: PendingArgumentBlock[] = [];
133
+ for (const pending of pendingArguments) {
134
+ const matches = pending.itemId !== undefined
135
+ ? itemId !== undefined && pending.itemId === itemId
136
+ : outputIndex !== undefined && pending.outputIndex === outputIndex;
137
+ (matches ? matched : remaining).push(pending);
138
+ }
139
+ pendingArguments = remaining;
140
+ const retainedBytes = matched.reduce((total, pending) => total + pending.retainedBytes, 0);
141
+ if (retainedBytes > 0) {
142
+ budget?.releaseRetained(retainedBytes, { kind: "retained_collectors" });
143
+ }
144
+ // Index-matched entries carry no item id. Stamp the resolved id so replay
145
+ // classifies the event instead of buffering it a second time.
146
+ return matched.map(pending => {
147
+ if (pending.itemId !== undefined || itemId === undefined) return pending.block;
148
+ const payload = sseDataPayload(pending.block);
149
+ if (payload === null) return pending.block;
150
+ try {
151
+ const parsed: unknown = JSON.parse(payload);
152
+ if (!isPlainObject(parsed)) return pending.block;
153
+ return replaceSseDataPayload(pending.block, JSON.stringify({ ...parsed, item_id: itemId }));
154
+ } catch {
155
+ return pending.block;
156
+ }
157
+ });
158
+ };
159
+
160
+ const rewrite: SseBlockRewrite = (block: string): readonly string[] => {
161
+ if (disposed) return [block];
162
+ const payload = sseDataPayload(block);
163
+ if (payload === null || payload === "[DONE]") return [block];
164
+ let parsed: unknown;
165
+ try {
166
+ parsed = JSON.parse(payload);
167
+ } catch {
168
+ return [block];
169
+ }
170
+ if (!isPlainObject(parsed)) return [block];
171
+
172
+ const type = typeof parsed.type === "string" ? parsed.type : "";
173
+ const outputIndex = typeof parsed.output_index === "number"
174
+ && Number.isInteger(parsed.output_index)
175
+ && parsed.output_index >= 0
176
+ ? parsed.output_index
177
+ : undefined;
178
+ if (
179
+ (type === "response.output_item.added" || type === "response.output_item.done")
180
+ && isPlainObject(parsed.item)
181
+ && parsed.item.type === "function_call"
182
+ && typeof parsed.item.name === "string"
183
+ ) {
184
+ const upstreamItemId = typeof parsed.item.id === "string" ? parsed.item.id : undefined;
185
+ const wireName = routedCustomToolWireName(parsed.item);
186
+ const routed = wireName !== undefined && names.has(wireName);
187
+ if (upstreamItemId) {
188
+ if (routed) {
189
+ itemNames.set(upstreamItemId, parsed.item.name);
190
+ ordinaryItemIds.delete(upstreamItemId);
191
+ } else {
192
+ ordinaryItemIds.add(upstreamItemId);
193
+ }
194
+ if (routed && type === "response.output_item.added") {
195
+ openCalls.set(upstreamItemId, { argumentsText: "", emittedInput: "", retainedBytes: 0 });
196
+ }
197
+ }
198
+ const pending = takePendingArguments(upstreamItemId, outputIndex);
199
+ if (!routed) {
200
+ if (type === "response.output_item.done" && upstreamItemId) ordinaryItemIds.delete(upstreamItemId);
201
+ return [...pending, block];
202
+ }
203
+ if (upstreamItemId && pending.length > 0 && !openCalls.has(upstreamItemId)) {
204
+ openCalls.set(upstreamItemId, { argumentsText: "", emittedInput: "", retainedBytes: 0 });
205
+ }
206
+ const restored = restoreRoutedCustomCalls(parsed, names);
207
+ const restoredBlock = restored.changed
208
+ ? replaceSseDataPayload(block, JSON.stringify(restored.value))
209
+ : block;
210
+ const replayed = pending.flatMap(pendingBlock => rewrite(pendingBlock));
211
+ if (type === "response.output_item.done" && upstreamItemId) releaseCall(upstreamItemId);
212
+ return type === "response.output_item.added"
213
+ ? [restoredBlock, ...replayed]
214
+ : [...replayed, restoredBlock];
215
+ }
216
+
217
+ const upstreamItemId = typeof parsed.item_id === "string" ? parsed.item_id : undefined;
218
+ const argumentEvent = type === "response.function_call_arguments.delta"
219
+ || type === "response.function_call_arguments.done";
220
+ if (argumentEvent && (!upstreamItemId || (!itemNames.has(upstreamItemId) && !ordinaryItemIds.has(upstreamItemId)))) {
221
+ retainPendingArgument(block, upstreamItemId, outputIndex);
222
+ return [];
223
+ }
224
+ if (
225
+ type === "response.function_call_arguments.delta"
226
+ && upstreamItemId
227
+ && itemNames.has(upstreamItemId)
228
+ ) {
229
+ const open = openCalls.get(upstreamItemId) ?? { argumentsText: "", emittedInput: "", retainedBytes: 0 };
230
+ const delta = typeof parsed.delta === "string" ? parsed.delta : "";
231
+ const deltaBytes = Buffer.byteLength(delta, "utf8");
232
+ if (deltaBytes > 0) budget?.chargeRetained(deltaBytes, { kind: "retained_collectors" });
233
+ open.argumentsText += delta;
234
+ open.retainedBytes += deltaBytes;
235
+ openCalls.set(upstreamItemId, open);
236
+ // Still accumulating toward the compact wrapper, or an unrecognized shape:
237
+ // suppress progressive emission and let the done event carry input.
238
+ if (FREEFORM_WRAP_PREFIX.startsWith(open.argumentsText)) return [];
239
+ const fullInput = partialCustomToolInput(open.argumentsText);
240
+ if (fullInput === null) return [];
241
+ if (!fullInput.startsWith(open.emittedInput) || fullInput.length === open.emittedInput.length) return [];
242
+ const inputDelta = fullInput.slice(open.emittedInput.length);
243
+ open.emittedInput = fullInput;
244
+ const nextType = "response.custom_tool_call_input.delta";
245
+ const next = {
246
+ ...parsed,
247
+ type: nextType,
248
+ item_id: customToolItemId(upstreamItemId),
249
+ delta: inputDelta,
250
+ };
251
+ return [replaceSseDataPayload(replaceSseEventName(block, nextType), JSON.stringify(next))];
252
+ }
253
+
254
+ if (
255
+ type === "response.function_call_arguments.done"
256
+ && upstreamItemId
257
+ && itemNames.has(upstreamItemId)
258
+ ) {
259
+ const nextType = "response.custom_tool_call_input.done";
260
+ const source = typeof parsed.arguments === "string"
261
+ ? parsed.arguments
262
+ : openCalls.get(upstreamItemId)?.argumentsText ?? "";
263
+ const { arguments: _arguments, ...rest } = parsed;
264
+ const next = {
265
+ ...rest,
266
+ type: nextType,
267
+ item_id: customToolItemId(upstreamItemId),
268
+ input: unwrapRoutedCustomToolArguments(source),
269
+ };
270
+ return [replaceSseDataPayload(replaceSseEventName(block, nextType), JSON.stringify(next))];
271
+ }
272
+
273
+ const restored = restoreRoutedCustomCalls(parsed, names);
274
+ const terminal = type === "response.completed" || type === "response.failed" || type === "response.incomplete";
275
+ if (terminal) releaseAll();
276
+ return restored.changed
277
+ ? [replaceSseDataPayload(block, JSON.stringify(restored.value))]
278
+ : [block];
279
+ };
280
+ rewrite.dispose = releaseAll;
281
+ return rewrite;
282
+ }