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.
Files changed (31) hide show
  1. package/dist/{chunk-OPV5U4WG.js → chunk-AN7MRAVW.js} +39 -0
  2. package/dist/index.d.ts +220 -6
  3. package/dist/index.js +321 -50
  4. package/dist/{jsx-runtime-DxATbnrP.d.ts → jsx-runtime-Co9uV6l7.d.ts} +39 -5
  5. package/dist/jsx-runtime.d.ts +1 -1
  6. package/dist/jsx-runtime.js +1 -1
  7. package/docs/adapters.mdx +28 -28
  8. package/docs/api/chat.mdx +85 -1
  9. package/docs/api/message.mdx +5 -1
  10. package/docs/api/thread.mdx +23 -1
  11. package/docs/contributing/publishing.mdx +33 -0
  12. package/docs/files.mdx +1 -0
  13. package/docs/getting-started.mdx +1 -11
  14. package/docs/meta.json +0 -2
  15. package/docs/modals.mdx +73 -1
  16. package/docs/streaming.mdx +13 -5
  17. package/docs/threads-messages-channels.mdx +34 -0
  18. package/package.json +3 -2
  19. package/resources/guides/create-a-discord-support-bot-with-nuxt-and-redis.md +180 -0
  20. package/resources/guides/how-to-build-a-slack-bot-with-next-js-and-redis.md +134 -0
  21. package/resources/guides/how-to-build-an-ai-agent-for-slack-with-chat-sdk-and-ai-sdk.md +220 -0
  22. package/resources/guides/run-and-track-deploys-from-slack.md +270 -0
  23. package/resources/guides/ship-a-github-code-review-bot-with-hono-and-redis.md +147 -0
  24. package/resources/guides/triage-form-submissions-with-chat-sdk.md +178 -0
  25. package/resources/templates.json +19 -0
  26. package/docs/guides/code-review-hono.mdx +0 -241
  27. package/docs/guides/discord-nuxt.mdx +0 -227
  28. package/docs/guides/durable-chat-sessions-nextjs.mdx +0 -337
  29. package/docs/guides/meta.json +0 -10
  30. package/docs/guides/scheduled-posts-neon.mdx +0 -447
  31. package/docs/guides/slack-nextjs.mdx +0 -234
@@ -0,0 +1,180 @@
1
+ # Create a Discord support bot with Nuxt and Redis
2
+
3
+ **Author:** Hayden Bleasel, Ben Sabic
4
+
5
+ ---
6
+
7
+ You can build a Discord support bot that answers questions with AI, sends interactive cards with buttons, and escalates to human agents on demand by combining Chat SDK, AI SDK, and Nuxt. Chat SDK handles the platform integration (Gateway connection, event parsing, and the Discord API), while AI SDK generates responses using Claude. A Redis state adapter tracks subscribed threads across serverless invocations so conversations stay in context.
8
+
9
+ This guide will walk you through scaffolding a Nuxt app, configuring a Discord application, wiring up Chat SDK with the Discord adapter, adding AI-powered responses and interactive cards, setting up the Gateway forwarder, 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 Discord server where you have admin access
20
+
21
+ * A Redis instance (local or hosted, such as [Upstash](https://vercel.com/marketplace/upstash))
22
+
23
+ * An [Anthropic API key](https://console.anthropic.com/)
24
+
25
+
26
+ ## How it works
27
+
28
+ Chat SDK is a unified TypeScript SDK for building chatbots across Discord, Slack, Teams, and other platforms. You register event handlers (like `onNewMention` and `onSubscribedMessage`), and the SDK routes incoming events to them. The Discord adapter handles Gateway connection, webhook verification, and the Discord API. The Redis state adapter tracks which threads your bot has subscribed to and manages distributed locking for concurrent message handling.
29
+
30
+ Discord doesn't push messages to HTTP webhooks like Slack does. Instead, messages arrive through Discord's Gateway WebSocket. The Discord adapter includes a built-in Gateway listener that connects to the WebSocket and forwards events to your webhook endpoint, so the rest of your bot logic looks the same as any other Chat SDK adapter.
31
+
32
+ When someone @mentions the bot, `onNewMention` fires and posts a support card. Calling `thread.subscribe()` tells the SDK to track that thread, so subsequent messages trigger `onSubscribedMessage` where AI SDK generates a response using Claude.
33
+
34
+ ## Steps
35
+
36
+ ### 1\. Scaffold the project and install dependencies
37
+
38
+ Create a new Nuxt app and add the Chat SDK, AI SDK, and adapter packages:
39
+
40
+ `npx nuxi@latest init my-discord-bot cd my-discord-bot pnpm add chat @chat-adapter/discord @chat-adapter/state-redis ai @ai-sdk/anthropic`
41
+
42
+ The `chat` package is the Chat SDK core. The `@chat-adapter/discord` and `@chat-adapter/state-redis` packages are the [Discord platform adapter](https://chat-sdk.dev/adapters/discord) and [Redis state adapter](https://chat-sdk.dev/adapters/redis). The `ai` and `@ai-sdk/anthropic` packages are used to generate responses with Claude.
43
+
44
+ ### 2\. Create a Discord app
45
+
46
+ 1. Go to [discord.com/developers/applications](https://discord.com/developers/applications)
47
+
48
+ 2. Click **New Application**, give it a name, and click **Create**
49
+
50
+ 3. Go to **Bot** in the sidebar and click **Reset Token**. Copy the token, you'll need this as `DISCORD_BOT_TOKEN`
51
+
52
+ 4. Under **Privileged Gateway Intents**, enable **Message Content Intent**
53
+
54
+ 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`
55
+
56
+
57
+ Then set up the Interactions endpoint:
58
+
59
+ 1. In **General Information**, set the **Interactions Endpoint URL** to [`https://your-domain.com/api/webhooks/discord`](https://your-domain.com/api/webhooks/discord)
60
+
61
+ 2. Discord will send a PING to verify the endpoint. You'll need to deploy first or use a tunnel
62
+
63
+
64
+ Then invite the bot to your server:
65
+
66
+ 1. Go to **OAuth2** in the sidebar
67
+
68
+ 2. Under **OAuth2 URL Generator**, select the `bot` scope
69
+
70
+ 3. Under **Bot Permissions**, select:
71
+
72
+ * Send Messages
73
+
74
+ * Create Public Threads
75
+
76
+ * Send Messages in Threads
77
+
78
+ * Read Message History
79
+
80
+ * Add Reactions
81
+
82
+ * Use Slash Commands
83
+
84
+ 4. Copy the generated URL and open it in your browser to invite the bot
85
+
86
+
87
+ ### 3\. Configure environment variables
88
+
89
+ Create a `.env` file in your project root:
90
+
91
+ `DISCORD_BOT_TOKEN=your_bot_token DISCORD_PUBLIC_KEY=your_public_key DISCORD_APPLICATION_ID=your_application_id REDIS_URL=redis://localhost:6379 ANTHROPIC_API_KEY=your_anthropic_api_key`
92
+
93
+ The Discord adapter reads `DISCORD_BOT_TOKEN`, `DISCORD_PUBLIC_KEY`, and `DISCORD_APPLICATION_ID` automatically. The Redis state adapter reads `REDIS_URL`, and AI SDK's Anthropic provider reads `ANTHROPIC_API_KEY`.
94
+
95
+ ### 4\. Create the bot
96
+
97
+ Create `server/lib/bot.tsx` with a `Chat` instance configured with the Discord adapter. This bot uses AI SDK to answer support questions:
98
+
99
+ ``import { Chat, Card, CardText as Text, Actions, Button, Divider } from "chat"; import { createDiscordAdapter } from "@chat-adapter/discord"; import { createRedisState } from "@chat-adapter/state-redis"; import { generateText } from "ai"; import { anthropic } from "@ai-sdk/anthropic"; export const bot = new Chat({ userName: "support-bot", adapters: { discord: createDiscordAdapter(), }, state: createRedisState(), }); bot.onNewMention(async (thread) => { await thread.subscribe(); await thread.post( <Card title="Support"> <Text>Hey! I'm here to help. Ask your question in this thread and I'll do my best to answer it.</Text> <Divider /> <Actions> <Button id="escalate" style="danger">Escalate to Human</Button> </Actions> </Card> ); }); bot.onSubscribedMessage(async (thread, message) => { await thread.startTyping(); const { text } = await generateText({ model: anthropic("claude-sonnet-4-5-20250514"), 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'.", prompt: message.text, }); await thread.post(text); }); bot.onAction("escalate", async (event) => { await event.thread.post( `${event.user.fullName} requested human support. A team member will follow up shortly.` ); });``
100
+
101
+ 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"`.
102
+
103
+ `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.
104
+
105
+ ### 5\. Create the webhook route
106
+
107
+ Create a server route that handles incoming Discord webhooks:
108
+
109
+ ``import { bot } from "../lib/bot"; type Platform = keyof typeof bot.webhooks; export default defineEventHandler(async (event) => { const platform = getRouterParam(event, "platform") as Platform; const handler = bot.webhooks[platform]; if (!handler) { throw createError({ statusCode: 404, message: `Unknown platform: ${platform}` }); } const request = toWebRequest(event); return handler(request, { waitUntil: (task) => event.waitUntil(task), }); });``
110
+
111
+ This creates a `POST /api/webhooks/discord` endpoint. The `waitUntil` option ensures message processing completes after the HTTP response is sent.
112
+
113
+ ### 6\. Set up the Gateway forwarder
114
+
115
+ 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.
116
+
117
+ Create a route that starts the Gateway listener:
118
+
119
+ ``import { bot } from "../../lib/bot"; export default defineEventHandler(async (event) => { await bot.initialize(); const discord = bot.getAdapter("discord"); if (!discord) { throw createError({ statusCode: 404, message: "Discord adapter not configured" }); } const baseUrl = process.env.NUXT_PUBLIC_SITE_URL || "http://localhost:3000"; const webhookUrl = `${baseUrl}/api/webhooks/discord`; const durationMs = 10 * 60 * 1000; // 10 minutes return discord.startGatewayListener( { waitUntil: (task: Promise<unknown>) => event.waitUntil(task) }, durationMs, undefined, webhookUrl, ); });``
120
+
121
+ 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.
122
+
123
+ ### 7\. Test locally
124
+
125
+ 1. Start your development server (`pnpm dev`)
126
+
127
+ 2. Trigger the Gateway listener by visiting [`http://localhost:3000/api/discord/gateway`](http://localhost:3000/api/discord/gateway) in your browser
128
+
129
+ 3. Expose your server with a tunnel (e.g. `ngrok http 3000`)
130
+
131
+ 4. Update the **Interactions Endpoint URL** in your Discord app settings to your tunnel URL (e.g. [`https://abc123.ngrok.io/api/webhooks/discord`](https://abc123.ngrok.io/api/webhooks/discord))
132
+
133
+ 5. @mention the bot in your Discord server. It should respond with a support card
134
+
135
+ 6. Reply in the thread. AI SDK should generate a response
136
+
137
+ 7. Click **Escalate to Human**. The bot should post an escalation message
138
+
139
+
140
+ ### 8\. Add a cron job for production
141
+
142
+ 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`:
143
+
144
+ `{ "crons": [ { "path": "/api/discord/gateway", "schedule": "*/9 * * * *" } ] }`
145
+
146
+ This restarts the Gateway listener every 9 minutes, ensuring continuous connectivity. Protect the endpoint with a `CRON_SECRET` environment variable in production.
147
+
148
+ ### 9\. Deploy to Vercel
149
+
150
+ Deploy your bot to Vercel:
151
+
152
+ `vercel deploy`
153
+
154
+ 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.
155
+
156
+ ## Troubleshooting
157
+
158
+ ### Bot doesn't respond to mentions
159
+
160
+ Check that **Message Content Intent** is enabled under **Privileged Gateway Intents** in your Discord app settings. Without it, the bot can't read message content and won't see @mentions. Also confirm the Gateway listener is running by visiting `/api/discord/gateway` or checking that the cron job is configured in production.
161
+
162
+ ### Interactions endpoint verification fails
163
+
164
+ Discord sends a signed PING request to verify your endpoint. Confirm that `DISCORD_PUBLIC_KEY` matches the value in your Discord app's **General Information** page. A mismatched or missing public key will cause the adapter to reject the verification request.
165
+
166
+ ### Gateway listener disconnects frequently
167
+
168
+ The listener runs for a fixed duration (10 minutes in this guide) and must be restarted. In production, use the cron job shown in step 8 to restart it every 9 minutes. If disconnections happen sooner, check your server logs for WebSocket errors and verify that `DISCORD_BOT_TOKEN` is valid.
169
+
170
+ ### AI responses are slow or time out
171
+
172
+ `generateText` blocks until the full response is returned. For long answers, consider switching to streaming with `streamText` and passing the stream directly to [`thread.post`](http://thread.post)`()`. See the [Streaming docs](https://chat-sdk.dev/docs/streaming) for details.
173
+
174
+ ### Redis connection errors
175
+
176
+ 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.
177
+
178
+ ---
179
+
180
+ [View full KB sitemap](/kb/sitemap.md)
@@ -0,0 +1,134 @@
1
+ # How to build a Slack bot with Next.js and Redis
2
+
3
+ **Author:** Hayden Bleasel, Ben Sabic
4
+
5
+ ---
6
+
7
+ You can build a Slack bot that responds to @mentions, tracks thread context, and sends rich interactive messages using Chat SDK with Next.js. Chat SDK handles the platform integration (webhook verification, message parsing, and the Slack API) while a Redis state adapter tracks which threads your bot has subscribed to across serverless invocations. Together with Vercel for deployment, you get a production-ready Slack bot without managing infrastructure or writing platform-specific glue code.
8
+
9
+ This guide will walk you through scaffolding a Next.js app, configuring a Slack app, wiring up event handlers with Chat SDK, adding interactive cards and buttons, 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 Slack workspace where you can install apps
20
+
21
+ * A Redis instance (local or hosted, such as [Upstash](https://vercel.com/marketplace/upstash))
22
+
23
+
24
+ ## How it works
25
+
26
+ Chat SDK is a unified TypeScript SDK for building chatbots across Slack, Teams, Discord, and other platforms. You register event handlers (like `onNewMention` and `onSubscribedMessage`), and the SDK routes incoming webhooks to them. The Slack adapter handles webhook verification, message parsing, and the Slack API. The Redis state adapter tracks which threads your bot has subscribed to and manages distributed locking for concurrent message handling.
27
+
28
+ When a user @mentions your bot, `onNewMention` fires. Calling `thread.subscribe()` tells the SDK to track that thread, so subsequent messages trigger `onSubscribedMessage`. This lets your bot maintain conversation context across multiple turns without you managing thread state yourself.
29
+
30
+ ## Steps
31
+
32
+ ### 1\. Scaffold the project and install dependencies
33
+
34
+ Create a new Next.js app and add the Chat SDK and adapter packages:
35
+
36
+ `npx create-next-app@latest my-slack-bot --typescript --app cd my-slack-bot pnpm add chat @chat-adapter/slack @chat-adapter/state-redis`
37
+
38
+ The `chat` package is the Chat SDK core. The `@chat-adapter/slack` and `@chat-adapter/state-redis` packages are the [Slack platform adapter](https://chat-sdk.dev/adapters/slack) and [Redis state adapter](https://chat-sdk.dev/adapters/redis).
39
+
40
+ ### 2\. Create a Slack app
41
+
42
+ Go to [api.slack.com/apps](https://api.slack.com/apps), click **Create New App**, then **From an app manifest**.
43
+
44
+ Select your workspace and paste the following manifest:
45
+
46
+ `display_information: name: My Bot description: A bot built with Chat SDK features: bot_user: display_name: My Bot always_online: true oauth_config: scopes: bot: - app_mentions:read - channels:history - channels:read - chat:write - groups:history - groups:read - im:history - im:read - mpim:history - mpim:read - reactions:read - reactions:write - users:read settings: event_subscriptions: request_url: https://your-domain.com/api/webhooks/slack bot_events: - app_mention - message.channels - message.groups - message.im - message.mpim interactivity: is_enabled: true request_url: https://your-domain.com/api/webhooks/slack org_deploy_enabled: false socket_mode_enabled: false token_rotation_enabled: false`
47
+
48
+ Replace [`https://your-domain.com/api/webhooks/slack`](https://your-domain.com/api/webhooks/slack) with your deployed webhook URL, then click **Create**.
49
+
50
+ After creating the app:
51
+
52
+ 1. Go to **OAuth & Permissions**, click **Install to Workspace**, and copy the **Bot User OAuth Token** (`xoxb-...`). You'll need this as `SLACK_BOT_TOKEN`
53
+
54
+ 2. Go to **Basic Information** → **App Credentials** and copy the **Signing Secret**. You'll need this as `SLACK_SIGNING_SECRET`
55
+
56
+
57
+ If you're distributing the app across multiple workspaces via OAuth instead of installing it to one workspace, configure `clientId` and `clientSecret` on the Slack adapter and pass the same redirect URI used during the authorize step into `handleOAuthCallback(request, { redirectUri })` in your callback route.
58
+
59
+ ### 3\. Configure environment variables
60
+
61
+ Create a `.env.local` file in your project root:
62
+
63
+ `SLACK_BOT_TOKEN=xoxb-your-bot-token SLACK_SIGNING_SECRET=your-signing-secret REDIS_URL=redis://localhost:6379`
64
+
65
+ The Slack adapter auto-detects `SLACK_BOT_TOKEN` and `SLACK_SIGNING_SECRET` from your environment, and `createRedisState()` reads `REDIS_URL` automatically.
66
+
67
+ ### 4\. Create the bot
68
+
69
+ Create `lib/bot.ts` with a `Chat` instance configured with the Slack adapter:
70
+
71
+ ``import { Chat } from "chat"; import { createSlackAdapter } from "@chat-adapter/slack"; import { createRedisState } from "@chat-adapter/state-redis"; export const bot = new Chat({ userName: "mybot", adapters: { slack: createSlackAdapter(), }, state: createRedisState(), }); // Respond when someone @mentions the bot bot.onNewMention(async (thread) => { await thread.subscribe(); await thread.post("Hello! I'm listening to this thread now."); }); // Respond to follow-up messages in subscribed threads bot.onSubscribedMessage(async (thread, message) => { await thread.post(`You said: ${message.text}`); });``
72
+
73
+ `onNewMention` fires when a user @mentions your bot. Calling `thread.subscribe()` tells the SDK to track that thread, so subsequent messages trigger `onSubscribedMessage`.
74
+
75
+ ### 5\. Create the webhook route
76
+
77
+ Create a dynamic API route that handles incoming webhooks:
78
+
79
+ ``import { after } from "next/server"; import { bot } from "@/lib/bot"; type Platform = keyof typeof bot.webhooks; export async function POST( request: Request, context: RouteContext<"/api/webhooks/[platform]"> ) { const { platform } = await context.params; const handler = bot.webhooks[platform as Platform]; if (!handler) { return new Response(`Unknown platform: ${platform}`, { status: 404 }); } return handler(request, { waitUntil: (task) => after(() => task), }); }``
80
+
81
+ This creates a `POST /api/webhooks/slack` endpoint. The `waitUntil` option ensures message processing completes after the HTTP response is sent. This is required on serverless platforms where the function would otherwise terminate before your handlers finish.
82
+
83
+ ### 6\. Test locally
84
+
85
+ 1. Start your development server (`pnpm dev`)
86
+
87
+ 2. Expose it with a tunnel (e.g. `ngrok http 3000`)
88
+
89
+ 3. Update the Slack Event Subscriptions **Request URL** to your tunnel URL
90
+
91
+ 4. Invite your bot to a Slack channel (`/invite @mybot`)
92
+
93
+ 5. @mention the bot. It should respond with "Hello! I'm listening to this thread now."
94
+
95
+ 6. Reply in the thread. It should echo your message back
96
+
97
+
98
+ ### 7\. Add interactive features
99
+
100
+ Chat SDK supports rich interactive messages using a JSX-like syntax. Update your bot to send cards with buttons:
101
+
102
+ ``import { Chat, Card, CardText as Text, Actions, Button, Divider } from "chat"; import { createSlackAdapter } from "@chat-adapter/slack"; import { createRedisState } from "@chat-adapter/state-redis"; export const bot = new Chat({ userName: "mybot", adapters: { slack: createSlackAdapter(), }, state: createRedisState(), }); bot.onNewMention(async (thread) => { await thread.subscribe(); await thread.post( <Card title="Welcome!"> <Text>I'm now listening to this thread. Try clicking a button:</Text> <Divider /> <Actions> <Button id="hello" style="primary">Say Hello</Button> <Button id="info">Show Info</Button> </Actions> </Card> ); }); bot.onAction("hello", async (event) => { await event.thread.post(`Hello, ${event.user.fullName}!`); }); bot.onAction("info", async (event) => { await event.thread.post(`You're on ${event.thread.adapter.name}.`); });``
103
+
104
+ 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"`.
105
+
106
+ ### 8\. Deploy to Vercel
107
+
108
+ Deploy your bot to Vercel:
109
+
110
+ `vercel deploy`
111
+
112
+ After deployment, set your environment variables in the Vercel dashboard (`SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET`, `REDIS_URL`). If your manifest used a placeholder URL, update the **Event Subscriptions** and **Interactivity** Request URLs in your [Slack app settings](https://api.slack.com/apps) to your production URL.
113
+
114
+ ## Troubleshooting
115
+
116
+ ### Bot doesn't respond to mentions
117
+
118
+ Check that your Slack app has the `app_mentions:read` scope and that the **Event Subscriptions** Request URL is correct. Slack sends a challenge request when you first set the URL, so your server must be running and reachable.
119
+
120
+ ### Webhook signature verification fails
121
+
122
+ Confirm that `SLACK_SIGNING_SECRET` matches the value in your Slack app's **Basic Information** → **App Credentials**. A mismatched or missing signing secret will cause the adapter to reject incoming webhooks.
123
+
124
+ ### Redis connection errors
125
+
126
+ Verify that `REDIS_URL` is reachable from your deployment environment. If running locally, make sure your Redis instance is started. The state adapter uses Redis for distributed locking, so the bot won't process messages without a working connection.
127
+
128
+ ### Handlers don't run to completion on Vercel
129
+
130
+ Make sure your webhook route passes `waitUntil` to the handler, as shown in step 5. Without it, serverless functions can terminate before your event handlers finish.
131
+
132
+ ---
133
+
134
+ [View full KB sitemap](/kb/sitemap.md)
@@ -0,0 +1,220 @@
1
+ # How to build an AI agent for Slack with Chat SDK and AI SDK
2
+
3
+ **Author:** Ben Sabic
4
+
5
+ ---
6
+
7
+ You can build an AI-powered Slack agent that responds to mentions, maintains conversation history, and calls tools autonomously using Chat SDK and AI SDK. Chat SDK handles the platform integration (webhooks, message formatting, thread tracking), while AI SDK's `ToolLoopAgent` manages the reasoning loop that lets your agent call tools and act on results. Together with Vercel AI Gateway and Redis for state, you get a production-ready Slack agent without managing infrastructure or juggling provider SDKs.
8
+
9
+ This guide will walk you through building a Slack agent with Chat SDK, AI SDK's `ToolLoopAgent`, and Claude via the [Vercel AI Gateway](https://vercel.com/ai-gateway). You'll wire up streaming responses, tool calling, and multi-turn conversation history, then scale your tool set for production with toolpick.
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 Slack workspace where you can install apps
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) with an AI Gateway API key
24
+
25
+
26
+ ## How it works
27
+
28
+ Chat SDK is a unified TypeScript SDK for building chatbots across Slack, Teams, Discord, and other platforms. You register event handlers (like `onNewMention` and `onSubscribedMessage`), and the SDK routes incoming webhooks to them. The Slack adapter handles webhook verification, message parsing, and the Slack API. The Redis state adapter tracks which threads your bot has subscribed to and manages distributed locking for concurrent message handling.
29
+
30
+ AI SDK's `ToolLoopAgent` wraps a language model with tools and runs an autonomous loop: the model generates text or calls a tool, the SDK executes the tool, feeds the result back, and repeats until the model finishes. When you pass a model string like `"anthropic/claude-sonnet-4.6"`, and host your application on Vercel, the AI SDK will route the request through the AI Gateway automatically.
31
+
32
+ Chat SDK accepts any `AsyncIterable<string>` as a message, so you can pass the agent's `fullStream` directly to `thread.post()` for real-time streaming in Slack.
33
+
34
+ ## Steps
35
+
36
+ ### 1\. Scaffold the project, install dependencies, and add Vercel Plugin
37
+
38
+ Create a new Next.js app and add the Chat SDK, AI SDK, and adapter packages:
39
+
40
+ `npx create-next-app@latest my-slack-agent --typescript --app cd my-slack-agent pnpm add chat @chat-adapter/slack @chat-adapter/state-redis ai zod`
41
+
42
+ The `chat` package is the Chat SDK core. The `@chat-adapter/slack` and `@chat-adapter/state-redis` packages are the [Slack platform adapter](https://chat-sdk.dev/adapters/slack) and [Redis state adapter.](https://chat-sdk.dev/adapters/redis) The `ai` package is the AI SDK, which includes the AI Gateway provider and `ToolLoopAgent`. `zod` is used to define tool input schemas.
43
+
44
+ The [Vercel Plugin](https://vercel.com/docs/agent-resources/vercel-plugin) equips your AI coding agent (e.g., Claude Code) with skills, specialist agents, slash commands, and more.
45
+
46
+ `npx plugins add vercel/vercel-plugin`
47
+
48
+ ### 2\. Create a Slack app
49
+
50
+ Go to [api.slack.com/apps](https://api.slack.com/apps), click **Create New App**, then **From a manifest**.
51
+
52
+ Select your workspace and paste this manifest:
53
+
54
+ `display_information: name: AI Agent description: An AI agent built with Chat SDK and AI SDK features: bot_user: display_name: AI Agent always_online: true oauth_config: scopes: bot: - app_mentions:read - channels:history - channels:read - chat:write - groups:history - groups:read - im:history - im:read - mpim:history - mpim:read - reactions:read - reactions:write - users:read settings: event_subscriptions: request_url: https://your-domain.com/api/webhooks/slack bot_events: - app_mention - message.channels - message.groups - message.im - message.mpim interactivity: is_enabled: true request_url: https://your-domain.com/api/webhooks/slack org_deploy_enabled: false socket_mode_enabled: false token_rotation_enabled: false`
55
+
56
+ After creating the app:
57
+
58
+ 1. Go to **Install App**, and install the app to your workspace
59
+
60
+ 2. Go to **OAuth & Permissions** > **OAuth Tokens** and copy the **Bot User OAuth Token**
61
+
62
+ 3. Go to **Basic Information** > **App Credentials** and copy the **Signing Secret**
63
+
64
+
65
+ You'll replace the `request_url` placeholders with your real domain after deploying (or a tunnel URL for local testing).
66
+
67
+ ### 3\. Configure environment variables
68
+
69
+ Create a `.env.local` file in your project root:
70
+
71
+ `SLACK_BOT_TOKEN=xoxb-your-bot-token SLACK_SIGNING_SECRET=your-signing-secret REDIS_URL=redis://localhost:6379 AI_GATEWAY_API_KEY=your-ai-gateway-api-key`
72
+
73
+ The Slack adapter reads `SLACK_BOT_TOKEN` and `SLACK_SIGNING_SECRET` automatically. The Redis state adapter reads `REDIS_URL`. AI SDK uses `AI_GATEWAY_API_KEY` to authenticate with the Vercel AI Gateway, or alternatively, use [OIDC authentication](https://ai-sdk.dev/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments).
74
+
75
+ You can create an AI Gateway API key from your [Vercel dashboard](https://vercel.com) under **AI Gateway** and click **Create an API Key**.
76
+
77
+ ### 4\. Define your agent's tools
78
+
79
+ Create `lib/tools.ts` with the tools your agent can call. This example defines a weather tool and docs tool, but you can add any tools your use case requires:
80
+
81
+ ``import { tool } from "ai"; import { z } from "zod"; export const tools = { getWeather: tool({ description: "Get the current weather for a location", inputSchema: z.object({ location: z.string().describe("City name, e.g. San Francisco"), }), execute: async ({ location }) => { // Replace with a real weather API call const response = await fetch( `https://api.weatherapi.com/v1/current.json?key=${process.env.WEATHER_API_KEY}&q=${encodeURIComponent(location)}` ); const data = await response.json(); return { location, temperature: data.current.temp_f, condition: data.current.condition.text, }; }, }), searchDocs: tool({ description: "Search the company documentation for a topic", inputSchema: z.object({ query: z.string().describe("The search query"), }), execute: async ({ query }) => { // Replace with your actual search implementation return { results: [`Result for: ${query}`] }; }, }), };``
82
+
83
+ Each tool has a `description` (which tells the model when to use it), an `inputSchema` (a Zod schema that the model fills in), and an `execute` function that runs when the tool is called.
84
+
85
+ ### 5\. Create the agent and bot
86
+
87
+ Create `lib/bot.ts` with a `ToolLoopAgent` and a `Chat` instance:
88
+
89
+ `import { Chat } from "chat"; import { toAiMessages } from "chat"; import { createSlackAdapter } from "@chat-adapter/slack"; import { createRedisState } from "@chat-adapter/state-redis"; import { ToolLoopAgent } from "ai"; import { tools } from "./tools"; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-4.6", instructions: "You are a helpful AI assistant in a Slack workspace. " + "Answer questions clearly and use your tools when you need " + "real-time data. Keep responses concise and well-formatted for chat.", tools, }); export const bot = new Chat({ userName: "ai-agent", adapters: { slack: createSlackAdapter(), }, state: createRedisState(), }); // Handle first-time mentions bot.onNewMention(async (thread, message) => { await thread.subscribe(); const result = await agent.stream({ prompt: message.text }); await thread.post(result.fullStream); }); // Handle follow-up messages in subscribed threads bot.onSubscribedMessage(async (thread, message) => { const allMessages = []; for await (const msg of thread.allMessages) { allMessages.push(msg); } const history = await toAiMessages(allMessages); const result = await agent.stream({ messages: history }); await thread.post(result.fullStream); });`
90
+
91
+ When someone @mentions the bot, `onNewMention` fires. The handler subscribes to the thread (to track future messages in that thread) and streams the agent's response. For follow-up messages, `onSubscribedMessage` retrieves the full thread history using `thread.allMessages`, converts it to the AI SDK message format with `toAiMessages`and passes it to the agent so it has a complete conversation context.
92
+
93
+ The `fullStream` is preferred over `textStream` because it preserves paragraph breaks between tool-calling steps. Chat SDK auto-detects the stream type and handles Slack's native streaming API for real-time updates.
94
+
95
+ ### 6\. Wire up the webhook route
96
+
97
+ Create the API route at `app/api/webhooks/[platform]/route.ts`:
98
+
99
+ ``import { after } from "next/server"; import { bot } from "@/lib/bot"; type Platform = keyof typeof bot.webhooks; export async function POST( request: Request, context: RouteContext<"/api/webhooks/[platform]"> ) { const { platform } = await context.params; const handler = bot.webhooks[platform as Platform]; if (!handler) { return new Response(`Unknown platform: ${platform}`, { status: 404 }); } return handler(request, { waitUntil: (task) => after(() => task), }); }``
100
+
101
+ This creates a `POST /api/webhooks/slack` endpoint. The `waitUntil` option ensures your event handlers finish processing after the HTTP response is sent, which is required on serverless platforms where the function would otherwise terminate early.
102
+
103
+ ### 7\. Test locally
104
+
105
+ 1. Start the dev server:
106
+
107
+ `pnpm dev`
108
+
109
+ 2. Expose it with a tunnel:
110
+
111
+ `npx ngrok http 3000`
112
+
113
+ 3. Copy the tunnel URL (for example, `https://abc123.ngrok-free.dev`) and update both **Event Subscriptions** and **Interactivity** Request URLs in your [Slack app settings](https://api.slack.com/apps) to `https://abc123.ngrok-free.dev/api/webhooks/slack`
114
+
115
+ 4. Invite the bot to a channel (`/invite @AI Agent`)
116
+
117
+ 5. @mention the bot with a question. You should see a streaming response appear in the thread. Try asking it to use one of your tools, such as "What's the weather in San Francisco?"
118
+
119
+
120
+ ### 8\. Deploy to Vercel
121
+
122
+ First, link your project and add your environment variables:
123
+
124
+ `vercel link vercel env add SLACK_BOT_TOKEN vercel env add SLACK_SIGNING_SECRET vercel env add REDIS_URL vercel env add AI_GATEWAY_API_KEY`
125
+
126
+ Alternatively, add them in the Vercel dashboard under **Settings** > **Environment Variables**.
127
+
128
+ Then deploy:
129
+
130
+ `vercel`
131
+
132
+ Update the **Event Subscriptions** and **Interactivity** Request URLs in your Slack app settings to your production URL, for example `https://my-slack-agent.vercel.app/api/webhooks/slack`.
133
+
134
+ When deployed to Vercel, AI Gateway supports OIDC-based authentication, so you can also authenticate without a static API key. See the [AI Gateway authentication docs](https://vercel.com/docs/ai-gateway/authentication-and-byok#oidc-tokens).
135
+
136
+ ## Troubleshooting
137
+
138
+ ### Bot doesn't respond to mentions
139
+
140
+ Check that your Slack app has the `app_mentions:read` scope and that the **Event Subscriptions** Request URL is correct. Slack sends a challenge request when you first set the URL, so your server must be running.
141
+
142
+ ### Streaming appears choppy or delayed
143
+
144
+ Chat SDK uses Slack's native streaming API for smooth updates. If you're seeing issues, check that your Redis connection is stable, as the SDK uses distributed locks to manage concurrent messages.
145
+
146
+ ### Tool calls fail silently
147
+
148
+ If the agent calls a tool but no result appears, check for errors in your tool's `execute` function. AI SDK surfaces tool execution errors back to the model, which may attempt to recover. Add error handling in your tools and check your server logs for details.
149
+
150
+ ### Thread history grows too large
151
+
152
+ For long-running threads, the conversation history can exceed the model's context window. Consider limiting the number of messages you pass to the agent by slicing the history array or by using a summarization step for older messages.
153
+
154
+ ## Scaling to many tools with toolpick
155
+
156
+ The agent in this guide has two tools. In production, a Slack agent often grows to 15, 20, or 30 tools as you integrate services like GitHub, [Linear](https://vercel.com/marketplace/linear), [Upstash](https://vercel.com/marketplace/upstash), calendars, and deploy pipelines. At that scale, every tool definition is sent to the model on every step, which increases token costs and makes it harder for the model to pick the right tool.
157
+
158
+ [toolpick](https://www.npmjs.com/package/toolpick) solves this by indexing your tools at startup and selecting only the most relevant ones for each step. It hooks into `ToolLoopAgent` via the `prepareStep` option, so you don't need to change your handler logic.
159
+
160
+ ### Install toolpick
161
+
162
+ `pnpm add toolpick`
163
+
164
+ ### Create a tool index
165
+
166
+ Build an index from your full tool set. toolpick uses a combination of keyword matching and semantic embeddings to find the best tools for each step:
167
+
168
+ `import { createToolIndex } from "toolpick"; const toolIndex = createToolIndex(tools, { embeddingModel: "openai/text-embedding-3-small", });`
169
+
170
+ For higher accuracy with vague queries (like "ship it" or "ping the team"), add a re-ranker model that uses a cheap LLM to pick the final candidates:
171
+
172
+ `const toolIndex = createToolIndex(tools, { embeddingModel: "openai/text-embedding-3-small", rerankerModel: "openai/gpt-4o-mini", });`
173
+
174
+ ### Update your agent to use toolpick
175
+
176
+ Pass `toolIndex.prepareStep()` to your `ToolLoopAgent`. This sets `activeTools` on each step, so the model only sees the tools it needs, while all tools remain available for execution:
177
+
178
+ `const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-4.6", instructions: "..." tools, prepareStep: toolIndex.prepareStep(), });`
179
+
180
+ If the model can't find a relevant tool in the current selection, toolpick automatically moves to the next page of results. After two misses, it exposes all tools as a fallback. Your agent never gets stuck in a loop, unable to find the right tool.
181
+
182
+ ### Enrich descriptions and cache embeddings
183
+
184
+ For an extra accuracy boost, enable `enrichDescriptions` to expand your tool descriptions with synonyms and alternative phrasings. This runs a one-time LLM call during `warmUp()` at server startup. You can also persist the computed embeddings to disk with `fileCache` so subsequent restarts skip the embedding API call entirely:
185
+
186
+ `import { createToolIndex, fileCache } from "toolpick"; const toolIndex = createToolIndex(tools, { embeddingModel: "openai/text-embedding-3-small", rerankerModel: "openai/gpt-4o-mini", enrichDescriptions: true, embeddingCache: fileCache(".toolpick-cache.json"), }); await toolIndex.warmUp();`
187
+
188
+ This setup is optional for agents with a handful of tools, but becomes worthwhile as your tool set grows. The per-step cost of re-ranking with `gpt-4o-mini` is approximately $0.0001, which is negligible compared to the token savings from sending fewer tool definitions to the primary model.
189
+
190
+ ## How to add Teams, Discord, or other platforms
191
+
192
+ Chat SDK supports multiple platforms from a single codebase. The event handlers and agent logic you've already defined work identically across all of them, since the SDK normalizes messages, threads, and reactions into a consistent format.
193
+
194
+ To add Microsoft Teams or another platform, register an additional adapter:
195
+
196
+ `import { createSlackAdapter } from "@chat-adapter/slack"; import { createTeamsAdapter } from "@chat-adapter/teams"; export const bot = new Chat({ adapters: { slack: createSlackAdapter(), teams: createTeamsAdapter(), }, state, userName: "ai-agent", });`
197
+
198
+ 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.
199
+
200
+ Streaming behavior varies by platform. Slack uses its native streaming API for smooth real-time updates, while Teams, Discord, and Google Chat fall back to a post-then-edit pattern that throttles updates to avoid rate limits. You can adjust the update interval with the `streamingUpdateIntervalMs` option when creating your `Chat` instance.
201
+
202
+ See the [Chat SDK adapter directory](https://chat-sdk.dev/adapters) for the full list of supported platforms.
203
+
204
+ ### Other Chat SDK Guides
205
+
206
+ ## Related resources
207
+
208
+ * [Chat SDK streaming](https://chat-sdk.dev/docs/streaming)
209
+
210
+ * [Chat SDK actions](https://chat-sdk.dev/docs/actions) and [cards](https://chat-sdk.dev/docs/cards)
211
+
212
+ * [AI SDK agent documentation](https://ai-sdk.dev/docs/agents/building-agents)
213
+
214
+ * [AI Gateway documentation](https://vercel.com/docs/ai-gateway)
215
+
216
+ * [toolpick documentation](https://github.com/pontusab/toolpick)
217
+
218
+ ---
219
+
220
+ [View full KB sitemap](/kb/sitemap.md)