|
|
import type { ElementType } from 'react'; |
|
|
import { |
|
|
FileText, |
|
|
Terminal, |
|
|
ExternalLink, |
|
|
FileEdit, |
|
|
Search, |
|
|
Globe, |
|
|
Code, |
|
|
MessageSquare, |
|
|
Folder, |
|
|
FileX, |
|
|
CloudUpload, |
|
|
Wrench, |
|
|
Cog, |
|
|
Network, |
|
|
FileSearch, |
|
|
FilePlus, |
|
|
PlugIcon, |
|
|
BookOpen, |
|
|
MessageCircleQuestion, |
|
|
CheckCircle2, |
|
|
} from 'lucide-react'; |
|
|
|
|
|
|
|
|
export const SHOULD_RENDER_TOOL_RESULTS = false; |
|
|
|
|
|
|
|
|
export function safeJsonParse<T>( |
|
|
jsonString: string | undefined | null, |
|
|
fallback: T, |
|
|
): T { |
|
|
if (!jsonString) { |
|
|
return fallback; |
|
|
} |
|
|
|
|
|
try { |
|
|
|
|
|
const parsed = JSON.parse(jsonString); |
|
|
|
|
|
|
|
|
if (typeof parsed === 'string' && |
|
|
(parsed.startsWith('{') || parsed.startsWith('['))) { |
|
|
try { |
|
|
|
|
|
return JSON.parse(parsed) as T; |
|
|
} catch (innerError) { |
|
|
|
|
|
return parsed as unknown as T; |
|
|
} |
|
|
} |
|
|
|
|
|
return parsed as T; |
|
|
} catch (outerError) { |
|
|
|
|
|
if (typeof jsonString === 'object') { |
|
|
return jsonString as T; |
|
|
} |
|
|
|
|
|
|
|
|
if (typeof jsonString === 'string') { |
|
|
|
|
|
if (jsonString === 'true') return true as unknown as T; |
|
|
if (jsonString === 'false') return false as unknown as T; |
|
|
if (jsonString === 'null') return null as unknown as T; |
|
|
if (!isNaN(Number(jsonString))) return Number(jsonString) as unknown as T; |
|
|
|
|
|
|
|
|
if (!jsonString.startsWith('{') && !jsonString.startsWith('[')) { |
|
|
return jsonString as unknown as T; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return fallback; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export const getToolIcon = (toolName: string): ElementType => { |
|
|
switch (toolName?.toLowerCase()) { |
|
|
case 'web-browser-takeover': |
|
|
case 'browser-navigate-to': |
|
|
case 'browser-click-element': |
|
|
case 'browser-input-text': |
|
|
case 'browser-scroll-down': |
|
|
case 'browser-scroll-up': |
|
|
case 'browser-click-coordinates': |
|
|
case 'browser-send-keys': |
|
|
case 'browser-switch-tab': |
|
|
case 'browser-go-back': |
|
|
case 'browser-close-tab': |
|
|
case 'browser-drag-drop': |
|
|
case 'browser-get-dropdown-options': |
|
|
case 'browser-select-dropdown-option': |
|
|
case 'browser-scroll-to-text': |
|
|
case 'browser-wait': |
|
|
return Globe; |
|
|
|
|
|
|
|
|
case 'create-file': |
|
|
return FileEdit; |
|
|
case 'str-replace': |
|
|
return FileSearch; |
|
|
case 'full-file-rewrite': |
|
|
return FilePlus; |
|
|
case 'read-file': |
|
|
return FileText; |
|
|
|
|
|
|
|
|
case 'execute-command': |
|
|
return Terminal; |
|
|
case 'check-command-output': |
|
|
return Terminal; |
|
|
case 'terminate-command': |
|
|
return Terminal; |
|
|
|
|
|
|
|
|
case 'web-search': |
|
|
return Search; |
|
|
case 'crawl-webpage': |
|
|
return Globe; |
|
|
case 'scrape-webpage': |
|
|
return Globe; |
|
|
|
|
|
|
|
|
case 'call-data-provider': |
|
|
return ExternalLink; |
|
|
case 'get-data-provider-endpoints': |
|
|
return Network; |
|
|
case 'execute-data-provider-call': |
|
|
return Network; |
|
|
|
|
|
|
|
|
case 'delete-file': |
|
|
return FileX; |
|
|
|
|
|
|
|
|
case 'deploy-site': |
|
|
return CloudUpload; |
|
|
|
|
|
|
|
|
case 'execute-code': |
|
|
return Code; |
|
|
|
|
|
|
|
|
case 'ask': |
|
|
return MessageCircleQuestion; |
|
|
|
|
|
|
|
|
case 'complete': |
|
|
return CheckCircle2; |
|
|
|
|
|
|
|
|
case 'call-mcp-tool': |
|
|
return PlugIcon; |
|
|
|
|
|
|
|
|
default: |
|
|
if (toolName?.startsWith('mcp_')) { |
|
|
const parts = toolName.split('_'); |
|
|
if (parts.length >= 3) { |
|
|
const serverName = parts[1]; |
|
|
const toolNamePart = parts.slice(2).join('_'); |
|
|
|
|
|
|
|
|
if (toolNamePart.includes('search') || toolNamePart.includes('web')) { |
|
|
return Search; |
|
|
} else if (toolNamePart.includes('research') || toolNamePart.includes('paper')) { |
|
|
return BookOpen; |
|
|
} else if (serverName === 'exa') { |
|
|
return Search; |
|
|
} |
|
|
} |
|
|
return PlugIcon; |
|
|
} |
|
|
|
|
|
|
|
|
console.log( |
|
|
`[PAGE] Using default icon for unknown tool type: ${toolName}`, |
|
|
); |
|
|
return Wrench; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
export const extractPrimaryParam = ( |
|
|
toolName: string, |
|
|
content: string | undefined, |
|
|
): string | null => { |
|
|
if (!content) return null; |
|
|
|
|
|
try { |
|
|
|
|
|
if (toolName?.toLowerCase().startsWith('browser-')) { |
|
|
|
|
|
const urlMatch = content.match(/url=(?:"|')([^"|']+)(?:"|')/); |
|
|
if (urlMatch) return urlMatch[1]; |
|
|
|
|
|
|
|
|
const goalMatch = content.match(/goal=(?:"|')([^"|']+)(?:"|')/); |
|
|
if (goalMatch) { |
|
|
const goal = goalMatch[1]; |
|
|
return goal.length > 30 ? goal.substring(0, 27) + '...' : goal; |
|
|
} |
|
|
|
|
|
return null; |
|
|
} |
|
|
|
|
|
|
|
|
if (content.startsWith('<') && content.includes('>')) { |
|
|
const xmlAttrs = content.match(/<[^>]+\s+([^>]+)>/); |
|
|
if (xmlAttrs && xmlAttrs[1]) { |
|
|
const attrs = xmlAttrs[1].trim(); |
|
|
const filePathMatch = attrs.match(/file_path=["']([^"']+)["']/); |
|
|
if (filePathMatch) { |
|
|
return filePathMatch[1].split('/').pop() || filePathMatch[1]; |
|
|
} |
|
|
|
|
|
|
|
|
if (toolName?.toLowerCase() === 'execute-command') { |
|
|
const commandMatch = attrs.match(/(?:command|cmd)=["']([^"']+)["']/); |
|
|
if (commandMatch) { |
|
|
const cmd = commandMatch[1]; |
|
|
return cmd.length > 30 ? cmd.substring(0, 27) + '...' : cmd; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
let match: RegExpMatchArray | null = null; |
|
|
|
|
|
switch (toolName?.toLowerCase()) { |
|
|
|
|
|
case 'create-file': |
|
|
case 'full-file-rewrite': |
|
|
case 'read-file': |
|
|
case 'delete-file': |
|
|
case 'str-replace': |
|
|
|
|
|
match = content.match(/file_path=(?:"|')([^"|']+)(?:"|')/); |
|
|
|
|
|
return match ? match[1].split('/').pop() || match[1] : null; |
|
|
|
|
|
|
|
|
case 'execute-command': |
|
|
|
|
|
match = content.match(/command=(?:"|')([^"|']+)(?:"|')/); |
|
|
if (match) { |
|
|
const cmd = match[1]; |
|
|
return cmd.length > 30 ? cmd.substring(0, 27) + '...' : cmd; |
|
|
} |
|
|
return null; |
|
|
|
|
|
|
|
|
case 'web-search': |
|
|
match = content.match(/query=(?:"|')([^"|']+)(?:"|')/); |
|
|
return match |
|
|
? match[1].length > 30 |
|
|
? match[1].substring(0, 27) + '...' |
|
|
: match[1] |
|
|
: null; |
|
|
|
|
|
|
|
|
case 'call-data-provider': |
|
|
match = content.match(/service_name=(?:"|')([^"|']+)(?:"|')/); |
|
|
const route = content.match(/route=(?:"|')([^"|']+)(?:"|')/); |
|
|
return match && route |
|
|
? `${match[1]}/${route[1]}` |
|
|
: match |
|
|
? match[1] |
|
|
: null; |
|
|
|
|
|
|
|
|
case 'deploy-site': |
|
|
match = content.match(/site_name=(?:"|')([^"|']+)(?:"|')/); |
|
|
return match ? match[1] : null; |
|
|
} |
|
|
|
|
|
return null; |
|
|
} catch (e) { |
|
|
console.warn('Error parsing tool parameters:', e); |
|
|
return null; |
|
|
} |
|
|
}; |
|
|
|
|
|
const TOOL_DISPLAY_NAMES = new Map([ |
|
|
['execute-command', 'Executing Command'], |
|
|
['check-command-output', 'Checking Command Output'], |
|
|
['terminate-command', 'Terminating Command'], |
|
|
['list-commands', 'Listing Commands'], |
|
|
|
|
|
['create-file', 'Creating File'], |
|
|
['delete-file', 'Deleting File'], |
|
|
['full-file-rewrite', 'Rewriting File'], |
|
|
['str-replace', 'Editing Text'], |
|
|
['str_replace', 'Editing Text'], |
|
|
|
|
|
['browser-click-element', 'Clicking Element'], |
|
|
['browser-close-tab', 'Closing Tab'], |
|
|
['browser-drag-drop', 'Dragging Element'], |
|
|
['browser-get-dropdown-options', 'Getting Options'], |
|
|
['browser-go-back', 'Going Back'], |
|
|
['browser-input-text', 'Entering Text'], |
|
|
['browser-navigate-to', 'Navigating to Page'], |
|
|
['browser-scroll-down', 'Scrolling Down'], |
|
|
['browser-scroll-to-text', 'Scrolling to Text'], |
|
|
['browser-scroll-up', 'Scrolling Up'], |
|
|
['browser-select-dropdown-option', 'Selecting Option'], |
|
|
['browser-click-coordinates', 'Clicking Coordinates'], |
|
|
['browser-send-keys', 'Pressing Keys'], |
|
|
['browser-switch-tab', 'Switching Tab'], |
|
|
['browser-wait', 'Waiting'], |
|
|
|
|
|
['execute-data-provider-call', 'Calling data provider'], |
|
|
['execute_data_provider_call', 'Calling data provider'], |
|
|
['get-data-provider-endpoints', 'Getting endpoints'], |
|
|
|
|
|
['deploy', 'Deploying'], |
|
|
['ask', 'Ask'], |
|
|
['complete', 'Completing Task'], |
|
|
['crawl-webpage', 'Crawling Website'], |
|
|
['expose-port', 'Exposing Port'], |
|
|
['scrape-webpage', 'Scraping Website'], |
|
|
['web-search', 'Searching Web'], |
|
|
['see-image', 'Viewing Image'], |
|
|
|
|
|
['call-mcp-tool', 'External Tool'], |
|
|
|
|
|
['update-agent', 'Updating Agent'], |
|
|
['get-current-agent-config', 'Getting Agent Config'], |
|
|
['search-mcp-servers', 'Searching MCP Servers'], |
|
|
['get-mcp-server-tools', 'Getting MCP Server Tools'], |
|
|
['configure-mcp-server', 'Configuring MCP Server'], |
|
|
['get-popular-mcp-servers', 'Getting Popular MCP Servers'], |
|
|
['test-mcp-server-connection', 'Testing MCP Server Connection'], |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
['execute_command', 'Executing Command'], |
|
|
['check_command_output', 'Checking Command Output'], |
|
|
['terminate_command', 'Terminating Command'], |
|
|
['list_commands', 'Listing Commands'], |
|
|
|
|
|
['create_file', 'Creating File'], |
|
|
['delete_file', 'Deleting File'], |
|
|
['full_file_rewrite', 'Rewriting File'], |
|
|
['str_replace', 'Editing Text'], |
|
|
|
|
|
['browser_click_element', 'Clicking Element'], |
|
|
['browser_close_tab', 'Closing Tab'], |
|
|
['browser_drag_drop', 'Dragging Element'], |
|
|
['browser_get_dropdown_options', 'Getting Options'], |
|
|
['browser_go_back', 'Going Back'], |
|
|
['browser_input_text', 'Entering Text'], |
|
|
['browser_navigate_to', 'Navigating to Page'], |
|
|
['browser_scroll_down', 'Scrolling Down'], |
|
|
['browser_scroll_to_text', 'Scrolling to Text'], |
|
|
['browser_scroll_up', 'Scrolling Up'], |
|
|
['browser_select_dropdown_option', 'Selecting Option'], |
|
|
['browser_click_coordinates', 'Clicking Coordinates'], |
|
|
['browser_send_keys', 'Pressing Keys'], |
|
|
['browser_switch_tab', 'Switching Tab'], |
|
|
['browser_wait', 'Waiting'], |
|
|
|
|
|
['execute_data_provider_call', 'Calling data provider'], |
|
|
['get_data_provider_endpoints', 'Getting endpoints'], |
|
|
|
|
|
['deploy', 'Deploying'], |
|
|
['ask', 'Ask'], |
|
|
['complete', 'Completing Task'], |
|
|
['crawl_webpage', 'Crawling Website'], |
|
|
['expose_port', 'Exposing Port'], |
|
|
['scrape_webpage', 'Scraping Website'], |
|
|
['web_search', 'Searching Web'], |
|
|
['see_image', 'Viewing Image'], |
|
|
|
|
|
['call_mcp_tool', 'External Tool'], |
|
|
|
|
|
['update_agent', 'Updating Agent'], |
|
|
['get_current_agent_config', 'Getting Agent Config'], |
|
|
['search_mcp_servers', 'Searching MCP Servers'], |
|
|
['get_mcp_server_tools', 'Getting MCP Server Tools'], |
|
|
['configure_mcp_server', 'Configuring MCP Server'], |
|
|
['get_popular_mcp_servers', 'Getting Popular MCP Servers'], |
|
|
['test_mcp_server_connection', 'Testing MCP Server Connection'], |
|
|
|
|
|
]); |
|
|
|
|
|
|
|
|
const MCP_SERVER_NAMES = new Map([ |
|
|
['exa', 'Exa Search'], |
|
|
['github', 'GitHub'], |
|
|
['notion', 'Notion'], |
|
|
['slack', 'Slack'], |
|
|
['filesystem', 'File System'], |
|
|
['memory', 'Memory'], |
|
|
]); |
|
|
|
|
|
function formatMCPToolName(serverName: string, toolName: string): string { |
|
|
const serverMappings: Record<string, string> = { |
|
|
'exa': 'Exa Search', |
|
|
'github': 'GitHub', |
|
|
'notion': 'Notion', |
|
|
'slack': 'Slack', |
|
|
'filesystem': 'File System', |
|
|
'memory': 'Memory', |
|
|
'anthropic': 'Anthropic', |
|
|
'openai': 'OpenAI', |
|
|
'composio': 'Composio', |
|
|
'langchain': 'LangChain', |
|
|
'llamaindex': 'LlamaIndex' |
|
|
}; |
|
|
|
|
|
const formattedServerName = serverMappings[serverName.toLowerCase()] || |
|
|
serverName.charAt(0).toUpperCase() + serverName.slice(1); |
|
|
|
|
|
let formattedToolName = toolName; |
|
|
|
|
|
if (toolName.includes('-')) { |
|
|
formattedToolName = toolName |
|
|
.split('-') |
|
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1)) |
|
|
.join(' '); |
|
|
} |
|
|
else if (toolName.includes('_')) { |
|
|
formattedToolName = toolName |
|
|
.split('_') |
|
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1)) |
|
|
.join(' '); |
|
|
} |
|
|
else if (/[a-z][A-Z]/.test(toolName)) { |
|
|
formattedToolName = toolName |
|
|
.replace(/([a-z])([A-Z])/g, '$1 $2') |
|
|
.split(' ') |
|
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1)) |
|
|
.join(' '); |
|
|
} |
|
|
else { |
|
|
formattedToolName = toolName.charAt(0).toUpperCase() + toolName.slice(1); |
|
|
} |
|
|
|
|
|
return `${formattedServerName}: ${formattedToolName}`; |
|
|
} |
|
|
|
|
|
export function getUserFriendlyToolName(toolName: string): string { |
|
|
if (toolName.startsWith('mcp_')) { |
|
|
const parts = toolName.split('_'); |
|
|
if (parts.length >= 3) { |
|
|
const serverName = parts[1]; |
|
|
const toolNamePart = parts.slice(2).join('_'); |
|
|
return formatMCPToolName(serverName, toolNamePart); |
|
|
} |
|
|
} |
|
|
if (toolName.includes('-') && !TOOL_DISPLAY_NAMES.has(toolName)) { |
|
|
const parts = toolName.split('-'); |
|
|
if (parts.length >= 2) { |
|
|
const serverName = parts[0]; |
|
|
const toolNamePart = parts.slice(1).join('-'); |
|
|
return formatMCPToolName(serverName, toolNamePart); |
|
|
} |
|
|
} |
|
|
return TOOL_DISPLAY_NAMES.get(toolName) || toolName; |
|
|
} |
|
|
|