From 096bca2fa26d9155c61b3327ff8ff65090687a1b Mon Sep 17 00:00:00 2001 From: ae Date: Tue, 19 Aug 2025 08:20:32 -0700 Subject: [PATCH] fix: exclude sysprompt etc from context left % (#2446) - Prevents the % left indicator from immediately decrementing to ~97%. - Tested by prompting "hi" and noting it only decremented to 99%. And by adding a bunch of debug logs and observing numbers. --- codex-rs/protocol/src/protocol.rs | 27 +++++++++++++++++++ codex-rs/tui/src/bottom_pane/chat_composer.rs | 25 +++++++++++++---- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 23ef4668..5d32b8c3 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -508,6 +508,33 @@ impl TokenUsage { self.total_tokens .saturating_sub(self.reasoning_output_tokens.unwrap_or(0)) } + + /// Estimate the remaining user-controllable percentage of the model's context window. + /// + /// `context_window` is the total size of the model's context window. + /// `baseline_used_tokens` should capture tokens that are always present in + /// the context (e.g., system prompt and fixed tool instructions) so that + /// the percentage reflects the portion the user can influence. + /// + /// This normalizes both the numerator and denominator by subtracting the + /// baseline, so immediately after the first prompt the UI shows 100% left + /// and trends toward 0% as the user fills the effective window. + pub fn percent_of_context_window_remaining( + &self, + context_window: u64, + baseline_used_tokens: u64, + ) -> u8 { + if context_window <= baseline_used_tokens { + return 0; + } + + let effective_window = context_window - baseline_used_tokens; + let used = self + .tokens_in_context_window() + .saturating_sub(baseline_used_tokens); + let remaining = effective_window.saturating_sub(used); + ((remaining as f32 / effective_window as f32) * 100.0).clamp(0.0, 100.0) as u8 + } } #[derive(Debug, Clone, Deserialize, Serialize)] diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index c801d6fd..71483f04 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -45,6 +45,15 @@ struct TokenUsageInfo { total_token_usage: TokenUsage, last_token_usage: TokenUsage, model_context_window: Option, + /// Baseline token count present in the context before the user's first + /// message content is considered. This is used to normalize the + /// "context left" percentage so it reflects the portion the user can + /// influence rather than fixed prompt overhead (system prompt, tool + /// instructions, etc.). + /// + /// Preferred source is `cached_input_tokens` from the first turn (when + /// available), otherwise we fall back to 0. + initial_prompt_tokens: u64, } pub(crate) struct ChatComposer { @@ -134,10 +143,17 @@ impl ChatComposer { last_token_usage: TokenUsage, model_context_window: Option, ) { + let initial_prompt_tokens = self + .token_usage_info + .as_ref() + .map(|info| info.initial_prompt_tokens) + .unwrap_or_else(|| last_token_usage.cached_input_tokens.unwrap_or(0)); + self.token_usage_info = Some(TokenUsageInfo { total_token_usage, last_token_usage, model_context_window, + initial_prompt_tokens, }); } @@ -673,11 +689,10 @@ impl WidgetRef for &ChatComposer { let last_token_usage = &token_usage_info.last_token_usage; if let Some(context_window) = token_usage_info.model_context_window { let percent_remaining: u8 = if context_window > 0 { - let percent = 100.0 - - (last_token_usage.tokens_in_context_window() as f32 - / context_window as f32 - * 100.0); - percent.clamp(0.0, 100.0) as u8 + last_token_usage.percent_of_context_window_remaining( + context_window, + token_usage_info.initial_prompt_tokens, + ) } else { 100 };