Add modify_file tool
Removed file_insert_lines, file_replace_lines
This commit is contained in:
178
pkg/agents/toolbox/modify_file.go
Normal file
178
pkg/agents/toolbox/modify_file.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"git.mlow.ca/mlow/lmcli/pkg/agents/toolbox/util"
|
||||
toolutil "git.mlow.ca/mlow/lmcli/pkg/agents/toolbox/util"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/api"
|
||||
)
|
||||
|
||||
var MODIFY_FILE_DESCRIPTION = []string{
|
||||
"Modify a file. If the file does not exist, it will be created.",
|
||||
"",
|
||||
"Content can be either inserted, replaced, or removed through a combination of the start, stop, and content parameters.",
|
||||
"Use the start and stop line numbers to limit the range of modification to the file.",
|
||||
"If both `start` and `stop` are left unset (or set to 0), the entire file's contents will be updated.",
|
||||
"If `start` is set to n and `stop` to n+1, content will be inserted at line n (the content that was at line n will be shifted below the newly inserted content).",
|
||||
"If only `start` is set, content from the given line and onwards will be updated.",
|
||||
"If only `stop` is set, content up to but not including the given line will be updated.",
|
||||
"",
|
||||
"Examples:",
|
||||
"1. Append to a file:",
|
||||
" {\"path\": \"example.txt\", \"start\": <last_line_number + 1>, \"content\": \"New content to append\"}",
|
||||
"",
|
||||
"2. Insert at a specific line:",
|
||||
" {\"path\": \"example.txt\", \"start\": 5, \"stop\": 5, \"content\": \"New line inserted above the previous line 5\"}",
|
||||
"",
|
||||
"3. Replace a range of lines:",
|
||||
" {\"path\": \"example.txt\", \"start\": 3, \"stop\": 7, \"content\": \"New content replacing lines 3-7\"}",
|
||||
"",
|
||||
"4. Remove a range of lines:",
|
||||
" {\"path\": \"example.txt\", \"start\": 2, \"stop\": 5}",
|
||||
"",
|
||||
"5. Replace entire file contents:",
|
||||
" {\"path\": \"example.txt\", \"content\": \"New file contents\"}",
|
||||
"",
|
||||
"6. Update from a specific line to the end of the file:",
|
||||
" {\"path\": \"example.txt\", \"start\": 10, \"content\": \"New content from line 10 onwards\"}",
|
||||
"",
|
||||
"7. Update from the beginning of the file to a specific line:",
|
||||
" {\"path\": \"example.txt\", \"stop\": 6, \"content\": \"New content for first 5 lines\"}",
|
||||
"",
|
||||
"Note: Always use specific line numbers based on the current file content. Avoid using arbitrarily large numbers for start or stop.",
|
||||
}
|
||||
|
||||
var ModifyFile = api.ToolSpec{
|
||||
Name: "modify_file",
|
||||
Description: strings.Join(MODIFY_FILE_DESCRIPTION, "\n"),
|
||||
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",
|
||||
Type: "integer",
|
||||
Description: `Start line of the range to modify (inclusive). If omitted, the beginning of the file is implied.`,
|
||||
},
|
||||
{
|
||||
Name: "stop",
|
||||
Type: "integer",
|
||||
Description: `End line of the range to modify (inclusive). If omitted, the end of the file is implied.`,
|
||||
},
|
||||
{
|
||||
Name: "content",
|
||||
Type: "string",
|
||||
Description: "Content to insert/replace at the range defined by `start` and `stop`. If omitted, the range is removed.",
|
||||
},
|
||||
},
|
||||
Impl: func(tool *api.ToolSpec, args map[string]interface{}) (string, error) {
|
||||
tmp, ok := args["path"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("path parameter to modify_file was not included.")
|
||||
}
|
||||
path, ok := tmp.(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("Invalid path in function arguments: %v", tmp)
|
||||
}
|
||||
var start int
|
||||
tmp, ok = args["start"]
|
||||
if ok {
|
||||
tmp, ok := tmp.(float64)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("Invalid start in function arguments: %v", tmp)
|
||||
}
|
||||
start = int(tmp)
|
||||
}
|
||||
var stop int
|
||||
tmp, ok = args["stop"]
|
||||
if ok {
|
||||
tmp, ok := tmp.(float64)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("Invalid stop in function arguments: %v", tmp)
|
||||
}
|
||||
stop = 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 := fileModifyContents(path, start, stop, content)
|
||||
ret, err := result.ToJson()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Could not serialize result: %v", err)
|
||||
}
|
||||
return ret, nil
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
func fileModifyContents(path string, startLine int, stopLine 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{}
|
||||
}
|
||||
|
||||
lines := strings.Split(string(data), "\n")
|
||||
contentLines := strings.Split(strings.TrimSuffix(content, "\n"), "\n")
|
||||
|
||||
// If both start and stop are unset, update the entire file
|
||||
if startLine == 0 && stopLine == 0 {
|
||||
lines = contentLines
|
||||
} else {
|
||||
if startLine < 1 {
|
||||
startLine = 1
|
||||
}
|
||||
if stopLine == 0 || stopLine > len(lines) {
|
||||
stopLine = len(lines)
|
||||
}
|
||||
|
||||
before := lines[:startLine-1]
|
||||
after := lines[stopLine:]
|
||||
|
||||
// Handle insertion case
|
||||
if startLine == stopLine {
|
||||
lines = append(before, append(contentLines, lines[startLine-1:]...)...)
|
||||
} else {
|
||||
// If content is omitted, remove the specified range
|
||||
if content == "" {
|
||||
lines = append(before, after...)
|
||||
} else {
|
||||
lines = append(before, append(contentLines, after...)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newContent := strings.Join(lines, "\n")
|
||||
|
||||
// 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: util.AddLineNumbers(newContent)}
|
||||
}
|
||||
Reference in New Issue
Block a user