From 57644a4be8bdf65eb0563724a16ae384720459c6 Mon Sep 17 00:00:00 2001 From: Ariane Emory <97994360+ariane-emory@users.noreply.github.com> Date: Wed, 26 Nov 2025 00:34:58 -0500 Subject: [PATCH 1/6] feat: add a diff_style option to allow disabling columnar diffs (resolve #4677) (#4756) Co-authored-by: Dax Raad Co-authored-by: GitHub Action Co-authored-by: opencode-agent[bot] Co-authored-by: rekram1-node --- .../opencode/src/cli/cmd/tui/routes/session/index.tsx | 9 ++++++++- packages/opencode/src/config/config.ts | 4 ++++ packages/sdk/js/src/gen/types.gen.ts | 4 ++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 74874f669..c0d173421 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -80,6 +80,7 @@ const context = createContext<{ conceal: () => boolean showThinking: () => boolean showTimestamps: () => boolean + sync: ReturnType }>() function use() { @@ -732,6 +733,7 @@ export function Session() { conceal, showThinking, showTimestamps, + sync, }} > @@ -1482,7 +1484,12 @@ ToolRegistry.register({ const ctx = use() const { theme, syntax } = useTheme() - const style = createMemo(() => (ctx.width > 120 ? "split" : "stacked")) + const style = createMemo(() => { + const diffStyle = ctx.sync.data.config.tui?.diff_style + if (diffStyle === "stacked") return "stacked" + // Default to "auto" behavior + return ctx.width > 120 ? "split" : "stacked" + }) const diff = createMemo(() => { const diff = props.metadata.diff ?? props.permission["diff"] diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 0ea0e8fa2..383a47566 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -456,6 +456,10 @@ export namespace Config { }) .optional() .describe("Scroll acceleration settings"), + diff_style: z + .enum(["auto", "stacked"]) + .optional() + .describe("Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column"), }) export const Layout = z.enum(["auto", "stretch"]).meta({ diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 7e211148f..4ec6b15df 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -1003,6 +1003,10 @@ export type Config = { */ enabled: boolean } + /** + * Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column + */ + diff_style?: "auto" | "stacked" } /** * Command configuration, see https://opencode.ai/docs/commands From 338229193f6c82308e2fb0ce5fc3547495bb8e4d Mon Sep 17 00:00:00 2001 From: george larson Date: Wed, 26 Nov 2025 05:40:52 +0000 Subject: [PATCH 2/6] docs: add Venice.ai provider (#4748) Co-authored-by: George Larson --- packages/web/src/content/docs/providers.mdx | 36 +++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/web/src/content/docs/providers.mdx b/packages/web/src/content/docs/providers.mdx index 013956569..3d6bd614e 100644 --- a/packages/web/src/content/docs/providers.mdx +++ b/packages/web/src/content/docs/providers.mdx @@ -1293,3 +1293,39 @@ If you are having trouble with configuring a provider, check the following: - Make sure the provider ID used in `opencode auth login` matches the ID in your opencode config. - The right npm package is used for the provider. For example, use `@ai-sdk/cerebras` for Cerebras. And for all other OpenAI-compatible providers, use `@ai-sdk/openai-compatible`. - Check correct API endpoint is used in the `options.baseURL` field. + +--- + +### Venice AI + +1. Head over to the [Venice AI console](https://venice.ai), create an account, and generate an API key. + +2. Run `opencode auth login` and select **Venice AI**. + + ```bash + $ opencode auth login + + ┌ Add credential + │ + ◆ Select provider + │ ● Venice AI + │ ... + └ + ``` + +3. Enter your Venice AI API key. + + ```bash + $ opencode auth login + + ┌ Add credential + │ + ◇ Select provider + │ Venice AI + │ + ◇ Enter your API key + │ _ + └ + ``` + +4. Run the `/models` command to select a model like _Llama 3.3 70B_. From 0ce64962d4c9eeaa7c9e4da84a4702f03927055a Mon Sep 17 00:00:00 2001 From: U Cirello <138827+ucirello@users.noreply.github.com> Date: Tue, 25 Nov 2025 21:45:19 -0800 Subject: [PATCH 3/6] feat(github): add ability to react to PR Review Comments in Workflow (#4705) Co-authored-by: GitHub Action --- .github/workflows/opencode.yml | 2 + github/README.md | 26 ++++++++++++ github/index.ts | 51 ++++++++++++++++++++---- packages/opencode/src/cli/cmd/github.ts | 46 ++++++++++++++++++--- packages/web/src/content/docs/github.mdx | 19 +++++++++ 5 files changed, 131 insertions(+), 13 deletions(-) diff --git a/.github/workflows/opencode.yml b/.github/workflows/opencode.yml index 75533df70..f7d7f6081 100644 --- a/.github/workflows/opencode.yml +++ b/.github/workflows/opencode.yml @@ -3,6 +3,8 @@ name: opencode on: issue_comment: types: [created] + pull_request_review_comment: + types: [created] jobs: opencode: diff --git a/github/README.md b/github/README.md index 8e5b6d813..36342b409 100644 --- a/github/README.md +++ b/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"}}}' +``` diff --git a/github/index.ts b/github/index.ts index b681ff92f..6d826326e 100644 --- a/github/index.ts +++ b/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>["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() + await client.app.log({ + 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`") })() diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index 1f60e81e9..5f9bcded1 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/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`") })() diff --git a/packages/web/src/content/docs/github.mdx b/packages/web/src/content/docs/github.mdx index 359f696fc..19c7782ef 100644 --- a/packages/web/src/content/docs/github.mdx +++ b/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. From e1cc98d448f8db971fc2fbed6055839b2f233d2f Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Tue, 25 Nov 2025 23:51:23 -0600 Subject: [PATCH 4/6] Revert "feat(github): add ability to react to PR Review Comments in Workflow (#4705)" This reverts commit 0ce64962d4c9eeaa7c9e4da84a4702f03927055a. --- .github/workflows/opencode.yml | 2 - github/README.md | 26 ------------ github/index.ts | 51 ++++-------------------- packages/opencode/src/cli/cmd/github.ts | 46 +++------------------ packages/web/src/content/docs/github.mdx | 19 --------- 5 files changed, 13 insertions(+), 131 deletions(-) diff --git a/.github/workflows/opencode.yml b/.github/workflows/opencode.yml index f7d7f6081..75533df70 100644 --- a/.github/workflows/opencode.yml +++ b/.github/workflows/opencode.yml @@ -3,8 +3,6 @@ name: opencode on: issue_comment: types: [created] - pull_request_review_comment: - types: [created] jobs: opencode: diff --git a/github/README.md b/github/README.md index 36342b409..8e5b6d813 100644 --- a/github/README.md +++ b/github/README.md @@ -30,24 +30,6 @@ 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: @@ -69,8 +51,6 @@ 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: @@ -155,9 +135,3 @@ 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"}}}' -``` diff --git a/github/index.ts b/github/index.ts index 6d826326e..b681ff92f 100644 --- a/github/index.ts +++ b/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, PullRequestReviewCommentEvent } from "@octokit/webhooks-types" +import type { IssueCommentEvent } 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>["promptFiles"] try { - assertContextEvent("issue_comment", "pull_request_review_comment") + assertContextEvent("issue_comment") assertPayloadKeyword() await assertOpencodeConnected() @@ -241,43 +241,19 @@ function createOpencode() { } function assertPayloadKeyword() { - const payload = useContext().payload as IssueCommentEvent | PullRequestReviewCommentEvent + const payload = useContext().payload as IssueCommentEvent 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.log({ - body: { - service: "github-workflow", - level: "info", - message: "Prepare to react to Github Workflow event", - }, - }) + await client.app.get() connected = true break } catch (e) {} @@ -407,24 +383,11 @@ 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") { - 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 - } + if (body === "/opencode" || body === "/oc") return "Summarize this thread" + if (body.includes("/opencode") || body.includes("/oc")) return body throw new Error("Comments must mention `/opencode` or `/oc`") })() diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index 5f9bcded1..1f60e81e9 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/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, PullRequestReviewCommentEvent } from "@octokit/webhooks-types" +import type { IssueCommentEvent } from "@octokit/webhooks-types" import { UI } from "../ui" import { cmd } from "./cmd" import { ModelsDev } from "../../provider/models" @@ -328,8 +328,6 @@ export const GithubInstallCommand = cmd({ on: issue_comment: types: [created] - pull_request_review_comment: - types: [created] jobs: opencode: @@ -380,7 +378,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" && context.eventName !== "pull_request_review_comment") { + if (context.eventName !== "issue_comment") { core.setFailed(`Unsupported event type: ${context.eventName}`) process.exit(1) } @@ -389,13 +387,9 @@ export const GithubRunCommand = cmd({ const runId = normalizeRunId() const share = normalizeShare() const { owner, repo } = context.repo - const payload = context.payload as IssueCommentEvent | PullRequestReviewCommentEvent + const payload = context.payload as IssueCommentEvent const actor = context.actor - - const issueId = - context.eventName === "pull_request_review_comment" - ? (payload as PullRequestReviewCommentEvent).pull_request.number - : (payload as IssueCommentEvent).issue.number + const issueId = payload.issue.number const runUrl = `/${owner}/${repo}/actions/runs/${runId}` const shareBaseUrl = isMock ? "https://dev.opencode.ai" : "https://opencode.ai" @@ -537,39 +531,11 @@ 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") { - 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 - } + if (body === "/opencode" || body === "/oc") return "Summarize this thread" + if (body.includes("/opencode") || body.includes("/oc")) return body throw new Error("Comments must mention `/opencode` or `/oc`") })() diff --git a/packages/web/src/content/docs/github.mdx b/packages/web/src/content/docs/github.mdx index 19c7782ef..359f696fc 100644 --- a/packages/web/src/content/docs/github.mdx +++ b/packages/web/src/content/docs/github.mdx @@ -45,8 +45,6 @@ Or you can set it up manually. on: issue_comment: types: [created] - pull_request_review_comment: - types: [created] jobs: opencode: @@ -131,20 +129,3 @@ 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. From 2fbd462e6e35263274e975a2084324b8721f886f Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Tue, 25 Nov 2025 23:51:46 -0600 Subject: [PATCH 5/6] Reapply "feat(github): add ability to react to PR Review Comments in Workflow (#4705)" This reverts commit e1cc98d448f8db971fc2fbed6055839b2f233d2f. --- .github/workflows/opencode.yml | 2 + github/README.md | 26 ++++++++++++ github/index.ts | 51 ++++++++++++++++++++---- packages/opencode/src/cli/cmd/github.ts | 46 ++++++++++++++++++--- packages/web/src/content/docs/github.mdx | 19 +++++++++ 5 files changed, 131 insertions(+), 13 deletions(-) diff --git a/.github/workflows/opencode.yml b/.github/workflows/opencode.yml index 75533df70..f7d7f6081 100644 --- a/.github/workflows/opencode.yml +++ b/.github/workflows/opencode.yml @@ -3,6 +3,8 @@ name: opencode on: issue_comment: types: [created] + pull_request_review_comment: + types: [created] jobs: opencode: diff --git a/github/README.md b/github/README.md index 8e5b6d813..36342b409 100644 --- a/github/README.md +++ b/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"}}}' +``` diff --git a/github/index.ts b/github/index.ts index b681ff92f..6d826326e 100644 --- a/github/index.ts +++ b/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>["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() + await client.app.log({ + 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`") })() diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index 1f60e81e9..5f9bcded1 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/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`") })() diff --git a/packages/web/src/content/docs/github.mdx b/packages/web/src/content/docs/github.mdx index 359f696fc..19c7782ef 100644 --- a/packages/web/src/content/docs/github.mdx +++ b/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. From 14e823e93878a4bc900696a43a9a90b70e81621f Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 26 Nov 2025 00:14:04 -0600 Subject: [PATCH 6/6] ignore: fix type issue --- packages/opencode/src/cli/cmd/github.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index 5f9bcded1..b255e17d1 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/packages/opencode/src/cli/cmd/github.ts @@ -390,6 +390,7 @@ export const GithubRunCommand = cmd({ const share = normalizeShare() const { owner, repo } = context.repo const payload = context.payload as IssueCommentEvent | PullRequestReviewCommentEvent + const issueEvent = isIssueCommentEvent(payload) ? payload : undefined const actor = context.actor const issueId = @@ -440,7 +441,7 @@ export const GithubRunCommand = cmd({ // 1. Issue // 2. Local PR // 3. Fork PR - if (payload.issue.pull_request) { + if (context.eventName === "pull_request_review_comment" || issueEvent?.issue.pull_request) { const prData = await fetchPR() // Local PR if (prData.headRepository.nameWithOwner === prData.baseRepository.nameWithOwner) { @@ -537,6 +538,12 @@ export const GithubRunCommand = cmd({ throw new Error(`Invalid share value: ${value}. Share must be a boolean.`) } + function isIssueCommentEvent( + event: IssueCommentEvent | PullRequestReviewCommentEvent, + ): event is IssueCommentEvent { + return "issue" in event + } + function getReviewCommentContext() { if (context.eventName !== "pull_request_review_comment") { return null @@ -686,7 +693,10 @@ export const GithubRunCommand = cmd({ try { return await chat(`Summarize the following in less than 40 characters:\n\n${response}`) } catch (e) { - return `Fix issue: ${payload.issue.title}` + const title = issueEvent + ? issueEvent.issue.title + : (payload as PullRequestReviewCommentEvent).pull_request.title + return `Fix issue: ${title}` } }