package tools import ( "context" "fmt" "os" "path/filepath" "strings" ) // EditFileTool edits a file by replacing old_text with new_text. // The old_text must exist exactly in the file. type EditFileTool struct { allowedDir string // Optional directory restriction for security } // NewEditFileTool creates a new EditFileTool with optional directory restriction. func NewEditFileTool(allowedDir string) *EditFileTool { return &EditFileTool{ allowedDir: allowedDir, } } func (t *EditFileTool) Name() string { return "edit_file" } func (t *EditFileTool) Description() string { return "Edit a file by replacing old_text with new_text. The old_text must exist exactly in the file." } func (t *EditFileTool) Parameters() map[string]interface{} { return map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "path": map[string]interface{}{ "type": "string", "description": "The file path to edit", }, "old_text": map[string]interface{}{ "type": "string", "description": "The exact text to find and replace", }, "new_text": map[string]interface{}{ "type": "string", "description": "The text to replace with", }, }, "required": []string{"path", "old_text", "new_text"}, } } func (t *EditFileTool) Execute(ctx context.Context, args map[string]interface{}) (string, error) { path, ok := args["path"].(string) if !ok { return "", fmt.Errorf("path is required") } oldText, ok := args["old_text"].(string) if !ok { return "", fmt.Errorf("old_text is required") } newText, ok := args["new_text"].(string) if !ok { return "", fmt.Errorf("new_text is required") } // Resolve path and enforce directory restriction if configured resolvedPath := path if filepath.IsAbs(path) { resolvedPath = filepath.Clean(path) } else { abs, err := filepath.Abs(path) if err != nil { return "", fmt.Errorf("failed to resolve path: %w", err) } resolvedPath = abs } // Check directory restriction if t.allowedDir != "" { allowedAbs, err := filepath.Abs(t.allowedDir) if err != nil { return "", fmt.Errorf("failed to resolve allowed directory: %w", err) } if !strings.HasPrefix(resolvedPath, allowedAbs) { return "", fmt.Errorf("path %s is outside allowed directory %s", path, t.allowedDir) } } if _, err := os.Stat(resolvedPath); os.IsNotExist(err) { return "", fmt.Errorf("file not found: %s", path) } content, err := os.ReadFile(resolvedPath) if err != nil { return "", fmt.Errorf("failed to read file: %w", err) } contentStr := string(content) if !strings.Contains(contentStr, oldText) { return "", fmt.Errorf("old_text not found in file. Make sure it matches exactly") } count := strings.Count(contentStr, oldText) if count > 1 { return "", fmt.Errorf("old_text appears %d times. Please provide more context to make it unique", count) } newContent := strings.Replace(contentStr, oldText, newText, 1) if err := os.WriteFile(resolvedPath, []byte(newContent), 0644); err != nil { return "", fmt.Errorf("failed to write file: %w", err) } return fmt.Sprintf("Successfully edited %s", path), nil } type AppendFileTool struct{} func NewAppendFileTool() *AppendFileTool { return &AppendFileTool{} } func (t *AppendFileTool) Name() string { return "append_file" } func (t *AppendFileTool) Description() string { return "Append content to the end of a file" } func (t *AppendFileTool) Parameters() map[string]interface{} { return map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "path": map[string]interface{}{ "type": "string", "description": "The file path to append to", }, "content": map[string]interface{}{ "type": "string", "description": "The content to append", }, }, "required": []string{"path", "content"}, } } func (t *AppendFileTool) Execute(ctx context.Context, args map[string]interface{}) (string, error) { path, ok := args["path"].(string) if !ok { return "", fmt.Errorf("path is required") } content, ok := args["content"].(string) if !ok { return "", fmt.Errorf("content is required") } filePath := filepath.Clean(path) f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return "", fmt.Errorf("failed to open file: %w", err) } defer f.Close() if _, err := f.WriteString(content); err != nil { return "", fmt.Errorf("failed to append to file: %w", err) } return fmt.Sprintf("Successfully appended to %s", path), nil }