Fix stale model test fixtures (#18719)

Fixes stale test fixtures left after the active bundled model catalog
updates in #18586 and #18388. Those changes made `gpt-5.4` the current
default and removed several older hardcoded slugs, which left Windows
Bazel shards failing TUI and config tests.

What changed:
- Refresh TUI model migration, availability NUX, plan-mode, status, and
snapshot fixtures to use active bundled model slugs.
- Update the config edit test expectation for the TOML-quoted
`"gpt-5.2"` migration key.
- Move the model catalog tests into
`codex-rs/tui/src/app/tests/model_catalog.rs` so touching them does not
trip the blob-size policy for `app.rs`.

Verification:
- CI Bazel/lint checks are expected to cover the affected test shards.
This commit is contained in:
Ahmed Ibrahim
2026-04-20 14:52:30 -07:00
committed by GitHub
Unverified
parent baa5dd7b29
commit cc96a03f10
31 changed files with 405 additions and 428 deletions
+1 -1
View File
@@ -547,7 +547,7 @@ existing = "value"
existing = "value"
[notice.model_migrations]
gpt-5 = "gpt-5.4"
"gpt-5.2" = "gpt-5.4"
"#;
assert_eq!(contents, expected);
}
+2 -308
View File
@@ -7114,7 +7114,6 @@ mod tests {
use codex_app_server_protocol::TurnStatus;
use codex_app_server_protocol::UserInput as AppServerUserInput;
use codex_app_server_protocol::WarningNotification;
use codex_config::types::ModelAvailabilityNuxConfig;
use codex_otel::SessionTelemetry;
use codex_protocol::ThreadId;
use codex_protocol::config_types::CollaborationMode;
@@ -7125,7 +7124,6 @@ mod tests {
use codex_protocol::models::FileSystemPermissions;
use codex_protocol::models::NetworkPermissions;
use codex_protocol::models::PermissionProfile;
use codex_protocol::openai_models::ModelAvailabilityNux;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::Event;
use codex_protocol::protocol::EventMsg;
@@ -7152,6 +7150,8 @@ mod tests {
use tempfile::tempdir;
use tokio::time;
mod model_catalog;
fn test_absolute_path(path: &str) -> AbsolutePathBuf {
AbsolutePathBuf::try_from(PathBuf::from(path)).expect("absolute test path")
}
@@ -11474,135 +11474,6 @@ guardian_approval = true
.and_then(TomlValue::as_bool)
}
fn all_model_presets() -> Vec<ModelPreset> {
crate::legacy_core::test_support::all_model_presets().clone()
}
fn model_availability_nux_config(shown_count: &[(&str, u32)]) -> ModelAvailabilityNuxConfig {
ModelAvailabilityNuxConfig {
shown_count: shown_count
.iter()
.map(|(model, count)| ((*model).to_string(), *count))
.collect(),
}
}
fn model_migration_copy_to_plain_text(
copy: &crate::model_migration::ModelMigrationCopy,
) -> String {
if let Some(markdown) = copy.markdown.as_ref() {
return markdown.clone();
}
let mut s = String::new();
for span in &copy.heading {
s.push_str(&span.content);
}
s.push('\n');
s.push('\n');
for line in &copy.content {
for span in &line.spans {
s.push_str(&span.content);
}
s.push('\n');
}
s
}
#[tokio::test]
async fn model_migration_prompt_only_shows_for_deprecated_models() {
let seen = BTreeMap::new();
assert!(should_show_model_migration_prompt(
"gpt-5",
"gpt-5.2-codex",
&seen,
&all_model_presets()
));
assert!(should_show_model_migration_prompt(
"gpt-5-codex",
"gpt-5.2-codex",
&seen,
&all_model_presets()
));
assert!(should_show_model_migration_prompt(
"gpt-5-codex-mini",
"gpt-5.2-codex",
&seen,
&all_model_presets()
));
assert!(should_show_model_migration_prompt(
"gpt-5.1-codex",
"gpt-5.2-codex",
&seen,
&all_model_presets()
));
assert!(!should_show_model_migration_prompt(
"gpt-5.1-codex",
"gpt-5.1-codex",
&seen,
&all_model_presets()
));
}
#[test]
fn select_model_availability_nux_picks_only_eligible_model() {
let mut presets = all_model_presets();
presets.iter_mut().for_each(|preset| {
preset.availability_nux = None;
});
let target = presets
.iter_mut()
.find(|preset| preset.model == "gpt-5")
.expect("target preset present");
target.availability_nux = Some(ModelAvailabilityNux {
message: "gpt-5 is available".to_string(),
});
let selected = select_model_availability_nux(&presets, &model_availability_nux_config(&[]));
assert_eq!(
selected,
Some(StartupTooltipOverride {
model_slug: "gpt-5".to_string(),
message: "gpt-5 is available".to_string(),
})
);
}
#[test]
fn select_model_availability_nux_skips_missing_and_exhausted_models() {
let mut presets = all_model_presets();
presets.iter_mut().for_each(|preset| {
preset.availability_nux = None;
});
let gpt_5 = presets
.iter_mut()
.find(|preset| preset.model == "gpt-5")
.expect("gpt-5 preset present");
gpt_5.availability_nux = Some(ModelAvailabilityNux {
message: "gpt-5 is available".to_string(),
});
let gpt_5_2 = presets
.iter_mut()
.find(|preset| preset.model == "gpt-5.2")
.expect("gpt-5.2 preset present");
gpt_5_2.availability_nux = Some(ModelAvailabilityNux {
message: "gpt-5.2 is available".to_string(),
});
let selected = select_model_availability_nux(
&presets,
&model_availability_nux_config(&[("gpt-5", MODEL_AVAILABILITY_NUX_MAX_SHOW_COUNT)]),
);
assert_eq!(
selected,
Some(StartupTooltipOverride {
model_slug: "gpt-5.2".to_string(),
message: "gpt-5.2 is available".to_string(),
})
);
}
#[test]
fn active_turn_not_steerable_turn_error_extracts_structured_server_error() {
let turn_error = AppServerTurnError {
@@ -11665,183 +11536,6 @@ guardian_approval = true
);
}
#[test]
fn select_model_availability_nux_uses_existing_model_order_as_priority() {
let mut presets = all_model_presets();
presets.iter_mut().for_each(|preset| {
preset.availability_nux = None;
});
let first = presets
.iter_mut()
.find(|preset| preset.model == "gpt-5")
.expect("gpt-5 preset present");
first.availability_nux = Some(ModelAvailabilityNux {
message: "first".to_string(),
});
let second = presets
.iter_mut()
.find(|preset| preset.model == "gpt-5.2")
.expect("gpt-5.2 preset present");
second.availability_nux = Some(ModelAvailabilityNux {
message: "second".to_string(),
});
let selected = select_model_availability_nux(&presets, &model_availability_nux_config(&[]));
assert_eq!(
selected,
Some(StartupTooltipOverride {
model_slug: "gpt-5.2".to_string(),
message: "second".to_string(),
})
);
}
#[test]
fn select_model_availability_nux_returns_none_when_all_models_are_exhausted() {
let mut presets = all_model_presets();
presets.iter_mut().for_each(|preset| {
preset.availability_nux = None;
});
let target = presets
.iter_mut()
.find(|preset| preset.model == "gpt-5")
.expect("target preset present");
target.availability_nux = Some(ModelAvailabilityNux {
message: "gpt-5 is available".to_string(),
});
let selected = select_model_availability_nux(
&presets,
&model_availability_nux_config(&[("gpt-5", MODEL_AVAILABILITY_NUX_MAX_SHOW_COUNT)]),
);
assert_eq!(selected, None);
}
#[tokio::test]
async fn model_migration_prompt_respects_hide_flag_and_self_target() {
let mut seen = BTreeMap::new();
seen.insert("gpt-5".to_string(), "gpt-5.1".to_string());
assert!(!should_show_model_migration_prompt(
"gpt-5",
"gpt-5.1",
&seen,
&all_model_presets()
));
assert!(!should_show_model_migration_prompt(
"gpt-5.1",
"gpt-5.1",
&seen,
&all_model_presets()
));
}
#[tokio::test]
async fn model_migration_prompt_skips_when_target_missing_or_hidden() {
let mut available = all_model_presets();
let mut current = available
.iter()
.find(|preset| preset.model == "gpt-5-codex")
.cloned()
.expect("preset present");
current.upgrade = Some(ModelUpgrade {
id: "missing-target".to_string(),
reasoning_effort_mapping: None,
migration_config_key: HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG.to_string(),
model_link: None,
upgrade_copy: None,
migration_markdown: None,
});
available.retain(|preset| preset.model != "gpt-5-codex");
available.push(current.clone());
assert!(!should_show_model_migration_prompt(
&current.model,
"missing-target",
&BTreeMap::new(),
&available,
));
assert!(target_preset_for_upgrade(&available, "missing-target").is_none());
let mut with_hidden_target = all_model_presets();
let target = with_hidden_target
.iter_mut()
.find(|preset| preset.model == "gpt-5.2-codex")
.expect("target preset present");
target.show_in_picker = false;
assert!(!should_show_model_migration_prompt(
"gpt-5-codex",
"gpt-5.2-codex",
&BTreeMap::new(),
&with_hidden_target,
));
assert!(target_preset_for_upgrade(&with_hidden_target, "gpt-5.2-codex").is_none());
}
#[tokio::test]
async fn model_migration_prompt_shows_for_hidden_model() {
let codex_home = tempdir().expect("temp codex home");
let config = ConfigBuilder::default()
.codex_home(codex_home.path().to_path_buf())
.build()
.await
.expect("config");
let mut available_models = all_model_presets();
let current = available_models
.iter()
.find(|preset| preset.model == "gpt-5.1-codex")
.cloned()
.expect("gpt-5.1-codex preset present");
assert!(
!current.show_in_picker,
"expected gpt-5.1-codex to be hidden from picker for this test"
);
let upgrade = current.upgrade.as_ref().expect("upgrade configured");
// Test "hidden current model still prompts" even if bundled
// catalog data changes the target model's picker visibility.
available_models
.iter_mut()
.find(|preset| preset.model == upgrade.id)
.expect("upgrade target present")
.show_in_picker = true;
assert!(
should_show_model_migration_prompt(
&current.model,
&upgrade.id,
&config.notices.model_migrations,
&available_models,
),
"expected migration prompt to be eligible for hidden model"
);
let target = target_preset_for_upgrade(&available_models, &upgrade.id)
.expect("upgrade target present");
let target_description =
(!target.description.is_empty()).then(|| target.description.clone());
let can_opt_out = true;
let copy = migration_copy_for_models(
&current.model,
&upgrade.id,
upgrade.model_link.clone(),
upgrade.upgrade_copy.clone(),
upgrade.migration_markdown.clone(),
target.display_name.clone(),
target_description,
can_opt_out,
);
// Snapshot the copy we would show; rendering is covered by model_migration snapshots.
assert_snapshot!(
"model_migration_prompt_shows_for_hidden_model",
model_migration_copy_to_plain_text(&copy)
);
}
#[tokio::test]
async fn update_reasoning_effort_updates_collaboration_mode() {
let mut app = make_test_app().await;
+293
View File
@@ -0,0 +1,293 @@
use super::*;
use codex_config::types::ModelAvailabilityNuxConfig;
use codex_protocol::openai_models::ModelAvailabilityNux;
use pretty_assertions::assert_eq;
fn all_model_presets() -> Vec<ModelPreset> {
crate::legacy_core::test_support::all_model_presets().clone()
}
fn model_availability_nux_config(shown_count: &[(&str, u32)]) -> ModelAvailabilityNuxConfig {
ModelAvailabilityNuxConfig {
shown_count: shown_count
.iter()
.map(|(model, count)| ((*model).to_string(), *count))
.collect(),
}
}
fn model_migration_copy_to_plain_text(copy: &crate::model_migration::ModelMigrationCopy) -> String {
if let Some(markdown) = copy.markdown.as_ref() {
return markdown.clone();
}
let mut s = String::new();
for span in &copy.heading {
s.push_str(&span.content);
}
s.push('\n');
s.push('\n');
for line in &copy.content {
for span in &line.spans {
s.push_str(&span.content);
}
s.push('\n');
}
s
}
#[tokio::test]
async fn model_migration_prompt_only_shows_for_deprecated_models() {
let seen = BTreeMap::new();
assert!(should_show_model_migration_prompt(
"gpt-5.2",
"gpt-5.4",
&seen,
&all_model_presets()
));
assert!(should_show_model_migration_prompt(
"gpt-5.3-codex",
"gpt-5.4",
&seen,
&all_model_presets()
));
assert!(!should_show_model_migration_prompt(
"gpt-5.3-codex",
"gpt-5.3-codex",
&seen,
&all_model_presets()
));
}
#[test]
fn select_model_availability_nux_picks_only_eligible_model() {
let mut presets = all_model_presets();
presets.iter_mut().for_each(|preset| {
preset.availability_nux = None;
});
let target = presets
.iter_mut()
.find(|preset| preset.model == "gpt-5.4")
.expect("target preset present");
target.availability_nux = Some(ModelAvailabilityNux {
message: "gpt-5.4 is available".to_string(),
});
let selected = select_model_availability_nux(&presets, &model_availability_nux_config(&[]));
assert_eq!(
selected,
Some(StartupTooltipOverride {
model_slug: "gpt-5.4".to_string(),
message: "gpt-5.4 is available".to_string(),
})
);
}
#[test]
fn select_model_availability_nux_skips_missing_and_exhausted_models() {
let mut presets = all_model_presets();
presets.iter_mut().for_each(|preset| {
preset.availability_nux = None;
});
let gpt_5 = presets
.iter_mut()
.find(|preset| preset.model == "gpt-5.4")
.expect("gpt-5.4 preset present");
gpt_5.availability_nux = Some(ModelAvailabilityNux {
message: "gpt-5.4 is available".to_string(),
});
let gpt_5_2 = presets
.iter_mut()
.find(|preset| preset.model == "gpt-5.4-mini")
.expect("gpt-5.4-mini preset present");
gpt_5_2.availability_nux = Some(ModelAvailabilityNux {
message: "gpt-5.4-mini is available".to_string(),
});
let selected = select_model_availability_nux(
&presets,
&model_availability_nux_config(&[("gpt-5.4", MODEL_AVAILABILITY_NUX_MAX_SHOW_COUNT)]),
);
assert_eq!(
selected,
Some(StartupTooltipOverride {
model_slug: "gpt-5.4-mini".to_string(),
message: "gpt-5.4-mini is available".to_string(),
})
);
}
#[test]
fn select_model_availability_nux_uses_existing_model_order_as_priority() {
let mut presets = all_model_presets();
presets.iter_mut().for_each(|preset| {
preset.availability_nux = None;
});
let first = presets
.iter_mut()
.find(|preset| preset.model == "gpt-5.4-mini")
.expect("gpt-5.4-mini preset present");
first.availability_nux = Some(ModelAvailabilityNux {
message: "first".to_string(),
});
let second = presets
.iter_mut()
.find(|preset| preset.model == "gpt-5.4")
.expect("gpt-5.4 preset present");
second.availability_nux = Some(ModelAvailabilityNux {
message: "second".to_string(),
});
let selected = select_model_availability_nux(&presets, &model_availability_nux_config(&[]));
assert_eq!(
selected,
Some(StartupTooltipOverride {
model_slug: "gpt-5.4".to_string(),
message: "second".to_string(),
})
);
}
#[test]
fn select_model_availability_nux_returns_none_when_all_models_are_exhausted() {
let mut presets = all_model_presets();
presets.iter_mut().for_each(|preset| {
preset.availability_nux = None;
});
let target = presets
.iter_mut()
.find(|preset| preset.model == "gpt-5.4")
.expect("target preset present");
target.availability_nux = Some(ModelAvailabilityNux {
message: "gpt-5.4 is available".to_string(),
});
let selected = select_model_availability_nux(
&presets,
&model_availability_nux_config(&[("gpt-5.4", MODEL_AVAILABILITY_NUX_MAX_SHOW_COUNT)]),
);
assert_eq!(selected, None);
}
#[tokio::test]
async fn model_migration_prompt_respects_hide_flag_and_self_target() {
let mut seen = BTreeMap::new();
seen.insert("gpt-5.2".to_string(), "gpt-5.4".to_string());
assert!(!should_show_model_migration_prompt(
"gpt-5.2",
"gpt-5.4",
&seen,
&all_model_presets()
));
assert!(!should_show_model_migration_prompt(
"gpt-5.4",
"gpt-5.4",
&seen,
&all_model_presets()
));
}
#[tokio::test]
async fn model_migration_prompt_skips_when_target_missing_or_hidden() {
let mut available = all_model_presets();
let mut current = available
.iter()
.find(|preset| preset.model == "gpt-5.2")
.cloned()
.expect("preset present");
current.upgrade = Some(ModelUpgrade {
id: "missing-target".to_string(),
reasoning_effort_mapping: None,
migration_config_key: HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG.to_string(),
model_link: None,
upgrade_copy: None,
migration_markdown: None,
});
available.retain(|preset| preset.model != "gpt-5.2");
available.push(current.clone());
assert!(!should_show_model_migration_prompt(
&current.model,
"missing-target",
&BTreeMap::new(),
&available,
));
assert!(target_preset_for_upgrade(&available, "missing-target").is_none());
let mut with_hidden_target = all_model_presets();
let target = with_hidden_target
.iter_mut()
.find(|preset| preset.model == "gpt-5.4")
.expect("target preset present");
target.show_in_picker = false;
assert!(!should_show_model_migration_prompt(
"gpt-5.2",
"gpt-5.4",
&BTreeMap::new(),
&with_hidden_target,
));
assert!(target_preset_for_upgrade(&with_hidden_target, "gpt-5.4").is_none());
}
#[tokio::test]
async fn model_migration_prompt_shows_for_hidden_model() {
let codex_home = tempdir().expect("temp codex home");
let config = ConfigBuilder::default()
.codex_home(codex_home.path().to_path_buf())
.build()
.await
.expect("config");
let mut available_models = all_model_presets();
let current = available_models
.iter_mut()
.find(|preset| preset.model == "gpt-5.3-codex")
.expect("gpt-5.3-codex preset present");
current.show_in_picker = false;
let current = current.clone();
assert!(
!current.show_in_picker,
"expected gpt-5.3-codex to be hidden from picker for this test"
);
let upgrade = current.upgrade.as_ref().expect("upgrade configured");
available_models
.iter_mut()
.find(|preset| preset.model == upgrade.id)
.expect("upgrade target present")
.show_in_picker = true;
assert!(
should_show_model_migration_prompt(
&current.model,
&upgrade.id,
&config.notices.model_migrations,
&available_models,
),
"expected migration prompt to be eligible for hidden model"
);
let target =
target_preset_for_upgrade(&available_models, &upgrade.id).expect("upgrade target present");
let target_description = (!target.description.is_empty()).then(|| target.description.clone());
let can_opt_out = true;
let copy = migration_copy_for_models(
&current.model,
&upgrade.id,
upgrade.model_link.clone(),
upgrade.upgrade_copy.clone(),
upgrade.migration_markdown.clone(),
target.display_name.clone(),
target_description,
can_opt_out,
);
assert_snapshot!(
"model_migration_prompt_shows_for_hidden_model",
model_migration_copy_to_plain_text(&copy)
);
}
@@ -0,0 +1,12 @@
---
source: tui/src/app/tests/model_catalog.rs
assertion_line: 289
expression: model_migration_copy_to_plain_text(&copy)
---
Introducing GPT-5.4
Codex just got an upgrade with GPT-5.4, our most capable model for professional work. It outperforms prior models while being more token efficient, with notable improvements on long-running tasks, tool calling, computer use, and frontend development.
Learn more: https://openai.com/index/introducing-gpt-5-4
You can always keep using GPT-5.3-Codex if you prefer.
@@ -10,4 +10,4 @@ expression: normalize_snapshot_paths(term.backend().vt100().screen().contents())
Ask Codex to do anything
gpt-5.3-codex default · /tmp/project
gpt-5.4 default · /tmp/project
@@ -17,4 +17,4 @@ expression: normalize_snapshot_paths(term.backend().vt100().screen().contents())
Ask Codex to do anything
gpt-5.3-codex default · /tmp/project
gpt-5.4 default · /tmp/project
@@ -8,4 +8,4 @@ expression: normalize_snapshot_paths(term.backend().vt100().screen().contents())
Ask Codex to do anything
gpt-5.3-codex default · /tmp/project
gpt-5.4 default · /tmp/project
@@ -26,4 +26,4 @@ expression: normalize_snapshot_paths(term.backend().vt100().screen().contents())
Ask Codex to do anything
gpt-5.3-codex default · /tmp/project
gpt-5.4 default · /tmp/project
@@ -9,4 +9,4 @@ expression: normalize_snapshot_paths(term.backend().vt100().screen().contents())
Ask Codex to do anything
gpt-5.3-codex default · /tmp/project
gpt-5.4 default · /tmp/project
@@ -8,4 +8,4 @@ expression: normalize_snapshot_paths(term.backend().vt100().screen().contents())
Ask Codex to do anything
gpt-5.3-codex default · /tmp/project
gpt-5.4 default · /tmp/project
@@ -14,4 +14,4 @@ expression: normalize_snapshot_paths(term.backend().vt100().screen().contents())
Ask Codex to do anything
gpt-5.3-codex default · /tmp/project
gpt-5.4 default · /tmp/project
@@ -9,4 +9,4 @@ expression: normalize_snapshot_paths(rendered)
Ask Codex to do anything
gpt-5.3-codex default · /tmp/project
gpt-5.4 default · /tmp/project
@@ -21,4 +21,4 @@ expression: normalize_snapshot_paths(term.backend().vt100().screen().contents())
Ask Codex to do anything
gpt-5.3-codex default · /tmp/project
gpt-5.4 default · /tmp/project
@@ -8,4 +8,4 @@ expression: normalized_backend_snapshot(terminal.backend())
" "
" Ask Codex to do anything "
" "
" gpt-5.3-codex default · /tmp/project "
" gpt-5.4 default · /tmp/project "
@@ -2,7 +2,7 @@
source: tui/src/chatwidget/tests.rs
expression: popup
---
Select Reasoning Level for gpt-5.1-codex-max
Select Reasoning Level for gpt-5.4
1. Low Fast responses with lighter reasoning
2. Medium (default) Balances speed and reasoning depth for everyday tasks
@@ -1,14 +1,16 @@
---
source: tui/src/chatwidget/tests.rs
source: tui/src/chatwidget/tests/popups_and_settings.rs
expression: popup
---
Select Reasoning Level for gpt-5.1-codex-max
Select Reasoning Level for gpt-5.2
1. Low Fast responses with lighter reasoning
2. Medium (default) Balances speed and reasoning depth for everyday
tasks
3. High Greater reasoning depth for complex problems
4. Extra high (current) Extra high reasoning depth for complex problems
1. Low Balances speed with some reasoning; useful for
straightforward queries and short explanations
2. Medium (default) Provides a solid balance of reasoning depth and
latency for general-purpose tasks
3. High Maximizes reasoning depth for complex or ambiguous
problems
4. Extra high (current) Extra high reasoning for complex problems
⚠ Extra high reasoning effort can quickly consume
Plus plan rate limits.
@@ -1,18 +1,14 @@
---
source: tui/src/chatwidget/tests.rs
source: tui/src/chatwidget/tests/popups_and_settings.rs
expression: popup
---
Select Model and Effort
Access legacy models by running codex -m <model_name> or in your config.toml
1. gpt-5.3-codex (default) Latest frontier agentic coding model.
2. gpt-5.4 Latest frontier agentic coding model.
3. gpt-5.2-codex Frontier agentic coding model.
4. gpt-5.1-codex-max Codex-optimized flagship for deep and fast
reasoning.
5. gpt-5.2 Latest frontier model with improvements across
knowledge, reasoning and coding
6. gpt-5.1-codex-mini Optimized for codex. Cheaper, faster, but less
capable.
1. gpt-5.4 (default) Latest frontier agentic coding model.
2. gpt-5.4-mini Smaller frontier agentic coding model.
3. gpt-5.3-codex Frontier Codex-optimized agentic coding model.
4. gpt-5.2 (current) Optimized for professional work and long-running
agents
Press enter to select reasoning effort, or esc to dismiss.
@@ -8,4 +8,4 @@ expression: normalized_backend_snapshot(terminal.backend())
" "
" Ask Codex to do anything "
" "
" gpt-5.3-codex default · /tmp/project "
" gpt-5.4 default · /tmp/project "
@@ -3,10 +3,10 @@ source: tui/src/chatwidget/tests.rs
expression: popup
---
Approaching rate limits
Switch to gpt-5.1-codex-mini for lower credit usage?
Switch to gpt-5.4-mini for lower credit usage?
1. Switch to gpt-5.1-codex-mini Optimized for codex. Cheaper,
faster, but less capable.
1. Switch to gpt-5.4-mini Smaller frontier agentic coding
model.
2. Keep current model
3. Keep current model (never show again) Hide future rate limit reminders
about switching models.
@@ -9,4 +9,4 @@ expression: normalize_snapshot_paths(term.backend().vt100().screen().contents())
Ask Codex to do anything
gpt-5.3-codex default · /tmp/project
gpt-5.4 default · /tmp/project
@@ -6,4 +6,4 @@ expression: terminal.backend()
" "
" Check recently modified functions for compatibility "
" "
" gpt-5.3-codex Side from main thread · Esc to return "
" gpt-5.4 Side from main thread · Esc to return "
@@ -6,4 +6,4 @@ expression: terminal.backend()
" "
" Check recently modified functions for compatibility "
" "
" gpt-5.3-codex defa… Side from main thread · main needs input · Esc to return "
" gpt-5.4 default · … Side from main thread · main needs input · Esc to return "
@@ -8,4 +8,4 @@ expression: normalized_backend_snapshot(terminal.backend())
" "
" Ask Codex to do anything "
" "
" gpt-5.3-codex default Side starting... "
" gpt-5.4 default Side starting... "
@@ -8,4 +8,4 @@ expression: normalized_backend_snapshot(terminal.backend())
" "
" Ask Codex to do anything "
" "
" gpt-5.3-codex default · /tmp/project "
" gpt-5.4 default · /tmp/project "
@@ -8,4 +8,4 @@ expression: normalized_backend_snapshot(terminal.backend())
" "
" Ask Codex to do anything "
" "
" gpt-5.3-codex default · /tmp/project "
" gpt-5.4 default · /tmp/project "
@@ -8,4 +8,4 @@ expression: normalize_snapshot_paths(rendered)
Ask Codex to do anything
gpt-5.3-codex default · /tmp/project
gpt-5.4 default · /tmp/project
+39 -48
View File
@@ -169,7 +169,7 @@ async fn submit_user_message_with_mode_sets_coding_collaboration_mode() {
#[tokio::test]
async fn reasoning_selection_in_plan_mode_opens_scope_prompt_event() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
chat.thread_id = Some(ThreadId::new());
chat.set_feature_enabled(Feature::CollaborationModes, /*enabled*/ true);
let plan_mask = collaboration_modes::plan_mask(chat.model_catalog.as_ref())
@@ -179,7 +179,7 @@ async fn reasoning_selection_in_plan_mode_opens_scope_prompt_event() {
set_chatgpt_auth(&mut chat);
chat.set_reasoning_effort(Some(ReasoningEffortConfig::High));
let preset = get_available_model(&chat, "gpt-5.1-codex-max");
let preset = get_available_model(&chat, "gpt-5.4");
chat.open_reasoning_popup(preset);
chat.handle_key_event(KeyEvent::from(KeyCode::Down));
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
@@ -190,13 +190,13 @@ async fn reasoning_selection_in_plan_mode_opens_scope_prompt_event() {
AppEvent::OpenPlanReasoningScopePrompt {
model,
effort: Some(_)
} if model == "gpt-5.1-codex-max"
} if model == "gpt-5.4"
);
}
#[tokio::test]
async fn reasoning_selection_in_plan_mode_without_effort_change_does_not_open_scope_prompt_event() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
chat.thread_id = Some(ThreadId::new());
chat.set_feature_enabled(Feature::CollaborationModes, /*enabled*/ true);
let plan_mask = collaboration_modes::plan_mask(chat.model_catalog.as_ref())
@@ -205,10 +205,10 @@ async fn reasoning_selection_in_plan_mode_without_effort_change_does_not_open_sc
let _ = drain_insert_history(&mut rx);
set_chatgpt_auth(&mut chat);
let current_preset = get_available_model(&chat, "gpt-5.1-codex-max");
let current_preset = get_available_model(&chat, "gpt-5.4");
chat.set_reasoning_effort(Some(current_preset.default_reasoning_effort));
let preset = get_available_model(&chat, "gpt-5.1-codex-max");
let preset = get_available_model(&chat, "gpt-5.4");
chat.open_reasoning_popup(preset);
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
@@ -216,7 +216,7 @@ async fn reasoning_selection_in_plan_mode_without_effort_change_does_not_open_sc
assert!(
events.iter().any(|event| matches!(
event,
AppEvent::UpdateModel(model) if model == "gpt-5.1-codex-max"
AppEvent::UpdateModel(model) if model == "gpt-5.4"
)),
"expected model update event; events: {events:?}"
);
@@ -231,7 +231,7 @@ async fn reasoning_selection_in_plan_mode_without_effort_change_does_not_open_sc
#[tokio::test]
async fn reasoning_selection_in_plan_mode_matching_plan_effort_but_different_global_opens_scope_prompt()
{
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
chat.thread_id = Some(ThreadId::new());
chat.set_feature_enabled(Feature::CollaborationModes, /*enabled*/ true);
let plan_mask = collaboration_modes::plan_mask(chat.model_catalog.as_ref())
@@ -245,7 +245,7 @@ async fn reasoning_selection_in_plan_mode_matching_plan_effort_but_different_glo
// should open the scope prompt rather than silently rewriting the global default.
chat.set_reasoning_effort(Some(ReasoningEffortConfig::High));
let preset = get_available_model(&chat, "gpt-5.1-codex-max");
let preset = get_available_model(&chat, "gpt-5.4");
chat.open_reasoning_popup(preset);
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
@@ -255,13 +255,13 @@ async fn reasoning_selection_in_plan_mode_matching_plan_effort_but_different_glo
AppEvent::OpenPlanReasoningScopePrompt {
model,
effort: Some(ReasoningEffortConfig::Medium)
} if model == "gpt-5.1-codex-max"
} if model == "gpt-5.4"
);
}
#[tokio::test]
async fn plan_mode_reasoning_override_is_marked_current_in_reasoning_popup() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
chat.set_feature_enabled(Feature::CollaborationModes, /*enabled*/ true);
set_chatgpt_auth(&mut chat);
chat.set_reasoning_effort(Some(ReasoningEffortConfig::High));
@@ -271,7 +271,7 @@ async fn plan_mode_reasoning_override_is_marked_current_in_reasoning_popup() {
.expect("expected plan collaboration mode");
chat.set_collaboration_mask(plan_mask);
let preset = get_available_model(&chat, "gpt-5.1-codex-max");
let preset = get_available_model(&chat, "gpt-5.4");
chat.open_reasoning_popup(preset);
let popup = render_bottom_popup(&chat, /*width*/ 100);
@@ -284,7 +284,7 @@ async fn plan_mode_reasoning_override_is_marked_current_in_reasoning_popup() {
#[tokio::test]
async fn reasoning_selection_in_plan_mode_model_switch_does_not_open_scope_prompt_event() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
chat.thread_id = Some(ThreadId::new());
chat.set_feature_enabled(Feature::CollaborationModes, /*enabled*/ true);
let plan_mask = collaboration_modes::plan_mask(chat.model_catalog.as_ref())
@@ -293,7 +293,7 @@ async fn reasoning_selection_in_plan_mode_model_switch_does_not_open_scope_promp
let _ = drain_insert_history(&mut rx);
set_chatgpt_auth(&mut chat);
let preset = get_available_model(&chat, "gpt-5");
let preset = get_available_model(&chat, "gpt-5.2");
chat.open_reasoning_popup(preset);
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
@@ -301,7 +301,7 @@ async fn reasoning_selection_in_plan_mode_model_switch_does_not_open_scope_promp
assert!(
events.iter().any(|event| matches!(
event,
AppEvent::UpdateModel(model) if model == "gpt-5"
AppEvent::UpdateModel(model) if model == "gpt-5.2"
)),
"expected model update event; events: {events:?}"
);
@@ -315,11 +315,8 @@ async fn reasoning_selection_in_plan_mode_model_switch_does_not_open_scope_promp
#[tokio::test]
async fn plan_reasoning_scope_popup_all_modes_persists_global_and_plan_override() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
chat.open_plan_reasoning_scope_prompt(
"gpt-5.1-codex-max".to_string(),
Some(ReasoningEffortConfig::High),
);
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
chat.open_plan_reasoning_scope_prompt("gpt-5.4".to_string(), Some(ReasoningEffortConfig::High));
chat.handle_key_event(KeyEvent::from(KeyCode::Down));
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
@@ -343,7 +340,7 @@ async fn plan_reasoning_scope_popup_all_modes_persists_global_and_plan_override(
events.iter().any(|event| matches!(
event,
AppEvent::PersistModelSelection { model, effort: Some(ReasoningEffortConfig::High) }
if model == "gpt-5.1-codex-max"
if model == "gpt-5.4"
)),
"expected global model reasoning selection persistence; events: {events:?}"
);
@@ -369,7 +366,7 @@ fn plan_mode_prompt_notification_uses_dedicated_type_name() {
#[tokio::test]
async fn open_plan_implementation_prompt_sets_pending_notification() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
chat.config.tui_notifications.notifications =
Notifications::Custom(vec!["plan-mode-prompt".to_string()]);
@@ -383,14 +380,11 @@ async fn open_plan_implementation_prompt_sets_pending_notification() {
#[tokio::test]
async fn open_plan_reasoning_scope_prompt_sets_pending_notification() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
chat.config.tui_notifications.notifications =
Notifications::Custom(vec!["plan-mode-prompt".to_string()]);
chat.open_plan_reasoning_scope_prompt(
"gpt-5.1-codex-max".to_string(),
Some(ReasoningEffortConfig::High),
);
chat.open_plan_reasoning_scope_prompt("gpt-5.4".to_string(), Some(ReasoningEffortConfig::High));
assert_matches!(
chat.pending_notification,
@@ -400,7 +394,7 @@ async fn open_plan_reasoning_scope_prompt_sets_pending_notification() {
#[tokio::test]
async fn agent_turn_complete_does_not_override_pending_plan_mode_prompt_notification() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
chat.open_plan_implementation_prompt();
chat.notify(Notification::AgentTurnComplete {
@@ -415,7 +409,7 @@ async fn agent_turn_complete_does_not_override_pending_plan_mode_prompt_notifica
#[tokio::test]
async fn request_user_input_notification_overrides_pending_agent_turn_complete_notification() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
chat.notify(Notification::AgentTurnComplete {
response: "done".to_string(),
@@ -444,7 +438,7 @@ async fn request_user_input_notification_overrides_pending_agent_turn_complete_n
#[tokio::test]
async fn handle_request_user_input_sets_pending_notification() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
chat.config.tui_notifications.notifications =
Notifications::Custom(vec!["plan-mode-prompt".to_string()]);
@@ -472,10 +466,10 @@ async fn handle_request_user_input_sets_pending_notification() {
#[tokio::test]
async fn plan_reasoning_scope_popup_mentions_selected_reasoning() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
chat.set_plan_mode_reasoning_effort(Some(ReasoningEffortConfig::Low));
chat.open_plan_reasoning_scope_prompt(
"gpt-5.1-codex-max".to_string(),
"gpt-5.4".to_string(),
Some(ReasoningEffortConfig::Medium),
);
@@ -489,9 +483,9 @@ async fn plan_reasoning_scope_popup_mentions_selected_reasoning() {
#[tokio::test]
async fn plan_reasoning_scope_popup_mentions_built_in_plan_default_when_no_override() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
chat.open_plan_reasoning_scope_prompt(
"gpt-5.1-codex-max".to_string(),
"gpt-5.4".to_string(),
Some(ReasoningEffortConfig::Medium),
);
@@ -501,11 +495,8 @@ async fn plan_reasoning_scope_popup_mentions_built_in_plan_default_when_no_overr
#[tokio::test]
async fn plan_reasoning_scope_popup_plan_only_does_not_update_all_modes_reasoning() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
chat.open_plan_reasoning_scope_prompt(
"gpt-5.1-codex-max".to_string(),
Some(ReasoningEffortConfig::High),
);
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
chat.open_plan_reasoning_scope_prompt("gpt-5.4".to_string(), Some(ReasoningEffortConfig::High));
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
@@ -1123,7 +1114,7 @@ async fn mode_switch_surfaces_model_change_notification_when_effective_model_cha
let mut plan_mask =
collaboration_modes::mask_for_kind(chat.model_catalog.as_ref(), ModeKind::Plan)
.expect("expected plan collaboration mode");
plan_mask.model = Some("gpt-5.1-codex-mini".to_string());
plan_mask.model = Some("gpt-5.4-mini".to_string());
chat.set_collaboration_mask(plan_mask);
let plan_messages = drain_insert_history(&mut rx)
@@ -1132,7 +1123,7 @@ async fn mode_switch_surfaces_model_change_notification_when_effective_model_cha
.collect::<Vec<_>>()
.join("\n");
assert!(
plan_messages.contains("Model changed to gpt-5.1-codex-mini medium for Plan mode."),
plan_messages.contains("Model changed to gpt-5.4-mini medium for Plan mode."),
"expected Plan-mode model switch notice, got: {plan_messages:?}"
);
@@ -1336,21 +1327,21 @@ async fn collaboration_modes_defaults_to_code_on_startup() {
#[tokio::test]
async fn set_model_updates_active_collaboration_mask() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1")).await;
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.2")).await;
chat.set_feature_enabled(Feature::CollaborationModes, /*enabled*/ true);
let plan_mask = collaboration_modes::mask_for_kind(chat.model_catalog.as_ref(), ModeKind::Plan)
.expect("expected plan collaboration mask");
chat.set_collaboration_mask(plan_mask);
chat.set_model("gpt-5.1-codex-mini");
chat.set_model("gpt-5.4-mini");
assert_eq!(chat.current_model(), "gpt-5.1-codex-mini");
assert_eq!(chat.current_model(), "gpt-5.4-mini");
assert_eq!(chat.active_collaboration_mode_kind(), ModeKind::Plan);
}
#[tokio::test]
async fn set_reasoning_effort_updates_active_collaboration_mask() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1")).await;
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.2")).await;
chat.set_feature_enabled(Feature::CollaborationModes, /*enabled*/ true);
let plan_mask = collaboration_modes::mask_for_kind(chat.model_catalog.as_ref(), ModeKind::Plan)
.expect("expected plan collaboration mask");
@@ -1367,7 +1358,7 @@ async fn set_reasoning_effort_updates_active_collaboration_mask() {
#[tokio::test]
async fn set_reasoning_effort_does_not_override_active_plan_override() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1")).await;
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.2")).await;
chat.set_feature_enabled(Feature::CollaborationModes, /*enabled*/ true);
chat.set_plan_mode_reasoning_effort(Some(ReasoningEffortConfig::High));
let plan_mask = collaboration_modes::mask_for_kind(chat.model_catalog.as_ref(), ModeKind::Plan)
@@ -1437,10 +1428,10 @@ async fn collab_mode_applies_default_preset() {
#[tokio::test]
async fn user_turn_includes_personality_from_config() {
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5.2-codex")).await;
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await;
chat.set_feature_enabled(Feature::Personality, /*enabled*/ true);
chat.thread_id = Some(ThreadId::new());
chat.set_model("gpt-5.2-codex");
chat.set_model("gpt-5.3-codex");
chat.set_personality(Personality::Friendly);
chat.bottom_pane
@@ -1901,7 +1901,7 @@ async fn memories_reset_confirmation_sends_event_on_confirm() {
#[tokio::test]
async fn model_selection_popup_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5-codex")).await;
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.2")).await;
chat.thread_id = Some(ThreadId::new());
chat.open_model_popup();
@@ -1911,7 +1911,7 @@ async fn model_selection_popup_snapshot() {
#[tokio::test]
async fn personality_selection_popup_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.2-codex")).await;
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await;
chat.thread_id = Some(ThreadId::new());
chat.open_personality_popup();
@@ -1922,7 +1922,7 @@ async fn personality_selection_popup_snapshot() {
#[cfg(not(target_os = "linux"))]
#[tokio::test]
async fn realtime_audio_selection_popup_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.2-codex")).await;
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await;
chat.open_realtime_audio_popup();
let popup = render_bottom_popup(&chat, /*width*/ 80);
@@ -1932,7 +1932,7 @@ async fn realtime_audio_selection_popup_snapshot() {
#[cfg(not(target_os = "linux"))]
#[tokio::test]
async fn realtime_audio_selection_popup_narrow_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.2-codex")).await;
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await;
chat.open_realtime_audio_popup();
let popup = render_bottom_popup(&chat, /*width*/ 56);
@@ -1942,7 +1942,7 @@ async fn realtime_audio_selection_popup_narrow_snapshot() {
#[cfg(not(target_os = "linux"))]
#[tokio::test]
async fn realtime_microphone_picker_popup_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.2-codex")).await;
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await;
chat.config.realtime_audio.microphone = Some("Studio Mic".to_string());
chat.open_realtime_audio_device_selection_with_names(
RealtimeAudioDeviceKind::Microphone,
@@ -1956,7 +1956,7 @@ async fn realtime_microphone_picker_popup_snapshot() {
#[cfg(not(target_os = "linux"))]
#[tokio::test]
async fn realtime_audio_picker_emits_persist_event() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.2-codex")).await;
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await;
chat.open_realtime_audio_device_selection_with_names(
RealtimeAudioDeviceKind::Speaker,
vec!["Desk Speakers".to_string(), "Headphones".to_string()],
@@ -2017,8 +2017,8 @@ async fn model_picker_hides_show_in_picker_false_models_from_cache() {
#[tokio::test]
async fn server_overloaded_error_does_not_switch_models() {
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5.2-codex")).await;
chat.set_model("gpt-5.2-codex");
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await;
chat.set_model("gpt-5.3-codex");
while rx.try_recv().is_ok() {}
while op_rx.try_recv().is_ok() {}
@@ -2033,7 +2033,7 @@ async fn server_overloaded_error_does_not_switch_models() {
while let Ok(event) = rx.try_recv() {
if let AppEvent::UpdateModel(model) = event {
assert_eq!(
model, "gpt-5.2-codex",
model, "gpt-5.3-codex",
"did not expect model switch on server-overloaded error"
);
}
@@ -2051,12 +2051,12 @@ async fn server_overloaded_error_does_not_switch_models() {
#[tokio::test]
async fn model_reasoning_selection_popup_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
set_chatgpt_auth(&mut chat);
chat.set_reasoning_effort(Some(ReasoningEffortConfig::High));
let preset = get_available_model(&chat, "gpt-5.1-codex-max");
let preset = get_available_model(&chat, "gpt-5.4");
chat.open_reasoning_popup(preset);
let popup = render_bottom_popup(&chat, /*width*/ 80);
@@ -2065,12 +2065,12 @@ async fn model_reasoning_selection_popup_snapshot() {
#[tokio::test]
async fn model_reasoning_selection_popup_extra_high_warning_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.2")).await;
set_chatgpt_auth(&mut chat);
chat.set_reasoning_effort(Some(ReasoningEffortConfig::XHigh));
let preset = get_available_model(&chat, "gpt-5.1-codex-max");
let preset = get_available_model(&chat, "gpt-5.2");
chat.open_reasoning_popup(preset);
let popup = render_bottom_popup(&chat, /*width*/ 80);
@@ -2079,11 +2079,11 @@ async fn model_reasoning_selection_popup_extra_high_warning_snapshot() {
#[tokio::test]
async fn reasoning_popup_shows_extra_high_with_space() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
set_chatgpt_auth(&mut chat);
let preset = get_available_model(&chat, "gpt-5.1-codex-max");
let preset = get_available_model(&chat, "gpt-5.4");
chat.open_reasoning_popup(preset);
let popup = render_bottom_popup(&chat, /*width*/ 120);
@@ -2193,11 +2193,11 @@ async fn feedback_good_result_consent_popup_includes_connectivity_diagnostics_fi
#[tokio::test]
async fn reasoning_popup_escape_returns_to_model_popup() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
chat.thread_id = Some(ThreadId::new());
chat.open_model_popup();
let preset = get_available_model(&chat, "gpt-5.1-codex-max");
let preset = get_available_model(&chat, "gpt-5.4");
chat.open_reasoning_popup(preset);
let before_escape = render_bottom_popup(&chat, /*width*/ 80);
@@ -299,7 +299,7 @@ async fn queued_bang_shell_waits_for_user_shell_completion_before_next_input() {
}
async fn assert_cancelled_queued_menu_drains_next_input(command: &str, expected_popup_text: &str) {
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5-codex")).await;
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5.2")).await;
chat.thread_id = Some(ThreadId::new());
chat.handle_codex_event(Event {
id: "turn-start".into(),
@@ -351,7 +351,7 @@ async fn queued_slash_menu_cancel_drains_next_input() {
#[tokio::test]
async fn queued_slash_menu_selection_drains_next_input() {
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5-codex")).await;
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5.2")).await;
chat.thread_id = Some(ThreadId::new());
chat.handle_codex_event(Event {
id: "turn-start".into(),
@@ -78,7 +78,7 @@ async fn status_command_renders_immediately_without_rate_limit_refresh() {
#[tokio::test]
async fn status_command_uses_catalog_default_reasoning_when_config_empty() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
chat.config.model_reasoning_effort = None;
chat.dispatch_command(SlashCommand::Status);
@@ -90,7 +90,7 @@ async fn status_command_uses_catalog_default_reasoning_when_config_empty() {
other => panic!("expected status output, got {other:?}"),
};
assert!(
rendered.contains("gpt-5.1-codex-max (reasoning medium, summaries auto)"),
rendered.contains("gpt-5.4 (reasoning medium, summaries auto)"),
"expected /status to render the catalog default reasoning effort, got: {rendered}"
);
}
@@ -1,11 +0,0 @@
---
source: tui/src/app.rs
expression: model_migration_copy_to_plain_text(&copy)
---
**Codex just got an upgrade. Introducing gpt-5.3-codex.**
Codex is now powered by gpt-5.3-codex, our most capable agentic coding model yet. It's built for long-running, project-scale work, with mid-turn steering + frequent progress updates so you can collaborate while it runs (and it's faster too).
Learn more: https://openai.com/index/introducing-gpt-5-3-codex/
You can keep using gpt-5.1-codex if you prefer.