@oh-my-pi/pi-ai 14.6.2 → 14.6.4

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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [14.6.4] - 2026-05-03
6
+
7
+ ### Fixed
8
+
9
+ - Fixed OpenAI Codex websocket continuations to retry with full context when `previous_response_id` expires server-side instead of surfacing `previous_response_not_found`.
10
+
5
11
  ## [14.6.2] - 2026-05-03
6
12
  ### Added
7
13
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-ai",
4
- "version": "14.6.2",
4
+ "version": "14.6.4",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://github.com/can1357/oh-my-pi",
7
7
  "author": "Can Boluk",
@@ -46,8 +46,8 @@
46
46
  "@aws-sdk/credential-provider-node": "^3.972.36",
47
47
  "@bufbuild/protobuf": "^2.12.0",
48
48
  "@google/genai": "^1.50.1",
49
- "@oh-my-pi/pi-natives": "14.6.2",
50
- "@oh-my-pi/pi-utils": "14.6.2",
49
+ "@oh-my-pi/pi-natives": "14.6.4",
50
+ "@oh-my-pi/pi-utils": "14.6.4",
51
51
  "@sinclair/typebox": "^0.34.49",
52
52
  "@smithy/node-http-handler": "^4.6.1",
53
53
  "ajv": "^8.20.0",
@@ -1962,6 +1962,25 @@ function isZaiAnthropicEndpoint(model: Model<"anthropic-messages">): boolean {
1962
1962
  }
1963
1963
  }
1964
1964
 
1965
+ /**
1966
+ * Returns true for providers whose Anthropic-compatible endpoints do NOT
1967
+ * implement signature-based thinking-chain integrity (DeepSeek, Z.AI, etc.).
1968
+ * For these providers, unsigned thinking blocks must be preserved as
1969
+ * `type: "thinking"` instead of being degraded to text.
1970
+ */
1971
+ function isNonSigningAnthropicEndpoint(model: Model<"anthropic-messages">): boolean {
1972
+ // Known non-signing providers
1973
+ if (model.provider === "zai" || model.provider === "deepseek") return true;
1974
+ const baseUrl = model.baseUrl;
1975
+ if (!baseUrl) return false;
1976
+ try {
1977
+ const hostname = new URL(baseUrl).hostname.toLowerCase();
1978
+ return hostname === "api.deepseek.com" || hostname.endsWith(".deepseek.com");
1979
+ } catch {
1980
+ return false;
1981
+ }
1982
+ }
1983
+
1965
1984
  function buildToolResultBlock(model: Model<"anthropic-messages">, msg: ToolResultMessage): ContentBlockParam {
1966
1985
  const block: ContentBlockParam = {
1967
1986
  type: "tool_result",
@@ -2061,10 +2080,18 @@ export function convertAnthropicMessages(
2061
2080
  }
2062
2081
  if (block.thinking.trim().length === 0) continue;
2063
2082
  if (!block.thinkingSignature || block.thinkingSignature.trim().length === 0) {
2064
- blocks.push({
2065
- type: "text",
2066
- text: block.thinking.toWellFormed(),
2067
- });
2083
+ if (isNonSigningAnthropicEndpoint(model)) {
2084
+ blocks.push({
2085
+ type: "thinking",
2086
+ thinking: block.thinking.toWellFormed(),
2087
+ signature: "",
2088
+ });
2089
+ } else {
2090
+ blocks.push({
2091
+ type: "text",
2092
+ text: block.thinking.toWellFormed(),
2093
+ });
2094
+ }
2068
2095
  } else {
2069
2096
  blocks.push({
2070
2097
  type: "thinking",
@@ -1221,6 +1221,9 @@ async function recoverCodexStreamError(
1221
1221
  if (await tryReconnectCodexWebSocketOnConnectionLimit(context, runtime, error)) {
1222
1222
  return true;
1223
1223
  }
1224
+ if (await tryRecoverCodexPreviousResponseNotFound(context, runtime, error)) {
1225
+ return true;
1226
+ }
1224
1227
  if (await tryReplayWebsocketFailureOverSse(context, runtime, error)) {
1225
1228
  return true;
1226
1229
  }
@@ -1279,6 +1282,44 @@ async function tryReconnectCodexWebSocketOnConnectionLimit(
1279
1282
  return true;
1280
1283
  }
1281
1284
 
1285
+ function isCodexPreviousResponseNotFound(error: unknown): boolean {
1286
+ return error instanceof CodexProviderStreamError && error.code === "previous_response_not_found";
1287
+ }
1288
+
1289
+ async function tryRecoverCodexPreviousResponseNotFound(
1290
+ context: CodexStreamProcessingContext,
1291
+ runtime: CodexStreamRuntime,
1292
+ error: unknown,
1293
+ ): Promise<boolean> {
1294
+ const websocketState = context.requestContext.websocketState;
1295
+ if (
1296
+ !isCodexPreviousResponseNotFound(error) ||
1297
+ !websocketState ||
1298
+ runtime.transport !== "websocket" ||
1299
+ context.output.content.length > 0 ||
1300
+ context.options?.signal?.aborted ||
1301
+ runtime.providerRetryAttempt >= CODEX_MAX_RETRIES
1302
+ ) {
1303
+ return false;
1304
+ }
1305
+
1306
+ runtime.providerRetryAttempt += 1;
1307
+ resetCodexWebSocketAppendState(websocketState);
1308
+ resetCodexSessionMetadata(websocketState);
1309
+ runtime.currentItem = null;
1310
+ runtime.currentBlock = null;
1311
+ runtime.sawTerminalEvent = false;
1312
+ runtime.nativeOutputItems.length = 0;
1313
+ resetOutputState(context.output);
1314
+ context.firstTokenTime = undefined;
1315
+
1316
+ logCodexDebug("codex previous_response_id expired; retrying with full context", {
1317
+ retry: runtime.providerRetryAttempt,
1318
+ });
1319
+ await reopenCodexWebSocketRuntimeStream(context, runtime, websocketState);
1320
+ return true;
1321
+ }
1322
+
1282
1323
  async function tryReplayWebsocketFailureOverSse(
1283
1324
  context: CodexStreamProcessingContext,
1284
1325
  runtime: CodexStreamRuntime,