Skip to content

Commit aa876a2

Browse files
authored
Merge pull request microsoft#257503 from microsoft/rebornix/equivalent-peacock
chat session providers static contribution
2 parents a020255 + 779947b commit aa876a2

File tree

5 files changed

+162
-30
lines changed

5 files changed

+162
-30
lines changed

src/vs/workbench/api/browser/mainThreadChatSessions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat
2929
chatSessionType,
3030
provideChatSessionItems: (token) => this._provideChatSessionItems(handle, token)
3131
};
32-
this._registrations.set(handle, this._chatSessionsService.registerChatSessionItemProvider(handle, provider));
32+
this._registrations.set(handle, this._chatSessionsService.registerChatSessionItemProvider(provider));
3333
}
3434

3535
private async _provideChatSessionItems(handle: number, token: CancellationToken): Promise<IChatSessionItem[]> {

src/vs/workbench/contrib/chat/browser/actions/chatActions.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ import { ChatEntitlement, IChatEntitlementService } from '../../common/chatEntit
5757
import { ChatMode, IChatMode, IChatModeService } from '../../common/chatModes.js';
5858
import { extractAgentAndCommand } from '../../common/chatParserTypes.js';
5959
import { IChatDetail, IChatService } from '../../common/chatService.js';
60-
import { IChatSessionsService } from '../../common/chatSessionsService.js';
60+
import { IChatSessionItem, IChatSessionsService } from '../../common/chatSessionsService.js';
6161
import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM } from '../../common/chatViewModel.js';
6262
import { IChatWidgetHistoryService } from '../../common/chatWidgetHistoryService.js';
6363
import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../common/constants.js';
@@ -565,13 +565,18 @@ export function registerChatActions() {
565565
const cancellationToken = new CancellationTokenSource();
566566

567567
try {
568-
const sessions = await chatSessionsService.provideChatSessionItems(cancellationToken.token);
568+
const providers = chatSessionsService.getChatSessionProviders();
569+
const providerNSessions: { providerType: string; session: IChatSessionItem }[] = [];
569570

570-
for (const session of sessions) {
571+
for (const provider of providers) {
572+
const sessions = await chatSessionsService.provideChatSessionItems(provider.id, cancellationToken.token);
573+
providerNSessions.push(...sessions.map(session => ({ providerType: provider.id, session })));
574+
}
575+
576+
for (const session of providerNSessions) {
571577
const sessionContent = session.session;
572-
const provider = session.provider;
573578

574-
const ckey = contextKeyService.createKey('chatSessionType', provider.chatSessionType);
579+
const ckey = contextKeyService.createKey('chatSessionType', session.providerType);
575580
const actions = menuService.getMenuActions(MenuId.ChatSessionsMenu, contextKeyService);
576581
const menuActions = getContextMenuActions(actions, 'navigation');
577582
ckey.reset();
@@ -622,7 +627,7 @@ export function registerChatActions() {
622627
currentPicks.push(...agentPicks);
623628

624629
// Add "Show more..." if needed and not showing all agents
625-
if (!showAllAgents && sessions.length > 5) {
630+
if (!showAllAgents && providerNSessions.length > 5) {
626631
currentPicks.push({
627632
label: localize('chat.history.showMoreAgents', 'Show more...'),
628633
description: '',
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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 { Disposable } from '../../../../base/common/lifecycle.js';
7+
import { localize } from '../../../../nls.js';
8+
import { ILogService } from '../../../../platform/log/common/log.js';
9+
import { Registry } from '../../../../platform/registry/common/platform.js';
10+
import { IWorkbenchContribution, Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from '../../../common/contributions.js';
11+
import { isProposedApiEnabled } from '../../../services/extensions/common/extensions.js';
12+
import { ExtensionsRegistry } from '../../../services/extensions/common/extensionsRegistry.js';
13+
import { LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js';
14+
import { IChatSessionsExtensionPoint, IChatSessionsService } from '../common/chatSessionsService.js';
15+
16+
const extensionPoint = ExtensionsRegistry.registerExtensionPoint<IChatSessionsExtensionPoint[]>({
17+
extensionPoint: 'chatSessions',
18+
jsonSchema: {
19+
description: localize('chatSessionsExtPoint', 'Contributes chat session integrations to the chat widget.'),
20+
type: 'array',
21+
items: {
22+
type: 'object',
23+
properties: {
24+
id: {
25+
description: localize('chatSessionsExtPoint.id', 'A unique identifier for this item.'),
26+
type: 'string',
27+
},
28+
name: {
29+
description: localize('chatSessionsExtPoint.name', 'Name shown in the chat widget. (eg: @agent)'),
30+
type: 'string',
31+
},
32+
displayName: {
33+
description: localize('chatSessionsExtPoint.displayName', 'A longer name for this item which is used for display in menus.'),
34+
type: 'string',
35+
},
36+
description: {
37+
description: localize('chatSessionsExtPoint.description', 'Description of the chat session for use in menus and tooltips.'),
38+
type: 'string'
39+
},
40+
when: {
41+
description: localize('chatSessionsExtPoint.when', 'Condition which must be true to show this item.'),
42+
type: 'string'
43+
}
44+
},
45+
required: ['id', 'name', 'displayName', 'description'],
46+
}
47+
},
48+
activationEventsGenerator: (contribs, results) => {
49+
for (const contrib of contribs) {
50+
results.push(`onChatSession:${contrib.id}`);
51+
}
52+
}
53+
});
54+
55+
export class ChatSessionsContribution extends Disposable implements IWorkbenchContribution {
56+
constructor(
57+
@ILogService private readonly logService: ILogService,
58+
@IChatSessionsService private readonly chatSessionsService: IChatSessionsService,
59+
) {
60+
super();
61+
62+
extensionPoint.setHandler(extensions => {
63+
for (const ext of extensions) {
64+
if (!isProposedApiEnabled(ext.description, 'chatSessionsProvider')) {
65+
continue;
66+
}
67+
if (!Array.isArray(ext.value)) {
68+
continue;
69+
}
70+
for (const contribution of ext.value) {
71+
const c: IChatSessionsExtensionPoint = {
72+
id: contribution.id,
73+
name: contribution.name,
74+
displayName: contribution.displayName,
75+
description: contribution.description,
76+
when: contribution.when,
77+
};
78+
this.logService.info(`Registering chat session from extension contribution: ${c.displayName} (id='${c.id}' name='${c.name}')`);
79+
this._register(this.chatSessionsService.registerContribution(c)); // TODO: Is it for contribution to own this? I think not
80+
}
81+
}
82+
});
83+
}
84+
}
85+
86+
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
87+
workbenchRegistry.registerWorkbenchContribution(ChatSessionsContribution, LifecyclePhase.Restored);

src/vs/workbench/contrib/chat/common/chatSessionsService.ts

Lines changed: 60 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta
1010
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
1111
import { URI } from '../../../../base/common/uri.js';
1212
import { ThemeIcon } from '../../../../base/common/themables.js';
13+
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
14+
15+
export interface IChatSessionsExtensionPoint {
16+
id: string;
17+
name: string;
18+
displayName: string;
19+
description: string;
20+
when?: string;
21+
}
1322

1423
export interface IChatSessionItem {
1524
id: string;
@@ -27,55 +36,83 @@ export interface IChatSessionItemProvider {
2736

2837
export interface IChatSessionsService {
2938
readonly _serviceBrand: undefined;
30-
registerChatSessionItemProvider(handle: number, provider: IChatSessionItemProvider): IDisposable;
39+
registerContribution(contribution: IChatSessionsExtensionPoint): IDisposable;
40+
getChatSessionProviders(): IChatSessionsExtensionPoint[];
41+
registerChatSessionItemProvider(provider: IChatSessionItemProvider): IDisposable;
3142
hasChatSessionItemProviders: boolean;
32-
provideChatSessionItems(token: CancellationToken): Promise<{ provider: IChatSessionItemProvider; session: IChatSessionItem }[]>;
43+
provideChatSessionItems(chatSessionType: string, token: CancellationToken): Promise<IChatSessionItem[]>;
3344
}
3445

3546
export const IChatSessionsService = createDecorator<IChatSessionsService>('chatSessionsService');
3647

3748
export class ChatSessionsService extends Disposable implements IChatSessionsService {
3849
readonly _serviceBrand: undefined;
39-
private _providers: Map<number, IChatSessionItemProvider> = new Map();
50+
private _itemsProviders: Map<string, IChatSessionItemProvider> = new Map();
51+
private _contributions: Map<string, IChatSessionsExtensionPoint> = new Map();
4052

4153
constructor(
54+
@IExtensionService private readonly _extensionService: IExtensionService,
4255
@ILogService private readonly _logService: ILogService,
4356
) {
4457
super();
4558
}
4659

47-
public async provideChatSessionItems(token: CancellationToken): Promise<{ provider: IChatSessionItemProvider; session: IChatSessionItem }[]> {
48-
const results: { provider: IChatSessionItemProvider; session: IChatSessionItem }[] = [];
49-
50-
// Iterate through all registered providers and collect their results
51-
for (const [handle, provider] of this._providers) {
52-
try {
53-
if (provider.provideChatSessionItems) {
54-
const sessions = await provider.provideChatSessionItems(token);
55-
results.push(...sessions.map(session => ({ provider, session })));
56-
}
57-
} catch (error) {
58-
this._logService.error(`Error getting chat sessions from provider ${handle}:`, error);
59-
}
60-
if (token.isCancellationRequested) {
61-
break;
60+
public registerContribution(contribution: IChatSessionsExtensionPoint): IDisposable {
61+
if (this._contributions.has(contribution.id)) {
62+
this._logService.warn(`Chat session contribution with id '${contribution.id}' is already registered.`);
63+
return { dispose: () => { } };
64+
}
65+
this._contributions.set(contribution.id, contribution);
66+
// const dynamicAgentDisposable = this.registerDynamicAgent(contribution);
67+
return {
68+
dispose: () => {
69+
this._contributions.delete(contribution.id);
70+
// dynamicAgentDisposable.dispose();
6271
}
72+
};
73+
}
74+
75+
getChatSessionProviders(): IChatSessionsExtensionPoint[] {
76+
return Array.from(this._contributions.values());
77+
}
78+
79+
async canResolve(chatViewType: string) {
80+
if (this._itemsProviders.has(chatViewType)) {
81+
return true;
82+
}
83+
84+
await this._extensionService.whenInstalledExtensionsRegistered();
85+
await this._extensionService.activateByEvent(`onChatSession:${chatViewType}`);
86+
87+
return this._itemsProviders.has(chatViewType);
88+
}
89+
90+
public async provideChatSessionItems(chatSessionType: string, token: CancellationToken): Promise<IChatSessionItem[]> {
91+
if (!(await this.canResolve(chatSessionType))) {
92+
throw Error(`Can not find provider for ${chatSessionType}`);
93+
}
94+
95+
const provider = this._itemsProviders.get(chatSessionType);
96+
97+
if (provider?.provideChatSessionItems) {
98+
const sessions = await provider.provideChatSessionItems(token);
99+
return sessions;
63100
}
64101

65-
return results;
102+
return [];
66103
}
67104

68-
public registerChatSessionItemProvider(handle: number, provider: IChatSessionItemProvider): IDisposable {
69-
this._providers.set(handle, provider);
105+
public registerChatSessionItemProvider(provider: IChatSessionItemProvider): IDisposable {
106+
this._itemsProviders.set(provider.chatSessionType, provider);
70107
return {
71108
dispose: () => {
72-
this._providers.delete(handle);
109+
this._itemsProviders.delete(provider.chatSessionType);
73110
}
74111
};
75112
}
76113

77114
public get hasChatSessionItemProviders(): boolean {
78-
return this._providers.size > 0;
115+
return this._itemsProviders.size > 0;
79116
}
80117
}
81118

src/vs/workbench/workbench.common.main.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,9 @@ import './contrib/editSessions/browser/editSessions.contribution.js';
378378
// Remote Coding Agents
379379
import './contrib/remoteCodingAgents/browser/remoteCodingAgents.contribution.js';
380380

381+
// Chat Sessions
382+
import './contrib/chat/browser/chatSessions.contribution.js';
383+
381384
// Code Actions
382385
import './contrib/codeActions/browser/codeActions.contribution.js';
383386

0 commit comments

Comments
 (0)