mirror of
https://github.com/vercel/chat.git
synced 2026-09-14 18:32:29 +08:00
4ac0455134
## summary adds `onMessageUpdated` and `onMessageDeleted`, so a bot can react when a message is edited or removed. Slack dispatches both today; other adapters can opt in later supersedes #549, which was verified there against real Slack webhooks. reopened from a branch in this repo with the original commits preserved and signed ```typescript bot.onMessageUpdated(async (thread, message, previousMessage) => { await mirror.update(message.id, message.text); }); bot.onMessageDeleted(async (event) => { await mirror.remove(event.messageId); }); ``` both are lifecycle events: they never route through `onNewMessage`, `onNewMention`, or `onSubscribedMessage`, and the concurrency strategies do not apply ### notes - **the bot's own edits are filtered.** slack sends a `message_changed` for every `chat.update`, and post-and-edit streaming calls it once per delta, so without this a single streamed reply would call the handler back repeatedly on its own message - **`previousMessage` is forwarded on edits.** slack sends the pre-edit message and it was being dropped. an edit handler usually needs the before to know what changed, so it is the optional third argument - **the two shapes differ deliberately.** an edit carries a full replacement message, so it gets `(thread, message, previousMessage?)`. a delete has no message, only the id of what was removed, so it gets an event. use `chat.thread(event.threadId)` when a delete handler needs one - **one thread id helper** now serves message, edit, and delete, so an edit cannot resolve to a different thread than the message it edits ## test plan core: - an edit dispatches to `onMessageUpdated` and not to the normal message handlers - the handler receives the pre-edit message as its third argument - the bot's own edits are skipped - a delete dispatches with normalized event data - both run inside the active conversation, so read tools built in these handlers stay scoped slack: - `message_changed` dispatches as an update, `message_deleted` as a delete - `previous_message` is forwarded, and left undefined when slack omits it - hidden unfurl updates stay ignored, hidden real edits still dispatch - message, edit, and delete resolve to one thread id in a flat DM and in a threaded `agent_view` DM verified against a real slack workspace over socket mode: editing and deleting a DM both routed to the same thread id as the original message --------- Co-authored-by: Miłosz Lenczewski <m.lenczewski@tidio.net>
298 lines
9.6 KiB
Plaintext
298 lines
9.6 KiB
Plaintext
---
|
|
title: GitHub
|
|
description: Respond to @mentions in PR and issue comment threads.
|
|
packageName: "@chat-adapter/github"
|
|
slug: github
|
|
type: platform
|
|
logo: github
|
|
tagline: Build bots that respond to pull request and issue comment threads. Treats issues and PRs as threads, comments as messages.
|
|
beta: true
|
|
features:
|
|
postMessage: yes
|
|
editMessage: yes
|
|
deleteMessage: yes
|
|
fileUploads: no
|
|
streaming:
|
|
status: partial
|
|
label: Buffered
|
|
scheduledMessages: no
|
|
cardFormat:
|
|
status: yes
|
|
label: GFM Markdown
|
|
buttons: no
|
|
linkButtons: no
|
|
selectMenus: no
|
|
tables:
|
|
status: yes
|
|
label: GFM
|
|
fields: yes
|
|
imagesInCards: yes
|
|
modals: no
|
|
slashCommands: no
|
|
mentions: yes
|
|
addReactions: yes
|
|
removeReactions:
|
|
status: partial
|
|
label: ""
|
|
typingIndicator: no
|
|
messageUpdatedEvents: no
|
|
messageDeletedEvents: no
|
|
directMessages: no
|
|
ephemeralMessages: no
|
|
userLookup: yes
|
|
parentSubject: yes
|
|
nativeClient: yes
|
|
customApiEndpoint: yes
|
|
fetchMessages: yes
|
|
fetchSingleMessage: no
|
|
fetchThreadInfo: yes
|
|
fetchChannelMessages: yes
|
|
listThreads: yes
|
|
fetchChannelInfo: yes
|
|
postChannelMessage: no
|
|
---
|
|
|
|
## Install
|
|
|
|
<PackageInstall package="@chat-adapter/github" />
|
|
|
|
## Quick start
|
|
|
|
<Callout type="info">
|
|
The adapter auto-detects credentials from `GITHUB_TOKEN` (or `GITHUB_APP_ID`/`GITHUB_PRIVATE_KEY`), `GITHUB_WEBHOOK_SECRET`, and `GITHUB_BOT_USERNAME`.
|
|
</Callout>
|
|
|
|
```typescript title="lib/bot.ts" lineNumbers
|
|
import { Chat } from "chat";
|
|
import { createGitHubAdapter } from "@chat-adapter/github";
|
|
|
|
const bot = new Chat({
|
|
userName: "my-bot",
|
|
adapters: {
|
|
github: createGitHubAdapter(),
|
|
},
|
|
});
|
|
|
|
bot.onNewMention(async (thread, message) => {
|
|
await thread.post("Hello from GitHub!");
|
|
});
|
|
```
|
|
|
|
## Configuration
|
|
|
|
<TypeTable
|
|
type={{
|
|
token: {
|
|
type: "string",
|
|
description:
|
|
"Personal Access Token. Auto-detected from `GITHUB_TOKEN`.",
|
|
},
|
|
appId: {
|
|
type: "string | number",
|
|
description: "GitHub App ID. Auto-detected from `GITHUB_APP_ID`.",
|
|
},
|
|
privateKey: {
|
|
type: "string",
|
|
description:
|
|
"GitHub App private key (PEM). Auto-detected from `GITHUB_PRIVATE_KEY`.",
|
|
},
|
|
installationId: {
|
|
type: "number",
|
|
description:
|
|
"Installation ID. Omit for multi-tenant setups so the adapter resolves it from each webhook.",
|
|
},
|
|
installationToken: {
|
|
type: "string | (() => string | Promise<string>)",
|
|
description:
|
|
"Vercel Connect mode: installation access token (or a resolver invoked per API call). Skips the GitHub App JWT exchange.",
|
|
},
|
|
webhookSecret: {
|
|
type: "string",
|
|
description:
|
|
"Webhook secret. Auto-detected from `GITHUB_WEBHOOK_SECRET`.",
|
|
},
|
|
webhookVerifier: {
|
|
type: "(request, body) => unknown | Promise<unknown>",
|
|
description:
|
|
"Custom verifier used in place of `webhookSecret` (e.g. Vercel Connect OIDC). Takes precedence over the secret; required in Connect mode.",
|
|
},
|
|
userName: {
|
|
type: "string",
|
|
default: '"github-bot"',
|
|
description:
|
|
"Bot username for @mention detection. Auto-detected from `GITHUB_BOT_USERNAME`.",
|
|
},
|
|
apiUrl: {
|
|
type: "string",
|
|
description:
|
|
"Override the GitHub API base URL (e.g. for GitHub Enterprise Server).",
|
|
},
|
|
}}
|
|
/>
|
|
|
|
One of `token`, `appId`+`privateKey`, or `installationToken` (Vercel Connect) is
|
|
required, plus either `webhookSecret` or a `webhookVerifier`.
|
|
|
|
## Authentication
|
|
|
|
### Option A — Personal Access Token
|
|
|
|
Best for personal projects, testing, or single-repo bots.
|
|
|
|
1. Go to [Settings then Developer settings then Personal access tokens](https://github.com/settings/tokens).
|
|
2. Create a token with `repo` scope.
|
|
3. Set `GITHUB_TOKEN`.
|
|
|
|
```typescript
|
|
createGitHubAdapter({
|
|
token: process.env.GITHUB_TOKEN!,
|
|
});
|
|
```
|
|
|
|
### Option B — GitHub App (recommended)
|
|
|
|
Better rate limits, security, and supports multiple installations.
|
|
|
|
**Create the app:**
|
|
|
|
1. Go to [Settings then Developer settings then GitHub Apps then New GitHub App](https://github.com/settings/apps/new).
|
|
2. Set **Webhook URL** to `https://your-domain.com/api/webhooks/github`.
|
|
3. Set a **Webhook secret**.
|
|
4. Permissions: Issues — Read & write, Pull requests — Read & write, Metadata — Read-only.
|
|
5. Subscribe to events: Issue comment, Pull request review comment.
|
|
6. Click **Create GitHub App** and generate a private key.
|
|
|
|
**Install the app:**
|
|
|
|
1. From your app's settings click **Install App** and choose repositories.
|
|
2. Note the **Installation ID** in the URL: `https://github.com/settings/installations/12345678`.
|
|
|
|
**Single-tenant config:**
|
|
|
|
```typescript
|
|
createGitHubAdapter({
|
|
appId: process.env.GITHUB_APP_ID!,
|
|
privateKey: process.env.GITHUB_PRIVATE_KEY!,
|
|
installationId: parseInt(process.env.GITHUB_INSTALLATION_ID!, 10),
|
|
});
|
|
```
|
|
|
|
**Multi-tenant** (omit `installationId`):
|
|
|
|
```typescript
|
|
createGitHubAdapter({
|
|
appId: process.env.GITHUB_APP_ID!,
|
|
privateKey: process.env.GITHUB_PRIVATE_KEY!,
|
|
});
|
|
```
|
|
|
|
The adapter automatically extracts installation IDs from webhooks and caches API clients per-installation.
|
|
|
|
### Option C — Vercel Connect
|
|
|
|
Use [Vercel Connect](https://vercel.com/docs/connect) to source installation access tokens at runtime instead of storing a GitHub App private key. The `connectGitHubAdapter()` helper from [`@vercel/connect/chat`](https://www.npmjs.com/package/@vercel/connect) wires an `installationToken` resolver (skipping the App JWT exchange) and a `webhookVerifier` for Connect trigger-forwarded webhooks:
|
|
|
|
```typescript
|
|
import { createGitHubAdapter } from "@chat-adapter/github";
|
|
import { connectGitHubAdapter } from "@vercel/connect/chat";
|
|
|
|
createGitHubAdapter({
|
|
...connectGitHubAdapter("github/acme-github"),
|
|
userName: "my-bot[bot]",
|
|
});
|
|
```
|
|
|
|
`installationToken` accepts a `string` or `() => string | Promise<string>` resolver invoked per API call. When `webhookVerifier` is set it takes precedence over `webhookSecret` and `GITHUB_WEBHOOK_SECRET`.
|
|
|
|
<Callout type="warn">
|
|
In Connect mode the adapter only holds an installation token, so it can't auto-detect its own bot user id (the `/app` lookup needs the App's JWT). Without it the adapter can't recognize its own comments and will reply to itself in a loop. It learns the id from the first comment it posts, but that's in-memory only — not enough on serverless, where each webhook can hit a fresh instance. Pass `botUserId` (the numeric id of your `…[bot]` user, e.g. from `curl -s 'https://api.github.com/users/your-app%5Bbot%5D'`) so every instance knows it up front:
|
|
|
|
```typescript
|
|
createGitHubAdapter({
|
|
...connectGitHubAdapter("github/acme-github"),
|
|
botUserId: 12345678,
|
|
});
|
|
```
|
|
|
|
Or set the `GITHUB_BOT_USER_ID` environment variable, which the adapter auto-detects.
|
|
</Callout>
|
|
|
|
## Advanced
|
|
|
|
### Installation lookup
|
|
|
|
Resolve the GitHub App installation ID associated with a `Thread` or `Message`:
|
|
|
|
```typescript
|
|
bot.onNewMention(async (thread, message) => {
|
|
const installationIdFromThread = await github.getInstallationId(thread);
|
|
const installationIdFromMessage = await github.getInstallationId(
|
|
message.threadId
|
|
);
|
|
});
|
|
```
|
|
|
|
- **PAT mode** — returns `undefined`.
|
|
- **Single-tenant GitHub App** — returns the configured installation ID.
|
|
- **Multi-tenant** — only succeeds after the adapter has received a webhook for that repository and cached the mapping. Use a persistent state adapter so the mapping survives restarts.
|
|
|
|
### Direct API client
|
|
|
|
Access the underlying [Octokit](https://github.com/octokit/octokit.js) instance via `.octokit`:
|
|
|
|
```typescript
|
|
const github = bot.getAdapter("github").octokit;
|
|
const { data: pulls } = await github.rest.pulls.list({
|
|
owner: "vercel",
|
|
repo: "chat",
|
|
state: "open",
|
|
});
|
|
```
|
|
|
|
PAT and single-tenant App modes return the same client anywhere. Multi-tenant mode requires webhook-handler context — calling `.octokit` outside a handler throws.
|
|
|
|
> The previous `.client` getter still works as a deprecated alias for `.octokit`.
|
|
|
|
### Webhook setup
|
|
|
|
For repository or organization webhooks:
|
|
|
|
1. **Settings** then **Webhooks** then **Add webhook**.
|
|
2. Set payload URL to `https://your-domain.com/api/webhooks/github`.
|
|
3. Set **Content type** to `application/json` (the default `application/x-www-form-urlencoded` does not work).
|
|
4. Set **Secret** to match `webhookSecret`.
|
|
5. Select events: Issue comments, Pull request review comments.
|
|
|
|
GitHub App webhooks are configured during app creation — make sure to select `application/json`.
|
|
|
|
### Thread model
|
|
|
|
| Type | Context | Thread ID format |
|
|
|------|---------|-----------------|
|
|
| PR-level | PR Conversation tab | `github:{owner}/{repo}:{prNumber}` |
|
|
| Review comments | PR Files Changed tab | `github:{owner}/{repo}:{prNumber}:rc:{commentId}` |
|
|
| Issue comments | Issue thread | `github:{owner}/{repo}:issue:{issueNumber}` |
|
|
|
|
### Reactions
|
|
|
|
| SDK emoji | GitHub reaction |
|
|
|-----------|----------------|
|
|
| `thumbs_up` | +1 |
|
|
| `thumbs_down` | -1 |
|
|
| `laugh` | laugh |
|
|
| `confused` | confused |
|
|
| `heart` | heart |
|
|
| `hooray` | hooray |
|
|
| `rocket` | rocket |
|
|
| `eyes` | eyes |
|
|
|
|
## Feature support
|
|
|
|
<FeatureSupport />
|
|
|
|
## Resources
|
|
|
|
- [Ship a GitHub code review bot with Hono and Redis](https://vercel.com/kb/guide/ship-a-github-code-review-bot-with-hono-and-redis?utm_source=chat-sdk_site&utm_medium=docs&utm_campaign=adapter-github&utm_content=ship-a-github-code-review-bot-with-hono-and-redis) — Walks through building a GitHub bot that reviews pull requests on demand. When a user @mentions the bot on a PR, Chat SDK picks up the mention, spins up a Vercel Sandbox with the repo cloned, and uses AI SDK to analyze the diff.
|
|
|
|
See all guides and templates on the [resources](/resources?utm_source=chat-sdk_site&utm_medium=docs&utm_campaign=adapter-github&utm_content=resources) page.
|