fix: skip tool calls from aborted assistant messages in transcript repair
When a streaming response is aborted mid-tool-call, the tool call may be incomplete (indicated by partialJson in the message). The transcript repair logic was still extracting tool calls from these aborted messages and inserting synthetic tool_result blocks for them. When sent to Anthropic API, the aborted tool_use block may be invalid/incomplete, causing the API to reject the request with: 'unexpected tool_use_id found in tool_result blocks' This permanently corrupts the session until manually repaired. Fix: Check for stopReason === 'aborted' in extractToolCallsFromAssistant and return early with an empty array, skipping synthetic result insertion. Fixes #4475
This commit is contained in:
parent
6af205a13a
commit
c9939063f5
@ -109,4 +109,38 @@ describe("sanitizeToolUseResultPairing", () => {
|
||||
expect(out.some((m) => m.role === "toolResult")).toBe(false);
|
||||
expect(out.map((m) => m.role)).toEqual(["user", "assistant"]);
|
||||
});
|
||||
|
||||
it("skips tool calls from aborted assistant messages", () => {
|
||||
// When a streaming response is aborted mid-tool-call, the tool call may be incomplete.
|
||||
// Inserting synthetic results for aborted tool calls causes API validation failures
|
||||
// because the original tool_use block may be malformed.
|
||||
const input = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "toolCall", id: "call_aborted", name: "process", arguments: {} }],
|
||||
stopReason: "aborted",
|
||||
},
|
||||
{ role: "user", content: "next message" },
|
||||
] as AgentMessage[];
|
||||
|
||||
const out = sanitizeToolUseResultPairing(input);
|
||||
// Should NOT insert a synthetic tool result for the aborted tool call
|
||||
expect(out.some((m) => m.role === "toolResult")).toBe(false);
|
||||
expect(out.map((m) => m.role)).toEqual(["assistant", "user"]);
|
||||
});
|
||||
|
||||
it("processes tool calls normally when stopReason is not aborted", () => {
|
||||
const input = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "toolCall", id: "call_1", name: "read", arguments: {} }],
|
||||
stopReason: "toolUse",
|
||||
},
|
||||
{ role: "user", content: "next" },
|
||||
] as AgentMessage[];
|
||||
|
||||
const out = sanitizeToolUseResultPairing(input);
|
||||
// Should insert synthetic result for the missing tool result
|
||||
expect(out.filter((m) => m.role === "toolResult")).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
@ -8,6 +8,11 @@ type ToolCallLike = {
|
||||
function extractToolCallsFromAssistant(
|
||||
msg: Extract<AgentMessage, { role: "assistant" }>,
|
||||
): ToolCallLike[] {
|
||||
// Skip extracting tool calls from aborted messages - they may be incomplete
|
||||
// and inserting synthetic results for them causes API validation failures
|
||||
const stopReason = (msg as { stopReason?: unknown }).stopReason;
|
||||
if (stopReason === "aborted") return [];
|
||||
|
||||
const content = msg.content;
|
||||
if (!Array.isArray(content)) return [];
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user