@@ -28,11 +28,13 @@ import {
2828 type ClientToolDisplay ,
2929 TOOL_DISPLAY_REGISTRY ,
3030} from '@/lib/copilot/tools/client/tool-display-registry'
31+ import { VFS_DIR_TO_RESOURCE } from '@/lib/copilot/resource-types'
3132
3233const logger = createLogger ( 'CopilotStoreUtils' )
3334
34- /** Respond tools are internal to copilot subagents and should never be shown in the UI */
35+ /** Respond tools are internal handoff tools shown with a friendly generic label. */
3536const HIDDEN_TOOL_SUFFIX = '_respond'
37+ const HIDDEN_TOOL_NAMES = new Set ( [ 'tool_search_tool_regex' , 'grep' , 'glob' ] )
3638
3739/** UI metadata sent by the copilot on SSE tool_call events. */
3840export interface ServerToolUI {
@@ -81,7 +83,11 @@ export function resolveToolDisplay(
8183 serverUI ?: ServerToolUI
8284) : ClientToolDisplay | undefined {
8385 if ( ! toolName ) return undefined
84- if ( toolName . endsWith ( HIDDEN_TOOL_SUFFIX ) ) return undefined
86+ if ( HIDDEN_TOOL_NAMES . has ( toolName ) ) return undefined
87+
88+ const specialDisplay = specialToolDisplay ( toolName , state , params )
89+ if ( specialDisplay ) return specialDisplay
90+
8591 const entry = TOOL_DISPLAY_REGISTRY [ toolName ]
8692 if ( ! entry ) {
8793 // Use copilot-provided UI as a better fallback than humanized name
@@ -115,6 +121,117 @@ export function resolveToolDisplay(
115121 return humanizedFallback ( toolName , state )
116122}
117123
124+ function specialToolDisplay (
125+ toolName : string ,
126+ state : ClientToolCallState ,
127+ params ?: Record < string , unknown >
128+ ) : ClientToolDisplay | undefined {
129+ if ( toolName . endsWith ( HIDDEN_TOOL_SUFFIX ) ) {
130+ return {
131+ text : formatRespondLabel ( state ) ,
132+ icon : Loader2 ,
133+ }
134+ }
135+
136+ const searchQuery =
137+ readStringParam ( params , 'pattern' ) || readStringParam ( params , 'query' ) || readStringParam ( params , 'glob' )
138+
139+ if ( ( toolName === 'grep' || toolName === 'glob' ) && searchQuery ) {
140+ return {
141+ text : formatSearchingLabel ( searchQuery , state ) ,
142+ icon : Search ,
143+ }
144+ }
145+
146+ if ( toolName === 'read' ) {
147+ const target = describeReadTarget ( readStringParam ( params , 'path' ) )
148+ return {
149+ text : formatReadingLabel ( target , state ) ,
150+ icon : FileText ,
151+ }
152+ }
153+
154+ return undefined
155+ }
156+
157+ function formatRespondLabel ( state : ClientToolCallState ) : string {
158+ switch ( state ) {
159+ case ClientToolCallState . success :
160+ return 'Returned results'
161+ case ClientToolCallState . error :
162+ return 'Failed returning results'
163+ case ClientToolCallState . rejected :
164+ case ClientToolCallState . aborted :
165+ return 'Skipped returning results'
166+ default :
167+ return 'Returning results'
168+ }
169+ }
170+
171+ function readStringParam (
172+ params : Record < string , unknown > | undefined ,
173+ key : string
174+ ) : string | undefined {
175+ const value = params ?. [ key ]
176+ return typeof value === 'string' && value . trim ( ) ? value . trim ( ) : undefined
177+ }
178+
179+ function formatSearchingLabel ( query : string , state : ClientToolCallState ) : string {
180+ switch ( state ) {
181+ case ClientToolCallState . success :
182+ return `Searched for ${ query } `
183+ case ClientToolCallState . error :
184+ return `Failed searching for ${ query } `
185+ case ClientToolCallState . rejected :
186+ case ClientToolCallState . aborted :
187+ return `Skipped searching for ${ query } `
188+ default :
189+ return `Searching for ${ query } `
190+ }
191+ }
192+
193+ function formatReadingLabel ( target : string | undefined , state : ClientToolCallState ) : string {
194+ const suffix = target ? ` ${ target } ` : ''
195+ switch ( state ) {
196+ case ClientToolCallState . success :
197+ return `Read${ suffix } `
198+ case ClientToolCallState . error :
199+ return `Failed reading${ suffix } `
200+ case ClientToolCallState . rejected :
201+ case ClientToolCallState . aborted :
202+ return `Skipped reading${ suffix } `
203+ default :
204+ return `Reading${ suffix } `
205+ }
206+ }
207+
208+ function describeReadTarget ( path : string | undefined ) : string | undefined {
209+ if ( ! path ) return undefined
210+
211+ const segments = path
212+ . split ( '/' )
213+ . map ( ( segment ) => segment . trim ( ) )
214+ . filter ( Boolean )
215+
216+ if ( segments . length === 0 ) return undefined
217+
218+ const resourceType = VFS_DIR_TO_RESOURCE [ segments [ 0 ] ]
219+ if ( ! resourceType ) {
220+ return stripExtension ( segments [ segments . length - 1 ] )
221+ }
222+
223+ if ( resourceType === 'file' ) {
224+ return segments . slice ( 1 ) . join ( '/' ) || segments [ segments . length - 1 ]
225+ }
226+
227+ const resourceName = segments [ 1 ] || segments [ segments . length - 1 ]
228+ return stripExtension ( resourceName )
229+ }
230+
231+ function stripExtension ( value : string ) : string {
232+ return value . replace ( / \. [ ^ / . ] + $ / , '' )
233+ }
234+
118235/** Generates display from copilot-provided UI metadata. */
119236function serverUIFallback ( serverUI : ServerToolUI , state : ClientToolCallState ) : ClientToolDisplay {
120237 const icon = resolveIcon ( serverUI . icon )
0 commit comments