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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion codex-rs/app-server/tests/suite/v2/sleep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ async fn sleep_emits_started_and_completed_items() -> Result<()> {
vec![
responses::sse(vec![
responses::ev_response_created("resp-1"),
responses::ev_function_call(
responses::ev_function_call_with_namespace(
CALL_ID,
"clock",
"sleep",
&serde_json::json!({ "duration_ms": DURATION_MS }).to_string(),
),
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core/src/tools/handlers/current_time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ impl ToolExecutor<ToolInvocation> for CurrentTimeHandler {
fn spec(&self) -> ToolSpec {
ToolSpec::Namespace(ResponsesApiNamespace {
name: NAMESPACE.to_string(),
description: "Tools for reading the current time.".to_string(),
description: "Tools for reading and waiting on time.".to_string(),
tools: vec![ResponsesApiNamespaceTool::Function(ResponsesApiTool {
name: TOOL_NAME.to_string(),
description: "Return the current time in UTC.".to_string(),
Expand Down
37 changes: 22 additions & 15 deletions codex-rs/core/src/tools/handlers/sleep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use crate::tools::registry::ToolExecutor;
use codex_protocol::items::SleepItem;
use codex_protocol::items::TurnItem;
use codex_tools::JsonSchema;
use codex_tools::ResponsesApiNamespace;
use codex_tools::ResponsesApiNamespaceTool;
use codex_tools::ResponsesApiTool;
use codex_tools::ToolName;
use codex_tools::ToolSpec;
Expand All @@ -17,7 +19,8 @@ use std::collections::BTreeMap;
use std::time::Duration;
use std::time::Instant;

const SLEEP_TOOL_NAME: &str = "sleep";
const NAMESPACE: &str = "clock";
const TOOL_NAME: &str = "sleep";
const MAX_SLEEP_DURATION_MS: u64 = 3_600_000;

pub struct SleepHandler;
Expand All @@ -36,24 +39,28 @@ fn create_sleep_tool() -> ToolSpec {
))),
)]);

ToolSpec::Function(ResponsesApiTool {
name: SLEEP_TOOL_NAME.to_string(),
description: "Pause execution for a specified duration. The sleep ends early when new input arrives for the active turn. Returns the elapsed wall-clock time."
.to_string(),
strict: false,
defer_loading: None,
parameters: JsonSchema::object(
properties,
Some(vec!["duration_ms".to_string()]),
/*additional_properties*/ Some(false.into()),
),
output_schema: None,
ToolSpec::Namespace(ResponsesApiNamespace {
name: NAMESPACE.to_string(),
description: "Tools for reading and waiting on time.".to_string(),
tools: vec![ResponsesApiNamespaceTool::Function(ResponsesApiTool {
name: TOOL_NAME.to_string(),
description: "Pause execution for a specified duration. The sleep ends early when new input arrives for the active turn. Returns the elapsed wall-clock time."
.to_string(),
strict: false,
defer_loading: None,
parameters: JsonSchema::object(
properties,
Some(vec!["duration_ms".to_string()]),
/*additional_properties*/ Some(false.into()),
),
output_schema: None,
})],
})
}

impl ToolExecutor<ToolInvocation> for SleepHandler {
fn tool_name(&self) -> ToolName {
ToolName::plain(SLEEP_TOOL_NAME)
ToolName::namespaced(NAMESPACE, TOOL_NAME)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve legacy sleep hook matching

When hooks are enabled and a user already has a PreToolUse/PostToolUse matcher for sleep, this rename changes the hook-facing name from sleep to clocksleep: the default function hook payload uses flat_tool_name(&invocation.tool_name), and the hook engine matches only that canonical name plus explicit aliases. Because SleepHandler does not add a sleep matcher alias, existing hooks that used to audit or block the sleep tool stop running as soon as the model calls the new clock.sleep; please preserve sleep as a matcher alias or override the hook payload while keeping the model-visible namespace.

Useful? React with 👍 / 👎.

}

fn spec(&self) -> ToolSpec {
Expand All @@ -71,7 +78,7 @@ impl ToolExecutor<ToolInvocation> for SleepHandler {
} = invocation;
let ToolPayload::Function { arguments } = payload else {
return Err(FunctionCallError::RespondToModel(format!(
"{SLEEP_TOOL_NAME} handler received unsupported payload"
"{TOOL_NAME} handler received unsupported payload"
)));
};
let args: SleepArgs = parse_arguments(&arguments)?;
Expand Down
5 changes: 3 additions & 2 deletions codex-rs/core/src/tools/spec_plan_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -726,13 +726,14 @@ async fn sleep_tool_follows_feature_gate() {
set_feature(turn, Feature::SleepTool, /*enabled*/ false);
})
.await;
disabled.assert_visible_lacks(&["sleep"]);
disabled.assert_visible_lacks(&["clock"]);

let enabled = probe(|turn| {
set_feature(turn, Feature::SleepTool, /*enabled*/ true);
})
.await;
enabled.assert_visible_contains(&["sleep"]);
enabled.assert_visible_contains(&["clock"]);
assert_eq!(enabled.namespace_function_names("clock"), ["sleep"]);
}

#[tokio::test]
Expand Down
6 changes: 4 additions & 2 deletions codex-rs/core/tests/suite/pending_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,17 +361,19 @@ async fn any_new_input_interrupts_sleep() {

let first_chunks = vec![
chunk(ev_response_created("resp-1")),
chunk(ev_function_call(
chunk(ev_function_call_with_namespace(
FIRST_SLEEP_CALL_ID,
"clock",
"sleep",
&sleep_arguments,
)),
chunk(ev_completed("resp-1")),
];
let second_chunks = vec![
chunk(ev_response_created("resp-2")),
chunk(ev_function_call(
chunk(ev_function_call_with_namespace(
SECOND_SLEEP_CALL_ID,
"clock",
"sleep",
&sleep_arguments,
)),
Expand Down
Loading