Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts
5262 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 assert from 'assert';
7
import { timeout } from '../../../../../base/common/async.js';
8
import { Emitter } from '../../../../../base/common/event.js';
9
import { URI } from '../../../../../base/common/uri.js';
10
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
11
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
12
import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
13
import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
14
import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js';
15
import { ILogService, NullLogService } from '../../../../../platform/log/common/log.js';
16
import { IStorageService } from '../../../../../platform/storage/common/storage.js';
17
import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js';
18
import { TestStorageService } from '../../../../test/common/workbenchTestServices.js';
19
import { IChatAgentService } from '../../common/participants/chatAgents.js';
20
import { ChatMode, ChatModeService } from '../../common/chatModes.js';
21
import { ChatModeKind } from '../../common/constants.js';
22
import { IAgentSource, ICustomAgent, IPromptsService, PromptsStorage, Target } from '../../common/promptSyntax/service/promptsService.js';
23
import { MockPromptsService } from './promptSyntax/service/mockPromptsService.js';
24
25
class TestChatAgentService implements Partial<IChatAgentService> {
26
_serviceBrand: undefined;
27
28
private _hasToolsAgent = true;
29
private readonly _onDidChangeAgents = new Emitter<any>();
30
31
get hasToolsAgent(): boolean {
32
return this._hasToolsAgent;
33
}
34
35
setHasToolsAgent(value: boolean): void {
36
this._hasToolsAgent = value;
37
this._onDidChangeAgents.fire(undefined);
38
}
39
40
readonly onDidChangeAgents = this._onDidChangeAgents.event;
41
}
42
43
suite('ChatModeService', () => {
44
const testDisposables = ensureNoDisposablesAreLeakedInTestSuite();
45
46
const workspaceSource: IAgentSource = { storage: PromptsStorage.local };
47
48
let instantiationService: TestInstantiationService;
49
let promptsService: MockPromptsService;
50
let chatAgentService: TestChatAgentService;
51
let storageService: TestStorageService;
52
let configurationService: TestConfigurationService;
53
let chatModeService: ChatModeService;
54
55
setup(async () => {
56
instantiationService = testDisposables.add(new TestInstantiationService());
57
promptsService = new MockPromptsService();
58
chatAgentService = new TestChatAgentService();
59
storageService = testDisposables.add(new TestStorageService());
60
configurationService = new TestConfigurationService();
61
62
instantiationService.stub(IPromptsService, promptsService);
63
instantiationService.stub(IChatAgentService, chatAgentService);
64
instantiationService.stub(IStorageService, storageService);
65
instantiationService.stub(ILogService, new NullLogService());
66
instantiationService.stub(IContextKeyService, new MockContextKeyService());
67
instantiationService.stub(IConfigurationService, configurationService);
68
69
chatModeService = testDisposables.add(instantiationService.createInstance(ChatModeService));
70
});
71
72
test('should return builtin modes', () => {
73
const modes = chatModeService.getModes();
74
75
assert.strictEqual(modes.builtin.length, 3);
76
assert.strictEqual(modes.custom.length, 0);
77
78
// Check that Ask mode is always present
79
const askMode = modes.builtin.find(mode => mode.id === ChatModeKind.Ask);
80
assert.ok(askMode);
81
assert.strictEqual(askMode.label.get(), 'Ask');
82
assert.strictEqual(askMode.name.get(), 'ask');
83
assert.strictEqual(askMode.kind, ChatModeKind.Ask);
84
});
85
86
test('should adjust builtin modes based on tools agent availability', () => {
87
// Agent mode should always be present regardless of tools agent availability
88
chatAgentService.setHasToolsAgent(true);
89
let agents = chatModeService.getModes();
90
assert.ok(agents.builtin.find(agent => agent.id === ChatModeKind.Agent));
91
92
// Without tools agent - Agent mode should not be present
93
chatAgentService.setHasToolsAgent(false);
94
agents = chatModeService.getModes();
95
assert.strictEqual(agents.builtin.find(agent => agent.id === ChatModeKind.Agent), undefined);
96
97
// Ask and Edit modes should always be present
98
assert.ok(agents.builtin.find(agent => agent.id === ChatModeKind.Ask));
99
assert.ok(agents.builtin.find(agent => agent.id === ChatModeKind.Edit));
100
});
101
102
test('should find builtin modes by id', () => {
103
const agentMode = chatModeService.findModeById(ChatModeKind.Agent);
104
assert.ok(agentMode);
105
assert.strictEqual(agentMode.id, ChatMode.Agent.id);
106
assert.strictEqual(agentMode.kind, ChatModeKind.Agent);
107
});
108
109
test('should return undefined for non-existent mode', () => {
110
const mode = chatModeService.findModeById('non-existent-mode');
111
assert.strictEqual(mode, undefined);
112
});
113
114
test('should handle custom modes from prompts service', async () => {
115
const customMode: ICustomAgent = {
116
uri: URI.parse('file:///test/custom-mode.md'),
117
name: 'Test Mode',
118
description: 'A test custom mode',
119
tools: ['tool1', 'tool2'],
120
agentInstructions: { content: 'Custom mode body', toolReferences: [] },
121
source: workspaceSource,
122
target: Target.Undefined,
123
visibility: { userInvokable: true, agentInvokable: true }
124
};
125
126
promptsService.setCustomModes([customMode]);
127
128
// Wait for the service to refresh
129
await timeout(0);
130
131
const modes = chatModeService.getModes();
132
assert.strictEqual(modes.custom.length, 1);
133
134
const testMode = modes.custom[0];
135
assert.strictEqual(testMode.id, customMode.uri.toString());
136
assert.strictEqual(testMode.name.get(), customMode.name);
137
assert.strictEqual(testMode.label.get(), customMode.name);
138
assert.strictEqual(testMode.description.get(), customMode.description);
139
assert.strictEqual(testMode.kind, ChatModeKind.Agent);
140
assert.deepStrictEqual(testMode.customTools?.get(), customMode.tools);
141
assert.deepStrictEqual(testMode.modeInstructions?.get(), customMode.agentInstructions);
142
assert.deepStrictEqual(testMode.handOffs?.get(), customMode.handOffs);
143
assert.strictEqual(testMode.uri?.get().toString(), customMode.uri.toString());
144
assert.deepStrictEqual(testMode.source, workspaceSource);
145
});
146
147
test('should fire change event when custom modes are updated', async () => {
148
let eventFired = false;
149
testDisposables.add(chatModeService.onDidChangeChatModes(() => {
150
eventFired = true;
151
}));
152
153
const customMode: ICustomAgent = {
154
uri: URI.parse('file:///test/custom-mode.md'),
155
name: 'Test Mode',
156
description: 'A test custom mode',
157
tools: [],
158
agentInstructions: { content: 'Custom mode body', toolReferences: [] },
159
source: workspaceSource,
160
target: Target.Undefined,
161
visibility: { userInvokable: true, agentInvokable: true }
162
};
163
164
promptsService.setCustomModes([customMode]);
165
166
// Wait for the event to fire
167
await timeout(0);
168
169
assert.ok(eventFired);
170
});
171
172
test('should find custom modes by id', async () => {
173
const customMode: ICustomAgent = {
174
uri: URI.parse('file:///test/findable-mode.md'),
175
name: 'Findable Mode',
176
description: 'A findable custom mode',
177
tools: [],
178
agentInstructions: { content: 'Findable mode body', toolReferences: [] },
179
source: workspaceSource,
180
target: Target.Undefined,
181
visibility: { userInvokable: true, agentInvokable: true }
182
};
183
184
promptsService.setCustomModes([customMode]);
185
186
// Wait for the service to refresh
187
await timeout(0);
188
189
const foundMode = chatModeService.findModeById(customMode.uri.toString());
190
assert.ok(foundMode);
191
assert.strictEqual(foundMode.id, customMode.uri.toString());
192
assert.strictEqual(foundMode.name.get(), customMode.name);
193
assert.strictEqual(foundMode.label.get(), customMode.name);
194
});
195
196
test('should update existing custom mode instances when data changes', async () => {
197
const uri = URI.parse('file:///test/updateable-mode.md');
198
const initialMode: ICustomAgent = {
199
uri,
200
name: 'Initial Mode',
201
description: 'Initial description',
202
tools: ['tool1'],
203
agentInstructions: { content: 'Initial body', toolReferences: [] },
204
model: ['gpt-4'],
205
source: workspaceSource,
206
target: Target.Undefined,
207
visibility: { userInvokable: true, agentInvokable: true }
208
};
209
210
promptsService.setCustomModes([initialMode]);
211
await timeout(0);
212
213
const initialModes = chatModeService.getModes();
214
const initialCustomMode = initialModes.custom[0];
215
assert.strictEqual(initialCustomMode.description.get(), 'Initial description');
216
217
// Update the mode data
218
const updatedMode: ICustomAgent = {
219
...initialMode,
220
description: 'Updated description',
221
tools: ['tool1', 'tool2'],
222
agentInstructions: { content: 'Updated body', toolReferences: [] },
223
model: ['Updated model']
224
};
225
226
promptsService.setCustomModes([updatedMode]);
227
await timeout(0);
228
229
const updatedModes = chatModeService.getModes();
230
const updatedCustomMode = updatedModes.custom[0];
231
232
// The instance should be the same (reused)
233
assert.strictEqual(initialCustomMode, updatedCustomMode);
234
235
// But the observable properties should be updated
236
assert.strictEqual(updatedCustomMode.description.get(), 'Updated description');
237
assert.deepStrictEqual(updatedCustomMode.customTools?.get(), ['tool1', 'tool2']);
238
assert.deepStrictEqual(updatedCustomMode.modeInstructions?.get(), { content: 'Updated body', toolReferences: [] });
239
assert.deepStrictEqual(updatedCustomMode.model?.get(), ['Updated model']);
240
assert.deepStrictEqual(updatedCustomMode.source, workspaceSource);
241
});
242
243
test('should remove custom modes that no longer exist', async () => {
244
const mode1: ICustomAgent = {
245
uri: URI.parse('file:///test/mode1.md'),
246
name: 'Mode 1',
247
description: 'First mode',
248
tools: [],
249
agentInstructions: { content: 'Mode 1 body', toolReferences: [] },
250
source: workspaceSource,
251
target: Target.Undefined,
252
visibility: { userInvokable: true, agentInvokable: true }
253
};
254
255
const mode2: ICustomAgent = {
256
uri: URI.parse('file:///test/mode2.md'),
257
name: 'Mode 2',
258
description: 'Second mode',
259
tools: [],
260
agentInstructions: { content: 'Mode 2 body', toolReferences: [] },
261
source: workspaceSource,
262
target: Target.Undefined,
263
visibility: { userInvokable: true, agentInvokable: true }
264
};
265
266
// Add both modes
267
promptsService.setCustomModes([mode1, mode2]);
268
await timeout(0);
269
270
let modes = chatModeService.getModes();
271
assert.strictEqual(modes.custom.length, 2);
272
273
// Remove one mode
274
promptsService.setCustomModes([mode1]);
275
await timeout(0);
276
277
modes = chatModeService.getModes();
278
assert.strictEqual(modes.custom.length, 1);
279
assert.strictEqual(modes.custom[0].id, mode1.uri.toString());
280
});
281
282
});
283
284