Separate plugin catalog listing from package resolution (#46567)

## What changed

- Move catalog types and the `PluginProvider` listing interface into `codex-core-plugins`, with a concrete `PluginListQuery` carrying thread, turn, and MCP resource context.
- Remove root resolution from `PluginProvider`; keep executor package access in `ExecutorPluginProvider::resolve_bound`, which retains the filesystem used to resolve the package.
- Add shared provider result and error types, and re-export the listing API from `codex-mcp-extension`.

## Testing

Update executor and manifest tests to use `resolve_bound`, and assert that malformed manifest errors preserve the underlying `serde_json::Error` as their source.

GitOrigin-RevId: c6cd5a0269ed3b8ac9b93f306a70821e63e3671e
This commit is contained in:
Anthony Tafoya
2026-09-19 01:49:01 +00:00
committed by copyberry
parent ef9f3d022a
commit a7e069dc3a
10 changed files with 311 additions and 321 deletions
-76
View File
@@ -1,76 +0,0 @@
//! 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,
},
}
+1 -9
View File
@@ -1,21 +1,16 @@
//! Shared plugin package models, source providers, identifiers, and telemetry summaries.
//! Shared plugin package models, identifiers, and telemetry summaries.
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;
@@ -25,9 +20,6 @@ pub use load_outcome::prompt_safe_plugin_description;
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 -36
View File
@@ -1,12 +1,7 @@
//! Shared APIs for catalog discovery and root-at-a-time plugin resolution.
//! Authority-bound plugin descriptors and resource locations.
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.
@@ -108,36 +103,6 @@ fn environment_resource(
})
}
/// 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.
///
/// 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;
/// 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 {
async { Ok(None) }
}
}
#[cfg(test)]
#[path = "provider_tests.rs"]
mod tests;