Files
codex/codex-rs/ext/memories/src/local/path.rs
T
jif-oai 2edae8d858 refactor: split memories extension crate modules (#22500)
## Why

The memories extension has several distinct responsibilities:
registering its prompt and tool contributors, enforcing local-memory
filesystem boundaries, implementing list/read/search behavior, and
wrapping that backend as extension tools. Those responsibilities were
concentrated in `lib.rs`, `local.rs`, and the tool modules, which made
follow-up work harder to review and risked growing files through
unrelated edits.

This PR reorganizes the crate so each responsibility has a narrower
owner while preserving the same extension entrypoint and memory tool
behavior.

## What Changed

- Moved extension lifecycle, prompt, and tool registration into
`src/extension.rs`, leaving `src/lib.rs` as the small crate entrypoint.
- Split `LocalMemoriesBackend` helpers into `local/list.rs`,
`local/path.rs`, `local/read.rs`, and `local/search.rs`.
- Centralized tool names and limits at the crate level, and kept the
backend and extension implementation crate-private.
- Made `memory_list`, `memory_read`, and `memory_search` tool executors
generic over `MemoriesBackend`, so tests can exercise the full executor
path without depending on tool internals.
- Consolidated and expanded memory extension tests in `src/tests.rs`,
including read/search tool output coverage, multi-query search, windowed
`all_within_lines`, and legacy `query` rejection.

## Testing

- Not run locally.
2026-05-13 17:39:50 +02:00

57 lines
1.5 KiB
Rust

use std::path::Component;
use std::path::Path;
use std::path::PathBuf;
use crate::backend::MemoriesBackendError;
pub(super) async fn read_sorted_dir_paths(
dir_path: &Path,
) -> Result<Vec<PathBuf>, MemoriesBackendError> {
let mut dir = match tokio::fs::read_dir(dir_path).await {
Ok(dir) => dir,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
Err(err) => return Err(err.into()),
};
let mut paths = Vec::new();
while let Some(entry) = dir.next_entry().await? {
paths.push(entry.path());
}
paths.sort();
Ok(paths)
}
pub(super) fn reject_symlink(
path: &str,
metadata: &std::fs::Metadata,
) -> Result<(), MemoriesBackendError> {
if metadata.file_type().is_symlink() {
return Err(MemoriesBackendError::invalid_path(
path,
"must not be a symlink",
));
}
Ok(())
}
pub(super) fn is_hidden_component(component: Component<'_>) -> bool {
matches!(
component,
Component::Normal(name) if name.to_string_lossy().starts_with('.')
)
}
pub(super) fn is_hidden_path(path: &Path) -> bool {
path.file_name()
.is_some_and(|name| name.to_string_lossy().starts_with('.'))
}
pub(super) fn display_relative_path(root: &Path, path: &Path) -> String {
path.strip_prefix(root)
.unwrap_or(path)
.components()
.map(|component| component.as_os_str().to_string_lossy())
.filter(|component| !component.is_empty())
.collect::<Vec<_>>()
.join("/")
}