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;