Add Github reviewer and improve reasoning
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mudler/LocalAGI/core/action"
|
||||
"github.com/mudler/LocalAGI/core/types"
|
||||
@@ -358,7 +359,7 @@ func (a *Agent) prepareHUD() (promptHUD *PromptHUD) {
|
||||
func (a *Agent) pickAction(ctx context.Context, templ string, messages []openai.ChatCompletionMessage, maxRetries int) (types.Action, types.ActionParams, string, error) {
|
||||
c := messages
|
||||
|
||||
xlog.Debug("picking action", "messages", messages)
|
||||
xlog.Debug("[pickAction] picking action", "messages", messages)
|
||||
|
||||
if !a.options.forceReasoning {
|
||||
xlog.Debug("not forcing reasoning")
|
||||
@@ -389,7 +390,7 @@ func (a *Agent) pickAction(ctx context.Context, templ string, messages []openai.
|
||||
return chosenAction, thought.actionParams, thought.message, nil
|
||||
}
|
||||
|
||||
xlog.Debug("forcing reasoning")
|
||||
xlog.Debug("[pickAction] forcing reasoning")
|
||||
|
||||
prompt, err := renderTemplate(templ, a.prepareHUD(), a.availableActions(), "")
|
||||
if err != nil {
|
||||
@@ -406,44 +407,77 @@ func (a *Agent) pickAction(ctx context.Context, templ string, messages []openai.
|
||||
}, c...)
|
||||
}
|
||||
|
||||
// We also could avoid to use functions here and get just a reply from the LLM
|
||||
// and then use the reply to get the action
|
||||
thought, err := a.decision(ctx,
|
||||
c,
|
||||
types.Actions{action.NewReasoning()}.ToTools(),
|
||||
action.NewReasoning().Definition().Name, maxRetries)
|
||||
if err != nil {
|
||||
return nil, nil, "", err
|
||||
}
|
||||
reason := ""
|
||||
response := &action.ReasoningResponse{}
|
||||
if thought.actionParams != nil {
|
||||
if err := thought.actionParams.Unmarshal(response); err != nil {
|
||||
return nil, nil, "", err
|
||||
}
|
||||
reason = response.Reasoning
|
||||
}
|
||||
if thought.message != "" {
|
||||
reason = thought.message
|
||||
}
|
||||
|
||||
xlog.Debug("thought", "reason", reason)
|
||||
|
||||
// From the thought, get the action call
|
||||
// Get all the available actions IDs
|
||||
actionsID := []string{}
|
||||
for _, m := range a.availableActions() {
|
||||
actionsID = append(actionsID, m.Definition().Name.String())
|
||||
}
|
||||
intentionsTools := action.NewIntention(actionsID...)
|
||||
|
||||
thoughtPromptStringBuilder := strings.Builder{}
|
||||
thoughtPromptStringBuilder.WriteString("You have to pick an action based on the conversation and the prompt. Describe the full reasoning process for your choice. Here is a list of actions: ")
|
||||
for _, m := range a.availableActions() {
|
||||
thoughtPromptStringBuilder.WriteString(
|
||||
m.Definition().Name.String() + ": " + m.Definition().Description + "\n",
|
||||
)
|
||||
}
|
||||
|
||||
thoughtPromptStringBuilder.WriteString("To not use any action, respond with 'none'")
|
||||
|
||||
//thoughtPromptStringBuilder.WriteString("\n\nConversation: " + Messages(c).String())
|
||||
|
||||
thoughtPrompt := thoughtPromptStringBuilder.String()
|
||||
|
||||
xlog.Debug("[pickAction] thought", "prompt", thoughtPrompt)
|
||||
|
||||
thought, err := a.askLLM(ctx,
|
||||
append(c, openai.ChatCompletionMessage{
|
||||
Role: "user",
|
||||
Content: thoughtPrompt,
|
||||
}),
|
||||
maxRetries,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, "", err
|
||||
}
|
||||
originalReasoning := thought.Content
|
||||
|
||||
xlog.Debug("[pickAction] thought", "reason", originalReasoning)
|
||||
|
||||
thought, err = a.askLLM(ctx,
|
||||
[]openai.ChatCompletionMessage{
|
||||
{
|
||||
Role: "system",
|
||||
Content: "Your only objective is to return the string of the action to take based on the user input",
|
||||
},
|
||||
{
|
||||
Role: "user",
|
||||
Content: "Given the following sentence, answer with the only action to take: " + originalReasoning,
|
||||
}},
|
||||
maxRetries,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, "", err
|
||||
}
|
||||
reason := thought.Content
|
||||
|
||||
xlog.Debug("[pickAction] filtered thought", "reason", reason)
|
||||
|
||||
// From the thought, get the action call
|
||||
// Get all the available actions IDs
|
||||
|
||||
intentionsTools := action.NewIntention(append(actionsID, "none")...)
|
||||
|
||||
// NOTE: we do not give the full conversation here to pick the action
|
||||
// to avoid hallucinations
|
||||
params, err := a.decision(ctx,
|
||||
[]openai.ChatCompletionMessage{{
|
||||
Role: "assistant",
|
||||
Content: reason,
|
||||
},
|
||||
[]openai.ChatCompletionMessage{
|
||||
{
|
||||
Role: "system",
|
||||
Content: "You have to pick the correct action based on the user reasoning",
|
||||
},
|
||||
{
|
||||
Role: "user",
|
||||
Content: reason,
|
||||
},
|
||||
},
|
||||
types.Actions{intentionsTools}.ToTools(),
|
||||
intentionsTools.Definition().Name, maxRetries)
|
||||
@@ -463,7 +497,7 @@ func (a *Agent) pickAction(ctx context.Context, templ string, messages []openai.
|
||||
}
|
||||
|
||||
if actionChoice.Tool == "" || actionChoice.Tool == "none" {
|
||||
return nil, nil, "", fmt.Errorf("no intent detected")
|
||||
return nil, nil, "", nil
|
||||
}
|
||||
|
||||
// Find the action
|
||||
@@ -472,5 +506,5 @@ func (a *Agent) pickAction(ctx context.Context, templ string, messages []openai.
|
||||
return nil, nil, "", fmt.Errorf("no action found for intent:" + actionChoice.Tool)
|
||||
}
|
||||
|
||||
return chosenAction, nil, actionChoice.Reasoning, nil
|
||||
return chosenAction, nil, originalReasoning, nil
|
||||
}
|
||||
|
||||
@@ -670,6 +670,7 @@ func (a *Agent) consumeJob(job *types.Job, role string) {
|
||||
!chosenAction.Definition().Name.Is(action.ReplyActionName) {
|
||||
|
||||
xlog.Info("Following action", "action", followingAction.Definition().Name, "agent", a.Character.Name)
|
||||
job.ConversationHistory = conv
|
||||
|
||||
// We need to do another action (?)
|
||||
// The agent decided to do another action
|
||||
|
||||
@@ -28,6 +28,7 @@ const (
|
||||
ActionGithubIssueCommenter = "github-issue-commenter"
|
||||
ActionGithubPRReader = "github-pr-reader"
|
||||
ActionGithubPRCommenter = "github-pr-commenter"
|
||||
ActionGithubPRReviewer = "github-pr-reviewer"
|
||||
ActionGithubREADME = "github-readme"
|
||||
ActionScraper = "scraper"
|
||||
ActionWikipedia = "wikipedia"
|
||||
@@ -53,6 +54,7 @@ var AvailableActions = []string{
|
||||
ActionGithubIssueCommenter,
|
||||
ActionGithubPRReader,
|
||||
ActionGithubPRCommenter,
|
||||
ActionGithubPRReviewer,
|
||||
ActionGithubREADME,
|
||||
ActionScraper,
|
||||
ActionBrowse,
|
||||
@@ -114,6 +116,8 @@ func Action(name, agentName string, config map[string]string, pool *state.AgentP
|
||||
a = actions.NewGithubPRReader(config)
|
||||
case ActionGithubPRCommenter:
|
||||
a = actions.NewGithubPRCommenter(config)
|
||||
case ActionGithubPRReviewer:
|
||||
a = actions.NewGithubPRReviewer(config)
|
||||
case ActionGithubIssueCommenter:
|
||||
a = actions.NewGithubIssueCommenter(config)
|
||||
case ActionGithubRepositoryGet:
|
||||
@@ -217,6 +221,11 @@ func ActionsConfigMeta() []config.FieldGroup {
|
||||
Label: "GitHub PR Commenter",
|
||||
Fields: actions.GithubPRCommenterConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-pr-reviewer",
|
||||
Label: "GitHub PR Reviewer",
|
||||
Fields: actions.GithubPRReviewerConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "twitter-post",
|
||||
Label: "Twitter Post",
|
||||
|
||||
@@ -3,11 +3,8 @@ package actions
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-github/v69/github"
|
||||
"github.com/mudler/LocalAGI/core/types"
|
||||
@@ -124,16 +121,10 @@ func NewGithubPRCommenter(config map[string]string) *GithubPRCommenter {
|
||||
|
||||
func (g *GithubPRCommenter) Run(ctx context.Context, params types.ActionParams) (types.ActionResult, error) {
|
||||
result := struct {
|
||||
Repository string `json:"repository"`
|
||||
Owner string `json:"owner"`
|
||||
PRNumber int `json:"pr_number"`
|
||||
GeneralComment string `json:"general_comment"`
|
||||
Comments []struct {
|
||||
File string `json:"file"`
|
||||
Line int `json:"line"`
|
||||
Comment string `json:"comment"`
|
||||
StartLine int `json:"start_line,omitempty"`
|
||||
} `json:"comments"`
|
||||
Repository string `json:"repository"`
|
||||
Owner string `json:"owner"`
|
||||
PRNumber int `json:"pr_number"`
|
||||
Comment string `json:"comment"`
|
||||
}{}
|
||||
err := params.Unmarshal(&result)
|
||||
if err != nil {
|
||||
@@ -159,134 +150,31 @@ func (g *GithubPRCommenter) Run(ctx context.Context, params types.ActionParams)
|
||||
return types.ActionResult{Result: fmt.Sprintf("Pull request #%d is not open (current state: %s)", result.PRNumber, *pr.State)}, nil
|
||||
}
|
||||
|
||||
// Get the list of changed files to verify the files exist in the PR
|
||||
files, _, err := g.client.PullRequests.ListFiles(ctx, result.Owner, result.Repository, result.PRNumber, &github.ListOptions{})
|
||||
if err != nil {
|
||||
return types.ActionResult{}, fmt.Errorf("failed to list PR files: %w", err)
|
||||
if result.Comment == "" {
|
||||
return types.ActionResult{Result: "No comment provided"}, nil
|
||||
}
|
||||
|
||||
// Create a map of valid files with their commit info
|
||||
validFiles := make(map[string]*commitFileInfo)
|
||||
for _, file := range files {
|
||||
if *file.Status != "deleted" {
|
||||
info, err := getCommitInfo(file)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
validFiles[*file.Filename] = info
|
||||
}
|
||||
}
|
||||
// Try both PullRequests and Issues API for general comments
|
||||
var resp *github.Response
|
||||
|
||||
// Process each comment
|
||||
var results []string
|
||||
for _, comment := range result.Comments {
|
||||
// Check if file exists in PR
|
||||
fileInfo, exists := validFiles[comment.File]
|
||||
if !exists {
|
||||
availableFiles := make([]string, 0, len(validFiles))
|
||||
for f := range validFiles {
|
||||
availableFiles = append(availableFiles, f)
|
||||
}
|
||||
results = append(results, fmt.Sprintf("Error: File %s not found in PR #%d. Available files: %v",
|
||||
comment.File, result.PRNumber, availableFiles))
|
||||
continue
|
||||
}
|
||||
// First try PullRequests API
|
||||
_, resp, err = g.client.PullRequests.CreateComment(ctx, result.Owner, result.Repository, result.PRNumber, &github.PullRequestComment{
|
||||
Body: &result.Comment,
|
||||
})
|
||||
|
||||
// Check if line is in a changed hunk
|
||||
if !fileInfo.isLineInChange(comment.Line) {
|
||||
results = append(results, fmt.Sprintf("Error: Line %d is not in a changed hunk in file %s",
|
||||
comment.Line, comment.File))
|
||||
continue
|
||||
}
|
||||
|
||||
// Calculate position
|
||||
position := fileInfo.calculatePosition(comment.Line)
|
||||
if position == nil {
|
||||
results = append(results, fmt.Sprintf("Error: Could not calculate position for line %d in file %s",
|
||||
comment.Line, comment.File))
|
||||
continue
|
||||
}
|
||||
|
||||
// Create the review comment
|
||||
reviewComment := &github.PullRequestComment{
|
||||
Path: &comment.File,
|
||||
Line: &comment.Line,
|
||||
Body: &comment.Comment,
|
||||
Position: position,
|
||||
CommitID: &fileInfo.sha,
|
||||
}
|
||||
|
||||
// Set start line if provided
|
||||
if comment.StartLine > 0 {
|
||||
reviewComment.StartLine = &comment.StartLine
|
||||
}
|
||||
|
||||
// Create the comment with retries
|
||||
var resp *github.Response
|
||||
for i := 0; i < 6; i++ {
|
||||
_, resp, err = g.client.PullRequests.CreateComment(ctx, result.Owner, result.Repository, result.PRNumber, reviewComment)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if resp != nil && resp.StatusCode == 422 {
|
||||
// Rate limit hit, wait and retry
|
||||
retrySeconds := i * i
|
||||
time.Sleep(time.Second * time.Duration(retrySeconds))
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errorDetails := fmt.Sprintf("Error commenting on file %s, line %d: %s", comment.File, comment.Line, err.Error())
|
||||
if resp != nil {
|
||||
errorDetails += fmt.Sprintf("\nResponse Status: %s", resp.Status)
|
||||
if resp.Body != nil {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
errorDetails += fmt.Sprintf("\nResponse Body: %s", string(body))
|
||||
}
|
||||
}
|
||||
results = append(results, errorDetails)
|
||||
continue
|
||||
}
|
||||
|
||||
results = append(results, fmt.Sprintf("Successfully commented on file %s, line %d", comment.File, comment.Line))
|
||||
}
|
||||
|
||||
if result.GeneralComment != "" {
|
||||
// Try both PullRequests and Issues API for general comments
|
||||
var resp *github.Response
|
||||
var err error
|
||||
|
||||
// First try PullRequests API
|
||||
_, resp, err = g.client.PullRequests.CreateComment(ctx, result.Owner, result.Repository, result.PRNumber, &github.PullRequestComment{
|
||||
Body: &result.GeneralComment,
|
||||
// If that fails with 403, try Issues API
|
||||
if err != nil && resp != nil && resp.StatusCode == 403 {
|
||||
_, resp, err = g.client.Issues.CreateComment(ctx, result.Owner, result.Repository, result.PRNumber, &github.IssueComment{
|
||||
Body: &result.Comment,
|
||||
})
|
||||
}
|
||||
|
||||
// If that fails with 403, try Issues API
|
||||
if err != nil && resp != nil && resp.StatusCode == 403 {
|
||||
_, resp, err = g.client.Issues.CreateComment(ctx, result.Owner, result.Repository, result.PRNumber, &github.IssueComment{
|
||||
Body: &result.GeneralComment,
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errorDetails := fmt.Sprintf("Error adding general comment: %s", err.Error())
|
||||
if resp != nil {
|
||||
errorDetails += fmt.Sprintf("\nResponse Status: %s", resp.Status)
|
||||
if resp.Body != nil {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
errorDetails += fmt.Sprintf("\nResponse Body: %s", string(body))
|
||||
}
|
||||
}
|
||||
results = append(results, errorDetails)
|
||||
} else {
|
||||
results = append(results, "Successfully added general comment to pull request")
|
||||
}
|
||||
if err != nil {
|
||||
return types.ActionResult{Result: fmt.Sprintf("Error adding general comment: %s", err.Error())}, nil
|
||||
}
|
||||
|
||||
return types.ActionResult{
|
||||
Result: strings.Join(results, "\n"),
|
||||
Result: "Successfully added general comment to pull request",
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -305,38 +193,12 @@ func (g *GithubPRCommenter) Definition() types.ActionDefinition {
|
||||
Type: jsonschema.Number,
|
||||
Description: "The number of the pull request to comment on.",
|
||||
},
|
||||
"general_comment": {
|
||||
"comment": {
|
||||
Type: jsonschema.String,
|
||||
Description: "A general comment to add to the pull request.",
|
||||
},
|
||||
"comments": {
|
||||
Type: jsonschema.Array,
|
||||
Items: &jsonschema.Definition{
|
||||
Type: jsonschema.Object,
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"file": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The file to comment on.",
|
||||
},
|
||||
"line": {
|
||||
Type: jsonschema.Number,
|
||||
Description: "The line number to comment on.",
|
||||
},
|
||||
"comment": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The comment text.",
|
||||
},
|
||||
"start_line": {
|
||||
Type: jsonschema.Number,
|
||||
Description: "Optional start line for multi-line comments.",
|
||||
},
|
||||
},
|
||||
Required: []string{"file", "line", "comment"},
|
||||
},
|
||||
Description: "Array of comments to add to the pull request.",
|
||||
},
|
||||
},
|
||||
Required: []string{"pr_number", "comments"},
|
||||
Required: []string{"pr_number", "comment"},
|
||||
}
|
||||
}
|
||||
return types.ActionDefinition{
|
||||
@@ -355,38 +217,12 @@ func (g *GithubPRCommenter) Definition() types.ActionDefinition {
|
||||
Type: jsonschema.String,
|
||||
Description: "The owner of the repository.",
|
||||
},
|
||||
"general_comment": {
|
||||
"comment": {
|
||||
Type: jsonschema.String,
|
||||
Description: "A general comment to add to the pull request.",
|
||||
},
|
||||
"comments": {
|
||||
Type: jsonschema.Array,
|
||||
Items: &jsonschema.Definition{
|
||||
Type: jsonschema.Object,
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"file": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The file to comment on.",
|
||||
},
|
||||
"line": {
|
||||
Type: jsonschema.Number,
|
||||
Description: "The line number to comment on.",
|
||||
},
|
||||
"comment": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The comment text.",
|
||||
},
|
||||
"start_line": {
|
||||
Type: jsonschema.Number,
|
||||
Description: "Optional start line for multi-line comments.",
|
||||
},
|
||||
},
|
||||
Required: []string{"file", "line", "comment"},
|
||||
},
|
||||
Description: "Array of comments to add to the pull request.",
|
||||
},
|
||||
},
|
||||
Required: []string{"pr_number", "repository", "owner", "comments"},
|
||||
Required: []string{"pr_number", "repository", "owner", "comment"},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
286
services/actions/githubprreviewer.go
Normal file
286
services/actions/githubprreviewer.go
Normal file
@@ -0,0 +1,286 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-github/v69/github"
|
||||
"github.com/mudler/LocalAGI/core/types"
|
||||
"github.com/mudler/LocalAGI/pkg/config"
|
||||
"github.com/mudler/LocalAGI/pkg/xlog"
|
||||
"github.com/sashabaranov/go-openai/jsonschema"
|
||||
)
|
||||
|
||||
type GithubPRReviewer struct {
|
||||
token, repository, owner, customActionName string
|
||||
client *github.Client
|
||||
}
|
||||
|
||||
func NewGithubPRReviewer(config map[string]string) *GithubPRReviewer {
|
||||
client := github.NewClient(nil).WithAuthToken(config["token"])
|
||||
|
||||
return &GithubPRReviewer{
|
||||
client: client,
|
||||
token: config["token"],
|
||||
customActionName: config["customActionName"],
|
||||
repository: config["repository"],
|
||||
owner: config["owner"],
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GithubPRReviewer) Run(ctx context.Context, params types.ActionParams) (types.ActionResult, error) {
|
||||
result := struct {
|
||||
Repository string `json:"repository"`
|
||||
Owner string `json:"owner"`
|
||||
PRNumber int `json:"pr_number"`
|
||||
ReviewComment string `json:"review_comment"`
|
||||
ReviewAction string `json:"review_action"` // APPROVE, REQUEST_CHANGES, or COMMENT
|
||||
Comments []struct {
|
||||
File string `json:"file"`
|
||||
Line int `json:"line"`
|
||||
Comment string `json:"comment"`
|
||||
StartLine int `json:"start_line,omitempty"`
|
||||
} `json:"comments"`
|
||||
}{}
|
||||
err := params.Unmarshal(&result)
|
||||
if err != nil {
|
||||
return types.ActionResult{}, fmt.Errorf("failed to unmarshal params: %w", err)
|
||||
}
|
||||
|
||||
if g.repository != "" && g.owner != "" {
|
||||
result.Repository = g.repository
|
||||
result.Owner = g.owner
|
||||
}
|
||||
|
||||
// First verify the PR exists and is in a valid state
|
||||
pr, _, err := g.client.PullRequests.Get(ctx, result.Owner, result.Repository, result.PRNumber)
|
||||
if err != nil {
|
||||
return types.ActionResult{}, fmt.Errorf("failed to fetch PR #%d: %w", result.PRNumber, err)
|
||||
}
|
||||
if pr == nil {
|
||||
return types.ActionResult{Result: fmt.Sprintf("Pull request #%d not found in repository %s/%s", result.PRNumber, result.Owner, result.Repository)}, nil
|
||||
}
|
||||
|
||||
// Check if PR is in a state that allows reviews
|
||||
if *pr.State != "open" {
|
||||
return types.ActionResult{Result: fmt.Sprintf("Pull request #%d is not open (current state: %s)", result.PRNumber, *pr.State)}, nil
|
||||
}
|
||||
|
||||
// Get the list of changed files to verify the files exist in the PR
|
||||
files, _, err := g.client.PullRequests.ListFiles(ctx, result.Owner, result.Repository, result.PRNumber, &github.ListOptions{})
|
||||
if err != nil {
|
||||
return types.ActionResult{}, fmt.Errorf("failed to list PR files: %w", err)
|
||||
}
|
||||
|
||||
// Create a map of valid files
|
||||
validFiles := make(map[string]bool)
|
||||
for _, file := range files {
|
||||
if *file.Status != "deleted" {
|
||||
validFiles[*file.Filename] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Process each comment
|
||||
var reviewComments []*github.DraftReviewComment
|
||||
for _, comment := range result.Comments {
|
||||
// Check if file exists in PR
|
||||
if !validFiles[comment.File] {
|
||||
continue
|
||||
}
|
||||
|
||||
reviewComment := &github.DraftReviewComment{
|
||||
Path: &comment.File,
|
||||
Line: &comment.Line,
|
||||
Body: &comment.Comment,
|
||||
}
|
||||
|
||||
// Set start line if provided
|
||||
if comment.StartLine > 0 {
|
||||
reviewComment.StartLine = &comment.StartLine
|
||||
}
|
||||
|
||||
reviewComments = append(reviewComments, reviewComment)
|
||||
}
|
||||
|
||||
// Create the review
|
||||
review := &github.PullRequestReviewRequest{
|
||||
Event: &result.ReviewAction,
|
||||
Body: &result.ReviewComment,
|
||||
Comments: reviewComments,
|
||||
}
|
||||
|
||||
xlog.Debug("[githubprreviewer] review", "review", review)
|
||||
|
||||
// Submit the review
|
||||
_, resp, err := g.client.PullRequests.CreateReview(ctx, result.Owner, result.Repository, result.PRNumber, review)
|
||||
if err != nil {
|
||||
errorDetails := fmt.Sprintf("Error submitting review: %s", err.Error())
|
||||
if resp != nil {
|
||||
errorDetails += fmt.Sprintf("\nResponse Status: %s", resp.Status)
|
||||
if resp.Body != nil {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
errorDetails += fmt.Sprintf("\nResponse Body: %s", string(body))
|
||||
}
|
||||
}
|
||||
return types.ActionResult{Result: errorDetails}, err
|
||||
}
|
||||
|
||||
actionResult := strings.Builder{}
|
||||
|
||||
actionResult.WriteString("========================== PR Reviewer Result ==========================\n")
|
||||
actionResult.WriteString(fmt.Sprintf("Pull request https://github.com/%s/%s/pull/%d", result.Owner, result.Repository, result.PRNumber))
|
||||
actionResult.WriteString("\n")
|
||||
actionResult.WriteString(fmt.Sprintf("Successfully reviewed with status: %s", strings.ToLower(result.ReviewAction)))
|
||||
actionResult.WriteString("\n")
|
||||
|
||||
return types.ActionResult{Result: actionResult.String()}, nil
|
||||
}
|
||||
|
||||
func (g *GithubPRReviewer) Definition() types.ActionDefinition {
|
||||
actionName := "review_github_pr"
|
||||
if g.customActionName != "" {
|
||||
actionName = g.customActionName
|
||||
}
|
||||
description := "Review a GitHub pull request by approving, requesting changes, or commenting."
|
||||
if g.repository != "" && g.owner != "" {
|
||||
return types.ActionDefinition{
|
||||
Name: types.ActionDefinitionName(actionName),
|
||||
Description: description,
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"pr_number": {
|
||||
Type: jsonschema.Number,
|
||||
Description: "The number of the pull request to review.",
|
||||
},
|
||||
"review_comment": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The main review comment to add to the pull request.",
|
||||
},
|
||||
"review_action": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The type of review to submit (APPROVE, REQUEST_CHANGES, or COMMENT).",
|
||||
Enum: []string{"APPROVE", "REQUEST_CHANGES", "COMMENT"},
|
||||
},
|
||||
"comments": {
|
||||
Type: jsonschema.Array,
|
||||
Items: &jsonschema.Definition{
|
||||
Type: jsonschema.Object,
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"file": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The file to comment on.",
|
||||
},
|
||||
"line": {
|
||||
Type: jsonschema.Number,
|
||||
Description: "The line number to comment on.",
|
||||
},
|
||||
"comment": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The comment text.",
|
||||
},
|
||||
"start_line": {
|
||||
Type: jsonschema.Number,
|
||||
Description: "Optional start line for multi-line comments.",
|
||||
},
|
||||
},
|
||||
Required: []string{"file", "line", "comment"},
|
||||
},
|
||||
Description: "Array of line-specific comments to add to the review.",
|
||||
},
|
||||
},
|
||||
Required: []string{"pr_number", "review_action"},
|
||||
}
|
||||
}
|
||||
return types.ActionDefinition{
|
||||
Name: types.ActionDefinitionName(actionName),
|
||||
Description: description,
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"pr_number": {
|
||||
Type: jsonschema.Number,
|
||||
Description: "The number of the pull request to review.",
|
||||
},
|
||||
"repository": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The repository containing the pull request.",
|
||||
},
|
||||
"owner": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The owner of the repository.",
|
||||
},
|
||||
"review_comment": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The main review comment to add to the pull request.",
|
||||
},
|
||||
"review_action": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The type of review to submit (APPROVE, REQUEST_CHANGES, or COMMENT).",
|
||||
Enum: []string{"APPROVE", "REQUEST_CHANGES", "COMMENT"},
|
||||
},
|
||||
"comments": {
|
||||
Type: jsonschema.Array,
|
||||
Items: &jsonschema.Definition{
|
||||
Type: jsonschema.Object,
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"file": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The file to comment on.",
|
||||
},
|
||||
"line": {
|
||||
Type: jsonschema.Number,
|
||||
Description: "The line number to comment on.",
|
||||
},
|
||||
"comment": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The comment text.",
|
||||
},
|
||||
"start_line": {
|
||||
Type: jsonschema.Number,
|
||||
Description: "Optional start line for multi-line comments.",
|
||||
},
|
||||
},
|
||||
Required: []string{"file", "line", "comment"},
|
||||
},
|
||||
Description: "Array of line-specific comments to add to the review.",
|
||||
},
|
||||
},
|
||||
Required: []string{"pr_number", "repository", "owner", "review_action"},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *GithubPRReviewer) Plannable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GithubPRReviewerConfigMeta returns the metadata for GitHub PR Reviewer action configuration fields
|
||||
func GithubPRReviewerConfigMeta() []config.Field {
|
||||
return []config.Field{
|
||||
{
|
||||
Name: "token",
|
||||
Label: "GitHub Token",
|
||||
Type: config.FieldTypeText,
|
||||
Required: true,
|
||||
HelpText: "GitHub API token with repository access",
|
||||
},
|
||||
{
|
||||
Name: "repository",
|
||||
Label: "Repository",
|
||||
Type: config.FieldTypeText,
|
||||
Required: false,
|
||||
HelpText: "GitHub repository name",
|
||||
},
|
||||
{
|
||||
Name: "owner",
|
||||
Label: "Owner",
|
||||
Type: config.FieldTypeText,
|
||||
Required: false,
|
||||
HelpText: "GitHub repository owner",
|
||||
},
|
||||
{
|
||||
Name: "customActionName",
|
||||
Label: "Custom Action Name",
|
||||
Type: config.FieldTypeText,
|
||||
HelpText: "Custom name for this action",
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user