Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/browser/agentPluginsView.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as dom from '../../../../base/browser/dom.js';
7
import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';
8
import { ActionViewItem, IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';
9
import { IListContextMenuEvent } from '../../../../base/browser/ui/list/list.js';
10
import { IPagedRenderer } from '../../../../base/browser/ui/list/listPaging.js';
11
import { Action, IAction, Separator } from '../../../../base/common/actions.js';
12
import { RunOnceScheduler } from '../../../../base/common/async.js';
13
import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';
14
import { Codicon } from '../../../../base/common/codicons.js';
15
import { Event } from '../../../../base/common/event.js';
16
import { Disposable, DisposableStore, disposeIfDisposable, IDisposable, isDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js';
17
import { ThemeIcon } from '../../../../base/common/themables.js';
18
import { autorun, derived, IObservable, IReaderWithStore } from '../../../../base/common/observable.js';
19
import { IPagedModel, PagedModel } from '../../../../base/common/paging.js';
20
import { dirname } from '../../../../base/common/resources.js';
21
import { localize, localize2 } from '../../../../nls.js';
22
import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';
23
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
24
import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
25
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
26
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
27
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
28
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
29
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
30
import { ILabelService } from '../../../../platform/label/common/label.js';
31
import { WorkbenchPagedList } from '../../../../platform/list/browser/listService.js';
32
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
33
import { Registry } from '../../../../platform/registry/common/platform.js';
34
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
35
import { getLocationBasedViewColors } from '../../../browser/parts/views/viewPane.js';
36
import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js';
37
import { IWorkbenchContribution } from '../../../common/contributions.js';
38
import { IViewDescriptorService, IViewsRegistry, Extensions as ViewExtensions } from '../../../common/views.js';
39
import { IEditorService } from '../../../services/editor/common/editorService.js';
40
import { VIEW_CONTAINER } from '../../extensions/browser/extensions.contribution.js';
41
import { manageExtensionIcon } from '../../extensions/browser/extensionsIcons.js';
42
import { AbstractExtensionsListView } from '../../extensions/browser/extensionsViews.js';
43
import { DefaultViewsContext, extensionsFilterSubMenu, IExtensionsWorkbenchService, SearchAgentPluginsContext } from '../../extensions/common/extensions.js';
44
import { ChatContextKeys } from '../common/actions/chatContextKeys.js';
45
import { IAgentPlugin, IAgentPluginService } from '../common/plugins/agentPluginService.js';
46
import { isContributionEnabled } from '../common/enablement.js';
47
import { IPluginInstallService } from '../common/plugins/pluginInstallService.js';
48
import { hasSourceChanged, IMarketplacePlugin, IPluginMarketplaceService } from '../common/plugins/pluginMarketplaceService.js';
49
import { AgentPluginEditorInput } from './agentPluginEditor/agentPluginEditorInput.js';
50
import { AgentPluginItemKind, IAgentPluginItem, IInstalledPluginItem, IMarketplacePluginItem } from './agentPluginEditor/agentPluginItems.js';
51
import { getInstalledPluginContextMenuActions, InstallPluginAction, OpenPluginReadmeAction } from './agentPluginActions.js';
52
import { InstalledAgentPluginsViewId, HasInstalledAgentPluginsContext } from './chat.js';
53
54
//#region Item model
55
56
function installedPluginToItem(plugin: IAgentPlugin, labelService: ILabelService, outdated?: IObservable<IMarketplacePlugin | undefined>): IInstalledPluginItem {
57
const name = plugin.label;
58
const description = plugin.fromMarketplace?.description ?? labelService.getUriLabel(dirname(plugin.uri), { relative: true });
59
const marketplace = plugin.fromMarketplace?.marketplace;
60
return { kind: AgentPluginItemKind.Installed, name, description, marketplace, plugin, outdated };
61
}
62
63
function marketplacePluginToItem(plugin: IMarketplacePlugin): IMarketplacePluginItem {
64
return {
65
kind: AgentPluginItemKind.Marketplace,
66
name: plugin.name,
67
description: plugin.description,
68
source: plugin.source,
69
sourceDescriptor: plugin.sourceDescriptor,
70
marketplace: plugin.marketplace,
71
marketplaceReference: plugin.marketplaceReference,
72
marketplaceType: plugin.marketplaceType,
73
readmeUri: plugin.readmeUri,
74
};
75
}
76
77
//#endregion
78
79
//#region Actions
80
81
//#region Actions
82
83
class UpdatePluginAction extends Action {
84
static readonly ID = 'agentPlugin.update';
85
86
constructor(
87
private readonly plugin: IAgentPlugin,
88
private readonly liveMarketplacePlugin: IMarketplacePlugin,
89
@IPluginInstallService private readonly pluginInstallService: IPluginInstallService,
90
@IPluginMarketplaceService private readonly pluginMarketplaceService: IPluginMarketplaceService,
91
) {
92
super(UpdatePluginAction.ID, localize('update', "Update"), 'extension-action label prominent install');
93
}
94
95
override async run(): Promise<void> {
96
if (await this.pluginInstallService.updatePlugin(this.liveMarketplacePlugin)) {
97
this.pluginMarketplaceService.addInstalledPlugin(this.plugin.uri, this.liveMarketplacePlugin);
98
}
99
}
100
}
101
102
class ManagePluginAction extends Action {
103
static readonly ID = 'agentPlugin.manage';
104
static readonly CLASS = `extension-action icon manage ${ThemeIcon.asClassName(manageExtensionIcon)}`;
105
106
private _actionViewItem: DropDownActionViewItem | null = null;
107
108
constructor(
109
private readonly getActionGroups: () => IAction[][],
110
@IInstantiationService private readonly instantiationService: IInstantiationService,
111
) {
112
super(ManagePluginAction.ID, '', ManagePluginAction.CLASS, true);
113
this.tooltip = localize('manage', "Manage");
114
}
115
116
createActionViewItem(options: IActionViewItemOptions): DropDownActionViewItem {
117
this._actionViewItem = this.instantiationService.createInstance(DropDownActionViewItem, this, options);
118
return this._actionViewItem;
119
}
120
121
override async run(): Promise<void> {
122
this._actionViewItem?.showMenu(this.getActionGroups());
123
}
124
}
125
126
class DropDownActionViewItem extends ActionViewItem {
127
constructor(
128
action: IAction,
129
options: IActionViewItemOptions,
130
@IContextMenuService private readonly contextMenuService: IContextMenuService,
131
) {
132
super(null, action, { ...options, icon: true, label: false });
133
}
134
135
showMenu(actionGroups: IAction[][]): void {
136
if (!this.element) {
137
return;
138
}
139
const actions = actionGroups.flatMap(group => [...group, new Separator()]);
140
if (actions.length > 0) {
141
actions.pop();
142
}
143
const { left, top, height } = dom.getDomNodePagePosition(this.element);
144
this.contextMenuService.showContextMenu({
145
getAnchor: () => ({ x: left, y: top + height + 10 }),
146
getActions: () => actions,
147
onHide: () => disposeIfDisposable(actions),
148
});
149
}
150
}
151
152
//#endregion
153
154
//#region Renderer
155
156
interface IAgentPluginTemplateData {
157
root: HTMLElement;
158
name: HTMLElement;
159
description: HTMLElement;
160
detail: HTMLElement;
161
actionbar: ActionBar;
162
disposables: IDisposable[];
163
elementDisposables: IDisposable[];
164
}
165
166
class AgentPluginRenderer implements IPagedRenderer<IAgentPluginItem, IAgentPluginTemplateData> {
167
168
static readonly templateId = 'agentPlugin';
169
readonly templateId = AgentPluginRenderer.templateId;
170
171
constructor(
172
@IInstantiationService private readonly instantiationService: IInstantiationService,
173
) { }
174
175
renderTemplate(root: HTMLElement): IAgentPluginTemplateData {
176
const element = dom.append(root, dom.$('.agent-plugin-item.extension-list-item'));
177
const details = dom.append(element, dom.$('.details'));
178
const headerContainer = dom.append(details, dom.$('.header-container'));
179
const header = dom.append(headerContainer, dom.$('.header'));
180
const name = dom.append(header, dom.$('span.name'));
181
const description = dom.append(details, dom.$('.description.ellipsis'));
182
const footer = dom.append(details, dom.$('.footer'));
183
const detailContainer = dom.append(footer, dom.$('.publisher-container'));
184
const detail = dom.append(detailContainer, dom.$('span.publisher-name'));
185
const actionbar = new ActionBar(footer, {
186
focusOnlyEnabledItems: true,
187
actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => {
188
if (action instanceof ManagePluginAction) {
189
return action.createActionViewItem(options);
190
}
191
return undefined;
192
}
193
});
194
actionbar.setFocusable(false);
195
return { root, name, description, detail, actionbar, disposables: [actionbar], elementDisposables: [] };
196
}
197
198
renderPlaceholder(_index: number, data: IAgentPluginTemplateData): void {
199
data.name.textContent = '';
200
data.description.textContent = '';
201
data.detail.textContent = '';
202
data.actionbar.clear();
203
this.disposeElement(undefined, 0, data);
204
}
205
206
renderElement(element: IAgentPluginItem, _index: number, data: IAgentPluginTemplateData): void {
207
this.disposeElement(undefined, 0, data);
208
209
data.name.textContent = element.name;
210
data.description.textContent = element.description;
211
212
data.elementDisposables.push(autorun(reader => {
213
data.root.classList.toggle('disabled', element.kind === AgentPluginItemKind.Installed && !isContributionEnabled(element.plugin.enablement.read(reader)));
214
}));
215
216
const updateActions = (reader: IReaderWithStore) => {
217
data.actionbar.clear();
218
if (element.kind === AgentPluginItemKind.Marketplace) {
219
data.detail.textContent = element.marketplace;
220
const installAction = this.instantiationService.createInstance(InstallPluginAction, element);
221
reader.store.add(installAction);
222
data.actionbar.push([installAction], { icon: true, label: true });
223
} else {
224
data.detail.textContent = element.marketplace ?? '';
225
const actions: Action[] = [];
226
const livePlugin = element.outdated?.read(reader);
227
if (livePlugin) {
228
const updateAction = this.instantiationService.createInstance(UpdatePluginAction, element.plugin, livePlugin);
229
reader.store.add(updateAction);
230
actions.push(updateAction);
231
}
232
const manageAction = this.instantiationService.createInstance(ManagePluginAction,
233
() => getInstalledPluginContextMenuActions(element.plugin, this.instantiationService));
234
reader.store.add(manageAction);
235
actions.push(manageAction);
236
data.actionbar.push(actions, { icon: true, label: true });
237
}
238
};
239
240
data.elementDisposables.push(autorun(updateActions));
241
}
242
243
disposeElement(_element: IAgentPluginItem | undefined, _index: number, data: IAgentPluginTemplateData): void {
244
for (const d of data.elementDisposables) {
245
d.dispose();
246
}
247
data.elementDisposables = [];
248
}
249
250
disposeTemplate(data: IAgentPluginTemplateData): void {
251
for (const d of data.disposables) {
252
d.dispose();
253
}
254
this.disposeElement(undefined, 0, data);
255
}
256
}
257
258
//#endregion
259
260
//#region List View
261
262
interface IAgentPluginsListViewOptions {
263
installedOnly?: boolean;
264
}
265
266
export class AgentPluginsListView extends AbstractExtensionsListView<IAgentPluginItem> {
267
268
private readonly actionStore = this._register(new DisposableStore());
269
private readonly queryCts = new MutableDisposable<CancellationTokenSource>();
270
private list: WorkbenchPagedList<IAgentPluginItem> | null = null;
271
private listContainer: HTMLElement | null = null;
272
private currentQuery = '@agentPlugins';
273
private readonly refreshOnPluginsChangedScheduler = this._register(new RunOnceScheduler(() => {
274
if (this.list) {
275
void this.show(this.currentQuery);
276
}
277
}, 0));
278
private bodyTemplate: {
279
messageContainer: HTMLElement;
280
messageBox: HTMLElement;
281
pluginsList: HTMLElement;
282
} | undefined;
283
284
constructor(
285
private readonly listOptions: IAgentPluginsListViewOptions,
286
options: IViewletViewOptions,
287
@IKeybindingService keybindingService: IKeybindingService,
288
@IContextMenuService contextMenuService: IContextMenuService,
289
@IInstantiationService instantiationService: IInstantiationService,
290
@IThemeService themeService: IThemeService,
291
@IHoverService hoverService: IHoverService,
292
@IConfigurationService configurationService: IConfigurationService,
293
@IContextKeyService contextKeyService: IContextKeyService,
294
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
295
@IOpenerService openerService: IOpenerService,
296
@IAgentPluginService private readonly agentPluginService: IAgentPluginService,
297
@IPluginMarketplaceService private readonly pluginMarketplaceService: IPluginMarketplaceService,
298
@IPluginInstallService private readonly pluginInstallService: IPluginInstallService,
299
@ILabelService private readonly labelService: ILabelService,
300
@IEditorService private readonly editorService: IEditorService,
301
) {
302
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);
303
304
this._register(autorun(reader => {
305
const plugins = this.agentPluginService.plugins.read(reader);
306
for (const plugin of plugins) {
307
plugin.enablement.read(reader);
308
}
309
if (this.list && this.isBodyVisible()) {
310
this.refreshOnPluginsChangedScheduler.schedule();
311
}
312
}));
313
314
this._register(this.pluginMarketplaceService.onDidChangeMarketplaces(() => {
315
if (this.list && this.isBodyVisible()) {
316
this.refreshOnPluginsChangedScheduler.schedule();
317
}
318
}));
319
}
320
321
protected override renderBody(container: HTMLElement): void {
322
super.renderBody(container);
323
324
const messageContainer = dom.append(container, dom.$('.message-container'));
325
const messageBox = dom.append(messageContainer, dom.$('.message'));
326
const pluginsList = dom.$('.agent-plugins-list');
327
328
this.bodyTemplate = { pluginsList, messageBox, messageContainer };
329
330
this.listContainer = dom.append(container, pluginsList);
331
this.list = this._register(this.instantiationService.createInstance(WorkbenchPagedList,
332
`${this.id}-Agent-Plugins`,
333
this.listContainer,
334
{
335
getHeight() { return 72; },
336
getTemplateId: () => AgentPluginRenderer.templateId,
337
},
338
[this.instantiationService.createInstance(AgentPluginRenderer)],
339
{
340
multipleSelectionSupport: false,
341
setRowLineHeight: false,
342
horizontalScrolling: false,
343
accessibilityProvider: {
344
getAriaLabel(item: IAgentPluginItem | null): string {
345
return item?.name ?? '';
346
},
347
getWidgetAriaLabel(): string {
348
return localize('agentPlugins', "Agent Plugins");
349
}
350
},
351
overrideStyles: getLocationBasedViewColors(this.viewDescriptorService.getViewLocationById(this.id)).listOverrideStyles,
352
}) as WorkbenchPagedList<IAgentPluginItem>);
353
354
this._register(this.list.onContextMenu(e => this.onContextMenu(e), this));
355
356
this._register(Event.debounce(Event.filter(this.list.onDidOpen, e => e.element !== null), (_, event) => event, 75, true)(options => {
357
this.editorService.openEditor(
358
this.instantiationService.createInstance(AgentPluginEditorInput, options.element!),
359
options.editorOptions
360
);
361
}));
362
}
363
364
private onContextMenu(e: IListContextMenuEvent<IAgentPluginItem>): void {
365
if (!e.element) {
366
return;
367
}
368
369
const actions = this.getContextMenuActions(e.element);
370
if (actions.length === 0) {
371
return;
372
}
373
374
this.contextMenuService.showContextMenu({
375
getAnchor: () => e.anchor,
376
getActions: () => actions,
377
});
378
}
379
380
private getContextMenuActions(item: IAgentPluginItem): IAction[] {
381
let actions: IAction[];
382
if (item.kind === AgentPluginItemKind.Installed) {
383
const groups = getInstalledPluginContextMenuActions(item.plugin, this.instantiationService);
384
actions = groups.flatMap(group => [...group, new Separator()]);
385
if (actions.length > 0) {
386
actions.pop();
387
}
388
} else {
389
actions = [];
390
if (item.readmeUri) {
391
actions.push(this.instantiationService.createInstance(OpenPluginReadmeAction, item.readmeUri));
392
}
393
actions.push(this.instantiationService.createInstance(InstallPluginAction, item));
394
}
395
396
this.actionStore.clear();
397
for (const action of actions) {
398
if (isDisposable(action)) {
399
this.actionStore.add(action);
400
}
401
}
402
403
return actions;
404
}
405
406
protected override layoutBody(height: number, width: number): void {
407
super.layoutBody(height, width);
408
this.list?.layout(height, width);
409
}
410
411
async show(query: string): Promise<IPagedModel<IAgentPluginItem>> {
412
this.currentQuery = query;
413
const stripped = query.replace(/@agentPlugins/i, '').trim();
414
const isRecommended = /^@recommended$/i.test(stripped);
415
const isInstalled = /(?:^|\s)@installed(?:\s|$)/i.test(stripped);
416
const text = isRecommended ? '' : stripped.replace(/(?:^|\s)@installed(?:\s|$)/gi, ' ').trim().toLowerCase();
417
418
let installed = this.queryInstalled();
419
if (text) {
420
installed = installed.filter(p =>
421
p.name.toLowerCase().includes(text) ||
422
p.description.toLowerCase().includes(text) ||
423
(p.marketplace ?? '').toLowerCase().includes(text)
424
);
425
}
426
427
// When @recommended, filter to plugins listed in workspace recommendations.
428
if (isRecommended) {
429
const recommended = this.pluginMarketplaceService.recommendedPlugins.get();
430
installed = installed.filter(p => {
431
const marketplace = p.plugin.fromMarketplace;
432
if (!marketplace) {
433
return false;
434
}
435
const key = `${marketplace.name}@${marketplace.marketplace}`;
436
return recommended.has(key);
437
});
438
}
439
440
let items: IAgentPluginItem[] = installed;
441
442
if (!this.listOptions.installedOnly && !isInstalled) {
443
const marketplacePlugins = await this.queryMarketplacePlugins();
444
let filteredMp = marketplacePlugins;
445
446
if (isRecommended) {
447
// When @recommended, filter marketplace plugins to those in recommendations.
448
const recommended = this.pluginMarketplaceService.recommendedPlugins.get();
449
filteredMp = filteredMp.filter(p => {
450
const key = `${p.name}@${p.marketplace}`;
451
return recommended.has(key);
452
});
453
} else {
454
const lowerText = text.toLowerCase();
455
filteredMp = filteredMp.filter(p => p.name.toLowerCase().includes(lowerText) || p.description.toLowerCase().includes(lowerText) || p.marketplace.toLowerCase().includes(lowerText));
456
}
457
458
const marketplace = filteredMp.map(marketplacePluginToItem);
459
460
// Filter out marketplace items that are already installed
461
const installedPaths = new Set(installed.map(i => i.plugin.uri.toString()));
462
const filteredMarketplace = marketplace.filter(m => {
463
const expectedUri = this.pluginInstallService.getPluginInstallUri({
464
name: m.name,
465
description: m.description,
466
version: '',
467
source: m.source,
468
sourceDescriptor: m.sourceDescriptor,
469
marketplace: m.marketplace,
470
marketplaceReference: m.marketplaceReference,
471
marketplaceType: m.marketplaceType,
472
});
473
return !installedPaths.has(expectedUri.toString());
474
});
475
476
items = [...installed, ...filteredMarketplace];
477
}
478
479
const model = new PagedModel(items);
480
if (this.list) {
481
this.list.model = model;
482
}
483
this.updateBody(model.length);
484
return model;
485
}
486
487
/**
488
* Builds the installed plugin list using only cached marketplace data
489
* (no IO). The cached data is populated by {@link fetchMarketplacePlugins}
490
* and exposed via the {@link IPluginMarketplaceService.lastFetchedPlugins}
491
* observable, which the view's autorun subscribes to for reactivity.
492
*/
493
private queryInstalled(): IInstalledPluginItem[] {
494
const marketplaceObs = derived(reader => {
495
const cachedMarketplace = this.pluginMarketplaceService.lastFetchedPlugins.read(reader);
496
const marketplaceByKey = new Map<string, IMarketplacePlugin>();
497
for (const mp of cachedMarketplace) {
498
marketplaceByKey.set(`${mp.marketplaceReference.canonicalId}::${mp.name}`, mp);
499
}
500
501
502
// Read fresh installed plugin metadata from the store (not from
503
// IAgentPlugin.fromMarketplace which may be stale after an update).
504
const installedByUri = new Map<string, IMarketplacePlugin>();
505
for (const entry of this.pluginMarketplaceService.installedPlugins.read(reader)) {
506
installedByUri.set(entry.pluginUri.toString(), entry.plugin);
507
}
508
509
return { marketplaceByKey, installedByUri };
510
});
511
512
513
const plugins = this.agentPluginService.plugins.get();
514
return plugins.map(p => {
515
const isOutdated = derived(reader => {
516
const { marketplaceByKey, installedByUri } = marketplaceObs.read(reader);
517
const storedPlugin = installedByUri.get(p.uri.toString()) ?? p.fromMarketplace;
518
if (storedPlugin) {
519
const key = `${storedPlugin.marketplaceReference.canonicalId}::${storedPlugin.name}`;
520
const live = marketplaceByKey.get(key);
521
if (live && hasSourceChanged(storedPlugin.sourceDescriptor, live.sourceDescriptor)) {
522
return live;
523
}
524
}
525
526
return undefined;
527
});
528
return installedPluginToItem(p, this.labelService, isOutdated);
529
});
530
}
531
532
private async queryMarketplacePlugins(): Promise<IMarketplacePlugin[]> {
533
this.queryCts.value?.cancel();
534
const cts = new CancellationTokenSource();
535
this.queryCts.value = cts;
536
537
try {
538
return await this.pluginMarketplaceService.fetchMarketplacePlugins(cts.token);
539
} catch {
540
return [];
541
}
542
}
543
544
private updateBody(count: number): void {
545
if (this.bodyTemplate) {
546
this.bodyTemplate.pluginsList.classList.toggle('hidden', count === 0);
547
this.bodyTemplate.messageContainer.classList.toggle('hidden', count > 0);
548
if (count === 0 && this.isBodyVisible()) {
549
this.bodyTemplate.messageBox.textContent = localize('noAgentPlugins', "No agent plugins found.");
550
}
551
}
552
}
553
}
554
555
//#endregion
556
557
//#region Browse command
558
559
class AgentPluginsBrowseCommand extends Action2 {
560
constructor() {
561
super({
562
id: 'workbench.agentPlugins.browse',
563
title: localize2('agentPlugins.browse', "Agent Plugins"),
564
tooltip: localize2('agentPlugins.browse.tooltip', "Browse Agent Plugins"),
565
icon: Codicon.search,
566
precondition: ContextKeyExpr.and(ChatContextKeys.Setup.hidden.negate(), ChatContextKeys.Setup.disabledInWorkspace.negate()),
567
menu: [{
568
id: extensionsFilterSubMenu,
569
group: '1_predefined',
570
order: 2,
571
when: ContextKeyExpr.and(ChatContextKeys.Setup.hidden.negate(), ChatContextKeys.Setup.disabledInWorkspace.negate()),
572
}, {
573
id: MenuId.ViewTitle,
574
when: ContextKeyExpr.and(ContextKeyExpr.equals('view', InstalledAgentPluginsViewId), ChatContextKeys.Setup.hidden.negate(), ChatContextKeys.Setup.disabledInWorkspace.negate()),
575
group: 'navigation',
576
}],
577
});
578
}
579
580
async run(accessor: ServicesAccessor) {
581
accessor.get(IExtensionsWorkbenchService).openSearch('@agentPlugins ');
582
}
583
}
584
585
class CheckForPluginUpdatesCommand extends Action2 {
586
constructor() {
587
super({
588
id: 'workbench.agentPlugins.checkForUpdates',
589
title: localize2('agentPlugins.checkForUpdates', "Update Plugins"),
590
category: localize2('chat.category', "Chat"),
591
precondition: ChatContextKeys.enabled,
592
f1: true,
593
});
594
}
595
596
async run(accessor: ServicesAccessor) {
597
await accessor.get(IPluginInstallService).updateAllPlugins({}, CancellationToken.None);
598
}
599
}
600
601
class ForceUpdatePluginsCommand extends Action2 {
602
constructor() {
603
super({
604
id: 'workbench.agentPlugins.forceUpdate',
605
title: localize2('agentPlugins.forceUpdate', "Update Plugins (Force)"),
606
category: localize2('chat.category', "Chat"),
607
precondition: ChatContextKeys.enabled,
608
f1: true,
609
});
610
}
611
612
async run(accessor: ServicesAccessor) {
613
await accessor.get(IPluginInstallService).updateAllPlugins({ force: true }, CancellationToken.None);
614
}
615
}
616
617
//#endregion
618
//#region Views contribution
619
620
export class AgentPluginsViewsContribution extends Disposable implements IWorkbenchContribution {
621
622
static ID = 'workbench.chat.agentPlugins.views.contribution';
623
624
constructor(
625
@IContextKeyService contextKeyService: IContextKeyService,
626
@IAgentPluginService agentPluginService: IAgentPluginService,
627
) {
628
super();
629
630
const hasInstalledKey = HasInstalledAgentPluginsContext.bindTo(contextKeyService);
631
this._register(autorun(reader => {
632
hasInstalledKey.set(agentPluginService.plugins.read(reader).length > 0);
633
}));
634
635
registerAction2(AgentPluginsBrowseCommand);
636
registerAction2(CheckForPluginUpdatesCommand);
637
registerAction2(ForceUpdatePluginsCommand);
638
639
Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews([
640
{
641
id: InstalledAgentPluginsViewId,
642
name: localize2('agent-plugins-installed', "Agent Plugins - Installed"),
643
ctorDescriptor: new SyncDescriptor(AgentPluginsListView, [{ installedOnly: true }]),
644
when: ContextKeyExpr.and(DefaultViewsContext, HasInstalledAgentPluginsContext, ChatContextKeys.Setup.hidden.negate()),
645
weight: 30,
646
order: 5,
647
canToggleVisibility: true,
648
},
649
{
650
id: 'workbench.views.agentPlugins.default.marketplace',
651
name: localize2('agent-plugins', "Agent Plugins"),
652
ctorDescriptor: new SyncDescriptor(AgentPluginsListView, [{}]),
653
when: ContextKeyExpr.and(DefaultViewsContext, HasInstalledAgentPluginsContext.toNegated(), ChatContextKeys.Setup.hidden.negate()),
654
weight: 30,
655
order: 5,
656
canToggleVisibility: true,
657
hideByDefault: true,
658
},
659
{
660
id: 'workbench.views.agentPlugins.marketplace',
661
name: localize2('agent-plugins', "Agent Plugins"),
662
ctorDescriptor: new SyncDescriptor(AgentPluginsListView, [{}]),
663
when: ContextKeyExpr.and(SearchAgentPluginsContext, ChatContextKeys.Setup.hidden.negate()),
664
},
665
], VIEW_CONTAINER);
666
}
667
}
668
669
//#endregion
670
671