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