diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 868f7cfd4e7..11d375461a2 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -53,7 +53,8 @@ "treeViewActiveItem", "treeViewReveal", "workspaceTrust", - "telemetry" + "telemetry", + "lmTools" ], "private": true, "activationEvents": [], diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index 9c6e94d1c4c..abfd6c5228c 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -42,9 +42,6 @@ const _allApiProposals = { chatTab: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatTab.d.ts', }, - chatTools: { - proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatTools.d.ts', - }, chatVariableResolver: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts', }, @@ -227,6 +224,7 @@ const _allApiProposals = { }, lmTools: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.lmTools.d.ts', + version: 2 }, mappedEditsProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts', diff --git a/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts b/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts index d4247860254..fbda6ced5a3 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts @@ -33,18 +33,18 @@ export class MainThreadLanguageModelTools extends Disposable implements MainThre return this._languageModelToolsService.invokeTool(name, parameters, token); } - $registerTool(id: string): void { + $registerTool(name: string): void { const disposable = this._languageModelToolsService.registerToolImplementation( - id, + name, { invoke: async (parameters, token) => { - return await this._proxy.$invokeTool(id, parameters, token); + return await this._proxy.$invokeTool(name, parameters, token); }, }); - this._tools.set(id, disposable); + this._tools.set(name, disposable); } - $unregisterTool(id: string): void { - this._tools.deleteAndDispose(id); + $unregisterTool(name: string): void { + this._tools.deleteAndDispose(name); } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 6daff426cda..4493a01248a 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1483,15 +1483,15 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I } }, registerTool(toolId: string, tool: vscode.LanguageModelTool) { - checkProposedApiEnabled(extension, 'chatVariableResolver'); + checkProposedApiEnabled(extension, 'lmTools'); return extHostLanguageModelTools.registerTool(extension, toolId, tool); }, invokeTool(toolId: string, parameters: Object, token: vscode.CancellationToken) { - checkProposedApiEnabled(extension, 'chatVariableResolver'); + checkProposedApiEnabled(extension, 'lmTools'); return extHostLanguageModelTools.invokeTool(toolId, parameters, token); }, get tools() { - checkProposedApiEnabled(extension, 'chatVariableResolver'); + checkProposedApiEnabled(extension, 'lmTools'); return extHostLanguageModelTools.tools; }, }; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 9e8bd3e587c..2bdeacb491a 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -22,7 +22,7 @@ import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extH import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { ChatAgentLocation, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IChatContentReference, IChatFollowup, IChatUserActionEvent, ChatAgentVoteDirection, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatAgentVoteDirection, IChatContentReference, IChatFollowup, IChatResponseErrorDetails, IChatUserActionEvent } from 'vs/workbench/contrib/chat/common/chatService'; import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import type * as vscode from 'vscode'; diff --git a/src/vs/workbench/api/common/extHostLanguageModelTools.ts b/src/vs/workbench/api/common/extHostLanguageModelTools.ts index 46ea26e07d7..e588f50ab6d 100644 --- a/src/vs/workbench/api/common/extHostLanguageModelTools.ts +++ b/src/vs/workbench/api/common/extHostLanguageModelTools.ts @@ -7,6 +7,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ExtHostLanguageModelToolsShape, IMainContext, MainContext, MainThreadLanguageModelToolsShape } from 'vs/workbench/api/common/extHost.protocol'; +import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import { IToolData, IToolDelta } from 'vs/workbench/contrib/chat/common/languageModelToolsService'; import type * as vscode from 'vscode'; @@ -23,7 +24,7 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape this._proxy.$getTools().then(tools => { for (const tool of tools) { - this._allTools.set(tool.id, tool); + this._allTools.set(tool.name, tool); } }); } @@ -35,7 +36,7 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape async $acceptToolDelta(delta: IToolDelta): Promise { if (delta.added) { - this._allTools.set(delta.added.id, delta.added); + this._allTools.set(delta.added.name, delta.added); } if (delta.removed) { @@ -44,25 +45,26 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape } get tools(): vscode.LanguageModelToolDescription[] { - return Array.from(this._allTools.values()); + return Array.from(this._allTools.values()) + .map(tool => typeConvert.LanguageModelToolDescription.to(tool)); } - async $invokeTool(id: string, parameters: any, token: CancellationToken): Promise { - const item = this._registeredTools.get(id); + async $invokeTool(name: string, parameters: any, token: CancellationToken): Promise { + const item = this._registeredTools.get(name); if (!item) { - throw new Error(`Unknown tool ${id}`); + throw new Error(`Unknown tool ${name}`); } return await item.tool.invoke(parameters, token); } - registerTool(extension: IExtensionDescription, id: string, tool: vscode.LanguageModelTool): IDisposable { - this._registeredTools.set(id, { extension, tool }); - this._proxy.$registerTool(id); + registerTool(extension: IExtensionDescription, name: string, tool: vscode.LanguageModelTool): IDisposable { + this._registeredTools.set(name, { extension, tool }); + this._proxy.$registerTool(name); return toDisposable(() => { - this._registeredTools.delete(id); - this._proxy.$unregisterTool(id); + this._registeredTools.delete(name); + this._proxy.$unregisterTool(name); }); } } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index fe20f202483..32d972e9862 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -53,6 +53,7 @@ import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/ed import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import type * as vscode from 'vscode'; import * as types from './extHostTypes'; +import { IToolData } from 'vs/workbench/contrib/chat/common/languageModelToolsService'; export namespace Command { @@ -2743,3 +2744,13 @@ export namespace DebugTreeItem { }; } } + +export namespace LanguageModelToolDescription { + export function to(item: IToolData): vscode.LanguageModelToolDescription { + return { + name: item.name, + description: item.description, + parametersSchema: item.parametersSchema, + }; + } +} diff --git a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts index a498aff9346..a1c70e6d1a4 100644 --- a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts @@ -11,7 +11,7 @@ import { Iterable } from 'vs/base/common/iterator'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; export interface IToolData { - id: string; + name: string; displayName?: string; description: string; parametersSchema?: Object; @@ -37,7 +37,7 @@ export interface ILanguageModelToolsService { _serviceBrand: undefined; onDidChangeTools: Event; registerToolData(toolData: IToolData): IDisposable; - registerToolImplementation(id: string, tool: IToolImpl): IDisposable; + registerToolImplementation(name: string, tool: IToolImpl): IDisposable; getTools(): Iterable>; invokeTool(name: string, parameters: any, token: CancellationToken): Promise; } @@ -55,28 +55,28 @@ export class LanguageModelToolsService implements ILanguageModelToolsService { ) { } registerToolData(toolData: IToolData): IDisposable { - if (this._tools.has(toolData.id)) { - throw new Error(`Tool "${toolData.id}" is already registered.`); + if (this._tools.has(toolData.name)) { + throw new Error(`Tool "${toolData.name}" is already registered.`); } - this._tools.set(toolData.id, { data: toolData }); + this._tools.set(toolData.name, { data: toolData }); this._onDidChangeTools.fire({ added: toolData }); return toDisposable(() => { - this._tools.delete(toolData.id); - this._onDidChangeTools.fire({ removed: toolData.id }); + this._tools.delete(toolData.name); + this._onDidChangeTools.fire({ removed: toolData.name }); }); } - registerToolImplementation(id: string, tool: IToolImpl): IDisposable { - const entry = this._tools.get(id); + registerToolImplementation(name: string, tool: IToolImpl): IDisposable { + const entry = this._tools.get(name); if (!entry) { - throw new Error(`Tool "${id}" was not contributed.`); + throw new Error(`Tool "${name}" was not contributed.`); } if (entry.impl) { - throw new Error(`Tool "${id}" already has an implementation.`); + throw new Error(`Tool "${name}" already has an implementation.`); } entry.impl = tool; diff --git a/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts b/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts index f75f30ff1c2..5cc34e72faf 100644 --- a/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts +++ b/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts @@ -8,12 +8,13 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { DisposableMap } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { ILanguageModelToolsService } from 'vs/workbench/contrib/chat/common/languageModelToolsService'; import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry'; interface IRawToolContribution { - id: string; + name: string; displayName?: string; description: string; parametersSchema?: IJSONSchema; @@ -23,7 +24,7 @@ const languageModelToolsExtensionPoint = extensionsRegistry.ExtensionsRegistry.r extensionPoint: 'languageModelTools', activationEventsGenerator: (contributions: IRawToolContribution[], result) => { for (const contrib of contributions) { - result.push(`onLanguageModelTool:${contrib.id}`); + result.push(`onLanguageModelTool:${contrib.name}`); } }, jsonSchema: { @@ -32,11 +33,11 @@ const languageModelToolsExtensionPoint = extensionsRegistry.ExtensionsRegistry.r items: { additionalProperties: false, type: 'object', - defaultSnippets: [{ body: { id: '', description: '' } }], - required: ['id', 'description'], + defaultSnippets: [{ body: { name: '', description: '' } }], + required: ['name', 'description'], properties: { - id: { - description: localize('toolId', "A unique id for this tool."), + name: { + description: localize('toolname', "A name for this tool which must be unique across all tools."), type: 'string' }, description: { @@ -57,8 +58,8 @@ const languageModelToolsExtensionPoint = extensionsRegistry.ExtensionsRegistry.r } }); -function toToolKey(extensionIdentifier: ExtensionIdentifier, toolId: string) { - return `${extensionIdentifier.value}/${toolId}`; +function toToolKey(extensionIdentifier: ExtensionIdentifier, toolName: string) { + return `${extensionIdentifier.value}/${toolName}`; } export class LanguageModelToolsExtensionPointHandler implements IWorkbenchContribution { @@ -67,19 +68,25 @@ export class LanguageModelToolsExtensionPointHandler implements IWorkbenchContri private _registrationDisposables = new DisposableMap(); constructor( - @ILanguageModelToolsService languageModelToolsService: ILanguageModelToolsService + @ILanguageModelToolsService languageModelToolsService: ILanguageModelToolsService, + @ILogService logService: ILogService, ) { languageModelToolsExtensionPoint.setHandler((extensions, delta) => { for (const extension of delta.added) { for (const tool of extension.value) { + if (!tool.name || !tool.description) { + logService.warn(`Invalid tool contribution from ${extension.description.identifier.value}: ${JSON.stringify(tool)}`); + continue; + } + const disposable = languageModelToolsService.registerToolData(tool); - this._registrationDisposables.set(toToolKey(extension.description.identifier, tool.id), disposable); + this._registrationDisposables.set(toToolKey(extension.description.identifier, tool.name), disposable); } } for (const extension of delta.removed) { for (const tool of extension.value) { - this._registrationDisposables.deleteAndDispose(toToolKey(extension.description.identifier, tool.id)); + this._registrationDisposables.deleteAndDispose(toToolKey(extension.description.identifier, tool.name)); } } }); diff --git a/src/vscode-dts/vscode.proposed.chatTools.d.ts b/src/vscode-dts/vscode.proposed.chatTools.d.ts deleted file mode 100644 index 97740f665a4..00000000000 --- a/src/vscode-dts/vscode.proposed.chatTools.d.ts +++ /dev/null @@ -1,35 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode' { - - export namespace lm { - /** - * Register a LanguageModelTool. The tool must also be registered in the package.json `languageModelTools` contribution point. - */ - export function registerTool(toolId: string, tool: LanguageModelTool): Disposable; - - /** - * A list of all available tools. - */ - export const tools: ReadonlyArray; - - /** - * Invoke a tool with the given parameters. - */ - export function invokeTool(toolId: string, parameters: Object, token: CancellationToken): Thenable; - } - - export interface LanguageModelToolDescription { - id: string; - description: string; - parametersSchema?: JSONSchema; - displayName?: string; - } - - export interface LanguageModelTool { - invoke(parameters: any, token: CancellationToken): Thenable; - } -} diff --git a/src/vscode-dts/vscode.proposed.lmTools.d.ts b/src/vscode-dts/vscode.proposed.lmTools.d.ts index 6aa0ac3a225..589d6d8fef1 100644 --- a/src/vscode-dts/vscode.proposed.lmTools.d.ts +++ b/src/vscode-dts/vscode.proposed.lmTools.d.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// version: 2 + declare module 'vscode' { // TODO@API capabilities @@ -13,7 +15,7 @@ declare module 'vscode' { export interface LanguageModelChatFunction { name: string; description: string; - parametersSchema: JSONSchema; + parametersSchema?: JSONSchema; } // API -> LM: add tools as request option @@ -55,4 +57,34 @@ declare module 'vscode' { export interface LanguageModelChatMessage { content2: string | LanguageModelChatMessageFunctionResultPart; } + + // Tool registration/invoking between extensions + + export namespace lm { + /** + * Register a LanguageModelTool. The tool must also be registered in the package.json `languageModelTools` contribution point. + */ + export function registerTool(name: string, tool: LanguageModelTool): Disposable; + + /** + * A list of all available tools. + */ + export const tools: ReadonlyArray; + + /** + * Invoke a tool with the given parameters. + */ + export function invokeTool(name: string, parameters: Object, token: CancellationToken): Thenable; + } + + // Is the same as LanguageModelChatFunction now, but could have more details in the future + export interface LanguageModelToolDescription { + name: string; + description: string; + parametersSchema?: JSONSchema; + } + + export interface LanguageModelTool { + invoke(parameters: any, token: CancellationToken): Thenable; + } }