94 lines
2.9 KiB
TypeScript
Executable File
94 lines
2.9 KiB
TypeScript
Executable File
import { ParsedToolCall } from '../types/toolcall';
|
|
import { logger } from '../utils/logger';
|
|
|
|
/**
|
|
* Parses XML-style tool calls from the message content.
|
|
* Expected formats:
|
|
* <function=read>
|
|
* <parameter=path>
|
|
* /tmp/test.txt
|
|
* </parameter>
|
|
* </function>
|
|
*
|
|
* Or wrapped in <tool_call></tool_call>
|
|
*
|
|
* Returns an array of parsed tool calls.
|
|
*/
|
|
export function parseXmlToolCalls(content: string): ParsedToolCall[] {
|
|
if (!content) return [];
|
|
|
|
const results: ParsedToolCall[] = [];
|
|
|
|
const pushParsedCall = (name: string, args: Record<string, any>) => {
|
|
if (!name) return;
|
|
if (name === 'FUNCTION_NAME') return;
|
|
if (Object.keys(args).some((key) => key === 'ARG_NAME')) return;
|
|
if (Object.values(args).some((value) => value === 'ARG_VALUE')) return;
|
|
results.push({ name, args });
|
|
};
|
|
|
|
// Match each <function=NAME>...</function> block
|
|
// We use `[\s\S]*?` for non-greedy multiline matching
|
|
const functionRegex = /<function=([^>]+)>([\s\S]*?)<\/function>/g;
|
|
|
|
let match;
|
|
while ((match = functionRegex.exec(content)) !== null) {
|
|
const name = match[1].trim();
|
|
const innerContent = match[2];
|
|
|
|
// Parse parameters inside the function block
|
|
const args: Record<string, any> = {};
|
|
const paramRegex = /<parameter=([^>]+)>([\s\S]*?)<\/parameter>/g;
|
|
|
|
let paramMatch;
|
|
while ((paramMatch = paramRegex.exec(innerContent)) !== null) {
|
|
const paramName = paramMatch[1].trim();
|
|
const paramValue = paramMatch[2].trim();
|
|
args[paramName] = paramValue;
|
|
}
|
|
|
|
// Sometimes arguments are JSON encoded strings inside XML, or we can just pass them as strings.
|
|
pushParsedCall(name, args);
|
|
}
|
|
|
|
// Match JSON tool calls wrapped in <tool_call>...</tool_call>
|
|
const jsonToolCallRegex = /<tool_call>\s*([\s\S]*?)\s*<\/tool_call>/g;
|
|
|
|
while ((match = jsonToolCallRegex.exec(content)) !== null) {
|
|
const rawPayload = match[1]?.trim();
|
|
if (!rawPayload || !rawPayload.startsWith('{')) {
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
const parsed = JSON.parse(rawPayload);
|
|
const name = typeof parsed?.name === 'string' ? parsed.name.trim() : '';
|
|
let args: Record<string, any> = {};
|
|
|
|
if (parsed?.arguments && typeof parsed.arguments === 'object' && !Array.isArray(parsed.arguments)) {
|
|
args = parsed.arguments;
|
|
} else if (typeof parsed?.arguments === 'string') {
|
|
try {
|
|
const nested = JSON.parse(parsed.arguments);
|
|
if (nested && typeof nested === 'object' && !Array.isArray(nested)) {
|
|
args = nested;
|
|
}
|
|
} catch {
|
|
args = { value: parsed.arguments };
|
|
}
|
|
}
|
|
|
|
pushParsedCall(name, args);
|
|
} catch {
|
|
logger.debug('Skipping invalid JSON tool call payload.');
|
|
}
|
|
}
|
|
|
|
// Debug logging if we found anything
|
|
if (results.length > 0) {
|
|
logger.debug(`Parsed ${results.length} tool calls from XML.`);
|
|
}
|
|
|
|
return results;
|
|
}
|