Matt Low
3fde58b77d
- More emphasis on `api` package. It now holds database model structs from `lmcli/models` (which is now gone) as well as the tool spec, call, and result types. `tools.Tool` is now `api.ToolSpec`. `api.ChatCompletionClient` was renamed to `api.ChatCompletionProvider`. - Change ChatCompletion interface and implementations to no longer do automatic tool call recursion - they simply return a ToolCall message which the caller can decide what to do with (e.g. prompt for user confirmation before executing) - `api.ChatCompletionProvider` functions have had their ReplyCallback parameter removed, as now they only return a single reply. - Added a top-level `agent` package, moved the current built-in tools implementations under `agent/toolbox`. `tools.ExecuteToolCalls` is now `agent.ExecuteToolCalls`. - Fixed request context handling in openai, google, ollama (use `NewRequestWithContext`), cleaned up request cancellation in TUI - Fix tool call tui persistence bug (we were skipping message with empty content) - Now handle tool calling from TUI layer TODO: - Prompt users before executing tool calls - Automatically send tool results to the model (or make this toggleable)
101 lines
2.1 KiB
Go
101 lines
2.1 KiB
Go
package toolbox
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
toolutil "git.mlow.ca/mlow/lmcli/pkg/agent/toolbox/util"
|
|
"git.mlow.ca/mlow/lmcli/pkg/api"
|
|
)
|
|
|
|
const READ_DIR_DESCRIPTION = `Return the contents of the CWD (current working directory).
|
|
|
|
Example result:
|
|
{
|
|
"message": "success",
|
|
"result": [
|
|
{"name": "a_file.txt", "type": "file", "size": 123},
|
|
{"name": "a_directory/", "type": "dir", "size": 11},
|
|
...
|
|
]
|
|
}
|
|
|
|
For files, size represents the size of the file, in bytes.
|
|
For directories, size represents the number of entries in that directory.`
|
|
|
|
var ReadDirTool = api.ToolSpec{
|
|
Name: "read_dir",
|
|
Description: READ_DIR_DESCRIPTION,
|
|
Parameters: []api.ToolParameter{
|
|
{
|
|
Name: "relative_dir",
|
|
Type: "string",
|
|
Description: "If set, read the contents of a directory relative to the current one.",
|
|
},
|
|
},
|
|
Impl: func(tool *api.ToolSpec, args map[string]interface{}) (string, error) {
|
|
var relativeDir string
|
|
tmp, ok := args["relative_dir"]
|
|
if ok {
|
|
relativeDir, ok = tmp.(string)
|
|
if !ok {
|
|
return "", fmt.Errorf("Invalid relative_dir in function arguments: %v", tmp)
|
|
}
|
|
}
|
|
result := readDir(relativeDir)
|
|
ret, err := result.ToJson()
|
|
if err != nil {
|
|
return "", fmt.Errorf("Could not serialize result: %v", err)
|
|
}
|
|
return ret, nil
|
|
},
|
|
}
|
|
|
|
func readDir(path string) api.CallResult {
|
|
if path == "" {
|
|
path = "."
|
|
}
|
|
ok, reason := toolutil.IsPathWithinCWD(path)
|
|
if !ok {
|
|
return api.CallResult{Message: reason}
|
|
}
|
|
|
|
files, err := os.ReadDir(path)
|
|
if err != nil {
|
|
return api.CallResult{
|
|
Message: err.Error(),
|
|
}
|
|
}
|
|
|
|
var dirContents []map[string]interface{}
|
|
for _, f := range files {
|
|
info, _ := f.Info()
|
|
|
|
name := f.Name()
|
|
if strings.HasPrefix(name, ".") {
|
|
// skip hidden files
|
|
continue
|
|
}
|
|
|
|
entryType := "file"
|
|
size := info.Size()
|
|
|
|
if info.IsDir() {
|
|
name += "/"
|
|
entryType = "dir"
|
|
subdirfiles, _ := os.ReadDir(filepath.Join(".", path, info.Name()))
|
|
size = int64(len(subdirfiles))
|
|
}
|
|
|
|
dirContents = append(dirContents, map[string]interface{}{
|
|
"name": name,
|
|
"type": entryType,
|
|
"size": size,
|
|
})
|
|
}
|
|
|
|
return api.CallResult{Result: dirContents}
|
|
}
|