diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index 96ebfeb4350..3412ece0624 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -55,7 +55,6 @@ import { IChatEditingService } from '../../common/chatEditingService.js'; import { IChatRequestVariableEntry, IDiagnosticVariableEntryFilterData, OmittedState } from '../../common/chatModel.js'; import { ChatRequestAgentPart } from '../../common/chatParserTypes.js'; import { ChatAgentLocation } from '../../common/constants.js'; -import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; import { IChatWidget, IChatWidgetService, IQuickChatService, showChatView } from '../chat.js'; import { imageToHash, isImage } from '../chatPasteProviders.js'; import { isQuickChat } from '../chatWidget.js'; @@ -682,7 +681,6 @@ export class AttachContextAction extends Action2 { const chatAgentService = accessor.get(IChatAgentService); const commandService = accessor.get(ICommandService); const widgetService = accessor.get(IChatWidgetService); - const languageModelToolsService = accessor.get(ILanguageModelToolsService); const quickChatService = accessor.get(IQuickChatService); const clipboardService = accessor.get(IClipboardService); const editorService = accessor.get(IEditorService); @@ -749,7 +747,7 @@ export class AttachContextAction extends Action2 { } } - for (const tool of languageModelToolsService.getTools()) { + for (const tool of widget.input.selectedToolsModel.tools.get()) { if (tool.canBeReferencedInPrompt) { const item: IToolQuickPickItem = { kind: 'tool', diff --git a/src/vs/workbench/contrib/chat/browser/chatVariables.ts b/src/vs/workbench/contrib/chat/browser/chatVariables.ts index 2f2fccd7bfe..573f54f0aad 100644 --- a/src/vs/workbench/contrib/chat/browser/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/browser/chatVariables.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IChatVariablesService, IDynamicVariable } from '../common/chatVariables.js'; +import { IToolData } from '../common/languageModelToolsService.js'; import { IChatWidgetService } from './chat.js'; import { ChatDynamicVariableModel } from './contrib/chatDynamicVariables.js'; @@ -32,4 +33,12 @@ export class ChatVariablesService implements IChatVariablesService { return model.variables; } + getSelectedTools(sessionId: string): ReadonlyArray { + const widget = this.chatWidgetService.getWidgetBySessionId(sessionId); + if (!widget) { + return []; + } + return widget.input.selectedToolsModel.tools.get(); + } + } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 8e75aa6c283..0538d12a89c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -985,6 +985,10 @@ export class ChatWidget extends Disposable implements IChatWidget { this.renderWelcomeViewContentIfNeeded(); this.refreshParsedInput(); })); + this._register(autorun(r => { + this.input.selectedToolsModel.tools.read(r); // SIGNAL + this.refreshParsedInput(); + })); } private onDidStyleChange(): void { diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts index 4551ebc74f1..fd59a3a72c3 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts @@ -42,7 +42,6 @@ import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestTextPa import { IChatSlashCommandService } from '../../common/chatSlashCommands.js'; import { IDynamicVariable } from '../../common/chatVariables.js'; import { ChatAgentLocation, ChatMode } from '../../common/constants.js'; -import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; import { ChatSubmitAction } from '../actions/chatExecuteActions.js'; import { IChatWidget, IChatWidgetService } from '../chat.js'; import { ChatInputPart } from '../chatInputPart.js'; @@ -887,7 +886,6 @@ class ToolCompletions extends Disposable { constructor( @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, - @ILanguageModelToolsService toolsService: ILanguageModelToolsService ) { super(); @@ -908,7 +906,7 @@ class ToolCompletions extends Disposable { const usedTools = widget.parsedInput.parts.filter((p): p is ChatRequestToolPart => p instanceof ChatRequestToolPart); const usedToolNames = new Set(usedTools.map(v => v.toolName)); const toolItems: CompletionItem[] = []; - toolItems.push(...Array.from(toolsService.getTools()) + toolItems.push(...widget.input.selectedToolsModel.tools.get() .filter(t => t.canBeReferencedInPrompt) .filter(t => !usedToolNames.has(t.toolReferenceName ?? '')) .map((t): CompletionItem => { diff --git a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts index ecf72050af0..6a1f688d5ba 100644 --- a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts +++ b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts @@ -11,7 +11,7 @@ import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestDynami import { IChatSlashCommandService } from './chatSlashCommands.js'; import { IChatVariablesService, IDynamicVariable } from './chatVariables.js'; import { ChatAgentLocation, ChatMode } from './constants.js'; -import { ILanguageModelToolsService } from './languageModelToolsService.js'; +import { IToolData } from './languageModelToolsService.js'; const agentReg = /^@([\w_\-\.]+)(?=(\s|$|\b))/i; // An @-agent const variableReg = /^#([\w_\-]+)(:\d+)?(?=(\s|$|\b))/i; // A #-variable with an optional numeric : arg (@response:2) @@ -28,12 +28,12 @@ export class ChatRequestParser { @IChatAgentService private readonly agentService: IChatAgentService, @IChatVariablesService private readonly variableService: IChatVariablesService, @IChatSlashCommandService private readonly slashCommandService: IChatSlashCommandService, - @ILanguageModelToolsService private readonly toolsService: ILanguageModelToolsService, ) { } parseChatRequest(sessionId: string, message: string, location: ChatAgentLocation = ChatAgentLocation.Panel, context?: IChatParserContext): IParsedChatRequest { const parts: IParsedChatRequestPart[] = []; const references = this.variableService.getDynamicVariables(sessionId); // must access this list before any async calls + const toolsByName = new Map((this.variableService.getSelectedTools(sessionId)).filter(t => t.toolReferenceName).map(t => [t.toolReferenceName!, t])); let lineNumber = 1; let column = 1; @@ -43,7 +43,7 @@ export class ChatRequestParser { let newPart: IParsedChatRequestPart | undefined; if (previousChar.match(/\s/) || i === 0) { if (char === chatVariableLeader) { - newPart = this.tryToParseVariable(message.slice(i), i, new Position(lineNumber, column), parts); + newPart = this.tryToParseVariable(message.slice(i), i, new Position(lineNumber, column), parts, toolsByName); } else if (char === chatAgentLeader) { newPart = this.tryToParseAgent(message.slice(i), message, i, new Position(lineNumber, column), parts, location, context); } else if (char === chatSubcommandLeader) { @@ -141,7 +141,7 @@ export class ChatRequestParser { return new ChatRequestAgentPart(agentRange, agentEditorRange, agent); } - private tryToParseVariable(message: string, offset: number, position: IPosition, parts: ReadonlyArray): ChatRequestAgentPart | ChatRequestToolPart | undefined { + private tryToParseVariable(message: string, offset: number, position: IPosition, parts: ReadonlyArray, toolsByName: ReadonlyMap): ChatRequestAgentPart | ChatRequestToolPart | undefined { const nextVariableMatch = message.match(variableReg); if (!nextVariableMatch) { return; @@ -151,7 +151,7 @@ export class ChatRequestParser { const varRange = new OffsetRange(offset, offset + full.length); const varEditorRange = new Range(position.lineNumber, position.column, position.lineNumber, position.column + full.length); - const tool = this.toolsService.getToolByName(name); + const tool = toolsByName.get(name); if (tool && tool.canBeReferencedInPrompt) { return new ChatRequestToolPart(varRange, varEditorRange, name, tool.id, tool.displayName, tool.icon); } diff --git a/src/vs/workbench/contrib/chat/common/chatVariables.ts b/src/vs/workbench/contrib/chat/common/chatVariables.ts index b70356da4d1..a5f936c7360 100644 --- a/src/vs/workbench/contrib/chat/common/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/common/chatVariables.ts @@ -11,6 +11,7 @@ import { Location } from '../../../../editor/common/languages.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IChatModel, IDiagnosticVariableEntryFilterData } from './chatModel.js'; import { IChatContentReference, IChatProgressMessage } from './chatService.js'; +import { IToolData } from './languageModelToolsService.js'; export interface IChatVariableData { id: string; @@ -45,6 +46,7 @@ export const IChatVariablesService = createDecorator('ICh export interface IChatVariablesService { _serviceBrand: undefined; getDynamicVariables(sessionId: string): ReadonlyArray; + getSelectedTools(sessionId: string): ReadonlyArray; } export interface IDynamicVariable { diff --git a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts index 21acec106cf..f6ddd14d1d3 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts @@ -19,9 +19,8 @@ import { IChatService } from '../../common/chatService.js'; import { IChatSlashCommandService } from '../../common/chatSlashCommands.js'; import { IChatVariablesService } from '../../common/chatVariables.js'; import { ChatMode, ChatAgentLocation } from '../../common/constants.js'; -import { ILanguageModelToolsService, IToolData } from '../../common/languageModelToolsService.js'; +import { IToolData } from '../../common/languageModelToolsService.js'; import { MockChatService } from './mockChatService.js'; -import { MockChatVariablesService } from './mockChatVariables.js'; suite('ChatRequestParser', () => { const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); @@ -29,7 +28,7 @@ suite('ChatRequestParser', () => { let instantiationService: TestInstantiationService; let parser: ChatRequestParser; - let toolsService: MockObject; + let variableService: MockObject; setup(async () => { instantiationService = testDisposables.add(new TestInstantiationService()); instantiationService.stub(IStorageService, testDisposables.add(new TestStorageService())); @@ -37,11 +36,13 @@ suite('ChatRequestParser', () => { instantiationService.stub(IExtensionService, new TestExtensionService()); instantiationService.stub(IChatService, new MockChatService()); instantiationService.stub(IContextKeyService, new MockContextKeyService()); - instantiationService.stub(IChatVariablesService, new MockChatVariablesService()); instantiationService.stub(IChatAgentService, testDisposables.add(instantiationService.createInstance(ChatAgentService))); - toolsService = mockObject()({}); - instantiationService.stub(ILanguageModelToolsService, toolsService as any); + variableService = mockObject()(); + variableService.getDynamicVariables.returns([]); + variableService.getSelectedTools.returns([]); + + instantiationService.stub(IChatVariablesService, variableService as any); }); test('plain text', async () => { @@ -198,8 +199,10 @@ suite('ChatRequestParser', () => { agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]); instantiationService.stub(IChatAgentService, agentsService as any); - toolsService.getToolByName.onCall(0).returns({ id: 'get_selection', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: { type: 'internal' } } satisfies IToolData); - toolsService.getToolByName.onCall(1).returns({ id: 'get_debugConsole', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: { type: 'internal' } } satisfies IToolData); + variableService.getSelectedTools.returns([ + { id: 'get_selection', toolReferenceName: 'selection', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: { type: 'internal' } }, + { id: 'get_debugConsole', toolReferenceName: 'debugConsole', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: { type: 'internal' } } + ] satisfies IToolData[]); parser = instantiationService.createInstance(ChatRequestParser); const result = parser.parseChatRequest('1', '@agent /subCommand \nPlease do with #selection\nand #debugConsole'); @@ -211,8 +214,10 @@ suite('ChatRequestParser', () => { agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]); instantiationService.stub(IChatAgentService, agentsService as any); - toolsService.getToolByName.onCall(0).returns({ id: 'get_selection', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: { type: 'internal' } } satisfies IToolData); - toolsService.getToolByName.onCall(1).returns({ id: 'get_debugConsole', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: { type: 'internal' } } satisfies IToolData); + variableService.getSelectedTools.returns([ + { id: 'get_selection', toolReferenceName: 'selection', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: { type: 'internal' } }, + { id: 'get_debugConsole', toolReferenceName: 'debugConsole', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: { type: 'internal' } } + ] satisfies IToolData[]); parser = instantiationService.createInstance(ChatRequestParser); const result = parser.parseChatRequest('1', '@agent Please \ndo /subCommand with #selection\nand #debugConsole'); diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts b/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts index fa4b2d8620f..796050f1a23 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IChatVariablesService, IDynamicVariable } from '../../common/chatVariables.js'; +import { IToolData } from '../../common/languageModelToolsService.js'; export class MockChatVariablesService implements IChatVariablesService { _serviceBrand: undefined; @@ -11,4 +12,8 @@ export class MockChatVariablesService implements IChatVariablesService { getDynamicVariables(sessionId: string): readonly IDynamicVariable[] { return []; } + + getSelectedTools(sessionId: string): readonly IToolData[] { + return []; + } }