diff --git a/README.md b/README.md index 27262326..0728bbce 100644 --- a/README.md +++ b/README.md @@ -24,25 +24,25 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review PRs
@@ -132,7 +132,7 @@ Here are several ways to install and run PR-Agent:
## How it works
-
+
Check out the [PR Compression strategy](./PR_COMPRESSION.md) page for more details on how we convert a code diff to a manageable LLM prompt
diff --git a/pr_agent/algo/utils.py b/pr_agent/algo/utils.py
index 1d85b1d6..0803f874 100644
--- a/pr_agent/algo/utils.py
+++ b/pr_agent/algo/utils.py
@@ -21,6 +21,7 @@ def convert_to_markdown(output_data: dict) -> str:
"Focused PR": "✨",
"Security concerns": "🔒",
"General PR suggestions": "💡",
+ "Insights from user's answers": "📝",
"Code suggestions": "🤖"
}
diff --git a/pr_agent/git_providers/bitbucket_provider.py b/pr_agent/git_providers/bitbucket_provider.py
index 1f3f9d6e..1470ce78 100644
--- a/pr_agent/git_providers/bitbucket_provider.py
+++ b/pr_agent/git_providers/bitbucket_provider.py
@@ -27,7 +27,7 @@ class BitbucketProvider:
self.set_pr(pr_url)
def is_supported(self, capability: str) -> bool:
- if capability == 'get_issue_comments':
+ if capability in ['get_issue_comments', 'create_inline_comment', 'publish_inline_comments']:
return False
return True
@@ -65,6 +65,12 @@ class BitbucketProvider:
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
pass
+ def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
+ raise NotImplementedError("Bitbucket provider does not support creating inline comments yet")
+
+ def publish_inline_comments(self, comments: list[dict]):
+ raise NotImplementedError("Bitbucket provider does not support publishing inline comments yet")
+
def get_title(self):
return self.pr.title
diff --git a/pr_agent/git_providers/git_provider.py b/pr_agent/git_providers/git_provider.py
index 55d2bf34..f7c7fa98 100644
--- a/pr_agent/git_providers/git_provider.py
+++ b/pr_agent/git_providers/git_provider.py
@@ -44,6 +44,14 @@ class GitProvider(ABC):
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
pass
+ @abstractmethod
+ def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
+ pass
+
+ @abstractmethod
+ def publish_inline_comments(self, comments: list[dict]):
+ pass
+
@abstractmethod
def publish_code_suggestion(self, body: str, relevant_file: str,
relevant_lines_start: int, relevant_lines_end: int):
diff --git a/pr_agent/git_providers/github_provider.py b/pr_agent/git_providers/github_provider.py
index c125b2d3..415daa06 100644
--- a/pr_agent/git_providers/github_provider.py
+++ b/pr_agent/git_providers/github_provider.py
@@ -3,7 +3,7 @@ from datetime import datetime
from typing import Optional, Tuple
from urllib.parse import urlparse
-from github import AppAuthentication, Github
+from github import AppAuthentication, Github, Auth
from pr_agent.config_loader import settings
@@ -103,6 +103,9 @@ class GithubProvider(GitProvider):
self.pr.comments_list.append(response)
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
+ self.publish_inline_comments([self.create_inline_comment(body, relevant_file, relevant_line_in_file)])
+
+ def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
self.diff_files = self.diff_files if self.diff_files else self.get_diff_files()
position = -1
for file in self.diff_files:
@@ -121,9 +124,16 @@ class GithubProvider(GitProvider):
if position == -1:
if settings.config.verbosity_level >= 2:
logging.info(f"Could not find position for {relevant_file} {relevant_line_in_file}")
+ subject_type = "FILE"
else:
- path = relevant_file.strip()
- self.pr.create_review_comment(body=body, commit_id=self.last_commit_id, path=path, position=position)
+ subject_type = "LINE"
+ path = relevant_file.strip()
+ # placeholder for future API support (already supported in single inline comment)
+ # return dict(body=body, path=path, position=position, subject_type=subject_type)
+ return dict(body=body, path=path, position=position) if subject_type == "LINE" else {}
+
+ def publish_inline_comments(self, comments: list[dict]):
+ self.pr.create_review(commit=self.last_commit_id, comments=comments)
def publish_code_suggestion(self, body: str,
relevant_file: str,
@@ -264,7 +274,7 @@ class GithubProvider(GitProvider):
raise ValueError(
"GitHub token is required when using user deployment. See: "
"https://github.com/Codium-ai/pr-agent#method-2-run-from-source") from e
- return Github(token)
+ return Github(auth=Auth.Token(token))
def _get_repo(self):
return self.github_client.get_repo(self.repo)
diff --git a/pr_agent/git_providers/gitlab_provider.py b/pr_agent/git_providers/gitlab_provider.py
index 17927dd1..95246647 100644
--- a/pr_agent/git_providers/gitlab_provider.py
+++ b/pr_agent/git_providers/gitlab_provider.py
@@ -35,7 +35,7 @@ class GitLabProvider(GitProvider):
self.incremental = incremental
def is_supported(self, capability: str) -> bool:
- if capability == 'get_issue_comments':
+ if capability in ['get_issue_comments', 'create_inline_comment', 'publish_inline_comments']:
return False
return True
@@ -109,6 +109,12 @@ class GitLabProvider(GitProvider):
self.send_inline_comment(body, edit_type, found, relevant_file, relevant_line_in_file, source_line_no,
target_file, target_line_no)
+ def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
+ raise NotImplementedError("Gitlab provider does not support creating inline comments yet")
+
+ def create_inline_comment(self, comments: list[dict]):
+ raise NotImplementedError("Gitlab provider does not support publishing inline comments yet")
+
def send_inline_comment(self, body, edit_type, found, relevant_file, relevant_line_in_file, source_line_no,
target_file, target_line_no):
if not found:
diff --git a/pr_agent/tools/pr_reviewer.py b/pr_agent/tools/pr_reviewer.py
index 38f918b9..4e147ace 100644
--- a/pr_agent/tools/pr_reviewer.py
+++ b/pr_agent/tools/pr_reviewer.py
@@ -24,7 +24,7 @@ class PRReviewer:
self.is_answer = is_answer
if self.is_answer and not self.git_provider.is_supported("get_issue_comments"):
raise Exception(f"Answer mode is not supported for {settings.config.git_provider} for now")
- answer_str = question_str = self._get_user_answers()
+ answer_str, question_str = self._get_user_answers()
self.ai_handler = AiHandler()
self.patches_diff = None
self.prediction = None
@@ -51,7 +51,7 @@ class PRReviewer:
async def review(self):
logging.info('Reviewing PR...')
if settings.config.publish_output:
- self.git_provider.publish_comment("Preparing review...", is_temporary=True)
+ self.git_provider.publish_comment("Preparing review...", is_temporary=True)
logging.info('Getting PR diff...')
self.patches_diff = get_pr_diff(self.git_provider, self.token_handler)
logging.info('Getting AI prediction...')
@@ -99,7 +99,13 @@ class PRReviewer:
if settings.config.git_provider != 'bitbucket' and \
settings.pr_reviewer.inline_code_comments and \
'Code suggestions' in data['PR Feedback']:
- del data['PR Feedback']['Code suggestions']
+ # keeping only code suggestions that can't be submitted as inline comments
+ data['PR Feedback']['Code suggestions'] = [
+ d for d in data['PR Feedback']['Code suggestions']
+ if any(key not in d for key in ('relevant file', 'relevant line in file', 'suggestion content'))
+ ]
+ if not data['PR Feedback']['Code suggestions']:
+ del data['PR Feedback']['Code suggestions']
markdown_text = convert_to_markdown(data)
user = self.git_provider.get_user_id()
@@ -125,16 +131,24 @@ class PRReviewer:
except json.decoder.JSONDecodeError:
data = try_fix_json(review)
- if settings.pr_reviewer.num_code_suggestions > 0:
- try:
- for d in data['PR Feedback']['Code suggestions']:
- relevant_file = d['relevant file'].strip()
- relevant_line_in_file = d['relevant line in file'].strip()
- content = d['suggestion content']
+ comments = []
+ for d in data['PR Feedback']['Code suggestions']:
+ relevant_file = d.get('relevant file', '').strip()
+ relevant_line_in_file = d.get('relevant line in file', '').strip()
+ content = d.get('suggestion content', '')
+ if not relevant_file or not relevant_line_in_file or not content:
+ logging.info("Skipping inline comment with missing file/line/content")
+ continue
- self.git_provider.publish_inline_comment(content, relevant_file, relevant_line_in_file)
- except KeyError:
- pass
+ if self.git_provider.is_supported("create_inline_comment"):
+ comment = self.git_provider.create_inline_comment(content, relevant_file, relevant_line_in_file)
+ if comment:
+ comments.append(comment)
+ else:
+ self.git_provider.publish_inline_comment(content, relevant_file, relevant_line_in_file)
+
+ if comments:
+ self.git_provider.publish_inline_comments(comments)
def _get_user_answers(self):
answer_str = question_str = ""
diff --git a/requirements.txt b/requirements.txt
index 8695f709..399cd3ab 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,6 @@
dynaconf==3.1.12
fastapi==0.99.0
-PyGithub==1.58.2
+PyGithub==1.59.*
retry==0.9.2
openai==0.27.8
Jinja2==3.1.2