diff --git a/codex-rs/exec/src/event_processor_with_human_output.rs b/codex-rs/exec/src/event_processor_with_human_output.rs index cfdba98461..81f6ecc2ad 100644 --- a/codex-rs/exec/src/event_processor_with_human_output.rs +++ b/codex-rs/exec/src/event_processor_with_human_output.rs @@ -25,6 +25,7 @@ use codex_core::protocol::TaskCompleteEvent; use codex_core::protocol::TurnAbortReason; use codex_core::protocol::TurnDiffEvent; use codex_core::protocol::WebSearchBeginEvent; +use codex_core::protocol::format_token_count; use owo_colors::OwoColorize; use owo_colors::Style; use shlex::try_join; @@ -189,7 +190,8 @@ impl EventProcessor for EventProcessorWithHumanOutput { return CodexStatus::InitiateShutdown; } EventMsg::TokenCount(token_usage) => { - ts_println!(self, "tokens used: {}", token_usage.blended_total()); + let tokens = format_token_count(token_usage.blended_total()); + ts_println!(self, "tokens used: {tokens}"); } EventMsg::AgentMessageDelta(AgentMessageDeltaEvent { delta }) => { if !self.answer_started { diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 71c1538197..348e6af232 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -558,6 +558,17 @@ impl TokenUsage { } } +/// Format a token count with thousands separators for readability. +pub fn format_token_count(value: u64) -> String { + let mut s = value.to_string(); + let mut i = s.len(); + while i > 3 { + i -= 3; + s.insert(i, ','); + } + s +} + #[derive(Debug, Clone, Deserialize, Serialize)] pub struct FinalOutput { pub token_usage: TokenUsage, @@ -572,21 +583,26 @@ impl From for FinalOutput { impl fmt::Display for FinalOutput { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let token_usage = &self.token_usage; + let total = format_token_count(token_usage.blended_total()); + let input = format_token_count(token_usage.non_cached_input()); + let cached = token_usage.cached_input(); + let cached_str = if cached > 0 { + let cached_tokens = format_token_count(cached); + format!(" (+ {cached_tokens} cached)") + } else { + String::new() + }; + let output = format_token_count(token_usage.output_tokens); + let reasoning = token_usage + .reasoning_output_tokens + .map(|r| { + let reasoning_tokens = format_token_count(r); + format!(" (reasoning {reasoning_tokens})") + }) + .unwrap_or_default(); write!( f, - "Token usage: total={} input={}{} output={}{}", - token_usage.blended_total(), - token_usage.non_cached_input(), - if token_usage.cached_input() > 0 { - format!(" (+ {} cached)", token_usage.cached_input()) - } else { - String::new() - }, - token_usage.output_tokens, - token_usage - .reasoning_output_tokens - .map(|r| format!(" (reasoning {r})")) - .unwrap_or_default() + "Token usage: total={total} input={input}{cached_str} output={output}{reasoning}" ) } } diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index 0d8a34a809..017302fadd 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -1,4 +1,5 @@ use codex_core::protocol::TokenUsage; +use codex_core::protocol::format_token_count; use crossterm::event::KeyCode; use crossterm::event::KeyEvent; use crossterm::event::KeyModifiers; @@ -1153,8 +1154,9 @@ impl WidgetRef for &ChatComposer { if let Some(token_usage_info) = &self.token_usage_info { let token_usage = &token_usage_info.total_token_usage; hint.push(Span::from(" ")); + let tokens = format_token_count(token_usage.blended_total()); hint.push( - Span::from(format!("{} tokens used", token_usage.blended_total())) + Span::from(format!("{tokens} tokens used")) .style(Style::default().add_modifier(Modifier::DIM)), ); let last_token_usage = &token_usage_info.last_token_usage; diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 0b2af7a100..cdf3fb189b 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -18,6 +18,7 @@ use codex_core::protocol::McpInvocation; use codex_core::protocol::SandboxPolicy; use codex_core::protocol::SessionConfiguredEvent; use codex_core::protocol::TokenUsage; +use codex_core::protocol::format_token_count; use codex_login::get_auth_file; use codex_login::try_read_auth_json; use codex_protocol::parse_command::ParsedCommand; @@ -734,23 +735,24 @@ pub(crate) fn new_status_output( // Input: [+ cached] let mut input_line_spans: Vec> = vec![ " • Input: ".into(), - usage.non_cached_input().to_string().into(), + format_token_count(usage.non_cached_input()).into(), ]; if let Some(cached) = usage.cached_input_tokens && cached > 0 { - input_line_spans.push(format!(" (+ {cached} cached)").into()); + let cached_tokens = format_token_count(cached); + input_line_spans.push(format!(" (+ {cached_tokens} cached)").into()); } lines.push(Line::from(input_line_spans)); // Output: lines.push(Line::from(vec![ " • Output: ".into(), - usage.output_tokens.to_string().into(), + format_token_count(usage.output_tokens).into(), ])); // Total: lines.push(Line::from(vec![ " • Total: ".into(), - usage.blended_total().to_string().into(), + format_token_count(usage.blended_total()).into(), ])); PlainHistoryCell { lines }