Path: blob/main/src/vs/workbench/contrib/extensions/common/searchExtensionsTool.ts
3296 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*--------------------------------------------------------------------------------------------*/45import { CancellationToken } from '../../../../base/common/cancellation.js';6import { Codicon } from '../../../../base/common/codicons.js';7import { ThemeIcon } from '../../../../base/common/themables.js';8import { localize } from '../../../../nls.js';9import { SortBy } from '../../../../platform/extensionManagement/common/extensionManagement.js';10import { EXTENSION_CATEGORIES } from '../../../../platform/extensions/common/extensions.js';11import { CountTokensCallback, IToolData, IToolImpl, IToolInvocation, IToolResult, ToolDataSource, ToolProgress } from '../../chat/common/languageModelToolsService.js';12import { ExtensionState, IExtension, IExtensionsWorkbenchService } from '../common/extensions.js';1314export const SearchExtensionsToolId = 'vscode_searchExtensions_internal';1516export const SearchExtensionsToolData: IToolData = {17id: SearchExtensionsToolId,18toolReferenceName: 'extensions',19canBeReferencedInPrompt: true,20icon: ThemeIcon.fromId(Codicon.extensions.id),21displayName: localize('searchExtensionsTool.displayName', 'Search Extensions'),22modelDescription: localize('searchExtensionsTool.modelDescription', "This is a tool for browsing Visual Studio Code Extensions Marketplace. It allows the model to search for extensions and retrieve detailed information about them. The model should use this tool whenever it needs to discover extensions or resolve information about known ones. To use the tool, the model has to provide the category of the extensions, relevant search keywords, or known extension IDs. Note that search results may include false positives, so reviewing and filtering is recommended."),23userDescription: localize('searchExtensionsTool.userDescription', 'Search for VS Code extensions'),24source: ToolDataSource.Internal,25inputSchema: {26type: 'object',27properties: {28category: {29type: 'string',30description: 'The category of extensions to search for',31enum: EXTENSION_CATEGORIES,32},33keywords: {34type: 'array',35items: {36type: 'string',37},38description: 'The keywords to search for',39},40ids: {41type: 'array',42items: {43type: 'string',44},45description: 'The ids of the extensions to search for',46},47},48}49};5051type InputParams = {52category?: string;53keywords?: string;54ids?: string[];55};5657type ExtensionData = {58id: string;59name: string;60description: string;61installed: boolean;62installCount: number;63rating: number;64categories: readonly string[];65tags: readonly string[];66};6768export class SearchExtensionsTool implements IToolImpl {6970constructor(71@IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService,72) { }7374async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, _progress: ToolProgress, token: CancellationToken): Promise<IToolResult> {75const params = invocation.parameters as InputParams;76if (!params.keywords?.length && !params.category && !params.ids?.length) {77return {78content: [{79kind: 'text',80value: localize('searchExtensionsTool.noInput', 'Please provide a category or keywords or ids to search for.')81}]82};83}8485const extensionsMap = new Map<string, ExtensionData>();8687const addExtension = (extensions: IExtension[]) => {88for (const extension of extensions) {89if (extension.deprecationInfo || extension.isMalicious) {90continue;91}92extensionsMap.set(extension.identifier.id.toLowerCase(), {93id: extension.identifier.id,94name: extension.displayName,95description: extension.description,96installed: extension.state === ExtensionState.Installed,97installCount: extension.installCount ?? 0,98rating: extension.rating ?? 0,99categories: extension.categories ?? [],100tags: extension.gallery?.tags ?? []101});102}103};104105const queryAndAddExtensions = async (text: string) => {106const extensions = await this.extensionWorkbenchService.queryGallery({107text,108pageSize: 10,109sortBy: SortBy.InstallCount110}, token);111if (extensions.firstPage.length) {112addExtension(extensions.firstPage);113}114};115116// Search for extensions by their ids117if (params.ids?.length) {118const extensions = await this.extensionWorkbenchService.getExtensions(params.ids.map(id => ({ id })), token);119addExtension(extensions);120}121122if (params.keywords?.length) {123for (const keyword of params.keywords ?? []) {124if (keyword === 'featured') {125await queryAndAddExtensions('featured');126} else {127let text = params.category ? `category:"${params.category}"` : '';128text = keyword ? `${text} ${keyword}`.trim() : text;129await queryAndAddExtensions(text);130}131}132} else {133await queryAndAddExtensions(`category:"${params.category}"`);134}135136const result = Array.from(extensionsMap.values());137138return {139content: [{140kind: 'text',141value: `Here are the list of extensions:\n${JSON.stringify(result)}\n. Important: Use the following format to display extensions to the user because there is a renderer available to parse these extensions in this format and display them with all details. So, do not describe about the extensions to the user.\n\`\`\`vscode-extensions\nextensionId1,extensionId2\n\`\`\`\n.`142}],143toolResultDetails: {144input: JSON.stringify(params),145output: [{ type: 'embed', isText: true, value: JSON.stringify(result.map(extension => extension.id)) }]146}147};148}149}150151152