diff --git a/src-tauri/src/wsl_bridge.rs b/src-tauri/src/wsl_bridge.rs index 1b14dd7..c3e5fc8 100644 --- a/src-tauri/src/wsl_bridge.rs +++ b/src-tauri/src/wsl_bridge.rs @@ -875,6 +875,32 @@ fn parse_subagent_stop_hook(line: &str) -> Option { }) } +/// Extract text content from a ToolResult's `content` field. +/// The content may be a JSON string or an array of typed content blocks. +fn extract_tool_result_text(content: &serde_json::Value) -> Option { + match content { + serde_json::Value::String(s) if !s.is_empty() => Some(s.clone()), + serde_json::Value::Array(blocks) => { + let texts: Vec = blocks + .iter() + .filter_map(|block| { + if block.get("type")?.as_str()? == "text" { + block.get("text")?.as_str().map(String::from) + } else { + None + } + }) + .collect(); + if texts.is_empty() { + None + } else { + Some(texts.join("\n")) + } + } + _ => None, + } +} + fn process_json_line( line: &str, app: &AppHandle, @@ -1176,8 +1202,8 @@ fn process_json_line( } ContentBlock::ToolResult { tool_use_id, + content, is_error, - .. } => { // Emit agent-end for all tool results // The frontend will ignore IDs that don't match known agents @@ -1195,7 +1221,7 @@ fn process_json_line( conversation_id: conversation_id.clone(), duration_ms: None, num_turns: None, - last_assistant_message: None, + last_assistant_message: extract_tool_result_text(content), }, ); } @@ -1613,8 +1639,8 @@ fn process_json_line( for block in &message.content { if let ContentBlock::ToolResult { tool_use_id, + content, is_error, - .. } = block { let now = SystemTime::now() @@ -1631,7 +1657,7 @@ fn process_json_line( conversation_id: conversation_id.clone(), duration_ms: None, num_turns: None, - last_assistant_message: None, + last_assistant_message: extract_tool_result_text(content), }, ); } @@ -2252,4 +2278,71 @@ mod tests { Some("Found 3 files, all passing.".to_string()) ); } + + // extract_tool_result_text tests + #[test] + fn test_extract_tool_result_text_plain_string() { + let content = serde_json::json!("Hello from agent"); + assert_eq!( + extract_tool_result_text(&content), + Some("Hello from agent".to_string()) + ); + } + + #[test] + fn test_extract_tool_result_text_empty_string() { + let content = serde_json::json!(""); + assert_eq!(extract_tool_result_text(&content), None); + } + + #[test] + fn test_extract_tool_result_text_array_single_text_block() { + let content = serde_json::json!([{"type": "text", "text": "Agent completed the task."}]); + assert_eq!( + extract_tool_result_text(&content), + Some("Agent completed the task.".to_string()) + ); + } + + #[test] + fn test_extract_tool_result_text_array_multiple_text_blocks() { + let content = serde_json::json!([ + {"type": "text", "text": "First part."}, + {"type": "text", "text": "Second part."} + ]); + assert_eq!( + extract_tool_result_text(&content), + Some("First part.\nSecond part.".to_string()) + ); + } + + #[test] + fn test_extract_tool_result_text_array_non_text_block() { + let content = serde_json::json!([{"type": "image", "source": {"type": "base64"}}]); + assert_eq!(extract_tool_result_text(&content), None); + } + + #[test] + fn test_extract_tool_result_text_array_mixed_blocks() { + let content = serde_json::json!([ + {"type": "image", "source": {}}, + {"type": "text", "text": "Found results."} + ]); + assert_eq!( + extract_tool_result_text(&content), + Some("Found results.".to_string()) + ); + } + + #[test] + fn test_extract_tool_result_text_null() { + let content = serde_json::Value::Null; + assert_eq!(extract_tool_result_text(&content), None); + } + + #[test] + fn test_extract_tool_result_text_empty_array() { + let content = serde_json::json!([]); + assert_eq!(extract_tool_result_text(&content), None); + } }