Skip to content

Commit 6afc692

Browse files
authored
Fix enabling user toolset containing disabled tool (microsoft#252059)
Fix microsoft#251640
1 parent 3444f74 commit 6afc692

File tree

2 files changed

+77
-5
lines changed

2 files changed

+77
-5
lines changed

src/vs/workbench/contrib/chat/browser/chatSelectedTools.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,12 @@ export class ChatSelectedTools extends Disposable {
164164
for (const [item, enabled] of map) {
165165
if (item instanceof ToolSet) {
166166
for (const tool of item.getTools()) {
167-
_set(tool, map.get(tool) ?? enabled); // tools from tool set can be explicitly set
167+
// Tools from an mcp tool set are explicitly enabled/disabled under the tool set.
168+
// Other toolsets don't show individual tools under the tool set and enablement just follows the toolset.
169+
const toolEnabled = item.source.type === 'mcp' ?
170+
map.get(tool) ?? enabled :
171+
enabled;
172+
_set(tool, toolEnabled);
168173
}
169174
} else {
170175
if (item.canBeReferencedInPrompt) {

src/vs/workbench/contrib/chat/test/browser/chatSelectedTools.test.ts

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { DisposableStore } from '../../../../../base/common/lifecycle.js';
1919
import { runWithFakedTimers } from '../../../../../base/test/common/timeTravelScheduler.js';
2020
import { timeout } from '../../../../../base/common/async.js';
2121
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
22+
import { URI } from '../../../../../base/common/uri.js';
2223

2324
suite('ChatSelectedTools', () => {
2425

@@ -48,6 +49,7 @@ suite('ChatSelectedTools', () => {
4849

4950
ensureNoDisposablesAreLeakedInTestSuite();
5051

52+
const mcpSource: ToolDataSource = { type: 'mcp', label: 'MCP', collectionId: '', definitionId: '' };
5153
test('Can\'t enable/disable MCP tools directly #18161', () => {
5254

5355
return runWithFakedTimers({}, async () => {
@@ -58,14 +60,14 @@ suite('ChatSelectedTools', () => {
5860
displayName: 'Test Tool 1',
5961
canBeReferencedInPrompt: true,
6062
toolReferenceName: 't1',
61-
source: ToolDataSource.Internal,
63+
source: mcpSource,
6264
};
6365

6466
const toolData2: IToolData = {
6567
id: 'testTool2',
6668
modelDescription: 'Test Tool 2',
6769
displayName: 'Test Tool 2',
68-
source: ToolDataSource.Internal,
70+
source: mcpSource,
6971
canBeReferencedInPrompt: true,
7072
toolReferenceName: 't2',
7173
};
@@ -74,13 +76,13 @@ suite('ChatSelectedTools', () => {
7476
id: 'testTool3',
7577
modelDescription: 'Test Tool 3',
7678
displayName: 'Test Tool 3',
77-
source: ToolDataSource.Internal,
79+
source: mcpSource,
7880
canBeReferencedInPrompt: true,
7981
toolReferenceName: 't3',
8082
};
8183

8284
const toolset = toolsService.createToolSet(
83-
ToolDataSource.Internal,
85+
mcpSource,
8486
'mcp', 'mcp'
8587
);
8688

@@ -112,4 +114,69 @@ suite('ChatSelectedTools', () => {
112114
assert.strictEqual(map.get(toolData3), false);
113115
});
114116
});
117+
118+
test('Can still enable/disable user toolsets #251640', () => {
119+
return runWithFakedTimers({}, async () => {
120+
const toolData1: IToolData = {
121+
id: 'testTool1',
122+
modelDescription: 'Test Tool 1',
123+
displayName: 'Test Tool 1',
124+
canBeReferencedInPrompt: true,
125+
toolReferenceName: 't1',
126+
source: ToolDataSource.Internal,
127+
};
128+
129+
const toolData2: IToolData = {
130+
id: 'testTool2',
131+
modelDescription: 'Test Tool 2',
132+
displayName: 'Test Tool 2',
133+
source: mcpSource,
134+
canBeReferencedInPrompt: true,
135+
toolReferenceName: 't2',
136+
};
137+
138+
const toolData3: IToolData = {
139+
id: 'testTool3',
140+
modelDescription: 'Test Tool 3',
141+
displayName: 'Test Tool 3',
142+
source: ToolDataSource.Internal,
143+
canBeReferencedInPrompt: true,
144+
toolReferenceName: 't3',
145+
};
146+
147+
const toolset = toolsService.createToolSet(
148+
{ type: 'user', label: 'User Toolset', file: URI.file('/userToolset.json') },
149+
'userToolset', 'userToolset'
150+
);
151+
152+
store.add(toolsService.registerToolData(toolData1));
153+
store.add(toolsService.registerToolData(toolData2));
154+
store.add(toolsService.registerToolData(toolData3));
155+
156+
store.add(toolset);
157+
store.add(toolset.addTool(toolData1));
158+
store.add(toolset.addTool(toolData2));
159+
store.add(toolset.addTool(toolData3));
160+
161+
assert.strictEqual(Iterable.length(toolsService.getTools()), 3);
162+
163+
const size = Iterable.length(toolset.getTools());
164+
assert.strictEqual(size, 3);
165+
166+
await timeout(1000); // UGLY the tools service updates its state sync but emits the event async (750ms) delay. This affects the observable that depends on the event
167+
168+
assert.strictEqual(selectedTools.entriesMap.size, 4); // 1 toolset, 3 tools
169+
170+
// Toolset is checked, tools 2 and 3 are unchecked
171+
selectedTools.disable([], [toolData2, toolData3], false);
172+
173+
const map = selectedTools.asEnablementMap();
174+
assert.strictEqual(map.size, 3); // 3 tools
175+
176+
// User toolset is enabled - all tools are enabled
177+
assert.strictEqual(map.get(toolData1), true);
178+
assert.strictEqual(map.get(toolData2), true);
179+
assert.strictEqual(map.get(toolData3), true);
180+
});
181+
});
115182
});

0 commit comments

Comments
 (0)