src/vs/platform/agentHost/common/copilotConfigSlashCommands.ts

261 LOC · 259 covered · 2 uncovered · 30 ranges · 935 concepts · 13 introducers · 471 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 > /*--------------------------------------------------------------------------------------------- copilotConfigSlashCommands.ts ×6
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 { matchesFuzzy2 } from '../../../base/common/filters.js';
7 > import { localize } from '../../../nls.js';
8 > import { SessionConfigKey } from './sessionConfigKeys.js';
9 >
10 > /**
11 > * Copilot agent-host "config action" slash commands: workbench-defined slash
12 > * commands that toggle a well-known session-config property (the `autoApprove`
13 > * permissions axis and/or the `mode` axis) instead of driving a chat turn.
14 > *
15 > * The Copilot agent owns the `autoApprove`/`mode` schema, so these commands are
16 > * produced server-side (see the Copilot slash-command completion provider) and
17 > * carry an `action` bag on their completion `_meta`. The workbench interprets
18 > * that bag on accept — applying the config via the active session's provider so
19 > * the permission/mode pickers update reactively — while the send path
20 > * (`CopilotAgentSession.send`) re-applies the change and strips the leading
21 > * token so it is not dispatched to the runtime as a runtime command.
22 > *
23 > * Values below are the well-known enum members of the Copilot platform session
24 > * schema (`autoApprove`: `default` | `autoApprove`; `mode`: `interactive` |
25 > * `plan` | `autopilot`).
26 > */
27 >
28 > const AUTO_APPROVE_BYPASS = 'autoApprove';
29 > const AUTO_APPROVE_DEFAULT = 'default';
30 > const MODE_INTERACTIVE = 'interactive';
31 > const MODE_PLAN = 'plan';
32 > const MODE_AUTOPILOT = 'autopilot';
33 >
34 > /**
35 > * A single flattened completion form of a config-action slash command (the bare
36 > * command or one of its named sub-arguments), ready to be emitted as a
37 > * completion item.
38 > */
39 > export interface ICopilotConfigSlashCommandItem {
40 > /**
41 > * The text inserted when accepted. Empty for a pure toggle (nothing is left
42 > * in the input); `/command ` (trailing space) for an item that keeps the text
43 > * so an argument can be typed.
44 > */
45 > readonly insertText: string;
46 > /** The display label shown in the picker (e.g. `/autopilot on`). */
47 > readonly label: string;
48 > /** The command name (without the leading `/`). */
49 > readonly command: string;
50 > /** Human-readable description shown in completion detail. */
51 > readonly description: string;
52 > /** Argument hint (ghost text) shown after acceptance for keep-text items. */
53 > readonly argumentHint?: string;
54 > /** The session-config change applied when accepted. */
55 > readonly applyConfig: Readonly<Record<string, string>>;
56 > /** Sort key used to order completions. */
57 > readonly sortText: string;
58 > }
59 >
60 > /** Internal catalog descriptor for one form of a config-action command. */
61 > interface IConfigSlashOption {
62 > /** Named sub-argument (e.g. `on`/`off`), or `undefined` for the bare command. */
63 > readonly arg?: string;
64 > readonly detail: string;
65 > readonly config: Readonly<Record<string, string>>;
66 > /**
67 > * When set, the option is a keep-text form: it inserts `/command ` and shows
68 > * this hint as ghost text so an argument can be typed. When omitted, the
69 > * option is a pure toggle that inserts nothing.
70 > */
71 > readonly argumentHint?: string;
72 > }
73 >
74 > interface IConfigSlashCommand {
75 > readonly command: string;
76 > readonly sortText: string;
77 > readonly options: readonly IConfigSlashOption[];
78 > }
79 >
80 > function setBypassDetail(): string { return localize('copilotConfigSlash.yolo', "Set permissions to bypass approvals"); } copilotConfigSlashCommands.ts ×2
81 > function setDefaultDetail(): string { return localize('copilotConfigSlash.default', "Set permissions back to default"); }
82 > function autopilotOnDetail(): string { return localize('copilotConfigSlash.autopilot.on', "Switch to autopilot mode"); }
83 > function exitAutopilotDetail(): string { return localize('copilotConfigSlash.exitAutopilot', "Switch to interactive mode"); }
84 > function autopilotPromptDetail(): string { return localize('copilotConfigSlash.autopilot.prompt', "Switch to autopilot mode with an objective"); }
85 > function planPromptDetail(): string { return localize('copilotConfigSlash.plan.prompt', "Create an implementation plan before coding"); }
86 > function autopilotArgumentHint(): string { return localize('copilotConfigSlash.autopilotHint', "objective"); }
87 > function promptArgumentHint(): string { return localize('copilotConfigSlash.promptHint', "Describe what you want to plan or research"); }
89 > function getConfigSlashCommands(): readonly IConfigSlashCommand[] { copilotConfigSlashCommands.ts ×2
90 > return [
91 > {
92 > command: 'yolo', sortText: 'z1_yolo',
93 > options: [
94 > { arg: 'on', detail: setBypassDetail(), config: { [SessionConfigKey.AutoApprove]: AUTO_APPROVE_BYPASS } },
95 > { arg: 'off', detail: setDefaultDetail(), config: { [SessionConfigKey.AutoApprove]: AUTO_APPROVE_DEFAULT } }
96 > ],
97 > },
98 > {
99 > command: 'allow-all', sortText: 'z1_allow-all',
100 > options: [
101 > { arg: 'on', detail: setBypassDetail(), config: { [SessionConfigKey.AutoApprove]: AUTO_APPROVE_BYPASS } },
102 > { arg: 'off', detail: setDefaultDetail(), config: { [SessionConfigKey.AutoApprove]: AUTO_APPROVE_DEFAULT } }
103 > ],
104 > },
105 > {
106 > command: 'autopilot', sortText: 'z1_autopilot',
107 > options: [
108 > { arg: 'on', detail: autopilotOnDetail(), config: { [SessionConfigKey.Mode]: MODE_AUTOPILOT } },
109 > { arg: 'off', detail: exitAutopilotDetail(), config: { [SessionConfigKey.Mode]: MODE_INTERACTIVE } },
110 > { detail: autopilotPromptDetail(), config: { [SessionConfigKey.Mode]: MODE_AUTOPILOT }, argumentHint: autopilotArgumentHint() },
111 > ],
112 > },
113 > {
114 > command: 'plan', sortText: 'z1_plan',
115 > options: [
116 > { detail: planPromptDetail(), config: { [SessionConfigKey.Mode]: MODE_PLAN }, argumentHint: promptArgumentHint() },
117 > ],
118 > },
119 > {
120 > command: 'goal', sortText: 'z1_goal',
121 > options: [
122 > { detail: planPromptDetail(), config: { [SessionConfigKey.Mode]: MODE_PLAN }, argumentHint: promptArgumentHint() },
123 > ],
124 > },
125 > ];
126 > }
128 > /**
129 > * The set of command names that are config-action commands. Used by the send
130 > * path to decide whether a leading slash command should be intercepted (applied
131 > * + stripped) rather than dispatched to the runtime.
132 > */
133 > export function isCopilotConfigSlashCommand(command: string): boolean {
134 > return getConfigSlashCommands().some(c => c.command.toLowerCase() === command.toLowerCase()); copilotConfigSlashCommands.ts ×1
135 > }
137 > /**
138 > * The current session-config state used to filter config-action slash command
139 > * completions so only the state-changing forms are offered (e.g. `/autopilot on`
140 > * is hidden while already in autopilot mode).
141 > */
142 > export interface ICopilotConfigSlashCommandState {
143 > /** The session's current `mode` axis value (e.g. `interactive` / `plan` / `autopilot`). */
144 > readonly mode?: string;
145 > /** The session's current `autoApprove` axis value (e.g. `default` / `autoApprove`). */
146 > readonly autoApprove?: string;
147 > }
148 >
149 > /**
150 > * Returns whether the option should be offered for the current session state.
151 > * Unknown state and keep-text options are always offered.
152 > */
153 > function shouldOfferOption(option: IConfigSlashOption, state: ICopilotConfigSlashCommandState | undefined): boolean { copilotConfigSlashCommands.ts ×4
154 > // Keep-text forms carry a typed prompt/objective and are always relevant.
155 > if (option.argumentHint !== undefined || !state) {
157 > }
158 > const autoApproveTarget = option.config[SessionConfigKey.AutoApprove]; copilotConfigSlashCommands.ts ×2
159 > if (autoApproveTarget !== undefined) {
160 > const isBypass = state.autoApprove === AUTO_APPROVE_BYPASS; copilotConfigSlashCommands.ts ×1
161 > return autoApproveTarget === AUTO_APPROVE_BYPASS ? !isBypass : isBypass;
162 > }
163 > const modeTarget = option.config[SessionConfigKey.Mode]; copilotConfigSlashCommands.ts ×1
164 > if (modeTarget === MODE_AUTOPILOT) {
165 > return state.mode !== MODE_AUTOPILOT;
166 > }
167 > if (modeTarget === MODE_INTERACTIVE) {
168 > return state.mode === MODE_AUTOPILOT;
169 > }
170 return true;
171 }
173 > /**
174 > * Returns the flattened completion items (one per command form) whose command
175 > * name fuzzy matches `typed` (the text after the leading `/`, case-insensitive).
176 > * When `typed` is empty, all items are returned.
177 > *
178 > * When `state` (the session's current config values) is provided, pure toggle
179 > * forms that would be a no-op are filtered out so only the state-changing forms
180 > * are offered (see {@link shouldOfferOption}).
181 > */
182 > export function getCopilotConfigSlashCommandItems(typed: string, state?: ICopilotConfigSlashCommandState): ICopilotConfigSlashCommandItem[] {
183 > const typedLower = typed.trim().toLowerCase(); copilotConfigSlashCommands.ts ×3
184 > const items: ICopilotConfigSlashCommandItem[] = [];
185 > for (const command of getConfigSlashCommands()) {
186 > if (typedLower
187 > && !command.command.toLowerCase().startsWith(typedLower) copilotConfigSlashCommands.ts ×2
188 > && (typedLower.length === 1 || matchesFuzzy2(typedLower, command.command) === null)
191 > }
192 > for (const option of command.options) { copilotConfigSlashCommands.ts ×4
193 > if (!shouldOfferOption(option, state)) {
195 > }
196 > // Keep-text items (those expecting a typed argument) insert `/command ` copilotConfigSlashCommands.ts ×4
197 > // and show the argument hint; pure toggles insert nothing (the display
198 > // comes from `label`).
199 > const keep = option.argumentHint !== undefined;
200 > const insertText = keep ? `/${command.command} ` : '';
201 > const label = keep
202 > ? `/${command.command}` copilotConfigSlashCommands.ts ×2
203 > : (option.arg ? `/${command.command} ${option.arg}` : `/${command.command}`); copilotConfigSlashCommands.ts ×1
205 > insertText,
206 > label,
207 > command: command.command,
208 > description: option.detail,
209 > ...(option.argumentHint !== undefined ? { argumentHint: option.argumentHint } : {}),
210 > applyConfig: option.config,
211 > sortText: option.arg ? `${command.sortText}_${option.arg}` : command.sortText,
212 > });
213 > }
214 > }
215 > return items; copilotConfigSlashCommands.ts ×3
216 > }
218 > /**
219 > * Result of resolving a config-action slash command on send.
220 > */
221 > export interface ICopilotConfigSlashCommandSendResult {
222 > /** The session-config change to (re-)apply. */
223 > readonly applyConfig: Readonly<Record<string, string>>;
224 > /**
225 > * The prompt text that should be forwarded to the runtime after stripping the
226 > * command token (and any recognized sub-argument). Empty when the command is a
227 > * pure toggle with no trailing prompt.
228 > */
229 > readonly strippedPrompt: string;
230 > }
231 >
232 > /**
233 > * Resolves a leading config-action slash command for the send path: maps the
234 > * command (and any recognized `on`/`off` sub-argument) to the session-config
235 > * change to apply, and returns the remaining prompt text to forward with the
236 > * command token stripped. Returns `undefined` for non-config-action commands so
237 > * callers fall through to their normal (runtime) handling.
238 > */
239 > export function resolveCopilotConfigSlashCommandOnSend(command: string, rest: string): ICopilotConfigSlashCommandSendResult | undefined {
240 > const descriptor = getConfigSlashCommands().find(c => c.command.toLowerCase() === command.toLowerCase()); copilotConfigSlashCommands.ts ×3
241 > if (!descriptor) {
242 > return undefined;
243 > }
244 > const trimmedRest = rest.trim(); copilotConfigSlashCommands.ts ×2
245 > const namedOptions = descriptor.options.filter(o => o.arg !== undefined);
246 > const baseOption = descriptor.options.find(o => o.arg === undefined);
247 > if (namedOptions.length > 0 && trimmedRest.length > 0) { copilotConfigSlashCommands.ts ×3
248 > const match = /^(\S+)(?:\s+([\s\S]*))?$/.exec(trimmedRest); copilotConfigSlashCommands.ts ×2
249 > const firstToken = match?.[1]?.toLowerCase();
250 > const matched = namedOptions.find(o => o.arg?.toLowerCase() === firstToken);
251 > if (matched) {
252 > return { applyConfig: matched.config, strippedPrompt: (match?.[2] ?? '').trim() };
253 > }
254 > if (!baseOption) {
255 > return undefined;
256 > }
257 > }
258 > // Fall back to the bare command form (the base/prompt option or the sole option).
259 > const fallback = baseOption ?? descriptor.options[0];
260 > return { applyConfig: fallback.config, strippedPrompt: trimmedRest }; copilotConfigSlashCommands.ts ×3
261 > }