package tools import ( "encoding/json" "fmt" "os" "os/exec" "path/filepath" "regexp" "strings" "github.com/sdaduanbilei/agent-team/internal/llm" ) type Executor struct { workspaceDir string } func NewExecutor(workspaceDir string) *Executor { return &Executor{workspaceDir: workspaceDir} } func (e *Executor) safePath(filename string) (string, error) { abs, err := filepath.Abs(filepath.Join(e.workspaceDir, filename)) if err != nil { return "", err } if !strings.HasPrefix(abs, e.workspaceDir) { return "", fmt.Errorf("path traversal detected: %s", filename) } return abs, nil } func (e *Executor) Execute(toolCall llm.ToolCall) (string, error) { switch toolCall.Function.Name { case "glob": return e.glob(toolCall.Function.Arguments) case "grep": return e.grep(toolCall.Function.Arguments) case "read_file": return e.readFile(toolCall.Function.Arguments) case "edit_file": return e.editFile(toolCall.Function.Arguments) case "write_file": return e.writeFile(toolCall.Function.Arguments) case "list_workspace": return e.listWorkspace() case "git_status": return e.gitStatus() case "git_diff": return e.gitDiff(toolCall.Function.Arguments) case "git_commit": return e.gitCommit(toolCall.Function.Arguments) default: return "", nil } } type GlobArgs struct { Pattern string `json:"pattern"` } func (e *Executor) glob(args string) (string, error) { var a GlobArgs if err := json.Unmarshal([]byte(args), &a); err != nil { return "", err } pattern := filepath.Join(e.workspaceDir, a.Pattern) files, err := filepath.Glob(pattern) if err != nil { return "", err } var result []string for _, f := range files { abs, err := filepath.Abs(f) if err != nil { continue } if !strings.HasPrefix(abs, e.workspaceDir) { continue } rel, _ := filepath.Rel(e.workspaceDir, abs) result = append(result, rel) } if len(result) == 0 { return "未找到匹配的文件", nil } return strings.Join(result, "\n"), nil } type GrepArgs struct { Pattern string `json:"pattern"` Path string `json:"path"` Include string `json:"include"` } func (e *Executor) grep(args string) (string, error) { var a GrepArgs if err := json.Unmarshal([]byte(args), &a); err != nil { return "", err } var err error searchDir := e.workspaceDir if a.Path != "" { searchDir, err = e.safePath(a.Path) if err != nil { return "", err } } re, err := regexp.Compile(a.Pattern) if err != nil { return "", err } var results []string err = filepath.Walk(searchDir, func(path string, info os.FileInfo, err error) error { if err != nil { return nil } if info.IsDir() { return nil } if a.Include != "" { matched, _ := filepath.Match(a.Include, info.Name()) if !matched { return nil } } content, err := os.ReadFile(path) if err != nil { return nil } lines := strings.Split(string(content), "\n") for i, line := range lines { if re.MatchString(line) { rel, _ := filepath.Rel(e.workspaceDir, path) results = append(results, fmt.Sprintf("%s:%d: %s", rel, i+1, line)) } } return nil }) if err != nil { return "", err } if len(results) == 0 { return "未找到匹配的内容", nil } if len(results) > 100 { results = results[:100] results = append(results, "... (还有更多结果)") } return strings.Join(results, "\n"), nil } type ReadFileArgs struct { Filename string `json:"filename"` Offset int `json:"offset"` Limit int `json:"limit"` } func (e *Executor) readFile(args string) (string, error) { var a ReadFileArgs if err := json.Unmarshal([]byte(args), &a); err != nil { return "", err } if a.Limit == 0 { a.Limit = 100 } fpath, err := e.safePath(a.Filename) if err != nil { return "", err } content, err := os.ReadFile(fpath) if err != nil { return "", err } lines := strings.Split(string(content), "\n") if a.Offset >= len(lines) { return "起始位置超出文件行数", nil } end := a.Offset + a.Limit if end > len(lines) { end = len(lines) } result := strings.Join(lines[a.Offset:end], "\n") if end < len(lines) { result += fmt.Sprintf("\n\n... (共 %d 行,当前显示 %d-%d)", len(lines), a.Offset+1, end) } return result, nil } type EditFileArgs struct { Filename string `json:"filename"` OldString string `json:"old_string"` NewString string `json:"new_string"` } func (e *Executor) editFile(args string) (string, error) { var a EditFileArgs if err := json.Unmarshal([]byte(args), &a); err != nil { return "", err } fpath, err := e.safePath(a.Filename) if err != nil { return "", err } original, err := os.ReadFile(fpath) if err != nil { return "", err } if !strings.Contains(string(original), a.OldString) { return "", fmt.Errorf("文件中未找到要替换的内容,请使用更精确的匹配字符串或使用 write_file 完整覆盖文件") } updated := strings.Replace(string(original), a.OldString, a.NewString, 1) if err := os.WriteFile(fpath, []byte(updated), 0644); err != nil { return "", err } return fmt.Sprintf("已更新文件: %s", a.Filename), nil } type WriteFileArgs struct { Filename string `json:"filename"` Content string `json:"content"` } func (e *Executor) writeFile(args string) (string, error) { var a WriteFileArgs if err := json.Unmarshal([]byte(args), &a); err != nil { return "", err } fpath, err := e.safePath(a.Filename) if err != nil { return "", err } os.MkdirAll(filepath.Dir(fpath), 0755) exists := "" if _, err := os.Stat(fpath); err == nil { exists = " (已存在,已覆盖)" } if err := os.WriteFile(fpath, []byte(a.Content), 0644); err != nil { return "", err } return fmt.Sprintf("已写入文件: %s%s", a.Filename, exists), nil } func (e *Executor) listWorkspace() (string, error) { var files []string err := filepath.Walk(e.workspaceDir, func(path string, info os.FileInfo, err error) error { if err != nil { return nil } if info.IsDir() { return nil } rel, _ := filepath.Rel(e.workspaceDir, path) if !strings.HasPrefix(rel, ".") { files = append(files, rel) } return nil }) if err != nil { return "", err } if len(files) == 0 { return "工作区为空", nil } return strings.Join(files, "\n"), nil } func (e *Executor) gitStatus() (string, error) { cmd := exec.Command("git", "status", "--porcelain") cmd.Dir = e.workspaceDir out, err := cmd.Output() if err != nil { return "", err } if len(out) == 0 { return "工作区干净,无待提交更改", nil } return string(out), nil } type GitDiffArgs struct { Filename string `json:"filename"` } func (e *Executor) gitDiff(args string) (string, error) { var a GitDiffArgs if err := json.Unmarshal([]byte(args), &a); err != nil { return "", err } filename := "" if a.Filename != "" { fpath, err := e.safePath(a.Filename) if err != nil { return "", err } filename, _ = filepath.Rel(e.workspaceDir, fpath) } var cmd *exec.Cmd if filename != "" { cmd = exec.Command("git", "diff", filename) } else { cmd = exec.Command("git", "diff") } cmd.Dir = e.workspaceDir out, err := cmd.Output() if err != nil { return "", err } if len(out) == 0 { return "无更改", nil } return string(out), nil } type GitCommitArgs struct { Message string `json:"message"` } func (e *Executor) gitCommit(args string) (string, error) { var a GitCommitArgs if err := json.Unmarshal([]byte(args), &a); err != nil { return "", err } cmd := exec.Command("git", "add", "-A") cmd.Dir = e.workspaceDir if _, err := cmd.Output(); err != nil { return "", err } cmd = exec.Command("git", "commit", "-m", a.Message) out, err := cmd.Output() if err != nil { return "", err } return string(out), nil }