Skip to content

Review Agent #298

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

Merged
merged 16 commits into from
May 12, 2025
Merged
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
14 changes: 13 additions & 1 deletion .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ AUTH_URL="http://localhost:3000"
# AUTH_GOOGLE_CLIENT_ID=""
# AUTH_GOOGLE_CLIENT_SECRET=""

#DATA_CACHE_DIR="" # Path to the sourcebot cache dir (ex. ~/sourcebot/.sourcebot)

# Email
# EMAIL_FROM_ADDRESS="" # The from address for transactional emails.
# SMTP_CONNECTION_URL="" # The SMTP connection URL for transactional emails.
Expand Down Expand Up @@ -51,6 +53,16 @@ REDIS_URL="redis://localhost:6379"
# STRIPE_WEBHOOK_SECRET: z.string().optional(),
# STRIPE_ENABLE_TEST_CLOCKS=false

# Agents

# GITHUB_APP_ID=
# GITHUB_APP_PRIVATE_KEY_PATH=
# GITHUB_APP_WEBHOOK_SECRET=
# OPENAI_API_KEY=
REVIEW_AGENT_LOGGING_ENABLED=true
REVIEW_AGENT_AUTO_REVIEW_ENABLED=false
REVIEW_AGENT_REVIEW_COMMAND=review

# Misc

# Generated using:
Expand Down Expand Up @@ -78,4 +90,4 @@ SOURCEBOT_TELEMETRY_DISABLED=true # Disables telemetry collection
# NODE_ENV=
# SOURCEBOT_TENANCY_MODE=single

# NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=
# NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=
7 changes: 7 additions & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@
}
]
},
{
"group": "Agents",
"pages": [
"docs/agents/overview",
"docs/agents/review-agent"
]
},
{
"group": "More",
"pages": [
Expand Down
17 changes: 17 additions & 0 deletions docs/docs/agents/overview.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
title: "Agents Overview"
sidebarTitle: "Overview"
---

<Note>
Have an idea for an agent that we haven't built? Submit a [feature request](https://github.com/sourcebot-dev/sourcebot/discussions/categories/feature-requests) on our GitHub
</Note>

Agents are automations that leverage the code indexed on Sourcebot to perform a specific task. Once you've setup Sourcebot, check out the
guides below to configure additional agents.

<CardGroup cols={2}>
<Card horizontal title="Review Agent" icon="gear" href="/docs/agents/review-agent">
An AI agent that reviews your PRs to identify issues
</Card>
</CardGroup>
93 changes: 93 additions & 0 deletions docs/docs/agents/review-agent.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
title: AI Code Review Agent
sidebarTitle: AI Code Review Agent
---

<Note>
This agent sends data to OpenAI (through an API key you supply) to perform code reviews. This data includes code from the PR being reviewed, as well as additional relevant context from your
codebase that the agent may fetch to perform the review.
</Note>

This agent provides codebase-aware reviews for your PRs. For each diff, this agent fetches relevant context from Sourcebot and feeds it into an LLM for a detailed review of your changes.

The AI Code Review Agent is open source and packaged in [Sourcebot](https://github.com/sourcebot-dev/sourcebot). To get started using this agent, [deploy Sourcebot](/self-hosting/overview)
and then follow the configuration instructions below.

![AI Code Review Agent Example](/images/review_agent_example.png)

# Configure

This agent currently only supports reviewing GitHub PRs. You configure the agent by creating a GitHub app, installing it into your GitHub organization, and then giving your app info to Sourcebot.

Before you get started, make sure you have an OpenAPI account that you can create an OpenAPI key with.

<Steps>
<Step title="Register a GitHub app">
Follow the official GitHub guide for [registering a GitHub app](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app)

- GitHub App name: You can make this whatever you want (ex. Sourcebot Review Agent)
- Homepage URL: You can make this whatever you want (ex. https://www.sourcebot.dev/)
- Webhook URL (**IMPORTANT**): You must set this to point to your Sourcebot deployment at /api/webhook (ex. https://sourcebot.aperture.com/api/webhook). Your Sourcebot deployment must be able to accept requests from GitHub
(either github.com or your self-hosted enterprise server) for this to work. If you're running Sourcebot locally, you can [use smee](https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/quickstart#step-2-get-a-webhook-proxy-url) to [forward webhooks](https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/quickstart#step-6-start-your-server) to your local deployment.
- Permissions
- Pull requests: Read & Write
- Issues: Read & Write
- Contents: Read
- Events:
- Pull request
- Issue comment
</Step>
<Step title="Install the GitHub app in your organization">
Navigate to your new [GitHub app's page](https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/quickstart#navigate-to-your-app-settings) and press `Install`
</Step>
<Step title="Configure the environment variables in Sourcebot">
Sourcebot requires the following environment variables to begin reviewing PRs through your new GitHub app:

- `GITHUB_APP_ID`: The client ID of your GitHub app. Can be found in your [app settings](https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/quickstart#navigate-to-your-app-settings)
- `GITHUB_APP_WEBHOOK_SECRET`: A random webhook secret that you've set in your [app settings](https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/quickstart#navigate-to-your-app-settings). This can be anything (ex. `python -c "import secrets; print(secrets.token_hex(10))"` to generate a random secret)
- `GITHUB_APP_PRIVATE_KEY_PATH`: The path to your app's private key. If you're running Sourcebot from a container, this is the path to this file from within your container
(ex `/data/review-agent-key.pem`). You must copy the private key file into the directory you mount to Sourcebot (similar to the config file).

You can generate a private key file for your app in the [app settings](https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/quickstart#navigate-to-your-app-settings). You must copy this private key file into the
directory that you mount to Sourcebot
![GitHub App Private Key](/images/github_app_private_key.png)
- `OPENAI_API_KEY`: Your OpenAI API key
- `REVIEW_AGENT_AUTO_REVIEW_ENABLED` (default: `false`): If enabled, the review agent will automatically review any new or updated PR. If disabled, you must invoke it using the command defined by `REVIEW_AGENT_REVIEW_COMMAND`
- `REVIEW_AGENT_REVIEW_COMMAND` (default: `review`): The command that invokes the review agent (ex. `/review`) when a user comments on the PR. Don't include the slash character in this value.

You can find an example docker compose file below.
- This docker compose file is placed in `~/sourcebot_review_agent_workspace`, and I'm mounting that directory to Sourcebot
- The config and the app private key files are placed in this directory
- The paths to these files are given to Sourcebot relative to `/data` since that's the directory in Sourcebot that I'm mounting to

```yaml
services:
sourcebot:
image: ghcr.io/sourcebot-dev/sourcebot:latest
pull_policy: always
container_name: sourcebot
ports:
- "3000:3000"
volumes:
- "/Users/michael/sourcebot_review_agent_workspace:/data"
environment:
CONFIG_PATH: "/data/config.json"
GITHUB_APP_ID: "my-github-app-id"
GITHUB_APP_WEBHOOK_SECRET: "my-github-app-webhook-secret"
GITHUB_APP_PRIVATE_KEY_PATH: "/data/review-agent-key.pem"
OPENAI_API_KEY: "sk-proj-my-open-api-key"
```
</Step>
<Step title="Verify configuration">
Navigate to the agents page by pressing `Agents` in the Sourcebot nav menu. If you've configured your environment variables correctly you'll see the following:

![Review Agent Configured](/images/review_agent_configured.png)
</Step>
</Steps>

# Using the agent

The review agent will not automatically review your PRs by default. To enable this feature, set the `REVIEW_AGENT_AUTO_REVIEW_ENABLED` environment variable to true.

You can invoke the review agent manually by commenting `/review` on the PR you'd like it to review. You can configure the command that triggers the agent by changing
the `REVIEW_AGENT_REVIEW_COMMAND` environment variable.
2 changes: 1 addition & 1 deletion docs/docs/more/mcp-server.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ sidebarTitle: Sourcebot MCP server
This feature is only available when [self-hosting](/self-hosting) with [authentication](/self-hosting/more/authentication) disabled.
</Note>

The [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP) is a open standard for providing context to LLMs. The [@sourcebot/mcp](https://www.npmjs.com/package/@sourcebot/mcp) package is a MCP server that enables LLMs to interface with your Sourcebot instance, enabling MCP clients like Cursor, Vscode, and others to have context over your entire codebase.
The [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP) is an open standard for providing context to LLMs. The [@sourcebot/mcp](https://www.npmjs.com/package/@sourcebot/mcp) package is a MCP server that enables LLMs to interface with your Sourcebot instance, enabling MCP clients like Cursor, Vscode, and others to have context over your entire codebase.

## Getting Started

Expand Down
Binary file added docs/images/github_app_private_key.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/review_agent_configured.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/review_agent_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"private": true,
"workspaces": [
"packages/*"
"packages/*",
"packages/agents/*"
],
"scripts": {
"build": "cross-env SKIP_ENV_VALIDATION=1 yarn workspaces foreach -A run build",
Expand All @@ -11,6 +12,7 @@
"dev:zoekt": "yarn with-env zoekt-webserver -index .sourcebot/index -rpc",
"dev:backend": "yarn with-env yarn workspace @sourcebot/backend dev:watch",
"dev:web": "yarn with-env yarn workspace @sourcebot/web dev",
"dev:review-agent": "yarn with-env yarn workspace @sourcebot/review-agent dev",
"watch:mcp": "yarn workspace @sourcebot/mcp build:watch",
"watch:schemas": "yarn workspace @sourcebot/schemas watch",
"dev:prisma:migrate:dev": "yarn with-env yarn workspace @sourcebot/db prisma:migrate:dev",
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export const env = createEnv({
SOURCEBOT_INSTALL_ID: z.string().default("unknown"),
NEXT_PUBLIC_SOURCEBOT_VERSION: z.string().default("unknown"),

DATA_CACHE_DIR: z.string(),

NEXT_PUBLIC_POSTHOG_PAPIK: z.string().optional(),

FALLBACK_GITHUB_CLOUD_TOKEN: z.string().optional(),
Expand Down
18 changes: 3 additions & 15 deletions packages/backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import path from 'path';
import { AppContext } from "./types.js";
import { main } from "./main.js"
import { PrismaClient } from "@sourcebot/db";
import { env } from "./env.js";

// Register handler for normal exit
process.on('exit', (code) => {
Expand Down Expand Up @@ -36,22 +37,9 @@ process.on('unhandledRejection', (reason, promise) => {
process.exit(1);
});

console.log(process.cwd());

const parser = new ArgumentParser({
description: "Sourcebot backend tool",
});

type Arguments = {
cacheDir: string;
}

parser.add_argument("--cacheDir", {
help: "Path to .sourcebot cache directory",
required: true,
});
const args = parser.parse_args() as Arguments;

const cacheDir = args.cacheDir;
const cacheDir = env.DATA_CACHE_DIR;
const reposPath = path.join(cacheDir, 'repos');
const indexPath = path.join(cacheDir, 'index');

Expand Down
2 changes: 1 addition & 1 deletion packages/mcp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@
"sourcebot",
"code-intelligence"
]
}
}
6 changes: 5 additions & 1 deletion packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@
"next-auth": "^5.0.0-beta.25",
"next-themes": "^0.3.0",
"nodemailer": "^6.10.0",
"octokit": "^4.1.3",
"openai": "^4.98.0",
"parse-diff": "^0.11.1",
"posthog-js": "^1.161.5",
"pretty-bytes": "^6.1.1",
"psl": "^1.15.0",
Expand All @@ -135,7 +138,8 @@
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7",
"usehooks-ts": "^3.1.0",
"zod": "^3.24.3"
"zod": "^3.24.3",
"zod-to-json-schema": "^3.24.5"
},
"devDependencies": {
"@types/micromatch": "^4.0.9",
Expand Down
69 changes: 69 additions & 0 deletions packages/web/src/app/[domain]/agents/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import Link from "next/link";
import { NavigationMenu } from "../components/navigationMenu";
import { FaCogs } from "react-icons/fa";
import { env } from "@/env.mjs";

const agents = [
{
id: "review-agent",
name: "Review Agent",
description: "An AI code review agent that reviews your PRs. Uses the code indexed on Sourcebot to provide codebase-wide context.",
requiredEnvVars: ["GITHUB_APP_ID", "GITHUB_APP_WEBHOOK_SECRET", "GITHUB_APP_PRIVATE_KEY_PATH", "OPENAI_API_KEY"],
configureUrl: "https://docs.sourcebot.dev/docs/agents/review-agent"
},
];

export default function AgentsPage({ params: { domain } }: { params: { domain: string } }) {
return (
<div className="flex flex-col items-center overflow-hidden min-h-screen">
<NavigationMenu domain={domain} />
<div className="w-full max-w-6xl px-4 mt-12 mb-24">
<div
className={
agents.length === 1
? "flex justify-center items-center min-h-[60vh]"
: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10"
}
>
{agents.map((agent) => (
<div
key={agent.id}
className={
agents.length === 1
? "relative flex flex-col items-center border border-border rounded-2xl p-8 bg-card shadow-xl w-full max-w-xl"
: "relative flex flex-col items-center border border-border rounded-2xl p-8 bg-card shadow-xl"
}
>
{/* Name and description */}
<div className="flex flex-col items-center w-full">
<h2 className="font-bold text-2xl mb-4 mt-2 text-center text-foreground drop-shadow-sm">
{agent.name}
</h2>
<p className="text-base text-muted-foreground text-center mb-4 min-h-[56px]">
{agent.description}
</p>
</div>
{/* Actions */}
<div className="flex flex-col items-center w-full mt-2">
{agent.requiredEnvVars.every(envVar => envVar in env && env[envVar as keyof typeof env] !== undefined) ? (
<div className="text-green-500 font-semibold">
Agent is configured and accepting requests on /api/webhook
</div>
) : (
<Link
href={agent.configureUrl}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center gap-2 px-5 py-2.5 rounded-md bg-primary text-primary-foreground font-mono font-semibold text-base border border-primary shadow-sm hover:bg-primary/80 focus:outline-none focus:ring-2 focus:ring-primary/60 transition w-1/2"
>
<FaCogs className="text-lg" /> Configure
</Link>
)}
</div>
</div>
))}
</div>
</div>
</div>
);
}
3 changes: 2 additions & 1 deletion packages/web/src/app/[domain]/browse/[...path]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { TopBar } from "@/app/[domain]/components/topBar";
import { Separator } from '@/components/ui/separator';
import { getFileSource } from '@/features/search/fileSourceApi';
import { listRepositories } from '@/features/search/listReposApi';
import { base64Decode, isServiceError } from "@/lib/utils";
import { isServiceError } from "@/lib/utils";
import { base64Decode } from "@/lib/utils";
import { CodePreview } from "./codePreview";
import { ErrorCode } from "@/lib/errorCodes";
import { LuFileX2, LuBookX } from "react-icons/lu";
Expand Down
7 changes: 7 additions & 0 deletions packages/web/src/app/[domain]/components/navigationMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ export const NavigationMenu = async ({
</NavigationMenuLink>
</Link>
</NavigationMenuItem>
<NavigationMenuItem>
<Link href={`/${domain}/agents`} legacyBehavior passHref>
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
Agents
</NavigationMenuLink>
</Link>
</NavigationMenuItem>
<NavigationMenuItem>
<Link href={`/${domain}/repos`} legacyBehavior passHref>
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/app/api/(server)/source/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const POST = async (request: NextRequest) => {
}


const postSource = (request: FileSourceRequest, domain: string) => sew(() =>
export const postSource = (request: FileSourceRequest, domain: string) => sew(() =>
withAuth(async (session) =>
withOrgMembership(session, domain, async ({ orgId }) => {
const response = await getFileSource(request, orgId);
Expand Down
Loading