mirror of https://github.com/kortix-ai/suna.git
wip
This commit is contained in:
parent
405095a463
commit
71b185b659
|
@ -133,21 +133,39 @@ interface StreamingToolCall {
|
||||||
function getMetadataField(metadata: any, field: string, defaultValue: any = null): any {
|
function getMetadataField(metadata: any, field: string, defaultValue: any = null): any {
|
||||||
if (!metadata) return defaultValue;
|
if (!metadata) return defaultValue;
|
||||||
|
|
||||||
|
// Debug the type of metadata
|
||||||
|
console.log(`[METADATA DEBUG] Type: ${typeof metadata}, Field: ${field}`);
|
||||||
|
|
||||||
// If it's already an object
|
// If it's already an object
|
||||||
if (typeof metadata === 'object' && !Array.isArray(metadata)) {
|
if (typeof metadata === 'object' && !Array.isArray(metadata)) {
|
||||||
return metadata[field] !== undefined ? metadata[field] : defaultValue;
|
// Log the metadata object to see its structure
|
||||||
|
console.log('[METADATA DEBUG] Object metadata:', metadata);
|
||||||
|
|
||||||
|
// Check if the field exists directly in the object
|
||||||
|
if (metadata[field] !== undefined) {
|
||||||
|
console.log(`[METADATA DEBUG] Found field ${field} in object:`, metadata[field]);
|
||||||
|
return metadata[field];
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's a string, try to parse it
|
// If it's a string, try to parse it
|
||||||
if (typeof metadata === 'string') {
|
if (typeof metadata === 'string') {
|
||||||
try {
|
try {
|
||||||
|
console.log('[METADATA DEBUG] Attempting to parse string metadata:', metadata);
|
||||||
const parsed = JSON.parse(metadata);
|
const parsed = JSON.parse(metadata);
|
||||||
return parsed[field] !== undefined ? parsed[field] : defaultValue;
|
if (parsed[field] !== undefined) {
|
||||||
|
console.log(`[METADATA DEBUG] Found field ${field} in parsed string:`, parsed[field]);
|
||||||
|
return parsed[field];
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.log('[METADATA DEBUG] Error parsing metadata string:', e);
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('[METADATA DEBUG] Metadata is neither object nor string, returning default value');
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,14 +342,20 @@ function renderAttachments(
|
||||||
|
|
||||||
// Restore the safeJsonParse function with improved typing
|
// Restore the safeJsonParse function with improved typing
|
||||||
function safeJsonParse<T>(jsonString: any, defaultValue: T): T {
|
function safeJsonParse<T>(jsonString: any, defaultValue: T): T {
|
||||||
|
// Debug the input
|
||||||
|
console.log(`[JSON PARSE DEBUG] Type: ${typeof jsonString}`);
|
||||||
|
|
||||||
if (typeof jsonString !== 'string') {
|
if (typeof jsonString !== 'string') {
|
||||||
// If it's already an object, just return it
|
// If it's already an object, just return it
|
||||||
|
console.log('[JSON PARSE DEBUG] Already an object, returning as-is');
|
||||||
return jsonString as unknown as T;
|
return jsonString as unknown as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log('[JSON PARSE DEBUG] Attempting to parse string:', jsonString.substring(0, 50) + (jsonString.length > 50 ? '...' : ''));
|
||||||
return JSON.parse(jsonString) as T;
|
return JSON.parse(jsonString) as T;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.log('[JSON PARSE DEBUG] Error parsing JSON string:', e);
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -902,13 +926,24 @@ export default function ThreadPage({
|
||||||
|
|
||||||
// Automatically detect and populate tool calls from messages
|
// Automatically detect and populate tool calls from messages
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
console.log('[TOOL CALLS] Starting tool calls detection');
|
||||||
|
|
||||||
// Calculate historical tool pairs regardless of panel state
|
// Calculate historical tool pairs regardless of panel state
|
||||||
const historicalToolPairs: ToolCallInput[] = [];
|
const historicalToolPairs: ToolCallInput[] = [];
|
||||||
const assistantMessages = messages.filter(
|
const assistantMessages = messages.filter(
|
||||||
(m) => m.type === 'assistant' && m.message_id,
|
(m) => m.type === 'assistant' && m.message_id,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log(`[TOOL CALLS] Found ${assistantMessages.length} assistant messages`);
|
||||||
|
|
||||||
assistantMessages.forEach((assistantMsg) => {
|
assistantMessages.forEach((assistantMsg) => {
|
||||||
|
console.log('[TOOL CALLS] Processing assistant message:', assistantMsg.message_id);
|
||||||
|
console.log('[TOOL CALLS] Assistant message content type:', typeof assistantMsg.content);
|
||||||
|
console.log('[TOOL CALLS] Assistant message content preview:',
|
||||||
|
typeof assistantMsg.content === 'string'
|
||||||
|
? assistantMsg.content.substring(0, 50)
|
||||||
|
: JSON.stringify(assistantMsg.content).substring(0, 50));
|
||||||
|
|
||||||
const resultMessage = messages.find((toolMsg) => {
|
const resultMessage = messages.find((toolMsg) => {
|
||||||
if (
|
if (
|
||||||
toolMsg.type !== 'tool' ||
|
toolMsg.type !== 'tool' ||
|
||||||
|
@ -917,34 +952,82 @@ export default function ThreadPage({
|
||||||
)
|
)
|
||||||
return false;
|
return false;
|
||||||
const metadata = getMetadataField(toolMsg.metadata, 'assistant_message_id');
|
const metadata = getMetadataField(toolMsg.metadata, 'assistant_message_id');
|
||||||
return metadata === assistantMsg.message_id;
|
const matches = metadata === assistantMsg.message_id;
|
||||||
|
if (matches) {
|
||||||
|
console.log('[TOOL CALLS] Found matching tool message:', toolMsg.message_id);
|
||||||
|
}
|
||||||
|
return matches;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (resultMessage) {
|
if (resultMessage) {
|
||||||
// Determine tool name from assistant message content
|
console.log('[TOOL CALLS] Processing result message for assistant:', assistantMsg.message_id);
|
||||||
|
|
||||||
|
// Determine tool name from assistant message content or metadata
|
||||||
let toolName = 'unknown';
|
let toolName = 'unknown';
|
||||||
try {
|
try {
|
||||||
// Try to extract tool name from content
|
// First check if we can extract the tool type from parsing_details in metadata
|
||||||
const xmlMatch = assistantMsg.content.match(
|
if (resultMessage.metadata) {
|
||||||
/<([a-zA-Z\-_]+)(?:\s+[^>]*)?>|<([a-zA-Z\-_]+)(?:\s+[^>]*)?\/>/,
|
// Try to get from metadata parsing_details
|
||||||
);
|
const parsingDetails = getMetadataField(resultMessage.metadata, 'parsing_details');
|
||||||
if (xmlMatch) {
|
console.log('[TOOL CALLS] Parsing details:', parsingDetails);
|
||||||
toolName = xmlMatch[1] || xmlMatch[2] || 'unknown';
|
|
||||||
} else {
|
if (parsingDetails && parsingDetails.raw_chunk) {
|
||||||
// Fallback to checking for tool_calls JSON structure
|
// Extract from the raw XML chunk - this is the most reliable way
|
||||||
const assistantContentParsed = safeJsonParse<{
|
const xmlTagMatch = parsingDetails.raw_chunk.match(/<([a-zA-Z\-_]+)(?:\s+[^>]*)?>|<([a-zA-Z\-_]+)(?:\s+[^>]*)?\/>/);
|
||||||
tool_calls?: Array<{ name: string }>
|
if (xmlTagMatch) {
|
||||||
}>(assistantMsg.content, { tool_calls: [] });
|
toolName = xmlTagMatch[1] || xmlTagMatch[2] || 'unknown';
|
||||||
|
console.log('[TOOL CALLS] Extracted tool name from raw_chunk:', toolName);
|
||||||
if (
|
}
|
||||||
assistantContentParsed &&
|
|
||||||
assistantContentParsed.tool_calls &&
|
|
||||||
assistantContentParsed.tool_calls.length > 0
|
|
||||||
) {
|
|
||||||
toolName = assistantContentParsed.tool_calls[0].name || 'unknown';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {}
|
|
||||||
|
// If we couldn't get it from the metadata, try from the assistant message content
|
||||||
|
if (toolName === 'unknown' && typeof assistantMsg.content === 'string') {
|
||||||
|
console.log('[TOOL CALLS] Trying to extract tool name from assistant content');
|
||||||
|
|
||||||
|
// Try to extract tool name from content
|
||||||
|
const xmlMatch = assistantMsg.content.match(
|
||||||
|
/<([a-zA-Z\-_]+)(?:\s+[^>]*)?>|<([a-zA-Z\-_]+)(?:\s+[^>]*)?\/>/,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (xmlMatch) {
|
||||||
|
toolName = xmlMatch[1] || xmlMatch[2] || 'unknown';
|
||||||
|
console.log('[TOOL CALLS] Found XML tool name from assistant content:', toolName);
|
||||||
|
} else {
|
||||||
|
console.log('[TOOL CALLS] No XML tags found in assistant content, checking for JSON structure');
|
||||||
|
|
||||||
|
// Fallback to checking for tool_calls JSON structure
|
||||||
|
try {
|
||||||
|
const assistantContentParsed = safeJsonParse<{
|
||||||
|
tool_calls?: Array<{ name?: string; function?: { name?: string } }>
|
||||||
|
}>(assistantMsg.content, { tool_calls: [] });
|
||||||
|
|
||||||
|
console.log('[TOOL CALLS] Parsed assistant content:', assistantContentParsed);
|
||||||
|
|
||||||
|
if (
|
||||||
|
assistantContentParsed &&
|
||||||
|
assistantContentParsed.tool_calls &&
|
||||||
|
assistantContentParsed.tool_calls.length > 0
|
||||||
|
) {
|
||||||
|
// Try to get name directly or from function.name
|
||||||
|
const toolCall = assistantContentParsed.tool_calls[0];
|
||||||
|
toolName =
|
||||||
|
toolCall.name ||
|
||||||
|
(toolCall.function ? toolCall.function.name : null) ||
|
||||||
|
'unknown';
|
||||||
|
|
||||||
|
console.log('[TOOL CALLS] Found tool name from JSON:', toolName);
|
||||||
|
}
|
||||||
|
} catch (parseError) {
|
||||||
|
console.log('[TOOL CALLS] Error parsing assistant content as JSON:', parseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('[TOOL CALLS] Error extracting tool name:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[TOOL CALLS] Final determined tool name:', toolName);
|
||||||
|
|
||||||
// Skip adding <ask> tags to the tool calls
|
// Skip adding <ask> tags to the tool calls
|
||||||
if (toolName === 'ask' || toolName === 'complete') {
|
if (toolName === 'ask' || toolName === 'complete') {
|
||||||
|
@ -953,32 +1036,76 @@ export default function ThreadPage({
|
||||||
|
|
||||||
let isSuccess = true;
|
let isSuccess = true;
|
||||||
try {
|
try {
|
||||||
const toolContent = resultMessage.content?.toLowerCase() || '';
|
console.log('[TOOL CALLS] Processing tool result message');
|
||||||
isSuccess = !(
|
console.log('[TOOL CALLS] Content type:', typeof resultMessage.content);
|
||||||
toolContent.includes('failed') ||
|
|
||||||
toolContent.includes('error') ||
|
// Prepare content for historicalToolPairs
|
||||||
toolContent.includes('failure')
|
let assistantCallContent = '';
|
||||||
);
|
let toolResultContent = '';
|
||||||
} catch {}
|
|
||||||
|
// Process assistant content
|
||||||
|
if (typeof assistantMsg.content === 'string') {
|
||||||
|
assistantCallContent = assistantMsg.content;
|
||||||
|
} else if (assistantMsg.content && typeof assistantMsg.content === 'object') {
|
||||||
|
try {
|
||||||
|
assistantCallContent = JSON.stringify(assistantMsg.content);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('[TOOL CALLS] Failed to stringify assistant content:', e);
|
||||||
|
assistantCallContent = 'Error: Content could not be displayed';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process tool result content
|
||||||
|
if (typeof resultMessage.content === 'string') {
|
||||||
|
toolResultContent = resultMessage.content;
|
||||||
|
} else if (resultMessage.content && typeof resultMessage.content === 'object') {
|
||||||
|
try {
|
||||||
|
toolResultContent = JSON.stringify(resultMessage.content, null, 2);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('[TOOL CALLS] Failed to stringify tool result content:', e);
|
||||||
|
toolResultContent = 'Error: Content could not be displayed';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[TOOL CALLS] Final assistantCallContent type:', typeof assistantCallContent);
|
||||||
|
console.log('[TOOL CALLS] Final toolResultContent type:', typeof toolResultContent);
|
||||||
|
|
||||||
|
// Determine success by checking for error indicators in a lower-cased version of the content
|
||||||
|
const lowerCaseContent = (typeof toolResultContent === 'string' ? toolResultContent : '').toLowerCase();
|
||||||
|
const errorIndicators = ['failed', 'error', 'failure', 'exception'];
|
||||||
|
const hasErrorIndicator = errorIndicators.some(indicator => lowerCaseContent.includes(indicator));
|
||||||
|
|
||||||
|
// Set success state
|
||||||
|
isSuccess = !hasErrorIndicator;
|
||||||
|
console.log('[TOOL CALLS] Success determination:', isSuccess);
|
||||||
|
|
||||||
historicalToolPairs.push({
|
historicalToolPairs.push({
|
||||||
assistantCall: {
|
assistantCall: {
|
||||||
name: toolName,
|
name: toolName,
|
||||||
content: assistantMsg.content,
|
content: assistantCallContent,
|
||||||
timestamp: assistantMsg.created_at,
|
timestamp: assistantMsg.created_at,
|
||||||
},
|
},
|
||||||
toolResult: {
|
toolResult: {
|
||||||
content: resultMessage.content,
|
content: toolResultContent,
|
||||||
isSuccess: isSuccess,
|
isSuccess: isSuccess,
|
||||||
timestamp: resultMessage.created_at,
|
timestamp: resultMessage.created_at,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log('[TOOL CALLS] Error processing tool result:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Log the tool pairs we've found
|
||||||
|
console.log(`[TOOL CALLS] Found ${historicalToolPairs.length} tool pairs`);
|
||||||
|
historicalToolPairs.forEach((pair, idx) => {
|
||||||
|
console.log(`[TOOL CALLS] Tool pair ${idx}:`, pair.assistantCall?.name);
|
||||||
|
});
|
||||||
|
|
||||||
// Always update the toolCalls state
|
// Always update the toolCalls state
|
||||||
setToolCalls(historicalToolPairs);
|
setToolCalls(historicalToolPairs);
|
||||||
|
|
||||||
// Logic to open/update the panel index
|
// Logic to open/update the panel index
|
||||||
if (historicalToolPairs.length > 0) {
|
if (historicalToolPairs.length > 0) {
|
||||||
// If the panel is open (or was just auto-opened) and the user didn't close it
|
// If the panel is open (or was just auto-opened) and the user didn't close it
|
||||||
|
@ -1084,53 +1211,94 @@ export default function ThreadPage({
|
||||||
'Tool Name:',
|
'Tool Name:',
|
||||||
clickedToolName,
|
clickedToolName,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Find the index of the tool call associated with the clicked assistant message
|
// Log toolCalls for debugging
|
||||||
const toolIndex = toolCalls.findIndex((tc) => {
|
console.log('[PAGE] Available tool calls:', toolCalls.length);
|
||||||
// Check if the assistant message ID matches the one stored in the tool result's metadata
|
toolCalls.forEach((tc, idx) => {
|
||||||
if (!tc.toolResult?.content || tc.toolResult.content === 'STREAMING')
|
console.log(`[PAGE] Tool call ${idx}: name=${tc.assistantCall?.name}`);
|
||||||
return false; // Skip streaming or incomplete calls
|
|
||||||
|
|
||||||
// Directly compare assistant message IDs if available in the structure
|
|
||||||
// Find the original assistant message based on the ID
|
|
||||||
const assistantMessage = messages.find(
|
|
||||||
(m) =>
|
|
||||||
m.message_id === clickedAssistantMessageId &&
|
|
||||||
m.type === 'assistant',
|
|
||||||
);
|
|
||||||
if (!assistantMessage) return false;
|
|
||||||
|
|
||||||
// Find the corresponding tool message using metadata
|
|
||||||
const toolMessage = messages.find((m) => {
|
|
||||||
if (m.type !== 'tool' || !m.metadata) return false;
|
|
||||||
const metadata = getMetadataField(m.metadata, 'assistant_message_id');
|
|
||||||
return metadata === assistantMessage.message_id;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if the current toolCall 'tc' corresponds to this assistant/tool message pair
|
|
||||||
return (
|
|
||||||
tc.assistantCall?.content === assistantMessage.content &&
|
|
||||||
tc.toolResult?.content === toolMessage?.content
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (toolIndex !== -1) {
|
// Find all assistant messages by ID (should be just one)
|
||||||
console.log(
|
const assistantMessage = messages.find(
|
||||||
`[PAGE] Found tool call at index ${toolIndex} for assistant message ${clickedAssistantMessageId}`,
|
(m) => m.message_id === clickedAssistantMessageId && m.type === 'assistant'
|
||||||
);
|
);
|
||||||
setCurrentToolIndex(toolIndex);
|
|
||||||
setIsSidePanelOpen(true); // Explicitly open the panel
|
if (!assistantMessage) {
|
||||||
} else {
|
console.error(`[PAGE] Could not find assistant message with ID: ${clickedAssistantMessageId}`);
|
||||||
console.warn(
|
toast.error('Could not find assistant message');
|
||||||
`[PAGE] Could not find matching tool call in toolCalls array for assistant message ID: ${clickedAssistantMessageId}`,
|
return;
|
||||||
);
|
|
||||||
toast.info('Could not find details for this tool call.');
|
|
||||||
// Optionally, still open the panel but maybe at the last index or show a message?
|
|
||||||
// setIsSidePanelOpen(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('[PAGE] Found assistant message:', assistantMessage.message_id);
|
||||||
|
|
||||||
|
// Find tool messages that reference this assistant message
|
||||||
|
const matchingToolMessages = messages.filter((m) => {
|
||||||
|
if (m.type !== 'tool' || !m.metadata) return false;
|
||||||
|
const metadata = getMetadataField(m.metadata, 'assistant_message_id');
|
||||||
|
const isMatch = metadata === assistantMessage.message_id;
|
||||||
|
if (isMatch) {
|
||||||
|
console.log('[PAGE] Found matching tool message:', m.message_id);
|
||||||
|
}
|
||||||
|
return isMatch;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (matchingToolMessages.length === 0) {
|
||||||
|
console.error('[PAGE] No tool messages found for this assistant message');
|
||||||
|
toast.error('No tool results found for this command');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for any tool call with matching tool name
|
||||||
|
const toolIndex = toolCalls.findIndex((tc) => {
|
||||||
|
// First try to match by the specific clicked tool name
|
||||||
|
if (tc.assistantCall?.name?.toLowerCase() === clickedToolName.toLowerCase()) {
|
||||||
|
console.log('[PAGE] Found tool call with matching name:', clickedToolName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise try to match by looking at the assistant content
|
||||||
|
if (typeof tc.assistantCall?.content === 'string' &&
|
||||||
|
tc.assistantCall.content.includes(`<${clickedToolName}`)) {
|
||||||
|
console.log('[PAGE] Found tool call with matching tag in content:', clickedToolName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// If no exact match, try to find any tool call with matching assistant message ID
|
||||||
|
if (toolIndex === -1) {
|
||||||
|
// Try a less strict approach - just find any tool call where assistant content matches
|
||||||
|
const lessStrictMatch = toolCalls.findIndex((tc) => {
|
||||||
|
return tc.assistantCall?.content === assistantMessage.content;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (lessStrictMatch !== -1) {
|
||||||
|
console.log('[PAGE] Found tool call with matching assistant content at index:', lessStrictMatch);
|
||||||
|
setCurrentToolIndex(lessStrictMatch);
|
||||||
|
setIsSidePanelOpen(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If still no match, just open the latest tool call
|
||||||
|
if (toolCalls.length > 0) {
|
||||||
|
console.log('[PAGE] No exact match found, showing latest tool call');
|
||||||
|
setCurrentToolIndex(toolCalls.length - 1);
|
||||||
|
setIsSidePanelOpen(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn('[PAGE] No tool calls available to show');
|
||||||
|
toast.info('Could not find details for this tool call.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[PAGE] Found matching tool call at index ${toolIndex}`);
|
||||||
|
setCurrentToolIndex(toolIndex);
|
||||||
|
setIsSidePanelOpen(true); // Explicitly open the panel
|
||||||
},
|
},
|
||||||
[messages, toolCalls],
|
[messages, toolCalls],
|
||||||
); // Add toolCalls as a dependency
|
);
|
||||||
|
|
||||||
// Handle streaming tool calls
|
// Handle streaming tool calls
|
||||||
const handleStreamingToolCall = useCallback(
|
const handleStreamingToolCall = useCallback(
|
||||||
|
|
Loading…
Reference in New Issue