//! Plugin mention capability enrichment for the TUI.//!//! Mention inventory comes from app-server `plugin/list`, matching the GUI//! client. The current API exposes plugin-level mention metadata there, but not//! effective per-session capability summaries.use super::background_requests::request_plugin_list;use super::*;use codex_app_server_protocol::PluginAvailability;use codex_app_server_protocol::PluginListResponse;use codex_app_server_protocol::PluginSummary;use codex_plugin::PluginCapabilitySummary;pub(super) async fn fetch_plugin_mentions( request_handle: AppServerRequestHandle, cwd: PathBuf,) -> Result<Vec<PluginCapabilitySummary>> { let response = request_plugin_list(request_handle, cwd).await?; Ok(plugin_mentions_from_list_response(response))}fn plugin_mentions_from_list_response( response: PluginListResponse,) -> Vec<PluginCapabilitySummary> { response .marketplaces .into_iter() .flat_map(|marketplace| { let marketplace_name = marketplace.name; marketplace .plugins .into_iter() .filter_map(move |plugin| plugin_mention_from_summary(&marketplace_name, plugin)) }) .collect()}fn plugin_is_eligible_for_mentions(plugin: &PluginSummary) -> bool { plugin.installed && plugin.enabled && plugin.availability != PluginAvailability::DisabledByAdmin}fn plugin_mention_from_summary( marketplace_name: &str, plugin: PluginSummary,) -> Option<PluginCapabilitySummary> { if !plugin_is_eligible_for_mentions(&plugin) { return None; } Some(PluginCapabilitySummary { config_name: plugin.id.clone(), display_name: plugin_mention_display_name(&plugin), description: plugin_mention_description(marketplace_name, &plugin), has_skills: false, mcp_server_names: Vec::new(), app_connector_ids: Vec::new(), })}fn plugin_mention_display_name(plugin: &PluginSummary) -> String { plugin .interface .as_ref() .and_then(|interface| interface.display_name.as_deref()) .map(str::trim) .filter(|display_name| !display_name.is_empty()) .map(str::to_string) .unwrap_or_else(|| plugin.name.clone())}fn plugin_mention_description(marketplace_name: &str, plugin: &PluginSummary) -> Option<String> { plugin .interface .as_ref() .and_then(|interface| interface.short_description.as_deref()) .map(str::trim) .filter(|description| !description.is_empty()) .map(str::to_string) .or_else(|| { let marketplace_name = marketplace_name.trim(); (!marketplace_name.is_empty()).then(|| marketplace_name.to_string()) })}#[cfg(test)]mod tests { use super::*; use codex_app_server_protocol::PluginAuthPolicy; use codex_app_server_protocol::PluginAvailability; use codex_app_server_protocol::PluginInstallPolicy; use codex_app_server_protocol::PluginListResponse; use codex_app_server_protocol::PluginMarketplaceEntry; use codex_app_server_protocol::PluginSource; use pretty_assertions::assert_eq; #[test] fn plugin_mentions_use_plugin_list_summaries_and_gui_eligibility() { let active = plugin_summary("active"); let mut disabled_by_admin = plugin_summary("disabled-by-admin"); disabled_by_admin.availability = PluginAvailability::DisabledByAdmin; let mut disabled = plugin_summary("disabled"); disabled.enabled = false; let mut uninstalled = plugin_summary("uninstalled"); uninstalled.installed = false; let response = PluginListResponse { marketplaces: vec![PluginMarketplaceEntry { name: "server-marketplace".to_string(), path: None, interface: None, plugins: vec![active, disabled_by_admin, disabled, uninstalled], }], marketplace_load_errors: Vec::new(), featured_plugin_ids: Vec::new(), }; assert_eq!( plugin_mentions_from_list_response(response), vec![PluginCapabilitySummary { config_name: "active@server-marketplace".to_string(), display_name: "active".to_string(), description: Some("server-marketplace".to_string()), has_skills: false, mcp_server_names: Vec::new(), app_connector_ids: Vec::new(), }] ); } fn plugin_summary(name: &str) -> PluginSummary { PluginSummary { id: format!("{name}@server-marketplace"), remote_plugin_id: Some(format!("plugins~{name}")), local_version: None, name: name.to_string(), share_context: None, source: PluginSource::Remote, installed: true, enabled: true, install_policy: PluginInstallPolicy::Available, auth_policy: PluginAuthPolicy::OnInstall, availability: PluginAvailability::Available, interface: None, keywords: Vec::new(), } }}