@@ -78,12 +78,12 @@ export function mapThreadEvent(event: ThreadEvent, model = "o3"): AgentEvent | n
7878 if ( item . type === "command_execution" ) {
7979 return {
8080 type : "message" ,
81- blocks : [ { type : "tool_use" , id : item . id ?? `codex-${ Date . now ( ) } ` , name : "command " , input : { command : item . command } } ] ,
81+ blocks : [ { type : "tool_use" , id : item . id ?? `codex-${ Date . now ( ) } ` , name : "Bash " , input : { command : item . command } } ] ,
8282 } ;
8383 }
8484 if ( item . type === "file_change" ) {
8585 const files = item . changes . map ( ( c ) => `${ c . kind } ${ c . path } ` ) . join ( ", " ) ;
86- return { type : "message" , blocks : [ { type : "tool_use" , id : item . id ?? `codex-${ Date . now ( ) } ` , name : "file_change " , input : { files } } ] } ;
86+ return { type : "message" , blocks : [ { type : "tool_use" , id : item . id ?? `codex-${ Date . now ( ) } ` , name : "Edit " , input : { files } } ] } ;
8787 }
8888 if ( item . type === "reasoning" && item . text ) {
8989 return { type : "message" , blocks : [ { type : "thinking" , text : item . text } ] } ;
@@ -131,15 +131,15 @@ function mapItemToBlock(item: { id?: string; type: string; [k: string]: any }):
131131 case "agent_message" :
132132 return item . text ? { type : "text" , text : item . text } : null ;
133133 case "command_execution" :
134- return { type : "tool_use" , id : item . id ?? `codex-${ Date . now ( ) } ` , name : "command " , input : { command : item . command } } ;
134+ return { type : "tool_use" , id : item . id ?? `codex-${ Date . now ( ) } ` , name : "Bash " , input : { command : item . command } } ;
135135 case "file_change" : {
136136 const files = item . changes ?. map ( ( c : any ) => `${ c . kind } ${ c . path } ` ) . join ( ", " ) ?? "" ;
137- return { type : "tool_use" , id : item . id ?? `codex-${ Date . now ( ) } ` , name : "file_change " , input : { files } } ;
137+ return { type : "tool_use" , id : item . id ?? `codex-${ Date . now ( ) } ` , name : "Edit " , input : { files } } ;
138138 }
139139 case "mcp_tool_call" :
140140 return { type : "tool_use" , id : item . id ?? `codex-${ Date . now ( ) } ` , name : item . name ?? "mcp_tool" , input : item . arguments ?? { } } ;
141141 case "web_search" :
142- return { type : "tool_use" , id : item . id ?? `codex-${ Date . now ( ) } ` , name : "web_search " , input : { query : item . query ?? "" } } ;
142+ return { type : "tool_use" , id : item . id ?? `codex-${ Date . now ( ) } ` , name : "WebSearch " , input : { query : item . query ?? "" } } ;
143143 case "reasoning" :
144144 return item . text ? { type : "thinking" , text : item . text } : null ;
145145 default :
@@ -311,6 +311,52 @@ function findSessionFile(threadId: string): string | null {
311311 return null ;
312312}
313313
314+ /**
315+ * Normalize a JSONL function_call to the same name/input shape as live
316+ * streaming (`mapItemToBlock`). This ensures the frontend renders history
317+ * and live events identically.
318+ */
319+ /**
320+ * Normalize a JSONL function_call to the canonical tool names the frontend
321+ * recognizes (Bash, Edit, Read, WebSearch, etc.), matching live streaming output.
322+ */
323+ function normalizeFunctionCall ( name : string , rawArgs : Record < string , unknown > ) : { name : string ; input : Record < string , unknown > } | null {
324+ switch ( name ) {
325+ // Shell execution — multiple Codex generations use different names/shapes
326+ case "exec_command" :
327+ return { name : "Bash" , input : { command : String ( rawArgs . cmd ?? "" ) } } ;
328+ case "shell" : {
329+ const cmd = Array . isArray ( rawArgs . command ) ? rawArgs . command . join ( " " ) : String ( rawArgs . command ?? "" ) ;
330+ return { name : "Bash" , input : { command : cmd } } ;
331+ }
332+ case "shell_command" :
333+ return { name : "Bash" , input : { command : String ( rawArgs . command ?? "" ) } } ;
334+ case "write_stdin" : {
335+ // write_stdin with empty chars is a poll for command output — skip it
336+ const chars = String ( rawArgs . chars ?? "" ) ;
337+ if ( ! chars ) return null ;
338+ return { name : "Bash" , input : { command : chars } } ;
339+ }
340+
341+ // Image viewing → Read (closest frontend equivalent)
342+ case "view_image" :
343+ return { name : "Read" , input : { file_path : String ( rawArgs . path ?? "" ) } } ;
344+
345+ // User interaction → AskUserQuestion
346+ case "request_user_input" :
347+ return { name : "AskUserQuestion" , input : rawArgs } ;
348+
349+ // Internal planner — no specific UI, keep original name for fallback
350+ case "update_plan" :
351+ case "list_mcp_resources" :
352+ case "read_mcp_resource" :
353+ return { name, input : rawArgs } ;
354+
355+ default :
356+ return { name, input : rawArgs } ;
357+ }
358+ }
359+
314360function mapResponseItem ( payload : Record < string , any > ) : AgentEvent | null {
315361 switch ( payload . type ) {
316362 case "message" : {
@@ -327,17 +373,19 @@ function mapResponseItem(payload: Record<string, any>): AgentEvent | null {
327373 return null ;
328374 }
329375 case "function_call" : {
330- let input : Record < string , unknown > = { } ;
376+ let rawArgs : Record < string , unknown > = { } ;
331377 if ( payload . arguments ) {
332378 try {
333- input = JSON . parse ( payload . arguments ) ;
379+ rawArgs = JSON . parse ( payload . arguments ) ;
334380 } catch {
335- input = { raw : payload . arguments } ;
381+ rawArgs = { raw : payload . arguments } ;
336382 }
337383 }
384+ const normalized = normalizeFunctionCall ( payload . name ?? "tool" , rawArgs ) ;
385+ if ( ! normalized ) return null ;
338386 return {
339387 type : "message" ,
340- blocks : [ { type : "tool_use" , id : payload . call_id ?? `codex-hist-${ Date . now ( ) } ` , name : payload . name ?? "tool" , input } ] ,
388+ blocks : [ { type : "tool_use" , id : payload . call_id ?? `codex-hist-${ Date . now ( ) } ` , name : normalized . name , input : normalized . input } ] ,
341389 } ;
342390 }
343391 case "function_call_output" :
0 commit comments