@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 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.1",
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.1",
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
+ }
@@ -0,0 +1,4 @@
1
+ <turn-aborted>
2
+ The previous turn was aborted. Any running tools/commands were terminated.
3
+ If tools were aborted, they may have partially executed; verify current state before retrying.
4
+ </turn-aborted>
@@ -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 = (ThinkingContent | TextContent | (ToolCall & { partialJson: string })) & { index: number };
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: TURN_ABORTED_GUIDANCE,
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;