llama1's picture
Upload 781 files
5da4770 verified
/**
* XML Tool Parser for new format
*
* Parses tool calls in the format:
* <function_calls>
* <invoke name="tool_name">
* <parameter name="param">value</parameter>
* </invoke>
* </function_calls>
*/
export interface ParsedToolCall {
functionName: string;
parameters: Record<string, any>;
rawXml: string;
}
export function parseXmlToolCalls(content: string): ParsedToolCall[] {
const toolCalls: ParsedToolCall[] = [];
const functionCallsRegex = /<function_calls>([\s\S]*?)<\/function_calls>/gi;
let functionCallsMatch;
while ((functionCallsMatch = functionCallsRegex.exec(content)) !== null) {
const functionCallsContent = functionCallsMatch[1];
const invokeRegex = /<invoke\s+name=["']([^"']+)["']>([\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<string, any> = {};
const paramRegex = /<parameter\s+name=["']([^"']+)["']>([\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 /<function_calls>[\s\S]*<invoke\s+name=/.test(content);
}
export function extractToolNameFromStream(content: string): string | null {
const invokeMatch = content.match(/<invoke\s+name=["']([^"']+)["']/i);
if (invokeMatch) {
const toolName = invokeMatch[1].replace(/_/g, '-');
return formatToolNameForDisplay(toolName);
}
const oldFormatMatch = content.match(/<([a-zA-Z\-_]+)(?:\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(' ');
}