Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/opencode-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: opencode review

on:
pull_request:
types: [review_requested]

jobs:
opencode-review:
if: github.event.requested_team.slug == 'opencode'
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
pull-requests: write
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v4

- uses: ./.github/actions/setup-bun

- name: Run opencode review
uses: sst/opencode/github@latest
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
with:
model: opencode/glm-4.6
prompt: Review this PR for code quality, potential bugs, security issues, and suggest improvements where needed
6 changes: 3 additions & 3 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 47 additions & 1 deletion github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ Leave the following comment on a GitHub PR. opencode will implement the requeste
Delete the attachment from S3 when the note is removed /oc
```

#### Automatic PR Reviews

Add opencode as a reviewer to automatically review PRs. When the `opencode` team is requested as a reviewer, the action will automatically trigger a comprehensive code review.

To set this up:

1. Create a team in your GitHub organization called `opencode`
2. When creating or viewing a PR, add the `opencode` team as a reviewer
3. The review workflow will automatically trigger and provide feedback

This requires the review workflow file `.github/workflows/opencode-review.yml` to be present (created automatically by `opencode github install`).

#### 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.
Expand Down Expand Up @@ -94,7 +106,41 @@ This will walk you through installing the GitHub app, creating the workflow, and
model: anthropic/claude-sonnet-4-20250514
```

3. Store the API keys in secrets. In your organization or project **settings**, expand **Secrets and variables** on the left and select **Actions**. Add the required API keys.
3. (Optional) For automatic PR reviews, add `.github/workflows/opencode-review.yml`:

```yml
name: opencode review

on:
pull_request:
types: [review_requested]

jobs:
opencode-review:
# Trigger when 'opencode' team is requested for review
if: github.event.requested_team.slug == 'opencode'
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
pull-requests: write
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Run opencode review
uses: sst/opencode/github@latest
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
with:
model: anthropic/claude-sonnet-4-20250514
prompt: "Review this PR for code quality, potential bugs, security issues, and suggest improvements where needed"
```

Then create a team called `opencode` in your GitHub organization. When you add this team as a reviewer to a PR, the workflow will automatically trigger a comprehensive code review.

4. Store the API keys in secrets. In your organization or project **settings**, expand **Secrets and variables** on the left and select **Actions**. Add the required API keys.

## Support

Expand Down
5 changes: 5 additions & 0 deletions github/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ inputs:
description: "Share the opencode session (defaults to true for public repos)"
required: false

prompt:
description: "Custom prompt for the action (optional)"
required: false

runs:
using: "composite"
steps:
Expand All @@ -27,3 +31,4 @@ runs:
env:
MODEL: ${{ inputs.model }}
SHARE: ${{ inputs.share }}
PROMPT: ${{ inputs.prompt }}
117 changes: 102 additions & 15 deletions packages/opencode/src/cli/cmd/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ 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,
PullRequestReviewCommentEvent,
PullRequestReviewRequestedEvent,
} from "@octokit/webhooks-types"
import { UI } from "../ui"
import { cmd } from "./cmd"
import { ModelsDev } from "../../provider/models"
Expand Down Expand Up @@ -125,6 +129,7 @@ type IssueQueryResponse = {
}

const WORKFLOW_FILE = ".github/workflows/opencode.yml"
const REVIEW_WORKFLOW_FILE = ".github/workflows/opencode-review.yml"

export const GithubCommand = cmd({
command: "github",
Expand Down Expand Up @@ -176,10 +181,12 @@ export const GithubInstallCommand = cmd({
[
"Next steps:",
"",
` 1. Commit the \`${WORKFLOW_FILE}\` file and push`,
` 1. Commit the workflow files and push`,
step2,
"",
" 3. Go to a GitHub issue and comment `/oc summarize` to see the agent in action",
" 3. Try the agent:",
" - Comment '/oc' on any issue or PR",
" - Add 'opencode' reviewer to a PR for automatic review",
"",
" Learn more about the GitHub agent - https://opencode.ai/docs/github/#usage-examples",
].join("\n"),
Expand Down Expand Up @@ -355,6 +362,40 @@ jobs:
)

prompts.log.success(`Added workflow file: "${WORKFLOW_FILE}"`)

// Add review workflow
await Bun.write(
path.join(app.root, REVIEW_WORKFLOW_FILE),
`name: opencode review

on:
pull_request:
types: [review_requested]

jobs:
opencode-review:
if: github.event.requested_team.slug == 'opencode'
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
pull-requests: write
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Run opencode review
uses: sst/opencode/github@latest${envStr}
with:
model: ${provider}/${model}
prompt: Review this PR for code quality, potential bugs, security issues, and suggest improvements where needed`,
)

prompts.log.success(`Added review workflow file: "${REVIEW_WORKFLOW_FILE}"`)
prompts.log.info(
`To use PR reviews: Create a team called "opencode" in your GitHub organization and add it as a reviewer to trigger automatic reviews.`,
)
}
}
},
Expand All @@ -380,7 +421,11 @@ 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" &&
context.eventName !== "pull_request_review_comment" &&
context.eventName !== "pull_request"
) {
core.setFailed(`Unsupported event type: ${context.eventName}`)
process.exit(1)
}
Expand All @@ -389,14 +434,19 @@ 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
| PullRequestReviewCommentEvent
| PullRequestReviewRequestedEvent
const issueEvent = isIssueCommentEvent(payload) ? payload : undefined
const actor = context.actor

const issueId =
context.eventName === "pull_request_review_comment"
? (payload as PullRequestReviewCommentEvent).pull_request.number
: (payload as IssueCommentEvent).issue.number
: context.eventName === "pull_request"
? (payload as PullRequestReviewRequestedEvent).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"

Expand Down Expand Up @@ -437,11 +487,16 @@ export const GithubRunCommand = cmd({
})()
console.log("opencode session", session.id)

// Handle 3 cases
// 1. Issue
// 2. Local PR
// 3. Fork PR
if (context.eventName === "pull_request_review_comment" || issueEvent?.issue.pull_request) {
// Handle 4 cases
// 1. Pull request event (review_requested)
// 2. Issue
// 3. Local PR
// 4. Fork PR
if (
context.eventName === "pull_request" ||
context.eventName === "pull_request_review_comment" ||
issueEvent?.issue.pull_request
) {
const prData = await fetchPR()
// Local PR
if (prData.headRepository.nameWithOwner === prData.baseRepository.nameWithOwner) {
Expand Down Expand Up @@ -539,11 +594,23 @@ export const GithubRunCommand = cmd({
}

function isIssueCommentEvent(
event: IssueCommentEvent | PullRequestReviewCommentEvent,
event: IssueCommentEvent | PullRequestReviewCommentEvent | PullRequestReviewRequestedEvent,
): event is IssueCommentEvent {
return "issue" in event
}

function isPullRequestEvent(
event: IssueCommentEvent | PullRequestReviewCommentEvent | PullRequestReviewRequestedEvent,
): event is PullRequestReviewRequestedEvent {
return (
context.eventName === "pull_request" &&
"action" in event &&
event.action === "review_requested" &&
"requested_team" in event &&
event.requested_team !== null
)
}

function getReviewCommentContext() {
if (context.eventName !== "pull_request_review_comment") {
return null
Expand All @@ -562,9 +629,23 @@ export const GithubRunCommand = cmd({
}

async function getUserPrompt() {
const envPrompt = process.env["PROMPT"]
if (envPrompt) {
return { userPrompt: envPrompt, promptFiles: [] }
}

if (isPullRequestEvent(payload)) {
return {
userPrompt:
"Review this PR for code quality, potential bugs, security issues, and suggest improvements where needed",
promptFiles: [],
}
}

const reviewContext = getReviewCommentContext()
let prompt = (() => {
const body = payload.comment.body.trim()
const commentPayload = payload as IssueCommentEvent | PullRequestReviewCommentEvent
const body = commentPayload.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}`
Expand Down Expand Up @@ -1021,10 +1102,13 @@ query($owner: String!, $repo: String!, $number: Int!) {
}

function buildPromptDataForIssue(issue: GitHubIssue) {
const commentPayload = !isPullRequestEvent(payload)
? (payload as IssueCommentEvent | PullRequestReviewCommentEvent)
: undefined
const comments = (issue.comments?.nodes || [])
.filter((c) => {
const id = parseInt(c.databaseId)
return id !== commentId && id !== payload.comment.id
return id !== commentId && (!commentPayload || id !== commentPayload.comment.id)
})
.map((c) => ` - ${c.author.login} at ${c.createdAt}: ${c.body}`)

Expand Down Expand Up @@ -1140,10 +1224,13 @@ query($owner: String!, $repo: String!, $number: Int!) {
}

function buildPromptDataForPR(pr: GitHubPullRequest) {
const commentPayload = !isPullRequestEvent(payload)
? (payload as IssueCommentEvent | PullRequestReviewCommentEvent)
: undefined
const comments = (pr.comments?.nodes || [])
.filter((c) => {
const id = parseInt(c.databaseId)
return id !== commentId && id !== payload.comment.id
return id !== commentId && (!commentPayload || id !== commentPayload.comment.id)
})
.map((c) => `- ${c.author.login} at ${c.createdAt}: ${c.body}`)

Expand Down