fix: stabilize list_dir pagination order (#8826)

Sort list_dir entries before applying offset/limit so pagination matches
the displayed order, update pagination/truncation expectations, and add
coverage for sorted pagination. This ensures stable, predictable
directory pages when list_dir is enabled.
This commit is contained in:
Thibault Sottiaux
2026-01-08 03:51:47 -08:00
committed by GitHub
parent 634650dd25
commit 267c05fb30

View File

@@ -125,6 +125,8 @@ async fn list_dir_slice(
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(
@@ -135,11 +137,10 @@ async fn list_dir_slice(
let remaining_entries = entries.len() - start_index;
let capped_limit = limit.min(remaining_entries);
let end_index = start_index + capped_limit;
let mut selected_entries = entries[start_index..end_index].to_vec();
selected_entries.sort_unstable_by(|a, b| a.name.cmp(&b.name));
let selected_entries = &entries[start_index..end_index];
let mut formatted = Vec::with_capacity(selected_entries.len());
for entry in &selected_entries {
for entry in selected_entries {
formatted.push(format_entry_line(entry));
}
@@ -273,6 +274,7 @@ impl From<&FileType> for DirEntryKind {
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use tempfile::tempdir;
#[tokio::test]
@@ -404,6 +406,44 @@ mod tests {
);
}
#[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, 1, 2, 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, 3, 2, 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");
@@ -450,7 +490,7 @@ mod tests {
}
#[tokio::test]
async fn bfs_truncation() -> anyhow::Result<()> {
async fn truncation_respects_sorted_order() -> anyhow::Result<()> {
let temp = tempdir()?;
let dir_path = temp.path();
let nested = dir_path.join("nested");
@@ -467,7 +507,7 @@ mod tests {
vec![
"nested/".to_string(),
" child.txt".to_string(),
"root.txt".to_string(),
" deeper/".to_string(),
"More than 3 entries found".to_string()
]
);