Use catalog model display names throughout the TUI (#46503)

## Why

Model pickers and session details show raw model IDs even when the catalog provides a display name.

## What changed

- Use catalog display names in model and reasoning pickers, session headers, status displays, and terminal titles, retaining fallback labels for models absent from the catalog.
- Keep model IDs for selection and persistence, and preserve picker highlights by ID when display names change or are shared by multiple models.
- Remove the legacy-model instruction from the full model picker.

## Testing

Add regression tests and snapshots for custom display names, startup and resumed session headers, fallback labels, terminal titles, and picker refreshes. Verify that selecting a display name still persists the original model ID.

GitOrigin-RevId: 101fe82b20f7a8a90ceeff7999bd07ad42ca46db
This commit is contained in:
Eric Traut
2026-09-18 23:15:39 +00:00
committed by copyberry
parent f5b941c910
commit 547c9a1aad
95 changed files with 412 additions and 161 deletions
+2 -2
View File
@@ -116,14 +116,14 @@ async fn daemon_startup(command: &str) -> Result<()> {
};
let expected = if command == "start" {
// The draft header is visible before the session's command composer is ready.
steps.push_back(("gpt-5.6-terra", b"/status\r"));
steps.push_back(("GPT-5.6-Terra", b"/status\r"));
"app-server-control.sock"
} else if bedrock_onboarding {
"UseAmazonBedrock"
} else {
args.extend([command.into(), "--strict-config".into()]);
steps.push_back(("Nosessionsyet", b"\x1b"));
steps.push_back(("gpt-5.6-terra", b"\x14"));
steps.push_back(("GPT-5.6-Terra", b"\x14"));
"Runningwithoutthesharedbackgroundserver:--strict-config"
};
let spawned = codex_utils_pty::spawn_pty_process(
+1 -1
View File
@@ -247,7 +247,7 @@ impl App {
version: &'static str,
) -> Vec<Line<'static>> {
history_cell::SessionHeaderHistoryCell::new(
self.chat_widget.current_model().to_string(),
self.chat_widget.model_display_name().to_string(),
self.chat_widget.current_reasoning_effort(),
self.chat_widget.should_show_fast_status(
self.chat_widget.current_model(),
+2
View File
@@ -5780,6 +5780,7 @@ async fn render_clear_ui_header_after_long_transcript_for_snapshot() -> String {
app.chat_widget.config_ref(),
&app.local_settings,
app.chat_widget.current_model(),
&session.model,
&session,
is_first,
/*tooltip_override*/ None,
@@ -7346,6 +7347,7 @@ async fn backtrack_selection_preserves_selected_prompt_and_requests_branch() {
app.chat_widget.config_ref(),
&app.local_settings,
app.chat_widget.current_model(),
&session.model,
&session,
is_first,
/*tooltip_override*/ None,
@@ -93,7 +93,7 @@ async fn review_regression_agents_overview_creation_is_fresh_but_returning_is_no
let before_footer = |output: &str| {
output
.lines()
.take_while(|line| !line.contains("gpt-6-astra"))
.take_while(|line| !line.contains("GPT-6-Astra"))
.collect::<Vec<_>>()
.join("\n")
};
@@ -2625,6 +2625,7 @@ async fn underfilled_scrollback_fetches_older_pages_without_opening_the_transcri
&app.config,
&app.local_settings,
started.session.model.as_str(),
started.session.model.as_str(),
&started.session,
/*is_first_event*/ false,
Some("This is a test announcement".to_string()),
@@ -8,4 +8,4 @@ expression: "normalize_snapshot_paths(render_bottom_popup(&app.chat_widget, 80))
› keep draft
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -6,4 +6,4 @@ expression: "buffer_text(crate::custom_terminal::test_support::last_rendered_buf
› EDGE-DRAFT-MUST-SURVIVE
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -371,7 +371,7 @@ async fn fresh_startup_reads_destination_and_cleared_model_uses_catalog() -> Res
insta::assert_snapshot!(rendered, @r"
› Ask Codex to do anything
gpt-6-astra high · <PROJECT>
GPT-6-Astra high · <PROJECT>
");
}
let expected_cwd = if override_cwd {
@@ -23,6 +23,7 @@ fn insert_splash(app: &mut App, tui: &mut tui::Tui) {
&app.config,
&app.local_settings,
"gpt-test",
&session.model,
&session,
/*is_first_event*/ false,
/*tooltip_override*/ None,
@@ -43,20 +43,23 @@ impl ChatWidget {
"Select Model",
"Other models return when ordinary usage is available again.",
);
self.show_model_selection_view(SelectionViewParams {
view_id: Some(view_id),
header,
footer_hint: Some(self.bottom_pane.standard_popup_hint_line()),
items: vec![SelectionItem {
name,
description,
is_current: true,
actions,
dismiss_on_select: single_supported_effort,
dismiss_parent_on_child_accept: !single_supported_effort,
self.show_model_selection_view(
vec![LUNA_RESERVE_MODEL.to_string()],
SelectionViewParams {
view_id: Some(view_id),
header,
footer_hint: Some(self.bottom_pane.standard_popup_hint_line()),
items: vec![SelectionItem {
name,
description,
is_current: true,
actions,
dismiss_on_select: single_supported_effort,
dismiss_parent_on_child_accept: !single_supported_effort,
..Default::default()
}],
..Default::default()
}],
..Default::default()
});
},
);
}
}
@@ -1,4 +1,5 @@
//! Accept current account-scoped model replies and refresh present pickers without reopening them.
//! Preserve highlighted models by wire slug, independent of their display names.
use super::model_popups::ALL_MODELS_SELECTION_VIEW_ID;
use super::model_popups::MODEL_SELECTION_VIEW_ID;
@@ -54,16 +55,19 @@ impl ChatWidget {
}
}
pub(super) fn show_model_selection_view(&mut self, mut params: SelectionViewParams) {
pub(super) fn show_model_selection_view(
&mut self,
model_ids: Vec<String>,
mut params: SelectionViewParams,
) {
let selected_index = params
.view_id
.and_then(|view_id| self.bottom_pane.selected_index_for_present_view(view_id));
let selected_model = selected_index.and_then(|index| self.model_popup_model_ids.get(index));
params.initial_selected_idx = params
.items
params.initial_selected_idx = model_ids
.iter()
.position(|item| Some(&item.name) == selected_model);
self.model_popup_model_ids = params.items.iter().map(|item| item.name.clone()).collect();
.position(|model| Some(model) == selected_model);
self.model_popup_model_ids = model_ids;
if let Some(view_id) = params.view_id.filter(|_| selected_index.is_some()) {
self.bottom_pane
.replace_selection_view_if_present(view_id, params);
+33 -26
View File
@@ -44,7 +44,9 @@ impl ChatWidget {
let subtitle = subtitle.to_string();
let mut header = ColumnRenderable::new();
header.push(Line::from(title.bold()));
header.push(Line::from(subtitle.dim()));
if !subtitle.is_empty() {
header.push(Line::from(subtitle.dim()));
}
if let Some(warning) = self.model_menu_warning_line() {
header.push(warning);
}
@@ -92,7 +94,7 @@ impl ChatWidget {
let current_label = presets
.iter()
.find(|preset| preset.model.as_str() == current_model)
.map(|preset| preset.model.to_string())
.map(|preset| preset.display_name.clone())
.unwrap_or_else(|| self.model_display_name().to_string());
let (mut auto_presets, other_presets): (Vec<ModelPreset>, Vec<ModelPreset>) = presets
@@ -105,6 +107,10 @@ impl ChatWidget {
}
auto_presets.sort_by_key(|preset| Self::auto_model_order(&preset.model));
let mut model_ids: Vec<String> = auto_presets
.iter()
.map(|preset| preset.model.clone())
.collect();
let mut items: Vec<SelectionItem> = auto_presets
.into_iter()
.map(|preset| {
@@ -137,7 +143,7 @@ impl ChatWidget {
)
};
SelectionItem {
name: model.clone(),
name: preset.display_name.clone(),
description,
is_current: model.as_str() == current_model,
is_default: preset.is_default,
@@ -158,6 +164,7 @@ impl ChatWidget {
.collect();
if !other_presets.is_empty() {
model_ids.push("All models".to_string());
let actions: Vec<SelectionAction> = vec![Box::new(|tx| {
tx.send(AppEvent::OpenAllModelsPopup);
})];
@@ -181,13 +188,16 @@ impl ChatWidget {
"Select Model",
"Pick a quick auto mode or browse all models.",
);
self.show_model_selection_view(SelectionViewParams {
view_id: Some(MODEL_SELECTION_VIEW_ID),
footer_hint: Some(standard_popup_hint_line()),
items,
header,
..Default::default()
});
self.show_model_selection_view(
model_ids,
SelectionViewParams {
view_id: Some(MODEL_SELECTION_VIEW_ID),
footer_hint: Some(standard_popup_hint_line()),
items,
header,
..Default::default()
},
);
}
pub(super) fn is_auto_model(model: &str) -> bool {
@@ -236,6 +246,7 @@ impl ChatWidget {
}
let mut items: Vec<SelectionItem> = Vec::new();
let model_ids = presets.iter().map(|preset| preset.model.clone()).collect();
for preset in presets.into_iter() {
let description =
(!preset.description.is_empty()).then_some(preset.description.to_string());
@@ -255,7 +266,7 @@ impl ChatWidget {
});
})];
items.push(SelectionItem {
name: preset.model.clone(),
name: preset.display_name.clone(),
description,
is_current,
is_default: preset.is_default,
@@ -269,17 +280,17 @@ impl ChatWidget {
});
}
let header = self.model_menu_header(
"Select Model and Effort",
"Access legacy models by running codex -m <model_name> or in your config.toml",
let header = self.model_menu_header("Select Model and Effort", "");
self.show_model_selection_view(
model_ids,
SelectionViewParams {
view_id: Some(view_id),
footer_hint: Some(self.bottom_pane.standard_popup_hint_line()),
items,
header,
..Default::default()
},
);
self.show_model_selection_view(SelectionViewParams {
view_id: Some(view_id),
footer_hint: Some(self.bottom_pane.standard_popup_hint_line()),
items,
header,
..Default::default()
});
}
fn model_selection_actions(
@@ -530,11 +541,7 @@ impl ChatWidget {
.then(|| default_effort.clone());
let model_slug = preset.model.to_string();
let model_label = if model_slug == LUNA_RESERVE_MODEL {
preset.display_name.clone()
} else {
model_slug.clone()
};
let model_label = preset.display_name.clone();
let is_current_model = self.current_model() == preset.model.as_str();
let highlight_choice = if is_current_model {
if in_plan_mode {
@@ -206,6 +206,7 @@ impl ChatWidget {
&self.config,
&self.local_settings,
&model_for_header,
self.model_catalog.display_name(&session.model),
&session,
self.show_welcome_banner,
startup_tooltip_override,
+2 -2
View File
@@ -543,12 +543,12 @@ impl ChatWidget {
self.refresh_model_dependent_surfaces();
}
pub(super) fn model_display_name(&self) -> &str {
pub(crate) fn model_display_name(&self) -> &str {
let model = self.current_model();
if model.is_empty() {
DEFAULT_MODEL_DISPLAY_NAME
} else {
crate::model_catalog::model_display_name(model)
self.model_catalog.display_name(model)
}
}
@@ -2,7 +2,7 @@
source: tui/src/chatwidget/tests/popups_and_settings.rs
expression: popup
---
Select Reasoning Level for gpt-5.5
Select Reasoning Level for GPT-5.5
› 1. More reasoning… Ultra consumes usage limits faster
@@ -17,4 +17,4 @@ expression: normalize_snapshot_paths(term.backend().vt100().screen().contents())
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -17,4 +17,4 @@ expression: normalize_snapshot_paths(term.backend().vt100().screen().contents())
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -11,4 +11,4 @@ expression: normalize_snapshot_paths(term.backend().vt100().screen().contents())
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -2,7 +2,7 @@
source: tui/src/chatwidget/tests/bedrock_catalog_tests.rs
expression: "render_bottom_popup(&chat, 100)"
---
Select Reasoning Level for openai.gpt-6-astra
Select Reasoning Level for GPT-6-Astra
› 1. Low (default) Fast responses with lighter reasoning
2. Medium Balances speed and reasoning depth for everyday tasks
@@ -3,14 +3,12 @@ source: tui/src/chatwidget/tests/bedrock_catalog_tests.rs
expression: "render_bottom_popup(&chat, 100)"
---
Select Model and Effort
Access legacy models by running codex -m <model_name> or in your config.toml
› 1. openai.gpt-5.6-sol (current) Latest frontier agentic coding model.
2. openai.gpt-6-astra Our most capable model for complex, demanding work.
3. openai.gpt-5.6-terra Balanced agentic coding model for everyday work.
4. openai.gpt-5.6-luna Fast and affordable agentic coding model.
5. openai.gpt-5.5 Frontier model for complex coding, research, and real-world
work.
6. openai.gpt-5.4 Strong model for everyday coding.
› 1. GPT-5.6 Sol (current) Latest frontier agentic coding model.
2. GPT-6-Astra Our most capable model for complex, demanding work.
3. GPT-5.6 Terra Balanced agentic coding model for everyday work.
4. GPT-5.6 Luna Fast and affordable agentic coding model.
5. GPT-5.5 Frontier model for complex coding, research, and real-world work.
6. GPT-5.4 Strong model for everyday coding.
Press enter to confirm or esc to go back
@@ -2,7 +2,7 @@
source: tui/src/chatwidget/tests/bedrock_catalog_tests.rs
expression: "render_bottom_popup(&chat, 100)"
---
Select Reasoning Level for global.openai.gpt-6-astra
Select Reasoning Level for GPT-6-Astra (Global)
› 1. Low (default) Fast responses with lighter reasoning
2. Medium Balances speed and reasoning depth for everyday tasks
@@ -3,15 +3,14 @@ source: tui/src/chatwidget/tests/bedrock_catalog_tests.rs
expression: "render_bottom_popup(&chat, 100)"
---
Select Model and Effort
Access legacy models by running codex -m <model_name> or in your config.toml
› 1. global.openai.gpt-5.6-sol (current) Latest frontier agentic coding model.
2. global.openai.gpt-6-astra Our most capable model for complex, demanding work.
3. global.openai.gpt-5.6-terra Balanced agentic coding model for everyday work.
4. global.openai.gpt-5.6-luna Fast and affordable agentic coding model.
5. us.openai.gpt-5.6-sol Latest frontier agentic coding model.
6. us.openai.gpt-6-astra Our most capable model for complex, demanding work.
7. us.openai.gpt-5.6-terra Balanced agentic coding model for everyday work.
8. us.openai.gpt-5.6-luna Fast and affordable agentic coding model.
› 1. GPT-5.6 Sol (Global) (current) Latest frontier agentic coding model.
2. GPT-6-Astra (Global) Our most capable model for complex, demanding work.
3. GPT-5.6 Terra (Global) Balanced agentic coding model for everyday work.
4. GPT-5.6 Luna (Global) Fast and affordable agentic coding model.
5. GPT-5.6 Sol (US cross-region) Latest frontier agentic coding model.
6. GPT-6-Astra (US cross-region) Our most capable model for complex, demanding work.
7. GPT-5.6 Terra (US cross-region) Balanced agentic coding model for everyday work.
8. GPT-5.6 Luna (US cross-region) Fast and affordable agentic coding model.
Press enter to confirm or esc to go back
@@ -0,0 +1,8 @@
---
source: tui/src/chatwidget/tests/app_server.rs
assertion_line: 99
expression: rendered
---
OpenAI Codex (v<VERSION>)
model: GPT-5.6 Luna high
directory: /tmp/thread-settings
@@ -0,0 +1,16 @@
---
source: tui/src/chatwidget/tests/app_server.rs
assertion_line: 99
expression: rendered
---
OpenAI Codex (v<VERSION>)
model: GPT-5.6 Luna high
directory: /tmp/thread-settings
To get started, describe a task or try one of these commands:
/init - create an AGENTS.md file with instructions for Codex
/status - show current session configuration
/permissions - choose what Codex is allowed to do
/model - choose what model and reasoning effort to use
/review - review any changes and find issues
@@ -0,0 +1,8 @@
---
source: tui/src/chatwidget/tests/app_server.rs
assertion_line: 99
expression: rendered
---
OpenAI Codex (v<VERSION>)
model: us.openai.gpt-5.6-luna high
directory: /tmp/thread-settings
@@ -25,4 +25,4 @@ expression: normalize_snapshot_paths(term.backend().vt100().screen().contents())
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -19,4 +19,4 @@ expression: normalize_snapshot_paths(term.backend().vt100().screen().contents())
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -8,4 +8,4 @@ expression: "normalize_compaction_snapshot(render_bottom_popup(&chat, 80))"
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -8,4 +8,4 @@ expression: "normalize_compaction_snapshot(render_bottom_popup(&chat, 40))"
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -10,7 +10,7 @@ activity:
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
history:
<empty>
@@ -20,7 +20,7 @@ activity:
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
history:
• Blocked by hook
└ command blocked by policy
@@ -10,7 +10,7 @@ activity:
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
history:
• Hook stopped
└ continue with more context
@@ -9,10 +9,10 @@ before completion:
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
after completion:
" "
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
@@ -15,4 +15,4 @@ Before account change:
After account change:
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -0,0 +1,9 @@
---
source: tui/src/chatwidget/tests/model_display_name_tests.rs
expression: "render_bottom_popup(&chat, 80)"
---
Select Model and Effort
› 1. GPT-5.6 Luna (current) Custom provider model
Press enter to confirm or esc to go back
@@ -0,0 +1,12 @@
---
source: tui/src/chatwidget/tests/model_display_name_tests.rs
expression: "render_bottom_popup(&chat, 80)"
---
Select Model
Pick a quick auto mode or browse all models.
1. Auto Fast Custom provider model
› 2. All models (current) Choose a specific model and reasoning level
(current: GPT-5.6 Luna)
Press enter to confirm or esc to go back
@@ -0,0 +1,10 @@
---
source: tui/src/chatwidget/tests/model_display_name_tests.rs
expression: "render_bottom_popup(&chat, 80)"
---
Select Reasoning Level for GPT-5.6 Luna
1. Low Quick answers
› 2. High (default) (current) Deeper reasoning
enter to set as default · s for this session · esc to go back
@@ -0,0 +1,9 @@
---
source: tui/src/chatwidget/tests/model_display_name_tests.rs
expression: normalized_backend_snapshot(terminal.backend())
---
" "
" "
"› Ask Codex to do anything "
" "
" GPT-5.6 Luna · GPT-5.6 Luna high "
@@ -4,4 +4,4 @@ expression: "render_bottom_popup(&chat, 100)"
---
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project · ⠋
GPT-5.6-Sol default · /tmp/project · ⠋
@@ -8,4 +8,4 @@ expression: snapshot
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project Goal paused (/goal resume) "
" GPT-5.6-Sol default · /tmp/project Goal paused (/goal resume) "
@@ -8,4 +8,4 @@ expression: snapshot
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project Goal paused (/goal resume) "
" GPT-5.6-Sol default · /tmp/project Goal paused (/goal resume) "
@@ -12,4 +12,4 @@ expression: normalize_snapshot_paths(term.backend().vt100().screen().contents())
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -12,4 +12,4 @@ expression: normalize_snapshot_paths(term.backend().vt100().screen().contents())
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -21,4 +21,4 @@ expression: normalize_snapshot_paths(term.backend().vt100().screen().contents())
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -8,4 +8,4 @@ expression: normalize_snapshot_paths(rendered)
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -9,4 +9,4 @@ expression: normalize_snapshot_paths(rendered)
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -21,4 +21,4 @@ expression: normalize_snapshot_paths(term.backend().vt100().screen().contents())
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -8,4 +8,4 @@ expression: "normalize_snapshot_paths(render_bottom_popup(&chat, 80))"
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -10,4 +10,4 @@ expression: normalize_snapshot_paths(render_bottom_popup(&chat, 80))
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -14,7 +14,7 @@ exec and hook running:
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
history after exec:
• Ran echo done
└ done
@@ -26,7 +26,7 @@ hook running after exec:
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
quiet hook completed:
" "
"• Working (0s • esc to interrupt) "
@@ -34,4 +34,4 @@ quiet hook completed:
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
@@ -8,4 +8,4 @@ expression: normalized_backend_snapshot(terminal.backend())
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
@@ -9,4 +9,4 @@ expression: "normalize_snapshot_paths(render_bottom_popup(&chat, 80))"
› x
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -6,4 +6,4 @@ expression: "render_bottom_popup(&chat, 80)"
› [Image #2] describe $file
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -9,10 +9,10 @@ before interrupt:
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
after interrupt:
" "
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
@@ -10,7 +10,7 @@ expression: "format!(\"80 columns:\\n{}\\n120 columns:\\n{}\", hook_status_frame
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
120 columns:
" "
"• Working (0s • esc to interrupt) · 1 background terminal running · /ps to view · /stop to close "
@@ -19,4 +19,4 @@ expression: "format!(\"80 columns:\\n{}\\n120 columns:\\n{}\", hook_status_frame
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
@@ -8,4 +8,4 @@ expression: "normalize_compaction_snapshot(render_bottom_popup(&chat, 80))"
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -7,5 +7,5 @@ transcript:
footer:
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project · review-fix
GPT-5.6-Sol default · /tmp/project · review-fix
title: review-fix | project
@@ -6,4 +6,4 @@ expression: normalized_backend_snapshot(terminal.backend())
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
@@ -10,7 +10,7 @@ expression: "format!(\"80 columns:\\n{}\\n120 columns:\\n{}\", hook_status_frame
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
120 columns:
" "
"• Working (0s • esc to interrupt) · 1 background terminal running · /ps to view · /stop to close · Running hooks "
@@ -18,4 +18,4 @@ expression: "format!(\"80 columns:\\n{}\\n120 columns:\\n{}\", hook_status_frame
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
@@ -3,7 +3,6 @@ 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. test-visible-model (current) test-visible-model description
@@ -3,10 +3,10 @@ source: tui/src/chatwidget/tests/popups_and_settings.rs
expression: "render_bottom_popup(&chat, 80)"
---
Select Model and Effort
Access legacy models by running codex -m <model_name> or in your config.toml
› 1. gpt-5.6-terra Balanced agentic coding model for everyday work.
2. gpt-5.5 (current) Frontier model for complex coding, research, and real-
world work.
› 1. Renamed model Balanced agentic coding model for everyday
work.
2. Shared display name (current) Frontier model for complex coding,
research, and real-world work.
Press enter to confirm or esc to go back
@@ -5,8 +5,8 @@ expression: "render_bottom_popup(&chat, 80)"
Select Model
Pick a quick auto mode or browse all models.
1. codex-auto-test Auto model
1. GPT-5.5 Auto model
› 2. All models (current) Choose a specific model and reasoning level
(current: gpt-5.5)
(current: GPT-5.5)
Press enter to confirm or esc to go back
@@ -2,7 +2,7 @@
source: tui/src/chatwidget/tests/popups_and_settings.rs
expression: popup
---
Select Reasoning Level for gpt-5.5
Select Reasoning Level for GPT-5.5
1. Low Fast responses with lighter reasoning
2. Medium (default) Balances speed and reasoning depth for everyday tasks
@@ -3,14 +3,13 @@ source: tui/src/chatwidget/tests/popups_and_settings.rs
expression: "render_bottom_popup(&chat, 80)"
---
Select Model and Effort
Access legacy models by running codex -m <model_name> or in your config.toml
1. gpt-6-astra (default) Our most capable model for complex, demanding
1. GPT-6-Astra (default) Our most capable model for complex, demanding
work.
2. gpt-5.6-sol Latest frontier agentic coding model.
3. gpt-5.6-terra Balanced agentic coding model for everyday work.
4. gpt-5.6-luna Fast and affordable agentic coding model.
› 5. gpt-5.5 (current) Frontier model for complex coding, research, and
2. GPT-5.6-Sol Latest frontier agentic coding model.
3. GPT-5.6-Terra Balanced agentic coding model for everyday work.
4. GPT-5.6-Luna Fast and affordable agentic coding model.
› 5. GPT-5.5 (current) Frontier model for complex coding, research, and
real-world work.
Press enter to confirm or esc to go back
@@ -10,7 +10,7 @@ activity:
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
history:
<empty>
@@ -22,7 +22,7 @@ activity:
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
history:
<empty>
@@ -34,7 +34,7 @@ activity:
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
history:
<empty>
@@ -46,6 +46,6 @@ activity:
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
history:
<empty>
@@ -8,4 +8,4 @@ expression: normalized_backend_snapshot(terminal.backend())
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
@@ -9,4 +9,4 @@ expression: normalized_backend_snapshot(terminal.backend())
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
@@ -7,4 +7,4 @@ expression: "normalize_completion_timestamps(& (render_bottom_popup(&chat, 80)))
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -19,4 +19,4 @@ expression: normalize_snapshot_paths(term.backend().vt100().screen().contents())
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -10,7 +10,7 @@ expression: "format!(\"80 columns:\\n{}\\n120 columns:\\n{}\", hook_status_frame
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
120 columns:
" "
"• Working (0s • esc to interrupt) · 1 background terminal running · /ps to view · /stop to close "
@@ -19,4 +19,4 @@ expression: "format!(\"80 columns:\\n{}\\n120 columns:\\n{}\", hook_status_frame
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
@@ -7,7 +7,7 @@ before reveal:
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
running:
" "
"• Saving session summary "
@@ -15,10 +15,10 @@ running:
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
completed:
" "
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
@@ -7,7 +7,7 @@ before reveal:
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
running:
" "
"• Running hook "
@@ -15,10 +15,10 @@ running:
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
completed:
" "
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
@@ -6,4 +6,4 @@ expression: terminal.backend()
" "
"› Ask a follow-up question "
" "
" gpt-5.6-sol Side from main thread · ctrl + / to switch · ctrl + c to close "
" GPT-5.6-Sol Side from main thread · ctrl + / to switch · ctrl + c to close "
@@ -6,4 +6,4 @@ expression: terminal.backend()
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol ctrl + / for side "
" GPT-5.6-Sol ctrl + / for side "
@@ -7,7 +7,7 @@ expression: "whole_status.replace(&directory,\n\"[[workspace]]\").replace(crate:
Visit https://chatgpt.com/codex/settings/usage for up-to-date
information on rate limits and credits
Model: gpt-5.5 (reasoning medium, summaries auto)
Model: GPT-5.5 (reasoning medium, summaries auto)
Model provider: openai
Directory: [[workspace]]
Permissions: Read Only (Ask for approval)
@@ -8,4 +8,4 @@ expression: normalized_backend_snapshot(terminal.backend())
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default Side starting... "
" GPT-5.6-Sol default Side starting... "
@@ -6,4 +6,4 @@ expression: normalized_backend_snapshot(terminal.backend())
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · 5.2 credits · ~$0.21 "
" GPT-5.6-Sol default · 5.2 credits · ~$0.21 "
@@ -6,4 +6,4 @@ expression: normalized_backend_snapshot(terminal.backend())
" "
"› Ask Codex to do anything "
" "
" gpt-5.4 Pursuing goal (40K / 50K) "
" GPT-5.4 Pursuing goal (40K / 50K) "
@@ -6,4 +6,4 @@ expression: normalized_backend_snapshot(terminal.backend())
" "
"› Ask Codex to do anything "
" "
" gpt-5.4 Goal achieved (2d 23h 42m) "
" GPT-5.4 Goal achieved (2d 23h 42m) "
@@ -8,4 +8,4 @@ expression: normalized_backend_snapshot(terminal.backend())
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
@@ -0,0 +1,5 @@
---
source: tui/src/chatwidget/tests/status_and_layout.rs
expression: "chat.last_terminal_title.as_deref().expect(\"terminal title\")"
---
GPT-5.4
@@ -8,4 +8,4 @@ expression: normalized_backend_snapshot(terminal.backend())
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
@@ -8,4 +8,4 @@ expression: normalize_snapshot_paths(rendered)
› Ask Codex to do anything
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
@@ -16,4 +16,4 @@ expression: rendered
" "
"› Ask Codex to do anything "
" "
" gpt-5.6-sol default · /tmp/project "
" GPT-5.6-Sol default · /tmp/project "
@@ -9,4 +9,4 @@ expression: "setup.replace(&chat.config.cwd.display().to_string(), \"[CWD]\")"
› Input disabled until setup completes.
gpt-5.6-sol default · [CWD]
GPT-5.6-Sol default · [CWD]
@@ -5,4 +5,4 @@ expression: popup
---
› /work
gpt-5.6-sol default · /tmp/project
GPT-5.6-Sol default · /tmp/project
+2
View File
@@ -258,6 +258,8 @@ mod luna_reserve_usage_tests;
mod mcp_startup;
#[path = "tests/misalignment_policy_tests.rs"]
mod misalignment_policy;
#[path = "tests/model_display_name_tests.rs"]
mod model_display_name_tests;
#[path = "tests/permission_shortcuts_tests.rs"]
mod permission_shortcuts_tests;
mod permissions;
@@ -68,6 +68,39 @@ fn configured_thread_session(thread_id: ThreadId) -> crate::session_state::Threa
}
}
#[tokio::test]
async fn session_header_uses_catalog_display_name_without_changing_model() {
let slug = "us.openai.gpt-5.6-luna";
for (name, first_event, display_name) in [
("startup", true, Some("GPT-5.6 Luna")),
("resume", false, Some("GPT-5.6 Luna")),
("unknown_model", false, None),
] {
let (mut chat, mut events, _ops) = make_chatwidget_manual(Some(slug)).await;
let mut preset = get_available_model(&chat, "gpt-5.5");
preset.model = slug.to_string();
preset.display_name = display_name.unwrap_or_default().to_string();
chat.model_catalog = Arc::new(ModelCatalog::new(
display_name.map(|_| preset).into_iter().collect(),
));
chat.show_welcome_banner = first_event;
chat.local_settings.tui.show_tooltips = false;
let mut session = configured_thread_session(ThreadId::new());
session.model = slug.to_string();
session.reasoning_effort = Some(ReasoningEffortConfig::High);
chat.handle_thread_session(session);
let rendered = drain_insert_history_with(&mut events, HistoryCell::raw_lines)
.iter()
.map(|lines| lines_to_single_string(lines))
.collect::<String>()
.replace(CODEX_CLI_VERSION, "<VERSION>")
.replace("C:\\tmp\\thread-settings", "/tmp/thread-settings");
assert_chatwidget_snapshot!(format!("catalog_model_session_header_{name}"), rendered);
assert_eq!(chat.current_model(), slug);
}
}
#[tokio::test]
async fn session_and_settings_sync_server_provider_id() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.2")).await;
@@ -0,0 +1,104 @@
//! Catalog display names are presentation only; model selection retains wire slugs.
use super::*;
use pretty_assertions::assert_eq;
#[tokio::test]
async fn custom_model_display_name_in_pickers_preserves_selection_slug() {
let slug = "us.openai.gpt-5.6-luna";
let (mut chat, mut events, _ops) = make_chatwidget_manual(Some(slug)).await;
let mut preset = get_available_model(&chat, "gpt-5.5");
preset.id = slug.to_string();
preset.model = slug.to_string();
preset.display_name = "GPT-5.6 Luna".to_string();
preset.description = "Custom provider model".to_string();
preset.default_reasoning_effort = ReasoningEffortConfig::High;
preset.supported_reasoning_efforts = vec![
ReasoningEffortPreset {
effort: ReasoningEffortConfig::Low,
description: "Quick answers".to_string(),
},
ReasoningEffortPreset {
effort: ReasoningEffortConfig::High,
description: "Deeper reasoning".to_string(),
},
];
let mut auto = preset.clone();
auto.id = "codex-auto-fast".to_string();
auto.model = auto.id.clone();
auto.display_name = "Auto Fast".to_string();
chat.model_catalog = Arc::new(ModelCatalog::new(vec![auto, preset.clone()]));
chat.set_reasoning_effort(Some(ReasoningEffortConfig::High));
chat.open_model_popup_with_presets(chat.model_catalog.models.clone());
assert_chatwidget_snapshot!(
"custom_model_display_name_quick_picker",
render_bottom_popup(&chat, /*width*/ 80)
);
chat.handle_key_event(KeyCode::Enter.into());
assert_matches!(events.try_recv(), Ok(AppEvent::OpenAllModelsPopup));
chat.open_all_models_popup();
assert_chatwidget_snapshot!(
"custom_model_display_name_all_models",
render_bottom_popup(&chat, /*width*/ 80)
);
chat.handle_key_event(KeyCode::Enter.into());
let selected =
assert_matches!(events.try_recv(), Ok(AppEvent::OpenReasoningPopup { model }) => model);
assert_eq!(selected, preset);
chat.open_reasoning_popup(selected);
assert_chatwidget_snapshot!(
"custom_model_display_name_reasoning",
render_bottom_popup(&chat, /*width*/ 80)
);
chat.handle_key_event(KeyCode::Enter.into());
assert_matches!(events.try_recv(), Ok(AppEvent::UpdateModel(model)) if model == slug);
assert_matches!(
events.try_recv(),
Ok(AppEvent::UpdateReasoningEffort(Some(
ReasoningEffortConfig::High
)))
);
let persisted = assert_matches!(events.try_recv(), Ok(AppEvent::PersistModelSelection { model, effort }) => (model, effort));
assert_eq!(
persisted,
(slug.to_string(), Some(ReasoningEffortConfig::High))
);
}
#[tokio::test]
async fn custom_model_display_name_in_status_line_and_fallback() {
use ratatui::Terminal;
use ratatui::backend::TestBackend;
let slug = "us.openai.gpt-5.6-luna";
let (mut chat, _events, _ops) = make_chatwidget_manual(Some(slug)).await;
let mut preset = get_available_model(&chat, "gpt-5.5");
preset.model = slug.to_string();
preset.display_name = "GPT-5.6 Luna".to_string();
preset.show_in_picker = false;
chat.model_catalog = Arc::new(ModelCatalog::new(vec![preset]));
chat.show_welcome_banner = false;
chat.local_settings.tui.status_line = Some(vec![
"model-name".to_string(),
"model-with-reasoning".to_string(),
]);
chat.set_reasoning_effort(Some(ReasoningEffortConfig::High));
chat.refresh_status_line();
let width = 80;
let mut terminal = Terminal::new(TestBackend::new(width, chat.desired_height(width)))
.expect("create terminal");
terminal
.draw(|frame| chat.render(frame.area(), frame.buffer_mut()))
.expect("draw model status line");
assert_chatwidget_snapshot!(
"custom_model_display_name_status_line",
normalized_backend_snapshot(terminal.backend())
);
Arc::make_mut(&mut chat.model_catalog).models.clear();
assert_eq!(chat.model_display_name(), slug);
chat.set_model(crate::model_catalog::LUNA_RESERVE_MODEL);
assert_eq!(chat.model_display_name(), "Luna Reserve");
chat.set_model("");
assert_eq!(chat.model_display_name(), DEFAULT_MODEL_DISPLAY_NAME);
}
@@ -3440,6 +3440,9 @@ async fn model_picker_refresh_preserves_highlight() {
get_available_model(&chat, "gpt-5.5"),
get_available_model(&chat, "gpt-5.6-terra"),
];
for preset in &mut presets {
preset.display_name = "Shared display name".to_string();
}
chat.model_catalog = Arc::new(ModelCatalog::new(presets.clone()));
chat.open_model_popup();
chat.handle_key_event(KeyEvent::from(KeyCode::Down));
@@ -3448,6 +3451,7 @@ async fn model_picker_refresh_preserves_highlight() {
}
let before = render_bottom_popup(&chat, /*width*/ 80);
presets.reverse();
presets[0].display_name = "Renamed model".to_string();
if remove_selected {
presets.remove(/*index*/ 0);
}
@@ -4022,7 +4022,10 @@ async fn terminal_title_model_updates_on_model_change_without_manual_refresh() {
chat.local_settings.tui.terminal_title = Some(vec!["model".to_string()]);
chat.refresh_terminal_title();
assert_eq!(chat.last_terminal_title, Some("gpt-5.4".to_string()));
assert_chatwidget_snapshot!(
"terminal_title_model_display_name",
chat.last_terminal_title.as_deref().expect("terminal title")
);
chat.set_model("gpt-5.2");
@@ -102,7 +102,7 @@ async fn status_command_uses_catalog_default_reasoning_when_config_empty() {
other => panic!("expected status output, got {other:?}"),
};
assert!(
rendered.contains("gpt-5.5 (reasoning medium, summaries auto)"),
rendered.contains("GPT-5.5 (reasoning medium, summaries auto)"),
"expected /status to render the catalog default reasoning effort, got: {rendered}"
);
}
+4 -2
View File
@@ -130,6 +130,7 @@ pub(crate) fn new_session_info(
config: &Config,
local_settings: &crate::local_settings::LocalSettings,
requested_model: &str,
model_display_name: &str,
session: &ThreadSessionState,
is_first_event: bool,
tooltip_override: Option<String>,
@@ -138,7 +139,7 @@ pub(crate) fn new_session_info(
) -> SessionInfoCell {
// Header box rendered as history (so it appears at the very top)
let header = SessionHeaderHistoryCell::new(
session.model.clone(),
model_display_name.to_string(),
session.reasoning_effort.clone(),
show_fast_status,
config.cwd.to_path_buf(),
@@ -227,6 +228,7 @@ pub(crate) fn has_yolo_permissions(
}
)
}
/// Session banner with a model label already resolved for presentation by its caller.
#[derive(Debug)]
pub(crate) struct SessionHeaderHistoryCell {
version: &'static str,
@@ -266,7 +268,7 @@ impl SessionHeaderHistoryCell {
) -> Self {
Self {
version,
model: crate::model_catalog::model_display_name(&model).to_string(),
model,
model_style,
reasoning_effort,
show_fast_status,
+4
View File
@@ -730,6 +730,7 @@ async fn session_info_uses_availability_nux_tooltip_override() {
&config,
&crate::local_settings::LocalSettings::from(&config),
"gpt-5",
"gpt-5",
&session_configured_event("gpt-5"),
/*is_first_event*/ false,
Some("Model just became available".to_string()),
@@ -753,6 +754,7 @@ async fn session_info_availability_nux_tooltip_snapshot() {
&config,
&crate::local_settings::LocalSettings::from(&config),
"gpt-5",
"gpt-5",
&session_configured_event("gpt-5"),
/*is_first_event*/ false,
Some("Model just became available".to_string()),
@@ -771,6 +773,7 @@ async fn session_info_first_event_suppresses_tooltips_and_nux() {
&config,
&crate::local_settings::LocalSettings::from(&config),
"gpt-5",
"gpt-5",
&session_configured_event("gpt-5"),
/*is_first_event*/ true,
Some("Model just became available".to_string()),
@@ -791,6 +794,7 @@ async fn session_info_hides_tooltips_when_disabled() {
&config,
&crate::local_settings::LocalSettings::from(&config),
"gpt-5",
"gpt-5",
&session_configured_event("gpt-5"),
/*is_first_event*/ false,
Some("Model just became available".to_string()),
+8
View File
@@ -37,4 +37,12 @@ impl ModelCatalog {
pub(crate) fn try_list_models(&self) -> Result<Vec<ModelPreset>, Infallible> {
Ok(self.models.clone())
}
pub(crate) fn display_name<'a>(&'a self, model: &'a str) -> &'a str {
self.models
.iter()
.find(|preset| preset.model == model)
.map(|preset| preset.display_name.as_str())
.unwrap_or_else(|| model_display_name(model))
}
}
@@ -4,4 +4,4 @@ expression: "crate::chatwidget::tests::helpers::render_bottom_popup(&chat, 40)"
---
› Ask Codex to do anything
gpt-5.4 default · /tmp/pr… Plan mode
GPT-5.4 default · /tmp/pr… Plan mode