package file import ( "context" "fmt" "os" "path/filepath" "github.com/enmanuel/agents/internal/config" "github.com/enmanuel/agents/tools" ) // maxAppendTotal is the maximum total file size after appending (10 MB). const maxAppendTotal = 10 * 1024 * 1024 // NewAppendFile creates an append_file tool that appends content to a local file. // Deny-by-default: if AllowedPaths is empty, all operations are rejected. // Rejects if ReadOnly is true. Creates the file (and parent directories) if it does not exist. func NewAppendFile(cfg config.FileOpsCfg) tools.Tool { return tools.Tool{ Def: tools.Def{ Name: "append_file", Description: "Append content to the end of a local file. Creates the file if it does not exist.", Parameters: []tools.Param{ {Name: "path", Type: "string", Description: "Absolute path to the file to append to", Required: true}, {Name: "content", Type: "string", Description: "Content to append to the file", Required: true}, }, }, Exec: func(ctx context.Context, args map[string]any) tools.Result { path := tools.GetString(args, "path") if path == "" { return tools.Result{Err: fmt.Errorf("append_file: path is required")} } content := tools.GetString(args, "content") if content == "" { return tools.Result{Err: fmt.Errorf("append_file: content is required")} } absPath, err := filepath.Abs(path) if err != nil { return tools.Result{Err: fmt.Errorf("append_file: %w", err)} } if err := validateWritePath(absPath, cfg.AllowedPaths, cfg.ReadOnly); err != nil { return tools.Result{Err: err} } // Check existing file size to enforce the total limit. var existingSize int64 info, err := os.Stat(absPath) if err == nil { existingSize = info.Size() } // err != nil means file doesn't exist, which is fine (will be created). newTotal := existingSize + int64(len(content)) if newTotal > maxAppendTotal { return tools.Result{Err: fmt.Errorf("append_file: resulting file size (%d bytes) exceeds 10 MB limit", newTotal)} } // Create parent directories if they don't exist. dir := filepath.Dir(absPath) if err := os.MkdirAll(dir, 0755); err != nil { return tools.Result{Err: fmt.Errorf("append_file: cannot create directories: %w", err)} } f, err := os.OpenFile(absPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return tools.Result{Err: fmt.Errorf("append_file: %w", err)} } defer f.Close() n, err := f.WriteString(content) if err != nil { return tools.Result{Err: fmt.Errorf("append_file: %w", err)} } finalSize := existingSize + int64(n) return tools.Result{Output: fmt.Sprintf("appended %d bytes to %s (total size: %d bytes)", n, absPath, finalSize)} }, } }