Path: blob/main/src/vs/workbench/contrib/chat/test/common/chatRequestParser.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 { MockObject, mockObject } from '../../../../../base/test/common/mock.js';6import { assertSnapshot } from '../../../../../base/test/common/snapshot.js';7import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';8import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';9import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';10import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js';11import { ILogService, NullLogService } from '../../../../../platform/log/common/log.js';12import { IStorageService } from '../../../../../platform/storage/common/storage.js';13import { IExtensionService, nullExtensionDescription } from '../../../../services/extensions/common/extensions.js';14import { TestExtensionService, TestStorageService } from '../../../../test/common/workbenchTestServices.js';15import { ChatAgentService, IChatAgentCommand, IChatAgentData, IChatAgentService } from '../../common/chatAgents.js';16import { ChatRequestParser } from '../../common/chatRequestParser.js';17import { IChatService } from '../../common/chatService.js';18import { IChatSlashCommandService } from '../../common/chatSlashCommands.js';19import { IChatVariablesService } from '../../common/chatVariables.js';20import { ChatModeKind, ChatAgentLocation } from '../../common/constants.js';21import { IToolData, ToolDataSource, ToolSet } from '../../common/languageModelToolsService.js';22import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js';23import { MockChatService } from './mockChatService.js';24import { MockPromptsService } from './mockPromptsService.js';2526suite('ChatRequestParser', () => {27const testDisposables = ensureNoDisposablesAreLeakedInTestSuite();2829let instantiationService: TestInstantiationService;30let parser: ChatRequestParser;3132let variableService: MockObject<IChatVariablesService>;33setup(async () => {34instantiationService = testDisposables.add(new TestInstantiationService());35instantiationService.stub(IStorageService, testDisposables.add(new TestStorageService()));36instantiationService.stub(ILogService, new NullLogService());37instantiationService.stub(IExtensionService, new TestExtensionService());38instantiationService.stub(IChatService, new MockChatService());39instantiationService.stub(IContextKeyService, new MockContextKeyService());40instantiationService.stub(IChatAgentService, testDisposables.add(instantiationService.createInstance(ChatAgentService)));41instantiationService.stub(IPromptsService, testDisposables.add(new MockPromptsService()));4243variableService = mockObject<IChatVariablesService>()();44variableService.getDynamicVariables.returns([]);45variableService.getSelectedToolAndToolSets.returns([]);4647instantiationService.stub(IChatVariablesService, variableService as any);48});4950test('plain text', async () => {51parser = instantiationService.createInstance(ChatRequestParser);52const result = parser.parseChatRequest('1', 'test');53await assertSnapshot(result);54});5556test('plain text with newlines', async () => {57parser = instantiationService.createInstance(ChatRequestParser);58const text = 'line 1\nline 2\r\nline 3';59const result = parser.parseChatRequest('1', text);60await assertSnapshot(result);61});6263test('slash in text', async () => {64parser = instantiationService.createInstance(ChatRequestParser);65const text = 'can we add a new file for an Express router to handle the / route';66const result = parser.parseChatRequest('1', text);67await assertSnapshot(result);68});6970test('slash command', async () => {71const slashCommandService = mockObject<IChatSlashCommandService>()({});72slashCommandService.getCommands.returns([{ command: 'fix' }]);73instantiationService.stub(IChatSlashCommandService, slashCommandService as any);7475parser = instantiationService.createInstance(ChatRequestParser);76const text = '/fix this';77const result = parser.parseChatRequest('1', text);78await assertSnapshot(result);79});8081test('invalid slash command', async () => {82const slashCommandService = mockObject<IChatSlashCommandService>()({});83slashCommandService.getCommands.returns([{ command: 'fix' }]);84instantiationService.stub(IChatSlashCommandService, slashCommandService as any);8586parser = instantiationService.createInstance(ChatRequestParser);87const text = '/explain this';88const result = parser.parseChatRequest('1', text);89await assertSnapshot(result);90});9192test('multiple slash commands', async () => {93const slashCommandService = mockObject<IChatSlashCommandService>()({});94slashCommandService.getCommands.returns([{ command: 'fix' }]);95instantiationService.stub(IChatSlashCommandService, slashCommandService as any);9697parser = instantiationService.createInstance(ChatRequestParser);98const text = '/fix /fix';99const result = parser.parseChatRequest('1', text);100await assertSnapshot(result);101});102103test('slash command not first', async () => {104const slashCommandService = mockObject<IChatSlashCommandService>()({});105slashCommandService.getCommands.returns([{ command: 'fix' }]);106instantiationService.stub(IChatSlashCommandService, slashCommandService as any);107108parser = instantiationService.createInstance(ChatRequestParser);109const text = 'Hello /fix';110const result = parser.parseChatRequest('1', text);111await assertSnapshot(result);112});113114test('slash command after whitespace', async () => {115const slashCommandService = mockObject<IChatSlashCommandService>()({});116slashCommandService.getCommands.returns([{ command: 'fix' }]);117instantiationService.stub(IChatSlashCommandService, slashCommandService as any);118119parser = instantiationService.createInstance(ChatRequestParser);120const text = ' /fix';121const result = parser.parseChatRequest('1', text);122await assertSnapshot(result);123});124125test('prompt slash command', async () => {126const slashCommandService = mockObject<IChatSlashCommandService>()({});127slashCommandService.getCommands.returns([{ command: 'fix' }]);128instantiationService.stub(IChatSlashCommandService, slashCommandService as any);129130const promptSlashCommandService = mockObject<IPromptsService>()({});131promptSlashCommandService.asPromptSlashCommand.callsFake((command: string) => {132if (command.match(/^[\w_\-\.]+$/)) {133return { command };134}135return undefined;136});137instantiationService.stub(IPromptsService, promptSlashCommandService as any);138139parser = instantiationService.createInstance(ChatRequestParser);140const text = ' /prompt';141const result = parser.parseChatRequest('1', text);142await assertSnapshot(result);143});144145test('prompt slash command after text', async () => {146const slashCommandService = mockObject<IChatSlashCommandService>()({});147slashCommandService.getCommands.returns([{ command: 'fix' }]);148instantiationService.stub(IChatSlashCommandService, slashCommandService as any);149150const promptSlashCommandService = mockObject<IPromptsService>()({});151promptSlashCommandService.asPromptSlashCommand.callsFake((command: string) => {152if (command.match(/^[\w_\-\.]+$/)) {153return { command };154}155return undefined;156});157instantiationService.stub(IPromptsService, promptSlashCommandService as any);158159parser = instantiationService.createInstance(ChatRequestParser);160const text = 'handle the / route and the request of /search-option';161const result = parser.parseChatRequest('1', text);162await assertSnapshot(result);163});164165test('prompt slash command after slash', async () => {166const slashCommandService = mockObject<IChatSlashCommandService>()({});167slashCommandService.getCommands.returns([{ command: 'fix' }]);168instantiationService.stub(IChatSlashCommandService, slashCommandService as any);169170const promptSlashCommandService = mockObject<IPromptsService>()({});171promptSlashCommandService.asPromptSlashCommand.callsFake((command: string) => {172if (command.match(/^[\w_\-\.]+$/)) {173return { command };174}175return undefined;176});177instantiationService.stub(IPromptsService, promptSlashCommandService as any);178179parser = instantiationService.createInstance(ChatRequestParser);180const text = '/ route and the request of /search-option';181const result = parser.parseChatRequest('1', text);182await assertSnapshot(result);183});184185test('prompt slash command with numbers', async () => {186const slashCommandService = mockObject<IChatSlashCommandService>()({});187slashCommandService.getCommands.returns([{ command: 'fix' }]);188instantiationService.stub(IChatSlashCommandService, slashCommandService as any);189190const promptSlashCommandService = mockObject<IPromptsService>()({});191promptSlashCommandService.asPromptSlashCommand.callsFake((command: string) => {192if (command.match(/^[\w_\-\.]+$/)) {193return { command };194}195return undefined;196});197instantiationService.stub(IPromptsService, promptSlashCommandService as any);198199parser = instantiationService.createInstance(ChatRequestParser);200const text = '/001-sample this is a test';201const result = parser.parseChatRequest('1', text);202await assertSnapshot(result);203});204205// test('variables', async () => {206// varService.hasVariable.returns(true);207// varService.getVariable.returns({ id: 'copilot.selection' });208209// parser = instantiationService.createInstance(ChatRequestParser);210// const text = 'What does #selection mean?';211// const result = parser.parseChatRequest('1', text);212// await assertSnapshot(result);213// });214215// test('variable with question mark', async () => {216// varService.hasVariable.returns(true);217// varService.getVariable.returns({ id: 'copilot.selection' });218219// parser = instantiationService.createInstance(ChatRequestParser);220// const text = 'What is #selection?';221// const result = parser.parseChatRequest('1', text);222// await assertSnapshot(result);223// });224225// test('invalid variables', async () => {226// varService.hasVariable.returns(false);227228// parser = instantiationService.createInstance(ChatRequestParser);229// const text = 'What does #selection mean?';230// const result = parser.parseChatRequest('1', text);231// await assertSnapshot(result);232// });233234const getAgentWithSlashCommands = (slashCommands: IChatAgentCommand[]) => {235return { id: 'agent', name: 'agent', extensionId: nullExtensionDescription.identifier, extensionVersion: undefined, publisherDisplayName: '', extensionDisplayName: '', extensionPublisherId: '', locations: [ChatAgentLocation.Panel], modes: [ChatModeKind.Ask], metadata: {}, slashCommands, disambiguation: [] } satisfies IChatAgentData;236};237238test('agent with subcommand after text', async () => {239const agentsService = mockObject<IChatAgentService>()({});240agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);241instantiationService.stub(IChatAgentService, agentsService as any);242243parser = instantiationService.createInstance(ChatRequestParser);244const result = parser.parseChatRequest('1', '@agent Please do /subCommand thanks');245await assertSnapshot(result);246});247248test('agents, subCommand', async () => {249const agentsService = mockObject<IChatAgentService>()({});250agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);251instantiationService.stub(IChatAgentService, agentsService as any);252253parser = instantiationService.createInstance(ChatRequestParser);254const result = parser.parseChatRequest('1', '@agent /subCommand Please do thanks');255await assertSnapshot(result);256});257258test('agent but edit mode', async () => {259const agentsService = mockObject<IChatAgentService>()({});260agentsService.getAgentsByName.returns([getAgentWithSlashCommands([])]);261instantiationService.stub(IChatAgentService, agentsService as any);262263parser = instantiationService.createInstance(ChatRequestParser);264const result = parser.parseChatRequest('1', '@agent hello', undefined, { mode: ChatModeKind.Edit });265await assertSnapshot(result);266});267268test('agent with question mark', async () => {269const agentsService = mockObject<IChatAgentService>()({});270agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);271instantiationService.stub(IChatAgentService, agentsService as any);272273parser = instantiationService.createInstance(ChatRequestParser);274const result = parser.parseChatRequest('1', '@agent? Are you there');275await assertSnapshot(result);276});277278test('agent and subcommand with leading whitespace', async () => {279const agentsService = mockObject<IChatAgentService>()({});280agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);281instantiationService.stub(IChatAgentService, agentsService as any);282283parser = instantiationService.createInstance(ChatRequestParser);284const result = parser.parseChatRequest('1', ' \r\n\t @agent \r\n\t /subCommand Thanks');285await assertSnapshot(result);286});287288test('agent and subcommand after newline', async () => {289const agentsService = mockObject<IChatAgentService>()({});290agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);291instantiationService.stub(IChatAgentService, agentsService as any);292293parser = instantiationService.createInstance(ChatRequestParser);294const result = parser.parseChatRequest('1', ' \n@agent\n/subCommand Thanks');295await assertSnapshot(result);296});297298test('agent not first', async () => {299const agentsService = mockObject<IChatAgentService>()({});300agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);301instantiationService.stub(IChatAgentService, agentsService as any);302303parser = instantiationService.createInstance(ChatRequestParser);304const result = parser.parseChatRequest('1', 'Hello Mr. @agent');305await assertSnapshot(result);306});307308test('agents and tools and multiline', async () => {309const agentsService = mockObject<IChatAgentService>()({});310agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);311instantiationService.stub(IChatAgentService, agentsService as any);312313variableService.getSelectedToolAndToolSets.returns(new Map([314[{ id: 'get_selection', toolReferenceName: 'selection', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: ToolDataSource.Internal }, true],315[{ id: 'get_debugConsole', toolReferenceName: 'debugConsole', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: ToolDataSource.Internal }, true]316] satisfies [IToolData | ToolSet, boolean][]));317318parser = instantiationService.createInstance(ChatRequestParser);319const result = parser.parseChatRequest('1', '@agent /subCommand \nPlease do with #selection\nand #debugConsole');320await assertSnapshot(result);321});322323test('agents and tools and multiline, part2', async () => {324const agentsService = mockObject<IChatAgentService>()({});325agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);326instantiationService.stub(IChatAgentService, agentsService as any);327328variableService.getSelectedToolAndToolSets.returns(new Map([329[{ id: 'get_selection', toolReferenceName: 'selection', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: ToolDataSource.Internal }, true],330[{ id: 'get_debugConsole', toolReferenceName: 'debugConsole', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: ToolDataSource.Internal }, true]331] satisfies [IToolData | ToolSet, boolean][]));332333parser = instantiationService.createInstance(ChatRequestParser);334const result = parser.parseChatRequest('1', '@agent Please \ndo /subCommand with #selection\nand #debugConsole');335await assertSnapshot(result);336});337});338339340