chat 4.26.0 → 4.28.1

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.
Files changed (50) hide show
  1. package/dist/{chunk-OPV5U4WG.js → chunk-V25FKIIL.js} +44 -1
  2. package/dist/index.d.ts +485 -33
  3. package/dist/index.js +862 -135
  4. package/dist/{jsx-runtime-DxATbnrP.d.ts → jsx-runtime-DxGwoLu2.d.ts} +49 -5
  5. package/dist/jsx-runtime.d.ts +1 -1
  6. package/dist/jsx-runtime.js +1 -1
  7. package/docs/actions.mdx +52 -1
  8. package/docs/adapters.mdx +43 -37
  9. package/docs/api/cards.mdx +4 -0
  10. package/docs/api/chat.mdx +172 -6
  11. package/docs/api/index.mdx +2 -0
  12. package/docs/api/markdown.mdx +28 -5
  13. package/docs/api/message.mdx +58 -1
  14. package/docs/api/meta.json +2 -0
  15. package/docs/api/modals.mdx +50 -0
  16. package/docs/api/postable-message.mdx +55 -1
  17. package/docs/api/thread.mdx +33 -3
  18. package/docs/api/transcripts.mdx +220 -0
  19. package/docs/cards.mdx +6 -0
  20. package/docs/concurrency.mdx +4 -0
  21. package/docs/contributing/building.mdx +73 -1
  22. package/docs/contributing/publishing.mdx +33 -0
  23. package/docs/conversation-history.mdx +137 -0
  24. package/docs/direct-messages.mdx +13 -4
  25. package/docs/ephemeral-messages.mdx +1 -1
  26. package/docs/error-handling.mdx +15 -3
  27. package/docs/files.mdx +2 -1
  28. package/docs/getting-started.mdx +1 -11
  29. package/docs/index.mdx +7 -5
  30. package/docs/meta.json +14 -5
  31. package/docs/modals.mdx +97 -1
  32. package/docs/posting-messages.mdx +7 -3
  33. package/docs/streaming.mdx +74 -18
  34. package/docs/subject.mdx +53 -0
  35. package/docs/threads-messages-channels.mdx +43 -0
  36. package/docs/usage.mdx +11 -2
  37. package/package.json +3 -2
  38. package/resources/guides/create-a-discord-support-bot-with-nuxt-and-redis.md +180 -0
  39. package/resources/guides/how-to-build-a-slack-bot-with-next-js-and-redis.md +134 -0
  40. package/resources/guides/how-to-build-an-ai-agent-for-slack-with-chat-sdk-and-ai-sdk.md +220 -0
  41. package/resources/guides/run-and-track-deploys-from-slack.md +270 -0
  42. package/resources/guides/ship-a-github-code-review-bot-with-hono-and-redis.md +147 -0
  43. package/resources/guides/triage-form-submissions-with-chat-sdk.md +178 -0
  44. package/resources/templates.json +19 -0
  45. package/docs/guides/code-review-hono.mdx +0 -241
  46. package/docs/guides/discord-nuxt.mdx +0 -227
  47. package/docs/guides/durable-chat-sessions-nextjs.mdx +0 -337
  48. package/docs/guides/meta.json +0 -10
  49. package/docs/guides/scheduled-posts-neon.mdx +0 -447
  50. 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,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
@@ -1,227 +0,0 @@
1
- ---
2
- title: Discord support bot with Nuxt and Redis
3
- description: This guide walks through building a Discord support bot with Nuxt, covering project setup, Discord app configuration, Gateway forwarding, AI-powered responses, and deployment.
4
- type: guide
5
- prerequisites: []
6
- related:
7
- - /adapters/discord
8
- - /docs/cards
9
- - /docs/actions
10
- ---
11
-
12
- ## Prerequisites
13
-
14
- - Node.js 18+
15
- - [pnpm](https://pnpm.io) (or npm/yarn)
16
- - A Discord server where you have admin access
17
- - A Redis instance for state management
18
-
19
- ## Create a Nuxt app
20
-
21
- Scaffold a new Nuxt project and install Chat SDK dependencies:
22
-
23
- ```sh title="Terminal"
24
- npx nuxi@latest init my-discord-bot
25
- cd my-discord-bot
26
- pnpm add chat @chat-adapter/discord @chat-adapter/state-redis ai @ai-sdk/anthropic
27
- ```
28
-
29
- ## Create a Discord app
30
-
31
- 1. Go to [discord.com/developers/applications](https://discord.com/developers/applications)
32
- 2. Click **New Application**, give it a name, and click **Create**
33
- 3. Go to **Bot** in the sidebar and click **Reset Token** — copy the token, you'll need this as `DISCORD_BOT_TOKEN`
34
- 4. Under **Privileged Gateway Intents**, enable **Message Content Intent**
35
- 5. Go to **General Information** and copy the **Application ID** and **Public Key** — you'll need these as `DISCORD_APPLICATION_ID` and `DISCORD_PUBLIC_KEY`
36
-
37
- ### Set up the Interactions endpoint
38
-
39
- 1. In **General Information**, set the **Interactions Endpoint URL** to `https://your-domain.com/api/webhooks/discord`
40
- 2. Discord will send a PING to verify the endpoint — you'll need to deploy first or use a tunnel
41
-
42
- ### Invite the bot to your server
43
-
44
- 1. Go to **OAuth2** in the sidebar
45
- 2. Under **OAuth2 URL Generator**, select the `bot` scope
46
- 3. Under **Bot Permissions**, select:
47
- - Send Messages
48
- - Create Public Threads
49
- - Send Messages in Threads
50
- - Read Message History
51
- - Add Reactions
52
- - Use Slash Commands
53
- 4. Copy the generated URL and open it in your browser to invite the bot
54
-
55
- ## Configure environment variables
56
-
57
- Create a `.env` file in your project root:
58
-
59
- ```bash title=".env"
60
- DISCORD_BOT_TOKEN=your_bot_token
61
- DISCORD_PUBLIC_KEY=your_public_key
62
- DISCORD_APPLICATION_ID=your_application_id
63
- REDIS_URL=redis://localhost:6379
64
- ANTHROPIC_API_KEY=your_anthropic_api_key
65
- ```
66
-
67
- ## Create the bot
68
-
69
- Create `server/lib/bot.ts` with a `Chat` instance configured with the Discord adapter. This bot uses AI SDK to answer support questions:
70
-
71
- ```typescript title="server/lib/bot.tsx" lineNumbers
72
- import { Chat, Card, CardText as Text, Actions, Button, Divider } from "chat";
73
- import { createDiscordAdapter } from "@chat-adapter/discord";
74
- import { createRedisState } from "@chat-adapter/state-redis";
75
- import { generateText } from "ai";
76
- import { anthropic } from "@ai-sdk/anthropic";
77
-
78
- export const bot = new Chat({
79
- userName: "support-bot",
80
- adapters: {
81
- discord: createDiscordAdapter(),
82
- },
83
- state: createRedisState(),
84
- });
85
-
86
- bot.onNewMention(async (thread) => {
87
- await thread.subscribe();
88
- await thread.post(
89
- <Card title="Support">
90
- <Text>Hey! I'm here to help. Ask your question in this thread and I'll do my best to answer it.</Text>
91
- <Divider />
92
- <Actions>
93
- <Button id="escalate" style="danger">Escalate to Human</Button>
94
- </Actions>
95
- </Card>
96
- );
97
- });
98
-
99
- bot.onSubscribedMessage(async (thread, message) => {
100
- await thread.startTyping();
101
-
102
- const { text } = await generateText({
103
- model: anthropic("claude-sonnet-4-5-20250514"),
104
- system: "You are a friendly support bot. Answer questions concisely. If you don't know the answer, say so and suggest the user click 'Escalate to Human'.",
105
- prompt: message.text,
106
- });
107
-
108
- await thread.post(text);
109
- });
110
-
111
- bot.onAction("escalate", async (event) => {
112
- await event.thread.post(
113
- `${event.user.fullName} requested human support. A team member will follow up shortly.`
114
- );
115
- });
116
- ```
117
-
118
- <Callout type="info">
119
- The file extension must be `.tsx` (not `.ts`) when using JSX components like `Card` and `Button`. Make sure your `tsconfig.json` has `"jsx": "react-jsx"` and `"jsxImportSource": "chat"`.
120
- </Callout>
121
-
122
- `onNewMention` fires when a user @mentions the bot. Calling `thread.subscribe()` tells the SDK to track that thread, so subsequent messages trigger `onSubscribedMessage` where AI SDK generates a response.
123
-
124
- ## Create the webhook route
125
-
126
- Create a server route that handles incoming Discord webhooks:
127
-
128
- ```typescript title="server/api/webhooks/[platform].post.ts" lineNumbers
129
- import { bot } from "../lib/bot";
130
-
131
- type Platform = keyof typeof bot.webhooks;
132
-
133
- export default defineEventHandler(async (event) => {
134
- const platform = getRouterParam(event, "platform") as Platform;
135
-
136
- const handler = bot.webhooks[platform];
137
- if (!handler) {
138
- throw createError({ statusCode: 404, message: `Unknown platform: ${platform}` });
139
- }
140
-
141
- const request = toWebRequest(event);
142
-
143
- return handler(request, {
144
- waitUntil: (task) => event.waitUntil(task),
145
- });
146
- });
147
- ```
148
-
149
- This creates a `POST /api/webhooks/discord` endpoint. The `waitUntil` option ensures message processing completes after the HTTP response is sent.
150
-
151
- ## Set up the Gateway forwarder
152
-
153
- Discord doesn't push messages to webhooks like Slack does. Instead, messages arrive through the Gateway WebSocket. The Discord adapter includes a built-in Gateway listener that connects to the WebSocket and forwards events to your webhook endpoint.
154
-
155
- Create a route that starts the Gateway listener:
156
-
157
- ```typescript title="server/api/discord/gateway.get.ts" lineNumbers
158
- import { bot } from "../../lib/bot";
159
-
160
- export default defineEventHandler(async (event) => {
161
- await bot.initialize();
162
-
163
- const discord = bot.getAdapter("discord");
164
- if (!discord) {
165
- throw createError({ statusCode: 404, message: "Discord adapter not configured" });
166
- }
167
-
168
- const baseUrl = process.env.NUXT_PUBLIC_SITE_URL || "http://localhost:3000";
169
- const webhookUrl = `${baseUrl}/api/webhooks/discord`;
170
-
171
- const durationMs = 10 * 60 * 1000; // 10 minutes
172
-
173
- return discord.startGatewayListener(
174
- { waitUntil: (task: Promise<unknown>) => event.waitUntil(task) },
175
- durationMs,
176
- undefined,
177
- webhookUrl,
178
- );
179
- });
180
- ```
181
-
182
- The Gateway listener connects to Discord's WebSocket, receives messages, and forwards them to your webhook endpoint for processing. In production, you'll want a cron job to restart it periodically.
183
-
184
- ## Test locally
185
-
186
- 1. Start your development server (`pnpm dev`)
187
- 2. Trigger the Gateway listener by visiting `http://localhost:3000/api/discord/gateway` in your browser
188
- 3. Expose your server with a tunnel (e.g. `ngrok http 3000`)
189
- 4. Update the **Interactions Endpoint URL** in your Discord app settings to your tunnel URL (e.g. `https://abc123.ngrok.io/api/webhooks/discord`)
190
- 5. @mention the bot in your Discord server — it should respond with a support card
191
- 6. Reply in the thread — AI SDK should generate a response
192
- 7. Click **Escalate to Human** — the bot should post an escalation message
193
-
194
- ## Add a cron job for production
195
-
196
- The Gateway listener runs for a fixed duration. In production, set up a cron job to restart it automatically. If you're deploying to Vercel, add a `vercel.json`:
197
-
198
- ```json title="vercel.json"
199
- {
200
- "crons": [
201
- {
202
- "path": "/api/discord/gateway",
203
- "schedule": "*/9 * * * *"
204
- }
205
- ]
206
- }
207
- ```
208
-
209
- This restarts the Gateway listener every 9 minutes, ensuring continuous connectivity. Protect the endpoint with a `CRON_SECRET` environment variable in production.
210
-
211
- ## Deploy to Vercel
212
-
213
- Deploy your bot to Vercel:
214
-
215
- ```sh title="Terminal"
216
- vercel deploy
217
- ```
218
-
219
- After deployment, set your environment variables in the Vercel dashboard (`DISCORD_BOT_TOKEN`, `DISCORD_PUBLIC_KEY`, `DISCORD_APPLICATION_ID`, `REDIS_URL`, `ANTHROPIC_API_KEY`). Update the **Interactions Endpoint URL** in your Discord app settings to your production URL.
220
-
221
- ## Next steps
222
-
223
- - [Cards](/docs/cards) — Build rich interactive messages with buttons, fields, and selects
224
- - [Actions](/docs/actions) — Handle button clicks, select menus, and other interactions
225
- - [Streaming](/docs/streaming) — Stream AI-generated responses to chat
226
- - [Discord adapter](/adapters/discord) — Full configuration reference and Gateway setup
227
- - [State Adapters](/docs/state) — PostgreSQL, ioredis, and other state adapter options