98 lines
2.3 KiB
Go
98 lines
2.3 KiB
Go
package builtin
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"nub/internal/tool"
|
|
)
|
|
|
|
const defaultReadLimit = 2000
|
|
|
|
type ReadTool struct{}
|
|
|
|
func (ReadTool) Name() string { return "read" }
|
|
func (ReadTool) Description() string {
|
|
return "Liest eine Datei mit Zeilennummern, optional ab einem Offset mit Limit."
|
|
}
|
|
|
|
func (ReadTool) Schema() json.RawMessage {
|
|
return json.RawMessage(`{
|
|
"type": "object",
|
|
"properties": {
|
|
"path": {"type": "string"},
|
|
"offset": {"type": "integer", "description": "1-basierte Startzeile, Default 1"},
|
|
"limit": {"type": "integer", "description": "maximale Zeilenzahl, Default 2000"}
|
|
},
|
|
"required": ["path"]
|
|
}`)
|
|
}
|
|
|
|
type readInput struct {
|
|
Path string `json:"path"`
|
|
Offset int `json:"offset"`
|
|
Limit int `json:"limit"`
|
|
}
|
|
|
|
func (ReadTool) Run(ctx context.Context, input json.RawMessage, env tool.Env) (tool.Result, error) {
|
|
var in readInput
|
|
if err := json.Unmarshal(input, &in); err != nil {
|
|
return tool.Result{ForModel: "invalid input: " + err.Error(), 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
|
|
}
|
|
if bytes.IndexByte(data, 0) != -1 {
|
|
return tool.Result{ForModel: fmt.Sprintf("%s: binary file, not readable as text", in.Path), IsError: true}, nil
|
|
}
|
|
|
|
offset := in.Offset
|
|
if offset < 1 {
|
|
offset = 1
|
|
}
|
|
limit := in.Limit
|
|
if limit <= 0 {
|
|
limit = defaultReadLimit
|
|
}
|
|
|
|
lines := strings.Split(string(data), "\n")
|
|
if offset > len(lines) {
|
|
return tool.Result{ForModel: fmt.Sprintf("%s: offset %d beyond file end (%d lines)", in.Path, offset, len(lines)), IsError: true}, nil
|
|
}
|
|
end := offset - 1 + limit
|
|
if end > len(lines) {
|
|
end = len(lines)
|
|
}
|
|
|
|
var b strings.Builder
|
|
for i := offset - 1; i < end; i++ {
|
|
fmt.Fprintf(&b, "%6d\t%s\n", i+1, lines[i])
|
|
}
|
|
truncated := end < len(lines)
|
|
|
|
return tool.Result{
|
|
ForModel: b.String(),
|
|
ForUI: FileResult{
|
|
Path: in.Path,
|
|
Lines: end - (offset - 1),
|
|
Truncated: truncated,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// FileResult ist die strukturierte UI-Repräsentation für read/write.
|
|
type FileResult struct {
|
|
Path string
|
|
Lines int
|
|
Truncated bool
|
|
}
|