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)
68 lines
1.8 KiB
Go
68 lines
1.8 KiB
Go
package util
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// isPathContained attempts to verify whether `path` is the same as or
|
|
// contained within `directory`. It is overly cautious, returning false even if
|
|
// `path` IS contained within `directory`, but the two paths use different
|
|
// casing, and we happen to be on a case-insensitive filesystem.
|
|
// This is ultimately to attempt to stop an LLM from going outside of where I
|
|
// tell it to. Additional layers of security should be considered.. run in a
|
|
// VM/container.
|
|
func IsPathContained(directory string, path string) (bool, error) {
|
|
// Clean and resolve symlinks for both paths
|
|
path, err := filepath.Abs(path)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
// check if path exists
|
|
_, err = os.Stat(path)
|
|
if err != nil {
|
|
if !os.IsNotExist(err) {
|
|
return false, fmt.Errorf("Could not stat path: %v", err)
|
|
}
|
|
} else {
|
|
path, err = filepath.EvalSymlinks(path)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
}
|
|
|
|
directory, err = filepath.Abs(directory)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
directory, err = filepath.EvalSymlinks(directory)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
// Case insensitive checks
|
|
if !strings.EqualFold(path, directory) &&
|
|
!strings.HasPrefix(strings.ToLower(path), strings.ToLower(directory)+string(os.PathSeparator)) {
|
|
return false, nil
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func IsPathWithinCWD(path string) (bool, string) {
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return false, "Failed to determine current working directory"
|
|
}
|
|
if ok, err := IsPathContained(cwd, path); !ok {
|
|
if err != nil {
|
|
return false, fmt.Sprintf("Could not determine whether path '%s' is within the current working directory: %s", path, err.Error())
|
|
}
|
|
return false, fmt.Sprintf("Path '%s' is not within the current working directory", path)
|
|
}
|
|
return true, ""
|
|
}
|