claudeToolDisplay.ts ×17

Frontier kind: Code frontier

unlabeled · c_3955fa535f1f

333 tests · 17764 LOC · 65 files · introduces 0 tests · 276 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
17 ranges276 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
1168 ranges17764 lines · 65 files · Browse complete extent
All tests (intent)
333 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

1 file ranked by introduced lines: 276 introduced LOC across 17 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/agentHost/node/claude/claudeToolDisplay.ts 276 introduced LOC · 17 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- claudeToolDisplay.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { localize } from '../../../../nls.js';
7 > import { appendEscapedMarkdownInlineCode, escapeMarkdownLinkLabel } from '../../../../base/common/htmlContent.js';
8 > import { basename } from '../../../../base/common/resources.js';
9 > import { truncate } from '../../../../base/common/strings.js';
10 > import { URI } from '../../../../base/common/uri.js';
11 > import { toToolCallMeta, type IToolCallMeta, type ToolKind } from '../../common/meta/agentToolCallMeta.js';
12 > import type { StringOrMarkdown } from '../../common/state/protocol/state.js';
13 > import { getServerToolDisplay } from '../shared/serverToolGroups.js';
14 >
15 > /**
16 > * Phase 7 S4 — pure tool-name → display/permission helpers for Claude.
17 > *
18 > * Mirrors the shape of [copilotToolDisplay.ts](../copilot/copilotToolDisplay.ts)
19 > * but is keyed off the SDK's built-in tool list. The mapping table lives
20 > * here (and is snapshot-tested in
21 > * [claudeToolDisplay.test.ts](../../test/node/claudeToolDisplay.test.ts))
22 > * so renames of either the SDK tool names or the host's `permissionKind`
23 > * union flow through compile-checks and the snapshot diff.
24 > *
25 > * No I/O, no DI; safe to import from any layer of `agentHost`.
26 > */
27 >
28 > /**
29 > * Auto-approval kind reported alongside `pending_confirmation` signals
30 > * (see `IAgentToolPendingConfirmationSignal.permissionKind` in
31 > * [agentService.ts:317](../../common/agentService.ts#L317)).
32 > *
33 > * Phase 7 only emits the subset relevant to Claude's built-in tools —
34 > * `hook` and `memory` are reserved for later phases.
35 > */
36 > export type ClaudePermissionKind =
37 > | 'shell'
38 > | 'write'
39 > | 'mcp'
40 > | 'read'
41 > | 'url'
42 > | 'skill'
43 > | 'custom-tool';
44 >
45 > /**
46 > * Phase 8.5 — rendering hint for the workbench. Drives terminal /
47 > * search / subagent renderers (the workbench picks a renderer off
48 > * `_meta.toolKind`; unknown values fall through to the generic tool
49 > * renderer). Mirror of
50 > * [`copilotToolDisplay.getToolKind`](../copilot/copilotToolDisplay.ts).
51 > */
52 > export type ClaudeToolKind = ToolKind;
53 >
54 > /**
55 > * Which field on the SDK's `tool_input` carries the path/url surfaced
56 > * to the user (and tracked by Phase 8 for file-edit tools). One field
57 > * per tool — tools without a path-bearing field omit this.
58 > */
59 > type ClaudeToolPathField = 'file_path' | 'notebook_path' | 'path' | 'url';
60 >
61 > /**
62 > * Single source-of-truth row for one of Claude's built-in tools. Every
63 > * structural fact the host needs about the tool sits in this row; the
64 > * exported helpers below are one-liners over the table. Adding a new
65 > * SDK tool means adding one row and one `displayName` arm. The
66 > * snapshot test in [claudeToolDisplay.test.ts](../../test/node/claudeToolDisplay.test.ts)
67 > * fails until both this map and the snapshot are updated together.
68 > *
69 > * `displayName` is intentionally NOT on the row — it is user-facing
70 > * and must be `localize()`-d, which we cannot do at module-init time
71 > * without freezing the bundle's locale. Lookup lives in
72 > * {@link getClaudeToolDisplayName}.
73 > */
74 > interface ClaudeToolRow {
75 > readonly permissionKind: ClaudePermissionKind;
76 > /** Field on `tool_input` carrying the path/url for this tool, if any. */
77 > readonly pathField?: ClaudeToolPathField;
78 > /** True for tools whose execution writes to disk and is tracked by `FileEditTracker` (Phase 8). */
79 > readonly isFileEdit?: true;
80 > /**
81 > * True for tools the SDK never auto-approves under any
82 > * `permissionMode` (so they always reach `canUseTool`). Drives
83 > * {@link INTERACTIVE_CLAUDE_TOOLS}.
84 > */
85 > readonly interactive?: true;
86 > /**
87 > * Phase 8.5 — rendering hint for the workbench (drives the
88 > * terminal / search / subagent renderers). Omit for tools that
89 > * render in the generic tool renderer (read, write, MCP, …).
90 > */
91 > readonly toolKind?: ClaudeToolKind;
92 > }
93 >
94 > const TOOL_ROWS: { readonly [toolName: string]: ClaudeToolRow } = {
95 > // shell tools — no `language` is carried: the workbench picks
96 > // `'shellscript'` from the tool name (it only special-cases
97 > // `'powershell'`), and the SDK's `Bash` tool is the generic shell
98 > // entry point (bash on POSIX, Git Bash on Windows), so claiming a
99 > // specific dialect here would be misleading and unused.
100 > Bash: { permissionKind: 'shell', toolKind: 'terminal' },
101 > BashOutput: { permissionKind: 'shell', toolKind: 'terminal' },
102 > KillBash: { permissionKind: 'shell', toolKind: 'terminal' },
103 >
104 > // read tools
105 > Read: { permissionKind: 'read', pathField: 'file_path' },
106 > Glob: { permissionKind: 'read', pathField: 'path', toolKind: 'search' },
107 > Grep: { permissionKind: 'read', pathField: 'path', toolKind: 'search' },
108 > LS: { permissionKind: 'read', pathField: 'path' },
109 > NotebookRead: { permissionKind: 'read', pathField: 'notebook_path' },
110 >
111 > // write tools
112 > Write: { permissionKind: 'write', pathField: 'file_path', isFileEdit: true },
113 > Edit: { permissionKind: 'write', pathField: 'file_path', isFileEdit: true },
114 > MultiEdit: { permissionKind: 'write', pathField: 'file_path', isFileEdit: true },
115 > NotebookEdit: { permissionKind: 'write', pathField: 'notebook_path', isFileEdit: true },
116 > TodoWrite: { permissionKind: 'write' },
117 >
118 > // network tools
119 > WebFetch: { permissionKind: 'url', pathField: 'url' },
120 >
121 > // host-routed / custom
122 > Task: { permissionKind: 'custom-tool', toolKind: 'subagent' },
123 > Agent: { permissionKind: 'custom-tool', toolKind: 'subagent' },
124 > ExitPlanMode: { permissionKind: 'custom-tool', interactive: true },
125 > AskUserQuestion: { permissionKind: 'custom-tool', interactive: true },
126 >
127 > // skill + task-list family — host-routed custom tools that render in the
128 > // generic tool renderer (no `toolKind`) but carry rich invocation /
129 > // past-tense messages so their collapsed row is self-explanatory.
130 > Skill: { permissionKind: 'skill' },
131 > TaskCreate: { permissionKind: 'custom-tool' },
132 > TaskUpdate: { permissionKind: 'custom-tool' },
133 > TaskList: { permissionKind: 'custom-tool' },
134 > TaskGet: { permissionKind: 'custom-tool' },
135 > };
136 >
137 > const MCP_TOOL_PREFIX = 'mcp__';
138 >
139 > /**
140 > * S4 row lookup. Falls back to `'custom-tool'` for unknown tools so
141 > * Claude's growing built-in list never breaks the host.
142 > */
143 > export function getClaudePermissionKind(toolName: string): ClaudePermissionKind {
144 const row = TOOL_ROWS[toolName];
145 if (row) {
151 return 'custom-tool';
152 }
154 > /**
155 > * Localized display name for the SDK's built-in tools (S4). Falls back
156 > * to the raw tool name so unknown tools still render something
157 > * sensible. For `mcp__server__tool` the prefix is stripped to surface
158 > * the server/tool pair.
159 > */
160 > export function getClaudeToolDisplayName(toolName: string): string {
161 const serverDisplay = getServerToolDisplay(toolName, undefined)?.displayName;
162 if (serverDisplay !== undefined) {
193 return toolName;
194 }
196 > /**
197 > * Read the `pathField` named on the tool's row from `input`. Returns
198 > * `undefined` for tools without a path field, for missing fields, or
199 > * for wrong-typed fields (defensive against malformed SDK input).
200 > *
201 > * Used both for `pending_confirmation.permissionPath` (S4) and Phase 8
202 > * file-edit tracking — callers that only care about edits gate with
203 > * {@link isClaudeFileEditTool} first.
204 > */
205 > export function getClaudeToolPath(toolName: string, input: unknown): string | undefined {
206 const row = TOOL_ROWS[toolName];
207 if (!row?.pathField || typeof input !== 'object' || input === null) {
211 return typeof value === 'string' ? value : undefined;
212 }
214 > /**
215 > * Phase 8 — true for tools that produce on-disk file edits tracked by
216 > * `FileEditTracker`. Excludes `TodoWrite` (in-memory) and `Bash` (edits
217 > * not surfaced as canonical SDK `tool_use` blocks the host can pair
218 > * with `tool_result`).
219 > */
220 > export function isClaudeFileEditTool(toolName: string): boolean {
221 return TOOL_ROWS[toolName]?.isFileEdit === true;
222 }
224 > /**
225 > * Phase 7 S3.5. Tools whose `canUseTool` invocation is satisfied by a
226 > * host-driven round-trip rather than the SDK's auto-approval:
227 > * - `AskUserQuestion` — carousel (S3.5a).
228 > * - `ExitPlanMode` — `pending_confirmation` with custom Approve/Deny
229 > * labels and the plan body as `invocationMessage` (S3.5b).
230 > *
231 > * Membership only signals that the SDK does not auto-approve under any
232 > * `permissionMode`, ensuring the call always reaches the host.
233 > * `_handleCanUseTool` dispatches via `INTERACTIVE_CLAUDE_TOOLS.has(toolName)`.
234 > *
235 > * Derived from the `interactive: true` rows above so the table stays
236 > * the single source of truth.
237 > */
238 > export const INTERACTIVE_CLAUDE_TOOLS: ReadonlySet<string> = new Set(
239 > Object.entries(TOOL_ROWS)
240 > .filter(([, row]) => row.interactive)
241 > .map(([name]) => name),
242 > );
243 >
244 > /**
245 > * Confirmation-card title shown when a tool needs explicit user
246 > * approval (S3.4 `pending_confirmation` flow). Mirrors the per-kind
247 > * titles in {@link getPermissionDisplay} for CopilotAgent so both
248 > * agents render identical wording. The workbench keys off
249 > * `confirmationTitle` to render the Approve/Deny buttons — when it
250 > * is absent, the tool card silently flips to "auto-approved" state
251 > * even though the agent is parked. See `sessionPermissions.ts`'s
252 > * `createToolReadyAction`.
253 > */
254 > export function getClaudeConfirmationTitle(toolName: string): string {
255 switch (getClaudePermissionKind(toolName)) {
256 case 'shell':
277 }
278 }
280 > // #region Phase 8.5 — rich tool-call rendering helpers
281 >
282 > /**
283 > * Phase 8.5 — workbench rendering hint. One-liner over `TOOL_ROWS`.
284 > * Returns `'terminal'` for shell tools (drives the terminal renderer),
285 > * `'search'` for `Grep` / `Glob` (drives the search renderer),
286 > * `'subagent'` for `Task` / `Agent` (drives the subagent renderer),
287 > * `undefined` for everything else (generic tool renderer).
288 > */
289 > export function getClaudeToolKind(toolName: string): ClaudeToolKind | undefined {
290 return TOOL_ROWS[toolName]?.toolKind;
291 }
293 > /**
294 > * Phase 8.5 — build the `_meta` bag stamped at the tool-open seam.
295 > * Returns `undefined` for tools that have no `toolKind` hint so the
296 > * resulting envelope stays minimal (a `Read` row gets no `_meta` at
297 > * all). Mirrors Copilot's
298 > * [`mapSessionEvents.ts:197`](../copilot/mapSessionEvents.ts#L197)
299 > * single-write pattern.
300 > */
301 > export function buildClaudeToolMeta(toolName: string): Record<string, unknown> | undefined {
302 const meta = buildClaudeToolCallMeta(toolName);
303 return meta ? toToolCallMeta(meta) : undefined;
304 }
306 > /**
307 > * Typed variant of {@link buildClaudeToolMeta} that returns the
308 > * {@link IToolCallMeta} directly, for callers that consume the typed view
309 > * rather than the serialized `_meta` bag. Returns `undefined` for tools that
310 > * have no `toolKind` hint.
311 > */
312 > export function buildClaudeToolCallMeta(toolName: string): IToolCallMeta | undefined {
313 const row = TOOL_ROWS[toolName];
314 if (!row?.toolKind) {
317 return { toolKind: row.toolKind };
318 }
320 function md(value: string): StringOrMarkdown {
321 return { markdown: value };
322 }
324 function formatPathAsMarkdownLink(path: string): string {
325 const uri = URI.file(path);
326 return `[${escapeMarkdownLinkLabel(basename(uri))}](${uri})`;
327 }
329 > /**
330 > * Defensive string-field access. Returns the field value when it is
331 > * a non-empty string, otherwise `undefined`.
332 > */
333 function readStringField(input: unknown, field: string): string | undefined {
334 if (input === null || typeof input !== 'object') {
338 return typeof value === 'string' && value.length > 0 ? value : undefined;
339 }
341 > /**
342 > * Phase 8.5 — first-line command extractor for shell tools. Mirrors
343 > * Copilot's `command.split('\n')[0]` pattern.
344 > */
345 function firstShellLine(input: unknown): string | undefined {
346 const command = readStringField(input, 'command');
347 return command ? command.split('\n')[0] : undefined;
348 }
350 > /**
351 > * Narrows a `TaskUpdate` call's `status` to the values that change the rendered
352 > * verb; any other or absent value yields `undefined` (generic "Updating" verb).
353 > */
354 function readTaskUpdateStatus(input: unknown): 'in_progress' | 'completed' | 'deleted' | undefined {
355 const status = readStringField(input, 'status');
356 return status === 'in_progress' || status === 'completed' || status === 'deleted' ? status : undefined;
357 }
359 > /**
360 > * Phase 8.5 — rich invocation message for a `pending_confirmation`
361 > * card or a streaming `ChatToolCallStart` action. Reads the
362 > * SDK's `tool_use.input` defensively and falls back to the static
363 > * `displayName` on any shape mismatch. Mirror of
364 > * [`copilotToolDisplay.getInvocationMessage`](../copilot/copilotToolDisplay.ts#L473).
365 > */
366 > export function getClaudeInvocationMessage(
367 toolName: string,
368 displayName: string,
470 }
471 }
473 > /**
474 > * Phase 8.5 — success-aware rich past-tense message. Mirror of
475 > * [`copilotToolDisplay.getPastTenseMessage`](../copilot/copilotToolDisplay.ts#L572).
476 > * Failure path returns a generic "failed" message; success path
477 > * mirrors the {@link getClaudeInvocationMessage} structure with
478 > * past-tense verbs.
479 > */
480 > export function getClaudePastTenseMessage(
481 toolName: string,
482 displayName: string,
584 }
585 }
587 > /**
588 > * Phase 8.5 — canonical "input as code" string rendered under the
589 > * tool-call row. Shell tools surface the raw `command`; search tools
590 > * surface the `pattern`; everything else falls back to pretty-printed
591 > * JSON. Returns `undefined` only when the input is itself absent.
592 > */
593 > export function getClaudeToolInputString(toolName: string, input: unknown): string | undefined {
594 if (input === undefined) {
595 return undefined;