Path: blob/main/src/vs/workbench/contrib/chat/common/participants/chatSlashCommands.ts
4780 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { CancellationToken } from '../../../../../base/common/cancellation.js';6import { Emitter, Event } from '../../../../../base/common/event.js';7import { Disposable, IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js';8import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js';9import { IProgress } from '../../../../../platform/progress/common/progress.js';10import { IChatMessage } from '../languageModels.js';11import { IChatFollowup, IChatProgress, IChatResponseProgressFileTreeData } from '../chatService/chatService.js';12import { IExtensionService } from '../../../../services/extensions/common/extensions.js';13import { ChatAgentLocation, ChatModeKind } from '../constants.js';14import { URI } from '../../../../../base/common/uri.js';1516//#region slash service, commands etc1718export interface IChatSlashData {19command: string;20detail: string;21sortText?: string;22/**23* Whether the command should execute as soon24* as it is entered. Defaults to `false`.25*/26executeImmediately?: boolean;2728/**29* Whether the command should be added as a request/response30* turn to the chat history. Defaults to `false`.31*32* For instance, the `/save` command opens an untitled document33* to the side hence does not contain any chatbot responses.34*/35silent?: boolean;3637locations: ChatAgentLocation[];38modes?: ChatModeKind[];39}4041export interface IChatSlashFragment {42content: string | { treeData: IChatResponseProgressFileTreeData };43}44export type IChatSlashCallback = { (prompt: string, progress: IProgress<IChatProgress>, history: IChatMessage[], location: ChatAgentLocation, sessionResource: URI, token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void> };4546export const IChatSlashCommandService = createDecorator<IChatSlashCommandService>('chatSlashCommandService');4748/**49* This currently only exists to drive /clear and /help50*/51export interface IChatSlashCommandService {52_serviceBrand: undefined;53readonly onDidChangeCommands: Event<void>;54registerSlashCommand(data: IChatSlashData, command: IChatSlashCallback): IDisposable;55executeCommand(id: string, prompt: string, progress: IProgress<IChatProgress>, history: IChatMessage[], location: ChatAgentLocation, sessionResource: URI, token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void>;56getCommands(location: ChatAgentLocation, mode: ChatModeKind): Array<IChatSlashData>;57hasCommand(id: string): boolean;58}5960type Tuple = { data: IChatSlashData; command?: IChatSlashCallback };6162export class ChatSlashCommandService extends Disposable implements IChatSlashCommandService {6364declare _serviceBrand: undefined;6566private readonly _commands = new Map<string, Tuple>();6768private readonly _onDidChangeCommands = this._register(new Emitter<void>());69readonly onDidChangeCommands: Event<void> = this._onDidChangeCommands.event;7071constructor(@IExtensionService private readonly _extensionService: IExtensionService) {72super();73}7475override dispose(): void {76super.dispose();77this._commands.clear();78}7980registerSlashCommand(data: IChatSlashData, command: IChatSlashCallback): IDisposable {81if (this._commands.has(data.command)) {82throw new Error(`Already registered a command with id ${data.command}}`);83}8485this._commands.set(data.command, { data, command });86this._onDidChangeCommands.fire();8788return toDisposable(() => {89if (this._commands.delete(data.command)) {90this._onDidChangeCommands.fire();91}92});93}9495getCommands(location: ChatAgentLocation, mode: ChatModeKind): Array<IChatSlashData> {96return Array97.from(this._commands.values(), v => v.data)98.filter(c => c.locations.includes(location) && (!c.modes || c.modes.includes(mode)));99}100101hasCommand(id: string): boolean {102return this._commands.has(id);103}104105async executeCommand(id: string, prompt: string, progress: IProgress<IChatProgress>, history: IChatMessage[], location: ChatAgentLocation, sessionResource: URI, token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void> {106const data = this._commands.get(id);107if (!data) {108throw new Error('No command with id ${id} NOT registered');109}110if (!data.command) {111await this._extensionService.activateByEvent(`onSlash:${id}`);112}113if (!data.command) {114throw new Error(`No command with id ${id} NOT resolved`);115}116117return await data.command(prompt, progress, history, location, sessionResource, token);118}119}120121122