package toolbox import ( "fmt" "os" "strings" toolutil "git.mlow.ca/mlow/lmcli/pkg/agents/toolbox/util" "git.mlow.ca/mlow/lmcli/pkg/api" ) const FILE_REPLACE_LINES_DESCRIPTION = `Replace or remove a range of lines within a file, must specify path. Useful for re-writing snippets/blocks of code or entire functions. Plan your edits carefully and ensure any new content matches the flow and indentation of surrounding text.` var FileReplaceLinesTool = api.ToolSpec{ Name: "file_replace_lines", Description: FILE_REPLACE_LINES_DESCRIPTION, Parameters: []api.ToolParameter{ { Name: "path", Type: "string", Description: "Path of the file to be modified, relative to the current working directory.", Required: true, }, { Name: "start_line", Type: "integer", Description: `Line number which specifies the start of the replacement range (inclusive).`, Required: true, }, { Name: "end_line", Type: "integer", Description: `Line number which specifies the end of the replacement range (inclusive). If unset, range extends to end of file.`, }, { Name: "content", Type: "string", Description: `Content to replace specified range. Omit to remove the specified range.`, }, }, Impl: func(tool *api.ToolSpec, args map[string]interface{}) (string, error) { tmp, ok := args["path"] if !ok { return "", fmt.Errorf("path parameter to write_file was not included.") } path, ok := tmp.(string) if !ok { return "", fmt.Errorf("Invalid path in function arguments: %v", tmp) } var start_line int tmp, ok = args["start_line"] if ok { tmp, ok := tmp.(float64) if !ok { return "", fmt.Errorf("Invalid start_line in function arguments: %v", tmp) } start_line = int(tmp) } var end_line int tmp, ok = args["end_line"] if ok { tmp, ok := tmp.(float64) if !ok { return "", fmt.Errorf("Invalid end_line in function arguments: %v", tmp) } end_line = int(tmp) } var content string tmp, ok = args["content"] if ok { content, ok = tmp.(string) if !ok { return "", fmt.Errorf("Invalid content in function arguments: %v", tmp) } } result := fileReplaceLines(path, start_line, end_line, content) ret, err := result.ToJson() if err != nil { return "", fmt.Errorf("Could not serialize result: %v", err) } return ret, nil }, } func fileReplaceLines(path string, startLine int, endLine int, content string) api.CallResult { ok, reason := toolutil.IsPathWithinCWD(path) if !ok { return api.CallResult{Message: reason} } // Read the existing file's content data, err := os.ReadFile(path) if err != nil { if !os.IsNotExist(err) { return api.CallResult{Message: fmt.Sprintf("Could not read path: %s", err.Error())} } _, err = os.Create(path) if err != nil { return api.CallResult{Message: fmt.Sprintf("Could not create new file: %s", err.Error())} } data = []byte{} } if startLine < 1 { return api.CallResult{Message: "start_line cannot be less than 1"} } lines := strings.Split(string(data), "\n") contentLines := strings.Split(strings.Trim(content, "\n"), "\n") if endLine == 0 || endLine > len(lines) { endLine = len(lines) } before := lines[:startLine-1] after := lines[endLine:] lines = append(before, append(contentLines, after...)...) newContent := strings.Join(lines, "\n") // Join the lines and write back to the file err = os.WriteFile(path, []byte(newContent), 0644) if err != nil { return api.CallResult{Message: fmt.Sprintf("Could not write to path: %s", err.Error())} } return api.CallResult{Result: newContent} }