@shawnstack/quickforge 1.3.18 → 1.3.19

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 (133) hide show
  1. package/README.md +10 -10
  2. package/bin/quickforge.mjs +258 -49
  3. package/dist/assets/anthropic-Bj3HAZgj.js +39 -0
  4. package/dist/assets/azure-openai-responses-IdZZrSrI.js +1 -0
  5. package/dist/assets/github-copilot-headers-CMb2BbzT.js +1 -0
  6. package/dist/assets/google-Brt_lS1J.js +1 -0
  7. package/dist/assets/{google-shared-XhYUKiGZ.js → google-shared-CLc4ziON.js} +3 -3
  8. package/dist/assets/google-vertex-B6HsoZ34.js +1 -0
  9. package/dist/assets/{index-Dm7aEWvT.js → index-D0CVLdX_.js} +525 -489
  10. package/dist/assets/index-D0W9hAl_.css +3 -0
  11. package/dist/assets/{mistral-DxhS4Wkn.js → mistral-CenXqwPz.js} +3 -3
  12. package/dist/assets/openai-codex-responses-D9ffGwbj.js +7 -0
  13. package/dist/assets/openai-completions-eWdeSGBG.js +5 -0
  14. package/dist/assets/openai-responses-Cavpmjeu.js +1 -0
  15. package/dist/assets/{openai-responses-shared-f_P3e1nz.js → openai-responses-shared-DF3ZGaUx.js} +5 -3
  16. package/dist/assets/transform-messages-CmnxG9RB.js +1 -0
  17. package/dist/index.html +2 -2
  18. package/node_modules/@anthropic-ai/sdk/CHANGELOG.md +34 -0
  19. package/node_modules/@anthropic-ai/sdk/bin/migration-config.json +185 -0
  20. package/node_modules/@anthropic-ai/sdk/package.json +1 -1
  21. package/node_modules/@anthropic-ai/sdk/resources/beta/beta.js +4 -0
  22. package/node_modules/@anthropic-ai/sdk/resources/beta/beta.mjs +4 -0
  23. package/node_modules/@anthropic-ai/sdk/resources/beta/files.js +5 -5
  24. package/node_modules/@anthropic-ai/sdk/resources/beta/files.mjs +5 -5
  25. package/node_modules/@anthropic-ai/sdk/resources/beta/index.js +11 -9
  26. package/node_modules/@anthropic-ai/sdk/resources/beta/index.mjs +1 -0
  27. package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/index.js +11 -0
  28. package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/index.mjs +5 -0
  29. package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memories.js +130 -0
  30. package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memories.mjs +126 -0
  31. package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-stores.js +145 -0
  32. package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-stores.mjs +140 -0
  33. package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-versions.js +81 -0
  34. package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-versions.mjs +77 -0
  35. package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores.js +6 -0
  36. package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores.mjs +3 -0
  37. package/node_modules/@anthropic-ai/sdk/tools/memory/node.js +12 -5
  38. package/node_modules/@anthropic-ai/sdk/tools/memory/node.mjs +12 -5
  39. package/node_modules/@anthropic-ai/sdk/version.js +1 -1
  40. package/node_modules/@anthropic-ai/sdk/version.mjs +1 -1
  41. package/node_modules/@aws-sdk/client-bedrock-runtime/package.json +5 -5
  42. package/node_modules/@aws-sdk/core/package.json +2 -2
  43. package/node_modules/@aws-sdk/credential-provider-env/package.json +2 -2
  44. package/node_modules/@aws-sdk/credential-provider-http/dist-cjs/fromHttp/fromHttp.js +12 -6
  45. package/node_modules/@aws-sdk/credential-provider-http/dist-es/fromHttp/fromHttp.js +12 -6
  46. package/node_modules/@aws-sdk/credential-provider-http/package.json +3 -2
  47. package/node_modules/@aws-sdk/credential-provider-ini/package.json +9 -9
  48. package/node_modules/@aws-sdk/credential-provider-login/package.json +3 -3
  49. package/node_modules/@aws-sdk/credential-provider-node/package.json +7 -7
  50. package/node_modules/@aws-sdk/credential-provider-process/package.json +2 -2
  51. package/node_modules/@aws-sdk/credential-provider-sso/package.json +4 -4
  52. package/node_modules/@aws-sdk/credential-provider-web-identity/package.json +3 -3
  53. package/node_modules/@aws-sdk/middleware-websocket/package.json +2 -2
  54. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/cognito-identity/index.js +1 -1
  55. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/signin/index.js +1 -1
  56. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso/index.js +1 -1
  57. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso-oidc/index.js +1 -1
  58. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sts/index.js +1 -1
  59. package/node_modules/@aws-sdk/nested-clients/package.json +3 -3
  60. package/node_modules/@aws-sdk/signature-v4-multi-region/package.json +1 -2
  61. package/node_modules/@aws-sdk/token-providers/package.json +3 -3
  62. package/node_modules/@aws-sdk/xml-builder/package.json +2 -2
  63. package/node_modules/@mariozechner/pi-agent-core/README.md +14 -0
  64. package/node_modules/@mariozechner/pi-agent-core/dist/agent-loop.js +9 -0
  65. package/node_modules/@mariozechner/pi-agent-core/dist/agent.js +1 -1
  66. package/node_modules/@mariozechner/pi-agent-core/package.json +2 -2
  67. package/node_modules/@mariozechner/pi-ai/README.md +20 -31
  68. package/node_modules/@mariozechner/pi-ai/dist/env-api-keys.js +7 -0
  69. package/node_modules/@mariozechner/pi-ai/dist/index.js +2 -0
  70. package/node_modules/@mariozechner/pi-ai/dist/models.generated.js +2420 -1213
  71. package/node_modules/@mariozechner/pi-ai/dist/models.js +28 -20
  72. package/node_modules/@mariozechner/pi-ai/dist/providers/amazon-bedrock.js +11 -11
  73. package/node_modules/@mariozechner/pi-ai/dist/providers/anthropic.js +43 -26
  74. package/node_modules/@mariozechner/pi-ai/dist/providers/azure-openai-responses.js +12 -6
  75. package/node_modules/@mariozechner/pi-ai/dist/providers/cloudflare.js +10 -3
  76. package/node_modules/@mariozechner/pi-ai/dist/providers/google-shared.js +4 -13
  77. package/node_modules/@mariozechner/pi-ai/dist/providers/google-vertex.js +4 -3
  78. package/node_modules/@mariozechner/pi-ai/dist/providers/google.js +4 -3
  79. package/node_modules/@mariozechner/pi-ai/dist/providers/mistral.js +8 -7
  80. package/node_modules/@mariozechner/pi-ai/dist/providers/openai-codex-responses.js +296 -41
  81. package/node_modules/@mariozechner/pi-ai/dist/providers/openai-completions.js +169 -153
  82. package/node_modules/@mariozechner/pi-ai/dist/providers/openai-responses-shared.js +14 -1
  83. package/node_modules/@mariozechner/pi-ai/dist/providers/openai-responses.js +22 -8
  84. package/node_modules/@mariozechner/pi-ai/dist/providers/register-builtins.js +0 -18
  85. package/node_modules/@mariozechner/pi-ai/dist/providers/simple-options.js +1 -0
  86. package/node_modules/@mariozechner/pi-ai/dist/session-resources.js +22 -0
  87. package/node_modules/@mariozechner/pi-ai/dist/utils/diagnostics.js +25 -0
  88. package/node_modules/@mariozechner/pi-ai/dist/utils/oauth/index.js +0 -10
  89. package/node_modules/@mariozechner/pi-ai/dist/utils/oauth/openai-codex.js +25 -14
  90. package/node_modules/@mariozechner/pi-ai/dist/utils/overflow.js +14 -0
  91. package/node_modules/@mariozechner/pi-ai/package.json +2 -6
  92. package/package.json +3 -3
  93. package/server/agent-manager.mjs +279 -12
  94. package/server/auto-compaction.mjs +1 -2
  95. package/server/conversation-compaction.mjs +0 -5
  96. package/server/index.mjs +1 -0
  97. package/server/routes/static.mjs +1 -0
  98. package/server/routes/tools.mjs +3 -1
  99. package/server/session-utils.mjs +6 -1
  100. package/server/share-store.mjs +27 -4
  101. package/server/subagents.mjs +101 -0
  102. package/server/system-prompt.mjs +30 -1
  103. package/server/tools/definitions.mjs +18 -0
  104. package/server/tools/index.mjs +1013 -911
  105. package/dist/assets/anthropic-Ck2DxOfr.js +0 -39
  106. package/dist/assets/azure-openai-responses-DIoz5q4Z.js +0 -1
  107. package/dist/assets/github-copilot-headers-CrI0CIJ7.js +0 -1
  108. package/dist/assets/google-Dau-4ve_.js +0 -1
  109. package/dist/assets/google-gemini-cli-DttMmbGb.js +0 -2
  110. package/dist/assets/google-vertex-BeukMl44.js +0 -1
  111. package/dist/assets/index-DgJVElbv.css +0 -3
  112. package/dist/assets/openai-codex-responses-X3sTzNAa.js +0 -7
  113. package/dist/assets/openai-completions-CRB9Vm0w.js +0 -5
  114. package/dist/assets/openai-responses-DXluu3oi.js +0 -1
  115. package/dist/assets/transform-messages-CV4kCtBB.js +0 -1
  116. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/LICENSE +0 -201
  117. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/README.md +0 -62
  118. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-cjs/index.js +0 -156
  119. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/constants.js +0 -2
  120. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/fromEnvSigningName.js +0 -16
  121. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/fromSso.js +0 -80
  122. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/fromStatic.js +0 -8
  123. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/getNewSsoOidcToken.js +0 -11
  124. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/getSsoOidcClient.js +0 -10
  125. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/index.js +0 -4
  126. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/nodeProvider.js +0 -5
  127. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/validateTokenExpiry.js +0 -7
  128. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/validateTokenKey.js +0 -7
  129. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/writeSSOTokenToFile.js +0 -8
  130. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/package.json +0 -69
  131. package/node_modules/@mariozechner/pi-ai/dist/providers/google-gemini-cli.js +0 -779
  132. package/node_modules/@mariozechner/pi-ai/dist/utils/oauth/google-antigravity.js +0 -377
  133. package/node_modules/@mariozechner/pi-ai/dist/utils/oauth/google-gemini-cli.js +0 -482
@@ -8,11 +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";
13
15
  import { headersToRecord } from "../utils/headers.js";
14
16
  import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.js";
15
- import { buildBaseOptions, clampReasoning } from "./simple-options.js";
17
+ import { buildBaseOptions } from "./simple-options.js";
16
18
  // ============================================================================
17
19
  // Configuration
18
20
  // ============================================================================
@@ -21,6 +23,7 @@ const JWT_CLAIM_PATH = "https://api.openai.com/auth";
21
23
  const MAX_RETRIES = 3;
22
24
  const BASE_DELAY_MS = 1000;
23
25
  const CODEX_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]);
26
+ const WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE = 1009;
24
27
  const CODEX_RESPONSE_STATUSES = new Set([
25
28
  "completed",
26
29
  "incomplete",
@@ -89,8 +92,12 @@ export const streamOpenAICodexResponses = (model, context, options) => {
89
92
  const sseHeaders = buildSSEHeaders(model.headers, options?.headers, accountId, apiKey, options?.sessionId);
90
93
  const websocketHeaders = buildWebSocketHeaders(model.headers, options?.headers, accountId, apiKey, websocketRequestId);
91
94
  const bodyJson = JSON.stringify(body);
92
- const transport = options?.transport || "sse";
93
- 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) {
94
101
  let websocketStarted = false;
95
102
  try {
96
103
  await processWebSocketStream(resolveCodexWebSocketUrl(model.baseUrl), body, websocketHeaders, output, stream, model, () => {
@@ -108,9 +115,22 @@ export const streamOpenAICodexResponses = (model, context, options) => {
108
115
  return;
109
116
  }
110
117
  catch (error) {
111
- 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) {
112
131
  throw error;
113
132
  }
133
+ recordWebSocketSseFallback(options?.sessionId);
114
134
  }
115
135
  }
116
136
  // Fetch with retry logic for rate limits and transient errors
@@ -194,7 +214,8 @@ export const streamSimpleOpenAICodexResponses = (model, context, options) => {
194
214
  throw new Error(`No API key for provider: ${model.provider}`);
195
215
  }
196
216
  const base = buildBaseOptions(model, options, apiKey);
197
- 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;
198
219
  return streamOpenAICodexResponses(model, context, {
199
220
  ...base,
200
221
  reasoningEffort,
@@ -211,7 +232,7 @@ function buildRequestBody(model, context, options) {
211
232
  model: model.id,
212
233
  store: false,
213
234
  stream: true,
214
- instructions: context.systemPrompt,
235
+ instructions: context.systemPrompt || "You are a helpful assistant.",
215
236
  input: messages,
216
237
  text: { verbosity: options?.textVerbosity || "low" },
217
238
  include: ["reasoning.encrypted_content"],
@@ -229,24 +250,18 @@ function buildRequestBody(model, context, options) {
229
250
  body.tools = convertResponsesTools(context.tools, { strict: null });
230
251
  }
231
252
  if (options?.reasoningEffort !== undefined) {
232
- body.reasoning = {
233
- effort: clampReasoningEffort(model.id, options.reasoningEffort),
234
- summary: options.reasoningSummary ?? "auto",
235
- };
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
+ }
236
262
  }
237
263
  return body;
238
264
  }
239
- function clampReasoningEffort(modelId, effort) {
240
- const id = modelId.includes("/") ? modelId.split("/").pop() : modelId;
241
- if ((id.startsWith("gpt-5.2") || id.startsWith("gpt-5.3") || id.startsWith("gpt-5.4") || id.startsWith("gpt-5.5")) &&
242
- effort === "minimal")
243
- return "low";
244
- if (id === "gpt-5.1" && effort === "xhigh")
245
- return "high";
246
- if (id === "gpt-5.1-codex-mini")
247
- return effort === "high" || effort === "xhigh" ? "high" : "medium";
248
- return effort;
249
- }
250
265
  function getServiceTierCostMultiplier(model, serviceTier) {
251
266
  switch (serviceTier) {
252
267
  case "flex":
@@ -300,6 +315,29 @@ async function processStream(response, output, stream, model, options) {
300
315
  applyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),
301
316
  });
302
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;
340
+ }
303
341
  async function* mapCodexEvents(events) {
304
342
  for await (const event of events) {
305
343
  const type = typeof event.type === "string" ? event.type : undefined;
@@ -308,11 +346,16 @@ async function* mapCodexEvents(events) {
308
346
  if (type === "error") {
309
347
  const code = event.code || "";
310
348
  const message = event.message || "";
311
- 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
+ });
312
353
  }
313
354
  if (type === "response.failed") {
314
- const msg = event.response?.error?.message;
315
- 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 });
316
359
  }
317
360
  if (type === "response.done" || type === "response.completed" || type === "response.incomplete") {
318
361
  const response = event.response;
@@ -359,7 +402,12 @@ async function* parseSSE(response) {
359
402
  try {
360
403
  yield JSON.parse(data);
361
404
  }
362
- catch { }
405
+ catch (cause) {
406
+ throw new CodexProtocolError(`Invalid Codex SSE JSON: ${formatThrownValue(cause)}`, {
407
+ cause,
408
+ payload: data,
409
+ });
410
+ }
363
411
  }
364
412
  }
365
413
  idx = buffer.indexOf("\n\n");
@@ -383,12 +431,96 @@ async function* parseSSE(response) {
383
431
  const OPENAI_BETA_RESPONSES_WEBSOCKETS = "responses_websockets=2026-02-06";
384
432
  const SESSION_WEBSOCKET_CACHE_TTL_MS = 5 * 60 * 1000;
385
433
  const websocketSessionCache = new Map();
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
+ }
386
506
  function getWebSocketConstructor() {
387
507
  const ctor = globalThis.WebSocket;
388
508
  if (typeof ctor !== "function")
389
509
  return null;
390
510
  return ctor;
391
511
  }
512
+ class WebSocketCloseError extends Error {
513
+ code;
514
+ reason;
515
+ wasClean;
516
+ constructor(message, options) {
517
+ super(message);
518
+ this.name = "WebSocketCloseError";
519
+ this.code = options?.code;
520
+ this.reason = options?.reason;
521
+ this.wasClean = options?.wasClean;
522
+ }
523
+ }
392
524
  function getWebSocketReadyState(socket) {
393
525
  const readyState = socket.readyState;
394
526
  return typeof readyState === "number" ? readyState : undefined;
@@ -480,6 +612,7 @@ async function acquireWebSocket(url, headers, sessionId, signal) {
480
612
  const socket = await connectWebSocket(url, headers, signal);
481
613
  return {
482
614
  socket,
615
+ reused: false,
483
616
  release: ({ keep } = {}) => {
484
617
  if (keep === false) {
485
618
  closeWebSocketSilently(socket);
@@ -499,6 +632,8 @@ async function acquireWebSocket(url, headers, sessionId, signal) {
499
632
  cached.busy = true;
500
633
  return {
501
634
  socket: cached.socket,
635
+ entry: cached,
636
+ reused: true,
502
637
  release: ({ keep } = {}) => {
503
638
  if (!keep || !isWebSocketReusable(cached.socket)) {
504
639
  closeWebSocketSilently(cached.socket);
@@ -514,6 +649,7 @@ async function acquireWebSocket(url, headers, sessionId, signal) {
514
649
  const socket = await connectWebSocket(url, headers, signal);
515
650
  return {
516
651
  socket,
652
+ reused: false,
517
653
  release: () => {
518
654
  closeWebSocketSilently(socket);
519
655
  },
@@ -529,6 +665,8 @@ async function acquireWebSocket(url, headers, sessionId, signal) {
529
665
  websocketSessionCache.set(sessionId, entry);
530
666
  return {
531
667
  socket,
668
+ entry,
669
+ reused: false,
532
670
  release: ({ keep } = {}) => {
533
671
  if (!keep || !isWebSocketReusable(entry.socket)) {
534
672
  closeWebSocketSilently(entry.socket);
@@ -545,11 +683,21 @@ async function acquireWebSocket(url, headers, sessionId, signal) {
545
683
  };
546
684
  }
547
685
  function extractWebSocketError(event) {
548
- if (event && typeof event === "object" && "message" in event) {
549
- const message = event.message;
686
+ if (event && typeof event === "object") {
687
+ const message = "message" in event ? event.message : undefined;
550
688
  if (typeof message === "string" && message.length > 0) {
551
689
  return new Error(message);
552
690
  }
691
+ const nestedError = "error" in event ? event.error : undefined;
692
+ if (nestedError instanceof Error && nestedError.message.length > 0) {
693
+ return nestedError;
694
+ }
695
+ if (nestedError && typeof nestedError === "object" && "message" in nestedError) {
696
+ const nestedMessage = nestedError.message;
697
+ if (typeof nestedMessage === "string" && nestedMessage.length > 0) {
698
+ return new Error(nestedMessage);
699
+ }
700
+ }
553
701
  }
554
702
  return new Error("WebSocket error");
555
703
  }
@@ -557,9 +705,17 @@ function extractWebSocketCloseError(event) {
557
705
  if (event && typeof event === "object") {
558
706
  const code = "code" in event ? event.code : undefined;
559
707
  const reason = "reason" in event ? event.reason : undefined;
708
+ const wasClean = "wasClean" in event ? event.wasClean : undefined;
560
709
  const codeText = typeof code === "number" ? ` ${code}` : "";
561
- const reasonText = typeof reason === "string" && reason.length > 0 ? ` ${reason}` : "";
562
- return new Error(`WebSocket closed${codeText}${reasonText}`.trim());
710
+ let reasonText = typeof reason === "string" && reason.length > 0 ? ` ${reason}` : "";
711
+ if (!reasonText && code === WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE) {
712
+ reasonText = " message too big";
713
+ }
714
+ return new WebSocketCloseError(`WebSocket closed${codeText}${reasonText}`.trim(), {
715
+ code: typeof code === "number" ? code : undefined,
716
+ reason: typeof reason === "string" && reason.length > 0 ? reason : undefined,
717
+ wasClean: typeof wasClean === "boolean" ? wasClean : undefined,
718
+ });
563
719
  }
564
720
  return new Error("WebSocket closed");
565
721
  }
@@ -595,12 +751,13 @@ async function* parseWebSocket(socket, signal) {
595
751
  };
596
752
  const onMessage = (event) => {
597
753
  void (async () => {
598
- if (!event || typeof event !== "object" || !("data" in event))
599
- return;
600
- const text = await decodeWebSocketData(event.data);
601
- if (!text)
602
- return;
754
+ let text = null;
603
755
  try {
756
+ if (!event || typeof event !== "object" || !("data" in event))
757
+ return;
758
+ text = await decodeWebSocketData(event.data);
759
+ if (!text)
760
+ return;
604
761
  const parsed = JSON.parse(text);
605
762
  const type = typeof parsed.type === "string" ? parsed.type : "";
606
763
  if (type === "response.completed" || type === "response.done" || type === "response.incomplete") {
@@ -610,7 +767,14 @@ async function* parseWebSocket(socket, signal) {
610
767
  queue.push(parsed);
611
768
  wake();
612
769
  }
613
- catch { }
770
+ catch (cause) {
771
+ failed = new CodexProtocolError(`Invalid Codex WebSocket JSON: ${formatThrownValue(cause)}`, {
772
+ cause,
773
+ payload: text,
774
+ });
775
+ done = true;
776
+ wake();
777
+ }
614
778
  })();
615
779
  };
616
780
  const onError = (event) => {
@@ -668,14 +832,92 @@ async function* parseWebSocket(socket, signal) {
668
832
  signal?.removeEventListener("abort", onAbort);
669
833
  }
670
834
  }
835
+ function requestBodyWithoutInput(body) {
836
+ const { input: _input, previous_response_id: _previousResponseId, ...rest } = body;
837
+ return rest;
838
+ }
839
+ function responseInputsEqual(a, b) {
840
+ return JSON.stringify(a ?? []) === JSON.stringify(b ?? []);
841
+ }
842
+ function requestBodiesMatchExceptInput(a, b) {
843
+ return JSON.stringify(requestBodyWithoutInput(a)) === JSON.stringify(requestBodyWithoutInput(b));
844
+ }
845
+ function getCachedWebSocketInputDelta(body, continuation) {
846
+ if (!requestBodiesMatchExceptInput(body, continuation.lastRequestBody)) {
847
+ return undefined;
848
+ }
849
+ const currentInput = body.input ?? [];
850
+ const baseline = [...(continuation.lastRequestBody.input ?? []), ...continuation.lastResponseItems];
851
+ if (currentInput.length < baseline.length) {
852
+ return undefined;
853
+ }
854
+ const prefix = currentInput.slice(0, baseline.length);
855
+ if (!responseInputsEqual(prefix, baseline)) {
856
+ return undefined;
857
+ }
858
+ return currentInput.slice(baseline.length);
859
+ }
860
+ function buildCachedWebSocketRequestBody(entry, body) {
861
+ const continuation = entry.continuation;
862
+ if (!continuation) {
863
+ return body;
864
+ }
865
+ const delta = getCachedWebSocketInputDelta(body, continuation);
866
+ if (!delta || !continuation.lastResponseId) {
867
+ entry.continuation = undefined;
868
+ return body;
869
+ }
870
+ return {
871
+ ...body,
872
+ previous_response_id: continuation.lastResponseId,
873
+ input: delta,
874
+ };
875
+ }
876
+ async function* startWebSocketOutputOnFirstEvent(events, output, stream, onStart) {
877
+ let started = false;
878
+ for await (const event of events) {
879
+ if (!started) {
880
+ started = true;
881
+ onStart();
882
+ stream.push({ type: "start", partial: output });
883
+ }
884
+ yield event;
885
+ }
886
+ }
671
887
  async function processWebSocketStream(url, body, headers, output, stream, model, onStart, options) {
672
- const { socket, release } = await acquireWebSocket(url, headers, options?.sessionId, options?.signal);
888
+ const { socket, entry, reused, release } = await acquireWebSocket(url, headers, options?.sessionId, options?.signal);
673
889
  let keepConnection = true;
890
+ const useCachedContext = options?.transport === "websocket-cached" || options?.transport === "auto";
891
+ // ChatGPT Codex Responses rejects `store: true` ("Store must be set to false").
892
+ // WebSocket continuation still works via connection-scoped previous_response_id state.
893
+ const fullBody = body;
894
+ const requestBody = useCachedContext && entry ? buildCachedWebSocketRequestBody(entry, fullBody) : fullBody;
895
+ const stats = options?.sessionId ? getOrCreateWebSocketDebugStats(options.sessionId) : undefined;
896
+ if (stats) {
897
+ stats.requests++;
898
+ if (reused)
899
+ stats.connectionsReused++;
900
+ else
901
+ stats.connectionsCreated++;
902
+ if (useCachedContext)
903
+ stats.cachedContextRequests++;
904
+ if (requestBody.store === true)
905
+ stats.storeTrueRequests++;
906
+ stats.lastInputItems = requestBody.input?.length ?? 0;
907
+ if (requestBody.previous_response_id) {
908
+ stats.deltaRequests++;
909
+ stats.lastDeltaInputItems = requestBody.input?.length ?? 0;
910
+ stats.lastPreviousResponseId = requestBody.previous_response_id;
911
+ }
912
+ else {
913
+ stats.fullContextRequests++;
914
+ stats.lastDeltaInputItems = undefined;
915
+ stats.lastPreviousResponseId = undefined;
916
+ }
917
+ }
674
918
  try {
675
- socket.send(JSON.stringify({ type: "response.create", ...body }));
676
- onStart();
677
- stream.push({ type: "start", partial: output });
678
- await processResponsesStream(mapCodexEvents(parseWebSocket(socket, options?.signal)), output, stream, model, {
919
+ socket.send(JSON.stringify({ type: "response.create", ...requestBody }));
920
+ await processResponsesStream(startWebSocketOutputOnFirstEvent(mapCodexEvents(parseWebSocket(socket, options?.signal)), output, stream, onStart), output, stream, model, {
679
921
  serviceTier: options?.serviceTier,
680
922
  resolveServiceTier: resolveCodexServiceTier,
681
923
  applyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),
@@ -683,8 +925,21 @@ async function processWebSocketStream(url, body, headers, output, stream, model,
683
925
  if (options?.signal?.aborted) {
684
926
  keepConnection = false;
685
927
  }
928
+ else if (useCachedContext && entry && output.responseId) {
929
+ const responseItems = convertResponsesMessages(model, { messages: [output] }, CODEX_TOOL_CALL_PROVIDERS, {
930
+ includeSystemPrompt: false,
931
+ }).filter((item) => item.type !== "function_call_output");
932
+ entry.continuation = {
933
+ lastRequestBody: fullBody,
934
+ lastResponseId: output.responseId,
935
+ lastResponseItems: responseItems,
936
+ };
937
+ }
686
938
  }
687
939
  catch (error) {
940
+ if (entry) {
941
+ entry.continuation = undefined;
942
+ }
688
943
  keepConnection = false;
689
944
  throw error;
690
945
  }