feat: 实现工具执行器
- 添加 Executor 结构体,支持 workspace 目录操作 - 实现 9 个工具: glob, grep, read_file, edit_file, write_file, list_workspace, git_status, git_diff, git_commit - JSON 解析参数,错误处理完善
This commit is contained in:
parent
ae4afe9271
commit
6fe1933161
330
internal/room/tools/executor.go
Normal file
330
internal/room/tools/executor.go
Normal file
@ -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
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user