mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
## 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.
91 lines
2.8 KiB
Rust
91 lines
2.8 KiB
Rust
use codex_utils_output_truncation::TruncationPolicy;
|
|
use codex_utils_output_truncation::truncate_text;
|
|
|
|
use crate::DEFAULT_READ_MAX_TOKENS;
|
|
use crate::backend::MemoriesBackendError;
|
|
use crate::backend::ReadMemoryRequest;
|
|
use crate::backend::ReadMemoryResponse;
|
|
|
|
use super::LocalMemoriesBackend;
|
|
use super::path::reject_symlink;
|
|
|
|
pub(super) async fn read(
|
|
backend: &LocalMemoriesBackend,
|
|
request: ReadMemoryRequest,
|
|
) -> Result<ReadMemoryResponse, MemoriesBackendError> {
|
|
if request.line_offset == 0 {
|
|
return Err(MemoriesBackendError::InvalidLineOffset);
|
|
}
|
|
if request.max_lines == Some(0) {
|
|
return Err(MemoriesBackendError::InvalidMaxLines);
|
|
}
|
|
|
|
let path = backend
|
|
.resolve_scoped_path(Some(request.path.as_str()))
|
|
.await?;
|
|
let Some(metadata) = LocalMemoriesBackend::metadata_or_none(&path).await? else {
|
|
return Err(MemoriesBackendError::NotFound { path: request.path });
|
|
};
|
|
reject_symlink(&request.path, &metadata)?;
|
|
if !metadata.is_file() {
|
|
return Err(MemoriesBackendError::NotFile { path: request.path });
|
|
}
|
|
|
|
let original_content = tokio::fs::read_to_string(&path).await?;
|
|
let start_byte = line_start_byte_offset(&original_content, request.line_offset)?;
|
|
let end_byte = line_end_byte_offset(&original_content, start_byte, request.max_lines);
|
|
let content_from_offset = &original_content[start_byte..end_byte];
|
|
let max_tokens = if request.max_tokens == 0 {
|
|
DEFAULT_READ_MAX_TOKENS
|
|
} else {
|
|
request.max_tokens
|
|
};
|
|
let content = truncate_text(content_from_offset, TruncationPolicy::Tokens(max_tokens));
|
|
let truncated = end_byte < original_content.len() || content != content_from_offset;
|
|
Ok(ReadMemoryResponse {
|
|
path: request.path,
|
|
start_line_number: request.line_offset,
|
|
content,
|
|
truncated,
|
|
})
|
|
}
|
|
|
|
fn line_start_byte_offset(
|
|
content: &str,
|
|
line_offset: usize,
|
|
) -> Result<usize, MemoriesBackendError> {
|
|
if line_offset == 1 {
|
|
return Ok(0);
|
|
}
|
|
|
|
let mut current_line = 1;
|
|
for (idx, ch) in content.char_indices() {
|
|
if ch == '\n' {
|
|
current_line += 1;
|
|
if current_line == line_offset {
|
|
return Ok(idx + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
Err(MemoriesBackendError::LineOffsetExceedsFileLength)
|
|
}
|
|
|
|
fn line_end_byte_offset(content: &str, start_byte: usize, max_lines: Option<usize>) -> usize {
|
|
let Some(max_lines) = max_lines else {
|
|
return content.len();
|
|
};
|
|
|
|
let mut lines_seen = 1;
|
|
for (relative_idx, ch) in content[start_byte..].char_indices() {
|
|
if ch == '\n' {
|
|
if lines_seen == max_lines {
|
|
return start_byte + relative_idx + 1;
|
|
}
|
|
lines_seen += 1;
|
|
}
|
|
}
|
|
|
|
content.len()
|
|
}
|