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.
/*---------------------------------------------------------------------------------------------
copilotConfigSlashCommands.ts ×6
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { matchesFuzzy2 } from '../../../base/common/filters.js';
import { localize } from '../../../nls.js';
import { SessionConfigKey } from './sessionConfigKeys.js';
/**
* Copilot agent-host "config action" slash commands: workbench-defined slash
* commands that toggle a well-known session-config property (the `autoApprove`
* permissions axis and/or the `mode` axis) instead of driving a chat turn.
*
* The Copilot agent owns the `autoApprove`/`mode` schema, so these commands are
* produced server-side (see the Copilot slash-command completion provider) and
* carry an `action` bag on their completion `_meta`. The workbench interprets
* that bag on accept — applying the config via the active session's provider so
* the permission/mode pickers update reactively — while the send path
* (`CopilotAgentSession.send`) re-applies the change and strips the leading
* token so it is not dispatched to the runtime as a runtime command.
*
* Values below are the well-known enum members of the Copilot platform session
* schema (`autoApprove`: `default` | `autoApprove`; `mode`: `interactive` |
* `plan` | `autopilot`).
*/
const AUTO_APPROVE_BYPASS = 'autoApprove';
const AUTO_APPROVE_DEFAULT = 'default';
const MODE_INTERACTIVE = 'interactive';
const MODE_PLAN = 'plan';
const MODE_AUTOPILOT = 'autopilot';
/**
* A single flattened completion form of a config-action slash command (the bare
* command or one of its named sub-arguments), ready to be emitted as a
* completion item.
*/
export interface ICopilotConfigSlashCommandItem {
/**
* The text inserted when accepted. Empty for a pure toggle (nothing is left
* in the input); `/command ` (trailing space) for an item that keeps the text
* so an argument can be typed.
*/
readonly insertText: string;
/** The display label shown in the picker (e.g. `/autopilot on`). */
readonly label: string;
/** The command name (without the leading `/`). */
readonly command: string;
/** Human-readable description shown in completion detail. */
readonly description: string;
/** Argument hint (ghost text) shown after acceptance for keep-text items. */
readonly argumentHint?: string;
/** The session-config change applied when accepted. */
readonly applyConfig: Readonly<Record<string, string>>;
/** Sort key used to order completions. */
readonly sortText: string;
}
/** Internal catalog descriptor for one form of a config-action command. */
interface IConfigSlashOption {
/** Named sub-argument (e.g. `on`/`off`), or `undefined` for the bare command. */
readonly arg?: string;
readonly detail: string;
readonly config: Readonly<Record<string, string>>;
/**
* When set, the option is a keep-text form: it inserts `/command ` and shows
* this hint as ghost text so an argument can be typed. When omitted, the
* option is a pure toggle that inserts nothing.
*/
readonly argumentHint?: string;
}
interface IConfigSlashCommand {
readonly command: string;
readonly sortText: string;
readonly options: readonly IConfigSlashOption[];
}
function setBypassDetail(): string { return localize('copilotConfigSlash.yolo', "Set permissions to bypass approvals"); }
copilotConfigSlashCommands.ts ×2
function setDefaultDetail(): string { return localize('copilotConfigSlash.default', "Set permissions back to default"); }
function autopilotOnDetail(): string { return localize('copilotConfigSlash.autopilot.on', "Switch to autopilot mode"); }
function exitAutopilotDetail(): string { return localize('copilotConfigSlash.exitAutopilot', "Switch to interactive mode"); }
function autopilotPromptDetail(): string { return localize('copilotConfigSlash.autopilot.prompt', "Switch to autopilot mode with an objective"); }
function planPromptDetail(): string { return localize('copilotConfigSlash.plan.prompt', "Create an implementation plan before coding"); }
function autopilotArgumentHint(): string { return localize('copilotConfigSlash.autopilotHint', "objective"); }
function promptArgumentHint(): string { return localize('copilotConfigSlash.promptHint', "Describe what you want to plan or research"); }
function getConfigSlashCommands(): readonly IConfigSlashCommand[] {
copilotConfigSlashCommands.ts ×2
return [
{
command: 'yolo', sortText: 'z1_yolo',
options: [
{ arg: 'on', detail: setBypassDetail(), config: { [SessionConfigKey.AutoApprove]: AUTO_APPROVE_BYPASS } },
{ arg: 'off', detail: setDefaultDetail(), config: { [SessionConfigKey.AutoApprove]: AUTO_APPROVE_DEFAULT } }
],
},
{
command: 'allow-all', sortText: 'z1_allow-all',
options: [
{ arg: 'on', detail: setBypassDetail(), config: { [SessionConfigKey.AutoApprove]: AUTO_APPROVE_BYPASS } },
{ arg: 'off', detail: setDefaultDetail(), config: { [SessionConfigKey.AutoApprove]: AUTO_APPROVE_DEFAULT } }
],
},
{
command: 'autopilot', sortText: 'z1_autopilot',
options: [
{ arg: 'on', detail: autopilotOnDetail(), config: { [SessionConfigKey.Mode]: MODE_AUTOPILOT } },
{ arg: 'off', detail: exitAutopilotDetail(), config: { [SessionConfigKey.Mode]: MODE_INTERACTIVE } },
{ detail: autopilotPromptDetail(), config: { [SessionConfigKey.Mode]: MODE_AUTOPILOT }, argumentHint: autopilotArgumentHint() },
],
},
{
command: 'plan', sortText: 'z1_plan',
options: [
{ detail: planPromptDetail(), config: { [SessionConfigKey.Mode]: MODE_PLAN }, argumentHint: promptArgumentHint() },
],
},
{
command: 'goal', sortText: 'z1_goal',
options: [
{ detail: planPromptDetail(), config: { [SessionConfigKey.Mode]: MODE_PLAN }, argumentHint: promptArgumentHint() },
],
},
];
}
/**
* The set of command names that are config-action commands. Used by the send
* path to decide whether a leading slash command should be intercepted (applied
* + stripped) rather than dispatched to the runtime.
*/
export function isCopilotConfigSlashCommand(command: string): boolean {
return getConfigSlashCommands().some(c => c.command.toLowerCase() === command.toLowerCase());
copilotConfigSlashCommands.ts ×1
}
/**
* The current session-config state used to filter config-action slash command
* completions so only the state-changing forms are offered (e.g. `/autopilot on`
* is hidden while already in autopilot mode).
*/
export interface ICopilotConfigSlashCommandState {
/** The session's current `mode` axis value (e.g. `interactive` / `plan` / `autopilot`). */
readonly mode?: string;
/** The session's current `autoApprove` axis value (e.g. `default` / `autoApprove`). */
readonly autoApprove?: string;
}
/**
* Returns whether the option should be offered for the current session state.
* Unknown state and keep-text options are always offered.
*/
function shouldOfferOption(option: IConfigSlashOption, state: ICopilotConfigSlashCommandState | undefined): boolean {
copilotConfigSlashCommands.ts ×4
// Keep-text forms carry a typed prompt/objective and are always relevant.
if (option.argumentHint !== undefined || !state) {
}
const autoApproveTarget = option.config[SessionConfigKey.AutoApprove];
copilotConfigSlashCommands.ts ×2
if (autoApproveTarget !== undefined) {
return autoApproveTarget === AUTO_APPROVE_BYPASS ? !isBypass : isBypass;
}
if (modeTarget === MODE_AUTOPILOT) {
return state.mode !== MODE_AUTOPILOT;
}
if (modeTarget === MODE_INTERACTIVE) {
return state.mode === MODE_AUTOPILOT;
}
return true;
}
/**
* Returns the flattened completion items (one per command form) whose command
* name fuzzy matches `typed` (the text after the leading `/`, case-insensitive).
* When `typed` is empty, all items are returned.
*
* When `state` (the session's current config values) is provided, pure toggle
* forms that would be a no-op are filtered out so only the state-changing forms
* are offered (see {@link shouldOfferOption}).
*/
export function getCopilotConfigSlashCommandItems(typed: string, state?: ICopilotConfigSlashCommandState): ICopilotConfigSlashCommandItem[] {
const items: ICopilotConfigSlashCommandItem[] = [];
for (const command of getConfigSlashCommands()) {
if (typedLower
&& (typedLower.length === 1 || matchesFuzzy2(typedLower, command.command) === null)
}
if (!shouldOfferOption(option, state)) {
}
// Keep-text items (those expecting a typed argument) insert `/command `
copilotConfigSlashCommands.ts ×4
// and show the argument hint; pure toggles insert nothing (the display
// comes from `label`).
const keep = option.argumentHint !== undefined;
const insertText = keep ? `/${command.command} ` : '';
const label = keep
: (option.arg ? `/${command.command} ${option.arg}` : `/${command.command}`);
copilotConfigSlashCommands.ts ×1
insertText,
label,
command: command.command,
description: option.detail,
...(option.argumentHint !== undefined ? { argumentHint: option.argumentHint } : {}),
applyConfig: option.config,
sortText: option.arg ? `${command.sortText}_${option.arg}` : command.sortText,
});
}
}
}
/**
* Result of resolving a config-action slash command on send.
*/
export interface ICopilotConfigSlashCommandSendResult {
/** The session-config change to (re-)apply. */
readonly applyConfig: Readonly<Record<string, string>>;
/**
* The prompt text that should be forwarded to the runtime after stripping the
* command token (and any recognized sub-argument). Empty when the command is a
* pure toggle with no trailing prompt.
*/
readonly strippedPrompt: string;
}
/**
* Resolves a leading config-action slash command for the send path: maps the
* command (and any recognized `on`/`off` sub-argument) to the session-config
* change to apply, and returns the remaining prompt text to forward with the
* command token stripped. Returns `undefined` for non-config-action commands so
* callers fall through to their normal (runtime) handling.
*/
export function resolveCopilotConfigSlashCommandOnSend(command: string, rest: string): ICopilotConfigSlashCommandSendResult | undefined {
const descriptor = getConfigSlashCommands().find(c => c.command.toLowerCase() === command.toLowerCase());
copilotConfigSlashCommands.ts ×3
if (!descriptor) {
return undefined;
}
const namedOptions = descriptor.options.filter(o => o.arg !== undefined);
const baseOption = descriptor.options.find(o => o.arg === undefined);
const firstToken = match?.[1]?.toLowerCase();
const matched = namedOptions.find(o => o.arg?.toLowerCase() === firstToken);
if (matched) {
return { applyConfig: matched.config, strippedPrompt: (match?.[2] ?? '').trim() };
}
if (!baseOption) {
return undefined;
}
}
// Fall back to the bare command form (the base/prompt option or the sole option).
const fallback = baseOption ?? descriptor.options[0];
return { applyConfig: fallback.config, strippedPrompt: trimmedRest };
copilotConfigSlashCommands.ts ×3
}