parent
7078a0b676
commit
2de731490e
12 changed files with 189 additions and 252 deletions
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<ModelsManager>,
|
||||
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<Box<dyn HistoryCell>>,
|
||||
config: Config,
|
||||
model_family: ModelFamily,
|
||||
model: String,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
models_manager: Arc<ModelsManager>,
|
||||
session_header: SessionHeader,
|
||||
|
|
@ -608,12 +607,10 @@ impl ChatWidget {
|
|||
}
|
||||
|
||||
fn context_remaining_percent(&self, info: &TokenUsageInfo) -> Option<i64> {
|
||||
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<i64> {
|
||||
|
|
@ -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,
|
||||
|
|
@ -2249,22 +2241,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,
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -2417,7 +2407,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<ModelPreset> =
|
||||
// todo(aibrahim): make this async function
|
||||
match self.models_manager.try_list_models(&self.config) {
|
||||
|
|
@ -2434,9 +2423,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<ModelPreset>, Vec<ModelPreset>) = presets
|
||||
.into_iter()
|
||||
|
|
@ -2462,7 +2451,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,
|
||||
|
|
@ -2525,12 +2514,11 @@ impl ChatWidget {
|
|||
return;
|
||||
}
|
||||
|
||||
let current_model = self.model_family.get_model_slug().to_string();
|
||||
let mut items: Vec<SelectionItem> = 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<SelectionAction> = vec![Box::new(move |tx| {
|
||||
|
|
@ -2656,7 +2644,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 {
|
||||
|
|
@ -3246,9 +3234,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<String>) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<ConversationId>,
|
||||
rate_limits: Option<&RateLimitSnapshotDisplay>,
|
||||
plan_type: Option<PlanType>,
|
||||
|
|
@ -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<ConversationId>,
|
||||
rate_limits: Option<&RateLimitSnapshotDisplay>,
|
||||
plan_type: Option<PlanType>,
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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<String> {
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -1328,11 +1324,6 @@ impl App {
|
|||
}
|
||||
|
||||
async fn handle_event(&mut self, tui: &mut tui::Tui, event: AppEvent) -> Result<bool> {
|
||||
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(
|
||||
|
|
@ -1351,10 +1342,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<Line<'static>> = vec![summary.usage_line.clone().into()];
|
||||
if let Some(command) = summary.resume_command {
|
||||
|
|
@ -1401,14 +1391,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<Line<'static>> =
|
||||
vec![summary.usage_line.clone().into()];
|
||||
|
|
@ -1534,12 +1523,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 } => {
|
||||
|
|
@ -2087,7 +2071,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(),
|
||||
|
|
@ -2136,7 +2120,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(),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<ModelsManager>,
|
||||
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<Box<dyn HistoryCell>>,
|
||||
config: Config,
|
||||
model_family: ModelFamily,
|
||||
model: String,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
models_manager: Arc<ModelsManager>,
|
||||
session_header: SessionHeader,
|
||||
|
|
@ -573,12 +572,10 @@ impl ChatWidget {
|
|||
}
|
||||
|
||||
fn context_remaining_percent(&self, info: &TokenUsageInfo) -> Option<i64> {
|
||||
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<i64> {
|
||||
|
|
@ -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,
|
||||
|
|
@ -2052,22 +2044,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) {
|
||||
|
|
@ -2210,7 +2200,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<ModelPreset> =
|
||||
// todo(aibrahim): make this async function
|
||||
match self.models_manager.try_list_models(&self.config) {
|
||||
|
|
@ -2227,9 +2216,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<ModelPreset>, Vec<ModelPreset>) = presets
|
||||
.into_iter()
|
||||
|
|
@ -2255,7 +2244,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,
|
||||
|
|
@ -2318,12 +2307,11 @@ impl ChatWidget {
|
|||
return;
|
||||
}
|
||||
|
||||
let current_model = self.model_family.get_model_slug().to_string();
|
||||
let mut items: Vec<SelectionItem> = 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<SelectionAction> = vec![Box::new(move |tx| {
|
||||
|
|
@ -2449,7 +2437,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 {
|
||||
|
|
@ -3008,9 +2996,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<String>) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<ConversationId>,
|
||||
rate_limits: Option<&RateLimitSnapshotDisplay>,
|
||||
plan_type: Option<PlanType>,
|
||||
|
|
@ -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<ConversationId>,
|
||||
rate_limits: Option<&RateLimitSnapshotDisplay>,
|
||||
plan_type: Option<PlanType>,
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<String> {
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue