core/src/tools/handlers/request_permissions.rs
120 lines
use codex_protocol::request_permissions::RequestPermissionsArgs;use codex_sandboxing::policy_transforms::normalize_additional_permissions;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::parse_arguments_with_base_path;use crate::tools::handlers::resolve_tool_environment;use crate::tools::handlers::shell_spec::create_request_permissions_tool;use crate::tools::handlers::shell_spec::request_permissions_tool_description;use crate::tools::registry::CoreToolRuntime;use crate::tools::registry::ToolExecutor;use codex_tools::ToolName;use codex_tools::ToolSpec;use serde::Deserialize;pub struct RequestPermissionsHandler;#[derive(Deserialize)]struct RequestPermissionsEnvironmentArgs { #[serde(default, rename = "environment_id", alias = "environmentId")] environment_id: Option<String>,}impl ToolExecutor<ToolInvocation> for RequestPermissionsHandler { fn tool_name(&self) -> ToolName { ToolName::plain("request_permissions") } fn spec(&self) -> ToolSpec { create_request_permissions_tool(request_permissions_tool_description()) } fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { Box::pin(self.handle_call(invocation)) }}impl RequestPermissionsHandler { async fn handle_call( &self, invocation: ToolInvocation, ) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> { let ToolInvocation { session, turn, cancellation_token, call_id, payload, .. } = invocation; let arguments = match payload { ToolPayload::Function { arguments } => arguments, _ => { return Err(FunctionCallError::RespondToModel( "request_permissions handler received unsupported payload".to_string(), )); } }; let environment_args: RequestPermissionsEnvironmentArgs = parse_arguments(&arguments)?; let Some(turn_environment) = resolve_tool_environment(turn.as_ref(), environment_args.environment_id.as_deref())? else { return Err(FunctionCallError::RespondToModel( "request_permissions requires a primary environment".to_string(), )); }; // TODO(anp): Migrate request_permissions parsing and permission profiles to PathUri so // environment-native foreign paths do not require host conversion. let native_cwd = turn_environment.cwd().to_abs_path().map_err(|err| { FunctionCallError::RespondToModel(format!( "request_permissions cwd `{}` is not native to the Codex host: {err}", turn_environment.cwd() )) })?; let mut args: RequestPermissionsArgs = parse_arguments_with_base_path(&arguments, &native_cwd)?; args.permissions = normalize_additional_permissions(args.permissions.into()) .map(codex_protocol::request_permissions::RequestPermissionProfile::from) .map_err(FunctionCallError::RespondToModel)?; if args.permissions.is_empty() { return Err(FunctionCallError::RespondToModel( "request_permissions requires at least one permission".to_string(), )); } let response = session .request_permissions_for_environment( &turn, call_id, args, turn_environment.selection(), cancellation_token, ) .await .ok_or_else(|| { FunctionCallError::RespondToModel( "request_permissions was cancelled before receiving a response".to_string(), ) })?; let content = serde_json::to_string(&response).map_err(|err| { FunctionCallError::Fatal(format!( "failed to serialize request_permissions response: {err}" )) })?; Ok(boxed_tool_output(FunctionToolOutput::from_text( content, Some(true), ))) }}impl CoreToolRuntime for RequestPermissionsHandler {}