86 lines
2.4 KiB
Go
86 lines
2.4 KiB
Go
package builtin
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"nub/internal/tool"
|
|
)
|
|
|
|
type EditTool struct{}
|
|
|
|
func (EditTool) Name() string { return "edit" }
|
|
func (EditTool) Description() string {
|
|
return "Ersetzt einen exakten String in einer Datei. Bei mehreren Treffern ohne replace_all: Fehler."
|
|
}
|
|
|
|
func (EditTool) Schema() json.RawMessage {
|
|
return json.RawMessage(`{
|
|
"type": "object",
|
|
"properties": {
|
|
"path": {"type": "string"},
|
|
"old_string": {"type": "string"},
|
|
"new_string": {"type": "string"},
|
|
"replace_all": {"type": "boolean"}
|
|
},
|
|
"required": ["path", "old_string", "new_string"]
|
|
}`)
|
|
}
|
|
|
|
type editInput struct {
|
|
Path string `json:"path"`
|
|
OldString string `json:"old_string"`
|
|
NewString string `json:"new_string"`
|
|
ReplaceAll bool `json:"replace_all"`
|
|
}
|
|
|
|
func (EditTool) Run(ctx context.Context, input json.RawMessage, env tool.Env) (tool.Result, error) {
|
|
var in editInput
|
|
if err := json.Unmarshal(input, &in); err != nil {
|
|
return tool.Result{ForModel: "invalid input: " + err.Error(), IsError: true}, nil
|
|
}
|
|
if in.OldString == in.NewString {
|
|
return tool.Result{ForModel: "old_string and new_string are identical", IsError: true}, nil
|
|
}
|
|
path, err := resolvePath(env.RepoRoot, env.Cwd, in.Path)
|
|
if err != nil {
|
|
return tool.Result{ForModel: err.Error(), IsError: true}, nil
|
|
}
|
|
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return tool.Result{ForModel: fmt.Sprintf("read %s: %v", in.Path, err), IsError: true}, nil
|
|
}
|
|
old := string(data)
|
|
|
|
count := strings.Count(old, in.OldString)
|
|
if count == 0 {
|
|
return tool.Result{ForModel: fmt.Sprintf("%s: old_string not found", in.Path), IsError: true}, nil
|
|
}
|
|
if count > 1 && !in.ReplaceAll {
|
|
return tool.Result{ForModel: fmt.Sprintf("%s: old_string is ambiguous (%d matches); use replace_all or a more specific old_string", in.Path, count), IsError: true}, nil
|
|
}
|
|
|
|
var next string
|
|
if in.ReplaceAll {
|
|
next = strings.ReplaceAll(old, in.OldString, in.NewString)
|
|
} else {
|
|
next = strings.Replace(old, in.OldString, in.NewString, 1)
|
|
}
|
|
|
|
if err := os.WriteFile(path, []byte(next), 0o644); err != nil {
|
|
return tool.Result{ForModel: fmt.Sprintf("write %s: %v", in.Path, err), IsError: true}, nil
|
|
}
|
|
|
|
replaced := 1
|
|
if in.ReplaceAll {
|
|
replaced = count
|
|
}
|
|
return tool.Result{
|
|
ForModel: fmt.Sprintf("%s: %d replacement(s)", in.Path, replaced),
|
|
ForUI: DiffResult{Path: in.Path, Lines: lineDiff(old, next)},
|
|
}, nil
|
|
}
|