Răsfoiți Sursa

Reapply "feat(github): add ability to react to PR Review Comments in Workflow (#4705)"

This reverts commit e1cc98d448f8db971fc2fbed6055839b2f233d2f.
Aiden Cline 4 luni în urmă
părinte
comite
2fbd462e6e

+ 2 - 0
.github/workflows/opencode.yml

@@ -3,6 +3,8 @@ name: opencode
 on:
   issue_comment:
     types: [created]
+  pull_request_review_comment:
+    types: [created]
 
 jobs:
   opencode:

+ 26 - 0
github/README.md

@@ -30,6 +30,24 @@ Leave the following comment on a GitHub PR. opencode will implement the requeste
 Delete the attachment from S3 when the note is removed /oc
 ```
 
+#### Review specific code lines
+
+Leave a comment directly on code lines in the PR's "Files" tab. opencode will automatically detect the file, line numbers, and diff context to provide precise responses.
+
+```
+[Comment on specific lines in Files tab]
+/oc add error handling here
+```
+
+When commenting on specific lines, opencode receives:
+
+- The exact file being reviewed
+- The specific lines of code
+- The surrounding diff context
+- Line number information
+
+This allows for more targeted requests without needing to specify file paths or line numbers manually.
+
 ## Installation
 
 Run the following command in the terminal from your GitHub repo:
@@ -51,6 +69,8 @@ This will walk you through installing the GitHub app, creating the workflow, and
    on:
      issue_comment:
        types: [created]
+     pull_request_review_comment:
+       types: [created]
 
    jobs:
      opencode:
@@ -135,3 +155,9 @@ Replace the image URL `https://github.com/user-attachments/assets/xxxxxxxx` with
 ```
 MOCK_EVENT='{"eventName":"issue_comment","repo":{"owner":"sst","repo":"hello-world"},"actor":"fwang","payload":{"issue":{"number":4,"pull_request":{}},"comment":{"id":1,"body":"hey opencode, summarize thread"}}}'
 ```
+
+### PR review comment event
+
+```
+MOCK_EVENT='{"eventName":"pull_request_review_comment","repo":{"owner":"sst","repo":"hello-world"},"actor":"fwang","payload":{"pull_request":{"number":7},"comment":{"id":1,"body":"hey opencode, add error handling","path":"src/components/Button.tsx","diff_hunk":"@@ -45,8 +45,11 @@\n- const handleClick = () => {\n-   console.log('clicked')\n+ const handleClick = useCallback(() => {\n+   console.log('clicked')\n+   doSomething()\n+ }, [doSomething])","line":47,"original_line":45,"position":10,"commit_id":"abc123","original_commit_id":"def456"}}}'
+```

+ 44 - 7
github/index.ts

@@ -5,7 +5,7 @@ import { graphql } from "@octokit/graphql"
 import * as core from "@actions/core"
 import * as github from "@actions/github"
 import type { Context as GitHubContext } from "@actions/github/lib/context"
-import type { IssueCommentEvent } from "@octokit/webhooks-types"
+import type { IssueCommentEvent, PullRequestReviewCommentEvent } from "@octokit/webhooks-types"
 import { createOpencodeClient } from "@opencode-ai/sdk"
 import { spawn } from "node:child_process"
 
@@ -124,7 +124,7 @@ let exitCode = 0
 type PromptFiles = Awaited<ReturnType<typeof getUserPrompt>>["promptFiles"]
 
 try {
-  assertContextEvent("issue_comment")
+  assertContextEvent("issue_comment", "pull_request_review_comment")
   assertPayloadKeyword()
   await assertOpencodeConnected()
 
@@ -241,19 +241,43 @@ function createOpencode() {
 }
 
 function assertPayloadKeyword() {
-  const payload = useContext().payload as IssueCommentEvent
+  const payload = useContext().payload as IssueCommentEvent | PullRequestReviewCommentEvent
   const body = payload.comment.body.trim()
   if (!body.match(/(?:^|\s)(?:\/opencode|\/oc)(?=$|\s)/)) {
     throw new Error("Comments must mention `/opencode` or `/oc`")
   }
 }
 
+function getReviewCommentContext() {
+  const context = useContext()
+  if (context.eventName !== "pull_request_review_comment") {
+    return null
+  }
+
+  const payload = context.payload as PullRequestReviewCommentEvent
+  return {
+    file: payload.comment.path,
+    diffHunk: payload.comment.diff_hunk,
+    line: payload.comment.line,
+    originalLine: payload.comment.original_line,
+    position: payload.comment.position,
+    commitId: payload.comment.commit_id,
+    originalCommitId: payload.comment.original_commit_id,
+  }
+}
+
 async function assertOpencodeConnected() {
   let retry = 0
   let connected = false
   do {
     try {
-      await client.app.get<true>()
+      await client.app.log<true>({
+        body: {
+          service: "github-workflow",
+          level: "info",
+          message: "Prepare to react to Github Workflow event",
+        },
+      })
       connected = true
       break
     } catch (e) {}
@@ -383,11 +407,24 @@ async function createComment() {
 }
 
 async function getUserPrompt() {
+  const context = useContext()
+  const payload = context.payload as IssueCommentEvent | PullRequestReviewCommentEvent
+  const reviewContext = getReviewCommentContext()
+
   let prompt = (() => {
-    const payload = useContext().payload as IssueCommentEvent
     const body = payload.comment.body.trim()
-    if (body === "/opencode" || body === "/oc") return "Summarize this thread"
-    if (body.includes("/opencode") || body.includes("/oc")) return body
+    if (body === "/opencode" || body === "/oc") {
+      if (reviewContext) {
+        return `Review this code change and suggest improvements for the commented lines:\n\nFile: ${reviewContext.file}\nLines: ${reviewContext.line}\n\n${reviewContext.diffHunk}`
+      }
+      return "Summarize this thread"
+    }
+    if (body.includes("/opencode") || body.includes("/oc")) {
+      if (reviewContext) {
+        return `${body}\n\nContext: You are reviewing a comment on file "${reviewContext.file}" at line ${reviewContext.line}.\n\nDiff context:\n${reviewContext.diffHunk}`
+      }
+      return body
+    }
     throw new Error("Comments must mention `/opencode` or `/oc`")
   })()
 

+ 40 - 6
packages/opencode/src/cli/cmd/github.ts

@@ -7,7 +7,7 @@ import { graphql } from "@octokit/graphql"
 import * as core from "@actions/core"
 import * as github from "@actions/github"
 import type { Context } from "@actions/github/lib/context"
-import type { IssueCommentEvent } from "@octokit/webhooks-types"
+import type { IssueCommentEvent, PullRequestReviewCommentEvent } from "@octokit/webhooks-types"
 import { UI } from "../ui"
 import { cmd } from "./cmd"
 import { ModelsDev } from "../../provider/models"
@@ -328,6 +328,8 @@ export const GithubInstallCommand = cmd({
 on:
   issue_comment:
     types: [created]
+  pull_request_review_comment:
+    types: [created]
 
 jobs:
   opencode:
@@ -378,7 +380,7 @@ export const GithubRunCommand = cmd({
       const isMock = args.token || args.event
 
       const context = isMock ? (JSON.parse(args.event!) as Context) : github.context
-      if (context.eventName !== "issue_comment") {
+      if (context.eventName !== "issue_comment" && context.eventName !== "pull_request_review_comment") {
         core.setFailed(`Unsupported event type: ${context.eventName}`)
         process.exit(1)
       }
@@ -387,9 +389,13 @@ export const GithubRunCommand = cmd({
       const runId = normalizeRunId()
       const share = normalizeShare()
       const { owner, repo } = context.repo
-      const payload = context.payload as IssueCommentEvent
+      const payload = context.payload as IssueCommentEvent | PullRequestReviewCommentEvent
       const actor = context.actor
-      const issueId = payload.issue.number
+
+      const issueId =
+        context.eventName === "pull_request_review_comment"
+          ? (payload as PullRequestReviewCommentEvent).pull_request.number
+          : (payload as IssueCommentEvent).issue.number
       const runUrl = `/${owner}/${repo}/actions/runs/${runId}`
       const shareBaseUrl = isMock ? "https://dev.opencode.ai" : "https://opencode.ai"
 
@@ -531,11 +537,39 @@ export const GithubRunCommand = cmd({
         throw new Error(`Invalid share value: ${value}. Share must be a boolean.`)
       }
 
+      function getReviewCommentContext() {
+        if (context.eventName !== "pull_request_review_comment") {
+          return null
+        }
+
+        const reviewPayload = payload as PullRequestReviewCommentEvent
+        return {
+          file: reviewPayload.comment.path,
+          diffHunk: reviewPayload.comment.diff_hunk,
+          line: reviewPayload.comment.line,
+          originalLine: reviewPayload.comment.original_line,
+          position: reviewPayload.comment.position,
+          commitId: reviewPayload.comment.commit_id,
+          originalCommitId: reviewPayload.comment.original_commit_id,
+        }
+      }
+
       async function getUserPrompt() {
+        const reviewContext = getReviewCommentContext()
         let prompt = (() => {
           const body = payload.comment.body.trim()
-          if (body === "/opencode" || body === "/oc") return "Summarize this thread"
-          if (body.includes("/opencode") || body.includes("/oc")) return body
+          if (body === "/opencode" || body === "/oc") {
+            if (reviewContext) {
+              return `Review this code change and suggest improvements for the commented lines:\n\nFile: ${reviewContext.file}\nLines: ${reviewContext.line}\n\n${reviewContext.diffHunk}`
+            }
+            return "Summarize this thread"
+          }
+          if (body.includes("/opencode") || body.includes("/oc")) {
+            if (reviewContext) {
+              return `${body}\n\nContext: You are reviewing a comment on file "${reviewContext.file}" at line ${reviewContext.line}.\n\nDiff context:\n${reviewContext.diffHunk}`
+            }
+            return body
+          }
           throw new Error("Comments must mention `/opencode` or `/oc`")
         })()
 

+ 19 - 0
packages/web/src/content/docs/github.mdx

@@ -45,6 +45,8 @@ Or you can set it up manually.
    on:
      issue_comment:
        types: [created]
+     pull_request_review_comment:
+       types: [created]
 
    jobs:
      opencode:
@@ -129,3 +131,20 @@ Here are some examples of how you can use opencode in GitHub.
   ```
 
   opencode will implement the requested change and commit it to the same PR.
+
+- **Review specific code lines**
+
+  Leave a comment directly on code lines in the PR's "Files" tab. opencode automatically detects the file, line numbers, and diff context to provide precise responses.
+
+  ```
+  [Comment on specific lines in Files tab]
+  /oc add error handling here
+  ```
+
+  When commenting on specific lines, opencode receives:
+  - The exact file being reviewed
+  - The specific lines of code
+  - The surrounding diff context
+  - Line number information
+
+  This allows for more targeted requests without needing to specify file paths or line numbers manually.