@@ -345,6 +345,17 @@ impl ToolCall {
345345 self . label. read( cx) . source( ) ,
346346 self . status
347347 ) ;
348+
349+ // Try to format raw_output nicely (e.g., Qwen Code shell output)
350+ if let Some ( raw_output) = & self . raw_output {
351+ if let Some ( formatted) = format_shell_output ( raw_output) {
352+ markdown. push_str ( & formatted) ;
353+ markdown. push_str ( "\n \n " ) ;
354+ return markdown;
355+ }
356+ }
357+
358+ // Fallback to content-based rendering
348359 for content in & self . content {
349360 markdown. push_str ( content. to_markdown ( cx) . as_str ( ) ) ;
350361 markdown. push_str ( "\n \n " ) ;
@@ -2377,14 +2388,139 @@ fn markdown_for_raw_output(
23772388 cx,
23782389 )
23792390 } ) ) ,
2380- value => Some ( cx. new ( |cx| {
2381- Markdown :: new (
2382- format ! ( "```json\n {}\n ```" , value) . into ( ) ,
2383- Some ( language_registry. clone ( ) ) ,
2384- None ,
2385- cx,
2386- )
2387- } ) ) ,
2391+ value => {
2392+ // Try to format shell output nicely
2393+ if let Some ( formatted) = format_shell_output ( value) {
2394+ Some ( cx. new ( |cx| {
2395+ Markdown :: new (
2396+ formatted. into ( ) ,
2397+ Some ( language_registry. clone ( ) ) ,
2398+ None ,
2399+ cx,
2400+ )
2401+ } ) )
2402+ } else {
2403+ Some ( cx. new ( |cx| {
2404+ Markdown :: new (
2405+ format ! ( "```json\n {}\n ```" , value) . into ( ) ,
2406+ Some ( language_registry. clone ( ) ) ,
2407+ None ,
2408+ cx,
2409+ )
2410+ } ) )
2411+ }
2412+ }
2413+ }
2414+ }
2415+
2416+ /// Format shell command output with a metadata table and output code block.
2417+ /// Extracts Command, Directory, Exit Code into a table, and Output into a code block.
2418+ /// Returns None if the output doesn't match the expected shell output format.
2419+ fn format_shell_output ( output : & serde_json:: Value ) -> Option < String > {
2420+ // Shell output comes as {"output":"Command: cd /path\nDirectory: ...\nOutput: ...\nExit Code: 0"}
2421+ let obj = output. as_object ( ) ?;
2422+ let output_str = obj. get ( "output" ) ?. as_str ( ) ?;
2423+
2424+ // Parse all the shell output fields
2425+ let field_markers = [
2426+ "Command:" ,
2427+ "Directory:" ,
2428+ "Output:" ,
2429+ "Error:" ,
2430+ "Exit Code:" ,
2431+ "Signal:" ,
2432+ "Background PIDs:" ,
2433+ "Process Group PGID:" ,
2434+ ] ;
2435+
2436+ let mut command: Option < String > = None ;
2437+ let mut directory: Option < String > = None ;
2438+ let mut exit_code: Option < String > = None ;
2439+ let mut signal: Option < String > = None ;
2440+ let mut cmd_output = Vec :: new ( ) ;
2441+ let mut in_output_field = false ;
2442+
2443+ for line in output_str. lines ( ) {
2444+ // Check if this line starts a new field
2445+ let starts_new_field = field_markers. iter ( ) . any ( |m| line. starts_with ( m) ) ;
2446+
2447+ if line. starts_with ( "Command:" ) {
2448+ in_output_field = false ;
2449+ command = Some ( line. strip_prefix ( "Command:" ) . unwrap_or ( "" ) . trim ( ) . to_string ( ) ) ;
2450+ } else if line. starts_with ( "Directory:" ) {
2451+ in_output_field = false ;
2452+ directory = Some ( line. strip_prefix ( "Directory:" ) . unwrap_or ( "" ) . trim ( ) . to_string ( ) ) ;
2453+ } else if line. starts_with ( "Exit Code:" ) {
2454+ in_output_field = false ;
2455+ exit_code = Some ( line. strip_prefix ( "Exit Code:" ) . unwrap_or ( "" ) . trim ( ) . to_string ( ) ) ;
2456+ } else if line. starts_with ( "Signal:" ) {
2457+ in_output_field = false ;
2458+ signal = Some ( line. strip_prefix ( "Signal:" ) . unwrap_or ( "" ) . trim ( ) . to_string ( ) ) ;
2459+ } else if line. starts_with ( "Output:" ) {
2460+ in_output_field = true ;
2461+ let value = line. strip_prefix ( "Output:" ) . unwrap_or ( "" ) . trim ( ) ;
2462+ if !value. is_empty ( ) {
2463+ cmd_output. push ( value. to_string ( ) ) ;
2464+ }
2465+ } else if starts_new_field {
2466+ in_output_field = false ;
2467+ } else if in_output_field {
2468+ cmd_output. push ( line. to_string ( ) ) ;
2469+ }
2470+ }
2471+
2472+ let full_output = cmd_output. join ( "\n " ) ;
2473+ let has_output = !full_output. is_empty ( ) && full_output != "(empty)" ;
2474+
2475+ // Build the formatted result with a markdown table for metadata
2476+ let mut result = String :: new ( ) ;
2477+
2478+ // Build table rows
2479+ let mut table_rows = Vec :: new ( ) ;
2480+ if let Some ( cmd) = & command {
2481+ table_rows. push ( format ! ( "| Command | `{}` |" , cmd) ) ;
2482+ }
2483+ if let Some ( dir) = & directory {
2484+ table_rows. push ( format ! ( "| Directory | `{}` |" , dir) ) ;
2485+ }
2486+ // Combine exit code and signal into one field
2487+ match ( & exit_code, & signal) {
2488+ ( Some ( code) , Some ( sig) ) => {
2489+ table_rows. push ( format ! ( "| Exit | {} (signal: {}) |" , code, sig) ) ;
2490+ }
2491+ ( Some ( code) , None ) => {
2492+ table_rows. push ( format ! ( "| Exit | {} |" , code) ) ;
2493+ }
2494+ ( None , Some ( sig) ) => {
2495+ table_rows. push ( format ! ( "| Exit | signal: {} |" , sig) ) ;
2496+ }
2497+ ( None , None ) => { }
2498+ }
2499+
2500+ // Add table if we have metadata
2501+ if !table_rows. is_empty ( ) {
2502+ result. push_str ( "| | |\n |---|---|\n " ) ;
2503+ result. push_str ( & table_rows. join ( "\n " ) ) ;
2504+ }
2505+
2506+ // Add output code block
2507+ if has_output {
2508+ if !result. is_empty ( ) {
2509+ result. push_str ( "\n \n " ) ;
2510+ }
2511+ result. push_str ( & format ! ( "```\n {}\n ```" , full_output) ) ;
2512+ }
2513+
2514+ if result. is_empty ( ) {
2515+ // Fallback: if we have an "output" field but couldn't parse it in the
2516+ // expected format, just show the raw output in a code block
2517+ if !output_str. is_empty ( ) {
2518+ Some ( format ! ( "```\n {}\n ```" , output_str) )
2519+ } else {
2520+ None
2521+ }
2522+ } else {
2523+ Some ( result)
23882524 }
23892525}
23902526
0 commit comments