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
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@ export const StreamMessage = () => {
[streamMessage?.role]
);

if (!isWaiting) {
return null;
}

return (
<>
{streamMessage && isAssistantStreamMessage && streamMessage.parts && isWaiting && (
{streamMessage && isAssistantStreamMessage && (
<div className="px-4 py-2 text-small content-start flex flex-col text-wrap gap-2">
<MessageContent
messageId={streamMessage.id}
Expand All @@ -24,13 +28,10 @@ export const StreamMessage = () => {
/>
</div>
)}
{isWaiting && (
<div className="flex w-full h-full flex-row items-center gap-2 px-4 my-2 text-small content-start text-foreground-secondary">
<Icons.LoadingSpinner className="animate-spin" />
<p>Thinking ...</p>
</div>
)}

<div className="flex w-full h-full flex-row items-center gap-2 px-4 my-2 text-small content-start text-foreground-secondary">
<Icons.LoadingSpinner className="animate-spin" />
<p>Thinking ...</p>
</div>
</>
);
};
31 changes: 20 additions & 11 deletions apps/web/client/src/components/tools/handlers/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ export async function handleSearchReplaceEditFileTool(args: z.infer<typeof SEARC
if (args.replace_all) {
newContent = file.content.replaceAll(args.old_string, args.new_string);
} else {
if (!file.content.includes(args.old_string)) {
const firstIndex = file.content.indexOf(args.old_string);
if (firstIndex === -1) {
throw new Error(`String not found in file: ${args.old_string}`);
}

const occurrences = file.content.split(args.old_string).length - 1;
if (occurrences > 1) {
const secondIndex = file.content.indexOf(args.old_string, firstIndex + args.old_string.length);
if (secondIndex !== -1) {
throw new Error(`Multiple occurrences found. Use replace_all=true or provide more context.`);
}

Expand Down Expand Up @@ -57,21 +58,29 @@ export async function handleSearchReplaceMultiEditFileTool(args: z.infer<typeof
throw new Error(`Cannot read file ${args.file_path}: file not found or not text`);
}

let content = file.content;
const originalContent = file.content;
let content = originalContent;

// Validate all edits first to avoid partial application
for (const edit of args.edits) {
if (edit.replace_all) {
content = content.replaceAll(edit.old_string, edit.new_string);
} else {
if (!content.includes(edit.old_string)) {
if (!edit.replace_all) {
const firstIndex = originalContent.indexOf(edit.old_string);
if (firstIndex === -1) {
throw new Error(`String not found in file: ${edit.old_string}`);
}

const occurrences = content.split(edit.old_string).length - 1;
if (occurrences > 1) {
const secondIndex = originalContent.indexOf(edit.old_string, firstIndex + edit.old_string.length);
if (secondIndex !== -1) {
throw new Error(`Multiple occurrences found for "${edit.old_string}". Use replace_all=true or provide more context.`);
}
}
}

// Apply edits after validation
for (const edit of args.edits) {
if (edit.replace_all) {
content = content.replaceAll(edit.old_string, edit.new_string);
} else {
content = content.replace(edit.old_string, edit.new_string);
}
}
Expand All @@ -83,7 +92,7 @@ export async function handleSearchReplaceMultiEditFileTool(args: z.infer<typeof

return `File ${args.file_path} edited with ${args.edits.length} changes`;
} catch (error) {
throw new Error(`Cannot multi-edit file ${args.file_path}: ${error}`);
throw new Error(`Cannot multi-edit file ${args.file_path}: ${(error as Error).message}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider standardizing error messaging. The multi-edit catch block uses (error as Error).message while the single-edit version interpolates the error directly.

}
}

Expand Down
22 changes: 15 additions & 7 deletions apps/web/client/src/components/tools/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import {
SCRAPE_URL_TOOL_PARAMETERS,
SEARCH_REPLACE_EDIT_FILE_TOOL_NAME,
SEARCH_REPLACE_EDIT_FILE_TOOL_PARAMETERS,
SEARCH_REPLACE_MULTI_EDIT_FILE_TOOL_NAME,
SEARCH_REPLACE_MULTI_EDIT_FILE_TOOL_PARAMETERS,
TERMINAL_COMMAND_TOOL_NAME,
TERMINAL_COMMAND_TOOL_PARAMETERS,
TYPECHECK_TOOL_NAME,
Expand All @@ -53,6 +55,7 @@ import {
handleSandboxTool,
handleScrapeUrlTool,
handleSearchReplaceEditFileTool,
handleSearchReplaceMultiEditFileTool,
handleTerminalCommandTool,
handleTypecheckTool,
handleWebSearchTool,
Expand Down Expand Up @@ -120,6 +123,12 @@ const TOOL_HANDLERS: ClientToolMap = {
handler: async (args: z.infer<typeof SEARCH_REPLACE_EDIT_FILE_TOOL_PARAMETERS>, editorEngine: EditorEngine) =>
handleSearchReplaceEditFileTool(args, editorEngine),
},
[SEARCH_REPLACE_MULTI_EDIT_FILE_TOOL_NAME]: {
name: SEARCH_REPLACE_MULTI_EDIT_FILE_TOOL_NAME,
inputSchema: SEARCH_REPLACE_MULTI_EDIT_FILE_TOOL_PARAMETERS,
handler: async (args: z.infer<typeof SEARCH_REPLACE_MULTI_EDIT_FILE_TOOL_PARAMETERS>, editorEngine: EditorEngine) =>
handleSearchReplaceMultiEditFileTool(args, editorEngine),
},
[WRITE_FILE_TOOL_NAME]: {
name: WRITE_FILE_TOOL_NAME,
inputSchema: WRITE_FILE_TOOL_PARAMETERS,
Expand Down Expand Up @@ -181,6 +190,8 @@ export function handleToolCall(toolCall: ToolCall<string, unknown>, editorEngine
const toolName = toolCall.toolName;
const currentChatMode = editorEngine.state.chatMode;
const availableTools = getToolSetFromType(currentChatMode);
let output: any = null;

try {
if (!availableTools[toolName]) {
toast.error(`Tool "${toolName}" not available in ask mode`, {
Expand All @@ -196,17 +207,14 @@ export function handleToolCall(toolCall: ToolCall<string, unknown>, editorEngine
if (!clientTool) {
throw new Error(`Unknown tool call: ${toolName}`);
}
const result = await clientTool.handler(toolCall.input, editorEngine);
addToolResult({
tool: toolName,
toolCallId: toolCall.toolCallId,
output: result,
});
output = await clientTool.handler(toolCall.input, editorEngine);
} catch (error) {
output = 'error handling tool call ' + error;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the handleToolCall catch block, consider extracting the error message (e.g. using (error as Error).message) for clearer error reporting.

Suggested change
output = 'error handling tool call ' + error;
output = 'error handling tool call ' + ((error instanceof Error) ? error.message : error);

} finally {
addToolResult({
tool: toolName,
toolCallId: toolCall.toolCallId,
output: 'error handling tool call ' + error,
output: output,
});
}
}
Expand Down
Loading