[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
Unverified
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");