Files
openclaw-ollama-toolcall-proxy/src/parsers/xml-toolcall.ts

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;
}