Path: blob/main/src/vs/workbench/contrib/chat/test/browser/chatSelectedTools.test.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js';7import { ContextKeyService } from '../../../../../platform/contextkey/browser/contextKeyService.js';8import { workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js';9import { LanguageModelToolsService } from '../../browser/languageModelToolsService.js';10import { IChatService } from '../../common/chatService.js';11import { ILanguageModelToolsService, IToolData, ToolDataSource, ToolSet } from '../../common/languageModelToolsService.js';12import { MockChatService } from '../common/mockChatService.js';13import { ChatSelectedTools } from '../../browser/chatSelectedTools.js';14import { constObservable } from '../../../../../base/common/observable.js';15import { Iterable } from '../../../../../base/common/iterator.js';16import { DisposableStore } from '../../../../../base/common/lifecycle.js';17import { runWithFakedTimers } from '../../../../../base/test/common/timeTravelScheduler.js';18import { timeout } from '../../../../../base/common/async.js';19import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';20import { URI } from '../../../../../base/common/uri.js';21import { ChatMode } from '../../common/chatModes.js';2223suite('ChatSelectedTools', () => {2425let store: DisposableStore;2627let toolsService: ILanguageModelToolsService;28let selectedTools: ChatSelectedTools;2930setup(() => {3132store = new DisposableStore();3334const instaService = workbenchInstantiationService({35contextKeyService: () => store.add(new ContextKeyService(new TestConfigurationService)),36}, store);37instaService.stub(IChatService, new MockChatService());38instaService.stub(ILanguageModelToolsService, instaService.createInstance(LanguageModelToolsService));3940store.add(instaService);41toolsService = instaService.get(ILanguageModelToolsService);42selectedTools = store.add(instaService.createInstance(ChatSelectedTools, constObservable(ChatMode.Agent)));43});4445teardown(function () {46store.dispose();47});4849ensureNoDisposablesAreLeakedInTestSuite();5051const mcpSource: ToolDataSource = { type: 'mcp', label: 'MCP', collectionId: '', definitionId: '', instructions: '', serverLabel: '' };52test('Can\'t enable/disable MCP tools directly #18161', () => {5354return runWithFakedTimers({}, async () => {5556const toolData1: IToolData = {57id: 'testTool1',58modelDescription: 'Test Tool 1',59displayName: 'Test Tool 1',60canBeReferencedInPrompt: true,61toolReferenceName: 't1',62source: mcpSource,63};6465const toolData2: IToolData = {66id: 'testTool2',67modelDescription: 'Test Tool 2',68displayName: 'Test Tool 2',69source: mcpSource,70canBeReferencedInPrompt: true,71toolReferenceName: 't2',72};7374const toolData3: IToolData = {75id: 'testTool3',76modelDescription: 'Test Tool 3',77displayName: 'Test Tool 3',78source: mcpSource,79canBeReferencedInPrompt: true,80toolReferenceName: 't3',81};8283const toolset = toolsService.createToolSet(84mcpSource,85'mcp', 'mcp'86);8788store.add(toolsService.registerToolData(toolData1));89store.add(toolsService.registerToolData(toolData2));90store.add(toolsService.registerToolData(toolData3));9192store.add(toolset);93store.add(toolset.addTool(toolData1));94store.add(toolset.addTool(toolData2));95store.add(toolset.addTool(toolData3));9697assert.strictEqual(Iterable.length(toolsService.getTools()), 3);9899const size = Iterable.length(toolset.getTools());100assert.strictEqual(size, 3);101102await 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 event103104assert.strictEqual(selectedTools.entriesMap.get().size, 4); // 1 toolset, 3 tools105106const toSet = new Map<IToolData | ToolSet, boolean>([[toolData1, true], [toolData2, false], [toolData3, false], [toolset, false]]);107selectedTools.set(toSet, false);108109const userSelectedTools = selectedTools.userSelectedTools.get();110assert.strictEqual(Object.keys(userSelectedTools).length, 3); // 3 tools111112assert.strictEqual(userSelectedTools[toolData1.id], true);113assert.strictEqual(userSelectedTools[toolData2.id], false);114assert.strictEqual(userSelectedTools[toolData3.id], false);115});116});117118test('Can still enable/disable user toolsets #251640', () => {119return runWithFakedTimers({}, async () => {120const toolData1: IToolData = {121id: 'testTool1',122modelDescription: 'Test Tool 1',123displayName: 'Test Tool 1',124canBeReferencedInPrompt: true,125toolReferenceName: 't1',126source: ToolDataSource.Internal,127};128129const toolData2: IToolData = {130id: 'testTool2',131modelDescription: 'Test Tool 2',132displayName: 'Test Tool 2',133source: mcpSource,134canBeReferencedInPrompt: true,135toolReferenceName: 't2',136};137138const toolData3: IToolData = {139id: 'testTool3',140modelDescription: 'Test Tool 3',141displayName: 'Test Tool 3',142source: ToolDataSource.Internal,143canBeReferencedInPrompt: true,144toolReferenceName: 't3',145};146147const toolset = toolsService.createToolSet(148{ type: 'user', label: 'User Toolset', file: URI.file('/userToolset.json') },149'userToolset', 'userToolset'150);151152store.add(toolsService.registerToolData(toolData1));153store.add(toolsService.registerToolData(toolData2));154store.add(toolsService.registerToolData(toolData3));155156store.add(toolset);157store.add(toolset.addTool(toolData1));158store.add(toolset.addTool(toolData2));159store.add(toolset.addTool(toolData3));160161assert.strictEqual(Iterable.length(toolsService.getTools()), 3);162163const size = Iterable.length(toolset.getTools());164assert.strictEqual(size, 3);165166await 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 event167168assert.strictEqual(selectedTools.entriesMap.get().size, 4); // 1 toolset, 3 tools169170// Toolset is checked, tools 2 and 3 are unchecked171const toSet = new Map<IToolData | ToolSet, boolean>([[toolData1, true], [toolData2, false], [toolData3, false], [toolset, true]]);172selectedTools.set(toSet, false);173174const userSelectedTools = selectedTools.userSelectedTools.get();175assert.strictEqual(Object.keys(userSelectedTools).length, 3); // 3 tools176177// User toolset is enabled - all tools are enabled178assert.strictEqual(userSelectedTools[toolData1.id], true);179assert.strictEqual(userSelectedTools[toolData2.id], true);180assert.strictEqual(userSelectedTools[toolData3.id], true);181});182});183});184185186