use core::fmt::Write as _;
const CYAN : &str = "\x1b[36m";
const GREEN : &str = "\x1b[32m";
const YELLOW : &str = "\x1b[33m";
const DIM : &str = "\x1b[2m";
const RESET : &str = "\x1b[0m";
fn extract_str( s : &str, key : &str ) -> Option< String >
{
let needle = format!( "\"{key}\":" );
let pos = s.find( &needle )?;
let rest = s[ pos + needle.len() .. ].trim_start_matches( ' ' );
if rest.starts_with( "null" ) { return None; }
if !rest.starts_with( '"' ) { return None; }
let inner = &rest[ 1 .. ];
let mut out = String::new();
let mut escape = false;
for c in inner.chars()
{
if escape
{
match c
{
'n' => out.push( '\n' ),
't' => out.push( '\t' ),
'r' => out.push( '\r' ),
'"' => out.push( '"' ),
'\\' => out.push( '\\' ),
'/' => out.push( '/' ),
_ => { out.push( '\\' ); out.push( c ); }
}
escape = false;
continue;
}
if c == '\\' { escape = true; continue; }
if c == '"' { return Some( out ); }
out.push( c );
}
Some( out )
}
fn extract_u64( s : &str, key : &str ) -> Option< u64 >
{
let needle = format!( "\"{key}\":" );
let pos = s.find( &needle )?;
let rest = s[ pos + needle.len() .. ].trim_start_matches( ' ' );
let end = rest.find( |c : char| !c.is_ascii_digit() ).unwrap_or( rest.len() );
rest[ ..end ].parse().ok()
}
fn extract_f64( s : &str, key : &str ) -> Option< f64 >
{
let needle = format!( "\"{key}\":" );
let pos = s.find( &needle )?;
let rest = s[ pos + needle.len() .. ].trim_start_matches( ' ' );
let end = rest
.find( |c : char| !matches!( c, '0'..='9' | '.' | '-' | 'e' | 'E' | '+' ) )
.unwrap_or( rest.len() );
rest[ ..end ].parse().ok()
}
pub( super ) fn render_summary( json : &str ) -> Option< String >
{
let session_id = extract_str( json, "session_id" )?;
let msg_type = extract_str( json, "type" ).unwrap_or_default();
let subtype = extract_str( json, "subtype" ).unwrap_or_default();
let is_error = json.contains( "\"is_error\":true" );
let result = extract_str( json, "result" ).unwrap_or_default();
let usage_marker = "\"usage\":{";
let usage_str = json.find( usage_marker ).map( |p| &json[ p + usage_marker.len() .. ] );
let input_tokens = usage_str.and_then( |s| extract_u64( s, "input_tokens" ) ).unwrap_or( 0 );
let output_tokens = usage_str.and_then( |s| extract_u64( s, "output_tokens" ) ).unwrap_or( 0 );
let cost = extract_f64( json, "total_cost_usd" ).unwrap_or( 0.0 );
let is_err_s = if is_error { "true" } else { "false" };
let mut out = String::new();
let _ = writeln!( out, "{CYAN}type:{RESET} {GREEN}{msg_type}{RESET}" );
let _ = writeln!( out, "{CYAN}subtype:{RESET} {GREEN}{subtype}{RESET}" );
let _ = writeln!( out, "{CYAN}session_id:{RESET} {GREEN}{session_id}{RESET}" );
let _ = writeln!( out, "{CYAN}is_error:{RESET} {YELLOW}{is_err_s}{RESET}" );
let _ = writeln!( out, "{CYAN}input_tokens:{RESET} {YELLOW}{input_tokens}{RESET}" );
let _ = writeln!( out, "{CYAN}output_tokens:{RESET} {YELLOW}{output_tokens}{RESET}" );
let _ = writeln!( out, "{CYAN}total_cost_usd:{RESET} {YELLOW}{cost:.4}{RESET}" );
let _ = writeln!( out, "{DIM}---{RESET}" );
out.push_str( &result );
if !result.is_empty() && !result.ends_with( '\n' ) { out.push( '\n' ); }
Some( out )
}
#[ cfg( test ) ]
mod tests
{
use super::render_summary;
#[ test ]
fn ec14_render_summary_clr_envelope_accepted()
{
let json = r#"{"type":"result","subtype":"success","session_id":"00000000-0000-0000-0000-000000000001","is_error":false,"result":"hello world","usage":{"input_tokens":3,"output_tokens":4},"total_cost_usd":0.001}"#;
let rendered = render_summary( json );
assert!( rendered.is_some(), "render_summary must return Some for valid CLR envelope; got None" );
let s = rendered.unwrap();
assert!( s.contains( "---" ), "rendered output must contain separator '---'. Got:\n{s}" );
assert!( s.contains( "hello world" ), "rendered output must contain the result text. Got:\n{s}" );
assert!( s.contains( "session_id:" ), "output must contain 'session_id:'. Got:\n{s}" );
}
#[ test ]
fn extract_str_unescapes_json_newlines()
{
let json = r#"{"type":"result","subtype":"success","session_id":"x","is_error":false,"result":"line1\nline2","usage":{"input_tokens":0,"output_tokens":0},"total_cost_usd":0.0}"#;
let rendered = render_summary( json ).expect( "must parse" );
assert!( rendered.contains( "line1\nline2" ), "\\n must be unescaped to actual newline. Got:\n{rendered}" );
}
}