From ce85be18bee239de64e0dcccc7ea140b37b8273f Mon Sep 17 00:00:00 2001 From: Jonathan Coens Date: Wed, 7 May 2025 21:50:01 +0000 Subject: [PATCH] feat: add --reasoning-effort to codex-rs --- codex-rs/core/src/client.rs | 6 ++++-- codex-rs/core/src/client_common.rs | 6 +++--- codex-rs/core/src/codex.rs | 4 +++- codex-rs/core/src/config.rs | 15 +++++++++++++++ codex-rs/core/src/protocol.rs | 3 +++ codex-rs/exec/src/cli.rs | 4 ++++ codex-rs/exec/src/lib.rs | 2 ++ codex-rs/mcp-server/src/codex_tool_config.rs | 1 + codex-rs/tui/src/cli.rs | 4 ++++ codex-rs/tui/src/lib.rs | 1 + 10 files changed, 40 insertions(+), 6 deletions(-) diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 5f4f2a1cb8..2f29421ace 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -94,14 +94,16 @@ pub struct ModelClient { model: String, client: reqwest::Client, provider: ModelProviderInfo, + reasoning_level: String, } impl ModelClient { - pub fn new(model: impl ToString, provider: ModelProviderInfo) -> Self { + pub fn new(model: impl ToString, provider: ModelProviderInfo, reasoning_level: String) -> Self { Self { model: model.to_string(), client: reqwest::Client::new(), provider, + reasoning_level, } } @@ -172,7 +174,7 @@ impl ModelClient { tool_choice: "auto", parallel_tool_calls: false, reasoning: Some(Reasoning { - effort: "high", + effort: &self.reasoning_level, generate_summary: None, }), previous_response_id: prompt.prev_id.clone(), diff --git a/codex-rs/core/src/client_common.rs b/codex-rs/core/src/client_common.rs index 514b6b60a8..a62800cc6a 100644 --- a/codex-rs/core/src/client_common.rs +++ b/codex-rs/core/src/client_common.rs @@ -33,8 +33,8 @@ pub enum ResponseEvent { } #[derive(Debug, Serialize)] -pub(crate) struct Reasoning { - pub(crate) effort: &'static str, +pub(crate) struct Reasoning<'a> { + pub(crate) effort: &'a str, #[serde(skip_serializing_if = "Option::is_none")] pub(crate) generate_summary: Option, } @@ -51,7 +51,7 @@ pub(crate) struct Payload<'a> { pub(crate) tools: &'a [serde_json::Value], pub(crate) tool_choice: &'static str, pub(crate) parallel_tool_calls: bool, - pub(crate) reasoning: Option, + pub(crate) reasoning: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub(crate) previous_response_id: Option, /// true when using the Responses API. diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 5cd5a6799d..0676fbb87a 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -92,6 +92,7 @@ impl Codex { disable_response_storage: config.disable_response_storage, notify: config.notify.clone(), cwd: config.cwd.clone(), + reasoning_level: config.reasoning_level.clone(), }; tokio::spawn(submission_loop(config, rx_sub, tx_event, ctrl_c)); @@ -521,6 +522,7 @@ async fn submission_loop( disable_response_storage, notify, cwd, + reasoning_level, } => { info!("Configuring session: model={model}; provider={provider:?}"); if !cwd.is_absolute() { @@ -536,7 +538,7 @@ async fn submission_loop( return; } - let client = ModelClient::new(model.clone(), provider.clone()); + let client = ModelClient::new(model.clone(), provider.clone(), reasoning_level); // abort any current running session and clone its state let retain_zdr_transcript = diff --git a/codex-rs/core/src/config.rs b/codex-rs/core/src/config.rs index 2264792bb8..227b9afc4d 100644 --- a/codex-rs/core/src/config.rs +++ b/codex-rs/core/src/config.rs @@ -72,6 +72,9 @@ pub struct Config { /// Combined provider map (defaults merged with user-defined overrides). pub model_providers: HashMap, + + /// Reasoning level for the agent. + pub reasoning_level: String, } /// Base config deserialized from ~/.codex/config.toml. @@ -111,6 +114,9 @@ pub struct ConfigToml { /// User-defined provider entries that extend/override the built-in list. #[serde(default)] pub model_providers: HashMap, + + /// Optional override of reasoning level for the agent. + pub reasoning_level: Option, } impl ConfigToml { @@ -171,6 +177,7 @@ pub struct ConfigOverrides { pub sandbox_policy: Option, pub disable_response_storage: Option, pub provider: Option, + pub reasoning_level: Option, } impl Config { @@ -199,6 +206,7 @@ impl Config { sandbox_policy, disable_response_storage, provider, + reasoning_level, } = overrides; let sandbox_policy = match sandbox_policy { @@ -263,6 +271,9 @@ impl Config { disable_response_storage: disable_response_storage .or(cfg.disable_response_storage) .unwrap_or(false), + reasoning_level: reasoning_level + .or(cfg.reasoning_level) + .unwrap_or_else(default_reasoning_level), notify: cfg.notify, instructions, mcp_servers: cfg.mcp_servers, @@ -292,6 +303,10 @@ fn default_model() -> String { OPENAI_DEFAULT_MODEL.to_string() } +fn default_reasoning_level() -> String { + "high".into() +} + /// Returns the path to the Codex configuration directory, which is `~/.codex`. /// Does not verify that the directory exists. pub fn codex_dir() -> std::io::Result { diff --git a/codex-rs/core/src/protocol.rs b/codex-rs/core/src/protocol.rs index 131ccb7af9..3b23178319 100644 --- a/codex-rs/core/src/protocol.rs +++ b/codex-rs/core/src/protocol.rs @@ -60,6 +60,9 @@ pub enum Op { /// `ConfigureSession` operation so that the business-logic layer can /// operate deterministically. cwd: std::path::PathBuf, + + /// Reasoning level for the agent. + reasoning_level: String, }, /// Abort current task. diff --git a/codex-rs/exec/src/cli.rs b/codex-rs/exec/src/cli.rs index 1248ef3b19..61889fdc65 100644 --- a/codex-rs/exec/src/cli.rs +++ b/codex-rs/exec/src/cli.rs @@ -39,6 +39,10 @@ pub struct Cli { /// Initial instructions for the agent. pub prompt: String, + + /// Reasoning level for the agent. + #[arg(long = "reasoning", value_parser = ["low", "medium", "high"])] + pub reasoning_level: Option, } #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, ValueEnum)] diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index d711388f35..be84e6fef6 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -32,6 +32,7 @@ pub async fn run_main(cli: Cli) -> anyhow::Result<()> { disable_response_storage, color, prompt, + reasoning_level, } = cli; let (stdout_with_ansi, stderr_with_ansi) = match color { @@ -63,6 +64,7 @@ pub async fn run_main(cli: Cli) -> anyhow::Result<()> { }, cwd: cwd.map(|p| p.canonicalize().unwrap_or(p)), provider: None, + reasoning_level, }; let config = Config::load_with_overrides(overrides)?; diff --git a/codex-rs/mcp-server/src/codex_tool_config.rs b/codex-rs/mcp-server/src/codex_tool_config.rs index 89b19f726a..1a69851847 100644 --- a/codex-rs/mcp-server/src/codex_tool_config.rs +++ b/codex-rs/mcp-server/src/codex_tool_config.rs @@ -159,6 +159,7 @@ impl CodexToolCallParam { sandbox_policy, disable_response_storage, provider: None, + reasoning_level: None, // TODO: Thread this from JsonSchema }; let cfg = codex_core::config::Config::load_with_overrides(overrides)?; diff --git a/codex-rs/tui/src/cli.rs b/codex-rs/tui/src/cli.rs index c260caa9f4..49e0837f8d 100644 --- a/codex-rs/tui/src/cli.rs +++ b/codex-rs/tui/src/cli.rs @@ -39,4 +39,8 @@ pub struct Cli { /// Disable server‑side response storage (sends the full conversation context with every request) #[arg(long = "disable-response-storage", default_value_t = false)] pub disable_response_storage: bool, + + /// Configure the amount of reasoning the model should do before executing a command. + #[arg(long = "reasoning", value_parser = ["low", "medium", "high"])] + pub reasoning_level: Option, } diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index fe4f995432..3c2b2e3c78 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -56,6 +56,7 @@ pub fn run_main(cli: Cli) -> std::io::Result<()> { }, cwd: cli.cwd.clone().map(|p| p.canonicalize().unwrap_or(p)), provider: None, + reasoning_level: cli.reasoning_level.clone(), }; #[allow(clippy::print_stderr)] match Config::load_with_overrides(overrides) {