From c4dc6a80bf349641a0270cb2ac78668ebade1849 Mon Sep 17 00:00:00 2001 From: ae Date: Thu, 7 Aug 2025 04:02:58 -0700 Subject: [PATCH] feat: improve output of /status (#1936) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now it looks like this: ``` /status πŸ“‚ Workspace β€’ Path: ~/code/codex/codex-rs β€’ Approval Mode: on-request β€’ Sandbox: workspace-write πŸ‘€ Account β€’ Signed in with ChatGPT β€’ Login: example@example.com β€’ Plan: Pro 🧠 Model β€’ Name: ?!?!?!?!?! β€’ Provider: OpenAI πŸ“Š Token Usage β€’ Input: 11940 (+ 7999 cached) β€’ Output: 2639 β€’ Total: 14579 ``` --- codex-rs/tui/src/history_cell.rs | 148 ++++++++++++++++++++++++------- 1 file changed, 118 insertions(+), 30 deletions(-) diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index bbf1d721..5d2ea35c 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -13,6 +13,7 @@ use codex_core::plan_tool::StepStatus; use codex_core::plan_tool::UpdatePlanArgs; use codex_core::protocol::FileChange; use codex_core::protocol::McpInvocation; +use codex_core::protocol::SandboxPolicy; use codex_core::protocol::SessionConfiguredEvent; use codex_core::protocol::TokenUsage; use codex_login::get_auth_file; @@ -134,6 +135,27 @@ pub(crate) enum HistoryCell { const TOOL_CALL_MAX_LINES: usize = 3; +fn title_case(s: &str) -> String { + if s.is_empty() { + return String::new(); + } + let mut chars = s.chars(); + let first = match chars.next() { + Some(c) => c, + None => return String::new(), + }; + let rest: String = chars.as_str().to_ascii_lowercase(); + first.to_uppercase().collect::() + &rest +} + +fn pretty_provider_name(id: &str) -> String { + if id.eq_ignore_ascii_case("openai") { + "OpenAI".to_string() + } else { + title_case(id) + } +} + impl HistoryCell { /// Return a cloned, plain representation of the cell's lines suitable for /// one‑shot insertion into the terminal scrollback. Image cells are @@ -471,35 +493,65 @@ impl HistoryCell { let mut lines: Vec> = Vec::new(); lines.push(Line::from("/status".magenta())); - // Config - for (key, value) in create_config_summary_entries(config) { - lines.push(Line::from(vec![format!("{key}: ").bold(), value.into()])); - } + let config_entries = create_config_summary_entries(config); + let lookup = |k: &str| -> String { + config_entries + .iter() + .find(|(key, _)| *key == k) + .map(|(_, v)| v.clone()) + .unwrap_or_default() + }; + + // πŸ“‚ Workspace + lines.push(Line::from(vec!["πŸ“‚ ".into(), "Workspace".bold()])); + // Path (home-relative, e.g., ~/code/project) + let cwd_str = match relativize_to_home(&config.cwd) { + Some(rel) if !rel.as_os_str().is_empty() => format!("~/{}", rel.display()), + Some(_) => "~".to_string(), + None => config.cwd.display().to_string(), + }; + lines.push(Line::from(vec![" β€’ Path: ".into(), cwd_str.into()])); + // Approval mode (as-is) + lines.push(Line::from(vec![ + " β€’ Approval Mode: ".into(), + lookup("approval").into(), + ])); + // Sandbox (simplified name only) + let sandbox_name = match &config.sandbox_policy { + SandboxPolicy::DangerFullAccess => "danger-full-access", + SandboxPolicy::ReadOnly => "read-only", + SandboxPolicy::WorkspaceWrite { .. } => "workspace-write", + }; + lines.push(Line::from(vec![ + " β€’ Sandbox: ".into(), + sandbox_name.into(), + ])); lines.push(Line::from("")); - // Auth + // πŸ‘€ Account (only if ChatGPT tokens exist), shown under the first block let auth_file = get_auth_file(&config.codex_home); if let Ok(auth) = try_read_auth_json(&auth_file) { - if auth.tokens.as_ref().is_some() { - lines.push(Line::from("signed in with chatgpt".bold())); + if let Some(tokens) = auth.tokens.clone() { + lines.push(Line::from(vec!["πŸ‘€ ".into(), "Account".bold()])); + lines.push(Line::from(" β€’ Signed in with ChatGPT")); - if let Some(tokens) = auth.tokens.as_ref() { - let info = tokens.id_token.clone(); - if let Some(email) = info.email { - lines.push(Line::from(vec![" login: ".bold(), email.into()])); + let info = tokens.id_token; + if let Some(email) = info.email { + lines.push(Line::from(vec![" β€’ Login: ".into(), email.into()])); + } + + match auth.openai_api_key.as_deref() { + Some(key) if !key.is_empty() => { + lines.push(Line::from(" β€’ Using API key")); } - - match auth.openai_api_key.as_deref() { - Some(key) if !key.is_empty() => { - lines.push(Line::from(" using api key")); - } - _ => { - let plan_text = info - .chatgpt_plan_type - .unwrap_or_else(|| "Unknown".to_string()); - lines.push(Line::from(vec![" plan: ".bold(), plan_text.into()])); - } + _ => { + let plan_text = info + .chatgpt_plan_type + .as_deref() + .map(title_case) + .unwrap_or_else(|| "Unknown".to_string()); + lines.push(Line::from(vec![" β€’ Plan: ".into(), plan_text.into()])); } } @@ -507,20 +559,56 @@ impl HistoryCell { } } - // Token usage - lines.push(Line::from("token usage".bold())); + // 🧠 Model + lines.push(Line::from(vec!["🧠 ".into(), "Model".bold()])); lines.push(Line::from(vec![ - " input: ".bold(), - usage.non_cached_input().to_string().into(), - " ".into(), - format!("(+ {} cached)", usage.cached_input()).into(), + " β€’ Name: ".into(), + config.model.clone().into(), ])); + let provider_disp = pretty_provider_name(&config.model_provider_id); lines.push(Line::from(vec![ - " output: ".bold(), + " β€’ Provider: ".into(), + provider_disp.into(), + ])); + // Only show Reasoning fields if present in config summary + let reff = lookup("reasoning effort"); + if !reff.is_empty() { + lines.push(Line::from(vec![ + " β€’ Reasoning Effort: ".into(), + title_case(&reff).into(), + ])); + } + let rsum = lookup("reasoning summaries"); + if !rsum.is_empty() { + lines.push(Line::from(vec![ + " β€’ Reasoning Summaries: ".into(), + title_case(&rsum).into(), + ])); + } + + lines.push(Line::from("")); + + // πŸ“Š Token Usage + lines.push(Line::from(vec!["πŸ“Š ".into(), "Token Usage".bold()])); + // Input: [+ cached] + let mut input_line_spans: Vec> = vec![ + " β€’ Input: ".into(), + usage.non_cached_input().to_string().into(), + ]; + if let Some(cached) = usage.cached_input_tokens { + if cached > 0 { + input_line_spans.push(format!(" (+ {cached} cached)").into()); + } + } + lines.push(Line::from(input_line_spans)); + // Output: + lines.push(Line::from(vec![ + " β€’ Output: ".into(), usage.output_tokens.to_string().into(), ])); + // Total: lines.push(Line::from(vec![ - " total: ".bold(), + " β€’ Total: ".into(), usage.blended_total().to_string().into(), ]));