mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Enable model upgrade popup even when selected model is no longer in picker (#8802)
With `config.toml`: ``` model = "gpt-5.1-codex" ``` (where `gpt-5.1-codex` has `show_in_picker: false` in [`model_presets.rs`](https://github.com/openai/codex/blob/main/codex-rs/core/src/models_manager/model_presets.rs); this happens if the user hasn't used codex in a while so they didn't see the popup before their model was changed to `show_in_picker: false`) The upgrade picker used to not show (because `gpt-5.1-codex` was filtered out of the model list in code). Now, the filtering is done downstream in tui and app-server, so the model upgrade popup shows: <img width="1503" height="227" alt="Screenshot 2026-01-06 at 5 04 37 PM" src="https://github.com/user-attachments/assets/26144cc2-0b3f-4674-ac17-e476781ec548" />
This commit is contained in:
1 parent
8b4d27dfcd
commit
3389465c8d
16 files changed
+535
-32
No files matched your search
@@ -2088,17 +2088,20 @@ mod tests {
|
||||
use codex_core::AuthManager;
|
||||
use codex_core::CodexAuth;
|
||||
use codex_core::ConversationManager;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::Event;
|
||||
use codex_core::protocol::EventMsg;
|
||||
use codex_core::protocol::SandboxPolicy;
|
||||
use codex_core::protocol::SessionConfiguredEvent;
|
||||
use codex_protocol::ConversationId;
|
||||
use insta::assert_snapshot;
|
||||
use pretty_assertions::assert_eq;
|
||||
use ratatui::prelude::Line;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use tempfile::tempdir;
|
||||
|
||||
async fn make_test_app() -> App {
|
||||
let (chat_widget, app_event_tx, _rx, _op_rx) = make_chatwidget_manual_with_sender().await;
|
||||
@@ -2208,6 +2211,24 @@ mod tests {
|
||||
codex_core::models_manager::model_presets::all_model_presets().clone()
|
||||
}
|
||||
|
||||
fn model_migration_copy_to_plain_text(
|
||||
copy: &crate::model_migration::ModelMigrationCopy,
|
||||
) -> String {
|
||||
let mut s = String::new();
|
||||
for span in ©.heading {
|
||||
s.push_str(&span.content);
|
||||
}
|
||||
s.push('\n');
|
||||
s.push('\n');
|
||||
for line in ©.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();
|
||||
@@ -2243,6 +2264,59 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[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 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");
|
||||
assert!(
|
||||
should_show_model_migration_prompt(
|
||||
¤t.model,
|
||||
&upgrade.id,
|
||||
&config.notices.model_migrations,
|
||||
&available_models,
|
||||
),
|
||||
"expected migration prompt to be eligible for hidden model"
|
||||
);
|
||||
|
||||
let target = available_models
|
||||
.iter()
|
||||
.find(|preset| preset.model == upgrade.id)
|
||||
.cloned()
|
||||
.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(
|
||||
¤t.model,
|
||||
&upgrade.id,
|
||||
target.display_name,
|
||||
target_description,
|
||||
can_opt_out,
|
||||
);
|
||||
|
||||
assert_snapshot!(
|
||||
"model_migration_prompt_shows_for_hidden_model",
|
||||
model_migration_copy_to_plain_text(©)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn transcript_selection_copy_includes_offscreen_lines() {
|
||||
let mut app = make_test_app().await;
|
||||
|
||||
@@ -2106,7 +2106,7 @@ impl ChatWidget {
|
||||
let models = self.models_manager.try_list_models(&self.config).ok()?;
|
||||
models
|
||||
.iter()
|
||||
.find(|preset| preset.model == NUDGE_MODEL_SLUG)
|
||||
.find(|preset| preset.show_in_picker && preset.model == NUDGE_MODEL_SLUG)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
@@ -2222,6 +2222,14 @@ impl ChatWidget {
|
||||
return;
|
||||
}
|
||||
};
|
||||
self.open_model_popup_with_presets(presets);
|
||||
}
|
||||
|
||||
pub(crate) fn open_model_popup_with_presets(&mut self, presets: Vec<ModelPreset>) {
|
||||
let presets: Vec<ModelPreset> = presets
|
||||
.into_iter()
|
||||
.filter(|preset| preset.show_in_picker)
|
||||
.collect();
|
||||
|
||||
let current_label = presets
|
||||
.iter()
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
---
|
||||
source: tui2/src/chatwidget/tests.rs
|
||||
assertion_line: 1758
|
||||
expression: popup
|
||||
---
|
||||
Select Model and Effort
|
||||
Access legacy models by running codex -m <model_name> or in your config.toml
|
||||
|
||||
› 1. test-visible-model (current) test-visible-model description
|
||||
|
||||
Press enter to select reasoning effort, or esc to dismiss.
|
||||
@@ -1731,6 +1731,41 @@ async fn model_selection_popup_snapshot() {
|
||||
assert_snapshot!("model_selection_popup", popup);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn model_picker_hides_show_in_picker_false_models_from_cache() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("test-visible-model")).await;
|
||||
let preset = |slug: &str, show_in_picker: bool| ModelPreset {
|
||||
id: slug.to_string(),
|
||||
model: slug.to_string(),
|
||||
display_name: slug.to_string(),
|
||||
description: format!("{slug} description"),
|
||||
default_reasoning_effort: ReasoningEffortConfig::Medium,
|
||||
supported_reasoning_efforts: vec![ReasoningEffortPreset {
|
||||
effort: ReasoningEffortConfig::Medium,
|
||||
description: "medium".to_string(),
|
||||
}],
|
||||
is_default: false,
|
||||
upgrade: None,
|
||||
show_in_picker,
|
||||
supported_in_api: true,
|
||||
};
|
||||
|
||||
chat.open_model_popup_with_presets(vec![
|
||||
preset("test-visible-model", true),
|
||||
preset("test-hidden-model", false),
|
||||
]);
|
||||
let popup = render_bottom_popup(&chat, 80);
|
||||
assert_snapshot!("model_picker_filters_hidden_models", popup);
|
||||
assert!(
|
||||
popup.contains("test-visible-model"),
|
||||
"expected visible model to appear in picker:\n{popup}"
|
||||
);
|
||||
assert!(
|
||||
!popup.contains("test-hidden-model"),
|
||||
"expected hidden model to be excluded from picker:\n{popup}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn approvals_selection_popup_snapshot() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
---
|
||||
source: tui2/src/app.rs
|
||||
assertion_line: 2314
|
||||
expression: model_migration_copy_to_plain_text(©)
|
||||
---
|
||||
Try gpt-5.2-codex
|
||||
|
||||
We recommend switching from gpt-5.1-codex to gpt-5.2-codex.
|
||||
|
||||
Latest frontier agentic coding model.
|
||||
|
||||
You can continue using gpt-5.1-codex if you prefer.
|
||||
Reference in new issue
Block a user