Codex Handbook
core/src/tools/handlers/multi_agents_v2/wait.rs 189 lines
use super::*;use crate::session::InputQueueActivity;use crate::tools::handlers::multi_agents_spec::WaitAgentTimeoutOptions;use crate::tools::handlers::multi_agents_spec::create_wait_agent_tool_v2;use crate::turn_timing::now_unix_timestamp_ms;use codex_tools::ToolSpec;use std::collections::HashMap;use std::time::Duration;use tokio::time::Instant;use tokio::time::timeout_at;#[derive(Default)]pub(crate) struct Handler {    options: WaitAgentTimeoutOptions,}impl Handler {    pub(crate) fn new(options: WaitAgentTimeoutOptions) -> Self {        Self { options }    }}impl ToolExecutor<ToolInvocation> for Handler {    fn tool_name(&self) -> ToolName {        ToolName::plain("wait_agent")    }    fn spec(&self) -> ToolSpec {        create_wait_agent_tool_v2(self.options)    }    fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> {        Box::pin(self.handle_call(invocation))    }}impl Handler {    async fn handle_call(        &self,        invocation: ToolInvocation,    ) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {        let ToolInvocation {            session,            turn,            payload,            call_id,            ..        } = invocation;        let arguments = function_arguments(payload)?;        let args: WaitArgs = parse_arguments(&arguments)?;        let min_timeout_ms = turn.config.multi_agent_v2.min_wait_timeout_ms;        let max_timeout_ms = turn.config.multi_agent_v2.max_wait_timeout_ms;        let default_timeout_ms = turn.config.multi_agent_v2.default_wait_timeout_ms;        let timeout_ms = match args.timeout_ms {            Some(ms) if ms < min_timeout_ms => {                return Err(FunctionCallError::RespondToModel(format!(                    "timeout_ms must be at least {min_timeout_ms}"                )));            }            Some(ms) if ms > max_timeout_ms => {                return Err(FunctionCallError::RespondToModel(format!(                    "timeout_ms must be at most {max_timeout_ms}"                )));            }            Some(ms) => ms,            None => default_timeout_ms,        };        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;        session            .send_event(                &turn,                CollabWaitingBeginEvent {                    started_at_ms: now_unix_timestamp_ms(),                    sender_thread_id: session.thread_id,                    receiver_thread_ids: Vec::new(),                    receiver_agents: Vec::new(),                    call_id: call_id.clone(),                }                .into(),            )            .await;        let deadline = Instant::now() + Duration::from_millis(timeout_ms as u64);        let outcome = wait_for_activity(&mut activity_rx, pending_activity, deadline).await;        let result = WaitAgentResult::from_outcome(outcome);        session            .send_event(                &turn,                CollabWaitingEndEvent {                    sender_thread_id: session.thread_id,                    call_id,                    completed_at_ms: now_unix_timestamp_ms(),                    agent_statuses: Vec::new(),                    statuses: HashMap::new(),                }                .into(),            )            .await;        Ok(boxed_tool_output(result))    }}impl CoreToolRuntime for Handler {    fn matches_kind(&self, payload: &ToolPayload) -> bool {        matches!(payload, ToolPayload::Function { .. })    }}#[derive(Debug, Deserialize)]#[serde(deny_unknown_fields)]struct WaitArgs {    timeout_ms: Option<i64>,}#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]pub(crate) struct WaitAgentResult {    pub(crate) message: String,    pub(crate) timed_out: bool,}impl WaitAgentResult {    fn from_outcome(outcome: WaitOutcome) -> Self {        let message = match outcome {            WaitOutcome::MailboxActivity => "Wait completed.",            WaitOutcome::Steered => "Wait interrupted by new input.",            WaitOutcome::TimedOut => "Wait timed out.",        };        Self {            message: message.to_string(),            timed_out: outcome == WaitOutcome::TimedOut,        }    }}impl ToolOutput for WaitAgentResult {    fn log_preview(&self) -> String {        tool_output_json_text(self, "wait_agent")    }    fn success_for_logging(&self) -> bool {        true    }    fn to_response_item(&self, call_id: &str, payload: &ToolPayload) -> ResponseInputItem {        tool_output_response_item(call_id, payload, self, /*success*/ None, "wait_agent")    }    fn code_mode_result(&self, _payload: &ToolPayload) -> JsonValue {        tool_output_code_mode_result(self, "wait_agent")    }}#[derive(Clone, Copy, Debug, Eq, PartialEq)]enum WaitOutcome {    MailboxActivity,    Steered,    TimedOut,}async fn wait_for_activity(    activity_rx: &mut tokio::sync::watch::Receiver<InputQueueActivity>,    pending_activity: Option<InputQueueActivity>,    deadline: Instant,) -> WaitOutcome {    if let Some(activity) = pending_activity {        return match activity {            InputQueueActivity::Mailbox => WaitOutcome::MailboxActivity,            InputQueueActivity::Steer => WaitOutcome::Steered,        };    }    match timeout_at(deadline, activity_rx.changed()).await {        Ok(Ok(())) => match *activity_rx.borrow_and_update() {            InputQueueActivity::Mailbox => WaitOutcome::MailboxActivity,            InputQueueActivity::Steer => WaitOutcome::Steered,        },        Ok(Err(_)) | Err(_) => WaitOutcome::TimedOut,    }}