Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions src/core/assistant-message/presentAssistantMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { isValidToolName, validateToolUse } from "../tools/validateToolUse"
import { codebaseSearchTool } from "../tools/CodebaseSearchTool"

import { formatResponse } from "../prompts/responses"
import { sanitizeToolUseId } from "../../utils/tool-id"

/**
* Processes and presents assistant message content to the user interface.
Expand Down Expand Up @@ -118,7 +119,7 @@ export async function presentAssistantMessage(cline: Task) {
if (toolCallId) {
cline.pushToolResultToUserContent({
type: "tool_result",
tool_use_id: toolCallId,
tool_use_id: sanitizeToolUseId(toolCallId),
content: errorMessage,
is_error: true,
})
Expand Down Expand Up @@ -169,7 +170,7 @@ export async function presentAssistantMessage(cline: Task) {
if (toolCallId) {
cline.pushToolResultToUserContent({
type: "tool_result",
tool_use_id: toolCallId,
tool_use_id: sanitizeToolUseId(toolCallId),
content: resultContent,
})

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

cline.pushToolResultToUserContent({
type: "tool_result",
tool_use_id: toolCallId,
tool_use_id: sanitizeToolUseId(toolCallId),
content: errorMessage,
is_error: true,
})
Expand Down Expand Up @@ -447,7 +448,7 @@ export async function presentAssistantMessage(cline: Task) {
// continue gracefully.
cline.pushToolResultToUserContent({
type: "tool_result",
tool_use_id: toolCallId,
tool_use_id: sanitizeToolUseId(toolCallId),
content: formatResponse.toolError(errorMessage),
is_error: true,
})
Expand Down Expand Up @@ -493,7 +494,7 @@ export async function presentAssistantMessage(cline: Task) {

cline.pushToolResultToUserContent({
type: "tool_result",
tool_use_id: toolCallId,
tool_use_id: sanitizeToolUseId(toolCallId),
content: resultContent,
})

Expand Down Expand Up @@ -644,7 +645,7 @@ export async function presentAssistantMessage(cline: Task) {
// Push tool_result directly without setting didAlreadyUseTool
cline.pushToolResultToUserContent({
type: "tool_result",
tool_use_id: toolCallId,
tool_use_id: sanitizeToolUseId(toolCallId),
content: typeof errorContent === "string" ? errorContent : "(validation error)",
is_error: true,
})
Expand Down Expand Up @@ -947,7 +948,7 @@ export async function presentAssistantMessage(cline: Task) {
// This prevents the stream from being interrupted with "Response interrupted by tool use result"
cline.pushToolResultToUserContent({
type: "tool_result",
tool_use_id: toolCallId,
tool_use_id: sanitizeToolUseId(toolCallId),
content: formatResponse.toolError(errorMessage),
is_error: true,
})
Expand Down
8 changes: 8 additions & 0 deletions src/utils/__tests__/tool-id.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ describe("sanitizeToolUseId", () => {
it("should replace multiple invalid characters", () => {
expect(sanitizeToolUseId("mcp.server:tool/name")).toBe("mcp_server_tool_name")
})

it("should sanitize Gemini/OpenRouter function call IDs with dots and colons", () => {
// This is the exact pattern seen in PostHog errors where tool_result IDs
// didn't match tool_use IDs due to missing sanitization
expect(sanitizeToolUseId("functions.read_file:0")).toBe("functions_read_file_0")
expect(sanitizeToolUseId("functions.write_to_file:1")).toBe("functions_write_to_file_1")
expect(sanitizeToolUseId("read_file:0")).toBe("read_file_0")
})
})

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