chat 4.25.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 +235 -6
- package/dist/index.js +353 -76
- 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 +30 -30
- package/docs/api/cards.mdx +5 -0
- package/docs/api/chat.mdx +95 -1
- package/docs/api/message.mdx +5 -1
- package/docs/api/modals.mdx +1 -1
- package/docs/api/thread.mdx +23 -1
- package/docs/cards.mdx +6 -0
- package/docs/contributing/publishing.mdx +33 -0
- package/docs/files.mdx +1 -0
- package/docs/getting-started.mdx +2 -12
- package/docs/meta.json +0 -2
- package/docs/modals.mdx +74 -2
- package/docs/state.mdx +1 -1
- package/docs/streaming.mdx +13 -5
- package/docs/threads-messages-channels.mdx +34 -0
- package/docs/usage.mdx +2 -2
- 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/adapters/whatsapp.mdx +0 -222
- 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 -331
- 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,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
|
+
}
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: WhatsApp
|
|
3
|
-
description: Configure the WhatsApp adapter for the WhatsApp Business Cloud API.
|
|
4
|
-
type: integration
|
|
5
|
-
prerequisites:
|
|
6
|
-
- /docs/getting-started
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Installation
|
|
10
|
-
|
|
11
|
-
```sh title="Terminal"
|
|
12
|
-
pnpm add @chat-adapter/whatsapp
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Usage
|
|
16
|
-
|
|
17
|
-
The adapter auto-detects `WHATSAPP_ACCESS_TOKEN`, `WHATSAPP_APP_SECRET`, `WHATSAPP_PHONE_NUMBER_ID`, and `WHATSAPP_VERIFY_TOKEN` from environment variables:
|
|
18
|
-
|
|
19
|
-
```typescript title="lib/bot.ts" lineNumbers
|
|
20
|
-
import { Chat } from "chat";
|
|
21
|
-
import { createWhatsAppAdapter } from "@chat-adapter/whatsapp";
|
|
22
|
-
|
|
23
|
-
const bot = new Chat({
|
|
24
|
-
userName: "mybot",
|
|
25
|
-
adapters: {
|
|
26
|
-
whatsapp: createWhatsAppAdapter(),
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
bot.onNewMention(async (thread, message) => {
|
|
31
|
-
await thread.post(`You said: ${message.text}`);
|
|
32
|
-
});
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Since all WhatsApp conversations are 1:1 DMs, every incoming message is treated as a mention.
|
|
36
|
-
|
|
37
|
-
## Webhook route
|
|
38
|
-
|
|
39
|
-
WhatsApp uses two webhook mechanisms:
|
|
40
|
-
|
|
41
|
-
1. **Verification handshake** (GET) — Meta sends a `hub.verify_token` challenge that must match your `WHATSAPP_VERIFY_TOKEN`.
|
|
42
|
-
2. **Event delivery** (POST) — incoming messages, reactions, and interactive responses, verified via `X-Hub-Signature-256`.
|
|
43
|
-
|
|
44
|
-
Both are handled by the same `handleWebhook` method:
|
|
45
|
-
|
|
46
|
-
```typescript title="app/api/webhooks/whatsapp/route.ts" lineNumbers
|
|
47
|
-
import { bot } from "@/lib/bot";
|
|
48
|
-
|
|
49
|
-
export async function GET(request: Request): Promise<Response> {
|
|
50
|
-
return bot.webhooks.whatsapp(request);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export async function POST(request: Request): Promise<Response> {
|
|
54
|
-
return bot.webhooks.whatsapp(request);
|
|
55
|
-
}
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## Meta app setup
|
|
59
|
-
|
|
60
|
-
### 1. Create a Meta app
|
|
61
|
-
|
|
62
|
-
1. Go to [developers.facebook.com/apps](https://developers.facebook.com/apps)
|
|
63
|
-
2. Click **Create App** and select **Business** type
|
|
64
|
-
3. Give it a name and click **Create App**
|
|
65
|
-
|
|
66
|
-
### 2. Add WhatsApp product
|
|
67
|
-
|
|
68
|
-
1. In the app dashboard, find **WhatsApp** and click **Set Up**
|
|
69
|
-
2. This creates a test phone number and sandbox environment
|
|
70
|
-
|
|
71
|
-
### 3. Get credentials
|
|
72
|
-
|
|
73
|
-
From the app dashboard:
|
|
74
|
-
|
|
75
|
-
| Credential | Where to find it |
|
|
76
|
-
|---|---|
|
|
77
|
-
| **Access Token** | WhatsApp > API Setup > Temporary access token (or create a System User token for production) |
|
|
78
|
-
| **Phone Number ID** | WhatsApp > API Setup > Phone number ID |
|
|
79
|
-
| **App Secret** | Settings > Basic > App Secret |
|
|
80
|
-
| **Verify Token** | You define this — any secret string you choose |
|
|
81
|
-
|
|
82
|
-
### 4. Configure webhooks
|
|
83
|
-
|
|
84
|
-
1. Go to **WhatsApp** > **Configuration** in your app dashboard
|
|
85
|
-
2. Click **Edit** next to Webhook URL
|
|
86
|
-
3. Set **Callback URL** to `https://your-domain.com/api/webhooks/whatsapp`
|
|
87
|
-
4. Set **Verify token** to the same value as your `WHATSAPP_VERIFY_TOKEN`
|
|
88
|
-
5. Click **Verify and Save**
|
|
89
|
-
6. Subscribe to the **messages** webhook field
|
|
90
|
-
|
|
91
|
-
### 5. Production access
|
|
92
|
-
|
|
93
|
-
For production use:
|
|
94
|
-
|
|
95
|
-
1. Add a real phone number under **WhatsApp** > **API Setup**
|
|
96
|
-
2. Create a **System User** in Meta Business Suite for a permanent access token
|
|
97
|
-
3. Complete Meta's **App Review** process for the `whatsapp_business_messaging` permission
|
|
98
|
-
|
|
99
|
-
## Interactive messages
|
|
100
|
-
|
|
101
|
-
Card elements are automatically converted to WhatsApp interactive messages:
|
|
102
|
-
|
|
103
|
-
- **3 or fewer buttons** — rendered as WhatsApp reply buttons (title max 20 characters)
|
|
104
|
-
- **More than 3 buttons** — falls back to formatted text
|
|
105
|
-
|
|
106
|
-
```typescript title="lib/bot.ts" lineNumbers
|
|
107
|
-
import { Card, Actions, Button, Body, BodyText } from "chat";
|
|
108
|
-
|
|
109
|
-
bot.onNewMention(async (thread) => {
|
|
110
|
-
await thread.post(
|
|
111
|
-
<Card>
|
|
112
|
-
<Body>
|
|
113
|
-
<BodyText>How can I help?</BodyText>
|
|
114
|
-
</Body>
|
|
115
|
-
<Actions>
|
|
116
|
-
<Button id="help" value="help">Get Help</Button>
|
|
117
|
-
<Button id="status" value="status">Check Status</Button>
|
|
118
|
-
</Actions>
|
|
119
|
-
</Card>
|
|
120
|
-
);
|
|
121
|
-
});
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
## Media attachments
|
|
125
|
-
|
|
126
|
-
Incoming media messages (images, documents, audio, video, voice, stickers) are exposed as attachments with a lazy `fetchData()` function. Media is downloaded in two steps via the Graph API — first fetching the media URL, then downloading the binary data.
|
|
127
|
-
|
|
128
|
-
```typescript title="lib/bot.ts" lineNumbers
|
|
129
|
-
bot.onNewMention(async (thread, message) => {
|
|
130
|
-
for (const attachment of message.attachments) {
|
|
131
|
-
if (attachment.fetchData) {
|
|
132
|
-
const data = await attachment.fetchData();
|
|
133
|
-
// Process the media buffer...
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
## 24-hour messaging window
|
|
140
|
-
|
|
141
|
-
WhatsApp enforces a [24-hour customer service window](https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-messages#customer-service-windows). You can only send free-form messages to a user within 24 hours of their last message. After that, you must use approved **message templates**.
|
|
142
|
-
|
|
143
|
-
## Configuration
|
|
144
|
-
|
|
145
|
-
All options are auto-detected from environment variables when not provided.
|
|
146
|
-
|
|
147
|
-
| Option | Required | Description |
|
|
148
|
-
|--------|----------|-------------|
|
|
149
|
-
| `accessToken` | No* | Meta access token. Auto-detected from `WHATSAPP_ACCESS_TOKEN` |
|
|
150
|
-
| `appSecret` | No* | App secret for webhook signature verification. Auto-detected from `WHATSAPP_APP_SECRET` |
|
|
151
|
-
| `phoneNumberId` | No* | Bot's phone number ID. Auto-detected from `WHATSAPP_PHONE_NUMBER_ID` |
|
|
152
|
-
| `verifyToken` | No* | Secret for webhook verification handshake. Auto-detected from `WHATSAPP_VERIFY_TOKEN` |
|
|
153
|
-
| `apiVersion` | No | Graph API version (defaults to `v21.0`) |
|
|
154
|
-
| `userName` | No | Bot username. Auto-detected from `WHATSAPP_BOT_USERNAME` or defaults to `whatsapp-bot` |
|
|
155
|
-
| `logger` | No | Logger instance (defaults to `ConsoleLogger("info")`) |
|
|
156
|
-
|
|
157
|
-
*All four credentials are required — either via config or environment variables.
|
|
158
|
-
|
|
159
|
-
## Environment variables
|
|
160
|
-
|
|
161
|
-
```bash title=".env.local"
|
|
162
|
-
WHATSAPP_ACCESS_TOKEN=EAAx... # Meta access token
|
|
163
|
-
WHATSAPP_APP_SECRET=abc123... # App secret for signature verification
|
|
164
|
-
WHATSAPP_PHONE_NUMBER_ID=1234567890 # Phone number ID from Meta dashboard
|
|
165
|
-
WHATSAPP_VERIFY_TOKEN=my-secret # Your chosen webhook verify token
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
## Features
|
|
169
|
-
|
|
170
|
-
| Feature | Supported |
|
|
171
|
-
|---------|-----------|
|
|
172
|
-
| Mentions | N/A (all messages are DMs) |
|
|
173
|
-
| Reactions (add/remove) | Yes |
|
|
174
|
-
| Cards | Interactive messages (max 3 buttons) / text fallback |
|
|
175
|
-
| Modals | No |
|
|
176
|
-
| Streaming | No |
|
|
177
|
-
| DMs | Yes (all conversations) |
|
|
178
|
-
| Ephemeral messages | No |
|
|
179
|
-
| File uploads | Receive only |
|
|
180
|
-
| Typing indicator | Yes |
|
|
181
|
-
| Message history | No |
|
|
182
|
-
| Edit message | No (throws) |
|
|
183
|
-
| Delete message | No (throws) |
|
|
184
|
-
|
|
185
|
-
## Thread ID format
|
|
186
|
-
|
|
187
|
-
```
|
|
188
|
-
whatsapp:{phoneNumberId}:{userWaId}
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
Example: `whatsapp:1234567890:15551234567`
|
|
192
|
-
|
|
193
|
-
## Notes
|
|
194
|
-
|
|
195
|
-
- All WhatsApp conversations are 1:1 DMs between the business phone number and the user, so every message sets `isMention: true`.
|
|
196
|
-
- `editMessage()` and `deleteMessage()` throw errors — WhatsApp does not support these operations.
|
|
197
|
-
- `fetchMessages()` returns empty results — WhatsApp does not provide a message history API.
|
|
198
|
-
- Messages exceeding 4096 characters are automatically split at paragraph or line boundaries.
|
|
199
|
-
- Webhook signatures are verified using HMAC-SHA256 with `timingSafeEqual` for timing-attack resistance.
|
|
200
|
-
|
|
201
|
-
## Troubleshooting
|
|
202
|
-
|
|
203
|
-
### Webhook verification failing
|
|
204
|
-
|
|
205
|
-
- Verify `WHATSAPP_VERIFY_TOKEN` matches what you set in the Meta dashboard
|
|
206
|
-
- Ensure both GET and POST routes are configured for the webhook URL
|
|
207
|
-
|
|
208
|
-
### "Invalid signature" error
|
|
209
|
-
|
|
210
|
-
- Check that `WHATSAPP_APP_SECRET` is correct (find it under Settings > Basic in your Meta app)
|
|
211
|
-
- Ensure the raw request body is not parsed before verification
|
|
212
|
-
|
|
213
|
-
### Bot not responding
|
|
214
|
-
|
|
215
|
-
- Confirm the **messages** webhook field is subscribed in Meta dashboard
|
|
216
|
-
- Check that you're within the 24-hour messaging window (or using template messages)
|
|
217
|
-
- Verify the phone number ID matches your configured `WHATSAPP_PHONE_NUMBER_ID`
|
|
218
|
-
|
|
219
|
-
### Media downloads failing
|
|
220
|
-
|
|
221
|
-
- Ensure your access token has the required permissions
|
|
222
|
-
- Check that the media hasn't expired (WhatsApp media URLs are temporary)
|
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Code review GitHub bot with Hono and Redis
|
|
3
|
-
description: This guide 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.
|
|
4
|
-
type: guide
|
|
5
|
-
prerequisites: []
|
|
6
|
-
related:
|
|
7
|
-
- /adapters/github
|
|
8
|
-
- /docs/state/redis
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## Prerequisites
|
|
12
|
-
|
|
13
|
-
- Node.js 18+
|
|
14
|
-
- [pnpm](https://pnpm.io) (or npm/yarn)
|
|
15
|
-
- A GitHub repository where you have admin access
|
|
16
|
-
- A Redis instance for state management
|
|
17
|
-
- A [Vercel](https://vercel.com) account
|
|
18
|
-
|
|
19
|
-
## Create a Hono app
|
|
20
|
-
|
|
21
|
-
Scaffold a new Hono project and install dependencies:
|
|
22
|
-
|
|
23
|
-
```sh title="Terminal"
|
|
24
|
-
pnpm create hono my-review-bot
|
|
25
|
-
cd my-review-bot
|
|
26
|
-
pnpm add @octokit/rest @vercel/functions @vercel/sandbox ai bash-tool chat @chat-adapter/github @chat-adapter/state-redis
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
<Callout type="info">
|
|
30
|
-
Select the `vercel` template when prompted by `create-hono`. This sets up the project for Vercel deployment with the correct entry point.
|
|
31
|
-
</Callout>
|
|
32
|
-
|
|
33
|
-
## Configure a GitHub webhook
|
|
34
|
-
|
|
35
|
-
1. Go to your repository **Settings** then **Webhooks** then **Add webhook**
|
|
36
|
-
2. Set **Payload URL** to `https://your-domain.com/api/webhooks/github`
|
|
37
|
-
3. Set **Content type** to `application/json`
|
|
38
|
-
4. Set a **Secret** and save it — you'll need this as `GITHUB_WEBHOOK_SECRET`
|
|
39
|
-
5. Under **Which events would you like to trigger this webhook?**, select **Let me select individual events** and check:
|
|
40
|
-
- **Issue comments** (for @mention on the PR conversation tab)
|
|
41
|
-
- **Pull request review comments** (for @mention on inline review threads)
|
|
42
|
-
|
|
43
|
-
### Get credentials
|
|
44
|
-
|
|
45
|
-
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`
|
|
46
|
-
2. Copy the **Webhook secret** you set above — you'll need this as `GITHUB_WEBHOOK_SECRET`
|
|
47
|
-
|
|
48
|
-
## Configure environment variables
|
|
49
|
-
|
|
50
|
-
Create a `.env` file in your project root:
|
|
51
|
-
|
|
52
|
-
```bash title=".env"
|
|
53
|
-
GITHUB_TOKEN=ghp_your_personal_access_token
|
|
54
|
-
GITHUB_WEBHOOK_SECRET=your_webhook_secret
|
|
55
|
-
REDIS_URL=redis://localhost:6379
|
|
56
|
-
BOT_USERNAME=my-review-bot
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
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`.
|
|
60
|
-
|
|
61
|
-
## Define the review function
|
|
62
|
-
|
|
63
|
-
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.
|
|
64
|
-
|
|
65
|
-
```typescript title="src/review.ts" lineNumbers
|
|
66
|
-
import { Sandbox } from "@vercel/sandbox";
|
|
67
|
-
import { ToolLoopAgent, stepCountIs } from "ai";
|
|
68
|
-
import { createBashTool } from "bash-tool";
|
|
69
|
-
|
|
70
|
-
interface ReviewInput {
|
|
71
|
-
owner: string;
|
|
72
|
-
repo: string;
|
|
73
|
-
prBranch: string;
|
|
74
|
-
baseBranch: string;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export async function reviewPullRequest(input: ReviewInput): Promise<string> {
|
|
78
|
-
const { owner, repo, prBranch, baseBranch } = input;
|
|
79
|
-
|
|
80
|
-
const sandbox = await Sandbox.create({
|
|
81
|
-
source: {
|
|
82
|
-
type: "git",
|
|
83
|
-
url: `https://github.com/${owner}/${repo}`,
|
|
84
|
-
username: "x-access-token",
|
|
85
|
-
password: process.env.GITHUB_TOKEN,
|
|
86
|
-
depth: 50,
|
|
87
|
-
},
|
|
88
|
-
timeout: 5 * 60 * 1000,
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
await sandbox.runCommand("git", ["fetch", "origin", prBranch, baseBranch]);
|
|
93
|
-
await sandbox.runCommand("git", ["checkout", prBranch]);
|
|
94
|
-
|
|
95
|
-
const diffResult = await sandbox.runCommand("git", [
|
|
96
|
-
"diff",
|
|
97
|
-
`origin/${baseBranch}...HEAD`,
|
|
98
|
-
]);
|
|
99
|
-
const diff = await diffResult.output("stdout");
|
|
100
|
-
|
|
101
|
-
const { tools } = await createBashTool({ sandbox });
|
|
102
|
-
|
|
103
|
-
const agent = new ToolLoopAgent({
|
|
104
|
-
model: "anthropic/claude-sonnet-4.6",
|
|
105
|
-
tools,
|
|
106
|
-
stopWhen: stepCountIs(20),
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
const result = await agent.generate({
|
|
110
|
-
prompt: `You are reviewing a pull request for bugs and issues.
|
|
111
|
-
|
|
112
|
-
Here is the diff for this PR:
|
|
113
|
-
|
|
114
|
-
\`\`\`diff
|
|
115
|
-
${diff}
|
|
116
|
-
\`\`\`
|
|
117
|
-
|
|
118
|
-
Use the bash and readFile tools to inspect any files you need more context on.
|
|
119
|
-
|
|
120
|
-
Look for bugs, security issues, performance problems, and missing error handling.
|
|
121
|
-
Organize findings by severity (critical, warning, suggestion).
|
|
122
|
-
If the code looks good, say so.`,
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
return result.text;
|
|
126
|
-
} finally {
|
|
127
|
-
await sandbox.stop();
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
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.
|
|
133
|
-
|
|
134
|
-
The function returns the review text instead of posting it directly. This lets the Chat SDK handler post it as a threaded reply.
|
|
135
|
-
|
|
136
|
-
## Create the bot
|
|
137
|
-
|
|
138
|
-
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.
|
|
139
|
-
|
|
140
|
-
```typescript title="src/bot.ts" lineNumbers
|
|
141
|
-
import { Chat } from "chat";
|
|
142
|
-
import { createGitHubAdapter } from "@chat-adapter/github";
|
|
143
|
-
import { createRedisState } from "@chat-adapter/state-redis";
|
|
144
|
-
import { Octokit } from "@octokit/rest";
|
|
145
|
-
import { reviewPullRequest } from "./review";
|
|
146
|
-
import type { GitHubRawMessage } from "@chat-adapter/github";
|
|
147
|
-
|
|
148
|
-
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
|
|
149
|
-
|
|
150
|
-
export const bot = new Chat({
|
|
151
|
-
userName: process.env.BOT_USERNAME!,
|
|
152
|
-
adapters: {
|
|
153
|
-
github: createGitHubAdapter(),
|
|
154
|
-
},
|
|
155
|
-
state: createRedisState(),
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
bot.onNewMention(async (thread, message) => {
|
|
159
|
-
const raw = message.raw as GitHubRawMessage;
|
|
160
|
-
const { owner, repo, prNumber } = {
|
|
161
|
-
owner: raw.repository.owner.login,
|
|
162
|
-
repo: raw.repository.name,
|
|
163
|
-
prNumber: raw.prNumber,
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
// Fetch PR branch info
|
|
167
|
-
const { data: pr } = await octokit.pulls.get({
|
|
168
|
-
owner,
|
|
169
|
-
repo,
|
|
170
|
-
pull_number: prNumber,
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
await thread.post("Starting code review...");
|
|
174
|
-
await thread.subscribe();
|
|
175
|
-
|
|
176
|
-
const review = await reviewPullRequest({
|
|
177
|
-
owner,
|
|
178
|
-
repo,
|
|
179
|
-
prBranch: pr.head.ref,
|
|
180
|
-
baseBranch: pr.base.ref,
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
await thread.post(review);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
bot.onSubscribedMessage(async (thread, message) => {
|
|
187
|
-
await thread.post(
|
|
188
|
-
"I've already reviewed this PR. @mention me on a new PR to start another review."
|
|
189
|
-
);
|
|
190
|
-
});
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
`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.
|
|
194
|
-
|
|
195
|
-
## Handle the webhook
|
|
196
|
-
|
|
197
|
-
Create the Hono app with a single webhook route that delegates to Chat SDK:
|
|
198
|
-
|
|
199
|
-
```typescript title="src/index.ts" lineNumbers
|
|
200
|
-
import { Hono } from "hono";
|
|
201
|
-
import { waitUntil } from "@vercel/functions";
|
|
202
|
-
import { bot } from "./bot";
|
|
203
|
-
|
|
204
|
-
const app = new Hono();
|
|
205
|
-
|
|
206
|
-
app.post("/api/webhooks/github", async (c) => {
|
|
207
|
-
const handler = bot.webhooks.github;
|
|
208
|
-
if (!handler) {
|
|
209
|
-
return c.text("GitHub adapter not configured", 404);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return handler(c.req.raw, { waitUntil });
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
export default app;
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
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 — required on serverless platforms where the function would otherwise terminate before your handlers finish.
|
|
219
|
-
|
|
220
|
-
## Test locally
|
|
221
|
-
|
|
222
|
-
1. Start your development server (`pnpm dev`)
|
|
223
|
-
2. Expose it with a tunnel (e.g. `ngrok http 3000`)
|
|
224
|
-
3. Update the webhook URL in your GitHub repository settings to your tunnel URL
|
|
225
|
-
4. Open a pull request
|
|
226
|
-
5. Comment `@my-review-bot can you review this?` — the bot should respond with "Starting code review..." followed by the full review
|
|
227
|
-
|
|
228
|
-
## Deploy to Vercel
|
|
229
|
-
|
|
230
|
-
Deploy your bot to Vercel:
|
|
231
|
-
|
|
232
|
-
```sh title="Terminal"
|
|
233
|
-
vercel deploy
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
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.
|
|
237
|
-
|
|
238
|
-
## Next steps
|
|
239
|
-
|
|
240
|
-
- [GitHub adapter](/adapters/github) — Authentication options, thread model, and full configuration reference
|
|
241
|
-
- [State Adapters](/docs/state) — Production state adapters (Redis, PostgreSQL, ioredis) for subscriptions and distributed locking
|