@xiaoso/dsh-tool-plus 0.1.0-beta.2 → 0.1.0-beta.4

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 (235) hide show
  1. package/README.en.md +122 -0
  2. package/README.md +3 -2
  3. package/THIRD_PARTY_NOTICES.md +4 -35
  4. package/lib/abort-B7izMMna.mjs +51 -0
  5. package/lib/abort-D1_Z5Ut5.mjs +51 -0
  6. package/lib/abort-dIkZBV7Q.mjs +51 -0
  7. package/lib/amazon-bedrock-B0nxT06F.mjs +917 -0
  8. package/lib/amazon-bedrock-D-shAr1X.mjs +917 -0
  9. package/lib/amazon-bedrock-WQhvjHYu.mjs +917 -0
  10. package/lib/anthropic-BWsZFzU8.mjs +262 -0
  11. package/lib/anthropic-CJCauvYz.mjs +3115 -0
  12. package/lib/anthropic-CMT1Qj1t.mjs +3115 -0
  13. package/lib/anthropic-CZghe_9U.mjs +3115 -0
  14. package/lib/anthropic-sCMH7JID.mjs +262 -0
  15. package/lib/anthropic-tTntD8BD.mjs +262 -0
  16. package/lib/api-key-login-BzqNdM2y.mjs +157 -0
  17. package/lib/api-key-login-CWVwvHT1.mjs +157 -0
  18. package/lib/api-key-login-CpzSezsi.mjs +157 -0
  19. package/lib/aws-credentials-8Mgn5MAy.mjs +867 -0
  20. package/lib/aws-credentials-CXryMobh.mjs +867 -0
  21. package/lib/aws-credentials-ChG79ey-.mjs +867 -0
  22. package/lib/azure-openai-responses-CNkHiLUh.mjs +274 -0
  23. package/lib/azure-openai-responses-Cgmc-rbL.mjs +274 -0
  24. package/lib/azure-openai-responses-DuukbD0F.mjs +274 -0
  25. package/lib/bun-shim.mjs +252 -12
  26. package/lib/bundled-references-CSuBco5p.mjs +55 -0
  27. package/lib/bundled-references-Cavaffah.mjs +55 -0
  28. package/lib/bundled-references-sLN0YdXU.mjs +55 -0
  29. package/lib/callback-server-BEFNf-jf.mjs +398 -0
  30. package/lib/callback-server-CQ5Jo8F-.mjs +398 -0
  31. package/lib/callback-server-DDSrZ6l_.mjs +398 -0
  32. package/lib/client.js +111 -41
  33. package/lib/cursor-B3zg7EHj.mjs +273 -0
  34. package/lib/cursor-B6TUEmgP.mjs +135 -0
  35. package/lib/cursor-BIQfBC6V.mjs +135 -0
  36. package/lib/cursor-D8gvtTL3.mjs +135 -0
  37. package/lib/cursor-DNk6eXDd.mjs +5470 -0
  38. package/lib/cursor-DkSX2h3H.mjs +5441 -0
  39. package/lib/cursor-MjSm74IJ.mjs +273 -0
  40. package/lib/cursor-aJrXeYCb.mjs +273 -0
  41. package/lib/cursor-mJQwUKXK.mjs +5441 -0
  42. package/lib/device-code-DBo3B5nh.mjs +51 -0
  43. package/lib/device-code-DIb0FU-H.mjs +51 -0
  44. package/lib/device-code-OI_tQcmc.mjs +51 -0
  45. package/lib/devin-BHZZyMhO.mjs +95 -0
  46. package/lib/devin-BK7DHeca.mjs +114 -0
  47. package/lib/devin-CfMp3cXS.mjs +95 -0
  48. package/lib/devin-CldyPD8K.mjs +95 -0
  49. package/lib/devin-CzRpTUOA.mjs +114 -0
  50. package/lib/devin-D9zR5B_R.mjs +542 -0
  51. package/lib/devin-DXOeNyU7.mjs +542 -0
  52. package/lib/devin-Q1yem3lp.mjs +114 -0
  53. package/lib/devin-vNZTNUON.mjs +542 -0
  54. package/lib/error-B3bevhrs.mjs +1449 -0
  55. package/lib/error-Dd8DIqs-.mjs +1449 -0
  56. package/lib/error-yCSG0mGC.mjs +1449 -0
  57. package/lib/event-stream-BDPi64mK.mjs +151 -0
  58. package/lib/event-stream-BbFZJy-z.mjs +151 -0
  59. package/lib/event-stream-loUa0s1T.mjs +151 -0
  60. package/lib/fs-promises-shim-Bx0fpxuk.mjs +57 -0
  61. package/lib/github-copilot-4GaMtQmg.mjs +208 -0
  62. package/lib/github-copilot-B1yiyVRh.mjs +104 -0
  63. package/lib/github-copilot-B2HGSIJK.mjs +104 -0
  64. package/lib/github-copilot-BWT3eAOF.mjs +208 -0
  65. package/lib/github-copilot-CYkjeTi3.mjs +208 -0
  66. package/lib/github-copilot-jscUZ8Hf.mjs +104 -0
  67. package/lib/gitlab-duo-CM7Ref1H.mjs +155 -0
  68. package/lib/gitlab-duo-akICyfeD.mjs +155 -0
  69. package/lib/gitlab-duo-gubO4FNw.mjs +155 -0
  70. package/lib/gitlab-duo-workflow-BGAF6JDw.mjs +91 -0
  71. package/lib/gitlab-duo-workflow-CSEgYk3N.mjs +91 -0
  72. package/lib/gitlab-duo-workflow-D78tdn8E.mjs +91 -0
  73. package/lib/google-BS4T25Pn.mjs +30 -0
  74. package/lib/google-TFe-7-of.mjs +30 -0
  75. package/lib/google-antigravity-DGmwC2KV.mjs +198 -0
  76. package/lib/google-antigravity-DNYhgTpA.mjs +198 -0
  77. package/lib/google-antigravity-Dlkdhahq.mjs +198 -0
  78. package/lib/google-auth-B6gjsZvg.mjs +232 -0
  79. package/lib/google-auth-De7_s3eo.mjs +232 -0
  80. package/lib/google-auth-DhqpOYrX.mjs +232 -0
  81. package/lib/google-gemini-cli-BG_LRdIV.mjs +850 -0
  82. package/lib/google-gemini-cli-CWu8HvXW.mjs +211 -0
  83. package/lib/google-gemini-cli-Cb97bzQc.mjs +850 -0
  84. package/lib/google-gemini-cli-Cqds_Q5T.mjs +850 -0
  85. package/lib/google-gemini-cli-Dsztq0N5.mjs +211 -0
  86. package/lib/google-gemini-cli-zJhiOcND.mjs +211 -0
  87. package/lib/google-oauth-shared-CuEHaDDj.mjs +147 -0
  88. package/lib/google-oauth-shared-CwNWYdmR.mjs +147 -0
  89. package/lib/google-oauth-shared-yqlyqPPC.mjs +147 -0
  90. package/lib/google-shared-CgKmmdO3.mjs +760 -0
  91. package/lib/google-shared-Dr-VG-ap.mjs +760 -0
  92. package/lib/google-shared-DzhCb9b0.mjs +760 -0
  93. package/lib/google-vertex-BDN2tLaD.mjs +99 -0
  94. package/lib/google-vertex-DopVOQOG.mjs +99 -0
  95. package/lib/google-vertex-kscbkyRb.mjs +99 -0
  96. package/lib/google-zyDOG9x7.mjs +30 -0
  97. package/lib/identity-Da1GPU9a.mjs +219 -0
  98. package/lib/identity-DnB63pS7.mjs +219 -0
  99. package/lib/identity-xRP7_eSR.mjs +219 -0
  100. package/lib/idle-iterator-Baoyn3Hp.mjs +366 -0
  101. package/lib/idle-iterator-C6XbWcuB.mjs +366 -0
  102. package/lib/idle-iterator-DUjKQEGT.mjs +366 -0
  103. package/lib/index.mjs +4608 -3935
  104. package/lib/kimi--wUOWER5.mjs +236 -0
  105. package/lib/kimi-DMVI-zrZ.mjs +236 -0
  106. package/lib/kimi-Dh0pv3WZ.mjs +236 -0
  107. package/lib/markit-BL3cH47U.mjs +1417 -0
  108. package/lib/markit-BNiNY6SE.mjs +1417 -0
  109. package/lib/markit-M0jhziqU.mjs +1417 -0
  110. package/lib/minimax-code-BK-3eBbz.mjs +53 -0
  111. package/lib/minimax-code-DCbuA6Yk.mjs +53 -0
  112. package/lib/minimax-code-Uq8Qu9LW.mjs +53 -0
  113. package/lib/models-CfT4coYT.mjs +104660 -0
  114. package/lib/models-D_XnT6ay.mjs +104660 -0
  115. package/lib/models-SGASygJ3.mjs +104660 -0
  116. package/lib/ollama-BQMGPxtV.mjs +589 -0
  117. package/lib/ollama-DPzBmq89.mjs +589 -0
  118. package/lib/ollama-DoBz5SFm.mjs +589 -0
  119. package/lib/openai-codex-Bi-4S4PX.mjs +255 -0
  120. package/lib/openai-codex-CQL_kvYL.mjs +255 -0
  121. package/lib/openai-codex-Ch8CkT3p.mjs +255 -0
  122. package/lib/openai-codex-responses-CvxL_FMF.mjs +3144 -0
  123. package/lib/openai-codex-responses-Cz-2qpC7.mjs +3144 -0
  124. package/lib/openai-codex-responses-pejZgdZo.mjs +3144 -0
  125. package/lib/openai-completions-CF8IyNtP.mjs +1360 -0
  126. package/lib/openai-completions-DIf361kF.mjs +1360 -0
  127. package/lib/openai-completions-i8doczAk.mjs +1360 -0
  128. package/lib/openai-reasoning-fallback-DGVVLgd1.mjs +286 -0
  129. package/lib/openai-reasoning-fallback-Nj6yB2rY.mjs +286 -0
  130. package/lib/openai-reasoning-fallback-OdJREP1o.mjs +286 -0
  131. package/lib/openai-responses-BqXJInHX.mjs +867 -0
  132. package/lib/openai-responses-DTkBiiy-.mjs +867 -0
  133. package/lib/openai-responses-Db_a-U4-.mjs +867 -0
  134. package/lib/opencode-DaJjcUbf.mjs +38 -0
  135. package/lib/opencode-DhMolSoB.mjs +38 -0
  136. package/lib/opencode-Rq_Q2vAO.mjs +38 -0
  137. package/lib/perplexity-9b-RQ0mV.mjs +194 -0
  138. package/lib/perplexity-C4iLVMUj.mjs +194 -0
  139. package/lib/perplexity-Uu7Jzg5b.mjs +194 -0
  140. package/lib/proxy-DDqrmjjC.mjs +190 -0
  141. package/lib/proxy-ZtUcl6r5.mjs +190 -0
  142. package/lib/proxy-pM_NIv-H.mjs +190 -0
  143. package/lib/request-debug-HJMoJncL.mjs +183 -0
  144. package/lib/sanitize-text-CfcXzw5X.mjs +33 -0
  145. package/lib/scrapers-BkFfcPHj.mjs +9198 -0
  146. package/lib/scrapers-CL-WgdU1.mjs +9198 -0
  147. package/lib/scrapers-CVptboMs.mjs +9198 -0
  148. package/lib/scrapers-CipyKyCH.mjs +9198 -0
  149. package/lib/scrapers-DGfrYhtw.mjs +9198 -0
  150. package/lib/scrapers-DSUPYoUF.mjs +9198 -0
  151. package/lib/scrapers-DZ2GXmp7.mjs +9198 -0
  152. package/lib/scrapers-nhQAvc8d.mjs +9198 -0
  153. package/lib/shell-snapshot-fn-env.sh +67 -43
  154. package/lib/src-BgpusnsB.mjs +5076 -0
  155. package/lib/src-DTid0g4K.mjs +5001 -0
  156. package/lib/src-lDmkSRbm.mjs +5006 -0
  157. package/lib/stream-BlldLkVq.mjs +10236 -0
  158. package/lib/stream-CRtexYLe.mjs +10236 -0
  159. package/lib/stream-C_RAoxJC.mjs +10236 -0
  160. package/lib/stream-markup-healing-2toHXWXM.mjs +491 -0
  161. package/lib/stream-markup-healing-DMsxBx1L.mjs +491 -0
  162. package/lib/stream-markup-healing-KMkiAA00.mjs +491 -0
  163. package/lib/turndown-DN7ByzEI.mjs +667 -0
  164. package/lib/types/client/locales.d.ts +1 -1
  165. package/lib/types/config/fields.d.ts +1 -1
  166. package/lib/types/config/settings.d.ts +25 -3
  167. package/lib/types/config/truncate.d.ts +9 -5
  168. package/lib/types/tools/bash/background.d.ts +1 -1
  169. package/lib/types/tools/bash/bash-executor.d.ts +4 -0
  170. package/lib/types/tools/bash/bash-interceptor.d.ts +10 -8
  171. package/lib/types/tools/bash/cd-workdir.d.ts +35 -0
  172. package/lib/types/tools/bash/non-interactive-env.d.ts +2 -0
  173. package/lib/types/tools/bash/render.d.ts +4 -3
  174. package/lib/types/tools/bash/sanitize-text.d.ts +5 -0
  175. package/lib/types/tools/bash/shell-snapshot.d.ts +4 -1
  176. package/lib/types/tools/bash/shell-tokenize.d.ts +60 -0
  177. package/lib/types/tools/bash/streaming-output.d.ts +12 -212
  178. package/lib/types/tools/bash/which.d.ts +20 -0
  179. package/lib/types/tools/bash/windows-shell.d.ts +31 -0
  180. package/lib/types/tools/edit/adapter/index.d.ts +25 -3
  181. package/lib/types/tools/edit/adapter/tools/auto-generated-guard.d.ts +7 -17
  182. package/lib/types/tools/glob/adapter/index.d.ts +27 -1
  183. package/lib/types/tools/grep/adapter/index.d.ts +29 -1
  184. package/lib/types/tools/omp/edit/dispatch.d.ts +43 -0
  185. package/lib/types/tools/omp/edit/hashline/noop-loop-guard.d.ts +3 -1
  186. package/lib/types/tools/omp/sdk.d.ts +6 -0
  187. package/lib/types/tools/omp/tools/auto-generated-guard.d.ts +20 -0
  188. package/lib/types/tools/omp/tools/fetch.d.ts +2 -0
  189. package/lib/types/tools/omp/tools/read.d.ts +2 -8
  190. package/lib/types/tools/omp/tools/write.d.ts +4 -4
  191. package/lib/types/tools/omp/utils/image-loading.d.ts +10 -36
  192. package/lib/types/tools/read/adapter/index.d.ts +4 -1
  193. package/lib/types/tools/read-image/index.d.ts +4 -0
  194. package/lib/types/tools/shared/auto-generated-guard.dsh.d.ts +16 -4
  195. package/lib/types/tools/shared/bun-shim.d.ts +39 -3
  196. package/lib/types/tools/shared/fs-promises-shim.d.ts +3 -1
  197. package/lib/types/tools/shared/image-bridge.d.ts +98 -0
  198. package/lib/types/tools/shared/omp-prompt.d.ts +17 -9
  199. package/lib/types/tools/shared/session-state.d.ts +9 -3
  200. package/lib/utils-2Wq2eATG.mjs +8119 -0
  201. package/lib/utils-BnX8O3vi.mjs +12303 -0
  202. package/lib/utils-Bw139LTz.mjs +8119 -0
  203. package/lib/utils-CE8N8P4Q.mjs +12315 -0
  204. package/lib/utils-CZw4tVZQ.mjs +12315 -0
  205. package/lib/utils-C_MrQ_0z.mjs +12301 -0
  206. package/lib/utils-CiGN7OHR.mjs +12315 -0
  207. package/lib/utils-Cp76gCyk.mjs +8119 -0
  208. package/lib/utils-D0GRDgAr.mjs +12315 -0
  209. package/lib/utils-D0Z1fyoO.mjs +12299 -0
  210. package/lib/utils-D2YknRk4.mjs +51 -0
  211. package/lib/utils-DppGHXQB.mjs +51 -0
  212. package/lib/utils-DzXZDfYc.mjs +51 -0
  213. package/lib/utils-W8WdSBpX.mjs +12315 -0
  214. package/lib/vision-guard-01qSwFpf.mjs +92 -0
  215. package/lib/vision-guard-BDD4OrKo.mjs +92 -0
  216. package/lib/vision-guard-DzZGaOga.mjs +92 -0
  217. package/lib/wafer-CgtPHerf.mjs +17 -0
  218. package/lib/wafer-DRU0EjZX.mjs +17 -0
  219. package/lib/wafer-cBtrn6q9.mjs +17 -0
  220. package/lib/xai-oauth-Buk8XVvM.mjs +446 -0
  221. package/lib/xai-oauth-CMal8l1O.mjs +446 -0
  222. package/lib/xai-oauth-DPHDXKD7.mjs +446 -0
  223. package/lib/xiaomi-CZgwvX2G.mjs +157 -0
  224. package/lib/xiaomi-D2qT5cAC.mjs +157 -0
  225. package/lib/xiaomi-Dd87KSg5.mjs +157 -0
  226. package/lib/zai-7DWMkEqM.mjs +203 -0
  227. package/lib/zai-C59YnomG.mjs +203 -0
  228. package/lib/zai-CFeOt-53.mjs +203 -0
  229. package/lib/zip-Cg9ANN_U.mjs +1316 -0
  230. package/lib/zip-D36ZKsP8.mjs +1316 -0
  231. package/lib/zip-D6QXpvaJ.mjs +1316 -0
  232. package/package.json +14 -10
  233. package/presets/package.json +1 -1
  234. package/presets/tool-plus-ptc/preset.yml +1 -1
  235. package/presets/tool-plus-standard/preset.yml +1 -1
@@ -0,0 +1,3144 @@
1
+ import { installBunShim as __installBunShim } from "./bun-shim.mjs";
2
+ __installBunShim();
3
+ import { r as __exportAll } from "./fs-promises-shim-Bx0fpxuk.mjs";
4
+ import { B as USER_AGENT, C as error, I as $flag, P as $env, S as debug, g as parseStreamingJson, o as readSseJson, q as getInstallId, t as structuredCloneJSON, w as time } from "./src-BgpusnsB.mjs";
5
+ import { G as fetchWithRetry, V as ProviderHttpError, g as MissingApiKeyError, l as finalize, v as AbortError } from "./error-Dd8DIqs-.mjs";
6
+ import { A as NO_STRICT, C as kStreamingPartialJson, L as sanitizeSchemaForOpenAIResponses, a as normalizeSystemPrompts, c as sanitizeOpenAIResponsesAssistantFallbackItemsForReplay, d as stripOpenAIResponsesComputerLinkedReasoningIdsForReplay, h as clearStreamingPartialJson, j as adaptSchemaForStrict, k as toolWireSchema, l as sanitizeOpenAIResponsesAssistantHistoryItemsForReplay, m as transformMessages, n as getOpenAIResponsesHistoryItems, p as redactSensitiveInObject, r as getOpenAIResponsesHistoryPayload, t as createOpenAIResponsesHistoryPayload } from "./utils-Cp76gCyk.mjs";
7
+ import { t as asRecord } from "./type-guards-B9VVjRDK.mjs";
8
+ import { A as computerCallMetadata, B as finalizeCustomToolCallInputDone, E as buildResponsesDeltaInput, F as createSequentialCutoffSummaryState, H as finalizePendingResponsesToolCalls, K as getOpenAIPromptCacheKey, L as encodeResponsesToolCallId, M as convertResponsesInputContent, Q as isOpenAIResponsesProgressEvent, R as encodeTextSignatureV1, U as finalizeReasoningThinking, V as finalizeMessageText, W as finalizeToolCallArgumentsDone, X as hasExecutableIncompleteResponsesToolCalls, a as accumulateCustomToolCallInputDelta, at as populateResponsesUsageFromResponse, bt as escapeHarmonyControlTokens, c as appendMessageTextDelta, d as appendReasoningSummaryTextDelta, f as appendResponsesImageResult, j as convertResponsesAssistantMessage, l as appendReasoningSummaryPart, n as getEnvApiKey, nt as mapOpenAIResponsesStopReason, o as accumulateToolCallArgumentsDelta, p as appendResponsesToolResultMessages, r as isOfficialCodexApiUrl, rt as normalizeOpenAIPromptCacheKey, s as appendMessageContentPart, st as promoteResponsesToolUseStopReason, tt as mapOpenAIReasoningEffort, u as appendReasoningSummaryPartDone, x as applyReasoningSummaryDone, xt as isHarmonyDialectModel, y as applyOpenAIServiceTier, z as escapeReplayedControlTokens } from "./stream-CRtexYLe.mjs";
9
+ import { r as getProxyForUrl } from "./proxy-pM_NIv-H.mjs";
10
+ import { E as supportsCodexReasoningSummary, T as supportsAllTurnsReasoningContext, s as requireSupportedEffort, t as calculateCost } from "./models-SGASygJ3.mjs";
11
+ import { a as toNumber } from "./utils-D2YknRk4.mjs";
12
+ import "./identity-Da1GPU9a.mjs";
13
+ import { t as AssistantMessageEventStream } from "./event-stream-BbFZJy-z.mjs";
14
+ import { a as getCodexAccountId, i as OPENAI_HEADER_VALUES, n as CODEX_CLIENT_VERSION, r as OPENAI_HEADERS, t as CODEX_BASE_URL } from "./codex-P1dGpX7G.mjs";
15
+ import { n as getOpenAIStreamFirstEventTimeoutMs, o as iterateWithIdleTimeout, r as getOpenAIStreamIdleTimeoutMs, t as armPreResponseTimeout } from "./idle-iterator-DUjKQEGT.mjs";
16
+ import { n as isRequestDebugEnabled, t as createRequestDebugSession } from "./request-debug-HJMoJncL.mjs";
17
+ import { t as notifyRawSseEvent } from "./sse-debug-CYdjOC96.mjs";
18
+ import { t as compactGrammarDefinition } from "./grammar-D-NqNUxQ.mjs";
19
+ import { scheduler } from "node:timers/promises";
20
+ import { type } from "@oh-my-pi/omptype";
21
+ //#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/providers/openai-codex/request-transformer.ts
22
+ /** Caller literal → catalog `Effort` bridge (the enum is nominal). */
23
+ const EFFORT_BY_NAME = {
24
+ minimal: "minimal",
25
+ low: "low",
26
+ medium: "medium",
27
+ high: "high",
28
+ xhigh: "xhigh",
29
+ max: "max"
30
+ };
31
+ /**
32
+ * Resolve whether a Codex request uses the Responses Lite transport: an
33
+ * explicit option wins, then the `PI_CODEX_RESPONSES_LITE` env override
34
+ * (`1`/`true` forces Lite, `0`/`false` forces the full Responses body),
35
+ * otherwise the model's catalog flag (codex-rs `model_info.use_responses_lite`)
36
+ * decides.
37
+ */
38
+ function resolveCodexResponsesLite(model, requested) {
39
+ if (requested !== void 0) return requested;
40
+ const env = $env.PI_CODEX_RESPONSES_LITE?.trim().toLowerCase();
41
+ if (env === "1" || env === "true") return true;
42
+ if (env === "0" || env === "false") return false;
43
+ return model.useResponsesLite === true;
44
+ }
45
+ /**
46
+ * Whether to request `stream_options.reasoning_summary_delivery =
47
+ * "sequential_cutoff"` (codex-rs `concurrent_reasoning_summaries`), enabled by
48
+ * `PI_CODEX_CONCURRENT_SUMMARIES=1`.
49
+ *
50
+ * Off by default because the mode cancels summary sections still in flight when
51
+ * the reasoning item closes: measured over 12 interleaved turns it halved
52
+ * visible thinking (0.83 vs 1.67 summary parts, 37 vs 69 chars per turn) and
53
+ * produced no summary at all on 3 of 12 turns. codex-rs ships it disabled too
54
+ * (`Stage::UnderDevelopment`, `default_enabled: false`).
55
+ */
56
+ function concurrentSummariesEnabled() {
57
+ const env = $env.PI_CODEX_CONCURRENT_SUMMARIES?.trim().toLowerCase();
58
+ return env === "1" || env === "true";
59
+ }
60
+ /**
61
+ * Clamp a user-facing effort to the model's ladder, then remap to the wire
62
+ * tier. User efforts map 1:1 onto wire tiers; the effort map only covers
63
+ * host quirks where a wire tier genuinely does not exist (e.g. `minimal→none`).
64
+ * A mapped value outside the Codex wire vocabulary is a broken compat/model
65
+ * effort map — fail loudly rather than silently sending a different tier.
66
+ */
67
+ function mapCodexWireEffort(model, effort) {
68
+ const mapped = mapOpenAIReasoningEffort(model, model.compat, requireSupportedEffort(model, EFFORT_BY_NAME[effort]));
69
+ switch (mapped) {
70
+ case "none":
71
+ case "minimal":
72
+ case "low":
73
+ case "medium":
74
+ case "high":
75
+ case "xhigh":
76
+ case "max": return mapped;
77
+ default: throw new Error(`Effort map for ${model.provider}/${model.id} produced invalid Codex reasoning effort "${mapped}"`);
78
+ }
79
+ }
80
+ function getReasoningConfig(model, effort, options) {
81
+ const config = { effort: effort === "none" ? "none" : mapCodexWireEffort(model, effort) };
82
+ if (options.reasoningSummary !== null && supportsCodexReasoningSummary(model.id)) config.summary = options.reasoningSummary ?? "auto";
83
+ return config;
84
+ }
85
+ function filterInput(input) {
86
+ if (!Array.isArray(input)) return input;
87
+ return input.filter((item) => item.type !== "item_reference").map((item) => {
88
+ if (item.type === "computer_call") return item;
89
+ if (item.id != null) {
90
+ const { id: _id, ...rest } = item;
91
+ return rest;
92
+ }
93
+ return item;
94
+ });
95
+ }
96
+ const CODEX_ORPHAN_OUTPUT_LIMIT = 16e3;
97
+ /** Placeholder output for a tool call whose result never landed in the input. */
98
+ const CODEX_INTERRUPTED_TOOL_OUTPUT = "[No tool output recorded: the tool call was interrupted before it produced a result.]";
99
+ function orphanFunctionOutputToMessage(item, callId) {
100
+ const itemRecord = item;
101
+ const toolName = typeof itemRecord.name === "string" ? itemRecord.name : "tool";
102
+ let text = "";
103
+ try {
104
+ const output = itemRecord.output;
105
+ text = typeof output === "string" ? output : JSON.stringify(output);
106
+ } catch {
107
+ text = String(itemRecord.output ?? "");
108
+ }
109
+ if (text.length > CODEX_ORPHAN_OUTPUT_LIMIT) text = `${text.slice(0, CODEX_ORPHAN_OUTPUT_LIMIT)}\n...[truncated]`;
110
+ return {
111
+ type: "message",
112
+ role: "assistant",
113
+ content: `[Previous ${toolName} result; call_id=${callId}]: ${text}`
114
+ };
115
+ }
116
+ function toolCallKind(type) {
117
+ if (type === "function_call") return "function";
118
+ if (type === "custom_tool_call") return "custom";
119
+ if (type === "computer_call") return "computer";
120
+ }
121
+ function toolOutputKind(type) {
122
+ if (type === "function_call_output") return "function";
123
+ if (type === "custom_tool_call_output") return "custom";
124
+ if (type === "computer_call_output") return "computer";
125
+ }
126
+ /**
127
+ * Repair both halves of unpaired tool exchanges so the Responses input grammar
128
+ * stays valid — the API rejects either orphan with a 400:
129
+ *
130
+ * - `function_call_output` / `custom_tool_call_output` with no matching call →
131
+ * folded into an assistant message (`400 No tool call found for … output`).
132
+ * Regression of #472 / #1351.
133
+ * - `function_call` / `custom_tool_call` with no matching `*_output` → a
134
+ * placeholder output is synthesized immediately after the call
135
+ * (`400 No tool output found for function call …`). Hit when the user
136
+ * branches/navigates the session tree to a node that ends on a tool call (the
137
+ * tool-result child is dropped from the reconstructed history) or when a turn
138
+ * is aborted/crashes after the call streamed but before its result persisted.
139
+ */
140
+ function repairToolCallPairs(input) {
141
+ const callKinds = /* @__PURE__ */ new Map();
142
+ const outputKinds = /* @__PURE__ */ new Map();
143
+ for (const item of input) {
144
+ const callId = typeof item.call_id === "string" ? item.call_id : void 0;
145
+ if (callId === void 0) continue;
146
+ const callKind = toolCallKind(item.type);
147
+ const outputKind = toolOutputKind(item.type);
148
+ if (callKind) callKinds.set(callId, callKind);
149
+ if (outputKind) outputKinds.set(callId, outputKind);
150
+ }
151
+ const repaired = [];
152
+ for (const item of input) {
153
+ const callId = typeof item.call_id === "string" ? item.call_id : void 0;
154
+ const callKind = toolCallKind(item.type);
155
+ const outputKind = toolOutputKind(item.type);
156
+ if (outputKind && callId !== void 0 && callKinds.get(callId) !== outputKind) {
157
+ repaired.push(orphanFunctionOutputToMessage(item, callId));
158
+ continue;
159
+ }
160
+ if (callKind && callId !== void 0 && outputKinds.get(callId) !== callKind) {
161
+ if (callKind === "computer") {
162
+ repaired.push({
163
+ type: "message",
164
+ role: "assistant",
165
+ content: `[Computer call interrupted before a screenshot was recorded; call_id=${callId}]`
166
+ });
167
+ continue;
168
+ }
169
+ repaired.push(item, {
170
+ type: callKind === "custom" ? "custom_tool_call_output" : "function_call_output",
171
+ call_id: callId,
172
+ output: CODEX_INTERRUPTED_TOOL_OUTPUT
173
+ });
174
+ continue;
175
+ }
176
+ repaired.push(item);
177
+ }
178
+ return repaired;
179
+ }
180
+ /**
181
+ * Responses Lite requests must not pin image detail levels: codex-rs strips
182
+ * `detail` from every input image (message content and tool outputs) before
183
+ * sending, letting the server choose.
184
+ */
185
+ function stripImageDetails(input) {
186
+ for (const item of input) {
187
+ if (!item || typeof item !== "object") continue;
188
+ const content = "content" in item ? item.content : void 0;
189
+ const output = "output" in item ? item.output : void 0;
190
+ for (const collection of [content, output]) {
191
+ if (!Array.isArray(collection)) continue;
192
+ for (const part of collection) {
193
+ if (!part || typeof part !== "object") continue;
194
+ if (!("type" in part) || part.type !== "input_image") continue;
195
+ if ("detail" in part) part.detail = void 0;
196
+ }
197
+ }
198
+ }
199
+ }
200
+ /**
201
+ * Applies the Responses Lite body contract in place (codex-rs
202
+ * `build_responses_request` with `use_responses_lite`): strips pinned image
203
+ * detail, forces parallel tool calling off, moves tools into a leading
204
+ * `additional_tools` developer item and the base instructions into a
205
+ * developer message, then omits top-level `instructions`/`tools`. Because the
206
+ * rewrite removes top-level `tools`, a forced hosted-tool choice (e.g.
207
+ * `{ type: "web_search" }`) would leave the backend unable to validate the
208
+ * choice against a tools collection and it rejects the request with HTTP 400
209
+ * (#5771). Native computer and named-function choices preserve exact forcing
210
+ * by isolating the selected declaration and using `"required"`; other hosted
211
+ * choices fall back to `"auto"`. Explicit string constraints such as `"none"`
212
+ * and `"required"` remain valid. Shared by normal turns and both remote-compaction
213
+ * paths — codex-rs routes `/responses/compact` through the same builder.
214
+ */
215
+ function applyCodexResponsesLiteShape(body) {
216
+ const input = Array.isArray(body.input) ? body.input : [];
217
+ stripImageDetails(input);
218
+ body.parallel_tool_calls = false;
219
+ const declaredTools = Array.isArray(body.tools) ? body.tools : [];
220
+ let additionalTools = declaredTools;
221
+ if (body.tool_choice && typeof body.tool_choice === "object" && "type" in body.tool_choice) {
222
+ const choice = body.tool_choice;
223
+ const selected = declaredTools.find((tool) => {
224
+ if (tool === null || typeof tool !== "object" || !("type" in tool)) return false;
225
+ if (choice.type === "computer") return tool.type === "computer";
226
+ return choice.type === "function" && tool.type === "function" && "name" in choice && typeof choice.name === "string" && "name" in tool && tool.name === choice.name;
227
+ });
228
+ if (selected) {
229
+ additionalTools = [selected];
230
+ body.tool_choice = "required";
231
+ }
232
+ }
233
+ const prefix = [{
234
+ type: "additional_tools",
235
+ role: "developer",
236
+ tools: additionalTools
237
+ }];
238
+ if (typeof body.instructions === "string" && body.instructions.length > 0) prefix.push({
239
+ type: "message",
240
+ role: "developer",
241
+ content: [{
242
+ type: "input_text",
243
+ text: body.instructions
244
+ }]
245
+ });
246
+ body.input = [...prefix, ...input];
247
+ if (body.tool_choice !== "none" && body.tool_choice !== "required") body.tool_choice = "auto";
248
+ delete body.instructions;
249
+ delete body.tools;
250
+ }
251
+ async function transformRequestBody(body, model, options = {}, prompt) {
252
+ body.store = false;
253
+ body.stream = true;
254
+ if (body.input && Array.isArray(body.input)) {
255
+ body.input = filterInput(body.input);
256
+ if (body.input) body.input = repairToolCallPairs(body.input);
257
+ }
258
+ if (prompt?.developerMessages && prompt.developerMessages.length > 0) {
259
+ const developerMessages = prompt.developerMessages.map((text) => ({
260
+ type: "message",
261
+ role: "developer",
262
+ content: [{
263
+ type: "input_text",
264
+ text
265
+ }]
266
+ }));
267
+ const input = Array.isArray(body.input) ? body.input : [];
268
+ body.input = [...developerMessages, ...input];
269
+ }
270
+ let finalInstruction = prompt?.developerMessages.findLast((text) => text.trim().length > 0);
271
+ if (finalInstruction === void 0 && Array.isArray(body.input)) for (let itemIndex = body.input.length - 1; itemIndex >= 0; itemIndex -= 1) {
272
+ const item = body.input[itemIndex];
273
+ if (item.role !== "developer" || !Array.isArray(item.content)) continue;
274
+ for (let partIndex = item.content.length - 1; partIndex >= 0; partIndex -= 1) {
275
+ const part = item.content[partIndex];
276
+ if (part && typeof part === "object" && "type" in part && part.type === "input_text" && "text" in part && typeof part.text === "string" && part.text.trim().length > 0) {
277
+ finalInstruction = part.text;
278
+ break;
279
+ }
280
+ }
281
+ if (finalInstruction !== void 0) break;
282
+ }
283
+ if (finalInstruction === void 0 && typeof body.instructions === "string" && body.instructions.trim().length > 0) finalInstruction = body.instructions;
284
+ if (finalInstruction !== void 0) {
285
+ const input = Array.isArray(body.input) ? body.input : [];
286
+ let hasVisibleInput = false;
287
+ for (const item of input) if (item.role !== "developer") {
288
+ hasVisibleInput = true;
289
+ break;
290
+ }
291
+ if (!hasVisibleInput) body.input = [...input, {
292
+ type: "message",
293
+ role: "user",
294
+ content: [{
295
+ type: "input_text",
296
+ text: finalInstruction
297
+ }]
298
+ }];
299
+ }
300
+ const responsesLite = resolveCodexResponsesLite(model, options.responsesLite);
301
+ if (responsesLite) applyCodexResponsesLiteShape(body);
302
+ if (options.reasoningOff || options.reasoningEffort !== void 0 || responsesLite) {
303
+ const reasoningConfig = options.reasoningOff ? { effort: "none" } : options.reasoningEffort !== void 0 ? getReasoningConfig(model, options.reasoningEffort, options) : {};
304
+ body.reasoning = {
305
+ ...body.reasoning,
306
+ ...reasoningConfig
307
+ };
308
+ if (responsesLite) body.reasoning.context = "all_turns";
309
+ else if (options.reasoningContext !== void 0) {
310
+ if (options.reasoningContext === "all_turns" && !supportsAllTurnsReasoningContext(model.id)) delete body.reasoning.context;
311
+ else body.reasoning.context = options.reasoningContext;
312
+ }
313
+ } else delete body.reasoning;
314
+ if (model.reasoningMode && !options.reasoningOff) body.reasoning = {
315
+ ...body.reasoning,
316
+ mode: model.reasoningMode
317
+ };
318
+ if (body.reasoning?.summary !== void 0 && concurrentSummariesEnabled()) body.stream_options = { reasoning_summary_delivery: "sequential_cutoff" };
319
+ else delete body.stream_options;
320
+ if (options.textVerbosity !== void 0) body.text = {
321
+ ...body.text,
322
+ verbosity: options.textVerbosity
323
+ };
324
+ const include = Array.isArray(options.include) ? [...options.include] : [];
325
+ include.push("reasoning.encrypted_content");
326
+ body.include = Array.from(new Set(include));
327
+ delete body.max_output_tokens;
328
+ delete body.max_completion_tokens;
329
+ return body;
330
+ }
331
+ //#endregion
332
+ //#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/providers/openai-codex/response-handler.ts
333
+ /** Non-2xx response from the Codex backend, with the parsed body retained. */
334
+ var CodexApiError = class CodexApiError extends ProviderHttpError {
335
+ info;
336
+ constructor(info, headers) {
337
+ super(info.friendlyMessage || info.message, info.status, {
338
+ headers,
339
+ code: info.code
340
+ });
341
+ this.name = "CodexApiError";
342
+ this.info = info;
343
+ }
344
+ static async fromResponse(response) {
345
+ return new CodexApiError(await parseCodexError(response), response.headers);
346
+ }
347
+ };
348
+ async function parseCodexError(response) {
349
+ const raw = await response.text();
350
+ let message = raw || response.statusText || "Request failed";
351
+ let friendlyMessage;
352
+ let rateLimits;
353
+ let errorCode;
354
+ try {
355
+ const err = JSON.parse(raw)?.error ?? {};
356
+ const headers = response.headers;
357
+ const primary = {
358
+ used_percent: toNumber(headers.get("x-codex-primary-used-percent")),
359
+ window_minutes: toInt(headers.get("x-codex-primary-window-minutes")),
360
+ resets_at: toInt(headers.get("x-codex-primary-reset-at"))
361
+ };
362
+ const secondary = {
363
+ used_percent: toNumber(headers.get("x-codex-secondary-used-percent")),
364
+ window_minutes: toInt(headers.get("x-codex-secondary-window-minutes")),
365
+ resets_at: toInt(headers.get("x-codex-secondary-reset-at"))
366
+ };
367
+ rateLimits = primary.used_percent !== void 0 || secondary.used_percent !== void 0 ? {
368
+ primary,
369
+ secondary
370
+ } : void 0;
371
+ const code = String(err.code ?? err.type ?? "");
372
+ errorCode = code || void 0;
373
+ const resetsAt = err.resets_at ?? primary.resets_at ?? secondary.resets_at;
374
+ const mins = resetsAt ? Math.max(0, Math.round((resetsAt * 1e3 - Date.now()) / 6e4)) : void 0;
375
+ if (/usage_limit_reached|usage_not_included/i.test(code)) {
376
+ const planType = err.plan_type;
377
+ friendlyMessage = `You have hit your ChatGPT usage limit${planType ? ` (${String(planType).toLowerCase()} plan)` : ""}.${mins !== void 0 ? ` Try again in ~${mins} min.` : ""}`.trim();
378
+ } else if (/rate_limit_exceeded/i.test(code) || response.status === 429) friendlyMessage = `ChatGPT rate limit exceeded.${mins !== void 0 ? ` Try again in ~${mins} min.` : ""}`.trim();
379
+ message = err.message || friendlyMessage || message;
380
+ } catch {}
381
+ return {
382
+ message,
383
+ status: response.status,
384
+ code: errorCode,
385
+ friendlyMessage,
386
+ rateLimits,
387
+ raw
388
+ };
389
+ }
390
+ function toInt(v) {
391
+ if (v == null) return void 0;
392
+ const n = parseInt(v, 10);
393
+ return Number.isFinite(n) ? n : void 0;
394
+ }
395
+ //#endregion
396
+ //#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/providers/openai-codex-responses.ts
397
+ var openai_codex_responses_exports = /* @__PURE__ */ __exportAll({
398
+ CodexWebSocketTransportError: () => CodexWebSocketTransportError,
399
+ buildTransformedCodexRequestBody: () => buildTransformedCodexRequestBody,
400
+ convertCodexResponsesMessages: () => convertMessages,
401
+ convertOpenAICodexResponsesTools: () => convertOpenAICodexResponsesTools,
402
+ createCodexProviderStreamError: () => createCodexProviderStreamError,
403
+ getCodexAttestationHeader: () => getCodexAttestationHeader,
404
+ isRetryableCodexFailureEvent: () => isRetryableCodexFailureEvent,
405
+ normalizeCodexToolChoice: () => normalizeCodexToolChoice,
406
+ resolveCodexResponsesUrl: () => resolveCodexResponsesUrl,
407
+ streamOpenAICodexResponses: () => streamOpenAICodexResponses
408
+ });
409
+ const CODEX_DEBUG = $flag("PI_CODEX_DEBUG");
410
+ const CODEX_MAX_RETRIES = 5;
411
+ const CODEX_RETRY_DELAY_MS = 500;
412
+ function resolveCodexSseMaxAttempts(value) {
413
+ if (value === void 0) return 6;
414
+ if (!Number.isFinite(value)) return 1;
415
+ return Math.max(1, Math.trunc(value));
416
+ }
417
+ const CODEX_WEBSOCKET_CONNECT_TIMEOUT_MS = 1e4;
418
+ const CODEX_WEBSOCKET_PING_INTERVAL_MS = Number($env.PI_CODEX_WEBSOCKET_PING_INTERVAL_MS || 1e4);
419
+ const CODEX_WEBSOCKET_PONG_TIMEOUT_MS = Number($env.PI_CODEX_WEBSOCKET_PONG_TIMEOUT_MS || 6e4);
420
+ const CODEX_WEBSOCKET_MESSAGE_QUEUE_CAPACITY = Number($env.PI_CODEX_WEBSOCKET_MESSAGE_QUEUE_CAPACITY || 4096);
421
+ /**
422
+ * Maximum quiet period (no inbound frames AND no observed pong) we'll trust a
423
+ * reused WebSocket for before forcing a fresh handshake. Codex backends and
424
+ * intermediaries occasionally evict idle sockets server-side without sending a
425
+ * FIN, leaving the local `readyState` as OPEN while the next `send()` becomes a
426
+ * write into a half-open buffer. Reusing such a socket parks the next request
427
+ * at `#nextMessage` until the first-event/idle timeout fires (issue #1450). The
428
+ * heartbeat below also catches dead sockets, but only after `pongTimeoutMs`
429
+ * (default 60s) and only while a request is active — this gate closes the door
430
+ * earlier and even when the gap between requests is purely client-side (tool
431
+ * execution, user typing, etc.). Set `PI_CODEX_WEBSOCKET_MAX_IDLE_REUSE_MS=0`
432
+ * to disable.
433
+ */
434
+ const CODEX_WEBSOCKET_MAX_IDLE_REUSE_MS = Number($env.PI_CODEX_WEBSOCKET_MAX_IDLE_REUSE_MS || 3e4);
435
+ /**
436
+ * Steady-state liveness ceiling for the Codex WebSocket transport. Distinct from
437
+ * the OMP-wide stream watchdog removed in #1392: a WebSocket can stay TCP-open
438
+ * indefinitely without exchanging frames (server crash after upgrade, half-open
439
+ * network path), so we still need a transport-internal cap to detect those
440
+ * states and trigger the WS→SSE fallback. Only applies AFTER the first event
441
+ * has arrived — slow first-token paths wait as long as the caller permits.
442
+ */
443
+ const CODEX_WEBSOCKET_IDLE_TIMEOUT_MS = Number($env.PI_CODEX_WEBSOCKET_IDLE_TIMEOUT_MS || 3e5);
444
+ /**
445
+ * Maximum wait for the first WebSocket event before falling back to SSE.
446
+ * Unlike a stream watchdog, this triggers a transport switch (not a request
447
+ * failure) — the outer retry loop catches the timeout error and re-runs on
448
+ * SSE. Generous default so legitimately slow first-token providers still get
449
+ * a chance on the WS transport before falling through.
450
+ */
451
+ const CODEX_WEBSOCKET_FIRST_EVENT_TIMEOUT_MS = Number($env.PI_CODEX_WEBSOCKET_FIRST_EVENT_TIMEOUT_MS || 3e5);
452
+ const CODEX_WEBSOCKET_RETRY_BUDGET = Number($env.PI_CODEX_WEBSOCKET_RETRY_BUDGET || CODEX_MAX_RETRIES);
453
+ const CODEX_WEBSOCKET_RETRY_DELAY_MS = Number($env.PI_CODEX_WEBSOCKET_RETRY_DELAY_MS || CODEX_RETRY_DELAY_MS);
454
+ const CODEX_WEBSOCKET_TRANSPORT_ERROR_PREFIX = "Codex websocket transport error";
455
+ const CODEX_RETRYABLE_EVENT_CODES = /* @__PURE__ */ new Set([
456
+ "model_error",
457
+ "server_error",
458
+ "internal_error"
459
+ ]);
460
+ const CODEX_RETRYABLE_EVENT_MESSAGE = /processing your request|retry your request|temporar(?:y|ily)|overloaded|service.?unavailable|internal error|server error/i;
461
+ const CODEX_PROVIDER_SESSION_STATE_KEY = "openai-codex-responses";
462
+ /**
463
+ * Resolve the `x-oai-attestation` header value for one upstream request.
464
+ * Gated on ChatGPT-OAuth credentials (a Codex JWT carries `chatgpt_account_id`;
465
+ * codex-rs gates on `auth.is_chatgpt_auth()`). A throwing hook degrades to no
466
+ * header rather than failing the request.
467
+ */
468
+ async function getCodexAttestationHeader(accountId) {}
469
+ const X_CODEX_TURN_STATE_HEADER = "x-codex-turn-state";
470
+ const X_MODELS_ETAG_HEADER = "x-models-etag";
471
+ /** WebSocket frames cannot carry per-request HTTP headers; codex-rs mirrors the lite marker into `client_metadata` under this key. */
472
+ const CODEX_WS_RESPONSES_LITE_CLIENT_METADATA_KEY = "ws_request_header_x_openai_internal_codex_responses_lite";
473
+ /** `response.metadata` payload key carrying ChatGPT moderation metadata. */
474
+ const CODEX_MODERATION_METADATA_KEY = "openai_chatgpt_moderation_metadata";
475
+ /** Connection-level websocket failures that should immediately fall back to SSE without retrying. */
476
+ const CODEX_WEBSOCKET_FATAL_PATTERNS = [
477
+ "websocket error:",
478
+ "websocket closed before open",
479
+ "connection timeout"
480
+ ];
481
+ /** Max total time to spend retrying 429s with server-provided delays (5 minutes). */
482
+ const CODEX_RATE_LIMIT_BUDGET_MS = 3e5;
483
+ const CODEX_ADDITIONAL_PROGRESS_EVENT_TYPES = /* @__PURE__ */ new Set(["response.done", "response.incomplete"]);
484
+ const CODEX_WHITESPACE_TOOL_CALL_ARGUMENT_DELTA_EVENT_LIMIT = 256;
485
+ const CODEX_WHITESPACE_TOOL_CALL_ARGUMENT_DELTA_CHAR_LIMIT = 16384;
486
+ const CODEX_WHITESPACE_LOOP_RETRY_LIMIT = 2;
487
+ const CODEX_WHITESPACE_LOOP_RETRY_DELAY_MS = 250;
488
+ function isCodexStreamProgressEvent(event) {
489
+ if (isOpenAIResponsesProgressEvent(event)) return true;
490
+ if (!event || typeof event !== "object") return false;
491
+ const type = event.type;
492
+ return typeof type === "string" && CODEX_ADDITIONAL_PROGRESS_EVENT_TYPES.has(type);
493
+ }
494
+ function extractCodexFrameResponseId(frame) {
495
+ const id = frame.response?.id;
496
+ return typeof id === "string" && id.length > 0 ? id : void 0;
497
+ }
498
+ function extractCodexFrameSequenceNumber(frame) {
499
+ const raw = frame.sequence_number;
500
+ return typeof raw === "number" && Number.isFinite(raw) ? Math.trunc(raw) : void 0;
501
+ }
502
+ function createCodexWebSocketTimeoutMessage(reason, details) {
503
+ const now = Date.now();
504
+ return `${reason} (last event: ${details.lastEventType ? `${details.lastEventType} ${Math.max(0, now - details.lastEventAt)}ms ago` : "none"}; last progress: ${details.lastProgressEventType ? `${details.lastProgressEventType} ${Math.max(0, now - details.lastProgressAt)}ms ago` : "none"})`;
505
+ }
506
+ const CODEX_RESERVED_METADATA_KEYS = {
507
+ installation_id: true,
508
+ [OPENAI_HEADERS.INSTALLATION_ID]: true,
509
+ session_id: true,
510
+ thread_id: true,
511
+ turn_id: true,
512
+ window_id: true,
513
+ [OPENAI_HEADERS.WINDOW_ID]: true,
514
+ [OPENAI_HEADERS.TURN_METADATA]: true,
515
+ [OPENAI_HEADERS.PARENT_THREAD_ID]: true,
516
+ [OPENAI_HEADERS.SUBAGENT]: true,
517
+ request_kind: true,
518
+ compaction: true,
519
+ code_mode_tool_names: true,
520
+ turn_started_at_unix_ms: true,
521
+ forked_from_thread_id: true,
522
+ parent_thread_id: true,
523
+ parent_turn_id: true,
524
+ subagent_kind: true,
525
+ thread_source: true,
526
+ sandbox: true,
527
+ workspaces: true
528
+ };
529
+ function createCodexMetadataSessionState(sessionId) {
530
+ return {
531
+ sessionId,
532
+ threadId: crypto.randomUUID(),
533
+ windowId: crypto.randomUUID()
534
+ };
535
+ }
536
+ function getOrCreateCodexMetadataSessionState(sessionId, providerState) {
537
+ if (!providerState) return createCodexMetadataSessionState(sessionId);
538
+ const existing = providerState.metadataSessions.get(sessionId);
539
+ if (existing) return existing;
540
+ const created = createCodexMetadataSessionState(sessionId);
541
+ providerState.metadataSessions.set(sessionId, created);
542
+ return created;
543
+ }
544
+ function createCodexCompatibilityIdentity(session) {
545
+ return {
546
+ installationId: getInstallId(),
547
+ sessionId: session.sessionId,
548
+ threadId: session.threadId,
549
+ windowId: session.windowId
550
+ };
551
+ }
552
+ function resolveCodexStartNewTurn(session, requestKind, compaction, override) {
553
+ if (requestKind !== "compaction") {
554
+ if (requestKind === "turn") {
555
+ const reuseCompactionTurn = session.reuseTurnForNextRequest === true;
556
+ session.reuseTurnForNextRequest = false;
557
+ session.compactionOperationId = void 0;
558
+ if (reuseCompactionTurn) return false;
559
+ }
560
+ return override ?? requestKind === "turn";
561
+ }
562
+ if (!compaction) return override ?? false;
563
+ const startsNewOperation = session.compactionOperationId !== compaction.operationId;
564
+ if (startsNewOperation) session.reuseTurnForNextRequest = false;
565
+ session.compactionOperationId = compaction.operationId;
566
+ return override ?? (compaction.phase !== "mid_turn" && startsNewOperation);
567
+ }
568
+ function toAsciiJsonString(value) {
569
+ return JSON.stringify(value).replace(/[\x7f-\uffff]/g, (char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`);
570
+ }
571
+ function createCodexRequestMetadata(session, requestKind, options) {
572
+ if (options.startNewTurn || !session.turnId) {
573
+ session.turnId = crypto.randomUUID();
574
+ session.turnStartedAtUnixMs = options.turnStartedAtUnixMs;
575
+ }
576
+ const identity = createCodexCompatibilityIdentity(session);
577
+ const parentTurnId = options.parentTurnId?.trim() ? options.parentTurnId : void 0;
578
+ const extra = {};
579
+ const callerMetadata = options.clientMetadata;
580
+ if (callerMetadata) {
581
+ for (const key in callerMetadata) if (!CODEX_RESERVED_METADATA_KEYS[key]) extra[key] = callerMetadata[key];
582
+ }
583
+ const turnMetadata = {
584
+ installation_id: identity.installationId,
585
+ session_id: identity.sessionId,
586
+ thread_id: identity.threadId,
587
+ turn_id: session.turnId,
588
+ window_id: identity.windowId,
589
+ request_kind: requestKind
590
+ };
591
+ if (parentTurnId) turnMetadata.parent_turn_id = parentTurnId;
592
+ if (options.compaction) turnMetadata.compaction = {
593
+ trigger: options.compaction.trigger,
594
+ reason: options.compaction.reason,
595
+ implementation: options.compaction.implementation,
596
+ phase: options.compaction.phase,
597
+ strategy: options.compaction.strategy
598
+ };
599
+ if (session.turnStartedAtUnixMs !== void 0) turnMetadata.turn_started_at_unix_ms = session.turnStartedAtUnixMs;
600
+ for (const key in extra) turnMetadata[key] = extra[key];
601
+ const turnMetadataJson = toAsciiJsonString(turnMetadata);
602
+ const clientMetadata = {
603
+ [OPENAI_HEADERS.INSTALLATION_ID]: identity.installationId,
604
+ session_id: identity.sessionId,
605
+ thread_id: identity.threadId,
606
+ [OPENAI_HEADERS.WINDOW_ID]: identity.windowId,
607
+ turn_id: session.turnId
608
+ };
609
+ if (parentTurnId) clientMetadata.parent_turn_id = parentTurnId;
610
+ clientMetadata[OPENAI_HEADERS.TURN_METADATA] = turnMetadataJson;
611
+ return {
612
+ ...identity,
613
+ turnId: session.turnId,
614
+ turnMetadataJson,
615
+ clientMetadata
616
+ };
617
+ }
618
+ function applyCodexCompatibilityHeaders(headers, metadata) {
619
+ headers.set(OPENAI_HEADERS.SCOPED_SESSION_ID, metadata.sessionId);
620
+ headers.set(OPENAI_HEADERS.THREAD_ID, metadata.threadId);
621
+ headers.set(OPENAI_HEADERS.WINDOW_ID, metadata.windowId);
622
+ if (metadata.turnMetadataJson) headers.set(OPENAI_HEADERS.TURN_METADATA, metadata.turnMetadataJson);
623
+ else headers.delete(OPENAI_HEADERS.TURN_METADATA);
624
+ }
625
+ var CodexStreamRuntime = class {
626
+ eventStream;
627
+ requestBodyForState;
628
+ transport;
629
+ websocketState;
630
+ /**
631
+ * Items open on the wire keyed by `item.id`. `response.output_item.added`
632
+ * registers here; `output_item.done` removes. A keyed event whose `item_id`
633
+ * is not present is dropped rather than appended to a sibling.
634
+ */
635
+ openItems = /* @__PURE__ */ new Map();
636
+ /**
637
+ * Items open on the wire keyed by `output_index` for streams whose function
638
+ * call items omit `id`; these still carry `output_index` on deltas/done.
639
+ */
640
+ openItemsByOutputIndex = /* @__PURE__ */ new Map();
641
+ /**
642
+ * Most recently added open item for events that omit both `item_id` and
643
+ * `output_index`. Always tracks the latest `output_item.added`, including
644
+ * fully keyless items that never make it into the keyed maps; cleared when
645
+ * its item closes.
646
+ */
647
+ currentEntry = null;
648
+ /** Convenience mirrors of {@link currentEntry} for legacy singleton handlers. */
649
+ currentItem = null;
650
+ currentBlock = null;
651
+ nativeOutputItems = [];
652
+ /** Sequential-cutoff summary sections/emitted text, global to the response (indices span reasoning items). */
653
+ cutoffSummaries = createSequentialCutoffSummaryState();
654
+ /** Summary deltas buffered while waiting to see whether atomic `.done` events arrive. */
655
+ pendingSummaryDeltas = /* @__PURE__ */ new Map();
656
+ websocketStreamRetries = 0;
657
+ providerRetryAttempt = 0;
658
+ sawTerminalEvent = false;
659
+ canSafelyReplayWebsocketOverSse = true;
660
+ whitespaceToolCallArgumentsDelta;
661
+ whitespaceLoopRetries = 0;
662
+ constructor(initial) {
663
+ this.eventStream = initial.eventStream;
664
+ this.requestBodyForState = initial.requestBodyForState;
665
+ this.transport = initial.transport;
666
+ this.websocketState = initial.websocketState;
667
+ }
668
+ /**
669
+ * Wipe per-attempt accumulator state before a recovery path replays the turn.
670
+ * Keeps {@link openItems} and the legacy singleton-current pointers in lockstep
671
+ * with {@link nativeOutputItems} so a stale delta from the failed attempt can't
672
+ * bind to a sibling on the retry.
673
+ */
674
+ resetAccumulators() {
675
+ this.openItems.clear();
676
+ this.openItemsByOutputIndex.clear();
677
+ this.currentEntry = null;
678
+ this.currentItem = null;
679
+ this.currentBlock = null;
680
+ this.nativeOutputItems.length = 0;
681
+ this.pendingSummaryDeltas.clear();
682
+ this.cutoffSummaries = createSequentialCutoffSummaryState();
683
+ }
684
+ /**
685
+ * Look up the open item a Codex stream event targets. `item_id` wins because it
686
+ * uniquely identifies a response item; `output_index` covers idless function
687
+ * call items. A keyed event whose target is already closed is dropped instead
688
+ * of being routed to a sibling. Only streams that omit both keys fall back to
689
+ * {@link currentEntry} — the most recently added item, including fully keyless
690
+ * ones that never reached the keyed maps.
691
+ */
692
+ openItemForEvent(rawEvent) {
693
+ const itemId = typeof rawEvent.item_id === "string" ? rawEvent.item_id : "";
694
+ if (itemId) return this.openItems.get(itemId) ?? null;
695
+ const outputIndex = typeof rawEvent.output_index === "number" && Number.isFinite(rawEvent.output_index) ? Math.trunc(rawEvent.output_index) : void 0;
696
+ if (outputIndex !== void 0) return this.openItemsByOutputIndex.get(outputIndex) ?? null;
697
+ return this.currentEntry;
698
+ }
699
+ queueSummaryDelta(entry, delta) {
700
+ if (entry?.block?.type !== "thinking" || delta.length === 0) return;
701
+ const pending = this.pendingSummaryDeltas.get(entry) ?? [];
702
+ pending.push(delta);
703
+ this.pendingSummaryDeltas.set(entry, pending);
704
+ }
705
+ takeSummaryDeltas(entry) {
706
+ if (!entry) return [];
707
+ const pending = this.pendingSummaryDeltas.get(entry) ?? [];
708
+ this.pendingSummaryDeltas.delete(entry);
709
+ return pending;
710
+ }
711
+ closeOpenItem(entry) {
712
+ if (!entry) return;
713
+ if (entry.itemId) this.openItems.delete(entry.itemId);
714
+ if (entry.outputIndex !== void 0) this.openItemsByOutputIndex.delete(entry.outputIndex);
715
+ if (this.currentEntry === entry) {
716
+ this.currentEntry = null;
717
+ this.currentItem = null;
718
+ this.currentBlock = null;
719
+ }
720
+ }
721
+ observeWhitespaceToolCallArgumentsDelta(rawEvent, delta) {
722
+ if (!isJsonWhitespaceOnly(delta)) {
723
+ this.whitespaceToolCallArgumentsDelta = void 0;
724
+ return;
725
+ }
726
+ const itemId = typeof rawEvent.item_id === "string" && rawEvent.item_id.length > 0 ? rawEvent.item_id : this.currentItem?.id ?? "";
727
+ const outputIndex = typeof rawEvent.output_index === "number" && Number.isFinite(rawEvent.output_index) ? Math.trunc(rawEvent.output_index) : void 0;
728
+ const sequenceNumber = typeof rawEvent.sequence_number === "number" && Number.isFinite(rawEvent.sequence_number) ? Math.trunc(rawEvent.sequence_number) : void 0;
729
+ let state = this.whitespaceToolCallArgumentsDelta;
730
+ if (!state || state.itemId !== itemId || state.outputIndex !== outputIndex) {
731
+ state = {
732
+ itemId,
733
+ outputIndex,
734
+ consecutiveEvents: 0,
735
+ consecutiveChars: 0,
736
+ firstSequenceNumber: sequenceNumber
737
+ };
738
+ this.whitespaceToolCallArgumentsDelta = state;
739
+ }
740
+ state.consecutiveEvents += 1;
741
+ state.consecutiveChars += delta.length;
742
+ state.lastSequenceNumber = sequenceNumber;
743
+ if (state.consecutiveEvents < CODEX_WHITESPACE_TOOL_CALL_ARGUMENT_DELTA_EVENT_LIMIT && state.consecutiveChars < CODEX_WHITESPACE_TOOL_CALL_ARGUMENT_DELTA_CHAR_LIMIT) return;
744
+ const itemLabel = itemId ? ` for item ${itemId}` : "";
745
+ const sequenceLabel = state.firstSequenceNumber === void 0 || state.lastSequenceNumber === void 0 ? "" : `, sequence ${state.firstSequenceNumber}..${state.lastSequenceNumber}`;
746
+ return { message: `Interrupted OpenAI Codex response after ${state.consecutiveEvents} consecutive whitespace-only tool-call argument delta events (${state.consecutiveChars} chars${sequenceLabel})${itemLabel}.` };
747
+ }
748
+ handleToolCallArgumentsDelta(rawEvent, stream, output) {
749
+ const delta = rawEvent.delta || "";
750
+ const interruption = this.observeWhitespaceToolCallArgumentsDelta(rawEvent, delta);
751
+ if (interruption) return interruption;
752
+ const entry = this.openItemForEvent(rawEvent);
753
+ if (!entry) return void 0;
754
+ if (entry.item.type !== "function_call" || entry.block?.type !== "toolCall") return void 0;
755
+ accumulateToolCallArgumentsDelta(entry.block, delta, stream, output, entry.contentIndex);
756
+ }
757
+ handleToolCallArgumentsDone(rawEvent) {
758
+ const entry = this.openItemForEvent(rawEvent);
759
+ if (entry?.item.type !== "function_call" || entry.block?.type !== "toolCall") return;
760
+ const args = rawEvent.arguments;
761
+ if (typeof args === "string") finalizeToolCallArgumentsDone(entry.block, args);
762
+ }
763
+ handleCustomToolCallInputDelta(rawEvent, stream, output) {
764
+ const delta = rawEvent.delta || "";
765
+ const interruption = this.observeWhitespaceToolCallArgumentsDelta(rawEvent, delta);
766
+ if (interruption) return interruption;
767
+ const entry = this.openItemForEvent(rawEvent);
768
+ if (!entry) return void 0;
769
+ if (entry.item.type !== "custom_tool_call" || entry.block?.type !== "toolCall") return void 0;
770
+ accumulateCustomToolCallInputDelta(entry.block, delta, stream, output, entry.contentIndex);
771
+ }
772
+ handleCustomToolCallInputDone(rawEvent) {
773
+ const entry = this.openItemForEvent(rawEvent);
774
+ if (entry?.item.type !== "custom_tool_call" || entry.block?.type !== "toolCall") return;
775
+ const input = rawEvent.input;
776
+ if (typeof input === "string") finalizeCustomToolCallInputDone(entry.block, input);
777
+ }
778
+ handleResponseCreated(rawEvent) {
779
+ const response = rawEvent.response;
780
+ const state = this.websocketState;
781
+ if (state && this.transport === "websocket" && typeof response?.id === "string" && response.id.length > 0) state.lastResponseId = response.id;
782
+ }
783
+ };
784
+ function createCodexProviderSessionState() {
785
+ const state = {
786
+ webSocketSessions: /* @__PURE__ */ new Map(),
787
+ webSocketPublicToPrivate: /* @__PURE__ */ new Map(),
788
+ metadataSessions: /* @__PURE__ */ new Map(),
789
+ close: () => {
790
+ for (const session of state.webSocketSessions.values()) session.connection?.close("session_disposed");
791
+ state.webSocketSessions.clear();
792
+ state.webSocketPublicToPrivate.clear();
793
+ state.metadataSessions.clear();
794
+ }
795
+ };
796
+ return state;
797
+ }
798
+ function isCodexProviderSessionState(state) {
799
+ return state !== void 0 && "webSocketSessions" in state && state.webSocketSessions instanceof Map && "webSocketPublicToPrivate" in state && state.webSocketPublicToPrivate instanceof Map && "metadataSessions" in state && state.metadataSessions instanceof Map;
800
+ }
801
+ function getCodexProviderSessionState(providerSessionState) {
802
+ if (!providerSessionState) return void 0;
803
+ const existing = providerSessionState.get(CODEX_PROVIDER_SESSION_STATE_KEY);
804
+ if (isCodexProviderSessionState(existing)) return existing;
805
+ const created = createCodexProviderSessionState();
806
+ providerSessionState.set(CODEX_PROVIDER_SESSION_STATE_KEY, created);
807
+ return created;
808
+ }
809
+ function isCodexWebSocketRetryableStreamError(error) {
810
+ if (!(error instanceof CodexWebSocketTransportError)) return false;
811
+ const message = error.message.toLowerCase();
812
+ return message.includes("websocket closed (") || message.includes("websocket closed before response completion") || message.includes("websocket connection is unavailable") || message.includes("websocket send failed") || message.includes("websocket ping failed") || message.includes("websocket pong timeout") || message.includes("websocket message queue exceeded") || message.includes("websocket request already in progress") || message.includes("idle timeout waiting for websocket") || message.includes("timeout waiting for first websocket event") || message.includes("syntaxerror") || message.includes("json");
813
+ }
814
+ function toCodexHeaderRecord(value) {
815
+ if (!value || typeof value !== "object") return null;
816
+ const headers = {};
817
+ for (const [key, entry] of Object.entries(value)) if (typeof entry === "string") headers[key] = entry;
818
+ else if (Array.isArray(entry) && entry.every((item) => typeof item === "string")) headers[key] = entry.join(",");
819
+ else if (typeof entry === "number" || typeof entry === "boolean") headers[key] = String(entry);
820
+ return Object.keys(headers).length > 0 ? headers : null;
821
+ }
822
+ function toCodexHeaders(value) {
823
+ if (!value) return void 0;
824
+ if (value instanceof Headers) return value;
825
+ if (Array.isArray(value)) try {
826
+ return new Headers(value);
827
+ } catch {
828
+ return;
829
+ }
830
+ const record = toCodexHeaderRecord(value);
831
+ if (!record) return void 0;
832
+ return new Headers(record);
833
+ }
834
+ function updateCodexSessionMetadataFromHeaders(state, headers) {
835
+ if (!state || !headers) return;
836
+ const resolvedHeaders = headers instanceof Headers ? headers : new Headers(headers);
837
+ const turnState = resolvedHeaders.get(X_CODEX_TURN_STATE_HEADER);
838
+ if (turnState && turnState.length > 0) state.turnState = turnState;
839
+ const modelsEtag = resolvedHeaders.get(X_MODELS_ETAG_HEADER);
840
+ if (modelsEtag && modelsEtag.length > 0) state.modelsEtag = modelsEtag;
841
+ }
842
+ function extractCodexWebSocketHandshakeHeaders(socket, openEvent) {
843
+ const eventRecord = openEvent;
844
+ const eventResponse = eventRecord?.response;
845
+ const socketRecord = socket;
846
+ const socketResponse = socketRecord.response;
847
+ const socketHandshake = socketRecord.handshake;
848
+ return toCodexHeaders(eventRecord?.responseHeaders) ?? toCodexHeaders(eventRecord?.headers) ?? toCodexHeaders(eventResponse?.headers) ?? toCodexHeaders(socketRecord.responseHeaders) ?? toCodexHeaders(socketRecord.handshakeHeaders) ?? toCodexHeaders(socketResponse?.headers) ?? toCodexHeaders(socketHandshake?.headers);
849
+ }
850
+ function notifyCodexWebSocketInbound(observer, parsed, text) {
851
+ const type = typeof parsed.type === "string" ? parsed.type : null;
852
+ const raw = [`: ws ← ${type ?? "(untyped)"}`];
853
+ if (type) raw.push(`event: ${type}`);
854
+ raw.push(`data: ${text}`);
855
+ notifyRawSseEvent(observer, {
856
+ event: type,
857
+ data: text,
858
+ raw
859
+ });
860
+ }
861
+ function notifyCodexWebSocketOutbound(observer, request, payload) {
862
+ const type = typeof request.type === "string" ? request.type : null;
863
+ const raw = [`: ws → ${type ?? "(untyped)"}`];
864
+ if (type) raw.push(`event: ${type}`);
865
+ raw.push(`data: ${payload}`);
866
+ notifyRawSseEvent(observer, {
867
+ event: type,
868
+ data: payload,
869
+ raw
870
+ });
871
+ }
872
+ function notifyCodexWebSocketMalformed(observer, data, error) {
873
+ const text = typeof data === "string" ? data : "";
874
+ const raw = [`: ws ← (parse-error: ${error instanceof Error ? error.message : String(error)})`];
875
+ if (text) raw.push(`data: ${text}`);
876
+ notifyRawSseEvent(observer, {
877
+ event: "parse_error",
878
+ data: text,
879
+ raw
880
+ });
881
+ }
882
+ /** @internal Exported for tests. */
883
+ function normalizeCodexToolChoice(choice, tools = [], model) {
884
+ if (!choice) return void 0;
885
+ if (typeof choice === "string") return choice;
886
+ const allowFreeform = model ? model.applyPatchToolType === "freeform" : false;
887
+ const mapName = (name) => {
888
+ const directTool = tools.find((tool) => tool.name === name);
889
+ const customTool = allowFreeform ? tools.find((tool) => tool.customFormat && (tool.name === name || tool.customWireName === name)) : void 0;
890
+ const offeredTool = customTool ?? directTool;
891
+ if (!offeredTool) return void 0;
892
+ if (offeredTool.native?.type === "computer" && model?.supportsComputerUse === true) return { type: "computer" };
893
+ return customTool ? {
894
+ type: "custom",
895
+ name: customTool.customWireName ?? customTool.name
896
+ } : {
897
+ type: "function",
898
+ name: offeredTool.name
899
+ };
900
+ };
901
+ if (choice.type === "computer") {
902
+ const computer = tools.find((tool) => tool.native?.type === "computer");
903
+ if (!computer) return void 0;
904
+ return model?.supportsComputerUse === true ? { type: "computer" } : {
905
+ type: "function",
906
+ name: computer.name
907
+ };
908
+ }
909
+ if (choice.type === "function") {
910
+ if ("function" in choice && choice.function?.name) return mapName(choice.function.name);
911
+ if ("name" in choice && choice.name) return mapName(choice.name);
912
+ }
913
+ if (choice.type === "tool" && choice.name) return mapName(choice.name);
914
+ }
915
+ function unrollCodexComputerItems(items, supportsImageDetailOriginal) {
916
+ const replayItems = stripOpenAIResponsesComputerLinkedReasoningIdsForReplay(items);
917
+ const unrolled = [];
918
+ for (const item of replayItems) {
919
+ if (item.type === "computer_call") {
920
+ const actions = item.actions ?? (item.action ? [item.action] : []);
921
+ unrolled.push({
922
+ type: "function_call",
923
+ call_id: item.call_id,
924
+ name: "computer",
925
+ arguments: JSON.stringify({ actions }),
926
+ status: item.status
927
+ });
928
+ continue;
929
+ }
930
+ if (item.type === "computer_call_output") {
931
+ const image = typeof item.output.image_url === "string" && item.output.image_url.length > 0 ? {
932
+ type: "input_image",
933
+ detail: supportsImageDetailOriginal ? "original" : "auto",
934
+ image_url: item.output.image_url
935
+ } : typeof item.output.file_id === "string" && item.output.file_id.length > 0 ? {
936
+ type: "input_image",
937
+ detail: supportsImageDetailOriginal ? "original" : "auto",
938
+ file_id: item.output.file_id
939
+ } : void 0;
940
+ unrolled.push({
941
+ type: "function_call_output",
942
+ call_id: item.call_id,
943
+ output: image ? "(see attached image)" : ""
944
+ });
945
+ if (image) unrolled.push({
946
+ role: "user",
947
+ content: [{
948
+ type: "input_text",
949
+ text: "Attached image from computer tool result:"
950
+ }, image]
951
+ });
952
+ continue;
953
+ }
954
+ unrolled.push(item);
955
+ }
956
+ return unrolled;
957
+ }
958
+ function unrollCodexComputerAssistantMessage(message) {
959
+ let changed = false;
960
+ const content = message.content.map((block) => {
961
+ if (block.type !== "toolCall" || block.providerMetadata?.type !== "computer") return block;
962
+ changed = true;
963
+ const call = {
964
+ ...block,
965
+ arguments: { actions: structuredCloneJSON(block.providerMetadata.actions) }
966
+ };
967
+ delete call.providerMetadata;
968
+ return call;
969
+ });
970
+ return changed ? {
971
+ ...message,
972
+ content
973
+ } : message;
974
+ }
975
+ function unrollCodexComputerToolResult(message) {
976
+ if (message.providerMetadata?.type !== "computer") return message;
977
+ const result = { ...message };
978
+ delete result.providerMetadata;
979
+ return result;
980
+ }
981
+ function getCodexServiceTierCostMultiplier(model, serviceTier) {
982
+ switch (serviceTier) {
983
+ case "flex": return .5;
984
+ case "priority": return model.id === "gpt-5.5" ? 2.5 : 2;
985
+ default: return 1;
986
+ }
987
+ }
988
+ function resolveCodexCostServiceTier(res, req) {
989
+ switch (res) {
990
+ case "flex": return "flex";
991
+ case "priority": return "priority";
992
+ default:
993
+ if (req === "flex" || req === "priority") return req;
994
+ return "default";
995
+ }
996
+ }
997
+ function applyCodexServiceTierPricing(model, usage, resTier, reqTier) {
998
+ const multiplier = getCodexServiceTierCostMultiplier(model, resolveCodexCostServiceTier(resTier, reqTier));
999
+ if (multiplier === 1) return;
1000
+ usage.cost.input *= multiplier;
1001
+ usage.cost.output *= multiplier;
1002
+ usage.cost.cacheRead *= multiplier;
1003
+ usage.cost.cacheWrite *= multiplier;
1004
+ usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
1005
+ }
1006
+ function resetOutputState(output) {
1007
+ output.content.length = 0;
1008
+ output.usage = {
1009
+ input: 0,
1010
+ output: 0,
1011
+ cacheRead: 0,
1012
+ cacheWrite: 0,
1013
+ totalTokens: 0,
1014
+ cost: {
1015
+ input: 0,
1016
+ output: 0,
1017
+ cacheRead: 0,
1018
+ cacheWrite: 0,
1019
+ total: 0
1020
+ }
1021
+ };
1022
+ output.stopReason = "stop";
1023
+ output.stopDetails = void 0;
1024
+ }
1025
+ function createRequestSetup(options) {
1026
+ const requestAbortController = new AbortController();
1027
+ const requestSignal = options?.signal ? AbortSignal.any([options.signal, requestAbortController.signal]) : requestAbortController.signal;
1028
+ const idleTimeoutMs = options?.streamIdleTimeoutMs ?? getOpenAIStreamIdleTimeoutMs();
1029
+ const websocketIdleTimeoutMs = options?.streamIdleTimeoutMs ?? CODEX_WEBSOCKET_IDLE_TIMEOUT_MS;
1030
+ const firstEventTimeoutMs = options?.streamFirstEventTimeoutMs ?? getOpenAIStreamFirstEventTimeoutMs(idleTimeoutMs);
1031
+ const websocketFirstEventTimeoutMs = options?.streamFirstEventTimeoutMs ?? CODEX_WEBSOCKET_FIRST_EVENT_TIMEOUT_MS;
1032
+ const wrapCodexSseStream = (source) => iterateWithIdleTimeout(source, {
1033
+ idleTimeoutMs,
1034
+ firstItemTimeoutMs: firstEventTimeoutMs,
1035
+ firstItemErrorMessage: "OpenAI Codex SSE stream timed out while waiting for the first event",
1036
+ errorMessage: "OpenAI Codex SSE stream stalled while waiting for the next event",
1037
+ onIdle: () => requestAbortController.abort(),
1038
+ onFirstItemTimeout: () => requestAbortController.abort(),
1039
+ abortSignal: options?.signal,
1040
+ isProgressItem: isCodexStreamProgressEvent
1041
+ });
1042
+ return {
1043
+ requestAbortController,
1044
+ requestSignal,
1045
+ wrapCodexSseStream,
1046
+ firstEventTimeoutMs,
1047
+ websocketIdleTimeoutMs,
1048
+ websocketFirstEventTimeoutMs
1049
+ };
1050
+ }
1051
+ function createCodexRequestContext(model, transformedBody, options, contextOptions) {
1052
+ const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
1053
+ if (!apiKey) throw new MissingApiKeyError(model.provider);
1054
+ const accountId = getCodexAccountId(apiKey);
1055
+ const baseUrl = model.baseUrl || "https://chatgpt.com/backend-api";
1056
+ const url = resolveCodexResponsesUrl(baseUrl);
1057
+ const transportSessionId = normalizeOpenAIPromptCacheKey(options?.sessionId);
1058
+ const codexClientVersion = CODEX_CLIENT_VERSION;
1059
+ const requestHeaders = {
1060
+ ...model.headers ?? {},
1061
+ ...options?.headers ?? {}
1062
+ };
1063
+ const rawRequestDump = {
1064
+ provider: model.provider,
1065
+ api: model.api,
1066
+ model: model.id,
1067
+ method: "POST",
1068
+ url,
1069
+ body: transformedBody
1070
+ };
1071
+ const providerSessionState = getCodexProviderSessionState(options?.providerSessionState);
1072
+ const isolatedTransportState = contextOptions.isolateCompactionTransport && options?.codexCompaction ? createCodexProviderSessionState() : void 0;
1073
+ const transportProviderSessionState = isolatedTransportState ?? providerSessionState;
1074
+ const responsesLite = resolveCodexResponsesLite(model, options?.responsesLite);
1075
+ const sessionKey = getCodexWebSocketSessionKey(transportSessionId, model, accountId, apiKey, baseUrl, responsesLite);
1076
+ const publicSessionKey = transportSessionId ? `${baseUrl}:${model.id}:${transportSessionId}` : void 0;
1077
+ if (sessionKey && publicSessionKey) transportProviderSessionState?.webSocketPublicToPrivate.set(publicSessionKey, sessionKey);
1078
+ const sharedWebsocketState = sessionKey && providerSessionState ? isolatedTransportState ? providerSessionState.webSocketSessions.get(sessionKey) : getCodexWebSocketSessionState(sessionKey, providerSessionState) : void 0;
1079
+ const websocketState = sessionKey && isolatedTransportState ? getCodexWebSocketSessionState(sessionKey, isolatedTransportState) : sharedWebsocketState;
1080
+ if (isolatedTransportState && websocketState && sharedWebsocketState) {
1081
+ websocketState.disableWebsocket = sharedWebsocketState.disableWebsocket;
1082
+ websocketState.turnState = sharedWebsocketState.turnState;
1083
+ websocketState.modelsEtag = sharedWebsocketState.modelsEtag;
1084
+ }
1085
+ const metadataSession = getOrCreateCodexMetadataSessionState(transportSessionId ?? crypto.randomUUID(), providerSessionState);
1086
+ const compaction = options?.codexCompaction;
1087
+ const requestKind = compaction ? "compaction" : "turn";
1088
+ const startNewTurn = resolveCodexStartNewTurn(metadataSession, requestKind, compaction, contextOptions.startNewTurn);
1089
+ if (websocketState && startNewTurn) websocketState.turnState = void 0;
1090
+ const requestMetadata = createCodexRequestMetadata(metadataSession, requestKind, {
1091
+ startNewTurn,
1092
+ turnStartedAtUnixMs: compaction ? startNewTurn || !metadataSession.turnId ? Date.now() : void 0 : contextOptions.turnStartedAtUnixMs,
1093
+ clientMetadata: transformedBody.client_metadata,
1094
+ parentTurnId: options?.parentTurnId,
1095
+ compaction
1096
+ });
1097
+ transformedBody.client_metadata = requestMetadata.clientMetadata;
1098
+ return {
1099
+ apiKey,
1100
+ accountId,
1101
+ baseUrl,
1102
+ url,
1103
+ requestHeaders,
1104
+ transportSessionId,
1105
+ providerSessionState,
1106
+ isolatedTransportState,
1107
+ websocketState,
1108
+ responsesLite,
1109
+ requestMetadata,
1110
+ codexClientVersion,
1111
+ transformedBody,
1112
+ rawRequestDump
1113
+ };
1114
+ }
1115
+ async function buildCodexRequestContext(model, context, options) {
1116
+ return createCodexRequestContext(model, await buildTransformedCodexRequestBody(model, context, options, getOpenAIPromptCacheKey(options)), options, {
1117
+ isolateCompactionTransport: true,
1118
+ startNewTurn: options?.codexCompaction ? void 0 : !isCodexWithinTurnContinuation(context),
1119
+ turnStartedAtUnixMs: options?.codexCompaction ? void 0 : getCodexTurnStartedAtUnixMs(context)
1120
+ });
1121
+ }
1122
+ /** @internal Exported for tests. */
1123
+ async function buildTransformedCodexRequestBody(model, context, options, promptCacheKey = getOpenAIPromptCacheKey(options)) {
1124
+ const params = {
1125
+ model: model.requestModelId ?? model.id,
1126
+ input: convertMessages(model, context),
1127
+ stream: true,
1128
+ prompt_cache_key: promptCacheKey
1129
+ };
1130
+ applyOpenAIServiceTier(params, options?.serviceTier, model);
1131
+ if (context.tools && context.tools.length > 0) {
1132
+ params.tools = convertOpenAICodexResponsesTools(context.tools, model);
1133
+ if (options?.toolChoice) {
1134
+ const toolChoice = normalizeCodexToolChoice(options.toolChoice, context.tools, model);
1135
+ if (toolChoice) params.tool_choice = toolChoice;
1136
+ }
1137
+ }
1138
+ const systemPrompts = normalizeSystemPrompts(context.systemPrompt);
1139
+ if (systemPrompts.length > 0) params.instructions = systemPrompts[0];
1140
+ const developerMessages = systemPrompts.slice(1);
1141
+ if (options?.clientMetadata && Object.keys(options.clientMetadata).length > 0) params.client_metadata = { ...options.clientMetadata };
1142
+ return transformRequestBody(params, model, {
1143
+ reasoningEffort: options?.reasoning,
1144
+ reasoningOff: options?.forceReasoningOff,
1145
+ reasoningSummary: options?.reasoningSummary,
1146
+ reasoningContext: options?.reasoningContext,
1147
+ textVerbosity: options?.textVerbosity,
1148
+ include: options?.include,
1149
+ responsesLite: options?.responsesLite
1150
+ }, { developerMessages });
1151
+ }
1152
+ async function openInitialCodexEventStream(model, options, requestSetup, requestContext) {
1153
+ const { transformedBody, websocketState } = requestContext;
1154
+ if (websocketState && shouldUseCodexWebSocket(model, websocketState, options?.preferWebsockets)) {
1155
+ const websocketRetryBudget = CODEX_WEBSOCKET_RETRY_BUDGET;
1156
+ let websocketRetries = 0;
1157
+ while (true) try {
1158
+ return await openCodexWebSocketTransport(model, options, requestContext, requestSetup, websocketState, websocketRetries, options ? (event) => options.onSseEvent?.(event, model) : void 0);
1159
+ } catch (error) {
1160
+ if (!(error instanceof CodexWebSocketTransportError)) throw error;
1161
+ const fatalWebSocketMessage = error.message.toLowerCase();
1162
+ const isFatal = CODEX_WEBSOCKET_FATAL_PATTERNS.some((pattern) => fatalWebSocketMessage.includes(pattern.toLowerCase()));
1163
+ const activateFallback = isFatal || websocketRetries >= websocketRetryBudget;
1164
+ recordCodexWebSocketFailure(websocketState, activateFallback);
1165
+ CODEX_DEBUG && debug("[codex] codex websocket fallback", {
1166
+ error: error.message,
1167
+ retry: websocketRetries,
1168
+ retryBudget: websocketRetryBudget,
1169
+ activated: activateFallback,
1170
+ fatal: isFatal
1171
+ });
1172
+ if (!activateFallback) {
1173
+ websocketRetries += 1;
1174
+ await scheduler.wait(CODEX_WEBSOCKET_RETRY_DELAY_MS * Math.max(1, websocketRetries), { signal: requestSetup.requestSignal });
1175
+ continue;
1176
+ }
1177
+ break;
1178
+ }
1179
+ }
1180
+ return openCodexSseTransport(model, requestContext, requestSetup, options, websocketState, transformedBody);
1181
+ }
1182
+ async function openCodexWebSocketTransport(model, options, requestContext, requestSetup, websocketState, retry, onSseEvent) {
1183
+ const canAppendBeforeRequest = websocketState.canAppend === true;
1184
+ const chainedBody = buildCodexChainedRequestBody(requestContext.transformedBody, websocketState);
1185
+ const websocketClientMetadata = { ...chainedBody.client_metadata ?? {} };
1186
+ if (requestContext.responsesLite) websocketClientMetadata[CODEX_WS_RESPONSES_LITE_CLIENT_METADATA_KEY] = "true";
1187
+ if (websocketState.turnState) websocketClientMetadata[X_CODEX_TURN_STATE_HEADER] = websocketState.turnState;
1188
+ let websocketRequest = {
1189
+ type: "response.create",
1190
+ ...chainedBody,
1191
+ client_metadata: websocketClientMetadata
1192
+ };
1193
+ const replacementWebsocketRequest = await options?.onPayload?.(websocketRequest, model);
1194
+ if (replacementWebsocketRequest !== void 0) websocketRequest = replacementWebsocketRequest;
1195
+ recordCodexTurnRequestDiagnostics(websocketState, websocketRequest, "websocket", canAppendBeforeRequest);
1196
+ const websocketHeaders = createCodexHeaders(requestContext.requestHeaders, requestContext.accountId, requestContext.apiKey, requestContext.codexClientVersion, requestContext.transportSessionId, "websocket", websocketState, requestContext.responsesLite, requestContext.requestMetadata, await getCodexAttestationHeader(requestContext.accountId));
1197
+ const requestBodyForState = structuredCloneJSON(requestContext.transformedBody);
1198
+ if (websocketRequest.stream_options === void 0) delete requestBodyForState.stream_options;
1199
+ else requestBodyForState.stream_options = websocketRequest.stream_options;
1200
+ requestContext.rawRequestDump.body = websocketRequest;
1201
+ CODEX_DEBUG && debug("[codex] codex websocket request", {
1202
+ url: toWebSocketUrl(requestContext.url),
1203
+ model: requestContext.transformedBody.model,
1204
+ reasoningEffort: requestContext.transformedBody.reasoning?.effort ?? null,
1205
+ headers: redactHeaders(websocketHeaders),
1206
+ sentTurnStateHeader: websocketHeaders.has(X_CODEX_TURN_STATE_HEADER),
1207
+ sentModelsEtagHeader: websocketHeaders.has(X_MODELS_ETAG_HEADER),
1208
+ requestType: websocketRequest.type,
1209
+ retry,
1210
+ retryBudget: CODEX_WEBSOCKET_RETRY_BUDGET
1211
+ });
1212
+ return {
1213
+ eventStream: (await getOrCreateCodexWebSocketConnection(websocketState, toWebSocketUrl(requestContext.url), websocketHeaders, model.provider, requestSetup.requestSignal)).streamRequest(websocketRequest, {
1214
+ idleTimeoutMs: requestSetup.websocketIdleTimeoutMs,
1215
+ firstEventTimeoutMs: requestSetup.websocketFirstEventTimeoutMs
1216
+ }, requestSetup.requestSignal, onSseEvent),
1217
+ requestBodyForState,
1218
+ transport: "websocket"
1219
+ };
1220
+ }
1221
+ function getCodexTurnStartedAtUnixMs(context) {
1222
+ for (let i = context.messages.length - 1; i >= 0; i--) {
1223
+ const message = context.messages[i];
1224
+ if (message?.role === "user" && Number.isFinite(message.timestamp)) return Math.trunc(message.timestamp);
1225
+ }
1226
+ return Date.now();
1227
+ }
1228
+ /**
1229
+ * True when the request continues the current turn (everything after the
1230
+ * last assistant message is tool results), false when a new user turn starts.
1231
+ * Mirrors codex-rs, which scopes `x-codex-turn-state` to a single turn and
1232
+ * clears it when the next one begins.
1233
+ */
1234
+ function isCodexWithinTurnContinuation(context) {
1235
+ for (let i = context.messages.length - 1; i >= 0; i--) {
1236
+ const role = context.messages[i]?.role;
1237
+ if (role === "toolResult") continue;
1238
+ return role === "assistant";
1239
+ }
1240
+ return false;
1241
+ }
1242
+ async function openCodexSseTransport(model, requestContext, requestSetup, options, state, body = requestContext.transformedBody) {
1243
+ const open = async (wireBody) => {
1244
+ requestContext.rawRequestDump.body = wireBody;
1245
+ return requestSetup.wrapCodexSseStream(await openCodexSseEventStream(requestContext.url, requestContext.requestHeaders, requestContext.accountId, requestContext.apiKey, requestContext.transportSessionId, wireBody, state, requestContext.responsesLite, requestContext.codexClientVersion, requestContext.requestMetadata, requestSetup.requestSignal, requestSetup.firstEventTimeoutMs, options?.codexSseMaxAttempts, (event) => options?.onSseEvent?.(event, model), options?.fetch));
1246
+ };
1247
+ const canAppendBeforeRequest = state?.canAppend === true;
1248
+ let wireBody = body;
1249
+ const replacementWireBody = await options?.onPayload?.(wireBody, model);
1250
+ if (replacementWireBody !== void 0) wireBody = replacementWireBody;
1251
+ recordCodexTurnRequestDiagnostics(state, wireBody, "sse", canAppendBeforeRequest);
1252
+ return {
1253
+ eventStream: await open(wireBody),
1254
+ requestBodyForState: structuredCloneJSON(wireBody),
1255
+ transport: "sse"
1256
+ };
1257
+ }
1258
+ function isJsonWhitespaceOnly(value) {
1259
+ for (let index = 0; index < value.length; index += 1) {
1260
+ const code = value.charCodeAt(index);
1261
+ if (code !== 9 && code !== 10 && code !== 13 && code !== 32) return false;
1262
+ }
1263
+ return true;
1264
+ }
1265
+ function createOutputBlockForItem(item) {
1266
+ if (item.type === "reasoning") return {
1267
+ type: "thinking",
1268
+ thinking: ""
1269
+ };
1270
+ if (item.type === "message") {
1271
+ const phase = item.phase === "commentary" || item.phase === "final_answer" ? item.phase : void 0;
1272
+ return {
1273
+ type: "text",
1274
+ text: "",
1275
+ textSignature: encodeTextSignatureV1(item.id, phase)
1276
+ };
1277
+ }
1278
+ if (item.type === "function_call") return {
1279
+ type: "toolCall",
1280
+ id: encodeResponsesToolCallId(item.call_id, item.id),
1281
+ name: item.name,
1282
+ arguments: {},
1283
+ [kStreamingPartialJson]: item.arguments || ""
1284
+ };
1285
+ if (item.type === "computer_call") return {
1286
+ type: "toolCall",
1287
+ id: encodeResponsesToolCallId(item.call_id, item.id),
1288
+ name: "computer",
1289
+ arguments: {},
1290
+ providerMetadata: computerCallMetadata(item),
1291
+ [kStreamingPartialJson]: ""
1292
+ };
1293
+ if (item.type === "custom_tool_call") return {
1294
+ type: "toolCall",
1295
+ id: encodeResponsesToolCallId(item.call_id, item.id),
1296
+ name: item.name,
1297
+ arguments: { input: item.input ?? "" },
1298
+ customWireName: item.name,
1299
+ [kStreamingPartialJson]: item.input ?? ""
1300
+ };
1301
+ return null;
1302
+ }
1303
+ function getOutputBlockStartEventType(block) {
1304
+ if (block.type === "thinking") return "thinking_start";
1305
+ if (block.type === "text") return "text_start";
1306
+ return "toolcall_start";
1307
+ }
1308
+ const CODEX_STALE_PREVIOUS_RESPONSE_CODES = {
1309
+ previous_response_not_found: true,
1310
+ codex_previous_response_stale: true
1311
+ };
1312
+ function isCodexStalePreviousResponseError(error) {
1313
+ if (!(error instanceof Error)) return false;
1314
+ if ("code" in error && typeof error.code === "string" && Object.hasOwn(CODEX_STALE_PREVIOUS_RESPONSE_CODES, error.code)) return true;
1315
+ return /previous[ _]?response/i.test(error.message) && /not[ _]?found|invalid|expired|stale|unsupported/i.test(error.message);
1316
+ }
1317
+ async function handleCodexStreamFailure(context, error) {
1318
+ const { output } = context;
1319
+ if (context.requestContext.websocketState) {
1320
+ resetCodexWebSocketAppendState(context.requestContext.websocketState);
1321
+ context.requestContext.websocketState.turnState = void 0;
1322
+ context.requestContext.websocketState.modelsEtag = void 0;
1323
+ }
1324
+ const result = await finalize(error, {
1325
+ api: context.model.api,
1326
+ signal: context.options?.signal,
1327
+ rawRequestDump: context.requestContext.rawRequestDump
1328
+ });
1329
+ output.stopReason = result.stopReason;
1330
+ output.errorStatus = result.status;
1331
+ output.errorId = result.id;
1332
+ output.errorMessage = result.message;
1333
+ output.duration = performance.now() - context.startTime;
1334
+ if (context.firstTokenTime) output.ttft = context.firstTokenTime - context.startTime;
1335
+ return output;
1336
+ }
1337
+ /**
1338
+ * Owns one `streamOpenAICodexResponses` call: the request scaffolding
1339
+ * (model/output/stream/options/request context) plus the per-attempt
1340
+ * {@link CodexStreamRuntime}. Drives the event loop in {@link process}, applies
1341
+ * the transport-fallback / retry recovery ladder, and emits the final message
1342
+ * in {@link finalize}. The runtime object is mutated in place across retries
1343
+ * (event stream and accumulators are swapped/reset), never reassigned.
1344
+ */
1345
+ var CodexStreamProcessor = class {
1346
+ runtime;
1347
+ model;
1348
+ output;
1349
+ stream;
1350
+ options;
1351
+ requestSetup;
1352
+ requestContext;
1353
+ startTime;
1354
+ firstTokenTime;
1355
+ constructor(init) {
1356
+ this.runtime = init.runtime;
1357
+ this.model = init.model;
1358
+ this.output = init.output;
1359
+ this.stream = init.stream;
1360
+ this.options = init.options;
1361
+ this.requestSetup = init.requestSetup;
1362
+ this.requestContext = init.requestContext;
1363
+ this.startTime = init.startTime;
1364
+ }
1365
+ /**
1366
+ * Whether the request actually sent (post-`onPayload`) opted into
1367
+ * sequential-cutoff summary delivery. Atomic `.done` events are preferred;
1368
+ * incremental deltas remain buffered as a compatibility fallback when a
1369
+ * transport emits them instead.
1370
+ */
1371
+ get #sequentialCutoffSummaries() {
1372
+ return this.runtime.requestBodyForState.stream_options?.reasoning_summary_delivery === "sequential_cutoff";
1373
+ }
1374
+ async process() {
1375
+ const { output, stream } = this;
1376
+ stream.push({
1377
+ type: "start",
1378
+ partial: output
1379
+ });
1380
+ while (true) try {
1381
+ let firstTokenTime = this.firstTokenTime;
1382
+ for await (const rawEvent of this.runtime.eventStream) {
1383
+ firstTokenTime = this.#handleStreamEvent(rawEvent, firstTokenTime);
1384
+ if (this.runtime.sawTerminalEvent) break;
1385
+ }
1386
+ return { firstTokenTime };
1387
+ } catch (error) {
1388
+ if (!await this.#recoverStreamError(error)) throw error;
1389
+ stream.push({
1390
+ type: "start",
1391
+ partial: output
1392
+ });
1393
+ }
1394
+ }
1395
+ #handleStreamEvent(rawEvent, firstTokenTime) {
1396
+ const { output, stream } = this;
1397
+ const eventType = typeof rawEvent.type === "string" ? rawEvent.type : "";
1398
+ if (!eventType) return firstTokenTime;
1399
+ if (eventType === "response.output_item.added") {
1400
+ this.runtime.whitespaceToolCallArgumentsDelta = void 0;
1401
+ if (!firstTokenTime) firstTokenTime = performance.now();
1402
+ const item = rawEvent.item;
1403
+ this.runtime.currentItem = item;
1404
+ this.runtime.currentBlock = createOutputBlockForItem(item);
1405
+ let contentIndex = -1;
1406
+ if (this.runtime.currentBlock) {
1407
+ output.content.push(this.runtime.currentBlock);
1408
+ contentIndex = output.content.length - 1;
1409
+ }
1410
+ const itemId = typeof item.id === "string" ? item.id : void 0;
1411
+ const outputIndex = typeof rawEvent.output_index === "number" && Number.isFinite(rawEvent.output_index) ? Math.trunc(rawEvent.output_index) : void 0;
1412
+ const entry = {
1413
+ item,
1414
+ block: this.runtime.currentBlock,
1415
+ contentIndex,
1416
+ itemId,
1417
+ outputIndex
1418
+ };
1419
+ this.runtime.currentEntry = entry;
1420
+ if (itemId) this.runtime.openItems.set(itemId, entry);
1421
+ if (outputIndex !== void 0) this.runtime.openItemsByOutputIndex.set(outputIndex, entry);
1422
+ if (!this.runtime.currentBlock) return firstTokenTime;
1423
+ stream.push({
1424
+ type: getOutputBlockStartEventType(this.runtime.currentBlock),
1425
+ contentIndex,
1426
+ partial: output
1427
+ });
1428
+ return firstTokenTime;
1429
+ }
1430
+ if (eventType === "response.reasoning_summary_part.added") {
1431
+ if (this.#sequentialCutoffSummaries) return firstTokenTime;
1432
+ const entry = this.runtime.openItemForEvent(rawEvent);
1433
+ if (entry?.item.type === "reasoning") appendReasoningSummaryPart(entry.item, rawEvent.part);
1434
+ return firstTokenTime;
1435
+ }
1436
+ if (eventType === "response.reasoning_summary_text.delta") {
1437
+ const entry = this.runtime.openItemForEvent(rawEvent);
1438
+ const delta = typeof rawEvent.delta === "string" ? rawEvent.delta : "";
1439
+ if (this.#sequentialCutoffSummaries) {
1440
+ this.runtime.queueSummaryDelta(entry, delta);
1441
+ return firstTokenTime;
1442
+ }
1443
+ if (entry?.item.type === "reasoning" && entry.block?.type === "thinking") appendReasoningSummaryTextDelta(entry.item, entry.block, delta, stream, output, entry.contentIndex);
1444
+ return firstTokenTime;
1445
+ }
1446
+ if (eventType === "response.reasoning_summary_text.done") {
1447
+ if (!this.#sequentialCutoffSummaries) return firstTokenTime;
1448
+ const entry = this.runtime.openItemForEvent(rawEvent);
1449
+ if (entry?.item.type === "reasoning" && entry.block?.type === "thinking") {
1450
+ this.runtime.takeSummaryDeltas(entry);
1451
+ if (!firstTokenTime) firstTokenTime = performance.now();
1452
+ const summaryIndex = typeof rawEvent.summary_index === "number" && Number.isFinite(rawEvent.summary_index) ? Math.trunc(rawEvent.summary_index) : 0;
1453
+ applyReasoningSummaryDone(this.runtime.cutoffSummaries, entry.block, typeof rawEvent.text === "string" ? rawEvent.text : "", summaryIndex, stream, output, entry.contentIndex);
1454
+ }
1455
+ return firstTokenTime;
1456
+ }
1457
+ if (eventType === "response.reasoning_text.delta") {
1458
+ const entry = this.runtime.openItemForEvent(rawEvent);
1459
+ const delta = typeof rawEvent.delta === "string" ? rawEvent.delta : "";
1460
+ if (entry?.item.type === "reasoning" && entry.block?.type === "thinking") {
1461
+ entry.block.thinking += delta;
1462
+ stream.push({
1463
+ type: "thinking_delta",
1464
+ contentIndex: entry.contentIndex,
1465
+ delta,
1466
+ partial: output
1467
+ });
1468
+ }
1469
+ return firstTokenTime;
1470
+ }
1471
+ if (eventType === "response.reasoning_summary_part.done") {
1472
+ const entry = this.runtime.openItemForEvent(rawEvent);
1473
+ if (this.#sequentialCutoffSummaries) {
1474
+ if (entry && this.runtime.pendingSummaryDeltas.has(entry)) this.runtime.queueSummaryDelta(entry, "\n\n");
1475
+ return firstTokenTime;
1476
+ }
1477
+ if (entry?.item.type === "reasoning" && entry.block?.type === "thinking") appendReasoningSummaryPartDone(entry.item, entry.block, stream, output, entry.contentIndex);
1478
+ return firstTokenTime;
1479
+ }
1480
+ if (eventType === "response.content_part.added") {
1481
+ const entry = this.runtime.openItemForEvent(rawEvent);
1482
+ if (entry?.item.type === "message") appendMessageContentPart(entry.item, rawEvent.part);
1483
+ return firstTokenTime;
1484
+ }
1485
+ if (eventType === "response.output_text.delta" || eventType === "response.refusal.delta") {
1486
+ const entry = this.runtime.openItemForEvent(rawEvent);
1487
+ if (entry?.item.type === "message" && entry.block?.type === "text") appendMessageTextDelta(entry.item, entry.block, rawEvent.delta || "", stream, output, entry.contentIndex, eventType === "response.refusal.delta" ? "refusal" : "output_text");
1488
+ return firstTokenTime;
1489
+ }
1490
+ if (eventType === "response.function_call_arguments.delta") {
1491
+ const interruption = this.runtime.handleToolCallArgumentsDelta(rawEvent, stream, output);
1492
+ if (interruption) {
1493
+ this.runtime.websocketState?.connection?.close("degenerate-tool-call");
1494
+ throw new CodexWhitespaceToolCallLoopError(interruption.message);
1495
+ }
1496
+ return firstTokenTime;
1497
+ }
1498
+ if (eventType === "response.function_call_arguments.done") {
1499
+ this.runtime.whitespaceToolCallArgumentsDelta = void 0;
1500
+ this.runtime.handleToolCallArgumentsDone(rawEvent);
1501
+ return firstTokenTime;
1502
+ }
1503
+ if (eventType === "response.custom_tool_call_input.delta") {
1504
+ const interruption = this.runtime.handleCustomToolCallInputDelta(rawEvent, stream, output);
1505
+ if (interruption) {
1506
+ this.runtime.websocketState?.connection?.close("degenerate-tool-call");
1507
+ throw new CodexWhitespaceToolCallLoopError(interruption.message);
1508
+ }
1509
+ return firstTokenTime;
1510
+ }
1511
+ if (eventType === "response.custom_tool_call_input.done") {
1512
+ this.runtime.whitespaceToolCallArgumentsDelta = void 0;
1513
+ this.runtime.handleCustomToolCallInputDone(rawEvent);
1514
+ return firstTokenTime;
1515
+ }
1516
+ if (eventType === "response.output_item.done") {
1517
+ this.runtime.whitespaceToolCallArgumentsDelta = void 0;
1518
+ this.#handleOutputItemDone(rawEvent);
1519
+ return firstTokenTime;
1520
+ }
1521
+ if (eventType === "response.created") {
1522
+ this.runtime.handleResponseCreated(rawEvent);
1523
+ return firstTokenTime;
1524
+ }
1525
+ if (eventType === "response.completed" || eventType === "response.done" || eventType === "response.incomplete") {
1526
+ this.#handleResponseCompleted(rawEvent);
1527
+ return firstTokenTime;
1528
+ }
1529
+ if (eventType === "response.metadata") {
1530
+ updateCodexSessionMetadataFromHeaders(this.requestContext.websocketState, toCodexHeaders(rawEvent.headers));
1531
+ const moderation = asRecord(rawEvent.metadata)?.[CODEX_MODERATION_METADATA_KEY];
1532
+ if (moderation !== void 0) try {
1533
+ this.options?.onModerationMetadata?.(moderation);
1534
+ } catch {}
1535
+ return firstTokenTime;
1536
+ }
1537
+ if (eventType === "error" || eventType === "response.failed") throw createCodexProviderStreamError(rawEvent);
1538
+ return firstTokenTime;
1539
+ }
1540
+ #flushSummaryDeltas(entry) {
1541
+ if (entry?.block?.type !== "thinking") return;
1542
+ for (const delta of this.runtime.takeSummaryDeltas(entry)) {
1543
+ entry.block.thinking += delta;
1544
+ this.stream.push({
1545
+ type: "thinking_delta",
1546
+ contentIndex: entry.contentIndex,
1547
+ delta,
1548
+ partial: this.output
1549
+ });
1550
+ }
1551
+ }
1552
+ #handleOutputItemDone(rawEvent) {
1553
+ const { runtime, output, stream } = this;
1554
+ const rawItem = rawEvent.item;
1555
+ if (!rawItem || typeof rawItem !== "object") return;
1556
+ const item = structuredCloneJSON(rawItem);
1557
+ if (item.type === "image_generation_call" && item.result) item.status = "completed";
1558
+ runtime.nativeOutputItems.push(item);
1559
+ const itemId = "id" in item && typeof item.id === "string" ? item.id : "";
1560
+ const entry = (itemId ? runtime.openItems.get(itemId) : null) ?? runtime.openItemForEvent(rawEvent);
1561
+ const block = entry?.block ?? null;
1562
+ const contentIndex = entry?.contentIndex ?? output.content.length - 1;
1563
+ if (item.type === "image_generation_call" && item.result) {
1564
+ appendResponsesImageResult(output, stream, item.result);
1565
+ runtime.closeOpenItem(entry);
1566
+ return;
1567
+ }
1568
+ if (item.type === "reasoning" && block?.type === "thinking") {
1569
+ this.#flushSummaryDeltas(entry);
1570
+ block.thinking = finalizeReasoningThinking(item, block.thinking, this.#sequentialCutoffSummaries ? this.runtime.cutoffSummaries : void 0);
1571
+ block.thinkingSignature = JSON.stringify(item);
1572
+ stream.push({
1573
+ type: "thinking_end",
1574
+ contentIndex,
1575
+ content: block.thinking,
1576
+ partial: output
1577
+ });
1578
+ runtime.closeOpenItem(entry);
1579
+ return;
1580
+ }
1581
+ if (item.type === "message" && block?.type === "text") {
1582
+ block.text = finalizeMessageText(item, block.text);
1583
+ const phase = item.phase === "commentary" || item.phase === "final_answer" ? item.phase : void 0;
1584
+ block.textSignature = encodeTextSignatureV1(item.id, phase);
1585
+ stream.push({
1586
+ type: "text_end",
1587
+ contentIndex,
1588
+ content: block.text,
1589
+ partial: output
1590
+ });
1591
+ runtime.closeOpenItem(entry);
1592
+ return;
1593
+ }
1594
+ if (item.type === "function_call") {
1595
+ const toolCall = {
1596
+ type: "toolCall",
1597
+ id: encodeResponsesToolCallId(item.call_id, item.id),
1598
+ name: item.name,
1599
+ arguments: parseStreamingJson(item.arguments || "{}")
1600
+ };
1601
+ if (block?.type === "toolCall") {
1602
+ block.arguments = toolCall.arguments;
1603
+ clearStreamingPartialJson(block);
1604
+ }
1605
+ runtime.closeOpenItem(entry);
1606
+ runtime.canSafelyReplayWebsocketOverSse = false;
1607
+ stream.push({
1608
+ type: "toolcall_end",
1609
+ contentIndex,
1610
+ toolCall,
1611
+ partial: output
1612
+ });
1613
+ return;
1614
+ }
1615
+ if (item.type === "computer_call") {
1616
+ const toolCall = {
1617
+ type: "toolCall",
1618
+ id: encodeResponsesToolCallId(item.call_id, item.id),
1619
+ name: "computer",
1620
+ arguments: {},
1621
+ providerMetadata: computerCallMetadata(item)
1622
+ };
1623
+ let resolvedContentIndex = contentIndex;
1624
+ if (block?.type === "toolCall") {
1625
+ block.id = toolCall.id;
1626
+ block.providerMetadata = toolCall.providerMetadata;
1627
+ clearStreamingPartialJson(block);
1628
+ } else {
1629
+ output.content.push(toolCall);
1630
+ resolvedContentIndex = output.content.length - 1;
1631
+ }
1632
+ runtime.closeOpenItem(entry);
1633
+ runtime.canSafelyReplayWebsocketOverSse = false;
1634
+ stream.push({
1635
+ type: "toolcall_end",
1636
+ contentIndex: resolvedContentIndex,
1637
+ toolCall,
1638
+ partial: output
1639
+ });
1640
+ return;
1641
+ }
1642
+ if (item.type === "custom_tool_call") {
1643
+ const partial = block?.type === "toolCall" ? block[kStreamingPartialJson] : void 0;
1644
+ const rawInput = partial && partial.length > 0 ? partial : item.input ?? "";
1645
+ const toolCall = {
1646
+ type: "toolCall",
1647
+ id: encodeResponsesToolCallId(item.call_id, item.id),
1648
+ name: item.name,
1649
+ arguments: { input: rawInput },
1650
+ customWireName: item.name
1651
+ };
1652
+ if (block?.type === "toolCall") {
1653
+ block.arguments = { input: rawInput };
1654
+ clearStreamingPartialJson(block);
1655
+ }
1656
+ runtime.closeOpenItem(entry);
1657
+ runtime.canSafelyReplayWebsocketOverSse = false;
1658
+ stream.push({
1659
+ type: "toolcall_end",
1660
+ contentIndex,
1661
+ toolCall,
1662
+ partial: output
1663
+ });
1664
+ return;
1665
+ }
1666
+ }
1667
+ #handleResponseCompleted(rawEvent) {
1668
+ const { runtime, model, output } = this;
1669
+ runtime.sawTerminalEvent = true;
1670
+ const rawResponse = rawEvent.response;
1671
+ const response = rawResponse && typeof rawResponse === "object" ? rawResponse : void 0;
1672
+ const responseId = response && "id" in response && typeof response.id === "string" ? response.id : void 0;
1673
+ const usage = response && "usage" in response ? parseCodexResponseUsage(response.usage) : void 0;
1674
+ const serviceTier = response && "service_tier" in response ? parseCodexServiceTier(response.service_tier) : void 0;
1675
+ const status = response && "status" in response ? parseCodexResponseStatus(response.status) : void 0;
1676
+ const endTurn = response && "end_turn" in response ? response.end_turn : void 0;
1677
+ populateResponsesUsageFromResponse(output, usage);
1678
+ recordCodexTurnUsageDiagnostics(runtime.websocketState, usage, output.usage);
1679
+ if (responseId) output.responseId = responseId;
1680
+ const state = runtime.websocketState;
1681
+ if (state) {
1682
+ if (runtime.transport !== "websocket") resetCodexWebSocketAppendState(state);
1683
+ else {
1684
+ state.lastRequest = structuredCloneJSON(runtime.requestBodyForState);
1685
+ const replayableResponseItems = sanitizeOpenAIResponsesAssistantHistoryItemsForReplay(structuredCloneJSON(runtime.nativeOutputItems));
1686
+ if (responseId && replayableResponseItems) {
1687
+ state.lastResponseId = responseId;
1688
+ state.lastResponseItems = replayableResponseItems;
1689
+ state.canAppend = rawEvent.type === "response.done" || rawEvent.type === "response.completed";
1690
+ } else state.canAppend = false;
1691
+ }
1692
+ }
1693
+ const incompleteDetails = response && "incomplete_details" in response && response.incomplete_details && typeof response.incomplete_details === "object" ? response.incomplete_details : void 0;
1694
+ const shouldPromoteIncompleteToolUse = status === "incomplete" && incompleteDetails !== void 0 && "reason" in incompleteDetails && incompleteDetails.reason === "max_output_tokens" && hasExecutableIncompleteResponsesToolCalls(output);
1695
+ finalizePendingResponsesToolCalls(output);
1696
+ calculateCost(model, output.usage);
1697
+ applyCodexServiceTierPricing(model, output.usage, serviceTier, runtime.requestBodyForState.service_tier);
1698
+ output.stopReason = mapOpenAIResponsesStopReason(status);
1699
+ promoteResponsesToolUseStopReason(output, endTurn === true ? true : endTurn === false ? false : void 0, shouldPromoteIncompleteToolUse);
1700
+ }
1701
+ async #recoverStreamError(error) {
1702
+ if (await this.#tryRecoverWhitespaceToolCallLoop(error)) return true;
1703
+ if (await this.#tryReconnectWebSocketOnConnectionLimit(error)) return true;
1704
+ if (await this.#tryRecoverPreviousResponseNotFound(error)) return true;
1705
+ if (await this.#tryReplayWebsocketFailureOverSse(error)) return true;
1706
+ if (await this.#tryRetryProviderError(error)) return true;
1707
+ return false;
1708
+ }
1709
+ /**
1710
+ * Recover from the degenerate whitespace-only tool-call argument loop
1711
+ * ({@link CodexWhitespaceToolCallLoopError}). The interrupted function call has
1712
+ * no usable arguments, so drop the partial turn and replay the request from
1713
+ * scratch — bounded by {@link CODEX_WHITESPACE_LOOP_RETRY_LIMIT}. Sampling
1714
+ * nondeterminism usually breaks the loop on a fresh attempt; once the budget is
1715
+ * exhausted the original error is surfaced (now without the junk tool call
1716
+ * polluting the message). Replay is refused once any visible content was already
1717
+ * delivered to the consumer — a finished tool call (`canSafelyReplayWebsocketOverSse`),
1718
+ * or any streamed text/commentary block still in `output.content` after the degenerate
1719
+ * tool call is dropped — because replaying re-emits already-streamed deltas.
1720
+ */
1721
+ async #tryRecoverWhitespaceToolCallLoop(error) {
1722
+ if (!(error instanceof CodexWhitespaceToolCallLoopError)) return false;
1723
+ this.#dropTrailingDegenerateToolCall();
1724
+ if (this.runtime.whitespaceLoopRetries >= CODEX_WHITESPACE_LOOP_RETRY_LIMIT || !this.runtime.canSafelyReplayWebsocketOverSse || this.output.content.some((block) => block.type !== "thinking") || this.options?.signal?.aborted) return false;
1725
+ this.runtime.whitespaceLoopRetries += 1;
1726
+ const websocketState = this.requestContext.websocketState;
1727
+ if (websocketState) {
1728
+ resetCodexWebSocketAppendState(websocketState);
1729
+ websocketState.turnState = void 0;
1730
+ websocketState.modelsEtag = void 0;
1731
+ }
1732
+ CODEX_DEBUG && debug("[codex] retrying codex turn after whitespace-only tool-call argument loop", {
1733
+ retry: this.runtime.whitespaceLoopRetries,
1734
+ retryBudget: CODEX_WHITESPACE_LOOP_RETRY_LIMIT,
1735
+ transport: this.runtime.transport
1736
+ });
1737
+ this.runtime.resetAccumulators();
1738
+ this.runtime.sawTerminalEvent = false;
1739
+ this.runtime.whitespaceToolCallArgumentsDelta = void 0;
1740
+ resetOutputState(this.output);
1741
+ this.firstTokenTime = void 0;
1742
+ await scheduler.wait(CODEX_WHITESPACE_LOOP_RETRY_DELAY_MS * this.runtime.whitespaceLoopRetries, { signal: this.requestSetup.requestSignal });
1743
+ if (this.runtime.transport === "websocket" && websocketState) {
1744
+ await this.#reopenWebSocketStream(websocketState);
1745
+ return true;
1746
+ }
1747
+ await this.#reopenSseStream(websocketState);
1748
+ return true;
1749
+ }
1750
+ /**
1751
+ * Pop the half-built degenerate tool-call block (the one whose arguments were
1752
+ * nothing but whitespace) off the output accumulator so it never surfaces in the
1753
+ * caller's message. Any legitimate content produced before it is preserved.
1754
+ */
1755
+ #dropTrailingDegenerateToolCall() {
1756
+ const { runtime, output } = this;
1757
+ const block = runtime.currentBlock;
1758
+ if (block && block.type === "toolCall" && output.content[output.content.length - 1] === block) output.content.pop();
1759
+ runtime.closeOpenItem(runtime.currentEntry);
1760
+ }
1761
+ /**
1762
+ * Handles `websocket_connection_limit_reached` errors by closing the stale connection
1763
+ * and opening a fresh websocket. If content has already been emitted to the caller,
1764
+ * falls back to SSE replay (same as other WS failures) since we cannot safely
1765
+ * continue a partial response on a new connection. If a tool call was already
1766
+ * delivered (`canSafelyReplayWebsocketOverSse` is false), the error surfaces
1767
+ * instead — replaying would re-emit the same tool calls.
1768
+ */
1769
+ async #tryReconnectWebSocketOnConnectionLimit(error) {
1770
+ if (!(error instanceof CodexProviderStreamError) || error.code !== "websocket_connection_limit_reached") return false;
1771
+ const websocketState = this.requestContext.websocketState;
1772
+ if (!websocketState || this.runtime.transport !== "websocket" || this.options?.signal?.aborted) return false;
1773
+ websocketState.connection?.close("connection_limit");
1774
+ websocketState.connection = void 0;
1775
+ resetCodexWebSocketAppendState(websocketState);
1776
+ if (this.output.content.length > 0 && !this.runtime.canSafelyReplayWebsocketOverSse) return false;
1777
+ CODEX_DEBUG && debug("[codex] codex websocket connection limit reached, reconnecting", {
1778
+ hadContent: this.output.content.length > 0,
1779
+ retry: this.runtime.websocketStreamRetries
1780
+ });
1781
+ if (this.output.content.length > 0) {
1782
+ this.runtime.resetAccumulators();
1783
+ resetOutputState(this.output);
1784
+ this.firstTokenTime = void 0;
1785
+ recordCodexWebSocketFailure(websocketState, true);
1786
+ await this.#reopenSseStream(websocketState);
1787
+ return true;
1788
+ }
1789
+ this.runtime.resetAccumulators();
1790
+ this.firstTokenTime = void 0;
1791
+ if (this.runtime.websocketStreamRetries >= CODEX_WEBSOCKET_RETRY_BUDGET) {
1792
+ recordCodexWebSocketFailure(websocketState, true);
1793
+ await this.#reopenSseStream(websocketState);
1794
+ return true;
1795
+ }
1796
+ this.runtime.websocketStreamRetries += 1;
1797
+ await scheduler.wait(CODEX_WEBSOCKET_RETRY_DELAY_MS * Math.max(1, this.runtime.websocketStreamRetries), { signal: this.requestSetup.requestSignal });
1798
+ await this.#reopenWebSocketStream(websocketState);
1799
+ return true;
1800
+ }
1801
+ async #tryRecoverPreviousResponseNotFound(error) {
1802
+ const websocketState = this.requestContext.websocketState;
1803
+ if (!isCodexStalePreviousResponseError(error) || !websocketState || this.output.content.length > 0 || this.options?.signal?.aborted || this.runtime.providerRetryAttempt >= CODEX_MAX_RETRIES) return false;
1804
+ if (this.runtime.transport !== "websocket") return false;
1805
+ this.runtime.providerRetryAttempt += 1;
1806
+ resetCodexWebSocketAppendState(websocketState);
1807
+ websocketState.turnState = void 0;
1808
+ websocketState.modelsEtag = void 0;
1809
+ this.runtime.resetAccumulators();
1810
+ this.runtime.sawTerminalEvent = false;
1811
+ resetOutputState(this.output);
1812
+ this.firstTokenTime = void 0;
1813
+ CODEX_DEBUG && debug("[codex] codex previous_response_id expired; retrying with full context", { retry: this.runtime.providerRetryAttempt });
1814
+ await this.#reopenWebSocketStream(websocketState);
1815
+ return true;
1816
+ }
1817
+ async #tryReplayWebsocketFailureOverSse(error) {
1818
+ const websocketState = this.requestContext.websocketState;
1819
+ if (!(this.runtime.transport === "websocket" && websocketState && isCodexWebSocketRetryableStreamError(error) && this.runtime.canSafelyReplayWebsocketOverSse && !this.runtime.sawTerminalEvent && !this.options?.signal?.aborted)) return false;
1820
+ const state = websocketState;
1821
+ const streamError = error instanceof Error ? error : new Error(String(error));
1822
+ const replayingBufferedOutputOverSse = this.output.content.length > 0;
1823
+ const fatalWebSocketMessage = streamError.message.toLowerCase();
1824
+ const isFatal = CODEX_WEBSOCKET_FATAL_PATTERNS.some((pattern) => fatalWebSocketMessage.includes(pattern.toLowerCase()));
1825
+ const activateFallback = replayingBufferedOutputOverSse || isFatal || this.runtime.websocketStreamRetries >= CODEX_WEBSOCKET_RETRY_BUDGET;
1826
+ recordCodexWebSocketFailure(state, activateFallback);
1827
+ CODEX_DEBUG && debug("[codex] codex websocket stream fallback", {
1828
+ error: streamError.message,
1829
+ retry: this.runtime.websocketStreamRetries,
1830
+ retryBudget: CODEX_WEBSOCKET_RETRY_BUDGET,
1831
+ activated: activateFallback,
1832
+ fatal: isFatal,
1833
+ replayedBufferedOutput: replayingBufferedOutputOverSse
1834
+ });
1835
+ if (!activateFallback) {
1836
+ this.runtime.websocketStreamRetries += 1;
1837
+ this.runtime.resetAccumulators();
1838
+ this.firstTokenTime = void 0;
1839
+ await scheduler.wait(CODEX_WEBSOCKET_RETRY_DELAY_MS * Math.max(1, this.runtime.websocketStreamRetries), { signal: this.requestSetup.requestSignal });
1840
+ await this.#reopenWebSocketStream(state);
1841
+ return true;
1842
+ }
1843
+ this.runtime.resetAccumulators();
1844
+ resetOutputState(this.output);
1845
+ this.firstTokenTime = void 0;
1846
+ await this.#reopenSseStream(state);
1847
+ return true;
1848
+ }
1849
+ async #tryRetryProviderError(error) {
1850
+ if (!(error instanceof CodexProviderStreamError && error.retryable) || this.output.content.length > 0 || this.runtime.providerRetryAttempt >= CODEX_MAX_RETRIES || this.options?.signal?.aborted) return false;
1851
+ this.runtime.providerRetryAttempt += 1;
1852
+ const websocketState = this.requestContext.websocketState;
1853
+ if (websocketState) {
1854
+ resetCodexWebSocketAppendState(websocketState);
1855
+ websocketState.turnState = void 0;
1856
+ websocketState.modelsEtag = void 0;
1857
+ }
1858
+ CODEX_DEBUG && debug("[codex] retrying codex provider stream error", {
1859
+ error: error instanceof Error ? error.message : String(error),
1860
+ retry: this.runtime.providerRetryAttempt,
1861
+ retryBudget: CODEX_MAX_RETRIES,
1862
+ transport: this.runtime.transport
1863
+ });
1864
+ this.runtime.resetAccumulators();
1865
+ this.runtime.sawTerminalEvent = false;
1866
+ resetOutputState(this.output);
1867
+ this.firstTokenTime = void 0;
1868
+ await scheduler.wait(CODEX_RETRY_DELAY_MS * this.runtime.providerRetryAttempt, { signal: this.requestSetup.requestSignal });
1869
+ if (this.runtime.transport === "websocket" && websocketState) {
1870
+ await this.#reopenWebSocketStream(websocketState);
1871
+ return true;
1872
+ }
1873
+ await this.#reopenSseStream(websocketState);
1874
+ return true;
1875
+ }
1876
+ async #reopenWebSocketStream(state) {
1877
+ try {
1878
+ const next = await openCodexWebSocketTransport(this.model, this.options, this.requestContext, this.requestSetup, state, this.runtime.websocketStreamRetries, this.options ? (event) => this.options?.onSseEvent?.(event, this.model) : void 0);
1879
+ this.runtime.eventStream = next.eventStream;
1880
+ this.runtime.requestBodyForState = next.requestBodyForState;
1881
+ this.runtime.transport = next.transport;
1882
+ state.lastTransport = next.transport;
1883
+ } catch (error) {
1884
+ if (!(error instanceof CodexWebSocketTransportError)) throw error;
1885
+ recordCodexWebSocketFailure(state, true);
1886
+ CODEX_DEBUG && debug("[codex] codex websocket reopen failed, falling back to SSE", {
1887
+ error: error.message,
1888
+ retry: this.runtime.websocketStreamRetries
1889
+ });
1890
+ await this.#reopenSseStream(state);
1891
+ }
1892
+ }
1893
+ async #reopenSseStream(state) {
1894
+ const next = await openCodexSseTransport(this.model, this.requestContext, this.requestSetup, this.options, state);
1895
+ this.runtime.eventStream = next.eventStream;
1896
+ this.runtime.requestBodyForState = next.requestBodyForState;
1897
+ this.runtime.transport = next.transport;
1898
+ if (state) state.lastTransport = next.transport;
1899
+ }
1900
+ finalize(completion) {
1901
+ const { output } = this;
1902
+ if (this.options?.signal?.aborted) throw new AbortError();
1903
+ if (!this.runtime.sawTerminalEvent) {
1904
+ if (this.requestContext.websocketState) {
1905
+ resetCodexWebSocketAppendState(this.requestContext.websocketState);
1906
+ this.requestContext.websocketState.turnState = void 0;
1907
+ this.requestContext.websocketState.modelsEtag = void 0;
1908
+ }
1909
+ CODEX_DEBUG && debug("[codex] codex stream ended unexpectedly", {
1910
+ transport: this.runtime.transport,
1911
+ terminalEventSeen: this.runtime.sawTerminalEvent,
1912
+ unexpectedStreamEnd: true,
1913
+ sentTurnStateHeader: Boolean(this.requestContext.websocketState?.turnState),
1914
+ sentModelsEtagHeader: Boolean(this.requestContext.websocketState?.modelsEtag)
1915
+ });
1916
+ throw new CodexProviderStreamError("Codex stream ended before terminal completion event", false);
1917
+ }
1918
+ if (output.stopReason === "aborted" || output.stopReason === "error") throw new CodexProviderStreamError("Codex response failed", false);
1919
+ output.providerPayload = createOpenAIResponsesHistoryPayload(this.model.provider, this.runtime.nativeOutputItems);
1920
+ output.duration = performance.now() - this.startTime;
1921
+ if (completion.firstTokenTime) output.ttft = completion.firstTokenTime - this.startTime;
1922
+ return output;
1923
+ }
1924
+ };
1925
+ const streamOpenAICodexResponses = (model, context, options) => {
1926
+ const stream = new AssistantMessageEventStream();
1927
+ (async () => {
1928
+ const startTime = performance.now();
1929
+ const output = {
1930
+ role: "assistant",
1931
+ content: [],
1932
+ api: "openai-codex-responses",
1933
+ provider: model.provider,
1934
+ model: model.id,
1935
+ usage: {
1936
+ input: 0,
1937
+ output: 0,
1938
+ cacheRead: 0,
1939
+ cacheWrite: 0,
1940
+ totalTokens: 0,
1941
+ cost: {
1942
+ input: 0,
1943
+ output: 0,
1944
+ cacheRead: 0,
1945
+ cacheWrite: 0,
1946
+ total: 0
1947
+ }
1948
+ },
1949
+ stopReason: "stop",
1950
+ timestamp: Date.now()
1951
+ };
1952
+ const requestSetup = createRequestSetup(options);
1953
+ let processingContext;
1954
+ let requestContext;
1955
+ try {
1956
+ requestContext = await buildCodexRequestContext(model, context, options);
1957
+ const initialTransport = await openInitialCodexEventStream(model, options, requestSetup, requestContext);
1958
+ const runtime = new CodexStreamRuntime({
1959
+ ...initialTransport,
1960
+ websocketState: requestContext.websocketState
1961
+ });
1962
+ if (requestContext.websocketState) requestContext.websocketState.lastTransport = initialTransport.transport;
1963
+ processingContext = new CodexStreamProcessor({
1964
+ runtime,
1965
+ model,
1966
+ output,
1967
+ stream,
1968
+ options,
1969
+ requestSetup,
1970
+ requestContext,
1971
+ startTime
1972
+ });
1973
+ const completion = await processingContext.process();
1974
+ processingContext.firstTokenTime = completion.firstTokenTime;
1975
+ const message = processingContext.finalize(completion);
1976
+ stream.push({
1977
+ type: "done",
1978
+ reason: message.stopReason,
1979
+ message
1980
+ });
1981
+ stream.end();
1982
+ } catch (error$1) {
1983
+ const failureContext = processingContext ?? {
1984
+ model,
1985
+ output,
1986
+ options,
1987
+ requestContext: requestContext ?? {
1988
+ apiKey: "",
1989
+ accountId: "",
1990
+ baseUrl: model.baseUrl || "https://chatgpt.com/backend-api",
1991
+ url: "",
1992
+ requestHeaders: {},
1993
+ codexClientVersion: "0.144.1",
1994
+ responsesLite: options?.responsesLite === true,
1995
+ transformedBody: { model: model.id },
1996
+ rawRequestDump: {
1997
+ provider: model.provider,
1998
+ api: output.api,
1999
+ model: model.id,
2000
+ method: "POST",
2001
+ url: "",
2002
+ body: { model: model.id }
2003
+ }
2004
+ },
2005
+ startTime
2006
+ };
2007
+ try {
2008
+ const failure = await handleCodexStreamFailure(failureContext, error$1);
2009
+ stream.push({
2010
+ type: "error",
2011
+ reason: failure.stopReason,
2012
+ error: failure
2013
+ });
2014
+ } catch (failureError) {
2015
+ error("Codex stream failure handler threw", { error: failureError instanceof Error ? failureError.message : String(failureError) });
2016
+ output.stopReason = "error";
2017
+ output.errorMessage ??= error$1 instanceof Error ? error$1.message : String(error$1);
2018
+ stream.push({
2019
+ type: "error",
2020
+ reason: "error",
2021
+ error: output
2022
+ });
2023
+ }
2024
+ stream.end();
2025
+ } finally {
2026
+ requestContext?.isolatedTransportState?.close();
2027
+ }
2028
+ })();
2029
+ return stream;
2030
+ };
2031
+ function getCodexWebSocketSessionKey(normalizedSessionId, model, accountId, apiKey, baseUrl, responsesLite) {
2032
+ if (!normalizedSessionId) return void 0;
2033
+ const credentialKey = accountId ? `account:${accountId}` : `token:${Bun.hash(apiKey).toString(36)}`;
2034
+ const liteSuffix = responsesLite ? ":lite" : "";
2035
+ return `${credentialKey}:${baseUrl}:${model.id}:${normalizedSessionId}${liteSuffix}`;
2036
+ }
2037
+ function getCodexWebSocketSessionState(sessionKey, providerSessionState) {
2038
+ const existing = providerSessionState.webSocketSessions.get(sessionKey);
2039
+ if (existing) return existing;
2040
+ const created = {
2041
+ disableWebsocket: false,
2042
+ canAppend: false,
2043
+ fallbackCount: 0,
2044
+ prewarmed: false,
2045
+ stats: {
2046
+ fullContextRequests: 0,
2047
+ deltaRequests: 0,
2048
+ lastInputItems: 0
2049
+ }
2050
+ };
2051
+ providerSessionState.webSocketSessions.set(sessionKey, created);
2052
+ return created;
2053
+ }
2054
+ function resetCodexWebSocketAppendState(state) {
2055
+ state.canAppend = false;
2056
+ state.lastRequest = void 0;
2057
+ state.lastResponseId = void 0;
2058
+ state.lastResponseItems = void 0;
2059
+ }
2060
+ function recordCodexWebSocketFailure(state, activateFallback) {
2061
+ resetCodexWebSocketAppendState(state);
2062
+ if (state.connection && !state.connection.isConnecting()) {
2063
+ state.connection.close("fallback");
2064
+ state.connection = void 0;
2065
+ }
2066
+ state.lastFallbackAt = Date.now();
2067
+ if (activateFallback && !state.disableWebsocket) {
2068
+ state.disableWebsocket = true;
2069
+ state.fallbackCount += 1;
2070
+ }
2071
+ }
2072
+ function getCodexWebSocketEnvValue() {
2073
+ if ($env.PI_CODEX_WEBSOCKET !== void 0) return $flag("PI_CODEX_WEBSOCKET");
2074
+ }
2075
+ function shouldUseCodexWebSocket(model, state, preferWebsockets) {
2076
+ if (model.preferWebsockets === false) return false;
2077
+ if (!state || state.disableWebsocket) return false;
2078
+ const envVal = getCodexWebSocketEnvValue();
2079
+ if (envVal !== void 0) return envVal;
2080
+ if (preferWebsockets === false) return false;
2081
+ return true;
2082
+ }
2083
+ const codexDiagnosticsTextEncoder = new TextEncoder();
2084
+ function jsonByteLength(value) {
2085
+ const json = JSON.stringify(value);
2086
+ return codexDiagnosticsTextEncoder.encode(json === void 0 ? "undefined" : json).byteLength;
2087
+ }
2088
+ function hashJson(value) {
2089
+ const json = JSON.stringify(value);
2090
+ return String(Bun.hash(json === void 0 ? "undefined" : json));
2091
+ }
2092
+ function parseCodexServiceTier(value) {
2093
+ switch (value) {
2094
+ case "auto":
2095
+ case "default":
2096
+ case "flex":
2097
+ case "scale":
2098
+ case "priority": return value;
2099
+ default: return;
2100
+ }
2101
+ }
2102
+ function parseCodexResponseStatus(value) {
2103
+ switch (value) {
2104
+ case "completed":
2105
+ case "failed":
2106
+ case "in_progress":
2107
+ case "cancelled":
2108
+ case "queued":
2109
+ case "incomplete": return value;
2110
+ default: return;
2111
+ }
2112
+ }
2113
+ function parseCodexResponseUsage(value) {
2114
+ if (!value || typeof value !== "object") return void 0;
2115
+ const usage = {};
2116
+ let hasUsage = false;
2117
+ if ("input_tokens" in value && typeof value.input_tokens === "number") {
2118
+ usage.input_tokens = value.input_tokens;
2119
+ hasUsage = true;
2120
+ }
2121
+ if ("output_tokens" in value && typeof value.output_tokens === "number") {
2122
+ usage.output_tokens = value.output_tokens;
2123
+ hasUsage = true;
2124
+ }
2125
+ if ("total_tokens" in value && typeof value.total_tokens === "number") {
2126
+ usage.total_tokens = value.total_tokens;
2127
+ hasUsage = true;
2128
+ }
2129
+ if ("prompt_cache_hit_tokens" in value && typeof value.prompt_cache_hit_tokens === "number") {
2130
+ usage.prompt_cache_hit_tokens = value.prompt_cache_hit_tokens;
2131
+ hasUsage = true;
2132
+ }
2133
+ if ("input_tokens_details" in value && value.input_tokens_details && typeof value.input_tokens_details === "object") {
2134
+ const details = value.input_tokens_details;
2135
+ const parsedDetails = {};
2136
+ let hasDetails = false;
2137
+ if ("cached_tokens" in details && typeof details.cached_tokens === "number") {
2138
+ parsedDetails.cached_tokens = details.cached_tokens;
2139
+ hasDetails = true;
2140
+ }
2141
+ if ("cache_write_tokens" in details && typeof details.cache_write_tokens === "number") {
2142
+ parsedDetails.cache_write_tokens = details.cache_write_tokens;
2143
+ hasDetails = true;
2144
+ }
2145
+ if ("orchestration_input_tokens" in details && typeof details.orchestration_input_tokens === "number") {
2146
+ parsedDetails.orchestration_input_tokens = details.orchestration_input_tokens;
2147
+ hasDetails = true;
2148
+ }
2149
+ if ("orchestration_input_cached_tokens" in details && typeof details.orchestration_input_cached_tokens === "number") {
2150
+ parsedDetails.orchestration_input_cached_tokens = details.orchestration_input_cached_tokens;
2151
+ hasDetails = true;
2152
+ }
2153
+ if (hasDetails) {
2154
+ usage.input_tokens_details = parsedDetails;
2155
+ hasUsage = true;
2156
+ }
2157
+ }
2158
+ if ("output_tokens_details" in value && value.output_tokens_details && typeof value.output_tokens_details === "object") {
2159
+ const details = value.output_tokens_details;
2160
+ const parsedDetails = {};
2161
+ let hasDetails = false;
2162
+ if ("reasoning_tokens" in details && typeof details.reasoning_tokens === "number") {
2163
+ parsedDetails.reasoning_tokens = details.reasoning_tokens;
2164
+ hasDetails = true;
2165
+ }
2166
+ if ("orchestration_output_tokens" in details && typeof details.orchestration_output_tokens === "number") {
2167
+ parsedDetails.orchestration_output_tokens = details.orchestration_output_tokens;
2168
+ hasDetails = true;
2169
+ }
2170
+ if (hasDetails) {
2171
+ usage.output_tokens_details = parsedDetails;
2172
+ hasUsage = true;
2173
+ }
2174
+ }
2175
+ return hasUsage ? usage : void 0;
2176
+ }
2177
+ function describeCodexInputItemType(item) {
2178
+ if (item && typeof item === "object") {
2179
+ if ("type" in item && typeof item.type === "string") return item.type;
2180
+ if ("role" in item && typeof item.role === "string") return item.role;
2181
+ }
2182
+ return typeof item;
2183
+ }
2184
+ function createCodexOptionsHash(request) {
2185
+ const options = {};
2186
+ for (const key in request) {
2187
+ if (key === "input" || key === "previous_response_id" || key === "type" || key === "client_metadata") continue;
2188
+ options[key] = request[key];
2189
+ }
2190
+ return hashJson(options);
2191
+ }
2192
+ function buildCodexTurnRequestDiagnostics(request, transport, canAppendBeforeRequest) {
2193
+ const input = request.input;
2194
+ const inputItems = Array.isArray(input) ? input : [];
2195
+ const inputItemTypes = inputItems.map(describeCodexInputItemType);
2196
+ const promptCacheKey = typeof request.prompt_cache_key === "string" ? request.prompt_cache_key : void 0;
2197
+ const toolsHash = request.tools === void 0 ? void 0 : hashJson(request.tools);
2198
+ return {
2199
+ transport,
2200
+ previousResponseIdPresent: typeof request.previous_response_id === "string" && request.previous_response_id.length > 0,
2201
+ inputItemCount: inputItems.length,
2202
+ inputItemTypes,
2203
+ ...inputItemTypes[0] ? { firstInputItemType: inputItemTypes[0] } : {},
2204
+ inputJsonBytes: jsonByteLength(inputItems),
2205
+ ...promptCacheKey !== void 0 ? { promptCacheKey } : {},
2206
+ ...toolsHash !== void 0 ? { toolsHash } : {},
2207
+ optionsHash: createCodexOptionsHash(request),
2208
+ canAppendBeforeRequest
2209
+ };
2210
+ }
2211
+ function recordCodexTurnRequestDiagnostics(state, request, transport, canAppendBeforeRequest) {
2212
+ if (!state) return;
2213
+ const input = request.input;
2214
+ state.stats.lastInputItems = Array.isArray(input) ? input.length : 0;
2215
+ const previousResponseId = typeof request.previous_response_id === "string" ? request.previous_response_id : void 0;
2216
+ if (previousResponseId && previousResponseId.length > 0) {
2217
+ state.stats.deltaRequests += 1;
2218
+ state.stats.lastDeltaInputItems = state.stats.lastInputItems;
2219
+ state.stats.lastPreviousResponseId = previousResponseId;
2220
+ } else {
2221
+ state.stats.fullContextRequests += 1;
2222
+ state.stats.lastDeltaInputItems = void 0;
2223
+ state.stats.lastPreviousResponseId = void 0;
2224
+ }
2225
+ state.stats.lastTurn = { request: buildCodexTurnRequestDiagnostics(request, transport, canAppendBeforeRequest) };
2226
+ CODEX_DEBUG && debug("[codex] codex turn request diagnostics", { diagnostics: state.stats.lastTurn.request });
2227
+ }
2228
+ function recordCodexTurnUsageDiagnostics(state, rawUsage, displayedUsage) {
2229
+ if (!state?.stats.lastTurn || !rawUsage) return;
2230
+ const details = rawUsage.input_tokens_details;
2231
+ const outputDetails = rawUsage.output_tokens_details;
2232
+ const rawInputTokens = rawUsage.input_tokens ?? 0;
2233
+ const rawCachedTokens = details?.cached_tokens ?? rawUsage.prompt_cache_hit_tokens ?? 0;
2234
+ const usageDiagnostics = {
2235
+ rawInputTokens,
2236
+ rawCachedTokens,
2237
+ rawUncachedTokens: Math.max(0, rawInputTokens - rawCachedTokens),
2238
+ rawOutputTokens: rawUsage.output_tokens ?? 0,
2239
+ ...typeof rawUsage.total_tokens === "number" ? { rawTotalTokens: rawUsage.total_tokens } : {},
2240
+ ...typeof details?.orchestration_input_tokens === "number" ? { rawOrchestrationInputTokens: details.orchestration_input_tokens } : {},
2241
+ ...typeof details?.orchestration_input_cached_tokens === "number" ? { rawOrchestrationCachedTokens: details.orchestration_input_cached_tokens } : {},
2242
+ ...typeof outputDetails?.orchestration_output_tokens === "number" ? { rawOrchestrationOutputTokens: outputDetails.orchestration_output_tokens } : {},
2243
+ displayedInputTokens: displayedUsage.input,
2244
+ displayedOutputTokens: displayedUsage.output,
2245
+ displayedCacheReadTokens: displayedUsage.cacheRead,
2246
+ displayedCacheWriteTokens: displayedUsage.cacheWrite,
2247
+ displayedTotalTokens: displayedUsage.totalTokens,
2248
+ displayedOrchestrationInputTokens: displayedUsage.orchestration?.input ?? 0,
2249
+ displayedOrchestrationCacheReadTokens: displayedUsage.orchestration?.cacheRead ?? 0,
2250
+ displayedOrchestrationOutputTokens: displayedUsage.orchestration?.output ?? 0
2251
+ };
2252
+ state.stats.lastTurn = {
2253
+ ...state.stats.lastTurn,
2254
+ usage: usageDiagnostics
2255
+ };
2256
+ CODEX_DEBUG && debug("[codex] codex turn diagnostics", { diagnostics: state.stats.lastTurn });
2257
+ }
2258
+ /**
2259
+ * Shape the next websocket turn's request body: when the session's append
2260
+ * baseline is intact (same options, strict history prefix), chain via
2261
+ * `previous_response_id` + delta-only `input`; otherwise break the chain and
2262
+ * replay the full transcript. SSE requests never chain — the HTTP endpoint's
2263
+ * request schema has no `previous_response_id` (codex-rs carries it only on
2264
+ * websocket `response.create` frames) and strict gateway validators 400 it
2265
+ * with `{"detail":"Unsupported parameter: previous_response_id"}`.
2266
+ */
2267
+ function buildCodexChainedRequestBody(requestBody, state) {
2268
+ const chainable = state?.canAppend === true;
2269
+ const appendInput = chainable ? buildResponsesDeltaInput(state.lastRequest, state.lastResponseItems, requestBody) : null;
2270
+ if (appendInput && appendInput.length > 0 && state?.lastResponseId) return {
2271
+ ...requestBody,
2272
+ previous_response_id: state.lastResponseId,
2273
+ input: appendInput
2274
+ };
2275
+ if (chainable && state) {
2276
+ CODEX_DEBUG && debug("[codex] codex append reset", {
2277
+ hadTurnStateHeader: Boolean(state.turnState),
2278
+ hadModelsEtagHeader: Boolean(state.modelsEtag)
2279
+ });
2280
+ resetCodexWebSocketAppendState(state);
2281
+ state.turnState = void 0;
2282
+ state.modelsEtag = void 0;
2283
+ }
2284
+ return requestBody;
2285
+ }
2286
+ function toWebSocketUrl(url) {
2287
+ const parsed = new URL(url);
2288
+ if (parsed.protocol === "https:") parsed.protocol = "wss:";
2289
+ else if (parsed.protocol === "http:") parsed.protocol = "ws:";
2290
+ return parsed.toString();
2291
+ }
2292
+ function headersToRecord(headers) {
2293
+ const result = {};
2294
+ for (const [key, value] of headers.entries()) result[key] = value;
2295
+ return result;
2296
+ }
2297
+ var CodexWebSocketConnection = class {
2298
+ #url;
2299
+ #headers;
2300
+ #proxy;
2301
+ #onHandshakeHeaders;
2302
+ #socket = null;
2303
+ #queue = [];
2304
+ #waiters = [];
2305
+ #connectPromise;
2306
+ #activeRequest = false;
2307
+ #streamObserver;
2308
+ #heartbeatInterval;
2309
+ #removePongListener;
2310
+ #handshakeHeaders;
2311
+ #debugResponseLog;
2312
+ /**
2313
+ * Wall-clock of the most recent inbound activity on this socket — any
2314
+ * decoded message, any pong, or the moment the handshake completed. Used
2315
+ * by {@link isHealthyForReuse} so we don't write a continuation frame into
2316
+ * a TCP-open-but-server-evicted socket whose `readyState` still says OPEN.
2317
+ */
2318
+ #lastInboundAt = 0;
2319
+ /** Wall-clock of the last heartbeat ping we issued; 0 if none yet. */
2320
+ #lastPingAt = 0;
2321
+ /**
2322
+ * Most recent `response.id` accepted on this socket, retained across
2323
+ * requests. Lets the next request drop a trailing/duplicate frame from the
2324
+ * previous (cleanly-completed) response that outlived the queue drain.
2325
+ */
2326
+ #lastSeenResponseId;
2327
+ constructor(url, headers, options) {
2328
+ this.#url = url;
2329
+ this.#headers = headers;
2330
+ this.#proxy = options.proxy;
2331
+ this.#onHandshakeHeaders = options.onHandshakeHeaders;
2332
+ }
2333
+ isOpen() {
2334
+ return this.#socket?.readyState === WebSocket.OPEN;
2335
+ }
2336
+ /** True while a handshake (possibly started by another caller) is still in flight. */
2337
+ isConnecting() {
2338
+ return this.#connectPromise !== void 0;
2339
+ }
2340
+ /**
2341
+ * Stricter variant of {@link isOpen} for the connection-pool reuse gate.
2342
+ * Refuses sockets that have been silent past {@link CODEX_WEBSOCKET_MAX_IDLE_REUSE_MS}.
2343
+ *
2344
+ * Bun's `WebSocket` does not always surface server-side eviction (no
2345
+ * `onclose`, no `onerror`), so a socket can sit in readyState OPEN long
2346
+ * after the upstream has dropped it. Reusing such a socket sends the next
2347
+ * `response.create` into a half-open write buffer and parks the reader
2348
+ * until the first-event / idle timeout fires (issue #1450). Forcing a
2349
+ * reconnect on any suspect socket trades a sub-second handshake for a
2350
+ * 60–300 s stall.
2351
+ */
2352
+ isHealthyForReuse() {
2353
+ if (!this.isOpen()) return false;
2354
+ const maxIdleMs = CODEX_WEBSOCKET_MAX_IDLE_REUSE_MS;
2355
+ if (maxIdleMs <= 0) return true;
2356
+ if (this.#lastInboundAt === 0) return false;
2357
+ return Date.now() - this.#lastInboundAt <= maxIdleMs;
2358
+ }
2359
+ matchesAuth(headers) {
2360
+ return this.#headers.authorization === headers.authorization;
2361
+ }
2362
+ close(reason = "done") {
2363
+ if (this.#socket && (this.#socket.readyState === WebSocket.OPEN || this.#socket.readyState === WebSocket.CONNECTING)) this.#socket.close(1e3, reason);
2364
+ this.#socket = null;
2365
+ this.#stopHeartbeat();
2366
+ }
2367
+ async connect(signal) {
2368
+ if (this.isOpen()) return;
2369
+ if (this.#connectPromise) {
2370
+ time("codexWs:awaitSharedHandshake");
2371
+ await this.#connectPromise;
2372
+ return;
2373
+ }
2374
+ const { promise, resolve, reject } = Promise.withResolvers();
2375
+ this.#connectPromise = promise;
2376
+ const socket = new WebSocket(this.#url, {
2377
+ headers: this.#headers,
2378
+ proxy: this.#proxy
2379
+ });
2380
+ socket.binaryType = "nodebuffer";
2381
+ this.#socket = socket;
2382
+ let settled = false;
2383
+ let timeout;
2384
+ const clearPending = () => {
2385
+ if (timeout !== void 0) {
2386
+ clearTimeout(timeout);
2387
+ timeout = void 0;
2388
+ }
2389
+ if (signal) signal.removeEventListener("abort", onAbort);
2390
+ };
2391
+ const onAbort = () => {
2392
+ socket.close(1e3, "aborted");
2393
+ if (!settled) {
2394
+ settled = true;
2395
+ clearPending();
2396
+ reject(new CodexWebSocketTransportError(`request was aborted`));
2397
+ }
2398
+ };
2399
+ if (signal) {
2400
+ if (signal.aborted) onAbort();
2401
+ else signal.addEventListener("abort", onAbort, { once: true });
2402
+ }
2403
+ if (!settled) timeout = setTimeout(() => {
2404
+ socket.close(1e3, "connect-timeout");
2405
+ if (!settled) {
2406
+ settled = true;
2407
+ clearPending();
2408
+ reject(new CodexWebSocketTransportError(`connection timeout`));
2409
+ }
2410
+ }, CODEX_WEBSOCKET_CONNECT_TIMEOUT_MS);
2411
+ socket.onopen = (event) => {
2412
+ if (!settled) {
2413
+ settled = true;
2414
+ clearPending();
2415
+ this.#lastInboundAt = Date.now();
2416
+ this.#captureHandshakeHeaders(socket, event);
2417
+ this.#startHeartbeat(socket);
2418
+ resolve();
2419
+ }
2420
+ };
2421
+ socket.onerror = (event) => {
2422
+ const eventRecord = event;
2423
+ const error = new CodexWebSocketTransportError(`websocket error: ${typeof eventRecord.message === "string" && eventRecord.message || eventRecord.error instanceof Error && eventRecord.error.message || String(event.type)}`);
2424
+ if (!settled) {
2425
+ settled = true;
2426
+ clearPending();
2427
+ reject(error);
2428
+ return;
2429
+ }
2430
+ this.#push(error);
2431
+ };
2432
+ socket.onclose = (event) => {
2433
+ this.#socket = null;
2434
+ this.#stopHeartbeat();
2435
+ if (!settled) {
2436
+ settled = true;
2437
+ clearPending();
2438
+ reject(new CodexWebSocketTransportError(`websocket closed before open (${event.code})`));
2439
+ return;
2440
+ }
2441
+ this.#push(new CodexWebSocketTransportError(`websocket closed (${event.code})`));
2442
+ this.#push(null);
2443
+ };
2444
+ socket.onmessage = (event) => {
2445
+ this.#lastInboundAt = Date.now();
2446
+ this.#writeDebugWebSocketFrame(event.data);
2447
+ try {
2448
+ const text = typeof event.data === "string" ? event.data : Buffer.from(event.data).toString("utf-8");
2449
+ if (!text) return;
2450
+ const parsed = JSON.parse(text);
2451
+ if (parsed.type === "error" && typeof parsed.error === "object" && parsed.error) {
2452
+ const inner = parsed.error;
2453
+ if (typeof parsed.code !== "string" && typeof inner.code === "string") parsed.code = inner.code;
2454
+ if (typeof parsed.message !== "string" && typeof inner.message === "string") parsed.message = inner.message;
2455
+ }
2456
+ notifyCodexWebSocketInbound(this.#streamObserver, parsed, text);
2457
+ this.#push(parsed);
2458
+ } catch (error) {
2459
+ notifyCodexWebSocketMalformed(this.#streamObserver, event.data, error);
2460
+ this.#push(new CodexWebSocketTransportError(`${String(error)}`));
2461
+ }
2462
+ };
2463
+ time("codexWs:awaitTcpHandshake");
2464
+ try {
2465
+ await promise;
2466
+ } finally {
2467
+ this.#connectPromise = void 0;
2468
+ }
2469
+ }
2470
+ async *streamRequest(request, timeouts, signal, onSseEvent) {
2471
+ if (!this.#socket || this.#socket.readyState !== WebSocket.OPEN) throw new CodexWebSocketTransportError(`websocket connection is unavailable`);
2472
+ if (this.#activeRequest) throw new CodexWebSocketTransportError(`websocket request already in progress`);
2473
+ if (signal?.aborted) throw new CodexWebSocketTransportError(`request was aborted`);
2474
+ this.#activeRequest = true;
2475
+ this.#streamObserver = onSseEvent;
2476
+ this.#dropStaleFrames();
2477
+ const onAbort = () => {
2478
+ this.close("aborted");
2479
+ this.#push(new CodexWebSocketTransportError(`request was aborted`));
2480
+ };
2481
+ if (signal) signal.addEventListener("abort", onAbort, { once: true });
2482
+ try {
2483
+ const debugSession = isRequestDebugEnabled() ? await createRequestDebugSession({
2484
+ protocol: "websocket",
2485
+ method: "POST",
2486
+ url: this.#url,
2487
+ headers: this.#headers,
2488
+ body: request
2489
+ }) : void 0;
2490
+ this.#debugResponseLog = debugSession ? await debugSession.openResponseLog("WebSocket 101 Switching Protocols", this.#handshakeHeaders) : void 0;
2491
+ const requestPayload = JSON.stringify(request);
2492
+ notifyCodexWebSocketOutbound(onSseEvent, request, requestPayload);
2493
+ const socket = this.#socket;
2494
+ if (!socket || socket.readyState !== WebSocket.OPEN) throw new CodexWebSocketTransportError(`websocket connection is unavailable`);
2495
+ try {
2496
+ socket.send(requestPayload);
2497
+ } catch (error) {
2498
+ throw new CodexWebSocketTransportError(`websocket send failed: ${error instanceof Error ? error.message : String(error)}`);
2499
+ }
2500
+ let sawFirstEvent = false;
2501
+ const { idleTimeoutMs, firstEventTimeoutMs } = timeouts;
2502
+ let lastProgressAt = Date.now();
2503
+ let lastProgressEventType;
2504
+ let lastEventAt = lastProgressAt;
2505
+ let lastEventType;
2506
+ let activeResponseId;
2507
+ let lastSequence;
2508
+ const priorResponseId = this.#lastSeenResponseId;
2509
+ while (true) {
2510
+ let timeoutMs;
2511
+ let timeoutReason;
2512
+ if (sawFirstEvent) {
2513
+ timeoutReason = createCodexWebSocketTimeoutMessage("idle timeout waiting for websocket", {
2514
+ lastEventAt,
2515
+ lastEventType,
2516
+ lastProgressAt,
2517
+ lastProgressEventType
2518
+ });
2519
+ if (idleTimeoutMs !== void 0 && idleTimeoutMs > 0) {
2520
+ timeoutMs = idleTimeoutMs - (Date.now() - lastProgressAt);
2521
+ if (timeoutMs <= 0) {
2522
+ CODEX_DEBUG && debug("[codex] codex websocket idle timeout", {
2523
+ lastEventType,
2524
+ lastProgressEventType,
2525
+ msSinceLastEvent: Date.now() - lastEventAt,
2526
+ msSinceLastProgress: Date.now() - lastProgressAt
2527
+ });
2528
+ throw new CodexWebSocketTransportError(`${timeoutReason}`);
2529
+ }
2530
+ }
2531
+ } else {
2532
+ timeoutReason = createCodexWebSocketTimeoutMessage("timeout waiting for first websocket event", {
2533
+ lastEventAt,
2534
+ lastEventType,
2535
+ lastProgressAt,
2536
+ lastProgressEventType
2537
+ });
2538
+ if (firstEventTimeoutMs !== void 0 && firstEventTimeoutMs > 0) timeoutMs = firstEventTimeoutMs;
2539
+ }
2540
+ const next = await this.#nextMessage(timeoutMs, timeoutReason);
2541
+ if (next instanceof Error) throw next;
2542
+ if (next === null) throw new CodexWebSocketTransportError(`websocket closed before response completion`);
2543
+ const eventType = typeof next.type === "string" ? next.type : "";
2544
+ const frameResponseId = extractCodexFrameResponseId(next);
2545
+ const frameSequence = extractCodexFrameSequenceNumber(next);
2546
+ if (frameResponseId !== void 0) {
2547
+ if (activeResponseId === void 0) {
2548
+ if (priorResponseId !== void 0 && frameResponseId === priorResponseId) continue;
2549
+ activeResponseId = frameResponseId;
2550
+ } else if (frameResponseId !== activeResponseId) {
2551
+ this.close("stale-frame");
2552
+ throw new CodexWebSocketTransportError(`websocket frame for response ${frameResponseId} interleaved into active response ${activeResponseId}`);
2553
+ }
2554
+ this.#lastSeenResponseId = frameResponseId;
2555
+ }
2556
+ if (frameSequence !== void 0) {
2557
+ if (activeResponseId !== void 0 && lastSequence !== void 0 && frameSequence < lastSequence) {
2558
+ this.close("stale-frame");
2559
+ throw new CodexWebSocketTransportError(`websocket sequence_number ${frameSequence} regressed below ${lastSequence} within response ${activeResponseId}`);
2560
+ }
2561
+ lastSequence = frameSequence;
2562
+ }
2563
+ sawFirstEvent = true;
2564
+ lastEventAt = Date.now();
2565
+ lastEventType = eventType || void 0;
2566
+ if (isCodexStreamProgressEvent(next)) {
2567
+ lastProgressAt = lastEventAt;
2568
+ lastProgressEventType = lastEventType;
2569
+ }
2570
+ yield next;
2571
+ if (eventType === "response.completed" || eventType === "response.done" || eventType === "response.incomplete" || eventType === "response.failed" || eventType === "error") break;
2572
+ }
2573
+ } finally {
2574
+ this.#activeRequest = false;
2575
+ this.#streamObserver = void 0;
2576
+ if (signal) signal.removeEventListener("abort", onAbort);
2577
+ const debugResponseLog = this.#debugResponseLog;
2578
+ this.#debugResponseLog = void 0;
2579
+ await debugResponseLog?.close();
2580
+ }
2581
+ }
2582
+ #captureHandshakeHeaders(socket, openEvent) {
2583
+ const headers = extractCodexWebSocketHandshakeHeaders(socket, openEvent);
2584
+ if (!headers) return;
2585
+ this.#handshakeHeaders = headers;
2586
+ this.#onHandshakeHeaders?.(headers);
2587
+ }
2588
+ #writeDebugWebSocketFrame(data) {
2589
+ const log = this.#debugResponseLog;
2590
+ if (!log) return;
2591
+ if (typeof data === "string") {
2592
+ log.write(data);
2593
+ return;
2594
+ }
2595
+ if (data instanceof Uint8Array) {
2596
+ log.write(data);
2597
+ return;
2598
+ }
2599
+ if (data instanceof ArrayBuffer) {
2600
+ log.write(new Uint8Array(data));
2601
+ return;
2602
+ }
2603
+ log.write(String(data));
2604
+ }
2605
+ #startHeartbeat(socket) {
2606
+ this.#stopHeartbeat();
2607
+ const intervalMs = CODEX_WEBSOCKET_PING_INTERVAL_MS;
2608
+ if (intervalMs <= 0) return;
2609
+ this.#lastPingAt = 0;
2610
+ const socketEventTarget = socket;
2611
+ const onPong = () => {
2612
+ this.#lastInboundAt = Date.now();
2613
+ };
2614
+ if (typeof socketEventTarget.addEventListener === "function" && typeof socketEventTarget.removeEventListener === "function") {
2615
+ socketEventTarget.addEventListener("pong", onPong);
2616
+ this.#removePongListener = () => socketEventTarget.removeEventListener("pong", onPong);
2617
+ }
2618
+ this.#heartbeatInterval = setInterval(() => {
2619
+ if (this.#socket !== socket || socket.readyState !== WebSocket.OPEN) {
2620
+ this.#stopHeartbeat();
2621
+ return;
2622
+ }
2623
+ const pongTimeoutMs = CODEX_WEBSOCKET_PONG_TIMEOUT_MS;
2624
+ if (pongTimeoutMs > 0 && this.#lastPingAt > 0 && this.#lastPingAt > this.#lastInboundAt && Date.now() - this.#lastPingAt > pongTimeoutMs) {
2625
+ this.#failQueue(new CodexWebSocketTransportError(`websocket pong timeout`), "pong-timeout");
2626
+ return;
2627
+ }
2628
+ if (typeof socket.ping !== "function") {
2629
+ this.#stopHeartbeat();
2630
+ return;
2631
+ }
2632
+ try {
2633
+ socket.ping();
2634
+ this.#lastPingAt = Date.now();
2635
+ } catch (error) {
2636
+ this.#failQueue(new CodexWebSocketTransportError(`websocket ping failed: ${error instanceof Error ? error.message : String(error)}`), "ping-failed");
2637
+ }
2638
+ }, intervalMs);
2639
+ this.#heartbeatInterval.unref();
2640
+ }
2641
+ #stopHeartbeat() {
2642
+ if (this.#heartbeatInterval) {
2643
+ clearInterval(this.#heartbeatInterval);
2644
+ this.#heartbeatInterval = void 0;
2645
+ }
2646
+ if (this.#removePongListener) {
2647
+ this.#removePongListener();
2648
+ this.#removePongListener = void 0;
2649
+ }
2650
+ this.#lastPingAt = 0;
2651
+ }
2652
+ #failQueue(error, closeReason) {
2653
+ CODEX_DEBUG && debug("[codex] codex websocket transport failure", {
2654
+ error: error.message,
2655
+ closeReason
2656
+ });
2657
+ this.#queue.length = 0;
2658
+ this.#queue.push(error);
2659
+ this.close(closeReason);
2660
+ this.#wakeWaiters();
2661
+ }
2662
+ /**
2663
+ * Discard data frames from a previous request that remained in `#queue`
2664
+ * after the consumer broke out on the terminal event. Preserves any queued
2665
+ * transport error (from `onerror` / `onclose` / `#failQueue`) so the next
2666
+ * `#nextMessage` surfaces the death signal instead of waiting it out.
2667
+ *
2668
+ * Returns the number of frames dropped (test/debug visibility only).
2669
+ */
2670
+ #dropStaleFrames() {
2671
+ if (this.#queue.length === 0) return 0;
2672
+ const surviving = this.#queue.filter((item) => item instanceof Error);
2673
+ const dropped = this.#queue.length - surviving.length;
2674
+ if (dropped === 0) return 0;
2675
+ this.#queue.length = 0;
2676
+ for (const item of surviving) this.#queue.push(item);
2677
+ CODEX_DEBUG && debug("[codex] codex websocket dropped stale frames before request", { dropped });
2678
+ return dropped;
2679
+ }
2680
+ #wakeWaiters() {
2681
+ for (;;) {
2682
+ const waiter = this.#waiters.shift();
2683
+ if (!waiter) break;
2684
+ waiter();
2685
+ }
2686
+ }
2687
+ #push(item) {
2688
+ if (item instanceof Error) {
2689
+ this.#queue.push(item);
2690
+ this.#wakeWaiters();
2691
+ return;
2692
+ }
2693
+ if (item !== null && this.#queue.length >= CODEX_WEBSOCKET_MESSAGE_QUEUE_CAPACITY) {
2694
+ this.#failQueue(new CodexWebSocketTransportError(`websocket message queue exceeded ${CODEX_WEBSOCKET_MESSAGE_QUEUE_CAPACITY} items`), "queue-overflow");
2695
+ return;
2696
+ }
2697
+ this.#queue.push(item);
2698
+ const waiter = this.#waiters.shift();
2699
+ if (waiter) waiter();
2700
+ }
2701
+ async #nextMessage(timeoutMs, timeoutReason) {
2702
+ while (this.#queue.length === 0) {
2703
+ const { promise, resolve } = Promise.withResolvers();
2704
+ this.#waiters.push(resolve);
2705
+ let timedOut = false;
2706
+ let timeout;
2707
+ if (timeoutMs !== void 0 && timeoutMs > 0) timeout = setTimeout(() => {
2708
+ timedOut = true;
2709
+ const waiterIndex = this.#waiters.indexOf(resolve);
2710
+ if (waiterIndex >= 0) this.#waiters.splice(waiterIndex, 1);
2711
+ resolve();
2712
+ }, timeoutMs);
2713
+ await promise;
2714
+ if (timeout) clearTimeout(timeout);
2715
+ if (timedOut && this.#queue.length === 0) return new CodexWebSocketTransportError(`${timeoutReason}`);
2716
+ }
2717
+ return this.#queue.shift() ?? null;
2718
+ }
2719
+ };
2720
+ async function getOrCreateCodexWebSocketConnection(state, url, headers, provider, signal) {
2721
+ const proxy = getProxyForUrl(provider, new URL(url));
2722
+ const headerRecord = headersToRecord(headers);
2723
+ for (let joinAttempt = 0; joinAttempt < 3; joinAttempt += 1) {
2724
+ const pending = state.connection;
2725
+ if (!pending || pending.isOpen() || !pending.isConnecting()) break;
2726
+ try {
2727
+ await pending.connect(signal);
2728
+ } catch {}
2729
+ }
2730
+ if (state.connection?.isOpen()) {
2731
+ if (!state.connection.matchesAuth(headerRecord)) {
2732
+ state.connection.close("token-refresh");
2733
+ resetCodexWebSocketAppendState(state);
2734
+ } else if (state.connection.isHealthyForReuse()) {
2735
+ time("codexWs:reuseOpenSocket");
2736
+ return state.connection;
2737
+ } else {
2738
+ CODEX_DEBUG && debug("[codex] codex websocket reuse rejected by health check", {});
2739
+ state.connection.close("stale-reuse");
2740
+ resetCodexWebSocketAppendState(state);
2741
+ }
2742
+ }
2743
+ state.connection?.close("reconnect");
2744
+ resetCodexWebSocketAppendState(state);
2745
+ time("codexWs:newSocket");
2746
+ state.connection = new CodexWebSocketConnection(url, headerRecord, {
2747
+ onHandshakeHeaders: (handshakeHeaders) => {
2748
+ updateCodexSessionMetadataFromHeaders(state, handshakeHeaders);
2749
+ },
2750
+ proxy
2751
+ });
2752
+ await state.connection.connect(signal);
2753
+ return state.connection;
2754
+ }
2755
+ /**
2756
+ * Compress an SSE request body with zstd. Returns `undefined` when
2757
+ * compression is disabled or fails, in which case the caller sends the
2758
+ * plain JSON string without a `content-encoding` header.
2759
+ */
2760
+ function compressCodexRequestBody(bodyJson, baseUrl) {
2761
+ if (!isOfficialCodexApiUrl(baseUrl) || !$flag("PI_CODEX_ZSTD", true)) return void 0;
2762
+ try {
2763
+ return Bun.zstdCompressSync(bodyJson, { level: 3 });
2764
+ } catch (error) {
2765
+ CODEX_DEBUG && debug("[codex] codex request body compression failed", { error: error instanceof Error ? error.message : String(error) });
2766
+ return;
2767
+ }
2768
+ }
2769
+ async function openCodexSseEventStream(url, requestHeaders, accountId, apiKey, sessionId, body, state, responsesLite, codexClientVersion, requestMetadata, signal, firstEventTimeoutMs, codexSseMaxAttempts, onSseEvent, fetchOverride) {
2770
+ const headers = createCodexHeaders(requestHeaders, accountId, apiKey, codexClientVersion, sessionId, "sse", state, responsesLite, requestMetadata, await getCodexAttestationHeader(accountId));
2771
+ let clearPreResponseTimeout;
2772
+ const fetchAttempt = async (input, init) => {
2773
+ try {
2774
+ return await (fetchOverride ?? fetch)(input, init);
2775
+ } finally {
2776
+ clearPreResponseTimeout?.();
2777
+ clearPreResponseTimeout = void 0;
2778
+ }
2779
+ };
2780
+ const bodyJson = JSON.stringify(body);
2781
+ const compressedBody = compressCodexRequestBody(bodyJson, url);
2782
+ if (compressedBody !== void 0) headers.set("content-encoding", "zstd");
2783
+ CODEX_DEBUG && debug("[codex] codex request", {
2784
+ url,
2785
+ model: body.model,
2786
+ headers: redactHeaders(headers),
2787
+ sentTurnStateHeader: headers.has(X_CODEX_TURN_STATE_HEADER),
2788
+ sentModelsEtagHeader: headers.has(X_MODELS_ETAG_HEADER)
2789
+ });
2790
+ const send = (requestBody) => fetchWithRetry(url, {
2791
+ method: "POST",
2792
+ headers,
2793
+ body: requestBody,
2794
+ signal,
2795
+ prepareInit: () => {
2796
+ const watchdog = armPreResponseTimeout(signal, firstEventTimeoutMs);
2797
+ clearPreResponseTimeout = watchdog.clear;
2798
+ return { signal: watchdog.signal };
2799
+ },
2800
+ maxAttempts: resolveCodexSseMaxAttempts(codexSseMaxAttempts),
2801
+ defaultDelayMs: (attempt) => CODEX_RETRY_DELAY_MS * (attempt + 1),
2802
+ maxDelayMs: CODEX_RATE_LIMIT_BUDGET_MS,
2803
+ fetch: fetchAttempt,
2804
+ timeout: false
2805
+ });
2806
+ let response;
2807
+ try {
2808
+ response = await send(compressedBody ?? bodyJson);
2809
+ if (compressedBody !== void 0 && (response.status === 400 || response.status === 415)) {
2810
+ const rejectedStatus = response.status;
2811
+ await response.body?.cancel();
2812
+ headers.delete("content-encoding");
2813
+ CODEX_DEBUG && debug("[codex] retrying request without zstd after encoding rejection", {
2814
+ url,
2815
+ status: rejectedStatus
2816
+ });
2817
+ response = await send(bodyJson);
2818
+ }
2819
+ } finally {
2820
+ clearPreResponseTimeout?.();
2821
+ }
2822
+ CODEX_DEBUG && debug("[codex] codex response", {
2823
+ url: response.url,
2824
+ status: response.status,
2825
+ statusText: response.statusText,
2826
+ contentType: response.headers.get("content-type") || null,
2827
+ cfRay: response.headers.get("cf-ray") || null
2828
+ });
2829
+ if (!response.ok) throw await CodexApiError.fromResponse(response);
2830
+ updateCodexSessionMetadataFromHeaders(state, response.headers);
2831
+ if (!response.body) throw new CodexProviderStreamError("No response body", false);
2832
+ return readSseJson(response.body, signal, (event) => onSseEvent?.({
2833
+ event: event.event,
2834
+ data: event.data,
2835
+ raw: [...event.raw]
2836
+ }, void 0));
2837
+ }
2838
+ function createCodexHeaders(initHeaders, accountId, accessToken, codexClientVersion, sessionId, transport = "sse", state, responsesLite = false, requestMetadata, attestation) {
2839
+ const headers = new Headers(initHeaders ?? {});
2840
+ headers.delete("x-api-key");
2841
+ headers.set("Authorization", `Bearer ${accessToken}`);
2842
+ if (accountId) headers.set(OPENAI_HEADERS.ACCOUNT_ID, accountId);
2843
+ if (attestation) headers.set(OPENAI_HEADERS.ATTESTATION, attestation);
2844
+ else headers.delete(OPENAI_HEADERS.ATTESTATION);
2845
+ const betaHeader = transport === "websocket" ? OPENAI_HEADER_VALUES.BETA_RESPONSES_WEBSOCKETS_V2 : OPENAI_HEADER_VALUES.BETA_RESPONSES;
2846
+ headers.delete(OPENAI_HEADERS.BETA);
2847
+ headers.delete("openai-beta");
2848
+ headers.set(OPENAI_HEADERS.BETA, betaHeader);
2849
+ headers.set(OPENAI_HEADERS.ORIGINATOR, OPENAI_HEADER_VALUES.ORIGINATOR_CODEX);
2850
+ headers.set(OPENAI_HEADERS.VERSION, codexClientVersion);
2851
+ headers.set("User-Agent", USER_AGENT);
2852
+ if (sessionId) {
2853
+ headers.set(OPENAI_HEADERS.CONVERSATION_ID, sessionId);
2854
+ headers.set(OPENAI_HEADERS.SESSION_ID, sessionId);
2855
+ headers.set("x-client-request-id", sessionId);
2856
+ } else {
2857
+ headers.delete(OPENAI_HEADERS.CONVERSATION_ID);
2858
+ headers.delete(OPENAI_HEADERS.SESSION_ID);
2859
+ headers.delete("x-client-request-id");
2860
+ }
2861
+ headers.delete(OPENAI_HEADERS.INSTALLATION_ID);
2862
+ if (requestMetadata) applyCodexCompatibilityHeaders(headers, requestMetadata);
2863
+ else {
2864
+ headers.delete(OPENAI_HEADERS.SCOPED_SESSION_ID);
2865
+ headers.delete(OPENAI_HEADERS.THREAD_ID);
2866
+ headers.delete(OPENAI_HEADERS.WINDOW_ID);
2867
+ headers.delete(OPENAI_HEADERS.TURN_METADATA);
2868
+ }
2869
+ if (state?.turnState) headers.set(X_CODEX_TURN_STATE_HEADER, state.turnState);
2870
+ else headers.delete(X_CODEX_TURN_STATE_HEADER);
2871
+ if (state?.modelsEtag) headers.set(X_MODELS_ETAG_HEADER, state.modelsEtag);
2872
+ else headers.delete(X_MODELS_ETAG_HEADER);
2873
+ if (responsesLite) headers.set(OPENAI_HEADERS.RESPONSES_LITE, "true");
2874
+ else headers.delete(OPENAI_HEADERS.RESPONSES_LITE);
2875
+ if (transport === "sse") {
2876
+ headers.set("accept", "text/event-stream");
2877
+ headers.set("content-type", "application/json");
2878
+ } else {
2879
+ headers.delete("accept");
2880
+ headers.delete("content-type");
2881
+ }
2882
+ return headers;
2883
+ }
2884
+ function redactHeaders(headers) {
2885
+ const redacted = {};
2886
+ for (const [key, value] of headers.entries()) {
2887
+ const lower = key.toLowerCase();
2888
+ if (lower === "authorization") {
2889
+ redacted[key] = "Bearer [redacted]";
2890
+ continue;
2891
+ }
2892
+ if (lower.includes("account") || lower.includes("session") || lower.includes("conversation") || lower.includes("thread") || lower.includes("window") || lower.includes("installation") || lower.startsWith("x-codex-turn") || lower === "x-client-request-id" || lower === "cookie") {
2893
+ redacted[key] = "[redacted]";
2894
+ continue;
2895
+ }
2896
+ redacted[key] = value;
2897
+ }
2898
+ return redacted;
2899
+ }
2900
+ /** Resolve a Codex Responses endpoint exactly as the chat and compaction transports do. */
2901
+ function resolveCodexResponsesUrl(baseUrl) {
2902
+ const normalized = (baseUrl && baseUrl.trim().length > 0 ? baseUrl : CODEX_BASE_URL).replace(/\/+$/, "");
2903
+ if (normalized.endsWith("/codex/responses")) return normalized;
2904
+ if (normalized.endsWith("/codex")) return `${normalized}/responses`;
2905
+ return `${normalized}/codex/responses`;
2906
+ }
2907
+ function convertMessages(model, context) {
2908
+ const messages = [];
2909
+ const normalizeToolCallId = (id) => {
2910
+ if (!id.includes("|")) return id;
2911
+ const [callId, itemId] = id.split("|");
2912
+ const sanitizedCallId = callId.replace(/[^a-zA-Z0-9_-]/g, "_");
2913
+ let sanitizedItemId = itemId.replace(/[^a-zA-Z0-9_-]/g, "_");
2914
+ if (!sanitizedItemId.startsWith("fc")) sanitizedItemId = `fc_${sanitizedItemId}`;
2915
+ let normalizedCallId = sanitizedCallId.length > 64 ? sanitizedCallId.slice(0, 64) : sanitizedCallId;
2916
+ let normalizedItemId = sanitizedItemId.length > 64 ? sanitizedItemId.slice(0, 64) : sanitizedItemId;
2917
+ normalizedCallId = normalizedCallId.replace(/_+$/, "");
2918
+ normalizedItemId = normalizedItemId.replace(/_+$/, "");
2919
+ return `${normalizedCallId}|${normalizedItemId}`;
2920
+ };
2921
+ const transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);
2922
+ const escapeControlTokens = isHarmonyDialectModel(model);
2923
+ let msgIndex = 0;
2924
+ const customCallIds = /* @__PURE__ */ new Set();
2925
+ const knownCallIds = /* @__PURE__ */ new Set();
2926
+ const computerCallIds = /* @__PURE__ */ new Set();
2927
+ for (const msg of transformedMessages) {
2928
+ if (msg.role === "user" || msg.role === "developer") {
2929
+ const providerPayload = msg.providerPayload;
2930
+ const historyItems = getOpenAIResponsesHistoryItems(providerPayload, model.provider);
2931
+ if (historyItems) {
2932
+ const redactedHistoryItems = redactSensitiveInObject(historyItems).result;
2933
+ const replayItems = model.supportsComputerUse === true ? redactedHistoryItems : unrollCodexComputerItems(redactedHistoryItems, model.compat.supportsImageDetailOriginal);
2934
+ for (const item of replayItems) {
2935
+ if (item.type === "custom_tool_call") customCallIds.add(item.call_id);
2936
+ if (item.type === "computer_call") computerCallIds.add(item.call_id);
2937
+ if ((item.type === "function_call" || item.type === "custom_tool_call") && item.call_id) knownCallIds.add(item.call_id);
2938
+ }
2939
+ messages.push(...escapeControlTokens ? escapeReplayedControlTokens(replayItems) : replayItems);
2940
+ msgIndex += 1;
2941
+ continue;
2942
+ }
2943
+ const normalizedContent = normalizeInputMessageContent(model, msg.content);
2944
+ if (normalizedContent.length === 0) continue;
2945
+ messages.push({
2946
+ role: msg.role,
2947
+ content: normalizedContent
2948
+ });
2949
+ msgIndex += 1;
2950
+ continue;
2951
+ }
2952
+ if (msg.role === "assistant") {
2953
+ const assistantMsg = msg;
2954
+ const providerPayload = assistantMsg.api === model.api && assistantMsg.model === model.id ? getOpenAIResponsesHistoryPayload(assistantMsg.providerPayload, model.provider, assistantMsg.provider) : void 0;
2955
+ const historyItems = providerPayload?.items;
2956
+ let suppressHiddenEmptyFallback = false;
2957
+ if (historyItems) {
2958
+ const sanitizedHistoryItems = sanitizeOpenAIResponsesAssistantHistoryItemsForReplay(historyItems);
2959
+ if (sanitizedHistoryItems) {
2960
+ const rawReplayItems = model.supportsComputerUse === true ? sanitizedHistoryItems : unrollCodexComputerItems(sanitizedHistoryItems, model.compat.supportsImageDetailOriginal);
2961
+ const replayItems = escapeControlTokens ? escapeReplayedControlTokens(rawReplayItems) : rawReplayItems;
2962
+ for (const item of replayItems) {
2963
+ if (item.type === "custom_tool_call") customCallIds.add(item.call_id);
2964
+ if (item.type === "computer_call") computerCallIds.add(item.call_id);
2965
+ if ((item.type === "function_call" || item.type === "custom_tool_call") && item.call_id) knownCallIds.add(item.call_id);
2966
+ }
2967
+ if (providerPayload?.dt) messages.push(...replayItems);
2968
+ else messages.splice(0, messages.length, ...replayItems);
2969
+ msgIndex += 1;
2970
+ continue;
2971
+ }
2972
+ suppressHiddenEmptyFallback = true;
2973
+ }
2974
+ const convertedOutputItems = convertResponsesAssistantMessage(model.supportsComputerUse === true ? assistantMsg : unrollCodexComputerAssistantMessage(assistantMsg), model, msgIndex, knownCallIds, !suppressHiddenEmptyFallback, customCallIds, false, true, void 0, computerCallIds);
2975
+ const outputItems = suppressHiddenEmptyFallback ? sanitizeOpenAIResponsesAssistantFallbackItemsForReplay(convertedOutputItems) : convertedOutputItems;
2976
+ if (outputItems.length > 0) messages.push(...escapeControlTokens ? escapeReplayedControlTokens(outputItems) : outputItems);
2977
+ msgIndex += 1;
2978
+ continue;
2979
+ }
2980
+ if (msg.role === "toolResult") appendResponsesToolResultMessages(messages, model.supportsComputerUse === true ? msg : unrollCodexComputerToolResult(msg), model, false, model.compat.supportsImageDetailOriginal, knownCallIds, customCallIds, true, computerCallIds);
2981
+ msgIndex += 1;
2982
+ }
2983
+ return messages;
2984
+ }
2985
+ function normalizeInputMessageContent(model, content) {
2986
+ const escapeControlTokens = isHarmonyDialectModel(model);
2987
+ if (typeof content === "string") {
2988
+ if (!content || content.trim() === "") return [];
2989
+ const text = content.toWellFormed();
2990
+ return [{
2991
+ type: "input_text",
2992
+ text: escapeControlTokens ? escapeHarmonyControlTokens(text) : text
2993
+ }];
2994
+ }
2995
+ return convertResponsesInputContent(content, model.input.includes("image"), model.compat.supportsImageDetailOriginal, escapeControlTokens) ?? [];
2996
+ }
2997
+ /** @internal Exported for tests. */
2998
+ function convertOpenAICodexResponsesTools(tools, model) {
2999
+ const allowFreeform = model.applyPatchToolType === "freeform";
3000
+ const payloads = [];
3001
+ for (const tool of tools) {
3002
+ if (tool.native?.type === "computer" && model.supportsComputerUse === true) {
3003
+ payloads.push({ type: "computer" });
3004
+ continue;
3005
+ }
3006
+ if (allowFreeform && tool.customFormat) {
3007
+ payloads.push({
3008
+ type: "custom",
3009
+ name: tool.customWireName ?? tool.name,
3010
+ description: tool.description || "",
3011
+ format: {
3012
+ type: "grammar",
3013
+ syntax: tool.customFormat.syntax,
3014
+ definition: compactGrammarDefinition(tool.customFormat.syntax, tool.customFormat.definition)
3015
+ }
3016
+ });
3017
+ continue;
3018
+ }
3019
+ const strict = !!(!NO_STRICT && tool.strict);
3020
+ const baseParameters = sanitizeSchemaForOpenAIResponses(toolWireSchema(tool));
3021
+ const { schema: parameters, strict: effectiveStrict } = adaptSchemaForStrict(baseParameters, strict);
3022
+ payloads.push({
3023
+ type: "function",
3024
+ name: tool.name,
3025
+ description: tool.description || "",
3026
+ parameters,
3027
+ ...effectiveStrict ? { strict: true } : !NO_STRICT && tool.strict === false ? { strict: false } : {}
3028
+ });
3029
+ }
3030
+ return payloads;
3031
+ }
3032
+ var CodexWebSocketTransportError = class extends Error {
3033
+ constructor(detail) {
3034
+ super(`${CODEX_WEBSOCKET_TRANSPORT_ERROR_PREFIX}: ${detail}`);
3035
+ this.name = "CodexWebSocketTransportError";
3036
+ }
3037
+ };
3038
+ var CodexWhitespaceToolCallLoopError = class extends Error {
3039
+ constructor(message) {
3040
+ super(message);
3041
+ this.name = "CodexWhitespaceToolCallLoopError";
3042
+ }
3043
+ };
3044
+ var CodexProviderStreamError = class extends Error {
3045
+ retryable;
3046
+ code;
3047
+ constructor(message, retryable, code) {
3048
+ super(message);
3049
+ this.name = "CodexProviderStreamError";
3050
+ this.retryable = retryable;
3051
+ this.code = code;
3052
+ }
3053
+ };
3054
+ const optionalCodexString = type("unknown").pipe((raw) => {
3055
+ const out = type("string")(raw);
3056
+ return out instanceof type.errors ? void 0 : out;
3057
+ });
3058
+ const innerErrorDetailSchema = type({
3059
+ "code?": optionalCodexString,
3060
+ "type?": optionalCodexString,
3061
+ "message?": optionalCodexString
3062
+ });
3063
+ const codexErrorDetailSchema = type("unknown").pipe((raw) => {
3064
+ const out = innerErrorDetailSchema(raw);
3065
+ return out instanceof type.errors ? void 0 : out;
3066
+ });
3067
+ const innerFailureEventSchema = type({
3068
+ "type?": optionalCodexString,
3069
+ "code?": optionalCodexString,
3070
+ "message?": optionalCodexString,
3071
+ "status?": optionalCodexString,
3072
+ "error?": codexErrorDetailSchema,
3073
+ "response?": type("unknown").pipe((raw) => {
3074
+ const out = type({
3075
+ "error?": codexErrorDetailSchema,
3076
+ "message?": optionalCodexString,
3077
+ "status?": optionalCodexString
3078
+ })(raw);
3079
+ return out instanceof type.errors ? void 0 : out;
3080
+ })
3081
+ });
3082
+ const codexFailureEventSchema = type("unknown").pipe((raw) => {
3083
+ const out = innerFailureEventSchema(raw);
3084
+ return out instanceof type.errors ? {
3085
+ type: void 0,
3086
+ code: void 0,
3087
+ message: void 0,
3088
+ status: void 0,
3089
+ error: void 0,
3090
+ response: void 0
3091
+ } : out;
3092
+ });
3093
+ function isRetryableCodexFailureEvent(rawEvent) {
3094
+ const event = codexFailureEventSchema(rawEvent);
3095
+ if (event instanceof type.errors) return false;
3096
+ const error = event.error ?? event.response?.error;
3097
+ const code = error?.code ?? error?.type ?? event.code;
3098
+ if (code && CODEX_RETRYABLE_EVENT_CODES.has(code.toLowerCase())) return true;
3099
+ const message = error?.message ?? event.message ?? event.response?.message;
3100
+ return !!message && CODEX_RETRYABLE_EVENT_MESSAGE.test(message);
3101
+ }
3102
+ function createCodexProviderStreamError(rawEvent) {
3103
+ const event = codexFailureEventSchema(rawEvent);
3104
+ if (event instanceof type.errors) return new CodexProviderStreamError("Codex response failed", false);
3105
+ const nestedError = event.error ?? event.response?.error;
3106
+ const code = nestedError?.code ?? nestedError?.type ?? event.code ?? "";
3107
+ const message = event.message ?? "";
3108
+ return new CodexProviderStreamError(event.type === "error" ? formatCodexErrorEvent(rawEvent, code, message) : formatCodexFailure(rawEvent) ?? "Codex response failed", isRetryableCodexFailureEvent(rawEvent), code || void 0);
3109
+ }
3110
+ function formatCodexFailure(rawEvent) {
3111
+ const event = codexFailureEventSchema(rawEvent);
3112
+ if (event instanceof type.errors) return null;
3113
+ const error = event.error ?? event.response?.error;
3114
+ const message = error?.message ?? event.message ?? event.response?.message;
3115
+ const code = error?.code ?? error?.type ?? event.code;
3116
+ const status = event.response?.status ?? event.status;
3117
+ const meta = [];
3118
+ if (code) meta.push(`code=${code}`);
3119
+ if (status) meta.push(`status=${status}`);
3120
+ if (message) return `Codex response failed: ${message}${meta.length ? ` (${meta.join(", ")})` : ""}`;
3121
+ if (meta.length) return `Codex response failed (${meta.join(", ")})`;
3122
+ try {
3123
+ const rawEventJson = JSON.stringify(rawEvent);
3124
+ return `Codex response failed: ${rawEventJson.length <= 800 ? rawEventJson : `${rawEventJson.slice(0, 800)}…[truncated ${rawEventJson.length - 800}]`}`;
3125
+ } catch {
3126
+ return "Codex response failed";
3127
+ }
3128
+ }
3129
+ function formatCodexErrorEvent(rawEvent, code, message) {
3130
+ const detail = formatCodexFailure(rawEvent);
3131
+ if (detail) return detail.replace("response failed", "error event");
3132
+ const meta = [];
3133
+ if (code) meta.push(`code=${code}`);
3134
+ if (message) meta.push(`message=${message}`);
3135
+ if (meta.length > 0) return `Codex error event (${meta.join(", ")})`;
3136
+ try {
3137
+ const rawEventJson = JSON.stringify(rawEvent);
3138
+ return `Codex error event: ${rawEventJson.length <= 800 ? rawEventJson : `${rawEventJson.slice(0, 800)}…[truncated ${rawEventJson.length - 800}]`}`;
3139
+ } catch {
3140
+ return "Codex error event";
3141
+ }
3142
+ }
3143
+ //#endregion
3144
+ export { openai_codex_responses_exports as t };