@@ -160,169 +160,156 @@ export function parseToolCallDetails(
160160 } ,
161161 content : string
162162) : ParsedToolCallDetails {
163+ // Parse arguments once with graceful fallback
163164 let args : { command ?: string , path ?: string , prDescription ?: string , commitMessage ?: string , view_range ?: unknown } = { } ;
164- try {
165- args = toolCall . function . arguments ? JSON . parse ( toolCall . function . arguments ) : { } ;
166- } catch {
167- // fallback to empty args
168- }
165+ try { args = toolCall . function . arguments ? JSON . parse ( toolCall . function . arguments ) : { } ; } catch { /* ignore */ }
169166
170167 const name = toolCall . function . name ;
171168
172- if ( name === 'str_replace_editor' ) {
173- if ( args . command === 'view' ) {
174- const parsedContent = parseDiff ( content ) ;
175- const parsedRange = parseRange ( args . view_range ) ;
176- if ( parsedContent ) {
177- const file = parsedContent . fileA ?? parsedContent . fileB ;
178- const fileLabel = file && toFileLabel ( file ) ;
179- return {
180- toolName : fileLabel === '' ? 'Read repository' : 'Read' ,
181- invocationMessage : fileLabel ? ( `Read [](${ fileLabel } )` + ( parsedRange ? `, lines ${ parsedRange . start } to ${ parsedRange . end } ` : '' ) ) : 'Read repository' ,
182- pastTenseMessage : fileLabel ? ( `Read [](${ fileLabel } )` + ( parsedRange ? `, lines ${ parsedRange . start } to ${ parsedRange . end } ` : '' ) ) : 'Read repository' ,
183- toolSpecificData : fileLabel ? {
184- command : 'view' ,
185- filePath : file ,
186- fileLabel : fileLabel ,
187- parsedContent : parsedContent ,
188- viewRange : parsedRange
189- } : undefined
190- } ;
191- } else {
192- const filePath = args . path ;
193- let fileLabel = filePath ? toFileLabel ( filePath ) : undefined ;
194-
195- if ( fileLabel === undefined || fileLabel === '' ) {
196- return {
197- toolName : 'Read repository' ,
198- invocationMessage : 'Read repository' ,
199- pastTenseMessage : 'Read repository' ,
200- } ;
201- } else {
202- return {
203- toolName : `Read` ,
204- invocationMessage : ( `Read ${ fileLabel } ` + ( parsedRange ? `, lines ${ parsedRange . start } to ${ parsedRange . end } ` : '' ) ) ,
205- pastTenseMessage : ( `Read ${ fileLabel } ` + ( parsedRange ? `, lines ${ parsedRange . start } to ${ parsedRange . end } ` : '' ) ) ,
206- toolSpecificData : {
207- command : 'view' ,
208- filePath : filePath ,
209- fileLabel : fileLabel ,
210- viewRange : parsedRange
211- }
212- } ;
213- }
214- }
215- } else {
216- const filePath = args . path ;
217- const fileLabel = filePath && toFileLabel ( filePath ) ;
218- const parsedRange = parseRange ( args . view_range ) ;
219-
220- return {
221- toolName : 'Edit' ,
222- invocationMessage : fileLabel ? ( `Edit [](${ fileLabel } )` + ( parsedRange ? `, lines ${ parsedRange . start } to ${ parsedRange . end } ` : '' ) ) : 'Edit' ,
223- pastTenseMessage : fileLabel ? ( `Edit [](${ fileLabel } )` + ( parsedRange ? `, lines ${ parsedRange . start } to ${ parsedRange . end } ` : '' ) ) : 'Edit' ,
224- toolSpecificData : fileLabel ? {
225- command : args . command || 'edit' ,
226- filePath : filePath ,
227- fileLabel : fileLabel ,
228- viewRange : parsedRange
229- } : undefined
230- } ;
231- }
232- } else if ( name === 'str_replace' ) {
233- const filePath = args . path ;
169+ // Small focused helpers to remove duplication while preserving behavior
170+ const buildReadDetails = ( filePath : string | undefined , parsedRange : { start : number , end : number } | undefined , opts ?: { parsedContent ?: { content : string ; fileA : string | undefined ; fileB : string | undefined ; } } ) : ParsedToolCallDetails => {
234171 const fileLabel = filePath && toFileLabel ( filePath ) ;
235-
172+ if ( fileLabel === undefined || fileLabel === '' ) {
173+ return { toolName : 'Read repository' , invocationMessage : 'Read repository' , pastTenseMessage : 'Read repository' } ;
174+ }
175+ const rangeSuffix = parsedRange ? `, lines ${ parsedRange . start } to ${ parsedRange . end } ` : '' ;
176+ // Default helper returns bracket variant (used for generic view). Plain variant handled separately for str_replace_editor non-diff.
236177 return {
237- toolName : 'Edit ' ,
238- invocationMessage : fileLabel ? `Edit [](${ fileLabel } )` : `Edit ${ filePath } `,
239- pastTenseMessage : fileLabel ? `Edit [](${ fileLabel } )` : `Edit ${ filePath } `,
240- toolSpecificData : fileLabel ? {
241- command : 'str_replace ' ,
178+ toolName : 'Read ' ,
179+ invocationMessage : `Read [](${ fileLabel } )${ rangeSuffix } `,
180+ pastTenseMessage : `Read [](${ fileLabel } )${ rangeSuffix } `,
181+ toolSpecificData : {
182+ command : 'view ' ,
242183 filePath : filePath ,
243184 fileLabel : fileLabel ,
244- } : undefined
185+ parsedContent : opts ?. parsedContent ,
186+ viewRange : parsedRange
187+ }
245188 } ;
246- } else if ( name === 'create' ) {
247- const filePath = args . path ;
248- const fileLabel = filePath && toFileLabel ( filePath ) ;
189+ } ;
249190
250- return {
251- toolName : 'Create' ,
252- invocationMessage : fileLabel ? `Create [](${ fileLabel } )` : `Create File ${ filePath } ` ,
253- pastTenseMessage : fileLabel ? `Create [](${ fileLabel } )` : `Create File ${ filePath } ` ,
254- toolSpecificData : fileLabel ? {
255- command : 'create' ,
256- filePath : filePath ,
257- fileLabel : fileLabel ,
258- } : undefined
259- } ;
260- } else if ( name === 'view' ) {
261- const filePath = args . path ;
191+ const buildEditDetails = ( filePath : string | undefined , command : string , parsedRange : { start : number , end : number } | undefined , opts ?: { defaultName ?: string } ) : ParsedToolCallDetails => {
262192 const fileLabel = filePath && toFileLabel ( filePath ) ;
263- const parsedRange = parseRange ( args . view_range ) ;
193+ const rangeSuffix = parsedRange ? `, lines ${ parsedRange . start } to ${ parsedRange . end } ` : '' ;
194+ let invocationMessage : string ;
195+ let pastTenseMessage : string ;
196+ if ( fileLabel ) {
197+ invocationMessage = `Edit [](${ fileLabel } )${ rangeSuffix } ` ;
198+ pastTenseMessage = `Edit [](${ fileLabel } )${ rangeSuffix } ` ;
199+ } else {
200+ if ( opts ?. defaultName === 'Create' ) {
201+ invocationMessage = pastTenseMessage = `Create File ${ filePath } ` ;
202+ } else {
203+ invocationMessage = pastTenseMessage = ( opts ?. defaultName || 'Edit' ) ;
204+ }
205+ invocationMessage += rangeSuffix ;
206+ pastTenseMessage += rangeSuffix ;
207+ }
264208
265209 return {
266- toolName : fileLabel === '' ? 'Read repository' : 'Read ',
267- invocationMessage : fileLabel ? ( `Read []( ${ fileLabel } )` + ( parsedRange ? `, lines ${ parsedRange . start } to ${ parsedRange . end } ` : '' ) ) : 'Read repository' ,
268- pastTenseMessage : fileLabel ? ( `Read []( ${ fileLabel } )` + ( parsedRange ? `, lines ${ parsedRange . start } to ${ parsedRange . end } ` : '' ) ) : 'Read repository' ,
210+ toolName : opts ?. defaultName || 'Edit ',
211+ invocationMessage,
212+ pastTenseMessage,
269213 toolSpecificData : fileLabel ? {
270- command : 'view' ,
214+ command : command || ( opts ?. defaultName === 'Create' ? 'create' : ( command || 'edit' ) ) ,
271215 filePath : filePath ,
272216 fileLabel : fileLabel ,
273217 viewRange : parsedRange
274218 } : undefined
275219 } ;
276- } else if ( name === 'think' ) {
277- const thought = ( args as unknown as { thought ?: string } ) . thought || content || 'Thought' ;
220+ } ;
221+
222+ const buildStrReplaceDetails = ( filePath : string | undefined ) : ParsedToolCallDetails => {
223+ const fileLabel = filePath && toFileLabel ( filePath ) ;
224+ const message = fileLabel ? `Edit [](${ fileLabel } )` : `Edit ${ filePath } ` ;
278225 return {
279- toolName : 'think' ,
280- invocationMessage : thought ,
226+ toolName : 'Edit' ,
227+ invocationMessage : message ,
228+ pastTenseMessage : message ,
229+ toolSpecificData : fileLabel ? { command : 'str_replace' , filePath, fileLabel } : undefined
281230 } ;
282- } else if ( name === 'report_progress' ) {
283- const details : ParsedToolCallDetails = {
284- toolName : 'Progress Update' ,
285- invocationMessage : `${ args . prDescription } ` || content || 'Progress Update'
231+ } ;
232+
233+ const buildCreateDetails = ( filePath : string | undefined ) : ParsedToolCallDetails => {
234+ const fileLabel = filePath && toFileLabel ( filePath ) ;
235+ const message = fileLabel ? `Create [](${ fileLabel } )` : `Create File ${ filePath } ` ;
236+ return {
237+ toolName : 'Create' ,
238+ invocationMessage : message ,
239+ pastTenseMessage : message ,
240+ toolSpecificData : fileLabel ? { command : 'create' , filePath, fileLabel } : undefined
286241 } ;
287- if ( args . commitMessage ) {
288- details . originMessage = `Commit: ${ args . commitMessage } ` ;
289- }
242+ } ;
243+
244+ const buildBashDetails = ( bashArgs : typeof args , contentStr : string ) : ParsedToolCallDetails => {
245+ const command = bashArgs . command ? `$ ${ bashArgs . command } ` : undefined ;
246+ const bashContent = [ command , contentStr ] . filter ( Boolean ) . join ( '\n' ) ;
247+ const details : ParsedToolCallDetails = { toolName : 'Run Bash command' , invocationMessage : bashContent || 'Run Bash command' } ;
248+ if ( bashArgs . command ) { details . toolSpecificData = { commandLine : { original : bashArgs . command } , language : 'bash' } ; }
290249 return details ;
291- } else if ( name === 'bash' ) {
292- const command = args . command ? `$ ${ args . command } ` : undefined ;
293- const bashContent = [ command , content ] . filter ( Boolean ) . join ( '\n' ) ;
294- const details : ParsedToolCallDetails = {
295- toolName : 'Run Bash command' ,
296- invocationMessage : bashContent || 'Run Bash command' ,
297- } ;
250+ } ;
298251
299- // Use the terminal-specific data for bash commands
300- if ( args . command ) {
301- const bashToolData : BashToolData = {
302- commandLine : {
303- original : args . command ,
304- } ,
305- language : 'bash'
306- } ;
307- details . toolSpecificData = bashToolData ;
252+ switch ( name ) {
253+ case 'str_replace_editor' : {
254+ if ( args . command === 'view' ) {
255+ const parsedContent = parseDiff ( content ) ;
256+ const parsedRange = parseRange ( args . view_range ) ;
257+ if ( parsedContent ) {
258+ const file = parsedContent . fileA ?? parsedContent . fileB ;
259+ const fileLabel = file && toFileLabel ( file ) ;
260+ if ( fileLabel === '' ) {
261+ return { toolName : 'Read repository' , invocationMessage : 'Read repository' , pastTenseMessage : 'Read repository' } ;
262+ } else if ( fileLabel === undefined ) {
263+ return { toolName : 'Read' , invocationMessage : 'Read repository' , pastTenseMessage : 'Read repository' } ;
264+ } else {
265+ const rangeSuffix = parsedRange ? `, lines ${ parsedRange . start } to ${ parsedRange . end } ` : '' ;
266+ return {
267+ toolName : 'Read' ,
268+ invocationMessage : `Read [](${ fileLabel } )${ rangeSuffix } ` ,
269+ pastTenseMessage : `Read [](${ fileLabel } )${ rangeSuffix } ` ,
270+ toolSpecificData : { command : 'view' , filePath : file , fileLabel, parsedContent, viewRange : parsedRange }
271+ } ;
272+ }
273+ }
274+ // No diff parsed: use PLAIN (non-bracket) variant for str_replace_editor views
275+ const plainRange = parseRange ( args . view_range ) ;
276+ const fp = args . path ; const fl = fp && toFileLabel ( fp ) ;
277+ if ( fl === undefined || fl === '' ) {
278+ return { toolName : 'Read repository' , invocationMessage : 'Read repository' , pastTenseMessage : 'Read repository' } ;
279+ }
280+ const suffix = plainRange ? `, lines ${ plainRange . start } to ${ plainRange . end } ` : '' ;
281+ return {
282+ toolName : 'Read' ,
283+ invocationMessage : `Read ${ fl } ${ suffix } ` ,
284+ pastTenseMessage : `Read ${ fl } ${ suffix } ` ,
285+ toolSpecificData : { command : 'view' , filePath : fp , fileLabel : fl , viewRange : plainRange }
286+ } ;
287+ }
288+ return buildEditDetails ( args . path , args . command || 'edit' , parseRange ( args . view_range ) ) ;
308289 }
309- return details ;
310- } else if ( name === 'read_bash' ) {
311- return {
312- toolName : 'read_bash' ,
313- invocationMessage : 'Read logs from Bash session'
314- } ;
315- } else if ( name === 'stop_bash' ) {
316- return {
317- toolName : 'stop_bash' ,
318- invocationMessage : 'Stop Bash session'
319- } ;
320- } else {
321- // Unknown tool type
322- return {
323- toolName : name || 'unknown' ,
324- invocationMessage : content || name || 'unknown'
325- } ;
290+ case 'str_replace' :
291+ return buildStrReplaceDetails ( args . path ) ;
292+ case 'create' :
293+ return buildCreateDetails ( args . path ) ;
294+ case 'view' :
295+ return buildReadDetails ( args . path , parseRange ( args . view_range ) ) ; // generic view always bracket variant
296+ case 'think' : {
297+ const thought = ( args as unknown as { thought ?: string } ) . thought || content || 'Thought' ;
298+ return { toolName : 'think' , invocationMessage : thought } ;
299+ }
300+ case 'report_progress' : {
301+ const details : ParsedToolCallDetails = { toolName : 'Progress Update' , invocationMessage : `${ args . prDescription } ` || content || 'Progress Update' } ;
302+ if ( args . commitMessage ) { details . originMessage = `Commit: ${ args . commitMessage } ` ; }
303+ return details ;
304+ }
305+ case 'bash' :
306+ return buildBashDetails ( args , content ) ;
307+ case 'read_bash' :
308+ return { toolName : 'read_bash' , invocationMessage : 'Read logs from Bash session' } ;
309+ case 'stop_bash' :
310+ return { toolName : 'stop_bash' , invocationMessage : 'Stop Bash session' } ;
311+ default :
312+ return { toolName : name || 'unknown' , invocationMessage : content || name || 'unknown' } ;
326313 }
327314}
328315
0 commit comments