1
1
"""Tool for listing directory contents."""
2
2
3
- from typing import ClassVar , Union
3
+ from typing import ClassVar
4
4
5
- from pydantic import BaseModel , Field
5
+ from pydantic import Field
6
6
7
7
from codegen .sdk .core .codebase import Codebase
8
8
from codegen .sdk .core .directory import Directory
9
9
10
10
from .observation import Observation
11
11
12
12
13
- class DirectoryInfo (BaseModel ):
13
+ class DirectoryInfo (Observation ):
14
14
"""Information about a directory."""
15
15
16
- name : str = Field (description = "Name of the directory" )
17
- path : str = Field (description = "Full path to the directory" )
18
- files : list [str ] = Field (description = "List of files in this directory" )
19
- subdirectories : list [Union [str , "DirectoryInfo" ]] = Field (
20
- description = "List of subdirectories (either names or full DirectoryInfo objects depending on depth)" ,
16
+ name : str = Field (
17
+ description = "Name of the directory" ,
18
+ )
19
+ path : str = Field (
20
+ description = "Full path to the directory" ,
21
+ )
22
+ files : list [str ] | None = Field (
23
+ default = None ,
24
+ description = "List of files in this directory (None if at max depth)" ,
25
+ )
26
+ subdirectories : list ["DirectoryInfo" ] = Field (
27
+ default_factory = list ,
28
+ description = "List of subdirectories" ,
29
+ )
30
+ is_leaf : bool = Field (
31
+ default = False ,
32
+ description = "Whether this is a leaf node (at max depth)" ,
21
33
)
22
34
35
+ str_template : ClassVar [str ] = "Directory {path} ({file_count} files, {dir_count} subdirs)"
36
+
37
+ def _get_details (self ) -> dict [str , int ]:
38
+ """Get details for string representation."""
39
+ return {
40
+ "file_count" : len (self .files or []),
41
+ "dir_count" : len (self .subdirectories ),
42
+ }
43
+
44
+ def render (self ) -> str :
45
+ """Render directory listing as a file tree."""
46
+ lines = [
47
+ f"[LIST DIRECTORY]: { self .path } " ,
48
+ "" ,
49
+ ]
50
+
51
+ def add_tree_item (name : str , prefix : str = "" , is_last : bool = False ) -> tuple [str , str ]:
52
+ """Helper to format a tree item with proper prefix."""
53
+ marker = "└── " if is_last else "├── "
54
+ indent = " " if is_last else "│ "
55
+ return prefix + marker + name , prefix + indent
56
+
57
+ def build_tree (items : list [tuple [str , bool , "DirectoryInfo | None" ]], prefix : str = "" ) -> list [str ]:
58
+ """Recursively build tree with proper indentation."""
59
+ if not items :
60
+ return []
61
+
62
+ result = []
63
+ for i , (name , is_dir , dir_info ) in enumerate (items ):
64
+ is_last = i == len (items ) - 1
65
+ line , new_prefix = add_tree_item (name , prefix , is_last )
66
+ result .append (line )
67
+
68
+ # If this is a directory and not a leaf node, show its contents
69
+ if dir_info and not dir_info .is_leaf :
70
+ subitems = []
71
+ # Add files first
72
+ if dir_info .files :
73
+ for f in sorted (dir_info .files ):
74
+ subitems .append ((f , False , None ))
75
+ # Then add subdirectories
76
+ for d in dir_info .subdirectories :
77
+ subitems .append ((d .name + "/" , True , d ))
78
+
79
+ result .extend (build_tree (subitems , new_prefix ))
80
+
81
+ return result
82
+
83
+ # Sort files and directories
84
+ items = []
85
+ if self .files :
86
+ for f in sorted (self .files ):
87
+ items .append ((f , False , None ))
88
+ for d in self .subdirectories :
89
+ items .append ((d .name + "/" , True , d ))
90
+
91
+ if not items :
92
+ lines .append ("(empty directory)" )
93
+ return "\n " .join (lines )
94
+
95
+ # Generate tree
96
+ lines .extend (build_tree (items ))
97
+
98
+ return "\n " .join (lines )
99
+
23
100
24
101
class ListDirectoryObservation (Observation ):
25
102
"""Response from listing directory contents."""
26
103
27
- path : str = Field (description = "Path to the listed directory" )
28
- directory_info : DirectoryInfo = Field (description = "Information about the directory and its contents" )
29
- depth : int = Field (description = "How deep the directory traversal went" )
104
+ directory_info : DirectoryInfo = Field (
105
+ description = "Information about the directory" ,
106
+ )
107
+
108
+ str_template : ClassVar [str ] = "{directory_info}"
30
109
31
- str_template : ClassVar [str ] = "Listed contents of {path} (depth={depth})"
110
+ def render (self ) -> str :
111
+ """Render directory listing."""
112
+ return self .directory_info .render ()
32
113
33
114
34
- def list_directory (codebase : Codebase , dirpath : str = "./" , depth : int = 1 ) -> ListDirectoryObservation :
115
+ def list_directory (codebase : Codebase , path : str = "./" , depth : int = 2 ) -> ListDirectoryObservation :
35
116
"""List contents of a directory.
36
117
37
118
Args:
38
119
codebase: The codebase to operate on
39
- dirpath : Path to directory relative to workspace root
120
+ path : Path to directory relative to workspace root
40
121
depth: How deep to traverse the directory tree. Default is 1 (immediate children only).
41
122
Use -1 for unlimited depth.
42
-
43
- Returns:
44
- ListDirectoryObservation containing directory contents and metadata
45
123
"""
46
124
try :
47
- directory = codebase .get_directory (dirpath )
125
+ directory = codebase .get_directory (path )
48
126
except ValueError :
49
127
return ListDirectoryObservation (
50
128
status = "error" ,
51
- error = f"Directory not found: { dirpath } " ,
52
- path = dirpath ,
129
+ error = f"Directory not found: { path } " ,
53
130
directory_info = DirectoryInfo (
54
- name = "" ,
55
- path = dirpath ,
131
+ status = "error" ,
132
+ name = path .split ("/" )[- 1 ],
133
+ path = path ,
56
134
files = [],
57
135
subdirectories = [],
58
136
),
59
- depth = depth ,
60
- )
61
-
62
- if not directory :
63
- return ListDirectoryObservation (
64
- status = "error" ,
65
- error = f"Directory not found: { dirpath } " ,
66
- path = dirpath ,
67
- directory_info = DirectoryInfo (
68
- name = "" ,
69
- path = dirpath ,
70
- files = [],
71
- subdirectories = [],
72
- ),
73
- depth = depth ,
74
137
)
75
138
76
139
def get_directory_info (dir_obj : Directory , current_depth : int ) -> DirectoryInfo :
77
140
"""Helper function to get directory info recursively."""
78
- # Get direct files
141
+ # Get direct files (always include files unless at max depth)
79
142
all_files = []
80
143
for file in dir_obj .files :
81
144
if file .directory == dir_obj :
@@ -86,38 +149,32 @@ def get_directory_info(dir_obj: Directory, current_depth: int) -> DirectoryInfo:
86
149
for subdir in dir_obj .subdirectories :
87
150
# Only include direct descendants
88
151
if subdir .parent == dir_obj :
89
- if current_depth != 1 :
152
+ if current_depth > 1 or current_depth == - 1 :
153
+ # For deeper traversal, get full directory info
90
154
new_depth = current_depth - 1 if current_depth > 1 else - 1
91
155
subdirs .append (get_directory_info (subdir , new_depth ))
92
156
else :
93
- # At max depth, just include name
94
- subdirs .append (subdir .name )
157
+ # At max depth, return a leaf node
158
+ subdirs .append (
159
+ DirectoryInfo (
160
+ status = "success" ,
161
+ name = subdir .name ,
162
+ path = subdir .dirpath ,
163
+ files = None , # Don't include files at max depth
164
+ is_leaf = True ,
165
+ )
166
+ )
95
167
96
168
return DirectoryInfo (
169
+ status = "success" ,
97
170
name = dir_obj .name ,
98
171
path = dir_obj .dirpath ,
99
- files = all_files ,
172
+ files = sorted ( all_files ) ,
100
173
subdirectories = subdirs ,
101
174
)
102
175
103
- try :
104
- directory_info = get_directory_info (directory , depth )
105
- return ListDirectoryObservation (
106
- status = "success" ,
107
- path = dirpath ,
108
- directory_info = directory_info ,
109
- depth = depth ,
110
- )
111
- except Exception as e :
112
- return ListDirectoryObservation (
113
- status = "error" ,
114
- error = f"Failed to list directory: { e !s} " ,
115
- path = dirpath ,
116
- directory_info = DirectoryInfo (
117
- name = "" ,
118
- path = dirpath ,
119
- files = [],
120
- subdirectories = [],
121
- ),
122
- depth = depth ,
123
- )
176
+ dir_info = get_directory_info (directory , depth )
177
+ return ListDirectoryObservation (
178
+ status = "success" ,
179
+ directory_info = dir_info ,
180
+ )
0 commit comments