stash
This commit is contained in:
225
tools.go
Normal file
225
tools.go
Normal file
@@ -0,0 +1,225 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Tool struct {
|
||||
Type string `json:"type"`
|
||||
Function ToolFunction `json:"function"`
|
||||
}
|
||||
|
||||
type ToolFunction struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Parameters map[string]any `json:"parameters"`
|
||||
}
|
||||
|
||||
func toolDefinitions() []Tool {
|
||||
return []Tool{
|
||||
{
|
||||
Type: "function",
|
||||
Function: ToolFunction{
|
||||
Name: "read_file",
|
||||
Description: "Read a UTF-8 text file relative to the project root. Returns the file contents.",
|
||||
Parameters: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"path": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Relative path from the project root.",
|
||||
},
|
||||
},
|
||||
"required": []string{"path"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "function",
|
||||
Function: ToolFunction{
|
||||
Name: "create_file",
|
||||
Description: "Create a new file with the given content. Fails if the file already exists. Parent directories are created as needed.",
|
||||
Parameters: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"path": map[string]any{"type": "string", "description": "Relative path from the project root."},
|
||||
"content": map[string]any{"type": "string", "description": "Full file contents to write."},
|
||||
},
|
||||
"required": []string{"path", "content"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "function",
|
||||
Function: ToolFunction{
|
||||
Name: "edit_file",
|
||||
Description: "Overwrite an existing file with new content. Fails if the file does not exist.",
|
||||
Parameters: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"path": map[string]any{"type": "string", "description": "Relative path from the project root."},
|
||||
"content": map[string]any{"type": "string", "description": "Full replacement contents."},
|
||||
},
|
||||
"required": []string{"path", "content"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "function",
|
||||
Function: ToolFunction{
|
||||
Name: "delete_file",
|
||||
Description: "Delete a file. Fails if the path is a directory or does not exist.",
|
||||
Parameters: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"path": map[string]any{"type": "string", "description": "Relative path from the project root."},
|
||||
},
|
||||
"required": []string{"path"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "function",
|
||||
Function: ToolFunction{
|
||||
Name: "list_directory",
|
||||
Description: "List the entries of a directory, relative to the project root. Use \".\" for the root.",
|
||||
Parameters: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"path": map[string]any{"type": "string", "description": "Relative directory path. Use \".\" for the project root."},
|
||||
},
|
||||
"required": []string{"path"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// runTool executes a tool call and returns a string payload for the model.
|
||||
// Errors are returned as strings too so the model can react, not fatal.
|
||||
func runTool(root, name, argsJSON string) string {
|
||||
var a struct {
|
||||
Path string `json:"path"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
if argsJSON != "" {
|
||||
if err := json.Unmarshal([]byte(argsJSON), &a); err != nil {
|
||||
return fmt.Sprintf("error: invalid arguments: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "read_file":
|
||||
return doRead(root, a.Path)
|
||||
case "create_file":
|
||||
return doCreate(root, a.Path, a.Content)
|
||||
case "edit_file":
|
||||
return doEdit(root, a.Path, a.Content)
|
||||
case "delete_file":
|
||||
return doDelete(root, a.Path)
|
||||
case "list_directory":
|
||||
return doList(root, a.Path)
|
||||
default:
|
||||
return fmt.Sprintf("error: unknown tool %q", name)
|
||||
}
|
||||
}
|
||||
|
||||
func doRead(root, p string) string {
|
||||
abs, err := resolveSafe(root, p)
|
||||
if err != nil {
|
||||
return "error: " + err.Error()
|
||||
}
|
||||
b, err := os.ReadFile(abs)
|
||||
if err != nil {
|
||||
return "error: " + err.Error()
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func doCreate(root, p, content string) string {
|
||||
abs, err := resolveSafe(root, p)
|
||||
if err != nil {
|
||||
return "error: " + err.Error()
|
||||
}
|
||||
if _, err := os.Stat(abs); err == nil {
|
||||
return fmt.Sprintf("error: file already exists: %s", p)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(abs), 0o755); err != nil {
|
||||
return "error: " + err.Error()
|
||||
}
|
||||
if err := os.WriteFile(abs, []byte(content), 0o644); err != nil {
|
||||
return "error: " + err.Error()
|
||||
}
|
||||
return fmt.Sprintf("created %s (%d bytes)", p, len(content))
|
||||
}
|
||||
|
||||
func doEdit(root, p, content string) string {
|
||||
abs, err := resolveSafe(root, p)
|
||||
if err != nil {
|
||||
return "error: " + err.Error()
|
||||
}
|
||||
info, err := os.Stat(abs)
|
||||
if err != nil {
|
||||
return "error: " + err.Error()
|
||||
}
|
||||
if info.IsDir() {
|
||||
return fmt.Sprintf("error: path is a directory: %s", p)
|
||||
}
|
||||
if err := os.WriteFile(abs, []byte(content), 0o644); err != nil {
|
||||
return "error: " + err.Error()
|
||||
}
|
||||
return fmt.Sprintf("updated %s (%d bytes)", p, len(content))
|
||||
}
|
||||
|
||||
func doDelete(root, p string) string {
|
||||
abs, err := resolveSafe(root, p)
|
||||
if err != nil {
|
||||
return "error: " + err.Error()
|
||||
}
|
||||
if abs == root {
|
||||
return "error: refusing to delete project root"
|
||||
}
|
||||
info, err := os.Stat(abs)
|
||||
if err != nil {
|
||||
return "error: " + err.Error()
|
||||
}
|
||||
if info.IsDir() {
|
||||
return fmt.Sprintf("error: path is a directory: %s", p)
|
||||
}
|
||||
if err := os.Remove(abs); err != nil {
|
||||
return "error: " + err.Error()
|
||||
}
|
||||
return fmt.Sprintf("deleted %s", p)
|
||||
}
|
||||
|
||||
func doList(root, p string) string {
|
||||
if p == "" {
|
||||
p = "."
|
||||
}
|
||||
abs, err := resolveSafe(root, p)
|
||||
if err != nil {
|
||||
return "error: " + err.Error()
|
||||
}
|
||||
entries, err := os.ReadDir(abs)
|
||||
if err != nil {
|
||||
return "error: " + err.Error()
|
||||
}
|
||||
lines := make([]string, 0, len(entries))
|
||||
for _, e := range entries {
|
||||
name := e.Name()
|
||||
if e.IsDir() {
|
||||
name += "/"
|
||||
}
|
||||
lines = append(lines, name)
|
||||
}
|
||||
sort.Strings(lines)
|
||||
if len(lines) == 0 {
|
||||
return "(empty)"
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
Reference in New Issue
Block a user