From cccf6694f624b95a16c59bf04d5b498920d3d6d9 Mon Sep 17 00:00:00 2001 From: Andrei Hasna Date: Thu, 23 Jul 2026 21:25:51 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20Fix=20gpt-5.6-sol=20'ultra'=20reasoning?= =?UTF-8?q?=20effort:=20API=20rejects=20effort=3D'ultra'=20(400)=20?= =?UTF-8?q?=E2=80=94=20map=20to=20a=20valid=20value?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codex-rs/core/src/client.rs | 19 +++++- codex-rs/core/tests/suite/remote_models.rs | 75 ++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index e340a44434..8d005277c6 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -705,6 +705,7 @@ impl ModelClient { let client = ApiMemoriesClient::new(transport, client_setup.api_provider, client_setup.api_auth) .with_telemetry(Some(request_telemetry)); + let effort = Self::normalize_reasoning_effort_for_wire(model_info, effort); let payload = ApiMemorySummarizeInput { model: model_info.slug.clone(), @@ -825,14 +826,30 @@ impl ModelClient { request_telemetry } + fn normalize_reasoning_effort_for_wire( + model_info: &ModelInfo, + effort: Option, + ) -> Option { + match effort { + Some(ReasoningEffortConfig::Custom(effort)) + if model_info.slug == "gpt-5.6-sol" && effort == "ultra" => + { + Some(ReasoningEffortConfig::Custom("max".to_string())) + } + effort => effort, + } + } + fn build_reasoning( model_info: &ModelInfo, effort: Option, summary: ReasoningSummaryConfig, ) -> Option { if model_info.supports_reasoning_summaries { + let effort = effort.or_else(|| model_info.default_reasoning_level.clone()); + let effort = Self::normalize_reasoning_effort_for_wire(model_info, effort); Some(Reasoning { - effort: effort.or_else(|| model_info.default_reasoning_level.clone()), + effort, summary: if summary == ReasoningSummaryConfig::None { None } else { diff --git a/codex-rs/core/tests/suite/remote_models.rs b/codex-rs/core/tests/suite/remote_models.rs index 4c219f6caa..4657cf0247 100644 --- a/codex-rs/core/tests/suite/remote_models.rs +++ b/codex-rs/core/tests/suite/remote_models.rs @@ -402,6 +402,81 @@ async fn remote_models_long_model_slug_is_sent_with_custom_reasoning() -> Result Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn remote_models_sol_ultra_reasoning_is_sent_as_max() -> Result<()> { + skip_if_no_network!(Ok(())); + skip_if_sandbox!(Ok(())); + + let server = MockServer::start().await; + let requested_model = "gpt-5.6-sol"; + let mut remote_model = test_remote_model_with_policy( + requested_model, + ModelVisibility::List, + /*priority*/ 1_000, + TruncationPolicyConfig::bytes(/*limit*/ 10_000), + ); + let ultra_reasoning_effort = ReasoningEffort::Custom("ultra".to_string()); + remote_model.supported_reasoning_levels = vec![ + ReasoningEffortPreset { + effort: ReasoningEffort::High, + description: ReasoningEffort::High.to_string(), + }, + ReasoningEffortPreset { + effort: ultra_reasoning_effort.clone(), + description: ultra_reasoning_effort.to_string(), + }, + ]; + remote_model.supports_reasoning_summaries = true; + mount_models_once( + &server, + ModelsResponse { + models: vec![remote_model], + }, + ) + .await; + + let response_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + + let TestCodex { codex, .. } = test_codex() + .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()) + .with_config(|config| { + config.model = Some(requested_model.to_string()); + config.model_reasoning_effort = Some(ultra_reasoning_effort); + }) + .build(&server) + .await?; + + codex + .submit(Op::UserInput { + items: vec![UserInput::Text { + text: "check reasoning effort".into(), + text_elements: Vec::new(), + }], + environments: None, + final_output_json_schema: None, + responsesapi_client_metadata: None, + additional_context: Default::default(), + thread_settings: Default::default(), + }) + .await?; + + wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await; + + let body = response_mock.single_request().body_json(); + let reasoning_effort = body + .get("reasoning") + .and_then(|reasoning| reasoning.get("effort")) + .and_then(|value| value.as_str()); + assert_eq!(body["model"].as_str(), Some(requested_model)); + assert_eq!(reasoning_effort, Some("max")); + + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn namespaced_model_slug_uses_catalog_metadata_without_fallback_warning() -> Result<()> { skip_if_no_network!(Ok(()));