@code-yeongyu/senpi 2026.6.16 → 2026.6.17

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 (147) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/bun/restore-sandbox-env.d.ts +4 -0
  3. package/dist/bun/restore-sandbox-env.d.ts.map +1 -1
  4. package/dist/bun/restore-sandbox-env.js +4 -0
  5. package/dist/bun/restore-sandbox-env.js.map +1 -1
  6. package/dist/core/agent-session.d.ts.map +1 -1
  7. package/dist/core/agent-session.js +8 -5
  8. package/dist/core/agent-session.js.map +1 -1
  9. package/dist/core/auth-storage.d.ts +5 -0
  10. package/dist/core/auth-storage.d.ts.map +1 -1
  11. package/dist/core/auth-storage.js +8 -1
  12. package/dist/core/auth-storage.js.map +1 -1
  13. package/dist/core/compaction/branch-summarization.d.ts +2 -0
  14. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  15. package/dist/core/compaction/branch-summarization.js +2 -2
  16. package/dist/core/compaction/branch-summarization.js.map +1 -1
  17. package/dist/core/compaction/compaction.d.ts +2 -4
  18. package/dist/core/compaction/compaction.d.ts.map +1 -1
  19. package/dist/core/compaction/compaction.js +11 -10
  20. package/dist/core/compaction/compaction.js.map +1 -1
  21. package/dist/core/export-html/vendor/marked.min.js +75 -3
  22. package/dist/core/http-dispatcher.d.ts +1 -0
  23. package/dist/core/http-dispatcher.d.ts.map +1 -1
  24. package/dist/core/http-dispatcher.js +17 -1
  25. package/dist/core/http-dispatcher.js.map +1 -1
  26. package/dist/core/model-registry.d.ts +1 -0
  27. package/dist/core/model-registry.d.ts.map +1 -1
  28. package/dist/core/model-registry.js +10 -4
  29. package/dist/core/model-registry.js.map +1 -1
  30. package/dist/core/provider-attribution.d.ts.map +1 -1
  31. package/dist/core/provider-attribution.js +10 -0
  32. package/dist/core/provider-attribution.js.map +1 -1
  33. package/dist/core/resolve-config-value.d.ts +10 -6
  34. package/dist/core/resolve-config-value.d.ts.map +1 -1
  35. package/dist/core/resolve-config-value.js +32 -17
  36. package/dist/core/resolve-config-value.js.map +1 -1
  37. package/dist/core/sdk.d.ts.map +1 -1
  38. package/dist/core/sdk.js +2 -0
  39. package/dist/core/sdk.js.map +1 -1
  40. package/dist/core/settings-manager.d.ts +1 -0
  41. package/dist/core/settings-manager.d.ts.map +1 -1
  42. package/dist/core/settings-manager.js.map +1 -1
  43. package/dist/main.d.ts.map +1 -1
  44. package/dist/main.js +17 -5
  45. package/dist/main.js.map +1 -1
  46. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  47. package/dist/modes/interactive/components/login-dialog.js +0 -1
  48. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  49. package/docs/providers.md +20 -2
  50. package/docs/settings.md +12 -0
  51. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  52. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  53. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  54. package/examples/extensions/gondolin/package-lock.json +2 -2
  55. package/examples/extensions/gondolin/package.json +1 -1
  56. package/examples/extensions/sandbox/package-lock.json +2 -2
  57. package/examples/extensions/sandbox/package.json +1 -1
  58. package/examples/extensions/with-deps/package-lock.json +2 -2
  59. package/examples/extensions/with-deps/package.json +1 -1
  60. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  61. package/node_modules/@earendil-works/pi-ai/README.md +19 -0
  62. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts +5 -5
  63. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts.map +1 -1
  64. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +21 -57
  65. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js.map +1 -1
  66. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +149 -3
  67. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  68. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +135 -25
  69. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js.map +1 -1
  70. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.d.ts.map +1 -1
  71. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +52 -33
  72. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  73. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  74. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +28 -12
  75. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js.map +1 -1
  76. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  77. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js +7 -4
  78. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  79. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare.d.ts +3 -3
  80. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare.d.ts.map +1 -1
  81. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare.js +4 -3
  82. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare.js.map +1 -1
  83. package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.d.ts.map +1 -1
  84. package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.js +13 -4
  85. package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.js.map +1 -1
  86. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  87. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js +107 -57
  88. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  89. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  90. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js +15 -7
  91. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js.map +1 -1
  92. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.d.ts.map +1 -1
  93. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.js +2 -1
  94. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.js.map +1 -1
  95. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  96. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js +8 -7
  97. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js.map +1 -1
  98. package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  99. package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.js +1 -0
  100. package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.js.map +1 -1
  101. package/node_modules/@earendil-works/pi-ai/dist/stream.js +1 -1
  102. package/node_modules/@earendil-works/pi-ai/dist/stream.js.map +1 -1
  103. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +8 -0
  104. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
  105. package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
  106. package/node_modules/@earendil-works/pi-ai/dist/utils/node-http-proxy.d.ts +2 -8
  107. package/node_modules/@earendil-works/pi-ai/dist/utils/node-http-proxy.d.ts.map +1 -1
  108. package/node_modules/@earendil-works/pi-ai/dist/utils/node-http-proxy.js +17 -22
  109. package/node_modules/@earendil-works/pi-ai/dist/utils/node-http-proxy.js.map +1 -1
  110. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/anthropic.d.ts.map +1 -1
  111. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/anthropic.js +2 -1
  112. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
  113. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.d.ts.map +1 -1
  114. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.js +2 -1
  115. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.js.map +1 -1
  116. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-env.d.ts +7 -0
  117. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-env.d.ts.map +1 -0
  118. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-env.js +44 -0
  119. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-env.js.map +1 -0
  120. package/node_modules/@earendil-works/pi-ai/package.json +1 -1
  121. package/node_modules/@earendil-works/pi-tui/dist/components/box.d.ts +3 -0
  122. package/node_modules/@earendil-works/pi-tui/dist/components/box.d.ts.map +1 -1
  123. package/node_modules/@earendil-works/pi-tui/dist/components/box.js +18 -0
  124. package/node_modules/@earendil-works/pi-tui/dist/components/box.js.map +1 -1
  125. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts +1 -0
  126. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
  127. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +5 -1
  128. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
  129. package/node_modules/@earendil-works/pi-tui/dist/components/loader.d.ts +1 -0
  130. package/node_modules/@earendil-works/pi-tui/dist/components/loader.d.ts.map +1 -1
  131. package/node_modules/@earendil-works/pi-tui/dist/components/loader.js +3 -0
  132. package/node_modules/@earendil-works/pi-tui/dist/components/loader.js.map +1 -1
  133. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.d.ts +5 -0
  134. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.d.ts.map +1 -1
  135. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js +57 -15
  136. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js.map +1 -1
  137. package/node_modules/@earendil-works/pi-tui/dist/index.d.ts +1 -1
  138. package/node_modules/@earendil-works/pi-tui/dist/index.d.ts.map +1 -1
  139. package/node_modules/@earendil-works/pi-tui/dist/index.js +1 -1
  140. package/node_modules/@earendil-works/pi-tui/dist/index.js.map +1 -1
  141. package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts +5 -0
  142. package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts.map +1 -1
  143. package/node_modules/@earendil-works/pi-tui/dist/tui.js +22 -0
  144. package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +1 -1
  145. package/node_modules/@earendil-works/pi-tui/package.json +2 -2
  146. package/npm-shrinkwrap.json +11 -11
  147. package/package.json +5 -5
@@ -21,6 +21,7 @@ import { combineAbortSignals } from "../utils/abort-signals.js";
21
21
  import { appendAssistantMessageDiagnostic, createAssistantMessageDiagnostic, formatThrownValue, } from "../utils/diagnostics.js";
22
22
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
23
23
  import { headersToRecord } from "../utils/headers.js";
24
+ import { resolveHttpProxyUrlForTarget } from "../utils/node-http-proxy.js";
24
25
  import { clampOpenAIPromptCacheKey } from "./openai-prompt-cache.js";
25
26
  import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.js";
26
27
  import { applyExtraBody, buildBaseOptions, clampMaxForOpenAI, OPENAI_RESPONSES_RESERVED_BODY_KEYS, } from "./simple-options.js";
@@ -38,6 +39,7 @@ const DEFAULT_SSE_HEADER_TIMEOUT_MS = 20_000;
38
39
  const DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS = 15_000;
39
40
  const CODEX_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]);
40
41
  const WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE = 1009;
42
+ const CODEX_PREVIOUS_RESPONSE_STALE_CODES = new Set(["previous_response_not_found", "codex_previous_response_stale"]);
41
43
  const CODEX_RESPONSE_STATUSES = new Set([
42
44
  "completed",
43
45
  "incomplete",
@@ -426,23 +428,46 @@ class CodexProtocolError extends Error {
426
428
  function isCodexNonTransportError(error) {
427
429
  return error instanceof CodexApiError || error instanceof CodexProtocolError;
428
430
  }
431
+ function asRecord(value) {
432
+ return value && typeof value === "object" ? value : null;
433
+ }
434
+ function getString(value) {
435
+ return typeof value === "string" ? value : undefined;
436
+ }
437
+ function getCodexEventError(event) {
438
+ const response = asRecord(event.response);
439
+ return asRecord(event.error) ?? (response ? asRecord(response.error) : null);
440
+ }
441
+ function getCodexEventErrorCode(event) {
442
+ const error = getCodexEventError(event);
443
+ return getString(error?.code) ?? getString(error?.type) ?? getString(event.code) ?? "";
444
+ }
445
+ function getCodexEventErrorMessage(event) {
446
+ const response = asRecord(event.response);
447
+ const error = getCodexEventError(event);
448
+ return getString(error?.message) ?? getString(event.message) ?? getString(response?.message) ?? "";
449
+ }
450
+ function isCodexPreviousResponseStale(error) {
451
+ return (error instanceof CodexApiError &&
452
+ typeof error.code === "string" &&
453
+ CODEX_PREVIOUS_RESPONSE_STALE_CODES.has(error.code));
454
+ }
429
455
  async function* mapCodexEvents(events) {
430
456
  for await (const event of events) {
431
457
  const type = typeof event.type === "string" ? event.type : undefined;
432
458
  if (!type)
433
459
  continue;
434
460
  if (type === "error") {
435
- const code = event.code || "";
436
- const message = event.message || "";
461
+ const code = getCodexEventErrorCode(event);
462
+ const message = getCodexEventErrorMessage(event);
437
463
  throw new CodexApiError(`Codex error: ${message || code || JSON.stringify(event)}`, {
438
464
  code: code || undefined,
439
465
  payload: event,
440
466
  });
441
467
  }
442
468
  if (type === "response.failed") {
443
- const response = event.response;
444
- const code = response?.error?.code;
445
- const message = response?.error?.message;
469
+ const code = getCodexEventErrorCode(event);
470
+ const message = getCodexEventErrorMessage(event);
446
471
  throw new CodexApiError(message || "Codex response failed", { code, payload: event });
447
472
  }
448
473
  if (type === "response.done" || type === "response.completed" || type === "response.incomplete") {
@@ -604,16 +629,13 @@ function recordWebSocketFailure(sessionId, error) {
604
629
  stats.websocketFallbackActive = true;
605
630
  }
606
631
  let _cachedWebsocket = null;
607
- async function getWebSocketConstructor() {
608
- if (_cachedWebsocket)
632
+ async function getWebSocketConstructor(env) {
633
+ if (!env && _cachedWebsocket)
609
634
  return _cachedWebsocket;
610
635
  // bun doesn't respect http proxy envs, ref: https://github.com/oven-sh/bun/issues/15489
611
636
  // TODO: remove this when bun supports proxy envs in websocket.
612
- if (process?.versions?.bun &&
613
- (process.env.HTTP_PROXY || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.https_proxy)) {
614
- const m = await dynamicImport("proxy-from-env");
615
- const getProxyForUrl = m.getProxyForUrl;
616
- _cachedWebsocket = class {
637
+ if (typeof process !== "undefined" && process.versions?.bun) {
638
+ const WebSocketWithProxy = class {
617
639
  constructor(url, options) {
618
640
  let _opts = {};
619
641
  if (Array.isArray(options) || typeof options === "string") {
@@ -622,10 +644,10 @@ async function getWebSocketConstructor() {
622
644
  else {
623
645
  _opts = { ...options };
624
646
  }
625
- const proxy = getProxyForUrl(url.toString().replace(/^wss:/, "https:").replace(/^ws:/, "http:"));
647
+ const proxyUrl = resolveHttpProxyUrlForTarget(url.toString().replace(/^wss:/, "https:").replace(/^ws:/, "http:"), env);
626
648
  this.socket = Reflect.construct(WebSocket, [
627
649
  url,
628
- { ..._opts, ...(proxy ? { proxy } : {}) },
650
+ { ..._opts, ...(proxyUrl ? { proxy: proxyUrl.toString() } : {}) },
629
651
  ]);
630
652
  }
631
653
  close(code, reason) {
@@ -641,7 +663,10 @@ async function getWebSocketConstructor() {
641
663
  this.socket.removeEventListener(type, listener);
642
664
  }
643
665
  };
644
- return _cachedWebsocket;
666
+ if (!env) {
667
+ _cachedWebsocket = WebSocketWithProxy;
668
+ }
669
+ return WebSocketWithProxy;
645
670
  }
646
671
  const ctor = globalThis.WebSocket;
647
672
  if (!isWebSocketConstructor(ctor))
@@ -686,8 +711,8 @@ function scheduleSessionWebSocketExpiry(sessionId, entry) {
686
711
  websocketSessionCache.delete(sessionId);
687
712
  }, SESSION_WEBSOCKET_CACHE_TTL_MS);
688
713
  }
689
- async function connectWebSocket(url, headers, signal, connectTimeoutMs = DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS) {
690
- const WebSocketCtor = await getWebSocketConstructor();
714
+ async function connectWebSocket(url, headers, signal, connectTimeoutMs = DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS, env) {
715
+ const WebSocketCtor = await getWebSocketConstructor(env);
691
716
  if (!WebSocketCtor) {
692
717
  throw new Error("WebSocket transport is not available in this runtime");
693
718
  }
@@ -754,9 +779,9 @@ async function connectWebSocket(url, headers, signal, connectTimeoutMs = DEFAULT
754
779
  }
755
780
  });
756
781
  }
757
- async function acquireWebSocket(url, headers, sessionId, signal, connectTimeoutMs) {
782
+ async function acquireWebSocket(url, headers, sessionId, signal, connectTimeoutMs, env) {
758
783
  if (!sessionId) {
759
- const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs);
784
+ const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);
760
785
  return {
761
786
  socket,
762
787
  reused: false,
@@ -787,7 +812,7 @@ async function acquireWebSocket(url, headers, sessionId, signal, connectTimeoutM
787
812
  };
788
813
  }
789
814
  if (cached.busy) {
790
- const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs);
815
+ const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);
791
816
  return {
792
817
  socket,
793
818
  reused: false,
@@ -801,7 +826,7 @@ async function acquireWebSocket(url, headers, sessionId, signal, connectTimeoutM
801
826
  websocketSessionCache.delete(sessionId);
802
827
  }
803
828
  }
804
- const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs);
829
+ const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);
805
830
  const entry = { socket, busy: true };
806
831
  websocketSessionCache.set(sessionId, entry);
807
832
  return {
@@ -1029,6 +1054,30 @@ function buildCachedWebSocketRequestBody(entry, body) {
1029
1054
  input: delta,
1030
1055
  };
1031
1056
  }
1057
+ function recordWebSocketRequestStats(stats, requestBody, reused, useCachedContext) {
1058
+ if (!stats)
1059
+ return;
1060
+ stats.requests++;
1061
+ if (reused)
1062
+ stats.connectionsReused++;
1063
+ else
1064
+ stats.connectionsCreated++;
1065
+ if (useCachedContext)
1066
+ stats.cachedContextRequests++;
1067
+ if (requestBody.store === true)
1068
+ stats.storeTrueRequests++;
1069
+ stats.lastInputItems = requestBody.input?.length ?? 0;
1070
+ if (requestBody.previous_response_id) {
1071
+ stats.deltaRequests++;
1072
+ stats.lastDeltaInputItems = requestBody.input?.length ?? 0;
1073
+ stats.lastPreviousResponseId = requestBody.previous_response_id;
1074
+ }
1075
+ else {
1076
+ stats.fullContextRequests++;
1077
+ stats.lastDeltaInputItems = undefined;
1078
+ stats.lastPreviousResponseId = undefined;
1079
+ }
1080
+ }
1032
1081
  async function* startWebSocketOutputOnFirstEvent(events, output, stream, onStart) {
1033
1082
  let started = false;
1034
1083
  for await (const event of events) {
@@ -1041,43 +1090,41 @@ async function* startWebSocketOutputOnFirstEvent(events, output, stream, onStart
1041
1090
  }
1042
1091
  }
1043
1092
  async function processWebSocketStream(url, body, headers, output, stream, model, onStart, idleTimeoutMs, websocketConnectTimeoutMs, options) {
1044
- const { socket, entry, reused, release } = await acquireWebSocket(url, headers, options?.sessionId, options?.signal, websocketConnectTimeoutMs);
1093
+ const { socket, entry, reused, release } = await acquireWebSocket(url, headers, options?.sessionId, options?.signal, websocketConnectTimeoutMs, options?.env);
1045
1094
  let keepConnection = true;
1046
1095
  const useCachedContext = options?.transport === "websocket-cached" || options?.transport === "auto";
1047
1096
  // ChatGPT Codex Responses rejects `store: true` ("Store must be set to false").
1048
1097
  // WebSocket continuation still works via connection-scoped previous_response_id state.
1049
1098
  const fullBody = body;
1050
- const requestBody = useCachedContext && entry ? buildCachedWebSocketRequestBody(entry, fullBody) : fullBody;
1099
+ let requestBody = useCachedContext && entry ? buildCachedWebSocketRequestBody(entry, fullBody) : fullBody;
1051
1100
  const stats = options?.sessionId ? getOrCreateWebSocketDebugStats(options.sessionId) : undefined;
1052
- if (stats) {
1053
- stats.requests++;
1054
- if (reused)
1055
- stats.connectionsReused++;
1056
- else
1057
- stats.connectionsCreated++;
1058
- if (useCachedContext)
1059
- stats.cachedContextRequests++;
1060
- if (requestBody.store === true)
1061
- stats.storeTrueRequests++;
1062
- stats.lastInputItems = requestBody.input?.length ?? 0;
1063
- if (requestBody.previous_response_id) {
1064
- stats.deltaRequests++;
1065
- stats.lastDeltaInputItems = requestBody.input?.length ?? 0;
1066
- stats.lastPreviousResponseId = requestBody.previous_response_id;
1067
- }
1068
- else {
1069
- stats.fullContextRequests++;
1070
- stats.lastDeltaInputItems = undefined;
1071
- stats.lastPreviousResponseId = undefined;
1072
- }
1073
- }
1101
+ let recoveredStalePreviousResponse = false;
1074
1102
  try {
1075
- socket.send(JSON.stringify({ type: "response.create", ...requestBody }));
1076
- await processResponsesStream(startWebSocketOutputOnFirstEvent(mapCodexEvents(parseWebSocket(socket, options?.signal, idleTimeoutMs)), output, stream, onStart), output, stream, model, {
1077
- serviceTier: options?.serviceTier,
1078
- resolveServiceTier: resolveCodexServiceTier,
1079
- applyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),
1080
- });
1103
+ while (true) {
1104
+ recordWebSocketRequestStats(stats, requestBody, reused && !recoveredStalePreviousResponse, useCachedContext);
1105
+ try {
1106
+ socket.send(JSON.stringify({ type: "response.create", ...requestBody }));
1107
+ await processResponsesStream(startWebSocketOutputOnFirstEvent(mapCodexEvents(parseWebSocket(socket, options?.signal, idleTimeoutMs)), output, stream, onStart), output, stream, model, {
1108
+ serviceTier: options?.serviceTier,
1109
+ resolveServiceTier: resolveCodexServiceTier,
1110
+ applyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),
1111
+ });
1112
+ break;
1113
+ }
1114
+ catch (error) {
1115
+ if (!recoveredStalePreviousResponse &&
1116
+ entry &&
1117
+ requestBody.previous_response_id &&
1118
+ isCodexPreviousResponseStale(error) &&
1119
+ !options?.signal?.aborted) {
1120
+ entry.continuation = undefined;
1121
+ requestBody = fullBody;
1122
+ recoveredStalePreviousResponse = true;
1123
+ continue;
1124
+ }
1125
+ throw error;
1126
+ }
1127
+ }
1081
1128
  if (options?.signal?.aborted) {
1082
1129
  keepConnection = false;
1083
1130
  }
@@ -1136,15 +1183,13 @@ function extractAccountId(token) {
1136
1183
  try {
1137
1184
  const parts = token.split(".");
1138
1185
  if (parts.length !== 3)
1139
- throw new Error("Invalid token");
1186
+ return undefined;
1140
1187
  const payload = JSON.parse(atob(parts[1]));
1141
1188
  const accountId = payload?.[JWT_CLAIM_PATH]?.chatgpt_account_id;
1142
- if (!accountId)
1143
- throw new Error("No account ID in token");
1144
- return accountId;
1189
+ return typeof accountId === "string" && accountId.length > 0 ? accountId : undefined;
1145
1190
  }
1146
1191
  catch {
1147
- throw new Error("Failed to extract accountId from token");
1192
+ return undefined;
1148
1193
  }
1149
1194
  }
1150
1195
  function createCodexRequestId() {
@@ -1159,7 +1204,12 @@ function buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token)
1159
1204
  headers.set(key, value);
1160
1205
  }
1161
1206
  headers.set("Authorization", `Bearer ${token}`);
1162
- headers.set("chatgpt-account-id", accountId);
1207
+ if (accountId) {
1208
+ headers.set("chatgpt-account-id", accountId);
1209
+ }
1210
+ else {
1211
+ headers.delete("chatgpt-account-id");
1212
+ }
1163
1213
  headers.set("originator", "senpi");
1164
1214
  const userAgent = _os ? `senpi (${_os.platform()} ${_os.release()}; ${_os.arch()})` : "senpi (browser)";
1165
1215
  headers.set("User-Agent", userAgent);