package tools import ( "bufio" "context" "fmt" "os" "strings" llm "gitea.stevedudenhoeffer.com/steve/go-llm/v2" ) // ReadFileParams defines parameters for the read file tool. type ReadFileParams struct { Path string `json:"path" description:"File path to read"` Start *int `json:"start,omitempty" description:"Starting line number (1-based, inclusive)"` End *int `json:"end,omitempty" description:"Ending line number (1-based, inclusive)"` } // ReadFile creates a file reading tool. // // Example: // // tools := llm.NewToolBox(tools.ReadFile()) func ReadFile() llm.Tool { return llm.Define[ReadFileParams]("read_file", "Read the contents of a file", func(ctx context.Context, p ReadFileParams) (string, error) { f, err := os.Open(p.Path) if err != nil { return "", fmt.Errorf("opening file: %w", err) } defer f.Close() // If no line range specified, read the whole file (limited to 1MB) if p.Start == nil && p.End == nil { info, err := f.Stat() if err != nil { return "", fmt.Errorf("stat file: %w", err) } if info.Size() > 1<<20 { return "", fmt.Errorf("file too large (%d bytes), use start/end to read a range", info.Size()) } data, err := os.ReadFile(p.Path) if err != nil { return "", fmt.Errorf("reading file: %w", err) } return string(data), nil } // Read specific line range start := 1 end := -1 if p.Start != nil { start = *p.Start } if p.End != nil { end = *p.End } var lines []string scanner := bufio.NewScanner(f) lineNum := 0 for scanner.Scan() { lineNum++ if lineNum < start { continue } if end > 0 && lineNum > end { break } lines = append(lines, fmt.Sprintf("%d: %s", lineNum, scanner.Text())) } if err := scanner.Err(); err != nil { return "", fmt.Errorf("scanning file: %w", err) } return strings.Join(lines, "\n"), nil }, ) }