diff --git a/codex-rs/core/src/tools/handlers/sleep.rs b/codex-rs/core/src/tools/handlers/sleep.rs index ecc55c2e1511..c3068e6e8dc2 100644 --- a/codex-rs/core/src/tools/handlers/sleep.rs +++ b/codex-rs/core/src/tools/handlers/sleep.rs @@ -13,6 +13,7 @@ use codex_tools::JsonSchema; use codex_tools::ResponsesApiNamespace; use codex_tools::ResponsesApiNamespaceTool; use codex_tools::ResponsesApiTool; +use codex_tools::ToolExposure; use codex_tools::ToolName; use codex_tools::ToolSpec; use serde::Deserialize; @@ -68,6 +69,10 @@ impl ToolExecutor for SleepHandler { create_sleep_tool() } + fn exposure(&self) -> ToolExposure { + ToolExposure::DirectModelOnly + } + fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { Box::pin(async move { let ToolInvocation { diff --git a/codex-rs/core/src/tools/spec_plan_tests.rs b/codex-rs/core/src/tools/spec_plan_tests.rs index 556979d443e4..c6e9ad51ffb8 100644 --- a/codex-rs/core/src/tools/spec_plan_tests.rs +++ b/codex-rs/core/src/tools/spec_plan_tests.rs @@ -726,6 +726,55 @@ async fn sleep_tool_follows_current_time_config() { ); } +#[tokio::test] +async fn sleep_tool_stays_direct_and_outside_code_mode() { + for code_mode_only in [false, true] { + let plan = probe(|turn| { + set_features( + turn, + &[ + Feature::CodeMode, + Feature::CurrentTimeReminder, + Feature::MultiAgentV2, + ], + ); + if code_mode_only { + set_feature(turn, Feature::CodeModeOnly, /*enabled*/ true); + } + update_config(turn, |config| { + config.current_time_reminder = Some(CurrentTimeReminderConfig { + sleep_tool: true, + ..CurrentTimeReminderConfig::default() + }); + config.multi_agent_v2.wait_agent_enabled = false; + }); + }) + .await; + + assert!( + plan.namespace_function_names("clock") + .iter() + .any(|name| name == "sleep") + ); + let sleep_tool_name = ToolName::namespaced("clock", "sleep").to_string(); + let wait_agent_tool_name = + ToolName::namespaced(MULTI_AGENT_V2_NAMESPACE, "wait_agent").to_string(); + assert_eq!( + plan.exposure(&sleep_tool_name), + ToolExposure::DirectModelOnly + ); + plan.assert_registered_lacks(&[wait_agent_tool_name.as_str()]); + + let ToolSpec::Freeform(exec) = plan.visible_spec(codex_code_mode::PUBLIC_TOOL_NAME) else { + panic!("expected code mode exec tool"); + }; + if code_mode_only { + assert!(exec.description.contains("clock__curr_time")); + } + assert!(!exec.description.contains("clock__sleep")); + } +} + #[tokio::test] async fn mcp_and_tool_search_follow_direct_and_deferred_tool_exposure() { let direct_mcp = probe_with(