/** * Recovery for tool calls a native-tool-calling model emitted as plain text. * * Symptom: the provider returns a normal `end_turn` whose visible text carries * a fully-formed Anthropic-style `…` block instead of * native `tool_use` content. The turn then holds zero runnable tool calls, so * the agent loop stops with the markup rendered verbatim to the user and the * announced work never runs. * * This module re-materializes such a turn into real `toolCall` blocks using the * same in-band scanner the owned-dialect path uses, so the loop dispatches them * unchanged. It is deliberately conservative: a turn is salvaged ONLY when the * markup is unambiguously a call the model meant to make (see * {@link salvageLeakedToolCalls}), never when it is quoted documentation. */ import type { AssistantMessage, ToolCall } from "../types"; import type { InbandTool } from "./types"; export interface SalvagedToolCalls { /** The rewritten turn: markup removed from text, real `toolCall` blocks appended. */ readonly message: AssistantMessage; /** The calls recovered from the leaked markup, in emission order. */ readonly calls: readonly ToolCall[]; } /** * Recover tool calls a model wrote as visible text instead of native tool_use. * * Returns `undefined` unless EVERY guard holds — each one exists to keep a turn * that merely *talks about* tool syntax from being executed: * * - The turn ended normally (`stop`). `length` may have truncated the markup * mid-argument, and `error`/`aborted` turns are already handled upstream. * - The turn carries no native `toolCall` block. A model that called tools * natively AND quoted markup is documenting, not leaking. * - At least one `