import type { GthCommand } from '#src/core/types.js'; /** The single canonical output-preview cap (lines). No other preview length exists anywhere. */ export declare const TOOL_OUTPUT_PREVIEW_LINES = 10; /** * [[TUI-C108]] — the fewest preview lines a FAILED call is rendered with, whatever depth the user * configured. See {@link buildToolPreviewLines} for why a failure is the one case the configured * depth does not get the last word on. */ export declare const TOOL_ERROR_PREVIEW_FLOOR_LINES = 3; /** * Per-line cap for preview lines, in terminal COLUMNS (a one-line minified bundle must not flood a * row). Every cap in this module is a column budget, never a character count: a character count is * not a width — see `#src/utils/displayWidth.js`. */ export declare const TOOL_PREVIEW_LINE_MAX_COLUMNS = 200; /** Per-value cap inside a params summary, in terminal COLUMNS. */ export declare const TOOL_PARAM_VALUE_MAX_COLUMNS = 48; /** Whole params-summary cap (everything inside the parentheses), in terminal COLUMNS. */ export declare const TOOL_SUMMARY_MAX_COLUMNS = 120; /** The overflow/truncation marker used everywhere in this module. */ export declare const ELLIPSIS = "\u2026"; /** * Columns {@link ELLIPSIS} itself occupies, which every truncator has to reserve out of its budget * before slicing. Derived rather than written as `1` so it stays true of whatever marker is used. */ export declare const ELLIPSIS_WIDTH: number; /** * Style tag for one rendered line. The two surfaces map these to their own colour systems * (Ink `` props / raw ANSI): `dim` = greyed preview text, `added`/`removed` = diff * green/red. DL-8 colour semantics; monochrome surfaces render the raw text unchanged. */ export type ToolDisplayStyle = 'dim' | 'added' | 'removed'; /** One line of a tool call's rendered body/preview. */ export interface ToolDisplayLine { text: string; style: ToolDisplayStyle; } /** * Everything a formatter may look at for one tool call. Both surfaces build this from their * own state (the TUI from `ToolCallViewModel`, the plain surface from the message stream). */ export interface ToolCallDisplayInput { /** The gth tool name (`read_file`, `run_shell_command`, a custom tool's name). */ name: string; /** The raw streamed args JSON (possibly partial mid-stream, possibly invalid). */ argsText?: string; /** The final model-facing tool result, when it has arrived. */ result?: string; /** Live streamed child output (TUI only — the `tool_output` channel's accumulation). */ output?: string; /** The real `ToolMessage.status === 'error'` signal (TUI-C7) — never sniffed from text. */ isError?: boolean; /** * [[TUI-C69]] §5.4 — the gate refused this call BACK TO THE AGENT as a negotiation round, so the * status row reads as a clarification request rather than as a failure. See * {@link toolStatusDisplay}. Display-only, and additive to {@link isError}. */ raterClarification?: boolean; /** * True when the live child output ALREADY streamed raw to the user's terminal (the plain * surface's default sink). Formatters then suppress the duplicated output body and render * only the closing status — the TUI-C17 "output AND result repeat each other" dedupe. */ liveOutputAlreadyShown?: boolean; } /** * [[TUI-C69]] §5.4 — **what a finished tool call's status row says, glyph, word and tone**, for * both surfaces at once. * * `tone` rather than a colour name, because the two surfaces paint through different systems (Ink * `` and raw ANSI) and only one of them has a `magenta`. What is shared is the * DISTINCTION, which is the thing a divergence would break. */ export type ToolStatusTone = 'success' | 'warn' | 'error'; /** Glyph + word + tone for one finished call. */ export interface ToolStatusDisplay { glyph: string; label: string; tone: ToolStatusTone; } /** * [[TUI-C69]] §5.4 — **an intermediate rating rejection is a clarification request, never a * failure**, and this is the one place that decides so for every surface. * * A `destructive` rating at `auto` does not end anything: it goes back to the agent naming what * would make the command acceptable (§5.2), and on the happy path the very next round succeeds. * Drawn in the same red `✗ … [error]` vocabulary a genuinely failed tool gets, it teaches the user * that a working safety mechanism is a malfunction — which is the fastest way to make someone want * it switched off. It is measured, not hypothetical: a live `auto` run rendered a round-1 rejection * as `▾ ✗ 🔧 run_shell_command(command=git clone …) [error]`. * * **The glyph and the word carry the distinction, not the colour.** A monochrome terminal — and a * user who cannot tell red from orange — must still be able to tell a refused round from a broken * command, so `⚠` and the words differ before any tone is applied. * * **`raterClarification` outranks `isError`, and both are facts.** The call did not run, so * LangChain's error status is correct and every reader that grades on it is right to; what this * answers is the different question of how to TONE the row. Neither is ever sniffed from the * result text — legitimate output may begin with "Error handling…". */ export declare function toolStatusDisplay(input: Pick): ToolStatusDisplay; /** * Tolerant parse of a (possibly partial) streamed args JSON. Mirrors the view-model's * defensive posture: a half-streamed or malformed buffer never throws — it returns `null`. */ export declare function parseToolArgsSafe(argsText: string | undefined): Record | null; /** * Register the live config for inline-secret collection (TUI-C32 residual a) and, since TUI-C105, * for the configurable preview depth ({@link buildToolPreviewLines}). Resets the secret cache so * the next `getDefaultSecrets` recomputes with the config's inline literals folded in. * Idempotent; a later call with a fresh config supersedes the previous one. */ export declare function setToolDisplayConfig(config: unknown, command?: GthCommand): void; /** * Redact one display string with the same secret set every tool panel is rendered through. * * **Why this exists here rather than the caller harvesting its own secrets.** The redaction itself * is `redactText`, which is pure and lives in `utils/redactSecrets.js`; what is NOT pure is the * secret SET, and that set is this module's state. It is assembled by `collectSecretValues` from * the process env plus the config registered through {@link setToolDisplayConfig} — a seam the * agent runner calls, into core. A caller outside core can import `collectSecretValues` (it is * exported), but it has no registered config to hand it, so an outside harvest would silently drop * the inline-config literals (technique 1b) and produce a redaction *weaker* than the panel beside * it — the failure mode being visibly redacted output that is not actually the same guarantee. It * would also stand up a second cache to keep in step with this one. * * So the boundary is drawn at the string: a display caller sends text in and gets redacted text * back, and the secret values never leave this module. That also keeps the answer to "what counts * as a secret on a rendered surface" in one place, which is the property that made the shared * redactor worth having. * * Deliberately **not** wrapped in its own `try`/`catch`: `redactText` is already fail-safe * end-to-end (it returns the withheld marker on any internal error, never the raw text) and the * secret harvest degrades to `[]` with the provider patterns still applying. A second error policy * layered on top is how a fail-safe becomes a fail-open. * * **Redact before any other treatment**, neutralisation included — see `formatParamValue` for the * full reasoning. A literal secret carrying a control character stops matching the moment that * character is rewritten to a printable escape. */ export declare function redactForToolDisplay(text: string): string; /** Test seam: drop the cached secret literals + registered config so specs can vary both. */ export declare function resetToolDisplaySecretsCacheForTests(): void; /** * Parse the shared shell-result shape (`Executing '…'…\n\n…\n\n * `). Returns the captured output body and the trailing status text, or `null` * when the result is not shell-shaped. */ export declare function parseCommandOutputResult(result: string | undefined): { body: string; tail: string; } | null; /** * TUI-C32 residual c — is THIS tool call genuinely a shell-shaped result (name allows it AND the * result actually carries the `` shape)? Used by the plain surface to decide * `liveOutputAlreadyShown` (the child streamed live via the tool-output channel's default sink), * so a non-shell tool whose result merely quotes the marker no longer has its body suppressed. */ export declare function isShellShapedResult(name: string, result: string | undefined): boolean; /** The registry glyph for a tool name (generic `⚙` when unknown). */ export declare function getToolGlyph(name: string): string; /** * One-line call summary: `name(arg=val, other=…)`. Key args only (per the registry entry, or * all args for unknown tools), each value inlined + truncated, the whole parenthesised part * capped at {@link TOOL_SUMMARY_MAX_COLUMNS}, and everything secret-redacted (literals + * provider patterns). Unparsable (mid-stream/malformed) args render as `name(…)` — never a * raw JSON dump. `secrets` defaults to the env-derived literals; pass explicitly for tests. * * Redaction runs BEFORE every truncation step (per value in `formatParamValue`, and again * before the whole-summary cap): truncating first would bisect a literal secret longer than a * cap so it no longer literal-matches, leaking its head into the rendered summary * (fix-cycle-1 finding). The final pass over the assembled string is defense in depth only — * `redactText` is idempotent, so re-redacting already-marked text is safe. */ export declare function summariseToolCall(name: string, argsText: string | undefined, secrets?: readonly string[]): string; /** * The FULL (uncapped) body lines for a call: the registry formatter when one applies, else the * shape-based shell formatter, else the generic fallback (live output lines, then the final * result — both dim). Every line is secret-redacted and then neutralised. Used by the TUI's * EXPANDED panel; cap it with {@link capToolDisplayLines} for the collapsed preview. */ export declare function buildToolBodyLines(input: ToolCallDisplayInput, secrets?: readonly string[]): ToolDisplayLine[]; /** The strings the expanded panel paints beside the body, ready to draw. */ export interface ToolExpansionText { /** The streamed args text as one row's content, or `null` when the call carries no args. */ args: string | null; /** The routed notice, one element per row the expansion draws. */ noticeLines: string[]; } /** * [[TUI-C102]] — the two untrusted strings the EXPANDED tool panel paints OUTSIDE the body: the * raw streamed args text and the routed "🔧 Executing …" notice. Neither passes through * {@link buildToolBodyLines}, and neither is ours: `argsText` is whatever the model streamed — * under prompt injection, attacker-chosen, and for a shell call it is the command itself — and a * notice quotes that same command back. Under [[TUI-C99]] this expansion is the route by which a * human inspects a call they are being asked to permit, so it gets the treatment the rest of the * display path already has. * * **Both the renderer and the row-count oracle must call this, on the same input.** Neutralised * text is WIDER than the raw text it replaces, so a renderer that neutralises beside an estimator * that measures the raw string disagree about how many rows the panel occupies — and a row * miscount shows as content in the wrong place on screen rather than as an error anywhere. One * helper for both sides is what stops them drifting apart again. * * Same order as everywhere else on this path: **redact the raw text, then neutralise.** A secret * literal that carries a control character stops literal-matching the moment that character is * rewritten. Redaction is here at all because the collapsed summary above this row already * redacts, and an expansion that did not would print, under `/verbose`, the secret the row above * it hid. * * The notice splits through the module's own `toLines`, so a `\r\n` inside a quoted command is one * break there too; the args text is a single value and keeps its own newlines as visible `\x0a`, * which is what a streamed JSON buffer carrying a real newline actually contains. */ export declare function buildToolExpansionText(input: { argsText?: string; notice?: string; }, secrets?: readonly string[]): ToolExpansionText; /** * Apply the canonical render cap: at most `maxLines` lines (each capped at * {@link TOOL_PREVIEW_LINE_MAX_COLUMNS} columns with `…`), plus a dim `… (+N more lines)` overflow * marker when anything was cut. The marker line is IN ADDITION to the cap so exactly how much * was hidden is always stated (DL-4 transparency). * * This stays a GENERIC width utility and does not neutralise: it measures a line by what it * RENDERS as, so a caller passing genuinely styled text (the renderer's own SGR) gets a cap that * counts the columns the user sees rather than the escapes' own bytes. * * **No production caller passes styled text today.** {@link buildToolPreviewLines} is the only * one, and its lines arrive already neutralised from {@link buildToolBodyLines}, so on the tool * path there is nothing left to discount. The discount is kept because this is public API — core * exports every module — and a width utility that mismeasured styled text would be wrong for any * caller that did pass some. It is a property of the utility, not a live requirement of the tool * path, and the spec covering it says the same. */ export declare function capToolDisplayLines(lines: ToolDisplayLine[], maxLines?: number): ToolDisplayLine[]; /** * The collapsed inline preview: {@link buildToolBodyLines} capped at the depth this tool resolves * to — the per-tool `builtInTools..previewLines`, else the root `toolOutputPreviewLines`, * else the canonical {@link TOOL_OUTPUT_PREVIEW_LINES}. Both render surfaces call this, so the * setting reaches the Ink TUI and the plain observer through one change rather than two. * * **Depth `0` returns NO lines at all — not even the overflow marker** — so the call renders as its * summary line alone. That is a deliberate, local exception to the DL-4 transparency rule the * marker exists to serve: everywhere else "how much was hidden" is worth a row, but a user who has * asked for one line is not served by being given two, and the whole point of the setting is that * a tool call stops occupying a block. The marker returns at any depth above 0. * * **[[TUI-C108]] — a FAILED call floors at {@link TOOL_ERROR_PREVIEW_FLOOR_LINES}, and that is the * second exception.** The two are read together: depth 0 says "a tool call stops occupying a * block", and this says that a call which did not work is not a tool call occupying a block, it is * the reason the run is about to go wrong. An errored `ToolMessage` carries its explanation in its * content — the softened shell and MCP failures put the child's output and the error message * there — so honouring a depth of 0 would print `✗ name(args…)` and nothing else, which reports * that something broke while withholding what. The floor is on the OUTCOME, never on the console * level, so a scripted run that quiets successes still gets a failure that explains itself. * * A user on the default 10, or any depth above the floor, is unaffected: the floor only ever * raises a depth below it, so this cannot shorten anything. * * **Both surfaces get it**, because both call this function. The Ink TUI's expansion affordance * makes a floored failure less urgent there, not wrong: the alternative is the two surfaces * disagreeing about what depth 0 means for a failure, which is exactly the divergence this * function exists to prevent. * * One case is out of reach by construction: a shell-shaped result renders with * `liveOutputAlreadyShown` on the plain surface, so its body is empty before any cap applies and * there is nothing for the floor to keep. Nothing is lost — that child's output already streamed * raw and ungated through the tool-output channel, at every console level. * * The cap stays step 3 of the [[TUI-C102]] order (redact → neutralise → cap): the depth decides * only HOW MANY already-neutralised lines survive, and the short-circuit above drops all of them * rather than reordering anything. Nothing here can put an escape back on a row. */ export declare function buildToolPreviewLines(input: ToolCallDisplayInput, secrets?: readonly string[]): ToolDisplayLine[]; /** * Render one styled line as a raw string for the plain surface. Each line is SELF-STYLED * (its own SGR open + reset) so lines compose safely regardless of surrounding styling. * With `colour` false the raw text is returned unchanged — the clean monochrome degradation * for non-TTY/piped output (DL-7); diff lines still read via their `+`/`-` prefixes. */ export declare function renderToolLineAnsi(line: ToolDisplayLine, colour: boolean): string;