mirror of
https://github.com/openai/codex.git
synced 2026-09-20 12:47:38 +00:00
Add shared plugin catalog discovery APIs (#46558)
## What changed - Add `PluginCatalog` types for discovery snapshots, warnings, plugin metadata, stable identities, and cloud or executor source locations. Keep MCP server declaration values out of debug output. - Extend `PluginProvider` with a generic list query, asynchronous catalog listing, and shared error and future types. Provide empty defaults for listing and root resolution so providers can implement their supported operation. - Add `PluginListQuery` in the MCP extension with thread and turn identifiers and an optional MCP resource client. GitOrigin-RevId: 0bf2a1713c25d912c41daab788b88729d4871fcf
This commit is contained in:
committed by
copyberry
parent
cd2f9ca692
commit
46d87f3b1c
@@ -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;
|
||||
|
||||
|
||||
16
codex-rs/ext/mcp/src/provider.rs
Normal file
16
codex-rs/ext/mcp/src/provider.rs
Normal file
@@ -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
codex-rs/plugin/src/catalog.rs
Normal file
76
codex-rs/plugin/src/catalog.rs
Normal file
@@ -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,
|
||||
},
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
@@ -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)]
|
||||
|
||||
Reference in New Issue
Block a user