/** * Streaming-safe filter for the Kimi K2 chat-template "tool-call section" * grammar. * * Some providers hosting Kimi K2 (the native `kimi-code` API, OpenRouter, * Fireworks, and others) leak the raw chat-template special tokens into * `delta.content` instead of emitting structured `tool_calls`. Visually * that looks like: * * <|tool_calls_section_begin|> * <|tool_call_begin|>functions.read:0<|tool_call_argument_begin|>{"path":"foo"}<|tool_call_end|> * <|tool_calls_section_end|> * * Without healing, the user sees the raw markers and the agent loop never * sees a tool call. This module reconstructs the embedded calls and strips * the markers from visible text. It is stream-aware: any partial token at * the end of a chunk is held back until the next chunk arrives. */ import { type UnicodeEscapeEvidence } from "./json-parse"; export interface HealedToolCall { readonly id: string; readonly name: string; readonly arguments: string; /** * Whether the raw leaked payload spelled a printable non-ASCII character as a * `\uXXXX` escape. Captured BEFORE the normalizing round-trip below, which * decodes escapes into literal characters and would otherwise erase the only * evidence that the text is unverifiable. */ readonly escapedNonAsciiArguments: boolean; readonly escapedUnicodeArgumentEvidence?: UnicodeEscapeEvidence; } /** * State machine that consumes streamed text, emits visible text with all * Kimi tool-call markers stripped, and accumulates the embedded tool calls * for the caller to drain after each `feed()`. * * One instance per stream. Feed only the channel that may carry leaked * markers (typically `delta.content`); mixing reasoning + content into the * same accumulator corrupts the holdback buffer if both channels race in * the same chunk. */ export declare class ToolCallHealer { #private; /** * Feed a chunk of streamed text. Returns the portion safe to emit * downstream (with all tokens stripped). Any partial token suffix is * held back until the next chunk arrives or {@link flushPending} is * called. */ feed(text: string): string; /** * Like {@link feed}, but discards any tool calls that the chunk completes. * Used when the upstream provider also emits structured `delta.tool_calls` * for the same chunk: the healer still strips leaked marker text from the * visible output, but the structured payload remains the single source of * truth for the call list. */ consumeWithoutCalls(text: string): string; /** * Drain accumulated tool calls. The internal list is cleared so a * subsequent section in the same stream (rare) yields fresh calls. */ drainCompleted(): HealedToolCall[]; /** * Flush any held-back fragment when the stream ends. If we were mid-call * the partial is dropped (emitting raw token bytes would surface markers * to the user); otherwise the fragment is returned verbatim so a literal * `<|` in prose is not silently lost. */ flushPending(): string; /** True once any tool-call section in this stream has fully closed. */ get sectionClosed(): boolean; } /** * Cheap test for whether a given model is known to leak Kimi-K2 chat-template * tool-call tokens into visible text. Used to gate the per-stream healer so * non-Kimi providers do not pay for the scan. */ export declare function modelMayLeakKimiToolCalls(provider: string, modelId: string): boolean;