mirror of
https://github.com/openai/codex.git
synced 2026-11-03 08:22:30 +00:00
Use model metadata for Fast Mode status (#16949)
Fast Mode status was still tied to one model name in the TUI and model-list plumbing. This changes the model metadata shape so a model can advertise additional speed tiers, carries that field through the app-server model list, and uses it to decide when to show Fast Mode status. For people using Codex, the behavior is intended to stay the same for existing models. Fast Mode still requires the existing signed-in / feature-gated path; the difference is that the UI can now recognize any model the model list marks as Fast-capable, instead of requiring a new client-side slug check.
This commit is contained in:
1 parent
600c3e49e0
commit
80ebc80be5
29 files changed
+172
-8
No files matched your search
@@ -6220,6 +6220,7 @@ mod tests {
|
||||
use crate::chatwidget::create_initial_user_message;
|
||||
use crate::chatwidget::tests::make_chatwidget_manual_with_sender;
|
||||
use crate::chatwidget::tests::set_chatgpt_auth;
|
||||
use crate::chatwidget::tests::set_fast_mode_test_catalog;
|
||||
use crate::file_search::FileSearchManager;
|
||||
use crate::history_cell::AgentMessageCell;
|
||||
use crate::history_cell::HistoryCell;
|
||||
@@ -9042,15 +9043,17 @@ guardian_approval = true
|
||||
target_os = "windows",
|
||||
ignore = "snapshot path rendering differs on Windows"
|
||||
)]
|
||||
async fn clear_ui_header_shows_fast_status_only_for_gpt54() {
|
||||
async fn clear_ui_header_shows_fast_status_for_fast_capable_models() {
|
||||
let mut app = make_test_app().await;
|
||||
app.config.cwd = PathBuf::from("/tmp/project").abs();
|
||||
app.chat_widget.set_model("gpt-5.4");
|
||||
set_fast_mode_test_catalog(&mut app.chat_widget);
|
||||
app.chat_widget
|
||||
.set_reasoning_effort(Some(ReasoningEffortConfig::XHigh));
|
||||
app.chat_widget
|
||||
.set_service_tier(Some(codex_protocol::config_types::ServiceTier::Fast));
|
||||
set_chatgpt_auth(&mut app.chat_widget);
|
||||
set_fast_mode_test_catalog(&mut app.chat_widget);
|
||||
|
||||
let rendered = app
|
||||
.clear_ui_header_lines_with_version(/*width*/ 80, "<VERSION>")
|
||||
@@ -9064,7 +9067,7 @@ guardian_approval = true
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
assert_snapshot!("clear_ui_header_fast_status_gpt54_only", rendered);
|
||||
assert_snapshot!("clear_ui_header_fast_status_fast_capable_models", rendered);
|
||||
}
|
||||
|
||||
async fn make_test_app() -> App {
|
||||
|
||||
@@ -821,6 +821,7 @@ fn model_preset_from_api_model(model: ApiModel) -> ModelPreset {
|
||||
})
|
||||
.collect(),
|
||||
supports_personality: model.supports_personality,
|
||||
additional_speed_tiers: model.additional_speed_tiers,
|
||||
is_default: model.is_default,
|
||||
upgrade,
|
||||
show_in_picker: !model.hidden,
|
||||
|
||||
@@ -387,7 +387,6 @@ use unicode_segmentation::UnicodeSegmentation;
|
||||
const USER_SHELL_COMMAND_HELP_TITLE: &str = "Prefix a command with ! to run it locally";
|
||||
const USER_SHELL_COMMAND_HELP_HINT: &str = "Example: !ls";
|
||||
const DEFAULT_OPENAI_BASE_URL: &str = "https://api.openai.com/v1";
|
||||
const FAST_STATUS_MODEL: &str = "gpt-5.4";
|
||||
const DEFAULT_STATUS_LINE_ITEMS: [&str; 3] =
|
||||
["model-with-reasoning", "context-remaining", "current-dir"];
|
||||
// Track information about an in-flight exec command.
|
||||
@@ -9491,7 +9490,7 @@ impl ChatWidget {
|
||||
model: &str,
|
||||
service_tier: Option<ServiceTier>,
|
||||
) -> bool {
|
||||
model == FAST_STATUS_MODEL
|
||||
self.model_supports_fast_mode(model)
|
||||
&& matches!(service_tier, Some(ServiceTier::Fast))
|
||||
&& self.has_chatgpt_account
|
||||
}
|
||||
@@ -9608,6 +9607,19 @@ impl ChatWidget {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn model_supports_fast_mode(&self, model: &str) -> bool {
|
||||
self.model_catalog
|
||||
.try_list_models()
|
||||
.ok()
|
||||
.and_then(|models| {
|
||||
models
|
||||
.into_iter()
|
||||
.find(|preset| preset.model == model)
|
||||
.map(|preset| preset.supports_fast_mode())
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Return whether the effective model currently advertises image-input support.
|
||||
///
|
||||
/// We intentionally default to `true` when model metadata cannot be read so transient catalog
|
||||
|
||||
@@ -117,7 +117,9 @@ pub(super) use codex_protocol::models::FileSystemPermissions;
|
||||
pub(super) use codex_protocol::models::MessagePhase;
|
||||
pub(super) use codex_protocol::models::NetworkPermissions;
|
||||
pub(super) use codex_protocol::models::PermissionProfile;
|
||||
pub(super) use codex_protocol::openai_models::ModelInfo;
|
||||
pub(super) use codex_protocol::openai_models::ModelPreset;
|
||||
pub(super) use codex_protocol::openai_models::ModelsResponse;
|
||||
pub(super) use codex_protocol::openai_models::ReasoningEffortPreset;
|
||||
pub(super) use codex_protocol::openai_models::default_input_modalities;
|
||||
pub(super) use codex_protocol::parse_command::ParsedCommand;
|
||||
@@ -197,6 +199,7 @@ pub(super) use crossterm::event::KeyCode;
|
||||
pub(super) use crossterm::event::KeyEvent;
|
||||
pub(super) use crossterm::event::KeyModifiers;
|
||||
pub(super) use insta::assert_snapshot;
|
||||
pub(super) use serde_json::json;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(super) use serial_test::serial;
|
||||
pub(super) use std::collections::BTreeMap;
|
||||
@@ -262,4 +265,5 @@ mod status_command_tests;
|
||||
|
||||
pub(crate) use helpers::make_chatwidget_manual_with_sender;
|
||||
pub(crate) use helpers::set_chatgpt_auth;
|
||||
pub(crate) use helpers::set_fast_mode_test_catalog;
|
||||
pub(super) use helpers::*;
|
||||
@@ -345,6 +345,69 @@ pub(crate) fn set_chatgpt_auth(chat: &mut ChatWidget) {
|
||||
chat.model_catalog = test_model_catalog(&chat.config);
|
||||
}
|
||||
|
||||
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]
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
serde_json::from_value(json!({
|
||||
"slug": slug,
|
||||
"display_name": slug,
|
||||
"description": format!("{slug} description"),
|
||||
"default_reasoning_level": "medium",
|
||||
"supported_reasoning_levels": [{"effort": "medium", "description": "medium"}],
|
||||
"shell_type": "shell_command",
|
||||
"visibility": "list",
|
||||
"supported_in_api": true,
|
||||
"priority": priority,
|
||||
"additional_speed_tiers": additional_speed_tiers,
|
||||
"availability_nux": null,
|
||||
"upgrade": null,
|
||||
"base_instructions": "base instructions",
|
||||
"supports_reasoning_summaries": false,
|
||||
"default_reasoning_summary": "none",
|
||||
"support_verbosity": false,
|
||||
"default_verbosity": null,
|
||||
"apply_patch_tool_type": null,
|
||||
"truncation_policy": {"mode": "bytes", "limit": 10_000},
|
||||
"supports_parallel_tool_calls": false,
|
||||
"supports_image_detail_original": false,
|
||||
"context_window": 272_000,
|
||||
"experimental_supported_tools": [],
|
||||
}))
|
||||
.expect("valid model info")
|
||||
}
|
||||
|
||||
pub(crate) fn set_fast_mode_test_catalog(chat: &mut ChatWidget) {
|
||||
let models: Vec<ModelPreset> = ModelsResponse {
|
||||
models: vec![
|
||||
test_model_info(
|
||||
"gpt-5.4", /*priority*/ 0, /*supports_fast_mode*/ true,
|
||||
),
|
||||
test_model_info(
|
||||
"gpt-5.3-codex",
|
||||
/*priority*/ 1,
|
||||
/*supports_fast_mode*/ false,
|
||||
),
|
||||
],
|
||||
}
|
||||
.models
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
|
||||
chat.model_catalog = Arc::new(ModelCatalog::new(
|
||||
models,
|
||||
CollaborationModesConfig {
|
||||
default_mode_request_user_input: chat
|
||||
.config
|
||||
.features
|
||||
.enabled(Feature::DefaultModeRequestUserInput),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
pub(crate) async fn make_chatwidget_manual_with_sender() -> (
|
||||
ChatWidget,
|
||||
AppEventSender,
|
||||
|
||||
@@ -1592,6 +1592,7 @@ async fn model_picker_hides_show_in_picker_false_models_from_cache() {
|
||||
description: "medium".to_string(),
|
||||
}],
|
||||
supports_personality: false,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
is_default: false,
|
||||
upgrade: None,
|
||||
show_in_picker,
|
||||
@@ -1714,6 +1715,7 @@ async fn single_reasoning_option_skips_selection() {
|
||||
default_reasoning_effort: ReasoningEffortConfig::High,
|
||||
supported_reasoning_efforts: single_effort,
|
||||
supports_personality: false,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
is_default: false,
|
||||
upgrade: None,
|
||||
show_in_picker: true,
|
||||
|
||||
@@ -574,20 +574,28 @@ async fn commentary_completion_restores_status_indicator_before_exec_begin() {
|
||||
#[tokio::test]
|
||||
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!(!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!(chat.should_show_fast_status(chat.current_model(), chat.current_service_tier(),));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fast_status_indicator_is_hidden_for_non_gpt54_model() {
|
||||
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));
|
||||
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!(!chat.should_show_fast_status(chat.current_model(), chat.current_service_tier(),));
|
||||
}
|
||||
@@ -595,7 +603,11 @@ async fn fast_status_indicator_is_hidden_for_non_gpt54_model() {
|
||||
#[tokio::test]
|
||||
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());
|
||||
set_chatgpt_auth(&mut chat);
|
||||
set_fast_mode_test_catalog(&mut chat);
|
||||
assert!(get_available_model(&chat, "gpt-5.4").supports_fast_mode());
|
||||
|
||||
assert!(!chat.should_show_fast_status(chat.current_model(), chat.current_service_tier(),));
|
||||
}
|
||||
@@ -946,8 +958,10 @@ async fn status_line_fast_mode_footer_snapshot() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn status_line_model_with_reasoning_includes_fast_for_gpt54_only() {
|
||||
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());
|
||||
chat.config.cwd = test_project_path().abs();
|
||||
chat.config.tui_status_line = Some(vec![
|
||||
"model-with-reasoning".to_string(),
|
||||
@@ -957,6 +971,8 @@ async fn status_line_model_with_reasoning_includes_fast_for_gpt54_only() {
|
||||
chat.set_reasoning_effort(Some(ReasoningEffortConfig::XHigh));
|
||||
chat.set_service_tier(Some(ServiceTier::Fast));
|
||||
set_chatgpt_auth(&mut chat);
|
||||
set_fast_mode_test_catalog(&mut chat);
|
||||
assert!(get_available_model(&chat, "gpt-5.4").supports_fast_mode());
|
||||
chat.refresh_status_line();
|
||||
let test_cwd = test_path_display("/tmp/project");
|
||||
|
||||
@@ -1051,6 +1067,8 @@ async fn status_line_model_with_reasoning_fast_footer_snapshot() {
|
||||
use ratatui::backend::TestBackend;
|
||||
|
||||
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.show_welcome_banner = false;
|
||||
chat.config.cwd = test_project_path().abs();
|
||||
chat.config.tui_status_line = Some(vec![
|
||||
@@ -1061,6 +1079,8 @@ async fn status_line_model_with_reasoning_fast_footer_snapshot() {
|
||||
chat.set_reasoning_effort(Some(ReasoningEffortConfig::XHigh));
|
||||
chat.set_service_tier(Some(ServiceTier::Fast));
|
||||
set_chatgpt_auth(&mut chat);
|
||||
set_fast_mode_test_catalog(&mut chat);
|
||||
assert!(get_available_model(&chat, "gpt-5.4").supports_fast_mode());
|
||||
chat.refresh_status_line();
|
||||
|
||||
let width = 80;
|
||||
|
||||
Reference in new issue
Block a user