mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Address the review of the search render card: - The search result view carries no `content`: it was a no-op for every consumer and serialized the whole search text twice. A UI without a search card falls back to the raw tool/result content; the TUI stays byte-identical to the pre-search-card generic fallback. - Bound the serialized presentationMeta with a configurable searchMetaMaxBytes (default 64 KiB): the inline item cap does not bound bytes, and spill-policy only shrinks content, never meta. capMetaBytes drops trailing groups/paths. - Share one retention pass (retainGrepMatches/retainGlobPaths in search-core) between the model-facing render and the meta projection; remove the second cap/preview implementation and the presentation<->grep module cycle by moving GrepMatch/previewLine to search-core. - Rename the result-view discriminant kind -> shape so it no longer collides with GenericCallView.kind (ToolCallKind, whose values include 'search'). - Narrow the entry export surface to consumed symbols. - Sync the three bilingual ToolResultView doc pairs and the Agent Note pair; document the deliberate empty-card acceptance vs diffsFromMeta. - Regenerate config/tool/cordis catalogs for the new config field.
256 lines
10 KiB
TypeScript
256 lines
10 KiB
TypeScript
/**
|
|
* 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).
|
|
* @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 or treatment. The
|
|
* provider-neutral vocabulary lets tools describe themselves without depending
|
|
* on a particular client; `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. `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. `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
|
|
}
|
|
|
|
/**
|
|
* Provider-neutral pending-call presentation. Tools declare one tagged intent;
|
|
* UI bridges map it without special-casing tool names.
|
|
*/
|
|
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 | SearchResultView
|
|
|
|
/**
|
|
* 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}. Because a completed UI update replaces the
|
|
* pending card content, mutation tools return this even when it repeats the
|
|
* call-time diff; otherwise raw result text would replace the diff.
|
|
*/
|
|
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[]
|
|
}
|
|
|
|
/** One matched line inside a {@link SearchFileMatches} group: its 1-based line number and text. */
|
|
export interface SearchLineMatch {
|
|
/** 1-based line number of the match within its file. */
|
|
lineNumber: number
|
|
/** The matched line text, as the tool surfaced it (the per-line preview budget already applied). */
|
|
line: string
|
|
}
|
|
|
|
/** One file's grouped content matches for a {@link SearchMatchesResultView}, in first-seen file order. */
|
|
export interface SearchFileMatches {
|
|
/** The file the matches belong to (the model-facing display path). */
|
|
path: string
|
|
/** The file's matched lines, in output order. */
|
|
matches: SearchLineMatch[]
|
|
}
|
|
|
|
/**
|
|
* A completed content search (`grep`) rendered as a search card whose matches are
|
|
* grouped by file, so a capable UI can list each file as an expandable group of
|
|
* its matched lines. `shape: 'matches'` discriminates this variant from the path
|
|
* variant ({@link SearchPathsResultView}) within {@link SearchResultView}. The
|
|
* discriminant is `shape`, not `kind`, so it never collides with the
|
|
* {@link ToolCallKind} `kind` an icon-picking bridge reads off a call view.
|
|
*/
|
|
export interface SearchMatchesResultView {
|
|
card: 'search'
|
|
shape: 'matches'
|
|
/** Replacement title for the completed call. Omit to keep the pending-state title. */
|
|
title?: string
|
|
/** Matched lines grouped by file, in first-seen file order. */
|
|
files: SearchFileMatches[]
|
|
/**
|
|
* Whether the tool capped the inline result: `files` carries only the retained
|
|
* matches, not every match the search found. A UI shows a capped indicator so it
|
|
* never presents a partial group as complete.
|
|
*/
|
|
truncated: boolean
|
|
/** Total matches the search found before capping (equals the retained count when not `truncated`). */
|
|
total: number
|
|
}
|
|
|
|
/**
|
|
* A completed path search (`glob`) rendered as a search card whose result is a flat
|
|
* path list. `shape: 'paths'` discriminates this variant from the grouped-matches
|
|
* variant ({@link SearchMatchesResultView}) within {@link SearchResultView}.
|
|
*/
|
|
export interface SearchPathsResultView {
|
|
card: 'search'
|
|
shape: 'paths'
|
|
/** Replacement title for the completed call. Omit to keep the pending-state title. */
|
|
title?: string
|
|
/** The discovered paths, in the tool's result order (the retained page when `truncated`). */
|
|
paths: string[]
|
|
/**
|
|
* Whether the tool capped the inline result: `paths` carries only the retained
|
|
* page, not every path the search found. A UI shows a capped indicator so it
|
|
* never presents a partial list as complete.
|
|
*/
|
|
truncated: boolean
|
|
/** Total paths the search found before capping (equals `paths.length` when not `truncated`). */
|
|
total: number
|
|
}
|
|
|
|
/**
|
|
* A completed search rendered as a search card, the result-time view a discovery
|
|
* tool (`grep`, `glob`) returns from `presentResult`. One `card: 'search'` view
|
|
* with two `shape`-discriminated variants: grouped-by-file content matches
|
|
* ({@link SearchMatchesResultView}) and a flat path list
|
|
* ({@link SearchPathsResultView}). Both carry a `truncated`/`total` signal so a UI
|
|
* never presents a capped result as complete. The view carries no result text: a
|
|
* UI without a search card falls back to the raw `tool/result` content. There is
|
|
* no call-time analogue: a search call stays a {@link GenericCallView}
|
|
* (`kind: 'search'`) because the pending state has no matches or paths to show —
|
|
* the structured shape exists only after `execute`.
|
|
*/
|
|
export type SearchResultView = SearchMatchesResultView | SearchPathsResultView
|