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