feat: support models with single reasoning effort (#6300)
This commit is contained in:
committed by
GitHub
parent
63e1ef25af
commit
667e841d3e
@@ -1544,7 +1544,8 @@ impl CodexMessageProcessor {
|
|||||||
|
|
||||||
async fn list_models(&self, request_id: RequestId, params: ModelListParams) {
|
async fn list_models(&self, request_id: RequestId, params: ModelListParams) {
|
||||||
let ModelListParams { limit, cursor } = params;
|
let ModelListParams { limit, cursor } = params;
|
||||||
let models = supported_models();
|
let auth_mode = self.auth_manager.auth().map(|auth| auth.mode);
|
||||||
|
let models = supported_models(auth_mode);
|
||||||
let total = models.len();
|
let total = models.len();
|
||||||
|
|
||||||
if total == 0 {
|
if total == 0 {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
use codex_app_server_protocol::AuthMode;
|
||||||
use codex_app_server_protocol::Model;
|
use codex_app_server_protocol::Model;
|
||||||
use codex_app_server_protocol::ReasoningEffortOption;
|
use codex_app_server_protocol::ReasoningEffortOption;
|
||||||
use codex_common::model_presets::ModelPreset;
|
use codex_common::model_presets::ModelPreset;
|
||||||
use codex_common::model_presets::ReasoningEffortPreset;
|
use codex_common::model_presets::ReasoningEffortPreset;
|
||||||
use codex_common::model_presets::builtin_model_presets;
|
use codex_common::model_presets::builtin_model_presets;
|
||||||
|
|
||||||
pub fn supported_models() -> Vec<Model> {
|
pub fn supported_models(auth_mode: Option<AuthMode>) -> Vec<Model> {
|
||||||
builtin_model_presets(None)
|
builtin_model_presets(auth_mode)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(model_from_preset)
|
.map(model_from_preset)
|
||||||
.collect()
|
.collect()
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ async fn list_models_returns_all_models_with_large_limit() -> Result<()> {
|
|||||||
id: "gpt-5-codex".to_string(),
|
id: "gpt-5-codex".to_string(),
|
||||||
model: "gpt-5-codex".to_string(),
|
model: "gpt-5-codex".to_string(),
|
||||||
display_name: "gpt-5-codex".to_string(),
|
display_name: "gpt-5-codex".to_string(),
|
||||||
description: "Optimized for coding tasks with many tools.".to_string(),
|
description: "Optimized for codex.".to_string(),
|
||||||
supported_reasoning_efforts: vec![
|
supported_reasoning_efforts: vec![
|
||||||
ReasoningEffortOption {
|
ReasoningEffortOption {
|
||||||
reasoning_effort: ReasoningEffort::Low,
|
reasoning_effort: ReasoningEffort::Low,
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const PRESETS: &[ModelPreset] = &[
|
|||||||
id: "gpt-5-codex",
|
id: "gpt-5-codex",
|
||||||
model: "gpt-5-codex",
|
model: "gpt-5-codex",
|
||||||
display_name: "gpt-5-codex",
|
display_name: "gpt-5-codex",
|
||||||
description: "Optimized for coding tasks with many tools.",
|
description: "Optimized for codex.",
|
||||||
default_reasoning_effort: ReasoningEffort::Medium,
|
default_reasoning_effort: ReasoningEffort::Medium,
|
||||||
supported_reasoning_efforts: &[
|
supported_reasoning_efforts: &[
|
||||||
ReasoningEffortPreset {
|
ReasoningEffortPreset {
|
||||||
@@ -52,6 +52,24 @@ const PRESETS: &[ModelPreset] = &[
|
|||||||
],
|
],
|
||||||
is_default: true,
|
is_default: true,
|
||||||
},
|
},
|
||||||
|
ModelPreset {
|
||||||
|
id: "desertfox",
|
||||||
|
model: "desertfox",
|
||||||
|
display_name: "desertfox",
|
||||||
|
description: "???",
|
||||||
|
default_reasoning_effort: ReasoningEffort::Medium,
|
||||||
|
supported_reasoning_efforts: &[
|
||||||
|
ReasoningEffortPreset {
|
||||||
|
effort: ReasoningEffort::Medium,
|
||||||
|
description: "Dynamically adjusts reasoning based on the task",
|
||||||
|
},
|
||||||
|
ReasoningEffortPreset {
|
||||||
|
effort: ReasoningEffort::High,
|
||||||
|
description: "Maximizes reasoning depth for complex or ambiguous problems",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
is_default: false,
|
||||||
|
},
|
||||||
ModelPreset {
|
ModelPreset {
|
||||||
id: "gpt-5",
|
id: "gpt-5",
|
||||||
model: "gpt-5",
|
model: "gpt-5",
|
||||||
@@ -80,8 +98,13 @@ const PRESETS: &[ModelPreset] = &[
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn builtin_model_presets(_auth_mode: Option<AuthMode>) -> Vec<ModelPreset> {
|
pub fn builtin_model_presets(auth_mode: Option<AuthMode>) -> Vec<ModelPreset> {
|
||||||
PRESETS.to_vec()
|
let allow_desertfox = matches!(auth_mode, Some(AuthMode::ChatGPT));
|
||||||
|
PRESETS
|
||||||
|
.iter()
|
||||||
|
.filter(|preset| allow_desertfox || preset.id != "desertfox")
|
||||||
|
.copied()
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -1798,6 +1798,7 @@ impl ChatWidget {
|
|||||||
};
|
};
|
||||||
let is_current = preset.model == current_model;
|
let is_current = preset.model == current_model;
|
||||||
let preset_for_action = preset;
|
let preset_for_action = preset;
|
||||||
|
let single_supported_effort = preset_for_action.supported_reasoning_efforts.len() == 1;
|
||||||
let actions: Vec<SelectionAction> = vec![Box::new(move |tx| {
|
let actions: Vec<SelectionAction> = vec![Box::new(move |tx| {
|
||||||
tx.send(AppEvent::OpenReasoningPopup {
|
tx.send(AppEvent::OpenReasoningPopup {
|
||||||
model: preset_for_action,
|
model: preset_for_action,
|
||||||
@@ -1808,7 +1809,7 @@ impl ChatWidget {
|
|||||||
description,
|
description,
|
||||||
is_current,
|
is_current,
|
||||||
actions,
|
actions,
|
||||||
dismiss_on_select: false,
|
dismiss_on_select: single_supported_effort,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1847,6 +1848,15 @@ 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));
|
||||||
|
} else {
|
||||||
|
self.apply_model_and_effort(preset.model.to_string(), None);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let default_choice: Option<ReasoningEffortConfig> = choices
|
let default_choice: Option<ReasoningEffortConfig> = choices
|
||||||
.iter()
|
.iter()
|
||||||
.any(|choice| choice.stored == Some(default_effort))
|
.any(|choice| choice.stored == Some(default_effort))
|
||||||
@@ -1885,7 +1895,7 @@ impl ChatWidget {
|
|||||||
|
|
||||||
let warning = "⚠ High reasoning effort can quickly consume Plus plan rate limits.";
|
let warning = "⚠ High reasoning effort can quickly consume Plus plan rate limits.";
|
||||||
let show_warning =
|
let show_warning =
|
||||||
preset.model == "gpt-5-codex" && effort == ReasoningEffortConfig::High;
|
preset.model.starts_with("gpt-5-codex") && effort == ReasoningEffortConfig::High;
|
||||||
let selected_description = show_warning.then(|| {
|
let selected_description = show_warning.then(|| {
|
||||||
description
|
description
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -1942,6 +1952,32 @@ impl ChatWidget {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn apply_model_and_effort(&self, model: String, effort: Option<ReasoningEffortConfig>) {
|
||||||
|
self.app_event_tx
|
||||||
|
.send(AppEvent::CodexOp(Op::OverrideTurnContext {
|
||||||
|
cwd: None,
|
||||||
|
approval_policy: None,
|
||||||
|
sandbox_policy: None,
|
||||||
|
model: Some(model.clone()),
|
||||||
|
effort: Some(effort),
|
||||||
|
summary: None,
|
||||||
|
}));
|
||||||
|
self.app_event_tx.send(AppEvent::UpdateModel(model.clone()));
|
||||||
|
self.app_event_tx
|
||||||
|
.send(AppEvent::UpdateReasoningEffort(effort));
|
||||||
|
self.app_event_tx.send(AppEvent::PersistModelSelection {
|
||||||
|
model: model.clone(),
|
||||||
|
effort,
|
||||||
|
});
|
||||||
|
tracing::info!(
|
||||||
|
"Selected model: {}, Selected effort: {}",
|
||||||
|
model,
|
||||||
|
effort
|
||||||
|
.map(|e| e.to_string())
|
||||||
|
.unwrap_or_else(|| "default".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Open a popup to choose the approvals mode (ask for approval policy + sandbox policy).
|
/// Open a popup to choose the approvals mode (ask for approval policy + sandbox policy).
|
||||||
pub(crate) fn open_approvals_popup(&mut self) {
|
pub(crate) fn open_approvals_popup(&mut self) {
|
||||||
let current_approval = self.config.approval_policy;
|
let current_approval = self.config.approval_policy;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ expression: popup
|
|||||||
Select Model and Effort
|
Select Model and Effort
|
||||||
Switch the model for this and future Codex CLI sessions
|
Switch the model for this and future Codex CLI sessions
|
||||||
|
|
||||||
› 1. gpt-5-codex (current) Optimized for coding tasks with many tools.
|
› 1. gpt-5-codex (current) Optimized for codex.
|
||||||
2. gpt-5 Broad world knowledge with strong general
|
2. gpt-5 Broad world knowledge with strong general
|
||||||
reasoning.
|
reasoning.
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ expression: popup
|
|||||||
Approaching rate limits
|
Approaching rate limits
|
||||||
You've used over 90% of your limit. Switch to gpt-5-codex for lower credit u
|
You've used over 90% of your limit. Switch to gpt-5-codex for lower credit u
|
||||||
|
|
||||||
› 1. Switch to gpt-5-codex Optimized for coding tasks with many tools.
|
› 1. Switch to gpt-5-codex Optimized for codex.
|
||||||
2. Keep current model
|
2. Keep current model
|
||||||
|
|
||||||
Press enter to confirm or esc to go back
|
Press enter to confirm or esc to go back
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ use crate::test_backend::VT100Backend;
|
|||||||
use crate::tui::FrameRequester;
|
use crate::tui::FrameRequester;
|
||||||
use assert_matches::assert_matches;
|
use assert_matches::assert_matches;
|
||||||
use codex_common::approval_presets::builtin_approval_presets;
|
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::AuthManager;
|
||||||
use codex_core::CodexAuth;
|
use codex_core::CodexAuth;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
@@ -1491,6 +1493,44 @@ fn model_reasoning_selection_popup_snapshot() {
|
|||||||
assert_snapshot!("model_reasoning_selection_popup", popup);
|
assert_snapshot!("model_reasoning_selection_popup", popup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_reasoning_option_skips_selection() {
|
||||||
|
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual();
|
||||||
|
|
||||||
|
static SINGLE_EFFORT: [ReasoningEffortPreset; 1] = [ReasoningEffortPreset {
|
||||||
|
effort: ReasoningEffortConfig::High,
|
||||||
|
description: "Maximizes reasoning depth for complex or ambiguous problems",
|
||||||
|
}];
|
||||||
|
let preset = ModelPreset {
|
||||||
|
id: "model-with-single-reasoning",
|
||||||
|
model: "model-with-single-reasoning",
|
||||||
|
display_name: "model-with-single-reasoning",
|
||||||
|
description: "",
|
||||||
|
default_reasoning_effort: ReasoningEffortConfig::High,
|
||||||
|
supported_reasoning_efforts: &SINGLE_EFFORT,
|
||||||
|
is_default: false,
|
||||||
|
};
|
||||||
|
chat.open_reasoning_popup(preset);
|
||||||
|
|
||||||
|
let popup = render_bottom_popup(&chat, 80);
|
||||||
|
assert!(
|
||||||
|
!popup.contains("Select Reasoning Level"),
|
||||||
|
"expected reasoning selection popup to be skipped"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut events = Vec::new();
|
||||||
|
while let Ok(ev) = rx.try_recv() {
|
||||||
|
events.push(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
events
|
||||||
|
.iter()
|
||||||
|
.any(|ev| matches!(ev, AppEvent::UpdateReasoningEffort(Some(effort)) if *effort == ReasoningEffortConfig::High)),
|
||||||
|
"expected reasoning effort to be applied automatically; events: {events:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn feedback_selection_popup_snapshot() {
|
fn feedback_selection_popup_snapshot() {
|
||||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual();
|
let (mut chat, _rx, _op_rx) = make_chatwidget_manual();
|
||||||
|
|||||||
Reference in New Issue
Block a user