codex: fix service tier tui compile

This commit is contained in:
Ahmed Ibrahim
2026-05-04 00:26:38 +03:00
parent a01ae19545
commit d7c0ce8115
11 changed files with 107 additions and 67 deletions
+7 -6
View File
@@ -5,6 +5,7 @@
use super::resize_reflow::trailing_run_start;
use super::*;
use codex_protocol::config_types::SERVICE_TIER_PRIORITY;
const SHUTDOWN_FIRST_EXIT_TIMEOUT: Duration = Duration::from_secs(/*secs*/ 2);
@@ -1252,20 +1253,20 @@ impl App {
AppEvent::PersistServiceTierSelection { service_tier } => {
self.refresh_status_line();
let profile = self.active_profile.as_deref();
self.config.service_tier = service_tier;
self.config.service_tier = service_tier.clone();
let mut edits = ConfigEditsBuilder::new(&self.config.codex_home)
.with_profile(profile)
.set_service_tier(service_tier);
.set_service_tier(service_tier.clone());
if service_tier.is_none() {
self.config.notices.fast_default_opt_out = Some(true);
edits = edits.set_fast_default_opt_out(/*opted_out*/ true);
}
match edits.apply().await {
Ok(()) => {
let status = if matches!(
service_tier,
Some(codex_protocol::config_types::ServiceTier::Fast)
) {
let status = if service_tier
.as_ref()
.is_some_and(|tier| tier.as_ref() == SERVICE_TIER_PRIORITY)
{
"on"
} else {
"off"
+5 -3
View File
@@ -71,6 +71,8 @@ use codex_protocol::ThreadId;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::CollaborationModeMask;
use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::SERVICE_TIER_PRIORITY;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::config_types::Settings;
use codex_protocol::models::FileSystemPermissions;
use codex_protocol::models::NetworkPermissions;
@@ -3703,7 +3705,7 @@ async fn clear_ui_header_shows_fast_status_for_fast_capable_models() {
app.chat_widget
.set_reasoning_effort(Some(ReasoningEffortConfig::XHigh));
app.chat_widget
.set_service_tier(Some(codex_protocol::config_types::ServiceTier::Fast));
.set_service_tier(Some(ServiceTier::from(SERVICE_TIER_PRIORITY)));
set_chatgpt_auth(&mut app.chat_widget);
set_fast_mode_test_catalog(&mut app.chat_widget);
@@ -4323,13 +4325,13 @@ fn active_turn_steer_race_extracts_actual_turn_id_from_mismatch() {
async fn fresh_session_config_uses_current_service_tier() {
let mut app = make_test_app().await;
app.chat_widget
.set_service_tier(Some(codex_protocol::config_types::ServiceTier::Fast));
.set_service_tier(Some(ServiceTier::from(SERVICE_TIER_PRIORITY)));
let config = app.fresh_session_config();
assert_eq!(
config.service_tier,
Some(codex_protocol::config_types::ServiceTier::Fast)
Some(ServiceTier::from(SERVICE_TIER_PRIORITY))
);
}
+1 -1
View File
@@ -604,7 +604,7 @@ impl App {
model.to_string(),
*effort,
*summary,
*service_tier,
service_tier.clone(),
collaboration_mode.clone(),
*personality,
final_output_json_schema.clone(),
+3 -3
View File
@@ -1334,7 +1334,7 @@ async fn thread_session_state_from_thread_start_response(
response.thread.path.clone(),
response.model.clone(),
response.model_provider.clone(),
response.service_tier,
response.service_tier.clone(),
response.approval_policy,
response.approvals_reviewer.to_core(),
permission_profile,
@@ -1366,7 +1366,7 @@ async fn thread_session_state_from_thread_resume_response(
response.thread.path.clone(),
response.model.clone(),
response.model_provider.clone(),
response.service_tier,
response.service_tier.clone(),
response.approval_policy,
response.approvals_reviewer.to_core(),
permission_profile,
@@ -1398,7 +1398,7 @@ async fn thread_session_state_from_thread_fork_response(
response.thread.path.clone(),
response.model.clone(),
response.model_provider.clone(),
response.service_tier,
response.service_tier.clone(),
response.approval_policy,
response.approvals_reviewer.to_core(),
permission_profile,
+16 -11
View File
@@ -150,6 +150,7 @@ use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::CollaborationModeMask;
use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::SERVICE_TIER_PRIORITY;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::config_types::Settings;
#[cfg(target_os = "windows")]
@@ -158,6 +159,7 @@ use codex_protocol::items::AgentMessageContent;
use codex_protocol::items::AgentMessageItem;
use codex_protocol::models::MessagePhase;
use codex_protocol::models::local_image_label_text;
use codex_protocol::openai_models::model_supports_service_tier;
use codex_protocol::parse_command::ParsedCommand;
use codex_protocol::plan_tool::PlanItemArg as UpdatePlanItemArg;
use codex_protocol::plan_tool::StepStatus as UpdatePlanItemStatus;
@@ -2025,7 +2027,7 @@ impl ChatWidget {
self.current_rollout_path = session.rollout_path.clone();
self.current_cwd = Some(session.cwd.to_path_buf());
self.config.cwd = session.cwd.clone();
self.effective_service_tier = session.service_tier;
self.effective_service_tier = session.service_tier.clone();
if let Err(err) = self
.config
.permissions
@@ -2074,7 +2076,7 @@ impl ChatWidget {
if display == SessionConfiguredDisplay::Normal {
let startup_tooltip_override = self.startup_tooltip_override.take();
let show_fast_status =
self.should_show_fast_status(&model_for_header, session.service_tier);
self.should_show_fast_status(&model_for_header, session.service_tier.clone());
let session_info_cell = history_cell::new_session_info(
&self.config,
&model_for_header,
@@ -4816,7 +4818,7 @@ impl ChatWidget {
let active_cell = Some(Self::placeholder_session_header_cell(&config));
let current_cwd = Some(config.cwd.to_path_buf());
let effective_service_tier = config.service_tier;
let effective_service_tier = config.service_tier.clone();
let current_terminal_info = terminal_info();
let runtime_keymap = RuntimeKeymap::from_config(&config.tui_keymap).ok();
let default_keymap = RuntimeKeymap::defaults();
@@ -5795,7 +5797,7 @@ impl ChatWidget {
.personality
.filter(|_| self.config.features.enabled(Feature::Personality))
.filter(|_| self.current_model_supports_personality());
let service_tier = match self.config.service_tier {
let service_tier = match self.config.service_tier.clone() {
Some(service_tier) => Some(Some(service_tier)),
None if self.config.notices.fast_default_opt_out == Some(true) => Some(None),
None => None,
@@ -9203,16 +9205,16 @@ impl ChatWidget {
/// Set Fast mode in the widget's config copy.
pub(crate) fn set_service_tier(&mut self, service_tier: Option<ServiceTier>) {
self.config.service_tier = service_tier;
self.config.service_tier = service_tier.clone();
self.effective_service_tier = service_tier;
}
pub(crate) fn current_service_tier(&self) -> Option<ServiceTier> {
self.effective_service_tier
self.effective_service_tier.clone()
}
pub(crate) fn configured_service_tier(&self) -> Option<ServiceTier> {
self.config.service_tier
self.config.service_tier.clone()
}
pub(crate) fn fast_default_opt_out(&self) -> Option<bool> {
@@ -9259,7 +9261,9 @@ impl ChatWidget {
service_tier: Option<ServiceTier>,
) -> bool {
self.model_supports_fast_mode(model)
&& matches!(service_tier, Some(ServiceTier::Fast))
&& service_tier
.as_ref()
.is_some_and(|tier| tier.as_ref() == SERVICE_TIER_PRIORITY)
&& self.has_chatgpt_account
}
@@ -9302,7 +9306,7 @@ impl ChatWidget {
if service_tier.is_none() {
self.config.notices.fast_default_opt_out = Some(true);
}
self.set_service_tier(service_tier);
self.set_service_tier(service_tier.clone());
self.app_event_tx
.send(AppEvent::CodexOp(AppCommand::override_turn_context(
/*cwd*/ None,
@@ -9313,7 +9317,7 @@ impl ChatWidget {
/*model*/ None,
/*effort*/ None,
/*summary*/ None,
Some(service_tier),
Some(service_tier.clone()),
/*collaboration_mode*/ None,
/*personality*/ None,
)));
@@ -9382,6 +9386,7 @@ impl ChatWidget {
}
fn model_supports_fast_mode(&self, model: &str) -> bool {
let service_tier = ServiceTier::from(SERVICE_TIER_PRIORITY);
self.model_catalog
.try_list_models()
.ok()
@@ -9389,7 +9394,7 @@ impl ChatWidget {
models
.into_iter()
.find(|preset| preset.model == model)
.map(|preset| preset.supports_fast_mode())
.map(|preset| model_supports_service_tier(&preset, &service_tier))
})
.unwrap_or(false)
}
+19 -9
View File
@@ -9,6 +9,8 @@ use super::*;
use crate::app_event::ThreadGoalSetMode;
use crate::bottom_pane::prompt_args::parse_slash_name;
use crate::bottom_pane::slash_commands;
use codex_protocol::config_types::SERVICE_TIER_PRIORITY;
use codex_protocol::config_types::ServiceTier;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum SlashCommandDispatchSource {
@@ -178,10 +180,14 @@ impl ChatWidget {
self.open_model_popup();
}
SlashCommand::Fast => {
let next_tier = if matches!(self.current_service_tier(), Some(ServiceTier::Fast)) {
let next_tier = if self
.current_service_tier()
.as_ref()
.is_some_and(|tier| tier.as_ref() == SERVICE_TIER_PRIORITY)
{
None
} else {
Some(ServiceTier::Fast)
Some(ServiceTier::from(SERVICE_TIER_PRIORITY))
};
self.set_service_tier_selection(next_tier);
}
@@ -556,15 +562,19 @@ impl ChatWidget {
match cmd {
SlashCommand::Fast => {
match trimmed.to_ascii_lowercase().as_str() {
"on" => self.set_service_tier_selection(Some(ServiceTier::Fast)),
"on" => self
.set_service_tier_selection(Some(ServiceTier::from(SERVICE_TIER_PRIORITY))),
"off" => self.set_service_tier_selection(/*service_tier*/ None),
"status" => {
let status =
if matches!(self.current_service_tier(), Some(ServiceTier::Fast)) {
"on"
} else {
"off"
};
let status = if self
.current_service_tier()
.as_ref()
.is_some_and(|tier| tier.as_ref() == SERVICE_TIER_PRIORITY)
{
"on"
} else {
"off"
};
self.add_info_message(
format!("Fast mode is {status}."),
/*hint*/ None,
@@ -6,6 +6,7 @@
use super::*;
use crate::bottom_pane::status_line_from_segments;
use crate::status::format_tokens_compact;
use codex_protocol::config_types::SERVICE_TIER_PRIORITY;
/// Items shown in the terminal title when the user has not configured a
/// custom selection. Intentionally minimal: activity indicator + project name.
@@ -558,7 +559,11 @@ impl ChatWidget {
)),
StatusLineItem::SessionId => self.thread_id.map(|id| id.to_string()),
StatusLineItem::FastMode => Some(
if matches!(self.current_service_tier(), Some(ServiceTier::Fast)) {
if self
.current_service_tier()
.as_ref()
.is_some_and(|tier| tier.as_ref() == SERVICE_TIER_PRIORITY)
{
"Fast on".to_string()
} else {
"Fast off".to_string()
+1
View File
@@ -148,6 +148,7 @@ pub(super) use codex_protocol::approvals::GuardianUserAuthorization;
pub(super) use codex_protocol::config_types::CollaborationMode;
pub(super) use codex_protocol::config_types::ModeKind;
pub(super) use codex_protocol::config_types::Personality;
pub(super) use codex_protocol::config_types::SERVICE_TIER_PRIORITY;
pub(super) use codex_protocol::config_types::ServiceTier;
pub(super) use codex_protocol::config_types::Settings;
pub(super) use codex_protocol::models::FileSystemPermissions;
+18 -4
View File
@@ -1,5 +1,8 @@
use super::*;
use codex_app_server_protocol::PluginAvailability;
use codex_protocol::config_types::SERVICE_TIER_PRIORITY;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::openai_models::model_supports_service_tier;
use pretty_assertions::assert_eq;
pub(super) async fn test_config() -> Config {
@@ -182,7 +185,7 @@ pub(super) async fn make_chatwidget_manual(
};
let current_collaboration_mode = base_mode;
let active_collaboration_mask = collaboration_modes::default_mask(model_catalog.as_ref());
let effective_service_tier = cfg.service_tier;
let effective_service_tier = cfg.service_tier.clone();
let mut widget = ChatWidget {
app_event_tx,
codex_op_target: super::CodexOpTarget::Direct(op_tx),
@@ -381,8 +384,12 @@ pub(crate) fn set_chatgpt_auth(chat: &mut ChatWidget) {
}
fn test_model_info(slug: &str, priority: i32, supports_fast_mode: bool) -> ModelInfo {
let additional_speed_tiers = if supports_fast_mode {
vec![codex_protocol::openai_models::SPEED_TIER_FAST]
let service_tiers = if supports_fast_mode {
vec![json!({
"id": SERVICE_TIER_PRIORITY,
"name": "Fast",
"description": "Run this model with faster responses.",
})]
} else {
Vec::new()
};
@@ -396,7 +403,7 @@ fn test_model_info(slug: &str, priority: i32, supports_fast_mode: bool) -> Model
"visibility": "list",
"supported_in_api": true,
"priority": priority,
"additional_speed_tiers": additional_speed_tiers,
"service_tiers": service_tiers,
"availability_nux": null,
"upgrade": null,
"base_instructions": "base instructions",
@@ -1250,6 +1257,13 @@ pub(super) fn get_available_model(chat: &ChatWidget, model: &str) -> ModelPreset
.unwrap_or_else(|| panic!("{model} preset not found"))
}
pub(super) fn model_supports_fast_mode(chat: &ChatWidget, model: &str) -> bool {
model_supports_service_tier(
&get_available_model(chat, model),
&ServiceTier::from(SERVICE_TIER_PRIORITY),
)
}
pub(super) async fn assert_shift_left_edits_most_recent_queued_message_for_terminal(
terminal_info: TerminalInfo,
) {
@@ -1697,13 +1697,14 @@ async fn fast_slash_command_updates_and_persists_local_service_tier() {
chat.dispatch_command(SlashCommand::Fast);
let events = std::iter::from_fn(|| rx.try_recv().ok()).collect::<Vec<_>>();
let expected_service_tier = ServiceTier::from(SERVICE_TIER_PRIORITY);
assert!(
events.iter().any(|event| matches!(
event,
AppEvent::CodexOp(Op::OverrideTurnContext {
service_tier: Some(Some(ServiceTier::Fast)),
service_tier: Some(Some(service_tier)),
..
})
}) if service_tier == &expected_service_tier
)),
"expected fast-mode override app event; events: {events:?}"
);
@@ -1711,8 +1712,8 @@ async fn fast_slash_command_updates_and_persists_local_service_tier() {
events.iter().any(|event| matches!(
event,
AppEvent::PersistServiceTierSelection {
service_tier: Some(ServiceTier::Fast),
}
service_tier: Some(service_tier),
} if service_tier == &expected_service_tier
)),
"expected fast-mode persistence app event; events: {events:?}"
);
@@ -1737,9 +1738,9 @@ async fn user_turn_carries_service_tier_after_fast_toggle() {
match next_submit_op(&mut op_rx) {
Op::UserTurn {
service_tier: Some(Some(ServiceTier::Fast)),
service_tier: Some(Some(service_tier)),
..
} => {}
} if service_tier == ServiceTier::from(SERVICE_TIER_PRIORITY) => {}
other => panic!("expected Op::UserTurn with fast service tier, got {other:?}"),
}
}
@@ -1758,13 +1759,14 @@ async fn queued_fast_slash_applies_before_next_queued_message() {
complete_turn_with_message(&mut chat, "turn-1", Some("done"));
let events = std::iter::from_fn(|| rx.try_recv().ok()).collect::<Vec<_>>();
let expected_service_tier = ServiceTier::from(SERVICE_TIER_PRIORITY);
assert!(
events.iter().any(|event| matches!(
event,
AppEvent::CodexOp(Op::OverrideTurnContext {
service_tier: Some(Some(ServiceTier::Fast)),
service_tier: Some(Some(service_tier)),
..
})
}) if service_tier == &expected_service_tier
)),
"expected queued /fast to update service tier before next turn; events: {events:?}"
);
@@ -1772,9 +1774,9 @@ async fn queued_fast_slash_applies_before_next_queued_message() {
match next_submit_op(&mut op_rx) {
Op::UserTurn {
items,
service_tier: Some(Some(ServiceTier::Fast)),
service_tier: Some(Some(service_tier)),
..
} => assert_eq!(
} if service_tier == expected_service_tier => assert_eq!(
items,
vec![UserInput::Text {
text: "hello after fast".to_string(),
@@ -1016,14 +1016,14 @@ async fn commentary_completion_restores_status_indicator_before_exec_begin() {
async fn fast_status_indicator_requires_chatgpt_auth() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
set_fast_mode_test_catalog(&mut chat);
assert!(get_available_model(&chat, "gpt-5.4").supports_fast_mode());
chat.set_service_tier(Some(ServiceTier::Fast));
assert!(model_supports_fast_mode(&chat, "gpt-5.4"));
chat.set_service_tier(Some(ServiceTier::from(SERVICE_TIER_PRIORITY)));
assert!(!chat.should_show_fast_status(chat.current_model(), chat.current_service_tier(),));
set_chatgpt_auth(&mut chat);
set_fast_mode_test_catalog(&mut chat);
assert!(get_available_model(&chat, "gpt-5.4").supports_fast_mode());
assert!(model_supports_fast_mode(&chat, "gpt-5.4"));
assert!(chat.should_show_fast_status(chat.current_model(), chat.current_service_tier(),));
}
@@ -1032,11 +1032,11 @@ async fn fast_status_indicator_requires_chatgpt_auth() {
async fn fast_status_indicator_is_hidden_for_models_without_fast_support() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await;
set_fast_mode_test_catalog(&mut chat);
assert!(!get_available_model(&chat, "gpt-5.3-codex").supports_fast_mode());
chat.set_service_tier(Some(ServiceTier::Fast));
assert!(!model_supports_fast_mode(&chat, "gpt-5.3-codex"));
chat.set_service_tier(Some(ServiceTier::from(SERVICE_TIER_PRIORITY)));
set_chatgpt_auth(&mut chat);
set_fast_mode_test_catalog(&mut chat);
assert!(!get_available_model(&chat, "gpt-5.3-codex").supports_fast_mode());
assert!(!model_supports_fast_mode(&chat, "gpt-5.3-codex"));
assert!(!chat.should_show_fast_status(chat.current_model(), chat.current_service_tier(),));
}
@@ -1045,10 +1045,10 @@ async fn fast_status_indicator_is_hidden_for_models_without_fast_support() {
async fn fast_status_indicator_is_hidden_when_fast_mode_is_off() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
set_fast_mode_test_catalog(&mut chat);
assert!(get_available_model(&chat, "gpt-5.4").supports_fast_mode());
assert!(model_supports_fast_mode(&chat, "gpt-5.4"));
set_chatgpt_auth(&mut chat);
set_fast_mode_test_catalog(&mut chat);
assert!(get_available_model(&chat, "gpt-5.4").supports_fast_mode());
assert!(model_supports_fast_mode(&chat, "gpt-5.4"));
assert!(!chat.should_show_fast_status(chat.current_model(), chat.current_service_tier(),));
}
@@ -1365,7 +1365,7 @@ async fn status_line_fast_mode_renders_on_and_off() {
chat.refresh_status_line();
assert_eq!(status_line_text(&chat), Some("Fast off".to_string()));
chat.set_service_tier(Some(ServiceTier::Fast));
chat.set_service_tier(Some(ServiceTier::from(SERVICE_TIER_PRIORITY)));
chat.refresh_status_line();
assert_eq!(status_line_text(&chat), Some("Fast on".to_string()));
}
@@ -1378,7 +1378,7 @@ async fn status_line_fast_mode_footer_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.show_welcome_banner = false;
chat.config.tui_status_line = Some(vec!["fast-mode".to_string()]);
chat.set_service_tier(Some(ServiceTier::Fast));
chat.set_service_tier(Some(ServiceTier::from(SERVICE_TIER_PRIORITY)));
chat.refresh_status_line();
let width = 80;
@@ -1397,7 +1397,7 @@ async fn status_line_fast_mode_footer_snapshot() {
async fn status_line_model_with_reasoning_includes_fast_for_fast_capable_models() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
set_fast_mode_test_catalog(&mut chat);
assert!(get_available_model(&chat, "gpt-5.4").supports_fast_mode());
assert!(model_supports_fast_mode(&chat, "gpt-5.4"));
chat.config.cwd = test_project_path().abs();
chat.config.tui_status_line = Some(vec![
"model-with-reasoning".to_string(),
@@ -1405,10 +1405,10 @@ async fn status_line_model_with_reasoning_includes_fast_for_fast_capable_models(
"current-dir".to_string(),
]);
chat.set_reasoning_effort(Some(ReasoningEffortConfig::XHigh));
chat.set_service_tier(Some(ServiceTier::Fast));
chat.set_service_tier(Some(ServiceTier::from(SERVICE_TIER_PRIORITY)));
set_chatgpt_auth(&mut chat);
set_fast_mode_test_catalog(&mut chat);
assert!(get_available_model(&chat, "gpt-5.4").supports_fast_mode());
assert!(model_supports_fast_mode(&chat, "gpt-5.4"));
chat.refresh_status_line();
let test_cwd = test_path_display("/tmp/project");
@@ -1544,7 +1544,7 @@ async fn status_line_model_with_reasoning_fast_footer_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
set_fast_mode_test_catalog(&mut chat);
assert!(get_available_model(&chat, "gpt-5.4").supports_fast_mode());
assert!(model_supports_fast_mode(&chat, "gpt-5.4"));
chat.show_welcome_banner = false;
chat.config.cwd = test_project_path().abs();
chat.config.tui_status_line = Some(vec![
@@ -1553,10 +1553,10 @@ async fn status_line_model_with_reasoning_fast_footer_snapshot() {
"current-dir".to_string(),
]);
chat.set_reasoning_effort(Some(ReasoningEffortConfig::XHigh));
chat.set_service_tier(Some(ServiceTier::Fast));
chat.set_service_tier(Some(ServiceTier::from(SERVICE_TIER_PRIORITY)));
set_chatgpt_auth(&mut chat);
set_fast_mode_test_catalog(&mut chat);
assert!(get_available_model(&chat, "gpt-5.4").supports_fast_mode());
assert!(model_supports_fast_mode(&chat, "gpt-5.4"));
chat.refresh_status_line();
let width = 80;
@@ -1578,7 +1578,7 @@ async fn status_line_model_with_reasoning_context_remaining_footer_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
set_fast_mode_test_catalog(&mut chat);
assert!(get_available_model(&chat, "gpt-5.4").supports_fast_mode());
assert!(model_supports_fast_mode(&chat, "gpt-5.4"));
chat.show_welcome_banner = false;
chat.config.cwd = test_project_path().abs();
chat.config.tui_status_line = Some(vec![
@@ -1587,10 +1587,10 @@ async fn status_line_model_with_reasoning_context_remaining_footer_snapshot() {
"current-dir".to_string(),
]);
chat.set_reasoning_effort(Some(ReasoningEffortConfig::XHigh));
chat.set_service_tier(Some(ServiceTier::Fast));
chat.set_service_tier(Some(ServiceTier::from(SERVICE_TIER_PRIORITY)));
set_chatgpt_auth(&mut chat);
set_fast_mode_test_catalog(&mut chat);
assert!(get_available_model(&chat, "gpt-5.4").supports_fast_mode());
assert!(model_supports_fast_mode(&chat, "gpt-5.4"));
chat.refresh_status_line();
let width = 80;