@@ -1032,6 +1032,9 @@ function StructuredContentPart({ part, index }: { part: Record<string, unknown>;
10321032 if ( type === "tool_use" ) {
10331033 return < ToolUsePart part = { part } index = { index } /> ;
10341034 }
1035+ if ( type === "function_call" || type === "custom_tool_call" ) {
1036+ return < FunctionCallPart part = { part } type = { type } /> ;
1037+ }
10351038 if ( type === "tool_result" ) {
10361039 return < ToolResultPart part = { part } /> ;
10371040 }
@@ -1097,6 +1100,24 @@ function ToolUsePart({ part, index }: { part: Record<string, unknown>; index: nu
10971100 ) ;
10981101}
10991102
1103+ function FunctionCallPart ( { part, type } : { part : Record < string , unknown > ; type : string } ) {
1104+ const name = stringRecordValue ( part . name ) || "function" ;
1105+ const id = stringRecordValue ( part . call_id ) || stringRecordValue ( part . id ) ;
1106+ const input = type === "custom_tool_call" ? part . input : part . arguments ;
1107+ return (
1108+ < div className = "tool-use-card" >
1109+ < div className = "tool-use-header" >
1110+ < div className = "tool-use-title" >
1111+ < span className = "part-type-badge" > { type } </ span >
1112+ < span className = "tool-name" > { name } </ span >
1113+ </ div >
1114+ { id && < span className = "tool-id" > { id } </ span > }
1115+ </ div >
1116+ < JsonValueBlock label = { type === "custom_tool_call" ? "Input" : "Arguments" } value = { input ?? { } } />
1117+ </ div >
1118+ ) ;
1119+ }
1120+
11001121function ToolResultPart ( { part } : { part : Record < string , unknown > } ) {
11011122 const id = stringRecordValue ( part . tool_use_id ) || stringRecordValue ( part . id ) || stringRecordValue ( part . call_id ) ;
11021123 return (
@@ -1187,13 +1208,41 @@ function JsonValueBlock({ label, value }: { label: string; value: unknown }) {
11871208
11881209function visualContentParts ( turn : Turn ) : Record < string , unknown > [ ] | null {
11891210 const raw = isRecord ( turn . raw ) ? turn . raw : null ;
1211+ const rawType = stringRecordValue ( raw ?. type ) ;
1212+ if ( raw && isStandaloneVisualPart ( rawType ) ) return [ raw ] ;
1213+
11901214 const content = raw ?. content ;
1191- if ( ! Array . isArray ( content ) ) return null ;
1192- const parts = content . filter ( isRecord ) ;
1215+ const output = raw ?. output ;
1216+ const parts = Array . isArray ( content )
1217+ ? content . filter ( isRecord )
1218+ : Array . isArray ( output )
1219+ ? output . flatMap ( responsesOutputVisualParts )
1220+ : [ ] ;
11931221 if ( parts . length === 0 ) return null ;
11941222 return parts . some ( ( part ) => ! isPlainTextPart ( part ) ) ? parts : null ;
11951223}
11961224
1225+ function responsesOutputVisualParts ( item : unknown ) : Record < string , unknown > [ ] {
1226+ if ( ! isRecord ( item ) ) return [ ] ;
1227+ const type = stringRecordValue ( item . type ) ;
1228+ if ( type === "message" && Array . isArray ( item . content ) ) {
1229+ return item . content . filter ( isRecord ) ;
1230+ }
1231+ return isStandaloneVisualPart ( type ) ? [ item ] : [ ] ;
1232+ }
1233+
1234+ function isStandaloneVisualPart ( type : string ) : boolean {
1235+ return (
1236+ type === "function_call" ||
1237+ type === "custom_tool_call" ||
1238+ type === "function_call_output" ||
1239+ type === "custom_tool_call_output" ||
1240+ type === "image_generation_call" ||
1241+ type === "web_search_call" ||
1242+ type === "reasoning"
1243+ ) ;
1244+ }
1245+
11971246function isPlainTextPart ( part : Record < string , unknown > ) : boolean {
11981247 const type = stringRecordValue ( part . type ) ;
11991248 return ( type === "text" || type === "input_text" || type === "output_text" ) && typeof part . text === "string" ;
@@ -1293,15 +1342,20 @@ function startsWithCollapsibleBlock(text: string): boolean {
12931342}
12941343
12951344function startsWithToolBlock ( text : string ) : boolean {
1296- return text . startsWith ( "**[tool_use " ) || text . startsWith ( "**[tool_result" ) ;
1345+ return (
1346+ text . startsWith ( "**[tool_use " ) ||
1347+ text . startsWith ( "**[tool_result" ) ||
1348+ text . startsWith ( "**[function_call " ) ||
1349+ text . startsWith ( "**[custom_tool_call " )
1350+ ) ;
12971351}
12981352
12991353function startsWithWebSearchCallBlock ( text : string ) : boolean {
13001354 return text . startsWith ( "**[web_search_call " ) ;
13011355}
13021356
13031357function chatPreview ( turn : Turn ) : string {
1304- const text = turnText ( turn ) . replace ( / [ ` * _ # > \[ \] ( ) ] / g, "" ) . replace ( / \s + / g, " " ) . trim ( ) ;
1358+ const text = turnText ( turn ) . replace ( / [ ` * # > \[ \] ( ) ] / g, "" ) . replace ( / \s + / g, " " ) . trim ( ) ;
13051359 if ( ! text ) return "(empty)" ;
13061360 return text . length > 160 ? text . slice ( 0 , 160 ) + "..." : text ;
13071361}
0 commit comments