From f0904922f6f679699fa11d144ab29f08dd5cc770 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 17 Aug 2026 16:27:03 +0000 Subject: [PATCH] Reduce retained MCP result data in TUI history (#39023) ## Why MCP tool results can contain multi-megabyte image, audio, or resource bodies, even though TUI history renders only summaries for those content types. ## What changed - Project completed MCP results into width-independent display data, dropping media and resource bodies that history does not render. - Preserve text output, JSON fallbacks, error state, Node REPL rendering, and the fully decoded image-output marker. - Add tests covering width-dependent rendering, image validation, and Node REPL text fields on non-text and unknown content blocks. GitOrigin-RevId: 2cfddf0864093e6837a4431e541f7a9cab544a68 --- codex-rs/tui/src/history_cell/mcp.rs | 158 +++++------------- codex-rs/tui/src/history_cell/mcp_result.rs | 169 ++++++++++++++++++++ codex-rs/tui/src/history_cell/mcp_tests.rs | 142 ++++++++++++++++ codex-rs/tui/src/history_cell/mod.rs | 5 - 4 files changed, 353 insertions(+), 121 deletions(-) create mode 100644 codex-rs/tui/src/history_cell/mcp_result.rs create mode 100644 codex-rs/tui/src/history_cell/mcp_tests.rs diff --git a/codex-rs/tui/src/history_cell/mcp.rs b/codex-rs/tui/src/history_cell/mcp.rs index 824c8104c3..fc1ba35280 100644 --- a/codex-rs/tui/src/history_cell/mcp.rs +++ b/codex-rs/tui/src/history_cell/mcp.rs @@ -2,6 +2,16 @@ use super::*; +#[path = "mcp_result.rs"] +mod result; + +use result::McpResultKind; +use result::McpToolResult; + +#[cfg(test)] +#[path = "mcp_tests.rs"] +mod tests; + #[derive(Debug)] struct McpImageOutputCell; @@ -29,7 +39,7 @@ pub(crate) struct McpToolCallCell { invocation: McpInvocation, start_time: Instant, duration: Option, - result: Option>, + result: Option>, animations_enabled: bool, } @@ -79,8 +89,12 @@ impl McpToolCallCell { duration: Duration, result: Result, ) -> Option> { - let image_cell = try_new_completed_mcp_tool_call_with_image_output(&result) - .map(|cell| Box::new(cell) as Box); + let result = result.map(|result| McpToolResult::new(result, self.result_kind())); + let image_cell = result + .as_ref() + .ok() + .filter(|result| result.has_image) + .map(|_| Box::new(McpImageOutputCell) as Box); self.duration = Some(duration); self.result = Some(result); image_cell @@ -88,7 +102,7 @@ impl McpToolCallCell { fn success(&self) -> Option { match self.result.as_ref() { - Some(Ok(result)) => Some(!result.is_error.unwrap_or(false)), + Some(Ok(result)) => Some(!result.is_error), Some(Err(_)) => Some(false), None => None, } @@ -100,40 +114,18 @@ impl McpToolCallCell { self.result = Some(Err("interrupted".to_string())); } - fn render_content_block(block: &serde_json::Value, width: usize) -> String { - let content = match serde_json::from_value::(block.clone()) { - Ok(content) => content, - Err(_) => { - return format_and_truncate_tool_result( - &block.to_string(), - TOOL_CALL_MAX_LINES, - width, - ); - } - }; - - match content { - rmcp::model::ContentBlock::Text(text) => { - format_and_truncate_tool_result(&text.text, TOOL_CALL_MAX_LINES, width) - } - rmcp::model::ContentBlock::Image(_) => "".to_string(), - rmcp::model::ContentBlock::Audio(_) => "