mirror of
https://github.com/openai/codex.git
synced 2026-10-31 19:28:49 +00:00
Add batched executor capability discovery (#33852)
## Why Selected capability roots can contribute plugins, MCP servers, connectors, and skills. Discovering each contribution separately requires repeated access to the executor filesystem. ## What changed - Add the `capabilityRoots/discoverV1` exec-server RPC to scan selected roots and materialize recognized plugin manifests, configuration files, skill instructions, and skill metadata in one bounded request. - Add the opt-in `executor_capability_discovery` feature, with a thread-scoped cache and per-step snapshot shared by MCP and skill discovery. - Parse MCP, connector, and skill contributions from the materialized snapshot, including serving cached skill instructions without another filesystem read. ## Testing - Cover discovery limits, manifest precedence, root-local failures, cache reuse, plugin contributions, and parity with the existing environment skill loader. GitOrigin-RevId: f98fd2321cafb58c596db02da1f83c09d8eb375d
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
||||
use codex_file_system::FileSystemSandboxContext;
|
||||
pub use codex_file_system::WalkOptions;
|
||||
pub use codex_file_system::WalkOutcome;
|
||||
use codex_network_proxy::ManagedNetworkSandboxContext;
|
||||
use codex_protocol::capabilities::SelectedCapabilityRoot;
|
||||
use codex_protocol::config_types::ShellEnvironmentPolicyInherit;
|
||||
use codex_shell_command::shell_detect::DetectedShell;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
@@ -37,6 +39,14 @@ pub const FS_READ_DIRECTORY_METHOD: &str = "fs/readDirectory";
|
||||
pub const FS_WALK_METHOD: &str = "fs/walk";
|
||||
pub const FS_REMOVE_METHOD: &str = "fs/remove";
|
||||
pub const FS_COPY_METHOD: &str = "fs/copy";
|
||||
/// Discovers capability manifests below selected roots using executor-local filesystem access.
|
||||
pub const CAPABILITY_ROOTS_DISCOVER_METHOD: &str = "capabilityRoots/discoverV1";
|
||||
/// Ordered plugin manifest paths recognized beneath a plugin root.
|
||||
pub const DISCOVERABLE_PLUGIN_MANIFEST_PATHS: &[&str] = &[
|
||||
".codex-plugin/plugin.json",
|
||||
".claude-plugin/plugin.json",
|
||||
".cursor-plugin/plugin.json",
|
||||
];
|
||||
/// JSON-RPC request method for executor-side HTTP requests.
|
||||
pub const HTTP_REQUEST_METHOD: &str = "http/request";
|
||||
/// JSON-RPC notification method for streamed executor HTTP response bodies.
|
||||
@@ -431,6 +441,119 @@ pub struct FsCopyParams {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FsCopyResponse {}
|
||||
|
||||
/// Roots to inspect for plugin and skill capability manifests.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CapabilityRootsDiscoverParams {
|
||||
pub roots: Vec<CapabilityRootDiscoverRequest>,
|
||||
}
|
||||
|
||||
/// One caller-selected capability root.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CapabilityRootDiscoverRequest {
|
||||
/// Opaque caller identity returned unchanged in the response.
|
||||
pub id: String,
|
||||
/// Absolute root URI interpreted using the exec-server host's path rules.
|
||||
pub path: PathUri,
|
||||
}
|
||||
|
||||
/// Executor-local discovery results in request order.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CapabilityRootsDiscoverResponse {
|
||||
pub roots: Vec<CapabilityRootDiscovery>,
|
||||
}
|
||||
|
||||
/// Recognized UTF-8 capability file materialized by the exec-server.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CapabilityTextFile {
|
||||
pub path: PathUri,
|
||||
pub contents: String,
|
||||
}
|
||||
|
||||
/// Plugin files declared directly by a selected root.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DiscoveredPluginFiles {
|
||||
pub manifest: CapabilityTextFile,
|
||||
/// File-backed MCP declarations, including the conventional `.mcp.json` fallback.
|
||||
#[serde(default)]
|
||||
pub mcp_config: Option<CapabilityTextFile>,
|
||||
/// File-backed connector declarations.
|
||||
#[serde(default)]
|
||||
pub apps_config: Option<CapabilityTextFile>,
|
||||
}
|
||||
|
||||
/// A skill instructions file and its optional sibling metadata.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DiscoveredSkillFiles {
|
||||
pub instructions: CapabilityTextFile,
|
||||
#[serde(default)]
|
||||
pub metadata: Option<CapabilityTextFile>,
|
||||
}
|
||||
|
||||
/// Manifest bundle for one selected root.
|
||||
///
|
||||
/// Discovery failures are root-local so one broken package does not discard valid siblings.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CapabilityRootDiscovery {
|
||||
pub id: String,
|
||||
pub path: PathUri,
|
||||
#[serde(default)]
|
||||
pub plugin: Option<DiscoveredPluginFiles>,
|
||||
#[serde(default)]
|
||||
pub skills: Vec<DiscoveredSkillFiles>,
|
||||
/// Plugin manifests found while scanning the root, used to namespace nested skills.
|
||||
#[serde(default)]
|
||||
pub namespace_manifests: Vec<CapabilityTextFile>,
|
||||
#[serde(default)]
|
||||
pub warnings: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
/// Immutable results for the selected capability roots visible in one model step.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ExecutorCapabilityDiscoverySnapshot {
|
||||
roots: Arc<[ExecutorCapabilityDiscoverySnapshotEntry]>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ExecutorCapabilityDiscoverySnapshotEntry {
|
||||
pub selected_root: SelectedCapabilityRoot,
|
||||
pub result: Result<Arc<CapabilityRootDiscovery>, String>,
|
||||
}
|
||||
|
||||
impl ExecutorCapabilityDiscoverySnapshot {
|
||||
pub fn new(
|
||||
selected_roots: &[SelectedCapabilityRoot],
|
||||
discoveries: Vec<Result<Arc<CapabilityRootDiscovery>, String>>,
|
||||
) -> Self {
|
||||
debug_assert_eq!(selected_roots.len(), discoveries.len());
|
||||
Self {
|
||||
roots: selected_roots
|
||||
.iter()
|
||||
.cloned()
|
||||
.zip(discoveries)
|
||||
.map(
|
||||
|(selected_root, result)| ExecutorCapabilityDiscoverySnapshotEntry {
|
||||
selected_root,
|
||||
result,
|
||||
},
|
||||
)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn roots(&self) -> &[ExecutorCapabilityDiscoverySnapshotEntry] {
|
||||
&self.roots
|
||||
}
|
||||
}
|
||||
|
||||
/// HTTP header represented in the executor protocol.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
||||
Reference in New Issue
Block a user