import { StreamingToolParser as HTMLStreamingToolParser, extractToolCalls as htmlExtractToolCalls, filterToolCalls as htmlFilterToolCalls, } from "./tool-parser-htmlparser2"; export interface ToolCallMatch { toolName: string; startIndex: number; endIndex?: number; args?: string; complete: boolean; } export class StreamingToolCallParser { private htmlParser: HTMLStreamingToolParser; private buffer = ""; constructor() { this.htmlParser = new HTMLStreamingToolParser(); } reset(): void { this.buffer = ""; this.htmlParser.reset(); } /** * Process incoming text and detect tool call boundaries * Returns the safe text (without tool calls) and any detected tool calls */ process(text: string): { safeText: string; toolCalls: ToolCallMatch[]; pendingToolCall: boolean; } { this.buffer += text; const result = this.htmlParser.write(text); return { safeText: result.safeText, toolCalls: result.toolCalls.map((tc) => ({ toolName: tc.name, startIndex: tc.startIndex || 0, endIndex: tc.endIndex, args: tc.rawArgs, complete: tc.complete, })), pendingToolCall: result.pendingToolCall, }; } /** * Get any buffered content that hasn't been processed yet */ getBuffer(): string { return this.buffer; } /** * Check if parser is currently inside a tool call */ isInToolCall(): boolean { const result = this.htmlParser.write(""); return result.pendingToolCall; } } /** * Static utility to filter tool calls from text (non-streaming) */ export function filterToolCalls(text: string): string { return htmlFilterToolCalls(text); } /** * Static utility to extract tool calls from text */ export function extractToolCalls( text: string, ): Array<{ name: string; args: Record }> { const htmlToolCalls = htmlExtractToolCalls(text); return htmlToolCalls.map((tc) => ({ name: tc.name, args: tc.args, })); }