Skip to content
Merged
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
101 changes: 53 additions & 48 deletions codex-rs/app-server/src/request_processors/plugins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1442,7 +1442,9 @@ impl PluginRequestProcessor {
.await;
}

let plugin_apps = load_plugin_apps(result.installed_path.as_path()).await;
let plugin_app_declarations = load_plugin_apps(result.installed_path.as_path()).await;
let plugin_apps =
codex_plugin::app_connector_ids_from_declarations(&plugin_app_declarations);
let auth = self.auth_manager.auth().await;
let apps_needing_auth = self
.plugin_apps_needing_auth_for_install(
Expand Down Expand Up @@ -1559,52 +1561,55 @@ impl PluginRequestProcessor {
}

let is_chatgpt_auth = auth.as_ref().is_some_and(CodexAuth::is_chatgpt_auth);
let apps_needing_auth =
if let Some(app_ids_needing_auth) = install_result.app_ids_needing_auth {
if app_ids_needing_auth.is_empty()
|| !config.features.apps_enabled_for_auth(is_chatgpt_auth)
{
Vec::new()
} else {
let plugin_apps = app_ids_needing_auth
.into_iter()
.map(codex_plugin::AppConnectorId)
.collect::<Vec<_>>();
let app_category_by_id = remote_detail
.app_manifest
.as_ref()
.map(plugin_app_category_by_id_from_value)
.unwrap_or_default();
let all_connectors = connectors::list_cached_all_connectors(&config)
.await
.unwrap_or_default();
connectors::connectors_for_plugin_apps(all_connectors, &plugin_apps)
.into_iter()
.map(|connector| {
let category = app_category_by_id
.get(&connector.id)
.cloned()
.or_else(|| connector.category());
AppSummary {
category,
id: connector.id,
name: connector.name,
description: connector.description,
install_url: connector.install_url,
}
})
.collect()
}
let apps_needing_auth = if let Some(app_ids_needing_auth) =
install_result.app_ids_needing_auth
{
if app_ids_needing_auth.is_empty()
|| !config.features.apps_enabled_for_auth(is_chatgpt_auth)
{
Vec::new()
} else {
let plugin_apps = load_plugin_apps(result.installed_path.as_path()).await;
self.plugin_apps_needing_auth_for_install(
&config,
is_chatgpt_auth,
&result.plugin_id.as_key(),
&plugin_apps,
)
.await
};
let plugin_apps = app_ids_needing_auth
.into_iter()
.map(codex_plugin::AppConnectorId)
.collect::<Vec<_>>();
let app_category_by_id = remote_detail
.app_manifest
.as_ref()
.map(plugin_app_category_by_id_from_value)
.unwrap_or_default();
let all_connectors = connectors::list_cached_all_connectors(&config)
.await
.unwrap_or_default();
connectors::connectors_for_plugin_apps(all_connectors, &plugin_apps)
.into_iter()
.map(|connector| {
let category = app_category_by_id
.get(&connector.id)
.cloned()
.or_else(|| connector.category());
AppSummary {
category,
id: connector.id,
name: connector.name,
description: connector.description,
install_url: connector.install_url,
}
})
.collect()
}
} else {
let plugin_app_declarations = load_plugin_apps(result.installed_path.as_path()).await;
let plugin_apps =
codex_plugin::app_connector_ids_from_declarations(&plugin_app_declarations);
self.plugin_apps_needing_auth_for_install(
&config,
is_chatgpt_auth,
&result.plugin_id.as_key(),
&plugin_apps,
)
.await
};

Ok(PluginInstallResponse {
auth_policy: remote_detail.summary.auth_policy,
Expand Down Expand Up @@ -1937,9 +1942,9 @@ async fn load_plugin_app_summaries(
}

fn plugin_app_category_by_id_from_value(value: &serde_json::Value) -> HashMap<String, String> {
codex_core_plugins::loader::plugin_app_metadata_from_value(value)
codex_core_plugins::loader::plugin_app_declarations_from_value(value)
.into_iter()
.filter_map(|app| app.category.map(|category| (app.id.0, category)))
.filter_map(|app| app.category.map(|category| (app.connector_id.0, category)))
.collect()
}

Expand Down
74 changes: 32 additions & 42 deletions codex-rs/core-plugins/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ use codex_exec_server::LOCAL_FS;
use codex_mcp::PluginMcpServerPlacement;
use codex_mcp::parse_plugin_mcp_config;
use codex_plugin::AppConnectorId;
use codex_plugin::AppDeclaration;
use codex_plugin::LoadedPlugin;
use codex_plugin::PluginCapabilitySummary;
use codex_plugin::PluginHookSource;
use codex_plugin::PluginId;
use codex_plugin::PluginIdError;
use codex_plugin::PluginLoadOutcome;
use codex_plugin::PluginTelemetryMetadata;
use codex_plugin::app_connector_ids_from_declarations;
use codex_protocol::protocol::Product;
use codex_protocol::protocol::SkillScope;
use codex_utils_absolute_path::AbsolutePathBuf;
Expand Down Expand Up @@ -63,12 +65,6 @@ pub struct PluginHookLoadOutcome {
pub hook_load_warnings: Vec<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginAppMetadata {
pub id: AppConnectorId,
pub category: Option<String>,
}

enum PluginLoadScope<'a> {
AllCapabilities {
restriction_product: Option<Product>,
Expand Down Expand Up @@ -835,15 +831,7 @@ fn default_mcp_config_paths(plugin_root: &Path) -> Vec<AbsolutePathBuf> {
paths
}

pub async fn load_plugin_apps(plugin_root: &Path) -> Vec<AppConnectorId> {
load_plugin_app_metadata(plugin_root)
.await
.into_iter()
.map(|app| app.id)
.collect()
}

pub async fn load_plugin_app_metadata(plugin_root: &Path) -> Vec<PluginAppMetadata> {
pub async fn load_plugin_apps(plugin_root: &Path) -> Vec<AppDeclaration> {
Comment thread
xl-openai marked this conversation as resolved.
if let Some(manifest) = load_plugin_manifest(plugin_root) {
return load_apps_from_paths(
plugin_root,
Expand All @@ -854,13 +842,13 @@ pub async fn load_plugin_app_metadata(plugin_root: &Path) -> Vec<PluginAppMetada
load_apps_from_paths(plugin_root, default_app_config_paths(plugin_root)).await
}

pub fn plugin_app_metadata_from_value(value: &JsonValue) -> Vec<PluginAppMetadata> {
pub fn plugin_app_declarations_from_value(value: &JsonValue) -> Vec<AppDeclaration> {
let Ok(parsed) = serde_json::from_value::<PluginAppFile>(value.clone()) else {
return Vec::new();
};
let mut apps = plugin_app_metadata_from_file(parsed, /*plugin_root*/ None);
let mut apps = app_declarations_from_file(parsed, /*plugin_root*/ None);
let mut seen_connector_ids = HashSet::new();
apps.retain(|app| seen_connector_ids.insert(app.id.0.clone()));
apps.retain(|app| seen_connector_ids.insert(app.connector_id.0.clone()));
apps
}

Expand Down Expand Up @@ -1000,8 +988,8 @@ fn append_plugin_hook_file(
async fn load_apps_from_paths(
plugin_root: &Path,
app_config_paths: Vec<AbsolutePathBuf>,
) -> Vec<PluginAppMetadata> {
let mut apps = Vec::new();
) -> Vec<AppDeclaration> {
let mut app_declarations = Vec::new();
for app_config_path in app_config_paths {
let Ok(contents) = tokio::fs::read_to_string(app_config_path.as_path()).await else {
continue;
Expand All @@ -1017,21 +1005,19 @@ async fn load_apps_from_paths(
}
};

apps.extend(plugin_app_metadata_from_file(parsed, Some(plugin_root)));
app_declarations.extend(app_declarations_from_file(parsed, Some(plugin_root)));
}
let mut seen_connector_ids = HashSet::new();
apps.retain(|app| seen_connector_ids.insert(app.id.0.clone()));
apps
app_declarations
}

fn plugin_app_metadata_from_file(
fn app_declarations_from_file(
parsed: PluginAppFile,
plugin_root: Option<&Path>,
) -> Vec<PluginAppMetadata> {
) -> Vec<AppDeclaration> {
parsed
.apps
.into_values()
.filter_map(|app| {
.into_iter()
.filter_map(|(name, app)| {
if app.id.trim().is_empty() {
if let Some(plugin_root) = plugin_root {
warn!(
Expand All @@ -1041,18 +1027,22 @@ fn plugin_app_metadata_from_file(
}
None
} else {
Some(PluginAppMetadata {
id: AppConnectorId(app.id),
category: app
.category
.map(|category| category.trim().to_string())
.filter(|category| !category.is_empty()),
Some(AppDeclaration {
name,
connector_id: AppConnectorId(app.id),
category: cleaned_app_category(app.category),
})
}
})
.collect()
}

fn cleaned_app_category(category: Option<String>) -> Option<String> {
category
.map(|category| category.trim().to_string())
.filter(|category| !category.is_empty())
}

pub async fn plugin_telemetry_metadata_from_root(
plugin_id: &PluginId,
plugin_root: &AbsolutePathBuf,
Expand All @@ -1075,6 +1065,13 @@ pub async fn plugin_telemetry_metadata_from_root(
mcp_server_names.sort_unstable();
mcp_server_names.dedup();

let app_declarations = load_apps_from_paths(
plugin_root.as_path(),
plugin_app_config_paths(plugin_root.as_path(), manifest_paths),
)
.await;
let app_connector_ids = app_connector_ids_from_declarations(&app_declarations);

PluginTelemetryMetadata {
plugin_id: plugin_id.clone(),
remote_plugin_id: None,
Expand All @@ -1084,14 +1081,7 @@ pub async fn plugin_telemetry_metadata_from_root(
description: None,
has_skills,
mcp_server_names,
app_connector_ids: load_apps_from_paths(
plugin_root.as_path(),
plugin_app_config_paths(plugin_root.as_path(), manifest_paths),
)
.await
.into_iter()
.map(|app| app.id)
.collect(),
app_connector_ids,
}),
}
}
Expand Down
29 changes: 21 additions & 8 deletions codex-rs/core-plugins/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::loader::PluginHookLoadOutcome;
use crate::loader::configured_curated_plugin_ids_from_codex_home;
use crate::loader::curated_plugin_cache_version;
use crate::loader::installed_plugin_telemetry_metadata;
use crate::loader::load_plugin_app_metadata;
use crate::loader::load_plugin_apps;
use crate::loader::load_plugin_hooks;
use crate::loader::load_plugin_hooks_from_layer_stack;
use crate::loader::load_plugin_mcp_servers;
Expand Down Expand Up @@ -63,6 +63,7 @@ use codex_plugin::AppConnectorId;
use codex_plugin::PluginCapabilitySummary;
use codex_plugin::PluginId;
use codex_plugin::PluginIdError;
use codex_plugin::app_connector_ids_from_declarations;
use codex_plugin::prompt_safe_plugin_description;
use codex_protocol::protocol::HookEventName;
use codex_protocol::protocol::Product;
Expand Down Expand Up @@ -214,7 +215,14 @@ fn project_plugin_load_outcome_for_auth(
for plugin in &mut plugins {
if apps_route_available {
if plugin.is_active() && !plugin.apps.is_empty() {
plugin.mcp_servers.clear();
let app_declaration_names = plugin
.apps
.iter()
.map(|app| app.name.as_str())
.collect::<HashSet<_>>();
plugin
.mcp_servers
.retain(|name, _| !app_declaration_names.contains(name.as_str()));
Comment thread
felixxia-oai marked this conversation as resolved.
Comment thread
felixxia-oai marked this conversation as resolved.
}
} else {
plugin.apps.clear();
Expand Down Expand Up @@ -1311,12 +1319,17 @@ impl PluginsManager {
event_name: hook.event_name,
})
.collect();
let app_metadata = load_plugin_app_metadata(source_path.as_path()).await;
let apps = app_metadata.iter().map(|app| app.id.clone()).collect();
let app_category_by_id = app_metadata
.into_iter()
.filter_map(|app| app.category.map(|category| (app.id.0, category)))
.collect();
let app_declarations = load_plugin_apps(source_path.as_path()).await;
let apps = app_connector_ids_from_declarations(&app_declarations);
let mut seen_app_connector_ids = HashSet::new();
let mut app_category_by_id = HashMap::new();
for app in &app_declarations {
if seen_app_connector_ids.insert(app.connector_id.0.as_str())
&& let Some(category) = &app.category
{
app_category_by_id.insert(app.connector_id.0.clone(), category.clone());
}
}
let mut mcp_server_names = load_plugin_mcp_servers(source_path.as_path())
.await
.into_keys()
Expand Down
Loading
Loading