From 99efeef6506cd7f6512404d0ad8755a87ff5a011 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Tue, 21 Jul 2026 02:02:15 +0000 Subject: [PATCH] Add buffered code-mode exec yields (#34441) ## What changed - Add the experimental `code_mode_buffered_exec` feature. - When enabled, default code-mode `exec` calls without an explicit `yield_time_ms` to 30 seconds instead of 10 seconds. - Report the effective default in the model-visible `exec` tool description. ## Testing - Verify that the generated tool description advertises the 30-second default when the feature is enabled. GitOrigin-RevId: 0eea4d0105137f09050ce31aad18c25c79df6b2a --- .../code-mode-protocol/src/description.rs | 21 +++++++++++-- codex-rs/core/config.schema.json | 6 ++++ codex-rs/core/src/session/session.rs | 7 +++-- codex-rs/core/src/session/tests.rs | 14 +++++---- .../core/src/tools/code_mode/execute_spec.rs | 4 +++ codex-rs/core/src/tools/code_mode/mod.rs | 31 +++++++++++++++---- codex-rs/core/src/tools/spec_plan.rs | 5 +++ codex-rs/core/src/tools/spec_plan_tests.rs | 14 +++++++++ codex-rs/features/src/lib.rs | 8 +++++ 9 files changed, 92 insertions(+), 18 deletions(-) diff --git a/codex-rs/code-mode-protocol/src/description.rs b/codex-rs/code-mode-protocol/src/description.rs index e8db333b3d..f2c787aaaf 100644 --- a/codex-rs/code-mode-protocol/src/description.rs +++ b/codex-rs/code-mode-protocol/src/description.rs @@ -253,10 +253,14 @@ pub fn build_exec_tool_description( enabled_tools: &[ToolDefinition], deferred_tools: &[ToolDefinition], namespace_descriptions: &BTreeMap, + default_exec_yield_time_ms: u64, code_mode_only: bool, ) -> String { let mut sections = Vec::new(); - sections.push(EXEC_DESCRIPTION_TEMPLATE.to_string()); + sections.push(EXEC_DESCRIPTION_TEMPLATE.replace( + "Defaults to 10000 ms.", + &format!("Defaults to {default_exec_yield_time_ms} ms."), + )); if !deferred_tools.is_empty() { sections.push(DEFERRED_NESTED_TOOLS_GUIDANCE.to_string()); } @@ -868,6 +872,7 @@ mod tests { }], &[], &BTreeMap::new(), + crate::DEFAULT_EXEC_YIELD_TIME_MS, /*code_mode_only*/ true, ); assert!(description.contains( @@ -879,8 +884,13 @@ bar" #[test] fn exec_description_mentions_timeout_helpers() { - let description = - build_exec_tool_description(&[], &[], &BTreeMap::new(), /*code_mode_only*/ false); + let description = build_exec_tool_description( + &[], + &[], + &BTreeMap::new(), + crate::DEFAULT_EXEC_YIELD_TIME_MS, + /*code_mode_only*/ false, + ); assert!(description.contains("`audio(audioUrlOrItem:")); assert!(description.contains("`setTimeout(callback: () => void, delayMs?: number)`")); assert!(description.contains("`clearTimeout(timeoutId?: number)`")); @@ -932,6 +942,7 @@ bar" ], &[], &namespace_descriptions, + crate::DEFAULT_EXEC_YIELD_TIME_MS, /*code_mode_only*/ true, ); assert_eq!(description.matches("## mcp__sample").count(), 1); @@ -972,6 +983,7 @@ bar" }], &[], &namespace_descriptions, + crate::DEFAULT_EXEC_YIELD_TIME_MS, /*code_mode_only*/ true, ); @@ -1071,6 +1083,7 @@ bar" ], &[], &BTreeMap::new(), + crate::DEFAULT_EXEC_YIELD_TIME_MS, /*code_mode_only*/ true, ); @@ -1106,6 +1119,7 @@ bar" &[], &[deferred_tool], &BTreeMap::new(), + crate::DEFAULT_EXEC_YIELD_TIME_MS, /*code_mode_only*/ true, ); @@ -1127,6 +1141,7 @@ bar" output_schema: None, }], &BTreeMap::new(), + crate::DEFAULT_EXEC_YIELD_TIME_MS, /*code_mode_only*/ false, ); diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 4b3549744f..1bbc849d9e 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -458,6 +458,9 @@ "code_mode": { "$ref": "#/definitions/FeatureToml_for_CodeModeConfigToml" }, + "code_mode_buffered_exec": { + "type": "boolean" + }, "code_mode_host": { "type": "boolean" }, @@ -4896,6 +4899,9 @@ "code_mode": { "$ref": "#/definitions/FeatureToml_for_CodeModeConfigToml" }, + "code_mode_buffered_exec": { + "type": "boolean" + }, "code_mode_host": { "type": "boolean" }, diff --git a/codex-rs/core/src/session/session.rs b/codex-rs/core/src/session/session.rs index 5b136c0492..89cb0c8d9f 100644 --- a/codex-rs/core/src/session/session.rs +++ b/codex-rs/core/src/session/session.rs @@ -1137,9 +1137,10 @@ impl Session { session_configuration.parent_thread_id, ), ), - code_mode_service: crate::tools::code_mode::CodeModeService::new(Arc::clone( - &code_mode_session_provider, - )), + code_mode_service: crate::tools::code_mode::CodeModeService::new( + Arc::clone(&code_mode_session_provider), + &config.features, + ), tool_search_handler_cache: Default::default(), turn_environments: Arc::clone(&turn_environments), }; diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 19fbf0cc18..aab520684e 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -5385,9 +5385,10 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) { /*attestation_provider*/ None, config.http_client_factory(), ), - code_mode_service: crate::tools::code_mode::CodeModeService::new(Arc::new( - codex_code_mode::InProcessCodeModeSessionProvider, - )), + code_mode_service: crate::tools::code_mode::CodeModeService::new( + Arc::new(codex_code_mode::InProcessCodeModeSessionProvider), + &config.features, + ), tool_search_handler_cache: Default::default(), turn_environments: Arc::clone(&turn_environments), }; @@ -7542,9 +7543,10 @@ where /*attestation_provider*/ None, config.http_client_factory(), ), - code_mode_service: crate::tools::code_mode::CodeModeService::new(Arc::new( - codex_code_mode::InProcessCodeModeSessionProvider, - )), + code_mode_service: crate::tools::code_mode::CodeModeService::new( + Arc::new(codex_code_mode::InProcessCodeModeSessionProvider), + &config.features, + ), tool_search_handler_cache: Default::default(), turn_environments: Arc::clone(&turn_environments), }; diff --git a/codex-rs/core/src/tools/code_mode/execute_spec.rs b/codex-rs/core/src/tools/code_mode/execute_spec.rs index 39fbf69eb4..9987396556 100644 --- a/codex-rs/core/src/tools/code_mode/execute_spec.rs +++ b/codex-rs/core/src/tools/code_mode/execute_spec.rs @@ -8,6 +8,7 @@ pub(crate) fn create_code_mode_tool( enabled_tools: &[CodeModeToolDefinition], deferred_tools: &[CodeModeToolDefinition], namespace_descriptions: &BTreeMap, + default_exec_yield_time_ms: u64, code_mode_only: bool, ) -> ToolSpec { const CODE_MODE_FREEFORM_GRAMMAR: &str = r#" @@ -26,6 +27,7 @@ SOURCE: /[\s\S]+/ enabled_tools, deferred_tools, namespace_descriptions, + default_exec_yield_time_ms, code_mode_only, ), format: FreeformToolFormat { @@ -58,6 +60,7 @@ mod tests { &enabled_tools, &[], &BTreeMap::new(), + codex_code_mode::DEFAULT_EXEC_YIELD_TIME_MS, /*code_mode_only*/ true, ), ToolSpec::Freeform(FreeformTool { @@ -66,6 +69,7 @@ mod tests { &enabled_tools, &[], &BTreeMap::new(), + codex_code_mode::DEFAULT_EXEC_YIELD_TIME_MS, /*code_mode_only*/ true, ), format: FreeformToolFormat { diff --git a/codex-rs/core/src/tools/code_mode/mod.rs b/codex-rs/core/src/tools/code_mode/mod.rs index 3d9b12e9b0..1cab908cb1 100644 --- a/codex-rs/core/src/tools/code_mode/mod.rs +++ b/codex-rs/core/src/tools/code_mode/mod.rs @@ -16,6 +16,8 @@ use codex_code_mode::CodeModeSession; use codex_code_mode::CodeModeSessionProvider; use codex_code_mode::CodeModeToolKind; use codex_code_mode::RuntimeResponse; +use codex_features::Feature; +use codex_features::Features; use codex_protocol::models::FunctionCallOutputContentItem; use serde_json::Value as JsonValue; use tokio::sync::OnceCell; @@ -52,6 +54,13 @@ pub(crate) use wait_handler::CodeModeWaitHandler; pub(crate) const PUBLIC_TOOL_NAME: &str = codex_code_mode::PUBLIC_TOOL_NAME; pub(crate) const WAIT_TOOL_NAME: &str = codex_code_mode::WAIT_TOOL_NAME; pub(crate) const DEFAULT_WAIT_YIELD_TIME_MS: u64 = codex_code_mode::DEFAULT_WAIT_YIELD_TIME_MS; +const BUFFERED_EXEC_YIELD_TIME_MS: u64 = 30_000; + +pub(crate) fn default_exec_yield_time_override_ms(features: &Features) -> Option { + features + .enabled(Feature::CodeModeBufferedExec) + .then_some(BUFFERED_EXEC_YIELD_TIME_MS) +} /// Returns true for the un-namespaced code-mode `exec` tool. pub(crate) fn is_exec_tool_name(tool_name: &ToolName) -> bool { @@ -68,16 +77,21 @@ pub(crate) struct CodeModeService { session: OnceCell>, session_provider: Arc, dispatch_broker: Arc, + default_exec_yield_time_override_ms: Option, shutting_down: AtomicBool, } impl CodeModeService { - pub(crate) fn new(session_provider: Arc) -> Self { + pub(crate) fn new( + session_provider: Arc, + features: &Features, + ) -> Self { let dispatch_broker = Arc::new(CodeModeDispatchBroker::new()); Self { session: OnceCell::new(), session_provider, dispatch_broker, + default_exec_yield_time_override_ms: default_exec_yield_time_override_ms(features), shutting_down: AtomicBool::new(false), } } @@ -88,8 +102,11 @@ impl CodeModeService { pub(crate) async fn execute( &self, - request: codex_code_mode::ExecuteRequest, + mut request: codex_code_mode::ExecuteRequest, ) -> Result { + if request.yield_time_ms.is_none() { + request.yield_time_ms = self.default_exec_yield_time_override_ms; + } self.session().await?.execute(request).await } @@ -371,6 +388,7 @@ mod tests { use codex_code_mode::FunctionCallOutputContentItem as CodeModeOutputContentItem; use codex_code_mode::ProcessOwnedCodeModeSessionProvider; use codex_code_mode::RuntimeResponse; + use codex_features::Features; use codex_protocol::models::FunctionCallOutputContentItem; use codex_tools::ToolName; use serde_json::json; @@ -444,11 +462,12 @@ mod tests { #[tokio::test] async fn missing_process_host_falls_back_to_in_process_session() { - let service = CodeModeService::new(Arc::new( - ProcessOwnedCodeModeSessionProvider::with_host_program( + let service = CodeModeService::new( + Arc::new(ProcessOwnedCodeModeSessionProvider::with_host_program( "codex-code-mode-host-does-not-exist".into(), - ), - )); + )), + &Features::with_defaults(), + ); let response = service .execute(ExecuteRequest { diff --git a/codex-rs/core/src/tools/spec_plan.rs b/codex-rs/core/src/tools/spec_plan.rs index fb58089485..1694c1b98a 100644 --- a/codex-rs/core/src/tools/spec_plan.rs +++ b/codex-rs/core/src/tools/spec_plan.rs @@ -2,6 +2,7 @@ use crate::agent::exceeds_thread_spawn_depth_limit; use crate::agent::next_thread_spawn_depth; use crate::session::step_context::StepContext; use crate::session::turn_context::TurnContext; +use crate::tools::code_mode::default_exec_yield_time_override_ms; use crate::tools::code_mode::execute_spec::create_code_mode_tool; use crate::tools::context::ToolInvocation; use crate::tools::effective_tool_mode; @@ -472,6 +473,9 @@ fn build_code_mode_executors( .sort_by(|left, right| compare_code_mode_tools(left, right, &namespace_descriptions)); let deferred_tools = collect_code_mode_exec_prompt_tool_definitions(deferred_exec_prompt_tool_specs.iter()); + let default_exec_yield_time_ms = + default_exec_yield_time_override_ms(&turn_context.config.features) + .unwrap_or(codex_code_mode::DEFAULT_EXEC_YIELD_TIME_MS); vec![ Arc::new(CodeModeExecuteHandler::new( @@ -479,6 +483,7 @@ fn build_code_mode_executors( &enabled_tools, &deferred_tools, &namespace_descriptions, + default_exec_yield_time_ms, tool_mode == ToolMode::CodeModeOnly, ), code_mode_nested_tool_specs, diff --git a/codex-rs/core/src/tools/spec_plan_tests.rs b/codex-rs/core/src/tools/spec_plan_tests.rs index 1422c61b84..6d51754ef5 100644 --- a/codex-rs/core/src/tools/spec_plan_tests.rs +++ b/codex-rs/core/src/tools/spec_plan_tests.rs @@ -1071,6 +1071,20 @@ async fn code_mode_only_exposes_code_executor_and_hides_nested_tools() { ); } +#[tokio::test] +async fn code_mode_buffered_exec_updates_exec_description() { + let plan = probe(|turn| { + set_features(turn, &[Feature::CodeMode, Feature::CodeModeBufferedExec]); + }) + .await; + + let ToolSpec::Freeform(exec) = plan.visible_spec(codex_code_mode::PUBLIC_TOOL_NAME) else { + panic!("expected code mode exec tool"); + }; + assert!(exec.description.contains("Defaults to 30000 ms.")); + assert!(!exec.description.contains("Defaults to 10000 ms.")); +} + #[tokio::test] async fn code_mode_only_exposes_configured_dynamic_namespace_directly() { let plan = probe_with( diff --git a/codex-rs/features/src/lib.rs b/codex-rs/features/src/lib.rs index 0b0f30a223..e7a80495a3 100644 --- a/codex-rs/features/src/lib.rs +++ b/codex-rs/features/src/lib.rs @@ -92,6 +92,8 @@ pub enum Feature { // Experimental /// Enable JavaScript code mode backed by the in-process V8 runtime. CodeMode, + /// Use a 30-second default yield timeout for code mode exec calls. + CodeModeBufferedExec, /// Run JavaScript code mode in the standalone host process. CodeModeHost, /// Restrict model-visible tools to code mode entrypoints (`exec`, `wait`). @@ -859,6 +861,12 @@ pub const FEATURES: &[FeatureSpec] = &[ stage: Stage::UnderDevelopment, default_enabled: false, }, + FeatureSpec { + id: Feature::CodeModeBufferedExec, + key: "code_mode_buffered_exec", + stage: Stage::UnderDevelopment, + default_enabled: false, + }, FeatureSpec { id: Feature::CodeModeHost, key: "code_mode_host",