/** * Tool render-intent vocabulary: the provider-neutral types a tool declares via * `ToolDefinition.presentCall`/`ToolDefinition.presentResult` to say * how ONE of its calls renders in a UI (an editor's tool-call card, a CLI log * line). A UI bridge switches on the `card` tag to map each intent to its own * wire shape, so a UI never special-cases tool names. * * This is the UI-facing surface of `dsh-tools`, kept separate from the registry * and execution core in `index.ts`: this module owns ONLY presentation * vocabulary and references none of the execution types, so the dependency runs * one way (`index.ts` imports these views for the `ToolDefinition` method * signatures). The opaque `meta` presentation channel is execution plumbing and * lives with the registry in `index.ts`, not here. * * See the render-intent-union RFC * (docs/rfc/implemented/architecture/2026-07-02-tool-render-intent-union.md). * * @module @deepseek-ai/dsh-tools/src/presentation */ import type { ContentBlock } from '@deepseek-ai/dsh-llm' /** * Category of a tool call, used by a UI to pick an icon / treatment. A neutral * vocabulary owned here (NOT an ACP type) so tools describe themselves without * depending on any client protocol; a UI bridge maps it to its own enum. The * member set mirrors the common ACP `ToolKind` values; `other` is the default. */ export type ToolCallKind = 'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'fetch' | 'other' /** * A file location a tool reads or modifies, so a capable UI can "follow along" — * highlight or jump to the file (and line) as the tool runs. Provider-neutral; * a UI bridge maps it to its own affordance (the ACP bridge forwards it as * `tool_call.locations`). `path` is what the tool operated on (the model-facing * path); `line` is an optional 1-based line to focus (e.g. a read's offset). */ export interface FileLocation { path: string line?: number } /** * A single-file change a tool is about to make, for a UI that renders inline * diffs (an editor's diff card). Provider-neutral; the ACP bridge forwards it as * a `{ type: 'diff' }` tool-call content block. `oldText` is `null` for a * new-file create (nothing to diff against); an overwrite also uses `null`, * because a call-time presenter has no access to the file's prior content. */ export interface FileDiff { path: string /** Prior content, or `null` for a new file / an overwrite (no prior content available at call time). */ oldText: string | null /** Content after the change. */ newText: string } /** * How a tool wants ONE of its calls shown in a UI (an editor's tool-call card, a * CLI log line) BEFORE the result is known — the *pending* state. A `card`-tagged * discriminated union: a tool declares its render INTENT once and a UI bridge * switches on `card` to map it to the bridge's own wire shape. Provider-neutral — * the tool owns its presentation, so a UI never special-cases tool names. * * Returned by `ToolDefinition.presentCall`. See the render-intent-union * RFC (docs/rfc/implemented/architecture/2026-07-02-tool-render-intent-union.md). */ export type ToolCallView = GenericCallView | TerminalCallView | DiffCallView /** * The default card: a titled tool-call row with an optional category icon, a * salient raw input, extra content blocks, and follow-along file locations. Any * tool whose call is not a terminal or a diff uses this. */ export interface GenericCallView { card: 'generic' /** * Human-readable, always-visible label describing what THIS call does. Keep it * short — a UI shows it as a card header / log line. */ title: string /** Category for icon/treatment; defaults to `other` when omitted. */ kind?: ToolCallKind /** * The salient input to surface in a detail/expanded view (e.g. a background * task id). Omit to show nothing; a string renders as-is, an object as pretty * JSON. NOT the full raw args object unless that is genuinely what a reader wants. */ rawInput?: unknown /** * UI-facing content blocks to show on the pending call alongside the title. * Omit to show none. A UI maps these to its own content blocks. */ content?: ContentBlock[] /** Files this call reads/modifies, for editor follow-along. Omit for a call that touches no file. */ locations?: FileLocation[] } /** * A call that IS a shell command running in a working directory: a capable UI * renders it as a terminal card (cwd-headed, with the command as the title and * live/afterward output from the {@link TerminalResultView}); an incapable UI * falls back to a generic card whose body is the fenced command output. Set by a * tool whose call is a foreground command (e.g. `bash`). */ export interface TerminalCallView { card: 'terminal' /** The command, shown as the terminal card's title / header line. */ title: string /** * A human-readable one-line summary of what the command does, rendered ABOVE * the terminal card (the card itself has no description slot). Omit for none. */ description?: string /** * Working directory the command runs in, shown as the terminal header. An * ABSOLUTE path is used as-is; a RELATIVE path is resolved by the UI bridge * against the session workspace (the pure presenter can't see the session cwd). * Omit entirely to let the bridge use the session workspace. */ cwd?: string } /** * A call that creates or modifies files, rendered as an inline diff card by a * capable UI. Set by a tool whose call writes/edits a file (e.g. `write`, * `edit`). The diffs are derived from the call ARGUMENTS (a create's `oldText` is * `null`); the tool emits a separate {@link DiffResultView} after `execute` — the * applied change (an edit/overwrite hunk with context, or a whole-file diff for a * create). */ export interface DiffCallView { card: 'diff' /** Card header (e.g. `Write foo.txt`). */ title: string /** One entry per file the call changes. */ diffs: FileDiff[] /** Files this call modifies, for editor follow-along (usually the diffs' paths). */ locations?: FileLocation[] } /** * How a tool wants the COMPLETED call shown — the *result* state, after `execute` * returns. A `card`-tagged union mirroring {@link ToolCallView}: a UI switches on * `card`. Lets the tool reformat its result for a UI distinctly from the * model-facing text it returned from `execute`. Returned by * `ToolDefinition.presentResult`; omitting the method keeps the pending * title and renders the raw result content. */ export type ToolResultView = GenericResultView | TerminalResultView | DiffResultView /** * The default completed card: an optional replacement title and reformatted * content. Omit a field to keep the pending title / render the raw result content. */ export interface GenericResultView { card: 'generic' /** Replacement title for the completed call. Omit to keep the pending-state title. */ title?: string /** * UI-facing result content (harness {@link ContentBlock}s), reformatted from * the model-facing result. Omit to let the UI render the raw result content. */ content?: ContentBlock[] } /** * The completed state of a {@link TerminalCallView}: the captured output and exit * status. A capable UI renders `output` in the terminal card and shows an * exit-status pill; an incapable UI gets a fenced ```console fallback the BRIDGE * derives from `output` (the tool does not double-encode it). */ export interface TerminalResultView { card: 'terminal' /** Replacement title for the completed call. Omit to keep the pending-state title. */ title?: string /** Captured command output (stdout+stderr as the tool chooses to combine them). */ output?: string /** * Process exit code, when the run ended by exiting (not a signal). Lets a * capable UI show an exit-status pill. Omit when killed by a signal or unknown. */ exitCode?: number /** Signal name that killed the process (e.g. `SIGTERM`). Mutually exclusive with `exitCode`. */ signal?: string } /** * A completed file mutation rendered as an inline diff card, the *result-time* * analogue of {@link DiffCallView}. Set by a tool whose `execute` applied a file * change (e.g. `write`, `edit`): `diffs` are the change to show — typically the * APPLIED hunks computed from the before/after content (one entry per hunk, each * with surrounding context lines), so the editor shows the real change in place; * a tool with no before-image (e.g. a file create) may instead give a whole-file * diff (`oldText: null`). A `tool_call_update`'s content REPLACES the call's * content in an editor, so a mutation tool returns this even when it duplicates * the call-time snippet — otherwise the model-facing result text would replace * (clobber) the pending diff card. */ export interface DiffResultView { card: 'diff' /** Replacement title for the completed call. Omit to keep the pending-state title. */ title?: string /** The change to show, in file order — applied contextual hunks, or a whole-file diff when there is no before-image. */ diffs: FileDiff[] }