mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Show token used when context window is unknown (#7497)
- Show context window usage in tokens instead of percentage when the window length is unknown.
This commit is contained in:
committed by
GitHub
Unverified
parent
21ad1c1c90
commit
127e307f89
@@ -76,6 +76,8 @@ pub(crate) fn get_model_info(model_family: &ModelFamily) -> Option<ModelInfo> {
|
||||
|
||||
_ if slug.starts_with("codex-") => Some(ModelInfo::new(CONTEXT_WINDOW_272K)),
|
||||
|
||||
_ if slug.starts_with("exp-") => Some(ModelInfo::new(CONTEXT_WINDOW_272K)),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@ pub(crate) struct ChatComposer {
|
||||
footer_mode: FooterMode,
|
||||
footer_hint_override: Option<Vec<(String, String)>>,
|
||||
context_window_percent: Option<i64>,
|
||||
context_window_used_tokens: Option<i64>,
|
||||
}
|
||||
|
||||
/// Popup state – at most one can be visible at any time.
|
||||
@@ -156,6 +157,7 @@ impl ChatComposer {
|
||||
footer_mode: FooterMode::ShortcutSummary,
|
||||
footer_hint_override: None,
|
||||
context_window_percent: None,
|
||||
context_window_used_tokens: None,
|
||||
};
|
||||
// Apply configuration via the setter to keep side-effects centralized.
|
||||
this.set_disable_paste_burst(disable_paste_burst);
|
||||
@@ -1387,6 +1389,7 @@ impl ChatComposer {
|
||||
use_shift_enter_hint: self.use_shift_enter_hint,
|
||||
is_task_running: self.is_task_running,
|
||||
context_window_percent: self.context_window_percent,
|
||||
context_window_used_tokens: self.context_window_used_tokens,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1517,10 +1520,13 @@ impl ChatComposer {
|
||||
self.is_task_running = running;
|
||||
}
|
||||
|
||||
pub(crate) fn set_context_window_percent(&mut self, percent: Option<i64>) {
|
||||
if self.context_window_percent != percent {
|
||||
self.context_window_percent = percent;
|
||||
pub(crate) fn set_context_window(&mut self, percent: Option<i64>, used_tokens: Option<i64>) {
|
||||
if self.context_window_percent == percent && self.context_window_used_tokens == used_tokens
|
||||
{
|
||||
return;
|
||||
}
|
||||
self.context_window_percent = percent;
|
||||
self.context_window_used_tokens = used_tokens;
|
||||
}
|
||||
|
||||
pub(crate) fn set_esc_backtrack_hint(&mut self, show: bool) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::key_hint;
|
||||
use crate::key_hint::KeyBinding;
|
||||
use crate::render::line_utils::prefix_lines;
|
||||
use crate::status::format_tokens_compact;
|
||||
use crate::ui_consts::FOOTER_INDENT_COLS;
|
||||
use crossterm::event::KeyCode;
|
||||
use ratatui::buffer::Buffer;
|
||||
@@ -18,6 +19,7 @@ pub(crate) struct FooterProps {
|
||||
pub(crate) use_shift_enter_hint: bool,
|
||||
pub(crate) is_task_running: bool,
|
||||
pub(crate) context_window_percent: Option<i64>,
|
||||
pub(crate) context_window_used_tokens: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
@@ -81,7 +83,10 @@ fn footer_lines(props: FooterProps) -> Vec<Line<'static>> {
|
||||
is_task_running: props.is_task_running,
|
||||
})],
|
||||
FooterMode::ShortcutSummary => {
|
||||
let mut line = context_window_line(props.context_window_percent);
|
||||
let mut line = context_window_line(
|
||||
props.context_window_percent,
|
||||
props.context_window_used_tokens,
|
||||
);
|
||||
line.push_span(" · ".dim());
|
||||
line.extend(vec![
|
||||
key_hint::plain(KeyCode::Char('?')).into(),
|
||||
@@ -94,7 +99,10 @@ fn footer_lines(props: FooterProps) -> Vec<Line<'static>> {
|
||||
esc_backtrack_hint: props.esc_backtrack_hint,
|
||||
}),
|
||||
FooterMode::EscHint => vec![esc_hint_line(props.esc_backtrack_hint)],
|
||||
FooterMode::ContextOnly => vec![context_window_line(props.context_window_percent)],
|
||||
FooterMode::ContextOnly => vec![context_window_line(
|
||||
props.context_window_percent,
|
||||
props.context_window_used_tokens,
|
||||
)],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,9 +229,18 @@ fn build_columns(entries: Vec<Line<'static>>) -> Vec<Line<'static>> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn context_window_line(percent: Option<i64>) -> Line<'static> {
|
||||
let percent = percent.unwrap_or(100).clamp(0, 100);
|
||||
Line::from(vec![Span::from(format!("{percent}% context left")).dim()])
|
||||
fn context_window_line(percent: Option<i64>, used_tokens: Option<i64>) -> Line<'static> {
|
||||
if let Some(percent) = percent {
|
||||
let percent = percent.clamp(0, 100);
|
||||
return Line::from(vec![Span::from(format!("{percent}% context left")).dim()]);
|
||||
}
|
||||
|
||||
if let Some(tokens) = used_tokens {
|
||||
let used_fmt = format_tokens_compact(tokens);
|
||||
return Line::from(vec![Span::from(format!("{used_fmt} used")).dim()]);
|
||||
}
|
||||
|
||||
Line::from(vec![Span::from("100% context left").dim()])
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
@@ -400,6 +417,7 @@ mod tests {
|
||||
use_shift_enter_hint: false,
|
||||
is_task_running: false,
|
||||
context_window_percent: None,
|
||||
context_window_used_tokens: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -411,6 +429,7 @@ mod tests {
|
||||
use_shift_enter_hint: true,
|
||||
is_task_running: false,
|
||||
context_window_percent: None,
|
||||
context_window_used_tokens: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -422,6 +441,7 @@ mod tests {
|
||||
use_shift_enter_hint: false,
|
||||
is_task_running: false,
|
||||
context_window_percent: None,
|
||||
context_window_used_tokens: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -433,6 +453,7 @@ mod tests {
|
||||
use_shift_enter_hint: false,
|
||||
is_task_running: true,
|
||||
context_window_percent: None,
|
||||
context_window_used_tokens: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -444,6 +465,7 @@ mod tests {
|
||||
use_shift_enter_hint: false,
|
||||
is_task_running: false,
|
||||
context_window_percent: None,
|
||||
context_window_used_tokens: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -455,6 +477,7 @@ mod tests {
|
||||
use_shift_enter_hint: false,
|
||||
is_task_running: false,
|
||||
context_window_percent: None,
|
||||
context_window_used_tokens: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -466,6 +489,19 @@ mod tests {
|
||||
use_shift_enter_hint: false,
|
||||
is_task_running: true,
|
||||
context_window_percent: Some(72),
|
||||
context_window_used_tokens: None,
|
||||
},
|
||||
);
|
||||
|
||||
snapshot_footer(
|
||||
"footer_context_tokens_used",
|
||||
FooterProps {
|
||||
mode: FooterMode::ShortcutSummary,
|
||||
esc_backtrack_hint: false,
|
||||
use_shift_enter_hint: false,
|
||||
is_task_running: false,
|
||||
context_window_percent: None,
|
||||
context_window_used_tokens: Some(123_456),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ pub(crate) struct BottomPane {
|
||||
/// Queued user messages to show above the composer while a turn is running.
|
||||
queued_user_messages: QueuedUserMessages,
|
||||
context_window_percent: Option<i64>,
|
||||
context_window_used_tokens: Option<i64>,
|
||||
}
|
||||
|
||||
pub(crate) struct BottomPaneParams {
|
||||
@@ -118,6 +119,7 @@ impl BottomPane {
|
||||
esc_backtrack_hint: false,
|
||||
animations_enabled,
|
||||
context_window_percent: None,
|
||||
context_window_used_tokens: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +132,11 @@ impl BottomPane {
|
||||
self.context_window_percent
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn context_window_used_tokens(&self) -> Option<i64> {
|
||||
self.context_window_used_tokens
|
||||
}
|
||||
|
||||
fn active_view(&self) -> Option<&dyn BottomPaneView> {
|
||||
self.view_stack.last().map(std::convert::AsRef::as_ref)
|
||||
}
|
||||
@@ -344,13 +351,16 @@ impl BottomPane {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_context_window_percent(&mut self, percent: Option<i64>) {
|
||||
if self.context_window_percent == percent {
|
||||
pub(crate) fn set_context_window(&mut self, percent: Option<i64>, used_tokens: Option<i64>) {
|
||||
if self.context_window_percent == percent && self.context_window_used_tokens == used_tokens
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
self.context_window_percent = percent;
|
||||
self.composer.set_context_window_percent(percent);
|
||||
self.context_window_used_tokens = used_tokens;
|
||||
self.composer
|
||||
.set_context_window(percent, self.context_window_used_tokens);
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: tui/src/bottom_pane/footer.rs
|
||||
expression: terminal.backend()
|
||||
---
|
||||
" 123K used · ? for shortcuts "
|
||||
@@ -515,7 +515,7 @@ impl ChatWidget {
|
||||
match info {
|
||||
Some(info) => self.apply_token_info(info),
|
||||
None => {
|
||||
self.bottom_pane.set_context_window_percent(None);
|
||||
self.bottom_pane.set_context_window(None, None);
|
||||
self.token_info = None;
|
||||
}
|
||||
}
|
||||
@@ -523,7 +523,8 @@ impl ChatWidget {
|
||||
|
||||
fn apply_token_info(&mut self, info: TokenUsageInfo) {
|
||||
let percent = self.context_remaining_percent(&info);
|
||||
self.bottom_pane.set_context_window_percent(percent);
|
||||
let used_tokens = self.context_used_tokens(&info, percent.is_some());
|
||||
self.bottom_pane.set_context_window(percent, used_tokens);
|
||||
self.token_info = Some(info);
|
||||
}
|
||||
|
||||
@@ -536,12 +537,20 @@ impl ChatWidget {
|
||||
})
|
||||
}
|
||||
|
||||
fn context_used_tokens(&self, info: &TokenUsageInfo, percent_known: bool) -> Option<i64> {
|
||||
if percent_known {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(info.total_token_usage.tokens_in_context_window())
|
||||
}
|
||||
|
||||
fn restore_pre_review_token_info(&mut self) {
|
||||
if let Some(saved) = self.pre_review_token_info.take() {
|
||||
match saved {
|
||||
Some(info) => self.apply_token_info(info),
|
||||
None => {
|
||||
self.bottom_pane.set_context_window_percent(None);
|
||||
self.bottom_pane.set_context_window(None, None);
|
||||
self.token_info = None;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,6 +297,41 @@ fn token_count_none_resets_context_indicator() {
|
||||
assert_eq!(chat.bottom_pane.context_window_percent(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn context_indicator_shows_used_tokens_when_window_unknown() {
|
||||
let (mut chat, _rx, _ops) = make_chatwidget_manual();
|
||||
|
||||
chat.config.model_context_window = None;
|
||||
let auto_compact_limit = 200_000;
|
||||
chat.config.model_auto_compact_token_limit = Some(auto_compact_limit);
|
||||
|
||||
// No model window, so the indicator should fall back to showing tokens used.
|
||||
let total_tokens = 106_000;
|
||||
let token_usage = TokenUsage {
|
||||
total_tokens,
|
||||
..TokenUsage::default()
|
||||
};
|
||||
let token_info = TokenUsageInfo {
|
||||
total_token_usage: token_usage.clone(),
|
||||
last_token_usage: token_usage,
|
||||
model_context_window: None,
|
||||
};
|
||||
|
||||
chat.handle_codex_event(Event {
|
||||
id: "token-usage".into(),
|
||||
msg: EventMsg::TokenCount(TokenCountEvent {
|
||||
info: Some(token_info),
|
||||
rate_limits: None,
|
||||
}),
|
||||
});
|
||||
|
||||
assert_eq!(chat.bottom_pane.context_window_percent(), None);
|
||||
assert_eq!(
|
||||
chat.bottom_pane.context_window_used_tokens(),
|
||||
Some(total_tokens)
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
target_os = "macos",
|
||||
ignore = "system configuration APIs are blocked under macOS seatbelt"
|
||||
|
||||
@@ -5,6 +5,7 @@ mod helpers;
|
||||
mod rate_limits;
|
||||
|
||||
pub(crate) use card::new_status_output;
|
||||
pub(crate) use helpers::format_tokens_compact;
|
||||
pub(crate) use rate_limits::RateLimitSnapshotDisplay;
|
||||
pub(crate) use rate_limits::rate_limit_snapshot_display;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user