package file import ( "context" "fmt" "os" "path/filepath" "github.com/enmanuel/agents/internal/config" "github.com/enmanuel/agents/tools" ) // NewDeleteFile creates a delete_file tool that deletes a single file. // Deny-by-default: if AllowedPaths is empty, all operations are rejected. // Rejects if ReadOnly is true. Only deletes files, never directories. // Resolves symlinks before deleting to prevent escaping allowed paths. func NewDeleteFile(cfg config.FileOpsCfg) tools.Tool { return tools.Tool{ Def: tools.Def{ Name: "delete_file", Description: "Delete a single file. Cannot delete directories.", Parameters: []tools.Param{ {Name: "path", Type: "string", Description: "Absolute path to the file to delete", 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("delete_file: path is required")} } absPath, err := filepath.Abs(path) if err != nil { return tools.Result{Err: fmt.Errorf("delete_file: %w", err)} } if err := validateWritePath(absPath, cfg.AllowedPaths, cfg.ReadOnly); err != nil { return tools.Result{Err: err} } // Stat the file to ensure it exists and is not a directory. info, err := os.Stat(absPath) if err != nil { return tools.Result{Err: fmt.Errorf("delete_file: %w", err)} } if info.IsDir() { return tools.Result{Err: fmt.Errorf("delete_file: %q is a directory, only files can be deleted", absPath)} } if err := os.Remove(absPath); err != nil { return tools.Result{Err: fmt.Errorf("delete_file: %w", err)} } return tools.Result{Output: fmt.Sprintf("deleted %s", absPath)} }, } }