diff --git a/internal/room/tools/executor.go b/internal/room/tools/executor.go new file mode 100644 index 0000000..47f4e04 --- /dev/null +++ b/internal/room/tools/executor.go @@ -0,0 +1,330 @@ +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) 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 { + rel, _ := filepath.Rel(e.workspaceDir, f) + 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 + } + + searchDir := e.workspaceDir + if a.Path != "" { + searchDir = filepath.Join(e.workspaceDir, a.Path) + } + + 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 := filepath.Join(e.workspaceDir, a.Filename) + 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 := filepath.Join(e.workspaceDir, a.Filename) + original, err := os.ReadFile(fpath) + if err != nil { + return "", err + } + + if !strings.Contains(string(original), a.OldString) { + return "", fmt.Errorf("文件中未找到要替换的内容") + } + + 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 := filepath.Join(e.workspaceDir, a.Filename) + 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 + } + + var cmd *exec.Cmd + if a.Filename != "" { + cmd = exec.Command("git", "diff", a.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 +}