diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index c91eafcbe7..e0af51e7eb 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -372,10 +372,6 @@ impl App { } let enhanced_keys_supported = tui.enhanced_keys_supported(); - let model_family = conversation_manager - .get_models_manager() - .construct_model_family(model.as_str(), &config) - .await; let mut chat_widget = match resume_selection { ResumeSelection::StartFresh | ResumeSelection::Exit => { let init = crate::chatwidget::ChatWidgetInit { @@ -389,7 +385,7 @@ impl App { models_manager: conversation_manager.get_models_manager(), feedback: feedback.clone(), is_first_run, - model_family: model_family.clone(), + model: model.clone(), }; ChatWidget::new(init, conversation_manager.clone()) } @@ -415,7 +411,7 @@ impl App { models_manager: conversation_manager.get_models_manager(), feedback: feedback.clone(), is_first_run, - model_family: model_family.clone(), + model: model.clone(), }; ChatWidget::new_from_existing( init, @@ -582,7 +578,7 @@ impl App { models_manager: self.server.get_models_manager(), feedback: self.feedback.clone(), is_first_run: false, - model_family: model_family.clone(), + model: self.current_model.clone(), }; self.chat_widget = ChatWidget::new(init, self.server.clone()); self.current_model = model_family.get_model_slug().to_string(); @@ -632,7 +628,7 @@ impl App { models_manager: self.server.get_models_manager(), feedback: self.feedback.clone(), is_first_run: false, - model_family: model_family.clone(), + model: self.current_model.clone(), }; self.chat_widget = ChatWidget::new_from_existing( init, @@ -767,12 +763,7 @@ impl App { self.on_update_reasoning_effort(effort); } AppEvent::UpdateModel(model) => { - let model_family = self - .server - .get_models_manager() - .construct_model_family(&model, &self.config) - .await; - self.chat_widget.set_model(&model, model_family); + self.chat_widget.set_model(&model); self.current_model = model; } AppEvent::OpenReasoningPopup { model } => { @@ -1357,7 +1348,7 @@ mod tests { async fn make_test_app() -> App { let (chat_widget, app_event_tx, _rx, _op_rx) = make_chatwidget_manual_with_sender().await; let config = chat_widget.config_ref().clone(); - let current_model = chat_widget.get_model_family().get_model_slug().to_string(); + let current_model = "gpt-5.2-codex".to_string(); let server = Arc::new(ConversationManager::with_models_provider( CodexAuth::from_api_key("Test API Key"), config.model_provider.clone(), @@ -1396,7 +1387,7 @@ mod tests { ) { let (chat_widget, app_event_tx, rx, op_rx) = make_chatwidget_manual_with_sender().await; let config = chat_widget.config_ref().clone(); - let current_model = chat_widget.get_model_family().get_model_slug().to_string(); + let current_model = "gpt-5.2-codex".to_string(); let server = Arc::new(ConversationManager::with_models_provider( CodexAuth::from_api_key("Test API Key"), config.model_provider.clone(), diff --git a/codex-rs/tui/src/app_backtrack.rs b/codex-rs/tui/src/app_backtrack.rs index 671702d308..ce5dff2ed8 100644 --- a/codex-rs/tui/src/app_backtrack.rs +++ b/codex-rs/tui/src/app_backtrack.rs @@ -338,10 +338,9 @@ impl App { ) { let conv = new_conv.conversation; let session_configured = new_conv.session_configured; - let model_family = self.chat_widget.get_model_family(); let init = crate::chatwidget::ChatWidgetInit { config: cfg, - model_family: model_family.clone(), + model: self.current_model.clone(), frame_requester: tui.frame_requester(), app_event_tx: self.app_event_tx.clone(), initial_prompt: None, @@ -354,7 +353,6 @@ impl App { }; self.chat_widget = crate::chatwidget::ChatWidget::new_from_existing(init, conv, session_configured); - self.current_model = model_family.get_model_slug().to_string(); // Trim transcript up to the selected user message and re-render it. self.trim_transcript_for_backtrack(nth_user_message); self.render_transcript_once(tui); diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 4578f58249..3342cd11ee 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -15,7 +15,6 @@ use codex_core::features::Feature; use codex_core::git_info::current_branch_name; use codex_core::git_info::local_git_branches; use codex_core::models_manager::manager::ModelsManager; -use codex_core::models_manager::model_family::ModelFamily; use codex_core::project_doc::DEFAULT_PROJECT_DOC_FILENAME; use codex_core::protocol::AgentMessageDeltaEvent; use codex_core::protocol::AgentMessageEvent; @@ -291,7 +290,7 @@ pub(crate) struct ChatWidgetInit { pub(crate) models_manager: Arc, pub(crate) feedback: codex_feedback::CodexFeedback, pub(crate) is_first_run: bool, - pub(crate) model_family: ModelFamily, + pub(crate) model: String, } #[derive(Default)] @@ -316,7 +315,7 @@ pub(crate) struct ChatWidget { bottom_pane: BottomPane, active_cell: Option>, config: Config, - model_family: ModelFamily, + model: String, auth_manager: Arc, models_manager: Arc, session_header: SessionHeader, @@ -608,12 +607,10 @@ impl ChatWidget { } fn context_remaining_percent(&self, info: &TokenUsageInfo) -> Option { - info.model_context_window - .or(self.model_family.context_window) - .map(|window| { - info.last_token_usage - .percent_of_context_window_remaining(window) - }) + info.model_context_window.map(|window| { + info.last_token_usage + .percent_of_context_window_remaining(window) + }) } fn context_used_tokens(&self, info: &TokenUsageInfo, percent_known: bool) -> Option { @@ -681,7 +678,7 @@ impl ChatWidget { if high_usage && !self.rate_limit_switch_prompt_hidden() - && self.model_family.get_model_slug() != NUDGE_MODEL_SLUG + && self.model != NUDGE_MODEL_SLUG && !matches!( self.rate_limit_switch_prompt, RateLimitSwitchPromptState::Shown @@ -715,9 +712,6 @@ impl ChatWidget { self.stream_controller = None; self.maybe_show_pending_rate_limit_prompt(); } - pub(crate) fn get_model_family(&self) -> ModelFamily { - self.model_family.clone() - } fn on_error(&mut self, message: String) { self.finalize_turn(); @@ -1420,11 +1414,10 @@ impl ChatWidget { models_manager, feedback, is_first_run, - model_family, + model, } = common; - let model_slug = model_family.get_model_slug().to_string(); let mut config = config; - config.model = Some(model_slug.clone()); + config.model = Some(model.clone()); let mut rng = rand::rng(); let placeholder = EXAMPLE_PROMPTS[rng.random_range(0..EXAMPLE_PROMPTS.len())].to_string(); let codex_op_tx = spawn_agent(config.clone(), app_event_tx.clone(), conversation_manager); @@ -1445,10 +1438,10 @@ impl ChatWidget { }), active_cell: None, config, - model_family, + model: model.clone(), auth_manager, models_manager, - session_header: SessionHeader::new(model_slug), + session_header: SessionHeader::new(model), initial_user_message: create_initial_user_message( initial_prompt.unwrap_or_default(), initial_images, @@ -1506,10 +1499,9 @@ impl ChatWidget { auth_manager, models_manager, feedback, - model_family, + model, .. } = common; - let model_slug = model_family.get_model_slug().to_string(); let mut rng = rand::rng(); let placeholder = EXAMPLE_PROMPTS[rng.random_range(0..EXAMPLE_PROMPTS.len())].to_string(); @@ -1532,10 +1524,10 @@ impl ChatWidget { }), active_cell: None, config, - model_family, + model: model.clone(), auth_manager, models_manager, - session_header: SessionHeader::new(model_slug), + session_header: SessionHeader::new(model), initial_user_message: create_initial_user_message( initial_prompt.unwrap_or_default(), initial_images, @@ -2247,22 +2239,20 @@ impl ChatWidget { pub(crate) fn add_status_output(&mut self) { let default_usage = TokenUsage::default(); - let (total_usage, context_usage) = if let Some(ti) = &self.token_info { - (&ti.total_token_usage, Some(&ti.last_token_usage)) - } else { - (&default_usage, Some(&default_usage)) - }; + let token_info = self.token_info.as_ref(); + let total_usage = token_info + .map(|ti| &ti.total_token_usage) + .unwrap_or(&default_usage); self.add_to_history(crate::status::new_status_output( &self.config, self.auth_manager.as_ref(), - &self.model_family, + token_info, total_usage, - context_usage, &self.conversation_id, self.rate_limit_snapshot.as_ref(), self.plan_type, Local::now(), - self.model_family.get_model_slug(), + &self.model, )); } @@ -2415,7 +2405,6 @@ impl ChatWidget { /// Open a popup to choose a quick auto model. Selecting "All models" /// opens the full picker with every available preset. pub(crate) fn open_model_popup(&mut self) { - let current_model = self.model_family.get_model_slug().to_string(); let presets: Vec = // todo(aibrahim): make this async function match self.models_manager.try_list_models(&self.config) { @@ -2432,9 +2421,9 @@ impl ChatWidget { let current_label = presets .iter() - .find(|preset| preset.model == current_model) + .find(|preset| preset.model == self.model) .map(|preset| preset.display_name.to_string()) - .unwrap_or_else(|| current_model.clone()); + .unwrap_or_else(|| self.model.clone()); let (mut auto_presets, other_presets): (Vec, Vec) = presets .into_iter() @@ -2460,7 +2449,7 @@ impl ChatWidget { SelectionItem { name: preset.display_name.clone(), description, - is_current: model == current_model, + is_current: model == self.model, is_default: preset.is_default, actions, dismiss_on_select: true, @@ -2523,12 +2512,11 @@ impl ChatWidget { return; } - let current_model = self.model_family.get_model_slug().to_string(); let mut items: Vec = Vec::new(); for preset in presets.into_iter() { let description = (!preset.description.is_empty()).then_some(preset.description.to_string()); - let is_current = preset.model == current_model; + let is_current = preset.model == self.model; let single_supported_effort = preset.supported_reasoning_efforts.len() == 1; let preset_for_action = preset.clone(); let actions: Vec = vec![Box::new(move |tx| { @@ -2654,7 +2642,7 @@ impl ChatWidget { .or(Some(default_effort)); let model_slug = preset.model.to_string(); - let is_current_model = self.model_family.get_model_slug() == preset.model; + let is_current_model = self.model == preset.model; let highlight_choice = if is_current_model { self.config.model_reasoning_effort } else { @@ -3244,9 +3232,9 @@ impl ChatWidget { } /// Set the model in the widget's config copy. - pub(crate) fn set_model(&mut self, model: &str, model_family: ModelFamily) { + pub(crate) fn set_model(&mut self, model: &str) { self.session_header.set_model(model); - self.model_family = model_family; + self.model = model.to_string(); } pub(crate) fn add_info_message(&mut self, message: String, hint: Option) { diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index ae3e7abd6a..631bf256a3 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -313,7 +313,6 @@ async fn helpers_are_available_and_do_not_panic() { let tx = AppEventSender::new(tx_raw); let cfg = test_config().await; let resolved_model = ModelsManager::get_model_offline(cfg.model.as_deref()); - let model_family = ModelsManager::construct_model_family_offline(&resolved_model, &cfg); let conversation_manager = Arc::new(ConversationManager::with_models_provider( CodexAuth::from_api_key("test"), cfg.model_provider.clone(), @@ -330,7 +329,7 @@ async fn helpers_are_available_and_do_not_panic() { models_manager: conversation_manager.get_models_manager(), feedback: codex_feedback::CodexFeedback::new(), is_first_run: true, - model_family, + model: resolved_model, }; let mut w = ChatWidget::new(init, conversation_manager); // Basic construction sanity. @@ -371,11 +370,11 @@ async fn make_chatwidget_manual( codex_op_tx: op_tx, bottom_pane: bottom, active_cell: None, - config: cfg.clone(), - model_family: ModelsManager::construct_model_family_offline(&resolved_model, &cfg), + config: cfg, + model: resolved_model.clone(), auth_manager: auth_manager.clone(), models_manager: Arc::new(ModelsManager::new(auth_manager)), - session_header: SessionHeader::new(resolved_model.clone()), + session_header: SessionHeader::new(resolved_model), initial_user_message: None, token_info: None, rate_limit_snapshot: None, diff --git a/codex-rs/tui/src/status/card.rs b/codex-rs/tui/src/status/card.rs index 429134362a..07cd5a1988 100644 --- a/codex-rs/tui/src/status/card.rs +++ b/codex-rs/tui/src/status/card.rs @@ -7,10 +7,10 @@ use chrono::DateTime; use chrono::Local; use codex_common::create_config_summary_entries; use codex_core::config::Config; -use codex_core::models_manager::model_family::ModelFamily; use codex_core::protocol::NetworkAccess; use codex_core::protocol::SandboxPolicy; use codex_core::protocol::TokenUsage; +use codex_core::protocol::TokenUsageInfo; use codex_protocol::ConversationId; use codex_protocol::account::PlanType; use ratatui::prelude::*; @@ -72,9 +72,8 @@ struct StatusHistoryCell { pub(crate) fn new_status_output( config: &Config, auth_manager: &AuthManager, - model_family: &ModelFamily, + token_info: Option<&TokenUsageInfo>, total_usage: &TokenUsage, - context_usage: Option<&TokenUsage>, session_id: &Option, rate_limits: Option<&RateLimitSnapshotDisplay>, plan_type: Option, @@ -85,9 +84,8 @@ pub(crate) fn new_status_output( let card = StatusHistoryCell::new( config, auth_manager, - model_family, + token_info, total_usage, - context_usage, session_id, rate_limits, plan_type, @@ -103,9 +101,8 @@ impl StatusHistoryCell { fn new( config: &Config, auth_manager: &AuthManager, - model_family: &ModelFamily, + token_info: Option<&TokenUsageInfo>, total_usage: &TokenUsage, - context_usage: Option<&TokenUsage>, session_id: &Option, rate_limits: Option<&RateLimitSnapshotDisplay>, plan_type: Option, @@ -134,12 +131,15 @@ impl StatusHistoryCell { let agents_summary = compose_agents_summary(config); let account = compose_account_display(auth_manager, plan_type); let session_id = session_id.as_ref().map(std::string::ToString::to_string); - let context_window = model_family.context_window.and_then(|window| { - context_usage.map(|usage| StatusContextWindowData { - percent_remaining: usage.percent_of_context_window_remaining(window), - tokens_in_context: usage.tokens_in_context_window(), - window, - }) + let default_usage = TokenUsage::default(); + let (context_usage, context_window) = match token_info { + Some(info) => (&info.last_token_usage, info.model_context_window), + None => (&default_usage, config.model_context_window), + }; + let context_window = context_window.map(|window| StatusContextWindowData { + percent_remaining: context_usage.percent_of_context_window_remaining(window), + tokens_in_context: context_usage.tokens_in_context_window(), + window, }); let token_usage = StatusTokenUsageData { @@ -348,6 +348,7 @@ impl HistoryCell for StatusHistoryCell { if self.token_usage.context_window.is_some() { push_label(&mut labels, &mut seen, "Context window"); } + self.collect_rate_limit_labels(&mut seen, &mut labels); let formatter = FieldFormatter::from_labels(labels.iter().map(String::as_str)); diff --git a/codex-rs/tui/src/status/tests.rs b/codex-rs/tui/src/status/tests.rs index 317a3d3270..c6f6c73599 100644 --- a/codex-rs/tui/src/status/tests.rs +++ b/codex-rs/tui/src/status/tests.rs @@ -8,12 +8,12 @@ use codex_core::AuthManager; use codex_core::config::Config; use codex_core::config::ConfigBuilder; use codex_core::models_manager::manager::ModelsManager; -use codex_core::models_manager::model_family::ModelFamily; use codex_core::protocol::CreditsSnapshot; use codex_core::protocol::RateLimitSnapshot; use codex_core::protocol::RateLimitWindow; use codex_core::protocol::SandboxPolicy; use codex_core::protocol::TokenUsage; +use codex_core::protocol::TokenUsageInfo; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::openai_models::ReasoningEffort; use insta::assert_snapshot; @@ -37,8 +37,15 @@ fn test_auth_manager(config: &Config) -> AuthManager { ) } -fn test_model_family(model_slug: &str, config: &Config) -> ModelFamily { - ModelsManager::construct_model_family_offline(model_slug, config) +fn token_info_for(model_slug: &str, config: &Config, usage: &TokenUsage) -> TokenUsageInfo { + let context_window = ModelsManager::construct_model_family_offline(model_slug, config) + .context_window + .or(config.model_context_window); + TokenUsageInfo { + total_token_usage: usage.clone(), + last_token_usage: usage.clone(), + model_context_window: context_window, + } } fn render_lines(lines: &[Line<'static>]) -> Vec { @@ -132,14 +139,13 @@ async fn status_snapshot_includes_reasoning_details() { let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -190,13 +196,12 @@ async fn status_snapshot_includes_monthly_limit() { let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -235,13 +240,12 @@ async fn status_snapshot_shows_unlimited_credits() { }; let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -279,13 +283,12 @@ async fn status_snapshot_shows_positive_credits() { }; let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -323,13 +326,12 @@ async fn status_snapshot_hides_zero_credits() { }; let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -365,13 +367,12 @@ async fn status_snapshot_hides_when_has_no_credits_flag() { }; let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -407,13 +408,12 @@ async fn status_card_token_usage_excludes_cached_tokens() { .expect("timestamp"); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, None, None, @@ -464,13 +464,12 @@ async fn status_snapshot_truncates_in_narrow_terminal() { let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -510,13 +509,12 @@ async fn status_snapshot_shows_missing_limits_message() { .expect("timestamp"); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, None, None, @@ -574,13 +572,12 @@ async fn status_snapshot_includes_credits_and_limits() { let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -626,13 +623,12 @@ async fn status_snapshot_shows_empty_limits_message() { let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -687,13 +683,12 @@ async fn status_snapshot_shows_stale_limits_message() { let now = captured_at + ChronoDuration::minutes(20); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -752,13 +747,12 @@ async fn status_snapshot_cached_limits_hide_credits_without_flag() { let now = captured_at + ChronoDuration::minutes(20); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -803,13 +797,16 @@ async fn status_context_window_uses_last_usage() { .expect("timestamp"); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = TokenUsageInfo { + total_token_usage: total_usage.clone(), + last_token_usage: last_usage, + model_context_window: config.model_context_window, + }; let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &total_usage, - Some(&last_usage), &None, None, None, diff --git a/codex-rs/tui2/src/app.rs b/codex-rs/tui2/src/app.rs index 78ccfb0387..135e45d491 100644 --- a/codex-rs/tui2/src/app.rs +++ b/codex-rs/tui2/src/app.rs @@ -410,10 +410,6 @@ impl App { } let enhanced_keys_supported = tui.enhanced_keys_supported(); - let model_family = conversation_manager - .get_models_manager() - .construct_model_family(model.as_str(), &config) - .await; let mut chat_widget = match resume_selection { ResumeSelection::StartFresh | ResumeSelection::Exit => { let init = crate::chatwidget::ChatWidgetInit { @@ -427,7 +423,7 @@ impl App { models_manager: conversation_manager.get_models_manager(), feedback: feedback.clone(), is_first_run, - model_family: model_family.clone(), + model: model.clone(), }; ChatWidget::new(init, conversation_manager.clone()) } @@ -453,7 +449,7 @@ impl App { models_manager: conversation_manager.get_models_manager(), feedback: feedback.clone(), is_first_run, - model_family: model_family.clone(), + model: model.clone(), }; ChatWidget::new_from_existing( init, @@ -1329,11 +1325,6 @@ impl App { } async fn handle_event(&mut self, tui: &mut tui::Tui, event: AppEvent) -> Result { - let model_family = self - .server - .get_models_manager() - .construct_model_family(self.current_model.as_str(), &self.config) - .await; match event { AppEvent::NewSession => { let summary = session_summary( @@ -1352,10 +1343,9 @@ impl App { models_manager: self.server.get_models_manager(), feedback: self.feedback.clone(), is_first_run: false, - model_family: model_family.clone(), + model: self.current_model.clone(), }; self.chat_widget = ChatWidget::new(init, self.server.clone()); - self.current_model = model_family.get_model_slug().to_string(); if let Some(summary) = summary { let mut lines: Vec> = vec![summary.usage_line.clone().into()]; if let Some(command) = summary.resume_command { @@ -1402,14 +1392,13 @@ impl App { models_manager: self.server.get_models_manager(), feedback: self.feedback.clone(), is_first_run: false, - model_family: model_family.clone(), + model: self.current_model.clone(), }; self.chat_widget = ChatWidget::new_from_existing( init, resumed.conversation, resumed.session_configured, ); - self.current_model = model_family.get_model_slug().to_string(); if let Some(summary) = summary { let mut lines: Vec> = vec![summary.usage_line.clone().into()]; @@ -1535,12 +1524,7 @@ impl App { self.on_update_reasoning_effort(effort); } AppEvent::UpdateModel(model) => { - let model_family = self - .server - .get_models_manager() - .construct_model_family(&model, &self.config) - .await; - self.chat_widget.set_model(&model, model_family); + self.chat_widget.set_model(&model); self.current_model = model; } AppEvent::OpenReasoningPopup { model } => { @@ -2088,7 +2072,7 @@ mod tests { async fn make_test_app() -> App { let (chat_widget, app_event_tx, _rx, _op_rx) = make_chatwidget_manual_with_sender().await; let config = chat_widget.config_ref().clone(); - let current_model = chat_widget.get_model_family().get_model_slug().to_string(); + let current_model = "gpt-5.2-codex".to_string(); let server = Arc::new(ConversationManager::with_models_provider( CodexAuth::from_api_key("Test API Key"), config.model_provider.clone(), @@ -2137,7 +2121,7 @@ mod tests { ) { let (chat_widget, app_event_tx, rx, op_rx) = make_chatwidget_manual_with_sender().await; let config = chat_widget.config_ref().clone(); - let current_model = chat_widget.get_model_family().get_model_slug().to_string(); + let current_model = "gpt-5.2-codex".to_string(); let server = Arc::new(ConversationManager::with_models_provider( CodexAuth::from_api_key("Test API Key"), config.model_provider.clone(), diff --git a/codex-rs/tui2/src/app_backtrack.rs b/codex-rs/tui2/src/app_backtrack.rs index 671702d308..ce5dff2ed8 100644 --- a/codex-rs/tui2/src/app_backtrack.rs +++ b/codex-rs/tui2/src/app_backtrack.rs @@ -338,10 +338,9 @@ impl App { ) { let conv = new_conv.conversation; let session_configured = new_conv.session_configured; - let model_family = self.chat_widget.get_model_family(); let init = crate::chatwidget::ChatWidgetInit { config: cfg, - model_family: model_family.clone(), + model: self.current_model.clone(), frame_requester: tui.frame_requester(), app_event_tx: self.app_event_tx.clone(), initial_prompt: None, @@ -354,7 +353,6 @@ impl App { }; self.chat_widget = crate::chatwidget::ChatWidget::new_from_existing(init, conv, session_configured); - self.current_model = model_family.get_model_slug().to_string(); // Trim transcript up to the selected user message and re-render it. self.trim_transcript_for_backtrack(nth_user_message); self.render_transcript_once(tui); diff --git a/codex-rs/tui2/src/chatwidget.rs b/codex-rs/tui2/src/chatwidget.rs index 723390ef8b..799e7f9934 100644 --- a/codex-rs/tui2/src/chatwidget.rs +++ b/codex-rs/tui2/src/chatwidget.rs @@ -13,7 +13,6 @@ use codex_core::config::types::Notifications; use codex_core::git_info::current_branch_name; use codex_core::git_info::local_git_branches; use codex_core::models_manager::manager::ModelsManager; -use codex_core::models_manager::model_family::ModelFamily; use codex_core::project_doc::DEFAULT_PROJECT_DOC_FILENAME; use codex_core::protocol::AgentMessageDeltaEvent; use codex_core::protocol::AgentMessageEvent; @@ -267,7 +266,7 @@ pub(crate) struct ChatWidgetInit { pub(crate) models_manager: Arc, pub(crate) feedback: codex_feedback::CodexFeedback, pub(crate) is_first_run: bool, - pub(crate) model_family: ModelFamily, + pub(crate) model: String, } #[derive(Default)] @@ -284,7 +283,7 @@ pub(crate) struct ChatWidget { bottom_pane: BottomPane, active_cell: Option>, config: Config, - model_family: ModelFamily, + model: String, auth_manager: Arc, models_manager: Arc, session_header: SessionHeader, @@ -573,12 +572,10 @@ impl ChatWidget { } fn context_remaining_percent(&self, info: &TokenUsageInfo) -> Option { - info.model_context_window - .or(self.model_family.context_window) - .map(|window| { - info.last_token_usage - .percent_of_context_window_remaining(window) - }) + info.model_context_window.map(|window| { + info.last_token_usage + .percent_of_context_window_remaining(window) + }) } fn context_used_tokens(&self, info: &TokenUsageInfo, percent_known: bool) -> Option { @@ -646,7 +643,7 @@ impl ChatWidget { if high_usage && !self.rate_limit_switch_prompt_hidden() - && self.model_family.get_model_slug() != NUDGE_MODEL_SLUG + && self.model != NUDGE_MODEL_SLUG && !matches!( self.rate_limit_switch_prompt, RateLimitSwitchPromptState::Shown @@ -680,9 +677,6 @@ impl ChatWidget { self.stream_controller = None; self.maybe_show_pending_rate_limit_prompt(); } - pub(crate) fn get_model_family(&self) -> ModelFamily { - self.model_family.clone() - } fn on_error(&mut self, message: String) { self.finalize_turn(); @@ -1280,11 +1274,10 @@ impl ChatWidget { models_manager, feedback, is_first_run, - model_family, + model, } = common; - let model_slug = model_family.get_model_slug().to_string(); let mut config = config; - config.model = Some(model_slug.clone()); + config.model = Some(model.clone()); let mut rng = rand::rng(); let placeholder = EXAMPLE_PROMPTS[rng.random_range(0..EXAMPLE_PROMPTS.len())].to_string(); let codex_op_tx = spawn_agent(config.clone(), app_event_tx.clone(), conversation_manager); @@ -1305,10 +1298,10 @@ impl ChatWidget { }), active_cell: None, config, - model_family, + model: model.clone(), auth_manager, models_manager, - session_header: SessionHeader::new(model_slug), + session_header: SessionHeader::new(model), initial_user_message: create_initial_user_message( initial_prompt.unwrap_or_default(), initial_images, @@ -1364,10 +1357,9 @@ impl ChatWidget { auth_manager, models_manager, feedback, - model_family, + model, .. } = common; - let model_slug = model_family.get_model_slug().to_string(); let mut rng = rand::rng(); let placeholder = EXAMPLE_PROMPTS[rng.random_range(0..EXAMPLE_PROMPTS.len())].to_string(); @@ -1390,10 +1382,10 @@ impl ChatWidget { }), active_cell: None, config, - model_family, + model: model.clone(), auth_manager, models_manager, - session_header: SessionHeader::new(model_slug), + session_header: SessionHeader::new(model), initial_user_message: create_initial_user_message( initial_prompt.unwrap_or_default(), initial_images, @@ -2050,22 +2042,20 @@ impl ChatWidget { pub(crate) fn add_status_output(&mut self) { let default_usage = TokenUsage::default(); - let (total_usage, context_usage) = if let Some(ti) = &self.token_info { - (&ti.total_token_usage, Some(&ti.last_token_usage)) - } else { - (&default_usage, Some(&default_usage)) - }; + let token_info = self.token_info.as_ref(); + let total_usage = token_info + .map(|ti| &ti.total_token_usage) + .unwrap_or(&default_usage); self.add_to_history(crate::status::new_status_output( &self.config, self.auth_manager.as_ref(), - &self.model_family, + token_info, total_usage, - context_usage, &self.conversation_id, self.rate_limit_snapshot.as_ref(), self.plan_type, Local::now(), - self.model_family.get_model_slug(), + &self.model, )); } fn stop_rate_limit_poller(&mut self) { @@ -2208,7 +2198,6 @@ impl ChatWidget { /// Open a popup to choose a quick auto model. Selecting "All models" /// opens the full picker with every available preset. pub(crate) fn open_model_popup(&mut self) { - let current_model = self.model_family.get_model_slug().to_string(); let presets: Vec = // todo(aibrahim): make this async function match self.models_manager.try_list_models(&self.config) { @@ -2225,9 +2214,9 @@ impl ChatWidget { let current_label = presets .iter() - .find(|preset| preset.model == current_model) + .find(|preset| preset.model == self.model) .map(|preset| preset.display_name.to_string()) - .unwrap_or_else(|| current_model.clone()); + .unwrap_or_else(|| self.model.clone()); let (mut auto_presets, other_presets): (Vec, Vec) = presets .into_iter() @@ -2253,7 +2242,7 @@ impl ChatWidget { SelectionItem { name: preset.display_name.clone(), description, - is_current: model == current_model, + is_current: model == self.model, is_default: preset.is_default, actions, dismiss_on_select: true, @@ -2316,12 +2305,11 @@ impl ChatWidget { return; } - let current_model = self.model_family.get_model_slug().to_string(); let mut items: Vec = Vec::new(); for preset in presets.into_iter() { let description = (!preset.description.is_empty()).then_some(preset.description.to_string()); - let is_current = preset.model == current_model; + let is_current = preset.model == self.model; let single_supported_effort = preset.supported_reasoning_efforts.len() == 1; let preset_for_action = preset.clone(); let actions: Vec = vec![Box::new(move |tx| { @@ -2447,7 +2435,7 @@ impl ChatWidget { .or(Some(default_effort)); let model_slug = preset.model.to_string(); - let is_current_model = self.model_family.get_model_slug() == preset.model; + let is_current_model = self.model == preset.model; let highlight_choice = if is_current_model { self.config.model_reasoning_effort } else { @@ -3006,9 +2994,9 @@ impl ChatWidget { } /// Set the model in the widget's config copy. - pub(crate) fn set_model(&mut self, model: &str, model_family: ModelFamily) { + pub(crate) fn set_model(&mut self, model: &str) { self.session_header.set_model(model); - self.model_family = model_family; + self.model = model.to_string(); } pub(crate) fn add_info_message(&mut self, message: String, hint: Option) { diff --git a/codex-rs/tui2/src/chatwidget/tests.rs b/codex-rs/tui2/src/chatwidget/tests.rs index a71be3a635..cc4f6843c0 100644 --- a/codex-rs/tui2/src/chatwidget/tests.rs +++ b/codex-rs/tui2/src/chatwidget/tests.rs @@ -311,7 +311,6 @@ async fn helpers_are_available_and_do_not_panic() { let tx = AppEventSender::new(tx_raw); let cfg = test_config().await; let resolved_model = ModelsManager::get_model_offline(cfg.model.as_deref()); - let model_family = ModelsManager::construct_model_family_offline(&resolved_model, &cfg); let conversation_manager = Arc::new(ConversationManager::with_models_provider( CodexAuth::from_api_key("test"), cfg.model_provider.clone(), @@ -328,7 +327,7 @@ async fn helpers_are_available_and_do_not_panic() { models_manager: conversation_manager.get_models_manager(), feedback: codex_feedback::CodexFeedback::new(), is_first_run: true, - model_family, + model: resolved_model, }; let mut w = ChatWidget::new(init, conversation_manager); // Basic construction sanity. @@ -369,11 +368,11 @@ async fn make_chatwidget_manual( codex_op_tx: op_tx, bottom_pane: bottom, active_cell: None, - config: cfg.clone(), - model_family: ModelsManager::construct_model_family_offline(&resolved_model, &cfg), + config: cfg, + model: resolved_model.clone(), auth_manager: auth_manager.clone(), models_manager: Arc::new(ModelsManager::new(auth_manager)), - session_header: SessionHeader::new(resolved_model.clone()), + session_header: SessionHeader::new(resolved_model), initial_user_message: None, token_info: None, rate_limit_snapshot: None, diff --git a/codex-rs/tui2/src/status/card.rs b/codex-rs/tui2/src/status/card.rs index 429134362a..3e7a626e40 100644 --- a/codex-rs/tui2/src/status/card.rs +++ b/codex-rs/tui2/src/status/card.rs @@ -7,10 +7,10 @@ use chrono::DateTime; use chrono::Local; use codex_common::create_config_summary_entries; use codex_core::config::Config; -use codex_core::models_manager::model_family::ModelFamily; use codex_core::protocol::NetworkAccess; use codex_core::protocol::SandboxPolicy; use codex_core::protocol::TokenUsage; +use codex_core::protocol::TokenUsageInfo; use codex_protocol::ConversationId; use codex_protocol::account::PlanType; use ratatui::prelude::*; @@ -72,9 +72,8 @@ struct StatusHistoryCell { pub(crate) fn new_status_output( config: &Config, auth_manager: &AuthManager, - model_family: &ModelFamily, + token_info: Option<&TokenUsageInfo>, total_usage: &TokenUsage, - context_usage: Option<&TokenUsage>, session_id: &Option, rate_limits: Option<&RateLimitSnapshotDisplay>, plan_type: Option, @@ -85,9 +84,8 @@ pub(crate) fn new_status_output( let card = StatusHistoryCell::new( config, auth_manager, - model_family, + token_info, total_usage, - context_usage, session_id, rate_limits, plan_type, @@ -103,9 +101,8 @@ impl StatusHistoryCell { fn new( config: &Config, auth_manager: &AuthManager, - model_family: &ModelFamily, + token_info: Option<&TokenUsageInfo>, total_usage: &TokenUsage, - context_usage: Option<&TokenUsage>, session_id: &Option, rate_limits: Option<&RateLimitSnapshotDisplay>, plan_type: Option, @@ -134,12 +131,15 @@ impl StatusHistoryCell { let agents_summary = compose_agents_summary(config); let account = compose_account_display(auth_manager, plan_type); let session_id = session_id.as_ref().map(std::string::ToString::to_string); - let context_window = model_family.context_window.and_then(|window| { - context_usage.map(|usage| StatusContextWindowData { - percent_remaining: usage.percent_of_context_window_remaining(window), - tokens_in_context: usage.tokens_in_context_window(), - window, - }) + let default_usage = TokenUsage::default(); + let (context_usage, context_window) = match token_info { + Some(info) => (&info.last_token_usage, info.model_context_window), + None => (&default_usage, config.model_context_window), + }; + let context_window = context_window.map(|window| StatusContextWindowData { + percent_remaining: context_usage.percent_of_context_window_remaining(window), + tokens_in_context: context_usage.tokens_in_context_window(), + window, }); let token_usage = StatusTokenUsageData { diff --git a/codex-rs/tui2/src/status/tests.rs b/codex-rs/tui2/src/status/tests.rs index 317a3d3270..7eb18dd48b 100644 --- a/codex-rs/tui2/src/status/tests.rs +++ b/codex-rs/tui2/src/status/tests.rs @@ -8,12 +8,12 @@ use codex_core::AuthManager; use codex_core::config::Config; use codex_core::config::ConfigBuilder; use codex_core::models_manager::manager::ModelsManager; -use codex_core::models_manager::model_family::ModelFamily; use codex_core::protocol::CreditsSnapshot; use codex_core::protocol::RateLimitSnapshot; use codex_core::protocol::RateLimitWindow; use codex_core::protocol::SandboxPolicy; use codex_core::protocol::TokenUsage; +use codex_core::protocol::TokenUsageInfo; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::openai_models::ReasoningEffort; use insta::assert_snapshot; @@ -37,8 +37,15 @@ fn test_auth_manager(config: &Config) -> AuthManager { ) } -fn test_model_family(model_slug: &str, config: &Config) -> ModelFamily { - ModelsManager::construct_model_family_offline(model_slug, config) +fn token_info_for(model_slug: &str, config: &Config, usage: &TokenUsage) -> TokenUsageInfo { + let context_window = ModelsManager::construct_model_family_offline(model_slug, config) + .context_window + .or(config.model_context_window); + TokenUsageInfo { + total_token_usage: usage.clone(), + last_token_usage: usage.clone(), + model_context_window: context_window, + } } fn render_lines(lines: &[Line<'static>]) -> Vec { @@ -132,14 +139,13 @@ async fn status_snapshot_includes_reasoning_details() { let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -155,7 +161,6 @@ async fn status_snapshot_includes_reasoning_details() { let sanitized = sanitize_directory(rendered_lines).join("\n"); assert_snapshot!(sanitized); } - #[tokio::test] async fn status_snapshot_includes_monthly_limit() { let temp_home = TempDir::new().expect("temp home"); @@ -190,13 +195,12 @@ async fn status_snapshot_includes_monthly_limit() { let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -212,7 +216,6 @@ async fn status_snapshot_includes_monthly_limit() { let sanitized = sanitize_directory(rendered_lines).join("\n"); assert_snapshot!(sanitized); } - #[tokio::test] async fn status_snapshot_shows_unlimited_credits() { let temp_home = TempDir::new().expect("temp home"); @@ -235,13 +238,12 @@ async fn status_snapshot_shows_unlimited_credits() { }; let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -279,13 +281,12 @@ async fn status_snapshot_shows_positive_credits() { }; let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -323,13 +324,12 @@ async fn status_snapshot_hides_zero_credits() { }; let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -365,13 +365,12 @@ async fn status_snapshot_hides_when_has_no_credits_flag() { }; let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -407,13 +406,12 @@ async fn status_card_token_usage_excludes_cached_tokens() { .expect("timestamp"); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, None, None, @@ -464,13 +462,12 @@ async fn status_snapshot_truncates_in_narrow_terminal() { let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -510,13 +507,12 @@ async fn status_snapshot_shows_missing_limits_message() { .expect("timestamp"); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, None, None, @@ -532,7 +528,6 @@ async fn status_snapshot_shows_missing_limits_message() { let sanitized = sanitize_directory(rendered_lines).join("\n"); assert_snapshot!(sanitized); } - #[tokio::test] async fn status_snapshot_includes_credits_and_limits() { let temp_home = TempDir::new().expect("temp home"); @@ -574,13 +569,12 @@ async fn status_snapshot_includes_credits_and_limits() { let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -626,13 +620,12 @@ async fn status_snapshot_shows_empty_limits_message() { let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -687,13 +680,12 @@ async fn status_snapshot_shows_stale_limits_message() { let now = captured_at + ChronoDuration::minutes(20); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -752,13 +744,12 @@ async fn status_snapshot_cached_limits_hide_credits_without_flag() { let now = captured_at + ChronoDuration::minutes(20); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = token_info_for(&model_slug, &config, &usage); let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &usage, - Some(&usage), &None, Some(&rate_display), None, @@ -803,13 +794,16 @@ async fn status_context_window_uses_last_usage() { .expect("timestamp"); let model_slug = ModelsManager::get_model_offline(config.model.as_deref()); - let model_family = test_model_family(&model_slug, &config); + let token_info = TokenUsageInfo { + total_token_usage: total_usage.clone(), + last_token_usage: last_usage, + model_context_window: config.model_context_window, + }; let composite = new_status_output( &config, &auth_manager, - &model_family, + Some(&token_info), &total_usage, - Some(&last_usage), &None, None, None,