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 }