chat 4.26.0 → 4.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-OPV5U4WG.js → chunk-AN7MRAVW.js} +39 -0
- package/dist/index.d.ts +220 -6
- package/dist/index.js +321 -50
- package/dist/{jsx-runtime-DxATbnrP.d.ts → jsx-runtime-Co9uV6l7.d.ts} +39 -5
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +1 -1
- package/docs/adapters.mdx +28 -28
- package/docs/api/chat.mdx +85 -1
- package/docs/api/message.mdx +5 -1
- package/docs/api/thread.mdx +23 -1
- package/docs/contributing/publishing.mdx +33 -0
- package/docs/files.mdx +1 -0
- package/docs/getting-started.mdx +1 -11
- package/docs/meta.json +0 -2
- package/docs/modals.mdx +73 -1
- package/docs/streaming.mdx +13 -5
- package/docs/threads-messages-channels.mdx +34 -0
- package/package.json +3 -2
- package/resources/guides/create-a-discord-support-bot-with-nuxt-and-redis.md +180 -0
- package/resources/guides/how-to-build-a-slack-bot-with-next-js-and-redis.md +134 -0
- package/resources/guides/how-to-build-an-ai-agent-for-slack-with-chat-sdk-and-ai-sdk.md +220 -0
- package/resources/guides/run-and-track-deploys-from-slack.md +270 -0
- package/resources/guides/ship-a-github-code-review-bot-with-hono-and-redis.md +147 -0
- package/resources/guides/triage-form-submissions-with-chat-sdk.md +178 -0
- package/resources/templates.json +19 -0
- package/docs/guides/code-review-hono.mdx +0 -241
- package/docs/guides/discord-nuxt.mdx +0 -227
- package/docs/guides/durable-chat-sessions-nextjs.mdx +0 -337
- package/docs/guides/meta.json +0 -10
- package/docs/guides/scheduled-posts-neon.mdx +0 -447
- package/docs/guides/slack-nextjs.mdx +0 -234
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# Run and track deploys from Slack
|
|
2
|
+
|
|
3
|
+
**Author:** Ben Sabic
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Build a Slack bot that orchestrates your entire deployment lifecycle in the workspace your team already uses daily.
|
|
8
|
+
|
|
9
|
+
This guide walks you through a Slack bot that orchestrates the entire deploy lifecycle from a single slash command. Type `/deploy staging` and the bot:
|
|
10
|
+
|
|
11
|
+
* Dispatches a GitHub Actions workflow
|
|
12
|
+
|
|
13
|
+
* Polls the run until it completes
|
|
14
|
+
|
|
15
|
+
* Comments on the relevant PR(s)
|
|
16
|
+
|
|
17
|
+
* Updates linked Linear issue(s)
|
|
18
|
+
|
|
19
|
+
* Posts a summary card back to Slack
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
For production deploys, the bot gates the workflow with an approval step, so the deploy proceeds only after an authorized team member approves it.
|
|
23
|
+
|
|
24
|
+
The bot is built with [Chat SDK](https://chat-sdk.dev) and [Vercel Workflow](https://vercel.com/workflow). Chat SDK handles the Slack interaction layer (cards, buttons, modals, and slash commands), while Vercel Workflow handles stateful orchestration (pausing for approval, polling GitHub, and resuming when events arrive). You write the deploy pipeline as a single function that pauses and resumes over minutes or hours without a database or state machine.
|
|
25
|
+
|
|
26
|
+
Deploy the template now, or read on for a deeper look at how it all works.
|
|
27
|
+
|
|
28
|
+
## Quick start with an AI coding agent
|
|
29
|
+
|
|
30
|
+
If you're working with an AI coding agent like Claude Code or Cursor, you can clone the template and hand off implementation with this prompt:
|
|
31
|
+
|
|
32
|
+
`I want to build a deploy bot for Slack using Chat SDK and Vercel Workflow. Clone the template repo at https://github.com/vercel-labs/chat-sdk-deploy-bot, install dependencies with pnpm, and walk me through setting up the environment variables in .env.local. I need a Slack app, a GitHub fine-grained personal access token with Actions (read/write), Contents (read), Issues (write), and Pull requests (read) permissions, and Redis (Upstash) configured. After setup, help me deploy it to Vercel and test the /deploy slash command. When searching for information, check for applicable skill(s) first and review local documentation.`
|
|
33
|
+
|
|
34
|
+
### Vercel Plugin
|
|
35
|
+
|
|
36
|
+
Turn your agent into a Vercel expert with this [plugin](https://vercel.com/docs/agent-resources/vercel-plugin). The [Chat SDK](https://skills.sh/vercel/chat/chat-sdk) and [Workflow](https://skills.sh/vercel/workflow/workflow) skills are both included.
|
|
37
|
+
|
|
38
|
+
`npx plugins add vercel/vercel-plugin`
|
|
39
|
+
|
|
40
|
+
## Setup and deployment
|
|
41
|
+
|
|
42
|
+
### What you need before deploying
|
|
43
|
+
|
|
44
|
+
You'll need accounts with these services:
|
|
45
|
+
|
|
46
|
+
* **Slack** for the bot interface. Create a new app at [api.slack.com/apps](https://api.slack.com/apps).
|
|
47
|
+
|
|
48
|
+
* **GitHub** for workflow dispatch. You'll need a fine-grained personal access token for the target repository.
|
|
49
|
+
|
|
50
|
+
* **Redis** for Chat SDK state and Vercel Workflow. Any Redis provider works. [Upstash](https://upstash.com) supports serverless deployments and has a free tier.
|
|
51
|
+
|
|
52
|
+
* **Linear** (optional) for issue tracking. Set `LINEAR_API_KEY` to enable it.
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
### Configure your Slack app
|
|
56
|
+
|
|
57
|
+
1. Create a new Slack app from a manifest at [api.slack.com/apps](https://api.slack.com/apps). Use the [slack-manifest.json](https://github.com/vercel-labs/chat-sdk-deploy-bot/blob/main/slack-manifest.json) file included in the template repo. Replace the `https://example.com` URLs with your production domain (e.g. `https://your-app.vercel.app/api/webhooks/slack`).
|
|
58
|
+
|
|
59
|
+
2. Install the app in your workspace and copy the **Bot User OAuth Token**.
|
|
60
|
+
|
|
61
|
+
3. Copy the **Signing Secret** from the **Basic Information** page.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
### Configure GitHub
|
|
65
|
+
|
|
66
|
+
1. Create a fine-grained [personal access token](https://github.com/settings/tokens) for the target repository with these permissions:
|
|
67
|
+
|
|
68
|
+
* Actions: read and write
|
|
69
|
+
|
|
70
|
+
* Contents: read
|
|
71
|
+
|
|
72
|
+
* Issues: write
|
|
73
|
+
|
|
74
|
+
* Pull requests: read
|
|
75
|
+
|
|
76
|
+
2. Configure the token for a repository that has a workflow triggered with `workflow_dispatch`. Here's an example:
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
`name: Deploy on: workflow_dispatch: inputs: environment: description: Target environment required: true type: choice options: - staging - production deploy_id: description: Optional deploy correlation ID required: false type: string run-name: Deploy ${{ inputs.environment }} (${{ inputs.deploy_id || github.sha }})`
|
|
80
|
+
|
|
81
|
+
The `deploy_id` input is optional, but including it in `run-name` helps the bot reliably match the run it dispatched against other concurrent runs.
|
|
82
|
+
|
|
83
|
+
If you want the bot to comment on GitHub PRs as a thread (with webhook-driven replies):
|
|
84
|
+
|
|
85
|
+
1. Add a repository webhook pointing at `https://<your-domain>/api/webhooks/github`
|
|
86
|
+
|
|
87
|
+
2. Set the content type to `application/json`
|
|
88
|
+
|
|
89
|
+
3. Use the same secret as `GITHUB_WEBHOOK_SECRET`
|
|
90
|
+
|
|
91
|
+
4. Subscribe to `issue_comment` and `pull_request_review_comment` events
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
### Configure Linear (optional)
|
|
95
|
+
|
|
96
|
+
Set `LINEAR_API_KEY` to enable Linear integration. No separate webhook setup is required.
|
|
97
|
+
|
|
98
|
+
The bot extracts issue keys from branch names and commit messages using a team prefix (defaults to `ENG`, configurable via `LINEAR_TEAM_PREFIX`). On successful deploys, staging deploys comment on linked issues. Production deploys comment and transition issues to the state configured in `LINEAR_PRODUCTION_STATE` (defaults to `Done`).
|
|
99
|
+
|
|
100
|
+
For the bot to know which commits are new in each deploy, your deploy pipeline must maintain four git tags in the target repo:
|
|
101
|
+
|
|
102
|
+
* `deploy/staging/previous`
|
|
103
|
+
|
|
104
|
+
* `deploy/staging/latest`
|
|
105
|
+
|
|
106
|
+
* `deploy/production/previous`
|
|
107
|
+
|
|
108
|
+
* `deploy/production/latest`
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
The bot compares `previous` to `latest` to find the commit range. It doesn't create or move these tags itself, so your CI pipeline should update them as part of the deploy process. If the tags don't exist, the bot skips Linear updates rather than guessing.
|
|
112
|
+
|
|
113
|
+
### Environment variables
|
|
114
|
+
|
|
115
|
+
| Variable | Required | Purpose |
|
|
116
|
+
| ------------------------- | -------- | -------------------------------------------------------------- |
|
|
117
|
+
| `SLACK_BOT_TOKEN` | Yes | Bot User OAuth Token (`xoxb-...`) |
|
|
118
|
+
| `SLACK_SIGNING_SECRET` | Yes | Request verification from the **Basic Information** page |
|
|
119
|
+
| `GITHUB_TOKEN` | Yes | Fine-grained personal access token (`github_pat_...`) |
|
|
120
|
+
| `GITHUB_WEBHOOK_SECRET` | Yes | Secret for verifying GitHub webhook payloads |
|
|
121
|
+
| `GITHUB_REPO_OWNER` | Yes | Repository owner or organization |
|
|
122
|
+
| `GITHUB_REPO_NAME` | Yes | Repository name |
|
|
123
|
+
| `GITHUB_WORKFLOW_ID` | Yes | Workflow filename (e.g. `deploy.yml`) or numeric ID |
|
|
124
|
+
| `REDIS_URL` | Yes | Redis connection string |
|
|
125
|
+
| `LINEAR_API_KEY` | No | Enables Linear integration (`lin_api_...`) |
|
|
126
|
+
| `LINEAR_TEAM_PREFIX` | No | Issue key prefix (default: `ENG`) |
|
|
127
|
+
| `LINEAR_PRODUCTION_STATE` | No | State to transition prod issues to (default: `Done`) |
|
|
128
|
+
| `DEPLOY_PROD_ALLOWED` | No | Comma-separated Slack user IDs allowed to trigger prod deploys |
|
|
129
|
+
| `DEPLOY_PROD_APPROVERS` | No | Comma-separated Slack user IDs allowed to approve prod deploys |
|
|
130
|
+
|
|
131
|
+
If `DEPLOY_PROD_ALLOWED` is empty or unset, nobody can trigger production deploys. If `DEPLOY_PROD_APPROVERS` is empty or unset, nobody can approve them. Staging deploys are available to everyone.
|
|
132
|
+
|
|
133
|
+
### Deploy to Vercel
|
|
134
|
+
|
|
135
|
+
[Deploy the bot with one click](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel-labs%2Fchat-sdk-deploy-bot&env=SLACK_BOT_TOKEN,SLACK_SIGNING_SECRET,GITHUB_TOKEN,GITHUB_WEBHOOK_SECRET,GITHUB_REPO_OWNER,GITHUB_REPO_NAME,GITHUB_WORKFLOW_ID,REDIS_URL), or clone the repo and deploy manually:
|
|
136
|
+
|
|
137
|
+
`git clone https://github.com/vercel-labs/chat-sdk-deploy-bot.git cd chat-sdk-deploy-bot pnpm install vercel`
|
|
138
|
+
|
|
139
|
+
After deploying, update your Slack app's request URLs to point to your production domain: `https://<your-vercel-domain>/api/webhooks/slack`.
|
|
140
|
+
|
|
141
|
+
### Test the slash command
|
|
142
|
+
|
|
143
|
+
Open Slack and type:
|
|
144
|
+
|
|
145
|
+
`/deploy staging`
|
|
146
|
+
|
|
147
|
+
The bot should post a deploy card to the channel and dispatch your GitHub Actions workflow. You'll see status updates in the Slack thread as the run progresses, followed by a summary card when it completes.
|
|
148
|
+
|
|
149
|
+
### Local development
|
|
150
|
+
|
|
151
|
+
`git clone https://github.com/vercel-labs/chat-sdk-deploy-bot.git cd chat-sdk-deploy-bot pnpm install cp .env.example .env.local pnpm dev`
|
|
152
|
+
|
|
153
|
+
This starts a Next.js dev server. To receive Slack webhooks locally, use [ngrok](https://ngrok.com) to create a public tunnel:
|
|
154
|
+
|
|
155
|
+
`ngrok http 3000`
|
|
156
|
+
|
|
157
|
+
Then update your Slack app's request URLs to the ngrok URL (e.g. `https://abc123.ngrok-free.dev/api/webhooks/slack`).
|
|
158
|
+
|
|
159
|
+
## How the deploy bot works
|
|
160
|
+
|
|
161
|
+
The bot has three interfaces: Slack for user interaction, GitHub for dispatching and monitoring workflows, and (optionally) Linear for issue tracking. Here's the flow:
|
|
162
|
+
|
|
163
|
+
1. A user types `/deploy staging`, `/deploy production`, or `/deploy` (which opens a modal with environment and branch options)
|
|
164
|
+
|
|
165
|
+
2. For staging deploys, the bot posts a deploy card to Slack and immediately dispatches a GitHub Actions workflow
|
|
166
|
+
|
|
167
|
+
3. For production deploys, the bot adds Approve and Cancel buttons to the card and pauses. The workflow only continues if an authorized approver clicks Approve
|
|
168
|
+
|
|
169
|
+
4. Once dispatched, the bot polls the GitHub Actions run every 5 seconds for up to 60 minutes, updating a status message in Slack as it progresses
|
|
170
|
+
|
|
171
|
+
5. When the run completes, the bot comments on associated GitHub PRs and (if Linear is enabled) comments on linked issues and transitions production issues to your configured done state
|
|
172
|
+
|
|
173
|
+
6. The bot posts a final summary card to Slack with the environment, branch, commit, duration, linked issues, and a link to the workflow run
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
[Vercel Workflow](https://vercel.com/workflow) makes this possible. A Vercel Workflow function can suspend itself mid-execution and resume later with full state preserved. The approval gate and the polling loop are both regular code. The function pauses while waiting for a button click, resumes when it arrives, then loops while polling GitHub. No cron jobs, no queues, no external state store.
|
|
177
|
+
|
|
178
|
+
## Code walkthrough
|
|
179
|
+
|
|
180
|
+
The template is a Next.js app. The bot logic lives in `lib/` (setup, handlers, and integrations) and `workflows/` (stateful deploy orchestration).
|
|
181
|
+
|
|
182
|
+
### Building the bot
|
|
183
|
+
|
|
184
|
+
The bot is a Chat SDK instance with adapters for Slack, GitHub, and optionally Linear, plus Redis-backed state:
|
|
185
|
+
|
|
186
|
+
`import { Chat } from "chat"; import { createGitHubAdapter } from "@chat-adapter/github"; import { createLinearAdapter } from "@chat-adapter/linear"; import { createSlackAdapter } from "@chat-adapter/slack"; import { createRedisState } from "@chat-adapter/state-redis"; const adapters = { github: createGitHubAdapter(), ...(LINEAR_ENABLED ? { linear: createLinearAdapter() } : {}), slack: createSlackAdapter(), }; export const bot = new Chat<typeof adapters, DeployThreadState>({ adapters, state: createRedisState(), userName: "deploy-bot", }).registerSingleton();`
|
|
187
|
+
|
|
188
|
+
Each deploy lives in a Slack thread with typed state (environment, branch, commit SHA, and the Slack user ID of whoever ran `/deploy`). This state is stored in Redis via Chat SDK's state adapter, so the approval handler and the workflow can coordinate without passing data through button payloads alone.
|
|
189
|
+
|
|
190
|
+
### Slash command and permissions
|
|
191
|
+
|
|
192
|
+
The bot registers a `/deploy` slash command with two paths. If the user provides an argument (`/deploy staging` or `/deploy production`), the bot deploys immediately on the `main` branch. If no argument is given, the bot opens a modal where the user can pick an environment and optionally specify a branch:
|
|
193
|
+
|
|
194
|
+
`bot.onSlashCommand("/deploy", async (event) => { const args = event.text.trim().toLowerCase(); if (!args) { await event.openModal( Modal({ callbackId: "deploy_form", children: [ Select({ id: "environment", label: "Environment", options: [ SelectOption({ label: "Staging", value: "staging" }), SelectOption({ label: "Production", value: "production" }), ], }), TextInput({ id: "branch", label: "Branch", optional: true, placeholder: "main", }), ], submitLabel: "Deploy", title: "Deploy", }) ); return; } const environment = args === "production" || args === "prod" ? "production" : "staging"; // ... permission check, payload build, workflow start });`
|
|
195
|
+
|
|
196
|
+
The bot resolves the HEAD commit for the branch, posts a deploy card to Slack, and starts the Vercel Workflow. Staging deploys are open to everyone. Production deploys are gated by `DEPLOY_PROD_ALLOWED` (who can trigger) and `DEPLOY_PROD_APPROVERS` (who can approve). When a permission check fails, the bot sends an ephemeral message visible only to the user who tried.
|
|
197
|
+
|
|
198
|
+
### The deploy workflow
|
|
199
|
+
|
|
200
|
+
The deploy workflow is the core of the bot.
|
|
201
|
+
|
|
202
|
+
It's a single function, marked with `"use workflow"`, that orchestrates the entire deploy lifecycle:
|
|
203
|
+
|
|
204
|
+
`export const deployWorkflow = async (rawPayload: string) => { "use workflow"; const parsed: unknown = JSON.parse(rawPayload); if (!isDeployWorkflowPayload(parsed)) { throw new Error("Invalid deploy workflow payload"); } const { thread: serializedThread, ...deploy } = parsed; // Gate production behind approval if (deploy.environment === "production") { const approved = await runApprovalGate(serializedThread, deploy); if (!approved) return; } // Dispatch and find the GitHub Actions run const githubRunId = await findGitHubRun(serializedThread, deploy); if (githubRunId === null) return; // Poll until complete (up to 60 minutes) const result = await pollUntilComplete(deploy, githubRunId); // Notify Linear and GitHub const { prCount, resolved } = await notifyExternalSystems( serializedThread, deploy, result ); // Post summary card await postFinalSummary(serializedThread, deploy, result, resolved, prCount); };`
|
|
205
|
+
|
|
206
|
+
This reads like sequential code, but it may take an hour to finish. Vercel Workflow handles the suspend-and-resume mechanics. When the function calls `sleep("5s")` during polling, or waits for a hook event during approval, it suspends. When the timer fires or the webhook arrives, it resumes exactly where it left off with all variables intact.
|
|
207
|
+
|
|
208
|
+
### Approval gate
|
|
209
|
+
|
|
210
|
+
For production deploys, the workflow creates a hook and waits:
|
|
211
|
+
|
|
212
|
+
`const runApprovalGate = async (serializedThread, deploy) => { const { workflowRunId } = getWorkflowMetadata(); await postApprovalCard(serializedThread, deploy, workflowRunId); using hook = createHook<ApprovalPayload>({ token: workflowRunId }); for await (const event of hook) { if (event.approved) return true; return false; } return false; };`
|
|
213
|
+
|
|
214
|
+
`createHook` registers a listener with a unique token (the workflow run ID). The workflow suspends at the `for await` loop. When someone clicks Approve in Slack, the action handler calls `resumeHook` with that same token, and the workflow picks up with `event.approved` set to `true`. If they click Cancel, it resumes with `false` and the workflow exits.
|
|
215
|
+
|
|
216
|
+
Only the person who triggered the deploy can cancel it. Anyone in the `DEPLOY_PROD_APPROVERS` list can approve.
|
|
217
|
+
|
|
218
|
+
### GitHub Actions dispatch and polling
|
|
219
|
+
|
|
220
|
+
The bot dispatches a `workflow_dispatch` event to your GitHub Actions workflow, then finds the resulting run by matching it against the branch, commit SHA, and a deploy correlation ID:
|
|
221
|
+
|
|
222
|
+
`const findGitHubRun = async (serializedThread, deploy) => { const dispatch = await dispatchGitHubWorkflow(deploy); let githubRunId = null; for (let attempt = 0; attempt < 10; attempt++) { await sleep("3s"); githubRunId = await findDispatchedRunOnce(deploy, dispatch); if (githubRunId !== null) break; } return githubRunId; };`
|
|
223
|
+
|
|
224
|
+
The dispatch function gracefully degrades if your workflow doesn't accept all the expected inputs. It tries `{ environment, deploy_id }` first, then `{ environment }` alone, then no inputs at all. This makes the bot compatible with most existing deploy workflows without changes.
|
|
225
|
+
|
|
226
|
+
Once a run is found, the bot polls every 5 seconds until the run completes or 60 minutes pass. Each `sleep("5s")` call suspends the Vercel Workflow function, and each `fetchRunSnapshot` is marked with `"use step"` so it retries automatically if the GitHub API call fails.
|
|
227
|
+
|
|
228
|
+
### Linear and GitHub notifications
|
|
229
|
+
|
|
230
|
+
On a successful deploy, the bot notifies both Linear and GitHub.
|
|
231
|
+
|
|
232
|
+
Linear issues are found by comparing deploy tags. The bot looks at the commit range between `deploy/{environment}/previous` and `deploy/{environment}/latest` in your repo, extracts Linear issue keys (like `ENG-123`) from branch names and commit messages, then comments on each issue with the deploy details. For production deploys, it also transitions issues to your configured done state.
|
|
233
|
+
|
|
234
|
+
GitHub pull requests associated with the deploy commit receive a comment with a summary table linking back to the workflow run.
|
|
235
|
+
|
|
236
|
+
Both steps are wrapped in `"use step"` directives, so they're retryable and isolated from each other. If the Linear step fails, the GitHub PR comments still proceed.
|
|
237
|
+
|
|
238
|
+
### Summary card
|
|
239
|
+
|
|
240
|
+
When the run completes, the bot posts a final card to the Slack thread with the environment, branch, commit, duration, linked issues, and a link to the GitHub Actions run. If Linear is enabled, the card also includes a table of issue identifiers and titles. If the deploy fails to dispatch or the run can't be matched, the triggerer is notified.
|
|
241
|
+
|
|
242
|
+
## How to add Teams, Discord, or other platforms
|
|
243
|
+
|
|
244
|
+
Chat SDK supports multiple platforms from a single codebase. The cards, fields, and buttons you've already defined render natively on each platform, including Block Kit on Slack, Adaptive Cards on Teams, and Google Chat Cards.
|
|
245
|
+
|
|
246
|
+
To add Microsoft Teams or another platform, register an additional adapter:
|
|
247
|
+
|
|
248
|
+
`import { createTeamsAdapter } from "@chat-adapter/teams"; export const bot = new Chat({ adapters: { github: createGitHubAdapter(), slack: createSlackAdapter(), teams: createTeamsAdapter(), }, state: createRedisState(), userName: "deploy-bot", });`
|
|
249
|
+
|
|
250
|
+
The existing webhook route at `app/api/webhooks/[platform]/route.ts` already uses a dynamic segment, so Teams webhooks would be handled at `/api/webhooks/teams` with no additional routing code.
|
|
251
|
+
|
|
252
|
+
Modals are currently Slack-only, so the `/deploy` command with no arguments (which opens a modal) only works on Slack. On other platforms, require the environment argument.
|
|
253
|
+
|
|
254
|
+
See the [Chat SDK adapter directory](https://chat-sdk.dev/adapters) for the full list of supported platforms.
|
|
255
|
+
|
|
256
|
+
## Related resources
|
|
257
|
+
|
|
258
|
+
* [Chat SDK Deploy Bot template](https://github.com/vercel-labs/chat-sdk-deploy-bot)
|
|
259
|
+
|
|
260
|
+
* [Chat SDK documentation](https://chat-sdk.dev/docs)
|
|
261
|
+
|
|
262
|
+
* [Chat SDK GitHub](https://github.com/vercel/chat)
|
|
263
|
+
|
|
264
|
+
* [Vercel Workflow documentation](https://vercel.com/docs/workflow)
|
|
265
|
+
|
|
266
|
+
* [Workflow SDK](https://useworkflow.dev/)
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
[View full KB sitemap](/kb/sitemap.md)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Ship a GitHub code review bot with Hono and Redis
|
|
2
|
+
|
|
3
|
+
**Author:** Hayden Bleasel, Ben Sabic
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You can ship a GitHub bot that reviews pull requests on demand by combining Chat SDK, Vercel Sandbox, and AI SDK. 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. The sandbox gives the agent safe shell access to the repository, so it can run `git diff`, read source files, and explore the codebase without any code escaping a disposable environment.
|
|
8
|
+
|
|
9
|
+
This guide will walk you through scaffolding a Hono app, configuring a GitHub webhook, wiring up Chat SDK with the GitHub adapter, running a sandboxed AI review, and deploying to Vercel.
|
|
10
|
+
|
|
11
|
+
## Prerequisites
|
|
12
|
+
|
|
13
|
+
Before you begin, make sure you have:
|
|
14
|
+
|
|
15
|
+
* Node.js 18+
|
|
16
|
+
|
|
17
|
+
* [pnpm](https://pnpm.io/) (or npm/yarn)
|
|
18
|
+
|
|
19
|
+
* A GitHub repository where you have admin access
|
|
20
|
+
|
|
21
|
+
* A Redis instance (local or hosted, such as [Upstash](https://vercel.com/marketplace/upstash))
|
|
22
|
+
|
|
23
|
+
* A [Vercel account](https://vercel.com/signup)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## How it works
|
|
27
|
+
|
|
28
|
+
Chat SDK is a unified TypeScript SDK for building chatbots across GitHub, Slack, Teams, and other platforms. You register event handlers (like `onNewMention` and `onSubscribedMessage`), and the SDK routes incoming webhooks to them. The GitHub adapter handles signature verification, event parsing, and routing, while the Redis state adapter tracks which threads your bot has subscribed to and manages distributed locking for concurrent message handling.
|
|
29
|
+
|
|
30
|
+
When someone @mentions the bot on a pull request, the handler fetches the PR's head and base branches, creates a Vercel Sandbox with the repo cloned, and gives an AI SDK `ToolLoopAgent` a `bash` tool scoped to that sandbox. The agent can run `git diff`, read files, and explore the codebase freely. Everything it runs stays inside the sandbox, which is destroyed after the review completes.
|
|
31
|
+
|
|
32
|
+
## Steps
|
|
33
|
+
|
|
34
|
+
### 1\. Scaffold the project and install dependencies
|
|
35
|
+
|
|
36
|
+
Create a new Hono app and add the Chat SDK, AI SDK, and adapter packages:
|
|
37
|
+
|
|
38
|
+
`pnpm create hono my-review-bot cd my-review-bot pnpm add @octokit/rest @vercel/functions @vercel/sandbox ai bash-tool chat @chat-adapter/github @chat-adapter/state-redis`
|
|
39
|
+
|
|
40
|
+
Select the `vercel` template when prompted by `create-hono`. This sets up the project for Vercel deployment with the correct entry point.
|
|
41
|
+
|
|
42
|
+
The `chat` package is the Chat SDK core. The `@chat-adapter/github` and `@chat-adapter/state-redis` packages are the [GitHub platform adapter](https://chat-sdk.dev/adapters/github) and [Redis state adapter](https://chat-sdk.dev/adapters/redis). `@vercel/sandbox` provides the ephemeral execution environment, and `bash-tool` wires it up as an AI SDK tool.
|
|
43
|
+
|
|
44
|
+
### 2\. Configure a GitHub webhook
|
|
45
|
+
|
|
46
|
+
1. Go to your repository **Settings**, then **Webhooks**, then **Add webhook**
|
|
47
|
+
|
|
48
|
+
2. Set **Payload URL** to [`https://your-domain.com/api/webhooks/github`](https://your-domain.com/api/webhooks/github)
|
|
49
|
+
|
|
50
|
+
3. Set **Content type** to `application/json`
|
|
51
|
+
|
|
52
|
+
4. Set a **Secret** and save it. You'll need this as `GITHUB_WEBHOOK_SECRET`
|
|
53
|
+
|
|
54
|
+
5. Under **Which events would you like to trigger this webhook?**, select **Let me select individual events** and check:
|
|
55
|
+
|
|
56
|
+
* **Issue comments** (for @mention on the PR conversation tab)
|
|
57
|
+
|
|
58
|
+
* **Pull request review comments** (for @mention on inline review threads)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
Then gather your credentials:
|
|
62
|
+
|
|
63
|
+
1. Go to [Settings > Developer settings > Personal access tokens](https://github.com/settings/tokens) and create a token with `repo` scope. You'll need this as `GITHUB_TOKEN`
|
|
64
|
+
|
|
65
|
+
2. Copy the **Webhook secret** you set above. You'll need this as `GITHUB_WEBHOOK_SECRET`
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
### 3\. Configure environment variables
|
|
69
|
+
|
|
70
|
+
Create a `.env` file in your project root:
|
|
71
|
+
|
|
72
|
+
`GITHUB_TOKEN=ghp_your_personal_access_token GITHUB_WEBHOOK_SECRET=your_webhook_secret REDIS_URL=redis://localhost:6379 BOT_USERNAME=my-review-bot`
|
|
73
|
+
|
|
74
|
+
The model (`anthropic/claude-sonnet-4.6`) uses AI Gateway. Develop locally by linking to your Vercel project with `vc link`, then pulling your OIDC token with `vc pull --environment development`.
|
|
75
|
+
|
|
76
|
+
### 4\. Define the review function
|
|
77
|
+
|
|
78
|
+
Create the core review logic. This clones the repo into a Vercel Sandbox, then uses AI SDK with a bash tool to let Claude analyze the diff and read files directly.
|
|
79
|
+
|
|
80
|
+
``import { Sandbox } from "@vercel/sandbox"; import { ToolLoopAgent, stepCountIs } from "ai"; import { createBashTool } from "bash-tool"; interface ReviewInput { owner: string; repo: string; prBranch: string; baseBranch: string; } export async function reviewPullRequest(input: ReviewInput): Promise<string> { const { owner, repo, prBranch, baseBranch } = input; const sandbox = await Sandbox.create({ source: { type: "git", url: `https://github.com/${owner}/${repo}`, username: "x-access-token", password: process.env.GITHUB_TOKEN, depth: 50, }, timeout: 5 * 60 * 1000, }); try { await sandbox.runCommand("git", ["fetch", "origin", prBranch, baseBranch]); await sandbox.runCommand("git", ["checkout", prBranch]); const diffResult = await sandbox.runCommand("git", [ "diff", `origin/${baseBranch}...HEAD`, ]); const diff = await diffResult.output("stdout"); const { tools } = await createBashTool({ sandbox }); const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-4.6", tools, stopWhen: stepCountIs(20), }); const result = await agent.generate({ prompt: `You are reviewing a pull request for bugs and issues. Here is the diff for this PR: \`\`\`diff ${diff} \`\`\` Use the bash and readFile tools to inspect any files you need more context on. Look for bugs, security issues, performance problems, and missing error handling. Organize findings by severity (critical, warning, suggestion). If the code looks good, say so.`, }); return result.text; } finally { await sandbox.stop(); } }``
|
|
81
|
+
|
|
82
|
+
The `createBashTool` gives the agent `bash`, `readFile`, and `writeFile` tools, all scoped to the sandbox. The agent can run `git diff`, read source files, and explore the repo freely without any code escaping the sandbox.
|
|
83
|
+
|
|
84
|
+
The function returns the review text instead of posting it directly. This lets the Chat SDK handler post it as a threaded reply.
|
|
85
|
+
|
|
86
|
+
### 5\. Create the bot
|
|
87
|
+
|
|
88
|
+
Create a `Chat` instance with the GitHub adapter. When someone @mentions the bot on a PR, it fetches the PR metadata, runs the review, and posts the result back to the thread.
|
|
89
|
+
|
|
90
|
+
`import { Chat } from "chat"; import { createGitHubAdapter } from "@chat-adapter/github"; import { createRedisState } from "@chat-adapter/state-redis"; import { Octokit } from "@octokit/rest"; import { reviewPullRequest } from "./review"; import type { GitHubRawMessage } from "@chat-adapter/github"; const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }); export const bot = new Chat({ userName: process.env.BOT_USERNAME!, adapters: { github: createGitHubAdapter(), }, state: createRedisState(), }); bot.onNewMention(async (thread, message) => { const raw = message.raw as GitHubRawMessage; const { owner, repo, prNumber } = { owner: raw.repository.owner.login, repo: raw.repository.name, prNumber: raw.prNumber, }; // Fetch PR branch info const { data: pr } = await octokit.pulls.get({ owner, repo, pull_number: prNumber, }); await thread.post("Starting code review..."); await thread.subscribe(); const review = await reviewPullRequest({ owner, repo, prBranch: pr.head.ref, baseBranch: pr.base.ref, }); await thread.post(review); }); bot.onSubscribedMessage(async (thread, message) => { await thread.post( "I've already reviewed this PR. @mention me on a new PR to start another review." ); });`
|
|
91
|
+
|
|
92
|
+
`onNewMention` fires when a user @mentions the bot, for example `@codereview can you review this?`. The handler extracts the PR details from the message's raw payload, runs the sandboxed review, and posts the result. Calling `thread.subscribe()` lets the bot respond to follow-up messages in the same thread.
|
|
93
|
+
|
|
94
|
+
### 6\. Handle the webhook
|
|
95
|
+
|
|
96
|
+
Create the Hono app with a single webhook route that delegates to Chat SDK:
|
|
97
|
+
|
|
98
|
+
`import { Hono } from "hono"; import { waitUntil } from "@vercel/functions"; import { bot } from "./bot"; const app = new Hono(); app.post("/api/webhooks/github", async (c) => { const handler = bot.webhooks.github; if (!handler) { return c.text("GitHub adapter not configured", 404); } return handler(c.req.raw, { waitUntil }); }); export default app;`
|
|
99
|
+
|
|
100
|
+
Chat SDK's GitHub adapter handles signature verification, event parsing, and routing internally. The `waitUntil` option ensures the review completes after the HTTP response is sent. This is required on serverless platforms where the function would otherwise terminate before your handlers finish.
|
|
101
|
+
|
|
102
|
+
### 7\. Test locally
|
|
103
|
+
|
|
104
|
+
1. Start your development server (`pnpm dev`)
|
|
105
|
+
|
|
106
|
+
2. Expose it with a tunnel (e.g. `ngrok http 3000`)
|
|
107
|
+
|
|
108
|
+
3. Update the webhook URL in your GitHub repository settings to your tunnel URL
|
|
109
|
+
|
|
110
|
+
4. Open a pull request
|
|
111
|
+
|
|
112
|
+
5. Comment `@my-review-bot can you review this?`. The bot should respond with "Starting code review..." followed by the full review
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
### 8\. Deploy to Vercel
|
|
116
|
+
|
|
117
|
+
Deploy your bot to Vercel:
|
|
118
|
+
|
|
119
|
+
`vercel deploy`
|
|
120
|
+
|
|
121
|
+
After deployment, set your environment variables in the Vercel dashboard (`GITHUB_TOKEN`, `GITHUB_WEBHOOK_SECRET`, `REDIS_URL`, `BOT_USERNAME`). Update the webhook URL in your GitHub repository settings to your production URL.
|
|
122
|
+
|
|
123
|
+
## Troubleshooting
|
|
124
|
+
|
|
125
|
+
### Bot doesn't respond to mentions
|
|
126
|
+
|
|
127
|
+
Check that your webhook is configured with the **Issue comments** and **Pull request review comments** events, and that the **Payload URL** matches your deployed endpoint. GitHub sends a `ping` event when you first save the webhook, so your server must be running and reachable.
|
|
128
|
+
|
|
129
|
+
### Webhook signature verification fails
|
|
130
|
+
|
|
131
|
+
Confirm that `GITHUB_WEBHOOK_SECRET` matches the secret you set in the webhook configuration. A mismatched or missing secret will cause the adapter to reject incoming webhooks.
|
|
132
|
+
|
|
133
|
+
### Sandbox fails to clone the repo
|
|
134
|
+
|
|
135
|
+
Verify that `GITHUB_TOKEN` has `repo` scope and hasn't expired. For private repositories, the token must also have access to the specific repo. Check the sandbox logs for authentication errors.
|
|
136
|
+
|
|
137
|
+
### Review times out or runs out of steps
|
|
138
|
+
|
|
139
|
+
The sandbox has a 5-minute timeout and the agent stops after 20 steps. For large PRs, increase these limits in `src/review.ts` by adjusting the `timeout` option on `Sandbox.create()` and the `stepCountIs()` value on the agent.
|
|
140
|
+
|
|
141
|
+
### Redis connection errors
|
|
142
|
+
|
|
143
|
+
Verify that `REDIS_URL` is reachable from your deployment environment. The state adapter uses Redis for distributed locking, so the bot won't process messages without a working connection.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
[View full KB sitemap](/kb/sitemap.md)
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Triage form submissions with Chat SDK
|
|
2
|
+
|
|
3
|
+
**Author:** Ben Sabic
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Build a Slack bot that triages form submissions where your team already works. When someone submits a form on your website, the bot posts an interactive card to a Slack channel. A reviewer clicks a button to forward the submission via email, edit it before forwarding, or mark it as spam. The whole workflow happens inside Slack, with no separate dashboard or inbox to monitor.
|
|
8
|
+
|
|
9
|
+
The bot is built with [Chat SDK](https://chat-sdk.dev/), the unified TypeScript SDK for building chatbots that work across Slack, Microsoft Teams, Discord, and other platforms from a single codebase. You write your logic once, and Chat SDK handles the platform-specific details, such as Block Kit on Slack or Adaptive Cards on Teams.
|
|
10
|
+
|
|
11
|
+
Deploy the template now, or read on for a deeper look at how it all works.
|
|
12
|
+
|
|
13
|
+
## Quick start with an AI coding agent
|
|
14
|
+
|
|
15
|
+
If you're working with an AI coding agent like Claude Code or Cursor, you can clone the template and hand off implementation with this prompt:
|
|
16
|
+
|
|
17
|
+
`I want to build a form triage Slack bot using Chat SDK. Clone the template repo at https://github.com/vercel-labs/chat-sdk-form-bot, install dependencies with pnpm, and walk me through setting up the environment variables in .env.local. I need a Slack app, Redis (Upstash), and Resend configured. After setup, help me deploy it to Vercel and test it with a curl POST to the /api/form endpoint. When searching for information, check for applicable skill(s) first and review local documentation.`
|
|
18
|
+
|
|
19
|
+
### Vercel Plugin
|
|
20
|
+
|
|
21
|
+
Turn your agent into a Vercel expert with this [plugin](https://vercel.com/docs/agent-resources/vercel-plugin); the [Chat SDK skill](https://skills.sh/vercel/chat/chat-sdk) is included.
|
|
22
|
+
|
|
23
|
+
`npx plugins add vercel/vercel-plugin`
|
|
24
|
+
|
|
25
|
+
## Setup and deployment
|
|
26
|
+
|
|
27
|
+
### What you need before deploying
|
|
28
|
+
|
|
29
|
+
You'll need accounts with three services:
|
|
30
|
+
|
|
31
|
+
* **Slack** for the bot itself. Create a new app at [api.slack.com/apps](https://api.slack.com/apps).
|
|
32
|
+
|
|
33
|
+
* **Redis** for temporary submission storage. Any Redis provider works. [Upstash](https://vercel.com/marketplace/upstash) supports serverless deployments and has a free tier.
|
|
34
|
+
|
|
35
|
+
* **Resend** to send forwarded submissions via email. Sign up at [resend.com](https://resend.com/) and verify a sending domain.
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
### Configure your Slack app
|
|
39
|
+
|
|
40
|
+
1. Create a new Slack app from a manifest at [api.slack.com/apps](https://api.slack.com/apps). Use the [slack-manifest.json](https://github.com/vercel-labs/chat-sdk-form-bot/blob/main/slack-manifest.json) file included in the template repo. Replace the two `https://example.com` URLs with your production domain (e.g. `https://your-app.vercel.app/api/webhooks/slack`).
|
|
41
|
+
|
|
42
|
+
2. Install the app in your workspace and copy the **Bot User OAuth Token**.
|
|
43
|
+
|
|
44
|
+
3. Copy the **Signing Secret** from the **Basic Information** page.
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
### Environment variables
|
|
48
|
+
|
|
49
|
+
The template needs these environment variables:
|
|
50
|
+
|
|
51
|
+
`# Slack SLACK_BOT_TOKEN=xoxb-... SLACK_SIGNING_SECRET=... SLACK_CHANNEL_ID=C... # Redis REDIS_URL=redis://... # Resend RESEND_API_KEY=re_... RESEND_FROM_ADDRESS=bot@yourdomain.com RESEND_FROM_NAME=Formbot # Optional, defaults to "Formbot" # Forwarding FORWARD_EMAIL=team@yourdomain.com # Where approved submissions are sent FORWARD_ENDPOINT= # Optional: webhook URL for downstream systems`
|
|
52
|
+
|
|
53
|
+
`SLACK_CHANNEL_ID` is the channel where submission cards will appear. You can find it by right-clicking a channel in Slack and selecting "View channel details" (the ID is at the bottom of the modal).
|
|
54
|
+
|
|
55
|
+
`FORWARD_ENDPOINT` is optional. If set, the bot will also POST the form data as JSON to that URL when a submission is forwarded. This is useful for piping approved submissions into a CRM, database, or another service.
|
|
56
|
+
|
|
57
|
+
### Deploy to Vercel
|
|
58
|
+
|
|
59
|
+
[Deploy the bot with one click](https://vercel.com/new/clone?repository-url=https://github.com/vercel-labs/chat-sdk-form-bot&env=SLACK_BOT_TOKEN,SLACK_SIGNING_SECRET,SLACK_CHANNEL_ID,REDIS_URL,RESEND_API_KEY,RESEND_FROM_ADDRESS,FORWARD_EMAIL), or clone the repo and deploy manually:
|
|
60
|
+
|
|
61
|
+
`git clone https://github.com/vercel-labs/chat-sdk-form-bot.git cd chat-sdk-form-bot pnpm install vercel`
|
|
62
|
+
|
|
63
|
+
After deploying, update your Slack app's interactivity request URL to point to your production domain: `https://<your-vercel-domain>/api/webhooks/slack`.
|
|
64
|
+
|
|
65
|
+
### Test the form endpoint
|
|
66
|
+
|
|
67
|
+
Send a test submission:
|
|
68
|
+
|
|
69
|
+
`curl -X POST https://<your-domain>/api/form \ -H "Content-Type: application/json" \ -d '{"Name": "Jane Doe", "Email": "jane@example.com", "Message": "Hello!"}'`
|
|
70
|
+
|
|
71
|
+
A card should appear in your configured Slack channel within a few seconds, and you should see a JSON response in your terminal:
|
|
72
|
+
|
|
73
|
+
`{"status":"received","submissionId":"a1b2c3d4-..."}`
|
|
74
|
+
|
|
75
|
+
### Local development
|
|
76
|
+
|
|
77
|
+
For local development, the template includes a Node.js server entrypoint:
|
|
78
|
+
|
|
79
|
+
`pnpm dev`
|
|
80
|
+
|
|
81
|
+
This starts a local server at `http://localhost:3000`. To receive Slack webhooks locally, use [ngrok](https://ngrok.com/) to create a public tunnel:
|
|
82
|
+
|
|
83
|
+
`ngrok http 3000`
|
|
84
|
+
|
|
85
|
+
Then update your Slack app's request URL to the ngrok URL (e.g. `https://abc123.ngrok-free.dev/api/webhooks/slack`).
|
|
86
|
+
|
|
87
|
+
## How the form triage bot works
|
|
88
|
+
|
|
89
|
+
The bot has three moving parts, including an HTTP endpoint that receives form data, a Redis store that temporarily holds submissions, and a Chat SDK bot that manages Slack interactions.
|
|
90
|
+
|
|
91
|
+
1. An external service (your website, a form provider, a webhook) sends a `POST` request to `/api/form` with JSON data
|
|
92
|
+
|
|
93
|
+
2. The bot generates a unique ID, stores the submission in Redis with a 7-day TTL, and posts an interactive card to a Slack channel
|
|
94
|
+
|
|
95
|
+
3. A reviewer sees the card and takes one of three actions:
|
|
96
|
+
|
|
97
|
+
* **Forward Submission** sends a styled HTML email via [Resend](https://vercel.com/marketplace/resend) to a configured recipient, then updates the card to show who forwarded it
|
|
98
|
+
|
|
99
|
+
* **Edit & Forward** opens a modal where the reviewer can modify fields before forwarding
|
|
100
|
+
|
|
101
|
+
* **Mark as Spam** updates the card and deletes the submission from Redis
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
Once an action is taken, the card updates in place. The reviewer sees the result immediately in the same channel, without a confirmation page or context switch.
|
|
105
|
+
|
|
106
|
+
## Code walkthrough
|
|
107
|
+
|
|
108
|
+
The entire bot is about 200 lines across six files.
|
|
109
|
+
|
|
110
|
+
### Receiving submissions
|
|
111
|
+
|
|
112
|
+
The HTTP layer uses [Hono](https://vercel.com/docs/frameworks/backend/hono), a lightweight web framework. The form endpoint accepts any JSON body, assigns it a UUID, and hands it off to the bot:
|
|
113
|
+
|
|
114
|
+
``import { Hono } from "hono"; import { cors } from "hono/cors"; import { bot, postFormCard } from "./bot.js"; const app = new Hono(); app.use("/api/form", cors()); app.post("/api/form", (c) => c.req.json().then(async (formData: Record<string, unknown>) => { const submissionId = crypto.randomUUID(); await Promise.resolve(postFormCard(formData, submissionId)); return c.json({ status: "received", submissionId }); }) ); app.post("/api/webhooks/:platform", (c) => { const platform = c.req.param("platform"); if (platform !== "slack") { return c.json({ error: `Unknown platform: ${platform}` }, 404); } return bot.webhooks.slack(c.req.raw); }); export default app;``
|
|
115
|
+
|
|
116
|
+
You can submit directly from a browser-based form on any origin. The webhook route at `/api/webhooks/:platform` handles Slack's interaction payloads (button clicks and modal submissions). The `:platform` parameter is already set up for adding other platforms later.
|
|
117
|
+
|
|
118
|
+
### Building the bot
|
|
119
|
+
|
|
120
|
+
The bot itself is a Chat SDK instance with a Slack adapter and Redis-backed state:
|
|
121
|
+
|
|
122
|
+
`import { Chat } from "chat"; import { createSlackAdapter } from "@chat-adapter/slack"; import { createRedisState } from "@chat-adapter/state-redis"; export const bot = new Chat({ adapters: { slack: createSlackAdapter(), }, state: createRedisState(), userName: "form-bot", });`
|
|
123
|
+
|
|
124
|
+
That's the entire bot setup. Chat SDK handles Slack's signature verification, payload parsing, and response formatting. The `state` object is a Redis adapter that Chat SDK uses for its internal state management, and the same Redis connection is reused to store form submissions.
|
|
125
|
+
|
|
126
|
+
### Interactive cards
|
|
127
|
+
|
|
128
|
+
Cards are built using Chat SDK's component functions. These are platform-agnostic: on Slack, they render as Block Kit, on Teams, they'd render as Adaptive Cards. Here's the card that appears when a new submission arrives:
|
|
129
|
+
|
|
130
|
+
`import { Actions, Button, Card, Fields, Field, Divider } from "chat"; export const newSubmissionCard = ( formData: Record<string, unknown>, submissionId: string ) => Card({ children: [ Fields( Object.entries(formData).map(([key, value]) => Field({ label: key, value: String(value) }) ) ), Divider(), Actions([ Button({ id: "forward", label: "Forward Submission", style: "primary", value: submissionId, }), Button({ id: "edit", label: "Edit & Forward", style: "primary", value: submissionId, }), ]), Actions([ Button({ id: "spam", label: "Mark as Spam", style: "danger", value: submissionId, }), ]), ], title: "New Form Submission", });`
|
|
131
|
+
|
|
132
|
+
The form data is dynamic. Whatever keys and values are in the JSON body become fields on the card. A submission with `{"Name": "Jane", "Email": "jane@example.com"}` produces a card with two fields. A submission with ten fields produces a card with ten fields. No schema changes needed.
|
|
133
|
+
|
|
134
|
+
### Handling actions
|
|
135
|
+
|
|
136
|
+
When a reviewer clicks a button, Chat SDK routes the event to the right handler based on the action ID:
|
|
137
|
+
|
|
138
|
+
`bot.onAction(["forward", "spam", "edit"], async (event) => { const submissionId = event.value; if (!submissionId || !event.thread) return; const formData = await getSubmission(submissionId); if (!formData) return; if (event.actionId === "edit") { await event.openModal(editSubmissionModal(formData, submissionId)); } else { const handler = event.actionId === "forward" ? handleForward : handleSpam; await handler(event, formData, submissionId, event.thread.id); } });`
|
|
139
|
+
|
|
140
|
+
The forward handler sends the email, optionally POSTs to a webhook endpoint, updates the Slack card, and cleans up Redis, all in parallel:
|
|
141
|
+
|
|
142
|
+
`const handleForward = async (event, formData, submissionId, threadId) => { const slack = bot.getAdapter("slack"); await Promise.all([ forwardToEmail(formData), forwardToEndpoint(formData), slack.editMessage( threadId, event.messageId, forwardedCard(formData, event.user.fullName) ), deleteSubmission(submissionId), ]); };`
|
|
143
|
+
|
|
144
|
+
After forwarding, the card is replaced with a read-only version showing who forwarded it and where. The submission is deleted from Redis since it's no longer needed.
|
|
145
|
+
|
|
146
|
+
## How to add Teams, Discord, or other platforms
|
|
147
|
+
|
|
148
|
+
Chat SDK supports multiple platforms from a single codebase. The cards, fields, and buttons you've already defined render natively on each platform, including Block Kit on Slack, Adaptive Cards on Teams, and Google Chat Cards.
|
|
149
|
+
|
|
150
|
+
To add Microsoft Teams or another platform, register an additional adapter:
|
|
151
|
+
|
|
152
|
+
`import { createSlackAdapter } from "@chat-adapter/slack"; import { createTeamsAdapter } from "@chat-adapter/teams"; export const bot = new Chat({ adapters: { slack: createSlackAdapter(), teams: createTeamsAdapter(), }, state, userName: "form-bot", });`
|
|
153
|
+
|
|
154
|
+
The existing webhook route in `src/index.ts` already uses a `:platform` parameter, so Teams webhooks would be handled at `/api/webhooks/teams` with no additional routing code.
|
|
155
|
+
|
|
156
|
+
You could also post to multiple platforms at once. For example, you might post form submissions to both a Slack channel and a Teams channel by calling `bot.channel()` with different platform prefixes:
|
|
157
|
+
|
|
158
|
+
``const slackChannel = bot.channel(`slack:${process.env.SLACK_CHANNEL_ID}`); const teamsChannel = bot.channel(`teams:${process.env.TEAMS_CHANNEL_ID}`); await Promise.all([ slackChannel.post(newSubmissionCard(formData, submissionId)), teamsChannel.post(newSubmissionCard(formData, submissionId)), ]);``
|
|
159
|
+
|
|
160
|
+
Modals are currently Slack-only, so the Edit & Forward button only works on Slack. On other platforms, you'd want to either hide that button or replace it with a different editing flow.
|
|
161
|
+
|
|
162
|
+
See the [Chat SDK adapter directory](https://chat-sdk.dev/adapters) for the full list of supported platforms.
|
|
163
|
+
|
|
164
|
+
## Related resources
|
|
165
|
+
|
|
166
|
+
* [Chat SDK Form Bot template](https://github.com/vercel-labs/chat-sdk-form-bot)
|
|
167
|
+
|
|
168
|
+
* [Chat SDK documentation](https://chat-sdk.dev/docs)
|
|
169
|
+
|
|
170
|
+
* [Chat SDK GitHub](https://github.com/vercel/chat)
|
|
171
|
+
|
|
172
|
+
* [Resend documentation](https://resend.com/docs/api-reference/emails/send-email)
|
|
173
|
+
|
|
174
|
+
* [Hono documentation](https://hono.dev/docs/getting-started/vercel)
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
[View full KB sitemap](/kb/sitemap.md)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"templates": [
|
|
3
|
+
{
|
|
4
|
+
"title": "Chat SDK Liveblocks Bot",
|
|
5
|
+
"description": "Build a bot that you can engage with inside Liveblocks.",
|
|
6
|
+
"href": "https://vercel.com/templates/next.js/chat-sdk-liveblocks-bot"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"title": "Knowledge Agent",
|
|
10
|
+
"description": "Open source file-system and knowledge based agent template. Build AI agents that stay up to date with your knowledge base.",
|
|
11
|
+
"href": "https://vercel.com/templates/nuxt/chat-sdk-knowledge-agent"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"title": "Community Agent",
|
|
15
|
+
"description": "Open source AI-powered Slack community management bot with a built-in Next.js admin panel. Uses Chat SDK, AI SDK, and Vercel Workflow.",
|
|
16
|
+
"href": "https://vercel.com/templates/next.js/chat-sdk-community-agent"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|