@oh-my-pi/pi-ai 13.9.1 → 13.9.2
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 +16 -0
- package/package.json +2 -2
- package/src/models.json +1 -1
- package/src/prompts/turn-aborted-guidance.md +4 -0
- package/src/providers/anthropic.ts +40 -1
- package/src/providers/transform-messages.ts +16 -8
- package/src/types.ts +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [13.9.2] - 2026-03-05
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Support for redacted thinking blocks in Anthropic messages, enabling secure handling of encrypted reasoning content
|
|
10
|
+
- Preservation of latest Anthropic thinking blocks and redacted thinking content during message transformation, even when switching between Anthropic models
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Assistant message content now includes `RedactedThinkingContent` type alongside existing text, thinking, and tool call blocks
|
|
15
|
+
- Message transformation logic now preserves signed thinking blocks and redacted thinking for the latest assistant message in Anthropic conversations
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Fixed Unicode normalization to consistently apply `toWellFormed()` to all text content, including thinking blocks, ensuring proper handling of malformed UTF-16 sequences
|
|
20
|
+
|
|
5
21
|
## [13.9.1] - 2026-03-05
|
|
6
22
|
### Breaking Changes
|
|
7
23
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-ai",
|
|
4
|
-
"version": "13.9.
|
|
4
|
+
"version": "13.9.2",
|
|
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",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@aws-sdk/client-bedrock-runtime": "^3",
|
|
42
42
|
"@bufbuild/protobuf": "^2.11",
|
|
43
43
|
"@google/genai": "^1.43",
|
|
44
|
-
"@oh-my-pi/pi-utils": "13.9.
|
|
44
|
+
"@oh-my-pi/pi-utils": "13.9.2",
|
|
45
45
|
"@sinclair/typebox": "^0.34",
|
|
46
46
|
"@smithy/node-http-handler": "^4.4",
|
|
47
47
|
"ajv": "^8.18",
|
package/src/models.json
CHANGED
|
@@ -38967,4 +38967,4 @@
|
|
|
38967
38967
|
"maxTokens": 128000
|
|
38968
38968
|
}
|
|
38969
38969
|
}
|
|
38970
|
-
}
|
|
38970
|
+
}
|
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
ImageContent,
|
|
19
19
|
Message,
|
|
20
20
|
Model,
|
|
21
|
+
RedactedThinkingContent,
|
|
21
22
|
SimpleStreamOptions,
|
|
22
23
|
StopReason,
|
|
23
24
|
StreamFunction,
|
|
@@ -613,7 +614,12 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
613
614
|
body: params,
|
|
614
615
|
};
|
|
615
616
|
|
|
616
|
-
type Block = (
|
|
617
|
+
type Block = (
|
|
618
|
+
| ThinkingContent
|
|
619
|
+
| RedactedThinkingContent
|
|
620
|
+
| TextContent
|
|
621
|
+
| (ToolCall & { partialJson: string })
|
|
622
|
+
) & { index: number };
|
|
617
623
|
const blocks = output.content as Block[];
|
|
618
624
|
stream.push({ type: "start", partial: output });
|
|
619
625
|
// Retry loop for transient errors from the stream.
|
|
@@ -664,6 +670,13 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
664
670
|
contentIndex: output.content.length - 1,
|
|
665
671
|
partial: output,
|
|
666
672
|
});
|
|
673
|
+
} else if (event.content_block.type === "redacted_thinking") {
|
|
674
|
+
const block: Block = {
|
|
675
|
+
type: "redactedThinking",
|
|
676
|
+
data: event.content_block.data,
|
|
677
|
+
index: event.index,
|
|
678
|
+
};
|
|
679
|
+
output.content.push(block);
|
|
667
680
|
} else if (event.content_block.type === "tool_use") {
|
|
668
681
|
const block: Block = {
|
|
669
682
|
type: "toolCall",
|
|
@@ -1403,6 +1416,10 @@ export function convertAnthropicMessages(
|
|
|
1403
1416
|
}
|
|
1404
1417
|
} else if (msg.role === "assistant") {
|
|
1405
1418
|
const blocks: ContentBlockParam[] = [];
|
|
1419
|
+
const hasSignedThinking = msg.content.some(
|
|
1420
|
+
block =>
|
|
1421
|
+
block.type === "thinking" && !!block.thinkingSignature && block.thinkingSignature.trim().length > 0,
|
|
1422
|
+
);
|
|
1406
1423
|
|
|
1407
1424
|
for (const block of msg.content) {
|
|
1408
1425
|
if (block.type === "text") {
|
|
@@ -1412,6 +1429,22 @@ export function convertAnthropicMessages(
|
|
|
1412
1429
|
text: block.text.toWellFormed(),
|
|
1413
1430
|
});
|
|
1414
1431
|
} else if (block.type === "thinking") {
|
|
1432
|
+
if (hasSignedThinking) {
|
|
1433
|
+
if (!block.thinkingSignature || block.thinkingSignature.trim().length === 0) {
|
|
1434
|
+
if (block.thinking.trim().length === 0) continue;
|
|
1435
|
+
blocks.push({
|
|
1436
|
+
type: "text",
|
|
1437
|
+
text: block.thinking.toWellFormed(),
|
|
1438
|
+
});
|
|
1439
|
+
continue;
|
|
1440
|
+
}
|
|
1441
|
+
blocks.push({
|
|
1442
|
+
type: "thinking",
|
|
1443
|
+
thinking: block.thinking,
|
|
1444
|
+
signature: block.thinkingSignature,
|
|
1445
|
+
});
|
|
1446
|
+
continue;
|
|
1447
|
+
}
|
|
1415
1448
|
if (block.thinking.trim().length === 0) continue;
|
|
1416
1449
|
if (!block.thinkingSignature || block.thinkingSignature.trim().length === 0) {
|
|
1417
1450
|
blocks.push({
|
|
@@ -1425,6 +1458,12 @@ export function convertAnthropicMessages(
|
|
|
1425
1458
|
signature: block.thinkingSignature,
|
|
1426
1459
|
});
|
|
1427
1460
|
}
|
|
1461
|
+
} else if (block.type === "redactedThinking") {
|
|
1462
|
+
if (block.data.trim().length === 0) continue;
|
|
1463
|
+
blocks.push({
|
|
1464
|
+
type: "redacted_thinking",
|
|
1465
|
+
data: block.data,
|
|
1466
|
+
});
|
|
1428
1467
|
} else if (block.type === "toolCall") {
|
|
1429
1468
|
blocks.push({
|
|
1430
1469
|
type: "tool_use",
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
+
import turnAbortedGuidance from "../prompts/turn-aborted-guidance.md" with { type: "text" };
|
|
1
2
|
import type { Api, AssistantMessage, DeveloperMessage, Message, Model, ToolCall, ToolResultMessage } from "../types";
|
|
2
3
|
|
|
3
|
-
const TURN_ABORTED_GUIDANCE =
|
|
4
|
-
"<turn-aborted>\n" +
|
|
5
|
-
"The previous turn was aborted. Any running tools/commands were terminated. " +
|
|
6
|
-
"If tools were aborted, they may have partially executed; verify current state before retrying.\n" +
|
|
7
|
-
"</turn-aborted>";
|
|
8
|
-
|
|
9
4
|
const enum ToolCallStatus {
|
|
10
5
|
/** Tool call has received a result (real or synthetic for orphan) */
|
|
11
6
|
Resolved = 1,
|
|
@@ -31,8 +26,9 @@ export function transformMessages<TApi extends Api>(
|
|
|
31
26
|
// Build a map of original tool call IDs to normalized IDs
|
|
32
27
|
const toolCallIdMap = new Map<string, string>();
|
|
33
28
|
|
|
29
|
+
const latestAssistantIndex = messages.findLastIndex(msg => msg.role === "assistant");
|
|
34
30
|
// First pass: transform messages (thinking blocks, tool call ID normalization)
|
|
35
|
-
const transformed = messages.map(msg => {
|
|
31
|
+
const transformed = messages.map((msg, index) => {
|
|
36
32
|
// User and developer messages pass through unchanged
|
|
37
33
|
if (msg.role === "user" || msg.role === "developer") {
|
|
38
34
|
return msg;
|
|
@@ -55,8 +51,14 @@ export function transformMessages<TApi extends Api>(
|
|
|
55
51
|
assistantMsg.api === model.api &&
|
|
56
52
|
assistantMsg.model === model.id;
|
|
57
53
|
|
|
54
|
+
const mustPreserveLatestAnthropicThinking =
|
|
55
|
+
index === latestAssistantIndex &&
|
|
56
|
+
model.api === "anthropic-messages" &&
|
|
57
|
+
assistantMsg.api === "anthropic-messages";
|
|
58
|
+
|
|
58
59
|
const transformedContent = assistantMsg.content.flatMap(block => {
|
|
59
60
|
if (block.type === "thinking") {
|
|
61
|
+
if (mustPreserveLatestAnthropicThinking) return block;
|
|
60
62
|
// For same model: keep thinking blocks with signatures (needed for replay)
|
|
61
63
|
// even if the thinking text is empty (OpenAI encrypted reasoning)
|
|
62
64
|
if (isSameModel && block.thinkingSignature) return block;
|
|
@@ -69,6 +71,12 @@ export function transformMessages<TApi extends Api>(
|
|
|
69
71
|
};
|
|
70
72
|
}
|
|
71
73
|
|
|
74
|
+
if (block.type === "redactedThinking") {
|
|
75
|
+
if (mustPreserveLatestAnthropicThinking) return block;
|
|
76
|
+
if (isSameModel) return block;
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
|
|
72
80
|
if (block.type === "text") {
|
|
73
81
|
if (isSameModel) return block;
|
|
74
82
|
return {
|
|
@@ -163,7 +171,7 @@ export function transformMessages<TApi extends Api>(
|
|
|
163
171
|
// Inject turn_aborted guidance marker as developer message
|
|
164
172
|
result.push({
|
|
165
173
|
role: "developer",
|
|
166
|
-
content:
|
|
174
|
+
content: turnAbortedGuidance,
|
|
167
175
|
timestamp: assistantMsg.timestamp + 1,
|
|
168
176
|
} as DeveloperMessage);
|
|
169
177
|
|
package/src/types.ts
CHANGED
|
@@ -224,6 +224,11 @@ export interface ThinkingContent {
|
|
|
224
224
|
thinkingSignature?: string; // e.g., for OpenAI responses, the reasoning item ID
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
export interface RedactedThinkingContent {
|
|
228
|
+
type: "redactedThinking";
|
|
229
|
+
data: string;
|
|
230
|
+
}
|
|
231
|
+
|
|
227
232
|
export interface ImageContent {
|
|
228
233
|
type: "image";
|
|
229
234
|
data: string; // base64 encoded image data
|
|
@@ -277,7 +282,7 @@ export interface DeveloperMessage {
|
|
|
277
282
|
|
|
278
283
|
export interface AssistantMessage {
|
|
279
284
|
role: "assistant";
|
|
280
|
-
content: (TextContent | ThinkingContent | ToolCall)[];
|
|
285
|
+
content: (TextContent | ThinkingContent | RedactedThinkingContent | ToolCall)[];
|
|
281
286
|
api: Api;
|
|
282
287
|
provider: Provider;
|
|
283
288
|
model: string;
|