Skip to content

Commit bdae787

Browse files
authored
chatSessions when clause (microsoft#257900)
chatSessions when clause
1 parent b8acb41 commit bdae787

File tree

3 files changed

+141
-14
lines changed

3 files changed

+141
-14
lines changed

src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts

Lines changed: 135 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Codicon } from '../../../../base/common/codicons.js';
88
import { Emitter, Event } from '../../../../base/common/event.js';
99
import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js';
1010
import { localize } from '../../../../nls.js';
11+
import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
1112
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
1213
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
1314
import { ILogService } from '../../../../platform/log/common/log.js';
@@ -104,11 +105,13 @@ class ContributedChatSessionData implements IDisposable {
104105
readonly session: ChatSession,
105106
readonly chatSessionType: string,
106107
readonly id: string,
107-
onWillDispose: (session: ChatSession, chatSessionType: string, id: string) => void
108+
private readonly onWillDispose: (session: ChatSession, chatSessionType: string, id: string) => void
108109
) {
109110
}
110111

111112
dispose(): void {
113+
this.onWillDispose(this.session, this.chatSessionType, this.id);
114+
this.session.dispose();
112115
}
113116
}
114117

@@ -121,33 +124,139 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ
121124
readonly onDidChangeItemsProviders: Event<IChatSessionItemProvider> = this._onDidChangeItemsProviders.event;
122125
private readonly _contentProviders: Map<string, IChatSessionContentProvider> = new Map();
123126
private readonly _contributions: Map<string, IChatSessionsExtensionPoint> = new Map();
127+
private readonly _dynamicAgentDisposables: Map<string, IDisposable> = new Map();
128+
private readonly _contextKeys = new Set<string>();
124129
private readonly _onDidChangeSessionItems = this._register(new Emitter<string>());
125130
readonly onDidChangeSessionItems: Event<string> = this._onDidChangeSessionItems.event;
131+
private readonly _onDidChangeAvailability = this._register(new Emitter<void>());
132+
readonly onDidChangeAvailability: Event<void> = this._onDidChangeAvailability.event;
126133

127134
constructor(
128135
@ILogService private readonly _logService: ILogService,
129136
@IInstantiationService private readonly _instantiationService: IInstantiationService,
130137
@IChatAgentService private readonly _chatAgentService: IChatAgentService,
131138
@IExtensionService private readonly _extensionService: IExtensionService,
139+
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
132140
) {
133141
super();
142+
143+
// Listen for context changes and re-evaluate contributions
144+
this._register(Event.filter(this._contextKeyService.onDidChangeContext, e => e.affectsSome(this._contextKeys))(() => {
145+
this._evaluateAvailability();
146+
}));
134147
}
135148
public registerContribution(contribution: IChatSessionsExtensionPoint): IDisposable {
136149
if (this._contributions.has(contribution.id)) {
137150
this._logService.warn(`Chat session contribution with id '${contribution.id}' is already registered.`);
138151
return { dispose: () => { } };
139152
}
153+
154+
// Track context keys from the when condition
155+
if (contribution.when) {
156+
const whenExpr = ContextKeyExpr.deserialize(contribution.when);
157+
if (whenExpr) {
158+
for (const key of whenExpr.keys()) {
159+
this._contextKeys.add(key);
160+
}
161+
}
162+
}
163+
140164
this._contributions.set(contribution.id, contribution);
141-
const dynamicAgentDisposable = this.registerDynamicAgent(contribution);
165+
166+
// Register dynamic agent if the when condition is satisfied
167+
this._registerDynamicAgentIfAvailable(contribution);
168+
142169
return {
143170
dispose: () => {
144171
this._contributions.delete(contribution.id);
145-
dynamicAgentDisposable.dispose();
172+
this._disposeDynamicAgent(contribution.id);
146173
}
147174
};
148175
}
149176

150-
private registerDynamicAgent(contribution: IChatSessionsExtensionPoint): IDisposable {
177+
private _isContributionAvailable(contribution: IChatSessionsExtensionPoint): boolean {
178+
if (!contribution.when) {
179+
return true;
180+
}
181+
182+
const whenExpr = ContextKeyExpr.deserialize(contribution.when);
183+
return !whenExpr || this._contextKeyService.contextMatchesRules(whenExpr);
184+
}
185+
186+
private _registerDynamicAgentIfAvailable(contribution: IChatSessionsExtensionPoint): void {
187+
if (this._isContributionAvailable(contribution)) {
188+
const disposable = this._registerDynamicAgent(contribution);
189+
this._dynamicAgentDisposables.set(contribution.id, disposable);
190+
}
191+
}
192+
193+
private _disposeDynamicAgent(contributionId: string): void {
194+
const disposable = this._dynamicAgentDisposables.get(contributionId);
195+
if (disposable) {
196+
disposable.dispose();
197+
this._dynamicAgentDisposables.delete(contributionId);
198+
}
199+
}
200+
201+
private _evaluateAvailability(): void {
202+
let hasChanges = false;
203+
204+
for (const contribution of this._contributions.values()) {
205+
const isCurrentlyRegistered = this._dynamicAgentDisposables.has(contribution.id);
206+
const shouldBeRegistered = this._isContributionAvailable(contribution);
207+
208+
if (isCurrentlyRegistered && !shouldBeRegistered) {
209+
// Should be unregistered
210+
this._disposeDynamicAgent(contribution.id);
211+
// Also dispose any cached sessions for this contribution
212+
this._disposeSessionsForContribution(contribution.id);
213+
hasChanges = true;
214+
} else if (!isCurrentlyRegistered && shouldBeRegistered) {
215+
// Should be registered
216+
this._registerDynamicAgentIfAvailable(contribution);
217+
hasChanges = true;
218+
}
219+
}
220+
221+
// Fire events to notify UI about provider availability changes
222+
if (hasChanges) {
223+
// Fire the main availability change event
224+
this._onDidChangeAvailability.fire();
225+
226+
// Notify that the list of available item providers has changed
227+
for (const provider of this._itemsProviders.values()) {
228+
this._onDidChangeItemsProviders.fire(provider);
229+
}
230+
231+
// Notify about session items changes for all chat session types
232+
for (const contribution of this._contributions.values()) {
233+
this._onDidChangeSessionItems.fire(contribution.id);
234+
}
235+
}
236+
}
237+
238+
private _disposeSessionsForContribution(contributionId: string): void {
239+
// Find and dispose all sessions that belong to this contribution
240+
const sessionsToDispose: string[] = [];
241+
for (const [sessionKey, sessionData] of this._sessions) {
242+
if (sessionData.chatSessionType === contributionId) {
243+
sessionsToDispose.push(sessionKey);
244+
}
245+
}
246+
247+
if (sessionsToDispose.length > 0) {
248+
this._logService.info(`Disposing ${sessionsToDispose.length} cached sessions for contribution '${contributionId}' due to when clause change`);
249+
}
250+
251+
for (const sessionKey of sessionsToDispose) {
252+
const sessionData = this._sessions.get(sessionKey);
253+
if (sessionData) {
254+
sessionData.dispose(); // This will call _onWillDisposeSession and clean up
255+
}
256+
}
257+
}
258+
259+
private _registerDynamicAgent(contribution: IChatSessionsExtensionPoint): IDisposable {
151260
const { id, name, displayName, description, extensionDescription } = contribution;
152261
const { identifier: extensionId, name: extensionName, displayName: extensionDisplayName, publisher: extensionPublisherId } = extensionDescription;
153262
const agentData: IChatAgentData = {
@@ -178,14 +287,26 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ
178287
}
179288

180289
getChatSessionContributions(): IChatSessionsExtensionPoint[] {
181-
return Array.from(this._contributions.values());
290+
return Array.from(this._contributions.values()).filter(contribution =>
291+
this._isContributionAvailable(contribution)
292+
);
182293
}
183294

184295
getChatSessionItemProviders(): IChatSessionItemProvider[] {
185-
return [...this._itemsProviders.values()];
296+
return [...this._itemsProviders.values()].filter(provider => {
297+
// Check if the provider's corresponding contribution is available
298+
const contribution = this._contributions.get(provider.chatSessionType);
299+
return !contribution || this._isContributionAvailable(contribution);
300+
});
186301
}
187302

188303
async canResolveItemProvider(chatViewType: string) {
304+
// First check if the contribution is available based on its when clause
305+
const contribution = this._contributions.get(chatViewType);
306+
if (contribution && !this._isContributionAvailable(contribution)) {
307+
return false;
308+
}
309+
189310
if (this._itemsProviders.has(chatViewType)) {
190311
return true;
191312
}
@@ -201,6 +322,12 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ
201322
}
202323

203324
async canResolveContentProvider(chatViewType: string) {
325+
// First check if the contribution is available based on its when clause
326+
const contribution = this._contributions.get(chatViewType);
327+
if (contribution && !this._isContributionAvailable(contribution)) {
328+
return false;
329+
}
330+
204331
if (this._contentProviders.has(chatViewType)) {
205332
return true;
206333
}
@@ -283,15 +410,9 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ
283410
this._sessions.set(sessionKey, sessionData);
284411

285412
return session;
286-
}
287-
288-
private _onWillDisposeSession(session: ChatSession, chatSessionType: string, id: string): void {
413+
} private _onWillDisposeSession(session: ChatSession, chatSessionType: string, id: string): void {
289414
const sessionKey = `${chatSessionType}_${id}`;
290-
const sessionData = this._sessions.get(sessionKey);
291-
if (sessionData) {
292-
this._sessions.delete(sessionKey);
293-
sessionData.dispose();
294-
}
415+
this._sessions.delete(sessionKey);
295416
}
296417

297418
public get hasChatSessionItemProviders(): boolean {

src/vs/workbench/contrib/chat/browser/chatSessions.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,11 @@ class ChatSessionsViewPaneContainer extends ViewPaneContainer {
379379
this._register(this.chatSessionsService.onDidChangeSessionItems((chatSessionType) => {
380380
this.refreshProviderTree(chatSessionType);
381381
}));
382+
383+
// Listen for contribution availability changes and update view registration
384+
this._register(this.chatSessionsService.onDidChangeAvailability(() => {
385+
this.updateViewRegistration();
386+
}));
382387
}
383388

384389
override getTitle(): string {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export interface IChatSessionsService {
6464
readonly _serviceBrand: undefined;
6565
readonly onDidChangeItemsProviders: Event<IChatSessionItemProvider>;
6666
readonly onDidChangeSessionItems: Event<string>;
67+
readonly onDidChangeAvailability: Event<void>;
6768
registerContribution(contribution: IChatSessionsExtensionPoint): IDisposable;
6869
getChatSessionContributions(): IChatSessionsExtensionPoint[];
6970
canResolveItemProvider(chatSessionType: string): Promise<boolean>;

0 commit comments

Comments
 (0)