1
>
/*---------------------------------------------------------------------------------------------
chatSlashCommands.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 { CancellationToken } from '../../../../../base/common/cancellation.js';
7
>
import { Emitter, Event } from '../../../../../base/common/event.js';
8
>
import { Disposable, IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js';
9
>
import { ContextKeyExpression } from '../../../../../platform/contextkey/common/contextkey.js';
10
>
import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js';
11
>
import { IProgress } from '../../../../../platform/progress/common/progress.js';
12
>
import { IChatMessage } from '../languageModels.js';
13
>
import { IChatFollowup, IChatProgress, IChatResponseProgressFileTreeData, IChatSendRequestOptions } from '../chatService/chatService.js';
14
>
import { IExtensionService } from '../../../../services/extensions/common/extensions.js';
15
>
import { ChatAgentLocation, ChatModeKind } from '../constants.js';
16
>
import { URI } from '../../../../../base/common/uri.js';
17
>
import { getChatSessionType } from '../model/chatUri.js';
18
>
import { matchesSessionType } from '../promptSyntax/service/promptsService.js';
19
>
20
>
//#region slash service, commands etc
21
>
22
>
export interface IChatSlashData {
23
>
command: string;
24
>
detail: string;
25
>
sortText?: string;
26
>
/**
27
>
* Whether the command should execute as soon
28
>
* as it is entered. Defaults to `false`.
29
>
*/
30
>
executeImmediately?: boolean;
31
>
32
>
/**
33
>
* Whether a silent command can execute independently while the chat has a request in progress.
34
>
*/
35
>
executeDuringRequest?: boolean;
36
>
37
>
/**
38
>
* Whether the command should be added as a request/response
39
>
* turn to the chat history. Defaults to `false`.
40
>
*
41
>
* For instance, the `/save` command opens an untitled document
42
>
* to the side hence does not contain any chatbot responses.
43
>
*/
44
>
silent?: boolean;
45
>
46
>
locations: ChatAgentLocation[];
47
>
modes?: ChatModeKind[];
48
>
sessionTypes?: string[];
49
>
50
>
/**
51
>
* Optional context key expression that controls visibility of this command.
52
>
* When set, the command is only shown if the expression evaluates to true.
53
>
*/
54
>
when?: ContextKeyExpression;
55
>
}
56
>
57
>
export interface IChatSlashFragment {
58
>
content: string | { treeData: IChatResponseProgressFileTreeData };
59
>
}
60
>
export type IChatSlashCallback = { (prompt: string, progress: IProgress<IChatProgress>, history: IChatMessage[], location: ChatAgentLocation, sessionResource: URI, token: CancellationToken, options?: IChatSendRequestOptions): Promise<{ followUp: IChatFollowup[] } | void> };
61
>
62
>
export const IChatSlashCommandService = createDecorator<IChatSlashCommandService>('chatSlashCommandService');
63
>
64
>
/**
65
>
* This currently only exists to drive /clear and /help
66
>
*/
67
>
export interface IChatSlashCommandService {
68
>
_serviceBrand: undefined;
69
>
readonly onDidChangeCommands: Event<void>;
70
>
registerSlashCommand(data: IChatSlashData, command: IChatSlashCallback): IDisposable;
71
>
executeCommand(id: string, prompt: string, progress: IProgress<IChatProgress>, history: IChatMessage[], location: ChatAgentLocation, sessionResource: URI, token: CancellationToken, options?: IChatSendRequestOptions): Promise<{ followUp: IChatFollowup[] } | void>;
72
>
getCommands(location: ChatAgentLocation, mode: ChatModeKind): Array<IChatSlashData>;
73
>
hasCommand(id: string, sessionType: string): boolean;
74
>
}
75
>
76
>
type RegisteredSlashCommand = { data: IChatSlashData; command?: IChatSlashCallback };
77
>
78
>
export class ChatSlashCommandService extends Disposable implements IChatSlashCommandService {
79
>
80
>
declare _serviceBrand: undefined;
81
>
82
>
private readonly _commands = new Map<string, RegisteredSlashCommand[]>();
83
>
84
>
private readonly _onDidChangeCommands = this._register(new Emitter<void>());
85
>
readonly onDidChangeCommands: Event<void> = this._onDidChangeCommands.event;
86
>
87
>
constructor(@IExtensionService private readonly _extensionService: IExtensionService) {
88
super();
89
}
91
>
override dispose(): void {
92
super.dispose();
93
this._commands.clear();
94
}
96
>
private getSessionScopedCommands(id: string): RegisteredSlashCommand[] {
97
return this._commands.get(id) ?? [];
98
}
100
>
private commandsOverlap(dataA: IChatSlashData, dataB: IChatSlashData): boolean {
101
if (dataA.sessionTypes === undefined || dataB.sessionTypes === undefined) {
102
return true;