1+ import math
12import shutil
23import subprocess
3- from typing import ClassVar
4+ from typing import ClassVar , Optional
45
56from pydantic import Field
67
@@ -20,33 +21,55 @@ class SearchFilesByNameResultObservation(Observation):
2021 files : list [str ] = Field (
2122 description = "List of matching file paths" ,
2223 )
24+ page : int = Field (
25+ description = "Current page number (1-based)" ,
26+ )
27+ total_pages : int = Field (
28+ description = "Total number of pages available" ,
29+ )
30+ total_files : int = Field (
31+ description = "Total number of files with matches" ,
32+ )
33+ files_per_page : int | float = Field (
34+ description = "Number of files shown per page" ,
35+ )
2336
24- str_template : ClassVar [str ] = "Found {total } files matching pattern: {pattern}"
37+ str_template : ClassVar [str ] = "Found {total_files } files matching pattern: {pattern} (page {page}/{total_pages}) "
2538
2639 @property
2740 def total (self ) -> int :
28- return len ( self .files )
41+ return self .total_files
2942
3043
3144def search_files_by_name (
3245 codebase : Codebase ,
3346 pattern : str ,
47+ page : int = 1 ,
48+ files_per_page : int | float = 10 ,
3449) -> SearchFilesByNameResultObservation :
3550 """Search for files by name pattern in the codebase.
3651
3752 Args:
3853 codebase: The codebase to search in
3954 pattern: Glob pattern to search for (e.g. "*.py", "test_*.py")
55+ page: Page number to return (1-based, default: 1)
56+ files_per_page: Number of files to return per page (default: 10)
4057 """
4158 try :
59+ # Validate pagination parameters
60+ if page < 1 :
61+ page = 1
62+ if files_per_page is not None and files_per_page < 1 :
63+ files_per_page = 20
64+
4265 if shutil .which ("fd" ) is None :
4366 logger .warning ("fd is not installed, falling back to find" )
4467 results = subprocess .check_output (
4568 ["find" , "-name" , pattern ],
4669 cwd = codebase .repo_path ,
4770 timeout = 30 ,
4871 )
49- files = [path .removeprefix ("./" ) for path in results .decode ("utf-8" ).strip ().split ("\n " )] if results .strip () else []
72+ all_files = [path .removeprefix ("./" ) for path in results .decode ("utf-8" ).strip ().split ("\n " )] if results .strip () else []
5073
5174 else :
5275 logger .info (f"Searching for files with pattern: { pattern } " )
@@ -55,12 +78,36 @@ def search_files_by_name(
5578 cwd = codebase .repo_path ,
5679 timeout = 30 ,
5780 )
58- files = results .decode ("utf-8" ).strip ().split ("\n " ) if results .strip () else []
81+ all_files = results .decode ("utf-8" ).strip ().split ("\n " ) if results .strip () else []
82+
83+ # Sort files for consistent pagination
84+ all_files .sort ()
85+
86+ # Calculate pagination
87+ total_files = len (all_files )
88+ if files_per_page == math .inf :
89+ files_per_page = total_files
90+ total_pages = 1
91+ else :
92+ total_pages = (total_files + files_per_page - 1 ) // files_per_page if total_files > 0 else 1
93+
94+
95+ # Ensure page is within valid range
96+ page = min (page , total_pages )
97+
98+ # Get paginated results
99+ start_idx = (page - 1 ) * files_per_page
100+ end_idx = start_idx + files_per_page
101+ paginated_files = all_files [start_idx :end_idx ]
59102
60103 return SearchFilesByNameResultObservation (
61104 status = "success" ,
62105 pattern = pattern ,
63- files = files ,
106+ files = paginated_files ,
107+ page = page ,
108+ total_pages = total_pages ,
109+ total_files = total_files ,
110+ files_per_page = files_per_page ,
64111 )
65112
66113 except Exception as e :
@@ -69,4 +116,8 @@ def search_files_by_name(
69116 error = f"Error searching files: { e !s} " ,
70117 pattern = pattern ,
71118 files = [],
119+ page = page ,
120+ total_pages = 0 ,
121+ total_files = 0 ,
122+ files_per_page = files_per_page ,
72123 )
0 commit comments