nub/internal/tool/builtin/edit.go
Tom a97013d876 initial commit
- v 0.1.0 siehe CHANGELOG.md
2026-07-25 11:08:01 +02:00

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
}