diff --git a/.github/actions/codex/bun.lock b/.github/actions/codex/bun.lock index 11b791654b..5f70136f8a 100644 --- a/.github/actions/codex/bun.lock +++ b/.github/actions/codex/bun.lock @@ -10,7 +10,7 @@ "devDependencies": { "@types/bun": "^1.2.11", "@types/node": "^22.15.21", - "prettier": "^3.5.3", + "prettier": "^3.6.2", "typescript": "^5.8.3", }, }, @@ -60,7 +60,7 @@ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], - "prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="], + "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], "tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="], diff --git a/.github/actions/codex/package.json b/.github/actions/codex/package.json index bb35ee3a47..ec6cff8276 100644 --- a/.github/actions/codex/package.json +++ b/.github/actions/codex/package.json @@ -15,7 +15,7 @@ "devDependencies": { "@types/bun": "^1.2.11", "@types/node": "^22.15.21", - "prettier": "^3.5.3", + "prettier": "^3.6.2", "typescript": "^5.8.3" } } diff --git a/codex-cli/Dockerfile b/codex-cli/Dockerfile index 78c33ce7fe..21a90a4838 100644 --- a/codex-cli/Dockerfile +++ b/codex-cli/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22-slim +FROM node:24-slim ARG TZ ENV TZ="$TZ" diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index f1c0917e37..7e9630b278 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -601,7 +601,7 @@ dependencies = [ "clap", "codex-core", "serde", - "toml 0.9.0", + "toml 0.9.1", ] [[package]] @@ -638,7 +638,7 @@ dependencies = [ "time", "tokio", "tokio-util", - "toml 0.9.0", + "toml 0.9.1", "tracing", "tree-sitter", "tree-sitter-bash", @@ -749,7 +749,7 @@ dependencies = [ "serde", "serde_json", "tokio", - "toml 0.9.0", + "toml 0.9.1", "tracing", "tracing-subscriber", ] @@ -4496,9 +4496,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f271e09bde39ab52250160a67e88577e0559ad77e9085de6e9051a2c4353f8f8" +checksum = "0207d6ed1852c2a124c1fbec61621acb8330d2bf969a5d0643131e9affd985a5" dependencies = [ "indexmap 2.10.0", "serde", diff --git a/codex-rs/config.md b/codex-rs/config.md index eeb9a266ec..438b7e767d 100644 --- a/codex-rs/config.md +++ b/codex-rs/config.md @@ -206,6 +206,14 @@ To disable reasoning summaries, set `model_reasoning_summary` to `"none"` in you model_reasoning_summary = "none" # disable reasoning summaries ``` +## model_supports_reasoning_summaries + +By default, `reasoning` is only set on requests to OpenAI models that are known to support them. To force `reasoning` to set on requests to the current model, you can force this behavior by setting the following in `config.toml`: + +```toml +model_supports_reasoning_summaries = true +``` + ## sandbox_mode Codex executes model-generated shell commands inside an OS-level sandbox. diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index da5ca5c29f..22636102c9 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -39,7 +39,7 @@ tokio = { version = "1", features = [ "signal", ] } tokio-util = "0.7.14" -toml = "0.9.0" +toml = "0.9.1" tracing = { version = "0.1.41", features = ["log"] } tree-sitter = "0.25.3" tree-sitter-bash = "0.25.0" diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 9dcb7289bc..4eccd7fa1e 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -23,6 +23,7 @@ use crate::client_common::ResponseEvent; use crate::client_common::ResponseStream; use crate::client_common::ResponsesApiRequest; use crate::client_common::create_reasoning_param_for_request; +use crate::config::Config; use crate::config_types::ReasoningEffort as ReasoningEffortConfig; use crate::config_types::ReasoningSummary as ReasoningSummaryConfig; use crate::error::CodexErr; @@ -36,9 +37,11 @@ use crate::models::ResponseItem; use crate::openai_tools::create_tools_json_for_responses_api; use crate::protocol::TokenUsage; use crate::util::backoff; +use std::sync::Arc; #[derive(Clone)] pub struct ModelClient { + config: Arc, model: String, client: reqwest::Client, provider: ModelProviderInfo, @@ -48,12 +51,14 @@ pub struct ModelClient { impl ModelClient { pub fn new( - model: impl ToString, + config: Arc, provider: ModelProviderInfo, effort: ReasoningEffortConfig, summary: ReasoningSummaryConfig, ) -> Self { + let model = config.model.clone(); Self { + config, model: model.to_string(), client: reqwest::Client::new(), provider, @@ -108,7 +113,7 @@ impl ModelClient { let full_instructions = prompt.get_full_instructions(&self.model); let tools_json = create_tools_json_for_responses_api(prompt, &self.model)?; - let reasoning = create_reasoning_param_for_request(&self.model, self.effort, self.summary); + let reasoning = create_reasoning_param_for_request(&self.config, self.effort, self.summary); let payload = ResponsesApiRequest { model: &self.model, instructions: &full_instructions, diff --git a/codex-rs/core/src/client_common.rs b/codex-rs/core/src/client_common.rs index 97d74baf91..f9a816a7a9 100644 --- a/codex-rs/core/src/client_common.rs +++ b/codex-rs/core/src/client_common.rs @@ -131,15 +131,16 @@ pub(crate) struct ResponsesApiRequest<'a> { pub(crate) stream: bool, } +use crate::config::Config; + pub(crate) fn create_reasoning_param_for_request( - model: &str, + config: &Config, effort: ReasoningEffortConfig, summary: ReasoningSummaryConfig, ) -> Option { - let effort: Option = effort.into(); - let effort = effort?; - - if model_supports_reasoning_summaries(model) { + if model_supports_reasoning_summaries(config) { + let effort: Option = effort.into(); + let effort = effort?; Some(Reasoning { effort, summary: summary.into(), @@ -149,19 +150,24 @@ pub(crate) fn create_reasoning_param_for_request( } } -pub fn model_supports_reasoning_summaries(model: &str) -> bool { - // Currently, we hardcode this rule to decide whether enable reasoning. +pub fn model_supports_reasoning_summaries(config: &Config) -> bool { + // Currently, we hardcode this rule to decide whether to enable reasoning. // We expect reasoning to apply only to OpenAI models, but we do not want // users to have to mess with their config to disable reasoning for models // that do not support it, such as `gpt-4.1`. // // Though if a user is using Codex with non-OpenAI models that, say, happen - // to start with "o", then they can set `model_reasoning_effort = "none` in + // to start with "o", then they can set `model_reasoning_effort = "none"` in // config.toml to disable reasoning. // - // Ultimately, this should also be configurable in config.toml, but we - // need to have defaults that "just work." Perhaps we could have a - // "reasoning models pattern" as part of ModelProviderInfo? + // Converseley, if a user has a non-OpenAI provider that supports reasoning, + // they can set the top-level `model_supports_reasoning_summaries = true` + // config option to enable reasoning. + if config.model_supports_reasoning_summaries { + return true; + } + + let model = &config.model; model.starts_with("o") || model.starts_with("codex") } diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 708db88950..52c37c51ee 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -586,7 +586,7 @@ async fn submission_loop( } let client = ModelClient::new( - model.clone(), + config.clone(), provider.clone(), model_reasoning_effort, model_reasoning_summary, diff --git a/codex-rs/core/src/config.rs b/codex-rs/core/src/config.rs index db4f9ffe6e..f372e5b0a3 100644 --- a/codex-rs/core/src/config.rs +++ b/codex-rs/core/src/config.rs @@ -130,6 +130,10 @@ pub struct Config { /// If not "none", the value to use for `reasoning.summary` when making a /// request using the Responses API. pub model_reasoning_summary: ReasoningSummary, + + /// When set to `true`, overrides the default heuristic and forces + /// `model_supports_reasoning_summaries()` to return `true`. + pub model_supports_reasoning_summaries: bool, } impl Config { @@ -308,6 +312,9 @@ pub struct ConfigToml { pub model_reasoning_effort: Option, pub model_reasoning_summary: Option, + + /// Override to force-enable reasoning summaries for the configured model. + pub model_supports_reasoning_summaries: Option, } impl ConfigToml { @@ -472,6 +479,10 @@ impl Config { .model_reasoning_summary .or(cfg.model_reasoning_summary) .unwrap_or_default(), + + model_supports_reasoning_summaries: cfg + .model_supports_reasoning_summaries + .unwrap_or(false), }; Ok(config) } @@ -776,6 +787,7 @@ disable_response_storage = true hide_agent_reasoning: false, model_reasoning_effort: ReasoningEffort::High, model_reasoning_summary: ReasoningSummary::Detailed, + model_supports_reasoning_summaries: false, }, o3_profile_config ); @@ -820,6 +832,7 @@ disable_response_storage = true hide_agent_reasoning: false, model_reasoning_effort: ReasoningEffort::default(), model_reasoning_summary: ReasoningSummary::default(), + model_supports_reasoning_summaries: false, }; assert_eq!(expected_gpt3_profile_config, gpt3_profile_config); diff --git a/codex-rs/exec/src/event_processor.rs b/codex-rs/exec/src/event_processor.rs index 4c7120cd49..540e014298 100644 --- a/codex-rs/exec/src/event_processor.rs +++ b/codex-rs/exec/src/event_processor.rs @@ -139,7 +139,7 @@ impl EventProcessor { ("sandbox", summarize_sandbox_policy(&config.sandbox_policy)), ]; if config.model_provider.wire_api == WireApi::Responses - && model_supports_reasoning_summaries(&config.model) + && model_supports_reasoning_summaries(config) { entries.push(( "reasoning effort", diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 18740f1144..0bfbc414b9 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -159,7 +159,7 @@ impl HistoryCell { ("sandbox", summarize_sandbox_policy(&config.sandbox_policy)), ]; if config.model_provider.wire_api == WireApi::Responses - && model_supports_reasoning_summaries(&config.model) + && model_supports_reasoning_summaries(config) { entries.push(( "reasoning effort",