phi-code-ai 0.56.4 → 0.74.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 (187) hide show
  1. package/README.md +258 -73
  2. package/dist/api-registry.d.ts.map +1 -1
  3. package/dist/api-registry.js.map +1 -1
  4. package/dist/bedrock-provider.d.ts.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +1 -1
  7. package/dist/cli.js.map +1 -1
  8. package/dist/env-api-keys.d.ts +9 -0
  9. package/dist/env-api-keys.d.ts.map +1 -1
  10. package/dist/env-api-keys.js +96 -30
  11. package/dist/env-api-keys.js.map +1 -1
  12. package/dist/image-models.d.ts +10 -0
  13. package/dist/image-models.d.ts.map +1 -0
  14. package/dist/image-models.generated.d.ts +305 -0
  15. package/dist/image-models.generated.d.ts.map +1 -0
  16. package/dist/image-models.generated.js +307 -0
  17. package/dist/image-models.generated.js.map +1 -0
  18. package/dist/image-models.js +23 -0
  19. package/dist/image-models.js.map +1 -0
  20. package/dist/images-api-registry.d.ts +14 -0
  21. package/dist/images-api-registry.d.ts.map +1 -0
  22. package/dist/images-api-registry.js +22 -0
  23. package/dist/images-api-registry.js.map +1 -0
  24. package/dist/images.d.ts +4 -0
  25. package/dist/images.d.ts.map +1 -0
  26. package/dist/images.js +14 -0
  27. package/dist/images.js.map +1 -0
  28. package/dist/index.d.ts +20 -11
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +8 -9
  31. package/dist/index.js.map +1 -1
  32. package/dist/models.d.ts +3 -9
  33. package/dist/models.d.ts.map +1 -1
  34. package/dist/models.generated.d.ts +6525 -2231
  35. package/dist/models.generated.d.ts.map +1 -1
  36. package/dist/models.generated.js +8992 -5524
  37. package/dist/models.generated.js.map +1 -1
  38. package/dist/models.js +28 -12
  39. package/dist/models.js.map +1 -1
  40. package/dist/oauth.d.ts.map +1 -1
  41. package/dist/providers/amazon-bedrock.d.ts +23 -0
  42. package/dist/providers/amazon-bedrock.d.ts.map +1 -1
  43. package/dist/providers/amazon-bedrock.js +206 -44
  44. package/dist/providers/amazon-bedrock.js.map +1 -1
  45. package/dist/providers/anthropic.d.ts +23 -2
  46. package/dist/providers/anthropic.d.ts.map +1 -1
  47. package/dist/providers/anthropic.js +294 -63
  48. package/dist/providers/anthropic.js.map +1 -1
  49. package/dist/providers/azure-openai-responses.d.ts.map +1 -1
  50. package/dist/providers/azure-openai-responses.js +47 -23
  51. package/dist/providers/azure-openai-responses.js.map +1 -1
  52. package/dist/providers/cloudflare.d.ts +13 -0
  53. package/dist/providers/cloudflare.d.ts.map +1 -0
  54. package/dist/providers/cloudflare.js +26 -0
  55. package/dist/providers/cloudflare.js.map +1 -0
  56. package/dist/providers/faux.d.ts +56 -0
  57. package/dist/providers/faux.d.ts.map +1 -0
  58. package/dist/providers/faux.js +368 -0
  59. package/dist/providers/faux.js.map +1 -0
  60. package/dist/providers/github-copilot-headers.d.ts.map +1 -1
  61. package/dist/providers/github-copilot-headers.js.map +1 -1
  62. package/dist/providers/google-shared.d.ts +7 -2
  63. package/dist/providers/google-shared.d.ts.map +1 -1
  64. package/dist/providers/google-shared.js +53 -24
  65. package/dist/providers/google-shared.js.map +1 -1
  66. package/dist/providers/google-vertex.d.ts +1 -1
  67. package/dist/providers/google-vertex.d.ts.map +1 -1
  68. package/dist/providers/google-vertex.js +87 -16
  69. package/dist/providers/google-vertex.js.map +1 -1
  70. package/dist/providers/google.d.ts +1 -1
  71. package/dist/providers/google.d.ts.map +1 -1
  72. package/dist/providers/google.js +57 -9
  73. package/dist/providers/google.js.map +1 -1
  74. package/dist/providers/images/openrouter.d.ts +3 -0
  75. package/dist/providers/images/openrouter.d.ts.map +1 -0
  76. package/dist/providers/images/openrouter.js +129 -0
  77. package/dist/providers/images/openrouter.js.map +1 -0
  78. package/dist/providers/images/register-builtins.d.ts +4 -0
  79. package/dist/providers/images/register-builtins.d.ts.map +1 -0
  80. package/dist/providers/images/register-builtins.js +34 -0
  81. package/dist/providers/images/register-builtins.js.map +1 -0
  82. package/dist/providers/mistral.d.ts +3 -0
  83. package/dist/providers/mistral.d.ts.map +1 -1
  84. package/dist/providers/mistral.js +49 -9
  85. package/dist/providers/mistral.js.map +1 -1
  86. package/dist/providers/openai-codex-responses.d.ts +21 -0
  87. package/dist/providers/openai-codex-responses.d.ts.map +1 -1
  88. package/dist/providers/openai-codex-responses.js +443 -86
  89. package/dist/providers/openai-codex-responses.js.map +1 -1
  90. package/dist/providers/openai-completions.d.ts +5 -1
  91. package/dist/providers/openai-completions.d.ts.map +1 -1
  92. package/dist/providers/openai-completions.js +459 -225
  93. package/dist/providers/openai-completions.js.map +1 -1
  94. package/dist/providers/openai-responses-shared.d.ts +1 -0
  95. package/dist/providers/openai-responses-shared.d.ts.map +1 -1
  96. package/dist/providers/openai-responses-shared.js +95 -45
  97. package/dist/providers/openai-responses-shared.js.map +1 -1
  98. package/dist/providers/openai-responses.d.ts.map +1 -1
  99. package/dist/providers/openai-responses.js +66 -44
  100. package/dist/providers/openai-responses.js.map +1 -1
  101. package/dist/providers/register-builtins.d.ts +27 -2
  102. package/dist/providers/register-builtins.d.ts.map +1 -1
  103. package/dist/providers/register-builtins.js +157 -52
  104. package/dist/providers/register-builtins.js.map +1 -1
  105. package/dist/providers/simple-options.d.ts.map +1 -1
  106. package/dist/providers/simple-options.js +5 -1
  107. package/dist/providers/simple-options.js.map +1 -1
  108. package/dist/providers/transform-messages.d.ts.map +1 -1
  109. package/dist/providers/transform-messages.js +63 -34
  110. package/dist/providers/transform-messages.js.map +1 -1
  111. package/dist/session-resources.d.ts +4 -0
  112. package/dist/session-resources.d.ts.map +1 -0
  113. package/dist/session-resources.js +22 -0
  114. package/dist/session-resources.js.map +1 -0
  115. package/dist/stream.d.ts.map +1 -1
  116. package/dist/stream.js.map +1 -1
  117. package/dist/types.d.ts +219 -15
  118. package/dist/types.d.ts.map +1 -1
  119. package/dist/types.js.map +1 -1
  120. package/dist/utils/diagnostics.d.ts +19 -0
  121. package/dist/utils/diagnostics.d.ts.map +1 -0
  122. package/dist/utils/diagnostics.js +25 -0
  123. package/dist/utils/diagnostics.js.map +1 -0
  124. package/dist/utils/event-stream.d.ts.map +1 -1
  125. package/dist/utils/event-stream.js +7 -3
  126. package/dist/utils/event-stream.js.map +1 -1
  127. package/dist/utils/hash.d.ts.map +1 -1
  128. package/dist/utils/hash.js.map +1 -1
  129. package/dist/utils/headers.d.ts +2 -0
  130. package/dist/utils/headers.d.ts.map +1 -0
  131. package/dist/utils/headers.js +8 -0
  132. package/dist/utils/headers.js.map +1 -0
  133. package/dist/utils/json-parse.d.ts +8 -1
  134. package/dist/utils/json-parse.d.ts.map +1 -1
  135. package/dist/utils/json-parse.js +89 -5
  136. package/dist/utils/json-parse.js.map +1 -1
  137. package/dist/utils/oauth/anthropic.d.ts +14 -6
  138. package/dist/utils/oauth/anthropic.d.ts.map +1 -1
  139. package/dist/utils/oauth/anthropic.js +288 -57
  140. package/dist/utils/oauth/anthropic.js.map +1 -1
  141. package/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  142. package/dist/utils/oauth/github-copilot.js +23 -12
  143. package/dist/utils/oauth/github-copilot.js.map +1 -1
  144. package/dist/utils/oauth/index.d.ts +0 -4
  145. package/dist/utils/oauth/index.d.ts.map +1 -1
  146. package/dist/utils/oauth/index.js +0 -10
  147. package/dist/utils/oauth/index.js.map +1 -1
  148. package/dist/utils/oauth/oauth-page.d.ts +3 -0
  149. package/dist/utils/oauth/oauth-page.d.ts.map +1 -0
  150. package/dist/utils/oauth/oauth-page.js +105 -0
  151. package/dist/utils/oauth/oauth-page.js.map +1 -0
  152. package/dist/utils/oauth/openai-codex.d.ts.map +1 -1
  153. package/dist/utils/oauth/openai-codex.js +51 -46
  154. package/dist/utils/oauth/openai-codex.js.map +1 -1
  155. package/dist/utils/oauth/pkce.d.ts.map +1 -1
  156. package/dist/utils/oauth/pkce.js.map +1 -1
  157. package/dist/utils/oauth/types.d.ts +10 -0
  158. package/dist/utils/oauth/types.d.ts.map +1 -1
  159. package/dist/utils/oauth/types.js.map +1 -1
  160. package/dist/utils/overflow.d.ts +7 -3
  161. package/dist/utils/overflow.d.ts.map +1 -1
  162. package/dist/utils/overflow.js +46 -13
  163. package/dist/utils/overflow.js.map +1 -1
  164. package/dist/utils/sanitize-unicode.d.ts.map +1 -1
  165. package/dist/utils/sanitize-unicode.js.map +1 -1
  166. package/dist/utils/typebox-helpers.d.ts +1 -1
  167. package/dist/utils/typebox-helpers.d.ts.map +1 -1
  168. package/dist/utils/typebox-helpers.js +1 -1
  169. package/dist/utils/typebox-helpers.js.map +1 -1
  170. package/dist/utils/validation.d.ts.map +1 -1
  171. package/dist/utils/validation.js +247 -38
  172. package/dist/utils/validation.js.map +1 -1
  173. package/package.json +43 -13
  174. package/bedrock-provider.d.ts +0 -1
  175. package/bedrock-provider.js +0 -1
  176. package/dist/providers/google-gemini-cli.d.ts +0 -74
  177. package/dist/providers/google-gemini-cli.d.ts.map +0 -1
  178. package/dist/providers/google-gemini-cli.js +0 -754
  179. package/dist/providers/google-gemini-cli.js.map +0 -1
  180. package/dist/utils/oauth/google-antigravity.d.ts +0 -26
  181. package/dist/utils/oauth/google-antigravity.d.ts.map +0 -1
  182. package/dist/utils/oauth/google-antigravity.js +0 -373
  183. package/dist/utils/oauth/google-antigravity.js.map +0 -1
  184. package/dist/utils/oauth/google-gemini-cli.d.ts +0 -26
  185. package/dist/utils/oauth/google-gemini-cli.d.ts.map +0 -1
  186. package/dist/utils/oauth/google-gemini-cli.js +0 -478
  187. package/dist/utils/oauth/google-gemini-cli.js.map +0 -1
@@ -8,10 +8,13 @@ if (typeof process !== "undefined" && (process.versions?.node || process.version
8
8
  });
9
9
  }
10
10
  import { getEnvApiKey } from "../env-api-keys.js";
11
- import { supportsXhigh } from "../models.js";
11
+ import { clampThinkingLevel } from "../models.js";
12
+ import { registerSessionResourceCleanup } from "../session-resources.js";
13
+ import { appendAssistantMessageDiagnostic, createAssistantMessageDiagnostic, formatThrownValue, } from "../utils/diagnostics.js";
12
14
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
15
+ import { headersToRecord } from "../utils/headers.js";
13
16
  import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.js";
14
- import { buildBaseOptions, clampReasoning } from "./simple-options.js";
17
+ import { buildBaseOptions } from "./simple-options.js";
15
18
  // ============================================================================
16
19
  // Configuration
17
20
  // ============================================================================
@@ -20,6 +23,7 @@ const JWT_CLAIM_PATH = "https://api.openai.com/auth";
20
23
  const MAX_RETRIES = 3;
21
24
  const BASE_DELAY_MS = 1000;
22
25
  const CODEX_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]);
26
+ const WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE = 1009;
23
27
  const CODEX_RESPONSE_STATUSES = new Set([
24
28
  "completed",
25
29
  "incomplete",
@@ -79,15 +83,24 @@ export const streamOpenAICodexResponses = (model, context, options) => {
79
83
  throw new Error(`No API key for provider: ${model.provider}`);
80
84
  }
81
85
  const accountId = extractAccountId(apiKey);
82
- const body = buildRequestBody(model, context, options);
83
- options?.onPayload?.(body);
84
- const headers = buildHeaders(model.headers, options?.headers, accountId, apiKey, options?.sessionId);
86
+ let body = buildRequestBody(model, context, options);
87
+ const nextBody = await options?.onPayload?.(body, model);
88
+ if (nextBody !== undefined) {
89
+ body = nextBody;
90
+ }
91
+ const websocketRequestId = options?.sessionId || createCodexRequestId();
92
+ const sseHeaders = buildSSEHeaders(model.headers, options?.headers, accountId, apiKey, options?.sessionId);
93
+ const websocketHeaders = buildWebSocketHeaders(model.headers, options?.headers, accountId, apiKey, websocketRequestId);
85
94
  const bodyJson = JSON.stringify(body);
86
- const transport = options?.transport || "sse";
87
- if (transport !== "sse") {
95
+ const transport = options?.transport || "auto";
96
+ const websocketDisabledForSession = transport !== "sse" && isWebSocketSseFallbackActive(options?.sessionId);
97
+ if (websocketDisabledForSession) {
98
+ recordWebSocketSseFallback(options?.sessionId);
99
+ }
100
+ if (transport !== "sse" && !websocketDisabledForSession) {
88
101
  let websocketStarted = false;
89
102
  try {
90
- await processWebSocketStream(resolveCodexWebSocketUrl(model.baseUrl), body, headers, output, stream, model, () => {
103
+ await processWebSocketStream(resolveCodexWebSocketUrl(model.baseUrl), body, websocketHeaders, output, stream, model, () => {
91
104
  websocketStarted = true;
92
105
  }, options);
93
106
  if (options?.signal?.aborted) {
@@ -102,9 +115,22 @@ export const streamOpenAICodexResponses = (model, context, options) => {
102
115
  return;
103
116
  }
104
117
  catch (error) {
105
- if (transport === "websocket" || websocketStarted) {
118
+ const aborted = options?.signal?.aborted;
119
+ if (aborted || isCodexNonTransportError(error)) {
120
+ throw error;
121
+ }
122
+ appendAssistantMessageDiagnostic(output, createAssistantMessageDiagnostic("provider_transport_failure", error, {
123
+ configuredTransport: transport,
124
+ fallbackTransport: websocketStarted ? undefined : "sse",
125
+ eventsEmitted: websocketStarted,
126
+ phase: websocketStarted ? "after_message_stream_start" : "before_message_stream_start",
127
+ requestBytes: new TextEncoder().encode(bodyJson).byteLength,
128
+ }));
129
+ recordWebSocketFailure(options?.sessionId, error);
130
+ if (websocketStarted) {
106
131
  throw error;
107
132
  }
133
+ recordWebSocketSseFallback(options?.sessionId);
108
134
  }
109
135
  }
110
136
  // Fetch with retry logic for rate limits and transient errors
@@ -117,10 +143,11 @@ export const streamOpenAICodexResponses = (model, context, options) => {
117
143
  try {
118
144
  response = await fetch(resolveCodexUrl(model.baseUrl), {
119
145
  method: "POST",
120
- headers,
146
+ headers: sseHeaders,
121
147
  body: bodyJson,
122
148
  signal: options?.signal,
123
149
  });
150
+ await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
124
151
  if (response.ok) {
125
152
  break;
126
153
  }
@@ -161,7 +188,7 @@ export const streamOpenAICodexResponses = (model, context, options) => {
161
188
  throw new Error("No response body");
162
189
  }
163
190
  stream.push({ type: "start", partial: output });
164
- await processStream(response, output, stream, model);
191
+ await processStream(response, output, stream, model, options);
165
192
  if (options?.signal?.aborted) {
166
193
  throw new Error("Request was aborted");
167
194
  }
@@ -169,6 +196,10 @@ export const streamOpenAICodexResponses = (model, context, options) => {
169
196
  stream.end();
170
197
  }
171
198
  catch (error) {
199
+ for (const block of output.content) {
200
+ // partialJson is only a streaming scratch buffer; never persist it.
201
+ delete block.partialJson;
202
+ }
172
203
  output.stopReason = options?.signal?.aborted ? "aborted" : "error";
173
204
  output.errorMessage = error instanceof Error ? error.message : String(error);
174
205
  stream.push({ type: "error", reason: output.stopReason, error: output });
@@ -183,7 +214,8 @@ export const streamSimpleOpenAICodexResponses = (model, context, options) => {
183
214
  throw new Error(`No API key for provider: ${model.provider}`);
184
215
  }
185
216
  const base = buildBaseOptions(model, options, apiKey);
186
- const reasoningEffort = supportsXhigh(model) ? options?.reasoning : clampReasoning(options?.reasoning);
217
+ const clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined;
218
+ const reasoningEffort = clampedReasoning === "off" ? undefined : clampedReasoning;
187
219
  return streamOpenAICodexResponses(model, context, {
188
220
  ...base,
189
221
  reasoningEffort,
@@ -200,9 +232,9 @@ function buildRequestBody(model, context, options) {
200
232
  model: model.id,
201
233
  store: false,
202
234
  stream: true,
203
- instructions: context.systemPrompt,
235
+ instructions: context.systemPrompt || "You are a helpful assistant.",
204
236
  input: messages,
205
- text: { verbosity: options?.textVerbosity || "medium" },
237
+ text: { verbosity: options?.textVerbosity || "low" },
206
238
  include: ["reasoning.encrypted_content"],
207
239
  prompt_cache_key: options?.sessionId,
208
240
  tool_choice: "auto",
@@ -211,26 +243,50 @@ function buildRequestBody(model, context, options) {
211
243
  if (options?.temperature !== undefined) {
212
244
  body.temperature = options.temperature;
213
245
  }
214
- if (context.tools) {
246
+ if (options?.serviceTier !== undefined) {
247
+ body.service_tier = options.serviceTier;
248
+ }
249
+ if (context.tools && context.tools.length > 0) {
215
250
  body.tools = convertResponsesTools(context.tools, { strict: null });
216
251
  }
217
252
  if (options?.reasoningEffort !== undefined) {
218
- body.reasoning = {
219
- effort: clampReasoningEffort(model.id, options.reasoningEffort),
220
- summary: options.reasoningSummary ?? "auto",
221
- };
253
+ const effort = options.reasoningEffort === "none"
254
+ ? (model.thinkingLevelMap?.off ?? "none")
255
+ : (model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort);
256
+ if (effort !== null) {
257
+ body.reasoning = {
258
+ effort,
259
+ summary: options.reasoningSummary ?? "auto",
260
+ };
261
+ }
222
262
  }
223
263
  return body;
224
264
  }
225
- function clampReasoningEffort(modelId, effort) {
226
- const id = modelId.includes("/") ? modelId.split("/").pop() : modelId;
227
- if ((id.startsWith("gpt-5.2") || id.startsWith("gpt-5.3") || id.startsWith("gpt-5.4")) && effort === "minimal")
228
- return "low";
229
- if (id === "gpt-5.1" && effort === "xhigh")
230
- return "high";
231
- if (id === "gpt-5.1-codex-mini")
232
- return effort === "high" || effort === "xhigh" ? "high" : "medium";
233
- return effort;
265
+ function getServiceTierCostMultiplier(model, serviceTier) {
266
+ switch (serviceTier) {
267
+ case "flex":
268
+ return 0.5;
269
+ case "priority":
270
+ return model.id === "gpt-5.5" ? 2.5 : 2;
271
+ default:
272
+ return 1;
273
+ }
274
+ }
275
+ function applyServiceTierPricing(usage, serviceTier, model) {
276
+ const multiplier = getServiceTierCostMultiplier(model, serviceTier);
277
+ if (multiplier === 1)
278
+ return;
279
+ usage.cost.input *= multiplier;
280
+ usage.cost.output *= multiplier;
281
+ usage.cost.cacheRead *= multiplier;
282
+ usage.cost.cacheWrite *= multiplier;
283
+ usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
284
+ }
285
+ function resolveCodexServiceTier(responseServiceTier, requestServiceTier) {
286
+ if (responseServiceTier === "default" && (requestServiceTier === "flex" || requestServiceTier === "priority")) {
287
+ return requestServiceTier;
288
+ }
289
+ return responseServiceTier ?? requestServiceTier;
234
290
  }
235
291
  function resolveCodexUrl(baseUrl) {
236
292
  const raw = baseUrl && baseUrl.trim().length > 0 ? baseUrl : DEFAULT_CODEX_BASE_URL;
@@ -252,8 +308,35 @@ function resolveCodexWebSocketUrl(baseUrl) {
252
308
  // ============================================================================
253
309
  // Response Processing
254
310
  // ============================================================================
255
- async function processStream(response, output, stream, model) {
256
- await processResponsesStream(mapCodexEvents(parseSSE(response)), output, stream, model);
311
+ async function processStream(response, output, stream, model, options) {
312
+ await processResponsesStream(mapCodexEvents(parseSSE(response)), output, stream, model, {
313
+ serviceTier: options?.serviceTier,
314
+ resolveServiceTier: resolveCodexServiceTier,
315
+ applyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),
316
+ });
317
+ }
318
+ class CodexApiError extends Error {
319
+ code;
320
+ payload;
321
+ constructor(message, options) {
322
+ super(message);
323
+ this.name = "CodexApiError";
324
+ this.code = options?.code;
325
+ this.payload = options?.payload;
326
+ this.cause = options?.cause;
327
+ }
328
+ }
329
+ class CodexProtocolError extends Error {
330
+ payload;
331
+ constructor(message, options) {
332
+ super(message);
333
+ this.name = "CodexProtocolError";
334
+ this.payload = options?.payload;
335
+ this.cause = options?.cause;
336
+ }
337
+ }
338
+ function isCodexNonTransportError(error) {
339
+ return error instanceof CodexApiError || error instanceof CodexProtocolError;
257
340
  }
258
341
  async function* mapCodexEvents(events) {
259
342
  for await (const event of events) {
@@ -263,19 +346,24 @@ async function* mapCodexEvents(events) {
263
346
  if (type === "error") {
264
347
  const code = event.code || "";
265
348
  const message = event.message || "";
266
- throw new Error(`Codex error: ${message || code || JSON.stringify(event)}`);
349
+ throw new CodexApiError(`Codex error: ${message || code || JSON.stringify(event)}`, {
350
+ code: code || undefined,
351
+ payload: event,
352
+ });
267
353
  }
268
354
  if (type === "response.failed") {
269
- const msg = event.response?.error?.message;
270
- throw new Error(msg || "Codex response failed");
355
+ const response = event.response;
356
+ const code = response?.error?.code;
357
+ const message = response?.error?.message;
358
+ throw new CodexApiError(message || "Codex response failed", { code, payload: event });
271
359
  }
272
- if (type === "response.done" || type === "response.completed") {
360
+ if (type === "response.done" || type === "response.completed" || type === "response.incomplete") {
273
361
  const response = event.response;
274
362
  const normalizedResponse = response
275
363
  ? { ...response, status: normalizeCodexStatus(response.status) }
276
364
  : response;
277
365
  yield { ...event, type: "response.completed", response: normalizedResponse };
278
- continue;
366
+ return;
279
367
  }
280
368
  yield event;
281
369
  }
@@ -294,31 +382,48 @@ async function* parseSSE(response) {
294
382
  const reader = response.body.getReader();
295
383
  const decoder = new TextDecoder();
296
384
  let buffer = "";
297
- while (true) {
298
- const { done, value } = await reader.read();
299
- if (done)
300
- break;
301
- buffer += decoder.decode(value, { stream: true });
302
- let idx = buffer.indexOf("\n\n");
303
- while (idx !== -1) {
304
- const chunk = buffer.slice(0, idx);
305
- buffer = buffer.slice(idx + 2);
306
- const dataLines = chunk
307
- .split("\n")
308
- .filter((l) => l.startsWith("data:"))
309
- .map((l) => l.slice(5).trim());
310
- if (dataLines.length > 0) {
311
- const data = dataLines.join("\n").trim();
312
- if (data && data !== "[DONE]") {
313
- try {
314
- yield JSON.parse(data);
385
+ try {
386
+ while (true) {
387
+ const { done, value } = await reader.read();
388
+ if (done)
389
+ break;
390
+ buffer += decoder.decode(value, { stream: true });
391
+ let idx = buffer.indexOf("\n\n");
392
+ while (idx !== -1) {
393
+ const chunk = buffer.slice(0, idx);
394
+ buffer = buffer.slice(idx + 2);
395
+ const dataLines = chunk
396
+ .split("\n")
397
+ .filter((l) => l.startsWith("data:"))
398
+ .map((l) => l.slice(5).trim());
399
+ if (dataLines.length > 0) {
400
+ const data = dataLines.join("\n").trim();
401
+ if (data && data !== "[DONE]") {
402
+ try {
403
+ yield JSON.parse(data);
404
+ }
405
+ catch (cause) {
406
+ throw new CodexProtocolError(`Invalid Codex SSE JSON: ${formatThrownValue(cause)}`, {
407
+ cause,
408
+ payload: data,
409
+ });
410
+ }
315
411
  }
316
- catch { }
317
412
  }
413
+ idx = buffer.indexOf("\n\n");
318
414
  }
319
- idx = buffer.indexOf("\n\n");
320
415
  }
321
416
  }
417
+ finally {
418
+ try {
419
+ await reader.cancel();
420
+ }
421
+ catch { }
422
+ try {
423
+ reader.releaseLock();
424
+ }
425
+ catch { }
426
+ }
322
427
  }
323
428
  // ============================================================================
324
429
  // WebSocket Parsing
@@ -326,18 +431,119 @@ async function* parseSSE(response) {
326
431
  const OPENAI_BETA_RESPONSES_WEBSOCKETS = "responses_websockets=2026-02-06";
327
432
  const SESSION_WEBSOCKET_CACHE_TTL_MS = 5 * 60 * 1000;
328
433
  const websocketSessionCache = new Map();
329
- function getWebSocketConstructor() {
434
+ const websocketDebugStats = new Map();
435
+ const websocketSseFallbackSessions = new Set();
436
+ function getOrCreateWebSocketDebugStats(sessionId) {
437
+ let stats = websocketDebugStats.get(sessionId);
438
+ if (!stats) {
439
+ stats = {
440
+ requests: 0,
441
+ connectionsCreated: 0,
442
+ connectionsReused: 0,
443
+ cachedContextRequests: 0,
444
+ storeTrueRequests: 0,
445
+ fullContextRequests: 0,
446
+ deltaRequests: 0,
447
+ lastInputItems: 0,
448
+ websocketFailures: 0,
449
+ sseFallbacks: 0,
450
+ };
451
+ websocketDebugStats.set(sessionId, stats);
452
+ }
453
+ return stats;
454
+ }
455
+ export function getOpenAICodexWebSocketDebugStats(sessionId) {
456
+ const stats = websocketDebugStats.get(sessionId);
457
+ return stats ? { ...stats } : undefined;
458
+ }
459
+ export function resetOpenAICodexWebSocketDebugStats(sessionId) {
460
+ if (sessionId) {
461
+ websocketDebugStats.delete(sessionId);
462
+ websocketSseFallbackSessions.delete(sessionId);
463
+ return;
464
+ }
465
+ websocketDebugStats.clear();
466
+ websocketSseFallbackSessions.clear();
467
+ }
468
+ export function closeOpenAICodexWebSocketSessions(sessionId) {
469
+ const closeEntry = (entry) => {
470
+ if (entry.idleTimer)
471
+ clearTimeout(entry.idleTimer);
472
+ closeWebSocketSilently(entry.socket, 1000, "debug_close");
473
+ };
474
+ if (sessionId) {
475
+ const entry = websocketSessionCache.get(sessionId);
476
+ if (entry)
477
+ closeEntry(entry);
478
+ websocketSessionCache.delete(sessionId);
479
+ return;
480
+ }
481
+ for (const entry of websocketSessionCache.values()) {
482
+ closeEntry(entry);
483
+ }
484
+ websocketSessionCache.clear();
485
+ }
486
+ registerSessionResourceCleanup(closeOpenAICodexWebSocketSessions);
487
+ function isWebSocketSseFallbackActive(sessionId) {
488
+ return sessionId ? websocketSseFallbackSessions.has(sessionId) : false;
489
+ }
490
+ function recordWebSocketSseFallback(sessionId) {
491
+ if (!sessionId)
492
+ return;
493
+ const stats = getOrCreateWebSocketDebugStats(sessionId);
494
+ stats.sseFallbacks++;
495
+ stats.websocketFallbackActive = isWebSocketSseFallbackActive(sessionId);
496
+ }
497
+ function recordWebSocketFailure(sessionId, error) {
498
+ if (!sessionId)
499
+ return;
500
+ websocketSseFallbackSessions.add(sessionId);
501
+ const stats = getOrCreateWebSocketDebugStats(sessionId);
502
+ stats.websocketFailures++;
503
+ stats.lastWebSocketError = formatThrownValue(error);
504
+ stats.websocketFallbackActive = true;
505
+ }
506
+ let _cachedWebsocket = null;
507
+ async function getWebSocketConstructor() {
508
+ if (_cachedWebsocket)
509
+ return _cachedWebsocket;
510
+ // bun doesn't respect http proxy envs, ref: https://github.com/oven-sh/bun/issues/15489
511
+ // TODO: remove this when bun supports proxy envs in websocket.
512
+ if (process?.versions?.bun &&
513
+ (process.env.HTTP_PROXY || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.https_proxy)) {
514
+ const m = await dynamicImport("proxy-from-env");
515
+ const getProxyForUrl = m.getProxyForUrl;
516
+ _cachedWebsocket = class extends WebSocket {
517
+ constructor(url, options) {
518
+ let _opts = {};
519
+ if (Array.isArray(options) || typeof options === "string") {
520
+ _opts = { protocols: options };
521
+ }
522
+ else {
523
+ _opts = { ...options };
524
+ }
525
+ const proxy = getProxyForUrl(url.toString().replace(/^wss:/, "https:").replace(/^ws:/, "http:"));
526
+ super(url, { ..._opts, ...(proxy ? { proxy } : {}) });
527
+ }
528
+ };
529
+ return _cachedWebsocket;
530
+ }
330
531
  const ctor = globalThis.WebSocket;
331
532
  if (typeof ctor !== "function")
332
533
  return null;
333
534
  return ctor;
334
535
  }
335
- function headersToRecord(headers) {
336
- const out = {};
337
- for (const [key, value] of headers.entries()) {
338
- out[key] = value;
536
+ class WebSocketCloseError extends Error {
537
+ code;
538
+ reason;
539
+ wasClean;
540
+ constructor(message, options) {
541
+ super(message);
542
+ this.name = "WebSocketCloseError";
543
+ this.code = options?.code;
544
+ this.reason = options?.reason;
545
+ this.wasClean = options?.wasClean;
339
546
  }
340
- return out;
341
547
  }
342
548
  function getWebSocketReadyState(socket) {
343
549
  const readyState = socket.readyState;
@@ -366,12 +572,12 @@ function scheduleSessionWebSocketExpiry(sessionId, entry) {
366
572
  }, SESSION_WEBSOCKET_CACHE_TTL_MS);
367
573
  }
368
574
  async function connectWebSocket(url, headers, signal) {
369
- const WebSocketCtor = getWebSocketConstructor();
575
+ const WebSocketCtor = await getWebSocketConstructor();
370
576
  if (!WebSocketCtor) {
371
577
  throw new Error("WebSocket transport is not available in this runtime");
372
578
  }
373
579
  const wsHeaders = headersToRecord(headers);
374
- wsHeaders["OpenAI-Beta"] = OPENAI_BETA_RESPONSES_WEBSOCKETS;
580
+ delete wsHeaders["OpenAI-Beta"];
375
581
  return new Promise((resolve, reject) => {
376
582
  let settled = false;
377
583
  let socket;
@@ -390,18 +596,20 @@ async function connectWebSocket(url, headers, signal) {
390
596
  resolve(socket);
391
597
  };
392
598
  const onError = (event) => {
599
+ const error = extractWebSocketError(event);
393
600
  if (settled)
394
601
  return;
395
602
  settled = true;
396
603
  cleanup();
397
- reject(extractWebSocketError(event));
604
+ reject(error);
398
605
  };
399
606
  const onClose = (event) => {
607
+ const error = extractWebSocketCloseError(event);
400
608
  if (settled)
401
609
  return;
402
610
  settled = true;
403
611
  cleanup();
404
- reject(extractWebSocketCloseError(event));
612
+ reject(error);
405
613
  };
406
614
  const onAbort = () => {
407
615
  if (settled)
@@ -428,6 +636,7 @@ async function acquireWebSocket(url, headers, sessionId, signal) {
428
636
  const socket = await connectWebSocket(url, headers, signal);
429
637
  return {
430
638
  socket,
639
+ reused: false,
431
640
  release: ({ keep } = {}) => {
432
641
  if (keep === false) {
433
642
  closeWebSocketSilently(socket);
@@ -447,6 +656,8 @@ async function acquireWebSocket(url, headers, sessionId, signal) {
447
656
  cached.busy = true;
448
657
  return {
449
658
  socket: cached.socket,
659
+ entry: cached,
660
+ reused: true,
450
661
  release: ({ keep } = {}) => {
451
662
  if (!keep || !isWebSocketReusable(cached.socket)) {
452
663
  closeWebSocketSilently(cached.socket);
@@ -462,6 +673,7 @@ async function acquireWebSocket(url, headers, sessionId, signal) {
462
673
  const socket = await connectWebSocket(url, headers, signal);
463
674
  return {
464
675
  socket,
676
+ reused: false,
465
677
  release: () => {
466
678
  closeWebSocketSilently(socket);
467
679
  },
@@ -477,6 +689,8 @@ async function acquireWebSocket(url, headers, sessionId, signal) {
477
689
  websocketSessionCache.set(sessionId, entry);
478
690
  return {
479
691
  socket,
692
+ entry,
693
+ reused: false,
480
694
  release: ({ keep } = {}) => {
481
695
  if (!keep || !isWebSocketReusable(entry.socket)) {
482
696
  closeWebSocketSilently(entry.socket);
@@ -493,11 +707,21 @@ async function acquireWebSocket(url, headers, sessionId, signal) {
493
707
  };
494
708
  }
495
709
  function extractWebSocketError(event) {
496
- if (event && typeof event === "object" && "message" in event) {
497
- const message = event.message;
710
+ if (event && typeof event === "object") {
711
+ const message = "message" in event ? event.message : undefined;
498
712
  if (typeof message === "string" && message.length > 0) {
499
713
  return new Error(message);
500
714
  }
715
+ const nestedError = "error" in event ? event.error : undefined;
716
+ if (nestedError instanceof Error && nestedError.message.length > 0) {
717
+ return nestedError;
718
+ }
719
+ if (nestedError && typeof nestedError === "object" && "message" in nestedError) {
720
+ const nestedMessage = nestedError.message;
721
+ if (typeof nestedMessage === "string" && nestedMessage.length > 0) {
722
+ return new Error(nestedMessage);
723
+ }
724
+ }
501
725
  }
502
726
  return new Error("WebSocket error");
503
727
  }
@@ -505,9 +729,17 @@ function extractWebSocketCloseError(event) {
505
729
  if (event && typeof event === "object") {
506
730
  const code = "code" in event ? event.code : undefined;
507
731
  const reason = "reason" in event ? event.reason : undefined;
732
+ const wasClean = "wasClean" in event ? event.wasClean : undefined;
508
733
  const codeText = typeof code === "number" ? ` ${code}` : "";
509
- const reasonText = typeof reason === "string" && reason.length > 0 ? ` ${reason}` : "";
510
- return new Error(`WebSocket closed${codeText}${reasonText}`.trim());
734
+ let reasonText = typeof reason === "string" && reason.length > 0 ? ` ${reason}` : "";
735
+ if (!reasonText && code === WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE) {
736
+ reasonText = " message too big";
737
+ }
738
+ return new WebSocketCloseError(`WebSocket closed${codeText}${reasonText}`.trim(), {
739
+ code: typeof code === "number" ? code : undefined,
740
+ reason: typeof reason === "string" && reason.length > 0 ? reason : undefined,
741
+ wasClean: typeof wasClean === "boolean" ? wasClean : undefined,
742
+ });
511
743
  }
512
744
  return new Error("WebSocket closed");
513
745
  }
@@ -543,22 +775,30 @@ async function* parseWebSocket(socket, signal) {
543
775
  };
544
776
  const onMessage = (event) => {
545
777
  void (async () => {
546
- if (!event || typeof event !== "object" || !("data" in event))
547
- return;
548
- const text = await decodeWebSocketData(event.data);
549
- if (!text)
550
- return;
778
+ let text = null;
551
779
  try {
780
+ if (!event || typeof event !== "object" || !("data" in event))
781
+ return;
782
+ text = await decodeWebSocketData(event.data);
783
+ if (!text)
784
+ return;
552
785
  const parsed = JSON.parse(text);
553
786
  const type = typeof parsed.type === "string" ? parsed.type : "";
554
- if (type === "response.completed" || type === "response.done") {
787
+ if (type === "response.completed" || type === "response.done" || type === "response.incomplete") {
555
788
  sawCompletion = true;
556
789
  done = true;
557
790
  }
558
791
  queue.push(parsed);
559
792
  wake();
560
793
  }
561
- catch { }
794
+ catch (cause) {
795
+ failed = new CodexProtocolError(`Invalid Codex WebSocket JSON: ${formatThrownValue(cause)}`, {
796
+ cause,
797
+ payload: text,
798
+ });
799
+ done = true;
800
+ wake();
801
+ }
562
802
  })();
563
803
  };
564
804
  const onError = (event) => {
@@ -616,19 +856,114 @@ async function* parseWebSocket(socket, signal) {
616
856
  signal?.removeEventListener("abort", onAbort);
617
857
  }
618
858
  }
859
+ function requestBodyWithoutInput(body) {
860
+ const { input: _input, previous_response_id: _previousResponseId, ...rest } = body;
861
+ return rest;
862
+ }
863
+ function responseInputsEqual(a, b) {
864
+ return JSON.stringify(a ?? []) === JSON.stringify(b ?? []);
865
+ }
866
+ function requestBodiesMatchExceptInput(a, b) {
867
+ return JSON.stringify(requestBodyWithoutInput(a)) === JSON.stringify(requestBodyWithoutInput(b));
868
+ }
869
+ function getCachedWebSocketInputDelta(body, continuation) {
870
+ if (!requestBodiesMatchExceptInput(body, continuation.lastRequestBody)) {
871
+ return undefined;
872
+ }
873
+ const currentInput = body.input ?? [];
874
+ const baseline = [...(continuation.lastRequestBody.input ?? []), ...continuation.lastResponseItems];
875
+ if (currentInput.length < baseline.length) {
876
+ return undefined;
877
+ }
878
+ const prefix = currentInput.slice(0, baseline.length);
879
+ if (!responseInputsEqual(prefix, baseline)) {
880
+ return undefined;
881
+ }
882
+ return currentInput.slice(baseline.length);
883
+ }
884
+ function buildCachedWebSocketRequestBody(entry, body) {
885
+ const continuation = entry.continuation;
886
+ if (!continuation) {
887
+ return body;
888
+ }
889
+ const delta = getCachedWebSocketInputDelta(body, continuation);
890
+ if (!delta || !continuation.lastResponseId) {
891
+ entry.continuation = undefined;
892
+ return body;
893
+ }
894
+ return {
895
+ ...body,
896
+ previous_response_id: continuation.lastResponseId,
897
+ input: delta,
898
+ };
899
+ }
900
+ async function* startWebSocketOutputOnFirstEvent(events, output, stream, onStart) {
901
+ let started = false;
902
+ for await (const event of events) {
903
+ if (!started) {
904
+ started = true;
905
+ onStart();
906
+ stream.push({ type: "start", partial: output });
907
+ }
908
+ yield event;
909
+ }
910
+ }
619
911
  async function processWebSocketStream(url, body, headers, output, stream, model, onStart, options) {
620
- const { socket, release } = await acquireWebSocket(url, headers, options?.sessionId, options?.signal);
912
+ const { socket, entry, reused, release } = await acquireWebSocket(url, headers, options?.sessionId, options?.signal);
621
913
  let keepConnection = true;
914
+ const useCachedContext = options?.transport === "websocket-cached" || options?.transport === "auto";
915
+ // ChatGPT Codex Responses rejects `store: true` ("Store must be set to false").
916
+ // WebSocket continuation still works via connection-scoped previous_response_id state.
917
+ const fullBody = body;
918
+ const requestBody = useCachedContext && entry ? buildCachedWebSocketRequestBody(entry, fullBody) : fullBody;
919
+ const stats = options?.sessionId ? getOrCreateWebSocketDebugStats(options.sessionId) : undefined;
920
+ if (stats) {
921
+ stats.requests++;
922
+ if (reused)
923
+ stats.connectionsReused++;
924
+ else
925
+ stats.connectionsCreated++;
926
+ if (useCachedContext)
927
+ stats.cachedContextRequests++;
928
+ if (requestBody.store === true)
929
+ stats.storeTrueRequests++;
930
+ stats.lastInputItems = requestBody.input?.length ?? 0;
931
+ if (requestBody.previous_response_id) {
932
+ stats.deltaRequests++;
933
+ stats.lastDeltaInputItems = requestBody.input?.length ?? 0;
934
+ stats.lastPreviousResponseId = requestBody.previous_response_id;
935
+ }
936
+ else {
937
+ stats.fullContextRequests++;
938
+ stats.lastDeltaInputItems = undefined;
939
+ stats.lastPreviousResponseId = undefined;
940
+ }
941
+ }
622
942
  try {
623
- socket.send(JSON.stringify({ type: "response.create", ...body }));
624
- onStart();
625
- stream.push({ type: "start", partial: output });
626
- await processResponsesStream(mapCodexEvents(parseWebSocket(socket, options?.signal)), output, stream, model);
943
+ socket.send(JSON.stringify({ type: "response.create", ...requestBody }));
944
+ await processResponsesStream(startWebSocketOutputOnFirstEvent(mapCodexEvents(parseWebSocket(socket, options?.signal)), output, stream, onStart), output, stream, model, {
945
+ serviceTier: options?.serviceTier,
946
+ resolveServiceTier: resolveCodexServiceTier,
947
+ applyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),
948
+ });
627
949
  if (options?.signal?.aborted) {
628
950
  keepConnection = false;
629
951
  }
952
+ else if (useCachedContext && entry && output.responseId) {
953
+ const responseItems = convertResponsesMessages(model, { messages: [output] }, CODEX_TOOL_CALL_PROVIDERS, {
954
+ includeSystemPrompt: false,
955
+ }).filter((item) => item.type !== "function_call_output");
956
+ entry.continuation = {
957
+ lastRequestBody: fullBody,
958
+ lastResponseId: output.responseId,
959
+ lastResponseItems: responseItems,
960
+ };
961
+ }
630
962
  }
631
963
  catch (error) {
964
+ if (entry) {
965
+ entry.continuation = undefined;
966
+ }
632
967
  keepConnection = false;
633
968
  throw error;
634
969
  }
@@ -680,22 +1015,44 @@ function extractAccountId(token) {
680
1015
  throw new Error("Failed to extract accountId from token");
681
1016
  }
682
1017
  }
683
- function buildHeaders(initHeaders, additionalHeaders, accountId, token, sessionId) {
1018
+ function createCodexRequestId() {
1019
+ if (typeof globalThis.crypto?.randomUUID === "function") {
1020
+ return globalThis.crypto.randomUUID();
1021
+ }
1022
+ return `codex_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
1023
+ }
1024
+ function buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token) {
684
1025
  const headers = new Headers(initHeaders);
1026
+ for (const [key, value] of Object.entries(additionalHeaders || {})) {
1027
+ headers.set(key, value);
1028
+ }
685
1029
  headers.set("Authorization", `Bearer ${token}`);
686
1030
  headers.set("chatgpt-account-id", accountId);
687
- headers.set("OpenAI-Beta", "responses=experimental");
688
1031
  headers.set("originator", "pi");
689
1032
  const userAgent = _os ? `pi (${_os.platform()} ${_os.release()}; ${_os.arch()})` : "pi (browser)";
690
1033
  headers.set("User-Agent", userAgent);
1034
+ return headers;
1035
+ }
1036
+ function buildSSEHeaders(initHeaders, additionalHeaders, accountId, token, sessionId) {
1037
+ const headers = buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token);
1038
+ headers.set("OpenAI-Beta", "responses=experimental");
691
1039
  headers.set("accept", "text/event-stream");
692
1040
  headers.set("content-type", "application/json");
693
- for (const [key, value] of Object.entries(additionalHeaders || {})) {
694
- headers.set(key, value);
695
- }
696
1041
  if (sessionId) {
697
1042
  headers.set("session_id", sessionId);
1043
+ headers.set("x-client-request-id", sessionId);
698
1044
  }
699
1045
  return headers;
700
1046
  }
1047
+ function buildWebSocketHeaders(initHeaders, additionalHeaders, accountId, token, requestId) {
1048
+ const headers = buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token);
1049
+ headers.delete("accept");
1050
+ headers.delete("content-type");
1051
+ headers.delete("OpenAI-Beta");
1052
+ headers.delete("openai-beta");
1053
+ headers.set("OpenAI-Beta", OPENAI_BETA_RESPONSES_WEBSOCKETS);
1054
+ headers.set("x-client-request-id", requestId);
1055
+ headers.set("session_id", requestId);
1056
+ return headers;
1057
+ }
701
1058
  //# sourceMappingURL=openai-codex-responses.js.map