Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 95 additions & 40 deletions codex-rs/core/src/tools/handlers/request_plugin_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@ use std::sync::Arc;
use codex_analytics::PluginInstallRequestSource;
use codex_analytics::PluginInstallRequested;
use codex_analytics::PluginInstallRequestedPlugin;
use codex_analytics::PluginInstallSuggestionOutcome;
use codex_analytics::PluginInstallSuggestionOutcomeTool;
use codex_analytics::PluginInstallSuggestionResponseAction;
use codex_analytics::PluginInstallSuggestionToolType;
use codex_analytics::build_track_events_context;
use codex_config::types::ToolSuggestDisabledTool;
use codex_core_plugins::remote::REMOTE_GLOBAL_MARKETPLACE_NAME;
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
use codex_rmcp_client::ElicitationAction;
use codex_rmcp_client::ElicitationResponse;
use codex_tools::DiscoverablePluginInfo;
use codex_tools::DiscoverableTool;
use codex_tools::DiscoverableToolAction;
use codex_tools::DiscoverableToolType;
Expand Down Expand Up @@ -180,23 +185,24 @@ impl RequestPluginInstallHandler {
let tool_type = tool.tool_type();

let suggestion_id = format!("request_plugin_install_{call_id}");
let source = match self.presentation {
ToolSuggestPresentation::ListTool => PluginInstallRequestSource::LegacyDiscovery,
ToolSuggestPresentation::RecommendationContext => {
PluginInstallRequestSource::EndpointRecommendation
}
};
let tracking = build_track_events_context(
turn.model_info.slug.clone(),
session.thread_id.to_string(),
turn.sub_id.clone(),
turn.originator.clone(),
);
if let DiscoverableTool::Plugin(plugin) = &tool {
let source = match self.presentation {
ToolSuggestPresentation::ListTool => PluginInstallRequestSource::LegacyDiscovery,
ToolSuggestPresentation::RecommendationContext => {
PluginInstallRequestSource::EndpointRecommendation
}
};
session
.services
.analytics_events_client
.track_plugin_install_requested(
build_track_events_context(
turn.model_info.slug.clone(),
session.thread_id.to_string(),
turn.sub_id.clone(),
turn.originator.clone(),
),
tracking.clone(),
PluginInstallRequested {
suggestion_id: suggestion_id.clone(),
plugins: vec![PluginInstallRequestedPlugin {
Expand All @@ -210,7 +216,7 @@ impl RequestPluginInstallHandler {
);
}

let request_id = RequestId::String(suggestion_id.into());
let request_id = RequestId::String(suggestion_id.clone().into());
let request = build_request_plugin_install_elicitation_request(suggest_reason, &tool);
let elicitation = session
.request_mcp_server_elicitation(
Expand Down Expand Up @@ -243,24 +249,64 @@ impl RequestPluginInstallHandler {
}

if elicitation.sent {
let tool_type = match tool_type {
DiscoverableToolType::Connector => "connector",
DiscoverableToolType::Plugin => "plugin",
let (outcome_tool_type, tool_type_name) = match tool_type {
DiscoverableToolType::Connector => {
(PluginInstallSuggestionToolType::Connector, "connector")
}
DiscoverableToolType::Plugin => (PluginInstallSuggestionToolType::Plugin, "plugin"),
};
let response_action = match response.as_ref().map(|response| &response.action) {
Some(ElicitationAction::Accept) => "accept",
Some(ElicitationAction::Decline) => "decline",
Some(ElicitationAction::Cancel) => "cancel",
None => "unavailable",
let (response_action, response_action_name) =
match response.as_ref().map(|response| &response.action) {
Some(ElicitationAction::Accept) => {
(PluginInstallSuggestionResponseAction::Accept, "accept")
}
Some(ElicitationAction::Decline) => {
(PluginInstallSuggestionResponseAction::Decline, "decline")
}
Some(ElicitationAction::Cancel) => {
(PluginInstallSuggestionResponseAction::Cancel, "cancel")
}
None => (
PluginInstallSuggestionResponseAction::Unavailable,
"unavailable",
),
};
let (remote_plugin_id, connector_ids) = match &tool {
DiscoverableTool::Connector(connector) => (None, vec![connector.id.clone()]),
DiscoverableTool::Plugin(plugin) => (
plugin.remote_plugin_id.clone(),
plugin.app_connector_ids.clone(),
),
};
turn.session_telemetry.record_plugin_install_suggestion(
tool_type,
tool_type_name,
tool.id(),
tool.name(),
response_action,
response_action_name,
user_confirmed,
completed,
);
session
.services
.analytics_events_client
.track_plugin_install_suggestion_outcome(
tracking,
PluginInstallSuggestionOutcome {
suggestion_id,
source,
tools: vec![PluginInstallSuggestionOutcomeTool {
tool_type: outcome_tool_type,
tool_id: tool.id().to_string(),
tool_name: tool.name().to_string(),
remote_plugin_id,
connector_ids,
selected: user_confirmed,
completed,
}],
response_action,
user_confirmed,
},
);
}

let content = serde_json::to_string(&RequestPluginInstallResult {
Expand Down Expand Up @@ -367,12 +413,9 @@ async fn verify_request_plugin_install_completed(
}),
DiscoverableTool::Plugin(plugin) => {
if is_remote_plugin_install_suggestion(&plugin.id) {
let (_, accessible_connectors) = tokio::join!(
let (completed, _) = tokio::join!(
refresh_remote_installed_plugins_cache_after_install(
session,
turn,
auth,
plugin.id.as_str(),
session, turn, auth, plugin,
),
refresh_missing_requested_connectors(
turn,
Expand All @@ -382,12 +425,7 @@ async fn verify_request_plugin_install_completed(
plugin.id.as_str(),
)
);
return accessible_connectors.is_some_and(|accessible_connectors| {
all_requested_connectors_picked_up(
&plugin.app_connector_ids,
&accessible_connectors,
)
});
return completed;
}

session.reload_user_config_layer().await;
Expand All @@ -414,11 +452,18 @@ async fn refresh_remote_installed_plugins_cache_after_install(
session: &crate::session::session::Session,
turn: &crate::session::turn_context::TurnContext,
auth: Option<&codex_login::CodexAuth>,
tool_id: &str,
) {
plugin: &DiscoverablePluginInfo,
) -> bool {
let Some(remote_plugin_id) = plugin.remote_plugin_id.as_deref() else {
warn!(
"remote plugin install suggestion did not include a remote plugin id for {}",
plugin.id
);
return false;
};
let plugins_manager = &session.services.plugins_manager;
let plugins_config = turn.config.plugins_config_input();
if let Err(err) = plugins_manager
match plugins_manager
.build_and_cache_remote_installed_plugin_marketplaces(
&plugins_config,
auth,
Expand All @@ -427,9 +472,19 @@ async fn refresh_remote_installed_plugins_cache_after_install(
)
.await
{
warn!(
"failed to refresh remote installed plugins cache after plugin install request for {tool_id}: {err:#}"
);
Ok(marketplaces) => marketplaces
.into_iter()
.flat_map(|marketplace| marketplace.plugins)
.any(|installed_plugin| {
installed_plugin.remote_plugin_id == remote_plugin_id && installed_plugin.installed
}),
Err(err) => {
warn!(
"failed to refresh remote installed plugins cache after plugin install request for {}: {err:#}",
plugin.id
);
false
}
}
}

Expand Down
Loading
Loading