Codex Handbook
tools/src/request_plugin_install.rs 136 lines
use std::collections::BTreeMap;use codex_app_server_protocol::AppInfo;use codex_app_server_protocol::McpElicitationObjectType;use codex_app_server_protocol::McpElicitationSchema;use codex_app_server_protocol::McpServerElicitationRequest;use codex_app_server_protocol::McpServerElicitationRequestParams;use serde::Deserialize;use serde::Serialize;use serde_json::json;use crate::DiscoverableTool;use crate::DiscoverableToolAction;use crate::DiscoverableToolType;pub const REQUEST_PLUGIN_INSTALL_APPROVAL_KIND_VALUE: &str = "tool_suggestion";pub const REQUEST_PLUGIN_INSTALL_PERSIST_KEY: &str = "persist";pub const REQUEST_PLUGIN_INSTALL_PERSIST_ALWAYS_VALUE: &str = "always";#[derive(Debug, Deserialize)]pub struct RequestPluginInstallArgs {    pub tool_type: DiscoverableToolType,    pub action_type: DiscoverableToolAction,    pub tool_id: String,    pub suggest_reason: String,}#[derive(Debug, Serialize, PartialEq, Eq)]pub struct RequestPluginInstallResult {    pub completed: bool,    pub user_confirmed: bool,    pub tool_type: DiscoverableToolType,    pub action_type: DiscoverableToolAction,    pub tool_id: String,    pub tool_name: String,    pub suggest_reason: String,}#[derive(Debug, Serialize, PartialEq, Eq)]pub struct RequestPluginInstallMeta<'a> {    pub codex_approval_kind: &'static str,    pub persist: &'static str,    pub tool_type: DiscoverableToolType,    pub suggest_type: DiscoverableToolAction,    pub suggest_reason: &'a str,    pub tool_id: &'a str,    pub tool_name: &'a str,    #[serde(skip_serializing_if = "Option::is_none")]    pub install_url: Option<&'a str>,    #[serde(skip_serializing_if = "Option::is_none")]    pub remote_plugin_id: Option<&'a str>,    #[serde(skip_serializing_if = "Option::is_none")]    pub app_connector_ids: Option<&'a [String]>,}pub fn build_request_plugin_install_elicitation_request(    server_name: &str,    thread_id: String,    turn_id: String,    args: &RequestPluginInstallArgs,    suggest_reason: &str,    tool: &DiscoverableTool,) -> McpServerElicitationRequestParams {    let message = suggest_reason.to_string();    McpServerElicitationRequestParams {        thread_id,        turn_id: Some(turn_id),        server_name: server_name.to_string(),        request: McpServerElicitationRequest::Form {            meta: Some(json!(build_request_plugin_install_meta(                args.tool_type,                args.action_type,                suggest_reason,                tool,            ))),            message,            requested_schema: McpElicitationSchema {                schema_uri: None,                type_: McpElicitationObjectType::Object,                properties: BTreeMap::new(),                required: None,            },        },    }}pub fn all_requested_connectors_picked_up(    expected_connector_ids: &[String],    accessible_connectors: &[AppInfo],) -> bool {    expected_connector_ids.iter().all(|connector_id| {        verified_connector_install_completed(connector_id, accessible_connectors)    })}pub fn verified_connector_install_completed(    tool_id: &str,    accessible_connectors: &[AppInfo],) -> bool {    accessible_connectors        .iter()        .find(|connector| connector.id == tool_id)        .is_some_and(|connector| connector.is_accessible)}fn build_request_plugin_install_meta<'a>(    tool_type: DiscoverableToolType,    action_type: DiscoverableToolAction,    suggest_reason: &'a str,    tool: &'a DiscoverableTool,) -> RequestPluginInstallMeta<'a> {    let (remote_plugin_id, app_connector_ids) = match tool {        DiscoverableTool::Connector(_) => (None, None),        DiscoverableTool::Plugin(plugin) => (            plugin.remote_plugin_id.as_deref(),            Some(plugin.app_connector_ids.as_slice()),        ),    };    RequestPluginInstallMeta {        codex_approval_kind: REQUEST_PLUGIN_INSTALL_APPROVAL_KIND_VALUE,        persist: REQUEST_PLUGIN_INSTALL_PERSIST_ALWAYS_VALUE,        tool_type,        suggest_type: action_type,        suggest_reason,        tool_id: tool.id(),        tool_name: tool.name(),        install_url: tool.install_url(),        remote_plugin_id,        app_connector_ids,    }}#[cfg(test)]#[path = "request_plugin_install_tests.rs"]mod tests;