Skip to content

Commit fe85422

Browse files
committed
fix: sanitize tool_use_id in tool_result blocks to match API history
Tool IDs from providers like Gemini/OpenRouter contain special characters (e.g., 'functions.read_file:0') that are sanitized when saving tool_use blocks to API history. However, tool_result blocks were using the original unsanitized IDs, causing ToolResultIdMismatchError. This fix ensures tool_result blocks use sanitizeToolUseId() to match the sanitized tool_use IDs in conversation history. Fixes EXT-711
1 parent fa93109 commit fe85422

File tree

2 files changed

+16
-7
lines changed

2 files changed

+16
-7
lines changed

src/core/assistant-message/presentAssistantMessage.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { isValidToolName, validateToolUse } from "../tools/validateToolUse"
4040
import { codebaseSearchTool } from "../tools/CodebaseSearchTool"
4141

4242
import { formatResponse } from "../prompts/responses"
43+
import { sanitizeToolUseId } from "../../utils/tool-id"
4344

4445
/**
4546
* Processes and presents assistant message content to the user interface.
@@ -118,7 +119,7 @@ export async function presentAssistantMessage(cline: Task) {
118119
if (toolCallId) {
119120
cline.pushToolResultToUserContent({
120121
type: "tool_result",
121-
tool_use_id: toolCallId,
122+
tool_use_id: sanitizeToolUseId(toolCallId),
122123
content: errorMessage,
123124
is_error: true,
124125
})
@@ -169,7 +170,7 @@ export async function presentAssistantMessage(cline: Task) {
169170
if (toolCallId) {
170171
cline.pushToolResultToUserContent({
171172
type: "tool_result",
172-
tool_use_id: toolCallId,
173+
tool_use_id: sanitizeToolUseId(toolCallId),
173174
content: resultContent,
174175
})
175176

@@ -410,7 +411,7 @@ export async function presentAssistantMessage(cline: Task) {
410411

411412
cline.pushToolResultToUserContent({
412413
type: "tool_result",
413-
tool_use_id: toolCallId,
414+
tool_use_id: sanitizeToolUseId(toolCallId),
414415
content: errorMessage,
415416
is_error: true,
416417
})
@@ -447,7 +448,7 @@ export async function presentAssistantMessage(cline: Task) {
447448
// continue gracefully.
448449
cline.pushToolResultToUserContent({
449450
type: "tool_result",
450-
tool_use_id: toolCallId,
451+
tool_use_id: sanitizeToolUseId(toolCallId),
451452
content: formatResponse.toolError(errorMessage),
452453
is_error: true,
453454
})
@@ -493,7 +494,7 @@ export async function presentAssistantMessage(cline: Task) {
493494

494495
cline.pushToolResultToUserContent({
495496
type: "tool_result",
496-
tool_use_id: toolCallId,
497+
tool_use_id: sanitizeToolUseId(toolCallId),
497498
content: resultContent,
498499
})
499500

@@ -644,7 +645,7 @@ export async function presentAssistantMessage(cline: Task) {
644645
// Push tool_result directly without setting didAlreadyUseTool
645646
cline.pushToolResultToUserContent({
646647
type: "tool_result",
647-
tool_use_id: toolCallId,
648+
tool_use_id: sanitizeToolUseId(toolCallId),
648649
content: typeof errorContent === "string" ? errorContent : "(validation error)",
649650
is_error: true,
650651
})
@@ -947,7 +948,7 @@ export async function presentAssistantMessage(cline: Task) {
947948
// This prevents the stream from being interrupted with "Response interrupted by tool use result"
948949
cline.pushToolResultToUserContent({
949950
type: "tool_result",
950-
tool_use_id: toolCallId,
951+
tool_use_id: sanitizeToolUseId(toolCallId),
951952
content: formatResponse.toolError(errorMessage),
952953
is_error: true,
953954
})

src/utils/__tests__/tool-id.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ describe("sanitizeToolUseId", () => {
4747
it("should replace multiple invalid characters", () => {
4848
expect(sanitizeToolUseId("mcp.server:tool/name")).toBe("mcp_server_tool_name")
4949
})
50+
51+
it("should sanitize Gemini/OpenRouter function call IDs with dots and colons", () => {
52+
// This is the exact pattern seen in PostHog errors where tool_result IDs
53+
// didn't match tool_use IDs due to missing sanitization
54+
expect(sanitizeToolUseId("functions.read_file:0")).toBe("functions_read_file_0")
55+
expect(sanitizeToolUseId("functions.write_to_file:1")).toBe("functions_write_to_file_1")
56+
expect(sanitizeToolUseId("read_file:0")).toBe("read_file_0")
57+
})
5058
})
5159

5260
describe("real-world MCP tool use ID patterns", () => {

0 commit comments

Comments
 (0)