Path: blob/main/extensions/copilot/test/simulation/fixtures/edit/issue-7202/languageModelToolsContribution.ts
13405 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*--------------------------------------------------------------------------------------------*/456import { IJSONSchema } from 'vs/base/common/jsonSchema';7import { DisposableMap } from 'vs/base/common/lifecycle';8import { joinPath } from 'vs/base/common/resources';9import { ThemeIcon } from 'vs/base/common/themables';10import { localize } from 'vs/nls';11import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';12import { ILogService } from 'vs/platform/log/common/log';13import { IWorkbenchContribution } from 'vs/workbench/common/contributions';14import { ILanguageModelToolsService, IToolData } from 'vs/workbench/contrib/chat/common/languageModelToolsService';15import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry';1617interface IRawToolContribution {18id: string;19name?: string;20icon?: string | { light: string; dark: string };21displayName?: string;22description: string;23parametersSchema?: IJSONSchema;24canBeInvokedManually?: boolean;25}2627const languageModelToolsExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<IRawToolContribution[]>({28extensionPoint: 'languageModelTools',29activationEventsGenerator: (contributions: IRawToolContribution[], result) => {30for (const contrib of contributions) {31result.push(`onLanguageModelTool:${contrib.name}`);32}33},34jsonSchema: {35description: localize('vscode.extension.contributes.tools', 'Contributes a tool that can be invoked by a language model.'),36type: 'array',37items: {38additionalProperties: false,39type: 'object',40defaultSnippets: [{ body: { name: '', description: '' } }],41required: ['id', 'description'],42properties: {43id: {44description: localize('toolId', "A unique id for this tool."),45type: 'string',46// Borrow OpenAI's requirement for tool names47pattern: '^[\\w-]+$'48},49name: {50description: localize('toolName', "If {0} is enabled for this tool, the user may use '#' with this name to invoke the tool in a query. Otherwise, the name is not required. Name must not contain whitespace.", '`canBeInvokedManually`'),51type: 'string',52pattern: '^[\\w-]+$'53},54displayName: {55description: localize('toolDisplayName', "A human-readable name for this tool that may be used to describe it in the UI."),56type: 'string'57},58description: {59description: localize('toolDescription', "A description of this tool that may be passed to a language model."),60type: 'string'61},62parametersSchema: {63description: localize('parametersSchema', "A JSON schema for the parameters this tool accepts."),64type: 'object',65$ref: 'http://json-schema.org/draft-07/schema#'66},67canBeInvokedManually: {68description: localize('canBeInvokedManually', "Whether this tool can be invoked manually by the user through the chat UX."),69type: 'boolean'70},71icon: {72description: localize('icon', "An icon that represents this tool. Either a file path, an object with file paths for dark and light themes, or a theme icon reference, like `\\$(zap)`"),73anyOf: [{74type: 'string'75},76{77type: 'object',78properties: {79light: {80description: localize('icon.light', 'Icon path when a light theme is used'),81type: 'string'82},83dark: {84description: localize('icon.dark', 'Icon path when a dark theme is used'),85type: 'string'86}87}88}]89}90}91}92}93});9495function toToolKey(extensionIdentifier: ExtensionIdentifier, toolName: string) {96return `${extensionIdentifier.value}/${toolName}`;97}9899export class LanguageModelToolsExtensionPointHandler implements IWorkbenchContribution {100static readonly ID = 'workbench.contrib.toolsExtensionPointHandler';101102private _registrationDisposables = new DisposableMap<string>();103104constructor(105@ILanguageModelToolsService languageModelToolsService: ILanguageModelToolsService,106@ILogService logService: ILogService,107) {108languageModelToolsExtensionPoint.setHandler((extensions, delta) => {109for (const extension of delta.added) {110for (const rawTool of extension.value) {111if (!rawTool.name || !rawTool.description) {112logService.error(`Invalid tool contribution from ${extension.description.identifier.value}: ${JSON.stringify(rawTool)}`);113continue;114}115116if (!rawTool.id.match(/^[\w-]+$/)) {117logService.error(`Extension '${extension.description.identifier.value}' CANNOT register participant with invalid id: ${rawTool.id}. The id must match /^[\\w-]+$/.`);118continue;119}120121const rawIcon = rawTool.icon;122let icon: IToolData['icon'] | undefined;123if (typeof rawIcon === 'string') {124icon = ThemeIcon.fromString(rawIcon) ?? {125dark: joinPath(extension.description.extensionLocation, rawIcon),126light: joinPath(extension.description.extensionLocation, rawIcon)127};128} else if (rawIcon) {129icon = {130dark: joinPath(extension.description.extensionLocation, rawIcon.dark),131light: joinPath(extension.description.extensionLocation, rawIcon.light)132};133}134135const tool: IToolData = {136...rawTool,137icon138};139const disposable = languageModelToolsService.registerToolData(tool);140this._registrationDisposables.set(toToolKey(extension.description.identifier, rawTool.id), disposable);141}142}143144for (const extension of delta.removed) {145for (const tool of extension.value) {146this._registrationDisposables.deleteAndDispose(toToolKey(extension.description.identifier, tool.id));147}148}149});150}151}152153154