copilotToolDisplay.ts ×31

Frontier kind: Code frontier

unlabeled · c_2075c50151ff

866 tests · 18157 LOC · 67 files · introduces 0 tests · 533 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
31 ranges533 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
1203 ranges18157 lines · 67 files · Browse complete extent
All tests (intent)
866 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: 533 introduced LOC across 31 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/agentHost/node/copilot/copilotToolDisplay.ts 533 introduced LOC · 31 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- copilotToolDisplay.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 type { PermissionRequest } from '@github/copilot-sdk';
7 > import { hasKey } from '../../../../base/common/types.js';
8 > import { URI } from '../../../../base/common/uri.js';
9 > import { appendEscapedMarkdownInlineCode, escapeMarkdownLinkLabel, MarkdownString } from '../../../../base/common/htmlContent.js';
10 > import { hash } from '../../../../base/common/hash.js';
11 > import { localize } from '../../../../nls.js';
12 > import type { IAgentToolPendingConfirmationSignal } from '../../common/agentService.js';
13 > import { stripRedundantCdPrefix } from '../../common/commandLineHelpers.js';
14 > import { StringOrMarkdown } from '../../common/state/protocol/state.js';
15 > import { basename } from '../../../../base/common/resources.js';
16 > import { getServerToolDisplay } from '../shared/serverToolGroups.js';
17 >
18 > // =============================================================================
19 > // Copilot CLI built-in tool interfaces
20 > //
21 > // The Copilot CLI (via @github/copilot-sdk) exposes these built-in tools. Tool names
22 > // and parameter shapes are not typed in the SDK -- they come from the CLI server
23 > // as plain strings. These interfaces are derived from observing the CLI's actual
24 > // tool events and the Copilot Chat extension's CLI display table.
25 > //
26 > // Shell tool names follow a pattern per ShellConfig:
27 > // shellToolName, readShellToolName, writeShellToolName,
28 > // stopShellToolName, listShellsToolName
29 > // For bash: bash, read_bash, write_bash, stop_bash/bash_shutdown, list_bash
30 > // For powershell: powershell, read_powershell, write_powershell, stop_powershell/powershell_shutdown, list_powershell
31 > // =============================================================================
32 >
33 > /**
34 > * Known Copilot CLI tool names. These are the `toolName` values that appear
35 > * in `tool.execution_start` events from the SDK.
36 > */
37 > const enum CopilotToolName {
38 > StrReplaceEditor = 'str_replace_editor',
39 > StrReplace = 'str_replace',
40 > Insert = 'insert',
41 >
42 > Bash = 'bash',
43 > ReadBash = 'read_bash',
44 > WriteBash = 'write_bash',
45 > StopBash = 'stop_bash',
46 > BashShutdown = 'bash_shutdown',
47 > ListBash = 'list_bash',
48 >
49 > PowerShell = 'powershell',
50 > ReadPowerShell = 'read_powershell',
51 > WritePowerShell = 'write_powershell',
52 > StopPowerShell = 'stop_powershell',
53 > PowerShellShutdown = 'powershell_shutdown',
54 > ListPowerShell = 'list_powershell',
55 >
56 > View = 'view',
57 > Edit = 'edit',
58 > Create = 'create',
59 > Grep = 'grep',
60 > Rg = 'rg',
61 > Glob = 'glob',
62 > SearchCodeSubagent = 'search_code_subagent',
63 > ReplyToComment = 'reply_to_comment',
64 > CodeReview = 'code_review',
65 > ApplyPatch = 'apply_patch',
66 > GitApplyPatch = 'git_apply_patch',
67 > WebSearch = 'web_search',
68 > WebFetch = 'web_fetch',
69 > AskUser = 'ask_user',
70 > ReportIntent = 'report_intent',
71 > Think = 'think',
72 > ReportProgress = 'report_progress',
73 > UpdateTodo = 'update_todo',
74 > ShowFile = 'show_file',
75 > FetchCopilotCliDocumentation = 'fetch_copilot_cli_documentation',
76 > ProposeWork = 'propose_work',
77 > TaskComplete = 'task_complete',
78 > Skill = 'skill',
79 > Task = 'task',
80 > ListAgents = 'list_agents',
81 > ReadAgent = 'read_agent',
82 > ExitPlanMode = 'exit_plan_mode',
83 > Sql = 'sql',
84 > Lsp = 'lsp',
85 > CreatePullRequest = 'create_pull_request',
86 > GhAdvisoryDatabase = 'gh-advisory-database',
87 > StoreMemory = 'store_memory',
88 > ParallelValidation = 'parallel_validation',
89 > WriteAgent = 'write_agent',
90 > McpReload = 'mcp_reload',
91 > McpValidate = 'mcp_validate',
92 > ToolSearchToolRegex = 'tool_search_tool_regex',
93 > CodeqlChecker = 'codeql_checker',
94 > }
95 >
96 > /** Parameters for the `bash` / `powershell` shell tools. */
97 > interface ICopilotShellToolArgs {
98 > command: string;
99 > timeout?: number;
100 > }
101 >
102 > /** Parameters for file tools (`view`, `edit`, `create`). */
103 > interface ICopilotFileToolArgs {
104 > path: string;
105 > }
106 >
107 > /**
108 > * Parameters for the `view` tool. The Copilot CLI accepts an optional
109 > * `view_range: [startLine, endLine]` (1-based, inclusive). `endLine` may be
110 > * `-1` to mean "to end of file".
111 > */
112 > interface ICopilotViewToolArgs extends ICopilotFileToolArgs {
113 > view_range?: number[];
114 > }
115 >
116 > /**
117 > * Normalizes a `view_range` array. Returns `undefined` unless the array has
118 > * exactly two integer elements with `startLine >= 0`. `endLine === -1` is
119 > * preserved as the "to end of file" sentinel; otherwise `endLine` must be
120 > * `>= startLine`.
121 > */
122 function formatViewRange(view_range: number[] | undefined): { startLine: number; endLine: number } | undefined {
123 if (!Array.isArray(view_range) || view_range.length !== 2) {
136 return { startLine, endLine };
137 }
139 > /**
140 > * Parameters for the `grep` tool. The Copilot CLI's `grep` accepts the same
141 > * rich rg-flag schema as `rg`; the older narrower shape (e.g. `include`) is
142 > * no longer used.
143 > */
144 > interface ICopilotGrepToolArgs {
145 > pattern: string;
146 > path?: string;
147 > output_mode?: 'content' | 'files_with_matches' | 'count';
148 > glob?: string;
149 > type?: string;
150 > '-i'?: boolean;
151 > '-A'?: number;
152 > '-B'?: number;
153 > '-C'?: number;
154 > '-n'?: boolean;
155 > head_limit?: number;
156 > multiline?: boolean;
157 > }
158 >
159 > /**
160 > * Parameters for the `rg` tool. Mirrors {@link ICopilotGrepToolArgs} today but
161 > * is kept as a distinct interface so the two tools can drift independently if
162 > * the SDK ever differentiates them.
163 > */
164 > interface ICopilotRgToolArgs {
165 > pattern: string;
166 > path?: string;
167 > output_mode?: 'content' | 'files_with_matches' | 'count';
168 > glob?: string;
169 > type?: string;
170 > '-i'?: boolean;
171 > '-A'?: number;
172 > '-B'?: number;
173 > '-C'?: number;
174 > '-n'?: boolean;
175 > head_limit?: number;
176 > multiline?: boolean;
177 > }
178 >
179 > /** Parameters for the `glob` tool. */
180 > interface ICopilotGlobToolArgs {
181 > pattern: string;
182 > path?: string;
183 > }
184 >
185 > /** Parameters for the `sql` tool. */
186 > interface ICopilotSqlToolArgs {
187 > description?: string;
188 > query?: string;
189 > }
190 >
191 > /** Parameters for the `web_fetch` tool. */
192 > interface ICopilotWebFetchToolArgs {
193 > url: string;
194 > }
195 >
196 > /**
197 > * Parameters shared by the agent-coordination tools (`read_agent`,
198 > * `write_agent`). The Copilot CLI identifies the target agent by its
199 > * human-readable `agent_id` (e.g. `math-helper`).
200 > */
201 > interface ICopilotAgentToolArgs {
202 > agent_id?: string;
203 > }
204 >
205 > /**
206 > * Reads a well-formed `agent_id` from untrusted tool parameters. Since these are
207 > * parsed from JSON they may not match the expected shape, so the id is returned
208 > * only when it is a non-empty string and is therefore safe to render as inline
209 > * markdown code.
210 > */
211 function getAgentId(parameters: Record<string, unknown> | undefined): string | undefined {
212 const agentId = (parameters as ICopilotAgentToolArgs | undefined)?.agent_id;
213 return typeof agentId === 'string' && agentId.length > 0 ? agentId : undefined;
214 }
216 > /**
217 > * Parameters for the `apply_patch` / `git_apply_patch` tools. The patch text
218 > * itself lives in `input` using the V4A diff format (file headers like
219 > * `*** Update File: <path>`), so file paths must be parsed out of the body
220 > * rather than read from a top-level field.
221 > */
222 > interface ICopilotApplyPatchToolArgs {
223 > input?: string;
224 > /** Some SDK callers send the patch under `patch` instead of `input`. */
225 > patch?: string;
226 > explanation?: string;
227 > }
228 >
229 > /**
230 > * Headers of the V4A patch format the `apply_patch` tool accepts. Tolerates
231 > * leading whitespace; trims the captured path.
232 > */
233 > const APPLY_PATCH_FILE_HEADERS = [
234 > /^\s*\*\*\*\s+Update File:\s*(.+?)\s*$/,
235 > /^\s*\*\*\*\s+Add File:\s*(.+?)\s*$/,
236 > /^\s*\*\*\*\s+Delete File:\s*(.+?)\s*$/,
237 > /^\s*\*\*\*\s+Move to:\s*(.+?)\s*$/,
238 > ];
239 >
240 > /**
241 > * Extracts the set of file paths affected by an `apply_patch` payload. Reads
242 > * the `*** Update File:` / `*** Add File:` / `*** Delete File:` / `*** Move to:`
243 > * headers from the V4A diff body. Returns paths in document order with
244 > * duplicates removed.
245 > *
246 > * Accepts either a structured args object ({@link ICopilotApplyPatchToolArgs})
247 > * or a bare patch string. The Copilot SDK delivers `apply_patch` with
248 > * `arguments` as a raw V4A patch string (custom tool format), not as a JSON
249 > * object, so the string fallback is the common case for apply_patch.
250 > */
251 function getApplyPatchFiles(args: string | ICopilotApplyPatchToolArgs | undefined): string[] {
252 const text = typeof args === 'string' ? args : (args?.input ?? args?.patch);
271 return out;
272 }
274 > /** Set of tool names that perform file edits. */
275 > const EDIT_TOOL_NAMES: ReadonlySet<string> = new Set([
276 > CopilotToolName.Edit,
277 > CopilotToolName.StrReplace,
278 > CopilotToolName.Insert,
279 > CopilotToolName.Create,
280 > CopilotToolName.ApplyPatch,
281 > CopilotToolName.GitApplyPatch,
282 > ]);
283 >
284 > const STR_REPLACE_EDITOR_EDIT_COMMANDS: ReadonlySet<string> = new Set([
285 > CopilotToolName.Edit,
286 > CopilotToolName.StrReplace,
287 > CopilotToolName.Insert,
288 > CopilotToolName.Create,
289 > ]);
290 >
291 > /**
292 > * Returns true if the tool modifies files on disk.
293 > */
294 > export function isEditTool(toolName: string, command?: string): boolean {
295 if (EDIT_TOOL_NAMES.has(toolName)) {
296 return true;
301 return false;
302 }
304 > /**
305 > * Extracts the target file path from an edit tool's parameters, if available.
306 > * For `apply_patch` / `git_apply_patch` the first file in the V4A patch body
307 > * is returned. Callers that need every affected file (for snapshotting all
308 > * edits in a multi-file patch) should use {@link getEditFilePaths} instead.
309 > */
310 > export function getEditFilePath(parameters: unknown): string | undefined {
311 return getEditFilePaths(parameters)[0];
312 }
314 > /**
315 > * Extracts every file path an edit tool will touch. For `edit` / `create` this
316 > * is the single `path` parameter; for `apply_patch` / `git_apply_patch` this
317 > * is the unique set of files declared in the V4A patch body, in document
318 > * order. Returns an empty array if no paths can be determined.
319 > */
320 > export function getEditFilePaths(parameters: unknown): string[] {
321 if (typeof parameters === 'string') {
322 // Could be either a JSON-encoded args object or a raw V4A patch
348 return typeof args.path === 'string' ? [args.path] : [];
349 }
351 > /** Set of tool names that execute shell commands (bash or powershell). */
352 > const SHELL_TOOL_NAMES: ReadonlySet<string> = new Set([
353 > CopilotToolName.Bash,
354 > CopilotToolName.PowerShell,
355 > ]);
356 >
357 > /** Set of tool names that write input to an interactive shell session. */
358 > const WRITE_SHELL_TOOL_NAMES: ReadonlySet<string> = new Set([
359 > CopilotToolName.WriteBash,
360 > CopilotToolName.WritePowerShell,
361 > ]);
362 >
363 > /** Set of tool names that read output from an interactive shell session. */
364 > const READ_SHELL_TOOL_NAMES: ReadonlySet<string> = new Set([
365 > CopilotToolName.ReadBash,
366 > CopilotToolName.ReadPowerShell,
367 > ]);
368 >
369 > /** Set of tool names that spawn subagent sessions. */
370 > const SUBAGENT_TOOL_NAMES: ReadonlySet<string> = new Set([
371 > 'task',
372 > ]);
373 >
374 > /** Set of tool names that perform file/text search. */
375 > const SEARCH_TOOL_NAMES: ReadonlySet<string> = new Set([
376 > CopilotToolName.Grep,
377 > CopilotToolName.Rg,
378 > CopilotToolName.Glob,
379 > ]);
380 >
381 > /**
382 > * Tools that should not be shown to the user. These are internal tools
383 > * used by the CLI for its own purposes (e.g., reporting intent to the model).
384 > *
385 > * `skill` is hidden because the SDK already emits a richer `skill.invoked`
386 > * lifecycle event with the resolved skill file path; the agent session
387 > * synthesizes a tool-start/complete pair from that event so the UI can
388 > * render a clickable file link instead of just the skill name. See
389 > * {@link synthesizeSkillToolCall}.
390 > */
391 > const HIDDEN_TOOL_NAMES: ReadonlySet<string> = new Set([
392 > CopilotToolName.ReportIntent,
393 > CopilotToolName.Skill,
394 > ]);
395 >
396 > /**
397 > * Returns true if the tool should be hidden from the UI.
398 > */
399 > export function isHiddenTool(toolName: string): boolean {
400 return HIDDEN_TOOL_NAMES.has(toolName);
401 }
403 > /**
404 > * Returns true for the auto-approved agent-coordination tools (list/read/write
405 > * agents). These are client-contributed tools that never go through the
406 > * permission flow, so the agent host auto-readies them at start to surface a
407 > * tailored invocation message instead of the generic fallback.
408 > */
409 > export function isAgentCoordinationTool(toolName: string): boolean {
410 return toolName === CopilotToolName.ListAgents
411 || toolName === CopilotToolName.ReadAgent
412 || toolName === CopilotToolName.WriteAgent;
413 }
415 > /**
416 > * Returns true when the tool is Copilot's internal Autopilot completion signal.
417 > */
418 > export function isTaskCompleteTool(toolName: string): boolean {
419 return toolName === CopilotToolName.TaskComplete;
420 }
422 > /**
423 > * Extracts the user-facing Autopilot completion summary from the tool output,
424 > * falling back to the original `summary` argument for older/incomplete events.
425 > */
426 > export function getTaskCompleteSummary(parameters: Record<string, unknown> | undefined, toolOutput: string | undefined): string | undefined {
427 if (toolOutput && toolOutput.trim().length > 0) {
428 return toolOutput;
431 return typeof summary === 'string' && summary.trim().length > 0 ? summary : undefined;
432 }
434 > /**
435 > * Formats the Autopilot completion summary as the markdown response part
436 > * content, including the localized prefix.
437 > */
438 > export function getTaskCompleteMarkdown(parameters: Record<string, unknown> | undefined, toolOutput: string | undefined): string | undefined {
439 const summary = getTaskCompleteSummary(parameters, toolOutput);
440 if (!summary) {
443 return '\n\n' + localize('toolMarkdown.taskComplete', "**Task completed:** {0}", summary);
444 }
446 > /**
447 > * Returns true if the tool should render as a markdown response part instead
448 > * of a tool-call entry.
449 > */
450 > export function isMarkdownRenderedTool(toolName: string): boolean {
451 return isTaskCompleteTool(toolName);
452 }
454 > /**
455 > * Returns markdown content for tools rendered as inline markdown response
456 > * parts.
457 > */
458 > export function getToolMarkdownContent(toolName: string, parameters: Record<string, unknown> | undefined): string | undefined {
459 if (!isMarkdownRenderedTool(toolName)) {
460 return undefined;
466 return getTaskCompleteMarkdown(parameters, undefined);
467 }
469 > /**
470 > * Returns true if the tool executes shell commands.
471 > */
472 > export function isShellTool(toolName: string): boolean {
473 return SHELL_TOOL_NAMES.has(toolName);
474 }
476 > /**
477 > * Extracts the intention for a shell tool call from its `description`
478 > * argument. The Copilot shell tools (`bash`/`powershell`) carry a short
479 > * human-readable description of what the command does, which matches the
480 > * model's intention summary. Non-shell tools have no such argument, so this
481 > * returns `undefined` for them.
482 > */
483 > export function getShellIntention(toolName: string, parameters: Record<string, unknown> | undefined): string | undefined {
484 if (isShellTool(toolName) && typeof parameters?.description === 'string' && parameters.description.length > 0) {
485 return parameters.description;
487 return undefined;
488 }
490 > // =============================================================================
491 > // Display helpers
492 > //
493 > // These functions translate Copilot CLI tool names and arguments into
494 > // human-readable display strings. This logic lives here -- in the agent-host
495 > // process -- so the IPC protocol stays agent-agnostic; the renderer never needs
496 > // to know about specific tool names.
497 > // =============================================================================
498 >
499 function truncate(text: string, maxLength: number): string {
500 return text.length > maxLength ? text.substring(0, maxLength - 3) + '...' : text;
501 }
503 > /**
504 > * Formats a file path as a markdown link `[](file-uri)` so it renders
505 > * as a clickable file widget in the chat UI.
506 > */
507 function formatPathAsMarkdownLink(path: string): string {
508 const uri = URI.file(path);
509 return `[${escapeMarkdownLinkLabel(basename(uri))}](${uri})`;
510 }
512 function formatUrlAsMarkdownLink(url: string): string {
513 return new MarkdownString().appendLink(url, truncate(url, 80)).value;
514 }
516 > /**
517 > * Wraps a localized message containing a markdown file link into a
518 > * `StringOrMarkdown` object so the renderer treats it as markdown.
519 > */
520 function md(value: string): StringOrMarkdown {
521 return { markdown: value };
522 }
524 > export function getToolDisplayName(toolName: string): string {
525 const serverDisplay = getServerToolDisplay(toolName, undefined)?.displayName;
526 if (serverDisplay !== undefined) {
584 }
585 }
587 > export function getInvocationMessage(toolName: string, displayName: string, parameters: Record<string, unknown> | undefined): StringOrMarkdown {
588 const serverDisplay = getServerToolDisplay(toolName, parameters)?.invocationMessage;
589 if (serverDisplay !== undefined) {
704 }
705 }
707 > export function getPastTenseMessage(toolName: string, displayName: string, parameters: Record<string, unknown> | undefined, success: boolean, resultText?: string): StringOrMarkdown {
708 if (!success) {
709 return localize('toolComplete.failed', "\"{0}\" failed", displayName);
837 }
838 }
840 > // =============================================================================
841 > // Skill event synthesis
842 > //
843 > // The Copilot SDK emits a `skill` tool call (which we hide) and, separately, a
844 > // `skill.invoked` lifecycle event with the resolved skill file path. We turn
845 > // the latter into a synthesized tool-start/complete pair so clients can render
846 > // a clickable file link to the SKILL.md the agent loaded -- matching the
847 > // existing `view`-tool display style. Live and replay paths share this helper
848 > // so they stay in lock-step (see also the mirrored-pair gotcha for tool-call
849 > // display in this file).
850 > // =============================================================================
851 >
852 > /** Subset of the SDK's `skill.invoked` payload that the synth helper needs. */
853 > export interface ICopilotSkillInvokedData {
854 > readonly name: string;
855 > readonly path?: string;
856 > readonly description?: string;
857 > }
858 >
859 > /**
860 > * Builds a stable synthetic tool call id for a `skill.invoked` event so
861 > * reconnect/replay produces the same id as the original live emit. The id
862 > * is used unencoded as a path segment (e.g. by `ChatResponseResource.createUri`),
863 > * so it must not contain characters like `/` -- we hash any fallback values
864 > * that could carry filesystem paths or arbitrary text.
865 > */
866 > export function getSkillSyntheticToolCallId(eventId: string | undefined, data: ICopilotSkillInvokedData): string {
867 if (eventId) {
868 return `synth-skill-${eventId}`;
871 return `synth-skill-${hash(seed).toString(16)}`;
872 }
874 > /**
875 > * Synthesized data for a `skill.invoked` tool call. Used by both the live
876 > * session handler and the history-replay mapper so the two paths render
877 > * identically. Callers wrap this into protocol actions or {@link Turn}
878 > * data; this helper avoids any agent-protocol coupling.
879 > */
880 > export interface ISynthesizedSkillToolCall {
881 > readonly toolCallId: string;
882 > readonly toolName: string;
883 > readonly displayName: string;
884 > readonly invocationMessage: StringOrMarkdown;
885 > readonly pastTenseMessage: StringOrMarkdown;
886 > }
887 >
888 > /**
889 > * Synthesizes the data for a `skill.invoked` tool call (a tool-start /
890 > * tool-complete pair). Returns the constituent fields without coupling to
891 > * any specific event or action shape — callers compose them into protocol
892 > * actions or {@link Turn} entries as needed.
893 > */
894 > export function synthesizeSkillToolCall(
895 data: ICopilotSkillInvokedData,
896 eventId: string | undefined,
924 };
925 }
927 > export function getToolInputString(toolName: string, parameters: Record<string, unknown> | undefined, rawArguments: string | undefined): string | undefined {
928 if (!parameters && !rawArguments) {
929 return undefined;
968 }
969 }
971 > /**
972 > * Returns a rendering hint for the given tool. Currently 'terminal', 'subagent',
973 > * and 'search' are supported, which tell the renderer to display the tool with
974 > * a terminal command block, a subagent widget, or a search icon respectively.
975 > */
976 > export function getToolKind(toolName: string): 'terminal' | 'subagent' | 'search' | undefined {
977 if (SHELL_TOOL_NAMES.has(toolName)) {
978 return 'terminal';
986 return undefined;
987 }
989 > /**
990 > * Extracts subagent metadata (agent name, description) from the parsed
991 > * arguments of a Copilot SDK subagent tool call. The Copilot `task` tool
992 > * uses `agent_type` (snake_case), which this normalizes into the generic
993 > * `subagentAgentName` / `subagentDescription` shape used by the rest of the
994 > * agent host code.
995 > *
996 > * Only call this for tools where {@link getToolKind} returned `'subagent'`.
997 > */
998 > export function getSubagentMetadata(parameters: Record<string, unknown> | undefined): { agentName?: string; description?: string } {
999 if (!parameters) {
1000 return {};
1008 return { agentName, description };
1009 }
1011 > /**
1012 > * Returns the shell language identifier for syntax highlighting.
1013 > * Used when creating terminal tool-specific data for the renderer.
1014 > */
1015 > export function getShellLanguage(toolName: string): string {
1016 switch (toolName) {
1017 case CopilotToolName.PowerShell:
1021 }
1022 }
1024 > // =============================================================================
1025 > // Permission display
1026 > //
1027 > // Derives display fields from SDK permission requests for the tool
1028 > // confirmation UI. Colocated with the tool-start display helpers above so
1029 > // that formatting utilities (formatPathAsMarkdownLink, md, etc.) are shared.
1030 > // =============================================================================
1031 >
1032 > export function tryStringify(value: unknown): string | undefined {
1033 try {
1034 return JSON.stringify(value);
1037 }
1038 }
1040 > /**
1041 > * Loose, optional-field projection of the SDK's {@link PermissionRequest}
1042 > * discriminated union. Lets the rest of the agent host read the well-known
1043 > * fields without `switch (request.kind)` narrowing at every access site.
1044 > *
1045 > * The SDK's `PermissionRequest` (a union with required per-variant fields) is
1046 > * structurally assignable to this interface — every variant carries `kind`
1047 > * and `toolCallId?`, and the variant-specific fields are listed here as
1048 > * optional. Use this type at the agent-host boundary so call sites and tests
1049 > * can rely on a single shape.
1050 > */
1051 > export interface ITypedPermissionRequest {
1052 > /** Permission kind discriminator from the SDK. */
1053 > kind: PermissionRequest['kind'];
1054 > /** Tool call ID that triggered this permission request, when available. */
1055 > toolCallId?: string;
1056 > /** File path — set for `read` permission requests. */
1057 > path?: string;
1058 > /** File path — set for `write` permission requests. */
1059 > fileName?: string;
1060 > /** Full shell command text — set for `shell` permission requests. */
1061 > fullCommandText?: string;
1062 > /**
1063 > * True when the model requested this `shell` command run outside the
1064 > * sandbox (via `requestSandboxBypass`) and the host opted in via
1065 > * `sandbox.allowBypass`.
1066 > */
1067 > requestSandboxBypass?: boolean;
1068 > /** Human-readable intention describing the operation. */
1069 > intention?: string;
1070 > /** MCP server name — set for `mcp` permission requests. */
1071 > serverName?: string;
1072 > /** Tool name — set for `mcp` and `custom-tool` permission requests. */
1073 > toolName?: string;
1074 > /** Tool arguments — set for `custom-tool` permission requests. */
1075 > args?: Record<string, unknown>;
1076 > /** URL — set for `url` permission requests. */
1077 > url?: string;
1078 > /** Unified diff of the proposed change — set for `write` permission requests. */
1079 > diff?: string;
1080 > /** New file contents that will be written — set for `write` permission requests. */
1081 > newFileContents?: string;
1082 > }
1083 >
1084 > /** Safely extract a string value from an SDK field that may be `unknown` at runtime. */
1085 function str(value: unknown): string | undefined {
1086 return typeof value === 'string' ? value : undefined;
1087 }
1089 > /**
1090 > * Derives display fields from a permission request for the tool confirmation UI.
1091 > */
1092 > export function getPermissionDisplay(request: ITypedPermissionRequest, workingDirectory?: URI, isNewFile?: boolean): {
1093 confirmationTitle: string;
1094 invocationMessage: StringOrMarkdown;