1
+ import math
1
2
import shutil
2
3
import subprocess
3
- from typing import ClassVar
4
+ from typing import ClassVar , Optional
4
5
5
6
from pydantic import Field
6
7
@@ -20,33 +21,55 @@ class SearchFilesByNameResultObservation(Observation):
20
21
files : list [str ] = Field (
21
22
description = "List of matching file paths" ,
22
23
)
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
+ )
23
36
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}) "
25
38
26
39
@property
27
40
def total (self ) -> int :
28
- return len ( self .files )
41
+ return self .total_files
29
42
30
43
31
44
def search_files_by_name (
32
45
codebase : Codebase ,
33
46
pattern : str ,
47
+ page : int = 1 ,
48
+ files_per_page : int | float = 10 ,
34
49
) -> SearchFilesByNameResultObservation :
35
50
"""Search for files by name pattern in the codebase.
36
51
37
52
Args:
38
53
codebase: The codebase to search in
39
54
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)
40
57
"""
41
58
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
+
42
65
if shutil .which ("fd" ) is None :
43
66
logger .warning ("fd is not installed, falling back to find" )
44
67
results = subprocess .check_output (
45
68
["find" , "-name" , pattern ],
46
69
cwd = codebase .repo_path ,
47
70
timeout = 30 ,
48
71
)
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 []
50
73
51
74
else :
52
75
logger .info (f"Searching for files with pattern: { pattern } " )
@@ -55,12 +78,36 @@ def search_files_by_name(
55
78
cwd = codebase .repo_path ,
56
79
timeout = 30 ,
57
80
)
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 ]
59
102
60
103
return SearchFilesByNameResultObservation (
61
104
status = "success" ,
62
105
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 ,
64
111
)
65
112
66
113
except Exception as e :
@@ -69,4 +116,8 @@ def search_files_by_name(
69
116
error = f"Error searching files: { e !s} " ,
70
117
pattern = pattern ,
71
118
files = [],
119
+ page = page ,
120
+ total_pages = 0 ,
121
+ total_files = 0 ,
122
+ files_per_page = files_per_page ,
72
123
)
0 commit comments