Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions codex-rs/core/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,34 @@ pub(crate) fn resolve_multi_agent_version(
})
}

fn validate_model_reasoning_effort(
model_info: &ModelInfo,
configured_effort: Option<&ReasoningEffortConfig>,
) -> Result<(), String> {
let Some(configured_effort) = configured_effort else {
return Ok(());
};
if model_info.supported_reasoning_levels.is_empty()
|| model_info
.supported_reasoning_levels
.iter()
.any(|preset| &preset.effort == configured_effort)
{
return Ok(());
}

let supported_efforts = model_info
.supported_reasoning_levels
.iter()
.map(|preset| preset.effort.to_string())
.collect::<Vec<_>>()
.join(", ");
Err(format!(
"model_reasoning_effort `{configured_effort}` is not supported for model `{}`. Supported values: {supported_efforts}",
model_info.slug
))
}

pub(crate) const INITIAL_SUBMIT_ID: &str = "";
pub(crate) const SUBMISSION_CHANNEL_CAPACITY: usize = 512;
const CYBER_VERIFY_URL: &str = "https://chatgpt.com/cyber";
Expand Down Expand Up @@ -595,6 +623,8 @@ impl Codex {
let model_info = models_manager
.get_model_info(model.as_str(), &models_manager_config)
.await;
validate_model_reasoning_effort(&model_info, config.model_reasoning_effort.as_ref())
.map_err(CodexErr::InvalidRequest)?;
let multi_agent_version =
resolve_multi_agent_version(&conversation_history, inherited_multi_agent_version);
config
Expand Down
80 changes: 80 additions & 0 deletions codex-rs/core/src/session/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ use codex_protocol::models::FunctionCallOutputPayload;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::SandboxEnforcement;
use codex_protocol::openai_models::ModelServiceTier;
use codex_protocol::openai_models::ReasoningEffortPreset;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
Expand Down Expand Up @@ -4442,6 +4443,85 @@ fn model_with_default_service_tier(default_service_tier: Option<&str>) -> ModelI
model_info
}

fn model_with_supported_reasoning_levels(
supported_reasoning_levels: Vec<ReasoningEffortConfig>,
) -> ModelInfo {
let mut model_info = model_info::model_info_from_slug("gpt-5.4");
model_info.supported_reasoning_levels = supported_reasoning_levels
.into_iter()
.map(|effort| ReasoningEffortPreset {
description: format!("{effort} reasoning"),
effort,
})
.collect();
model_info
}

#[test]
fn model_reasoning_effort_rejects_unadvertised_custom_value() {
let model_info = model_with_supported_reasoning_levels(vec![
ReasoningEffortConfig::Low,
ReasoningEffortConfig::High,
]);

assert_eq!(
validate_model_reasoning_effort(
&model_info,
Some(&ReasoningEffortConfig::Custom("unknown".to_string())),
),
Err(
"model_reasoning_effort `unknown` is not supported for model `gpt-5.4`. Supported values: low, high"
.to_string()
)
);
}

#[test]
fn model_reasoning_effort_accepts_ultra_alias_when_max_is_advertised() {
let model_info = model_with_supported_reasoning_levels(vec![ReasoningEffortConfig::Custom(
"max".to_string(),
)]);
let ultra = "ultra"
.parse::<ReasoningEffortConfig>()
.expect("ultra should parse as the max alias");

assert_eq!(
validate_model_reasoning_effort(&model_info, Some(&ultra)),
Ok(())
);
}

#[test]
fn model_reasoning_effort_rejects_ultra_alias_when_max_is_not_advertised() {
let model_info = model_with_supported_reasoning_levels(vec![ReasoningEffortConfig::XHigh]);
let ultra = "ultra"
.parse::<ReasoningEffortConfig>()
.expect("ultra should parse as the max alias");

assert_eq!(
validate_model_reasoning_effort(&model_info, Some(&ultra)),
Err(
"model_reasoning_effort `max` is not supported for model `gpt-5.4`. Supported values: xhigh"
.to_string()
)
);
}

#[test]
fn model_reasoning_effort_allows_custom_value_without_model_metadata() {
let model_info = model_with_supported_reasoning_levels(Vec::new());

assert_eq!(
validate_model_reasoning_effort(
&model_info,
Some(&ReasoningEffortConfig::Custom(
"provider-defined".to_string()
)),
),
Ok(())
);
}

#[test]
fn get_service_tier_does_not_use_model_default_when_absent_and_fast_mode_enabled() {
let model_info = model_with_default_service_tier(Some(ServiceTier::Fast.request_value()));
Expand Down
Loading