/** * Parser for tool calls that a model leaked into plain text output as * antml-style XML markup instead of emitting them through the native * `tool_calls` stream. * * Observed shape (OpenRouter models occasionally emit this despite native * tools being passed in the request): * * wrapper-open tag * invoke-open tag with name attribute * parameter-open tag with name attribute ... value ... parameter-close tag * invoke-close tag * wrapper-close tag * * where the wrapper tag is `function_calls` (optionally with an `antml:` * namespace prefix) and invoke/parameter tags follow the same convention. * * Models also emit the same markup WITHOUT the wrapper — orphan * `…` blocks straight in prose (pi-style) — or * truncated blocks that never receive their closing tags before the stream * ends. Both shapes are recovered: orphan blocks during streaming, truncated * parameter-complete tails at end of stream (see salvageLeakedToolCallTail). * * Everything here is pure string logic — no provider imports — so the tests * cover it without any streaming harness. */ /** * Floor for how much tail a caller should expect a partial opener to occupy * (the longest opener tag is the antml-prefixed one, 22 chars, plus margin). * Partial detection anchors at the last `<` of the buffered text, so an * invoke opener whose name attribute is still open can hold back more than * this when the tool name is long — it is a floor, not a cap. */ export declare const LEAK_TAIL_LENGTH = 32; export type LeakedToolCall = { name: string; args: Record; }; export type LeakOpenerMatch = "full" | "partial" | "none"; /** * Locate a leaked opener tag in `text` — either the wrapper * (``) or an orphan invoke (``, pi-style * markup emitted without the wrapper). * - "full": a complete opener tag exists; `index` points at its `<`. * - "partial": the text ENDS mid-opener (an opener split across streaming * deltas); `index` points at the start of that tail. A partial always * starts at the last `<` — the tail matcher admits no further `<` — so the * scan is a single anchored match there instead of a prefix enumeration. * - "none": no opener, complete or partial. */ export declare function findLeakedToolCallOpener(text: string): { match: LeakOpenerMatch; index: number; }; /** * Extract tool calls from a leaked wrapper block. * * Returns: * - `complete: true` → the wrapper closer was found; `calls` holds every * invoke in order and `remaining` is the text outside the block (prose * before + after). `consumed` is the raw block text (opener→closer * inclusive) so callers can fall back to emitting it as text when the * parsed tool names turn out to be unknown. * When no wrapper opener exists at all, orphan invoke blocks (pi-style, no * wrapper) are parsed instead — but only when EVERY invoke opener has a * matching closer; otherwise `complete: false` and `remaining`/`consumed` * echo the input so nothing is cut prematurely. (A wrapper opener blocks the * orphan path: wrapper-less invokes around a wrapper block are picked up * later from `remaining`, once the wrapper block itself is consumed.) */ export declare function extractLeakedToolCalls(text: string): { calls: LeakedToolCall[]; complete: boolean; remaining: string; consumed: string; }; /** * End-of-stream recovery for a leaked buffer that never received its closing * tags (stream cut off, or a model that forgets `` / the wrapper * closer). Parses invoke blocks tolerantly: a missing closer is fine as long * as every `` value is fully closed. A truncated parameter value * means the model was cut mid-argument — guessing at the value is unsafe, so * the whole tail degrades to plain text (null). * * Returns the parsed calls (leading wrapper skeleton dropped), or null when * nothing salvageable remains. Callers still validate tool names before * emitting synthetic tool calls. */ export declare function salvageLeakedToolCallTail(text: string): LeakedToolCall[] | null; /** * Coerce a leaked parameter string into a JSON value when it clearly parses * as one — native `tool_calls` arguments are real JSON, so a leaked * `[1,2]` should arrive as an array, not a * string. Anything that does not fully parse stays a string. */ export declare function coerceLeakedParameterValue(raw: string): unknown; //# sourceMappingURL=leaked-tool-call-parser.d.ts.map