Read connector declarations from executor plugins (#29852)

## Why

Selected capability roots can live on a different executor and operating
system from app-server. Their connector declarations must therefore be
read through the executor that owns the package, without converting
executor URIs into host paths.

This PR adds that authority-bound reader without activating connectors
or changing thread startup.

## What changed

- Add a small `codex-connectors-extension` crate for executor-owned
connector I/O.
- Read only the app configuration explicitly declared by the resolved
plugin manifest.
- Read through the `ExecutorFileSystem` retained by
`ResolvedExecutorPlugin`; there is no host-filesystem fallback or
default-file probe.
- Keep `PathUri` values intact so Windows, Unix, and remote executor
paths work from any orchestrator OS.
- Return full `AppDeclaration` values so the caller retains declaration
names and categories for routing.
- Preserve the selected plugin ID and exact executor URI in read and
parse errors.

The contract is intentionally narrow: selected packages are trusted,
valid packages and packages that provide connectors explicitly declare
their app configuration.

## Stack scope

This PR is stacked on #29851. It only provides the executor-backed
reader. #29856 resolves selected roots at thread start, freezes their
connector snapshot, and contains the remote-capable end-to-end authority
test for the complete path.
This commit is contained in:
jif
2026-06-24 23:56:50 +01:00
committed by GitHub
Unverified
parent 81f340436c
commit 9ff8068880
6 changed files with 111 additions and 0 deletions
+12
View File
@@ -2596,6 +2596,18 @@ dependencies = [
"urlencoding",
]
[[package]]
name = "codex-connectors-extension"
version = "0.0.0"
dependencies = [
"codex-connectors",
"codex-core-plugins",
"codex-plugin",
"codex-utils-path-uri",
"serde_json",
"thiserror 2.0.18",
]
[[package]]
name = "codex-context-fragments"
version = "0.0.0"
+1
View File
@@ -48,6 +48,7 @@ members = [
"exec-server",
"execpolicy",
"execpolicy-legacy",
"ext/connectors",
"ext/extension-api",
"ext/goal",
"ext/guardian",
+6
View File
@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "connectors",
crate_name = "codex_connectors_extension",
)
+22
View File
@@ -0,0 +1,22 @@
[package]
edition.workspace = true
license.workspace = true
name = "codex-connectors-extension"
version.workspace = true
[lib]
name = "codex_connectors_extension"
path = "src/lib.rs"
doctest = false
test = false
[lints]
workspace = true
[dependencies]
codex-connectors = { workspace = true }
codex-core-plugins = { workspace = true }
codex-plugin = { workspace = true }
codex-utils-path-uri = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
@@ -0,0 +1,64 @@
use codex_connectors::parse_plugin_app_config;
use codex_core_plugins::ResolvedExecutorPlugin;
use codex_plugin::AppDeclaration;
use codex_plugin::PluginResourceLocator;
use codex_utils_path_uri::PathUri;
use std::io;
use thiserror::Error;
/// Loads connector declarations from a resolved plugin through its owning executor.
#[derive(Clone, Copy, Debug, Default)]
pub struct ExecutorPluginConnectorProvider;
/// Failure to load connector declarations from an executor plugin.
#[derive(Debug, Error)]
pub enum ExecutorPluginConnectorProviderError {
#[error("failed to read app config for selected plugin `{plugin_id}` at `{path}`: {source}")]
ReadConfig {
plugin_id: String,
path: PathUri,
#[source]
source: io::Error,
},
#[error("failed to parse app config for selected plugin `{plugin_id}` at `{path}`: {source}")]
ParseConfig {
plugin_id: String,
path: PathUri,
#[source]
source: serde_json::Error,
},
}
impl ExecutorPluginConnectorProvider {
/// Returns the connector declarations contributed by `plugin`.
pub async fn load(
&self,
plugin: &ResolvedExecutorPlugin,
) -> Result<Vec<AppDeclaration>, ExecutorPluginConnectorProviderError> {
let resolved_plugin = plugin.plugin();
let plugin_id = resolved_plugin.selected_root_id();
let Some(PluginResourceLocator::Environment {
path: config_path, ..
}) = resolved_plugin.manifest().paths.apps.as_ref()
else {
return Ok(Vec::new());
};
let contents = plugin
.file_system()
.read_file_text(config_path, /*sandbox*/ None)
.await
.map_err(|source| ExecutorPluginConnectorProviderError::ReadConfig {
plugin_id: plugin_id.to_string(),
path: config_path.clone(),
source,
})?;
parse_plugin_app_config(&contents).map_err(|source| {
ExecutorPluginConnectorProviderError::ParseConfig {
plugin_id: plugin_id.to_string(),
path: config_path.clone(),
source,
}
})
}
}
+6
View File
@@ -0,0 +1,6 @@
//! Executor-backed connector declaration loading.
mod executor_plugin;
pub use executor_plugin::ExecutorPluginConnectorProvider;
pub use executor_plugin::ExecutorPluginConnectorProviderError;