Skip to content

Commit af6c237

Browse files
committed
fix: preserve anthropic thinking blocks on replay
1 parent 295c1c5 commit af6c237

File tree

4 files changed

+62
-13
lines changed

4 files changed

+62
-13
lines changed

src/agents/pi-embedded-helpers/bootstrap.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ export function stripThoughtSignatures<T>(
6565
if (!block || typeof block !== "object") {
6666
return block;
6767
}
68+
const blockType = (block as { type?: unknown }).type;
69+
if (blockType === "thinking" || blockType === "redacted_thinking") {
70+
return block;
71+
}
6872
const rec = block as ContentBlockWithSignature;
6973
const stripSnake = shouldStripSignature(rec.thought_signature);
7074
const stripCamel = includeCamelCase ? shouldStripSignature(rec.thoughtSignature) : false;

src/agents/pi-embedded-runner/proxy-stream-wrappers.ts

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,17 +71,32 @@ export function createOpenRouterSystemCacheWrapper(baseStreamFn: StreamFn | unde
7171
const messages = payloadObj.messages;
7272
if (Array.isArray(messages)) {
7373
for (const msg of messages as Array<{ role?: string; content?: unknown }>) {
74-
if (msg.role !== "system" && msg.role !== "developer") {
74+
if (msg.role === "system" || msg.role === "developer") {
75+
if (typeof msg.content === "string") {
76+
msg.content = [
77+
{ type: "text", text: msg.content, cache_control: { type: "ephemeral" } },
78+
];
79+
} else if (Array.isArray(msg.content) && msg.content.length > 0) {
80+
const last = msg.content[msg.content.length - 1];
81+
if (last && typeof last === "object") {
82+
const record = last as Record<string, unknown>;
83+
if (record.type !== "thinking" && record.type !== "redacted_thinking") {
84+
record.cache_control = { type: "ephemeral" };
85+
}
86+
}
87+
}
7588
continue;
7689
}
77-
if (typeof msg.content === "string") {
78-
msg.content = [
79-
{ type: "text", text: msg.content, cache_control: { type: "ephemeral" } },
80-
];
81-
} else if (Array.isArray(msg.content) && msg.content.length > 0) {
82-
const last = msg.content[msg.content.length - 1];
83-
if (last && typeof last === "object") {
84-
(last as Record<string, unknown>).cache_control = { type: "ephemeral" };
90+
91+
if (msg.role === "assistant" && Array.isArray(msg.content)) {
92+
for (const block of msg.content) {
93+
if (!block || typeof block !== "object") {
94+
continue;
95+
}
96+
const record = block as Record<string, unknown>;
97+
if (record.type === "thinking" || record.type === "redacted_thinking") {
98+
delete record.cache_control;
99+
}
85100
}
86101
}
87102
}

src/agents/pi-embedded-runner/thinking.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ export function isAssistantMessageWithContent(message: AgentMessage): message is
1212
);
1313
}
1414

15+
function isThinkingBlock(block: AssistantContentBlock): boolean {
16+
return (
17+
!!block &&
18+
typeof block === "object" &&
19+
((block as { type?: unknown }).type === "thinking" ||
20+
(block as { type?: unknown }).type === "redacted_thinking")
21+
);
22+
}
23+
1524
/**
1625
* Strip all `type: "thinking"` content blocks from assistant messages.
1726
*
@@ -23,17 +32,30 @@ export function isAssistantMessageWithContent(message: AgentMessage): message is
2332
* use reference equality to skip downstream work).
2433
*/
2534
export function dropThinkingBlocks(messages: AgentMessage[]): AgentMessage[] {
35+
let latestAssistantIndex = -1;
36+
for (let i = messages.length - 1; i >= 0; i -= 1) {
37+
if (isAssistantMessageWithContent(messages[i])) {
38+
latestAssistantIndex = i;
39+
break;
40+
}
41+
}
42+
2643
let touched = false;
2744
const out: AgentMessage[] = [];
28-
for (const msg of messages) {
45+
for (let i = 0; i < messages.length; i += 1) {
46+
const msg = messages[i];
2947
if (!isAssistantMessageWithContent(msg)) {
3048
out.push(msg);
3149
continue;
3250
}
51+
if (i === latestAssistantIndex) {
52+
out.push(msg);
53+
continue;
54+
}
3355
const nextContent: AssistantContentBlock[] = [];
3456
let changed = false;
3557
for (const block of msg.content) {
36-
if (block && typeof block === "object" && (block as { type?: unknown }).type === "thinking") {
58+
if (isThinkingBlock(block)) {
3759
touched = true;
3860
changed = true;
3961
continue;

src/agents/pi-hooks/context-pruning/pruner.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,16 @@ function estimateMessageChars(message: AgentMessage): number {
146146
if (b.type === "text" && typeof b.text === "string") {
147147
chars += estimateWeightedTextChars(b.text);
148148
}
149-
if (b.type === "thinking" && typeof b.thinking === "string") {
150-
chars += estimateWeightedTextChars(b.thinking);
149+
const blockType = (b as { type?: unknown }).type;
150+
if (blockType === "thinking" || blockType === "redacted_thinking") {
151+
const thinking = (b as { thinking?: unknown }).thinking;
152+
if (typeof thinking === "string") {
153+
chars += estimateWeightedTextChars(thinking);
154+
}
155+
const signature = (b as { thinkingSignature?: unknown }).thinkingSignature;
156+
if (typeof signature === "string") {
157+
chars += estimateWeightedTextChars(signature);
158+
}
151159
}
152160
if (b.type === "toolCall") {
153161
try {

0 commit comments

Comments
 (0)