@@ -186,6 +186,19 @@ pub struct Session {
186186 pub usage : Option < SessionUsage > ,
187187}
188188
189+ #[ derive( Debug , Serialize , Deserialize ) ]
190+ #[ serde( tag = "type" ) ]
191+ pub enum ContentBlock {
192+ #[ serde( rename = "text" ) ]
193+ Text { text : String } ,
194+ #[ serde( rename = "tool_use" ) ]
195+ ToolUse { id : String , name : String , summary : String } ,
196+ #[ serde( rename = "tool_result" ) ]
197+ ToolResult { tool_use_id : String , content : String } ,
198+ #[ serde( rename = "thinking" ) ]
199+ Thinking { thinking : String } ,
200+ }
201+
189202#[ derive( Debug , Serialize , Deserialize ) ]
190203pub struct Message {
191204 pub uuid : String ,
@@ -195,6 +208,7 @@ pub struct Message {
195208 pub is_meta : bool , // slash command 展开的内容
196209 pub is_tool : bool , // tool_use 或 tool_result
197210 pub line_number : usize ,
211+ pub content_blocks : Option < Vec < ContentBlock > > ,
198212}
199213
200214#[ derive( Debug , Serialize , Deserialize ) ]
@@ -1053,6 +1067,7 @@ async fn get_session_messages(
10531067 if let Some ( msg) = & parsed. message {
10541068 let role = msg. role . clone ( ) . unwrap_or_default ( ) ;
10551069 let ( content, is_tool) = extract_content_with_meta ( & msg. content ) ;
1070+ let content_blocks = extract_content_blocks ( & msg. content ) ;
10561071 let is_meta = parsed. is_meta . unwrap_or ( false ) ;
10571072
10581073 if !content. is_empty ( ) {
@@ -1064,6 +1079,7 @@ async fn get_session_messages(
10641079 is_meta,
10651080 is_tool,
10661081 line_number : idx + 1 ,
1082+ content_blocks,
10671083 } ) ;
10681084 }
10691085 }
@@ -1391,6 +1407,103 @@ fn search_chats(
13911407 Ok ( results)
13921408}
13931409
1410+ fn summarize_tool_input ( name : & str , input : & serde_json:: Value ) -> String {
1411+ let obj = match input. as_object ( ) {
1412+ Some ( o) => o,
1413+ None => return String :: new ( ) ,
1414+ } ;
1415+ match name {
1416+ "Read" | "Write" => obj. get ( "file_path" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( "" ) . to_string ( ) ,
1417+ "Edit" => {
1418+ let path = obj. get ( "file_path" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( "" ) ;
1419+ let old = obj. get ( "old_string" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( "" ) ;
1420+ if old. is_empty ( ) { path. to_string ( ) } else {
1421+ format ! ( "{} ({}...)" , path, & old. chars( ) . take( 40 ) . collect:: <String >( ) )
1422+ }
1423+ }
1424+ "Bash" => obj. get ( "command" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( "" ) . to_string ( ) ,
1425+ "Grep" => obj. get ( "pattern" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( "" ) . to_string ( ) ,
1426+ "Glob" => obj. get ( "pattern" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( "" ) . to_string ( ) ,
1427+ "Task" => obj. get ( "description" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( "" ) . to_string ( ) ,
1428+ "WebFetch" => obj. get ( "url" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( "" ) . to_string ( ) ,
1429+ "WebSearch" => obj. get ( "query" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( "" ) . to_string ( ) ,
1430+ _ => {
1431+ // Try common field names
1432+ for key in & [ "file_path" , "path" , "command" , "query" , "pattern" , "url" , "description" ] {
1433+ if let Some ( v) = obj. get ( * key) . and_then ( |v| v. as_str ( ) ) {
1434+ return v. to_string ( ) ;
1435+ }
1436+ }
1437+ String :: new ( )
1438+ }
1439+ }
1440+ }
1441+
1442+ fn extract_tool_result_content ( value : & serde_json:: Value ) -> String {
1443+ match value {
1444+ serde_json:: Value :: String ( s) => s. clone ( ) ,
1445+ serde_json:: Value :: Array ( arr) => {
1446+ arr. iter ( )
1447+ . filter_map ( |item| {
1448+ let obj = item. as_object ( ) ?;
1449+ if obj. get ( "type" ) . and_then ( |v| v. as_str ( ) ) == Some ( "text" ) {
1450+ obj. get ( "text" ) . and_then ( |v| v. as_str ( ) ) . map ( String :: from)
1451+ } else {
1452+ None
1453+ }
1454+ } )
1455+ . collect :: < Vec < _ > > ( )
1456+ . join ( "\n " )
1457+ }
1458+ _ => String :: new ( ) ,
1459+ }
1460+ }
1461+
1462+ fn extract_content_blocks ( value : & Option < serde_json:: Value > ) -> Option < Vec < ContentBlock > > {
1463+ let arr = match value {
1464+ Some ( serde_json:: Value :: Array ( arr) ) => arr,
1465+ Some ( serde_json:: Value :: String ( s) ) => {
1466+ return Some ( vec ! [ ContentBlock :: Text { text: s. clone( ) } ] ) ;
1467+ }
1468+ _ => return None ,
1469+ } ;
1470+
1471+ let blocks: Vec < ContentBlock > = arr
1472+ . iter ( )
1473+ . filter_map ( |item| {
1474+ let obj = item. as_object ( ) ?;
1475+ let block_type = obj. get ( "type" ) . and_then ( |v| v. as_str ( ) ) ?;
1476+ match block_type {
1477+ "text" => {
1478+ let text = obj. get ( "text" ) . and_then ( |v| v. as_str ( ) ) ?. to_string ( ) ;
1479+ if text. is_empty ( ) { None } else { Some ( ContentBlock :: Text { text } ) }
1480+ }
1481+ "tool_use" => {
1482+ let id = obj. get ( "id" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( "" ) . to_string ( ) ;
1483+ let name = obj. get ( "name" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( "" ) . to_string ( ) ;
1484+ let input = obj. get ( "input" ) . cloned ( ) . unwrap_or ( serde_json:: Value :: Null ) ;
1485+ let summary = summarize_tool_input ( & name, & input) ;
1486+ Some ( ContentBlock :: ToolUse { id, name, summary } )
1487+ }
1488+ "tool_result" => {
1489+ let tool_use_id = obj. get ( "tool_use_id" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( "" ) . to_string ( ) ;
1490+ let content = obj. get ( "content" )
1491+ . map ( |v| extract_tool_result_content ( v) )
1492+ . unwrap_or_default ( ) ;
1493+ Some ( ContentBlock :: ToolResult { tool_use_id, content } )
1494+ }
1495+ "thinking" => {
1496+ let thinking = obj. get ( "thinking" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( "" ) . to_string ( ) ;
1497+ if thinking. is_empty ( ) { None } else { Some ( ContentBlock :: Thinking { thinking } ) }
1498+ }
1499+ _ => None ,
1500+ }
1501+ } )
1502+ . collect ( ) ;
1503+
1504+ if blocks. is_empty ( ) { None } else { Some ( blocks) }
1505+ }
1506+
13941507fn extract_content_with_meta ( value : & Option < serde_json:: Value > ) -> ( String , bool ) {
13951508 match value {
13961509 Some ( serde_json:: Value :: String ( s) ) => ( s. clone ( ) , false ) ,
0 commit comments