codex_tools/
request_plugin_install.rs1use codex_connectors::AppInfo;
2use codex_protocol::approvals::ElicitationRequest;
3use serde::Deserialize;
4use serde::Serialize;
5use serde_json::json;
6
7use crate::DiscoverableTool;
8use crate::DiscoverableToolAction;
9use crate::DiscoverableToolType;
10
11pub const REQUEST_PLUGIN_INSTALL_APPROVAL_KIND_VALUE: &str = "tool_suggestion";
12pub const REQUEST_PLUGIN_INSTALL_PERSIST_KEY: &str = "persist";
13pub const REQUEST_PLUGIN_INSTALL_PERSIST_ALWAYS_VALUE: &str = "always";
14
15#[derive(Debug, Deserialize)]
16pub struct RequestPluginInstallArgs {
17 pub tool_type: DiscoverableToolType,
18 pub action_type: DiscoverableToolAction,
19 pub tool_id: String,
20 pub suggest_reason: String,
21}
22
23#[derive(Debug, Serialize, PartialEq, Eq)]
24pub struct RequestPluginInstallResult {
25 pub completed: bool,
26 pub user_confirmed: bool,
27 pub tool_type: DiscoverableToolType,
28 pub action_type: DiscoverableToolAction,
29 pub tool_id: String,
30 pub tool_name: String,
31 pub suggest_reason: String,
32}
33
34#[derive(Debug, Serialize, PartialEq, Eq)]
35pub struct RequestPluginInstallMeta<'a> {
36 pub codex_approval_kind: &'static str,
37 pub persist: &'static str,
38 pub tool_type: DiscoverableToolType,
39 pub suggest_type: DiscoverableToolAction,
40 pub suggest_reason: &'a str,
41 pub tool_id: &'a str,
42 pub tool_name: &'a str,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 pub install_url: Option<&'a str>,
45 #[serde(skip_serializing_if = "Option::is_none")]
46 pub remote_plugin_id: Option<&'a str>,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub app_connector_ids: Option<&'a [String]>,
49}
50
51pub fn build_request_plugin_install_elicitation_request(
52 suggest_reason: &str,
53 tool: &DiscoverableTool,
54) -> ElicitationRequest {
55 let message = suggest_reason.to_string();
56
57 ElicitationRequest::Form {
58 meta: Some(json!(build_request_plugin_install_meta(
59 suggest_reason,
60 tool,
61 ))),
62 message,
63 requested_schema: json!({
64 "type": "object",
65 "properties": {},
66 }),
67 }
68}
69
70pub fn all_requested_connectors_picked_up(
71 expected_connector_ids: &[String],
72 accessible_connectors: &[AppInfo],
73) -> bool {
74 expected_connector_ids.iter().all(|connector_id| {
75 verified_connector_install_completed(connector_id, accessible_connectors)
76 })
77}
78
79pub fn verified_connector_install_completed(
80 tool_id: &str,
81 accessible_connectors: &[AppInfo],
82) -> bool {
83 accessible_connectors
84 .iter()
85 .find(|connector| connector.id == tool_id)
86 .is_some_and(|connector| connector.is_accessible)
87}
88
89fn build_request_plugin_install_meta<'a>(
90 suggest_reason: &'a str,
91 tool: &'a DiscoverableTool,
92) -> RequestPluginInstallMeta<'a> {
93 let (tool_type, remote_plugin_id, app_connector_ids) = match tool {
94 DiscoverableTool::Connector(_) => (DiscoverableToolType::Connector, None, None),
95 DiscoverableTool::Plugin(plugin) => (
96 DiscoverableToolType::Plugin,
97 plugin.remote_plugin_id.as_deref(),
98 Some(plugin.app_connector_ids.as_slice()),
99 ),
100 };
101 RequestPluginInstallMeta {
102 codex_approval_kind: REQUEST_PLUGIN_INSTALL_APPROVAL_KIND_VALUE,
103 persist: REQUEST_PLUGIN_INSTALL_PERSIST_ALWAYS_VALUE,
104 tool_type,
105 suggest_type: DiscoverableToolAction::Install,
106 suggest_reason,
107 tool_id: tool.id(),
108 tool_name: tool.name(),
109 install_url: tool.install_url(),
110 remote_plugin_id,
111 app_connector_ids,
112 }
113}
114
115#[cfg(test)]
116#[path = "request_plugin_install_tests.rs"]
117mod tests;