Private
Public Access
1
0
Files
lmcli/pkg/agents/toolbox/dir_tree.go
Matt Low 8e2991da1a Add pkg/util/dirtree
Update dir_tree tool to use it
Update filepicker bubble to use it
2025-01-01 05:13:08 +00:00

109 lines
2.7 KiB
Go

package toolbox
import (
"fmt"
"os"
"strconv"
toolutil "git.mlow.ca/mlow/lmcli/pkg/agents/toolbox/util"
"git.mlow.ca/mlow/lmcli/pkg/api"
"git.mlow.ca/mlow/lmcli/pkg/util/dirtree"
)
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.
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{
Name: "dir_tree",
Description: TREE_DESCRIPTION,
Parameters: []api.ToolParameter{
{
Name: "relative_path",
Type: "string",
Description: "If set, display the tree starting from this path relative to the current one.",
},
{
Name: "depth",
Type: "integer",
Description: "Depth of directory recursion. Defaults to 0 (no recursion), maximum of 5.",
},
},
Impl: func(tool *api.ToolSpec, args map[string]interface{}) (string, error) {
var relativeDir string
if tmp, ok := args["relative_path"]; ok {
relativeDir, ok = tmp.(string)
if !ok {
return "", fmt.Errorf("expected string for relative_path, got %T", tmp)
}
}
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)
}
depth = max(0, min(5, depth))
default:
return "", fmt.Errorf("expected int or string for max_depth, got %T", tmp)
}
}
result := tree(relativeDir, depth)
ret, err := result.ToJson()
if err != nil {
return "", fmt.Errorf("could not serialize result: %v", err)
}
return ret, nil
},
}
func tree(path string, depth int) api.CallResult {
if path == "" {
path = "."
}
ok, reason := toolutil.IsPathWithinCWD(path)
if !ok {
return api.CallResult{Message: reason}
}
tree := dirtree.NewTree(path)
err := tree.Root.LoadChildren(true, depth, nil)
if err != nil {
return api.CallResult{
Message: err.Error(),
}
}
nodes := dirtree.FlattenTree(tree.Root, true)
rendered := dirtree.RenderNodes(nodes, dirtree.Standard, -1, func(n *dirtree.Node) string {
if n.IsDir() {
return fmt.Sprintf("%s/", n.Name)
} else {
info, err := os.Stat(n.Path)
if err != nil {
return fmt.Sprintf("%s (ERR: %s bytes)", n.Name, err.Error())
}
return fmt.Sprintf("%s (%d bytes)", n.Name, info.Size())
}
})
return api.CallResult{Result: rendered}
}