Tools API tweaks, merge into lmTools (#216750)

* Tools API tweaks, merge into lmTools

* Rename more id -> name

* Fix

* Add lmTools API version
This commit is contained in:
Rob Lourens 2024-06-20 18:54:00 -07:00 committed by GitHub
parent 5f330d3864
commit ee173b0e65
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 99 additions and 83 deletions

View file

@ -53,7 +53,8 @@
"treeViewActiveItem",
"treeViewReveal",
"workspaceTrust",
"telemetry"
"telemetry",
"lmTools"
],
"private": true,
"activationEvents": [],

View file

@ -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',

View file

@ -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);
}
}

View file

@ -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;
},
};

View file

@ -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';

View file

@ -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<void> {
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<string> {
const item = this._registeredTools.get(id);
async $invokeTool(name: string, parameters: any, token: CancellationToken): Promise<string> {
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);
});
}
}

View file

@ -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,
};
}
}

View file

@ -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<IToolDelta>;
registerToolData(toolData: IToolData): IDisposable;
registerToolImplementation(id: string, tool: IToolImpl): IDisposable;
registerToolImplementation(name: string, tool: IToolImpl): IDisposable;
getTools(): Iterable<Readonly<IToolData>>;
invokeTool(name: string, parameters: any, token: CancellationToken): Promise<string>;
}
@ -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;

View file

@ -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<string>();
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));
}
}
});

View file

@ -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<LanguageModelToolDescription>;
/**
* Invoke a tool with the given parameters.
*/
export function invokeTool(toolId: string, parameters: Object, token: CancellationToken): Thenable<string>;
}
export interface LanguageModelToolDescription {
id: string;
description: string;
parametersSchema?: JSONSchema;
displayName?: string;
}
export interface LanguageModelTool {
invoke(parameters: any, token: CancellationToken): Thenable<string>;
}
}

View file

@ -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<LanguageModelToolDescription>;
/**
* Invoke a tool with the given parameters.
*/
export function invokeTool(name: string, parameters: Object, token: CancellationToken): Thenable<string>;
}
// 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<string>;
}
}