@entelligentsia/forgecli 1.1.0 → 1.1.3

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 (284) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +4 -0
  3. package/dist/CHANGELOG-forge-plugin.md +114 -0
  4. package/dist/CHANGELOG-pi.md +56 -0
  5. package/dist/extensions/forgecli/forge-subagent.js +58 -0
  6. package/dist/extensions/forgecli/forge-subagent.js.map +1 -1
  7. package/dist/extensions/forgecli/mcp-bridge/grove.d.ts +38 -0
  8. package/dist/extensions/forgecli/mcp-bridge/grove.js +88 -2
  9. package/dist/extensions/forgecli/mcp-bridge/grove.js.map +1 -1
  10. package/dist/extensions/forgecli/mcp-bridge/json-rpc-stdio.d.ts +26 -2
  11. package/dist/extensions/forgecli/mcp-bridge/json-rpc-stdio.js +68 -13
  12. package/dist/extensions/forgecli/mcp-bridge/json-rpc-stdio.js.map +1 -1
  13. package/dist/extensions/forgecli/mcp-bridge/mcp-bridge.js +13 -2
  14. package/dist/extensions/forgecli/mcp-bridge/mcp-bridge.js.map +1 -1
  15. package/dist/extensions/forgecli/mcp-bridge/mcp-session.d.ts +3 -3
  16. package/dist/extensions/forgecli/mcp-bridge/mcp-session.js +3 -6
  17. package/dist/extensions/forgecli/mcp-bridge/mcp-session.js.map +1 -1
  18. package/dist/extensions/forgecli/orchestrators/init/init-phase-dispatch.d.ts +49 -40
  19. package/dist/extensions/forgecli/orchestrators/init/init-phase-dispatch.js +59 -188
  20. package/dist/extensions/forgecli/orchestrators/init/init-phase-dispatch.js.map +1 -1
  21. package/dist/extensions/forgecli/orchestrators/init/init-phases.d.ts +0 -43
  22. package/dist/extensions/forgecli/orchestrators/init/init-phases.js +11 -59
  23. package/dist/extensions/forgecli/orchestrators/init/init-phases.js.map +1 -1
  24. package/dist/extensions/forgecli/orchestrators/init/init-steps.d.ts +115 -0
  25. package/dist/extensions/forgecli/orchestrators/init/init-steps.js +125 -0
  26. package/dist/extensions/forgecli/orchestrators/init/init-steps.js.map +1 -0
  27. package/dist/extensions/forgecli/orchestrators/init/run-init-pipeline.js +400 -211
  28. package/dist/extensions/forgecli/orchestrators/init/run-init-pipeline.js.map +1 -1
  29. package/dist/extensions/forgecli/orchestrators/init/run-init-types.d.ts +8 -2
  30. package/dist/extensions/forgecli/orchestrators/init/run-init-types.js +15 -5
  31. package/dist/extensions/forgecli/orchestrators/init/run-init-types.js.map +1 -1
  32. package/dist/forge-payload/.base-pack/workflows-js/wfl-init.js +99 -38
  33. package/dist/forge-payload/.claude-plugin/plugin.json +1 -1
  34. package/dist/forge-payload/.init/generation/generate-kb-doc.md +24 -0
  35. package/dist/forge-payload/.schemas/migrations.json +77 -0
  36. package/dist/forge-payload/init/generation/generate-kb-doc.md +24 -0
  37. package/dist/forge-payload/init/phases/phase-2/context.md +23 -0
  38. package/dist/forge-payload/init/phases/phase-2/database.md +26 -0
  39. package/dist/forge-payload/init/phases/phase-2/deployment.md +26 -0
  40. package/dist/forge-payload/init/phases/phase-2/domain-concepts.md +26 -0
  41. package/dist/forge-payload/init/phases/phase-2/domain-model.md +26 -0
  42. package/dist/forge-payload/init/phases/phase-2/entity-model.md +26 -0
  43. package/dist/forge-payload/init/phases/phase-2/index.md +25 -0
  44. package/dist/forge-payload/init/phases/phase-2/processes.md +26 -0
  45. package/dist/forge-payload/init/phases/phase-2/routing.md +25 -0
  46. package/dist/forge-payload/init/phases/phase-2/stack-checklist.md +25 -0
  47. package/dist/forge-payload/init/phases/phase-2/stack.md +27 -0
  48. package/dist/forge-payload/init/phases/phase-2/testing.md +26 -0
  49. package/dist/forge-payload/init/phases/phase-2-discover.md +15 -9
  50. package/dist/forge-payload/payload-manifest.json +2 -2
  51. package/dist/forge-payload/tools/seed-store.cjs +14 -0
  52. package/dist/forge-payload/tools/verify-phase.cjs +61 -4
  53. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts +3 -1
  54. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
  55. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +10 -1
  56. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
  57. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.d.ts.map +1 -1
  58. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +4 -6
  59. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js.map +1 -1
  60. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
  61. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js +20 -2
  62. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
  63. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  64. package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.d.ts.map +1 -1
  65. package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js +2 -14
  66. package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js.map +1 -1
  67. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.d.ts.map +1 -1
  68. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js +16 -5
  69. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js.map +1 -1
  70. package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.d.ts.map +1 -1
  71. package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.js +2 -1
  72. package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.js.map +1 -1
  73. package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.d.ts.map +1 -1
  74. package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.js +2 -1
  75. package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.js.map +1 -1
  76. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.d.ts.map +1 -1
  77. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js +61 -41
  78. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js.map +1 -1
  79. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts.map +1 -1
  80. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +8 -3
  81. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
  82. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.d.ts.map +1 -1
  83. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js +2 -14
  84. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js.map +1 -1
  85. package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.d.ts.map +1 -1
  86. package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.js +2 -1
  87. package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.js.map +1 -1
  88. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts +15 -45
  89. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  90. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +15 -45
  91. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
  92. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +461 -22
  93. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  94. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.d.ts +140 -0
  95. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.d.ts.map +1 -1
  96. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.js +141 -4
  97. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.js.map +1 -1
  98. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.d.ts +20 -0
  99. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.d.ts.map +1 -1
  100. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.js +18 -0
  101. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.js.map +1 -1
  102. package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.d.ts +21 -0
  103. package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.d.ts.map +1 -1
  104. package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.js +18 -0
  105. package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.js.map +1 -1
  106. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.d.ts +21 -0
  107. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.d.ts.map +1 -1
  108. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.js +18 -0
  109. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.js.map +1 -1
  110. package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.d.ts +10 -5
  111. package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.d.ts.map +1 -1
  112. package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.js +4 -3
  113. package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.js.map +1 -1
  114. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.d.ts +82 -0
  115. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.d.ts.map +1 -1
  116. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js +57 -0
  117. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js.map +1 -1
  118. package/node_modules/@earendil-works/pi-ai/dist/providers/groq.models.d.ts.map +1 -1
  119. package/node_modules/@earendil-works/pi-ai/dist/providers/groq.models.js +1 -1
  120. package/node_modules/@earendil-works/pi-ai/dist/providers/groq.models.js.map +1 -1
  121. package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.models.d.ts +1 -1
  122. package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.models.d.ts.map +1 -1
  123. package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.models.js +4 -4
  124. package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.models.js.map +1 -1
  125. package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.d.ts +1 -1
  126. package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.d.ts.map +1 -1
  127. package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.js +4 -4
  128. package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.js.map +1 -1
  129. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.d.ts.map +1 -1
  130. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.js +5 -5
  131. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.js.map +1 -1
  132. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.d.ts +88 -0
  133. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.d.ts.map +1 -1
  134. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.js +75 -2
  135. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.js.map +1 -1
  136. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts +33 -15
  137. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts.map +1 -1
  138. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +101 -84
  139. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js.map +1 -1
  140. package/node_modules/@earendil-works/pi-ai/dist/providers/together.models.d.ts.map +1 -1
  141. package/node_modules/@earendil-works/pi-ai/dist/providers/together.models.js +2 -2
  142. package/node_modules/@earendil-works/pi-ai/dist/providers/together.models.js.map +1 -1
  143. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts +44 -0
  144. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -1
  145. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js +41 -4
  146. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js.map +1 -1
  147. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.d.ts.map +1 -1
  148. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.js +12 -12
  149. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.js.map +1 -1
  150. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.d.ts.map +1 -1
  151. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.js +12 -12
  152. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.js.map +1 -1
  153. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.d.ts.map +1 -1
  154. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.js +12 -12
  155. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.js.map +1 -1
  156. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi.models.d.ts.map +1 -1
  157. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi.models.js +15 -15
  158. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi.models.js.map +1 -1
  159. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.d.ts +25 -0
  160. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.d.ts.map +1 -0
  161. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.js +109 -0
  162. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.js.map +1 -0
  163. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.d.ts +2 -0
  164. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.d.ts.map +1 -1
  165. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js +15 -2
  166. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js.map +1 -1
  167. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  168. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js +3 -2
  169. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  170. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts +1 -0
  171. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
  172. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +3 -0
  173. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
  174. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
  175. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +1 -0
  176. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
  177. package/node_modules/@earendil-works/pi-ai/package.json +1 -1
  178. package/node_modules/@earendil-works/pi-coding-agent/CHANGELOG.md +56 -0
  179. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.d.ts +6 -1
  180. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  181. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +39 -15
  182. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  183. package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  184. package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.js +13 -5
  185. package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  186. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  187. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/compaction.js +5 -8
  188. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  189. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/index.d.ts +1 -1
  190. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
  191. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
  192. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  193. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js +6 -0
  194. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  195. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -1
  196. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  197. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.js +33 -1
  198. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  199. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.d.ts +16 -2
  200. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  201. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  202. package/node_modules/@earendil-works/pi-coding-agent/dist/core/http-dispatcher.d.ts.map +1 -1
  203. package/node_modules/@earendil-works/pi-coding-agent/dist/core/http-dispatcher.js +28 -1
  204. package/node_modules/@earendil-works/pi-coding-agent/dist/core/http-dispatcher.js.map +1 -1
  205. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.d.ts +10 -0
  206. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  207. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js +14 -17
  208. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  209. package/node_modules/@earendil-works/pi-coding-agent/dist/core/provider-attribution.d.ts.map +1 -1
  210. package/node_modules/@earendil-works/pi-coding-agent/dist/core/provider-attribution.js +0 -10
  211. package/node_modules/@earendil-works/pi-coding-agent/dist/core/provider-attribution.js.map +1 -1
  212. package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.d.ts +20 -1
  213. package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  214. package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js +87 -68
  215. package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  216. package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
  217. package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  218. package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js +8 -0
  219. package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  220. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  221. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.js +20 -5
  222. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  223. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  224. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.js +2 -2
  225. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  226. package/node_modules/@earendil-works/pi-coding-agent/dist/index.d.ts +3 -2
  227. package/node_modules/@earendil-works/pi-coding-agent/dist/index.d.ts.map +1 -1
  228. package/node_modules/@earendil-works/pi-coding-agent/dist/index.js +2 -1
  229. package/node_modules/@earendil-works/pi-coding-agent/dist/index.js.map +1 -1
  230. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +3 -1
  231. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  232. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +15 -7
  233. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  234. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/custom-entry.d.ts +19 -0
  235. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/custom-entry.d.ts.map +1 -0
  236. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/custom-entry.js +52 -0
  237. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/custom-entry.js.map +1 -0
  238. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  239. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  240. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +13 -1
  241. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  242. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/status-indicator.d.ts +28 -0
  243. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/status-indicator.d.ts.map +1 -0
  244. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/status-indicator.js +60 -0
  245. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/status-indicator.js.map +1 -0
  246. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +6 -2
  247. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  248. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/user-message.js +18 -5
  249. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
  250. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +10 -10
  251. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  252. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +143 -130
  253. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  254. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts +15 -0
  255. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  256. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-client.js +14 -0
  257. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-client.js.map +1 -1
  258. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  259. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js +16 -0
  260. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  261. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +26 -0
  262. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  263. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
  264. package/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.d.ts.map +1 -1
  265. package/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js +7 -0
  266. package/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js.map +1 -1
  267. package/node_modules/@earendil-works/pi-coding-agent/docs/extensions.md +55 -8
  268. package/node_modules/@earendil-works/pi-coding-agent/docs/rpc.md +58 -0
  269. package/node_modules/@earendil-works/pi-coding-agent/docs/sdk.md +28 -0
  270. package/node_modules/@earendil-works/pi-coding-agent/docs/session-format.md +18 -8
  271. package/node_modules/@earendil-works/pi-coding-agent/docs/settings.md +1 -0
  272. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/README.md +1 -0
  273. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-provider-anthropic/package.json +1 -1
  274. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  275. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/entry-renderer.ts +41 -0
  276. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/gondolin/package-lock.json +2 -2
  277. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/gondolin/package.json +1 -1
  278. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/question.ts +1 -0
  279. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/sandbox/package.json +1 -1
  280. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/with-deps/package.json +1 -1
  281. package/node_modules/@earendil-works/pi-coding-agent/npm-shrinkwrap.json +12 -12
  282. package/node_modules/@earendil-works/pi-coding-agent/package.json +4 -4
  283. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  284. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"openai-codex-responses.d.ts","sourceRoot":"","sources":["../../src/api/openai-codex-responses.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEX,6BAA6B,EAG7B,MAAM,yCAAyC,CAAC;AAkBjD,OAAO,KAAK,EAOX,mBAAmB,EACnB,cAAc,EACd,aAAa,EAEb,MAAM,aAAa,CAAC;AA4CrB,MAAM,WAAW,2BAA4B,SAAQ,aAAa;IACjE,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IAC3E,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,CAAC;IACzE,WAAW,CAAC,EAAE,6BAA6B,CAAC,cAAc,CAAC,CAAC;IAC5D,aAAa,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;CAC1C;AAmHD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,wBAAwB,EAAE,2BAA2B,CA6NxF,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,wBAAwB,EAAE,mBAAmB,CAkBtF,CAAC;AAgTF,MAAM,WAAW,8BAA8B;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AA0BD,wBAAgB,iCAAiC,CAAC,SAAS,EAAE,MAAM,GAAG,8BAA8B,GAAG,SAAS,CAG/G;AAED,wBAAgB,mCAAmC,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAQ5E;AAED,wBAAgB,iCAAiC,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAe1E","sourcesContent":["import type * as NodeOs from \"node:os\";\nimport type {\n\tTool as OpenAITool,\n\tResponseCreateParamsStreaming,\n\tResponseInput,\n\tResponseStreamEvent,\n} from \"openai/resources/responses/responses.js\";\n\n// NEVER convert to top-level runtime imports - breaks browser/Vite builds\nlet _os: typeof NodeOs | null = null;\n\ntype DynamicImport = (specifier: string) => Promise<unknown>;\n\nconst dynamicImport: DynamicImport = (specifier) => import(specifier);\nconst NODE_OS_SPECIFIER = \"node:\" + \"os\";\n\nif (typeof process !== \"undefined\" && (process.versions?.node || process.versions?.bun)) {\n\tdynamicImport(NODE_OS_SPECIFIER).then((m) => {\n\t\t_os = m as typeof NodeOs;\n\t});\n}\n\nimport { clampThinkingLevel } from \"../models.ts\";\nimport { registerSessionResourceCleanup } from \"../session-resources.ts\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tContext,\n\tModel,\n\tProviderEnv,\n\tProviderHeaders,\n\tSimpleStreamOptions,\n\tStreamFunction,\n\tStreamOptions,\n\tUsage,\n} from \"../types.ts\";\nimport { combineAbortSignals } from \"../utils/abort-signals.ts\";\nimport {\n\tappendAssistantMessageDiagnostic,\n\tcreateAssistantMessageDiagnostic,\n\tformatThrownValue,\n} from \"../utils/diagnostics.ts\";\nimport { AssistantMessageEventStream } from \"../utils/event-stream.ts\";\nimport { headersToRecord } from \"../utils/headers.ts\";\nimport { resolveHttpProxyUrlForTarget } from \"../utils/node-http-proxy.ts\";\nimport { clampOpenAIPromptCacheKey } from \"./openai-prompt-cache.ts\";\nimport { convertResponsesMessages, convertResponsesTools, processResponsesStream } from \"./openai-responses-shared.ts\";\nimport { buildBaseOptions } from \"./simple-options.ts\";\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nconst DEFAULT_CODEX_BASE_URL = \"https://chatgpt.com/backend-api\";\nconst JWT_CLAIM_PATH = \"https://api.openai.com/auth\" as const;\nconst DEFAULT_MAX_RETRIES = 0;\nconst BASE_DELAY_MS = 1000;\nconst DEFAULT_MAX_RETRY_DELAY_MS = 60_000;\n// Keep a bounded pre-header timeout so zero-event Codex SSE stalls fail instead of\n// leaving callers stuck on \"Working...\" indefinitely. See #4945.\nconst DEFAULT_SSE_HEADER_TIMEOUT_MS = 20_000;\nconst DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS = 15_000;\nconst CODEX_TOOL_CALL_PROVIDERS = new Set([\"openai\", \"openai-codex\", \"opencode\"]);\nconst WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE = 1009;\nconst WEBSOCKET_CONNECTION_LIMIT_REACHED_CODE = \"websocket_connection_limit_reached\";\n\nconst CODEX_RESPONSE_STATUSES = new Set<CodexResponseStatus>([\n\t\"completed\",\n\t\"incomplete\",\n\t\"failed\",\n\t\"cancelled\",\n\t\"queued\",\n\t\"in_progress\",\n]);\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface OpenAICodexResponsesOptions extends StreamOptions {\n\treasoningEffort?: \"none\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\";\n\treasoningSummary?: \"auto\" | \"concise\" | \"detailed\" | \"off\" | \"on\" | null;\n\tserviceTier?: ResponseCreateParamsStreaming[\"service_tier\"];\n\ttextVerbosity?: \"low\" | \"medium\" | \"high\";\n}\n\ntype CodexResponseStatus = \"completed\" | \"incomplete\" | \"failed\" | \"cancelled\" | \"queued\" | \"in_progress\";\n\ninterface RequestBody {\n\tmodel: string;\n\tstore?: boolean;\n\tstream?: boolean;\n\tinstructions?: string;\n\tprevious_response_id?: string;\n\tinput?: ResponseInput;\n\ttools?: OpenAITool[];\n\ttool_choice?: \"auto\";\n\tparallel_tool_calls?: boolean;\n\ttemperature?: number;\n\treasoning?: { effort?: string; summary?: string };\n\tservice_tier?: ResponseCreateParamsStreaming[\"service_tier\"];\n\ttext?: { verbosity?: string };\n\tinclude?: string[];\n\tprompt_cache_key?: string;\n\t[key: string]: unknown;\n}\n\n// ============================================================================\n// Retry Helpers\n// ============================================================================\n\nfunction isTerminalRateLimitError(errorText: string): boolean {\n\treturn /GoUsageLimitError|FreeUsageLimitError|Monthly usage limit reached|available balance|insufficient_quota|out of budget|quota exceeded|billing/i.test(\n\t\terrorText,\n\t);\n}\n\nfunction isRetryableError(status: number, errorText: string): boolean {\n\tif (status === 429 && isTerminalRateLimitError(errorText)) {\n\t\treturn false;\n\t}\n\tif (status === 429 || status === 500 || status === 502 || status === 503 || status === 504) {\n\t\treturn true;\n\t}\n\treturn /rate.?limit|overloaded|service.?unavailable|upstream.?connect|connection.?refused/i.test(errorText);\n}\n\nfunction getRetryAfterDelayMs(headers: Headers): number | undefined {\n\tconst retryAfterMs = headers.get(\"retry-after-ms\");\n\tif (retryAfterMs !== null) {\n\t\tconst millis = Number(retryAfterMs);\n\t\tif (Number.isFinite(millis)) {\n\t\t\treturn Math.max(0, millis);\n\t\t}\n\t}\n\n\tconst retryAfter = headers.get(\"retry-after\");\n\tif (!retryAfter) {\n\t\treturn undefined;\n\t}\n\n\tconst seconds = Number(retryAfter);\n\tif (Number.isFinite(seconds)) {\n\t\treturn Math.max(0, seconds * 1000);\n\t}\n\n\tconst date = Date.parse(retryAfter);\n\tif (!Number.isNaN(date)) {\n\t\treturn Math.max(0, date - Date.now());\n\t}\n\n\treturn undefined;\n}\n\nfunction capRetryDelayMs(delayMs: number, options?: StreamOptions): number {\n\tconst maxRetryDelayMs = options?.maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;\n\treturn maxRetryDelayMs > 0 ? Math.min(delayMs, maxRetryDelayMs) : delayMs;\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(new Error(\"Request was aborted\"));\n\t\t\treturn;\n\t\t}\n\t\tconst timeout = setTimeout(resolve, ms);\n\t\tsignal?.addEventListener(\"abort\", () => {\n\t\t\tclearTimeout(timeout);\n\t\t\treject(new Error(\"Request was aborted\"));\n\t\t});\n\t});\n}\n\nfunction normalizeTimeoutMs(value: number | undefined): number | undefined {\n\tif (value === undefined) return undefined;\n\tif (!Number.isFinite(value) || value < 0) {\n\t\tthrow new Error(`Invalid timeoutMs: ${String(value)}`);\n\t}\n\treturn Math.floor(value);\n}\n\nfunction createSSEHeaderTimeout(): { signal: AbortSignal; clear: () => void; error: () => Error | undefined } {\n\tconst controller = new AbortController();\n\tlet error: Error | undefined;\n\tconst timeout = setTimeout(() => {\n\t\terror = new Error(`Codex SSE response headers timed out after ${DEFAULT_SSE_HEADER_TIMEOUT_MS}ms`);\n\t\tcontroller.abort(error);\n\t}, DEFAULT_SSE_HEADER_TIMEOUT_MS);\n\treturn {\n\t\tsignal: controller.signal,\n\t\tclear: () => clearTimeout(timeout),\n\t\terror: () => error,\n\t};\n}\n\n// ============================================================================\n// Main Stream Function\n// ============================================================================\n\nexport const stream: StreamFunction<\"openai-codex-responses\", OpenAICodexResponsesOptions> = (\n\tmodel: Model<\"openai-codex-responses\">,\n\tcontext: Context,\n\toptions?: OpenAICodexResponsesOptions,\n): AssistantMessageEventStream => {\n\tconst stream = new AssistantMessageEventStream();\n\n\t(async () => {\n\t\tconst output: AssistantMessage = {\n\t\t\trole: \"assistant\",\n\t\t\tcontent: [],\n\t\t\tapi: \"openai-codex-responses\" as Api,\n\t\t\tprovider: model.provider,\n\t\t\tmodel: model.id,\n\t\t\tusage: {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\ttotalTokens: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t},\n\t\t\tstopReason: \"stop\",\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\n\t\ttry {\n\t\t\tconst apiKey = options?.apiKey;\n\t\t\tif (!apiKey) {\n\t\t\t\tthrow new Error(`No API key for provider: ${model.provider}`);\n\t\t\t}\n\n\t\t\tconst accountId = extractAccountId(apiKey);\n\t\t\tlet body = buildRequestBody(model, context, options);\n\t\t\tconst nextBody = await options?.onPayload?.(body, model);\n\t\t\tif (nextBody !== undefined) {\n\t\t\t\tbody = nextBody as RequestBody;\n\t\t\t}\n\t\t\tconst websocketRequestId = options?.sessionId || createCodexRequestId();\n\t\t\tconst sseHeaders = buildSSEHeaders(model.headers, options?.headers, accountId, apiKey, options?.sessionId);\n\t\t\tconst websocketHeaders = buildWebSocketHeaders(\n\t\t\t\tmodel.headers,\n\t\t\t\toptions?.headers,\n\t\t\t\taccountId,\n\t\t\t\tapiKey,\n\t\t\t\twebsocketRequestId,\n\t\t\t);\n\t\t\tconst bodyJson = JSON.stringify(body);\n\t\t\tconst idleTimeoutMs = normalizeTimeoutMs(options?.timeoutMs);\n\t\t\tconst websocketConnectTimeoutMs = normalizeTimeoutMs(options?.websocketConnectTimeoutMs);\n\t\t\tconst transport = options?.transport || \"auto\";\n\t\t\tconst websocketDisabledForSession = transport !== \"sse\" && isWebSocketSseFallbackActive(options?.sessionId);\n\t\t\tif (websocketDisabledForSession) {\n\t\t\t\trecordWebSocketSseFallback(options?.sessionId);\n\t\t\t}\n\n\t\t\tif (transport !== \"sse\" && !websocketDisabledForSession) {\n\t\t\t\tlet websocketStarted = false;\n\t\t\t\tlet retriedWebSocketConnectionLimit = false;\n\t\t\t\twhile (true) {\n\t\t\t\t\twebsocketStarted = false;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait processWebSocketStream(\n\t\t\t\t\t\t\tresolveCodexWebSocketUrl(model.baseUrl),\n\t\t\t\t\t\t\tbody,\n\t\t\t\t\t\t\twebsocketHeaders,\n\t\t\t\t\t\t\toutput,\n\t\t\t\t\t\t\tstream,\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\t\twebsocketStarted = true;\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tidleTimeoutMs,\n\t\t\t\t\t\t\twebsocketConnectTimeoutMs,\n\t\t\t\t\t\t\toptions,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tif (options?.signal?.aborted) {\n\t\t\t\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\ttype: \"done\",\n\t\t\t\t\t\t\treason: output.stopReason as \"stop\" | \"length\" | \"toolUse\",\n\t\t\t\t\t\t\tmessage: output,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tstream.end();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst aborted = options?.signal?.aborted;\n\t\t\t\t\t\tconst connectionLimitBeforeStart = !websocketStarted && isWebSocketConnectionLimitReachedError(error);\n\t\t\t\t\t\tif (!aborted && connectionLimitBeforeStart && !retriedWebSocketConnectionLimit) {\n\t\t\t\t\t\t\tretriedWebSocketConnectionLimit = true;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (aborted || (isCodexNonTransportError(error) && !connectionLimitBeforeStart)) {\n\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tappendAssistantMessageDiagnostic(\n\t\t\t\t\t\t\toutput,\n\t\t\t\t\t\t\tcreateAssistantMessageDiagnostic(\"provider_transport_failure\", error, {\n\t\t\t\t\t\t\t\tconfiguredTransport: transport,\n\t\t\t\t\t\t\t\tfallbackTransport: websocketStarted ? undefined : \"sse\",\n\t\t\t\t\t\t\t\teventsEmitted: websocketStarted,\n\t\t\t\t\t\t\t\tphase: websocketStarted ? \"after_message_stream_start\" : \"before_message_stream_start\",\n\t\t\t\t\t\t\t\trequestBytes: new TextEncoder().encode(bodyJson).byteLength,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t\trecordWebSocketFailure(options?.sessionId, error);\n\t\t\t\t\t\tif (websocketStarted) {\n\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t}\n\t\t\t\t\t\trecordWebSocketSseFallback(options?.sessionId);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Fetch with retry logic for rate limits and transient errors\n\t\t\tlet response: Response | undefined;\n\t\t\tlet lastError: Error | undefined;\n\t\t\tconst maxRetries = options?.maxRetries ?? DEFAULT_MAX_RETRIES;\n\n\t\t\tfor (let attempt = 0; attempt <= maxRetries; attempt++) {\n\t\t\t\tif (options?.signal?.aborted) {\n\t\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst headerTimeout = createSSEHeaderTimeout();\n\t\t\t\t\tconst combinedSignal = combineAbortSignals([options?.signal, headerTimeout.signal]);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tresponse = await fetch(resolveCodexUrl(model.baseUrl), {\n\t\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\t\theaders: sseHeaders,\n\t\t\t\t\t\t\tbody: bodyJson,\n\t\t\t\t\t\t\tsignal: combinedSignal.signal,\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst timeoutError = headerTimeout.error();\n\t\t\t\t\t\tthrow timeoutError && !options?.signal?.aborted ? timeoutError : error;\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tcombinedSignal.cleanup();\n\t\t\t\t\t\theaderTimeout.clear();\n\t\t\t\t\t}\n\t\t\t\t\tawait options?.onResponse?.(\n\t\t\t\t\t\t{ status: response.status, headers: headersToRecord(response.headers) },\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t);\n\n\t\t\t\t\tif (response.ok) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst errorText = await response.text();\n\t\t\t\t\tif (attempt < maxRetries && isRetryableError(response.status, errorText)) {\n\t\t\t\t\t\tconst retryAfterDelayMs = getRetryAfterDelayMs(response.headers);\n\t\t\t\t\t\tconst delayMs =\n\t\t\t\t\t\t\tretryAfterDelayMs === undefined\n\t\t\t\t\t\t\t\t? BASE_DELAY_MS * 2 ** attempt\n\t\t\t\t\t\t\t\t: response.status === 429\n\t\t\t\t\t\t\t\t\t? capRetryDelayMs(retryAfterDelayMs, options)\n\t\t\t\t\t\t\t\t\t: retryAfterDelayMs;\n\n\t\t\t\t\t\tawait sleep(delayMs, options?.signal);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Parse error for friendly message on final attempt or non-retryable error\n\t\t\t\t\tconst fakeResponse = new Response(errorText, {\n\t\t\t\t\t\tstatus: response.status,\n\t\t\t\t\t\tstatusText: response.statusText,\n\t\t\t\t\t});\n\t\t\t\t\tconst info = await parseErrorResponse(fakeResponse);\n\t\t\t\t\tthrow new Error(info.friendlyMessage || info.message);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof Error) {\n\t\t\t\t\t\tif (error.name === \"AbortError\" || error.message === \"Request was aborted\") {\n\t\t\t\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tlastError = error instanceof Error ? error : new Error(String(error));\n\t\t\t\t\t// Network errors are retryable\n\t\t\t\t\tif (attempt < maxRetries && !lastError.message.includes(\"usage limit\")) {\n\t\t\t\t\t\tconst delayMs = BASE_DELAY_MS * 2 ** attempt;\n\t\t\t\t\t\tawait sleep(delayMs, options?.signal);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tthrow lastError;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!response?.ok) {\n\t\t\t\tthrow lastError ?? new Error(\"Failed after retries\");\n\t\t\t}\n\n\t\t\tif (!response.body) {\n\t\t\t\tthrow new Error(\"No response body\");\n\t\t\t}\n\n\t\t\tstream.push({ type: \"start\", partial: output });\n\t\t\tawait processStream(response, output, stream, model, options);\n\n\t\t\tif (options?.signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\n\t\t\tstream.push({ type: \"done\", reason: output.stopReason as \"stop\" | \"length\" | \"toolUse\", message: output });\n\t\t\tstream.end();\n\t\t} catch (error) {\n\t\t\tfor (const block of output.content) {\n\t\t\t\t// partialJson is only a streaming scratch buffer; never persist it.\n\t\t\t\tdelete (block as { partialJson?: string }).partialJson;\n\t\t\t}\n\t\t\toutput.stopReason = options?.signal?.aborted ? \"aborted\" : \"error\";\n\t\t\toutput.errorMessage = error instanceof Error ? error.message : String(error);\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\nexport const streamSimple: StreamFunction<\"openai-codex-responses\", SimpleStreamOptions> = (\n\tmodel: Model<\"openai-codex-responses\">,\n\tcontext: Context,\n\toptions?: SimpleStreamOptions,\n): AssistantMessageEventStream => {\n\tconst apiKey = options?.apiKey;\n\tif (!apiKey) {\n\t\tthrow new Error(`No API key for provider: ${model.provider}`);\n\t}\n\n\tconst base = buildBaseOptions(model, context, options, apiKey);\n\tconst clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined;\n\tconst reasoningEffort = clampedReasoning === \"off\" ? undefined : clampedReasoning;\n\n\treturn stream(model, context, {\n\t\t...base,\n\t\treasoningEffort,\n\t} satisfies OpenAICodexResponsesOptions);\n};\n\n// ============================================================================\n// Request Building\n// ============================================================================\n\nfunction buildRequestBody(\n\tmodel: Model<\"openai-codex-responses\">,\n\tcontext: Context,\n\toptions?: OpenAICodexResponsesOptions,\n): RequestBody {\n\tconst messages = convertResponsesMessages(model, context, CODEX_TOOL_CALL_PROVIDERS, {\n\t\tincludeSystemPrompt: false,\n\t});\n\n\tconst body: RequestBody = {\n\t\tmodel: model.id,\n\t\tstore: false,\n\t\tstream: true,\n\t\tinstructions: context.systemPrompt || \"You are a helpful assistant.\",\n\t\tinput: messages,\n\t\ttext: { verbosity: options?.textVerbosity || \"low\" },\n\t\tinclude: [\"reasoning.encrypted_content\"],\n\t\tprompt_cache_key: clampOpenAIPromptCacheKey(options?.sessionId),\n\t\ttool_choice: \"auto\",\n\t\tparallel_tool_calls: true,\n\t};\n\n\tif (options?.temperature !== undefined) {\n\t\tbody.temperature = options.temperature;\n\t}\n\n\tif (options?.serviceTier !== undefined) {\n\t\tbody.service_tier = options.serviceTier;\n\t}\n\n\tif (context.tools && context.tools.length > 0) {\n\t\tbody.tools = convertResponsesTools(context.tools, { strict: null });\n\t}\n\n\tif (options?.reasoningEffort !== undefined) {\n\t\tconst effort =\n\t\t\toptions.reasoningEffort === \"none\"\n\t\t\t\t? (model.thinkingLevelMap?.off ?? \"none\")\n\t\t\t\t: (model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort);\n\t\tif (effort !== null) {\n\t\t\tbody.reasoning = {\n\t\t\t\teffort,\n\t\t\t\tsummary: options.reasoningSummary ?? \"auto\",\n\t\t\t};\n\t\t}\n\t}\n\n\treturn body;\n}\n\nfunction getServiceTierCostMultiplier(\n\tmodel: Pick<Model<\"openai-codex-responses\">, \"id\">,\n\tserviceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n): number {\n\tswitch (serviceTier) {\n\t\tcase \"flex\":\n\t\t\treturn 0.5;\n\t\tcase \"priority\":\n\t\t\treturn model.id === \"gpt-5.5\" ? 2.5 : 2;\n\t\tdefault:\n\t\t\treturn 1;\n\t}\n}\n\nfunction applyServiceTierPricing(\n\tusage: Usage,\n\tserviceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n\tmodel: Pick<Model<\"openai-codex-responses\">, \"id\">,\n) {\n\tconst multiplier = getServiceTierCostMultiplier(model, serviceTier);\n\tif (multiplier === 1) return;\n\n\tusage.cost.input *= multiplier;\n\tusage.cost.output *= multiplier;\n\tusage.cost.cacheRead *= multiplier;\n\tusage.cost.cacheWrite *= multiplier;\n\tusage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;\n}\n\nfunction resolveCodexServiceTier(\n\tresponseServiceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n\trequestServiceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n): ResponseCreateParamsStreaming[\"service_tier\"] | undefined {\n\tif (responseServiceTier === \"default\" && (requestServiceTier === \"flex\" || requestServiceTier === \"priority\")) {\n\t\treturn requestServiceTier;\n\t}\n\treturn responseServiceTier ?? requestServiceTier;\n}\n\nfunction resolveCodexUrl(baseUrl?: string): string {\n\tconst raw = baseUrl && baseUrl.trim().length > 0 ? baseUrl : DEFAULT_CODEX_BASE_URL;\n\tconst normalized = raw.replace(/\\/+$/, \"\");\n\tif (normalized.endsWith(\"/codex/responses\")) return normalized;\n\tif (normalized.endsWith(\"/codex\")) return `${normalized}/responses`;\n\treturn `${normalized}/codex/responses`;\n}\n\nfunction resolveCodexWebSocketUrl(baseUrl?: string): string {\n\tconst url = new URL(resolveCodexUrl(baseUrl));\n\tif (url.protocol === \"https:\") url.protocol = \"wss:\";\n\tif (url.protocol === \"http:\") url.protocol = \"ws:\";\n\treturn url.toString();\n}\n\n// ============================================================================\n// Response Processing\n// ============================================================================\n\nasync function processStream(\n\tresponse: Response,\n\toutput: AssistantMessage,\n\tstream: AssistantMessageEventStream,\n\tmodel: Model<\"openai-codex-responses\">,\n\toptions?: OpenAICodexResponsesOptions,\n): Promise<void> {\n\tawait processResponsesStream(mapCodexEvents(parseSSE(response, options?.signal)), output, stream, model, {\n\t\tserviceTier: options?.serviceTier,\n\t\tresolveServiceTier: resolveCodexServiceTier,\n\t\tapplyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),\n\t});\n}\n\nclass CodexApiError extends Error {\n\treadonly code?: string;\n\treadonly payload?: Record<string, unknown>;\n\n\tconstructor(message: string, options?: { code?: string; payload?: Record<string, unknown>; cause?: unknown }) {\n\t\tsuper(message);\n\t\tthis.name = \"CodexApiError\";\n\t\tthis.code = options?.code;\n\t\tthis.payload = options?.payload;\n\t\tthis.cause = options?.cause;\n\t}\n}\n\nclass CodexProtocolError extends Error {\n\treadonly payload?: unknown;\n\n\tconstructor(message: string, options?: { payload?: unknown; cause?: unknown }) {\n\t\tsuper(message);\n\t\tthis.name = \"CodexProtocolError\";\n\t\tthis.payload = options?.payload;\n\t\tthis.cause = options?.cause;\n\t}\n}\n\nfunction isCodexNonTransportError(error: unknown): boolean {\n\treturn error instanceof CodexApiError || error instanceof CodexProtocolError;\n}\n\nfunction isWebSocketConnectionLimitReachedError(error: unknown): boolean {\n\treturn error instanceof CodexApiError && error.code === WEBSOCKET_CONNECTION_LIMIT_REACHED_CODE;\n}\n\nfunction extractCodexEventError(event: Record<string, unknown>): { code?: string; message?: string } {\n\tconst nested = event.error && typeof event.error === \"object\" ? (event.error as Record<string, unknown>) : undefined;\n\treturn {\n\t\tcode: typeof event.code === \"string\" ? event.code : typeof nested?.code === \"string\" ? nested.code : undefined,\n\t\tmessage:\n\t\t\ttypeof event.message === \"string\"\n\t\t\t\t? event.message\n\t\t\t\t: typeof nested?.message === \"string\"\n\t\t\t\t\t? nested.message\n\t\t\t\t\t: undefined,\n\t};\n}\n\nasync function* mapCodexEvents(events: AsyncIterable<Record<string, unknown>>): AsyncGenerator<ResponseStreamEvent> {\n\tfor await (const event of events) {\n\t\tconst type = typeof event.type === \"string\" ? event.type : undefined;\n\t\tif (!type) continue;\n\n\t\tif (type === \"error\") {\n\t\t\tconst { code, message } = extractCodexEventError(event);\n\t\t\tthrow new CodexApiError(`Codex error: ${message || code || JSON.stringify(event)}`, {\n\t\t\t\tcode,\n\t\t\t\tpayload: event,\n\t\t\t});\n\t\t}\n\n\t\tif (type === \"response.failed\") {\n\t\t\tconst response = (event as { response?: { error?: { code?: string; message?: string } } }).response;\n\t\t\tconst code = response?.error?.code;\n\t\t\tconst message = response?.error?.message;\n\t\t\tthrow new CodexApiError(message || \"Codex response failed\", { code, payload: event });\n\t\t}\n\n\t\tif (type === \"response.done\" || type === \"response.completed\" || type === \"response.incomplete\") {\n\t\t\tconst response = (event as { response?: { status?: unknown } }).response;\n\t\t\tconst normalizedResponse = response\n\t\t\t\t? { ...response, status: normalizeCodexStatus(response.status) }\n\t\t\t\t: response;\n\t\t\tyield { ...event, type: \"response.completed\", response: normalizedResponse } as ResponseStreamEvent;\n\t\t\treturn;\n\t\t}\n\n\t\tyield event as unknown as ResponseStreamEvent;\n\t}\n}\n\nfunction normalizeCodexStatus(status: unknown): CodexResponseStatus | undefined {\n\tif (typeof status !== \"string\") return undefined;\n\treturn CODEX_RESPONSE_STATUSES.has(status as CodexResponseStatus) ? (status as CodexResponseStatus) : undefined;\n}\n\n// ============================================================================\n// SSE Parsing\n// ============================================================================\n\nasync function* parseSSE(response: Response, signal?: AbortSignal): AsyncGenerator<Record<string, unknown>> {\n\tif (!response.body) return;\n\n\tconst reader = response.body.getReader();\n\tconst decoder = new TextDecoder();\n\tlet buffer = \"\";\n\tconst onAbort = () => {\n\t\tvoid reader.cancel().catch(() => {});\n\t};\n\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\ttry {\n\t\twhile (true) {\n\t\t\tif (signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\t\t\tconst { done, value } = await reader.read();\n\t\t\tif (signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\t\t\tif (done) break;\n\t\t\tbuffer += decoder.decode(value, { stream: true });\n\n\t\t\tlet idx = buffer.indexOf(\"\\n\\n\");\n\t\t\twhile (idx !== -1) {\n\t\t\t\tconst chunk = buffer.slice(0, idx);\n\t\t\t\tbuffer = buffer.slice(idx + 2);\n\n\t\t\t\tconst dataLines = chunk\n\t\t\t\t\t.split(\"\\n\")\n\t\t\t\t\t.filter((l) => l.startsWith(\"data:\"))\n\t\t\t\t\t.map((l) => l.slice(5).trim());\n\t\t\t\tif (dataLines.length > 0) {\n\t\t\t\t\tconst data = dataLines.join(\"\\n\").trim();\n\t\t\t\t\tif (data && data !== \"[DONE]\") {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tyield JSON.parse(data) as Record<string, unknown>;\n\t\t\t\t\t\t} catch (cause) {\n\t\t\t\t\t\t\tthrow new CodexProtocolError(`Invalid Codex SSE JSON: ${formatThrownValue(cause)}`, {\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\tpayload: data,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tidx = buffer.indexOf(\"\\n\\n\");\n\t\t\t}\n\t\t}\n\t} finally {\n\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\ttry {\n\t\t\tawait reader.cancel();\n\t\t} catch {}\n\t\ttry {\n\t\t\treader.releaseLock();\n\t\t} catch {}\n\t}\n}\n\n// ============================================================================\n// WebSocket Parsing\n// ============================================================================\n\nconst OPENAI_BETA_RESPONSES_WEBSOCKETS = \"responses_websockets=2026-02-06\";\nconst SESSION_WEBSOCKET_CACHE_TTL_MS = 5 * 60 * 1000;\n\ntype WebSocketEventType = \"open\" | \"message\" | \"error\" | \"close\";\ntype WebSocketListener = (event: unknown) => void;\n\ninterface WebSocketLike {\n\tclose(code?: number, reason?: string): void;\n\tsend(data: string): void;\n\taddEventListener(type: WebSocketEventType, listener: WebSocketListener): void;\n\tremoveEventListener(type: WebSocketEventType, listener: WebSocketListener): void;\n}\n\ninterface CachedWebSocketContinuationState {\n\tlastRequestBody: RequestBody;\n\tlastResponseId: string;\n\tlastResponseItems: ResponseInput;\n}\n\ninterface CachedWebSocketConnection {\n\tsocket: WebSocketLike;\n\tbusy: boolean;\n\tidleTimer?: ReturnType<typeof setTimeout>;\n\tcontinuation?: CachedWebSocketContinuationState;\n}\n\nexport interface OpenAICodexWebSocketDebugStats {\n\trequests: number;\n\tconnectionsCreated: number;\n\tconnectionsReused: number;\n\tcachedContextRequests: number;\n\tstoreTrueRequests: number;\n\tfullContextRequests: number;\n\tdeltaRequests: number;\n\tlastInputItems: number;\n\tlastDeltaInputItems?: number;\n\tlastPreviousResponseId?: string;\n\twebsocketFailures: number;\n\tsseFallbacks: number;\n\twebsocketFallbackActive?: boolean;\n\tlastWebSocketError?: string;\n}\n\nconst websocketSessionCache = new Map<string, CachedWebSocketConnection>();\nconst websocketDebugStats = new Map<string, OpenAICodexWebSocketDebugStats>();\nconst websocketSseFallbackSessions = new Set<string>();\n\nfunction getOrCreateWebSocketDebugStats(sessionId: string): OpenAICodexWebSocketDebugStats {\n\tlet stats = websocketDebugStats.get(sessionId);\n\tif (!stats) {\n\t\tstats = {\n\t\t\trequests: 0,\n\t\t\tconnectionsCreated: 0,\n\t\t\tconnectionsReused: 0,\n\t\t\tcachedContextRequests: 0,\n\t\t\tstoreTrueRequests: 0,\n\t\t\tfullContextRequests: 0,\n\t\t\tdeltaRequests: 0,\n\t\t\tlastInputItems: 0,\n\t\t\twebsocketFailures: 0,\n\t\t\tsseFallbacks: 0,\n\t\t};\n\t\twebsocketDebugStats.set(sessionId, stats);\n\t}\n\treturn stats;\n}\n\nexport function getOpenAICodexWebSocketDebugStats(sessionId: string): OpenAICodexWebSocketDebugStats | undefined {\n\tconst stats = websocketDebugStats.get(sessionId);\n\treturn stats ? { ...stats } : undefined;\n}\n\nexport function resetOpenAICodexWebSocketDebugStats(sessionId?: string): void {\n\tif (sessionId) {\n\t\twebsocketDebugStats.delete(sessionId);\n\t\twebsocketSseFallbackSessions.delete(sessionId);\n\t\treturn;\n\t}\n\twebsocketDebugStats.clear();\n\twebsocketSseFallbackSessions.clear();\n}\n\nexport function closeOpenAICodexWebSocketSessions(sessionId?: string): void {\n\tconst closeEntry = (entry: CachedWebSocketConnection) => {\n\t\tif (entry.idleTimer) clearTimeout(entry.idleTimer);\n\t\tcloseWebSocketSilently(entry.socket, 1000, \"debug_close\");\n\t};\n\tif (sessionId) {\n\t\tconst entry = websocketSessionCache.get(sessionId);\n\t\tif (entry) closeEntry(entry);\n\t\twebsocketSessionCache.delete(sessionId);\n\t\treturn;\n\t}\n\tfor (const entry of websocketSessionCache.values()) {\n\t\tcloseEntry(entry);\n\t}\n\twebsocketSessionCache.clear();\n}\n\nregisterSessionResourceCleanup(closeOpenAICodexWebSocketSessions);\n\nfunction isWebSocketSseFallbackActive(sessionId: string | undefined): boolean {\n\treturn sessionId ? websocketSseFallbackSessions.has(sessionId) : false;\n}\n\nfunction recordWebSocketSseFallback(sessionId: string | undefined): void {\n\tif (!sessionId) return;\n\tconst stats = getOrCreateWebSocketDebugStats(sessionId);\n\tstats.sseFallbacks++;\n\tstats.websocketFallbackActive = isWebSocketSseFallbackActive(sessionId);\n}\n\nfunction recordWebSocketFailure(sessionId: string | undefined, error: unknown): void {\n\tif (!sessionId) return;\n\twebsocketSseFallbackSessions.add(sessionId);\n\n\tconst stats = getOrCreateWebSocketDebugStats(sessionId);\n\tstats.websocketFailures++;\n\tstats.lastWebSocketError = formatThrownValue(error);\n\tstats.websocketFallbackActive = true;\n}\n\ntype WebSocketConstructor = new (\n\turl: string,\n\tprotocols?: string | string[] | { headers?: Record<string, string> },\n) => WebSocketLike;\n\nlet _cachedWebsocket: WebSocketConstructor | null = null;\nasync function getWebSocketConstructor(env?: ProviderEnv): Promise<WebSocketConstructor | null> {\n\tif (!env && _cachedWebsocket) return _cachedWebsocket;\n\n\t// bun doesn't respect http proxy envs, ref: https://github.com/oven-sh/bun/issues/15489\n\t// TODO: remove this when bun supports proxy envs in websocket.\n\tif (typeof process !== \"undefined\" && process.versions?.bun) {\n\t\tconst WebSocketWithProxy = class extends WebSocket {\n\t\t\tconstructor(url: string | URL, options?: string | string[] | Record<string, unknown>) {\n\t\t\t\tlet _opts: Record<string, unknown> = {};\n\t\t\t\tif (Array.isArray(options) || typeof options === \"string\") {\n\t\t\t\t\t_opts = { protocols: options };\n\t\t\t\t} else {\n\t\t\t\t\t_opts = { ...options };\n\t\t\t\t}\n\n\t\t\t\tconst proxyUrl = resolveHttpProxyUrlForTarget(\n\t\t\t\t\turl.toString().replace(/^wss:/, \"https:\").replace(/^ws:/, \"http:\"),\n\t\t\t\t\tenv,\n\t\t\t\t);\n\t\t\t\tsuper(url, { ..._opts, ...(proxyUrl ? { proxy: proxyUrl.toString() } : {}) } as any);\n\t\t\t}\n\t\t};\n\t\tif (!env) {\n\t\t\t_cachedWebsocket = WebSocketWithProxy;\n\t\t}\n\t\treturn WebSocketWithProxy;\n\t}\n\n\tconst ctor = (globalThis as { WebSocket?: unknown }).WebSocket;\n\tif (typeof ctor !== \"function\") return null;\n\treturn ctor as unknown as WebSocketConstructor;\n}\n\nclass WebSocketCloseError extends Error {\n\treadonly code?: number;\n\treadonly reason?: string;\n\treadonly wasClean?: boolean;\n\n\tconstructor(message: string, options?: { code?: number; reason?: string; wasClean?: boolean }) {\n\t\tsuper(message);\n\t\tthis.name = \"WebSocketCloseError\";\n\t\tthis.code = options?.code;\n\t\tthis.reason = options?.reason;\n\t\tthis.wasClean = options?.wasClean;\n\t}\n}\n\nfunction getWebSocketReadyState(socket: WebSocketLike): number | undefined {\n\tconst readyState = (socket as { readyState?: unknown }).readyState;\n\treturn typeof readyState === \"number\" ? readyState : undefined;\n}\n\nfunction isWebSocketReusable(socket: WebSocketLike): boolean {\n\tconst readyState = getWebSocketReadyState(socket);\n\t// If readyState is unavailable, assume the runtime keeps it open/reusable.\n\treturn readyState === undefined || readyState === 1;\n}\n\nfunction closeWebSocketSilently(socket: WebSocketLike, code = 1000, reason = \"done\"): void {\n\ttry {\n\t\tsocket.close(code, reason);\n\t} catch {}\n}\n\nfunction scheduleSessionWebSocketExpiry(sessionId: string, entry: CachedWebSocketConnection): void {\n\tif (entry.idleTimer) {\n\t\tclearTimeout(entry.idleTimer);\n\t}\n\tentry.idleTimer = setTimeout(() => {\n\t\tif (entry.busy) return;\n\t\tcloseWebSocketSilently(entry.socket, 1000, \"idle_timeout\");\n\t\twebsocketSessionCache.delete(sessionId);\n\t}, SESSION_WEBSOCKET_CACHE_TTL_MS);\n}\n\nasync function connectWebSocket(\n\turl: string,\n\theaders: Headers,\n\tsignal?: AbortSignal,\n\tconnectTimeoutMs = DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS,\n\tenv?: ProviderEnv,\n): Promise<WebSocketLike> {\n\tconst WebSocketCtor = await getWebSocketConstructor(env);\n\tif (!WebSocketCtor) {\n\t\tthrow new Error(\"WebSocket transport is not available in this runtime\");\n\t}\n\n\tconst wsHeaders = headersToRecord(headers);\n\tdelete wsHeaders[\"OpenAI-Beta\"];\n\n\treturn new Promise<WebSocketLike>((resolve, reject) => {\n\t\tlet settled = false;\n\t\tlet timeout: ReturnType<typeof setTimeout> | undefined;\n\t\tlet socket: WebSocketLike;\n\n\t\ttry {\n\t\t\tsocket = new WebSocketCtor(url, { headers: wsHeaders });\n\t\t} catch (error) {\n\t\t\treject(error instanceof Error ? error : new Error(String(error)));\n\t\t\treturn;\n\t\t}\n\n\t\tconst cleanup = () => {\n\t\t\tif (timeout) {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\ttimeout = undefined;\n\t\t\t}\n\t\t\tsocket.removeEventListener(\"open\", onOpen);\n\t\t\tsocket.removeEventListener(\"error\", onError);\n\t\t\tsocket.removeEventListener(\"close\", onClose);\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t};\n\t\tconst fail = (error: Error, closeReason?: string) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tcleanup();\n\t\t\tif (closeReason) {\n\t\t\t\tcloseWebSocketSilently(socket, 1000, closeReason);\n\t\t\t}\n\t\t\treject(error);\n\t\t};\n\t\tconst onOpen: WebSocketListener = () => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tcleanup();\n\t\t\tresolve(socket);\n\t\t};\n\t\tconst onError: WebSocketListener = (event) => {\n\t\t\tfail(extractWebSocketError(event));\n\t\t};\n\t\tconst onClose: WebSocketListener = (event) => {\n\t\t\tfail(extractWebSocketCloseError(event));\n\t\t};\n\t\tconst onAbort = () => {\n\t\t\tfail(new Error(\"Request was aborted\"), \"aborted\");\n\t\t};\n\n\t\tsocket.addEventListener(\"open\", onOpen);\n\t\tsocket.addEventListener(\"error\", onError);\n\t\tsocket.addEventListener(\"close\", onClose);\n\t\tsignal?.addEventListener(\"abort\", onAbort);\n\n\t\tif (connectTimeoutMs > 0) {\n\t\t\ttimeout = setTimeout(() => {\n\t\t\t\tfail(new Error(`WebSocket connect timeout after ${connectTimeoutMs}ms`), \"connect_timeout\");\n\t\t\t}, connectTimeoutMs);\n\t\t}\n\t\tif (signal?.aborted) {\n\t\t\tonAbort();\n\t\t}\n\t});\n}\n\nasync function acquireWebSocket(\n\turl: string,\n\theaders: Headers,\n\tsessionId: string | undefined,\n\tsignal?: AbortSignal,\n\tconnectTimeoutMs?: number,\n\tenv?: ProviderEnv,\n): Promise<{\n\tsocket: WebSocketLike;\n\tentry?: CachedWebSocketConnection;\n\treused: boolean;\n\trelease: (options?: { keep?: boolean }) => void;\n}> {\n\tif (!sessionId) {\n\t\tconst socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);\n\t\treturn {\n\t\t\tsocket,\n\t\t\treused: false,\n\t\t\trelease: () => closeWebSocketSilently(socket),\n\t\t};\n\t}\n\n\tconst cached = websocketSessionCache.get(sessionId);\n\tif (cached) {\n\t\tif (cached.idleTimer) {\n\t\t\tclearTimeout(cached.idleTimer);\n\t\t\tcached.idleTimer = undefined;\n\t\t}\n\t\tif (!cached.busy && isWebSocketReusable(cached.socket)) {\n\t\t\tcached.busy = true;\n\t\t\treturn {\n\t\t\t\tsocket: cached.socket,\n\t\t\t\tentry: cached,\n\t\t\t\treused: true,\n\t\t\t\trelease: ({ keep } = {}) => {\n\t\t\t\t\tif (!keep || !isWebSocketReusable(cached.socket)) {\n\t\t\t\t\t\tcloseWebSocketSilently(cached.socket);\n\t\t\t\t\t\twebsocketSessionCache.delete(sessionId);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcached.busy = false;\n\t\t\t\t\tscheduleSessionWebSocketExpiry(sessionId, cached);\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tif (cached.busy) {\n\t\t\tconst socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);\n\t\t\treturn {\n\t\t\t\tsocket,\n\t\t\t\treused: false,\n\t\t\t\trelease: () => {\n\t\t\t\t\tcloseWebSocketSilently(socket);\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tif (!isWebSocketReusable(cached.socket)) {\n\t\t\tcloseWebSocketSilently(cached.socket);\n\t\t\twebsocketSessionCache.delete(sessionId);\n\t\t}\n\t}\n\n\tconst socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);\n\tconst entry: CachedWebSocketConnection = { socket, busy: true };\n\twebsocketSessionCache.set(sessionId, entry);\n\treturn {\n\t\tsocket,\n\t\tentry,\n\t\treused: false,\n\t\trelease: ({ keep } = {}) => {\n\t\t\tif (!keep || !isWebSocketReusable(entry.socket)) {\n\t\t\t\tcloseWebSocketSilently(entry.socket);\n\t\t\t\tif (entry.idleTimer) clearTimeout(entry.idleTimer);\n\t\t\t\tif (websocketSessionCache.get(sessionId) === entry) {\n\t\t\t\t\twebsocketSessionCache.delete(sessionId);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tentry.busy = false;\n\t\t\tscheduleSessionWebSocketExpiry(sessionId, entry);\n\t\t},\n\t};\n}\n\nfunction extractWebSocketError(event: unknown): Error {\n\tif (event && typeof event === \"object\") {\n\t\tconst message = \"message\" in event ? (event as { message?: unknown }).message : undefined;\n\t\tif (typeof message === \"string\" && message.length > 0) {\n\t\t\treturn new Error(message);\n\t\t}\n\n\t\tconst nestedError = \"error\" in event ? (event as { error?: unknown }).error : undefined;\n\t\tif (nestedError instanceof Error && nestedError.message.length > 0) {\n\t\t\treturn nestedError;\n\t\t}\n\t\tif (nestedError && typeof nestedError === \"object\" && \"message\" in nestedError) {\n\t\t\tconst nestedMessage = (nestedError as { message?: unknown }).message;\n\t\t\tif (typeof nestedMessage === \"string\" && nestedMessage.length > 0) {\n\t\t\t\treturn new Error(nestedMessage);\n\t\t\t}\n\t\t}\n\t}\n\treturn new Error(\"WebSocket error\");\n}\n\nfunction extractWebSocketCloseError(event: unknown): Error {\n\tif (event && typeof event === \"object\") {\n\t\tconst code = \"code\" in event ? (event as { code?: unknown }).code : undefined;\n\t\tconst reason = \"reason\" in event ? (event as { reason?: unknown }).reason : undefined;\n\t\tconst wasClean = \"wasClean\" in event ? (event as { wasClean?: unknown }).wasClean : undefined;\n\t\tconst codeText = typeof code === \"number\" ? ` ${code}` : \"\";\n\t\tlet reasonText = typeof reason === \"string\" && reason.length > 0 ? ` ${reason}` : \"\";\n\t\tif (!reasonText && code === WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE) {\n\t\t\treasonText = \" message too big\";\n\t\t}\n\t\treturn new WebSocketCloseError(`WebSocket closed${codeText}${reasonText}`.trim(), {\n\t\t\tcode: typeof code === \"number\" ? code : undefined,\n\t\t\treason: typeof reason === \"string\" && reason.length > 0 ? reason : undefined,\n\t\t\twasClean: typeof wasClean === \"boolean\" ? wasClean : undefined,\n\t\t});\n\t}\n\treturn new Error(\"WebSocket closed\");\n}\n\nasync function decodeWebSocketData(data: unknown): Promise<string | null> {\n\tif (typeof data === \"string\") return data;\n\tif (data instanceof ArrayBuffer) {\n\t\treturn new TextDecoder().decode(new Uint8Array(data));\n\t}\n\tif (ArrayBuffer.isView(data)) {\n\t\tconst view = data as ArrayBufferView;\n\t\treturn new TextDecoder().decode(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));\n\t}\n\tif (data && typeof data === \"object\" && \"arrayBuffer\" in data) {\n\t\tconst blobLike = data as { arrayBuffer: () => Promise<ArrayBuffer> };\n\t\tconst arrayBuffer = await blobLike.arrayBuffer();\n\t\treturn new TextDecoder().decode(new Uint8Array(arrayBuffer));\n\t}\n\treturn null;\n}\n\nasync function* parseWebSocket(\n\tsocket: WebSocketLike,\n\tsignal?: AbortSignal,\n\tidleTimeoutMs?: number,\n): AsyncGenerator<Record<string, unknown>> {\n\tconst queue: Record<string, unknown>[] = [];\n\tlet pending: (() => void) | null = null;\n\tlet done = false;\n\tlet failed: Error | null = null;\n\tlet sawCompletion = false;\n\n\tconst wake = () => {\n\t\tif (!pending) return;\n\t\tconst resolve = pending;\n\t\tpending = null;\n\t\tresolve();\n\t};\n\n\tconst onMessage: WebSocketListener = (event) => {\n\t\tvoid (async () => {\n\t\t\tlet text: string | null = null;\n\t\t\ttry {\n\t\t\t\tif (!event || typeof event !== \"object\" || !(\"data\" in event)) return;\n\t\t\t\ttext = await decodeWebSocketData((event as { data?: unknown }).data);\n\t\t\t\tif (!text) return;\n\t\t\t\tconst parsed = JSON.parse(text) as Record<string, unknown>;\n\t\t\t\tconst type = typeof parsed.type === \"string\" ? parsed.type : \"\";\n\t\t\t\tif (type === \"response.completed\" || type === \"response.done\" || type === \"response.incomplete\") {\n\t\t\t\t\tsawCompletion = true;\n\t\t\t\t\tdone = true;\n\t\t\t\t}\n\t\t\t\tqueue.push(parsed);\n\t\t\t\twake();\n\t\t\t} catch (cause) {\n\t\t\t\tfailed = new CodexProtocolError(`Invalid Codex WebSocket JSON: ${formatThrownValue(cause)}`, {\n\t\t\t\t\tcause,\n\t\t\t\t\tpayload: text,\n\t\t\t\t});\n\t\t\t\tdone = true;\n\t\t\t\twake();\n\t\t\t}\n\t\t})();\n\t};\n\n\tconst onError: WebSocketListener = (event) => {\n\t\tfailed = extractWebSocketError(event);\n\t\tdone = true;\n\t\twake();\n\t};\n\n\tconst onClose: WebSocketListener = (event) => {\n\t\tif (sawCompletion) {\n\t\t\tdone = true;\n\t\t\twake();\n\t\t\treturn;\n\t\t}\n\t\tif (!failed) {\n\t\t\tfailed = extractWebSocketCloseError(event);\n\t\t}\n\t\tdone = true;\n\t\twake();\n\t};\n\n\tconst onAbort = () => {\n\t\tfailed = new Error(\"Request was aborted\");\n\t\tdone = true;\n\t\twake();\n\t};\n\n\tsocket.addEventListener(\"message\", onMessage);\n\tsocket.addEventListener(\"error\", onError);\n\tsocket.addEventListener(\"close\", onClose);\n\tsignal?.addEventListener(\"abort\", onAbort);\n\n\ttry {\n\t\twhile (true) {\n\t\t\tif (signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\t\t\tif (queue.length > 0) {\n\t\t\t\tyield queue.shift()!;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (done) break;\n\t\t\tlet timeout: ReturnType<typeof setTimeout> | undefined;\n\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\tpending = resolve;\n\t\t\t\tif (idleTimeoutMs !== undefined && idleTimeoutMs > 0) {\n\t\t\t\t\ttimeout = setTimeout(() => {\n\t\t\t\t\t\tconst error = new Error(`WebSocket idle timeout after ${idleTimeoutMs}ms`);\n\t\t\t\t\t\tfailed = error;\n\t\t\t\t\t\tdone = true;\n\t\t\t\t\t\tpending = null;\n\t\t\t\t\t\tcloseWebSocketSilently(socket, 1000, \"idle_timeout\");\n\t\t\t\t\t\treject(error);\n\t\t\t\t\t}, idleTimeoutMs);\n\t\t\t\t}\n\t\t\t}).finally(() => {\n\t\t\t\tif (timeout) {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tif (failed) {\n\t\t\tthrow failed;\n\t\t}\n\t\tif (!sawCompletion) {\n\t\t\tthrow new Error(\"WebSocket stream closed before response.completed\");\n\t\t}\n\t} finally {\n\t\tsocket.removeEventListener(\"message\", onMessage);\n\t\tsocket.removeEventListener(\"error\", onError);\n\t\tsocket.removeEventListener(\"close\", onClose);\n\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t}\n}\n\nfunction requestBodyWithoutInput(body: RequestBody): RequestBody {\n\tconst { input: _input, previous_response_id: _previousResponseId, ...rest } = body;\n\treturn rest;\n}\n\nfunction responseInputsEqual(a: ResponseInput | undefined, b: ResponseInput | undefined): boolean {\n\treturn JSON.stringify(a ?? []) === JSON.stringify(b ?? []);\n}\n\nfunction requestBodiesMatchExceptInput(a: RequestBody, b: RequestBody): boolean {\n\treturn JSON.stringify(requestBodyWithoutInput(a)) === JSON.stringify(requestBodyWithoutInput(b));\n}\n\nfunction getCachedWebSocketInputDelta(\n\tbody: RequestBody,\n\tcontinuation: CachedWebSocketContinuationState,\n): ResponseInput | undefined {\n\tif (!requestBodiesMatchExceptInput(body, continuation.lastRequestBody)) {\n\t\treturn undefined;\n\t}\n\n\tconst currentInput = body.input ?? [];\n\tconst baseline = [...(continuation.lastRequestBody.input ?? []), ...continuation.lastResponseItems];\n\tif (currentInput.length < baseline.length) {\n\t\treturn undefined;\n\t}\n\n\tconst prefix = currentInput.slice(0, baseline.length);\n\tif (!responseInputsEqual(prefix, baseline)) {\n\t\treturn undefined;\n\t}\n\n\treturn currentInput.slice(baseline.length);\n}\n\nfunction buildCachedWebSocketRequestBody(entry: CachedWebSocketConnection, body: RequestBody): RequestBody {\n\tconst continuation = entry.continuation;\n\tif (!continuation) {\n\t\treturn body;\n\t}\n\n\tconst delta = getCachedWebSocketInputDelta(body, continuation);\n\tif (!delta || !continuation.lastResponseId) {\n\t\tentry.continuation = undefined;\n\t\treturn body;\n\t}\n\n\treturn {\n\t\t...body,\n\t\tprevious_response_id: continuation.lastResponseId,\n\t\tinput: delta,\n\t};\n}\n\nasync function* startWebSocketOutputOnFirstEvent(\n\tevents: AsyncIterable<ResponseStreamEvent>,\n\toutput: AssistantMessage,\n\tstream: AssistantMessageEventStream,\n\tonStart: () => void,\n): AsyncGenerator<ResponseStreamEvent> {\n\tlet started = false;\n\tfor await (const event of events) {\n\t\tif (!started) {\n\t\t\tstarted = true;\n\t\t\tonStart();\n\t\t\tstream.push({ type: \"start\", partial: output });\n\t\t}\n\t\tyield event;\n\t}\n}\n\nasync function processWebSocketStream(\n\turl: string,\n\tbody: RequestBody,\n\theaders: Headers,\n\toutput: AssistantMessage,\n\tstream: AssistantMessageEventStream,\n\tmodel: Model<\"openai-codex-responses\">,\n\tonStart: () => void,\n\tidleTimeoutMs: number | undefined,\n\twebsocketConnectTimeoutMs: number | undefined,\n\toptions?: OpenAICodexResponsesOptions,\n): Promise<void> {\n\tconst { socket, entry, reused, release } = await acquireWebSocket(\n\t\turl,\n\t\theaders,\n\t\toptions?.sessionId,\n\t\toptions?.signal,\n\t\twebsocketConnectTimeoutMs,\n\t\toptions?.env,\n\t);\n\tlet keepConnection = true;\n\tconst useCachedContext = options?.transport === \"websocket-cached\" || options?.transport === \"auto\";\n\t// ChatGPT Codex Responses rejects `store: true` (\"Store must be set to false\").\n\t// WebSocket continuation still works via connection-scoped previous_response_id state.\n\tconst fullBody = body;\n\tconst requestBody = useCachedContext && entry ? buildCachedWebSocketRequestBody(entry, fullBody) : fullBody;\n\tconst stats = options?.sessionId ? getOrCreateWebSocketDebugStats(options.sessionId) : undefined;\n\tif (stats) {\n\t\tstats.requests++;\n\t\tif (reused) stats.connectionsReused++;\n\t\telse stats.connectionsCreated++;\n\t\tif (useCachedContext) stats.cachedContextRequests++;\n\t\tif (requestBody.store === true) stats.storeTrueRequests++;\n\t\tstats.lastInputItems = requestBody.input?.length ?? 0;\n\t\tif (requestBody.previous_response_id) {\n\t\t\tstats.deltaRequests++;\n\t\t\tstats.lastDeltaInputItems = requestBody.input?.length ?? 0;\n\t\t\tstats.lastPreviousResponseId = requestBody.previous_response_id;\n\t\t} else {\n\t\t\tstats.fullContextRequests++;\n\t\t\tstats.lastDeltaInputItems = undefined;\n\t\t\tstats.lastPreviousResponseId = undefined;\n\t\t}\n\t}\n\ttry {\n\t\tsocket.send(JSON.stringify({ type: \"response.create\", ...requestBody }));\n\t\tawait processResponsesStream(\n\t\t\tstartWebSocketOutputOnFirstEvent(\n\t\t\t\tmapCodexEvents(parseWebSocket(socket, options?.signal, idleTimeoutMs)),\n\t\t\t\toutput,\n\t\t\t\tstream,\n\t\t\t\tonStart,\n\t\t\t),\n\t\t\toutput,\n\t\t\tstream,\n\t\t\tmodel,\n\t\t\t{\n\t\t\t\tserviceTier: options?.serviceTier,\n\t\t\t\tresolveServiceTier: resolveCodexServiceTier,\n\t\t\t\tapplyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),\n\t\t\t},\n\t\t);\n\t\tif (options?.signal?.aborted) {\n\t\t\tkeepConnection = false;\n\t\t} else if (useCachedContext && entry && output.responseId) {\n\t\t\tconst responseItems = convertResponsesMessages(model, { messages: [output] }, CODEX_TOOL_CALL_PROVIDERS, {\n\t\t\t\tincludeSystemPrompt: false,\n\t\t\t}).filter((item) => item.type !== \"function_call_output\");\n\t\t\tentry.continuation = {\n\t\t\t\tlastRequestBody: fullBody,\n\t\t\t\tlastResponseId: output.responseId,\n\t\t\t\tlastResponseItems: responseItems,\n\t\t\t};\n\t\t}\n\t} catch (error) {\n\t\tif (entry) {\n\t\t\tentry.continuation = undefined;\n\t\t}\n\t\tkeepConnection = false;\n\t\tthrow error;\n\t} finally {\n\t\trelease({ keep: keepConnection });\n\t}\n}\n\n// ============================================================================\n// Error Handling\n// ============================================================================\n\nasync function parseErrorResponse(response: Response): Promise<{ message: string; friendlyMessage?: string }> {\n\tconst raw = await response.text();\n\tlet message = raw || response.statusText || \"Request failed\";\n\tlet friendlyMessage: string | undefined;\n\n\ttry {\n\t\tconst parsed = JSON.parse(raw) as {\n\t\t\terror?: { code?: string; type?: string; message?: string; plan_type?: string; resets_at?: number };\n\t\t};\n\t\tconst err = parsed?.error;\n\t\tif (err) {\n\t\t\tconst code = err.code || err.type || \"\";\n\t\t\tif (/usage_limit_reached|usage_not_included|rate_limit_exceeded/i.test(code) || response.status === 429) {\n\t\t\t\tconst plan = err.plan_type ? ` (${err.plan_type.toLowerCase()} plan)` : \"\";\n\t\t\t\tconst mins = err.resets_at\n\t\t\t\t\t? Math.max(0, Math.round((err.resets_at * 1000 - Date.now()) / 60000))\n\t\t\t\t\t: undefined;\n\t\t\t\tconst when = mins !== undefined ? ` Try again in ~${mins} min.` : \"\";\n\t\t\t\tfriendlyMessage = `You have hit your ChatGPT usage limit${plan}.${when}`.trim();\n\t\t\t}\n\t\t\tmessage = err.message || friendlyMessage || message;\n\t\t}\n\t} catch {}\n\n\treturn { message, friendlyMessage };\n}\n\n// ============================================================================\n// Auth & Headers\n// ============================================================================\n\nfunction extractAccountId(token: string): string {\n\ttry {\n\t\tconst parts = token.split(\".\");\n\t\tif (parts.length !== 3) throw new Error(\"Invalid token\");\n\t\tconst payload = JSON.parse(atob(parts[1]));\n\t\tconst accountId = payload?.[JWT_CLAIM_PATH]?.chatgpt_account_id;\n\t\tif (!accountId) throw new Error(\"No account ID in token\");\n\t\treturn accountId;\n\t} catch {\n\t\tthrow new Error(\"Failed to extract accountId from token\");\n\t}\n}\n\nfunction createCodexRequestId(): string {\n\tif (typeof globalThis.crypto?.randomUUID === \"function\") {\n\t\treturn globalThis.crypto.randomUUID();\n\t}\n\treturn `codex_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction buildBaseCodexHeaders(\n\tinitHeaders: Record<string, string> | undefined,\n\tadditionalHeaders: ProviderHeaders | undefined,\n\taccountId: string,\n\ttoken: string,\n): Headers {\n\tconst headers = new Headers(initHeaders);\n\tfor (const [key, value] of Object.entries(additionalHeaders || {})) {\n\t\tif (value === null) {\n\t\t\theaders.delete(key);\n\t\t} else {\n\t\t\theaders.set(key, value);\n\t\t}\n\t}\n\theaders.set(\"Authorization\", `Bearer ${token}`);\n\theaders.set(\"chatgpt-account-id\", accountId);\n\theaders.set(\"originator\", \"pi\");\n\tconst userAgent = _os ? `pi (${_os.platform()} ${_os.release()}; ${_os.arch()})` : \"pi (browser)\";\n\theaders.set(\"User-Agent\", userAgent);\n\treturn headers;\n}\n\nfunction buildSSEHeaders(\n\tinitHeaders: Record<string, string> | undefined,\n\tadditionalHeaders: ProviderHeaders | undefined,\n\taccountId: string,\n\ttoken: string,\n\tsessionId?: string,\n): Headers {\n\tconst headers = buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token);\n\theaders.set(\"OpenAI-Beta\", \"responses=experimental\");\n\theaders.set(\"accept\", \"text/event-stream\");\n\theaders.set(\"content-type\", \"application/json\");\n\n\tif (sessionId) {\n\t\theaders.set(\"session-id\", sessionId);\n\t\theaders.set(\"x-client-request-id\", sessionId);\n\t}\n\n\treturn headers;\n}\n\nfunction buildWebSocketHeaders(\n\tinitHeaders: Record<string, string> | undefined,\n\tadditionalHeaders: ProviderHeaders | undefined,\n\taccountId: string,\n\ttoken: string,\n\trequestId: string,\n): Headers {\n\tconst headers = buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token);\n\theaders.delete(\"accept\");\n\theaders.delete(\"content-type\");\n\theaders.delete(\"OpenAI-Beta\");\n\theaders.delete(\"openai-beta\");\n\theaders.set(\"OpenAI-Beta\", OPENAI_BETA_RESPONSES_WEBSOCKETS);\n\theaders.set(\"x-client-request-id\", requestId);\n\theaders.set(\"session-id\", requestId);\n\treturn headers;\n}\n"]}
1
+ {"version":3,"file":"openai-codex-responses.d.ts","sourceRoot":"","sources":["../../src/api/openai-codex-responses.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEX,6BAA6B,EAG7B,MAAM,yCAAyC,CAAC;AAkBjD,OAAO,KAAK,EAOX,mBAAmB,EACnB,cAAc,EACd,aAAa,EAEb,MAAM,aAAa,CAAC;AA6CrB,MAAM,WAAW,2BAA4B,SAAQ,aAAa;IACjE,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IAC3E,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,CAAC;IACzE,WAAW,CAAC,EAAE,6BAA6B,CAAC,cAAc,CAAC,CAAC;IAC5D,aAAa,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;CAC1C;AAsID,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,wBAAwB,EAAE,2BAA2B,CAwOxF,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,wBAAwB,EAAE,mBAAmB,CAkBtF,CAAC;AAkTF,MAAM,WAAW,8BAA8B;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AA0BD,wBAAgB,iCAAiC,CAAC,SAAS,EAAE,MAAM,GAAG,8BAA8B,GAAG,SAAS,CAG/G;AAED,wBAAgB,mCAAmC,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAQ5E;AAED,wBAAgB,iCAAiC,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAe1E","sourcesContent":["import type * as NodeOs from \"node:os\";\nimport type * as NodeZlib from \"node:zlib\";\nimport type {\n\tTool as OpenAITool,\n\tResponseCreateParamsStreaming,\n\tResponseInput,\n\tResponseStreamEvent,\n} from \"openai/resources/responses/responses.js\";\n\ntype ProcessWithOsBuiltinModule = typeof process & {\n\tgetBuiltinModule?: (id: \"node:os\") => typeof NodeOs;\n};\n\nfunction loadNodeOs(): typeof NodeOs | null {\n\tif (typeof process === \"undefined\" || !(process.versions?.node || process.versions?.bun)) {\n\t\treturn null;\n\t}\n\treturn (process as ProcessWithOsBuiltinModule).getBuiltinModule?.(\"node:os\") ?? null;\n}\n\n// NEVER convert to top-level runtime imports - breaks browser/Vite builds\nconst _os: typeof NodeOs | null = loadNodeOs();\n\nimport { clampThinkingLevel } from \"../models.ts\";\nimport { registerSessionResourceCleanup } from \"../session-resources.ts\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tContext,\n\tModel,\n\tProviderEnv,\n\tProviderHeaders,\n\tSimpleStreamOptions,\n\tStreamFunction,\n\tStreamOptions,\n\tUsage,\n} from \"../types.ts\";\nimport { combineAbortSignals } from \"../utils/abort-signals.ts\";\nimport {\n\tappendAssistantMessageDiagnostic,\n\tcreateAssistantMessageDiagnostic,\n\tformatThrownValue,\n} from \"../utils/diagnostics.ts\";\nimport { formatProviderError, normalizeProviderError } from \"../utils/error-body.ts\";\nimport { AssistantMessageEventStream } from \"../utils/event-stream.ts\";\nimport { headersToRecord } from \"../utils/headers.ts\";\nimport { resolveHttpProxyUrlForTarget } from \"../utils/node-http-proxy.ts\";\nimport { clampOpenAIPromptCacheKey } from \"./openai-prompt-cache.ts\";\nimport { convertResponsesMessages, convertResponsesTools, processResponsesStream } from \"./openai-responses-shared.ts\";\nimport { buildBaseOptions } from \"./simple-options.ts\";\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nconst DEFAULT_CODEX_BASE_URL = \"https://chatgpt.com/backend-api\";\nconst JWT_CLAIM_PATH = \"https://api.openai.com/auth\" as const;\nconst DEFAULT_MAX_RETRIES = 0;\nconst BASE_DELAY_MS = 1000;\nconst DEFAULT_MAX_RETRY_DELAY_MS = 60_000;\nconst DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS = 15_000;\n// The Codex backend accepts zstd-compressed request bodies on the SSE responses\n// endpoint (the same endpoint the official Codex client compresses against).\nconst REQUEST_COMPRESSION_ZSTD_LEVEL = 3;\nconst CODEX_TOOL_CALL_PROVIDERS = new Set([\"openai\", \"openai-codex\", \"opencode\"]);\nconst WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE = 1009;\nconst WEBSOCKET_CONNECTION_LIMIT_REACHED_CODE = \"websocket_connection_limit_reached\";\n\nconst CODEX_RESPONSE_STATUSES = new Set<CodexResponseStatus>([\n\t\"completed\",\n\t\"incomplete\",\n\t\"failed\",\n\t\"cancelled\",\n\t\"queued\",\n\t\"in_progress\",\n]);\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface OpenAICodexResponsesOptions extends StreamOptions {\n\treasoningEffort?: \"none\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\";\n\treasoningSummary?: \"auto\" | \"concise\" | \"detailed\" | \"off\" | \"on\" | null;\n\tserviceTier?: ResponseCreateParamsStreaming[\"service_tier\"];\n\ttextVerbosity?: \"low\" | \"medium\" | \"high\";\n}\n\ntype CodexResponseStatus = \"completed\" | \"incomplete\" | \"failed\" | \"cancelled\" | \"queued\" | \"in_progress\";\n\ninterface RequestBody {\n\tmodel: string;\n\tstore?: boolean;\n\tstream?: boolean;\n\tinstructions?: string;\n\tprevious_response_id?: string;\n\tinput?: ResponseInput;\n\ttools?: OpenAITool[];\n\ttool_choice?: \"auto\";\n\tparallel_tool_calls?: boolean;\n\ttemperature?: number;\n\treasoning?: { effort?: string; summary?: string };\n\tservice_tier?: ResponseCreateParamsStreaming[\"service_tier\"];\n\ttext?: { verbosity?: string };\n\tinclude?: string[];\n\tprompt_cache_key?: string;\n\t[key: string]: unknown;\n}\n\n// ============================================================================\n// Retry Helpers\n// ============================================================================\n\nfunction isTerminalRateLimitError(errorText: string): boolean {\n\treturn /GoUsageLimitError|FreeUsageLimitError|Monthly usage limit reached|available balance|insufficient_quota|out of budget|quota exceeded|billing/i.test(\n\t\terrorText,\n\t);\n}\n\nfunction isRetryableError(status: number, errorText: string): boolean {\n\tif (status === 429 && isTerminalRateLimitError(errorText)) {\n\t\treturn false;\n\t}\n\tif (status === 429 || status === 500 || status === 502 || status === 503 || status === 504) {\n\t\treturn true;\n\t}\n\treturn /rate.?limit|overloaded|service.?unavailable|upstream.?connect|connection.?refused/i.test(errorText);\n}\n\nfunction getRetryAfterDelayMs(headers: Headers): number | undefined {\n\tconst retryAfterMs = headers.get(\"retry-after-ms\");\n\tif (retryAfterMs !== null) {\n\t\tconst millis = Number(retryAfterMs);\n\t\tif (Number.isFinite(millis)) {\n\t\t\treturn Math.max(0, millis);\n\t\t}\n\t}\n\n\tconst retryAfter = headers.get(\"retry-after\");\n\tif (!retryAfter) {\n\t\treturn undefined;\n\t}\n\n\tconst seconds = Number(retryAfter);\n\tif (Number.isFinite(seconds)) {\n\t\treturn Math.max(0, seconds * 1000);\n\t}\n\n\tconst date = Date.parse(retryAfter);\n\tif (!Number.isNaN(date)) {\n\t\treturn Math.max(0, date - Date.now());\n\t}\n\n\treturn undefined;\n}\n\nfunction capRetryDelayMs(delayMs: number, options?: StreamOptions): number {\n\tconst maxRetryDelayMs = options?.maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;\n\treturn maxRetryDelayMs > 0 ? Math.min(delayMs, maxRetryDelayMs) : delayMs;\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(new Error(\"Request was aborted\"));\n\t\t\treturn;\n\t\t}\n\t\tconst timeout = setTimeout(resolve, ms);\n\t\tsignal?.addEventListener(\"abort\", () => {\n\t\t\tclearTimeout(timeout);\n\t\t\treject(new Error(\"Request was aborted\"));\n\t\t});\n\t});\n}\n\nfunction normalizeTimeoutMs(value: number | undefined): number | undefined {\n\tif (value === undefined) return undefined;\n\tif (!Number.isFinite(value) || value < 0) {\n\t\tthrow new Error(`Invalid timeoutMs: ${String(value)}`);\n\t}\n\treturn Math.floor(value);\n}\n\n// ============================================================================\n// Request Compression\n// ============================================================================\n\ntype ProcessWithBuiltinModule = typeof process & {\n\tgetBuiltinModule?: (id: \"node:zlib\") => typeof NodeZlib;\n};\n\nfunction loadNodeZlib(): typeof NodeZlib | null {\n\tif (typeof process === \"undefined\" || !(process.versions?.node || process.versions?.bun)) {\n\t\treturn null;\n\t}\n\treturn (process as ProcessWithBuiltinModule).getBuiltinModule?.(\"node:zlib\") ?? null;\n}\n\n// Returns the zstd-compressed body bytes, or null when compression is\n// unavailable (browser/Vite builds). Callers fall back to sending the\n// uncompressed JSON when this returns null.\nfunction compressRequestBodyZstd(bodyJson: string): Uint8Array | null {\n\tconst zlib = loadNodeZlib();\n\tif (!zlib || typeof zlib.zstdCompressSync !== \"function\") {\n\t\treturn null;\n\t}\n\ttry {\n\t\tconst compressed = zlib.zstdCompressSync(bodyJson, {\n\t\t\tparams: { [zlib.constants.ZSTD_c_compressionLevel]: REQUEST_COMPRESSION_ZSTD_LEVEL },\n\t\t});\n\t\treturn new Uint8Array(compressed.buffer, compressed.byteOffset, compressed.byteLength);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n// ============================================================================\n// Main Stream Function\n// ============================================================================\n\nexport const stream: StreamFunction<\"openai-codex-responses\", OpenAICodexResponsesOptions> = (\n\tmodel: Model<\"openai-codex-responses\">,\n\tcontext: Context,\n\toptions?: OpenAICodexResponsesOptions,\n): AssistantMessageEventStream => {\n\tconst stream = new AssistantMessageEventStream();\n\n\t(async () => {\n\t\tconst output: AssistantMessage = {\n\t\t\trole: \"assistant\",\n\t\t\tcontent: [],\n\t\t\tapi: \"openai-codex-responses\" as Api,\n\t\t\tprovider: model.provider,\n\t\t\tmodel: model.id,\n\t\t\tusage: {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\ttotalTokens: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t},\n\t\t\tstopReason: \"stop\",\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\n\t\ttry {\n\t\t\tconst apiKey = options?.apiKey;\n\t\t\tif (!apiKey) {\n\t\t\t\tthrow new Error(`No API key for provider: ${model.provider}`);\n\t\t\t}\n\n\t\t\tconst accountId = extractAccountId(apiKey);\n\t\t\tlet body = buildRequestBody(model, context, options);\n\t\t\tconst nextBody = await options?.onPayload?.(body, model);\n\t\t\tif (nextBody !== undefined) {\n\t\t\t\tbody = nextBody as RequestBody;\n\t\t\t}\n\t\t\tconst websocketRequestId = options?.sessionId || createCodexRequestId();\n\t\t\tconst sseHeaders = buildSSEHeaders(model.headers, options?.headers, accountId, apiKey, options?.sessionId);\n\t\t\tconst websocketHeaders = buildWebSocketHeaders(\n\t\t\t\tmodel.headers,\n\t\t\t\toptions?.headers,\n\t\t\t\taccountId,\n\t\t\t\tapiKey,\n\t\t\t\twebsocketRequestId,\n\t\t\t);\n\t\t\tconst bodyJson = JSON.stringify(body);\n\t\t\tconst httpTimeoutMs = normalizeTimeoutMs(options?.timeoutMs);\n\t\t\tconst websocketConnectTimeoutMs = normalizeTimeoutMs(options?.websocketConnectTimeoutMs);\n\t\t\tconst transport = options?.transport || \"auto\";\n\t\t\tconst websocketDisabledForSession = transport !== \"sse\" && isWebSocketSseFallbackActive(options?.sessionId);\n\t\t\tif (websocketDisabledForSession) {\n\t\t\t\trecordWebSocketSseFallback(options?.sessionId);\n\t\t\t}\n\n\t\t\tif (transport !== \"sse\" && !websocketDisabledForSession) {\n\t\t\t\tlet websocketStarted = false;\n\t\t\t\tlet retriedWebSocketConnectionLimit = false;\n\t\t\t\twhile (true) {\n\t\t\t\t\twebsocketStarted = false;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait processWebSocketStream(\n\t\t\t\t\t\t\tresolveCodexWebSocketUrl(model.baseUrl),\n\t\t\t\t\t\t\tbody,\n\t\t\t\t\t\t\twebsocketHeaders,\n\t\t\t\t\t\t\toutput,\n\t\t\t\t\t\t\tstream,\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\t\twebsocketStarted = true;\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\thttpTimeoutMs,\n\t\t\t\t\t\t\twebsocketConnectTimeoutMs,\n\t\t\t\t\t\t\toptions,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tif (options?.signal?.aborted) {\n\t\t\t\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\ttype: \"done\",\n\t\t\t\t\t\t\treason: output.stopReason as \"stop\" | \"length\" | \"toolUse\",\n\t\t\t\t\t\t\tmessage: output,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tstream.end();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst aborted = options?.signal?.aborted;\n\t\t\t\t\t\tconst connectionLimitBeforeStart = !websocketStarted && isWebSocketConnectionLimitReachedError(error);\n\t\t\t\t\t\tif (!aborted && connectionLimitBeforeStart && !retriedWebSocketConnectionLimit) {\n\t\t\t\t\t\t\tretriedWebSocketConnectionLimit = true;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (aborted || (isCodexNonTransportError(error) && !connectionLimitBeforeStart)) {\n\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tappendAssistantMessageDiagnostic(\n\t\t\t\t\t\t\toutput,\n\t\t\t\t\t\t\tcreateAssistantMessageDiagnostic(\"provider_transport_failure\", error, {\n\t\t\t\t\t\t\t\tconfiguredTransport: transport,\n\t\t\t\t\t\t\t\tfallbackTransport: websocketStarted ? undefined : \"sse\",\n\t\t\t\t\t\t\t\teventsEmitted: websocketStarted,\n\t\t\t\t\t\t\t\tphase: websocketStarted ? \"after_message_stream_start\" : \"before_message_stream_start\",\n\t\t\t\t\t\t\t\trequestBytes: new TextEncoder().encode(bodyJson).byteLength,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t\trecordWebSocketFailure(options?.sessionId, error);\n\t\t\t\t\t\tif (websocketStarted) {\n\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t}\n\t\t\t\t\t\trecordWebSocketSseFallback(options?.sessionId);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Compress the request body once for the SSE path. The Codex backend\n\t\t\t// decodes Content-Encoding: zstd; the WebSocket transport above sends the\n\t\t\t// uncompressed JSON frame, matching the official Codex client.\n\t\t\tconst compressedBody = compressRequestBodyZstd(bodyJson);\n\t\t\tif (compressedBody) {\n\t\t\t\tsseHeaders.set(\"content-encoding\", \"zstd\");\n\t\t\t}\n\t\t\tconst sseBody: Uint8Array | string = compressedBody ?? bodyJson;\n\n\t\t\t// Fetch with retry logic for rate limits and transient errors\n\t\t\tlet response: Response | undefined;\n\t\t\tlet lastError: Error | undefined;\n\t\t\tconst maxRetries = options?.maxRetries ?? DEFAULT_MAX_RETRIES;\n\n\t\t\tfor (let attempt = 0; attempt <= maxRetries; attempt++) {\n\t\t\t\tif (options?.signal?.aborted) {\n\t\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst headerTimeoutSignal =\n\t\t\t\t\t\thttpTimeoutMs !== undefined && httpTimeoutMs > 0 ? AbortSignal.timeout(httpTimeoutMs) : undefined;\n\t\t\t\t\tconst combinedSignal = combineAbortSignals([options?.signal, headerTimeoutSignal]);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tresponse = await fetch(resolveCodexUrl(model.baseUrl), {\n\t\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\t\theaders: sseHeaders,\n\t\t\t\t\t\t\tbody: sseBody,\n\t\t\t\t\t\t\tsignal: combinedSignal.signal,\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tif (headerTimeoutSignal?.aborted && !options?.signal?.aborted) {\n\t\t\t\t\t\t\tthrow new Error(`Codex SSE response headers timed out after ${httpTimeoutMs}ms`);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tcombinedSignal.cleanup();\n\t\t\t\t\t}\n\t\t\t\t\tawait options?.onResponse?.(\n\t\t\t\t\t\t{ status: response.status, headers: headersToRecord(response.headers) },\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t);\n\n\t\t\t\t\tif (response.ok) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst errorText = await response.text();\n\t\t\t\t\tif (attempt < maxRetries && isRetryableError(response.status, errorText)) {\n\t\t\t\t\t\tconst retryAfterDelayMs = getRetryAfterDelayMs(response.headers);\n\t\t\t\t\t\tconst delayMs =\n\t\t\t\t\t\t\tretryAfterDelayMs === undefined\n\t\t\t\t\t\t\t\t? BASE_DELAY_MS * 2 ** attempt\n\t\t\t\t\t\t\t\t: response.status === 429\n\t\t\t\t\t\t\t\t\t? capRetryDelayMs(retryAfterDelayMs, options)\n\t\t\t\t\t\t\t\t\t: retryAfterDelayMs;\n\n\t\t\t\t\t\tawait sleep(delayMs, options?.signal);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Parse error for friendly message on final attempt or non-retryable error\n\t\t\t\t\tconst fakeResponse = new Response(errorText, {\n\t\t\t\t\t\tstatus: response.status,\n\t\t\t\t\t\tstatusText: response.statusText,\n\t\t\t\t\t});\n\t\t\t\t\tconst info = await parseErrorResponse(fakeResponse);\n\t\t\t\t\tthrow new Error(info.friendlyMessage || info.message);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof Error) {\n\t\t\t\t\t\tif (error.name === \"AbortError\" || error.message === \"Request was aborted\") {\n\t\t\t\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tlastError = error instanceof Error ? error : new Error(String(error));\n\t\t\t\t\t// Network errors are retryable\n\t\t\t\t\tif (attempt < maxRetries && !lastError.message.includes(\"usage limit\")) {\n\t\t\t\t\t\tconst delayMs = BASE_DELAY_MS * 2 ** attempt;\n\t\t\t\t\t\tawait sleep(delayMs, options?.signal);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tthrow lastError;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!response?.ok) {\n\t\t\t\tthrow lastError ?? new Error(\"Failed after retries\");\n\t\t\t}\n\n\t\t\tif (!response.body) {\n\t\t\t\tthrow new Error(\"No response body\");\n\t\t\t}\n\n\t\t\tstream.push({ type: \"start\", partial: output });\n\t\t\tawait processStream(response, output, stream, model, options);\n\n\t\t\tif (options?.signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\n\t\t\tstream.push({ type: \"done\", reason: output.stopReason as \"stop\" | \"length\" | \"toolUse\", message: output });\n\t\t\tstream.end();\n\t\t} catch (error) {\n\t\t\tfor (const block of output.content) {\n\t\t\t\t// partialJson is only a streaming scratch buffer; never persist it.\n\t\t\t\tdelete (block as { partialJson?: string }).partialJson;\n\t\t\t}\n\t\t\toutput.stopReason = options?.signal?.aborted ? \"aborted\" : \"error\";\n\t\t\toutput.errorMessage = formatProviderError(normalizeProviderError(error));\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\nexport const streamSimple: StreamFunction<\"openai-codex-responses\", SimpleStreamOptions> = (\n\tmodel: Model<\"openai-codex-responses\">,\n\tcontext: Context,\n\toptions?: SimpleStreamOptions,\n): AssistantMessageEventStream => {\n\tconst apiKey = options?.apiKey;\n\tif (!apiKey) {\n\t\tthrow new Error(`No API key for provider: ${model.provider}`);\n\t}\n\n\tconst base = buildBaseOptions(model, context, options, apiKey);\n\tconst clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined;\n\tconst reasoningEffort = clampedReasoning === \"off\" ? undefined : clampedReasoning;\n\n\treturn stream(model, context, {\n\t\t...base,\n\t\treasoningEffort,\n\t} satisfies OpenAICodexResponsesOptions);\n};\n\n// ============================================================================\n// Request Building\n// ============================================================================\n\nfunction buildRequestBody(\n\tmodel: Model<\"openai-codex-responses\">,\n\tcontext: Context,\n\toptions?: OpenAICodexResponsesOptions,\n): RequestBody {\n\tconst messages = convertResponsesMessages(model, context, CODEX_TOOL_CALL_PROVIDERS, {\n\t\tincludeSystemPrompt: false,\n\t});\n\n\tconst body: RequestBody = {\n\t\tmodel: model.id,\n\t\tstore: false,\n\t\tstream: true,\n\t\tinstructions: context.systemPrompt || \"You are a helpful assistant.\",\n\t\tinput: messages,\n\t\ttext: { verbosity: options?.textVerbosity || \"low\" },\n\t\tinclude: [\"reasoning.encrypted_content\"],\n\t\tprompt_cache_key: clampOpenAIPromptCacheKey(options?.sessionId),\n\t\ttool_choice: \"auto\",\n\t\tparallel_tool_calls: true,\n\t};\n\n\tif (options?.temperature !== undefined) {\n\t\tbody.temperature = options.temperature;\n\t}\n\n\tif (options?.serviceTier !== undefined) {\n\t\tbody.service_tier = options.serviceTier;\n\t}\n\n\tif (context.tools && context.tools.length > 0) {\n\t\tbody.tools = convertResponsesTools(context.tools, { strict: null });\n\t}\n\n\tif (options?.reasoningEffort !== undefined) {\n\t\tconst effort =\n\t\t\toptions.reasoningEffort === \"none\"\n\t\t\t\t? (model.thinkingLevelMap?.off ?? \"none\")\n\t\t\t\t: (model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort);\n\t\tif (effort !== null) {\n\t\t\tbody.reasoning = {\n\t\t\t\teffort,\n\t\t\t\tsummary: options.reasoningSummary ?? \"auto\",\n\t\t\t};\n\t\t}\n\t}\n\n\treturn body;\n}\n\nfunction getServiceTierCostMultiplier(\n\tmodel: Pick<Model<\"openai-codex-responses\">, \"id\">,\n\tserviceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n): number {\n\tswitch (serviceTier) {\n\t\tcase \"flex\":\n\t\t\treturn 0.5;\n\t\tcase \"priority\":\n\t\t\treturn model.id === \"gpt-5.5\" ? 2.5 : 2;\n\t\tdefault:\n\t\t\treturn 1;\n\t}\n}\n\nfunction applyServiceTierPricing(\n\tusage: Usage,\n\tserviceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n\tmodel: Pick<Model<\"openai-codex-responses\">, \"id\">,\n) {\n\tconst multiplier = getServiceTierCostMultiplier(model, serviceTier);\n\tif (multiplier === 1) return;\n\n\tusage.cost.input *= multiplier;\n\tusage.cost.output *= multiplier;\n\tusage.cost.cacheRead *= multiplier;\n\tusage.cost.cacheWrite *= multiplier;\n\tusage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;\n}\n\nfunction resolveCodexServiceTier(\n\tresponseServiceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n\trequestServiceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n): ResponseCreateParamsStreaming[\"service_tier\"] | undefined {\n\tif (responseServiceTier === \"default\" && (requestServiceTier === \"flex\" || requestServiceTier === \"priority\")) {\n\t\treturn requestServiceTier;\n\t}\n\treturn responseServiceTier ?? requestServiceTier;\n}\n\nfunction resolveCodexUrl(baseUrl?: string): string {\n\tconst raw = baseUrl && baseUrl.trim().length > 0 ? baseUrl : DEFAULT_CODEX_BASE_URL;\n\tconst normalized = raw.replace(/\\/+$/, \"\");\n\tif (normalized.endsWith(\"/codex/responses\")) return normalized;\n\tif (normalized.endsWith(\"/codex\")) return `${normalized}/responses`;\n\treturn `${normalized}/codex/responses`;\n}\n\nfunction resolveCodexWebSocketUrl(baseUrl?: string): string {\n\tconst url = new URL(resolveCodexUrl(baseUrl));\n\tif (url.protocol === \"https:\") url.protocol = \"wss:\";\n\tif (url.protocol === \"http:\") url.protocol = \"ws:\";\n\treturn url.toString();\n}\n\n// ============================================================================\n// Response Processing\n// ============================================================================\n\nasync function processStream(\n\tresponse: Response,\n\toutput: AssistantMessage,\n\tstream: AssistantMessageEventStream,\n\tmodel: Model<\"openai-codex-responses\">,\n\toptions?: OpenAICodexResponsesOptions,\n): Promise<void> {\n\tawait processResponsesStream(mapCodexEvents(parseSSE(response, options?.signal)), output, stream, model, {\n\t\tserviceTier: options?.serviceTier,\n\t\tresolveServiceTier: resolveCodexServiceTier,\n\t\tapplyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),\n\t});\n}\n\nclass CodexApiError extends Error {\n\treadonly code?: string;\n\treadonly payload?: Record<string, unknown>;\n\n\tconstructor(message: string, options?: { code?: string; payload?: Record<string, unknown>; cause?: unknown }) {\n\t\tsuper(message);\n\t\tthis.name = \"CodexApiError\";\n\t\tthis.code = options?.code;\n\t\tthis.payload = options?.payload;\n\t\tthis.cause = options?.cause;\n\t}\n}\n\nclass CodexProtocolError extends Error {\n\treadonly payload?: unknown;\n\n\tconstructor(message: string, options?: { payload?: unknown; cause?: unknown }) {\n\t\tsuper(message);\n\t\tthis.name = \"CodexProtocolError\";\n\t\tthis.payload = options?.payload;\n\t\tthis.cause = options?.cause;\n\t}\n}\n\nfunction isCodexNonTransportError(error: unknown): boolean {\n\treturn error instanceof CodexApiError || error instanceof CodexProtocolError;\n}\n\nfunction isWebSocketConnectionLimitReachedError(error: unknown): boolean {\n\treturn error instanceof CodexApiError && error.code === WEBSOCKET_CONNECTION_LIMIT_REACHED_CODE;\n}\n\nfunction extractCodexEventError(event: Record<string, unknown>): { code?: string; message?: string } {\n\tconst nested = event.error && typeof event.error === \"object\" ? (event.error as Record<string, unknown>) : undefined;\n\treturn {\n\t\tcode: typeof event.code === \"string\" ? event.code : typeof nested?.code === \"string\" ? nested.code : undefined,\n\t\tmessage:\n\t\t\ttypeof event.message === \"string\"\n\t\t\t\t? event.message\n\t\t\t\t: typeof nested?.message === \"string\"\n\t\t\t\t\t? nested.message\n\t\t\t\t\t: undefined,\n\t};\n}\n\nasync function* mapCodexEvents(events: AsyncIterable<Record<string, unknown>>): AsyncGenerator<ResponseStreamEvent> {\n\tfor await (const event of events) {\n\t\tconst type = typeof event.type === \"string\" ? event.type : undefined;\n\t\tif (!type) continue;\n\n\t\tif (type === \"error\") {\n\t\t\tconst { code, message } = extractCodexEventError(event);\n\t\t\tthrow new CodexApiError(`Codex error: ${message || code || JSON.stringify(event)}`, {\n\t\t\t\tcode,\n\t\t\t\tpayload: event,\n\t\t\t});\n\t\t}\n\n\t\tif (type === \"response.failed\") {\n\t\t\tconst response = (event as { response?: { error?: { code?: string; message?: string } } }).response;\n\t\t\tconst code = response?.error?.code;\n\t\t\tconst message = response?.error?.message;\n\t\t\tthrow new CodexApiError(message || \"Codex response failed\", { code, payload: event });\n\t\t}\n\n\t\tif (type === \"response.done\" || type === \"response.completed\" || type === \"response.incomplete\") {\n\t\t\tconst response = (event as { response?: { status?: unknown } }).response;\n\t\t\tconst normalizedResponse = response\n\t\t\t\t? { ...response, status: normalizeCodexStatus(response.status) }\n\t\t\t\t: response;\n\t\t\tyield { ...event, type: \"response.completed\", response: normalizedResponse } as ResponseStreamEvent;\n\t\t\treturn;\n\t\t}\n\n\t\tyield event as unknown as ResponseStreamEvent;\n\t}\n}\n\nfunction normalizeCodexStatus(status: unknown): CodexResponseStatus | undefined {\n\tif (typeof status !== \"string\") return undefined;\n\treturn CODEX_RESPONSE_STATUSES.has(status as CodexResponseStatus) ? (status as CodexResponseStatus) : undefined;\n}\n\n// ============================================================================\n// SSE Parsing\n// ============================================================================\n\nasync function* parseSSE(response: Response, signal?: AbortSignal): AsyncGenerator<Record<string, unknown>> {\n\tif (!response.body) return;\n\n\tconst reader = response.body.getReader();\n\tconst decoder = new TextDecoder();\n\tlet buffer = \"\";\n\tconst onAbort = () => {\n\t\tvoid reader.cancel().catch(() => {});\n\t};\n\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\ttry {\n\t\twhile (true) {\n\t\t\tif (signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\t\t\tconst { done, value } = await reader.read();\n\t\t\tif (signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\t\t\tif (done) break;\n\t\t\tbuffer += decoder.decode(value, { stream: true });\n\n\t\t\tlet idx = buffer.indexOf(\"\\n\\n\");\n\t\t\twhile (idx !== -1) {\n\t\t\t\tconst chunk = buffer.slice(0, idx);\n\t\t\t\tbuffer = buffer.slice(idx + 2);\n\n\t\t\t\tconst dataLines = chunk\n\t\t\t\t\t.split(\"\\n\")\n\t\t\t\t\t.filter((l) => l.startsWith(\"data:\"))\n\t\t\t\t\t.map((l) => l.slice(5).trim());\n\t\t\t\tif (dataLines.length > 0) {\n\t\t\t\t\tconst data = dataLines.join(\"\\n\").trim();\n\t\t\t\t\tif (data && data !== \"[DONE]\") {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tyield JSON.parse(data) as Record<string, unknown>;\n\t\t\t\t\t\t} catch (cause) {\n\t\t\t\t\t\t\tthrow new CodexProtocolError(`Invalid Codex SSE JSON: ${formatThrownValue(cause)}`, {\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\tpayload: data,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tidx = buffer.indexOf(\"\\n\\n\");\n\t\t\t}\n\t\t}\n\t} finally {\n\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\ttry {\n\t\t\tawait reader.cancel();\n\t\t} catch {}\n\t\ttry {\n\t\t\treader.releaseLock();\n\t\t} catch {}\n\t}\n}\n\n// ============================================================================\n// WebSocket Parsing\n// ============================================================================\n\nconst OPENAI_BETA_RESPONSES_WEBSOCKETS = \"responses_websockets=2026-02-06\";\nconst SESSION_WEBSOCKET_CACHE_TTL_MS = 5 * 60 * 1000;\nconst SESSION_WEBSOCKET_MAX_AGE_MS = 55 * 60 * 1000;\n\ntype WebSocketEventType = \"open\" | \"message\" | \"error\" | \"close\";\ntype WebSocketListener = (event: unknown) => void;\n\ninterface WebSocketLike {\n\tclose(code?: number, reason?: string): void;\n\tsend(data: string): void;\n\taddEventListener(type: WebSocketEventType, listener: WebSocketListener): void;\n\tremoveEventListener(type: WebSocketEventType, listener: WebSocketListener): void;\n}\n\ninterface CachedWebSocketContinuationState {\n\tlastRequestBody: RequestBody;\n\tlastResponseId: string;\n\tlastResponseItems: ResponseInput;\n}\n\ninterface CachedWebSocketConnection {\n\tsocket: WebSocketLike;\n\tbusy: boolean;\n\tcreatedAt: number;\n\tidleTimer?: ReturnType<typeof setTimeout>;\n\tcontinuation?: CachedWebSocketContinuationState;\n}\n\nexport interface OpenAICodexWebSocketDebugStats {\n\trequests: number;\n\tconnectionsCreated: number;\n\tconnectionsReused: number;\n\tcachedContextRequests: number;\n\tstoreTrueRequests: number;\n\tfullContextRequests: number;\n\tdeltaRequests: number;\n\tlastInputItems: number;\n\tlastDeltaInputItems?: number;\n\tlastPreviousResponseId?: string;\n\twebsocketFailures: number;\n\tsseFallbacks: number;\n\twebsocketFallbackActive?: boolean;\n\tlastWebSocketError?: string;\n}\n\nconst websocketSessionCache = new Map<string, CachedWebSocketConnection>();\nconst websocketDebugStats = new Map<string, OpenAICodexWebSocketDebugStats>();\nconst websocketSseFallbackSessions = new Set<string>();\n\nfunction getOrCreateWebSocketDebugStats(sessionId: string): OpenAICodexWebSocketDebugStats {\n\tlet stats = websocketDebugStats.get(sessionId);\n\tif (!stats) {\n\t\tstats = {\n\t\t\trequests: 0,\n\t\t\tconnectionsCreated: 0,\n\t\t\tconnectionsReused: 0,\n\t\t\tcachedContextRequests: 0,\n\t\t\tstoreTrueRequests: 0,\n\t\t\tfullContextRequests: 0,\n\t\t\tdeltaRequests: 0,\n\t\t\tlastInputItems: 0,\n\t\t\twebsocketFailures: 0,\n\t\t\tsseFallbacks: 0,\n\t\t};\n\t\twebsocketDebugStats.set(sessionId, stats);\n\t}\n\treturn stats;\n}\n\nexport function getOpenAICodexWebSocketDebugStats(sessionId: string): OpenAICodexWebSocketDebugStats | undefined {\n\tconst stats = websocketDebugStats.get(sessionId);\n\treturn stats ? { ...stats } : undefined;\n}\n\nexport function resetOpenAICodexWebSocketDebugStats(sessionId?: string): void {\n\tif (sessionId) {\n\t\twebsocketDebugStats.delete(sessionId);\n\t\twebsocketSseFallbackSessions.delete(sessionId);\n\t\treturn;\n\t}\n\twebsocketDebugStats.clear();\n\twebsocketSseFallbackSessions.clear();\n}\n\nexport function closeOpenAICodexWebSocketSessions(sessionId?: string): void {\n\tconst closeEntry = (entry: CachedWebSocketConnection) => {\n\t\tif (entry.idleTimer) clearTimeout(entry.idleTimer);\n\t\tcloseWebSocketSilently(entry.socket, 1000, \"debug_close\");\n\t};\n\tif (sessionId) {\n\t\tconst entry = websocketSessionCache.get(sessionId);\n\t\tif (entry) closeEntry(entry);\n\t\twebsocketSessionCache.delete(sessionId);\n\t\treturn;\n\t}\n\tfor (const entry of websocketSessionCache.values()) {\n\t\tcloseEntry(entry);\n\t}\n\twebsocketSessionCache.clear();\n}\n\nregisterSessionResourceCleanup(closeOpenAICodexWebSocketSessions);\n\nfunction isWebSocketSseFallbackActive(sessionId: string | undefined): boolean {\n\treturn sessionId ? websocketSseFallbackSessions.has(sessionId) : false;\n}\n\nfunction recordWebSocketSseFallback(sessionId: string | undefined): void {\n\tif (!sessionId) return;\n\tconst stats = getOrCreateWebSocketDebugStats(sessionId);\n\tstats.sseFallbacks++;\n\tstats.websocketFallbackActive = isWebSocketSseFallbackActive(sessionId);\n}\n\nfunction recordWebSocketFailure(sessionId: string | undefined, error: unknown): void {\n\tif (!sessionId) return;\n\twebsocketSseFallbackSessions.add(sessionId);\n\n\tconst stats = getOrCreateWebSocketDebugStats(sessionId);\n\tstats.websocketFailures++;\n\tstats.lastWebSocketError = formatThrownValue(error);\n\tstats.websocketFallbackActive = true;\n}\n\ntype WebSocketConstructor = new (\n\turl: string,\n\tprotocols?: string | string[] | { headers?: Record<string, string> },\n) => WebSocketLike;\n\nlet _cachedWebsocket: WebSocketConstructor | null = null;\nasync function getWebSocketConstructor(env?: ProviderEnv): Promise<WebSocketConstructor | null> {\n\tif (!env && _cachedWebsocket) return _cachedWebsocket;\n\n\t// bun doesn't respect http proxy envs, ref: https://github.com/oven-sh/bun/issues/15489\n\t// TODO: remove this when bun supports proxy envs in websocket.\n\tif (typeof process !== \"undefined\" && process.versions?.bun) {\n\t\tconst WebSocketWithProxy = class extends WebSocket {\n\t\t\tconstructor(url: string | URL, options?: string | string[] | Record<string, unknown>) {\n\t\t\t\tlet _opts: Record<string, unknown> = {};\n\t\t\t\tif (Array.isArray(options) || typeof options === \"string\") {\n\t\t\t\t\t_opts = { protocols: options };\n\t\t\t\t} else {\n\t\t\t\t\t_opts = { ...options };\n\t\t\t\t}\n\n\t\t\t\tconst proxyUrl = resolveHttpProxyUrlForTarget(\n\t\t\t\t\turl.toString().replace(/^wss:/, \"https:\").replace(/^ws:/, \"http:\"),\n\t\t\t\t\tenv,\n\t\t\t\t);\n\t\t\t\tsuper(url, { ..._opts, ...(proxyUrl ? { proxy: proxyUrl.toString() } : {}) } as any);\n\t\t\t}\n\t\t};\n\t\tif (!env) {\n\t\t\t_cachedWebsocket = WebSocketWithProxy;\n\t\t}\n\t\treturn WebSocketWithProxy;\n\t}\n\n\tconst ctor = (globalThis as { WebSocket?: unknown }).WebSocket;\n\tif (typeof ctor !== \"function\") return null;\n\treturn ctor as unknown as WebSocketConstructor;\n}\n\nclass WebSocketCloseError extends Error {\n\treadonly code?: number;\n\treadonly reason?: string;\n\treadonly wasClean?: boolean;\n\n\tconstructor(message: string, options?: { code?: number; reason?: string; wasClean?: boolean }) {\n\t\tsuper(message);\n\t\tthis.name = \"WebSocketCloseError\";\n\t\tthis.code = options?.code;\n\t\tthis.reason = options?.reason;\n\t\tthis.wasClean = options?.wasClean;\n\t}\n}\n\nfunction getWebSocketReadyState(socket: WebSocketLike): number | undefined {\n\tconst readyState = (socket as { readyState?: unknown }).readyState;\n\treturn typeof readyState === \"number\" ? readyState : undefined;\n}\n\nfunction isWebSocketReusable(socket: WebSocketLike): boolean {\n\tconst readyState = getWebSocketReadyState(socket);\n\t// If readyState is unavailable, assume the runtime keeps it open/reusable.\n\treturn readyState === undefined || readyState === 1;\n}\n\nfunction isWebSocketSessionExpired(entry: CachedWebSocketConnection): boolean {\n\treturn Date.now() - entry.createdAt >= SESSION_WEBSOCKET_MAX_AGE_MS;\n}\n\nfunction closeWebSocketSilently(socket: WebSocketLike, code = 1000, reason = \"done\"): void {\n\ttry {\n\t\tsocket.close(code, reason);\n\t} catch {}\n}\n\nfunction scheduleSessionWebSocketExpiry(sessionId: string, entry: CachedWebSocketConnection): void {\n\tif (entry.idleTimer) {\n\t\tclearTimeout(entry.idleTimer);\n\t}\n\tentry.idleTimer = setTimeout(() => {\n\t\tif (entry.busy) return;\n\t\tcloseWebSocketSilently(entry.socket, 1000, \"idle_timeout\");\n\t\twebsocketSessionCache.delete(sessionId);\n\t}, SESSION_WEBSOCKET_CACHE_TTL_MS);\n}\n\nasync function connectWebSocket(\n\turl: string,\n\theaders: Headers,\n\tsignal?: AbortSignal,\n\tconnectTimeoutMs = DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS,\n\tenv?: ProviderEnv,\n): Promise<WebSocketLike> {\n\tconst WebSocketCtor = await getWebSocketConstructor(env);\n\tif (!WebSocketCtor) {\n\t\tthrow new Error(\"WebSocket transport is not available in this runtime\");\n\t}\n\n\tconst wsHeaders = headersToRecord(headers);\n\tdelete wsHeaders[\"OpenAI-Beta\"];\n\n\treturn new Promise<WebSocketLike>((resolve, reject) => {\n\t\tlet settled = false;\n\t\tlet timeout: ReturnType<typeof setTimeout> | undefined;\n\t\tlet socket: WebSocketLike;\n\n\t\ttry {\n\t\t\tsocket = new WebSocketCtor(url, { headers: wsHeaders });\n\t\t} catch (error) {\n\t\t\treject(error instanceof Error ? error : new Error(String(error)));\n\t\t\treturn;\n\t\t}\n\n\t\tconst cleanup = () => {\n\t\t\tif (timeout) {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\ttimeout = undefined;\n\t\t\t}\n\t\t\tsocket.removeEventListener(\"open\", onOpen);\n\t\t\tsocket.removeEventListener(\"error\", onError);\n\t\t\tsocket.removeEventListener(\"close\", onClose);\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t};\n\t\tconst fail = (error: Error, closeReason?: string) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tcleanup();\n\t\t\tif (closeReason) {\n\t\t\t\tcloseWebSocketSilently(socket, 1000, closeReason);\n\t\t\t}\n\t\t\treject(error);\n\t\t};\n\t\tconst onOpen: WebSocketListener = () => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tcleanup();\n\t\t\tresolve(socket);\n\t\t};\n\t\tconst onError: WebSocketListener = (event) => {\n\t\t\tfail(extractWebSocketError(event));\n\t\t};\n\t\tconst onClose: WebSocketListener = (event) => {\n\t\t\tfail(extractWebSocketCloseError(event));\n\t\t};\n\t\tconst onAbort = () => {\n\t\t\tfail(new Error(\"Request was aborted\"), \"aborted\");\n\t\t};\n\n\t\tsocket.addEventListener(\"open\", onOpen);\n\t\tsocket.addEventListener(\"error\", onError);\n\t\tsocket.addEventListener(\"close\", onClose);\n\t\tsignal?.addEventListener(\"abort\", onAbort);\n\n\t\tif (connectTimeoutMs > 0) {\n\t\t\ttimeout = setTimeout(() => {\n\t\t\t\tfail(new Error(`WebSocket connect timeout after ${connectTimeoutMs}ms`), \"connect_timeout\");\n\t\t\t}, connectTimeoutMs);\n\t\t}\n\t\tif (signal?.aborted) {\n\t\t\tonAbort();\n\t\t}\n\t});\n}\n\nasync function acquireWebSocket(\n\turl: string,\n\theaders: Headers,\n\tsessionId: string | undefined,\n\tsignal?: AbortSignal,\n\tconnectTimeoutMs?: number,\n\tenv?: ProviderEnv,\n): Promise<{\n\tsocket: WebSocketLike;\n\tentry?: CachedWebSocketConnection;\n\treused: boolean;\n\trelease: (options?: { keep?: boolean }) => void;\n}> {\n\tif (!sessionId) {\n\t\tconst socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);\n\t\treturn {\n\t\t\tsocket,\n\t\t\treused: false,\n\t\t\trelease: () => closeWebSocketSilently(socket),\n\t\t};\n\t}\n\n\tconst cached = websocketSessionCache.get(sessionId);\n\tif (cached) {\n\t\tif (cached.idleTimer) {\n\t\t\tclearTimeout(cached.idleTimer);\n\t\t\tcached.idleTimer = undefined;\n\t\t}\n\t\tif (!cached.busy && isWebSocketSessionExpired(cached)) {\n\t\t\tcloseWebSocketSilently(cached.socket, 1000, \"connection_age_limit\");\n\t\t\twebsocketSessionCache.delete(sessionId);\n\t\t} else if (!cached.busy && isWebSocketReusable(cached.socket)) {\n\t\t\tcached.busy = true;\n\t\t\treturn {\n\t\t\t\tsocket: cached.socket,\n\t\t\t\tentry: cached,\n\t\t\t\treused: true,\n\t\t\t\trelease: ({ keep } = {}) => {\n\t\t\t\t\tif (!keep || !isWebSocketReusable(cached.socket)) {\n\t\t\t\t\t\tcloseWebSocketSilently(cached.socket);\n\t\t\t\t\t\twebsocketSessionCache.delete(sessionId);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcached.busy = false;\n\t\t\t\t\tscheduleSessionWebSocketExpiry(sessionId, cached);\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tif (cached.busy) {\n\t\t\tconst socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);\n\t\t\treturn {\n\t\t\t\tsocket,\n\t\t\t\treused: false,\n\t\t\t\trelease: () => {\n\t\t\t\t\tcloseWebSocketSilently(socket);\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tif (!isWebSocketReusable(cached.socket)) {\n\t\t\tcloseWebSocketSilently(cached.socket);\n\t\t\twebsocketSessionCache.delete(sessionId);\n\t\t}\n\t}\n\n\tconst socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);\n\tconst entry: CachedWebSocketConnection = { socket, busy: true, createdAt: Date.now() };\n\twebsocketSessionCache.set(sessionId, entry);\n\treturn {\n\t\tsocket,\n\t\tentry,\n\t\treused: false,\n\t\trelease: ({ keep } = {}) => {\n\t\t\tif (!keep || !isWebSocketReusable(entry.socket)) {\n\t\t\t\tcloseWebSocketSilently(entry.socket);\n\t\t\t\tif (entry.idleTimer) clearTimeout(entry.idleTimer);\n\t\t\t\tif (websocketSessionCache.get(sessionId) === entry) {\n\t\t\t\t\twebsocketSessionCache.delete(sessionId);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tentry.busy = false;\n\t\t\tscheduleSessionWebSocketExpiry(sessionId, entry);\n\t\t},\n\t};\n}\n\nfunction extractWebSocketError(event: unknown): Error {\n\tif (event && typeof event === \"object\") {\n\t\tconst message = \"message\" in event ? (event as { message?: unknown }).message : undefined;\n\t\tif (typeof message === \"string\" && message.length > 0) {\n\t\t\treturn new Error(message);\n\t\t}\n\n\t\tconst nestedError = \"error\" in event ? (event as { error?: unknown }).error : undefined;\n\t\tif (nestedError instanceof Error && nestedError.message.length > 0) {\n\t\t\treturn nestedError;\n\t\t}\n\t\tif (nestedError && typeof nestedError === \"object\" && \"message\" in nestedError) {\n\t\t\tconst nestedMessage = (nestedError as { message?: unknown }).message;\n\t\t\tif (typeof nestedMessage === \"string\" && nestedMessage.length > 0) {\n\t\t\t\treturn new Error(nestedMessage);\n\t\t\t}\n\t\t}\n\t}\n\treturn new Error(\"WebSocket error\");\n}\n\nfunction extractWebSocketCloseError(event: unknown): Error {\n\tif (event && typeof event === \"object\") {\n\t\tconst code = \"code\" in event ? (event as { code?: unknown }).code : undefined;\n\t\tconst reason = \"reason\" in event ? (event as { reason?: unknown }).reason : undefined;\n\t\tconst wasClean = \"wasClean\" in event ? (event as { wasClean?: unknown }).wasClean : undefined;\n\t\tconst codeText = typeof code === \"number\" ? ` ${code}` : \"\";\n\t\tlet reasonText = typeof reason === \"string\" && reason.length > 0 ? ` ${reason}` : \"\";\n\t\tif (!reasonText && code === WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE) {\n\t\t\treasonText = \" message too big\";\n\t\t}\n\t\treturn new WebSocketCloseError(`WebSocket closed${codeText}${reasonText}`.trim(), {\n\t\t\tcode: typeof code === \"number\" ? code : undefined,\n\t\t\treason: typeof reason === \"string\" && reason.length > 0 ? reason : undefined,\n\t\t\twasClean: typeof wasClean === \"boolean\" ? wasClean : undefined,\n\t\t});\n\t}\n\treturn new Error(\"WebSocket closed\");\n}\n\nasync function decodeWebSocketData(data: unknown): Promise<string | null> {\n\tif (typeof data === \"string\") return data;\n\tif (data instanceof ArrayBuffer) {\n\t\treturn new TextDecoder().decode(new Uint8Array(data));\n\t}\n\tif (ArrayBuffer.isView(data)) {\n\t\tconst view = data as ArrayBufferView;\n\t\treturn new TextDecoder().decode(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));\n\t}\n\tif (data && typeof data === \"object\" && \"arrayBuffer\" in data) {\n\t\tconst blobLike = data as { arrayBuffer: () => Promise<ArrayBuffer> };\n\t\tconst arrayBuffer = await blobLike.arrayBuffer();\n\t\treturn new TextDecoder().decode(new Uint8Array(arrayBuffer));\n\t}\n\treturn null;\n}\n\nasync function* parseWebSocket(\n\tsocket: WebSocketLike,\n\tsignal?: AbortSignal,\n\tidleTimeoutMs?: number,\n): AsyncGenerator<Record<string, unknown>> {\n\tconst queue: Record<string, unknown>[] = [];\n\tlet pending: (() => void) | null = null;\n\tlet done = false;\n\tlet failed: Error | null = null;\n\tlet sawCompletion = false;\n\n\tconst wake = () => {\n\t\tif (!pending) return;\n\t\tconst resolve = pending;\n\t\tpending = null;\n\t\tresolve();\n\t};\n\n\tconst onMessage: WebSocketListener = (event) => {\n\t\tvoid (async () => {\n\t\t\tlet text: string | null = null;\n\t\t\ttry {\n\t\t\t\tif (!event || typeof event !== \"object\" || !(\"data\" in event)) return;\n\t\t\t\ttext = await decodeWebSocketData((event as { data?: unknown }).data);\n\t\t\t\tif (!text) return;\n\t\t\t\tconst parsed = JSON.parse(text) as Record<string, unknown>;\n\t\t\t\tconst type = typeof parsed.type === \"string\" ? parsed.type : \"\";\n\t\t\t\tif (type === \"response.completed\" || type === \"response.done\" || type === \"response.incomplete\") {\n\t\t\t\t\tsawCompletion = true;\n\t\t\t\t\tdone = true;\n\t\t\t\t}\n\t\t\t\tqueue.push(parsed);\n\t\t\t\twake();\n\t\t\t} catch (cause) {\n\t\t\t\tfailed = new CodexProtocolError(`Invalid Codex WebSocket JSON: ${formatThrownValue(cause)}`, {\n\t\t\t\t\tcause,\n\t\t\t\t\tpayload: text,\n\t\t\t\t});\n\t\t\t\tdone = true;\n\t\t\t\twake();\n\t\t\t}\n\t\t})();\n\t};\n\n\tconst onError: WebSocketListener = (event) => {\n\t\tfailed = extractWebSocketError(event);\n\t\tdone = true;\n\t\twake();\n\t};\n\n\tconst onClose: WebSocketListener = (event) => {\n\t\tif (sawCompletion) {\n\t\t\tdone = true;\n\t\t\twake();\n\t\t\treturn;\n\t\t}\n\t\tif (!failed) {\n\t\t\tfailed = extractWebSocketCloseError(event);\n\t\t}\n\t\tdone = true;\n\t\twake();\n\t};\n\n\tconst onAbort = () => {\n\t\tfailed = new Error(\"Request was aborted\");\n\t\tdone = true;\n\t\twake();\n\t};\n\n\tsocket.addEventListener(\"message\", onMessage);\n\tsocket.addEventListener(\"error\", onError);\n\tsocket.addEventListener(\"close\", onClose);\n\tsignal?.addEventListener(\"abort\", onAbort);\n\n\ttry {\n\t\twhile (true) {\n\t\t\tif (signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\t\t\tif (queue.length > 0) {\n\t\t\t\tyield queue.shift()!;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (done) break;\n\t\t\tlet timeout: ReturnType<typeof setTimeout> | undefined;\n\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\tpending = resolve;\n\t\t\t\tif (idleTimeoutMs !== undefined && idleTimeoutMs > 0) {\n\t\t\t\t\ttimeout = setTimeout(() => {\n\t\t\t\t\t\tconst error = new Error(`WebSocket idle timeout after ${idleTimeoutMs}ms`);\n\t\t\t\t\t\tfailed = error;\n\t\t\t\t\t\tdone = true;\n\t\t\t\t\t\tpending = null;\n\t\t\t\t\t\tcloseWebSocketSilently(socket, 1000, \"idle_timeout\");\n\t\t\t\t\t\treject(error);\n\t\t\t\t\t}, idleTimeoutMs);\n\t\t\t\t}\n\t\t\t}).finally(() => {\n\t\t\t\tif (timeout) {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tif (failed) {\n\t\t\tthrow failed;\n\t\t}\n\t\tif (!sawCompletion) {\n\t\t\tthrow new Error(\"WebSocket stream closed before response.completed\");\n\t\t}\n\t} finally {\n\t\tsocket.removeEventListener(\"message\", onMessage);\n\t\tsocket.removeEventListener(\"error\", onError);\n\t\tsocket.removeEventListener(\"close\", onClose);\n\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t}\n}\n\nfunction requestBodyWithoutInput(body: RequestBody): RequestBody {\n\tconst { input: _input, previous_response_id: _previousResponseId, ...rest } = body;\n\treturn rest;\n}\n\nfunction responseInputsEqual(a: ResponseInput | undefined, b: ResponseInput | undefined): boolean {\n\treturn JSON.stringify(a ?? []) === JSON.stringify(b ?? []);\n}\n\nfunction requestBodiesMatchExceptInput(a: RequestBody, b: RequestBody): boolean {\n\treturn JSON.stringify(requestBodyWithoutInput(a)) === JSON.stringify(requestBodyWithoutInput(b));\n}\n\nfunction getCachedWebSocketInputDelta(\n\tbody: RequestBody,\n\tcontinuation: CachedWebSocketContinuationState,\n): ResponseInput | undefined {\n\tif (!requestBodiesMatchExceptInput(body, continuation.lastRequestBody)) {\n\t\treturn undefined;\n\t}\n\n\tconst currentInput = body.input ?? [];\n\tconst baseline = [...(continuation.lastRequestBody.input ?? []), ...continuation.lastResponseItems];\n\tif (currentInput.length < baseline.length) {\n\t\treturn undefined;\n\t}\n\n\tconst prefix = currentInput.slice(0, baseline.length);\n\tif (!responseInputsEqual(prefix, baseline)) {\n\t\treturn undefined;\n\t}\n\n\treturn currentInput.slice(baseline.length);\n}\n\nfunction buildCachedWebSocketRequestBody(entry: CachedWebSocketConnection, body: RequestBody): RequestBody {\n\tconst continuation = entry.continuation;\n\tif (!continuation) {\n\t\treturn body;\n\t}\n\n\tconst delta = getCachedWebSocketInputDelta(body, continuation);\n\tif (!delta || !continuation.lastResponseId) {\n\t\tentry.continuation = undefined;\n\t\treturn body;\n\t}\n\n\treturn {\n\t\t...body,\n\t\tprevious_response_id: continuation.lastResponseId,\n\t\tinput: delta,\n\t};\n}\n\nasync function* startWebSocketOutputOnFirstEvent(\n\tevents: AsyncIterable<ResponseStreamEvent>,\n\toutput: AssistantMessage,\n\tstream: AssistantMessageEventStream,\n\tonStart: () => void,\n): AsyncGenerator<ResponseStreamEvent> {\n\tlet started = false;\n\tfor await (const event of events) {\n\t\tif (!started) {\n\t\t\tstarted = true;\n\t\t\tonStart();\n\t\t\tstream.push({ type: \"start\", partial: output });\n\t\t}\n\t\tyield event;\n\t}\n}\n\nasync function processWebSocketStream(\n\turl: string,\n\tbody: RequestBody,\n\theaders: Headers,\n\toutput: AssistantMessage,\n\tstream: AssistantMessageEventStream,\n\tmodel: Model<\"openai-codex-responses\">,\n\tonStart: () => void,\n\tidleTimeoutMs: number | undefined,\n\twebsocketConnectTimeoutMs: number | undefined,\n\toptions?: OpenAICodexResponsesOptions,\n): Promise<void> {\n\tconst { socket, entry, reused, release } = await acquireWebSocket(\n\t\turl,\n\t\theaders,\n\t\toptions?.sessionId,\n\t\toptions?.signal,\n\t\twebsocketConnectTimeoutMs,\n\t\toptions?.env,\n\t);\n\tlet keepConnection = true;\n\tconst useCachedContext = options?.transport === \"websocket-cached\" || options?.transport === \"auto\";\n\t// ChatGPT Codex Responses rejects `store: true` (\"Store must be set to false\").\n\t// WebSocket continuation still works via connection-scoped previous_response_id state.\n\tconst fullBody = body;\n\tconst requestBody = useCachedContext && entry ? buildCachedWebSocketRequestBody(entry, fullBody) : fullBody;\n\tconst stats = options?.sessionId ? getOrCreateWebSocketDebugStats(options.sessionId) : undefined;\n\tif (stats) {\n\t\tstats.requests++;\n\t\tif (reused) stats.connectionsReused++;\n\t\telse stats.connectionsCreated++;\n\t\tif (useCachedContext) stats.cachedContextRequests++;\n\t\tif (requestBody.store === true) stats.storeTrueRequests++;\n\t\tstats.lastInputItems = requestBody.input?.length ?? 0;\n\t\tif (requestBody.previous_response_id) {\n\t\t\tstats.deltaRequests++;\n\t\t\tstats.lastDeltaInputItems = requestBody.input?.length ?? 0;\n\t\t\tstats.lastPreviousResponseId = requestBody.previous_response_id;\n\t\t} else {\n\t\t\tstats.fullContextRequests++;\n\t\t\tstats.lastDeltaInputItems = undefined;\n\t\t\tstats.lastPreviousResponseId = undefined;\n\t\t}\n\t}\n\ttry {\n\t\tsocket.send(JSON.stringify({ type: \"response.create\", ...requestBody }));\n\t\tawait processResponsesStream(\n\t\t\tstartWebSocketOutputOnFirstEvent(\n\t\t\t\tmapCodexEvents(parseWebSocket(socket, options?.signal, idleTimeoutMs)),\n\t\t\t\toutput,\n\t\t\t\tstream,\n\t\t\t\tonStart,\n\t\t\t),\n\t\t\toutput,\n\t\t\tstream,\n\t\t\tmodel,\n\t\t\t{\n\t\t\t\tserviceTier: options?.serviceTier,\n\t\t\t\tresolveServiceTier: resolveCodexServiceTier,\n\t\t\t\tapplyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),\n\t\t\t},\n\t\t);\n\t\tif (options?.signal?.aborted) {\n\t\t\tkeepConnection = false;\n\t\t} else if (useCachedContext && entry && output.responseId) {\n\t\t\tconst responseItems = convertResponsesMessages(model, { messages: [output] }, CODEX_TOOL_CALL_PROVIDERS, {\n\t\t\t\tincludeSystemPrompt: false,\n\t\t\t}).filter((item) => item.type !== \"function_call_output\");\n\t\t\tentry.continuation = {\n\t\t\t\tlastRequestBody: fullBody,\n\t\t\t\tlastResponseId: output.responseId,\n\t\t\t\tlastResponseItems: responseItems,\n\t\t\t};\n\t\t}\n\t} catch (error) {\n\t\tif (entry) {\n\t\t\tentry.continuation = undefined;\n\t\t}\n\t\tkeepConnection = false;\n\t\tthrow error;\n\t} finally {\n\t\trelease({ keep: keepConnection });\n\t}\n}\n\n// ============================================================================\n// Error Handling\n// ============================================================================\n\nasync function parseErrorResponse(response: Response): Promise<{ message: string; friendlyMessage?: string }> {\n\tconst raw = await response.text();\n\tlet message = raw || response.statusText || \"Request failed\";\n\tlet friendlyMessage: string | undefined;\n\n\ttry {\n\t\tconst parsed = JSON.parse(raw) as {\n\t\t\terror?: { code?: string; type?: string; message?: string; plan_type?: string; resets_at?: number };\n\t\t};\n\t\tconst err = parsed?.error;\n\t\tif (err) {\n\t\t\tconst code = err.code || err.type || \"\";\n\t\t\tif (/usage_limit_reached|usage_not_included|rate_limit_exceeded/i.test(code) || response.status === 429) {\n\t\t\t\tconst plan = err.plan_type ? ` (${err.plan_type.toLowerCase()} plan)` : \"\";\n\t\t\t\tconst mins = err.resets_at\n\t\t\t\t\t? Math.max(0, Math.round((err.resets_at * 1000 - Date.now()) / 60000))\n\t\t\t\t\t: undefined;\n\t\t\t\tconst when = mins !== undefined ? ` Try again in ~${mins} min.` : \"\";\n\t\t\t\tfriendlyMessage = `You have hit your ChatGPT usage limit${plan}.${when}`.trim();\n\t\t\t}\n\t\t\tmessage = err.message || friendlyMessage || message;\n\t\t}\n\t} catch {}\n\n\treturn { message, friendlyMessage };\n}\n\n// ============================================================================\n// Auth & Headers\n// ============================================================================\n\nfunction extractAccountId(token: string): string {\n\ttry {\n\t\tconst parts = token.split(\".\");\n\t\tif (parts.length !== 3) throw new Error(\"Invalid token\");\n\t\tconst payload = JSON.parse(atob(parts[1]));\n\t\tconst accountId = payload?.[JWT_CLAIM_PATH]?.chatgpt_account_id;\n\t\tif (!accountId) throw new Error(\"No account ID in token\");\n\t\treturn accountId;\n\t} catch {\n\t\tthrow new Error(\"Failed to extract accountId from token\");\n\t}\n}\n\nfunction createCodexRequestId(): string {\n\tif (typeof globalThis.crypto?.randomUUID === \"function\") {\n\t\treturn globalThis.crypto.randomUUID();\n\t}\n\treturn `codex_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction buildBaseCodexHeaders(\n\tinitHeaders: Record<string, string> | undefined,\n\tadditionalHeaders: ProviderHeaders | undefined,\n\taccountId: string,\n\ttoken: string,\n): Headers {\n\tconst headers = new Headers(initHeaders);\n\tfor (const [key, value] of Object.entries(additionalHeaders || {})) {\n\t\tif (value === null) {\n\t\t\theaders.delete(key);\n\t\t} else {\n\t\t\theaders.set(key, value);\n\t\t}\n\t}\n\theaders.set(\"Authorization\", `Bearer ${token}`);\n\theaders.set(\"chatgpt-account-id\", accountId);\n\theaders.set(\"originator\", \"pi\");\n\tconst userAgent = _os ? `pi (${_os.platform()} ${_os.release()}; ${_os.arch()})` : \"pi (browser)\";\n\theaders.set(\"User-Agent\", userAgent);\n\treturn headers;\n}\n\nfunction buildSSEHeaders(\n\tinitHeaders: Record<string, string> | undefined,\n\tadditionalHeaders: ProviderHeaders | undefined,\n\taccountId: string,\n\ttoken: string,\n\tsessionId?: string,\n): Headers {\n\tconst headers = buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token);\n\theaders.set(\"OpenAI-Beta\", \"responses=experimental\");\n\theaders.set(\"accept\", \"text/event-stream\");\n\theaders.set(\"content-type\", \"application/json\");\n\n\tif (sessionId) {\n\t\theaders.set(\"session-id\", sessionId);\n\t\theaders.set(\"x-client-request-id\", sessionId);\n\t}\n\n\treturn headers;\n}\n\nfunction buildWebSocketHeaders(\n\tinitHeaders: Record<string, string> | undefined,\n\tadditionalHeaders: ProviderHeaders | undefined,\n\taccountId: string,\n\ttoken: string,\n\trequestId: string,\n): Headers {\n\tconst headers = buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token);\n\theaders.delete(\"accept\");\n\theaders.delete(\"content-type\");\n\theaders.delete(\"OpenAI-Beta\");\n\theaders.delete(\"openai-beta\");\n\theaders.set(\"OpenAI-Beta\", OPENAI_BETA_RESPONSES_WEBSOCKETS);\n\theaders.set(\"x-client-request-id\", requestId);\n\theaders.set(\"session-id\", requestId);\n\treturn headers;\n}\n"]}
@@ -1,24 +1,16 @@
1
- var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
- if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
- return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
- return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
- });
1
+ function loadNodeOs() {
2
+ if (typeof process === "undefined" || !(process.versions?.node || process.versions?.bun)) {
3
+ return null;
6
4
  }
7
- return path;
8
- };
9
- // NEVER convert to top-level runtime imports - breaks browser/Vite builds
10
- let _os = null;
11
- const dynamicImport = (specifier) => import(__rewriteRelativeImportExtension(specifier));
12
- const NODE_OS_SPECIFIER = "node:" + "os";
13
- if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) {
14
- dynamicImport(NODE_OS_SPECIFIER).then((m) => {
15
- _os = m;
16
- });
5
+ return process.getBuiltinModule?.("node:os") ?? null;
17
6
  }
7
+ // NEVER convert to top-level runtime imports - breaks browser/Vite builds
8
+ const _os = loadNodeOs();
18
9
  import { clampThinkingLevel } from "../models.js";
19
10
  import { registerSessionResourceCleanup } from "../session-resources.js";
20
11
  import { combineAbortSignals } from "../utils/abort-signals.js";
21
12
  import { appendAssistantMessageDiagnostic, createAssistantMessageDiagnostic, formatThrownValue, } from "../utils/diagnostics.js";
13
+ import { formatProviderError, normalizeProviderError } from "../utils/error-body.js";
22
14
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
23
15
  import { headersToRecord } from "../utils/headers.js";
24
16
  import { resolveHttpProxyUrlForTarget } from "../utils/node-http-proxy.js";
@@ -33,10 +25,10 @@ const JWT_CLAIM_PATH = "https://api.openai.com/auth";
33
25
  const DEFAULT_MAX_RETRIES = 0;
34
26
  const BASE_DELAY_MS = 1000;
35
27
  const DEFAULT_MAX_RETRY_DELAY_MS = 60_000;
36
- // Keep a bounded pre-header timeout so zero-event Codex SSE stalls fail instead of
37
- // leaving callers stuck on "Working..." indefinitely. See #4945.
38
- const DEFAULT_SSE_HEADER_TIMEOUT_MS = 20_000;
39
28
  const DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS = 15_000;
29
+ // The Codex backend accepts zstd-compressed request bodies on the SSE responses
30
+ // endpoint (the same endpoint the official Codex client compresses against).
31
+ const REQUEST_COMPRESSION_ZSTD_LEVEL = 3;
40
32
  const CODEX_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]);
41
33
  const WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE = 1009;
42
34
  const WEBSOCKET_CONNECTION_LIMIT_REACHED_CODE = "websocket_connection_limit_reached";
@@ -110,18 +102,29 @@ function normalizeTimeoutMs(value) {
110
102
  }
111
103
  return Math.floor(value);
112
104
  }
113
- function createSSEHeaderTimeout() {
114
- const controller = new AbortController();
115
- let error;
116
- const timeout = setTimeout(() => {
117
- error = new Error(`Codex SSE response headers timed out after ${DEFAULT_SSE_HEADER_TIMEOUT_MS}ms`);
118
- controller.abort(error);
119
- }, DEFAULT_SSE_HEADER_TIMEOUT_MS);
120
- return {
121
- signal: controller.signal,
122
- clear: () => clearTimeout(timeout),
123
- error: () => error,
124
- };
105
+ function loadNodeZlib() {
106
+ if (typeof process === "undefined" || !(process.versions?.node || process.versions?.bun)) {
107
+ return null;
108
+ }
109
+ return process.getBuiltinModule?.("node:zlib") ?? null;
110
+ }
111
+ // Returns the zstd-compressed body bytes, or null when compression is
112
+ // unavailable (browser/Vite builds). Callers fall back to sending the
113
+ // uncompressed JSON when this returns null.
114
+ function compressRequestBodyZstd(bodyJson) {
115
+ const zlib = loadNodeZlib();
116
+ if (!zlib || typeof zlib.zstdCompressSync !== "function") {
117
+ return null;
118
+ }
119
+ try {
120
+ const compressed = zlib.zstdCompressSync(bodyJson, {
121
+ params: { [zlib.constants.ZSTD_c_compressionLevel]: REQUEST_COMPRESSION_ZSTD_LEVEL },
122
+ });
123
+ return new Uint8Array(compressed.buffer, compressed.byteOffset, compressed.byteLength);
124
+ }
125
+ catch {
126
+ return null;
127
+ }
125
128
  }
126
129
  // ============================================================================
127
130
  // Main Stream Function
@@ -161,7 +164,7 @@ export const stream = (model, context, options) => {
161
164
  const sseHeaders = buildSSEHeaders(model.headers, options?.headers, accountId, apiKey, options?.sessionId);
162
165
  const websocketHeaders = buildWebSocketHeaders(model.headers, options?.headers, accountId, apiKey, websocketRequestId);
163
166
  const bodyJson = JSON.stringify(body);
164
- const idleTimeoutMs = normalizeTimeoutMs(options?.timeoutMs);
167
+ const httpTimeoutMs = normalizeTimeoutMs(options?.timeoutMs);
165
168
  const websocketConnectTimeoutMs = normalizeTimeoutMs(options?.websocketConnectTimeoutMs);
166
169
  const transport = options?.transport || "auto";
167
170
  const websocketDisabledForSession = transport !== "sse" && isWebSocketSseFallbackActive(options?.sessionId);
@@ -176,7 +179,7 @@ export const stream = (model, context, options) => {
176
179
  try {
177
180
  await processWebSocketStream(resolveCodexWebSocketUrl(model.baseUrl), body, websocketHeaders, output, stream, model, () => {
178
181
  websocketStarted = true;
179
- }, idleTimeoutMs, websocketConnectTimeoutMs, options);
182
+ }, httpTimeoutMs, websocketConnectTimeoutMs, options);
180
183
  if (options?.signal?.aborted) {
181
184
  throw new Error("Request was aborted");
182
185
  }
@@ -214,6 +217,14 @@ export const stream = (model, context, options) => {
214
217
  }
215
218
  }
216
219
  }
220
+ // Compress the request body once for the SSE path. The Codex backend
221
+ // decodes Content-Encoding: zstd; the WebSocket transport above sends the
222
+ // uncompressed JSON frame, matching the official Codex client.
223
+ const compressedBody = compressRequestBodyZstd(bodyJson);
224
+ if (compressedBody) {
225
+ sseHeaders.set("content-encoding", "zstd");
226
+ }
227
+ const sseBody = compressedBody ?? bodyJson;
217
228
  // Fetch with retry logic for rate limits and transient errors
218
229
  let response;
219
230
  let lastError;
@@ -223,23 +234,24 @@ export const stream = (model, context, options) => {
223
234
  throw new Error("Request was aborted");
224
235
  }
225
236
  try {
226
- const headerTimeout = createSSEHeaderTimeout();
227
- const combinedSignal = combineAbortSignals([options?.signal, headerTimeout.signal]);
237
+ const headerTimeoutSignal = httpTimeoutMs !== undefined && httpTimeoutMs > 0 ? AbortSignal.timeout(httpTimeoutMs) : undefined;
238
+ const combinedSignal = combineAbortSignals([options?.signal, headerTimeoutSignal]);
228
239
  try {
229
240
  response = await fetch(resolveCodexUrl(model.baseUrl), {
230
241
  method: "POST",
231
242
  headers: sseHeaders,
232
- body: bodyJson,
243
+ body: sseBody,
233
244
  signal: combinedSignal.signal,
234
245
  });
235
246
  }
236
247
  catch (error) {
237
- const timeoutError = headerTimeout.error();
238
- throw timeoutError && !options?.signal?.aborted ? timeoutError : error;
248
+ if (headerTimeoutSignal?.aborted && !options?.signal?.aborted) {
249
+ throw new Error(`Codex SSE response headers timed out after ${httpTimeoutMs}ms`);
250
+ }
251
+ throw error;
239
252
  }
240
253
  finally {
241
254
  combinedSignal.cleanup();
242
- headerTimeout.clear();
243
255
  }
244
256
  await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
245
257
  if (response.ok) {
@@ -300,7 +312,7 @@ export const stream = (model, context, options) => {
300
312
  delete block.partialJson;
301
313
  }
302
314
  output.stopReason = options?.signal?.aborted ? "aborted" : "error";
303
- output.errorMessage = error instanceof Error ? error.message : String(error);
315
+ output.errorMessage = formatProviderError(normalizeProviderError(error));
304
316
  stream.push({ type: "error", reason: output.stopReason, error: output });
305
317
  stream.end();
306
318
  }
@@ -553,6 +565,7 @@ async function* parseSSE(response, signal) {
553
565
  // ============================================================================
554
566
  const OPENAI_BETA_RESPONSES_WEBSOCKETS = "responses_websockets=2026-02-06";
555
567
  const SESSION_WEBSOCKET_CACHE_TTL_MS = 5 * 60 * 1000;
568
+ const SESSION_WEBSOCKET_MAX_AGE_MS = 55 * 60 * 1000;
556
569
  const websocketSessionCache = new Map();
557
570
  const websocketDebugStats = new Map();
558
571
  const websocketSseFallbackSessions = new Set();
@@ -677,6 +690,9 @@ function isWebSocketReusable(socket) {
677
690
  // If readyState is unavailable, assume the runtime keeps it open/reusable.
678
691
  return readyState === undefined || readyState === 1;
679
692
  }
693
+ function isWebSocketSessionExpired(entry) {
694
+ return Date.now() - entry.createdAt >= SESSION_WEBSOCKET_MAX_AGE_MS;
695
+ }
680
696
  function closeWebSocketSilently(socket, code = 1000, reason = "done") {
681
697
  try {
682
698
  socket.close(code, reason);
@@ -777,7 +793,11 @@ async function acquireWebSocket(url, headers, sessionId, signal, connectTimeoutM
777
793
  clearTimeout(cached.idleTimer);
778
794
  cached.idleTimer = undefined;
779
795
  }
780
- if (!cached.busy && isWebSocketReusable(cached.socket)) {
796
+ if (!cached.busy && isWebSocketSessionExpired(cached)) {
797
+ closeWebSocketSilently(cached.socket, 1000, "connection_age_limit");
798
+ websocketSessionCache.delete(sessionId);
799
+ }
800
+ else if (!cached.busy && isWebSocketReusable(cached.socket)) {
781
801
  cached.busy = true;
782
802
  return {
783
803
  socket: cached.socket,
@@ -810,7 +830,7 @@ async function acquireWebSocket(url, headers, sessionId, signal, connectTimeoutM
810
830
  }
811
831
  }
812
832
  const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);
813
- const entry = { socket, busy: true };
833
+ const entry = { socket, busy: true, createdAt: Date.now() };
814
834
  websocketSessionCache.set(sessionId, entry);
815
835
  return {
816
836
  socket,