Skip to content
Closed
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
13 changes: 13 additions & 0 deletions codex-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions codex-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ members = [
"exec",
"execpolicy",
"execpolicy-legacy",
"extensions",
"keyring-store",
"file-search",
"linux-sandbox",
Expand Down Expand Up @@ -105,6 +106,7 @@ codex-config = { path = "config" }
codex-core = { path = "core" }
codex-exec = { path = "exec" }
codex-execpolicy = { path = "execpolicy" }
codex-extensions = { path = "extensions" }
codex-experimental-api-macros = { path = "codex-experimental-api-macros" }
codex-feedback = { path = "feedback" }
codex-file-search = { path = "file-search" }
Expand Down
1 change: 1 addition & 0 deletions codex-rs/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ codex-config = { workspace = true }
codex-shell-command = { workspace = true }
codex-skills = { workspace = true }
codex-execpolicy = { workspace = true }
codex-extensions = { workspace = true }
codex-file-search = { workspace = true }
codex-git = { workspace = true }
codex-hooks = { workspace = true }
Expand Down
160 changes: 63 additions & 97 deletions codex-rs/core/src/plugins/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ use crate::skills::loader::SkillRoot;
use crate::skills::loader::load_skills_from_roots;
use codex_app_server_protocol::ConfigValueWriteParams;
use codex_app_server_protocol::MergeStrategy;
use codex_extensions::plugins::AppConnectorId;
use codex_extensions::plugins::PluginCapabilitySummary;
use codex_extensions::plugins::PluginTelemetryMetadata;
use codex_protocol::protocol::SkillScope;
use codex_utils_absolute_path::AbsolutePathBuf;
use serde::Deserialize;
Expand All @@ -70,10 +73,7 @@ const DEFAULT_MCP_CONFIG_FILE: &str = ".mcp.json";
const DEFAULT_APP_CONFIG_FILE: &str = ".app.json";
pub const OPENAI_CURATED_MARKETPLACE_NAME: &str = "openai-curated";
static CURATED_REPO_SYNC_STARTED: AtomicBool = AtomicBool::new(false);
const MAX_CAPABILITY_SUMMARY_DESCRIPTION_LEN: usize = 1024;

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AppConnectorId(pub String);
const MAX_CAPABILITY_SUMMARY_DESCRIPTION_LEN: usize = 140;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginInstallRequest {
Expand Down Expand Up @@ -157,98 +157,6 @@ impl LoadedPlugin {
}
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct PluginCapabilitySummary {
pub config_name: String,
pub display_name: String,
pub description: Option<String>,
pub has_skills: bool,
pub mcp_server_names: Vec<String>,
pub app_connector_ids: Vec<AppConnectorId>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginTelemetryMetadata {
pub plugin_id: PluginId,
pub capability_summary: Option<PluginCapabilitySummary>,
}

impl PluginTelemetryMetadata {
pub fn from_plugin_id(plugin_id: &PluginId) -> Self {
Self {
plugin_id: plugin_id.clone(),
capability_summary: None,
}
}
}

impl PluginCapabilitySummary {
fn from_plugin(plugin: &LoadedPlugin) -> Option<Self> {
if !plugin.is_active() {
return None;
}

let mut mcp_server_names: Vec<String> = plugin.mcp_servers.keys().cloned().collect();
mcp_server_names.sort_unstable();

let summary = Self {
config_name: plugin.config_name.clone(),
display_name: plugin
.manifest_name
.clone()
.unwrap_or_else(|| plugin.config_name.clone()),
description: prompt_safe_plugin_description(plugin.manifest_description.as_deref()),
has_skills: !plugin.skill_roots.is_empty(),
mcp_server_names,
app_connector_ids: plugin.apps.clone(),
};

(summary.has_skills
|| !summary.mcp_server_names.is_empty()
|| !summary.app_connector_ids.is_empty())
.then_some(summary)
}

pub fn telemetry_metadata(&self) -> Option<PluginTelemetryMetadata> {
PluginId::parse(&self.config_name)
.ok()
.map(|plugin_id| PluginTelemetryMetadata {
plugin_id,
capability_summary: Some(self.clone()),
})
}
}

impl From<PluginDetailSummary> for PluginCapabilitySummary {
fn from(value: PluginDetailSummary) -> Self {
Self {
config_name: value.id,
display_name: value.name,
description: prompt_safe_plugin_description(value.description.as_deref()),
has_skills: !value.skills.is_empty(),
mcp_server_names: value.mcp_server_names,
app_connector_ids: value.apps,
}
}
}

fn prompt_safe_plugin_description(description: Option<&str>) -> Option<String> {
let description = description?
.split_whitespace()
.collect::<Vec<_>>()
.join(" ");
if description.is_empty() {
return None;
}

Some(
description
.chars()
.take(MAX_CAPABILITY_SUMMARY_DESCRIPTION_LEN)
.collect(),
)
}

#[derive(Debug, Clone, PartialEq)]
pub struct PluginLoadOutcome {
plugins: Vec<LoadedPlugin>,
Expand All @@ -265,7 +173,7 @@ impl PluginLoadOutcome {
fn from_plugins(plugins: Vec<LoadedPlugin>) -> Self {
let capability_summaries = plugins
.iter()
.filter_map(PluginCapabilitySummary::from_plugin)
.filter_map(plugin_capability_summary_from_loaded_plugin)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Badge Restore missing capability-summary helper call target

PluginLoadOutcome::from_plugins still calls plugin_capability_summary_from_loaded_plugin, but that symbol is no longer defined/imported after the extraction. cargo check -p codex-core now fails with E0425 at this line, so the crate cannot build.

Useful? React with 👍 / 👎.

.collect::<Vec<_>>();
Self {
plugins,
Expand Down Expand Up @@ -321,6 +229,64 @@ impl PluginLoadOutcome {
}
}

fn plugin_capability_summary_from_loaded_plugin(
plugin: &LoadedPlugin,
) -> Option<PluginCapabilitySummary> {
if !plugin.is_active() {
return None;
}

let mut mcp_server_names: Vec<String> = plugin.mcp_servers.keys().cloned().collect();
mcp_server_names.sort_unstable();

let summary = PluginCapabilitySummary {
config_name: plugin.config_name.clone(),
display_name: plugin
.manifest_name
.clone()
.unwrap_or_else(|| plugin.config_name.clone()),
description: prompt_safe_plugin_description(plugin.manifest_description.as_deref()),
has_skills: !plugin.skill_roots.is_empty(),
mcp_server_names,
app_connector_ids: plugin.apps.clone(),
};

(summary.has_skills
|| !summary.mcp_server_names.is_empty()
|| !summary.app_connector_ids.is_empty())
.then_some(summary)
}

impl From<PluginDetailSummary> for PluginCapabilitySummary {
fn from(value: PluginDetailSummary) -> Self {
Self {
config_name: value.id,
display_name: value.name,
description: prompt_safe_plugin_description(value.description.as_deref()),
has_skills: !value.skills.is_empty(),
mcp_server_names: value.mcp_server_names,
app_connector_ids: value.apps,
}
}
}

fn prompt_safe_plugin_description(description: Option<&str>) -> Option<String> {
let description = description?
.split_whitespace()
.collect::<Vec<_>>()
.join(" ");
if description.is_empty() {
return None;
}

Some(
description
.chars()
.take(MAX_CAPABILITY_SUMMARY_DESCRIPTION_LEN)
.collect(),
)
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct RemotePluginSyncResult {
/// Plugin ids newly installed into the local plugin cache.
Expand Down
Loading
Loading