@oh-my-pi/pi-ai 12.11.2 → 12.12.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-ai",
3
- "version": "12.11.2",
3
+ "version": "12.12.0",
4
4
  "description": "Unified LLM API with automatic model discovery and provider configuration",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -63,7 +63,7 @@
63
63
  "@connectrpc/connect-node": "^2.1.1",
64
64
  "@google/genai": "^1.41.0",
65
65
  "@mistralai/mistralai": "^1.14.0",
66
- "@oh-my-pi/pi-utils": "12.11.2",
66
+ "@oh-my-pi/pi-utils": "12.12.0",
67
67
  "@sinclair/typebox": "^0.34.48",
68
68
  "@smithy/node-http-handler": "^4.4.10",
69
69
  "ajv": "^8.18.0",
@@ -387,6 +387,7 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
387
387
  const websocketV2Enabled = isCodexWebSocketV2Enabled();
388
388
  let websocketRetries = 0;
389
389
  while (true) {
390
+ const websocketRequest = buildCodexWebSocketRequest(transformedBody, websocketState, websocketV2Enabled);
390
391
  const websocketHeaders = createCodexHeaders(
391
392
  requestHeaders,
392
393
  accountId,
@@ -396,13 +397,14 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
396
397
  websocketState,
397
398
  websocketV2Enabled,
398
399
  );
399
- const websocketRequest = buildCodexWebSocketRequest(transformedBody, websocketState, websocketV2Enabled);
400
400
  requestBodyForState = cloneRequestBody(transformedBody);
401
401
  logCodexDebug("codex websocket request", {
402
402
  url: toWebSocketUrl(url),
403
403
  model: params.model,
404
404
  reasoningEffort,
405
405
  headers: redactHeaders(websocketHeaders),
406
+ sentTurnStateHeader: websocketHeaders.has(X_CODEX_TURN_STATE_HEADER),
407
+ sentModelsEtagHeader: websocketHeaders.has(X_MODELS_ETAG_HEADER),
406
408
  requestType: websocketRequest.type,
407
409
  retry: websocketRetries,
408
410
  retryBudget: websocketRetryBudget,
@@ -467,6 +469,7 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
467
469
  const blocks = output.content;
468
470
  const blockIndex = () => blocks.length - 1;
469
471
  let websocketStreamRetries = 0;
472
+ let sawTerminalEvent = false;
470
473
  while (true) {
471
474
  try {
472
475
  for await (const rawEvent of eventStream) {
@@ -645,6 +648,7 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
645
648
  }
646
649
  }
647
650
  } else if (eventType === "response.completed" || eventType === "response.done") {
651
+ sawTerminalEvent = true;
648
652
  const response = (
649
653
  rawEvent as {
650
654
  response?: {
@@ -712,6 +716,11 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
712
716
  websocketStreamRetries += 1;
713
717
  await abortableSleep(getCodexWebSocketRetryDelayMs(websocketStreamRetries), options?.signal);
714
718
  const websocketV2Enabled = isCodexWebSocketV2Enabled();
719
+ const websocketRequest = buildCodexWebSocketRequest(
720
+ transformedBody,
721
+ websocketState,
722
+ websocketV2Enabled,
723
+ );
715
724
  const websocketHeaders = createCodexHeaders(
716
725
  requestHeaders,
717
726
  accountId,
@@ -721,11 +730,6 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
721
730
  websocketState,
722
731
  websocketV2Enabled,
723
732
  );
724
- const websocketRequest = buildCodexWebSocketRequest(
725
- transformedBody,
726
- websocketState,
727
- websocketV2Enabled,
728
- );
729
733
  requestBodyForState = cloneRequestBody(transformedBody);
730
734
  eventStream = await openCodexWebSocketEventStream(
731
735
  toWebSocketUrl(url),
@@ -761,6 +765,21 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
761
765
  throw new Error("Request was aborted");
762
766
  }
763
767
 
768
+ if (!sawTerminalEvent) {
769
+ if (usingWebsocket && websocketState) {
770
+ resetCodexWebSocketAppendState(websocketState);
771
+ resetCodexSessionMetadata(websocketState);
772
+ }
773
+ logCodexDebug("codex stream ended unexpectedly", {
774
+ transport: usingWebsocket ? "websocket" : "sse",
775
+ terminalEventSeen: sawTerminalEvent,
776
+ unexpectedStreamEnd: true,
777
+ sentTurnStateHeader: Boolean(websocketState?.turnState),
778
+ sentModelsEtagHeader: Boolean(websocketState?.modelsEtag),
779
+ });
780
+ throw new Error("Codex stream ended before terminal completion event");
781
+ }
782
+
764
783
  if (output.stopReason === "aborted" || output.stopReason === "error") {
765
784
  throw new Error("Codex response failed");
766
785
  }
@@ -773,6 +792,7 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
773
792
  for (const block of output.content) delete (block as { index?: number }).index;
774
793
  if (usingWebsocket && websocketState) {
775
794
  resetCodexWebSocketAppendState(websocketState);
795
+ resetCodexSessionMetadata(websocketState);
776
796
  }
777
797
  output.stopReason = options?.signal?.aborted ? "aborted" : "error";
778
798
  output.errorMessage = formatErrorMessageWithRetryAfter(error);
@@ -866,6 +886,12 @@ function resetCodexWebSocketAppendState(state: CodexWebSocketSessionState): void
866
886
  state.lastResponseId = undefined;
867
887
  }
868
888
 
889
+ function resetCodexSessionMetadata(state: CodexWebSocketSessionState): void {
890
+ state.turnState = undefined;
891
+ state.modelsEtag = undefined;
892
+ state.reasoningIncluded = undefined;
893
+ }
894
+
869
895
  function recordCodexWebSocketFailure(state: CodexWebSocketSessionState, activateFallback: boolean): void {
870
896
  resetCodexWebSocketAppendState(state);
871
897
  state.connection?.close("fallback");
@@ -970,6 +996,14 @@ function buildCodexWebSocketRequest(
970
996
  input: appendInput,
971
997
  };
972
998
  }
999
+ if (state?.canAppend) {
1000
+ logCodexDebug("codex websocket append reset", {
1001
+ hadTurnStateHeader: Boolean(state.turnState),
1002
+ hadModelsEtagHeader: Boolean(state.modelsEtag),
1003
+ });
1004
+ resetCodexWebSocketAppendState(state);
1005
+ resetCodexSessionMetadata(state);
1006
+ }
973
1007
  return {
974
1008
  type: "response.create",
975
1009
  ...requestBody,
@@ -1245,6 +1279,8 @@ async function openCodexSseEventStream(
1245
1279
  url,
1246
1280
  model: body.model,
1247
1281
  headers: redactHeaders(headers),
1282
+ sentTurnStateHeader: headers.has(X_CODEX_TURN_STATE_HEADER),
1283
+ sentModelsEtagHeader: headers.has(X_MODELS_ETAG_HEADER),
1248
1284
  });
1249
1285
  const response = await fetchWithRetry(
1250
1286
  url,