Skip to content

Commit 31c2086

Browse files
authored
Merge pull request #85 from editor-code-assistant/issue-82
Improving eca_directory_tree tool
2 parents e06d3a3 + 9921ab4 commit 31c2086

File tree

6 files changed

+81
-25
lines changed

6 files changed

+81
-25
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## Unreleased
44

5+
6+
- Improved the `eca_directory_tree` tool #82
7+
58
## 0.36.0
69

710
- Support relative contexts additions via `~`, `./` `../` and `/`. #61

integration-test/integration/chat/anthropic_test.clj

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,9 @@
274274
:arguments {:path (h/project-path->canon-path "resources")}
275275
:summary "Listing file tree"
276276
:error false
277-
:outputs [{:type "text" :text (str "[FILE] " (h/project-path->canon-path "resources/file1.md\n")
278-
"[FILE] " (h/project-path->canon-path "resources/file2.md\n"))}]})
277+
:outputs [{:type "text" :text (str "├── file1.md\n"
278+
"└── file2.md\n\n"
279+
"0 directories, 2 files")}]})
279280
(match-content chat-id req-id "assistant" {:type "text" :text "The files I see:\n"})
280281
(match-content chat-id req-id "assistant" {:type "text" :text "file1\nfile2\n"})
281282
(match-content chat-id req-id "system" {:type "usage"
@@ -300,8 +301,9 @@
300301
{:role "user"
301302
:content [{:type "tool_result"
302303
:tool_use_id "tool-1"
303-
:content (str "[FILE] " (h/project-path->canon-path "resources/file1.md\n")
304-
"[FILE] " (h/project-path->canon-path "resources/file2.md\n\n"))}]}]
304+
:content (str "├── file1.md\n"
305+
"└── file2.md\n\n"
306+
"0 directories, 2 files\n")}]}]
305307
:tools (m/embeds
306308
[{:name "eca_directory_tree"}])
307309
:system (m/pred vector?)}

integration-test/integration/chat/ollama_test.clj

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,9 @@
241241
:arguments {:path (h/project-path->canon-path "resources")}
242242
:summary "Listing file tree"
243243
:error false
244-
:outputs [{:type "text" :text (str "[FILE] " (h/project-path->canon-path "resources/file1.md\n")
245-
"[FILE] " (h/project-path->canon-path "resources/file2.md\n"))}]})
244+
:outputs [{:type "text" :text (str "├── file1.md\n"
245+
"└── file2.md\n\n"
246+
"0 directories, 2 files")}]})
246247
(match-content chat-id req-id "assistant" {:type "text" :text "The files I see:\n"})
247248
(match-content chat-id req-id "assistant" {:type "text" :text "file1\nfile2\n"})
248249
(match-content chat-id req-id "system" {:type "progress" :state "finished"})
@@ -256,7 +257,8 @@
256257
:arguments {:path (h/project-path->canon-path "resources")}
257258
:summary "Listing file tree"
258259
:origin "native"}}]}
259-
{:role "tool" :content (str "[FILE] " (h/project-path->canon-path "resources/file1.md\n")
260-
"[FILE] " (h/project-path->canon-path "resources/file2.md\n\n"))}]
260+
{:role "tool" :content (str "├── file1.md\n"
261+
"└── file2.md\n\n"
262+
"0 directories, 2 files\n")}]
261263
:tools (m/embeds [{:type "function" :function {:name "eca_directory_tree"}}])}
262264
llm.mocks/*last-req-body*))))))

integration-test/integration/chat/openai_test.clj

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,9 @@
274274
:arguments {:path (h/project-path->canon-path "resources")}
275275
:summary "Listing file tree"
276276
:error false
277-
:outputs [{:type "text" :text (str "[FILE] " (h/project-path->canon-path "resources/file1.md\n")
278-
"[FILE] " (h/project-path->canon-path "resources/file2.md\n"))}]})
277+
:outputs [{:type "text" :text (str "├── file1.md\n"
278+
"└── file2.md\n\n"
279+
"0 directories, 2 files")}]})
279280
(match-content chat-id req-id "assistant" {:type "text" :text "The files I see:\n"})
280281
(match-content chat-id req-id "assistant" {:type "text" :text "file1\nfile2\n"})
281282
(match-content chat-id req-id "system" {:type "usage"
@@ -298,8 +299,9 @@
298299
:arguments (str "{\"path\":\"" (h/project-path->canon-path "resources") "\"}")}
299300
{:type "function_call_output"
300301
:call_id "tool-1"
301-
:output (str "[FILE] " (h/project-path->canon-path "resources/file1.md\n")
302-
"[FILE] " (h/project-path->canon-path "resources/file2.md\n\n"))}]
302+
:output (str "├── file1.md\n"
303+
"└── file2.md\n\n"
304+
"0 directories, 2 files\n")}]
303305
:tools (m/embeds
304306
[{:name "eca_directory_tree"}])
305307
:instructions (m/pred string?)}

src/eca/features/tools/filesystem.clj

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,58 @@
1818
[["path" fs/exists? "$path is not a valid path"]
1919
["path" (partial allowed-path? db) (str "Access denied - path $path outside allowed directories: " (tools.util/workspace-roots-strs db))]])
2020

21+
(defn ^:private tree-walk
22+
"Recursively walks a directory tree, building a visual tree representation with counts."
23+
([directory stats]
24+
(tree-walk directory "" stats 1 0))
25+
([directory stats max-depth]
26+
(tree-walk directory "" stats max-depth 0))
27+
([directory prefix stats max-depth current-depth]
28+
(let [filepaths (->> (fs/list-dir directory)
29+
(map fs/file-name)
30+
(remove #(string/starts-with? % "."))
31+
sort
32+
vec)]
33+
(loop [index 0
34+
output ""]
35+
(if (>= index (count filepaths))
36+
output
37+
(let [filename (nth filepaths index)
38+
absolute (fs/path directory filename)
39+
is-last? (= index (dec (count filepaths)))
40+
current-prefix (if is-last? "└── " "├── ")
41+
next-prefix (if is-last? " " "")]
42+
43+
;; Register file/directory in stats
44+
(if (fs/directory? absolute)
45+
(swap! (:dir-count stats) inc)
46+
(swap! (:file-count stats) inc))
47+
48+
(let [current-line (str prefix current-prefix filename "\n")
49+
;; Only recurse if we haven't reached max depth
50+
subtree-output (if (and (fs/directory? absolute)
51+
(< current-depth max-depth))
52+
(tree-walk absolute
53+
(str prefix next-prefix)
54+
stats
55+
max-depth
56+
(inc current-depth))
57+
"")]
58+
(recur (inc index)
59+
(str output current-line subtree-output)))))))))
60+
2161
(defn ^:private directory-tree [arguments {:keys [db]}]
2262
(let [path (delay (fs/canonicalize (get arguments "path")))]
2363
(or (tools.util/invalid-arguments arguments (path-validations db))
24-
(tools.util/single-text-content
25-
(reduce
26-
(fn [out path]
27-
(str out
28-
(format "[%s] %s\n"
29-
(if (fs/directory? path) "DIR" "FILE")
30-
path)))
31-
""
32-
(sort (fs/list-dir @path)))))))
64+
(let [max-depth (or (get arguments "max_depth") Integer/MAX_VALUE)
65+
stats {:dir-count (atom 0)
66+
:file-count (atom 0)}
67+
tree-output (tree-walk @path stats max-depth)
68+
summary (format "%d directories, %d files"
69+
@(:dir-count stats)
70+
@(:file-count stats))]
71+
(tools.util/single-text-content
72+
(str tree-output "\n" summary))))))
3373

3474
(def ^:private read-file-max-lines 2000)
3575

test/eca/features/tools/filesystem_test.clj

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,19 @@
3838
(is (match?
3939
{:error false
4040
:contents [{:type :text
41-
:text (str "[DIR] " (h/file-path "/foo/bar/baz/qux") "\n"
42-
"[FILE] " (h/file-path "/foo/bar/baz/some.clj") "\n")}]}
41+
:text (str "├── qux\n"
42+
"└── some.clj\n\n"
43+
"1 directories, 1 files")}]}
4344
(with-redefs [fs/exists? (constantly true)
4445
fs/starts-with? (constantly true)
45-
fs/list-dir (constantly [(fs/path (h/file-path "/foo/bar/baz/some.clj"))
46-
(fs/path (h/file-path "/foo/bar/baz/qux"))])
46+
fs/list-dir (fn [path]
47+
(let [p (str path)]
48+
(cond
49+
(= p (h/file-path "/foo/bar/baz"))
50+
[(fs/path (h/file-path "/foo/bar/baz/some.clj"))
51+
(fs/path (h/file-path "/foo/bar/baz/qux"))]
52+
(= p (h/file-path "/foo/bar/baz/qux"))
53+
[]))) ; make "qux" an empty directory
4754
fs/directory? (fn [path] (not (string/ends-with? (str path) ".clj")))
4855
fs/canonicalize (constantly (h/file-path "/foo/bar/baz"))]
4956
((get-in f.tools.filesystem/definitions ["eca_directory_tree" :handler])

0 commit comments

Comments
 (0)