From 84b1df03b0691b583aa4ff41c40dd8cd3baacfab Mon Sep 17 00:00:00 2001 From: Kalvin C Date: Thu, 6 Mar 2025 11:32:55 -0800 Subject: [PATCH] fix: respond to interrupted tool calls with a ToolResponseMessageContent (#1557) --- ui/desktop/src/components/ChatView.tsx | 71 ++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/ui/desktop/src/components/ChatView.tsx b/ui/desktop/src/components/ChatView.tsx index c1ed9269..b66a0657 100644 --- a/ui/desktop/src/components/ChatView.tsx +++ b/ui/desktop/src/components/ChatView.tsx @@ -15,7 +15,16 @@ import { askAi } from '../utils/askAI'; import Splash from './Splash'; import 'react-toastify/dist/ReactToastify.css'; import { useMessageStream } from '../hooks/useMessageStream'; -import { Message, createUserMessage, getTextContent } from '../types/message'; +import { + Message, + createUserMessage, + ToolCall, + ToolCallResult, + ToolRequestMessageContent, + ToolResponseMessageContent, + ToolConfirmationRequestMessageContent, + getTextContent, +} from '../types/message'; export interface ChatType { id: number; @@ -182,16 +191,70 @@ export default function ChatView({ // Handle stopping the message stream const lastMessage = messages[messages.length - 1]; - if (lastMessage && lastMessage.role === 'user') { + + // isUserMessage also checks if the message is a toolConfirmationRequest + if (lastMessage && isUserMessage(lastMessage)) { // Remove the last user message if it's the most recent one if (messages.length > 1) { setMessages(messages.slice(0, -1)); } else { setMessages([]); } + } else if (!isUserMessage(lastMessage)) { + // check if we have any tool requests or tool confirmation requests + const toolRequests: [string, ToolCallResult][] = lastMessage.content + .filter( + (content): content is ToolRequestMessageContent | ToolConfirmationRequestMessageContent => + content.type === 'toolRequest' || content.type === 'toolConfirmationRequest' + ) + .map((content) => { + if (content.type === 'toolRequest') { + return [content.id, content.toolCall]; + } else { + // extract tool call from confirmation + const toolCall: ToolCallResult = { + status: 'success', + value: { + name: content.toolName, + arguments: content.arguments, + }, + }; + return [content.id, toolCall]; + } + }); + + if (toolRequests.length !== 0) { + // This means we were interrupted during a tool request + // Create tool responses for all interrupted tool requests + + let responseMessage: Message = { + role: 'user', + created: Date.now(), + content: [], + }; + + // get the last tool's name or just "tool" + const lastToolName = toolRequests.at(-1)?.[1].value?.name ?? 'tool'; + const notification = 'Interrupted by the user to make a correction'; + + // generate a response saying it was interrupted for each tool request + for (const [reqId, _] of toolRequests) { + const toolResponse: ToolResponseMessageContent = { + type: 'toolResponse', + id: reqId, + toolResult: { + status: 'error', + error: notification, + }, + }; + + responseMessage.content.push(toolResponse); + } + + // Use an immutable update to add the response message to the messages array + setMessages([...messages, responseMessage]); + } } - // Note: Tool call interruption handling would need to be implemented - // differently with the new message format }; // Filter out standalone tool response messages for rendering