Codex Handbook
core/src/tools/handlers/request_user_input.rs 99 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::handlers::request_user_input_spec::REQUEST_USER_INPUT_TOOL_NAME;use crate::tools::handlers::request_user_input_spec::create_request_user_input_tool;use crate::tools::handlers::request_user_input_spec::normalize_request_user_input_args;use crate::tools::handlers::request_user_input_spec::request_user_input_tool_description;use crate::tools::handlers::request_user_input_spec::request_user_input_unavailable_message;use crate::tools::registry::CoreToolRuntime;use crate::tools::registry::ToolExecutor;use codex_protocol::config_types::ModeKind;use codex_protocol::request_user_input::RequestUserInputArgs;use codex_tools::ToolName;use codex_tools::ToolSpec;pub struct RequestUserInputHandler {    pub available_modes: Vec<ModeKind>,}impl ToolExecutor<ToolInvocation> for RequestUserInputHandler {    fn tool_name(&self) -> ToolName {        ToolName::plain(REQUEST_USER_INPUT_TOOL_NAME)    }    fn spec(&self) -> ToolSpec {        create_request_user_input_tool(request_user_input_tool_description(&self.available_modes))    }    fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> {        Box::pin(self.handle_call(invocation))    }}impl RequestUserInputHandler {    async fn handle_call(        &self,        invocation: ToolInvocation,    ) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {        let ToolInvocation {            session,            turn,            call_id,            payload,            ..        } = invocation;        let arguments = match payload {            ToolPayload::Function { arguments } => arguments,            _ => {                return Err(FunctionCallError::RespondToModel(format!(                    "{REQUEST_USER_INPUT_TOOL_NAME} handler received unsupported payload"                )));            }        };        if turn.session_source.is_non_root_agent() {            return Err(FunctionCallError::RespondToModel(                "request_user_input can only be used by the root thread".to_string(),            ));        }        let mode = session.collaboration_mode().await.mode;        if let Some(message) = request_user_input_unavailable_message(mode, &self.available_modes) {            return Err(FunctionCallError::RespondToModel(message));        }        let args: RequestUserInputArgs = parse_arguments(&arguments)?;        let args =            normalize_request_user_input_args(args).map_err(FunctionCallError::RespondToModel)?;        let response = session            .request_user_input(turn.as_ref(), call_id, args)            .await            .ok_or_else(|| {                FunctionCallError::RespondToModel(format!(                    "{REQUEST_USER_INPUT_TOOL_NAME} was cancelled before receiving a response"                ))            })?;        let content = serde_json::to_string(&response).map_err(|err| {            FunctionCallError::Fatal(format!(                "failed to serialize {REQUEST_USER_INPUT_TOOL_NAME} response: {err}"            ))        })?;        Ok(boxed_tool_output(FunctionToolOutput::from_text(            content,            Some(true),        )))    }}impl CoreToolRuntime for RequestUserInputHandler {}#[cfg(test)]#[path = "request_user_input_tests.rs"]mod tests;