@@ -8,6 +8,7 @@ import { Codicon } from '../../../../base/common/codicons.js';
8
8
import { Emitter , Event } from '../../../../base/common/event.js' ;
9
9
import { Disposable , IDisposable } from '../../../../base/common/lifecycle.js' ;
10
10
import { localize } from '../../../../nls.js' ;
11
+ import { ContextKeyExpr , IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js' ;
11
12
import { InstantiationType , registerSingleton } from '../../../../platform/instantiation/common/extensions.js' ;
12
13
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js' ;
13
14
import { ILogService } from '../../../../platform/log/common/log.js' ;
@@ -104,11 +105,13 @@ class ContributedChatSessionData implements IDisposable {
104
105
readonly session : ChatSession ,
105
106
readonly chatSessionType : string ,
106
107
readonly id : string ,
107
- onWillDispose : ( session : ChatSession , chatSessionType : string , id : string ) => void
108
+ private readonly onWillDispose : ( session : ChatSession , chatSessionType : string , id : string ) => void
108
109
) {
109
110
}
110
111
111
112
dispose ( ) : void {
113
+ this . onWillDispose ( this . session , this . chatSessionType , this . id ) ;
114
+ this . session . dispose ( ) ;
112
115
}
113
116
}
114
117
@@ -121,33 +124,139 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ
121
124
readonly onDidChangeItemsProviders : Event < IChatSessionItemProvider > = this . _onDidChangeItemsProviders . event ;
122
125
private readonly _contentProviders : Map < string , IChatSessionContentProvider > = new Map ( ) ;
123
126
private readonly _contributions : Map < string , IChatSessionsExtensionPoint > = new Map ( ) ;
127
+ private readonly _dynamicAgentDisposables : Map < string , IDisposable > = new Map ( ) ;
128
+ private readonly _contextKeys = new Set < string > ( ) ;
124
129
private readonly _onDidChangeSessionItems = this . _register ( new Emitter < string > ( ) ) ;
125
130
readonly onDidChangeSessionItems : Event < string > = this . _onDidChangeSessionItems . event ;
131
+ private readonly _onDidChangeAvailability = this . _register ( new Emitter < void > ( ) ) ;
132
+ readonly onDidChangeAvailability : Event < void > = this . _onDidChangeAvailability . event ;
126
133
127
134
constructor (
128
135
@ILogService private readonly _logService : ILogService ,
129
136
@IInstantiationService private readonly _instantiationService : IInstantiationService ,
130
137
@IChatAgentService private readonly _chatAgentService : IChatAgentService ,
131
138
@IExtensionService private readonly _extensionService : IExtensionService ,
139
+ @IContextKeyService private readonly _contextKeyService : IContextKeyService ,
132
140
) {
133
141
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
+ } ) ) ;
134
147
}
135
148
public registerContribution ( contribution : IChatSessionsExtensionPoint ) : IDisposable {
136
149
if ( this . _contributions . has ( contribution . id ) ) {
137
150
this . _logService . warn ( `Chat session contribution with id '${ contribution . id } ' is already registered.` ) ;
138
151
return { dispose : ( ) => { } } ;
139
152
}
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
+
140
164
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
+
142
169
return {
143
170
dispose : ( ) => {
144
171
this . _contributions . delete ( contribution . id ) ;
145
- dynamicAgentDisposable . dispose ( ) ;
172
+ this . _disposeDynamicAgent ( contribution . id ) ;
146
173
}
147
174
} ;
148
175
}
149
176
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 {
151
260
const { id, name, displayName, description, extensionDescription } = contribution ;
152
261
const { identifier : extensionId , name : extensionName , displayName : extensionDisplayName , publisher : extensionPublisherId } = extensionDescription ;
153
262
const agentData : IChatAgentData = {
@@ -178,14 +287,26 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ
178
287
}
179
288
180
289
getChatSessionContributions ( ) : IChatSessionsExtensionPoint [ ] {
181
- return Array . from ( this . _contributions . values ( ) ) ;
290
+ return Array . from ( this . _contributions . values ( ) ) . filter ( contribution =>
291
+ this . _isContributionAvailable ( contribution )
292
+ ) ;
182
293
}
183
294
184
295
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
+ } ) ;
186
301
}
187
302
188
303
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
+
189
310
if ( this . _itemsProviders . has ( chatViewType ) ) {
190
311
return true ;
191
312
}
@@ -201,6 +322,12 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ
201
322
}
202
323
203
324
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
+
204
331
if ( this . _contentProviders . has ( chatViewType ) ) {
205
332
return true ;
206
333
}
@@ -283,15 +410,9 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ
283
410
this . _sessions . set ( sessionKey , sessionData ) ;
284
411
285
412
return session ;
286
- }
287
-
288
- private _onWillDisposeSession ( session : ChatSession , chatSessionType : string , id : string ) : void {
413
+ } private _onWillDisposeSession ( session : ChatSession , chatSessionType : string , id : string ) : void {
289
414
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 ) ;
295
416
}
296
417
297
418
public get hasChatSessionItemProviders ( ) : boolean {
0 commit comments