/** * Pure parsing and classification for model output: recovering tool calls * from the many shapes different models emit (fenced JSON, XML wrappers, * Kimi sentinel tokens, bare args objects), and text-pattern classifiers * (build/pentest task detection, narration detection) used * to steer the agent loop. Nothing here touches process state, the file * system, or any store — nothing here executes a tool call, either. */ import type { ChatMessage, ToolCall } from "../types.js"; export declare function preprocessJson(raw: string): string; /** Strip any leftover Kimi/Moonshot sentinel tokens from final answers * so a model that mixes prose and tool-call markers never bleeds raw * `<|tool_call_begin|>` strings to the terminal. */ export declare function stripSentinelTokens(text: string): string; export interface ParseToolCallOptions { /** * When true, only formats that are explicitly tool-call delimited are * accepted: ```tool fenced JSON, XML, and the Kimi sentinel * token format. Loose formats (any fenced block, heading-prefix, trailing * JSON) are dropped — useful when models routinely emit JSON examples in * prose. Default is `false` so existing free-tier models keep working. */ strict?: boolean | undefined; } export declare function parseToolCall(text: string, options?: ParseToolCallOptions): ToolCall | undefined; /** * When a model emits a bare args object with no {"name", "args"} wrapper and * no ```tool fence, infer which tool it MEANT from the argument keys so we * can run it directly instead of nudging the model to re-emit (the user * should not have to type "run"). Only unambiguous key signatures map to a * tool; genuinely ambiguous shapes (a lone `path` could be fs.read / fs.list * / pdf.read / image.ocr; a lone `target` could be whois / dns / scan) return * undefined so the caller falls back to a re-emit nudge. Inferred calls still * pass through the normal safety classifier + confirmation, so inference can * never bypass a confirm/block gate. */ export declare function inferToolFromArgs(obj: Record): string | undefined; /** * When a model means to call a tool but emits ONLY a bare JSON object — * either a proper {"name","args"} that the strict matchers missed, or a bare * args object like {"path":"file.pdf"} with the wrapper/fence dropped — this * recognizes it. Returns: * - { call } when the object is a complete {name, args} tool call, or * - { argsOnly: true } when it looks like a bare args object (so the caller * can nudge the model to re-emit a properly named, fenced tool call). * Returns undefined for anything that is plainly a normal prose/JSON answer. * * Also handles the case where a model emits prose followed by a non-`tool` * fenced code block (e.g. ```web\n{"url":"..."}\n```) that contains a bare * args object — the fence is scanned even when it's not the sole content. */ export declare function recognizeBareToolJson(text: string): { call?: ToolCall; argsOnly?: boolean; } | undefined; /** * Detect an opened-but-unparseable tool call. This happens when the model's * output is truncated by the token limit mid-JSON: we see the ```tool fence * (or a bare {"name":"...","args" prefix) open, but parseToolCall returns * undefined because the JSON never closed. Without this, the broken block * leaks to the screen as a "final answer" and the requested action (e.g. a * multi-file fs.writeMany scaffold) silently never runs. */ export declare function looksLikeTruncatedToolCall(text: string): boolean; export interface SalvagedWrite { operation: "write" | "append"; path: string; content: string; lastLine: string; expectedPriorBytes?: number | undefined; } export declare function salvageTruncatedWrite(text: string): SalvagedWrite | undefined; /** * Salvage partial file content from a native tool call's raw argument JSON * (streaming cut off / finish_reason length / _parseError). Reuses the * text-path salvage by reconstructing a minimal {"name","args"} shape. */ export declare function salvageTruncatedWriteFromNative(name: string, rawArguments: string | undefined): ReturnType; /** * Count the number of ```tool fenced blocks in a message. Models sometimes * emit MULTIPLE tool calls in one response (e.g. fs.writeMany + npm install + * npm run dev). Only the FIRST is parsed and executed; the rest are silently * dropped and leak to the screen as code fences, while the model believes it * ran all of them — a major cause of "everything is done" fabrications. We * detect this so the runner can run the first and explicitly tell the model * the others did NOT run and must be re-sent one at a time. */ export declare function countToolFences(text: string): number; /** * Parse every explicitly-delimited tool call in a message (```tool fences, * XML, Kimi sentinel blocks), in document order, so the runner * can execute a batch emitted in one turn instead of only the first call. */ export declare function parseAllToolCalls(text: string): ToolCall[]; export declare function isMutatingToolName(name: string): boolean; /** Structural equality for two tool calls (name + canonical args JSON). */ export declare function sameToolCall(a: ToolCall, b: ToolCall): boolean; /** * Partition a batch of tool calls (in document order) into execution groups. * A run of consecutive parallel-safe calls forms one group to be run * concurrently (bounded by maxGroupSize); every non-parallel-safe call is its * own single-element group, i.e. a sequential barrier. Because plan updates * and side-effecting tools are never parallel-safe, they always split the * batch — which keeps parallelism scoped within a single task and prevents * plan-state races and overlapping writes. */ export declare function groupToolCallsForExecution(calls: ToolCall[], isParallelSafe: (call: ToolCall) => boolean, maxGroupSize?: number): ToolCall[][]; /** * Build the conversation to hand back to the caller at turn end. Strips system * prompts (they're re-added each turn) but keeps the user turn plus every * assistant tool-call and tool result, then appends the final answer if it * isn't already the last message. Persisting this is what lets a resumed * session give the model back what it actually did — commands, outputs, and * results — instead of only its prose answers. */ export declare function buildTurnHistory(messages: ChatMessage[], answer: string): ChatMessage[]; /** * Collapse pathological repetition before a message is stored in history. * Some models degenerate into emitting the same short phrase hundreds of * times ("We need to wait.We need to wait.…"), which otherwise bloats the * context window and wastes tokens on every subsequent turn. We keep a few * copies and note the collapse so the meaning is preserved without the bulk. */ export declare function collapseRepeatedText(text: string): string; /** Extract the text before the tool call block for display purposes */ export declare function textBeforeToolCall(text: string): string; /** Compact line window for fs.read card headers, e.g. "11–20" or "1–10". */ export declare function formatFsReadLineRange(args: Record | undefined): string | undefined; export declare function formatToolArgs(call: ToolCall): string; /** * Detect pentest/security tasks that need the full step budget. * Mirrors looksLikeBuildTask but for security work. */ export declare function looksLikePentestTask(prompt: string, history?: ChatMessage[] | undefined): boolean; /** * Decide whether this turn should get the build workflow (explore → plan → * implement) and a generous step budget. Looks at the current prompt first, * then falls back to recent USER turns so a terse follow-up inherits an * ongoing build — but NOT the agent's own (possibly mistaken) plan narration. */ export declare function looksLikeBuildTask(prompt: string, history?: ChatMessage[] | undefined): boolean; /** * Is THIS prompt a plain informational question (as opposed to a request to * do work)? Used to stop a resumed/continuing build or pentest session from * forcing "act, don't narrate" behavior — and the explore→plan build * workflow — onto a question like "what do you know so far", "what did you * find", or "summarize the results". A follow-up question in a work session * should be ANSWERED from context, not treated as a signal to start executing * or to invent a brand-new plan. * * Explicit build/continuation/plan-execution phrasing is NOT informational, * even when it opens with a question word (e.g. "can you build the api", * "should I add auth" → those still want work). */ export declare function looksLikeInformationalQuery(prompt: string): boolean; /** * Model diagnosed a concrete failure (build/runtime/HTTP) and implies a fix * but has not yet applied it — must not end the turn on diagnosis alone. * Returns false for post-fix summaries ("I've fixed…", "build passed"). */ export declare function looksLikeErrorDiagnosisWithFixIntent(text: string): boolean; /** True when tool output is a local HTTP probe that did not return 2xx. */ export declare function localHttpProbeIsFailure(output: string): boolean; /** True when tool output shows a successful 2xx local probe. */ export declare function localHttpProbeIsSuccess(output: string): boolean; /** * Detect a pure social / idle user prompt (greetings, thanks, short acks). * These must never force tool use or plan workflows. */ export declare function looksLikeIdleOrSocialPrompt(prompt: string): boolean; /** * Detect a message that narrates an *upcoming* action ("let me explore the * directory", "I'll create the components") rather than an actual answer or * tool call. Used to catch models that describe intent but emit no tool call, * which would otherwise end the turn with nothing done. A real completion * summary (past tense, longer, or containing a code block) is NOT flagged. * * Capability offers, greetings, educational framing, and explicit denials of * pending work are intentionally NOT flagged — those false positives used to * burn recovery turns (and tokens) on web.search nudges after a simple "hi". */ export declare function looksLikeActionNarration(text: string): boolean; /** * Narration specifically about an upcoming web/browse/search action. Used to * choose the web-oriented recovery nudge instead of treating every non-build * stall as a web action. */ export declare function looksLikeWebActionNarration(text: string): boolean; /** * Detect a message that narrates a PLAN as prose ("Goal: … Tasks: 1. … Please * approve the plan") instead of calling plan.create. Such a turn leaves no * real plan, so the user can't /implement it — we nudge the model to emit the * plan.create tool call instead. */ export declare function looksLikePlanNarration(text: string): boolean; /** * Detect a low-quality "everything in one step" plan task. A single task that * itself enumerates many files/actions (multiple commas, an "and", several * slashes, or an overlong title) means the model lumped the whole build into * one checkbox instead of producing a real ordered checklist. */ export declare function isLumpedSingleTask(taskTitles: string[]): boolean; /** * Compact build inject — reinforces judgment defaults without restating the * full system playbook. Stack-agnostic. */ export declare function buildWorkflowDirective(): string; export declare function narrowNmapOperationDirective(): string; /** * Compact pentest inject — objective-first red team / VAPT defaults. */ export declare function pentestWorkflowDirective(): string; /** * Always-on reminder when the session is already a remote/security engagement * (plan kind=pentest or pentest-like turn), including after tasks complete. */ export declare function pentestNoLocalServerDirective(): string; export declare function shouldDimToolChatter(call: ToolCall): boolean; /** * Returns true when the model's output looks like it is repeating the system * prompt rather than giving a genuine answer. Used to suppress execution of * tool-call examples embedded in the regurgitated instructions. */ export declare function looksLikePromptLeak(text: string): boolean;