chat 4.13.1 → 4.13.2

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 (53) hide show
  1. package/README.md +19 -31
  2. package/dist/{chunk-WKJEG4FE.js → chunk-THM4ACIE.js} +12 -4
  3. package/dist/chunk-THM4ACIE.js.map +1 -0
  4. package/dist/index.d.ts +409 -415
  5. package/dist/index.js +63 -29
  6. package/dist/index.js.map +1 -1
  7. package/dist/{jsx-runtime-COVsDskT.d.ts → jsx-runtime-Bdt1Dwzf.d.ts} +71 -71
  8. package/dist/jsx-runtime.d.ts +1 -1
  9. package/dist/jsx-runtime.js +1 -1
  10. package/docs/actions.mdx +98 -0
  11. package/docs/adapters/discord.mdx +217 -0
  12. package/docs/adapters/gchat.mdx +232 -0
  13. package/docs/adapters/github.mdx +225 -0
  14. package/docs/adapters/index.mdx +110 -0
  15. package/docs/adapters/linear.mdx +207 -0
  16. package/docs/adapters/meta.json +12 -0
  17. package/docs/adapters/slack.mdx +293 -0
  18. package/docs/adapters/teams.mdx +225 -0
  19. package/docs/api/cards.mdx +217 -0
  20. package/docs/api/channel.mdx +176 -0
  21. package/docs/api/chat.mdx +469 -0
  22. package/docs/api/index.mdx +29 -0
  23. package/docs/api/markdown.mdx +235 -0
  24. package/docs/api/message.mdx +163 -0
  25. package/docs/api/meta.json +14 -0
  26. package/docs/api/modals.mdx +222 -0
  27. package/docs/api/postable-message.mdx +166 -0
  28. package/docs/api/thread.mdx +186 -0
  29. package/docs/cards.mdx +213 -0
  30. package/docs/direct-messages.mdx +56 -0
  31. package/docs/emoji.mdx +77 -0
  32. package/docs/ephemeral-messages.mdx +77 -0
  33. package/docs/error-handling.mdx +147 -0
  34. package/docs/files.mdx +77 -0
  35. package/docs/getting-started.mdx +12 -0
  36. package/docs/guides/code-review-hono.mdx +248 -0
  37. package/docs/guides/discord-nuxt.mdx +237 -0
  38. package/docs/guides/meta.json +4 -0
  39. package/docs/guides/slack-nextjs.mdx +245 -0
  40. package/docs/index.mdx +92 -0
  41. package/docs/meta.json +20 -0
  42. package/docs/modals.mdx +208 -0
  43. package/docs/posting-messages.mdx +177 -0
  44. package/docs/slash-commands.mdx +110 -0
  45. package/docs/state/index.mdx +31 -0
  46. package/docs/state/ioredis.mdx +81 -0
  47. package/docs/state/memory.mdx +52 -0
  48. package/docs/state/meta.json +9 -0
  49. package/docs/state/redis.mdx +93 -0
  50. package/docs/streaming.mdx +99 -0
  51. package/docs/usage.mdx +338 -0
  52. package/package.json +10 -10
  53. package/dist/chunk-WKJEG4FE.js.map +0 -1
@@ -0,0 +1,93 @@
1
+ ---
2
+ title: Redis
3
+ description: Production state adapter using the official redis package.
4
+ type: reference
5
+ prerequisites:
6
+ - /docs/getting-started
7
+ ---
8
+
9
+ The recommended state adapter for production. Uses the official [redis](https://www.npmjs.com/package/redis) package.
10
+
11
+ ## Installation
12
+
13
+ ```sh title="Terminal"
14
+ pnpm add @chat-adapter/state-redis
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```typescript title="lib/bot.ts" lineNumbers
20
+ import { Chat } from "chat";
21
+ import { createRedisState } from "@chat-adapter/state-redis";
22
+
23
+ const bot = new Chat({
24
+ userName: "mybot",
25
+ adapters: { /* ... */ },
26
+ state: createRedisState({
27
+ url: process.env.REDIS_URL!,
28
+ }),
29
+ });
30
+ ```
31
+
32
+ ### Using an existing client
33
+
34
+ If you already have a connected Redis client, pass it directly:
35
+
36
+ ```typescript title="lib/bot.ts" lineNumbers
37
+ import { createClient } from "redis";
38
+
39
+ const client = createClient({ url: "redis://localhost:6379" });
40
+ await client.connect();
41
+
42
+ const state = createRedisState({ client });
43
+ ```
44
+
45
+ ### Key prefix
46
+
47
+ All keys are namespaced under a configurable prefix (default: `"chat-sdk"`):
48
+
49
+ ```typescript title="lib/bot.ts" lineNumbers
50
+ const state = createRedisState({
51
+ url: process.env.REDIS_URL!,
52
+ keyPrefix: "my-bot",
53
+ });
54
+ ```
55
+
56
+ ## Configuration
57
+
58
+ | Option | Required | Description |
59
+ |--------|----------|-------------|
60
+ | `url` | Yes* | Redis connection URL |
61
+ | `client` | No | Existing `redis` client instance |
62
+ | `keyPrefix` | No | Prefix for all keys (default: `"chat-sdk"`) |
63
+
64
+ *Either `url` or `client` is required.
65
+
66
+ ## Environment variables
67
+
68
+ ```bash title=".env.local"
69
+ REDIS_URL=redis://localhost:6379
70
+ ```
71
+
72
+ For serverless deployments (Vercel, AWS Lambda), use a serverless-compatible Redis provider like [Upstash](https://upstash.com).
73
+
74
+ ## Key structure
75
+
76
+ ```
77
+ {keyPrefix}:subscriptions - SET of subscribed thread IDs
78
+ {keyPrefix}:lock:{threadId} - Lock key with TTL
79
+ ```
80
+
81
+ ## Production recommendations
82
+
83
+ - Use Redis 6.0+ for best performance
84
+ - Enable Redis persistence (RDB or AOF)
85
+ - Use Redis Cluster for high availability
86
+ - Set appropriate memory limits
87
+
88
+ ## Features
89
+
90
+ - Persistent subscriptions across restarts
91
+ - Distributed locking across multiple instances
92
+ - Automatic reconnection
93
+ - Key prefix namespacing
@@ -0,0 +1,99 @@
1
+ ---
2
+ title: Streaming
3
+ description: Stream real-time text responses from AI models and other async sources to chat platforms.
4
+ type: guide
5
+ prerequisites:
6
+ - /docs/usage
7
+ ---
8
+
9
+ Chat SDK accepts any `AsyncIterable<string>` as a message, enabling real-time streaming of AI responses and other incremental content to chat platforms.
10
+
11
+ ## AI SDK integration
12
+
13
+ Pass an AI SDK `textStream` directly to `thread.post()`:
14
+
15
+ ```typescript title="lib/bot.ts" lineNumbers
16
+ import { ToolLoopAgent } from "ai";
17
+
18
+ const agent = new ToolLoopAgent({
19
+ model: "anthropic/claude-4.5-sonnet",
20
+ instructions: "You are a helpful assistant.",
21
+ });
22
+
23
+ bot.onNewMention(async (thread, message) => {
24
+ const result = await agent.stream({ prompt: message.text });
25
+ await thread.post(result.textStream);
26
+ });
27
+ ```
28
+
29
+ ## Custom streams
30
+
31
+ Any async iterable works:
32
+
33
+ ```typescript title="lib/bot.ts" lineNumbers
34
+ const stream = (async function* () {
35
+ yield "Processing";
36
+ yield "...";
37
+ yield " done!";
38
+ })();
39
+
40
+ await thread.post(stream);
41
+ ```
42
+
43
+ ## Platform behavior
44
+
45
+ | Platform | Method | Description |
46
+ |----------|--------|-------------|
47
+ | Slack | Native streaming API | Uses Slack's `chatStream` for smooth, real-time updates |
48
+ | Teams | Post + Edit | Posts a message then edits it as chunks arrive |
49
+ | Google Chat | Post + Edit | Posts a message then edits it as chunks arrive |
50
+ | Discord | Post + Edit | Posts a message then edits it as chunks arrive |
51
+
52
+ The post+edit fallback throttles edits to avoid rate limits. Configure the update interval when creating your `Chat` instance:
53
+
54
+ ```typescript title="lib/bot.ts" lineNumbers
55
+ const bot = new Chat({
56
+ // ...
57
+ streamingUpdateIntervalMs: 500, // Default: 500ms
58
+ });
59
+ ```
60
+
61
+ ## Stop blocks (Slack only)
62
+
63
+ When streaming in Slack, you can attach Block Kit elements to the final message using `stopBlocks`. This is useful for adding action buttons after a streamed response completes:
64
+
65
+ ```typescript title="lib/bot.ts" lineNumbers
66
+ await thread.stream(textStream, {
67
+ stopBlocks: [
68
+ {
69
+ type: "actions",
70
+ elements: [{
71
+ type: "button",
72
+ text: { type: "plain_text", text: "Retry" },
73
+ action_id: "retry",
74
+ }],
75
+ },
76
+ ],
77
+ });
78
+ ```
79
+
80
+ ## Streaming with conversation history
81
+
82
+ Combine message history with streaming for multi-turn AI conversations:
83
+
84
+ ```typescript title="lib/bot.ts" lineNumbers
85
+ bot.onSubscribedMessage(async (thread, message) => {
86
+ // Fetch recent messages for context
87
+ const result = await thread.adapter.fetchMessages(thread.id, { limit: 20 });
88
+
89
+ const history = result.messages
90
+ .filter((msg) => msg.text.trim())
91
+ .map((msg) => ({
92
+ role: msg.author.isMe ? "assistant" as const : "user" as const,
93
+ content: msg.text,
94
+ }));
95
+
96
+ const response = await agent.stream({ prompt: history });
97
+ await thread.post(response.textStream);
98
+ });
99
+ ```
package/docs/usage.mdx ADDED
@@ -0,0 +1,338 @@
1
+ ---
2
+ title: Usage
3
+ description: Event handlers, threads, channels, messages, and the core patterns of Chat SDK.
4
+ type: guide
5
+ prerequisites:
6
+ - /docs/getting-started
7
+ ---
8
+
9
+ Chat SDK uses an event-driven architecture. You register handlers for different event types, and the SDK routes incoming webhooks to the appropriate handler.
10
+
11
+ ## Event handlers
12
+
13
+ ### Mention handler
14
+
15
+ `onNewMention` fires when your bot is @-mentioned in a thread it hasn't subscribed to yet. This is the primary entry point for new conversations.
16
+
17
+ ```typescript title="lib/bot.ts" lineNumbers
18
+ bot.onNewMention(async (thread, message) => {
19
+ await thread.subscribe();
20
+ await thread.post("Hello! I'm now listening to this thread.");
21
+ });
22
+ ```
23
+
24
+ ### Subscribed message handler
25
+
26
+ `onSubscribedMessage` fires for every new message in a thread your bot has subscribed to. Once subscribed, all messages (including @mentions) route here instead of `onNewMention`.
27
+
28
+ ```typescript title="lib/bot.ts" lineNumbers
29
+ bot.onSubscribedMessage(async (thread, message) => {
30
+ // Check if the bot was @-mentioned specifically
31
+ if (message.isMention) {
32
+ await thread.post("You mentioned me!");
33
+ return;
34
+ }
35
+
36
+ await thread.post(`Got your message: ${message.text}`);
37
+ });
38
+ ```
39
+
40
+ ### Pattern handler
41
+
42
+ `onNewMessage` fires for messages matching a regex pattern in threads the bot is **not** subscribed to. Useful for keyword-triggered responses.
43
+
44
+ ```typescript title="lib/bot.ts" lineNumbers
45
+ bot.onNewMessage(/^help$/i, async (thread, message) => {
46
+ await thread.post("Here's how I can help...");
47
+ });
48
+ ```
49
+
50
+ ### Slash command handler
51
+
52
+ `onSlashCommand` fires when a user invokes a `/command` in the message composer. See [Slash Commands](/docs/slash-commands) for the full guide.
53
+
54
+ ```typescript title="lib/bot.ts" lineNumbers
55
+ bot.onSlashCommand("/status", async (event) => {
56
+ await event.channel.post("All systems operational!");
57
+ });
58
+ ```
59
+
60
+ ### Reaction handler
61
+
62
+ `onReaction` fires when users add or remove emoji reactions to messages.
63
+
64
+ ```typescript title="lib/bot.ts" lineNumbers
65
+ import { emoji } from "chat";
66
+
67
+ bot.onReaction(["thumbs_up", "heart"], async (event) => {
68
+ if (!event.added) return;
69
+
70
+ await event.adapter.addReaction(
71
+ event.threadId,
72
+ event.messageId,
73
+ emoji.raised_hands
74
+ );
75
+ });
76
+ ```
77
+
78
+ The `event` object includes:
79
+
80
+ | Property | Type | Description |
81
+ |----------|------|-------------|
82
+ | `emoji` | `EmojiValue` | Normalized emoji for comparison |
83
+ | `rawEmoji` | `string` | Platform-specific emoji string |
84
+ | `added` | `boolean` | `true` if added, `false` if removed |
85
+ | `user` | `Author` | The user who reacted |
86
+ | `message` | `Message` (optional) | The message that was reacted to |
87
+ | `thread` | `Thread` | Thread for posting replies |
88
+ | `adapter` | `Adapter` | The platform adapter |
89
+
90
+ ### Assistant thread handler
91
+
92
+ `onAssistantThreadStarted` fires when a user opens a new assistant thread in Slack. Use it with the Slack Assistants API to set suggested prompts and status indicators. See [Slack Assistants API](/docs/adapters/slack#slack-assistants-api) for details.
93
+
94
+ ```typescript title="lib/bot.ts" lineNumbers
95
+ bot.onAssistantThreadStarted(async (event) => {
96
+ const slack = bot.getAdapter("slack") as SlackAdapter;
97
+ await slack.setSuggestedPrompts(event.channelId, event.threadTs, [
98
+ { title: "Get started", message: "What can you help me with?" },
99
+ ]);
100
+ });
101
+ ```
102
+
103
+ ### App Home handler
104
+
105
+ `onAppHomeOpened` fires when a user opens your bot's Home tab in Slack. Use it to publish a dynamic view.
106
+
107
+ ```typescript title="lib/bot.ts" lineNumbers
108
+ bot.onAppHomeOpened(async (event) => {
109
+ const slack = bot.getAdapter("slack") as SlackAdapter;
110
+ await slack.publishHomeView(event.userId, {
111
+ type: "home",
112
+ blocks: [{ type: "section", text: { type: "mrkdwn", text: "Welcome!" } }],
113
+ });
114
+ });
115
+ ```
116
+
117
+ ## Threads
118
+
119
+ A `Thread` represents a conversation thread on any platform. It provides methods for posting messages, managing subscriptions, and accessing message history.
120
+
121
+ ### Post a message
122
+
123
+ ```typescript title="lib/bot.ts" lineNumbers
124
+ // Plain text
125
+ await thread.post("Hello world");
126
+
127
+ // Markdown (converted to each platform's format)
128
+ await thread.post("**Bold** and _italic_ text");
129
+
130
+ // Structured message with attachments
131
+ await thread.post({
132
+ markdown: "Here's a file:",
133
+ files: [{ data: buffer, filename: "report.pdf" }],
134
+ });
135
+ ```
136
+
137
+ ### Subscribe and unsubscribe
138
+
139
+ Subscriptions persist across restarts (stored in your state adapter). When a thread is subscribed, all messages route to `onSubscribedMessage`.
140
+
141
+ ```typescript title="lib/bot.ts" lineNumbers
142
+ await thread.subscribe();
143
+ await thread.unsubscribe();
144
+
145
+ const subscribed = await thread.isSubscribed();
146
+ ```
147
+
148
+ ### Typing indicator
149
+
150
+ ```typescript title="lib/bot.ts"
151
+ await thread.startTyping();
152
+ ```
153
+
154
+ <Callout type="info">
155
+ Not all platforms support typing indicators. The call is a no-op on unsupported platforms. See the [adapter feature matrix](/docs/adapters) for details.
156
+ </Callout>
157
+
158
+ ### Message history
159
+
160
+ Access recent messages or iterate through full history:
161
+
162
+ ```typescript title="lib/bot.ts" lineNumbers
163
+ // Cached messages from the webhook payload
164
+ const recent = thread.recentMessages;
165
+
166
+ // Newest first (auto-paginates)
167
+ for await (const msg of thread.messages) {
168
+ console.log(msg.text);
169
+ }
170
+
171
+ // Oldest first (auto-paginates)
172
+ for await (const msg of thread.allMessages) {
173
+ console.log(msg.text);
174
+ }
175
+ ```
176
+
177
+ ### Thread state
178
+
179
+ Store typed, per-thread state that persists across requests:
180
+
181
+ ```typescript title="lib/bot.ts" lineNumbers
182
+ interface ThreadState {
183
+ aiMode?: boolean;
184
+ context?: string;
185
+ }
186
+
187
+ const bot = new Chat<typeof adapters, ThreadState>({
188
+ // ...config
189
+ });
190
+
191
+ bot.onNewMention(async (thread) => {
192
+ await thread.setState({ aiMode: true });
193
+
194
+ const state = await thread.state; // ThreadState | null
195
+ if (state?.aiMode) {
196
+ // AI mode is enabled
197
+ }
198
+ });
199
+ ```
200
+
201
+ State is stored in your state adapter with a 30-day TTL. Use `{ replace: true }` to replace state entirely instead of merging:
202
+
203
+ ```typescript title="lib/bot.ts"
204
+ await thread.setState({ aiMode: false }, { replace: true });
205
+ ```
206
+
207
+ ## Messages
208
+
209
+ Incoming messages are normalized across platforms into a consistent format:
210
+
211
+ | Property | Type | Description |
212
+ |----------|------|-------------|
213
+ | `id` | `string` | Platform message ID |
214
+ | `threadId` | `string` | Thread ID in `adapter:channel:thread` format |
215
+ | `text` | `string` | Plain text content |
216
+ | `formatted` | `Root` | mdast AST representation |
217
+ | `raw` | `unknown` | Original platform-specific payload |
218
+ | `author` | `Author` | Message author info |
219
+ | `metadata` | `MessageMetadata` | Timestamps and edit status |
220
+ | `attachments` | `Attachment[]` (optional) | File attachments |
221
+ | `isMention` | `boolean` (optional) | Whether the bot was @-mentioned |
222
+
223
+ ### Author
224
+
225
+ ```typescript lineNumbers
226
+ interface Author {
227
+ userId: string;
228
+ userName: string;
229
+ fullName: string;
230
+ isBot: boolean | "unknown";
231
+ isMe: boolean; // true if message is from the bot itself
232
+ }
233
+ ```
234
+
235
+ ### Sent messages
236
+
237
+ When you post a message, you get back a `SentMessage` with methods to edit, delete, and react:
238
+
239
+ ```typescript title="lib/bot.ts" lineNumbers
240
+ const sent = await thread.post("Processing...");
241
+ // Do some work...
242
+ await sent.edit("Done!");
243
+
244
+ // Or delete
245
+ await sent.delete();
246
+
247
+ // Add/remove reactions
248
+ await sent.addReaction(emoji.check);
249
+ await sent.removeReaction(emoji.check);
250
+ ```
251
+
252
+ ## Channels
253
+
254
+ A `Channel` represents the container that holds threads (e.g., a Slack channel, a Teams conversation). Navigate to a channel from a thread or get one directly:
255
+
256
+ ```typescript title="lib/bot.ts" lineNumbers
257
+ // From a thread
258
+ const channel = thread.channel;
259
+
260
+ // Directly by ID
261
+ const channel = bot.channel("slack:C123ABC");
262
+ ```
263
+
264
+ ### List threads
265
+
266
+ Iterate threads in a channel, most recently active first:
267
+
268
+ ```typescript title="lib/bot.ts" lineNumbers
269
+ for await (const thread of channel.threads()) {
270
+ console.log(thread.rootMessage.text, thread.replyCount);
271
+ }
272
+ ```
273
+
274
+ ### Channel messages
275
+
276
+ Iterate top-level messages (not thread replies):
277
+
278
+ ```typescript title="lib/bot.ts" lineNumbers
279
+ for await (const msg of channel.messages) {
280
+ console.log(msg.text);
281
+ }
282
+ ```
283
+
284
+ ### Post to a channel
285
+
286
+ Post a top-level message (not inside a thread):
287
+
288
+ ```typescript title="lib/bot.ts"
289
+ await channel.post("Hello channel!");
290
+ ```
291
+
292
+ ### Channel metadata
293
+
294
+ ```typescript title="lib/bot.ts"
295
+ const info = await channel.fetchMetadata();
296
+ console.log(info.name, info.memberCount);
297
+ ```
298
+
299
+ ## Thread ID format
300
+
301
+ All thread IDs follow the pattern `{adapter}:{channel}:{thread}`:
302
+
303
+ - **Slack**: `slack:C123ABC:1234567890.123456`
304
+ - **Teams**: `teams:{base64(conversationId)}:{base64(serviceUrl)}`
305
+ - **Google Chat**: `gchat:spaces/ABC123:{base64(threadName)}`
306
+ - **Discord**: `discord:{guildId}:{channelId}/{messageId}`
307
+
308
+ You typically don't need to construct these yourself — they're provided by the SDK in event handlers.
309
+
310
+ ## Logging
311
+
312
+ Configure logging when creating the `Chat` instance:
313
+
314
+ ```typescript title="lib/bot.ts" lineNumbers
315
+ // Use a built-in log level
316
+ const bot = new Chat({
317
+ // ...
318
+ logger: "debug", // "debug" | "info" | "warn" | "error" | "silent"
319
+ });
320
+
321
+ // Or use ConsoleLogger for child loggers
322
+ import { ConsoleLogger } from "chat";
323
+
324
+ const logger = new ConsoleLogger("info");
325
+ const bot = new Chat({
326
+ // ...
327
+ logger,
328
+ });
329
+ ```
330
+
331
+ Pass child loggers to adapters for prefixed log output:
332
+
333
+ ```typescript title="lib/bot.ts"
334
+ createSlackAdapter({
335
+ // ...
336
+ logger: logger.child("slack"),
337
+ });
338
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chat",
3
- "version": "4.13.1",
3
+ "version": "4.13.2",
4
4
  "description": "Unified chat abstraction for Slack, Teams, Google Chat, and Discord",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -21,10 +21,11 @@
21
21
  }
22
22
  },
23
23
  "files": [
24
- "dist"
24
+ "dist",
25
+ "docs"
25
26
  ],
26
27
  "dependencies": {
27
- "@workflow/serde": "4.0.1-beta.1",
28
+ "@workflow/serde": "4.1.0-beta.2",
28
29
  "mdast-util-to-string": "^4.0.0",
29
30
  "remark-gfm": "^4.0.0",
30
31
  "remark-parse": "^11.0.0",
@@ -40,12 +41,12 @@
40
41
  },
41
42
  "repository": {
42
43
  "type": "git",
43
- "url": "git+https://github.com/vercel-labs/chat.git",
44
+ "url": "git+https://github.com/vercel/chat.git",
44
45
  "directory": "packages/chat-sdk"
45
46
  },
46
- "homepage": "https://github.com/vercel-labs/chat#readme",
47
+ "homepage": "https://github.com/vercel/chat#readme",
47
48
  "bugs": {
48
- "url": "https://github.com/vercel-labs/chat/issues"
49
+ "url": "https://github.com/vercel/chat/issues"
49
50
  },
50
51
  "publishConfig": {
51
52
  "access": "public"
@@ -60,12 +61,11 @@
60
61
  ],
61
62
  "license": "MIT",
62
63
  "scripts": {
63
- "build": "rm -rf dist && tsup",
64
+ "build": "pnpm clean && tsup && cp -r ../../apps/docs/content/docs ./docs",
64
65
  "dev": "tsup --watch",
65
- "test": "vitest run",
66
+ "test": "vitest run --coverage",
66
67
  "test:watch": "vitest",
67
68
  "typecheck": "tsc --noEmit",
68
- "lint": "biome check src",
69
- "clean": "rm -rf dist"
69
+ "clean": "rm -rf dist docs"
70
70
  }
71
71
  }