presentation.ts 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. /**
  2. * Tool render-intent vocabulary: the provider-neutral types a tool declares via
  3. * `ToolDefinition.presentCall`/`ToolDefinition.presentResult` to say
  4. * how ONE of its calls renders in a UI (an editor's tool-call card, a CLI log
  5. * line). A UI bridge switches on the `card` tag to map each intent to its own
  6. * wire shape, so a UI never special-cases tool names.
  7. *
  8. * This is the UI-facing surface of `dsh-tools`, kept separate from the registry
  9. * and execution core in `index.ts`: this module owns ONLY presentation
  10. * vocabulary and references none of the execution types, so the dependency runs
  11. * one way (`index.ts` imports these views for the `ToolDefinition` method
  12. * signatures). The opaque `meta` presentation channel is execution plumbing and
  13. * lives with the registry in `index.ts`, not here.
  14. *
  15. * See the render-intent-union RFC
  16. * (docs/rfc/implemented/architecture/2026-07-02-tool-render-intent-union.md).
  17. *
  18. * @module @deepseek-ai/dsh-tools/src/presentation
  19. */
  20. import type { ContentBlock } from '@deepseek-ai/dsh-llm'
  21. /**
  22. * Category of a tool call, used by a UI to pick an icon / treatment. A neutral
  23. * vocabulary owned here (NOT an ACP type) so tools describe themselves without
  24. * depending on any client protocol; a UI bridge maps it to its own enum. The
  25. * member set mirrors the common ACP `ToolKind` values; `other` is the default.
  26. */
  27. export type ToolCallKind = 'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'fetch' | 'other'
  28. /**
  29. * A file location a tool reads or modifies, so a capable UI can "follow along" —
  30. * highlight or jump to the file (and line) as the tool runs. Provider-neutral;
  31. * a UI bridge maps it to its own affordance (the ACP bridge forwards it as
  32. * `tool_call.locations`). `path` is what the tool operated on (the model-facing
  33. * path); `line` is an optional 1-based line to focus (e.g. a read's offset).
  34. */
  35. export interface FileLocation {
  36. path: string
  37. line?: number
  38. }
  39. /**
  40. * A single-file change a tool is about to make, for a UI that renders inline
  41. * diffs (an editor's diff card). Provider-neutral; the ACP bridge forwards it as
  42. * a `{ type: 'diff' }` tool-call content block. `oldText` is `null` for a
  43. * new-file create (nothing to diff against); an overwrite also uses `null`,
  44. * because a call-time presenter has no access to the file's prior content.
  45. */
  46. export interface FileDiff {
  47. path: string
  48. /** Prior content, or `null` for a new file / an overwrite (no prior content available at call time). */
  49. oldText: string | null
  50. /** Content after the change. */
  51. newText: string
  52. }
  53. /**
  54. * How a tool wants ONE of its calls shown in a UI (an editor's tool-call card, a
  55. * CLI log line) BEFORE the result is known — the *pending* state. A `card`-tagged
  56. * discriminated union: a tool declares its render INTENT once and a UI bridge
  57. * switches on `card` to map it to the bridge's own wire shape. Provider-neutral —
  58. * the tool owns its presentation, so a UI never special-cases tool names.
  59. *
  60. * Returned by `ToolDefinition.presentCall`. See the render-intent-union
  61. * RFC (docs/rfc/implemented/architecture/2026-07-02-tool-render-intent-union.md).
  62. */
  63. export type ToolCallView = GenericCallView | TerminalCallView | DiffCallView
  64. /**
  65. * The default card: a titled tool-call row with an optional category icon, a
  66. * salient raw input, extra content blocks, and follow-along file locations. Any
  67. * tool whose call is not a terminal or a diff uses this.
  68. */
  69. export interface GenericCallView {
  70. card: 'generic'
  71. /**
  72. * Human-readable, always-visible label describing what THIS call does. Keep it
  73. * short — a UI shows it as a card header / log line.
  74. */
  75. title: string
  76. /** Category for icon/treatment; defaults to `other` when omitted. */
  77. kind?: ToolCallKind
  78. /**
  79. * The salient input to surface in a detail/expanded view (e.g. a background
  80. * task id). Omit to show nothing; a string renders as-is, an object as pretty
  81. * JSON. NOT the full raw args object unless that is genuinely what a reader wants.
  82. */
  83. rawInput?: unknown
  84. /**
  85. * UI-facing content blocks to show on the pending call alongside the title.
  86. * Omit to show none. A UI maps these to its own content blocks.
  87. */
  88. content?: ContentBlock[]
  89. /** Files this call reads/modifies, for editor follow-along. Omit for a call that touches no file. */
  90. locations?: FileLocation[]
  91. }
  92. /**
  93. * A call that IS a shell command running in a working directory: a capable UI
  94. * renders it as a terminal card (cwd-headed, with the command as the title and
  95. * live/afterward output from the {@link TerminalResultView}); an incapable UI
  96. * falls back to a generic card whose body is the fenced command output. Set by a
  97. * tool whose call is a foreground command (e.g. `bash`).
  98. */
  99. export interface TerminalCallView {
  100. card: 'terminal'
  101. /** The command, shown as the terminal card's title / header line. */
  102. title: string
  103. /**
  104. * A human-readable one-line summary of what the command does, rendered ABOVE
  105. * the terminal card (the card itself has no description slot). Omit for none.
  106. */
  107. description?: string
  108. /**
  109. * Working directory the command runs in, shown as the terminal header. An
  110. * ABSOLUTE path is used as-is; a RELATIVE path is resolved by the UI bridge
  111. * against the session workspace (the pure presenter can't see the session cwd).
  112. * Omit entirely to let the bridge use the session workspace.
  113. */
  114. cwd?: string
  115. }
  116. /**
  117. * A call that creates or modifies files, rendered as an inline diff card by a
  118. * capable UI. Set by a tool whose call writes/edits a file (e.g. `write`,
  119. * `edit`). The diffs are derived from the call ARGUMENTS (a create's `oldText` is
  120. * `null`); the tool emits a separate {@link DiffResultView} after `execute` — the
  121. * applied change (an edit/overwrite hunk with context, or a whole-file diff for a
  122. * create).
  123. */
  124. export interface DiffCallView {
  125. card: 'diff'
  126. /** Card header (e.g. `Write foo.txt`). */
  127. title: string
  128. /** One entry per file the call changes. */
  129. diffs: FileDiff[]
  130. /** Files this call modifies, for editor follow-along (usually the diffs' paths). */
  131. locations?: FileLocation[]
  132. }
  133. /**
  134. * How a tool wants the COMPLETED call shown — the *result* state, after `execute`
  135. * returns. A `card`-tagged union mirroring {@link ToolCallView}: a UI switches on
  136. * `card`. Lets the tool reformat its result for a UI distinctly from the
  137. * model-facing text it returned from `execute`. Returned by
  138. * `ToolDefinition.presentResult`; omitting the method keeps the pending
  139. * title and renders the raw result content.
  140. */
  141. export type ToolResultView = GenericResultView | TerminalResultView | DiffResultView
  142. /**
  143. * The default completed card: an optional replacement title and reformatted
  144. * content. Omit a field to keep the pending title / render the raw result content.
  145. */
  146. export interface GenericResultView {
  147. card: 'generic'
  148. /** Replacement title for the completed call. Omit to keep the pending-state title. */
  149. title?: string
  150. /**
  151. * UI-facing result content (harness {@link ContentBlock}s), reformatted from
  152. * the model-facing result. Omit to let the UI render the raw result content.
  153. */
  154. content?: ContentBlock[]
  155. }
  156. /**
  157. * The completed state of a {@link TerminalCallView}: the captured output and exit
  158. * status. A capable UI renders `output` in the terminal card and shows an
  159. * exit-status pill; an incapable UI gets a fenced ```console fallback the BRIDGE
  160. * derives from `output` (the tool does not double-encode it).
  161. */
  162. export interface TerminalResultView {
  163. card: 'terminal'
  164. /** Replacement title for the completed call. Omit to keep the pending-state title. */
  165. title?: string
  166. /** Captured command output (stdout+stderr as the tool chooses to combine them). */
  167. output?: string
  168. /**
  169. * Process exit code, when the run ended by exiting (not a signal). Lets a
  170. * capable UI show an exit-status pill. Omit when killed by a signal or unknown.
  171. */
  172. exitCode?: number
  173. /** Signal name that killed the process (e.g. `SIGTERM`). Mutually exclusive with `exitCode`. */
  174. signal?: string
  175. }
  176. /**
  177. * A completed file mutation rendered as an inline diff card, the *result-time*
  178. * analogue of {@link DiffCallView}. Set by a tool whose `execute` applied a file
  179. * change (e.g. `write`, `edit`): `diffs` are the change to show — typically the
  180. * APPLIED hunks computed from the before/after content (one entry per hunk, each
  181. * with surrounding context lines), so the editor shows the real change in place;
  182. * a tool with no before-image (e.g. a file create) may instead give a whole-file
  183. * diff (`oldText: null`). A `tool_call_update`'s content REPLACES the call's
  184. * content in an editor, so a mutation tool returns this even when it duplicates
  185. * the call-time snippet — otherwise the model-facing result text would replace
  186. * (clobber) the pending diff card.
  187. */
  188. export interface DiffResultView {
  189. card: 'diff'
  190. /** Replacement title for the completed call. Omit to keep the pending-state title. */
  191. title?: string
  192. /** The change to show, in file order — applied contextual hunks, or a whole-file diff when there is no before-image. */
  193. diffs: FileDiff[]
  194. }