diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index 93a683a2..c424d513 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -11,12 +11,12 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo import { URI } from '../../../../base/common/uri.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { ILLMMessageService } from '../common/sendLLMMessageService.js'; -import { chat_userMessageContent } from '../common/prompt/prompts.js'; +import { chat_userMessageContent, isABuiltinToolName } from '../common/prompt/prompts.js'; import { AnthropicReasoning, getErrorMessage, RawToolCallObj, RawToolParamsObj } from '../common/sendLLMMessageTypes.js'; import { generateUuid } from '../../../../base/common/uuid.js'; import { FeatureName, ModelSelection, ModelSelectionOptions } from '../common/voidSettingsTypes.js'; import { IVoidSettingsService } from '../common/voidSettingsService.js'; -import { approvalTypeOfBuiltinToolName, BuiltinToolCallParams, isABuiltinToolName, ToolCallParams, ToolName, ToolResult } from '../common/toolsServiceTypes.js'; +import { approvalTypeOfBuiltinToolName, BuiltinToolCallParams, ToolCallParams, ToolName, ToolResult } from '../common/toolsServiceTypes.js'; import { IToolsService } from './toolsService.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js'; @@ -676,7 +676,7 @@ class ChatThreadService extends Disposable implements IChatThreadService { } else { const mcpTools = this._mcpService.getMCPTools() - const mcpTool = mcpTools[toolName] + const mcpTool = mcpTools?.find(t => t.name === toolName) if (!mcpTool) { throw new Error(`MCP tool ${toolName} not found`) } resolveInterruptor(() => { }) diff --git a/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts b/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts index f27828c2..d8f398ac 100644 --- a/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts +++ b/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts @@ -17,6 +17,7 @@ import { IVoidModelService } from '../common/voidModelService.js'; import { URI } from '../../../../base/common/uri.js'; import { EndOfLinePreference } from '../../../../editor/common/model.js'; import { ToolName } from '../common/toolsServiceTypes.js'; +import { IMCPService } from '../common/mcpService.js'; export const EMPTY_MESSAGE = '(empty message)' @@ -539,6 +540,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess @ITerminalToolService private readonly terminalToolService: ITerminalToolService, @IVoidSettingsService private readonly voidSettingsService: IVoidSettingsService, @IVoidModelService private readonly voidModelService: IVoidModelService, + @IMCPService private readonly mcpService: IMCPService, ) { super() } @@ -588,8 +590,10 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess const includeXMLToolDefinitions = !specialToolFormat + const mcpTools = this.mcpService.getMCPTools() + const persistentTerminalIDs = this.terminalToolService.listPersistentTerminalIds() - const systemMessage = chat_systemMessage({ workspaceFolders, openedURIs, directoryStr, activeURI, persistentTerminalIDs, chatMode, includeXMLToolDefinitions }) + const systemMessage = chat_systemMessage({ workspaceFolders, openedURIs, directoryStr, activeURI, persistentTerminalIDs, chatMode, mcpTools, includeXMLToolDefinitions }) return systemMessage } diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 16c9c2ee..3b935267 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -24,11 +24,11 @@ import { WarningBox } from '../void-settings-tsx/WarningBox.js'; import { getModelCapabilities, getIsReasoningEnabledState } from '../../../../common/modelCapabilities.js'; import { AlertTriangle, File, Ban, Check, ChevronRight, Dot, FileIcon, Pencil, Undo, Undo2, X, Flag, Copy as CopyIcon, Info, CirclePlus, Ellipsis, CircleEllipsis, Folder, ALargeSmall, TypeOutline, Text } from 'lucide-react'; import { ChatMessage, CheckpointEntry, StagingSelectionItem, ToolMessage } from '../../../../common/chatThreadServiceTypes.js'; -import { approvalTypeOfBuiltinToolName, BuiltinToolCallParams, BuiltinToolName, builtinToolNames, isABuiltinToolName, ToolName, LintErrorItem, ToolApprovalType, toolApprovalTypes } from '../../../../common/toolsServiceTypes.js'; +import { approvalTypeOfBuiltinToolName, BuiltinToolCallParams, BuiltinToolName, ToolName, LintErrorItem, ToolApprovalType, toolApprovalTypes } from '../../../../common/toolsServiceTypes.js'; import { CopyButton, EditToolAcceptRejectButtonsHTML, IconShell1, JumpToFileButton, JumpToTerminalButton, StatusIndicator, StatusIndicatorForApplyButton, useApplyStreamState, useEditToolStreamState } from '../markdown/ApplyBlockHoverButtons.js'; import { IsRunningType } from '../../../chatThreadService.js'; import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg, rejectBg, rejectBorder } from '../../../../common/helpers/colors.js'; -import { MAX_FILE_CHARS_PAGE, MAX_TERMINAL_INACTIVE_TIME } from '../../../../common/prompt/prompts.js'; +import { builtinToolNames, isABuiltinToolName, MAX_FILE_CHARS_PAGE, MAX_TERMINAL_INACTIVE_TIME } from '../../../../common/prompt/prompts.js'; import { RawToolCallObj } from '../../../../common/sendLLMMessageTypes.js'; import ErrorBoundary from './ErrorBoundary.js'; import { ToolApprovalTypeSwitch } from '../void-settings-tsx/Settings.js'; diff --git a/src/vs/workbench/contrib/void/common/mcpService.ts b/src/vs/workbench/contrib/void/common/mcpService.ts index ad2215dc..8d03544b 100644 --- a/src/vs/workbench/contrib/void/common/mcpService.ts +++ b/src/vs/workbench/contrib/void/common/mcpService.ts @@ -34,13 +34,8 @@ export interface IMCPService { readonly state: MCPServiceState; // NOT persisted onDidChangeState: Event; - getMCPTools(): Record; - + getMCPTools(): InternalToolInfo[] | undefined; callMCPTool(toolData: MCPToolCallParams): Promise<{ result: RawMCPToolCall }>; - - - // this is outdated: - // getMCPToolFns(): MCPCallToolOfToolName; } export const IMCPService = createDecorator('mcpConfigService'); @@ -177,21 +172,20 @@ class MCPService extends Disposable implements IMCPService { } } - public getMCPTools(): Record { - const allTools: Record = {}; + public getMCPTools(): InternalToolInfo[] | undefined { + const allTools: InternalToolInfo[] = [] for (const serverName in this.state.mcpServerOfName) { const server = this.state.mcpServerOfName[serverName]; - if (server.tools) { - server.tools.forEach(tool => { - allTools[tool.name] = { - description: tool.description || '', - params: this._transformInputSchemaToParams(tool.inputSchema), - name: tool.name, - mcpServerName: serverName, - }; - }); - } + server.tools?.forEach(tool => { + allTools.push({ + description: tool.description || '', + params: this._transformInputSchemaToParams(tool.inputSchema), + name: tool.name, + mcpServerName: serverName, + }) + }) } + if (allTools.length === 0) return undefined return allTools } diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index 2ddaa369..9e9c3b57 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -183,186 +183,197 @@ export type SnakeCaseKeys> = { -// export const voidTools = { -export const builtinTools - : { - [T in keyof BuiltinToolCallParams]: { - name: string; - description: string; - // more params can be generated than exist here, but these params must be a subset of them - params: Partial<{ [paramName in keyof SnakeCaseKeys]: { description: string } }> - } +export const builtinTools: { + [T in keyof BuiltinToolCallParams]: { + name: string; + description: string; + // more params can be generated than exist here, but these params must be a subset of them + params: Partial<{ [paramName in keyof SnakeCaseKeys]: { description: string } }> } - = { - // --- context-gathering (read/search/list) --- +} = { + // --- context-gathering (read/search/list) --- - read_file: { - name: 'read_file', - description: `Returns full contents of a given file.`, - params: { - ...uriParam('file'), - start_line: { description: 'Optional. Do NOT fill this field in unless you were specifically given exact line numbers to search. Defaults to the beginning of the file.' }, - end_line: { description: 'Optional. Do NOT fill this field in unless you were specifically given exact line numbers to search. Defaults to the end of the file.' }, - ...paginationParam, - }, + read_file: { + name: 'read_file', + description: `Returns full contents of a given file.`, + params: { + ...uriParam('file'), + start_line: { description: 'Optional. Do NOT fill this field in unless you were specifically given exact line numbers to search. Defaults to the beginning of the file.' }, + end_line: { description: 'Optional. Do NOT fill this field in unless you were specifically given exact line numbers to search. Defaults to the end of the file.' }, + ...paginationParam, }, + }, - ls_dir: { - name: 'ls_dir', - description: `Lists all files and folders in the given URI.`, - params: { - uri: { description: `Optional. The FULL path to the ${'folder'}. Leave this as empty or "" to search all folders.` }, - ...paginationParam, - }, + ls_dir: { + name: 'ls_dir', + description: `Lists all files and folders in the given URI.`, + params: { + uri: { description: `Optional. The FULL path to the ${'folder'}. Leave this as empty or "" to search all folders.` }, + ...paginationParam, }, + }, - get_dir_tree: { - name: 'get_dir_tree', - description: `This is a very effective way to learn about the user's codebase. Returns a tree diagram of all the files and folders in the given folder. `, - params: { - ...uriParam('folder') - } - }, - - // pathname_search: { - // name: 'pathname_search', - // description: `Returns all pathnames that match a given \`find\`-style query over the entire workspace. ONLY searches file names. ONLY searches the current workspace. You should use this when looking for a file with a specific name or path. ${paginationHelper.desc}`, - - search_pathnames_only: { - name: 'search_pathnames_only', - description: `Returns all pathnames that match a given query (searches ONLY file names). You should use this when looking for a file with a specific name or path.`, - params: { - query: { description: `Your query for the search.` }, - include_pattern: { description: 'Optional. Only fill this in if you need to limit your search because there were too many results.' }, - ...paginationParam, - }, - }, - - - - search_for_files: { - name: 'search_for_files', - description: `Returns a list of file names whose content matches the given query. The query can be any substring or regex.`, - params: { - query: { description: `Your query for the search.` }, - search_in_folder: { description: 'Optional. Leave as blank by default. ONLY fill this in if your previous search with the same query was truncated. Searches descendants of this folder only.' }, - is_regex: { description: 'Optional. Default is false. Whether the query is a regex.' }, - ...paginationParam, - }, - }, - - // add new search_in_file tool - search_in_file: { - name: 'search_in_file', - description: `Returns an array of all the start line numbers where the content appears in the file.`, - params: { - ...uriParam('file'), - query: { description: 'The string or regex to search for in the file.' }, - is_regex: { description: 'Optional. Default is false. Whether the query is a regex.' } - } - }, - - read_lint_errors: { - name: 'read_lint_errors', - description: `Use this tool to view all the lint errors on a file.`, - params: { - ...uriParam('file'), - }, - }, - - // --- editing (create/delete) --- - - create_file_or_folder: { - name: 'create_file_or_folder', - description: `Create a file or folder at the given path. To create a folder, the path MUST end with a trailing slash.`, - params: { - ...uriParam('file or folder'), - }, - }, - - delete_file_or_folder: { - name: 'delete_file_or_folder', - description: `Delete a file or folder at the given path.`, - params: { - ...uriParam('file or folder'), - is_recursive: { description: 'Optional. Return true to delete recursively.' } - }, - }, - - edit_file: { - name: 'edit_file', - description: `Edit the contents of a file. You must provide the file's URI as well as a SINGLE string of SEARCH/REPLACE block(s) that will be used to apply the edit.`, - params: { - ...uriParam('file'), - search_replace_blocks: { description: replaceTool_description } - }, - }, - - rewrite_file: { - name: 'rewrite_file', - description: `Edits a file, deleting all the old contents and replacing them with your new contents. Use this tool if you want to edit a file you just created.`, - params: { - ...uriParam('file'), - new_content: { description: `The new contents of the file. Must be a string.` } - }, - }, - run_command: { - name: 'run_command', - description: `Runs a terminal command and waits for the result (times out after ${MAX_TERMINAL_INACTIVE_TIME}s of inactivity). ${terminalDescHelper}`, - params: { - command: { description: 'The terminal command to run.' }, - cwd: { description: cwdHelper }, - }, - }, - - run_persistent_command: { - name: 'run_persistent_command', - description: `Runs a terminal command in the persistent terminal that you created with open_persistent_terminal (results after ${MAX_TERMINAL_BG_COMMAND_TIME} are returned, and command continues running in background). ${terminalDescHelper}`, - params: { - command: { description: 'The terminal command to run.' }, - persistent_terminal_id: { description: 'The ID of the terminal created using open_persistent_terminal.' }, - }, - }, - - - - open_persistent_terminal: { - name: 'open_persistent_terminal', - description: `Use this tool when you want to run a terminal command indefinitely, like a dev server (eg \`npm run dev\`), a background listener, etc. Opens a new terminal in the user's environment which will not awaited for or killed.`, - params: { - cwd: { description: cwdHelper }, - } - }, - - - kill_persistent_terminal: { - name: 'kill_persistent_terminal', - description: `Interrupts and closes a persistent terminal that you opened with open_persistent_terminal.`, - params: { persistent_terminal_id: { description: `The ID of the persistent terminal.` } } + get_dir_tree: { + name: 'get_dir_tree', + description: `This is a very effective way to learn about the user's codebase. Returns a tree diagram of all the files and folders in the given folder. `, + params: { + ...uriParam('folder') } + }, + // pathname_search: { + // name: 'pathname_search', + // description: `Returns all pathnames that match a given \`find\`-style query over the entire workspace. ONLY searches file names. ONLY searches the current workspace. You should use this when looking for a file with a specific name or path. ${paginationHelper.desc}`, - // go_to_definition - // go_to_usages - - } satisfies { [T in keyof BuiltinToolResultType]: InternalToolInfo } + search_pathnames_only: { + name: 'search_pathnames_only', + description: `Returns all pathnames that match a given query (searches ONLY file names). You should use this when looking for a file with a specific name or path.`, + params: { + query: { description: `Your query for the search.` }, + include_pattern: { description: 'Optional. Only fill this in if you need to limit your search because there were too many results.' }, + ...paginationParam, + }, + }, -export const availableTools = (chatMode: ChatMode) => { + search_for_files: { + name: 'search_for_files', + description: `Returns a list of file names whose content matches the given query. The query can be any substring or regex.`, + params: { + query: { description: `Your query for the search.` }, + search_in_folder: { description: 'Optional. Leave as blank by default. ONLY fill this in if your previous search with the same query was truncated. Searches descendants of this folder only.' }, + is_regex: { description: 'Optional. Default is false. Whether the query is a regex.' }, + ...paginationParam, + }, + }, - // TOOL_TODO!!!! - // merge MCP tools with these builtin tools - // Note: This requires refactoring the messaging architecture to pass MCP tools from the renderer process - // to the electron-main process through the IPC channel. For now, MCP tools are handled separately - // in the chatThreadService where both built-in and MCP tools are called appropriately. + // add new search_in_file tool + search_in_file: { + name: 'search_in_file', + description: `Returns an array of all the start line numbers where the content appears in the file.`, + params: { + ...uriParam('file'), + query: { description: 'The string or regex to search for in the file.' }, + is_regex: { description: 'Optional. Default is false. Whether the query is a regex.' } + } + }, - const toolNames: BuiltinToolName[] | undefined = chatMode === 'normal' ? undefined + read_lint_errors: { + name: 'read_lint_errors', + description: `Use this tool to view all the lint errors on a file.`, + params: { + ...uriParam('file'), + }, + }, + + // --- editing (create/delete) --- + + create_file_or_folder: { + name: 'create_file_or_folder', + description: `Create a file or folder at the given path. To create a folder, the path MUST end with a trailing slash.`, + params: { + ...uriParam('file or folder'), + }, + }, + + delete_file_or_folder: { + name: 'delete_file_or_folder', + description: `Delete a file or folder at the given path.`, + params: { + ...uriParam('file or folder'), + is_recursive: { description: 'Optional. Return true to delete recursively.' } + }, + }, + + edit_file: { + name: 'edit_file', + description: `Edit the contents of a file. You must provide the file's URI as well as a SINGLE string of SEARCH/REPLACE block(s) that will be used to apply the edit.`, + params: { + ...uriParam('file'), + search_replace_blocks: { description: replaceTool_description } + }, + }, + + rewrite_file: { + name: 'rewrite_file', + description: `Edits a file, deleting all the old contents and replacing them with your new contents. Use this tool if you want to edit a file you just created.`, + params: { + ...uriParam('file'), + new_content: { description: `The new contents of the file. Must be a string.` } + }, + }, + run_command: { + name: 'run_command', + description: `Runs a terminal command and waits for the result (times out after ${MAX_TERMINAL_INACTIVE_TIME}s of inactivity). ${terminalDescHelper}`, + params: { + command: { description: 'The terminal command to run.' }, + cwd: { description: cwdHelper }, + }, + }, + + run_persistent_command: { + name: 'run_persistent_command', + description: `Runs a terminal command in the persistent terminal that you created with open_persistent_terminal (results after ${MAX_TERMINAL_BG_COMMAND_TIME} are returned, and command continues running in background). ${terminalDescHelper}`, + params: { + command: { description: 'The terminal command to run.' }, + persistent_terminal_id: { description: 'The ID of the terminal created using open_persistent_terminal.' }, + }, + }, + + + + open_persistent_terminal: { + name: 'open_persistent_terminal', + description: `Use this tool when you want to run a terminal command indefinitely, like a dev server (eg \`npm run dev\`), a background listener, etc. Opens a new terminal in the user's environment which will not awaited for or killed.`, + params: { + cwd: { description: cwdHelper }, + } + }, + + + kill_persistent_terminal: { + name: 'kill_persistent_terminal', + description: `Interrupts and closes a persistent terminal that you opened with open_persistent_terminal.`, + params: { persistent_terminal_id: { description: `The ID of the persistent terminal.` } } + } + + + // go_to_definition + // go_to_usages + +} satisfies { [T in keyof BuiltinToolResultType]: InternalToolInfo } + + + + +export const builtinToolNames = Object.keys(builtinTools) as BuiltinToolName[] +const toolNamesSet = new Set(builtinToolNames) +export const isABuiltinToolName = (toolName: string): toolName is BuiltinToolName => { + const isAToolName = toolNamesSet.has(toolName) + return isAToolName +} + + + + + +export const availableTools = (chatMode: ChatMode | null, mcpTools: InternalToolInfo[] | undefined) => { + + const builtinToolNames: BuiltinToolName[] | undefined = chatMode === 'normal' ? undefined : chatMode === 'gather' ? (Object.keys(builtinTools) as BuiltinToolName[]).filter(toolName => !(toolName in approvalTypeOfBuiltinToolName)) : chatMode === 'agent' ? Object.keys(builtinTools) as BuiltinToolName[] : undefined - const tools: InternalToolInfo[] | undefined = toolNames?.map(toolName => builtinTools[toolName]) + const effectiveBuiltinTools = builtinToolNames?.map(toolName => builtinTools[toolName]) ?? undefined + const effectiveMCPTools = chatMode === 'agent' ? mcpTools : undefined + + const tools: InternalToolInfo[] | undefined = !(builtinToolNames || mcpTools) ? undefined + : [ + ...effectiveBuiltinTools ?? [], + ...effectiveMCPTools ?? [], + ] + return tools } @@ -388,8 +399,8 @@ export const reParsedToolXMLString = (toolName: ToolName, toolParams: RawToolPar /* We expect tools to come at the end - not a hard limit, but that's just how we process them, and the flow makes more sense that way. */ // - You are allowed to call multiple tools by specifying them consecutively. However, there should be NO text or writing between tool calls or after them. -const systemToolsXMLPrompt = (chatMode: ChatMode) => { - const tools = availableTools(chatMode) +const systemToolsXMLPrompt = (chatMode: ChatMode, mcpTools: InternalToolInfo[] | undefined) => { + const tools = availableTools(chatMode, mcpTools) if (!tools || tools.length === 0) return null const toolXMLDefinitions = (`\ @@ -414,7 +425,7 @@ const systemToolsXMLPrompt = (chatMode: ChatMode) => { // ======================================================== chat (normal, gather, agent) ======================================================== -export const chat_systemMessage = ({ workspaceFolders, openedURIs, activeURI, persistentTerminalIDs, directoryStr, chatMode: mode, includeXMLToolDefinitions }: { workspaceFolders: string[], directoryStr: string, openedURIs: string[], activeURI: string | undefined, persistentTerminalIDs: string[], chatMode: ChatMode, includeXMLToolDefinitions: boolean }) => { +export const chat_systemMessage = ({ workspaceFolders, openedURIs, activeURI, persistentTerminalIDs, directoryStr, chatMode: mode, mcpTools, includeXMLToolDefinitions }: { workspaceFolders: string[], directoryStr: string, openedURIs: string[], activeURI: string | undefined, persistentTerminalIDs: string[], chatMode: ChatMode, mcpTools: InternalToolInfo[] | undefined, includeXMLToolDefinitions: boolean }) => { const header = (`You are an expert coding ${mode === 'agent' ? 'agent' : 'assistant'} whose job is \ ${mode === 'agent' ? `to help the user develop, run, and make changes to their codebase.` : mode === 'gather' ? `to search, understand, and reference files in the user's codebase.` @@ -448,7 +459,7 @@ ${directoryStr} `) - const toolDefinitions = includeXMLToolDefinitions ? systemToolsXMLPrompt(mode) : null + const toolDefinitions = includeXMLToolDefinitions ? systemToolsXMLPrompt(mode, mcpTools) : null const details: string[] = [] diff --git a/src/vs/workbench/contrib/void/common/sendLLMMessageService.ts b/src/vs/workbench/contrib/void/common/sendLLMMessageService.ts index 7579cfec..7618e736 100644 --- a/src/vs/workbench/contrib/void/common/sendLLMMessageService.ts +++ b/src/vs/workbench/contrib/void/common/sendLLMMessageService.ts @@ -13,6 +13,7 @@ import { generateUuid } from '../../../../base/common/uuid.js'; import { Event } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { IVoidSettingsService } from './voidSettingsService.js'; +import { IMCPService } from './mcpService.js'; // calls channel to implement features export const ILLMMessageService = createDecorator('llmMessageService'); @@ -61,6 +62,7 @@ export class LLMMessageService extends Disposable implements ILLMMessageService @IMainProcessService private readonly mainProcessService: IMainProcessService, // used as a renderer (only usable on client side) @IVoidSettingsService private readonly voidSettingsService: IVoidSettingsService, // @INotificationService private readonly notificationService: INotificationService, + @IMCPService private readonly mcpService: IMCPService, ) { super() @@ -116,6 +118,8 @@ export class LLMMessageService extends Disposable implements ILLMMessageService const { settingsOfProvider, } = this.voidSettingsService.state + const mcpTools = this.mcpService.getMCPTools() + // add state for request id const requestId = generateUuid(); this.llmMessageHooks.onText[requestId] = onText @@ -129,6 +133,7 @@ export class LLMMessageService extends Disposable implements ILLMMessageService requestId, settingsOfProvider, modelSelection, + mcpTools, } satisfies MainSendLLMMessageParams); return requestId diff --git a/src/vs/workbench/contrib/void/common/sendLLMMessageTypes.ts b/src/vs/workbench/contrib/void/common/sendLLMMessageTypes.ts index 79140ebb..f476b851 100644 --- a/src/vs/workbench/contrib/void/common/sendLLMMessageTypes.ts +++ b/src/vs/workbench/contrib/void/common/sendLLMMessageTypes.ts @@ -3,6 +3,7 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ +import { InternalToolInfo } from './prompt/prompts.js' import { ToolName, ToolParamName } from './toolsServiceTypes.js' import { ChatMode, ModelSelection, ModelSelectionOptions, OverridesOfModel, ProviderName, RefreshableProviderName, SettingsOfProvider } from './voidSettingsTypes.js' @@ -133,6 +134,7 @@ export type SendLLMMessageParams = { overridesOfModel: OverridesOfModel | undefined; settingsOfProvider: SettingsOfProvider; + mcpTools: InternalToolInfo[] | undefined; } & SendLLMType diff --git a/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts b/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts index 413baef7..d930f5de 100644 --- a/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts +++ b/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts @@ -87,21 +87,11 @@ export type BuiltinToolResultType = { export type ToolCallParams = T extends BuiltinToolName ? BuiltinToolCallParams[T] : RawToolParamsObj export type ToolResult = T extends BuiltinToolName ? BuiltinToolResultType[T] : RawMCPToolCall - export type BuiltinToolName = keyof BuiltinToolResultType -export const builtinToolNames = Object.keys(builtinTools) as BuiltinToolName[] type BuiltinToolParamNameOfTool = keyof (typeof builtinTools)[T]['params'] export type BuiltinToolParamName = { [T in BuiltinToolName]: BuiltinToolParamNameOfTool }[BuiltinToolName] -const toolNamesSet = new Set(builtinToolNames) - -export const isABuiltinToolName = (toolName: string): toolName is BuiltinToolName => { - const isAToolName = toolNamesSet.has(toolName) - return isAToolName -} - - export type ToolName = BuiltinToolName | (string & {}) export type ToolParamName = T extends BuiltinToolName ? BuiltinToolParamNameOfTool : string diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/extractGrammar.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/extractGrammar.ts index 03f07bda..3f1a4224 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/extractGrammar.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/extractGrammar.ts @@ -261,11 +261,14 @@ const parseXMLPrefixToToolCall = (toolName: BuiltinToolName, toolId: string, str } export const extractXMLToolsWrapper = ( - onText: OnText, onFinalMessage: OnFinalMessage, chatMode: ChatMode | null + onText: OnText, + onFinalMessage: OnFinalMessage, + chatMode: ChatMode | null, + mcpTools: InternalToolInfo[] | undefined, ): { newOnText: OnText, newOnFinalMessage: OnFinalMessage } => { if (!chatMode) return { newOnText: onText, newOnFinalMessage: onFinalMessage } - const tools = availableTools(chatMode) + const tools = availableTools(chatMode, mcpTools) if (!tools) return { newOnText: onText, newOnFinalMessage: onFinalMessage } const toolOfToolName: ToolOfToolName = {} diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts index 37d50c9f..baa1b585 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts @@ -18,9 +18,9 @@ import { AnthropicLLMChatMessage, GeminiLLMChatMessage, LLMChatMessage, LLMFIMMe import { ChatMode, displayInfoOfProviderName, ModelSelectionOptions, OverridesOfModel, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js'; import { getSendableReasoningInfo, getModelCapabilities, getProviderCapabilities, defaultProviderSettings, getReservedOutputTokenSpace } from '../../common/modelCapabilities.js'; import { extractReasoningWrapper, extractXMLToolsWrapper } from './extractGrammar.js'; -import { availableTools, InternalToolInfo, builtinTools } from '../../common/prompt/prompts.js'; +import { availableTools, InternalToolInfo, builtinTools, isABuiltinToolName } from '../../common/prompt/prompts.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; -import { isABuiltinToolName, BuiltinToolParamName } from '../../common/toolsServiceTypes.js'; +import { BuiltinToolParamName } from '../../common/toolsServiceTypes.js'; const getGoogleApiKey = async () => { // module‑level singleton @@ -49,6 +49,7 @@ type SendChatParams_Internal = InternalCommonMessageParams & { messages: LLMChatMessage[]; separateSystemMessage: string | undefined; chatMode: ChatMode | null; + mcpTools: InternalToolInfo[] | undefined; } type SendFIMParams_Internal = InternalCommonMessageParams & { messages: LLMFIMMessage; separateSystemMessage: string | undefined; } export type ListParams_Internal = ModelListParams @@ -207,8 +208,8 @@ const toOpenAICompatibleTool = (toolInfo: InternalToolInfo) => { } satisfies OpenAI.Chat.Completions.ChatCompletionTool } -const openAITools = (chatMode: ChatMode) => { - const allowedTools = availableTools(chatMode) +const openAITools = (chatMode: ChatMode | null, mcpTools: InternalToolInfo[] | undefined) => { + const allowedTools = availableTools(chatMode, mcpTools) if (!allowedTools || Object.keys(allowedTools).length === 0) return null const openAITools: OpenAI.Chat.Completions.ChatCompletionTool[] = [] @@ -242,7 +243,7 @@ const rawToolCallObjOf = (name: string, toolParamsStr: string, id: string): RawT // ------------ OPENAI-COMPATIBLE ------------ -const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, providerName, chatMode, separateSystemMessage, overridesOfModel }: SendChatParams_Internal) => { +const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, providerName, chatMode, separateSystemMessage, overridesOfModel, mcpTools }: SendChatParams_Internal) => { const { modelName, specialToolFormat, @@ -262,7 +263,7 @@ const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onE } // tools - const potentialTools = chatMode !== null ? openAITools(chatMode) : null + const potentialTools = openAITools(chatMode, mcpTools) const nativeToolsObj = potentialTools && specialToolFormat === 'openai-style' ? { tools: potentialTools } as const : {} @@ -293,7 +294,7 @@ const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onE // manually parse out tool results if XML if (!specialToolFormat) { - const { newOnText, newOnFinalMessage } = extractXMLToolsWrapper(onText, onFinalMessage, chatMode) + const { newOnText, newOnFinalMessage } = extractXMLToolsWrapper(onText, onFinalMessage, chatMode, mcpTools) onText = newOnText onFinalMessage = newOnFinalMessage } @@ -413,8 +414,8 @@ const toAnthropicTool = (toolInfo: InternalToolInfo) => { } satisfies Anthropic.Messages.Tool } -const anthropicTools = (chatMode: ChatMode) => { - const allowedTools = availableTools(chatMode) +const anthropicTools = (chatMode: ChatMode | null, mcpTools: InternalToolInfo[] | undefined) => { + const allowedTools = availableTools(chatMode, mcpTools) if (!allowedTools || Object.keys(allowedTools).length === 0) return null const anthropicTools: Anthropic.Messages.ToolUnion[] = [] @@ -437,7 +438,7 @@ const anthropicToolToRawToolCallObj = (toolBlock: Anthropic.Messages.ToolUseBloc } // ------------ ANTHROPIC ------------ -const sendAnthropicChat = async ({ messages, providerName, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, overridesOfModel, modelName: modelName_, _setAborter, separateSystemMessage, chatMode }: SendChatParams_Internal) => { +const sendAnthropicChat = async ({ messages, providerName, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, overridesOfModel, modelName: modelName_, _setAborter, separateSystemMessage, chatMode, mcpTools }: SendChatParams_Internal) => { const { modelName, specialToolFormat, @@ -454,7 +455,7 @@ const sendAnthropicChat = async ({ messages, providerName, onText, onFinalMessag const maxTokens = getReservedOutputTokenSpace(providerName, modelName_, { isReasoningEnabled: !!reasoningInfo?.isReasoningEnabled, overridesOfModel }) // tools - const potentialTools = chatMode !== null ? anthropicTools(chatMode) : null + const potentialTools = anthropicTools(chatMode, mcpTools) const nativeToolsObj = potentialTools && specialToolFormat === 'anthropic-style' ? { tools: potentialTools, tool_choice: { type: 'auto' } } as const : {} @@ -478,7 +479,7 @@ const sendAnthropicChat = async ({ messages, providerName, onText, onFinalMessag // manually parse out tool results if XML if (!specialToolFormat) { - const { newOnText, newOnFinalMessage } = extractXMLToolsWrapper(onText, onFinalMessage, chatMode) + const { newOnText, newOnFinalMessage } = extractXMLToolsWrapper(onText, onFinalMessage, chatMode, mcpTools) onText = newOnText onFinalMessage = newOnFinalMessage } @@ -679,8 +680,8 @@ const toGeminiFunctionDecl = (toolInfo: InternalToolInfo) => { } satisfies FunctionDeclaration } -const geminiTools = (chatMode: ChatMode): GeminiTool[] | null => { - const allowedTools = availableTools(chatMode) +const geminiTools = (chatMode: ChatMode | null, mcpTools: InternalToolInfo[] | undefined): GeminiTool[] | null => { + const allowedTools = availableTools(chatMode, mcpTools) if (!allowedTools || Object.keys(allowedTools).length === 0) return null const functionDecls: FunctionDeclaration[] = [] for (const t in allowedTools ?? {}) { @@ -706,6 +707,7 @@ const sendGeminiChat = async ({ providerName, modelSelectionOptions, chatMode, + mcpTools, }: SendChatParams_Internal) => { if (providerName !== 'gemini') throw new Error(`Sending Gemini chat, but provider was ${providerName}`) @@ -731,7 +733,7 @@ const sendGeminiChat = async ({ : undefined // tools - const potentialTools = chatMode !== null ? geminiTools(chatMode) : undefined + const potentialTools = geminiTools(chatMode, mcpTools) const toolConfig = potentialTools && specialToolFormat === 'gemini-style' ? potentialTools : undefined @@ -742,7 +744,7 @@ const sendGeminiChat = async ({ // manually parse out tool results if XML if (!specialToolFormat) { - const { newOnText, newOnFinalMessage } = extractXMLToolsWrapper(onText, onFinalMessage, chatMode) + const { newOnText, newOnFinalMessage } = extractXMLToolsWrapper(onText, onFinalMessage, chatMode, mcpTools) onText = newOnText onFinalMessage = newOnFinalMessage } diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts index 969dec8f..27f35ad5 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts @@ -23,6 +23,7 @@ export const sendLLMMessage = async ({ overridesOfModel, chatMode, separateSystemMessage, + mcpTools, }: SendLLMMessageParams, metricsService: IMetricsService @@ -107,7 +108,7 @@ export const sendLLMMessage = async ({ } const { sendFIM, sendChat } = implementation if (messagesType === 'chatMessages') { - await sendChat({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, overridesOfModel, modelName, _setAborter, providerName, separateSystemMessage, chatMode }) + await sendChat({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, overridesOfModel, modelName, _setAborter, providerName, separateSystemMessage, chatMode, mcpTools }) return } if (messagesType === 'FIMMessage') {