Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
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 { MockObject, mockObject } from '../../../../../base/test/common/mock.js';
7
import { assertSnapshot } from '../../../../../base/test/common/snapshot.js';
8
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
9
import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
10
import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
11
import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js';
12
import { ILogService, NullLogService } from '../../../../../platform/log/common/log.js';
13
import { IStorageService } from '../../../../../platform/storage/common/storage.js';
14
import { IExtensionService, nullExtensionDescription } from '../../../../services/extensions/common/extensions.js';
15
import { TestExtensionService, TestStorageService } from '../../../../test/common/workbenchTestServices.js';
16
import { ChatAgentService, IChatAgentCommand, IChatAgentData, IChatAgentService } from '../../common/chatAgents.js';
17
import { ChatRequestParser } from '../../common/chatRequestParser.js';
18
import { IChatService } from '../../common/chatService.js';
19
import { IChatSlashCommandService } from '../../common/chatSlashCommands.js';
20
import { IChatVariablesService } from '../../common/chatVariables.js';
21
import { ChatModeKind, ChatAgentLocation } from '../../common/constants.js';
22
import { IToolData, ToolDataSource, ToolSet } from '../../common/languageModelToolsService.js';
23
import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js';
24
import { MockChatService } from './mockChatService.js';
25
import { MockPromptsService } from './mockPromptsService.js';
26
27
suite('ChatRequestParser', () => {
28
const testDisposables = ensureNoDisposablesAreLeakedInTestSuite();
29
30
let instantiationService: TestInstantiationService;
31
let parser: ChatRequestParser;
32
33
let variableService: MockObject<IChatVariablesService>;
34
setup(async () => {
35
instantiationService = testDisposables.add(new TestInstantiationService());
36
instantiationService.stub(IStorageService, testDisposables.add(new TestStorageService()));
37
instantiationService.stub(ILogService, new NullLogService());
38
instantiationService.stub(IExtensionService, new TestExtensionService());
39
instantiationService.stub(IChatService, new MockChatService());
40
instantiationService.stub(IContextKeyService, new MockContextKeyService());
41
instantiationService.stub(IChatAgentService, testDisposables.add(instantiationService.createInstance(ChatAgentService)));
42
instantiationService.stub(IPromptsService, testDisposables.add(new MockPromptsService()));
43
44
variableService = mockObject<IChatVariablesService>()();
45
variableService.getDynamicVariables.returns([]);
46
variableService.getSelectedToolAndToolSets.returns([]);
47
48
instantiationService.stub(IChatVariablesService, variableService as any);
49
});
50
51
test('plain text', async () => {
52
parser = instantiationService.createInstance(ChatRequestParser);
53
const result = parser.parseChatRequest('1', 'test');
54
await assertSnapshot(result);
55
});
56
57
test('plain text with newlines', async () => {
58
parser = instantiationService.createInstance(ChatRequestParser);
59
const text = 'line 1\nline 2\r\nline 3';
60
const result = parser.parseChatRequest('1', text);
61
await assertSnapshot(result);
62
});
63
64
test('slash in text', async () => {
65
parser = instantiationService.createInstance(ChatRequestParser);
66
const text = 'can we add a new file for an Express router to handle the / route';
67
const result = parser.parseChatRequest('1', text);
68
await assertSnapshot(result);
69
});
70
71
test('slash command', async () => {
72
const slashCommandService = mockObject<IChatSlashCommandService>()({});
73
slashCommandService.getCommands.returns([{ command: 'fix' }]);
74
instantiationService.stub(IChatSlashCommandService, slashCommandService as any);
75
76
parser = instantiationService.createInstance(ChatRequestParser);
77
const text = '/fix this';
78
const result = parser.parseChatRequest('1', text);
79
await assertSnapshot(result);
80
});
81
82
test('invalid slash command', async () => {
83
const slashCommandService = mockObject<IChatSlashCommandService>()({});
84
slashCommandService.getCommands.returns([{ command: 'fix' }]);
85
instantiationService.stub(IChatSlashCommandService, slashCommandService as any);
86
87
parser = instantiationService.createInstance(ChatRequestParser);
88
const text = '/explain this';
89
const result = parser.parseChatRequest('1', text);
90
await assertSnapshot(result);
91
});
92
93
test('multiple slash commands', async () => {
94
const slashCommandService = mockObject<IChatSlashCommandService>()({});
95
slashCommandService.getCommands.returns([{ command: 'fix' }]);
96
instantiationService.stub(IChatSlashCommandService, slashCommandService as any);
97
98
parser = instantiationService.createInstance(ChatRequestParser);
99
const text = '/fix /fix';
100
const result = parser.parseChatRequest('1', text);
101
await assertSnapshot(result);
102
});
103
104
test('slash command not first', async () => {
105
const slashCommandService = mockObject<IChatSlashCommandService>()({});
106
slashCommandService.getCommands.returns([{ command: 'fix' }]);
107
instantiationService.stub(IChatSlashCommandService, slashCommandService as any);
108
109
parser = instantiationService.createInstance(ChatRequestParser);
110
const text = 'Hello /fix';
111
const result = parser.parseChatRequest('1', text);
112
await assertSnapshot(result);
113
});
114
115
test('slash command after whitespace', async () => {
116
const slashCommandService = mockObject<IChatSlashCommandService>()({});
117
slashCommandService.getCommands.returns([{ command: 'fix' }]);
118
instantiationService.stub(IChatSlashCommandService, slashCommandService as any);
119
120
parser = instantiationService.createInstance(ChatRequestParser);
121
const text = ' /fix';
122
const result = parser.parseChatRequest('1', text);
123
await assertSnapshot(result);
124
});
125
126
test('prompt slash command', async () => {
127
const slashCommandService = mockObject<IChatSlashCommandService>()({});
128
slashCommandService.getCommands.returns([{ command: 'fix' }]);
129
instantiationService.stub(IChatSlashCommandService, slashCommandService as any);
130
131
const promptSlashCommandService = mockObject<IPromptsService>()({});
132
promptSlashCommandService.asPromptSlashCommand.callsFake((command: string) => {
133
if (command.match(/^[\w_\-\.]+$/)) {
134
return { command };
135
}
136
return undefined;
137
});
138
instantiationService.stub(IPromptsService, promptSlashCommandService as any);
139
140
parser = instantiationService.createInstance(ChatRequestParser);
141
const text = ' /prompt';
142
const result = parser.parseChatRequest('1', text);
143
await assertSnapshot(result);
144
});
145
146
test('prompt slash command after text', async () => {
147
const slashCommandService = mockObject<IChatSlashCommandService>()({});
148
slashCommandService.getCommands.returns([{ command: 'fix' }]);
149
instantiationService.stub(IChatSlashCommandService, slashCommandService as any);
150
151
const promptSlashCommandService = mockObject<IPromptsService>()({});
152
promptSlashCommandService.asPromptSlashCommand.callsFake((command: string) => {
153
if (command.match(/^[\w_\-\.]+$/)) {
154
return { command };
155
}
156
return undefined;
157
});
158
instantiationService.stub(IPromptsService, promptSlashCommandService as any);
159
160
parser = instantiationService.createInstance(ChatRequestParser);
161
const text = 'handle the / route and the request of /search-option';
162
const result = parser.parseChatRequest('1', text);
163
await assertSnapshot(result);
164
});
165
166
test('prompt slash command after slash', async () => {
167
const slashCommandService = mockObject<IChatSlashCommandService>()({});
168
slashCommandService.getCommands.returns([{ command: 'fix' }]);
169
instantiationService.stub(IChatSlashCommandService, slashCommandService as any);
170
171
const promptSlashCommandService = mockObject<IPromptsService>()({});
172
promptSlashCommandService.asPromptSlashCommand.callsFake((command: string) => {
173
if (command.match(/^[\w_\-\.]+$/)) {
174
return { command };
175
}
176
return undefined;
177
});
178
instantiationService.stub(IPromptsService, promptSlashCommandService as any);
179
180
parser = instantiationService.createInstance(ChatRequestParser);
181
const text = '/ route and the request of /search-option';
182
const result = parser.parseChatRequest('1', text);
183
await assertSnapshot(result);
184
});
185
186
test('prompt slash command with numbers', async () => {
187
const slashCommandService = mockObject<IChatSlashCommandService>()({});
188
slashCommandService.getCommands.returns([{ command: 'fix' }]);
189
instantiationService.stub(IChatSlashCommandService, slashCommandService as any);
190
191
const promptSlashCommandService = mockObject<IPromptsService>()({});
192
promptSlashCommandService.asPromptSlashCommand.callsFake((command: string) => {
193
if (command.match(/^[\w_\-\.]+$/)) {
194
return { command };
195
}
196
return undefined;
197
});
198
instantiationService.stub(IPromptsService, promptSlashCommandService as any);
199
200
parser = instantiationService.createInstance(ChatRequestParser);
201
const text = '/001-sample this is a test';
202
const result = parser.parseChatRequest('1', text);
203
await assertSnapshot(result);
204
});
205
206
// test('variables', async () => {
207
// varService.hasVariable.returns(true);
208
// varService.getVariable.returns({ id: 'copilot.selection' });
209
210
// parser = instantiationService.createInstance(ChatRequestParser);
211
// const text = 'What does #selection mean?';
212
// const result = parser.parseChatRequest('1', text);
213
// await assertSnapshot(result);
214
// });
215
216
// test('variable with question mark', async () => {
217
// varService.hasVariable.returns(true);
218
// varService.getVariable.returns({ id: 'copilot.selection' });
219
220
// parser = instantiationService.createInstance(ChatRequestParser);
221
// const text = 'What is #selection?';
222
// const result = parser.parseChatRequest('1', text);
223
// await assertSnapshot(result);
224
// });
225
226
// test('invalid variables', async () => {
227
// varService.hasVariable.returns(false);
228
229
// parser = instantiationService.createInstance(ChatRequestParser);
230
// const text = 'What does #selection mean?';
231
// const result = parser.parseChatRequest('1', text);
232
// await assertSnapshot(result);
233
// });
234
235
const getAgentWithSlashCommands = (slashCommands: IChatAgentCommand[]) => {
236
return { id: 'agent', name: 'agent', extensionId: nullExtensionDescription.identifier, extensionVersion: undefined, publisherDisplayName: '', extensionDisplayName: '', extensionPublisherId: '', locations: [ChatAgentLocation.Panel], modes: [ChatModeKind.Ask], metadata: {}, slashCommands, disambiguation: [] } satisfies IChatAgentData;
237
};
238
239
test('agent with subcommand after text', async () => {
240
const agentsService = mockObject<IChatAgentService>()({});
241
agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);
242
instantiationService.stub(IChatAgentService, agentsService as any);
243
244
parser = instantiationService.createInstance(ChatRequestParser);
245
const result = parser.parseChatRequest('1', '@agent Please do /subCommand thanks');
246
await assertSnapshot(result);
247
});
248
249
test('agents, subCommand', async () => {
250
const agentsService = mockObject<IChatAgentService>()({});
251
agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);
252
instantiationService.stub(IChatAgentService, agentsService as any);
253
254
parser = instantiationService.createInstance(ChatRequestParser);
255
const result = parser.parseChatRequest('1', '@agent /subCommand Please do thanks');
256
await assertSnapshot(result);
257
});
258
259
test('agent but edit mode', async () => {
260
const agentsService = mockObject<IChatAgentService>()({});
261
agentsService.getAgentsByName.returns([getAgentWithSlashCommands([])]);
262
instantiationService.stub(IChatAgentService, agentsService as any);
263
264
parser = instantiationService.createInstance(ChatRequestParser);
265
const result = parser.parseChatRequest('1', '@agent hello', undefined, { mode: ChatModeKind.Edit });
266
await assertSnapshot(result);
267
});
268
269
test('agent with question mark', async () => {
270
const agentsService = mockObject<IChatAgentService>()({});
271
agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);
272
instantiationService.stub(IChatAgentService, agentsService as any);
273
274
parser = instantiationService.createInstance(ChatRequestParser);
275
const result = parser.parseChatRequest('1', '@agent? Are you there');
276
await assertSnapshot(result);
277
});
278
279
test('agent and subcommand with leading whitespace', async () => {
280
const agentsService = mockObject<IChatAgentService>()({});
281
agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);
282
instantiationService.stub(IChatAgentService, agentsService as any);
283
284
parser = instantiationService.createInstance(ChatRequestParser);
285
const result = parser.parseChatRequest('1', ' \r\n\t @agent \r\n\t /subCommand Thanks');
286
await assertSnapshot(result);
287
});
288
289
test('agent and subcommand after newline', async () => {
290
const agentsService = mockObject<IChatAgentService>()({});
291
agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);
292
instantiationService.stub(IChatAgentService, agentsService as any);
293
294
parser = instantiationService.createInstance(ChatRequestParser);
295
const result = parser.parseChatRequest('1', ' \n@agent\n/subCommand Thanks');
296
await assertSnapshot(result);
297
});
298
299
test('agent not first', async () => {
300
const agentsService = mockObject<IChatAgentService>()({});
301
agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);
302
instantiationService.stub(IChatAgentService, agentsService as any);
303
304
parser = instantiationService.createInstance(ChatRequestParser);
305
const result = parser.parseChatRequest('1', 'Hello Mr. @agent');
306
await assertSnapshot(result);
307
});
308
309
test('agents and tools and multiline', async () => {
310
const agentsService = mockObject<IChatAgentService>()({});
311
agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);
312
instantiationService.stub(IChatAgentService, agentsService as any);
313
314
variableService.getSelectedToolAndToolSets.returns(new Map([
315
[{ id: 'get_selection', toolReferenceName: 'selection', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: ToolDataSource.Internal }, true],
316
[{ id: 'get_debugConsole', toolReferenceName: 'debugConsole', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: ToolDataSource.Internal }, true]
317
] satisfies [IToolData | ToolSet, boolean][]));
318
319
parser = instantiationService.createInstance(ChatRequestParser);
320
const result = parser.parseChatRequest('1', '@agent /subCommand \nPlease do with #selection\nand #debugConsole');
321
await assertSnapshot(result);
322
});
323
324
test('agents and tools and multiline, part2', async () => {
325
const agentsService = mockObject<IChatAgentService>()({});
326
agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);
327
instantiationService.stub(IChatAgentService, agentsService as any);
328
329
variableService.getSelectedToolAndToolSets.returns(new Map([
330
[{ id: 'get_selection', toolReferenceName: 'selection', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: ToolDataSource.Internal }, true],
331
[{ id: 'get_debugConsole', toolReferenceName: 'debugConsole', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: ToolDataSource.Internal }, true]
332
] satisfies [IToolData | ToolSet, boolean][]));
333
334
parser = instantiationService.createInstance(ChatRequestParser);
335
const result = parser.parseChatRequest('1', '@agent Please \ndo /subCommand with #selection\nand #debugConsole');
336
await assertSnapshot(result);
337
});
338
});
339
340