Codex Handbook
core/src/tools/handlers/sleep.rs 131 lines
use crate::function_tool::FunctionCallError;use crate::tools::context::FunctionToolOutput;use crate::tools::context::ToolInvocation;use crate::tools::context::ToolPayload;use crate::tools::context::boxed_tool_output;use crate::tools::handlers::parse_arguments;use crate::tools::registry::CoreToolRuntime;use crate::tools::registry::ToolExecutor;use codex_protocol::items::SleepItem;use codex_protocol::items::TurnItem;use codex_tools::JsonSchema;use codex_tools::ResponsesApiTool;use codex_tools::ToolName;use codex_tools::ToolSpec;use serde::Deserialize;use std::collections::BTreeMap;use std::time::Duration;use std::time::Instant;const SLEEP_TOOL_NAME: &str = "sleep";const MAX_SLEEP_DURATION_MS: u64 = 3_600_000;pub struct SleepHandler;#[derive(Debug, Deserialize)]#[serde(deny_unknown_fields)]struct SleepArgs {    duration_ms: u64,}fn create_sleep_tool() -> ToolSpec {    let properties = BTreeMap::from([(        "duration_ms".to_string(),        JsonSchema::number(Some(format!(            "How long to sleep in milliseconds. Must be between 1 and {MAX_SLEEP_DURATION_MS}."        ))),    )]);    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,    })}impl ToolExecutor<ToolInvocation> for SleepHandler {    fn tool_name(&self) -> ToolName {        ToolName::plain(SLEEP_TOOL_NAME)    }    fn spec(&self) -> ToolSpec {        create_sleep_tool()    }    fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> {        Box::pin(async move {            let ToolInvocation {                session,                turn,                call_id,                payload,                ..            } = invocation;            let ToolPayload::Function { arguments } = payload else {                return Err(FunctionCallError::RespondToModel(format!(                    "{SLEEP_TOOL_NAME} handler received unsupported payload"                )));            };            let args: SleepArgs = parse_arguments(&arguments)?;            if !(1..=MAX_SLEEP_DURATION_MS).contains(&args.duration_ms) {                return Err(FunctionCallError::RespondToModel(format!(                    "duration_ms must be between 1 and {MAX_SLEEP_DURATION_MS}"                )));            }            let started = Instant::now();            let item = TurnItem::Sleep(SleepItem {                id: call_id,                duration_ms: args.duration_ms,            });            session.emit_turn_item_started(turn.as_ref(), &item).await;            let turn_state = session                .input_queue                .turn_state_for_sub_id(&session.active_turn, &turn.sub_id)                .await;            let (mut activity_rx, pending_activity) = session                .input_queue                .subscribe_activity(turn_state.as_deref())                .await;            let interrupted = if pending_activity.is_some() {                true            } else {                let sleep = tokio::time::sleep(Duration::from_millis(args.duration_ms));                tokio::pin!(sleep);                tokio::select! {                    () = &mut sleep => false,                    result = activity_rx.changed() => {                        if result.is_ok() {                            true                        } else {                            sleep.await;                            false                        }                    }                }            };            session.emit_turn_item_completed(turn.as_ref(), item).await;            let message = if interrupted {                "Sleep interrupted by new input."            } else {                "Sleep completed."            };            let wall_time_seconds = started.elapsed().as_secs_f64();            Ok(boxed_tool_output(FunctionToolOutput::from_text(                format!("Wall time: {wall_time_seconds:.4} seconds\n{message}"),                /*success*/ Some(true),            )))        })    }}impl CoreToolRuntime for SleepHandler {}