[codex] Treat max as a first-class reasoning effort (#30467)

## Why

The Bedrock GPT-5.6 catalog advertises `max`, but Codex treated it as an
opaque custom effort. That made the reasoning picker render it as
lowercase `max` while known efforts use productized labels.

Making `max` a known effort aligns catalog data, parsing, and UI
presentation without changing the `max` wire value or persisted
representation.

## What changed

- Add first-class `ReasoningEffort::Max` parsing and serialization.
- Use the typed effort in the Bedrock catalog and render it as `Max` in
the TUI.
- Preserve forward-compatible custom-effort coverage with a genuinely
unknown `future` value.

### Before
<img width="559" height="124" alt="Screenshot 2026-06-28 at 12 08 47 PM"
src="https://github.com/user-attachments/assets/7c43cf4f-020b-4605-9239-0a9c97eb7364"
/>

### After
<img width="558" height="107" alt="Screenshot 2026-06-28 at 12 09 10 PM"
src="https://github.com/user-attachments/assets/b9cc5ded-c940-43b4-b024-bba25abe0a17"
/>
This commit is contained in:
Shijie Rao
2026-06-29 09:38:49 -07:00
committed by GitHub
parent ccdfb4f342
commit 80f54d1266
10 changed files with 26 additions and 20 deletions
+1 -1
View File
@@ -169,7 +169,7 @@ pub(crate) struct CompactConversationRequestSettings {
fn reasoning_effort_for_request(effort: ReasoningEffortConfig) -> ReasoningEffortConfig {
match effort {
ReasoningEffortConfig::Ultra => ReasoningEffortConfig::Custom("max".to_string()),
ReasoningEffortConfig::Ultra => ReasoningEffortConfig::Max,
effort => effort,
}
}
+1 -4
View File
@@ -292,10 +292,7 @@ fn ultra_reasoning_uses_max_for_requests() {
super::reasoning_effort_for_request(ReasoningEffort::Ultra),
super::reasoning_effort_for_request(ReasoningEffort::High),
),
(
ReasoningEffort::Custom("max".to_string()),
ReasoningEffort::High,
)
(ReasoningEffort::Max, ReasoningEffort::High,)
);
}
+3 -3
View File
@@ -2171,7 +2171,7 @@ async fn skills_use_aliases_in_developer_message_under_budget_pressure() {
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn includes_configured_effort_in_request() -> anyhow::Result<()> {
async fn includes_configured_max_effort_in_request() -> anyhow::Result<()> {
skip_if_no_network!(Ok(()));
let server = MockServer::start().await;
@@ -2183,7 +2183,7 @@ async fn includes_configured_effort_in_request() -> anyhow::Result<()> {
let TestCodex { codex, .. } = test_codex()
.with_model("gpt-5.4")
.with_config(|config| {
config.model_reasoning_effort = Some(ReasoningEffort::Medium);
config.model_reasoning_effort = Some(ReasoningEffort::Max);
})
.build(&server)
.await?;
@@ -2212,7 +2212,7 @@ async fn includes_configured_effort_in_request() -> anyhow::Result<()> {
.get("reasoning")
.and_then(|t| t.get("effort"))
.and_then(|v| v.as_str()),
Some("medium")
Some("max")
);
Ok(())
+2 -2
View File
@@ -329,7 +329,7 @@ async fn remote_models_long_model_slug_is_sent_with_custom_reasoning() -> Result
/*priority*/ 1_000,
TruncationPolicyConfig::bytes(/*limit*/ 10_000),
);
let custom_reasoning_effort = ReasoningEffort::Custom("max".to_string());
let custom_reasoning_effort = ReasoningEffort::Custom("future".to_string());
remote_model.default_reasoning_level = Some(custom_reasoning_effort.clone());
remote_model.supported_reasoning_levels = vec![
ReasoningEffortPreset {
@@ -391,7 +391,7 @@ async fn remote_models_long_model_slug_is_sent_with_custom_reasoning() -> Result
.and_then(|reasoning| reasoning.get("summary"))
.and_then(|value| value.as_str());
assert_eq!(body["model"].as_str(), Some(requested_model));
assert_eq!(reasoning_effort, Some("max"));
assert_eq!(reasoning_effort, Some("future"));
assert_eq!(reasoning_summary, Some("detailed"));
Ok(())
@@ -70,7 +70,7 @@ fn gpt_5_6_bedrock_model(bedrock_slug: &str, display_name: &str, priority: i32)
model
.supported_reasoning_levels
.push(ReasoningEffortPreset {
effort: ReasoningEffort::Custom("max".to_string()),
effort: ReasoningEffort::Max,
description: "Maximum reasoning depth for the hardest problems".to_string(),
});
model
@@ -148,7 +148,7 @@ mod tests {
expected
.supported_reasoning_levels
.push(ReasoningEffortPreset {
effort: ReasoningEffort::Custom("max".to_string()),
effort: ReasoningEffort::Max,
description: "Maximum reasoning depth for the hardest problems".to_string(),
});
+12 -4
View File
@@ -45,6 +45,7 @@ pub enum ReasoningEffort {
Medium,
High,
XHigh,
Max,
Ultra,
/// A model-defined effort value that this client does not know yet.
Custom(String),
@@ -60,6 +61,7 @@ impl ReasoningEffort {
Self::Medium => "medium",
Self::High => "high",
Self::XHigh => "xhigh",
Self::Max => "max",
Self::Ultra => "ultra",
Self::Custom(effort) => effort,
}
@@ -125,6 +127,7 @@ impl FromStr for ReasoningEffort {
"medium" => Ok(Self::Medium),
"high" => Ok(Self::High),
"xhigh" => Ok(Self::XHigh),
"max" => Ok(Self::Max),
"ultra" => Ok(Self::Ultra),
"" => Err("reasoning_effort must not be empty".to_string()),
effort => Ok(Self::Custom(effort.to_string())),
@@ -703,30 +706,35 @@ mod tests {
#[test]
fn reasoning_effort_accepts_known_and_custom_values() {
let custom = ReasoningEffort::Custom("max".to_string());
let deserialized = from_str::<ReasoningEffort>(r#""max""#)
let custom = ReasoningEffort::Custom("future".to_string());
let deserialized = from_str::<ReasoningEffort>(r#""future""#)
.expect("custom reasoning effort should deserialize");
let serialized = to_string(&custom).expect("custom reasoning effort should serialize");
let serialized_max = to_string(&ReasoningEffort::Max).expect("Max should serialize");
let serialized_ultra = to_string(&ReasoningEffort::Ultra).expect("Ultra should serialize");
assert_eq!(
(
"high".parse(),
"ultra".parse(),
"max".parse(),
"ultra".parse(),
"future".parse(),
deserialized,
serialized,
serialized_max,
serialized_ultra,
custom.to_string(),
),
(
Ok(ReasoningEffort::High),
Ok(ReasoningEffort::Max),
Ok(ReasoningEffort::Ultra),
Ok(custom.clone()),
custom,
r#""future""#.to_string(),
r#""max""#.to_string(),
r#""ultra""#.to_string(),
"max".to_string(),
"future".to_string(),
)
);
}
@@ -505,6 +505,7 @@ impl ChatWidget {
ReasoningEffortConfig::Medium => "Medium".to_string(),
ReasoningEffortConfig::High => "High".to_string(),
ReasoningEffortConfig::XHigh => "Extra high".to_string(),
ReasoningEffortConfig::Max => "Max".to_string(),
ReasoningEffortConfig::Ultra => "Ultra".to_string(),
ReasoningEffortConfig::Custom(value) => value.clone(),
}
@@ -225,7 +225,7 @@ mod tests {
#[test]
fn next_reasoning_effort_uses_advertised_order_for_custom_levels() {
let custom_effort = ReasoningEffortConfig::Custom("max".to_string());
let custom_effort = ReasoningEffortConfig::Custom("future".to_string());
let choices = vec![
ReasoningEffortConfig::High,
ReasoningEffortConfig::Low,
@@ -6,7 +6,7 @@ expression: popup
1. Low Fast responses with lighter reasoning
2. Medium (default) Balances speed and reasoning depth for everyday tasks
3. max Maximum available reasoning
3. Max Maximum available reasoning
› 4. High (current) Greater reasoning depth for complex problems
5. Extra high Extra high reasoning depth for complex problems
@@ -3191,7 +3191,7 @@ async fn model_reasoning_selection_popup_snapshot() {
preset.supported_reasoning_efforts.insert(
2,
ReasoningEffortPreset {
effort: ReasoningEffortConfig::Custom("max".to_string()),
effort: ReasoningEffortConfig::Max,
description: "Maximum available reasoning".to_string(),
},
);
@@ -3204,7 +3204,7 @@ async fn model_reasoning_selection_popup_snapshot() {
#[tokio::test]
async fn model_reasoning_selection_popup_applies_custom_effort() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
let custom_effort = ReasoningEffortConfig::Custom("max".to_string());
let custom_effort = ReasoningEffortConfig::Custom("future".to_string());
chat.set_reasoning_effort(Some(ReasoningEffortConfig::XHigh));
let mut preset = get_available_model(&chat, "gpt-5.4");