fix: preserve thinking turns during transcript repair

This commit is contained in:
Shakker
2026-04-12 03:35:25 +01:00
committed by Shakker
parent 3704069c3f
commit 2d1f4af67a
2 changed files with 50 additions and 0 deletions

View File

@@ -380,6 +380,40 @@ describe("sanitizeToolCallInputs", () => {
expect(types).toEqual(["text", "toolUse"]);
});
it("preserves assistant turns that include thinking blocks", () => {
const input = castAgentMessages([
{
role: "assistant",
content: [
{
type: "thinking",
thinking: "Let me check the gateway config.",
thinkingSignature: "sig_gateway",
},
{
type: "toolCall",
id: "call_gateway",
name: "gateway",
arguments: {
action: "config.get",
path: "channels.telegram",
},
},
],
},
]);
const out = sanitizeToolCallInputs(input, { allowedToolNames: ["read"] });
expect(out).toBe(input);
const assistant = out[0] as Extract<AgentMessage, { role: "assistant" }>;
const types = Array.isArray(assistant.content)
? assistant.content.map((block) => (block as { type?: unknown }).type)
: [];
expect(types).toEqual(["thinking", "toolCall"]);
expect((assistant.content?.[1] as { name?: unknown })?.name).toBe("gateway");
});
it.each([
{
name: "trims leading whitespace from tool names",

View File

@@ -17,6 +17,14 @@ type RawToolCallBlock = {
arguments?: unknown;
};
function isThinkingLikeBlock(block: unknown): boolean {
if (!block || typeof block !== "object") {
return false;
}
const type = (block as { type?: unknown }).type;
return type === "thinking" || type === "redacted_thinking";
}
function isRawToolCallBlock(block: unknown): block is RawToolCallBlock {
if (!block || typeof block !== "object") {
return false;
@@ -239,6 +247,14 @@ export function repairToolCallInputs(
continue;
}
// Preserve provider-owned thinking turns verbatim. Anthropic replays can
// reject any historical assistant turn whose signed thinking block no
// longer matches the original response, including sibling tool calls.
if (msg.content.some((block) => isThinkingLikeBlock(block))) {
out.push(msg);
continue;
}
const nextContent: typeof msg.content = [];
let droppedInMessage = 0;
let messageChanged = false;