Reasoning level update (#6586)

Automatically update reasoning levels when migrating between models
This commit is contained in:
pakrym-oai authored and GitHub committed 2025-11-13 06:24:36 +00:00
1 parent 305fe73d83
commit e3dd362c94
11 files changed
+234 -190

No files matched your search

+1
View File
@@ -1048,6 +1048,7 @@ dependencies = [
"codex-app-server-protocol", "codex-app-server-protocol",
"codex-core", "codex-core",
"codex-protocol", "codex-protocol",
"once_cell",
"serde", "serde",
"toml", "toml",
] ]
+3 -2
View File
@@ -122,8 +122,8 @@ eventsource-stream = "0.2.3"
futures = { version = "0.3", default-features = false } futures = { version = "0.3", default-features = false }
http = "1.3.1" http = "1.3.1"
icu_decimal = "2.1" icu_decimal = "2.1"
icu_provider = { version = "2.1", features = ["sync"] }
icu_locale_core = "2.1" icu_locale_core = "2.1"
icu_provider = { version = "2.1", features = ["sync"] }
ignore = "0.4.23" ignore = "0.4.23"
image = { version = "^0.25.8", default-features = false } image = { version = "^0.25.8", default-features = false }
indexmap = "2.12.0" indexmap = "2.12.0"
@@ -140,6 +140,7 @@ mime_guess = "2.0.5"
multimap = "0.10.0" multimap = "0.10.0"
notify = "8.2.0" notify = "8.2.0"
nucleo-matcher = "0.3.1" nucleo-matcher = "0.3.1"
once_cell = "1"
openssl-sys = "*" openssl-sys = "*"
opentelemetry = "0.30.0" opentelemetry = "0.30.0"
opentelemetry-appender-tracing = "0.30.0" opentelemetry-appender-tracing = "0.30.0"
@@ -278,8 +279,8 @@ opt-level = 0
[patch.crates-io] [patch.crates-io]
# Uncomment to debug local changes. # Uncomment to debug local changes.
# ratatui = { path = "../../ratatui" } # ratatui = { path = "../../ratatui" }
ratatui = { git = "https://github.com/nornagon/ratatui", branch = "nornagon-v0.29.0-patch" }
crossterm = { git = "https://github.com/nornagon/crossterm", branch = "nornagon/color-query" } crossterm = { git = "https://github.com/nornagon/crossterm", branch = "nornagon/color-query" }
ratatui = { git = "https://github.com/nornagon/ratatui", branch = "nornagon-v0.29.0-patch" }
# Uncomment to debug local changes. # Uncomment to debug local changes.
# rmcp = { path = "../../rust-sdk/crates/rmcp" } # rmcp = { path = "../../rust-sdk/crates/rmcp" }
@@ -93,10 +93,6 @@ async fn list_models_returns_all_models_with_large_limit() -> Result<()> {
display_name: "gpt-5.1".to_string(), display_name: "gpt-5.1".to_string(),
description: "Broad world knowledge with strong general reasoning.".to_string(), description: "Broad world knowledge with strong general reasoning.".to_string(),
supported_reasoning_efforts: vec![ supported_reasoning_efforts: vec![
ReasoningEffortOption {
reasoning_effort: ReasoningEffort::Minimal,
description: "Fastest responses with little reasoning".to_string(),
},
ReasoningEffortOption { ReasoningEffortOption {
reasoning_effort: ReasoningEffort::Low, reasoning_effort: ReasoningEffort::Low,
description: "Balances speed with some reasoning; useful for straightforward \ description: "Balances speed with some reasoning; useful for straightforward \
+2 -1
View File
@@ -8,9 +8,10 @@ workspace = true
[dependencies] [dependencies]
clap = { workspace = true, features = ["derive", "wrap_help"], optional = true } clap = { workspace = true, features = ["derive", "wrap_help"], optional = true }
codex-app-server-protocol = { workspace = true }
codex-core = { workspace = true } codex-core = { workspace = true }
codex-protocol = { workspace = true } codex-protocol = { workspace = true }
codex-app-server-protocol = { workspace = true } once_cell = { workspace = true }
serde = { workspace = true, optional = true } serde = { workspace = true, optional = true }
toml = { workspace = true, optional = true } toml = { workspace = true, optional = true }
+166 -147
View File
@@ -1,5 +1,8 @@
use std::collections::HashMap;
use codex_app_server_protocol::AuthMode; use codex_app_server_protocol::AuthMode;
use codex_core::protocol_config_types::ReasoningEffort; use codex_core::protocol_config_types::ReasoningEffort;
use once_cell::sync::Lazy;
/// A reasoning effort option that can be surfaced for a model. /// A reasoning effort option that can be surfaced for a model.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@@ -10,8 +13,14 @@ pub struct ReasoningEffortPreset {
pub description: &'static str, pub description: &'static str,
} }
#[derive(Debug, Clone)]
pub struct ModelUpgrade {
pub id: &'static str,
pub reasoning_effort_mapping: Option<HashMap<ReasoningEffort, ReasoningEffort>>,
}
/// Metadata describing a Codex-supported model. /// Metadata describing a Codex-supported model.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone)]
pub struct ModelPreset { pub struct ModelPreset {
/// Stable identifier for the preset. /// Stable identifier for the preset.
pub id: &'static str, pub id: &'static str,
@@ -28,162 +37,172 @@ pub struct ModelPreset {
/// Whether this is the default model for new users. /// Whether this is the default model for new users.
pub is_default: bool, pub is_default: bool,
/// recommended upgrade model /// recommended upgrade model
pub recommended_upgrade_model: Option<&'static str>, pub upgrade: Option<ModelUpgrade>,
} }
const PRESETS: &[ModelPreset] = &[ static PRESETS: Lazy<Vec<ModelPreset>> = Lazy::new(|| {
ModelPreset { vec![
id: "gpt-5.1-codex", ModelPreset {
model: "gpt-5.1-codex", id: "gpt-5.1-codex",
display_name: "gpt-5.1-codex", model: "gpt-5.1-codex",
description: "Optimized for codex.", display_name: "gpt-5.1-codex",
default_reasoning_effort: ReasoningEffort::Medium, description: "Optimized for codex.",
supported_reasoning_efforts: &[ default_reasoning_effort: ReasoningEffort::Medium,
ReasoningEffortPreset { supported_reasoning_efforts: &[
effort: ReasoningEffort::Low, ReasoningEffortPreset {
description: "Fastest responses with limited reasoning", effort: ReasoningEffort::Low,
}, description: "Fastest responses with limited reasoning",
ReasoningEffortPreset { },
effort: ReasoningEffort::Medium, ReasoningEffortPreset {
description: "Dynamically adjusts reasoning based on the task", effort: ReasoningEffort::Medium,
}, description: "Dynamically adjusts reasoning based on the task",
ReasoningEffortPreset { },
effort: ReasoningEffort::High, ReasoningEffortPreset {
description: "Maximizes reasoning depth for complex or ambiguous problems", effort: ReasoningEffort::High,
}, description: "Maximizes reasoning depth for complex or ambiguous problems",
], },
is_default: true, ],
recommended_upgrade_model: None, is_default: true,
}, upgrade: None,
ModelPreset { },
id: "gpt-5.1-codex-mini", ModelPreset {
model: "gpt-5.1-codex-mini", id: "gpt-5.1-codex-mini",
display_name: "gpt-5.1-codex-mini", model: "gpt-5.1-codex-mini",
description: "Optimized for codex. Cheaper, faster, but less capable.", display_name: "gpt-5.1-codex-mini",
default_reasoning_effort: ReasoningEffort::Medium, description: "Optimized for codex. Cheaper, faster, but less capable.",
supported_reasoning_efforts: &[ default_reasoning_effort: ReasoningEffort::Medium,
ReasoningEffortPreset { supported_reasoning_efforts: &[
effort: ReasoningEffort::Medium, ReasoningEffortPreset {
description: "Dynamically adjusts reasoning based on the task", effort: ReasoningEffort::Medium,
}, description: "Dynamically adjusts reasoning based on the task",
ReasoningEffortPreset { },
effort: ReasoningEffort::High, ReasoningEffortPreset {
description: "Maximizes reasoning depth for complex or ambiguous problems", effort: ReasoningEffort::High,
}, description: "Maximizes reasoning depth for complex or ambiguous problems",
], },
is_default: false, ],
recommended_upgrade_model: None, is_default: false,
}, upgrade: None,
ModelPreset { },
id: "gpt-5.1", ModelPreset {
model: "gpt-5.1", id: "gpt-5.1",
display_name: "gpt-5.1", model: "gpt-5.1",
description: "Broad world knowledge with strong general reasoning.", display_name: "gpt-5.1",
default_reasoning_effort: ReasoningEffort::Medium, description: "Broad world knowledge with strong general reasoning.",
supported_reasoning_efforts: &[ default_reasoning_effort: ReasoningEffort::Medium,
ReasoningEffortPreset { supported_reasoning_efforts: &[
effort: ReasoningEffort::Minimal, ReasoningEffortPreset {
description: "Fastest responses with little reasoning", effort: ReasoningEffort::Low,
}, description: "Balances speed with some reasoning; useful for straightforward queries and short explanations",
ReasoningEffortPreset { },
effort: ReasoningEffort::Low, ReasoningEffortPreset {
description: "Balances speed with some reasoning; useful for straightforward queries and short explanations", effort: ReasoningEffort::Medium,
}, description: "Provides a solid balance of reasoning depth and latency for general-purpose tasks",
ReasoningEffortPreset { },
effort: ReasoningEffort::Medium, ReasoningEffortPreset {
description: "Provides a solid balance of reasoning depth and latency for general-purpose tasks", effort: ReasoningEffort::High,
}, description: "Maximizes reasoning depth for complex or ambiguous problems",
ReasoningEffortPreset { },
effort: ReasoningEffort::High, ],
description: "Maximizes reasoning depth for complex or ambiguous problems", is_default: false,
}, upgrade: None,
], },
is_default: false, // Deprecated models.
recommended_upgrade_model: None, ModelPreset {
}, id: "gpt-5-codex",
// Deprecated models. model: "gpt-5-codex",
ModelPreset { display_name: "gpt-5-codex",
id: "gpt-5-codex", description: "Optimized for codex.",
model: "gpt-5-codex", default_reasoning_effort: ReasoningEffort::Medium,
display_name: "gpt-5-codex", supported_reasoning_efforts: &[
description: "Optimized for codex.", ReasoningEffortPreset {
default_reasoning_effort: ReasoningEffort::Medium, effort: ReasoningEffort::Low,
supported_reasoning_efforts: &[ description: "Fastest responses with limited reasoning",
ReasoningEffortPreset { },
effort: ReasoningEffort::Low, ReasoningEffortPreset {
description: "Fastest responses with limited reasoning", effort: ReasoningEffort::Medium,
}, description: "Dynamically adjusts reasoning based on the task",
ReasoningEffortPreset { },
effort: ReasoningEffort::Medium, ReasoningEffortPreset {
description: "Dynamically adjusts reasoning based on the task", effort: ReasoningEffort::High,
}, description: "Maximizes reasoning depth for complex or ambiguous problems",
ReasoningEffortPreset { },
effort: ReasoningEffort::High, ],
description: "Maximizes reasoning depth for complex or ambiguous problems", is_default: false,
}, upgrade: Some(ModelUpgrade {
], id: "gpt-5.1-codex",
is_default: false, reasoning_effort_mapping: None,
recommended_upgrade_model: Some("gpt-5.1-codex"), }),
}, },
ModelPreset { ModelPreset {
id: "gpt-5-codex-mini", id: "gpt-5-codex-mini",
model: "gpt-5-codex-mini", model: "gpt-5-codex-mini",
display_name: "gpt-5-codex-mini", display_name: "gpt-5-codex-mini",
description: "Optimized for codex. Cheaper, faster, but less capable.", description: "Optimized for codex. Cheaper, faster, but less capable.",
default_reasoning_effort: ReasoningEffort::Medium, default_reasoning_effort: ReasoningEffort::Medium,
supported_reasoning_efforts: &[ supported_reasoning_efforts: &[
ReasoningEffortPreset { ReasoningEffortPreset {
effort: ReasoningEffort::Medium, effort: ReasoningEffort::Medium,
description: "Dynamically adjusts reasoning based on the task", description: "Dynamically adjusts reasoning based on the task",
}, },
ReasoningEffortPreset { ReasoningEffortPreset {
effort: ReasoningEffort::High, effort: ReasoningEffort::High,
description: "Maximizes reasoning depth for complex or ambiguous problems", description: "Maximizes reasoning depth for complex or ambiguous problems",
}, },
], ],
is_default: false, is_default: false,
recommended_upgrade_model: Some("gpt-5.1-codex-mini"), upgrade: Some(ModelUpgrade {
}, id: "gpt-5.1-codex-mini",
ModelPreset { reasoning_effort_mapping: None,
id: "gpt-5", }),
model: "gpt-5", },
display_name: "gpt-5", ModelPreset {
description: "Broad world knowledge with strong general reasoning.", id: "gpt-5",
default_reasoning_effort: ReasoningEffort::Medium, model: "gpt-5",
supported_reasoning_efforts: &[ display_name: "gpt-5",
ReasoningEffortPreset { description: "Broad world knowledge with strong general reasoning.",
effort: ReasoningEffort::Minimal, default_reasoning_effort: ReasoningEffort::Medium,
description: "Fastest responses with little reasoning", supported_reasoning_efforts: &[
}, ReasoningEffortPreset {
ReasoningEffortPreset { effort: ReasoningEffort::Minimal,
effort: ReasoningEffort::Low, description: "Fastest responses with little reasoning",
description: "Balances speed with some reasoning; useful for straightforward queries and short explanations", },
}, ReasoningEffortPreset {
ReasoningEffortPreset { effort: ReasoningEffort::Low,
effort: ReasoningEffort::Medium, description: "Balances speed with some reasoning; useful for straightforward queries and short explanations",
description: "Provides a solid balance of reasoning depth and latency for general-purpose tasks", },
}, ReasoningEffortPreset {
ReasoningEffortPreset { effort: ReasoningEffort::Medium,
effort: ReasoningEffort::High, description: "Provides a solid balance of reasoning depth and latency for general-purpose tasks",
description: "Maximizes reasoning depth for complex or ambiguous problems", },
}, ReasoningEffortPreset {
], effort: ReasoningEffort::High,
is_default: false, description: "Maximizes reasoning depth for complex or ambiguous problems",
recommended_upgrade_model: Some("gpt-5.1"), },
}, ],
]; is_default: false,
upgrade: Some(ModelUpgrade {
id: "gpt-5.1",
reasoning_effort_mapping: Some(HashMap::from([(
ReasoningEffort::Minimal,
ReasoningEffort::Low,
)])),
}),
},
]
});
pub fn builtin_model_presets(_auth_mode: Option<AuthMode>) -> Vec<ModelPreset> { pub fn builtin_model_presets(_auth_mode: Option<AuthMode>) -> Vec<ModelPreset> {
// leave auth mode for later use // leave auth mode for later use
PRESETS PRESETS
.iter() .iter()
.filter(|preset| preset.recommended_upgrade_model.is_none()) .filter(|preset| preset.upgrade.is_none())
.copied() .cloned()
.collect() .collect()
} }
pub fn all_model_presets() -> &'static [ModelPreset] { pub fn all_model_presets() -> &'static Vec<ModelPreset> {
PRESETS &PRESETS
} }
#[cfg(test)] #[cfg(test)]
+2
View File
@@ -19,10 +19,12 @@ use ts_rs::TS;
JsonSchema, JsonSchema,
TS, TS,
EnumIter, EnumIter,
Hash,
)] )]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")] #[strum(serialize_all = "lowercase")]
pub enum ReasoningEffort { pub enum ReasoningEffort {
None,
Minimal, Minimal,
Low, Low,
#[default] #[default]
+51 -29
View File
@@ -17,6 +17,7 @@ use crate::tui;
use crate::tui::TuiEvent; use crate::tui::TuiEvent;
use crate::update_action::UpdateAction; use crate::update_action::UpdateAction;
use codex_ansi_escape::ansi_escape_line; use codex_ansi_escape::ansi_escape_line;
use codex_common::model_presets::ModelUpgrade;
use codex_common::model_presets::all_model_presets; use codex_common::model_presets::all_model_presets;
use codex_core::AuthManager; use codex_core::AuthManager;
use codex_core::ConversationManager; use codex_core::ConversationManager;
@@ -64,7 +65,7 @@ fn should_show_model_migration_prompt(
all_model_presets() all_model_presets()
.iter() .iter()
.filter(|preset| preset.recommended_upgrade_model.is_some()) .filter(|preset| preset.upgrade.is_some())
.any(|preset| preset.model == current_model) .any(|preset| preset.model == current_model)
} }
@@ -73,38 +74,59 @@ async fn handle_model_migration_prompt_if_needed(
config: &mut Config, config: &mut Config,
app_event_tx: &AppEventSender, app_event_tx: &AppEventSender,
) -> Option<AppExitInfo> { ) -> Option<AppExitInfo> {
let target_model = all_model_presets() let upgrade = all_model_presets()
.iter() .iter()
.find(|preset| preset.model == config.model) .find(|preset| preset.model == config.model)
.and_then(|preset| preset.recommended_upgrade_model) .and_then(|preset| preset.upgrade.as_ref());
.unwrap_or(&config.model)
.to_string();
let hide_prompt_flag = config.notices.hide_gpt5_1_migration_prompt;
if !should_show_model_migration_prompt(&config.model, &target_model, hide_prompt_flag) {
return None;
}
match run_model_migration_prompt(tui).await { if let Some(ModelUpgrade {
ModelMigrationOutcome::Accepted => { id: target_model,
app_event_tx.send(AppEvent::PersistModelMigrationPromptAcknowledged { reasoning_effort_mapping,
migration_config: "hide_gpt5_1_migration_prompt".to_string(), }) = upgrade
}); {
config.model = target_model.clone(); let target_model = target_model.to_string();
if let Some(family) = find_family_for_model(&target_model) { let hide_prompt_flag = config.notices.hide_gpt5_1_migration_prompt;
config.model_family = family; if !should_show_model_migration_prompt(&config.model, &target_model, hide_prompt_flag) {
} return None;
app_event_tx.send(AppEvent::UpdateModel(target_model.clone()));
app_event_tx.send(AppEvent::PersistModelSelection {
model: target_model,
effort: config.model_reasoning_effort,
});
} }
ModelMigrationOutcome::Exit => {
return Some(AppExitInfo { match run_model_migration_prompt(tui).await {
token_usage: TokenUsage::default(), ModelMigrationOutcome::Accepted => {
conversation_id: None, app_event_tx.send(AppEvent::PersistModelMigrationPromptAcknowledged {
update_action: None, migration_config: "hide_gpt5_1_migration_prompt".to_string(),
}); });
config.model = target_model.to_string();
if let Some(family) = find_family_for_model(&target_model) {
config.model_family = family;
}
let mapped_effort = if let Some(reasoning_effort_mapping) = reasoning_effort_mapping
&& let Some(reasoning_effort) = config.model_reasoning_effort
{
reasoning_effort_mapping
.get(&reasoning_effort)
.cloned()
.or(config.model_reasoning_effort)
} else {
config.model_reasoning_effort
};
config.model_reasoning_effort = mapped_effort;
app_event_tx.send(AppEvent::UpdateModel(target_model.clone()));
app_event_tx.send(AppEvent::UpdateReasoningEffort(mapped_effort));
app_event_tx.send(AppEvent::PersistModelSelection {
model: target_model.clone(),
effort: mapped_effort,
});
}
ModelMigrationOutcome::Exit => {
return Some(AppExitInfo {
token_usage: TokenUsage::default(),
conversation_id: None,
update_action: None,
});
}
} }
} }
+4 -3
View File
@@ -1836,11 +1836,12 @@ impl ChatWidget {
Some(preset.description.to_string()) Some(preset.description.to_string())
}; };
let is_current = preset.model == current_model; let is_current = preset.model == current_model;
let preset_for_action = preset; let single_supported_effort = preset.supported_reasoning_efforts.len() == 1;
let single_supported_effort = preset_for_action.supported_reasoning_efforts.len() == 1; let preset_for_action = preset.clone();
let actions: Vec<SelectionAction> = vec![Box::new(move |tx| { let actions: Vec<SelectionAction> = vec![Box::new(move |tx| {
let preset_for_event = preset_for_action.clone();
tx.send(AppEvent::OpenReasoningPopup { tx.send(AppEvent::OpenReasoningPopup {
model: preset_for_action, model: preset_for_event,
}); });
})]; })];
items.push(SelectionItem { items.push(SelectionItem {
+1 -1
View File
@@ -1533,7 +1533,7 @@ fn single_reasoning_option_skips_selection() {
default_reasoning_effort: ReasoningEffortConfig::High, default_reasoning_effort: ReasoningEffortConfig::High,
supported_reasoning_efforts: &SINGLE_EFFORT, supported_reasoning_efforts: &SINGLE_EFFORT,
is_default: false, is_default: false,
recommended_upgrade_model: None, upgrade: None,
}; };
chat.open_reasoning_popup(preset); chat.open_reasoning_popup(preset);
+1
View File
@@ -712,6 +712,7 @@ impl SessionHeaderHistoryCell {
ReasoningEffortConfig::Low => "low", ReasoningEffortConfig::Low => "low",
ReasoningEffortConfig::Medium => "medium", ReasoningEffortConfig::Medium => "medium",
ReasoningEffortConfig::High => "high", ReasoningEffortConfig::High => "high",
ReasoningEffortConfig::None => "none",
}) })
} }
} }
+3 -3
View File
@@ -1,14 +1,14 @@
[package] [package]
name = "codex-git"
version.workspace = true
edition.workspace = true edition.workspace = true
name = "codex-git"
readme = "README.md" readme = "README.md"
version.workspace = true
[lints] [lints]
workspace = true workspace = true
[dependencies] [dependencies]
once_cell = "1" once_cell = { workspace = true }
regex = "1" regex = "1"
schemars = { workspace = true } schemars = { workspace = true }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }