[codex] remove EnvironmentPathRef (#27433)

We're switching to using a static encoding of the host path in
`PathUri`. We may need a type like this again but we can add it when
it's more compelling.

Stacked on #27454.
This commit is contained in:
Adam Perry @ OpenAI
2026-06-11 11:26:12 -07:00
committed by GitHub
Unverified
parent 0b08bbb9d9
commit 4a05d3b282
5 changed files with 34 additions and 602 deletions
@@ -1,527 +0,0 @@
use std::fmt;
use std::hash::Hash;
use std::hash::Hasher;
use std::io;
use std::path::Path;
use std::sync::Arc;
use codex_utils_absolute_path::AbsolutePathBuf;
use crate::ExecutorFileSystem;
use crate::FileMetadata;
use crate::FileSystemSandboxContext;
use crate::LOCAL_FS;
use crate::ReadDirectoryEntry;
/// Binds an absolute path to the executor filesystem that owns it.
#[derive(Clone)]
pub struct EnvironmentPathRef {
file_system: Arc<dyn ExecutorFileSystem>,
path: AbsolutePathBuf,
}
impl EnvironmentPathRef {
/// Creates a path ref bound to the filesystem that owns `path`.
pub fn new(file_system: Arc<dyn ExecutorFileSystem>, path: AbsolutePathBuf) -> Self {
Self { file_system, path }
}
/// Creates a path ref bound to the shared unsandboxed local filesystem.
pub fn local(path: AbsolutePathBuf) -> Self {
Self::new(Arc::clone(&LOCAL_FS), path)
}
/// Returns the absolute path held by this ref.
pub fn path(&self) -> &AbsolutePathBuf {
&self.path
}
/// Returns the filesystem that owns this path.
pub fn file_system(&self) -> Arc<dyn ExecutorFileSystem> {
Arc::clone(&self.file_system)
}
/// Reads this path as UTF-8 text through its bound filesystem.
pub async fn read_to_string(
&self,
sandbox: Option<&FileSystemSandboxContext>,
) -> io::Result<String> {
self.file_system.read_file_text(&self.path, sandbox).await
}
/// Reads metadata for this path through its bound filesystem.
pub async fn metadata(
&self,
sandbox: Option<&FileSystemSandboxContext>,
) -> io::Result<FileMetadata> {
self.file_system.get_metadata(&self.path, sandbox).await
}
/// Reads directory entries for this path through its bound filesystem.
pub async fn read_directory(
&self,
sandbox: Option<&FileSystemSandboxContext>,
) -> io::Result<Vec<ReadDirectoryEntry>> {
self.file_system.read_directory(&self.path, sandbox).await
}
/// Returns a ref with the same filesystem and a replacement path.
pub fn with_path(&self, path: AbsolutePathBuf) -> Self {
Self::new(Arc::clone(&self.file_system), path)
}
/// Lexically joins `path` onto this path through its bound filesystem.
pub async fn join<P: AsRef<Path>>(&self, path: P) -> io::Result<Self> {
self.file_system
.join(&self.path, path.as_ref())
.await
.map(|path| self.with_path(path))
}
/// Returns the parent of this path through its bound filesystem.
pub async fn parent(&self) -> io::Result<Option<Self>> {
self.file_system
.parent(&self.path)
.await
.map(|path| path.map(|path| self.with_path(path)))
}
/// Canonicalizes this path through its bound filesystem.
pub async fn canonicalize(
&self,
sandbox: Option<&FileSystemSandboxContext>,
) -> io::Result<Self> {
self.file_system
.canonicalize(&self.path, sandbox)
.await
.map(|path| self.with_path(path))
}
}
impl PartialEq for EnvironmentPathRef {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.file_system, &other.file_system) && self.path == other.path
}
}
impl Eq for EnvironmentPathRef {}
impl Hash for EnvironmentPathRef {
fn hash<H: Hasher>(&self, state: &mut H) {
(Arc::as_ptr(&self.file_system) as *const () as usize).hash(state);
self.path.hash(state);
}
}
impl fmt::Debug for EnvironmentPathRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EnvironmentPathRef")
.field("path", &self.path)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use async_trait::async_trait;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_utils_absolute_path::test_support::PathBufExt;
use pretty_assertions::assert_eq;
use std::collections::HashSet;
use std::path::Path;
use std::sync::Mutex;
use crate::LOCAL_FS;
#[derive(Clone, Debug, Eq, PartialEq)]
enum RecordedMethod {
Canonicalize,
Join,
Parent,
ReadFileText,
Metadata,
ReadDirectory,
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct RecordedCall {
method: RecordedMethod,
path: AbsolutePathBuf,
sandbox: Option<FileSystemSandboxContext>,
}
struct RecordingFileSystem {
calls: Mutex<Vec<RecordedCall>>,
}
impl Default for RecordingFileSystem {
fn default() -> Self {
Self {
calls: Mutex::new(Vec::new()),
}
}
}
impl RecordingFileSystem {
fn recorded_calls(&self) -> Vec<RecordedCall> {
match self.calls.lock() {
Ok(calls) => calls.clone(),
Err(err) => err.into_inner().clone(),
}
}
fn push_call(&self, call: RecordedCall) {
match self.calls.lock() {
Ok(mut calls) => calls.push(call),
Err(err) => err.into_inner().push(call),
}
}
}
fn local_path_ref(path: AbsolutePathBuf) -> EnvironmentPathRef {
EnvironmentPathRef::new(Arc::clone(&LOCAL_FS), path)
}
#[async_trait]
impl ExecutorFileSystem for RecordingFileSystem {
async fn canonicalize(
&self,
path: &AbsolutePathBuf,
_sandbox: Option<&FileSystemSandboxContext>,
) -> io::Result<AbsolutePathBuf> {
self.push_call(RecordedCall {
method: RecordedMethod::Canonicalize,
path: path.clone(),
sandbox: None,
});
Ok(path.parent().unwrap())
}
async fn join(
&self,
base_path: &AbsolutePathBuf,
path: &Path,
) -> io::Result<AbsolutePathBuf> {
self.push_call(RecordedCall {
method: RecordedMethod::Join,
path: base_path.clone(),
sandbox: None,
});
AbsolutePathBuf::from_absolute_path_checked(base_path.as_path().join(path))
}
async fn parent(&self, path: &AbsolutePathBuf) -> io::Result<Option<AbsolutePathBuf>> {
self.push_call(RecordedCall {
method: RecordedMethod::Parent,
path: path.clone(),
sandbox: None,
});
Ok(path.parent())
}
async fn read_file(
&self,
path: &AbsolutePathBuf,
sandbox: Option<&FileSystemSandboxContext>,
) -> io::Result<Vec<u8>> {
self.push_call(RecordedCall {
method: RecordedMethod::ReadFileText,
path: path.clone(),
sandbox: sandbox.cloned(),
});
Ok(b"skill contents".to_vec())
}
async fn write_file(
&self,
_path: &AbsolutePathBuf,
_contents: Vec<u8>,
_sandbox: Option<&FileSystemSandboxContext>,
) -> io::Result<()> {
unreachable!("write_file should not be called")
}
async fn create_directory(
&self,
_path: &AbsolutePathBuf,
_create_directory_options: crate::CreateDirectoryOptions,
_sandbox: Option<&FileSystemSandboxContext>,
) -> io::Result<()> {
unreachable!("create_directory should not be called")
}
async fn get_metadata(
&self,
path: &AbsolutePathBuf,
sandbox: Option<&FileSystemSandboxContext>,
) -> io::Result<FileMetadata> {
self.push_call(RecordedCall {
method: RecordedMethod::Metadata,
path: path.clone(),
sandbox: sandbox.cloned(),
});
Ok(FileMetadata {
is_directory: true,
is_file: false,
is_symlink: false,
created_at_ms: 1,
modified_at_ms: 2,
})
}
async fn read_directory(
&self,
path: &AbsolutePathBuf,
sandbox: Option<&FileSystemSandboxContext>,
) -> io::Result<Vec<ReadDirectoryEntry>> {
self.push_call(RecordedCall {
method: RecordedMethod::ReadDirectory,
path: path.clone(),
sandbox: sandbox.cloned(),
});
Ok(vec![ReadDirectoryEntry {
file_name: "SKILL.md".to_string(),
is_directory: false,
is_file: true,
}])
}
async fn remove(
&self,
_path: &AbsolutePathBuf,
_remove_options: crate::RemoveOptions,
_sandbox: Option<&FileSystemSandboxContext>,
) -> io::Result<()> {
unreachable!("remove should not be called")
}
async fn copy(
&self,
_source_path: &AbsolutePathBuf,
_destination_path: &AbsolutePathBuf,
_copy_options: crate::CopyOptions,
_sandbox: Option<&FileSystemSandboxContext>,
) -> io::Result<()> {
unreachable!("copy should not be called")
}
}
fn restricted_sandbox() -> FileSystemSandboxContext {
FileSystemSandboxContext::from_permission_profile(
PermissionProfile::from_runtime_permissions(
&FileSystemSandboxPolicy::restricted(Vec::new()),
NetworkSandboxPolicy::Restricted,
),
)
}
#[tokio::test]
async fn environment_path_ref_forwards_sandbox_to_file_system_methods() {
let path = std::env::temp_dir().join("skills/demo").abs();
let file_system = Arc::new(RecordingFileSystem::default());
let path_ref = EnvironmentPathRef::new(file_system.clone(), path.clone());
let sandbox = restricted_sandbox();
assert_eq!(
path_ref
.read_to_string(Some(&sandbox))
.await
.expect("read skill contents"),
"skill contents".to_string()
);
assert_eq!(
path_ref
.metadata(Some(&sandbox))
.await
.expect("read metadata"),
FileMetadata {
is_directory: true,
is_file: false,
is_symlink: false,
created_at_ms: 1,
modified_at_ms: 2,
}
);
assert_eq!(
path_ref
.read_directory(Some(&sandbox))
.await
.expect("read directory"),
vec![ReadDirectoryEntry {
file_name: "SKILL.md".to_string(),
is_directory: false,
is_file: true,
}]
);
assert_eq!(
file_system.recorded_calls(),
vec![
RecordedCall {
method: RecordedMethod::ReadFileText,
path: path.clone(),
sandbox: Some(sandbox.clone()),
},
RecordedCall {
method: RecordedMethod::Metadata,
path: path.clone(),
sandbox: Some(sandbox.clone()),
},
RecordedCall {
method: RecordedMethod::ReadDirectory,
path,
sandbox: Some(sandbox),
},
]
);
}
#[test]
fn environment_path_ref_equality_and_hash_include_file_system_identity() {
let path = std::env::temp_dir().join("skills/demo").abs();
let file_system = Arc::new(RecordingFileSystem::default());
let same_file_system: Arc<dyn ExecutorFileSystem> = file_system.clone();
let different_file_system: Arc<dyn ExecutorFileSystem> =
Arc::new(RecordingFileSystem::default());
let left = EnvironmentPathRef::new(same_file_system.clone(), path.clone());
let same = EnvironmentPathRef::new(same_file_system, path.clone());
let different_path = EnvironmentPathRef::new(file_system, path.parent().unwrap());
let different_fs = EnvironmentPathRef::new(different_file_system, path);
assert_eq!(left, same);
assert_ne!(left, different_path);
assert_ne!(left, different_fs);
let set = HashSet::from([left, same, different_path, different_fs]);
assert_eq!(set.len(), 3);
}
#[tokio::test]
async fn canonicalize_keeps_bound_file_system_identity() {
let path = std::env::temp_dir().join("skills/demo").abs();
let file_system = Arc::new(RecordingFileSystem::default());
let path_ref = EnvironmentPathRef::new(file_system.clone(), path.clone());
let canonicalized = path_ref
.canonicalize(/*sandbox*/ None)
.await
.expect("canonicalize");
assert_eq!(canonicalized.path(), &path.parent().unwrap());
assert_eq!(
canonicalized,
EnvironmentPathRef::new(file_system.clone(), path.parent().unwrap())
);
assert_eq!(
file_system.recorded_calls(),
vec![RecordedCall {
method: RecordedMethod::Canonicalize,
path,
sandbox: None,
}]
);
}
#[tokio::test]
async fn join_keeps_bound_file_system_identity() {
let path = std::env::temp_dir().join("skills").abs();
let file_system = Arc::new(RecordingFileSystem::default());
let path_ref = EnvironmentPathRef::new(file_system.clone(), path.clone());
assert_eq!(
path_ref.join(Path::new("demo")).await.ok(),
Some(EnvironmentPathRef::new(
file_system.clone(),
std::env::temp_dir().join("skills/demo").abs(),
))
);
assert_eq!(
file_system.recorded_calls(),
vec![RecordedCall {
method: RecordedMethod::Join,
path,
sandbox: None,
}]
);
}
#[tokio::test]
async fn join_matches_absolute_path_buf_for_tilde_paths() {
let path_ref = local_path_ref(std::env::temp_dir().join("skills").abs());
assert_eq!(
path_ref
.join(Path::new("~"))
.await
.ok()
.map(|path_ref| path_ref.path().clone()),
Some(path_ref.path().join(Path::new("~")))
);
}
#[tokio::test]
async fn join_matches_absolute_path_buf_for_parent_dirs() {
let path_ref = local_path_ref(std::env::temp_dir().join("skills").abs());
assert_eq!(
path_ref
.join(Path::new("../outside"))
.await
.expect("join")
.path()
.clone(),
path_ref.path().join(Path::new("../outside"))
);
}
#[tokio::test]
async fn parent_keeps_bound_file_system_identity() {
let path = std::env::temp_dir().join("skills/demo").abs();
let file_system = Arc::new(RecordingFileSystem::default());
let path_ref = EnvironmentPathRef::new(file_system.clone(), path.clone());
assert_eq!(
path_ref.parent().await.expect("parent"),
Some(EnvironmentPathRef::new(
file_system.clone(),
std::env::temp_dir().join("skills").abs(),
))
);
assert_eq!(
file_system.recorded_calls(),
vec![RecordedCall {
method: RecordedMethod::Parent,
path,
sandbox: None,
}]
);
}
#[cfg(windows)]
#[tokio::test]
async fn join_matches_absolute_path_buf_for_windows_prefixed_and_rooted_paths() {
let path_ref = local_path_ref(std::env::temp_dir().join("skills").abs());
assert_eq!(
path_ref
.join(Path::new(r"C:temp"))
.await
.expect("join")
.path()
.clone(),
path_ref.path().join(Path::new(r"C:temp"))
);
assert_eq!(
path_ref
.join(Path::new(r"\temp"))
.await
.expect("join")
.path()
.clone(),
path_ref.path().join(Path::new(r"\temp"))
);
}
}
-2
View File
@@ -3,7 +3,6 @@ mod client_api;
mod client_transport;
mod connection;
mod environment;
mod environment_path;
mod environment_provider;
mod environment_toml;
mod fs_helper;
@@ -44,7 +43,6 @@ pub use environment::Environment;
pub use environment::EnvironmentManager;
pub use environment::LOCAL_ENVIRONMENT_ID;
pub use environment::REMOTE_ENVIRONMENT_ID;
pub use environment_path::EnvironmentPathRef;
pub use environment_provider::DefaultEnvironmentProvider;
pub use environment_provider::EnvironmentProvider;
pub use fs_helper::CODEX_FS_HELPER_ARG1;
+21 -6
View File
@@ -1,5 +1,5 @@
use codex_core_skills::model::SkillDependencies;
use codex_exec_server::EnvironmentPathRef;
use codex_utils_absolute_path::AbsolutePathBuf;
/// Source authority that owns a skill package and must be used to read it.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
@@ -62,7 +62,7 @@ pub struct SkillPackageId(pub String);
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct SkillResourceId {
id: String,
environment_path: Option<EnvironmentPathRef>,
environment_path: Option<EnvironmentSkillResource>,
}
impl SkillResourceId {
@@ -73,10 +73,17 @@ impl SkillResourceId {
}
}
pub fn environment(id: impl Into<String>, path: EnvironmentPathRef) -> Self {
pub fn environment(
id: impl Into<String>,
environment_id: impl Into<String>,
path: AbsolutePathBuf,
) -> Self {
Self {
id: id.into(),
environment_path: Some(path),
environment_path: Some(EnvironmentSkillResource {
environment_id: environment_id.into(),
path,
}),
}
}
@@ -84,11 +91,19 @@ impl SkillResourceId {
&self.id
}
pub(crate) fn environment_path(&self) -> Option<&EnvironmentPathRef> {
self.environment_path.as_ref()
pub(crate) fn environment_path(&self) -> Option<(&str, &AbsolutePathBuf)> {
self.environment_path
.as_ref()
.map(|resource| (resource.environment_id.as_str(), &resource.path))
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
struct EnvironmentSkillResource {
environment_id: String,
path: AbsolutePathBuf,
}
/// Metadata shown in the always-visible skills catalog.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SkillCatalogEntry {
+13 -7
View File
@@ -6,7 +6,6 @@ use codex_core_skills::filter_skill_load_outcome_for_product;
use codex_core_skills::loader::SkillRoot;
use codex_core_skills::loader::load_skills_from_roots;
use codex_exec_server::EnvironmentManager;
use codex_exec_server::EnvironmentPathRef;
use codex_protocol::capabilities::CapabilityRootLocation;
use codex_protocol::protocol::Product;
use codex_protocol::protocol::SkillScope;
@@ -99,7 +98,7 @@ impl SkillProvider for ExecutorSkillProvider {
enabled,
authority.clone(),
&selected_root_id,
Arc::clone(&file_system),
&environment_id,
));
}
}
@@ -121,13 +120,19 @@ impl SkillProvider for ExecutorSkillProvider {
"executor skill resource does not match its package",
));
}
let Some(resource_path) = request.resource.environment_path() else {
let Some((environment_id, resource_path)) = request.resource.environment_path() else {
return Err(SkillProviderError::new(
"executor skill resource is not bound to an environment",
));
};
let contents = resource_path
.read_to_string(/*sandbox*/ None)
let Some(environment) = self.environment_manager.get_environment(environment_id) else {
return Err(SkillProviderError::new(format!(
"executor skill resource references unavailable environment `{environment_id}`"
)));
};
let contents = environment
.get_filesystem()
.read_file_text(resource_path, /*sandbox*/ None)
.await
.map_err(|err| {
SkillProviderError::new(format!(
@@ -153,7 +158,7 @@ fn catalog_entry_from_skill(
enabled: bool,
authority: SkillAuthority,
selected_root_id: &str,
file_system: Arc<dyn codex_exec_server::ExecutorFileSystem>,
environment_id: &str,
) -> SkillCatalogEntry {
let skill_path = skill.path_to_skills_md.to_string_lossy().into_owned();
let normalized_path = skill_path.replace('\\', "/");
@@ -168,7 +173,8 @@ fn catalog_entry_from_skill(
skill.description.clone(),
SkillResourceId::environment(
display_path.clone(),
EnvironmentPathRef::new(file_system, skill.path_to_skills_md.clone()),
environment_id,
skill.path_to_skills_md.clone(),
),
)
.with_short_description(skill.short_description.clone())
@@ -21,10 +21,8 @@ use codex_protocol::capabilities::CapabilityRootLocation;
use codex_protocol::capabilities::SelectedCapabilityRoot;
use codex_protocol::protocol::SkillScope;
use codex_skills_extension::ExecutorSkillProvider;
use codex_skills_extension::catalog::SkillReadResult;
use codex_skills_extension::provider::SkillListQuery;
use codex_skills_extension::provider::SkillProvider;
use codex_skills_extension::provider::SkillReadRequest;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
@@ -202,64 +200,6 @@ async fn skill_loading_and_reads_use_the_supplied_executor_file_system() {
);
}
#[tokio::test]
async fn executor_provider_reads_from_the_environment_instance_used_for_listing() {
let test_root = create_local_skill_root("bound-instance").expect("create local skill root");
let root_path = test_root.to_string_lossy().into_owned();
let environment_manager = Arc::new(EnvironmentManager::default_for_tests());
let provider = ExecutorSkillProvider::new_with_restriction_product(
Arc::clone(&environment_manager),
/*restriction_product*/ None,
);
let catalog = provider
.list(SkillListQuery {
turn_id: "turn-1".to_string(),
executor_roots: vec![SelectedCapabilityRoot {
id: "root-a".to_string(),
location: CapabilityRootLocation::Environment {
environment_id: "local".to_string(),
path: root_path,
},
}],
host: None,
include_host_skills: false,
include_bundled_skills: true,
include_orchestrator_skills: false,
mcp_resources: None,
})
.await
.expect("list executor skills");
let entry = catalog
.entries
.into_iter()
.next()
.expect("listed executor skill");
let resource = entry.main_prompt.clone();
environment_manager
.upsert_environment("local".to_string(), "http://127.0.0.1:1".to_string())
.expect("replace environment");
assert_eq!(
provider
.read(SkillReadRequest {
authority: entry.authority,
package: entry.id,
resource: resource.clone(),
host: None,
mcp_resources: None,
})
.await
.expect("read bound executor skill"),
SkillReadResult {
resource,
contents: SKILL_CONTENTS.to_string(),
}
);
std::fs::remove_dir_all(test_root).expect("remove skill directory");
}
#[tokio::test]
async fn selected_root_id_distinguishes_identical_executor_paths() {
let test_root = create_local_skill_root("root-identity").expect("create local skill root");