mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-14 12:11:43 +01:00
introduce search extensions tool (#246721)
* introduce search extensions tool * update tool reference name
This commit is contained in:
committed by
GitHub
parent
c7f69b4b93
commit
cebb5e5a05
@@ -19,6 +19,7 @@ import { Dto } from '../../services/extensions/common/proxyIdentifier.js';
|
||||
import { ExtHostLanguageModelToolsShape, IMainContext, IToolDataDto, MainContext, MainThreadLanguageModelToolsShape } from './extHost.protocol.js';
|
||||
import { ExtHostLanguageModels } from './extHostLanguageModels.js';
|
||||
import * as typeConvert from './extHostTypeConverters.js';
|
||||
import { SearchExtensionsToolId } from '../../contrib/extensions/common/searchExtensionsTool.js';
|
||||
|
||||
export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape {
|
||||
/** A map of tools that were registered in this EH */
|
||||
@@ -97,6 +98,7 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape
|
||||
case InternalEditToolId:
|
||||
case ExtensionEditToolId:
|
||||
case InternalFetchWebPageToolId:
|
||||
case SearchExtensionsToolId:
|
||||
return isProposedApiEnabled(extension, 'chatParticipantPrivate');
|
||||
default:
|
||||
return true;
|
||||
|
||||
@@ -15,6 +15,9 @@ import { IChatRendererContent } from '../../common/chatViewModel.js';
|
||||
import { ChatTreeItem, ChatViewId, IChatCodeBlockInfo } from '../chat.js';
|
||||
import { IChatContentPart } from './chatContentParts.js';
|
||||
import { PagedModel } from '../../../../../base/common/paging.js';
|
||||
import { Codicon } from '../../../../../base/common/codicons.js';
|
||||
import { ThemeIcon } from '../../../../../base/common/themables.js';
|
||||
import { localize } from '../../../../../nls.js';
|
||||
|
||||
export class ChatExtensionsContentPart extends Disposable implements IChatContentPart {
|
||||
public readonly domNode: HTMLElement;
|
||||
@@ -38,10 +41,13 @@ export class ChatExtensionsContentPart extends Disposable implements IChatConten
|
||||
super();
|
||||
|
||||
this.domNode = dom.$('.chat-extensions-content-part');
|
||||
const loadingElement = dom.append(this.domNode, dom.$('.loading-extensions-element'));
|
||||
dom.append(loadingElement, dom.$(ThemeIcon.asCSSSelector(ThemeIcon.modify(Codicon.loading, 'spin'))), dom.$('span.loading-message', undefined, localize('chat.extensions.loading', 'Loading extensions...')));
|
||||
|
||||
const extensionsList = dom.append(this.domNode, dom.$('.extensions-list'));
|
||||
const list = this._register(instantiationService.createInstance(ExtensionsList, extensionsList, ChatViewId, { alwaysConsumeMouseWheel: false }, { onFocus: Event.None, onBlur: Event.None, filters: {} }));
|
||||
getExtensions(extensionsContent.extensions, extensionsWorkbenchService).then(extensions => {
|
||||
loadingElement.remove();
|
||||
if (this._store.isDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ import { CodeBlockPart, ICodeBlockData, ICodeBlockRenderOptions, localFileLangua
|
||||
import '../media/chatCodeBlockPill.css';
|
||||
import { IDisposableReference, ResourcePool } from './chatCollections.js';
|
||||
import { IChatContentPart, IChatContentPartRenderContext } from './chatContentParts.js';
|
||||
import { ChatExtensionsContentPart } from './chatExtensionsContentPart.js';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
@@ -105,6 +106,11 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP
|
||||
hideEmptyCodeblock.style.display = 'none';
|
||||
return hideEmptyCodeblock;
|
||||
}
|
||||
if (languageId === 'vscode-extensions') {
|
||||
const chatExtensions = this._register(instantiationService.createInstance(ChatExtensionsContentPart, { kind: 'extensions', extensions: text.split(',') }));
|
||||
this._register(chatExtensions.onDidChangeHeight(() => this._onDidChangeHeight.fire()));
|
||||
return chatExtensions.domNode;
|
||||
}
|
||||
const globalIndex = globalCodeBlockIndexStart++;
|
||||
const thisPartIndex = thisPartCodeBlockIndexStart++;
|
||||
let textModel: Promise<ITextModel>;
|
||||
|
||||
+13
@@ -12,3 +12,16 @@
|
||||
.chat-extensions-content-part .extension-list-item {
|
||||
border-bottom: 1px solid var(--vscode-chat-requestBorder);
|
||||
}
|
||||
|
||||
.chat-extensions-content-part .loading-extensions-element {
|
||||
line-height: 18px;
|
||||
padding: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
user-select: none;
|
||||
border-bottom: 1px solid var(--vscode-chat-requestBorder);
|
||||
}
|
||||
|
||||
.chat-extensions-content-part .loading-extensions-element .loading-message {
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
@@ -313,13 +313,13 @@
|
||||
font-weight: unset;
|
||||
}
|
||||
|
||||
.interactive-item-container .value .rendered-markdown {
|
||||
/* Codicons next to text need to be aligned with the text */
|
||||
.codicon {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
/* Codicons next to text need to be aligned with the text */
|
||||
.interactive-item-container .value .rendered-markdown:not(:has(.chat-extensions-content-part)) .codicon {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.interactive-item-container .value .rendered-markdown {
|
||||
.chat-codeblock-pill-widget .codicon {
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { InstantiationType, registerSingleton } from '../../../../platform/insta
|
||||
import { ExtensionsLocalizedLabel, IExtensionManagementService, IExtensionGalleryService, PreferencesLocalizedLabel, EXTENSION_INSTALL_SOURCE_CONTEXT, ExtensionInstallSource, UseUnpkgResourceApiConfigKey, SortBy, FilterType, VerifyExtensionSignatureConfigKey } from '../../../../platform/extensionManagement/common/extensionManagement.js';
|
||||
import { EnablementState, IExtensionManagementServerService, IPublisherInfo, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from '../../../services/extensionManagement/common/extensionManagement.js';
|
||||
import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from '../../../services/extensionRecommendations/common/extensionRecommendations.js';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from '../../../common/contributions.js';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
|
||||
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
|
||||
import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, WORKSPACE_RECOMMENDATIONS_VIEW_ID, IWorkspaceRecommendedExtensionsView, AutoUpdateConfigurationKey, HasOutdatedExtensionsContext, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, ExtensionEditorTab, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, OUTDATED_EXTENSIONS_VIEW_ID, CONTEXT_HAS_GALLERY, extensionsSearchActionsMenu, UPDATE_ACTIONS_GROUP, IExtensionArg, ExtensionRuntimeActionType, EXTENSIONS_CATEGORY, AutoRestartConfigurationKey } from '../common/extensions.js';
|
||||
import { InstallSpecificVersionOfExtensionAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, SetColorThemeAction, SetFileIconThemeAction, SetProductIconThemeAction, ClearLanguageAction, ToggleAutoUpdateForExtensionAction, ToggleAutoUpdatesForPublisherAction, TogglePreReleaseExtensionAction, InstallAnotherVersionAction, InstallAction } from './extensionsActions.js';
|
||||
@@ -81,6 +81,8 @@ import { IProductService } from '../../../../platform/product/common/productServ
|
||||
import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';
|
||||
import product from '../../../../platform/product/common/product.js';
|
||||
import { ExtensionGalleryResourceType, ExtensionGalleryServiceUrlConfigKey, getExtensionGalleryManifestResourceUri, IExtensionGalleryManifest, IExtensionGalleryManifestService } from '../../../../platform/extensionManagement/common/extensionGalleryManifest.js';
|
||||
import { ILanguageModelToolsService } from '../../chat/common/languageModelToolsService.js';
|
||||
import { SearchExtensionsTool, SearchExtensionsToolData } from '../common/searchExtensionsTool.js';
|
||||
|
||||
// Singletons
|
||||
registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService, InstantiationType.Eager /* Auto updates extensions */);
|
||||
@@ -1963,6 +1965,21 @@ class TrustedPublishersInitializer implements IWorkbenchContribution {
|
||||
}
|
||||
}
|
||||
|
||||
class ExtensionToolsContribution extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
static readonly ID = 'extensions.chat.toolsContribution';
|
||||
|
||||
constructor(
|
||||
@ILanguageModelToolsService toolsService: ILanguageModelToolsService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
const searchExtensionsTool = instantiationService.createInstance(SearchExtensionsTool);
|
||||
this._register(toolsService.registerToolData(SearchExtensionsToolData));
|
||||
this._register(toolsService.registerToolImplementation(SearchExtensionsToolData.id, searchExtensionsTool));
|
||||
}
|
||||
}
|
||||
|
||||
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
|
||||
workbenchRegistry.registerWorkbenchContribution(ExtensionsContributions, LifecyclePhase.Restored);
|
||||
workbenchRegistry.registerWorkbenchContribution(StatusUpdater, LifecyclePhase.Eventually);
|
||||
@@ -1979,6 +1996,8 @@ if (isWeb) {
|
||||
workbenchRegistry.registerWorkbenchContribution(ExtensionStorageCleaner, LifecyclePhase.Eventually);
|
||||
}
|
||||
|
||||
registerWorkbenchContribution2(ExtensionToolsContribution.ID, ExtensionToolsContribution, WorkbenchPhase.AfterRestored);
|
||||
|
||||
|
||||
// Running Extensions
|
||||
registerAction2(ShowRuntimeExtensionsAction);
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from '../../../../base/common/cancellation.js';
|
||||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { ThemeIcon } from '../../../../base/common/themables.js';
|
||||
import { localize } from '../../../../nls.js';
|
||||
import { SortBy } from '../../../../platform/extensionManagement/common/extensionManagement.js';
|
||||
import { EXTENSION_CATEGORIES } from '../../../../platform/extensions/common/extensions.js';
|
||||
import { CountTokensCallback, IToolData, IToolImpl, IToolInvocation, IToolResult } from '../../chat/common/languageModelToolsService.js';
|
||||
import { ExtensionState, IExtensionsWorkbenchService } from '../common/extensions.js';
|
||||
|
||||
export const SearchExtensionsToolId = 'vscode_searchExtensions_internal';
|
||||
|
||||
export const SearchExtensionsToolData: IToolData = {
|
||||
id: SearchExtensionsToolId,
|
||||
toolReferenceName: 'extensions',
|
||||
canBeReferencedInPrompt: true,
|
||||
icon: ThemeIcon.fromId(Codicon.extensions.id),
|
||||
supportsToolPicker: true,
|
||||
displayName: localize('searchExtensionsTool.displayName', 'Search Extensions'),
|
||||
modelDescription: localize('searchExtensionsTool.modelDescription', "This tool helps the model search for VS Code extensions from the Marketplace. The model should specify the category of extensions and keywords to search for. Note that the results may include false positives, so further filtering by reviewing the results is recommended."),
|
||||
source: { type: 'internal' },
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
category: {
|
||||
type: 'string',
|
||||
description: 'The category of extensions to search for',
|
||||
enum: EXTENSION_CATEGORIES,
|
||||
},
|
||||
keywords: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
description: 'The keywords to search for',
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
type InputParams = {
|
||||
category?: string;
|
||||
keywords?: string;
|
||||
};
|
||||
|
||||
type ExtensionData = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
installed: boolean;
|
||||
installCount: number;
|
||||
rating: number;
|
||||
categories: readonly string[];
|
||||
tags: readonly string[];
|
||||
};
|
||||
|
||||
export class SearchExtensionsTool implements IToolImpl {
|
||||
|
||||
constructor(
|
||||
@IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService,
|
||||
) { }
|
||||
|
||||
async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, token: CancellationToken): Promise<IToolResult> {
|
||||
const params = invocation.parameters as InputParams;
|
||||
if (!params.keywords?.length && !params.category) {
|
||||
return {
|
||||
content: [{
|
||||
kind: 'text',
|
||||
value: localize('searchExtensionsTool.noInput', 'Please provide a category or keyword to search for.')
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
const extensionsMap = new Map<string, ExtensionData>();
|
||||
const queryAndAddExtensions = async (text: string) => {
|
||||
const extensions = await this.extensionWorkbenchService.queryGallery({
|
||||
text,
|
||||
pageSize: 10,
|
||||
sortBy: SortBy.InstallCount
|
||||
}, token);
|
||||
if (extensions.firstPage.length) {
|
||||
for (const extension of extensions.firstPage) {
|
||||
if (extension.deprecationInfo || extension.isMalicious) {
|
||||
continue;
|
||||
}
|
||||
extensionsMap.set(extension.identifier.id.toLowerCase(), {
|
||||
id: extension.identifier.id,
|
||||
name: extension.displayName,
|
||||
description: extension.description,
|
||||
installed: extension.state === ExtensionState.Installed,
|
||||
installCount: extension.installCount ?? 0,
|
||||
rating: extension.rating ?? 0,
|
||||
categories: extension.categories ?? [],
|
||||
tags: extension.gallery?.tags ?? []
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (params.keywords?.length) {
|
||||
for (const keyword of params.keywords ?? []) {
|
||||
if (keyword === 'featured') {
|
||||
await queryAndAddExtensions('featured');
|
||||
} else {
|
||||
let text = params.category ? `category:"${params.category}"` : '';
|
||||
text = keyword ? `${text} ${keyword}`.trim() : text;
|
||||
await queryAndAddExtensions(text);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await queryAndAddExtensions(`category:"${params.category}"`);
|
||||
}
|
||||
|
||||
const result = Array.from(extensionsMap.values());
|
||||
|
||||
return {
|
||||
content: [{
|
||||
kind: 'text',
|
||||
value: `Here are the list of extensions:\n${JSON.stringify(result)}\n. 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.`
|
||||
}],
|
||||
toolResultDetails: {
|
||||
input: JSON.stringify(params),
|
||||
output: JSON.stringify(result.map(extension => extension.id))
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user