Skip to content

Commit 0f3cc8f

Browse files
authored
feat: make reasoning effort/summaries configurable (openai#1199)
Previous to this PR, we always set `reasoning` when making a request using the Responses API: https://github.com/openai/codex/blob/d7245cbbc9d8ff5446da45e5951761103492476d/codex-rs/core/src/client.rs#L108-L111 Though if you tried to use the Rust CLI with `--model gpt-4.1`, this would fail with: ```shell "Unsupported parameter: 'reasoning.effort' is not supported with this model." ``` We take a cue from the TypeScript CLI, which does a check on the model name: https://github.com/openai/codex/blob/d7245cbbc9d8ff5446da45e5951761103492476d/codex-cli/src/utils/agent/agent-loop.ts#L786-L789 This PR does a similar check, though also adds support for the following config options: ``` model_reasoning_effort = "low" | "medium" | "high" | "none" model_reasoning_summary = "auto" | "concise" | "detailed" | "none" ``` This way, if you have a model whose name happens to start with `"o"` (or `"codex"`?), you can set these to `"none"` to explicitly disable reasoning, if necessary. (That said, it seems unlikely anyone would use the Responses API with non-OpenAI models, but we provide an escape hatch, anyway.) This PR also updates both the TUI and `codex exec` to show `reasoning effort` and `reasoning summaries` in the header.
1 parent d7245cb commit 0f3cc8f

File tree

12 files changed

+226
-20
lines changed

12 files changed

+226
-20
lines changed

codex-rs/Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/config.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,34 @@ Users can specify config values at multiple levels. Order of precedence is as fo
142142
3. as an entry in `config.toml`, e.g., `model = "o3"`
143143
4. the default value that comes with Codex CLI (i.e., Codex CLI defaults to `codex-mini-latest`)
144144

145+
## model_reasoning_effort
146+
147+
If the model name starts with `"o"` (as in `"o3"` or `"o4-mini"`) or `"codex"`, reasoning is enabled by default when using the Responses API. As explained in the [OpenAI Platform documentation](https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning), this can be set to:
148+
149+
- `"low"`
150+
- `"medium"` (default)
151+
- `"high"`
152+
153+
To disable reasoning, set `model_reasoning_effort` to `"none"` in your config:
154+
155+
```toml
156+
model_reasoning_effort = "none" # disable reasoning
157+
```
158+
159+
## model_reasoning_summary
160+
161+
If the model name starts with `"o"` (as in `"o3"` or `"o4-mini"`) or `"codex"`, reasoning is enabled by default when using the Responses API. As explained in the [OpenAI Platform documentation](https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries), this can be set to:
162+
163+
- `"auto"` (default)
164+
- `"concise"`
165+
- `"detailed"`
166+
167+
To disable reasoning summaries, set `model_reasoning_summary` to `"none"` in your config:
168+
169+
```toml
170+
model_reasoning_summary = "none" # disable reasoning summaries
171+
```
172+
145173
## sandbox_permissions
146174

147175
List of permissions to grant to the sandbox that Codex uses to execute untrusted commands:

codex-rs/core/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ rand = "0.9"
3131
reqwest = { version = "0.12", features = ["json", "stream"] }
3232
serde = { version = "1", features = ["derive"] }
3333
serde_json = "1"
34+
strum = "0.27.1"
35+
strum_macros = "0.27.1"
3436
thiserror = "2.0.12"
3537
time = { version = "0.3", features = ["formatting", "local-offset", "macros"] }
3638
tokio = { version = "1", features = [

codex-rs/core/src/client.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@ use tracing::warn;
1818

1919
use crate::chat_completions::AggregateStreamExt;
2020
use crate::chat_completions::stream_chat_completions;
21-
use crate::client_common::Payload;
2221
use crate::client_common::Prompt;
23-
use crate::client_common::Reasoning;
2422
use crate::client_common::ResponseEvent;
2523
use crate::client_common::ResponseStream;
26-
use crate::client_common::Summary;
24+
use crate::client_common::ResponsesApiRequest;
25+
use crate::client_common::create_reasoning_param_for_request;
26+
use crate::config_types::ReasoningEffort as ReasoningEffortConfig;
27+
use crate::config_types::ReasoningSummary as ReasoningSummaryConfig;
2728
use crate::error::CodexErr;
2829
use crate::error::EnvVarError;
2930
use crate::error::Result;
@@ -41,14 +42,23 @@ pub struct ModelClient {
4142
model: String,
4243
client: reqwest::Client,
4344
provider: ModelProviderInfo,
45+
effort: ReasoningEffortConfig,
46+
summary: ReasoningSummaryConfig,
4447
}
4548

4649
impl ModelClient {
47-
pub fn new(model: impl ToString, provider: ModelProviderInfo) -> Self {
50+
pub fn new(
51+
model: impl ToString,
52+
provider: ModelProviderInfo,
53+
effort: ReasoningEffortConfig,
54+
summary: ReasoningSummaryConfig,
55+
) -> Self {
4856
Self {
4957
model: model.to_string(),
5058
client: reqwest::Client::new(),
5159
provider,
60+
effort,
61+
summary,
5262
}
5363
}
5464

@@ -98,17 +108,15 @@ impl ModelClient {
98108

99109
let full_instructions = prompt.get_full_instructions();
100110
let tools_json = create_tools_json_for_responses_api(prompt, &self.model)?;
101-
let payload = Payload {
111+
let reasoning = create_reasoning_param_for_request(&self.model, self.effort, self.summary);
112+
let payload = ResponsesApiRequest {
102113
model: &self.model,
103114
instructions: &full_instructions,
104115
input: &prompt.input,
105116
tools: &tools_json,
106117
tool_choice: "auto",
107118
parallel_tool_calls: false,
108-
reasoning: Some(Reasoning {
109-
effort: "high",
110-
summary: Some(Summary::Auto),
111-
}),
119+
reasoning,
112120
previous_response_id: prompt.prev_id.clone(),
113121
store: prompt.store,
114122
stream: true,

codex-rs/core/src/client_common.rs

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use crate::config_types::ReasoningEffort as ReasoningEffortConfig;
2+
use crate::config_types::ReasoningSummary as ReasoningSummaryConfig;
13
use crate::error::Result;
24
use crate::models::ResponseItem;
35
use futures::Stream;
@@ -52,25 +54,59 @@ pub enum ResponseEvent {
5254

5355
#[derive(Debug, Serialize)]
5456
pub(crate) struct Reasoning {
55-
pub(crate) effort: &'static str,
57+
pub(crate) effort: OpenAiReasoningEffort,
5658
#[serde(skip_serializing_if = "Option::is_none")]
57-
pub(crate) summary: Option<Summary>,
59+
pub(crate) summary: Option<OpenAiReasoningSummary>,
60+
}
61+
62+
/// See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning
63+
#[derive(Debug, Serialize, Default, Clone, Copy)]
64+
#[serde(rename_all = "lowercase")]
65+
pub(crate) enum OpenAiReasoningEffort {
66+
Low,
67+
#[default]
68+
Medium,
69+
High,
70+
}
71+
72+
impl From<ReasoningEffortConfig> for Option<OpenAiReasoningEffort> {
73+
fn from(effort: ReasoningEffortConfig) -> Self {
74+
match effort {
75+
ReasoningEffortConfig::Low => Some(OpenAiReasoningEffort::Low),
76+
ReasoningEffortConfig::Medium => Some(OpenAiReasoningEffort::Medium),
77+
ReasoningEffortConfig::High => Some(OpenAiReasoningEffort::High),
78+
ReasoningEffortConfig::None => None,
79+
}
80+
}
5881
}
5982

6083
/// A summary of the reasoning performed by the model. This can be useful for
6184
/// debugging and understanding the model's reasoning process.
62-
#[derive(Debug, Serialize)]
85+
/// See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries
86+
#[derive(Debug, Serialize, Default, Clone, Copy)]
6387
#[serde(rename_all = "lowercase")]
64-
pub(crate) enum Summary {
88+
pub(crate) enum OpenAiReasoningSummary {
89+
#[default]
6590
Auto,
66-
#[allow(dead_code)] // Will go away once this is configurable.
6791
Concise,
68-
#[allow(dead_code)] // Will go away once this is configurable.
6992
Detailed,
7093
}
7194

95+
impl From<ReasoningSummaryConfig> for Option<OpenAiReasoningSummary> {
96+
fn from(summary: ReasoningSummaryConfig) -> Self {
97+
match summary {
98+
ReasoningSummaryConfig::Auto => Some(OpenAiReasoningSummary::Auto),
99+
ReasoningSummaryConfig::Concise => Some(OpenAiReasoningSummary::Concise),
100+
ReasoningSummaryConfig::Detailed => Some(OpenAiReasoningSummary::Detailed),
101+
ReasoningSummaryConfig::None => None,
102+
}
103+
}
104+
}
105+
106+
/// Request object that is serialized as JSON and POST'ed when using the
107+
/// Responses API.
72108
#[derive(Debug, Serialize)]
73-
pub(crate) struct Payload<'a> {
109+
pub(crate) struct ResponsesApiRequest<'a> {
74110
pub(crate) model: &'a str,
75111
pub(crate) instructions: &'a str,
76112
// TODO(mbolin): ResponseItem::Other should not be serialized. Currently,
@@ -88,6 +124,40 @@ pub(crate) struct Payload<'a> {
88124
pub(crate) stream: bool,
89125
}
90126

127+
pub(crate) fn create_reasoning_param_for_request(
128+
model: &str,
129+
effort: ReasoningEffortConfig,
130+
summary: ReasoningSummaryConfig,
131+
) -> Option<Reasoning> {
132+
let effort: Option<OpenAiReasoningEffort> = effort.into();
133+
let effort = effort?;
134+
135+
if model_supports_reasoning_summaries(model) {
136+
Some(Reasoning {
137+
effort,
138+
summary: summary.into(),
139+
})
140+
} else {
141+
None
142+
}
143+
}
144+
145+
pub fn model_supports_reasoning_summaries(model: &str) -> bool {
146+
// Currently, we hardcode this rule to decide whether enable reasoning.
147+
// We expect reasoning to apply only to OpenAI models, but we do not want
148+
// users to have to mess with their config to disable reasoning for models
149+
// that do not support it, such as `gpt-4.1`.
150+
//
151+
// Though if a user is using Codex with non-OpenAI models that, say, happen
152+
// to start with "o", then they can set `model_reasoning_effort = "none` in
153+
// config.toml to disable reasoning.
154+
//
155+
// Ultimately, this should also be configurable in config.toml, but we
156+
// need to have defaults that "just work." Perhaps we could have a
157+
// "reasoning models pattern" as part of ModelProviderInfo?
158+
model.starts_with("o") || model.starts_with("codex")
159+
}
160+
91161
pub(crate) struct ResponseStream {
92162
pub(crate) rx_event: mpsc::Receiver<Result<ResponseEvent>>,
93163
}

codex-rs/core/src/codex.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ impl Codex {
108108
let configure_session = Op::ConfigureSession {
109109
provider: config.model_provider.clone(),
110110
model: config.model.clone(),
111+
model_reasoning_effort: config.model_reasoning_effort,
112+
model_reasoning_summary: config.model_reasoning_summary,
111113
instructions,
112114
approval_policy: config.approval_policy,
113115
sandbox_policy: config.sandbox_policy.clone(),
@@ -554,6 +556,8 @@ async fn submission_loop(
554556
Op::ConfigureSession {
555557
provider,
556558
model,
559+
model_reasoning_effort,
560+
model_reasoning_summary,
557561
instructions,
558562
approval_policy,
559563
sandbox_policy,
@@ -575,7 +579,12 @@ async fn submission_loop(
575579
return;
576580
}
577581

578-
let client = ModelClient::new(model.clone(), provider.clone());
582+
let client = ModelClient::new(
583+
model.clone(),
584+
provider.clone(),
585+
model_reasoning_effort,
586+
model_reasoning_summary,
587+
);
579588

580589
// abort any current running session and clone its state
581590
let retain_zdr_transcript =

codex-rs/core/src/config.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use crate::config_profile::ConfigProfile;
22
use crate::config_types::History;
33
use crate::config_types::McpServerConfig;
4+
use crate::config_types::ReasoningEffort;
5+
use crate::config_types::ReasoningSummary;
46
use crate::config_types::ShellEnvironmentPolicy;
57
use crate::config_types::ShellEnvironmentPolicyToml;
68
use crate::config_types::Tui;
@@ -112,6 +114,14 @@ pub struct Config {
112114
///
113115
/// When this program is invoked, arg0 will be set to `codex-linux-sandbox`.
114116
pub codex_linux_sandbox_exe: Option<PathBuf>,
117+
118+
/// If not "none", the value to use for `reasoning.effort` when making a
119+
/// request using the Responses API.
120+
pub model_reasoning_effort: ReasoningEffort,
121+
122+
/// If not "none", the value to use for `reasoning.summary` when making a
123+
/// request using the Responses API.
124+
pub model_reasoning_summary: ReasoningSummary,
115125
}
116126

117127
impl Config {
@@ -281,6 +291,9 @@ pub struct ConfigToml {
281291
/// When set to `true`, `AgentReasoning` events will be hidden from the
282292
/// UI/output. Defaults to `false`.
283293
pub hide_agent_reasoning: Option<bool>,
294+
295+
pub model_reasoning_effort: Option<ReasoningEffort>,
296+
pub model_reasoning_summary: Option<ReasoningSummary>,
284297
}
285298

286299
fn deserialize_sandbox_permissions<'de, D>(
@@ -444,6 +457,8 @@ impl Config {
444457
codex_linux_sandbox_exe,
445458

446459
hide_agent_reasoning: cfg.hide_agent_reasoning.unwrap_or(false),
460+
model_reasoning_effort: cfg.model_reasoning_effort.unwrap_or_default(),
461+
model_reasoning_summary: cfg.model_reasoning_summary.unwrap_or_default(),
447462
};
448463
Ok(config)
449464
}
@@ -786,6 +801,8 @@ disable_response_storage = true
786801
tui: Tui::default(),
787802
codex_linux_sandbox_exe: None,
788803
hide_agent_reasoning: false,
804+
model_reasoning_effort: ReasoningEffort::default(),
805+
model_reasoning_summary: ReasoningSummary::default(),
789806
},
790807
o3_profile_config
791808
);
@@ -826,6 +843,8 @@ disable_response_storage = true
826843
tui: Tui::default(),
827844
codex_linux_sandbox_exe: None,
828845
hide_agent_reasoning: false,
846+
model_reasoning_effort: ReasoningEffort::default(),
847+
model_reasoning_summary: ReasoningSummary::default(),
829848
};
830849

831850
assert_eq!(expected_gpt3_profile_config, gpt3_profile_config);
@@ -881,6 +900,8 @@ disable_response_storage = true
881900
tui: Tui::default(),
882901
codex_linux_sandbox_exe: None,
883902
hide_agent_reasoning: false,
903+
model_reasoning_effort: ReasoningEffort::default(),
904+
model_reasoning_summary: ReasoningSummary::default(),
884905
};
885906

886907
assert_eq!(expected_zdr_profile_config, zdr_profile_config);

codex-rs/core/src/config_types.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
// definitions that do not contain business logic.
55

66
use std::collections::HashMap;
7+
use strum_macros::Display;
78
use wildmatch::WildMatchPattern;
89

910
use serde::Deserialize;
11+
use serde::Serialize;
1012

1113
#[derive(Deserialize, Debug, Clone, PartialEq)]
1214
pub struct McpServerConfig {
@@ -175,3 +177,31 @@ impl From<ShellEnvironmentPolicyToml> for ShellEnvironmentPolicy {
175177
}
176178
}
177179
}
180+
181+
/// See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning
182+
#[derive(Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, Display)]
183+
#[serde(rename_all = "lowercase")]
184+
#[strum(serialize_all = "lowercase")]
185+
pub enum ReasoningEffort {
186+
Low,
187+
#[default]
188+
Medium,
189+
High,
190+
/// Option to disable reasoning.
191+
None,
192+
}
193+
194+
/// A summary of the reasoning performed by the model. This can be useful for
195+
/// debugging and understanding the model's reasoning process.
196+
/// See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries
197+
#[derive(Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, Display)]
198+
#[serde(rename_all = "lowercase")]
199+
#[strum(serialize_all = "lowercase")]
200+
pub enum ReasoningSummary {
201+
#[default]
202+
Auto,
203+
Concise,
204+
Detailed,
205+
/// Option to disable reasoning summaries.
206+
None,
207+
}

codex-rs/core/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,5 @@ mod rollout;
3434
mod safety;
3535
mod user_notification;
3636
pub mod util;
37+
38+
pub use client_common::model_supports_reasoning_summaries;

0 commit comments

Comments
 (0)