Files
Agent-Native/runtime/tools/builtin.go
T
weichenandCursor fedf671a48
ci / vet-and-test (push) Successful in 5s
ci / docker-checkout-hostgateway (push) Successful in 3s
Import the current agent-native module as a fresh history.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-29 12:29:04 +00:00

199 lines
5.8 KiB
Go

package tools
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
)
type readFile struct{ root string }
func (t *readFile) Name() string { return "read_file" }
func (t *readFile) Description() string {
return "Read a UTF-8 text file inside the workspace."
}
func (t *readFile) Parameters() json.RawMessage {
return json.RawMessage(`{"type":"object","required":["path"],"properties":{"path":{"type":"string"}}}`)
}
func (t *readFile) Execute(_ context.Context, args json.RawMessage) (Result, error) {
rel, err := readArg(args, "path")
if err != nil {
return Result{}, err
}
path, err := mustPath(t.root, rel)
if err != nil {
return Result{Content: err.Error(), IsError: true}, nil
}
b, err := os.ReadFile(path)
if err != nil {
return Result{Content: err.Error(), IsError: true}, nil
}
if len(b) > maxReadBytes {
b = b[:maxReadBytes]
}
return Result{Content: string(b)}, nil
}
type writeFile struct{ root string }
func (t *writeFile) Name() string { return "write_file" }
func (t *writeFile) Description() string {
return "Create or replace a UTF-8 text file inside the workspace."
}
func (t *writeFile) Parameters() json.RawMessage {
return json.RawMessage(`{"type":"object","required":["path","content"],"properties":{"path":{"type":"string"},"content":{"type":"string"}}}`)
}
func (t *writeFile) Execute(_ context.Context, args json.RawMessage) (Result, error) {
var obj struct {
Path string `json:"path"`
Content string `json:"content"`
}
if err := json.Unmarshal(args, &obj); err != nil {
return Result{}, err
}
path, err := mustPath(t.root, obj.Path)
if err != nil {
return Result{Content: err.Error(), IsError: true}, nil
}
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return Result{Content: err.Error(), IsError: true}, nil
}
if err := os.WriteFile(path, []byte(obj.Content), 0o644); err != nil {
return Result{Content: err.Error(), IsError: true}, nil
}
return Result{Content: "wrote " + obj.Path}, nil
}
type editFile struct{ root string }
func (t *editFile) Name() string { return "edit_file" }
func (t *editFile) Description() string {
return "Replace one unique exact text span in a workspace file."
}
func (t *editFile) Parameters() json.RawMessage {
return json.RawMessage(`{"type":"object","required":["path","old","new"],"properties":{"path":{"type":"string"},"old":{"type":"string"},"new":{"type":"string"}}}`)
}
func (t *editFile) Execute(_ context.Context, args json.RawMessage) (Result, error) {
var obj struct {
Path string `json:"path"`
Old string `json:"old"`
New string `json:"new"`
}
if err := json.Unmarshal(args, &obj); err != nil {
return Result{}, err
}
if obj.Old == "" {
return Result{Content: "old text is empty", IsError: true}, nil
}
path, err := mustPath(t.root, obj.Path)
if err != nil {
return Result{Content: err.Error(), IsError: true}, nil
}
b, err := os.ReadFile(path)
if err != nil {
return Result{Content: err.Error(), IsError: true}, nil
}
text := string(b)
n := strings.Count(text, obj.Old)
if n != 1 {
return Result{Content: fmt.Sprintf("expected one match, found %d", n), IsError: true}, nil
}
updated := strings.Replace(text, obj.Old, obj.New, 1)
if err := os.WriteFile(path, []byte(updated), 0o644); err != nil {
return Result{Content: err.Error(), IsError: true}, nil
}
return Result{Content: "edited " + obj.Path}, nil
}
type listDir struct{ root string }
func (t *listDir) Name() string { return "list_dir" }
func (t *listDir) Description() string {
return "List names in a workspace directory."
}
func (t *listDir) Parameters() json.RawMessage {
return json.RawMessage(`{"type":"object","properties":{"path":{"type":"string"}}}`)
}
func (t *listDir) Execute(_ context.Context, args json.RawMessage) (Result, error) {
rel, _ := readArg(args, "path")
if rel == "" {
rel = "."
}
path, err := mustPath(t.root, rel)
if err != nil {
return Result{Content: err.Error(), IsError: true}, nil
}
entries, err := os.ReadDir(path)
if err != nil {
return Result{Content: err.Error(), IsError: true}, nil
}
names := make([]string, 0, len(entries))
for _, e := range entries {
name := e.Name()
if e.IsDir() {
name += "/"
}
names = append(names, name)
}
return Result{Content: strings.Join(names, "\n")}, nil
}
type grepTool struct{ root string }
func (t *grepTool) Name() string { return "grep" }
func (t *grepTool) Description() string {
return "Search workspace text files for a literal substring."
}
func (t *grepTool) Parameters() json.RawMessage {
return json.RawMessage(`{"type":"object","required":["pattern"],"properties":{"pattern":{"type":"string"},"path":{"type":"string"}}}`)
}
func (t *grepTool) Execute(_ context.Context, args json.RawMessage) (Result, error) {
var obj struct {
Pattern string `json:"pattern"`
Path string `json:"path"`
}
if err := json.Unmarshal(args, &obj); err != nil {
return Result{}, err
}
if obj.Path == "" {
obj.Path = "."
}
start, err := mustPath(t.root, obj.Path)
if err != nil {
return Result{Content: err.Error(), IsError: true}, nil
}
var hits []string
err = filepath.WalkDir(start, func(path string, d os.DirEntry, walkErr error) error {
if walkErr != nil || d.IsDir() {
return nil
}
if len(hits) >= 50 {
return fmt.Errorf("stop")
}
b, err := os.ReadFile(path)
if err != nil || len(b) > maxReadBytes {
return nil
}
rel, _ := filepath.Rel(t.root, path)
for i, line := range strings.Split(string(b), "\n") {
if strings.Contains(line, obj.Pattern) {
hits = append(hits, fmt.Sprintf("%s:%d:%s", filepath.ToSlash(rel), i+1, line))
if len(hits) >= 50 {
return fmt.Errorf("stop")
}
}
}
return nil
})
if err != nil && err.Error() != "stop" {
return Result{Content: err.Error(), IsError: true}, nil
}
if len(hits) == 0 {
return Result{Content: "no matches"}, nil
}
return Result{Content: strings.Join(hits, "\n")}, nil
}