From 71504325d3d34a399f5ad958fd13043d6d74062c Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Wed, 3 Dec 2025 12:30:43 -0800 Subject: [PATCH] Migrate model preset (#7542) - Introduce `openai_models` in `/core` - Move `PRESETS` under it - Move `ModelPreset`, `ModelUpgrade`, `ReasoningEffortPreset`, `ReasoningEffortPreset`, and `ReasoningEffortPreset` to `protocol` - Introduce `Op::ListModels` and `EventMsg::AvailableModels` Next steps: - migrate `app-server` and `tui` to use the introduced Operation --- codex-rs/Cargo.lock | 2 - .../app-server-protocol/src/protocol/v1.rs | 2 +- .../app-server-protocol/src/protocol/v2.rs | 2 +- codex-rs/app-server/src/models.rs | 8 +- .../suite/codex_message_processor_flow.rs | 2 +- codex-rs/app-server/tests/suite/config.rs | 2 +- .../app-server/tests/suite/v2/model_list.rs | 2 +- .../app-server/tests/suite/v2/turn_start.rs | 2 +- codex-rs/codex-api/src/common.rs | 2 +- codex-rs/common/Cargo.toml | 2 - codex-rs/common/src/lib.rs | 2 - codex-rs/core/src/client.rs | 2 +- codex-rs/core/src/codex.rs | 26 ++- codex-rs/core/src/config/edit.rs | 4 +- codex-rs/core/src/config/mod.rs | 2 +- codex-rs/core/src/config/profile.rs | 2 +- codex-rs/core/src/lib.rs | 1 + codex-rs/core/src/model_family.rs | 2 +- codex-rs/core/src/openai_models/mod.rs | 1 + .../src/openai_models}/model_presets.rs | 184 +++++++---------- codex-rs/core/src/rollout/policy.rs | 1 + codex-rs/core/src/sandboxing/assessment.rs | 2 +- codex-rs/core/tests/suite/client.rs | 2 +- codex-rs/core/tests/suite/list_models.rs | 187 ++++++++++++++++++ codex-rs/core/tests/suite/mod.rs | 1 + codex-rs/core/tests/suite/model_overrides.rs | 2 +- codex-rs/core/tests/suite/prompt_caching.rs | 2 +- .../src/event_processor_with_human_output.rs | 1 + codex-rs/mcp-server/src/codex_tool_runner.rs | 1 + codex-rs/mcp-server/src/outgoing_message.rs | 2 +- codex-rs/otel/src/otel_event_manager.rs | 2 +- codex-rs/protocol/src/config_types.rs | 29 --- codex-rs/protocol/src/lib.rs | 1 + codex-rs/protocol/src/openai_models.rs | 80 ++++++++ codex-rs/protocol/src/protocol.rs | 7 +- codex-rs/tui/src/app.rs | 10 +- codex-rs/tui/src/app_event.rs | 4 +- codex-rs/tui/src/chatwidget.rs | 13 +- codex-rs/tui/src/chatwidget/tests.rs | 18 +- codex-rs/tui/src/history_cell.rs | 2 +- codex-rs/tui/src/model_migration.rs | 6 +- codex-rs/tui/src/status/tests.rs | 2 +- 42 files changed, 430 insertions(+), 197 deletions(-) create mode 100644 codex-rs/core/src/openai_models/mod.rs rename codex-rs/{common/src => core/src/openai_models}/model_presets.rs (68%) create mode 100644 codex-rs/core/tests/suite/list_models.rs create mode 100644 codex-rs/protocol/src/openai_models.rs diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index a1684bf0f..4429858c9 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1117,12 +1117,10 @@ name = "codex-common" version = "0.0.0" dependencies = [ "clap", - "codex-app-server-protocol", "codex-core", "codex-lmstudio", "codex-ollama", "codex-protocol", - "once_cell", "serde", "toml", ] diff --git a/codex-rs/app-server-protocol/src/protocol/v1.rs b/codex-rs/app-server-protocol/src/protocol/v1.rs index 54f80c9fd..1576eb0d9 100644 --- a/codex-rs/app-server-protocol/src/protocol/v1.rs +++ b/codex-rs/app-server-protocol/src/protocol/v1.rs @@ -3,11 +3,11 @@ use std::path::PathBuf; use codex_protocol::ConversationId; use codex_protocol::config_types::ForcedLoginMethod; -use codex_protocol::config_types::ReasoningEffort; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::config_types::SandboxMode; use codex_protocol::config_types::Verbosity; use codex_protocol::models::ResponseItem; +use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::parse_command::ParsedCommand; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::EventMsg; diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index c67208072..e3c9fc055 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -4,11 +4,11 @@ use std::path::PathBuf; use crate::protocol::common::AuthMode; use codex_protocol::account::PlanType; use codex_protocol::approvals::SandboxCommandAssessment as CoreSandboxCommandAssessment; -use codex_protocol::config_types::ReasoningEffort; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::items::AgentMessageContent as CoreAgentMessageContent; use codex_protocol::items::TurnItem as CoreTurnItem; use codex_protocol::models::ResponseItem; +use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::parse_command::ParsedCommand as CoreParsedCommand; use codex_protocol::plan_tool::PlanItemArg as CorePlanItemArg; use codex_protocol::plan_tool::StepStatus as CorePlanStepStatus; diff --git a/codex-rs/app-server/src/models.rs b/codex-rs/app-server/src/models.rs index d03795c2d..78f6fd585 100644 --- a/codex-rs/app-server/src/models.rs +++ b/codex-rs/app-server/src/models.rs @@ -1,9 +1,9 @@ use codex_app_server_protocol::AuthMode; use codex_app_server_protocol::Model; use codex_app_server_protocol::ReasoningEffortOption; -use codex_common::model_presets::ModelPreset; -use codex_common::model_presets::ReasoningEffortPreset; -use codex_common::model_presets::builtin_model_presets; +use codex_core::openai_models::model_presets::builtin_model_presets; +use codex_protocol::openai_models::ModelPreset; +use codex_protocol::openai_models::ReasoningEffortPreset; pub fn supported_models(auth_mode: Option) -> Vec { builtin_model_presets(auth_mode) @@ -27,7 +27,7 @@ fn model_from_preset(preset: ModelPreset) -> Model { } fn reasoning_efforts_from_preset( - efforts: &'static [ReasoningEffortPreset], + efforts: Vec, ) -> Vec { efforts .iter() diff --git a/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs b/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs index a64aca805..4b206436c 100644 --- a/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs +++ b/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs @@ -23,10 +23,10 @@ use codex_app_server_protocol::SendUserTurnResponse; use codex_app_server_protocol::ServerRequest; use codex_core::protocol::AskForApproval; use codex_core::protocol::SandboxPolicy; -use codex_core::protocol_config_types::ReasoningEffort; use codex_core::protocol_config_types::ReasoningSummary; use codex_core::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR; use codex_protocol::config_types::SandboxMode; +use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::parse_command::ParsedCommand; use codex_protocol::protocol::Event; use codex_protocol::protocol::EventMsg; diff --git a/codex-rs/app-server/tests/suite/config.rs b/codex-rs/app-server/tests/suite/config.rs index 75dba5722..88e74a6fb 100644 --- a/codex-rs/app-server/tests/suite/config.rs +++ b/codex-rs/app-server/tests/suite/config.rs @@ -10,10 +10,10 @@ use codex_app_server_protocol::Tools; use codex_app_server_protocol::UserSavedConfig; use codex_core::protocol::AskForApproval; use codex_protocol::config_types::ForcedLoginMethod; -use codex_protocol::config_types::ReasoningEffort; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::config_types::SandboxMode; use codex_protocol::config_types::Verbosity; +use codex_protocol::openai_models::ReasoningEffort; use pretty_assertions::assert_eq; use std::collections::HashMap; use std::path::Path; diff --git a/codex-rs/app-server/tests/suite/v2/model_list.rs b/codex-rs/app-server/tests/suite/v2/model_list.rs index 3c4844fed..8ca85c9c3 100644 --- a/codex-rs/app-server/tests/suite/v2/model_list.rs +++ b/codex-rs/app-server/tests/suite/v2/model_list.rs @@ -11,7 +11,7 @@ use codex_app_server_protocol::ModelListParams; use codex_app_server_protocol::ModelListResponse; use codex_app_server_protocol::ReasoningEffortOption; use codex_app_server_protocol::RequestId; -use codex_protocol::config_types::ReasoningEffort; +use codex_protocol::openai_models::ReasoningEffort; use pretty_assertions::assert_eq; use tempfile::TempDir; use tokio::time::timeout; diff --git a/codex-rs/app-server/tests/suite/v2/turn_start.rs b/codex-rs/app-server/tests/suite/v2/turn_start.rs index 03ee279e5..e4cd72294 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start.rs @@ -30,8 +30,8 @@ use codex_app_server_protocol::TurnStartResponse; use codex_app_server_protocol::TurnStartedNotification; use codex_app_server_protocol::TurnStatus; use codex_app_server_protocol::UserInput as V2UserInput; -use codex_core::protocol_config_types::ReasoningEffort; use codex_core::protocol_config_types::ReasoningSummary; +use codex_protocol::openai_models::ReasoningEffort; use core_test_support::skip_if_no_network; use pretty_assertions::assert_eq; use std::path::Path; diff --git a/codex-rs/codex-api/src/common.rs b/codex-rs/codex-api/src/common.rs index addab02dc..19e82de33 100644 --- a/codex-rs/codex-api/src/common.rs +++ b/codex-rs/codex-api/src/common.rs @@ -1,8 +1,8 @@ use crate::error::ApiError; -use codex_protocol::config_types::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig; use codex_protocol::config_types::Verbosity as VerbosityConfig; use codex_protocol::models::ResponseItem; +use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::protocol::RateLimitSnapshot; use codex_protocol::protocol::TokenUsage; use futures::Stream; diff --git a/codex-rs/common/Cargo.toml b/codex-rs/common/Cargo.toml index 377d05448..25264eff0 100644 --- a/codex-rs/common/Cargo.toml +++ b/codex-rs/common/Cargo.toml @@ -9,12 +9,10 @@ workspace = true [dependencies] clap = { workspace = true, features = ["derive", "wrap_help"], optional = true } -codex-app-server-protocol = { workspace = true } codex-core = { workspace = true } codex-lmstudio = { workspace = true } codex-ollama = { workspace = true } codex-protocol = { workspace = true } -once_cell = { workspace = true } serde = { workspace = true, optional = true } toml = { workspace = true, optional = true } diff --git a/codex-rs/common/src/lib.rs b/codex-rs/common/src/lib.rs index 5092b3be2..d5513b832 100644 --- a/codex-rs/common/src/lib.rs +++ b/codex-rs/common/src/lib.rs @@ -32,8 +32,6 @@ mod config_summary; pub use config_summary::create_config_summary_entries; // Shared fuzzy matcher (used by TUI selection popups and other UI filtering) pub mod fuzzy_match; -// Shared model presets used by TUI and MCP server -pub mod model_presets; // Shared approval presets (AskForApproval + Sandbox) used by TUI and MCP server // Not to be confused with AskForApproval, which we should probably rename to EscalationPolicy. pub mod approval_presets; diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 82839522c..f4248f30a 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -20,9 +20,9 @@ use codex_api::error::ApiError; use codex_app_server_protocol::AuthMode; use codex_otel::otel_event_manager::OtelEventManager; use codex_protocol::ConversationId; -use codex_protocol::config_types::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig; use codex_protocol::models::ResponseItem; +use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::protocol::SessionSource; use eventsource_stream::Event; use eventsource_stream::EventStreamError; diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 682861d64..29e7a1ce8 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -22,6 +22,7 @@ use crate::user_notification::UserNotifier; use crate::util::error_or_panic; use async_channel::Receiver; use async_channel::Sender; +use codex_app_server_protocol::AuthMode; use codex_protocol::ConversationId; use codex_protocol::items::TurnItem; use codex_protocol::protocol::FileChange; @@ -126,12 +127,12 @@ use crate::util::backoff; use codex_async_utils::OrCancelExt; use codex_execpolicy::Policy as ExecPolicy; use codex_otel::otel_event_manager::OtelEventManager; -use codex_protocol::config_types::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig; use codex_protocol::models::ContentItem; use codex_protocol::models::FunctionCallOutputPayload; use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseItem; +use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::protocol::CodexErrorInfo; use codex_protocol::protocol::InitialHistory; use codex_protocol::user_input::UserInput; @@ -638,6 +639,14 @@ impl Session { Ok(sess) } + pub(crate) fn get_auth_mode(&self) -> AuthMode { + self.services + .auth_manager + .auth() + .map(|a| a.mode) + .unwrap_or(AuthMode::ApiKey) + } + pub(crate) fn get_tx_event(&self) -> Sender { self.tx_event.clone() } @@ -1478,6 +1487,9 @@ async fn submission_loop(sess: Arc, config: Arc, rx_sub: Receiv Op::Review { review_request } => { handlers::review(&sess, &config, sub.id.clone(), review_request).await; } + Op::ListModels => { + handlers::list_models(&sess, sub.id.clone(), Some(sess.get_auth_mode())).await; + } _ => {} // Ignore unknown ops; enum is non_exhaustive to allow extensions. } } @@ -1494,12 +1506,15 @@ mod handlers { use crate::config::Config; use crate::mcp::auth::compute_auth_statuses; use crate::mcp::collect_mcp_snapshot_from_manager; + use crate::openai_models::model_presets::builtin_model_presets; use crate::review_prompts::resolve_review_request; use crate::tasks::CompactTask; use crate::tasks::RegularTask; use crate::tasks::UndoTask; use crate::tasks::UserShellCommandTask; + use codex_app_server_protocol::AuthMode; use codex_protocol::custom_prompts::CustomPrompt; + use codex_protocol::openai_models::AvailableModelsEvent; use codex_protocol::protocol::CodexErrorInfo; use codex_protocol::protocol::ErrorEvent; use codex_protocol::protocol::Event; @@ -1814,6 +1829,15 @@ mod handlers { } } } + + pub async fn list_models(sess: &Arc, sub_id: String, auth_mode: Option) { + let models = builtin_model_presets(auth_mode); + let event = Event { + id: sub_id, + msg: EventMsg::ListModelsResponse(AvailableModelsEvent { models }), + }; + sess.send_event_raw(event).await; + } } /// Spawn a review thread using the given prompt. diff --git a/codex-rs/core/src/config/edit.rs b/codex-rs/core/src/config/edit.rs index b8862fa5c..68e2d206f 100644 --- a/codex-rs/core/src/config/edit.rs +++ b/codex-rs/core/src/config/edit.rs @@ -2,8 +2,8 @@ use crate::config::CONFIG_TOML_FILE; use crate::config::types::McpServerConfig; use crate::config::types::Notice; use anyhow::Context; -use codex_protocol::config_types::ReasoningEffort; use codex_protocol::config_types::TrustLevel; +use codex_protocol::openai_models::ReasoningEffort; use std::collections::BTreeMap; use std::path::Path; use std::path::PathBuf; @@ -574,7 +574,7 @@ impl ConfigEditsBuilder { mod tests { use super::*; use crate::config::types::McpServerTransportConfig; - use codex_protocol::config_types::ReasoningEffort; + use codex_protocol::openai_models::ReasoningEffort; use pretty_assertions::assert_eq; use tempfile::tempdir; use tokio::runtime::Builder; diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 6276d3b6e..dccf0556f 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -38,11 +38,11 @@ use crate::util::resolve_path; use codex_app_server_protocol::Tools; use codex_app_server_protocol::UserSavedConfig; use codex_protocol::config_types::ForcedLoginMethod; -use codex_protocol::config_types::ReasoningEffort; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::config_types::SandboxMode; use codex_protocol::config_types::TrustLevel; use codex_protocol::config_types::Verbosity; +use codex_protocol::openai_models::ReasoningEffort; use codex_rmcp_client::OAuthCredentialsStoreMode; use dirs::home_dir; use dunce::canonicalize; diff --git a/codex-rs/core/src/config/profile.rs b/codex-rs/core/src/config/profile.rs index 3d9e60b8e..5629465c4 100644 --- a/codex-rs/core/src/config/profile.rs +++ b/codex-rs/core/src/config/profile.rs @@ -2,10 +2,10 @@ use serde::Deserialize; use std::path::PathBuf; use crate::protocol::AskForApproval; -use codex_protocol::config_types::ReasoningEffort; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::config_types::SandboxMode; use codex_protocol::config_types::Verbosity; +use codex_protocol::openai_models::ReasoningEffort; /// Collection of common configuration options that a user can define as a unit /// in `config.toml`. diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index d9ab6ee51..d32366476 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -32,6 +32,7 @@ pub mod git_info; pub mod landlock; pub mod mcp; mod mcp_connection_manager; +pub mod openai_models; pub use mcp_connection_manager::MCP_SANDBOX_STATE_CAPABILITY; pub use mcp_connection_manager::MCP_SANDBOX_STATE_NOTIFICATION; pub use mcp_connection_manager::SandboxState; diff --git a/codex-rs/core/src/model_family.rs b/codex-rs/core/src/model_family.rs index 5dea1c016..0417f13b1 100644 --- a/codex-rs/core/src/model_family.rs +++ b/codex-rs/core/src/model_family.rs @@ -1,5 +1,5 @@ -use codex_protocol::config_types::ReasoningEffort; use codex_protocol::config_types::Verbosity; +use codex_protocol::openai_models::ReasoningEffort; use crate::config::types::ReasoningSummaryFormat; use crate::tools::handlers::apply_patch::ApplyPatchToolType; diff --git a/codex-rs/core/src/openai_models/mod.rs b/codex-rs/core/src/openai_models/mod.rs new file mode 100644 index 000000000..7df68c4ab --- /dev/null +++ b/codex-rs/core/src/openai_models/mod.rs @@ -0,0 +1 @@ +pub mod model_presets; diff --git a/codex-rs/common/src/model_presets.rs b/codex-rs/core/src/openai_models/model_presets.rs similarity index 68% rename from codex-rs/common/src/model_presets.rs rename to codex-rs/core/src/openai_models/model_presets.rs index a031f23b1..f649d8874 100644 --- a/codex-rs/common/src/model_presets.rs +++ b/codex-rs/core/src/openai_models/model_presets.rs @@ -1,76 +1,38 @@ -use std::collections::HashMap; - use codex_app_server_protocol::AuthMode; -use codex_core::protocol_config_types::ReasoningEffort; +use codex_protocol::openai_models::ModelPreset; +use codex_protocol::openai_models::ModelUpgrade; +use codex_protocol::openai_models::ReasoningEffort; +use codex_protocol::openai_models::ReasoningEffortPreset; use once_cell::sync::Lazy; pub const HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG: &str = "hide_gpt5_1_migration_prompt"; pub const HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG: &str = "hide_gpt-5.1-codex-max_migration_prompt"; -/// A reasoning effort option that can be surfaced for a model. -#[derive(Debug, Clone, Copy)] -pub struct ReasoningEffortPreset { - /// Effort level that the model supports. - pub effort: ReasoningEffort, - /// Short human description shown next to the effort in UIs. - pub description: &'static str, -} - -#[derive(Debug, Clone)] -pub struct ModelUpgrade { - pub id: &'static str, - pub reasoning_effort_mapping: Option>, - pub migration_config_key: &'static str, -} - -/// Metadata describing a Codex-supported model. -#[derive(Debug, Clone)] -pub struct ModelPreset { - /// Stable identifier for the preset. - pub id: &'static str, - /// Model slug (e.g., "gpt-5"). - pub model: &'static str, - /// Display name shown in UIs. - pub display_name: &'static str, - /// Short human description shown in UIs. - pub description: &'static str, - /// Reasoning effort applied when none is explicitly chosen. - pub default_reasoning_effort: ReasoningEffort, - /// Supported reasoning effort options. - pub supported_reasoning_efforts: &'static [ReasoningEffortPreset], - /// Whether this is the default model for new users. - pub is_default: bool, - /// recommended upgrade model - pub upgrade: Option, - /// Whether this preset should appear in the picker UI. - pub show_in_picker: bool, -} - static PRESETS: Lazy> = Lazy::new(|| { vec![ ModelPreset { - id: "gpt-5.1-codex-max", - model: "gpt-5.1-codex-max", - display_name: "gpt-5.1-codex-max", - description: "Latest Codex-optimized flagship for deep and fast reasoning.", + id: "gpt-5.1-codex-max".to_string(), + model: "gpt-5.1-codex-max".to_string(), + display_name: "gpt-5.1-codex-max".to_string(), + description: "Latest Codex-optimized flagship for deep and fast reasoning.".to_string(), default_reasoning_effort: ReasoningEffort::Medium, - supported_reasoning_efforts: &[ + supported_reasoning_efforts: vec![ ReasoningEffortPreset { effort: ReasoningEffort::Low, - description: "Fast responses with lighter reasoning", + description: "Fast responses with lighter reasoning".to_string(), }, ReasoningEffortPreset { effort: ReasoningEffort::Medium, - description: "Balances speed and reasoning depth for everyday tasks", + description: "Balances speed and reasoning depth for everyday tasks".to_string(), }, ReasoningEffortPreset { effort: ReasoningEffort::High, - description: "Maximizes reasoning depth for complex problems", + description: "Maximizes reasoning depth for complex problems".to_string(), }, ReasoningEffortPreset { effort: ReasoningEffort::XHigh, - description: "Extra high reasoning depth for complex problems", + description: "Extra high reasoning depth for complex problems".to_string(), }, ], is_default: true, @@ -78,167 +40,169 @@ static PRESETS: Lazy> = Lazy::new(|| { show_in_picker: true, }, ModelPreset { - id: "gpt-5.1-codex", - model: "gpt-5.1-codex", - display_name: "gpt-5.1-codex", - description: "Optimized for codex.", + id: "gpt-5.1-codex".to_string(), + model: "gpt-5.1-codex".to_string(), + display_name: "gpt-5.1-codex".to_string(), + description: "Optimized for codex.".to_string(), default_reasoning_effort: ReasoningEffort::Medium, - supported_reasoning_efforts: &[ + supported_reasoning_efforts: vec![ ReasoningEffortPreset { effort: ReasoningEffort::Low, - description: "Fastest responses with limited reasoning", + description: "Fastest responses with limited reasoning".to_string(), }, ReasoningEffortPreset { effort: ReasoningEffort::Medium, - description: "Dynamically adjusts reasoning based on the task", + description: "Dynamically adjusts reasoning based on the task".to_string(), }, ReasoningEffortPreset { effort: ReasoningEffort::High, - description: "Maximizes reasoning depth for complex or ambiguous problems", + description: "Maximizes reasoning depth for complex or ambiguous problems" + .to_string(), }, ], is_default: false, upgrade: Some(ModelUpgrade { - id: "gpt-5.1-codex-max", + id: "gpt-5.1-codex-max".to_string(), reasoning_effort_mapping: None, - migration_config_key: HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG, + migration_config_key: HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG.to_string(), }), show_in_picker: true, }, ModelPreset { - id: "gpt-5.1-codex-mini", - model: "gpt-5.1-codex-mini", - display_name: "gpt-5.1-codex-mini", - description: "Optimized for codex. Cheaper, faster, but less capable.", + id: "gpt-5.1-codex-mini".to_string(), + model: "gpt-5.1-codex-mini".to_string(), + display_name: "gpt-5.1-codex-mini".to_string(), + description: "Optimized for codex. Cheaper, faster, but less capable.".to_string(), default_reasoning_effort: ReasoningEffort::Medium, - supported_reasoning_efforts: &[ + supported_reasoning_efforts: vec![ ReasoningEffortPreset { effort: ReasoningEffort::Medium, - description: "Dynamically adjusts reasoning based on the task", + description: "Dynamically adjusts reasoning based on the task".to_string(), }, ReasoningEffortPreset { effort: ReasoningEffort::High, - description: "Maximizes reasoning depth for complex or ambiguous problems", + description: "Maximizes reasoning depth for complex or ambiguous problems" + .to_string(), }, ], is_default: false, upgrade: Some(ModelUpgrade { - id: "gpt-5.1-codex-max", + id: "gpt-5.1-codex-max".to_string(), reasoning_effort_mapping: None, - migration_config_key: HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG, + migration_config_key: HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG.to_string(), }), show_in_picker: true, }, ModelPreset { - id: "gpt-5.1", - model: "gpt-5.1", - display_name: "gpt-5.1", - description: "Broad world knowledge with strong general reasoning.", + id: "gpt-5.1".to_string(), + model: "gpt-5.1".to_string(), + display_name: "gpt-5.1".to_string(), + description: "Broad world knowledge with strong general reasoning.".to_string(), default_reasoning_effort: ReasoningEffort::Medium, - supported_reasoning_efforts: &[ + supported_reasoning_efforts: vec![ ReasoningEffortPreset { effort: ReasoningEffort::Low, - description: "Balances speed with some reasoning; useful for straightforward queries and short explanations", + description: "Balances speed with some reasoning; useful for straightforward queries and short explanations".to_string(), }, ReasoningEffortPreset { effort: ReasoningEffort::Medium, - description: "Provides a solid balance of reasoning depth and latency for general-purpose tasks", + description: "Provides a solid balance of reasoning depth and latency for general-purpose tasks".to_string(), }, ReasoningEffortPreset { effort: ReasoningEffort::High, - description: "Maximizes reasoning depth for complex or ambiguous problems", + description: "Maximizes reasoning depth for complex or ambiguous problems".to_string(), }, ], is_default: false, upgrade: Some(ModelUpgrade { - id: "gpt-5.1-codex-max", + id: "gpt-5.1-codex-max".to_string(), reasoning_effort_mapping: None, - migration_config_key: HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG, + migration_config_key: HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG.to_string(), }), show_in_picker: true, }, // Deprecated models. ModelPreset { - id: "gpt-5-codex", - model: "gpt-5-codex", - display_name: "gpt-5-codex", - description: "Optimized for codex.", + id: "gpt-5-codex".to_string(), + model: "gpt-5-codex".to_string(), + display_name: "gpt-5-codex".to_string(), + description: "Optimized for codex.".to_string(), default_reasoning_effort: ReasoningEffort::Medium, - supported_reasoning_efforts: &[ + supported_reasoning_efforts: vec![ ReasoningEffortPreset { effort: ReasoningEffort::Low, - description: "Fastest responses with limited reasoning", + description: "Fastest responses with limited reasoning".to_string(), }, ReasoningEffortPreset { effort: ReasoningEffort::Medium, - description: "Dynamically adjusts reasoning based on the task", + description: "Dynamically adjusts reasoning based on the task".to_string(), }, ReasoningEffortPreset { effort: ReasoningEffort::High, - description: "Maximizes reasoning depth for complex or ambiguous problems", + description: "Maximizes reasoning depth for complex or ambiguous problems".to_string(), }, ], is_default: false, upgrade: Some(ModelUpgrade { - id: "gpt-5.1-codex-max", + id: "gpt-5.1-codex-max".to_string(), reasoning_effort_mapping: None, - migration_config_key: HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG, + migration_config_key: HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG.to_string(), }), show_in_picker: false, }, ModelPreset { - id: "gpt-5-codex-mini", - model: "gpt-5-codex-mini", - display_name: "gpt-5-codex-mini", - description: "Optimized for codex. Cheaper, faster, but less capable.", + id: "gpt-5-codex-mini".to_string(), + model: "gpt-5-codex-mini".to_string(), + display_name: "gpt-5-codex-mini".to_string(), + description: "Optimized for codex. Cheaper, faster, but less capable.".to_string(), default_reasoning_effort: ReasoningEffort::Medium, - supported_reasoning_efforts: &[ + supported_reasoning_efforts: vec![ ReasoningEffortPreset { effort: ReasoningEffort::Medium, - description: "Dynamically adjusts reasoning based on the task", + description: "Dynamically adjusts reasoning based on the task".to_string(), }, ReasoningEffortPreset { effort: ReasoningEffort::High, - description: "Maximizes reasoning depth for complex or ambiguous problems", + description: "Maximizes reasoning depth for complex or ambiguous problems".to_string(), }, ], is_default: false, upgrade: Some(ModelUpgrade { - id: "gpt-5.1-codex-mini", + id: "gpt-5.1-codex-mini".to_string(), reasoning_effort_mapping: None, - migration_config_key: HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG, + migration_config_key: HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG.to_string(), }), show_in_picker: false, }, ModelPreset { - id: "gpt-5", - model: "gpt-5", - display_name: "gpt-5", - description: "Broad world knowledge with strong general reasoning.", + id: "gpt-5".to_string(), + model: "gpt-5".to_string(), + display_name: "gpt-5".to_string(), + description: "Broad world knowledge with strong general reasoning.".to_string(), default_reasoning_effort: ReasoningEffort::Medium, - supported_reasoning_efforts: &[ + supported_reasoning_efforts: vec![ ReasoningEffortPreset { effort: ReasoningEffort::Minimal, - description: "Fastest responses with little reasoning", + description: "Fastest responses with little reasoning".to_string(), }, ReasoningEffortPreset { effort: ReasoningEffort::Low, - description: "Balances speed with some reasoning; useful for straightforward queries and short explanations", + description: "Balances speed with some reasoning; useful for straightforward queries and short explanations".to_string(), }, ReasoningEffortPreset { effort: ReasoningEffort::Medium, - description: "Provides a solid balance of reasoning depth and latency for general-purpose tasks", + description: "Provides a solid balance of reasoning depth and latency for general-purpose tasks".to_string(), }, ReasoningEffortPreset { effort: ReasoningEffort::High, - description: "Maximizes reasoning depth for complex or ambiguous problems", + description: "Maximizes reasoning depth for complex or ambiguous problems".to_string(), }, ], is_default: false, upgrade: Some(ModelUpgrade { - id: "gpt-5.1-codex-max", + id: "gpt-5.1-codex-max".to_string(), reasoning_effort_mapping: None, - migration_config_key: HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG, + migration_config_key: HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG.to_string(), }), show_in_picker: false, }, diff --git a/codex-rs/core/src/rollout/policy.rs b/codex-rs/core/src/rollout/policy.rs index 58072f933..4b7d7d306 100644 --- a/codex-rs/core/src/rollout/policy.rs +++ b/codex-rs/core/src/rollout/policy.rs @@ -51,6 +51,7 @@ pub(crate) fn should_persist_event_msg(ev: &EventMsg) -> bool { | EventMsg::Warning(_) | EventMsg::TaskStarted(_) | EventMsg::TaskComplete(_) + | EventMsg::ListModelsResponse(_) | EventMsg::AgentMessageDelta(_) | EventMsg::AgentReasoningDelta(_) | EventMsg::AgentReasoningRawContentDelta(_) diff --git a/codex-rs/core/src/sandboxing/assessment.rs b/codex-rs/core/src/sandboxing/assessment.rs index 719e3be1f..225825c93 100644 --- a/codex-rs/core/src/sandboxing/assessment.rs +++ b/codex-rs/core/src/sandboxing/assessment.rs @@ -14,9 +14,9 @@ use crate::protocol::SandboxPolicy; use askama::Template; use codex_otel::otel_event_manager::OtelEventManager; use codex_protocol::ConversationId; -use codex_protocol::config_types::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::models::ContentItem; use codex_protocol::models::ResponseItem; +use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::protocol::SandboxCommandAssessment; use codex_protocol::protocol::SessionSource; use futures::StreamExt; diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index e074d2975..e0e06757b 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -22,11 +22,11 @@ use codex_core::protocol::Op; use codex_core::protocol::SessionSource; use codex_otel::otel_event_manager::OtelEventManager; use codex_protocol::ConversationId; -use codex_protocol::config_types::ReasoningEffort; use codex_protocol::config_types::Verbosity; use codex_protocol::models::ReasoningItemContent; use codex_protocol::models::ReasoningItemReasoningSummary; use codex_protocol::models::WebSearchAction; +use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::user_input::UserInput; use core_test_support::load_default_config_for_test; use core_test_support::load_sse_fixture_with_id; diff --git a/codex-rs/core/tests/suite/list_models.rs b/codex-rs/core/tests/suite/list_models.rs new file mode 100644 index 000000000..ecfe9138e --- /dev/null +++ b/codex-rs/core/tests/suite/list_models.rs @@ -0,0 +1,187 @@ +use anyhow::Result; +use codex_core::CodexAuth; +use codex_core::protocol::EventMsg; +use codex_core::protocol::Op; +use codex_protocol::openai_models::ModelPreset; +use codex_protocol::openai_models::ReasoningEffort; +use codex_protocol::openai_models::ReasoningEffortPreset; +use core_test_support::responses::start_mock_server; +use core_test_support::test_codex::test_codex; +use core_test_support::wait_for_event_match; +use pretty_assertions::assert_eq; + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn list_models_returns_api_key_models() -> Result<()> { + let server = start_mock_server().await; + let mut builder = test_codex().with_auth(CodexAuth::from_api_key("sk-test")); + let test = builder.build(&server).await?; + + test.codex.submit(Op::ListModels).await?; + + let event = wait_for_event_match(&test.codex, |event| match event { + EventMsg::ListModelsResponse(models) => Some(models.clone()), + _ => None, + }) + .await; + + let expected_models = expected_models_for_api_key(); + assert_eq!(expected_models, event.models); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn list_models_returns_chatgpt_models() -> Result<()> { + let server = start_mock_server().await; + let mut builder = test_codex().with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()); + let test = builder.build(&server).await?; + + test.codex.submit(Op::ListModels).await?; + + let event = wait_for_event_match(&test.codex, |event| match event { + EventMsg::ListModelsResponse(models) => Some(models.clone()), + _ => None, + }) + .await; + + let expected_models = expected_models_for_chatgpt(); + assert_eq!(expected_models, event.models); + + Ok(()) +} + +fn expected_models_for_api_key() -> Vec { + vec![gpt_5_1_codex(), gpt_5_1_codex_mini(), gpt_5_1()] +} + +fn expected_models_for_chatgpt() -> Vec { + vec![ + gpt_5_1_codex_max(), + gpt_5_1_codex(), + gpt_5_1_codex_mini(), + gpt_5_1(), + ] +} + +fn gpt_5_1_codex_max() -> ModelPreset { + ModelPreset { + id: "gpt-5.1-codex-max".to_string(), + model: "gpt-5.1-codex-max".to_string(), + display_name: "gpt-5.1-codex-max".to_string(), + description: "Latest Codex-optimized flagship for deep and fast reasoning.".to_string(), + default_reasoning_effort: ReasoningEffort::Medium, + supported_reasoning_efforts: vec![ + effort( + ReasoningEffort::Low, + "Fast responses with lighter reasoning", + ), + effort( + ReasoningEffort::Medium, + "Balances speed and reasoning depth for everyday tasks", + ), + effort( + ReasoningEffort::High, + "Maximizes reasoning depth for complex problems", + ), + effort( + ReasoningEffort::XHigh, + "Extra high reasoning depth for complex problems", + ), + ], + is_default: true, + upgrade: None, + show_in_picker: true, + } +} + +fn gpt_5_1_codex() -> ModelPreset { + ModelPreset { + id: "gpt-5.1-codex".to_string(), + model: "gpt-5.1-codex".to_string(), + display_name: "gpt-5.1-codex".to_string(), + description: "Optimized for codex.".to_string(), + default_reasoning_effort: ReasoningEffort::Medium, + supported_reasoning_efforts: vec![ + effort( + ReasoningEffort::Low, + "Fastest responses with limited reasoning", + ), + effort( + ReasoningEffort::Medium, + "Dynamically adjusts reasoning based on the task", + ), + effort( + ReasoningEffort::High, + "Maximizes reasoning depth for complex or ambiguous problems", + ), + ], + is_default: false, + upgrade: Some(gpt_5_1_codex_max_upgrade()), + show_in_picker: true, + } +} + +fn gpt_5_1_codex_mini() -> ModelPreset { + ModelPreset { + id: "gpt-5.1-codex-mini".to_string(), + model: "gpt-5.1-codex-mini".to_string(), + display_name: "gpt-5.1-codex-mini".to_string(), + description: "Optimized for codex. Cheaper, faster, but less capable.".to_string(), + default_reasoning_effort: ReasoningEffort::Medium, + supported_reasoning_efforts: vec![ + effort( + ReasoningEffort::Medium, + "Dynamically adjusts reasoning based on the task", + ), + effort( + ReasoningEffort::High, + "Maximizes reasoning depth for complex or ambiguous problems", + ), + ], + is_default: false, + upgrade: Some(gpt_5_1_codex_max_upgrade()), + show_in_picker: true, + } +} + +fn gpt_5_1() -> ModelPreset { + ModelPreset { + id: "gpt-5.1".to_string(), + model: "gpt-5.1".to_string(), + display_name: "gpt-5.1".to_string(), + description: "Broad world knowledge with strong general reasoning.".to_string(), + default_reasoning_effort: ReasoningEffort::Medium, + supported_reasoning_efforts: vec![ + effort( + ReasoningEffort::Low, + "Balances speed with some reasoning; useful for straightforward queries and short explanations", + ), + effort( + ReasoningEffort::Medium, + "Provides a solid balance of reasoning depth and latency for general-purpose tasks", + ), + effort( + ReasoningEffort::High, + "Maximizes reasoning depth for complex or ambiguous problems", + ), + ], + is_default: false, + upgrade: Some(gpt_5_1_codex_max_upgrade()), + show_in_picker: true, + } +} + +fn gpt_5_1_codex_max_upgrade() -> codex_protocol::openai_models::ModelUpgrade { + codex_protocol::openai_models::ModelUpgrade { + id: "gpt-5.1-codex-max".to_string(), + reasoning_effort_mapping: None, + migration_config_key: "hide_gpt-5.1-codex-max_migration_prompt".to_string(), + } +} + +fn effort(reasoning_effort: ReasoningEffort, description: &str) -> ReasoningEffortPreset { + ReasoningEffortPreset { + effort: reasoning_effort, + description: description.to_string(), + } +} diff --git a/codex-rs/core/tests/suite/mod.rs b/codex-rs/core/tests/suite/mod.rs index b87766361..35d4eb52a 100644 --- a/codex-rs/core/tests/suite/mod.rs +++ b/codex-rs/core/tests/suite/mod.rs @@ -34,6 +34,7 @@ mod grep_files; mod items; mod json_result; mod list_dir; +mod list_models; mod live_cli; mod model_overrides; mod model_tools; diff --git a/codex-rs/core/tests/suite/model_overrides.rs b/codex-rs/core/tests/suite/model_overrides.rs index a186c13ef..f67196312 100644 --- a/codex-rs/core/tests/suite/model_overrides.rs +++ b/codex-rs/core/tests/suite/model_overrides.rs @@ -2,7 +2,7 @@ use codex_core::CodexAuth; use codex_core::ConversationManager; use codex_core::protocol::EventMsg; use codex_core::protocol::Op; -use codex_core::protocol_config_types::ReasoningEffort; +use codex_protocol::openai_models::ReasoningEffort; use core_test_support::load_default_config_for_test; use core_test_support::wait_for_event; use pretty_assertions::assert_eq; diff --git a/codex-rs/core/tests/suite/prompt_caching.rs b/codex-rs/core/tests/suite/prompt_caching.rs index f4455fd02..0c908e35b 100644 --- a/codex-rs/core/tests/suite/prompt_caching.rs +++ b/codex-rs/core/tests/suite/prompt_caching.rs @@ -7,10 +7,10 @@ use codex_core::protocol::ENVIRONMENT_CONTEXT_OPEN_TAG; use codex_core::protocol::EventMsg; use codex_core::protocol::Op; use codex_core::protocol::SandboxPolicy; -use codex_core::protocol_config_types::ReasoningEffort; use codex_core::protocol_config_types::ReasoningSummary; use codex_core::shell::Shell; use codex_core::shell::default_user_shell; +use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::user_input::UserInput; use core_test_support::load_sse_fixture_with_id; use core_test_support::responses::mount_sse_once; diff --git a/codex-rs/exec/src/event_processor_with_human_output.rs b/codex-rs/exec/src/event_processor_with_human_output.rs index 64a5358f3..17a167585 100644 --- a/codex-rs/exec/src/event_processor_with_human_output.rs +++ b/codex-rs/exec/src/event_processor_with_human_output.rs @@ -583,6 +583,7 @@ impl EventProcessor for EventProcessorWithHumanOutput { | EventMsg::ReasoningContentDelta(_) | EventMsg::ReasoningRawContentDelta(_) | EventMsg::UndoCompleted(_) + | EventMsg::ListModelsResponse(_) | EventMsg::UndoStarted(_) => {} } CodexStatus::Running diff --git a/codex-rs/mcp-server/src/codex_tool_runner.rs b/codex-rs/mcp-server/src/codex_tool_runner.rs index 55808f17c..2b3fd0770 100644 --- a/codex-rs/mcp-server/src/codex_tool_runner.rs +++ b/codex-rs/mcp-server/src/codex_tool_runner.rs @@ -307,6 +307,7 @@ async fn run_codex_tool_session_inner( | EventMsg::UndoCompleted(_) | EventMsg::ExitedReviewMode(_) | EventMsg::ContextCompacted(_) + | EventMsg::ListModelsResponse(_) | EventMsg::DeprecationNotice(_) => { // For now, we do not do anything extra for these // events. Note that diff --git a/codex-rs/mcp-server/src/outgoing_message.rs b/codex-rs/mcp-server/src/outgoing_message.rs index 9e9d07930..83ac25fdf 100644 --- a/codex-rs/mcp-server/src/outgoing_message.rs +++ b/codex-rs/mcp-server/src/outgoing_message.rs @@ -239,7 +239,7 @@ mod tests { use codex_core::protocol::SandboxPolicy; use codex_core::protocol::SessionConfiguredEvent; use codex_protocol::ConversationId; - use codex_protocol::config_types::ReasoningEffort; + use codex_protocol::openai_models::ReasoningEffort; use pretty_assertions::assert_eq; use serde_json::json; use tempfile::NamedTempFile; diff --git a/codex-rs/otel/src/otel_event_manager.rs b/codex-rs/otel/src/otel_event_manager.rs index b6bc07e79..c300f3fb8 100644 --- a/codex-rs/otel/src/otel_event_manager.rs +++ b/codex-rs/otel/src/otel_event_manager.rs @@ -2,9 +2,9 @@ use chrono::SecondsFormat; use chrono::Utc; use codex_app_server_protocol::AuthMode; use codex_protocol::ConversationId; -use codex_protocol::config_types::ReasoningEffort; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::models::ResponseItem; +use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::ReviewDecision; use codex_protocol::protocol::SandboxPolicy; diff --git a/codex-rs/protocol/src/config_types.rs b/codex-rs/protocol/src/config_types.rs index 2ee6d3974..a98ec4e2b 100644 --- a/codex-rs/protocol/src/config_types.rs +++ b/codex-rs/protocol/src/config_types.rs @@ -2,37 +2,8 @@ use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; use strum_macros::Display; -use strum_macros::EnumIter; use ts_rs::TS; -/// See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning -#[derive( - Debug, - Serialize, - Deserialize, - Default, - Clone, - Copy, - PartialEq, - Eq, - Display, - JsonSchema, - TS, - EnumIter, - Hash, -)] -#[serde(rename_all = "lowercase")] -#[strum(serialize_all = "lowercase")] -pub enum ReasoningEffort { - None, - Minimal, - Low, - #[default] - Medium, - High, - XHigh, -} - /// A summary of the reasoning performed by the model. This can be useful for /// debugging and understanding the model's reasoning process. /// See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries diff --git a/codex-rs/protocol/src/lib.rs b/codex-rs/protocol/src/lib.rs index 08ea75334..0d6a0594f 100644 --- a/codex-rs/protocol/src/lib.rs +++ b/codex-rs/protocol/src/lib.rs @@ -8,6 +8,7 @@ pub mod items; pub mod message_history; pub mod models; pub mod num_format; +pub mod openai_models; pub mod parse_command; pub mod plan_tool; pub mod protocol; diff --git a/codex-rs/protocol/src/openai_models.rs b/codex-rs/protocol/src/openai_models.rs new file mode 100644 index 000000000..f9a05f9ff --- /dev/null +++ b/codex-rs/protocol/src/openai_models.rs @@ -0,0 +1,80 @@ +use std::collections::HashMap; + +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; +use strum_macros::Display; +use strum_macros::EnumIter; +use ts_rs::TS; + +/// See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning +#[derive( + Debug, + Serialize, + Deserialize, + Default, + Clone, + Copy, + PartialEq, + Eq, + Display, + JsonSchema, + TS, + EnumIter, + Hash, +)] +#[serde(rename_all = "lowercase")] +#[strum(serialize_all = "lowercase")] +pub enum ReasoningEffort { + None, + Minimal, + Low, + #[default] + Medium, + High, + XHigh, +} + +#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema)] +pub struct AvailableModelsEvent { + pub models: Vec, +} + +/// A reasoning effort option that can be surfaced for a model. +#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema, PartialEq)] +pub struct ReasoningEffortPreset { + /// Effort level that the model supports. + pub effort: ReasoningEffort, + /// Short human description shown next to the effort in UIs. + pub description: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema, PartialEq)] +pub struct ModelUpgrade { + pub id: String, + pub reasoning_effort_mapping: Option>, + pub migration_config_key: String, +} + +/// Metadata describing a Codex-supported model. +#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema, PartialEq)] +pub struct ModelPreset { + /// Stable identifier for the preset. + pub id: String, + /// Model slug (e.g., "gpt-5"). + pub model: String, + /// Display name shown in UIs. + pub display_name: String, + /// Short human description shown in UIs. + pub description: String, + /// Reasoning effort applied when none is explicitly chosen. + pub default_reasoning_effort: ReasoningEffort, + /// Supported reasoning effort options. + pub supported_reasoning_efforts: Vec, + /// Whether this is the default model for new users. + pub is_default: bool, + /// recommended upgrade model + pub upgrade: Option, + /// Whether this preset should appear in the picker UI. + pub show_in_picker: bool, +} diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 347cc119f..6b787afe1 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -12,7 +12,6 @@ use std::time::Duration; use crate::ConversationId; use crate::approvals::ElicitationRequestEvent; -use crate::config_types::ReasoningEffort as ReasoningEffortConfig; use crate::config_types::ReasoningSummary as ReasoningSummaryConfig; use crate::custom_prompts::CustomPrompt; use crate::items::TurnItem; @@ -20,6 +19,8 @@ use crate::message_history::HistoryEntry; use crate::models::ContentItem; use crate::models::ResponseItem; use crate::num_format::format_with_separators; +use crate::openai_models::AvailableModelsEvent; +use crate::openai_models::ReasoningEffort as ReasoningEffortConfig; use crate::parse_command::ParsedCommand; use crate::plan_tool::UpdatePlanArgs; use crate::user_input::UserInput; @@ -208,6 +209,9 @@ pub enum Op { /// The raw command string after '!' command: String, }, + + /// Request the list of available models. + ListModels, } /// Determines the conditions under which the user is consulted to approve @@ -578,6 +582,7 @@ pub enum EventMsg { AgentMessageContentDelta(AgentMessageContentDeltaEvent), ReasoningContentDelta(ReasoningContentDeltaEvent), ReasoningRawContentDelta(ReasoningRawContentDeltaEvent), + ListModelsResponse(AvailableModelsEvent), } /// Codex errors that we expose to clients. diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 32fc09566..409f04721 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -21,10 +21,6 @@ use crate::tui::TuiEvent; use crate::update_action::UpdateAction; use codex_ansi_escape::ansi_escape_line; use codex_app_server_protocol::AuthMode; -use codex_common::model_presets::HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG; -use codex_common::model_presets::HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG; -use codex_common::model_presets::ModelUpgrade; -use codex_common::model_presets::all_model_presets; use codex_core::AuthManager; use codex_core::ConversationManager; use codex_core::config::Config; @@ -32,14 +28,18 @@ use codex_core::config::edit::ConfigEditsBuilder; #[cfg(target_os = "windows")] use codex_core::features::Feature; use codex_core::model_family::find_family_for_model; +use codex_core::openai_models::model_presets::HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG; +use codex_core::openai_models::model_presets::HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG; +use codex_core::openai_models::model_presets::all_model_presets; use codex_core::protocol::EventMsg; use codex_core::protocol::FinalOutput; use codex_core::protocol::Op; use codex_core::protocol::SessionSource; use codex_core::protocol::TokenUsage; -use codex_core::protocol_config_types::ReasoningEffort as ReasoningEffortConfig; use codex_core::skills::load_skills; use codex_protocol::ConversationId; +use codex_protocol::openai_models::ModelUpgrade; +use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use color_eyre::eyre::Result; use color_eyre::eyre::WrapErr; use crossterm::event::KeyCode; diff --git a/codex-rs/tui/src/app_event.rs b/codex-rs/tui/src/app_event.rs index 944eeda81..3a199593b 100644 --- a/codex-rs/tui/src/app_event.rs +++ b/codex-rs/tui/src/app_event.rs @@ -1,18 +1,18 @@ use std::path::PathBuf; use codex_common::approval_presets::ApprovalPreset; -use codex_common::model_presets::ModelPreset; use codex_core::protocol::ConversationPathResponseEvent; use codex_core::protocol::Event; use codex_core::protocol::RateLimitSnapshot; use codex_file_search::FileMatch; +use codex_protocol::openai_models::ModelPreset; use crate::bottom_pane::ApprovalRequest; use crate::history_cell::HistoryCell; use codex_core::protocol::AskForApproval; use codex_core::protocol::SandboxPolicy; -use codex_core::protocol_config_types::ReasoningEffort; +use codex_protocol::openai_models::ReasoningEffort; #[allow(clippy::large_enum_variant)] #[derive(Debug)] diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 0f1d6918d..f18725d8b 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -122,15 +122,15 @@ use std::path::Path; use chrono::Local; use codex_common::approval_presets::ApprovalPreset; use codex_common::approval_presets::builtin_approval_presets; -use codex_common::model_presets::ModelPreset; -use codex_common::model_presets::builtin_model_presets; use codex_core::AuthManager; use codex_core::CodexAuth; use codex_core::ConversationManager; +use codex_core::openai_models::model_presets::builtin_model_presets; use codex_core::protocol::AskForApproval; use codex_core::protocol::SandboxPolicy; -use codex_core::protocol_config_types::ReasoningEffort as ReasoningEffortConfig; use codex_file_search::FileMatch; +use codex_protocol::openai_models::ModelPreset; +use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::plan_tool::UpdatePlanArgs; use strum::IntoEnumIterator; @@ -1833,6 +1833,7 @@ impl ChatWidget { | EventMsg::ItemCompleted(_) | EventMsg::AgentMessageContentDelta(_) | EventMsg::ReasoningContentDelta(_) + | EventMsg::ListModelsResponse(_) | EventMsg::ReasoningRawContentDelta(_) => {} } } @@ -2074,7 +2075,7 @@ impl ChatWidget { let description = if preset.description.is_empty() { Some("Uses fewer credits for upcoming turns.".to_string()) } else { - Some(preset.description.to_string()) + Some(preset.description) }; let items = vec![ @@ -2210,9 +2211,9 @@ impl ChatWidget { if choices.len() == 1 { if let Some(effort) = choices.first().and_then(|c| c.stored) { - self.apply_model_and_effort(preset.model.to_string(), Some(effort)); + self.apply_model_and_effort(preset.model, Some(effort)); } else { - self.apply_model_and_effort(preset.model.to_string(), None); + self.apply_model_and_effort(preset.model, None); } return; } diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 43056464d..a4d21608c 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -5,8 +5,6 @@ use crate::test_backend::VT100Backend; use crate::tui::FrameRequester; use assert_matches::assert_matches; use codex_common::approval_presets::builtin_approval_presets; -use codex_common::model_presets::ModelPreset; -use codex_common::model_presets::ReasoningEffortPreset; use codex_core::AuthManager; use codex_core::CodexAuth; use codex_core::config::Config; @@ -48,6 +46,8 @@ use codex_core::protocol::UndoStartedEvent; use codex_core::protocol::ViewImageToolCallEvent; use codex_core::protocol::WarningEvent; use codex_protocol::ConversationId; +use codex_protocol::openai_models::ModelPreset; +use codex_protocol::openai_models::ReasoningEffortPreset; use codex_protocol::parse_command::ParsedCommand; use codex_protocol::plan_tool::PlanItemArg; use codex_protocol::plan_tool::StepStatus; @@ -1805,17 +1805,17 @@ fn reasoning_popup_shows_extra_high_with_space() { fn single_reasoning_option_skips_selection() { let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(); - static SINGLE_EFFORT: [ReasoningEffortPreset; 1] = [ReasoningEffortPreset { + let single_effort = vec![ReasoningEffortPreset { effort: ReasoningEffortConfig::High, - description: "Maximizes reasoning depth for complex or ambiguous problems", + description: "Maximizes reasoning depth for complex or ambiguous problems".to_string(), }]; let preset = ModelPreset { - id: "model-with-single-reasoning", - model: "model-with-single-reasoning", - display_name: "model-with-single-reasoning", - description: "", + id: "model-with-single-reasoning".to_string(), + model: "model-with-single-reasoning".to_string(), + display_name: "model-with-single-reasoning".to_string(), + description: "".to_string(), default_reasoning_effort: ReasoningEffortConfig::High, - supported_reasoning_efforts: &SINGLE_EFFORT, + supported_reasoning_efforts: single_effort, is_default: false, upgrade: None, show_in_picker: true, diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 475eb1db1..c4fd31f54 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -31,7 +31,7 @@ use codex_core::protocol::FileChange; use codex_core::protocol::McpAuthStatus; use codex_core::protocol::McpInvocation; use codex_core::protocol::SessionConfiguredEvent; -use codex_core::protocol_config_types::ReasoningEffort as ReasoningEffortConfig; +use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::plan_tool::PlanItemArg; use codex_protocol::plan_tool::StepStatus; use codex_protocol::plan_tool::UpdatePlanArgs; diff --git a/codex-rs/tui/src/model_migration.rs b/codex-rs/tui/src/model_migration.rs index 283007e02..1f93fd9a4 100644 --- a/codex-rs/tui/src/model_migration.rs +++ b/codex-rs/tui/src/model_migration.rs @@ -7,8 +7,8 @@ use crate::selection_list::selection_option_row; use crate::tui::FrameRequester; use crate::tui::Tui; use crate::tui::TuiEvent; -use codex_common::model_presets::HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG; -use codex_common::model_presets::HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG; +use codex_core::openai_models::model_presets::HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG; +use codex_core::openai_models::model_presets::HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG; use crossterm::event::KeyCode; use crossterm::event::KeyEvent; use crossterm::event::KeyEventKind; @@ -329,7 +329,7 @@ mod tests { use crate::custom_terminal::Terminal; use crate::test_backend::VT100Backend; use crate::tui::FrameRequester; - use codex_common::model_presets::HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG; + use codex_core::openai_models::model_presets::HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG; use crossterm::event::KeyCode; use crossterm::event::KeyEvent; use insta::assert_snapshot; diff --git a/codex-rs/tui/src/status/tests.rs b/codex-rs/tui/src/status/tests.rs index ae379aae6..0709e366d 100644 --- a/codex-rs/tui/src/status/tests.rs +++ b/codex-rs/tui/src/status/tests.rs @@ -13,8 +13,8 @@ use codex_core::protocol::RateLimitSnapshot; use codex_core::protocol::RateLimitWindow; use codex_core::protocol::SandboxPolicy; use codex_core::protocol::TokenUsage; -use codex_protocol::config_types::ReasoningEffort; use codex_protocol::config_types::ReasoningSummary; +use codex_protocol::openai_models::ReasoningEffort; use insta::assert_snapshot; use ratatui::prelude::*; use std::path::PathBuf;