diff --git a/README.md b/README.md index b412eea5..8d12019a 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,8 @@ Example: ![PR Conversation](./docs/images/openai-review-conversation.png) +![PR Conversation Request](./docs/images/openai-review-request.png) + #### Environment variables - `GITHUB_TOKEN`: This should already be available to the GitHub Action diff --git a/action.yml b/action.yml index eef76a8c..3f42f5ee 100644 --- a/action.yml +++ b/action.yml @@ -70,13 +70,13 @@ inputs: required: false description: 'System message to be sent to OpenAI' default: | - You are a highly experienced software engineer with a strong ability to - review code changes thoroughly.You can uncover issues such as logic - errors, syntax errors, out of bound errors, potential data races, + You are `@openai`, a highly experienced software engineer with a strong + ability to review code changes thoroughly.You can uncover issues such as + logic errors, syntax errors, out of bound errors, potential data races, livelocks, starvation, suspension, order violation, atomicity violation, consistency, complexity, error handling, and more. - We will be conducting code reviews today. + You will be conducting code reviews today and write code if asked to do so. summarize_beginning: required: false description: 'The prompt for the whole pull request' @@ -156,7 +156,7 @@ inputs: $summary ``` - Reply "OK" to confirm that you are ready for further instructions. + Reply "OK" to confirm. review_file: required: false description: 'The prompt for each file' @@ -167,6 +167,8 @@ inputs: ``` $file_content ``` + + Reply "OK" to confirm. review_file_diff: required: false description: 'The prompt for each file diff' @@ -177,6 +179,8 @@ inputs: ```diff $file_diff ``` + + Reply "OK" to confirm. review_patch_begin: required: false description: 'The prompt for each file diff' @@ -187,7 +191,9 @@ inputs: "LGTM!" with a short comment. Your responses will be recorded as review comments on the pull request. - Markdown format is preferred for your responses. Reply "OK" to confirm. + Markdown format is preferred for your responses. + + Reply "OK" to confirm. review_patch: required: false description: 'The prompt for each chunks/patches' @@ -211,11 +217,13 @@ inputs: $system_message A comment was made on a review for a diff patch on file - `$filename`. You will be replying directly to that. If possible, I - will provide you the file and the entire diff to help - provide overall context. + `$filename`. I would like you to follow the instructions + in that comment. - For context, the pull request has the title "$title" and the following + If possible, I will provide you the file and the entire diff + to help provide overall context for your response. + + The pull request has the title "$title" and the following description: ``` @@ -238,6 +246,8 @@ inputs: ``` $file_content ``` + + Reply "OK" to confirm. comment_file_diff: required: false description: 'Prompt for file diff' @@ -247,12 +257,14 @@ inputs: ```diff $file_diff ``` + + Reply "OK" to confirm. comment: required: false description: 'Prompt for comment' default: | - I would like you to reply to the new comment made on - a conversation chain on a code review diff. + I would like you to follow the instructions in the new + comment made on a conversation chain on a code review diff. Diff being commented on: @@ -269,19 +281,21 @@ inputs: $comment_chain ``` - Please reply directly to the new comment in the conversation - chain (instead of suggesting a reply) and your reply will be - posted as-is. + Please reply directly to the new comment (instead of suggesting + a reply) and your reply will be posted as-is. + + If the comment contains instructions/request for you, please comply. + For example, if the comment is asking you to generate documentation + comments on the code, in your reply please generate the required code. - In your reply, please make sure to begin the reply by - tagging the user with "@user". + In your reply, please make sure to begin the reply by tagging the user + with "@user". - The comment that you need to directly reply to: + The comment/request that you need to directly reply to: ``` $comment ``` - runs: using: 'node16' main: 'dist/index.js' diff --git a/dist/index.js b/dist/index.js index aa1c400e..4a5d950f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -29273,6 +29273,7 @@ const handleReviewComment = async (bot, prompts) => { if (comment_chain.includes(_commenter_js__WEBPACK_IMPORTED_MODULE_2__/* .COMMENT_TAG */ .Rs) || comment_chain.includes(_commenter_js__WEBPACK_IMPORTED_MODULE_2__/* .COMMENT_REPLY_TAG */ .aD) || comment.body.startsWith(ASK_BOT)) { + let file_content = ''; try { const contents = await octokit.repos.getContent({ owner: repo.owner, @@ -29283,7 +29284,7 @@ const handleReviewComment = async (bot, prompts) => { if (contents.data) { if (!Array.isArray(contents.data)) { if (contents.data.type === 'file' && contents.data.content) { - inputs.file_content = Buffer.from(contents.data.content, 'base64').toString(); + file_content = Buffer.from(contents.data.content, 'base64').toString(); } } } @@ -29291,6 +29292,7 @@ const handleReviewComment = async (bot, prompts) => { catch (error) { _actions_core__WEBPACK_IMPORTED_MODULE_0__.warning(`Failed to get file contents: ${error}, skipping.`); } + let file_diff = ''; try { // get diff for this file by comparing the base and head commits const diffAll = await octokit.repos.compareCommits({ @@ -29304,7 +29306,7 @@ const handleReviewComment = async (bot, prompts) => { if (files) { const file = files.find(f => f.filename === comment.path); if (file && file.patch) { - inputs.file_diff = file.patch; + file_diff = file.patch; } } } @@ -29320,8 +29322,9 @@ const handleReviewComment = async (bot, prompts) => { // begin comment generation const [, comment_begin_ids] = await bot.chat(prompts.render_comment_beginning(inputs), {}); let next_comment_ids = comment_begin_ids; - if (inputs.file_content.length > 0) { - const file_content_tokens = _tokenizer_js__WEBPACK_IMPORTED_MODULE_4__/* .get_token_count */ .u(inputs.file_content); + if (file_content.length > 0) { + inputs.file_content = file_content; + const file_content_tokens = _tokenizer_js__WEBPACK_IMPORTED_MODULE_4__/* .get_token_count */ .u(file_content); if (file_content_tokens < MAX_TOKENS_FOR_EXTRA_CONTENT) { const [file_content_resp, file_content_ids] = await bot.chat(prompts.render_comment_file(inputs), next_comment_ids); if (file_content_resp) { @@ -29329,8 +29332,13 @@ const handleReviewComment = async (bot, prompts) => { } } } - if (inputs.file_diff.length > 0) { - const file_diff_tokens = _tokenizer_js__WEBPACK_IMPORTED_MODULE_4__/* .get_token_count */ .u(inputs.file_diff); + if (file_diff.length > 0) { + inputs.file_diff = file_diff; + // use file diff if no diff was found in the comment + if (inputs.diff.length === 0) { + inputs.diff = file_diff; + } + const file_diff_tokens = _tokenizer_js__WEBPACK_IMPORTED_MODULE_4__/* .get_token_count */ .u(file_diff); if (file_diff_tokens < MAX_TOKENS_FOR_EXTRA_CONTENT) { const [file_diff_resp, file_diff_ids] = await bot.chat(prompts.render_comment_file_diff(inputs), next_comment_ids); if (file_diff_resp) { @@ -29367,6 +29375,18 @@ ${_commenter_js__WEBPACK_IMPORTED_MODULE_2__/* .COMMENT_REPLY_TAG */ .aD} } catch (error) { _actions_core__WEBPACK_IMPORTED_MODULE_0__.warning(`Failed to reply to the top-level comment`); + try { + await octokit.pulls.createReplyForReviewComment({ + owner: repo.owner, + repo: repo.repo, + pull_number, + body: `Could not post the reply to the top-level comment due to the following error: ${error}`, + comment_id: topLevelCommentId + }); + } + catch (error) { + _actions_core__WEBPACK_IMPORTED_MODULE_0__.warning(`Failed to reply to the top-level comment`); + } } } else { @@ -29657,9 +29677,11 @@ const codeReview = async (bot, options, prompts) => { const generateSummary = async (filename, file_content, file_diff) => { const ins = inputs.clone(); ins.filename = filename; - ins.file_content = file_content; - ins.file_diff = file_diff; + if (file_content.length > 0) { + ins.file_content = file_content; + } if (file_diff.length > 0) { + ins.file_diff = file_diff; const file_diff_tokens = tokenizer/* get_token_count */.u(file_diff); if (file_diff_tokens < MAX_TOKENS_FOR_EXTRA_CONTENT) { // summarize diff @@ -29705,8 +29727,8 @@ ${filename}: ${summary} --- Tips: -- You can reply on the review comment left by this bot to ask follow-up questions. -- You can invite the bot into a review conversation by typing \`@openai\` in the beginning of the comment. +- Reply on the review comment left by this bot to ask follow-up questions. +- Invite the bot into a review conversation by typing \`@openai\` in the beginning of the comment. `; next_summarize_ids = summarize_final_response_ids; await commenter.comment(`${summarize_comment}`, lib_commenter/* SUMMARIZE_TAG */.Rp, 'replace'); @@ -29731,9 +29753,8 @@ Tips: // make a copy of inputs const ins = inputs.clone(); ins.filename = filename; - ins.file_content = file_content; - ins.file_diff = file_diff; if (file_content.length > 0) { + ins.file_content = file_content; const file_content_tokens = tokenizer/* get_token_count */.u(file_content); if (file_content_tokens < MAX_TOKENS_FOR_EXTRA_CONTENT) { try { @@ -29755,6 +29776,7 @@ Tips: } } if (file_diff.length > 0) { + ins.file_diff = file_diff; const file_diff_tokens = tokenizer/* get_token_count */.u(file_diff); if (file_diff_tokens < MAX_TOKENS_FOR_EXTRA_CONTENT) { try { diff --git a/docs/images/openai-review-request.png b/docs/images/openai-review-request.png new file mode 100644 index 00000000..9847f017 Binary files /dev/null and b/docs/images/openai-review-request.png differ diff --git a/src/review-comment.ts b/src/review-comment.ts index 0a79315c..86753227 100644 --- a/src/review-comment.ts +++ b/src/review-comment.ts @@ -79,6 +79,7 @@ export const handleReviewComment = async (bot: Bot, prompts: Prompts) => { comment_chain.includes(COMMENT_REPLY_TAG) || comment.body.startsWith(ASK_BOT) ) { + let file_content = '' try { const contents = await octokit.repos.getContent({ owner: repo.owner, @@ -89,7 +90,7 @@ export const handleReviewComment = async (bot: Bot, prompts: Prompts) => { if (contents.data) { if (!Array.isArray(contents.data)) { if (contents.data.type === 'file' && contents.data.content) { - inputs.file_content = Buffer.from( + file_content = Buffer.from( contents.data.content, 'base64' ).toString() @@ -99,6 +100,8 @@ export const handleReviewComment = async (bot: Bot, prompts: Prompts) => { } catch (error) { core.warning(`Failed to get file contents: ${error}, skipping.`) } + + let file_diff = '' try { // get diff for this file by comparing the base and head commits const diffAll = await octokit.repos.compareCommits({ @@ -112,7 +115,7 @@ export const handleReviewComment = async (bot: Bot, prompts: Prompts) => { if (files) { const file = files.find(f => f.filename === comment.path) if (file && file.patch) { - inputs.file_diff = file.patch + file_diff = file.patch } } } @@ -135,10 +138,9 @@ export const handleReviewComment = async (bot: Bot, prompts: Prompts) => { {} ) let next_comment_ids = comment_begin_ids - if (inputs.file_content.length > 0) { - const file_content_tokens = tokenizer.get_token_count( - inputs.file_content - ) + if (file_content.length > 0) { + inputs.file_content = file_content + const file_content_tokens = tokenizer.get_token_count(file_content) if (file_content_tokens < MAX_TOKENS_FOR_EXTRA_CONTENT) { const [file_content_resp, file_content_ids] = await bot.chat( prompts.render_comment_file(inputs), @@ -150,8 +152,13 @@ export const handleReviewComment = async (bot: Bot, prompts: Prompts) => { } } - if (inputs.file_diff.length > 0) { - const file_diff_tokens = tokenizer.get_token_count(inputs.file_diff) + if (file_diff.length > 0) { + inputs.file_diff = file_diff + // use file diff if no diff was found in the comment + if (inputs.diff.length === 0) { + inputs.diff = file_diff + } + const file_diff_tokens = tokenizer.get_token_count(file_diff) if (file_diff_tokens < MAX_TOKENS_FOR_EXTRA_CONTENT) { const [file_diff_resp, file_diff_ids] = await bot.chat( prompts.render_comment_file_diff(inputs), @@ -198,6 +205,17 @@ ${COMMENT_REPLY_TAG} }) } catch (error) { core.warning(`Failed to reply to the top-level comment`) + try { + await octokit.pulls.createReplyForReviewComment({ + owner: repo.owner, + repo: repo.repo, + pull_number, + body: `Could not post the reply to the top-level comment due to the following error: ${error}`, + comment_id: topLevelCommentId + }) + } catch (error) { + core.warning(`Failed to reply to the top-level comment`) + } } } else { core.warning(`Failed to find the top-level comment to reply to`) diff --git a/src/review.ts b/src/review.ts index 3fbfc328..50396c7c 100644 --- a/src/review.ts +++ b/src/review.ts @@ -164,9 +164,12 @@ export const codeReview = async ( ): Promise<[string, string] | null> => { const ins = inputs.clone() ins.filename = filename - ins.file_content = file_content - ins.file_diff = file_diff + + if (file_content.length > 0) { + ins.file_content = file_content + } if (file_diff.length > 0) { + ins.file_diff = file_diff const file_diff_tokens = tokenizer.get_token_count(file_diff) if (file_diff_tokens < MAX_TOKENS_FOR_EXTRA_CONTENT) { // summarize diff @@ -225,8 +228,8 @@ ${filename}: ${summary} --- Tips: -- You can reply on the review comment left by this bot to ask follow-up questions. -- You can invite the bot into a review conversation by typing \`@openai\` in the beginning of the comment. +- Reply on the review comment left by this bot to ask follow-up questions. +- Invite the bot into a review conversation by typing \`@openai\` in the beginning of the comment. ` next_summarize_ids = summarize_final_response_ids @@ -263,10 +266,9 @@ Tips: const ins: Inputs = inputs.clone() ins.filename = filename - ins.file_content = file_content - ins.file_diff = file_diff if (file_content.length > 0) { + ins.file_content = file_content const file_content_tokens = tokenizer.get_token_count(file_content) if (file_content_tokens < MAX_TOKENS_FOR_EXTRA_CONTENT) { try { @@ -291,6 +293,7 @@ Tips: } if (file_diff.length > 0) { + ins.file_diff = file_diff const file_diff_tokens = tokenizer.get_token_count(file_diff) if (file_diff_tokens < MAX_TOKENS_FOR_EXTRA_CONTENT) { try {