Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
  • Loading branch information
pakrym-oai authored and copyberry committed Jul 21, 2026
commit 99efeef6506cd7f6512404d0ad8755a87ff5a011
21 changes: 18 additions & 3 deletions codex-rs/code-mode-protocol/src/description.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,14 @@ pub fn build_exec_tool_description(
enabled_tools: &[ToolDefinition],
deferred_tools: &[ToolDefinition],
namespace_descriptions: &BTreeMap<String, ToolNamespaceDescription>,
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());
}
Expand Down Expand Up @@ -868,6 +872,7 @@ mod tests {
}],
&[],
&BTreeMap::new(),
crate::DEFAULT_EXEC_YIELD_TIME_MS,
/*code_mode_only*/ true,
);
assert!(description.contains(
Expand All @@ -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)`"));
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -972,6 +983,7 @@ bar"
}],
&[],
&namespace_descriptions,
crate::DEFAULT_EXEC_YIELD_TIME_MS,
/*code_mode_only*/ true,
);

Expand Down Expand Up @@ -1071,6 +1083,7 @@ bar"
],
&[],
&BTreeMap::new(),
crate::DEFAULT_EXEC_YIELD_TIME_MS,
/*code_mode_only*/ true,
);

Expand Down Expand Up @@ -1106,6 +1119,7 @@ bar"
&[],
&[deferred_tool],
&BTreeMap::new(),
crate::DEFAULT_EXEC_YIELD_TIME_MS,
/*code_mode_only*/ true,
);

Expand All @@ -1127,6 +1141,7 @@ bar"
output_schema: None,
}],
&BTreeMap::new(),
crate::DEFAULT_EXEC_YIELD_TIME_MS,
/*code_mode_only*/ false,
);

Expand Down
6 changes: 6 additions & 0 deletions codex-rs/core/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,9 @@
"code_mode": {
"$ref": "#/definitions/FeatureToml_for_CodeModeConfigToml"
},
"code_mode_buffered_exec": {
"type": "boolean"
},
"code_mode_host": {
"type": "boolean"
},
Expand Down Expand Up @@ -4896,6 +4899,9 @@
"code_mode": {
"$ref": "#/definitions/FeatureToml_for_CodeModeConfigToml"
},
"code_mode_buffered_exec": {
"type": "boolean"
},
"code_mode_host": {
"type": "boolean"
},
Expand Down
7 changes: 4 additions & 3 deletions codex-rs/core/src/session/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
};
Expand Down
14 changes: 8 additions & 6 deletions codex-rs/core/src/session/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
};
Expand Down Expand Up @@ -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),
};
Expand Down
4 changes: 4 additions & 0 deletions codex-rs/core/src/tools/code_mode/execute_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub(crate) fn create_code_mode_tool(
enabled_tools: &[CodeModeToolDefinition],
deferred_tools: &[CodeModeToolDefinition],
namespace_descriptions: &BTreeMap<String, codex_code_mode::ToolNamespaceDescription>,
default_exec_yield_time_ms: u64,
code_mode_only: bool,
) -> ToolSpec {
const CODE_MODE_FREEFORM_GRAMMAR: &str = r#"
Expand All @@ -26,6 +27,7 @@ SOURCE: /[\s\S]+/
enabled_tools,
deferred_tools,
namespace_descriptions,
default_exec_yield_time_ms,
code_mode_only,
),
format: FreeformToolFormat {
Expand Down Expand Up @@ -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 {
Expand All @@ -66,6 +69,7 @@ mod tests {
&enabled_tools,
&[],
&BTreeMap::new(),
codex_code_mode::DEFAULT_EXEC_YIELD_TIME_MS,
/*code_mode_only*/ true,
),
format: FreeformToolFormat {
Expand Down
31 changes: 25 additions & 6 deletions codex-rs/core/src/tools/code_mode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<u64> {
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 {
Expand All @@ -68,16 +77,21 @@ pub(crate) struct CodeModeService {
session: OnceCell<Arc<dyn CodeModeSession>>,
session_provider: Arc<dyn CodeModeSessionProvider>,
dispatch_broker: Arc<CodeModeDispatchBroker>,
default_exec_yield_time_override_ms: Option<u64>,
shutting_down: AtomicBool,
}

impl CodeModeService {
pub(crate) fn new(session_provider: Arc<dyn CodeModeSessionProvider>) -> Self {
pub(crate) fn new(
session_provider: Arc<dyn CodeModeSessionProvider>,
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),
}
}
Expand All @@ -88,8 +102,11 @@ impl CodeModeService {

pub(crate) async fn execute(
&self,
request: codex_code_mode::ExecuteRequest,
mut request: codex_code_mode::ExecuteRequest,
) -> Result<codex_code_mode::StartedCell, String> {
if request.yield_time_ms.is_none() {
request.yield_time_ms = self.default_exec_yield_time_override_ms;
}
self.session().await?.execute(request).await
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions codex-rs/core/src/tools/spec_plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -472,13 +473,17 @@ 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(
create_code_mode_tool(
&enabled_tools,
&deferred_tools,
&namespace_descriptions,
default_exec_yield_time_ms,
tool_mode == ToolMode::CodeModeOnly,
),
code_mode_nested_tool_specs,
Expand Down
14 changes: 14 additions & 0 deletions codex-rs/core/src/tools/spec_plan_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
8 changes: 8 additions & 0 deletions codex-rs/features/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
Expand Down Expand Up @@ -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",
Expand Down
Loading