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
21 changes: 18 additions & 3 deletions client/src/components/ToolCallMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
import React from "react";
import { Wrench, AlertTriangle, Clock } from "lucide-react";
import { Wrench, AlertTriangle, Clock, CheckCircle } from "lucide-react";
import { cn } from "@/lib/utils";

// Tool call message types
export interface ToolCallInfo {
type: "tool_call" | "tool_error" | "tool_warning";
type: "tool_call" | "tool_error" | "tool_warning" | "tool_result";
toolName: string;
args?: string | Record<string, unknown>;
error?: string;
message?: string;
result?: string;
}

// Tool call message component
export const ToolCallMessage: React.FC<{ toolCall: ToolCallInfo }> = ({
toolCall,
}) => {
const { type, toolName, args, error, message } = toolCall;
const { type, toolName, args, error, message, result } = toolCall;

const getIcon = () => {
switch (type) {
case "tool_call":
return <Wrench className="w-3 h-3" />;
case "tool_result":
return <CheckCircle className="w-3 h-3" />;
case "tool_error":
return <AlertTriangle className="w-3 h-3" />;
case "tool_warning":
Expand All @@ -34,6 +37,8 @@ export const ToolCallMessage: React.FC<{ toolCall: ToolCallInfo }> = ({
switch (type) {
case "tool_call":
return "bg-blue-50 dark:bg-blue-950/30 border-blue-200 dark:border-blue-800 text-blue-800 dark:text-blue-300";
case "tool_result":
return "bg-green-50 dark:bg-green-950/30 border-green-200 dark:border-green-800 text-green-800 dark:text-green-300";
case "tool_error":
return "bg-red-50 dark:bg-red-950/30 border-red-200 dark:border-red-800 text-red-800 dark:text-red-300";
case "tool_warning":
Expand Down Expand Up @@ -64,6 +69,7 @@ export const ToolCallMessage: React.FC<{ toolCall: ToolCallInfo }> = ({
{getIcon()}
<span className="font-semibold">
{type === "tool_call" && `Calling ${toolName}`}
{type === "tool_result" && `${toolName} result`}
{type === "tool_error" && `${toolName} failed`}
{type === "tool_warning" && "Warning"}
</span>
Expand Down Expand Up @@ -94,6 +100,15 @@ export const ToolCallMessage: React.FC<{ toolCall: ToolCallInfo }> = ({
</div>
</div>
)}

{type === "tool_result" && result && (
<div className="mt-2">
<div className="text-xs opacity-75 mb-1">Result:</div>
<pre className="text-xs bg-black/10 dark:bg-white/10 rounded p-2 overflow-x-auto whitespace-pre-wrap">
{result}
</pre>
</div>
)}
</div>
);
};
Expand Down
21 changes: 14 additions & 7 deletions client/src/lib/chatLoop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,12 @@ export class QueryProcessor {
tools: AnthropicTool[],
onUpdate?: (content: string) => void,
model: string = "claude-3-5-sonnet-latest",
provider?: string,
provider?: SupportedProvider,
signal?: AbortSignal,
): Promise<string> {
// Get the specified provider or fall back to default
const aiProvider = provider
? providerManager.getProvider(provider as SupportedProvider)
? providerManager.getProvider(provider)
: providerManager.getDefaultProvider();

if (!aiProvider) {
Expand Down Expand Up @@ -337,12 +337,16 @@ export class QueryProcessor {

try {
this.toolCaller.addClientLog(`Executing tool: ${content.name}`, "debug");
await this.executeToolAndUpdateMessages(
const toolResultMessage = await this.executeToolAndUpdateMessages(
content,
context,
assistantContent,
signal,
);
// Add the tool result to iteration content for real-time display
if (toolResultMessage) {
iterationContent.push(toolResultMessage);
}
this.toolCaller.addClientLog(`Tool execution successful: ${content.name}`, "debug");
} catch (error) {
this.toolCaller.addClientLog(
Expand All @@ -368,7 +372,7 @@ export class QueryProcessor {
context: ReturnType<typeof this.initializeQueryContext>,
assistantContent: ContentBlock[],
signal?: AbortSignal,
) {
): Promise<string> {
// Check if aborted before tool execution
if (signal?.aborted) {
throw new Error("Chat was cancelled");
Expand Down Expand Up @@ -404,12 +408,18 @@ export class QueryProcessor {
resultContent = typeof result === 'string' ? result : JSON.stringify(result);
}

// Add tool result to the displayed text (for user to see)
const toolResultMessage = `[Tool ${content.name} result: ${resultContent}]`;
context.finalText.push(toolResultMessage);

this.addMessagesToContext(
context,
assistantContent,
content.id,
resultContent,
);

return toolResultMessage;
}

private handleToolError(
Expand Down Expand Up @@ -537,9 +547,6 @@ export class ChatLoop {
});

try {
console.log("\nMCP Client Started!");
console.log("Type your queries or 'quit' to exit.");

while (true) {
const message = await rl.question("\nQuery: ");
if (message.toLowerCase() === "quit") {
Expand Down
3 changes: 2 additions & 1 deletion client/src/mcpjamAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { ClientLogLevels } from "./hooks/helpers/types";
import { ElicitationResponse } from "./components/ElicitationModal";
import { ChatLoopProvider, ChatLoop, mappedTools, QueryProcessor, ToolCaller } from "./lib/chatLoop";
import { Tool as AnthropicTool } from "@anthropic-ai/sdk/resources/messages/messages.mjs";
import { SupportedProvider } from "./lib/providers";

export interface MCPClientOptions {
id?: string;
Expand Down Expand Up @@ -473,7 +474,7 @@ export class MCPJamAgent implements ChatLoopProvider, ToolCaller {
tools: AnthropicTool[],
onUpdate?: (content: string) => void,
model: string = "claude-3-5-sonnet-latest",
provider?: string,
provider?: SupportedProvider,
signal?: AbortSignal,
): Promise<string> {
return this.queryProcessor.processQuery(query, tools, onUpdate, model, provider, signal);
Expand Down
3 changes: 2 additions & 1 deletion client/src/mcpjamClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
import {
AIProvider,
providerManager,
SupportedProvider,
} from "@/lib/providers";
import {
Tool,
Expand Down Expand Up @@ -715,7 +716,7 @@ export class MCPJamClient extends Client<Request, Notification, Result> implemen
tools: Tool[],
onUpdate?: (content: string) => void,
model: string = "claude-3-5-sonnet-latest",
provider?: string,
provider?: SupportedProvider,
signal?: AbortSignal,
): Promise<string> {
return this.queryProcessor.processQuery(query, tools, onUpdate, model, provider, signal);
Expand Down
12 changes: 12 additions & 0 deletions client/src/utils/toolCallHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ export const parseToolCallContent = (content: string): ParsedContent => {
cleanText = cleanText.replace(fullMatch, "").trim();
}

// Pattern for tool results: [Tool TOOL_NAME result: RESULT]
const toolResultPattern = /\[Tool (\w+) result: ([\s\S]*?)\](?=\s*(?:\n|$|\[(?:Tool|Warning|Calling)))/g;
while ((match = toolResultPattern.exec(content)) !== null) {
const [fullMatch, toolName, result] = match;
toolCalls.push({
type: "tool_result",
toolName,
result: result.trim(),
});
cleanText = cleanText.replace(fullMatch, "").trim();
}

// Pattern for tool errors: [Tool TOOL_NAME failed: ERROR]
// Handle complex multi-line errors with nested structures
const toolErrorPattern =
Expand Down