Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/agents/vscode-node/test/askAgentProvider.spec.ts
13405 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 'chai';
7
import * as os from 'os';
8
import * as path from 'path';
9
import { afterEach, beforeEach, suite, test } from 'vitest';
10
import * as vscode from 'vscode';
11
import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
12
import { InMemoryConfigurationService } from '../../../../platform/configuration/test/common/inMemoryConfigurationService';
13
import { IVSCodeExtensionContext } from '../../../../platform/extContext/common/extensionContext';
14
import { IFileSystemService } from '../../../../platform/filesystem/common/fileSystemService';
15
import { MockExtensionContext } from '../../../../platform/test/node/extensionContext';
16
import { ITestingServicesAccessor } from '../../../../platform/test/node/services';
17
import { DisposableStore } from '../../../../util/vs/base/common/lifecycle';
18
import { SyncDescriptor } from '../../../../util/vs/platform/instantiation/common/descriptors';
19
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
20
import { createExtensionUnitTestingServices } from '../../../test/node/services';
21
import { AskAgentProvider } from '../askAgentProvider';
22
23
suite('AskAgentProvider', () => {
24
let disposables: DisposableStore;
25
let mockConfigurationService: InMemoryConfigurationService;
26
let fileSystemService: IFileSystemService;
27
let accessor: ITestingServicesAccessor;
28
let instantiationService: IInstantiationService;
29
30
beforeEach(() => {
31
disposables = new DisposableStore();
32
33
const testingServiceCollection = createExtensionUnitTestingServices(disposables);
34
const globalStoragePath = path.join(os.tmpdir(), 'ask-agent-test-' + Date.now());
35
testingServiceCollection.define(IVSCodeExtensionContext, new SyncDescriptor(MockExtensionContext, [globalStoragePath]));
36
accessor = testingServiceCollection.createTestingAccessor();
37
disposables.add(accessor);
38
instantiationService = accessor.get(IInstantiationService);
39
40
mockConfigurationService = accessor.get(IConfigurationService) as InMemoryConfigurationService;
41
fileSystemService = accessor.get(IFileSystemService);
42
});
43
44
afterEach(() => {
45
disposables.dispose();
46
});
47
48
function createProvider() {
49
const provider = instantiationService.createInstance(AskAgentProvider);
50
disposables.add(provider);
51
return provider;
52
}
53
54
async function getAgentContent(agent: vscode.ChatResource): Promise<string> {
55
const content = await fileSystemService.readFile(agent.uri);
56
return new TextDecoder().decode(content);
57
}
58
59
test('provideCustomAgents() returns an Ask agent with correct structure', async () => {
60
const provider = createProvider();
61
62
const agents = await provider.provideCustomAgents({}, {} as any);
63
64
assert.equal(agents.length, 1);
65
assert.ok(agents[0].uri, 'Agent should have a URI');
66
assert.ok(agents[0].uri.path.endsWith('.agent.md'), 'Agent URI should end with .agent.md');
67
});
68
69
test('returns agent content with base frontmatter when no settings configured', async () => {
70
const provider = createProvider();
71
72
const agents = await provider.provideCustomAgents({}, {} as any);
73
74
assert.equal(agents.length, 1);
75
const content = await getAgentContent(agents[0]);
76
77
// Should contain base read-only tools
78
assert.ok(content.includes('search'));
79
assert.ok(content.includes('read'));
80
assert.ok(content.includes('web'));
81
assert.ok(content.includes('github/issue_read'));
82
83
// Should NOT contain editing tools
84
85
assert.ok(!content.includes('\'edit'), 'Should not have edit or edit/... tools');
86
assert.ok(!content.includes('\'execute/run'), 'Should not have any execute/run... tool');
87
88
// Should have correct metadata
89
assert.ok(content.includes('name: Ask'));
90
assert.ok(content.includes('description: Answers questions without making changes'));
91
});
92
93
test('merges additionalTools setting with base tools', async () => {
94
await mockConfigurationService.setConfig(ConfigKey.AskAgentAdditionalTools, ['customTool1', 'customTool2']);
95
96
const provider = createProvider();
97
const agents = await provider.provideCustomAgents({}, {} as any);
98
99
assert.equal(agents.length, 1);
100
const content = await getAgentContent(agents[0]);
101
102
// Should contain base tools
103
assert.ok(content.includes('search'));
104
assert.ok(content.includes('read'));
105
106
// Should contain additional tools
107
assert.ok(content.includes('customTool1'));
108
assert.ok(content.includes('customTool2'));
109
});
110
111
test('deduplicates tools when additionalTools overlaps with base tools', async () => {
112
await mockConfigurationService.setConfig(ConfigKey.AskAgentAdditionalTools, ['search', 'newTool']);
113
114
const provider = createProvider();
115
const agents = await provider.provideCustomAgents({}, {} as any);
116
117
assert.equal(agents.length, 1);
118
const content = await getAgentContent(agents[0]);
119
120
// Count occurrences of 'search' in tools list
121
const toolsMatch = content.match(/tools: \[([^\]]+)\]/);
122
assert.ok(toolsMatch, 'Tools list not found in agent content');
123
const toolsSection = toolsMatch[1];
124
const searchCount = (toolsSection.match(/'search'/g) || []).length;
125
assert.equal(searchCount, 1, 'search tool should appear only once after deduplication');
126
127
// Should contain new tool
128
assert.ok(content.includes('newTool'));
129
});
130
131
test('applies model override from settings', async () => {
132
await mockConfigurationService.setConfig(ConfigKey.AskAgentModel, 'Claude Haiku 4.5 (copilot)');
133
134
const provider = createProvider();
135
const agents = await provider.provideCustomAgents({}, {} as any);
136
137
assert.equal(agents.length, 1);
138
const content = await getAgentContent(agents[0]);
139
140
assert.ok(content.includes('model: Claude Haiku 4.5 (copilot)'));
141
});
142
143
test('applies both additionalTools and model settings together', async () => {
144
await mockConfigurationService.setConfig(ConfigKey.AskAgentAdditionalTools, ['extraTool']);
145
await mockConfigurationService.setConfig(ConfigKey.AskAgentModel, 'claude-3-sonnet');
146
147
const provider = createProvider();
148
const agents = await provider.provideCustomAgents({}, {} as any);
149
150
assert.equal(agents.length, 1);
151
const content = await getAgentContent(agents[0]);
152
153
assert.ok(content.includes('extraTool'));
154
assert.ok(content.includes('model: claude-3-sonnet'));
155
});
156
157
test('fires onDidChangeCustomAgents when additionalTools setting changes', async () => {
158
const provider = createProvider();
159
160
let eventFired = false;
161
provider.onDidChangeCustomAgents(() => {
162
eventFired = true;
163
});
164
165
await mockConfigurationService.setConfig(ConfigKey.AskAgentAdditionalTools, ['newTool']);
166
167
assert.equal(eventFired, true);
168
});
169
170
test('fires onDidChangeCustomAgents when model setting changes', async () => {
171
const provider = createProvider();
172
173
let eventFired = false;
174
provider.onDidChangeCustomAgents(() => {
175
eventFired = true;
176
});
177
178
await mockConfigurationService.setConfig(ConfigKey.AskAgentModel, 'new-model');
179
180
assert.equal(eventFired, true);
181
});
182
183
test('does not fire onDidChangeCustomAgents for unrelated setting changes', async () => {
184
const provider = createProvider();
185
186
let eventFired = false;
187
provider.onDidChangeCustomAgents(() => {
188
eventFired = true;
189
});
190
191
await mockConfigurationService.setConfig(ConfigKey.Advanced.FeedbackOnChange, true);
192
193
assert.equal(eventFired, false);
194
});
195
196
test('always includes askQuestions tool in generated content', async () => {
197
const provider = createProvider();
198
const agents = await provider.provideCustomAgents({}, {} as any);
199
200
assert.equal(agents.length, 1);
201
const content = await getAgentContent(agents[0]);
202
203
assert.ok(content.includes('vscode/askQuestions'));
204
});
205
206
test('has correct label property', () => {
207
const provider = createProvider();
208
assert.ok(provider.label.includes('Ask'));
209
});
210
211
test('preserves body content after frontmatter when applying settings', async () => {
212
await mockConfigurationService.setConfig(ConfigKey.AskAgentModel, 'test-model');
213
214
const provider = createProvider();
215
const agents = await provider.provideCustomAgents({}, {} as any);
216
217
const content = await getAgentContent(agents[0]);
218
219
assert.ok(content.includes('You are an ASK AGENT'));
220
assert.ok(content.includes('NEVER modify files or run commands that change state'));
221
});
222
223
test('handles empty additionalTools array gracefully', async () => {
224
await mockConfigurationService.setConfig(ConfigKey.AskAgentAdditionalTools, []);
225
226
const provider = createProvider();
227
const agents = await provider.provideCustomAgents({}, {} as any);
228
229
assert.equal(agents.length, 1);
230
const content = await getAgentContent(agents[0]);
231
232
// Should have base tools only
233
assert.ok(content.includes('search'));
234
assert.ok(content.includes('read'));
235
});
236
237
test('handles empty model string gracefully', async () => {
238
await mockConfigurationService.setConfig(ConfigKey.AskAgentModel, '');
239
240
const provider = createProvider();
241
const agents = await provider.provideCustomAgents({}, {} as any);
242
243
assert.equal(agents.length, 1);
244
const content = await getAgentContent(agents[0]);
245
246
assert.ok(!content.includes('model:'));
247
});
248
249
test('does not include handoffs section', async () => {
250
const provider = createProvider();
251
const agents = await provider.provideCustomAgents({}, {} as any);
252
253
const content = await getAgentContent(agents[0]);
254
255
assert.ok(!content.includes('handoffs:'), 'Ask agent should not have handoffs');
256
});
257
258
test('body content instructs not to edit files', async () => {
259
const provider = createProvider();
260
const agents = await provider.provideCustomAgents({}, {} as any);
261
262
const content = await getAgentContent(agents[0]);
263
264
assert.ok(content.includes('NEVER modify files'));
265
assert.ok(content.includes('NEVER use file editing tools'));
266
});
267
});
268
269