@@ -55,7 +55,7 @@ interface IBucketTreeItem extends IToolTreeItem {
5555 toolset ?: ToolSet ; // For MCP servers where the bucket represents the ToolSet - mutable
5656 readonly status ?: string ;
5757 readonly children : AnyTreeItem [ ] ;
58- checked : boolean | 'partial' ;
58+ checked : boolean | 'partial' | undefined ;
5959}
6060
6161/**
@@ -65,7 +65,7 @@ interface IBucketTreeItem extends IToolTreeItem {
6565interface IToolSetTreeItem extends IToolTreeItem {
6666 readonly itemType : 'toolset' ;
6767 readonly toolset : ToolSet ;
68- readonly children : AnyTreeItem [ ] ;
68+ children : AnyTreeItem [ ] | undefined ;
6969 checked : boolean | 'partial' ;
7070}
7171
@@ -143,6 +143,31 @@ function createToolTreeItemFromData(tool: IToolData, checked: boolean): IToolTre
143143 } ;
144144}
145145
146+ function createToolSetTreeItem ( toolset : ToolSet , checked : boolean , editorService : IEditorService ) : IToolSetTreeItem {
147+ const iconProps = mapIconToTreeItem ( toolset . icon ) ;
148+ const buttons = [ ] ;
149+ if ( toolset . source . type === 'user' ) {
150+ const resource = toolset . source . file ;
151+ buttons . push ( {
152+ iconClass : ThemeIcon . asClassName ( Codicon . edit ) ,
153+ tooltip : localize ( 'editUserBucket' , "Edit Tool Set" ) ,
154+ action : ( ) => editorService . openEditor ( { resource } )
155+ } ) ;
156+ }
157+ return {
158+ itemType : 'toolset' ,
159+ toolset,
160+ buttons,
161+ id : toolset . id ,
162+ label : toolset . referenceName ,
163+ description : toolset . description ,
164+ checked,
165+ children : undefined ,
166+ collapsed : true ,
167+ ...iconProps
168+ } ;
169+ }
170+
146171/**
147172 * New QuickTree implementation of the tools picker.
148173 * Uses IQuickTree to provide a true hierarchical tree structure with:
@@ -200,162 +225,161 @@ export async function showToolsPicker(
200225 const treeItems : AnyTreeItem [ ] = [ ] ;
201226 const bucketMap = new Map < string , IBucketTreeItem > ( ) ;
202227
203- // Process entries and organize into buckets
204- for ( const [ toolSetOrTool , picked ] of toolsEntries ) {
205- let bucketItem : IBucketTreeItem | undefined ;
206-
207- if ( toolSetOrTool . source . type === 'mcp' ) {
208- const key = ToolDataSource . toKey ( toolSetOrTool . source ) ;
209- bucketItem = bucketMap . get ( key ) ;
210- if ( ! bucketItem ) {
211- const { definitionId } = toolSetOrTool . source ;
212- const mcpServer = mcpService . servers . get ( ) . find ( candidate => candidate . definition . id === definitionId ) ;
213- if ( ! mcpServer ) {
214- continue ;
215- }
216-
217- const buttons : ActionableButton [ ] = [ ] ;
218- const collection = mcpRegistry . collections . get ( ) . find ( c => c . id === mcpServer . collection . id ) ;
219- if ( collection ?. source ) {
220- buttons . push ( {
221- iconClass : ThemeIcon . asClassName ( Codicon . settingsGear ) ,
222- tooltip : localize ( 'configMcpCol' , "Configure {0}" , collection . label ) ,
223- action : ( ) => collection . source ? collection . source instanceof ExtensionIdentifier ? extensionsWorkbenchService . open ( collection . source . value , { tab : ExtensionEditorTab . Features , feature : 'mcp' } ) : mcpWorkbenchService . open ( collection . source , { tab : McpServerEditorTab . Configuration } ) : undefined
224- } ) ;
225- } else if ( collection ?. presentation ?. origin ) {
226- buttons . push ( {
227- iconClass : ThemeIcon . asClassName ( Codicon . settingsGear ) ,
228- tooltip : localize ( 'configMcpCol' , "Configure {0}" , collection . label ) ,
229- action : ( ) => editorService . openEditor ( {
230- resource : collection ! . presentation ! . origin ,
231- } )
232- } ) ;
233- }
234- if ( mcpServer . connectionState . get ( ) . state === McpConnectionState . Kind . Error ) {
235- buttons . push ( {
236- iconClass : ThemeIcon . asClassName ( Codicon . warning ) ,
237- tooltip : localize ( 'mcpShowOutput' , "Show Output" ) ,
238- action : ( ) => mcpServer . showOutput ( ) ,
239- } ) ;
240- }
228+ const getKey = ( source : ToolDataSource ) : string => {
229+ switch ( source . type ) {
230+ case 'mcp' :
231+ case 'extension' :
232+ return ToolDataSource . toKey ( source ) ;
233+ case 'internal' :
234+ return BucketOrdinal . BuiltIn . toString ( ) ;
235+ case 'user' :
236+ return BucketOrdinal . User . toString ( ) ;
237+ default :
238+ assertNever ( source ) ;
239+ }
240+ } ;
241241
242- bucketItem = {
243- itemType : 'bucket' ,
244- ordinal : BucketOrdinal . Mcp ,
245- id : key ,
246- label : localize ( 'mcplabel' , "MCP Server: {0}" , toolSetOrTool . source . label ) ,
247- checked : false ,
248- collapsed : true ,
249- children : [ ] ,
250- buttons,
251- iconClass : ThemeIcon . asClassName ( Codicon . mcp )
252- } ;
253- bucketMap . set ( key , bucketItem ) ;
242+ const createBucket = ( source : ToolDataSource , key : string ) : IBucketTreeItem | undefined => {
243+ if ( source . type === 'mcp' ) {
244+ const { definitionId } = source ;
245+ const mcpServer = mcpService . servers . get ( ) . find ( candidate => candidate . definition . id === definitionId ) ;
246+ if ( ! mcpServer ) {
247+ return undefined ;
254248 }
255249
256- if ( toolSetOrTool instanceof ToolSet ) {
257- // MCP ToolSets are hidden - store in bucket for special handling
258- bucketItem . toolset = toolSetOrTool ;
259- bucketItem . checked = picked ;
260- } else if ( toolSetOrTool . canBeReferencedInPrompt ) {
261- // Add MCP tools directly as children
262- const toolTreeItem = createToolTreeItemFromData ( toolSetOrTool , picked ) ;
263- bucketItem . children . push ( toolTreeItem ) ;
250+ const buttons : ActionableButton [ ] = [ ] ;
251+ const collection = mcpRegistry . collections . get ( ) . find ( c => c . id === mcpServer . collection . id ) ;
252+ if ( collection ?. source ) {
253+ buttons . push ( {
254+ iconClass : ThemeIcon . asClassName ( Codicon . settingsGear ) ,
255+ tooltip : localize ( 'configMcpCol' , "Configure {0}" , collection . label ) ,
256+ action : ( ) => collection . source ? collection . source instanceof ExtensionIdentifier ? extensionsWorkbenchService . open ( collection . source . value , { tab : ExtensionEditorTab . Features , feature : 'mcp' } ) : mcpWorkbenchService . open ( collection . source , { tab : McpServerEditorTab . Configuration } ) : undefined
257+ } ) ;
258+ } else if ( collection ?. presentation ?. origin ) {
259+ buttons . push ( {
260+ iconClass : ThemeIcon . asClassName ( Codicon . settingsGear ) ,
261+ tooltip : localize ( 'configMcpCol' , "Configure {0}" , collection . label ) ,
262+ action : ( ) => editorService . openEditor ( {
263+ resource : collection ! . presentation ! . origin ,
264+ } )
265+ } ) ;
264266 }
265-
266- } else {
267- // Handle other tool sources (extension, internal, user)
268- let ordinal : BucketOrdinal ;
269- let label : string ;
270- let key : string ;
271- let collapsed : boolean | undefined ;
272- if ( toolSetOrTool . source . type === 'extension' ) {
273- ordinal = BucketOrdinal . Extension ;
274- label = localize ( 'ext' , 'Extension: {0}' , toolSetOrTool . source . label ) ;
275- // Create separate buckets per extension, similar to MCP servers
276- key = ToolDataSource . toKey ( toolSetOrTool . source ) ;
277- collapsed = true ;
278- } else if ( toolSetOrTool . source . type === 'internal' ) {
279- ordinal = BucketOrdinal . BuiltIn ;
280- label = localize ( 'defaultBucketLabel' , "Built-In" ) ;
281- // Group all internal tools under one bucket
282- key = ordinal . toString ( ) ;
283- } else if ( toolSetOrTool . source . type === 'user' ) {
284- ordinal = BucketOrdinal . User ;
285- label = localize ( 'userBucket' , "User Defined Tool Sets" ) ;
286- // Group all user tools under one bucket
287- key = ordinal . toString ( ) ;
288- } else {
289- assertNever ( toolSetOrTool . source ) ;
267+ if ( mcpServer . connectionState . get ( ) . state === McpConnectionState . Kind . Error ) {
268+ buttons . push ( {
269+ iconClass : ThemeIcon . asClassName ( Codicon . warning ) ,
270+ tooltip : localize ( 'mcpShowOutput' , "Show Output" ) ,
271+ action : ( ) => mcpServer . showOutput ( ) ,
272+ } ) ;
290273 }
274+ return {
275+ itemType : 'bucket' ,
276+ ordinal : BucketOrdinal . Mcp ,
277+ id : key ,
278+ label : localize ( 'mcplabel' , "MCP Server: {0}" , source . label ) ,
279+ checked : undefined ,
280+ collapsed : true ,
281+ children : [ ] ,
282+ buttons,
283+ iconClass : ThemeIcon . asClassName ( Codicon . mcp )
284+ } ;
285+ } else if ( source . type === 'extension' ) {
286+ return {
287+ itemType : 'bucket' ,
288+ ordinal : BucketOrdinal . Extension ,
289+ id : key ,
290+ label : localize ( 'ext' , 'Extension: {0}' , source . label ) ,
291+ checked : undefined ,
292+ children : [ ] ,
293+ buttons : [ ] ,
294+ collapsed : true ,
295+ iconClass : ThemeIcon . asClassName ( Codicon . extensions )
296+ } ;
297+ } else if ( source . type === 'internal' ) {
298+ return {
299+ itemType : 'bucket' ,
300+ ordinal : BucketOrdinal . BuiltIn ,
301+ id : key ,
302+ label : localize ( 'defaultBucketLabel' , "Built-In" ) ,
303+ checked : undefined ,
304+ children : [ ] ,
305+ buttons : [ ] ,
306+ collapsed : false
307+ } ;
308+ } else {
309+ return {
310+ itemType : 'bucket' ,
311+ ordinal : BucketOrdinal . User ,
312+ id : key ,
313+ label : localize ( 'userBucket' , "User Defined Tool Sets" ) ,
314+ checked : undefined ,
315+ children : [ ] ,
316+ buttons : [ ] ,
317+ collapsed : true
318+ } ;
319+ }
320+ } ;
291321
292- bucketItem = bucketMap . get ( key ) ;
293- if ( ! bucketItem ) {
294- const iconProps = toolSetOrTool . source . type === 'extension'
295- ? { iconClass : ThemeIcon . asClassName ( Codicon . extensions ) }
296- : { } ;
297-
298- bucketItem = {
299- itemType : 'bucket' ,
300- ordinal,
301- id : key ,
302- label,
303- checked : false ,
304- children : [ ] ,
305- buttons : [ ] ,
306- collapsed,
307- ...iconProps
308- } ;
309- bucketMap . set ( key , bucketItem ) ;
322+ const getBucket = ( source : ToolDataSource ) : IBucketTreeItem | undefined => {
323+ const key = getKey ( source ) ;
324+ let bucket = bucketMap . get ( key ) ;
325+ if ( ! bucket ) {
326+ bucket = createBucket ( source , key ) ;
327+ if ( bucket ) {
328+ bucketMap . set ( key , bucket ) ;
310329 }
330+ }
331+ return bucket ;
332+ } ;
311333
312- if ( toolSetOrTool instanceof ToolSet ) {
313- // Add ToolSet as child with its tools as grandchildren - create directly instead of using legacy pick structure
314- const iconProps = mapIconToTreeItem ( toolSetOrTool . icon ) ;
315- const buttons = [ ] ;
316- if ( toolSetOrTool . source . type === 'user' ) {
317- const resource = toolSetOrTool . source . file ;
318- buttons . push ( {
319- iconClass : ThemeIcon . asClassName ( Codicon . edit ) ,
320- tooltip : localize ( 'editUserBucket' , "Edit Tool Set" ) ,
321- action : ( ) => editorService . openEditor ( { resource } )
322- } ) ;
334+ for ( const toolSet of toolsService . toolSets . get ( ) ) {
335+ if ( ! toolsEntries . has ( toolSet ) ) {
336+ continue ;
337+ }
338+ const bucket = getBucket ( toolSet . source ) ;
339+ if ( ! bucket ) {
340+ continue ;
341+ }
342+ const toolSetChecked = toolsEntries . get ( toolSet ) === true ;
343+ if ( toolSet . source . type === 'mcp' ) {
344+ // bucket represents the toolset
345+ bucket . toolset = toolSet ;
346+ if ( toolSetChecked ) {
347+ bucket . checked = toolSetChecked ;
348+ }
349+ // all mcp tools are part of toolsService.getTools()
350+ } else {
351+ const treeItem = createToolSetTreeItem ( toolSet , toolSetChecked , editorService ) ;
352+ bucket . children . push ( treeItem ) ;
353+ const children = [ ] ;
354+ for ( const tool of toolSet . getTools ( ) ) {
355+ if ( tool . canBeReferencedInPrompt ) {
356+ const toolChecked = toolSetChecked || toolsEntries . get ( tool ) === true ;
357+ const toolTreeItem = createToolTreeItemFromData ( tool , toolChecked ) ;
358+ children . push ( toolTreeItem ) ;
323359 }
324- const toolSetTreeItem : IToolSetTreeItem = {
325- itemType : 'toolset' ,
326- toolset : toolSetOrTool ,
327- buttons,
328- id : toolSetOrTool . id ,
329- label : toolSetOrTool . referenceName ,
330- description : toolSetOrTool . description ,
331- checked : picked ,
332- children : [ ] ,
333- collapsed : true ,
334- // TODO: Bring this back when tools in toolsets can be enabled/disabled.
335- // children: Array.from(toolSetOrTool.getTools()).map(tool => createToolTreeItemFromData(tool, picked)),
336- ...iconProps
337- } ;
338- bucketItem . children . push ( toolSetTreeItem ) ;
339- } else if ( toolSetOrTool . canBeReferencedInPrompt ) {
340- // Add individual tool as child
341- const toolTreeItem = createToolTreeItemFromData ( toolSetOrTool , picked ) ;
342- bucketItem . children . push ( toolTreeItem ) ;
360+ }
361+ if ( children . length > 0 ) {
362+ treeItem . children = children ;
343363 }
344364 }
345365 }
366+ for ( const tool of toolsService . getTools ( ) ) {
367+ if ( ! tool . canBeReferencedInPrompt || ! toolsEntries . has ( tool ) ) {
368+ continue ;
369+ }
370+ const bucket = getBucket ( tool . source ) ;
371+ if ( ! bucket ) {
372+ continue ;
373+ }
374+ const toolChecked = bucket . checked === true || toolsEntries . get ( tool ) === true ;
375+ const toolTreeItem = createToolTreeItemFromData ( tool , toolChecked ) ;
376+ bucket . children . push ( toolTreeItem ) ;
377+ }
346378
347379 // Convert bucket map to sorted tree items
348380 const sortedBuckets = Array . from ( bucketMap . values ( ) ) . sort ( ( a , b ) => a . ordinal - b . ordinal ) ;
349381 treeItems . push ( ...sortedBuckets ) ;
350382
351- // Set up checkbox states based on parent-child relationships
352- for ( const bucketItem of sortedBuckets ) {
353- if ( bucketItem . checked === true ) { // only set for MCP tool sets
354- // Check all children if bucket is checked
355- bucketItem . children . forEach ( child => child . checked = true ) ;
356- }
357- }
358-
359383 // Create and configure the tree picker
360384 const store = new DisposableStore ( ) ;
361385 const treePicker = store . add ( quickPickService . createQuickTree < AnyTreeItem > ( ) ) ;
@@ -386,7 +410,9 @@ export async function showToolsPicker(
386410 const traverse = ( items : readonly AnyTreeItem [ ] ) => {
387411 for ( const item of items ) {
388412 if ( isBucketTreeItem ( item ) || isToolSetTreeItem ( item ) ) {
389- traverse ( item . children ) ;
413+ if ( item . children ) {
414+ traverse ( item . children ) ;
415+ }
390416 } else if ( isToolTreeItem ( item ) && item . checked ) {
391417 count ++ ;
392418 }
@@ -418,7 +444,9 @@ export async function showToolsPicker(
418444 traverse ( item . children ) ;
419445 } else if ( isToolSetTreeItem ( item ) ) {
420446 result . set ( item . toolset , item . checked === true ) ;
421- traverse ( item . children ) ;
447+ if ( item . children ) {
448+ traverse ( item . children ) ;
449+ }
422450 } else if ( isToolTreeItem ( item ) ) {
423451 result . set ( item . tool , item . checked ) ;
424452 }
0 commit comments