lmcli/pkg/agents/toolbox/dir_tree.go

143 lines
3.3 KiB
Go
Raw Normal View History

package toolbox
2024-03-22 14:30:34 -06:00
import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
toolutil "git.mlow.ca/mlow/lmcli/pkg/agents/toolbox/util"
"git.mlow.ca/mlow/lmcli/pkg/api"
2024-03-22 14:30:34 -06:00
)
2024-05-14 17:00:00 -06:00
const TREE_DESCRIPTION = `Retrieve a tree-like view of a directory's contents.
Use these results for your own reference in completing your task, they do not need to be shown to the user.
2024-03-22 14:30:34 -06:00
Example result:
{
"message": "success",
"result": ".
a_directory/
file1.txt (100 bytes)
file2.txt (200 bytes)
a_file.txt (123 bytes)
another_file.txt (456 bytes)"
}
`
var DirTreeTool = api.ToolSpec{
2024-03-22 14:30:34 -06:00
Name: "dir_tree",
Description: TREE_DESCRIPTION,
Parameters: []api.ToolParameter{
2024-03-22 14:30:34 -06:00
{
Name: "relative_path",
Type: "string",
Description: "If set, display the tree starting from this path relative to the current one.",
},
{
2024-05-14 17:00:00 -06:00
Name: "depth",
2024-03-22 14:30:34 -06:00
Type: "integer",
Description: "Depth of directory recursion. Defaults to 0 (no recursion), maximum of 5.",
2024-03-22 14:30:34 -06:00
},
},
Impl: func(tool *api.ToolSpec, args map[string]interface{}) (string, error) {
2024-03-22 14:30:34 -06:00
var relativeDir string
2024-05-14 17:00:00 -06:00
if tmp, ok := args["relative_path"]; ok {
2024-03-22 14:30:34 -06:00
relativeDir, ok = tmp.(string)
if !ok {
2024-05-14 17:00:00 -06:00
return "", fmt.Errorf("expected string for relative_path, got %T", tmp)
2024-03-22 14:30:34 -06:00
}
}
2024-05-14 17:00:00 -06:00
var depth int = 0 // Default value if not provided
if tmp, ok := args["depth"]; ok {
switch v := tmp.(type) {
case float64:
depth = int(v)
case string:
var err error
if depth, err = strconv.Atoi(v); err != nil {
return "", fmt.Errorf("invalid `depth` value, expected integer but got string that cannot convert: %v", tmp)
2024-03-22 14:30:34 -06:00
}
depth = max(0, min(5, depth))
2024-05-14 17:00:00 -06:00
default:
return "", fmt.Errorf("expected int or string for max_depth, got %T", tmp)
2024-03-22 14:30:34 -06:00
}
}
2024-05-14 17:00:00 -06:00
result := tree(relativeDir, depth)
2024-03-22 14:30:34 -06:00
ret, err := result.ToJson()
if err != nil {
2024-05-14 17:00:00 -06:00
return "", fmt.Errorf("could not serialize result: %v", err)
2024-03-22 14:30:34 -06:00
}
return ret, nil
},
}
func tree(path string, depth int) api.CallResult {
2024-03-22 14:30:34 -06:00
if path == "" {
path = "."
}
ok, reason := toolutil.IsPathWithinCWD(path)
if !ok {
return api.CallResult{Message: reason}
2024-03-22 14:30:34 -06:00
}
var treeOutput strings.Builder
treeOutput.WriteString(path + "\n")
2024-05-14 17:00:00 -06:00
err := buildTree(&treeOutput, path, "", depth)
2024-03-22 14:30:34 -06:00
if err != nil {
return api.CallResult{
2024-03-22 14:30:34 -06:00
Message: err.Error(),
}
}
return api.CallResult{Result: treeOutput.String()}
2024-03-22 14:30:34 -06:00
}
2024-05-14 17:00:00 -06:00
func buildTree(output *strings.Builder, path string, prefix string, depth int) error {
2024-03-22 14:30:34 -06:00
files, err := os.ReadDir(path)
if err != nil {
return err
}
for i, file := range files {
if strings.HasPrefix(file.Name(), ".") {
// Skip hidden files and directories
continue
}
isLast := i == len(files)-1
var branch string
if isLast {
branch = "└── "
} else {
branch = "├── "
}
info, _ := file.Info()
size := info.Size()
sizeStr := fmt.Sprintf(" (%d bytes)", size)
output.WriteString(prefix + branch + file.Name())
if file.IsDir() {
output.WriteString("/\n")
if depth > 0 {
2024-03-22 14:30:34 -06:00
var nextPrefix string
if isLast {
nextPrefix = prefix + " "
} else {
nextPrefix = prefix + "│ "
}
2024-05-14 17:00:00 -06:00
buildTree(output, filepath.Join(path, file.Name()), nextPrefix, depth-1)
2024-03-22 14:30:34 -06:00
}
} else {
output.WriteString(sizeStr + "\n")
}
}
return nil
}