presentation.ts 7.4 KB

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