@xiaoso/dsh-tool-plus 0.1.16 → 0.1.17-beta.0

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