Files
codex/codex-rs/tui/src/token_usage.rs
Eric Traut c70cdc108f Remove core protocol dependency [1/2] (#20324)
## Why

This stack moves `codex-tui` away from the core protocol event surface
and toward app-server API shapes plus TUI-owned local models. This first
PR sets up the lower-risk foundation: it introduces the local model
surface and extracts app-server event routing into focused TUI modules
while preserving the existing behavior for the larger migration in PR2.

This PR is part 1 of a 2-PR stack:

1. Add TUI-owned replacement models and extract app-server event
routing.
2. Move the active TUI flow to app-server notifications and delete
obsolete adapter code.

## What changed

- Added TUI-owned approval, diff, session state, session resume, token
usage, and user-message models.
- Added `app/app_server_event_targets.rs` and `app/app_server_events.rs`
to hold app-server event targeting and dispatch logic outside `app.rs`.
- Updated app/status tests to use the local model layer and added
focused routing coverage.
- Boxed a few large async TUI test futures so this base layer remains
checkable without overflowing the default test stack.

## Verification

- `cargo check -p codex-tui --tests`
2026-04-30 10:52:19 -07:00

88 lines
2.6 KiB
Rust

//! TUI token usage models and display formatting.
use std::fmt;
use codex_protocol::num_format::format_with_separators;
use serde::Deserialize;
use serde::Serialize;
const BASELINE_TOKENS: i64 = 12000;
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct TokenUsage {
pub input_tokens: i64,
pub cached_input_tokens: i64,
pub output_tokens: i64,
pub reasoning_output_tokens: i64,
pub total_tokens: i64,
}
impl TokenUsage {
pub fn is_zero(&self) -> bool {
self.total_tokens == 0
}
pub(crate) fn cached_input(&self) -> i64 {
self.cached_input_tokens.max(0)
}
pub(crate) fn non_cached_input(&self) -> i64 {
(self.input_tokens - self.cached_input()).max(0)
}
pub(crate) fn blended_total(&self) -> i64 {
(self.non_cached_input() + self.output_tokens.max(0)).max(0)
}
pub(crate) fn tokens_in_context_window(&self) -> i64 {
self.total_tokens
}
pub(crate) fn percent_of_context_window_remaining(&self, context_window: i64) -> i64 {
if context_window <= BASELINE_TOKENS {
return 0;
}
let effective_window = context_window - BASELINE_TOKENS;
let used = (self.tokens_in_context_window() - BASELINE_TOKENS).max(0);
let remaining = (effective_window - used).max(0);
((remaining as f64 / effective_window as f64) * 100.0)
.clamp(0.0, 100.0)
.round() as i64
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct TokenUsageInfo {
pub(crate) total_token_usage: TokenUsage,
pub(crate) last_token_usage: TokenUsage,
pub(crate) model_context_window: Option<i64>,
}
impl fmt::Display for TokenUsage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Token usage: total={} input={}{} output={}{}",
format_with_separators(self.blended_total()),
format_with_separators(self.non_cached_input()),
if self.cached_input() > 0 {
format!(
" (+ {} cached)",
format_with_separators(self.cached_input())
)
} else {
String::new()
},
format_with_separators(self.output_tokens),
if self.reasoning_output_tokens > 0 {
format!(
" (reasoning {})",
format_with_separators(self.reasoning_output_tokens)
)
} else {
String::new()
}
)
}
}