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
5 changes: 5 additions & 0 deletions codex-rs/ext/mcp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ use codex_mcp::hosted_plugin_runtime_mcp_server_config;
#[path = "event_stream_tests.rs"]
mod event_stream_tests;
mod executor_plugin;
mod provider;
mod stream_manager;

pub use provider::PluginListQuery;
pub use provider::PluginProvider;
pub use provider::PluginProviderError;
pub use provider::PluginProviderFuture;
pub use stream_manager::McpEventStreamManager;
pub use stream_manager::McpEventStreamUpdate;

Expand Down
16 changes: 16 additions & 0 deletions codex-rs/ext/mcp/src/provider.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//! MCP context passed to plugin catalog providers.

use codex_mcp::McpResourceClient;
use std::sync::Arc;

pub use codex_plugin::PluginProvider;
pub use codex_plugin::PluginProviderError;
pub use codex_plugin::PluginProviderFuture;

/// Turn identifiers and MCP client used to request one catalog snapshot.
#[derive(Clone, Debug)]
pub struct PluginListQuery {
pub thread_id: String,
pub turn_id: String,
pub mcp_resources: Option<Arc<McpResourceClient>>,
}
76 changes: 76 additions & 0 deletions codex-rs/plugin/src/catalog.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//! Source-neutral plugin discovery types.

use codex_utils_path_uri::PathUri;
use std::collections::BTreeMap;

/// One source's complete discovery snapshot; no entries means no plugins were found.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct PluginCatalog {
pub entries: Vec<PluginCatalogEntry>,
pub warnings: Vec<String>,
}

/// Manifest metadata and every location that can supply the same logical plugin.
#[derive(Clone, PartialEq, Eq)]
pub struct PluginCatalogEntry {
pub id: PluginIdentity,
pub display_name: String,
pub version: Option<String>,
/// Serialized `.mcp.json` server declarations keyed by their authored names.
pub mcp_servers: BTreeMap<String, String>,
pub connector_ids: Vec<String>,
pub locations: Vec<PluginSourceLocation>,
}

impl std::fmt::Debug for PluginCatalogEntry {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("PluginCatalogEntry")
.field("id", &self.id)
.field("display_name", &self.display_name)
.field("version", &self.version)
// Declaration values can contain secrets, so only log their keys.
.field(
"mcp_server_names",
&self.mcp_servers.keys().collect::<Vec<_>>(),
)
.field("connector_ids", &self.connector_ids)
.field("locations", &self.locations)
.finish()
}
}

/// Stable key used to merge discoveries of the same logical plugin.
/// Identity is independent of whether the plugin is supplied by the cloud or an executor.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum PluginIdentity {
/// Stable ID assigned by plugin-service and shared by its materialized installations.
Remote { remote_plugin_id: String },
/// Local `name@marketplace` config key for a plugin without a remote ID.
Local { plugin_id: String },
}

impl PluginIdentity {
pub fn as_str(&self) -> &str {
match self {
Self::Remote { remote_plugin_id } => remote_plugin_id,
Self::Local { plugin_id } => plugin_id,
}
}
}

/// A source that can supply a plugin; cloud URIs are opaque, not local paths.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PluginSourceLocation {
Cloud {
resource_uri: String,
bundle_uri: Option<String>,
},
Executor {
/// Executor environment that owns `root`.
environment_id: String,
/// Local `name@marketplace` key used for this installation in configuration.
plugin_id: String,
root: PathUri,
},
}
7 changes: 7 additions & 0 deletions codex-rs/plugin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ use std::collections::HashSet;
pub use codex_utils_plugins::mention_syntax;

mod bundled_hooks;
mod catalog;
mod load_outcome;
pub mod manifest;
mod plugin_id;
mod provider;

pub use bundled_hooks::is_allowlisted_bundled_cleanup_hook;
pub use catalog::PluginCatalog;
pub use catalog::PluginCatalogEntry;
pub use catalog::PluginIdentity;
pub use catalog::PluginSourceLocation;
use codex_config::HookEventsToml;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
Expand All @@ -21,6 +26,8 @@ pub use plugin_id::PluginId;
pub use plugin_id::PluginIdError;
pub use plugin_id::validate_plugin_segment;
pub use provider::PluginProvider;
pub use provider::PluginProviderError;
pub use provider::PluginProviderFuture;
pub use provider::PluginResourceLocator;
pub use provider::ResolvedPlugin;
pub use provider::ResolvedPluginError;
Expand Down
36 changes: 27 additions & 9 deletions codex-rs/plugin/src/provider.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
//! Shared APIs for catalog discovery and root-at-a-time plugin resolution.

use crate::PluginCatalog;
use crate::manifest::PluginManifest;
use codex_protocol::capabilities::SelectedCapabilityRoot;
use codex_utils_path_uri::PathUri;
use std::error::Error as StdError;
use std::future::Future;
use std::pin::Pin;
use thiserror::Error;

/// A plugin resource paired with the environment that owns its filesystem.
Expand Down Expand Up @@ -104,20 +108,34 @@ fn environment_resource(
})
}

/// Resolves source-owned package roots into inert plugin descriptors.
/// Error returned when a provider cannot produce a complete catalog snapshot.
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[error("{0}")]
pub struct PluginProviderError(pub String);

pub type PluginProviderFuture<'a, T> =
Pin<Box<dyn Future<Output = Result<T, PluginProviderError>> + Send + 'a>>;

/// Discovers plugin metadata through batch listing or single-root resolution.
///
/// Implementations must perform all filesystem access through the authority
/// named by the selected root. `None` means the root contains no plugin
/// manifest and may be handled as another standalone capability.
pub trait PluginProvider: Send + Sync {
/// Source-specific resolution failure.
/// Cloud providers implement `list`; executor providers continue implementing `resolve`.
/// Each contributor invokes its provider's supported operation; the other is a no-op.
pub trait PluginProvider<ListQuery = ()>: Send + Sync {
/// Error returned by root-at-a-time resolution.
type Error: StdError + Send + Sync + 'static;

/// Resolves one selected root without activating any of its components.
/// Returns a complete snapshot.
fn list(&self, _query: ListQuery) -> PluginProviderFuture<'_, PluginCatalog> {
Box::pin(async { Ok(PluginCatalog::default()) })
}

/// Resolves one selected root using the filesystem authority named by that root.
fn resolve(
&self,
root: &SelectedCapabilityRoot,
) -> impl Future<Output = Result<Option<ResolvedPlugin>, Self::Error>> + Send;
_root: &SelectedCapabilityRoot,
) -> impl Future<Output = Result<Option<ResolvedPlugin>, Self::Error>> + Send {
async { Ok(None) }
}
}

#[cfg(test)]
Expand Down
Loading