/** * Tool-card presentation: headers, diffs, compact bursts, JSON bodies. */ import type { AskUserQuestionItem } from '@deepseek-ai/dsh-user-questions'; import { type TextSegment } from './term-text.js'; import type { DiffDisplayLine, Row, ToolDiffHunk } from './transcript-types.js'; import { type ToolApproval } from './approval-state.js'; export declare const SHELL_TOOL_NAMES: Set; export declare const DIFF_TOOL_NAMES: Set; export declare const JOB_TOOL_NAMES: Set; /** Format a model list compactly: show the first few entries and an ellipsis. */ export declare function formatModelList(models: readonly string[], max?: number): string; /** Prefer the fields a human scans for; fall back to the first scalar pairs. */ export declare function friendlyArgsSummary(name: string, args: string): string; export declare function countDiffLines(hunks: readonly ToolDiffHunk[] | undefined): number; export declare function countDiffAddDel(hunks: readonly ToolDiffHunk[] | undefined): { add: number; del: number; }; /** * Per-file change counts for the compact view's collapsed line. * * The summary header says "3 files" and one total; a reader deciding whether to * expand needs to know *which* file moved. Files keep the order they first * appear in, so the line does not reshuffle as the turn progresses. */ export declare function compactFileStats(edits: readonly { name: string; args: string; summary?: string; diff?: readonly ToolDiffHunk[]; }[]): Array<{ path: string; add: number; del: number; }>; /** * The failed tools of one burst, one line each. * * A count in the header is not enough: the reader has to see *what* failed * without expanding, because a failure is the reason to expand at all. */ export declare function compactFailureLines(calls: readonly { title: string; summary?: string; status?: string; }[]): string[]; /** * Git diffstat token, deletions first like `-13 +24`. Zero parts drop out * (a new file shows only `+24`); empty when the diff has no counted lines. */ export declare function diffStatToken(add: number, del: number): string; export declare const READ_TOOL_NAMES: Set; export declare const TOOL_FLIP_MS = 280; export declare function toolTargetPath(name: string, args: string, fallback?: string): string; /** Path shown on a compact single-file edit summary. */ export declare function compactEditPath(item: { name: string; args: string; summary?: string; diff?: readonly { path?: string; }[]; }): string; export declare function sameToolPath(left: string, right: string): boolean; export declare function countOutputLines(text: string): number; export declare function mergeableToolKind(name: string): 'read' | 'edit' | undefined; /** * Consecutive same-path reads (or edits) collapse onto one card. * A → B → C → A becomes four cards; A ×5 stays one card with repeats=5. */ export declare function canMergeToolCall(previous: Extract | undefined, next: { name: string; args: string; }): previous is Extract; export declare function compactToolGroups(tools: readonly Extract[]): { edits: Extract[]; calls: Extract[]; failedCalls: number; }; /** * Split compact-view tools into the bursts that belong with each assistant * reply: tools after reply N sit with that reply, until the next reply. */ export declare function compactToolBursts(rows: readonly Row[]): Array<{ after: Extract | undefined; groups: ReturnType; }>; export declare const SUBAGENT_TOOL_NAMES: Set; /** * Tool calls that already have a dedicated transcript card (goal/change, * plan dock, question dialog). Showing them again as raw `get_goal` cards * just duplicates chrome. */ export declare const HIDDEN_TOOL_NAMES: Set; /** * Tools whose card is the *question* they asked, not a generic tool card (B2.4). * * `ask_user_question` is the only one: its durable representation is the question * card built from the call's arguments, and drawing both was the duplicate this * round removes. It is a separate set from {@link HIDDEN_TOOL_NAMES} on purpose — * hidden means "no representation at all" (`get_goal`), while this means "the * representation is drawn by the question projection". A tool card that never * appears is a *different* decision from one whose content is shown elsewhere, and * the audit has to be able to tell them apart. */ export declare const QUESTION_TOOL_NAMES: Set; /** * Tools whose card is the *artifact* they act on, not a tool card of their own. * * `exit_plan_mode` is the plan review: the plan artifact carries the reviewed body, * the review's outcome and the lifecycle lines, so a generic card beside all of that * was a third representation of one event (B2.5 §15). */ export declare const PLAN_TOOL_NAMES: Set; export declare function toolTitle(name: string): string; export declare function planReviewOf(question: AskUserQuestionItem): boolean; /** Derive the intended file change from a mutation tool's arguments. */ export declare function diffHunksFromArgs(name: string, argsRaw: string): ToolDiffHunk[] | null; /** One-line friendly tool-call presentation (command / path / arg summary). */ export declare function presentToolCall(name: string, args: string): { title: string; summary: string; command?: string; cwd?: string; diff?: ToolDiffHunk[]; }; /** Validate a tool/result meta payload's structured diff, mirroring the web card. */ export declare function diffMetaDiffs(meta: unknown): ToolDiffHunk[] | null; /** Split one diff side into content lines (trailing newline is a terminator). */ export declare function diffContentLines(text: string): string[]; export type { DiffDisplayLine } from './transcript-types.js'; /** Cap one flat diff/body row list to `maxLines` while preserving the final line. */ export declare function capDisplayLines(lines: readonly DiffDisplayLine[], maxLines: number): DiffDisplayLine[]; /** Running / ok / error → theme token for the status dot and status word only. */ export declare function toolStateColor(status: 'running' | 'ok' | 'error' | undefined): string; export declare function toolStateLabel(status: 'running' | 'ok' | 'error' | undefined): string; /** Header + SGR spans: default title, dim operand, colored ●. `[ok]` is omitted — the green dot is enough. */ export declare function buildToolHeader(input: { focused: boolean; expanded: boolean; title: string; summary: string; status?: 'running' | 'ok' | 'error'; command?: string; signal?: string; exitCode?: number; spinner?: string; flipping?: boolean; diffStat?: { add: number; del: number; }; /** * The approval that guarded this call (B2.4), when one did. * * It rides on the title — right after the tool's own name — rather than at the * end of the row: the tail is what a narrow terminal truncates first, and a card * whose whole point is "this needed a decision" must not be the one card that * loses it. It is also the only place the state is shown, since approval no * longer writes a row of its own. */ approval?: ToolApproval; }): { plain: string; segments: TextSegment[]; }; /** * The title chip: the state, and — when the reader is being told something * weaker — why they can trust it less. * * `live` and `durable` are the same fact at two moments (a person decided, and * the log still says so), so they print the state alone. `policy` adds `auto`, * and `inferred` says so: a resume that found only the Harness's refusal sentence * in a tool result is a reading, and the card has to admit it. */ export declare function approvalChipText(approval: ToolApproval): string; /** How many terminal rows a tool body occupies after wrapping. */ export declare function wrappedToolBodyLineCount(lines: readonly { text: string; }[], width: number): number; /** * True when the full tool body plus a one-line header fits in the workspace * (the rows between the title bar and the input chrome). Oversized bodies * open a dedicated inspect overlay instead of dumping into the transcript. */ export declare function toolBodyFitsWorkspace(bodyLines: number, workspaceRows: number): boolean; /** * Flatten hunks into a line diff with context, plus the counts footer. * * The card printed every old line as `-` and every new line as `+` — a one-line * change in a hundred-line file became two hundred rows. Changed lines are now * paired with their context, distant changes are separate runs with the gap * counted, and a replaced pair emphasises the characters that differ. When the * whole thing cannot fit the card's budget, the counts and a pointer to the full * view are what remain: a truncated diff is worse than none. * @param diffs - the tool's hunks. * @param maxLines - the card body budget. * @returns display lines, at most `maxLines` of them. */ export declare function renderToolDiff(diffs: ToolDiffHunk[], maxLines: number, width?: number): DiffDisplayLine[]; /** Convert any parsed JSON value into readable indented display lines. */ export declare function friendlyJsonLines(value: unknown, depth?: number): string[]; /** Minimal tool-row shape the expanded-body renderer reads. */ export interface ToolBodySource { name?: string; diff?: ToolDiffHunk[]; command?: string; status?: 'running' | 'ok' | 'error'; output: string; args: string; } /** Try to parse a result body as one JSON document, when it looks like one. */ export declare function parseJsonBody(text: string): unknown | null; /** * The expanded body of one tool card: diffs and shell output keep their * dedicated views; every other tool's JSON arguments and JSON result are * converted into readable indented content instead of raw JSON text. */ export declare function toolBodyLines(row: ToolBodySource, maxLines: number, width?: number): DiffDisplayLine[]; export interface NamedToolBodySource extends ToolBodySource { name?: string; } export declare function specializedToolBody(row: NamedToolBodySource, maxLines?: number): DiffDisplayLine[] | null; /** Recover the shell tools' exit marker, mirroring @deepseek-ai/dsh-shell/render. */ export declare function parseExitStatus(text: string): { body: string; exitCode?: number; signal?: string; };