@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.
- package/CHANGELOG.md +14 -0
- package/README.md +5 -5
- package/dist/api/anthropic-messages.d.ts.map +1 -1
- package/dist/api/anthropic-messages.js +32 -23
- package/dist/api/anthropic-messages.js.map +1 -1
- package/dist/api/bedrock-converse-stream.js +14 -16
- package/dist/api/bedrock-converse-stream.js.map +1 -1
- package/dist/api/google-shared.js +1 -1
- package/dist/api/google-shared.js.map +1 -1
- package/dist/api/openai-codex-responses.d.ts +1 -0
- package/dist/api/openai-codex-responses.d.ts.map +1 -1
- package/dist/api/openai-codex-responses.js +2 -1
- package/dist/api/openai-codex-responses.js.map +1 -1
- package/dist/api/openai-completions.js +1 -1
- package/dist/api/openai-completions.js.map +1 -1
- package/dist/api/openai-responses-shared.js +1 -1
- package/dist/api/openai-responses-shared.js.map +1 -1
- package/dist/api/transform-messages.d.ts +16 -1
- package/dist/api/transform-messages.d.ts.map +1 -1
- package/dist/api/transform-messages.js +28 -15
- package/dist/api/transform-messages.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/compat-schema.d.ts +156 -0
- package/dist/providers/compat-schema.d.ts.map +1 -1
- package/dist/providers/compat-schema.js +4 -2
- package/dist/providers/compat-schema.js.map +1 -1
- package/dist/providers/data/.manifest.json +1 -1
- package/dist/providers/data/anthropic.json +1 -1
- package/dist/providers/data/opencode.json +1 -1
- package/dist/providers/data/openrouter.json +1 -1
- package/dist/providers/data/radius.json +1 -1
- package/dist/providers/data/together.json +1 -1
- package/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/dist/providers/model-schema.d.ts.map +1 -1
- package/dist/providers/model-schema.js +4 -2
- package/dist/providers/model-schema.js.map +1 -1
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +7 -1
- package/dist/utils/retry.js.map +1 -1
- 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
|
|
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
|
|
1540
|
-
- **Assistant messages from different
|
|
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
|
|
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 (
|
|
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;
|
|
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
|
|
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: (
|
|
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 (
|
|
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
|
-
|
|
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
|
}
|