mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
tools: remove unused experimental list_dir tool (#21170)
## Why `list_dir` still carries a full spec/handler/test path, but nothing in the current model catalog advertises it via `experimental_supported_tools`. That leaves us maintaining an environment-backed tool surface that is effectively unused. ## What changed - delete the `list_dir` handler and its tests from `codex-core` - remove the `list_dir` spec builder, handler kind, and registry wiring from `codex-tools` - clean up the remaining internal README and registry tests so they no longer mention the removed tool
This commit is contained in:
committed by
GitHub
Unverified
parent
9d579813bb
commit
70807730f5
@@ -1,294 +0,0 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::FileType;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use codex_protocol::permissions::ReadDenyMatcher;
|
||||
use codex_utils_string::take_bytes_at_char_boundary;
|
||||
use serde::Deserialize;
|
||||
use tokio::fs;
|
||||
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::registry::ToolKind;
|
||||
|
||||
pub struct ListDirHandler;
|
||||
|
||||
const DENY_READ_POLICY_MESSAGE: &str =
|
||||
"access denied: reading this path is blocked by filesystem deny_read policy";
|
||||
const MAX_ENTRY_LENGTH: usize = 500;
|
||||
const INDENTATION_SPACES: usize = 2;
|
||||
|
||||
fn default_offset() -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn default_limit() -> usize {
|
||||
25
|
||||
}
|
||||
|
||||
fn default_depth() -> usize {
|
||||
2
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ListDirArgs {
|
||||
dir_path: String,
|
||||
#[serde(default = "default_offset")]
|
||||
offset: usize,
|
||||
#[serde(default = "default_limit")]
|
||||
limit: usize,
|
||||
#[serde(default = "default_depth")]
|
||||
depth: usize,
|
||||
}
|
||||
|
||||
impl ToolHandler for ListDirHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation { payload, turn, .. } = invocation;
|
||||
|
||||
let arguments = match payload {
|
||||
ToolPayload::Function { arguments } => arguments,
|
||||
_ => {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"list_dir handler received unsupported payload".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let args: ListDirArgs = parse_arguments(&arguments)?;
|
||||
|
||||
let ListDirArgs {
|
||||
dir_path,
|
||||
offset,
|
||||
limit,
|
||||
depth,
|
||||
} = args;
|
||||
|
||||
if offset == 0 {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"offset must be a 1-indexed entry number".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if limit == 0 {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"limit must be greater than zero".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if depth == 0 {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"depth must be greater than zero".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let path = PathBuf::from(&dir_path);
|
||||
if !path.is_absolute() {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"dir_path must be an absolute path".to_string(),
|
||||
));
|
||||
}
|
||||
let file_system_sandbox_policy = turn.file_system_sandbox_policy();
|
||||
let read_deny_matcher = ReadDenyMatcher::new(&file_system_sandbox_policy, &turn.cwd);
|
||||
if read_deny_matcher
|
||||
.as_ref()
|
||||
.is_some_and(|matcher| matcher.is_read_denied(&path))
|
||||
{
|
||||
return Err(FunctionCallError::RespondToModel(format!(
|
||||
"{DENY_READ_POLICY_MESSAGE}: `{}`",
|
||||
path.display()
|
||||
)));
|
||||
}
|
||||
|
||||
let entries =
|
||||
list_dir_slice_with_policy(&path, offset, limit, depth, read_deny_matcher.as_ref())
|
||||
.await?;
|
||||
let mut output = Vec::with_capacity(entries.len() + 1);
|
||||
output.push(format!("Absolute path: {}", path.display()));
|
||||
output.extend(entries);
|
||||
Ok(FunctionToolOutput::from_text(output.join("\n"), Some(true)))
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_dir_slice_with_policy(
|
||||
path: &Path,
|
||||
offset: usize,
|
||||
limit: usize,
|
||||
depth: usize,
|
||||
read_deny_matcher: Option<&ReadDenyMatcher>,
|
||||
) -> Result<Vec<String>, FunctionCallError> {
|
||||
let mut entries = Vec::new();
|
||||
collect_entries(path, Path::new(""), depth, read_deny_matcher, &mut entries).await?;
|
||||
|
||||
if entries.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
entries.sort_unstable_by(|a, b| a.name.cmp(&b.name));
|
||||
|
||||
let start_index = offset - 1;
|
||||
if start_index >= entries.len() {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"offset exceeds directory entry count".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let remaining_entries = entries.len() - start_index;
|
||||
let capped_limit = limit.min(remaining_entries);
|
||||
let end_index = start_index + capped_limit;
|
||||
let selected_entries = &entries[start_index..end_index];
|
||||
let mut formatted = Vec::with_capacity(selected_entries.len());
|
||||
|
||||
for entry in selected_entries {
|
||||
formatted.push(format_entry_line(entry));
|
||||
}
|
||||
|
||||
if end_index < entries.len() {
|
||||
formatted.push(format!("More than {capped_limit} entries found"));
|
||||
}
|
||||
|
||||
Ok(formatted)
|
||||
}
|
||||
|
||||
async fn collect_entries(
|
||||
dir_path: &Path,
|
||||
relative_prefix: &Path,
|
||||
depth: usize,
|
||||
read_deny_matcher: Option<&ReadDenyMatcher>,
|
||||
entries: &mut Vec<DirEntry>,
|
||||
) -> Result<(), FunctionCallError> {
|
||||
let mut queue = VecDeque::new();
|
||||
queue.push_back((dir_path.to_path_buf(), relative_prefix.to_path_buf(), depth));
|
||||
|
||||
while let Some((current_dir, prefix, remaining_depth)) = queue.pop_front() {
|
||||
let mut read_dir = fs::read_dir(¤t_dir).await.map_err(|err| {
|
||||
FunctionCallError::RespondToModel(format!("failed to read directory: {err}"))
|
||||
})?;
|
||||
|
||||
let mut dir_entries = Vec::new();
|
||||
|
||||
while let Some(entry) = read_dir.next_entry().await.map_err(|err| {
|
||||
FunctionCallError::RespondToModel(format!("failed to read directory: {err}"))
|
||||
})? {
|
||||
let entry_path = entry.path();
|
||||
if let Some(read_deny_matcher) = read_deny_matcher
|
||||
&& read_deny_matcher.is_read_denied(&entry_path)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let file_type = entry.file_type().await.map_err(|err| {
|
||||
FunctionCallError::RespondToModel(format!("failed to inspect entry: {err}"))
|
||||
})?;
|
||||
|
||||
let file_name = entry.file_name();
|
||||
let relative_path = if prefix.as_os_str().is_empty() {
|
||||
PathBuf::from(&file_name)
|
||||
} else {
|
||||
prefix.join(&file_name)
|
||||
};
|
||||
|
||||
let display_name = format_entry_component(&file_name);
|
||||
let display_depth = prefix.components().count();
|
||||
let sort_key = format_entry_name(&relative_path);
|
||||
let kind = DirEntryKind::from(&file_type);
|
||||
dir_entries.push((
|
||||
entry_path,
|
||||
relative_path,
|
||||
kind,
|
||||
DirEntry {
|
||||
name: sort_key,
|
||||
display_name,
|
||||
depth: display_depth,
|
||||
kind,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
dir_entries.sort_unstable_by(|a, b| a.3.name.cmp(&b.3.name));
|
||||
|
||||
for (entry_path, relative_path, kind, dir_entry) in dir_entries {
|
||||
if kind == DirEntryKind::Directory && remaining_depth > 1 {
|
||||
queue.push_back((entry_path, relative_path, remaining_depth - 1));
|
||||
}
|
||||
entries.push(dir_entry);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_entry_name(path: &Path) -> String {
|
||||
let normalized = path.to_string_lossy().replace("\\", "/");
|
||||
if normalized.len() > MAX_ENTRY_LENGTH {
|
||||
take_bytes_at_char_boundary(&normalized, MAX_ENTRY_LENGTH).to_string()
|
||||
} else {
|
||||
normalized
|
||||
}
|
||||
}
|
||||
|
||||
fn format_entry_component(name: &OsStr) -> String {
|
||||
let normalized = name.to_string_lossy();
|
||||
if normalized.len() > MAX_ENTRY_LENGTH {
|
||||
take_bytes_at_char_boundary(&normalized, MAX_ENTRY_LENGTH).to_string()
|
||||
} else {
|
||||
normalized.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn format_entry_line(entry: &DirEntry) -> String {
|
||||
let indent = " ".repeat(entry.depth * INDENTATION_SPACES);
|
||||
let mut name = entry.display_name.clone();
|
||||
match entry.kind {
|
||||
DirEntryKind::Directory => name.push('/'),
|
||||
DirEntryKind::Symlink => name.push('@'),
|
||||
DirEntryKind::Other => name.push('?'),
|
||||
DirEntryKind::File => {}
|
||||
}
|
||||
format!("{indent}{name}")
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct DirEntry {
|
||||
name: String,
|
||||
display_name: String,
|
||||
depth: usize,
|
||||
kind: DirEntryKind,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum DirEntryKind {
|
||||
Directory,
|
||||
File,
|
||||
Symlink,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl From<&FileType> for DirEntryKind {
|
||||
fn from(file_type: &FileType) -> Self {
|
||||
if file_type.is_symlink() {
|
||||
DirEntryKind::Symlink
|
||||
} else if file_type.is_dir() {
|
||||
DirEntryKind::Directory
|
||||
} else if file_type.is_file() {
|
||||
DirEntryKind::File
|
||||
} else {
|
||||
DirEntryKind::Other
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "list_dir_tests.rs"]
|
||||
mod tests;
|
||||
@@ -1,331 +0,0 @@
|
||||
use super::*;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::ReadDenyMatcher;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::tempdir;
|
||||
|
||||
async fn list_dir_slice(
|
||||
path: &Path,
|
||||
offset: usize,
|
||||
limit: usize,
|
||||
depth: usize,
|
||||
) -> Result<Vec<String>, FunctionCallError> {
|
||||
list_dir_slice_with_policy(path, offset, limit, depth, /*read_deny_matcher*/ None).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn lists_directory_entries() {
|
||||
let temp = tempdir().expect("create tempdir");
|
||||
let dir_path = temp.path();
|
||||
|
||||
let sub_dir = dir_path.join("nested");
|
||||
tokio::fs::create_dir(&sub_dir)
|
||||
.await
|
||||
.expect("create sub dir");
|
||||
|
||||
let deeper_dir = sub_dir.join("deeper");
|
||||
tokio::fs::create_dir(&deeper_dir)
|
||||
.await
|
||||
.expect("create deeper dir");
|
||||
|
||||
tokio::fs::write(dir_path.join("entry.txt"), b"content")
|
||||
.await
|
||||
.expect("write file");
|
||||
tokio::fs::write(sub_dir.join("child.txt"), b"child")
|
||||
.await
|
||||
.expect("write child");
|
||||
tokio::fs::write(deeper_dir.join("grandchild.txt"), b"grandchild")
|
||||
.await
|
||||
.expect("write grandchild");
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::symlink;
|
||||
let link_path = dir_path.join("link");
|
||||
symlink(dir_path.join("entry.txt"), &link_path).expect("create symlink");
|
||||
}
|
||||
|
||||
let entries = list_dir_slice(
|
||||
dir_path, /*offset*/ 1, /*limit*/ 20, /*depth*/ 3,
|
||||
)
|
||||
.await
|
||||
.expect("list directory");
|
||||
|
||||
#[cfg(unix)]
|
||||
let expected = vec![
|
||||
"entry.txt".to_string(),
|
||||
"link@".to_string(),
|
||||
"nested/".to_string(),
|
||||
" child.txt".to_string(),
|
||||
" deeper/".to_string(),
|
||||
" grandchild.txt".to_string(),
|
||||
];
|
||||
|
||||
#[cfg(not(unix))]
|
||||
let expected = vec![
|
||||
"entry.txt".to_string(),
|
||||
"nested/".to_string(),
|
||||
" child.txt".to_string(),
|
||||
" deeper/".to_string(),
|
||||
" grandchild.txt".to_string(),
|
||||
];
|
||||
|
||||
assert_eq!(entries, expected);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn errors_when_offset_exceeds_entries() {
|
||||
let temp = tempdir().expect("create tempdir");
|
||||
let dir_path = temp.path();
|
||||
tokio::fs::create_dir(dir_path.join("nested"))
|
||||
.await
|
||||
.expect("create sub dir");
|
||||
|
||||
let err = list_dir_slice(
|
||||
dir_path, /*offset*/ 10, /*limit*/ 1, /*depth*/ 2,
|
||||
)
|
||||
.await
|
||||
.expect_err("offset exceeds entries");
|
||||
assert_eq!(
|
||||
err,
|
||||
FunctionCallError::RespondToModel("offset exceeds directory entry count".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn respects_depth_parameter() {
|
||||
let temp = tempdir().expect("create tempdir");
|
||||
let dir_path = temp.path();
|
||||
let nested = dir_path.join("nested");
|
||||
let deeper = nested.join("deeper");
|
||||
tokio::fs::create_dir(&nested).await.expect("create nested");
|
||||
tokio::fs::create_dir(&deeper).await.expect("create deeper");
|
||||
tokio::fs::write(dir_path.join("root.txt"), b"root")
|
||||
.await
|
||||
.expect("write root");
|
||||
tokio::fs::write(nested.join("child.txt"), b"child")
|
||||
.await
|
||||
.expect("write nested");
|
||||
tokio::fs::write(deeper.join("grandchild.txt"), b"deep")
|
||||
.await
|
||||
.expect("write deeper");
|
||||
|
||||
let entries_depth_one = list_dir_slice(
|
||||
dir_path, /*offset*/ 1, /*limit*/ 10, /*depth*/ 1,
|
||||
)
|
||||
.await
|
||||
.expect("list depth 1");
|
||||
assert_eq!(
|
||||
entries_depth_one,
|
||||
vec!["nested/".to_string(), "root.txt".to_string(),]
|
||||
);
|
||||
|
||||
let entries_depth_two = list_dir_slice(
|
||||
dir_path, /*offset*/ 1, /*limit*/ 20, /*depth*/ 2,
|
||||
)
|
||||
.await
|
||||
.expect("list depth 2");
|
||||
assert_eq!(
|
||||
entries_depth_two,
|
||||
vec![
|
||||
"nested/".to_string(),
|
||||
" child.txt".to_string(),
|
||||
" deeper/".to_string(),
|
||||
"root.txt".to_string(),
|
||||
]
|
||||
);
|
||||
|
||||
let entries_depth_three = list_dir_slice(
|
||||
dir_path, /*offset*/ 1, /*limit*/ 30, /*depth*/ 3,
|
||||
)
|
||||
.await
|
||||
.expect("list depth 3");
|
||||
assert_eq!(
|
||||
entries_depth_three,
|
||||
vec![
|
||||
"nested/".to_string(),
|
||||
" child.txt".to_string(),
|
||||
" deeper/".to_string(),
|
||||
" grandchild.txt".to_string(),
|
||||
"root.txt".to_string(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn paginates_in_sorted_order() {
|
||||
let temp = tempdir().expect("create tempdir");
|
||||
let dir_path = temp.path();
|
||||
|
||||
let dir_a = dir_path.join("a");
|
||||
let dir_b = dir_path.join("b");
|
||||
tokio::fs::create_dir(&dir_a).await.expect("create a");
|
||||
tokio::fs::create_dir(&dir_b).await.expect("create b");
|
||||
|
||||
tokio::fs::write(dir_a.join("a_child.txt"), b"a")
|
||||
.await
|
||||
.expect("write a child");
|
||||
tokio::fs::write(dir_b.join("b_child.txt"), b"b")
|
||||
.await
|
||||
.expect("write b child");
|
||||
|
||||
let first_page = list_dir_slice(
|
||||
dir_path, /*offset*/ 1, /*limit*/ 2, /*depth*/ 2,
|
||||
)
|
||||
.await
|
||||
.expect("list page one");
|
||||
assert_eq!(
|
||||
first_page,
|
||||
vec![
|
||||
"a/".to_string(),
|
||||
" a_child.txt".to_string(),
|
||||
"More than 2 entries found".to_string()
|
||||
]
|
||||
);
|
||||
|
||||
let second_page = list_dir_slice(
|
||||
dir_path, /*offset*/ 3, /*limit*/ 2, /*depth*/ 2,
|
||||
)
|
||||
.await
|
||||
.expect("list page two");
|
||||
assert_eq!(
|
||||
second_page,
|
||||
vec!["b/".to_string(), " b_child.txt".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handles_large_limit_without_overflow() {
|
||||
let temp = tempdir().expect("create tempdir");
|
||||
let dir_path = temp.path();
|
||||
tokio::fs::write(dir_path.join("alpha.txt"), b"alpha")
|
||||
.await
|
||||
.expect("write alpha");
|
||||
tokio::fs::write(dir_path.join("beta.txt"), b"beta")
|
||||
.await
|
||||
.expect("write beta");
|
||||
tokio::fs::write(dir_path.join("gamma.txt"), b"gamma")
|
||||
.await
|
||||
.expect("write gamma");
|
||||
|
||||
let entries = list_dir_slice(dir_path, /*offset*/ 2, usize::MAX, /*depth*/ 1)
|
||||
.await
|
||||
.expect("list without overflow");
|
||||
assert_eq!(
|
||||
entries,
|
||||
vec!["beta.txt".to_string(), "gamma.txt".to_string(),]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn indicates_truncated_results() {
|
||||
let temp = tempdir().expect("create tempdir");
|
||||
let dir_path = temp.path();
|
||||
|
||||
for idx in 0..40 {
|
||||
let file = dir_path.join(format!("file_{idx:02}.txt"));
|
||||
tokio::fs::write(file, b"content")
|
||||
.await
|
||||
.expect("write file");
|
||||
}
|
||||
|
||||
let entries = list_dir_slice(
|
||||
dir_path, /*offset*/ 1, /*limit*/ 25, /*depth*/ 1,
|
||||
)
|
||||
.await
|
||||
.expect("list directory");
|
||||
assert_eq!(entries.len(), 26);
|
||||
assert_eq!(
|
||||
entries.last(),
|
||||
Some(&"More than 25 entries found".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn truncation_respects_sorted_order() -> anyhow::Result<()> {
|
||||
let temp = tempdir()?;
|
||||
let dir_path = temp.path();
|
||||
let nested = dir_path.join("nested");
|
||||
let deeper = nested.join("deeper");
|
||||
tokio::fs::create_dir(&nested).await?;
|
||||
tokio::fs::create_dir(&deeper).await?;
|
||||
tokio::fs::write(dir_path.join("root.txt"), b"root").await?;
|
||||
tokio::fs::write(nested.join("child.txt"), b"child").await?;
|
||||
tokio::fs::write(deeper.join("grandchild.txt"), b"deep").await?;
|
||||
|
||||
let entries_depth_three = list_dir_slice(
|
||||
dir_path, /*offset*/ 1, /*limit*/ 3, /*depth*/ 3,
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
entries_depth_three,
|
||||
vec![
|
||||
"nested/".to_string(),
|
||||
" child.txt".to_string(),
|
||||
" deeper/".to_string(),
|
||||
"More than 3 entries found".to_string()
|
||||
]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn hides_denied_entries_and_prunes_denied_subtrees() {
|
||||
let temp = tempdir().expect("create tempdir");
|
||||
let dir_path = temp.path();
|
||||
let visible_dir = dir_path.join("visible");
|
||||
let denied_dir = dir_path.join("private");
|
||||
tokio::fs::create_dir(&visible_dir)
|
||||
.await
|
||||
.expect("create visible dir");
|
||||
tokio::fs::create_dir(&denied_dir)
|
||||
.await
|
||||
.expect("create denied dir");
|
||||
tokio::fs::write(visible_dir.join("ok.txt"), b"ok")
|
||||
.await
|
||||
.expect("write visible file");
|
||||
tokio::fs::write(denied_dir.join("secret.txt"), b"secret")
|
||||
.await
|
||||
.expect("write denied file");
|
||||
tokio::fs::write(dir_path.join("top_secret.txt"), b"secret")
|
||||
.await
|
||||
.expect("write denied top-level file");
|
||||
|
||||
let policy = FileSystemSandboxPolicy::restricted(vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: denied_dir.try_into().expect("absolute denied dir"),
|
||||
},
|
||||
access: FileSystemAccessMode::None,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: dir_path
|
||||
.join("top_secret.txt")
|
||||
.try_into()
|
||||
.expect("absolute denied file"),
|
||||
},
|
||||
access: FileSystemAccessMode::None,
|
||||
},
|
||||
]);
|
||||
|
||||
let read_deny_matcher = ReadDenyMatcher::new(&policy, dir_path);
|
||||
let entries = list_dir_slice_with_policy(
|
||||
dir_path,
|
||||
/*offset*/ 1,
|
||||
/*limit*/ 20,
|
||||
/*depth*/ 3,
|
||||
read_deny_matcher.as_ref(),
|
||||
)
|
||||
.await
|
||||
.expect("list directory");
|
||||
|
||||
assert_eq!(
|
||||
entries,
|
||||
vec!["visible/".to_string(), " ok.txt".to_string(),]
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,6 @@ pub(crate) mod agent_jobs;
|
||||
pub(crate) mod apply_patch;
|
||||
mod dynamic;
|
||||
mod goal;
|
||||
mod list_dir;
|
||||
mod mcp;
|
||||
mod mcp_resource;
|
||||
pub(crate) mod multi_agents;
|
||||
@@ -38,7 +37,6 @@ use codex_protocol::models::AdditionalPermissionProfile;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
pub use dynamic::DynamicToolHandler;
|
||||
pub use goal::GoalHandler;
|
||||
pub use list_dir::ListDirHandler;
|
||||
pub use mcp::McpHandler;
|
||||
pub use mcp_resource::McpResourceHandler;
|
||||
pub use plan::PlanHandler;
|
||||
|
||||
@@ -78,7 +78,6 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
use crate::tools::handlers::CodeModeWaitHandler;
|
||||
use crate::tools::handlers::DynamicToolHandler;
|
||||
use crate::tools::handlers::GoalHandler;
|
||||
use crate::tools::handlers::ListDirHandler;
|
||||
use crate::tools::handlers::McpHandler;
|
||||
use crate::tools::handlers::McpResourceHandler;
|
||||
use crate::tools::handlers::PlanHandler;
|
||||
@@ -223,9 +222,6 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
ToolHandlerKind::ListAgentsV2 => {
|
||||
builder.register_handler(handler.name, Arc::new(ListAgentsHandlerV2));
|
||||
}
|
||||
ToolHandlerKind::ListDir => {
|
||||
builder.register_handler(handler.name, Arc::new(ListDirHandler));
|
||||
}
|
||||
ToolHandlerKind::Mcp => {
|
||||
builder.register_handler(handler.name, mcp_handler.clone());
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ schema and Responses API tool primitives that no longer need to live in
|
||||
- `ResponsesApiNamespace`
|
||||
- `ResponsesApiNamespaceTool`
|
||||
- code-mode `ToolSpec` adapters and `exec` / `wait` spec builders
|
||||
- MCP resource, `list_dir`, and `test_sync_tool` spec builders
|
||||
- MCP resource and `test_sync_tool` spec builders
|
||||
- local host tool spec builders for shell/exec/request-permissions/view-image
|
||||
- collaboration and agent-job `ToolSpec` builders for spawn/send/wait/close,
|
||||
`request_user_input`, and CSV fanout/reporting
|
||||
|
||||
@@ -150,7 +150,6 @@ pub use tool_spec::create_image_generation_tool;
|
||||
pub use tool_spec::create_local_shell_tool;
|
||||
pub use tool_spec::create_tools_json_for_responses_api;
|
||||
pub use tool_spec::create_web_search_tool;
|
||||
pub use utility_tool::create_list_dir_tool;
|
||||
pub use utility_tool::create_test_sync_tool;
|
||||
pub use view_image::ViewImageToolOptions;
|
||||
pub use view_image::create_view_image_tool;
|
||||
|
||||
@@ -32,7 +32,6 @@ use crate::create_followup_task_tool;
|
||||
use crate::create_get_goal_tool;
|
||||
use crate::create_image_generation_tool;
|
||||
use crate::create_list_agents_tool;
|
||||
use crate::create_list_dir_tool;
|
||||
use crate::create_list_mcp_resource_templates_tool;
|
||||
use crate::create_list_mcp_resources_tool;
|
||||
use crate::create_local_shell_tool;
|
||||
@@ -348,20 +347,6 @@ pub fn build_tool_registry_plan(
|
||||
plan.register_handler("apply_patch", ToolHandlerKind::ApplyPatch);
|
||||
}
|
||||
|
||||
if config.environment_mode.has_environment()
|
||||
&& config
|
||||
.experimental_supported_tools
|
||||
.iter()
|
||||
.any(|tool| tool == "list_dir")
|
||||
{
|
||||
plan.push_spec(
|
||||
create_list_dir_tool(),
|
||||
/*supports_parallel_tool_calls*/ true,
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
plan.register_handler("list_dir", ToolHandlerKind::ListDir);
|
||||
}
|
||||
|
||||
if config
|
||||
.experimental_supported_tools
|
||||
.iter()
|
||||
|
||||
@@ -535,7 +535,7 @@ fn disabled_environment_omits_environment_backed_tools() {
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
let available_models = Vec::new();
|
||||
let mut tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
available_models: &available_models,
|
||||
features: &features,
|
||||
@@ -546,9 +546,6 @@ fn disabled_environment_omits_environment_backed_tools() {
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
})
|
||||
.with_environment_mode(ToolEnvironmentMode::None);
|
||||
tools_config
|
||||
.experimental_supported_tools
|
||||
.push("list_dir".to_string());
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
@@ -559,7 +556,6 @@ fn disabled_environment_omits_environment_backed_tools() {
|
||||
assert_lacks_tool_name(&tools, "exec_command");
|
||||
assert_lacks_tool_name(&tools, "write_stdin");
|
||||
assert_lacks_tool_name(&tools, "apply_patch");
|
||||
assert_lacks_tool_name(&tools, "list_dir");
|
||||
assert_lacks_tool_name(&tools, VIEW_IMAGE_TOOL_NAME);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ pub enum ToolHandlerKind {
|
||||
FollowupTaskV2,
|
||||
Goal,
|
||||
ListAgentsV2,
|
||||
ListDir,
|
||||
Mcp,
|
||||
McpResource,
|
||||
Plan,
|
||||
|
||||
@@ -3,42 +3,6 @@ use crate::ResponsesApiTool;
|
||||
use crate::ToolSpec;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub fn create_list_dir_tool() -> ToolSpec {
|
||||
let properties = BTreeMap::from([
|
||||
(
|
||||
"dir_path".to_string(),
|
||||
JsonSchema::string(Some("Absolute path to the directory to list.".to_string())),
|
||||
),
|
||||
(
|
||||
"offset".to_string(),
|
||||
JsonSchema::number(Some(
|
||||
"The entry number to start listing from. Must be 1 or greater.".to_string(),
|
||||
)),
|
||||
),
|
||||
(
|
||||
"limit".to_string(),
|
||||
JsonSchema::number(Some("The maximum number of entries to return.".to_string())),
|
||||
),
|
||||
(
|
||||
"depth".to_string(),
|
||||
JsonSchema::number(Some(
|
||||
"The maximum directory depth to traverse. Must be 1 or greater.".to_string(),
|
||||
)),
|
||||
),
|
||||
]);
|
||||
|
||||
ToolSpec::Function(ResponsesApiTool {
|
||||
name: "list_dir".to_string(),
|
||||
description:
|
||||
"Lists entries in a local directory with 1-indexed entry numbers and simple type labels."
|
||||
.to_string(),
|
||||
strict: false,
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::object(properties, Some(vec!["dir_path".to_string()]), Some(false.into())),
|
||||
output_schema: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_test_sync_tool() -> ToolSpec {
|
||||
let barrier_properties = BTreeMap::from([
|
||||
(
|
||||
|
||||
@@ -3,50 +3,6 @@ use crate::JsonSchema;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[test]
|
||||
fn list_dir_tool_matches_expected_spec() {
|
||||
assert_eq!(
|
||||
create_list_dir_tool(),
|
||||
ToolSpec::Function(ResponsesApiTool {
|
||||
name: "list_dir".to_string(),
|
||||
description:
|
||||
"Lists entries in a local directory with 1-indexed entry numbers and simple type labels."
|
||||
.to_string(),
|
||||
strict: false,
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::object(BTreeMap::from([
|
||||
(
|
||||
"depth".to_string(),
|
||||
JsonSchema::number(Some(
|
||||
"The maximum directory depth to traverse. Must be 1 or greater."
|
||||
.to_string(),
|
||||
)),
|
||||
),
|
||||
(
|
||||
"dir_path".to_string(),
|
||||
JsonSchema::string(Some(
|
||||
"Absolute path to the directory to list.".to_string(),
|
||||
)),
|
||||
),
|
||||
(
|
||||
"limit".to_string(),
|
||||
JsonSchema::number(Some(
|
||||
"The maximum number of entries to return.".to_string(),
|
||||
)),
|
||||
),
|
||||
(
|
||||
"offset".to_string(),
|
||||
JsonSchema::number(Some(
|
||||
"The entry number to start listing from. Must be 1 or greater."
|
||||
.to_string(),
|
||||
)),
|
||||
),
|
||||
]), Some(vec!["dir_path".to_string()]), Some(false.into())),
|
||||
output_schema: None,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sync_tool_matches_expected_spec() {
|
||||
assert_eq!(
|
||||
|
||||
Reference in New Issue
Block a user