-
Notifications
You must be signed in to change notification settings - Fork 266
feat(web): add optional PR/MR summary comment to review agent #1175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
4303ebd
1865c33
3a7b45a
ff2c992
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import { sourcebot_pr_payload } from "@/features/agents/review-agent/types"; | ||
| import { getAISDKLanguageModelAndOptions, getConfiguredLanguageModels } from "@/features/chat/utils.server"; | ||
| import { env } from "@sourcebot/shared"; | ||
| import { generateText } from "ai"; | ||
| import { createLogger } from "@sourcebot/shared"; | ||
|
|
||
| const logger = createLogger('generate-pr-summary'); | ||
|
|
||
| export const generatePrSummary = async (prPayload: sourcebot_pr_payload): Promise<string> => { | ||
| const maxSummaryLength = env.REVIEW_AGENT_SUMMARY_MAX_LENGTH; | ||
| logger.debug("Executing generate_pr_summary"); | ||
|
|
||
| const models = await getConfiguredLanguageModels(); | ||
| if (models.length === 0) { | ||
| throw new Error("No language models are configured"); | ||
| } | ||
|
|
||
| let selectedModel = models[0]; | ||
| if (env.REVIEW_AGENT_MODEL) { | ||
| const match = models.find((m) => m.displayName === env.REVIEW_AGENT_MODEL); | ||
| if (match) { | ||
| selectedModel = match; | ||
| } else { | ||
| logger.warn(`REVIEW_AGENT_MODEL="${env.REVIEW_AGENT_MODEL}" did not match any configured model displayName. Falling back to the first configured model.`); | ||
| } | ||
| } | ||
|
|
||
| const { model, providerOptions, temperature } = await getAISDKLanguageModelAndOptions(selectedModel); | ||
|
|
||
| const filesChanged = prPayload.file_diffs.map(f => f.to).join(", "); | ||
|
|
||
| const prompt = `Summarize the following pull request changes in ${maxSummaryLength} characters or fewer. Be concise and focus on what changed and why. You may use inline markdown (e.g. \`code\`, **bold**) but avoid headers, bullet lists, and block-level formatting. | ||
|
|
||
| PR Title: ${prPayload.title} | ||
| PR Description: ${prPayload.description} | ||
| Files changed: ${filesChanged} | ||
| `; | ||
|
|
||
| const result = await generateText({ | ||
| model, | ||
| system: `You are a code review assistant. Generate a concise markdown-compatible summary of pull request changes. The summary must be ${maxSummaryLength} characters or fewer. Avoid headers, bullet lists, and block-level formatting.`, | ||
| prompt, | ||
| providerOptions, | ||
| temperature, | ||
| }); | ||
|
|
||
| const summary = result.text.trim().slice(0, maxSummaryLength); | ||
|
|
||
| logger.debug("Completed generate_pr_summary"); | ||
| return summary; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,9 +4,40 @@ import { createLogger } from "@sourcebot/shared"; | |
|
|
||
| const logger = createLogger('github-push-pr-reviews'); | ||
|
|
||
| export const githubPushPrReviews = async (octokit: Octokit, pr_payload: sourcebot_pr_payload, file_diff_reviews: sourcebot_file_diff_review[]) => { | ||
| export const githubPushPrReviews = async (octokit: Octokit, pr_payload: sourcebot_pr_payload, file_diff_reviews: sourcebot_file_diff_review[], summary?: string) => { | ||
| logger.info("Executing github_push_pr_reviews"); | ||
|
|
||
| if (summary) { | ||
| const SUMMARY_MARKER = "<!-- sourcebot-review-summary -->"; | ||
| try { | ||
| const { data: comments } = await octokit.rest.issues.listComments({ | ||
| owner: pr_payload.owner, | ||
| repo: pr_payload.repo, | ||
| issue_number: pr_payload.number, | ||
| }); | ||
| const existing = comments.find(c => c.body?.includes(SUMMARY_MARKER)); | ||
| const action = existing ? "Updated" : "Created"; | ||
| const body = `${SUMMARY_MARKER}\n${summary}\n\n---\n*${action}: ${new Date().toUTCString()}*`; | ||
|
Comment on lines
+13
to
+20
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n packages/web/src/features/agents/review-agent/nodes/githubPushPrReviews.tsRepository: sourcebot-dev/sourcebot Length of output: 3544 🌐 Web query:
💡 Result: For the Octokit REST endpoint that lists issue comments (GET /repos/{owner}/{repo}/issues/{issue_number}/comments), pagination works via GitHub’s standard REST pagination parameters (page, per_page) and the Link response header. Octokit’s pagination helpers follow that Link header. 1) REST endpoint (what Octokit is calling) - The endpoint supports per_page (max 100) and page, with defaults per_page=30 and page=1.[1] - Issue comments are ordered by ascending ID.[1] 2) How pagination is signaled (Link header) - When a response is paginated, GitHub includes a link header containing URLs for next/prev/first/last pages.[2] - If the endpoint does not support pagination or everything fits on one page, the link header is omitted.[2] 3) Octokit pagination behavior (what to expect in code) - Octokit’s REST pagination plugin (octokit.paginate from Citations:
Fetch all issue-comment pages before deciding create vs. update.
🤖 Prompt for AI Agents |
||
| if (existing) { | ||
| await octokit.rest.issues.updateComment({ | ||
| owner: pr_payload.owner, | ||
| repo: pr_payload.repo, | ||
| comment_id: existing.id, | ||
| body, | ||
| }); | ||
| } else { | ||
| await octokit.rest.issues.createComment({ | ||
| owner: pr_payload.owner, | ||
| repo: pr_payload.repo, | ||
| issue_number: pr_payload.number, | ||
| body, | ||
| }); | ||
| } | ||
| } catch (error) { | ||
| logger.error(`Error posting PR summary comment: ${error}`); | ||
| } | ||
| } | ||
|
|
||
| try { | ||
| for (const file_diff_review of file_diff_reviews) { | ||
| for (const review of file_diff_review.reviews) { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.