diff --git a/src/agents/session-transcript-repair.test.ts b/src/agents/session-transcript-repair.test.ts index ccc63ec7f..b4effd736 100644 --- a/src/agents/session-transcript-repair.test.ts +++ b/src/agents/session-transcript-repair.test.ts @@ -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); + }); }); diff --git a/src/agents/session-transcript-repair.ts b/src/agents/session-transcript-repair.ts index 2728bb89c..406824eaf 100644 --- a/src/agents/session-transcript-repair.ts +++ b/src/agents/session-transcript-repair.ts @@ -8,6 +8,11 @@ type ToolCallLike = { function extractToolCallsFromAssistant( msg: Extract, ): 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 [];