src/vs/platform/agentHost/node/claude/claudeToolDisplay.ts

616 LOC · 580 covered · 36 uncovered · 157 ranges · 687 concepts · 54 introducers · 333 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

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 related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- claudeToolDisplay.ts ×17
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]; claudeToolDisplay.ts ×1
145 > if (row) {
146 > return row.permissionKind; claudeToolDisplay.ts ×1
147 > }
148 > if (toolName.startsWith(MCP_TOOL_PREFIX)) { claudeToolDisplay.ts ×1
149 > return 'mcp'; claudeToolDisplay.ts ×1
150 > }
151 > return 'custom-tool'; claudeToolDisplay.ts ×1
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; claudeToolDisplay.ts ×2
162 > if (serverDisplay !== undefined) {
163 return serverDisplay;
164 }
165 > switch (toolName) { claudeToolDisplay.ts ×2
166 > case 'Bash': return localize('claude.tool.bash', "Run shell command");
167 > case 'BashOutput': return localize('claude.tool.bashOutput', "Read shell output");
168 > case 'KillBash': return localize('claude.tool.killBash', "Kill shell command");
169 > case 'Read': return localize('claude.tool.read', "Read file");
170 > case 'Glob': return localize('claude.tool.glob', "Find files");
171 > case 'Grep': return localize('claude.tool.grep', "Search files");
172 > case 'LS': return localize('claude.tool.ls', "List directory");
173 > case 'NotebookRead': return localize('claude.tool.notebookRead', "Read notebook");
174 > case 'Write': return localize('claude.tool.write', "Write file");
175 > case 'Edit': return localize('claude.tool.edit', "Edit file");
176 > case 'MultiEdit': return localize('claude.tool.multiEdit', "Edit file");
177 > case 'NotebookEdit': return localize('claude.tool.notebookEdit', "Edit notebook");
178 > case 'TodoWrite': return localize('claude.tool.todoWrite', "Update todo list");
179 > case 'WebFetch': return localize('claude.tool.webFetch', "Fetch URL");
180 > case 'Task':
181 > case 'Agent': return localize('claude.tool.task', "Run subagent task");
182 > case 'ExitPlanMode': return localize('claude.tool.exitPlanMode', "Ready to code?");
183 > case 'AskUserQuestion': return localize('claude.tool.askUserQuestion', "Ask user a question");
184 > case 'Skill': return localize('claude.tool.skill', "Run skill");
185 > case 'TaskCreate': return localize('claude.tool.taskCreate', "Create task");
186 > case 'TaskUpdate': return localize('claude.tool.taskUpdate', "Update task");
187 > case 'TaskList': return localize('claude.tool.taskList', "List tasks");
188 > case 'TaskGet': return localize('claude.tool.taskGet', "Read task");
189 > }
190 > if (toolName.startsWith(MCP_TOOL_PREFIX)) { claudeToolDisplay.ts ×1
191 > return localize('claude.tool.mcp', "Run MCP tool {0}", toolName.slice(MCP_TOOL_PREFIX.length)); claudeToolDisplay.ts ×1
192 > }
193 > return toolName; claudeToolDisplay.ts ×1
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]; claudeToolDisplay.ts ×2
207 > if (!row?.pathField || typeof input !== 'object' || input === null) {
208 > return undefined; claudeToolDisplay.ts ×1
209 > }
210 > const value = (input as Record<string, unknown>)[row.pathField]; claudeToolDisplay.ts ×1
211 > return typeof value === 'string' ? value : undefined; claudeToolDisplay.ts ×2
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; claudeToolDisplay.ts ×1
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)) { claudeToolDisplay.ts ×7
256 > case 'shell':
257 > return localize('claude.permission.shell.title', "Run in terminal?"); claudeToolDisplay.ts ×8
258 > case 'write': claudeToolDisplay.ts ×7
259 > return localize('claude.permission.write.title', "Edit file?"); claudeToolDisplay.ts ×8
260 > case 'read': claudeToolDisplay.ts ×7
261 > return localize('claude.permission.read.title', "Read file?");
262 > case 'url':
263 > return localize('claude.permission.url.title', "Fetch URL?"); claudeToolDisplay.ts ×8
264 > case 'skill': claudeToolDisplay.ts ×7
265 > return localize('claude.permission.skill.title', "Run skill?"); claudeToolDisplay.ts ×8
266 > case 'mcp': { claudeToolDisplay.ts ×7
267 > const serverName = toolName.startsWith(MCP_TOOL_PREFIX) claudeToolDisplay.ts ×8
268 > ? toolName.slice(MCP_TOOL_PREFIX.length).split('__')[0]
269 : undefined;
270 > return serverName claudeToolDisplay.ts ×8
271 > ? localize('claude.permission.mcp.title', "Allow tool from {0}?", serverName)
272 : localize('claude.permission.default.title', "Allow tool call?");
274 > case 'custom-tool': claudeToolDisplay.ts ×7
275 > default:
276 > return localize('claude.permission.default.title', "Allow tool call?"); claudeToolDisplay.ts ×8
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; claudeToolDisplay.ts ×1
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); claudeToolDisplay.ts ×1
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]; claudeToolDisplay.ts ×1
314 > if (!row?.toolKind) {
315 > return undefined; claudeToolDisplay.ts ×1
316 > }
317 > return { toolKind: row.toolKind }; claudeToolDisplay.ts ×1
318 > }
320 > function md(value: string): StringOrMarkdown { claudeToolDisplay.ts ×1
321 > return { markdown: value };
322 > }
324 > function formatPathAsMarkdownLink(path: string): string { claudeToolDisplay.ts ×2
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 { claudeToolDisplay.ts ×2
334 > if (input === null || typeof input !== 'object') {
335 > return undefined; claudeToolDisplay.ts ×1
336 > }
337 > const value = (input as Record<string, unknown>)[field]; claudeToolDisplay.ts ×1
338 > return typeof value === 'string' && value.length > 0 ? value : undefined; claudeToolDisplay.ts ×2
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 { claudeToolDisplay.ts ×2
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 { claudeToolDisplay.ts ×3
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, claudeToolDisplay.ts ×19
368 > displayName: string,
369 > input: unknown,
370 > ): StringOrMarkdown {
371 > const serverDisplay = getServerToolDisplay(toolName, input)?.invocationMessage;
372 > if (serverDisplay !== undefined) {
373 return serverDisplay;
374 }
375 > switch (toolName) { claudeToolDisplay.ts ×19
376 > case 'Bash': {
377 > const firstLine = firstShellLine(input); claudeToolDisplay.ts ×2
378 > if (firstLine) {
379 > return md(localize('claude.toolInvoke.bashCmd', "Running {0}", appendEscapedMarkdownInlineCode(truncate(firstLine, 80)))); claudeToolDisplay.ts ×2
380 > }
381 > return localize('claude.toolInvoke.bash', "Running shell command"); claudeToolDisplay.ts ×1
382 > }
383 > case 'BashOutput': claudeToolDisplay.ts ×19
384 > return localize('claude.toolInvoke.bashOutput', "Reading shell output"); claudeToolDisplay.ts ×24
385 > case 'KillBash': claudeToolDisplay.ts ×19
386 > return localize('claude.toolInvoke.killBash', "Killing shell command"); claudeToolDisplay.ts ×24
387 > case 'Read': claudeToolDisplay.ts ×19
388 > case 'NotebookRead': {
389 > const path = getClaudeToolPath(toolName, input); claudeToolDisplay.ts ×1
390 > if (path) {
391 > return md(localize('claude.toolInvoke.readFile', "Reading {0}", formatPathAsMarkdownLink(path))); claudeToolDisplay.ts ×2
392 > }
393 > return localize('claude.toolInvoke.read', "Reading file"); claudeToolDisplay.ts ×1
394 > }
395 > case 'LS': { claudeToolDisplay.ts ×19
396 > const path = getClaudeToolPath(toolName, input); claudeToolDisplay.ts ×24
397 > if (path) {
398 > return md(localize('claude.toolInvoke.lsPath', "Listing {0}", formatPathAsMarkdownLink(path)));
399 > }
400 return localize('claude.toolInvoke.ls', "Listing directory");
401 }
402 > case 'Write': claudeToolDisplay.ts ×19
403 > case 'Edit':
404 > case 'MultiEdit':
405 > case 'NotebookEdit': {
406 > const path = getClaudeToolPath(toolName, input); claudeToolDisplay.ts ×24
407 > if (path) {
408 > return md(localize('claude.toolInvoke.editFile', "Editing {0}", formatPathAsMarkdownLink(path)));
409 > }
410 return localize('claude.toolInvoke.edit', "Editing file");
411 }
412 > case 'TodoWrite': claudeToolDisplay.ts ×19
413 > return localize('claude.toolInvoke.todoWrite', "Updating todo list"); claudeToolDisplay.ts ×24
414 > case 'Grep': { claudeToolDisplay.ts ×19
415 > const pattern = readStringField(input, 'pattern'); claudeToolDisplay.ts ×1
416 > if (pattern) {
417 > return md(localize('claude.toolInvoke.grepPattern', "Searching for {0}", appendEscapedMarkdownInlineCode(truncate(pattern, 80)))); claudeToolDisplay.ts ×24
418 > }
419 > return localize('claude.toolInvoke.grep', "Searching files"); claudeToolDisplay.ts ×1
420 > }
421 > case 'Glob': { claudeToolDisplay.ts ×19
422 > const pattern = readStringField(input, 'pattern'); claudeToolDisplay.ts ×3
423 > if (pattern) {
424 > return md(localize('claude.toolInvoke.globPattern', "Finding files matching {0}", appendEscapedMarkdownInlineCode(truncate(pattern, 80))));
425 > }
426 return localize('claude.toolInvoke.glob', "Finding files");
427 }
428 > case 'WebFetch': { claudeToolDisplay.ts ×19
429 > const url = readStringField(input, 'url'); claudeToolDisplay.ts ×24
430 > if (url) {
431 > return md(localize('claude.toolInvoke.webFetch', "Fetching {0}", `[${escapeMarkdownLinkLabel(truncate(url, 80))}](${url})`));
432 > }
433 return localize('claude.toolInvoke.webFetchGeneric', "Fetching URL");
434 }
435 > case 'Task': claudeToolDisplay.ts ×19
436 > case 'Agent': {
437 > const description = readStringField(input, 'description'); claudeToolDisplay.ts ×1
438 > if (description) {
439 > return description; claudeToolDisplay.ts ×1
440 > }
441 > return displayName; claudeToolDisplay.ts ×1
442 > }
443 > case 'Skill': { claudeToolDisplay.ts ×19
444 > const skill = readStringField(input, 'skill'); claudeToolDisplay.ts ×24
445 > if (skill) {
446 > return md(localize('claude.toolInvoke.skillNamed', "Running skill {0}", appendEscapedMarkdownInlineCode(truncate(skill, 80))));
447 > }
448 return localize('claude.toolInvoke.skill', "Running skill");
449 }
450 > case 'TaskCreate': { claudeToolDisplay.ts ×19
451 > const subject = readStringField(input, 'subject'); claudeToolDisplay.ts ×24
452 > if (subject) {
453 > return localize('claude.toolInvoke.taskCreateNamed', "Creating task: {0}", truncate(subject, 80));
454 > }
455 return localize('claude.toolInvoke.taskCreate', "Creating task");
456 }
457 > case 'TaskUpdate': claudeToolDisplay.ts ×19
458 > switch (readTaskUpdateStatus(input)) { claudeToolDisplay.ts ×3
459 > case 'in_progress': return localize('claude.toolInvoke.taskStart', "Starting task");
460 > case 'completed': return localize('claude.toolInvoke.taskComplete', "Completing task");
461 > case 'deleted': return localize('claude.toolInvoke.taskDelete', "Deleting task");
462 > default: return localize('claude.toolInvoke.taskUpdate', "Updating task");
463 > }
464 > case 'TaskList': claudeToolDisplay.ts ×19
465 > return localize('claude.toolInvoke.taskList', "Reading task list"); claudeToolDisplay.ts ×24
466 > case 'TaskGet': claudeToolDisplay.ts ×19
467 > return localize('claude.toolInvoke.taskGet', "Reading task"); claudeToolDisplay.ts ×24
468 > default: claudeToolDisplay.ts ×19
469 > return displayName; claudeToolDisplay.ts ×1
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, claudeToolDisplay.ts ×19
482 > displayName: string,
483 > input: unknown,
484 > success: boolean,
485 > resultText?: string,
486 > ): StringOrMarkdown {
487 > if (!success) {
488 > return localize('claude.toolComplete.failed', "\"{0}\" failed", displayName); claudeToolDisplay.ts ×1
489 > }
490 > const serverDisplay = getServerToolDisplay(toolName, input, { text: resultText, success })?.pastTenseMessage; claudeToolDisplay.ts ×2
491 > if (serverDisplay !== undefined) { claudeToolDisplay.ts ×19
492 return serverDisplay;
493 }
494 > switch (toolName) { claudeToolDisplay.ts ×2
495 > case 'Bash': {
496 > const firstLine = firstShellLine(input); claudeToolDisplay.ts ×1
497 > if (firstLine) {
498 > return md(localize('claude.toolComplete.bashCmd', "Ran {0}", appendEscapedMarkdownInlineCode(truncate(firstLine, 80)))); claudeToolDisplay.ts ×1
499 > }
500 > return localize('claude.toolComplete.bash', "Ran shell command"); claudeReplayMapper.ts ×2
501 > }
502 > case 'BashOutput': claudeToolDisplay.ts ×19
503 > return localize('claude.toolComplete.bashOutput', "Read shell output"); claudeToolDisplay.ts ×24
504 > case 'KillBash': claudeToolDisplay.ts ×19
505 > return localize('claude.toolComplete.killBash', "Killed shell command"); claudeToolDisplay.ts ×24
506 > case 'Read': claudeToolDisplay.ts ×19
507 > case 'NotebookRead': {
508 > const path = getClaudeToolPath(toolName, input); claudeToolDisplay.ts ×1
509 > if (path) {
510 > return md(localize('claude.toolComplete.readFile', "Read {0}", formatPathAsMarkdownLink(path))); claudeToolDisplay.ts ×24
511 > }
512 > return localize('claude.toolComplete.read', "Read file"); claudeToolDisplay.ts ×1
513 > }
514 > case 'LS': { claudeToolDisplay.ts ×19
515 > const path = getClaudeToolPath(toolName, input); claudeToolDisplay.ts ×24
516 > if (path) {
517 > return md(localize('claude.toolComplete.lsPath', "Listed {0}", formatPathAsMarkdownLink(path)));
518 > }
519 return localize('claude.toolComplete.ls', "Listed directory");
520 }
521 > case 'Write': claudeToolDisplay.ts ×19
522 > case 'Edit':
523 > case 'MultiEdit':
524 > case 'NotebookEdit': {
525 > const path = getClaudeToolPath(toolName, input); claudeToolDisplay.ts ×24
526 > if (path) {
527 > return md(localize('claude.toolComplete.editFile', "Edited {0}", formatPathAsMarkdownLink(path)));
528 > }
529 return localize('claude.toolComplete.edit', "Edited file");
530 }
531 > case 'TodoWrite': claudeToolDisplay.ts ×19
532 > return localize('claude.toolComplete.todoWrite', "Updated todo list"); claudeToolDisplay.ts ×24
533 > case 'Grep': { claudeToolDisplay.ts ×19
534 > const pattern = readStringField(input, 'pattern'); claudeToolDisplay.ts ×24
535 > if (pattern) {
536 > return md(localize('claude.toolComplete.grepPattern', "Searched for {0}", appendEscapedMarkdownInlineCode(truncate(pattern, 80))));
537 > }
538 return localize('claude.toolComplete.grep', "Searched files");
539 }
540 > case 'Glob': { claudeToolDisplay.ts ×19
541 > const pattern = readStringField(input, 'pattern'); claudeToolDisplay.ts ×3
542 > if (pattern) {
543 > return md(localize('claude.toolComplete.globPattern', "Found files matching {0}", appendEscapedMarkdownInlineCode(truncate(pattern, 80))));
544 > }
545 return localize('claude.toolComplete.glob', "Found files");
546 }
547 > case 'WebFetch': { claudeToolDisplay.ts ×19
548 > const url = readStringField(input, 'url'); claudeToolDisplay.ts ×24
549 > if (url) {
550 > return md(localize('claude.toolComplete.webFetch', "Fetched {0}", `[${escapeMarkdownLinkLabel(truncate(url, 80))}](${url})`));
551 > }
552 return localize('claude.toolComplete.webFetchGeneric', "Fetched URL");
553 }
554 > case 'Task': claudeToolDisplay.ts ×19
555 > case 'Agent':
556 > return localize('claude.toolComplete.task', "Ran subagent"); claudeToolDisplay.ts ×1
557 > case 'Skill': { claudeToolDisplay.ts ×19
558 > const skill = readStringField(input, 'skill'); claudeToolDisplay.ts ×24
559 > if (skill) {
560 > return md(localize('claude.toolComplete.skillNamed', "Ran skill {0}", appendEscapedMarkdownInlineCode(truncate(skill, 80))));
561 > }
562 return localize('claude.toolComplete.skill', "Ran skill");
563 }
564 > case 'TaskCreate': { claudeToolDisplay.ts ×19
565 > const subject = readStringField(input, 'subject'); claudeToolDisplay.ts ×24
566 > if (subject) {
567 > return localize('claude.toolComplete.taskCreateNamed', "Created task: {0}", truncate(subject, 80));
568 > }
569 return localize('claude.toolComplete.taskCreate', "Created task");
570 }
571 > case 'TaskUpdate': claudeToolDisplay.ts ×19
572 > switch (readTaskUpdateStatus(input)) { claudeToolDisplay.ts ×3
573 > case 'in_progress': return localize('claude.toolComplete.taskStart', "Started task");
574 > case 'completed': return localize('claude.toolComplete.taskComplete', "Completed task");
575 > case 'deleted': return localize('claude.toolComplete.taskDelete', "Deleted task");
576 > default: return localize('claude.toolComplete.taskUpdate', "Updated task");
577 > }
578 > case 'TaskList': claudeToolDisplay.ts ×19
579 > return localize('claude.toolComplete.taskList', "Read task list"); claudeToolDisplay.ts ×24
580 > case 'TaskGet': claudeToolDisplay.ts ×19
581 > return localize('claude.toolComplete.taskGet', "Read task"); claudeToolDisplay.ts ×24
582 > default: claudeToolDisplay.ts ×19
583 > return displayName; claudeToolDisplay.ts ×24
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) { claudeToolDisplay.ts ×4
595 > return undefined; claudeToolDisplay.ts ×1
596 > }
597 > if (toolName === 'Bash' || toolName === 'BashOutput' || toolName === 'KillBash') { claudeToolDisplay.ts ×4
598 > const command = readStringField(input, 'command'); claudeToolDisplay.ts ×2
599 > if (command) {
600 > return command; claudeToolDisplay.ts ×2
601 > }
603 > if (toolName === 'Grep' || toolName === 'Glob') { claudeToolDisplay.ts ×4
604 > const pattern = readStringField(input, 'pattern'); claudeToolDisplay.ts ×3
605 > if (pattern) {
606 > return pattern;
607 > }
608 > }
610 > return JSON.stringify(input, null, 2);
611 > } catch {
612 return undefined;
613 }
616 > // #endregion