/** * XML Tool Parser for new format * * Parses tool calls in the format: * * * value * * */ export interface ParsedToolCall { functionName: string; parameters: Record; rawXml: string; } export function parseXmlToolCalls(content: string): ParsedToolCall[] { const toolCalls: ParsedToolCall[] = []; const functionCallsRegex = /([\s\S]*?)<\/function_calls>/gi; let functionCallsMatch; while ((functionCallsMatch = functionCallsRegex.exec(content)) !== null) { const functionCallsContent = functionCallsMatch[1]; const invokeRegex = /([\s\S]*?)<\/invoke>/gi; let invokeMatch; while ((invokeMatch = invokeRegex.exec(functionCallsContent)) !== null) { const functionName = invokeMatch[1].replace(/_/g, '-'); const invokeContent = invokeMatch[2]; const parameters: Record = {}; const paramRegex = /([\s\S]*?)<\/parameter>/gi; let paramMatch; while ((paramMatch = paramRegex.exec(invokeContent)) !== null) { const paramName = paramMatch[1]; const paramValue = paramMatch[2].trim(); parameters[paramName] = parseParameterValue(paramValue); } toolCalls.push({ functionName, parameters, rawXml: invokeMatch[0] }); } } return toolCalls; } function parseParameterValue(value: string): any { const trimmed = value.trim(); if (trimmed.startsWith('{') || trimmed.startsWith('[')) { try { return JSON.parse(trimmed); } catch { } } if (trimmed.toLowerCase() === 'true') return true; if (trimmed.toLowerCase() === 'false') return false; if (/^-?\d+(\.\d+)?$/.test(trimmed)) { const num = parseFloat(trimmed); if (!isNaN(num)) return num; } return value; } export function extractToolName(content: string): string | null { if (isNewXmlFormat(content)) { const toolCalls = parseXmlToolCalls(content); if (toolCalls.length > 0) { return toolCalls[0].functionName.replace(/_/g, '-'); } } const xmlRegex = /<([a-zA-Z\-_]+)(?:\s+[^>]*)?>(?:[\s\S]*?)<\/\1>|<([a-zA-Z\-_]+)(?:\s+[^>]*)?\/>/; const match = content.match(xmlRegex); if (match) { const toolName = match[1] || match[2]; return toolName.replace(/_/g, '-'); } return null; } export function isNewXmlFormat(content: string): boolean { return /[\s\S]*]*)?>(?!\/)/); if (oldFormatMatch) { const toolName = oldFormatMatch[1].replace(/_/g, '-'); return formatToolNameForDisplay(toolName); } return null; } export function formatToolNameForDisplay(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('_'); const formattedServerName = serverName.charAt(0).toUpperCase() + serverName.slice(1); let formattedToolName = toolNamePart; if (toolNamePart.includes('-')) { formattedToolName = toolNamePart .split('-') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); } else if (toolNamePart.includes('_')) { formattedToolName = toolNamePart .split('_') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); } else { formattedToolName = toolNamePart.charAt(0).toUpperCase() + toolNamePart.slice(1); } return `${formattedServerName}: ${formattedToolName}`; } } return toolName .split('-') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); }