@@ -160,177 +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 ) {
196- fileLabel = filePath ;
197-
198- return {
199- toolName : fileLabel ? ( `Read ${ fileLabel } ` + ( parsedRange ? `, lines ${ parsedRange . start } to ${ parsedRange . end } ` : '' ) ) : 'Read repository' ,
200- invocationMessage : fileLabel ? ( `Read ${ fileLabel } ` + ( parsedRange ? `, lines ${ parsedRange . start } to ${ parsedRange . end } ` : '' ) ) : 'Read repository' ,
201- pastTenseMessage : fileLabel ? ( `Read ${ fileLabel } ` + ( parsedRange ? `, lines ${ parsedRange . start } to ${ parsedRange . end } ` : '' ) ) : 'Read repository' ,
202- } ;
203- } else if ( fileLabel === '' ) {
204- return {
205- toolName : 'Read repository' ,
206- invocationMessage : 'Read repository' ,
207- pastTenseMessage : 'Read repository' ,
208- } ;
209- } else {
210- return {
211- toolName : `Read` ,
212- invocationMessage : ( `Read ${ fileLabel } ` + ( parsedRange ? `, lines ${ parsedRange . start } to ${ parsedRange . end } ` : '' ) ) ,
213- pastTenseMessage : ( `Read ${ fileLabel } ` + ( parsedRange ? `, lines ${ parsedRange . start } to ${ parsedRange . end } ` : '' ) ) ,
214- toolSpecificData : {
215- command : 'view' ,
216- filePath : filePath ,
217- fileLabel : fileLabel ,
218- viewRange : parsedRange
219- }
220- } ;
221- }
222- }
223- } else {
224- const filePath = args . path ;
225- const fileLabel = filePath && toFileLabel ( filePath ) ;
226- const parsedRange = parseRange ( args . view_range ) ;
227-
228- return {
229- toolName : 'Edit' ,
230- invocationMessage : fileLabel ? ( `Edit [](${ fileLabel } )` + ( parsedRange ? `, lines ${ parsedRange . start } to ${ parsedRange . end } ` : '' ) ) : 'Edit' ,
231- pastTenseMessage : fileLabel ? ( `Edit [](${ fileLabel } )` + ( parsedRange ? `, lines ${ parsedRange . start } to ${ parsedRange . end } ` : '' ) ) : 'Edit' ,
232- toolSpecificData : fileLabel ? {
233- command : args . command || 'edit' ,
234- filePath : filePath ,
235- fileLabel : fileLabel ,
236- viewRange : parsedRange
237- } : undefined
238- } ;
239- }
240- } else if ( name === 'str_replace' ) {
241- 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 => {
242171 const fileLabel = filePath && toFileLabel ( filePath ) ;
243-
244- return {
245- toolName : 'Edit' ,
246- invocationMessage : fileLabel ? `Edit [](${ fileLabel } )` : `Edit ${ filePath } ` ,
247- pastTenseMessage : fileLabel ? `Edit [](${ fileLabel } )` : `Edit ${ filePath } ` ,
248- toolSpecificData : fileLabel ? {
249- command : 'str_replace' ,
250- filePath : filePath ,
251- fileLabel : fileLabel ,
252- } : undefined
172+ if ( fileLabel === undefined || fileLabel === '' ) {
173+ return { toolName : 'Read repository' , invocationMessage : 'Read repository' , pastTenseMessage : 'Read repository' } ;
253174 }
254- } else if ( name === 'create' ) {
255- const filePath = args . path ;
256- const fileLabel = filePath && toFileLabel ( filePath ) ;
257-
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.
258177 return {
259- toolName : 'Create ' ,
260- invocationMessage : fileLabel ? `Create [](${ fileLabel } )` : `Create File ${ filePath } `,
261- pastTenseMessage : fileLabel ? `Create [](${ fileLabel } )` : `Create File ${ filePath } `,
262- toolSpecificData : fileLabel ? {
263- command : 'create ' ,
178+ toolName : 'Read ' ,
179+ invocationMessage : `Read [](${ fileLabel } )${ rangeSuffix } `,
180+ pastTenseMessage : `Read [](${ fileLabel } )${ rangeSuffix } `,
181+ toolSpecificData : {
182+ command : 'view ' ,
264183 filePath : filePath ,
265184 fileLabel : fileLabel ,
266- } : undefined
267- }
268- } else if ( name === 'view' ) {
269- const filePath = args . path ;
185+ parsedContent : opts ?. parsedContent ,
186+ viewRange : parsedRange
187+ }
188+ } ;
189+ } ;
190+
191+ const buildEditDetails = ( filePath : string | undefined , command : string , parsedRange : { start : number , end : number } | undefined , opts ?: { defaultName ?: string } ) : ParsedToolCallDetails => {
270192 const fileLabel = filePath && toFileLabel ( filePath ) ;
271- 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+ }
272208
273209 return {
274- toolName : fileLabel === '' ? 'Read repository' : 'Read ',
275- invocationMessage : fileLabel ? ( `Read []( ${ fileLabel } )` + ( parsedRange ? `, lines ${ parsedRange . start } to ${ parsedRange . end } ` : '' ) ) : 'Read repository' ,
276- pastTenseMessage : fileLabel ? ( `Read []( ${ fileLabel } )` + ( parsedRange ? `, lines ${ parsedRange . start } to ${ parsedRange . end } ` : '' ) ) : 'Read repository' ,
210+ toolName : opts ?. defaultName || 'Edit ',
211+ invocationMessage,
212+ pastTenseMessage,
277213 toolSpecificData : fileLabel ? {
278- command : 'view' ,
214+ command : command || ( opts ?. defaultName === 'Create' ? 'create' : ( command || 'edit' ) ) ,
279215 filePath : filePath ,
280216 fileLabel : fileLabel ,
281217 viewRange : parsedRange
282218 } : undefined
283219 } ;
284- } else if ( name === 'think' ) {
285- 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 } ` ;
286225 return {
287- toolName : 'think' ,
288- invocationMessage : thought ,
226+ toolName : 'Edit' ,
227+ invocationMessage : message ,
228+ pastTenseMessage : message ,
229+ toolSpecificData : fileLabel ? { command : 'str_replace' , filePath, fileLabel } : undefined
289230 } ;
290- } else if ( name === 'report_progress' ) {
291- const details : ParsedToolCallDetails = {
292- toolName : 'Progress Update' ,
293- 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
294241 } ;
295- if ( args . commitMessage ) {
296- details . originMessage = `Commit: ${ args . commitMessage } ` ;
297- }
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' } ; }
298249 return details ;
299- } else if ( name === 'bash' ) {
300- const command = args . command ? `$ ${ args . command } ` : undefined ;
301- const bashContent = [ command , content ] . filter ( Boolean ) . join ( '\n' ) ;
302- const details : ParsedToolCallDetails = {
303- toolName : 'Run Bash command' ,
304- invocationMessage : bashContent || 'Run Bash command' ,
305- } ;
250+ } ;
306251
307- // Use the terminal-specific data for bash commands
308- if ( args . command ) {
309- const bashToolData : BashToolData = {
310- commandLine : {
311- original : args . command ,
312- } ,
313- language : 'bash'
314- } ;
315- 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 ) ) ;
316289 }
317- return details ;
318- } else if ( name === 'read_bash' ) {
319- return {
320- toolName : 'read_bash' ,
321- invocationMessage : 'Read logs from Bash session'
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 } ;
322299 }
323- } else if ( name === 'stop_bash' ) {
324- return {
325- toolName : 'stop_bash' ,
326- invocationMessage : 'Stop Bash session'
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 ;
327304 }
328- } else {
329- // Unknown tool type
330- return {
331- toolName : name || 'unknown' ,
332- invocationMessage : content || name || 'unknown'
333- } ;
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' } ;
334313 }
335314}
336315
0 commit comments