|
|
@@ -482,6 +482,96 @@ describe("validateAndFixToolResultIds", () => {
|
|
|
expect(resultContent[1].type).toBe("text")
|
|
|
expect((resultContent[1] as Anthropic.TextBlockParam).text).toBe("Some additional context")
|
|
|
})
|
|
|
+
|
|
|
+ // Verifies fix for GitHub #10465: Terminal fallback race condition can generate
|
|
|
+ // duplicate tool_results with the same valid tool_use_id, causing API protocol violations.
|
|
|
+ it("should filter out duplicate tool_results with identical valid tool_use_ids (terminal fallback scenario)", () => {
|
|
|
+ const assistantMessage: Anthropic.MessageParam = {
|
|
|
+ role: "assistant",
|
|
|
+ content: [
|
|
|
+ {
|
|
|
+ type: "tool_use",
|
|
|
+ id: "tooluse_QZ-pU8v2QKO8L8fHoJRI2g",
|
|
|
+ name: "execute_command",
|
|
|
+ input: { command: "ps aux | grep test", cwd: "/path/to/project" },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ }
|
|
|
+
|
|
|
+ // Two tool_results with the SAME valid tool_use_id from terminal fallback race condition
|
|
|
+ const userMessage: Anthropic.MessageParam = {
|
|
|
+ role: "user",
|
|
|
+ content: [
|
|
|
+ {
|
|
|
+ type: "tool_result",
|
|
|
+ tool_use_id: "tooluse_QZ-pU8v2QKO8L8fHoJRI2g", // First result from command execution
|
|
|
+ content: "No test processes found",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: "tool_result",
|
|
|
+ tool_use_id: "tooluse_QZ-pU8v2QKO8L8fHoJRI2g", // Duplicate from user approval during fallback
|
|
|
+ content: '{"status":"approved","message":"The user approved this operation"}',
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ }
|
|
|
+
|
|
|
+ const result = validateAndFixToolResultIds(userMessage, [assistantMessage])
|
|
|
+
|
|
|
+ expect(Array.isArray(result.content)).toBe(true)
|
|
|
+ const resultContent = result.content as Anthropic.ToolResultBlockParam[]
|
|
|
+
|
|
|
+ // Only ONE tool_result should remain to prevent API protocol violation
|
|
|
+ expect(resultContent.length).toBe(1)
|
|
|
+ expect(resultContent[0].tool_use_id).toBe("tooluse_QZ-pU8v2QKO8L8fHoJRI2g")
|
|
|
+ expect(resultContent[0].content).toBe("No test processes found")
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should preserve text blocks while deduplicating tool_results with same valid ID", () => {
|
|
|
+ const assistantMessage: Anthropic.MessageParam = {
|
|
|
+ role: "assistant",
|
|
|
+ content: [
|
|
|
+ {
|
|
|
+ type: "tool_use",
|
|
|
+ id: "tool-123",
|
|
|
+ name: "read_file",
|
|
|
+ input: { path: "test.txt" },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ }
|
|
|
+
|
|
|
+ const userMessage: Anthropic.MessageParam = {
|
|
|
+ role: "user",
|
|
|
+ content: [
|
|
|
+ {
|
|
|
+ type: "tool_result",
|
|
|
+ tool_use_id: "tool-123",
|
|
|
+ content: "First result",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: "text",
|
|
|
+ text: "Environment details here",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: "tool_result",
|
|
|
+ tool_use_id: "tool-123", // Duplicate with same valid ID
|
|
|
+ content: "Duplicate result from fallback",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ }
|
|
|
+
|
|
|
+ const result = validateAndFixToolResultIds(userMessage, [assistantMessage])
|
|
|
+
|
|
|
+ expect(Array.isArray(result.content)).toBe(true)
|
|
|
+ const resultContent = result.content as Array<Anthropic.ToolResultBlockParam | Anthropic.TextBlockParam>
|
|
|
+
|
|
|
+ // Should have: 1 tool_result + 1 text block (duplicate filtered out)
|
|
|
+ expect(resultContent.length).toBe(2)
|
|
|
+ expect(resultContent[0].type).toBe("tool_result")
|
|
|
+ expect((resultContent[0] as Anthropic.ToolResultBlockParam).tool_use_id).toBe("tool-123")
|
|
|
+ expect((resultContent[0] as Anthropic.ToolResultBlockParam).content).toBe("First result")
|
|
|
+ expect(resultContent[1].type).toBe("text")
|
|
|
+ expect((resultContent[1] as Anthropic.TextBlockParam).text).toBe("Environment details here")
|
|
|
+ })
|
|
|
})
|
|
|
|
|
|
describe("when there are more tool_uses than tool_results", () => {
|