@bastani/pi-ai 0.9.38-alpha.6 → 0.9.38-alpha.8

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 (43) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +5 -5
  3. package/dist/api/anthropic-messages.d.ts.map +1 -1
  4. package/dist/api/anthropic-messages.js +32 -23
  5. package/dist/api/anthropic-messages.js.map +1 -1
  6. package/dist/api/bedrock-converse-stream.js +14 -16
  7. package/dist/api/bedrock-converse-stream.js.map +1 -1
  8. package/dist/api/google-shared.js +1 -1
  9. package/dist/api/google-shared.js.map +1 -1
  10. package/dist/api/openai-codex-responses.d.ts +1 -0
  11. package/dist/api/openai-codex-responses.d.ts.map +1 -1
  12. package/dist/api/openai-codex-responses.js +2 -1
  13. package/dist/api/openai-codex-responses.js.map +1 -1
  14. package/dist/api/openai-completions.js +1 -1
  15. package/dist/api/openai-completions.js.map +1 -1
  16. package/dist/api/openai-responses-shared.js +1 -1
  17. package/dist/api/openai-responses-shared.js.map +1 -1
  18. package/dist/api/transform-messages.d.ts +16 -1
  19. package/dist/api/transform-messages.d.ts.map +1 -1
  20. package/dist/api/transform-messages.js +28 -15
  21. package/dist/api/transform-messages.js.map +1 -1
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +1 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/providers/compat-schema.d.ts +156 -0
  27. package/dist/providers/compat-schema.d.ts.map +1 -1
  28. package/dist/providers/compat-schema.js +4 -2
  29. package/dist/providers/compat-schema.js.map +1 -1
  30. package/dist/providers/data/.manifest.json +1 -1
  31. package/dist/providers/data/anthropic.json +1 -1
  32. package/dist/providers/data/opencode.json +1 -1
  33. package/dist/providers/data/openrouter.json +1 -1
  34. package/dist/providers/data/radius.json +1 -1
  35. package/dist/providers/data/together.json +1 -1
  36. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  37. package/dist/providers/model-schema.d.ts.map +1 -1
  38. package/dist/providers/model-schema.js +4 -2
  39. package/dist/providers/model-schema.js.map +1 -1
  40. package/dist/utils/retry.d.ts.map +1 -1
  41. package/dist/utils/retry.js +7 -1
  42. package/dist/utils/retry.js.map +1 -1
  43. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -4,6 +4,20 @@ This package is a Bastani fork of `@earendil-works/pi-ai`. Upstream history at t
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.9.38-alpha.7] - 2026-10-17
8
+
9
+ ### Fixed
10
+
11
+ - Fixed `closeOpenAICodexWebSocketSessions("")` closing other sessions' cached sockets. Empty keys now do nothing; omitting the key still closes all cached sockets.
12
+ - Fixed the root `AssistantMessageEventStream` export being visible only as a type to TypeScript consumers. It can now be imported and constructed as a value, matching the runtime API.
13
+ - Fixed automatic retry not triggering for `The service is temporarily unavailable` errors, which also made compaction fail on the first attempt ([#10965](https://github.com/earendil-works/pi/issues/10965)).
14
+ - Fixed Claude Haiku 5.5 and Claude Opus 5 not offering the `off` thinking level on Anthropic. `off` now sends `thinking: {type: "disabled"}` without effort settings ([#10753](https://github.com/earendil-works/pi/issues/10753)).
15
+ - Fixed images in error tool results being replaced with a note on Anthropic, and Claude on Amazon Bedrock rejecting error tool results that contain images. The images are now sent after the tool results of the turn, so the model still sees them ([#10794](https://github.com/earendil-works/pi/issues/10794)).
16
+ - Fixed model switching exposing another model's thinking as visible reply text. Foreign thinking blocks are now dropped; Anthropic models that adjudicate thinking-block model binding still receive compatible blocks from the same provider ([#10995](https://github.com/earendil-works/pi/issues/10995)).
17
+ - Fixed automatic retry not triggering for node-fetch request failures during Google Vertex authentication token refresh ([#10996](https://github.com/earendil-works/pi/issues/10996)).
18
+ - Fixed whitespace-only tool output (for example a bare `\r\n` from bash) causing OpenAI Completions, OpenAI Responses, and Google providers to reject requests. It now uses the same placeholder as empty output ([#8720](https://github.com/earendil-works/pi/issues/8720)).
19
+ - Fixed automatic retry not triggering for gateway upstream failures such as `upstream_error: Upstream request failed`, `upstream_stream_read_error`, and `Upstream HTTP/2 stream failed` ([#10920](https://github.com/earendil-works/pi/issues/10920)).
20
+
7
21
  ## [0.9.38-alpha.6] - 2026-10-16
8
22
 
9
23
  ### Fixed
package/README.md CHANGED
@@ -1531,13 +1531,13 @@ Notes:
1531
1531
 
1532
1532
  ## Cross-Provider Handoffs
1533
1533
 
1534
- The library supports seamless handoffs between different LLM providers within the same conversation. This allows you to switch models mid-conversation while preserving context, including thinking blocks, tool calls, and tool results.
1534
+ The library supports seamless handoffs between different LLM providers within the same conversation. This allows you to switch models mid-conversation while preserving context, including text, tool calls, and tool results.
1535
1535
 
1536
1536
  When messages from one provider are sent to a different provider, the library automatically transforms them for compatibility:
1537
1537
 
1538
1538
  - **User and tool result messages** are passed through unchanged
1539
- - **Assistant messages from the same provider/API** are preserved as-is
1540
- - **Assistant messages from different providers** have their thinking blocks converted to text with `<thinking>` tags
1539
+ - **Assistant messages from the same provider, API, and model** are preserved as-is, including thinking blocks
1540
+ - **Assistant messages from a different model** have their thinking blocks dropped. Signatures and encrypted reasoning only work with the model that produced them, and readable thinking sent as plain text makes the new model treat it as part of the reply. Anthropic models whose API decides which thinking blocks a model may read receive signed blocks from other models on the same provider unchanged
1541
1541
  - **Tool calls and regular text** are preserved unchanged
1542
1542
 
1543
1543
  ```typescript
@@ -1558,7 +1558,7 @@ const claude = models.getModel('anthropic', 'claude-sonnet-4-5')!;
1558
1558
  context.messages.push({ role: 'user', content: 'What is 25 * 18?', timestamp: Date.now() });
1559
1559
  context.messages.push(await models.completeSimple(claude, context, { reasoning: 'medium' }));
1560
1560
 
1561
- // Switch to GPT-5 - it will see Claude's thinking as <thinking> tagged text
1561
+ // Switch to GPT-5 - it sees Claude's answer, but not Claude's thinking
1562
1562
  const gpt5 = models.getModel('openai', 'gpt-5-mini')!;
1563
1563
  context.messages.push({ role: 'user', content: 'Is that calculation correct?', timestamp: Date.now() });
1564
1564
  context.messages.push(await models.complete(gpt5, context));
@@ -1569,7 +1569,7 @@ context.messages.push({ role: 'user', content: 'What was the original question?'
1569
1569
  const geminiResponse = await models.complete(gemini, context);
1570
1570
  ```
1571
1571
 
1572
- All providers can handle messages from other providers — text, tool calls and results (including images), thinking blocks (transformed to tagged text), and aborted messages with partial content. This enables flexible workflows: start with a fast model, switch to a more capable one for complex reasoning, or maintain continuity across provider outages.
1572
+ All providers can handle messages from other providers — text, tool calls and results (including images), thinking blocks (dropped when the model changes), and aborted messages with partial content. This enables flexible workflows: start with a fast model, switch to a more capable one for complex reasoning, or maintain continuity across provider outages.
1573
1573
 
1574
1574
  ## System Messages
1575
1575
 
@@ -1 +1 @@
1
- {"version":3,"file":"anthropic-messages.d.ts","sourceRoot":"","sources":["../../src/api/anthropic-messages.ts"],"names":[],"mappings":"AAAA,OAAO,SAAiC,MAAM,mBAAmB,CAAC;AAiBlE,OAAO,KAAK,EASX,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAOb,MAAM,aAAa,CAAC;AAoKrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AAmPhE,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACtD;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,EAAE,wBAAwB,CAAC;IAC3C;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;CACnB;AAgSD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CAyZzE,CAAC;AA4BF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CAgDlF,CAAC"}
1
+ {"version":3,"file":"anthropic-messages.d.ts","sourceRoot":"","sources":["../../src/api/anthropic-messages.ts"],"names":[],"mappings":"AAAA,OAAO,SAAiC,MAAM,mBAAmB,CAAC;AAiBlE,OAAO,KAAK,EASX,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAOb,MAAM,aAAa,CAAC;AAwKrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AAmPhE,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACtD;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,EAAE,wBAAwB,CAAC;IAC3C;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;CACnB;AAgSD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CAwZzE,CAAC;AA4BF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CAgDlF,CAAC"}
@@ -16,7 +16,7 @@ import { getCurrentTools, getInitialSystemMessage, resolveTranscript, } from "..
16
16
  import { ANTHROPIC_STRICT_JSON_SCHEMA_PROFILE, getJsonSchemaToolParameters, resolveJsonSchemaStrictSampling, } from "./constrained-sampling.js";
17
17
  import { buildCopilotDynamicHeaders, hasCopilotVisionInput, preserveCopilotIntegrationHeader, } from "./github-copilot-headers.js";
18
18
  import { adjustMaxTokensForThinking, buildBaseOptions, clampMaxTokensToContext } from "./simple-options.js";
19
- import { transformMessages } from "./transform-messages.js";
19
+ import { splitErrorToolResultImages, transformMessages } from "./transform-messages.js";
20
20
  /**
21
21
  * Resolve cache retention preference.
22
22
  * Defaults to "short" and uses PI_CACHE_RETENTION for backward compatibility.
@@ -85,6 +85,16 @@ const fromClaudeCodeName = (name, tools) => {
85
85
  }
86
86
  return name;
87
87
  };
88
+ function convertImageBlock(image) {
89
+ return {
90
+ type: "image",
91
+ source: {
92
+ type: "base64",
93
+ media_type: image.mimeType,
94
+ data: image.data,
95
+ },
96
+ };
97
+ }
88
98
  /**
89
99
  * Convert tool-result content blocks to Anthropic API format.
90
100
  *
@@ -105,14 +115,7 @@ function convertContentBlocks(content) {
105
115
  text: sanitizeSurrogates(block.text),
106
116
  };
107
117
  }
108
- return {
109
- type: "image",
110
- source: {
111
- type: "base64",
112
- media_type: block.mimeType,
113
- data: block.data,
114
- },
115
- };
118
+ return convertImageBlock(block);
116
119
  });
117
120
  // If only images (no text), add placeholder text block
118
121
  const hasText = blocks.some((b) => b.type === "text");
@@ -557,14 +560,12 @@ export const stream = (model, context, options) => {
557
560
  const normalizedContext = resolveTranscript(context, getAnthropicCompat(model).supportsMidConvoSystemMessages);
558
561
  const currentTools = getCurrentTools(normalizedContext.messages);
559
562
  (async () => {
560
- const providerThinkingLevel = supportsMidConvoEffort(model) ? (options?.effort ?? "high") : undefined;
561
563
  const output = {
562
564
  role: "assistant",
563
565
  content: [],
564
566
  api: model.api,
565
567
  provider: model.provider,
566
568
  model: model.id,
567
- ...(providerThinkingLevel === undefined ? {} : { providerThinkingLevel }),
568
569
  usage: {
569
570
  input: 0,
570
571
  output: 0,
@@ -619,6 +620,8 @@ export const stream = (model, context, options) => {
619
620
  for (;;) {
620
621
  streamDeadline.signal?.throwIfAborted();
621
622
  const built = buildParams(model, normalizedContext, isOAuth, options, disablePreferredStrictTools);
623
+ if (built.providerThinkingLevel)
624
+ output.providerThinkingLevel = built.providerThinkingLevel;
622
625
  let params = built.params;
623
626
  const nextParams = await options?.onPayload?.(params, model);
624
627
  if (nextParams !== undefined)
@@ -1144,11 +1147,14 @@ function buildParams(model, context, isOAuthToken, options, disablePreferredStri
1144
1147
  };
1145
1148
  const initialTools = initialSystemMessage?.toolsAdded ?? [];
1146
1149
  const nativeToolChanges = compat.supportsMidConvoSystemMessages && compat.supportsMidConvoToolChanges && initialTools.length > 0;
1147
- const converted = convertMessages(conversationMessages, isOAuthToken, cacheControl, compat.allowEmptySignature, supportsMidConvoEffort(model) ? model.provider : undefined, nativeToolChanges ? convertToolDefinitions : undefined);
1150
+ const thinkingDisabled = model.reasoning && options?.thinkingEnabled === false && model.thinkingLevelMap?.off !== null;
1151
+ // Effort cannot change between messages while thinking is disabled, so "off" sends no effort markers.
1152
+ const effortMessages = supportsMidConvoEffort(model) && !thinkingDisabled;
1153
+ const converted = convertMessages(conversationMessages, isOAuthToken, cacheControl, compat.allowEmptySignature, effortMessages ? model.provider : undefined, nativeToolChanges ? convertToolDefinitions : undefined);
1148
1154
  const activeEffort = options?.effort ?? "high";
1149
1155
  const params = {
1150
1156
  model: upstreamModelId(model),
1151
- messages: (supportsMidConvoEffort(model)
1157
+ messages: (effortMessages
1152
1158
  ? insertThinkingLevelMessages(converted, activeEffort)
1153
1159
  : converted.messages),
1154
1160
  max_tokens: options?.maxTokens ?? model.maxTokens,
@@ -1205,7 +1211,7 @@ function buildParams(model, context, isOAuthToken, options, disablePreferredStri
1205
1211
  }
1206
1212
  // Managed effort models always use adaptive thinking. Their per-turn markers
1207
1213
  // carry the actual effort; the top-level high value deliberately stays stable.
1208
- if (supportsMidConvoEffort(model)) {
1214
+ if (effortMessages) {
1209
1215
  params.thinking = {
1210
1216
  type: "adaptive",
1211
1217
  display: options?.thinkingDisplay ?? "summarized",
@@ -1301,18 +1307,13 @@ function buildParams(model, context, isOAuthToken, options, disablePreferredStri
1301
1307
  if (allowedFallbackModels && allowedFallbackModels.length > 0) {
1302
1308
  params.fallbacks = allowedFallbackModels.map((fallback) => ({ model: fallback.model }));
1303
1309
  }
1304
- return { params, hasPreferredStrictTools };
1310
+ return { params, hasPreferredStrictTools, providerThinkingLevel: effortMessages ? activeEffort : undefined };
1305
1311
  }
1306
1312
  // Normalize tool call IDs to match Anthropic's required pattern and length
1307
1313
  function normalizeToolCallId(id) {
1308
1314
  return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
1309
1315
  }
1310
- function convertToolResult(msg) {
1311
- const content = msg.isError
1312
- ? msg.content.map((block) => block.type === "image"
1313
- ? { type: "text", text: "[image omitted: error tool results cannot contain images]" }
1314
- : block)
1315
- : msg.content;
1316
+ function convertToolResult(msg, content = msg.content) {
1316
1317
  return {
1317
1318
  type: "tool_result",
1318
1319
  tool_use_id: msg.toolCallId,
@@ -1506,16 +1507,24 @@ function convertMessages(transformedMessages, isOAuthToken, cacheControl, allowE
1506
1507
  else if (msg.role === "toolResult") {
1507
1508
  // Collect all consecutive toolResult messages, needed for z.ai Anthropic endpoint.
1508
1509
  const toolResults = [];
1510
+ // Anthropic rejects error tool results that contain anything but text, so their images follow
1511
+ // all tool results of the turn, which must come first in the user message (#10794).
1512
+ const errorImages = [];
1509
1513
  let j = i;
1510
1514
  while (j < transformedMessages.length && transformedMessages[j].role === "toolResult") {
1511
- toolResults.push(convertToolResult(transformedMessages[j]));
1515
+ const toolMsg = transformedMessages[j];
1516
+ const split = splitErrorToolResultImages(toolMsg);
1517
+ toolResults.push(convertToolResult(toolMsg, split?.content));
1518
+ if (split) {
1519
+ errorImages.push({ type: "text", text: split.caption }, ...split.images.map(convertImageBlock));
1520
+ }
1512
1521
  j++;
1513
1522
  }
1514
1523
  // Skip the messages we've already processed.
1515
1524
  i = j - 1;
1516
1525
  params.push({
1517
1526
  role: "user",
1518
- content: toolResults,
1527
+ content: [...toolResults, ...errorImages],
1519
1528
  });
1520
1529
  }
1521
1530
  }