make model optional in config (#7769)

- Make Config.model optional and centralize default-selection logic in
ModelsManager, including a default_model helper (with
codex-auto-balanced when available) so sessions now carry an explicit
chosen model separate from the base config.
- Resolve `model` once in `core` and `tui` from config. Then store the
state of it on other structs.
- Move refreshing models to be before resolving the default model
This commit is contained in:
Ahmed Ibrahim
2025-12-10 11:19:00 -08:00
committed by GitHub
parent 8a71f8b634
commit cb9a189857
44 changed files with 838 additions and 429 deletions

View File

@@ -39,8 +39,8 @@ fn test_auth_manager(config: &Config) -> AuthManager {
)
}
fn test_model_family(config: &Config) -> ModelFamily {
ModelsManager::construct_model_family_offline(config.model.as_str(), config)
fn test_model_family(model_slug: &str, config: &Config) -> ModelFamily {
ModelsManager::construct_model_family_offline(model_slug, config)
}
fn render_lines(lines: &[Line<'static>]) -> Vec<String> {
@@ -88,7 +88,7 @@ fn reset_at_from(captured_at: &chrono::DateTime<chrono::Local>, seconds: i64) ->
fn status_snapshot_includes_reasoning_details() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home);
config.model = "gpt-5.1-codex-max".to_string();
config.model = Some("gpt-5.1-codex-max".to_string());
config.model_provider_id = "openai".to_string();
config.model_reasoning_effort = Some(ReasoningEffort::High);
config.model_reasoning_summary = ReasoningSummary::Detailed;
@@ -130,7 +130,8 @@ fn status_snapshot_includes_reasoning_details() {
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let model_family = test_model_family(&config);
let model_slug = ModelsManager::get_model_offline(config.model.as_deref());
let model_family = test_model_family(&model_slug, &config);
let composite = new_status_output(
&config,
@@ -142,6 +143,7 @@ fn status_snapshot_includes_reasoning_details() {
Some(&rate_display),
None,
captured_at,
&model_slug,
);
let mut rendered_lines = render_lines(&composite.display_lines(80));
if cfg!(windows) {
@@ -157,7 +159,7 @@ fn status_snapshot_includes_reasoning_details() {
fn status_snapshot_includes_monthly_limit() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home);
config.model = "gpt-5.1-codex-max".to_string();
config.model = Some("gpt-5.1-codex-max".to_string());
config.model_provider_id = "openai".to_string();
config.cwd = PathBuf::from("/workspace/tests");
@@ -186,7 +188,8 @@ fn status_snapshot_includes_monthly_limit() {
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let model_family = test_model_family(&config);
let model_slug = ModelsManager::get_model_offline(config.model.as_deref());
let model_family = test_model_family(&model_slug, &config);
let composite = new_status_output(
&config,
&auth_manager,
@@ -197,6 +200,7 @@ fn status_snapshot_includes_monthly_limit() {
Some(&rate_display),
None,
captured_at,
&model_slug,
);
let mut rendered_lines = render_lines(&composite.display_lines(80));
if cfg!(windows) {
@@ -229,7 +233,8 @@ fn status_snapshot_shows_unlimited_credits() {
plan_type: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let model_family = test_model_family(&config);
let model_slug = ModelsManager::get_model_offline(config.model.as_deref());
let model_family = test_model_family(&model_slug, &config);
let composite = new_status_output(
&config,
&auth_manager,
@@ -240,6 +245,7 @@ fn status_snapshot_shows_unlimited_credits() {
Some(&rate_display),
None,
captured_at,
&model_slug,
);
let rendered = render_lines(&composite.display_lines(120));
assert!(
@@ -271,7 +277,8 @@ fn status_snapshot_shows_positive_credits() {
plan_type: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let model_family = test_model_family(&config);
let model_slug = ModelsManager::get_model_offline(config.model.as_deref());
let model_family = test_model_family(&model_slug, &config);
let composite = new_status_output(
&config,
&auth_manager,
@@ -282,6 +289,7 @@ fn status_snapshot_shows_positive_credits() {
Some(&rate_display),
None,
captured_at,
&model_slug,
);
let rendered = render_lines(&composite.display_lines(120));
assert!(
@@ -313,7 +321,8 @@ fn status_snapshot_hides_zero_credits() {
plan_type: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let model_family = test_model_family(&config);
let model_slug = ModelsManager::get_model_offline(config.model.as_deref());
let model_family = test_model_family(&model_slug, &config);
let composite = new_status_output(
&config,
&auth_manager,
@@ -324,6 +333,7 @@ fn status_snapshot_hides_zero_credits() {
Some(&rate_display),
None,
captured_at,
&model_slug,
);
let rendered = render_lines(&composite.display_lines(120));
assert!(
@@ -353,7 +363,8 @@ fn status_snapshot_hides_when_has_no_credits_flag() {
plan_type: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let model_family = test_model_family(&config);
let model_slug = ModelsManager::get_model_offline(config.model.as_deref());
let model_family = test_model_family(&model_slug, &config);
let composite = new_status_output(
&config,
&auth_manager,
@@ -364,6 +375,7 @@ fn status_snapshot_hides_when_has_no_credits_flag() {
Some(&rate_display),
None,
captured_at,
&model_slug,
);
let rendered = render_lines(&composite.display_lines(120));
assert!(
@@ -376,7 +388,7 @@ fn status_snapshot_hides_when_has_no_credits_flag() {
fn status_card_token_usage_excludes_cached_tokens() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home);
config.model = "gpt-5.1-codex-max".to_string();
config.model = Some("gpt-5.1-codex-max".to_string());
config.cwd = PathBuf::from("/workspace/tests");
let auth_manager = test_auth_manager(&config);
@@ -393,7 +405,8 @@ fn status_card_token_usage_excludes_cached_tokens() {
.single()
.expect("timestamp");
let model_family = test_model_family(&config);
let model_slug = ModelsManager::get_model_offline(config.model.as_deref());
let model_family = test_model_family(&model_slug, &config);
let composite = new_status_output(
&config,
&auth_manager,
@@ -404,6 +417,7 @@ fn status_card_token_usage_excludes_cached_tokens() {
None,
None,
now,
&model_slug,
);
let rendered = render_lines(&composite.display_lines(120));
@@ -417,7 +431,7 @@ fn status_card_token_usage_excludes_cached_tokens() {
fn status_snapshot_truncates_in_narrow_terminal() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home);
config.model = "gpt-5.1-codex-max".to_string();
config.model = Some("gpt-5.1-codex-max".to_string());
config.model_provider_id = "openai".to_string();
config.model_reasoning_effort = Some(ReasoningEffort::High);
config.model_reasoning_summary = ReasoningSummary::Detailed;
@@ -448,7 +462,8 @@ fn status_snapshot_truncates_in_narrow_terminal() {
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let model_family = test_model_family(&config);
let model_slug = ModelsManager::get_model_offline(config.model.as_deref());
let model_family = test_model_family(&model_slug, &config);
let composite = new_status_output(
&config,
&auth_manager,
@@ -459,6 +474,7 @@ fn status_snapshot_truncates_in_narrow_terminal() {
Some(&rate_display),
None,
captured_at,
&model_slug,
);
let mut rendered_lines = render_lines(&composite.display_lines(70));
if cfg!(windows) {
@@ -475,7 +491,7 @@ fn status_snapshot_truncates_in_narrow_terminal() {
fn status_snapshot_shows_missing_limits_message() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home);
config.model = "gpt-5.1-codex-max".to_string();
config.model = Some("gpt-5.1-codex-max".to_string());
config.cwd = PathBuf::from("/workspace/tests");
let auth_manager = test_auth_manager(&config);
@@ -492,7 +508,8 @@ fn status_snapshot_shows_missing_limits_message() {
.single()
.expect("timestamp");
let model_family = test_model_family(&config);
let model_slug = ModelsManager::get_model_offline(config.model.as_deref());
let model_family = test_model_family(&model_slug, &config);
let composite = new_status_output(
&config,
&auth_manager,
@@ -503,6 +520,7 @@ fn status_snapshot_shows_missing_limits_message() {
None,
None,
now,
&model_slug,
);
let mut rendered_lines = render_lines(&composite.display_lines(80));
if cfg!(windows) {
@@ -518,7 +536,7 @@ fn status_snapshot_shows_missing_limits_message() {
fn status_snapshot_includes_credits_and_limits() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home);
config.model = "gpt-5.1-codex".to_string();
config.model = Some("gpt-5.1-codex".to_string());
config.cwd = PathBuf::from("/workspace/tests");
let auth_manager = test_auth_manager(&config);
@@ -554,7 +572,8 @@ fn status_snapshot_includes_credits_and_limits() {
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let model_family = test_model_family(&config);
let model_slug = ModelsManager::get_model_offline(config.model.as_deref());
let model_family = test_model_family(&model_slug, &config);
let composite = new_status_output(
&config,
&auth_manager,
@@ -565,6 +584,7 @@ fn status_snapshot_includes_credits_and_limits() {
Some(&rate_display),
None,
captured_at,
&model_slug,
);
let mut rendered_lines = render_lines(&composite.display_lines(80));
if cfg!(windows) {
@@ -580,7 +600,7 @@ fn status_snapshot_includes_credits_and_limits() {
fn status_snapshot_shows_empty_limits_message() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home);
config.model = "gpt-5.1-codex-max".to_string();
config.model = Some("gpt-5.1-codex-max".to_string());
config.cwd = PathBuf::from("/workspace/tests");
let auth_manager = test_auth_manager(&config);
@@ -604,7 +624,8 @@ fn status_snapshot_shows_empty_limits_message() {
.expect("timestamp");
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let model_family = test_model_family(&config);
let model_slug = ModelsManager::get_model_offline(config.model.as_deref());
let model_family = test_model_family(&model_slug, &config);
let composite = new_status_output(
&config,
&auth_manager,
@@ -615,6 +636,7 @@ fn status_snapshot_shows_empty_limits_message() {
Some(&rate_display),
None,
captured_at,
&model_slug,
);
let mut rendered_lines = render_lines(&composite.display_lines(80));
if cfg!(windows) {
@@ -630,7 +652,7 @@ fn status_snapshot_shows_empty_limits_message() {
fn status_snapshot_shows_stale_limits_message() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home);
config.model = "gpt-5.1-codex-max".to_string();
config.model = Some("gpt-5.1-codex-max".to_string());
config.cwd = PathBuf::from("/workspace/tests");
let auth_manager = test_auth_manager(&config);
@@ -663,7 +685,8 @@ fn status_snapshot_shows_stale_limits_message() {
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let now = captured_at + ChronoDuration::minutes(20);
let model_family = test_model_family(&config);
let model_slug = ModelsManager::get_model_offline(config.model.as_deref());
let model_family = test_model_family(&model_slug, &config);
let composite = new_status_output(
&config,
&auth_manager,
@@ -674,6 +697,7 @@ fn status_snapshot_shows_stale_limits_message() {
Some(&rate_display),
None,
now,
&model_slug,
);
let mut rendered_lines = render_lines(&composite.display_lines(80));
if cfg!(windows) {
@@ -689,7 +713,7 @@ fn status_snapshot_shows_stale_limits_message() {
fn status_snapshot_cached_limits_hide_credits_without_flag() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home);
config.model = "gpt-5.1-codex".to_string();
config.model = Some("gpt-5.1-codex".to_string());
config.cwd = PathBuf::from("/workspace/tests");
let auth_manager = test_auth_manager(&config);
@@ -726,7 +750,8 @@ fn status_snapshot_cached_limits_hide_credits_without_flag() {
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let now = captured_at + ChronoDuration::minutes(20);
let model_family = test_model_family(&config);
let model_slug = ModelsManager::get_model_offline(config.model.as_deref());
let model_family = test_model_family(&model_slug, &config);
let composite = new_status_output(
&config,
&auth_manager,
@@ -737,6 +762,7 @@ fn status_snapshot_cached_limits_hide_credits_without_flag() {
Some(&rate_display),
None,
now,
&model_slug,
);
let mut rendered_lines = render_lines(&composite.display_lines(80));
if cfg!(windows) {
@@ -775,7 +801,8 @@ fn status_context_window_uses_last_usage() {
.single()
.expect("timestamp");
let model_family = test_model_family(&config);
let model_slug = ModelsManager::get_model_offline(config.model.as_deref());
let model_family = test_model_family(&model_slug, &config);
let composite = new_status_output(
&config,
&auth_manager,
@@ -786,6 +813,7 @@ fn status_context_window_uses_last_usage() {
None,
None,
now,
&model_slug,
);
let rendered_lines = render_lines(&composite.display_lines(80));
let context_line = rendered_lines