chat 4.28.1 → 4.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/index.d.ts +501 -0
- package/dist/ai/index.js +500 -0
- package/dist/chat-BPjXsoIl.d.ts +3158 -0
- package/dist/chunk-HD375J7S.js +128 -0
- package/dist/index.d.ts +6 -3143
- package/dist/index.js +45 -139
- package/dist/{jsx-runtime-DxGwoLu2.d.ts → jsx-runtime-CnDs8rPr.d.ts} +1 -1
- package/dist/jsx-runtime.d.ts +1 -1
- package/docs/adapters.mdx +35 -5
- package/docs/ai/ai-sdk-tools.mdx +227 -0
- package/docs/ai/index.mdx +69 -0
- package/docs/ai/meta.json +4 -0
- package/docs/{api → ai}/to-ai-messages.mdx +16 -3
- package/docs/ai/types.mdx +243 -0
- package/docs/api/chat.mdx +50 -10
- package/docs/api/index.mdx +4 -6
- package/docs/api/message.mdx +1 -1
- package/docs/api/meta.json +0 -1
- package/docs/api/postable-message.mdx +3 -3
- package/docs/api/thread.mdx +1 -1
- package/docs/concurrency.mdx +54 -15
- package/docs/contributing/building.mdx +1 -1
- package/docs/contributing/testing.mdx +4 -0
- package/docs/direct-messages.mdx +10 -1
- package/docs/files.mdx +20 -0
- package/docs/getting-started.mdx +5 -1
- package/docs/handling-events.mdx +10 -7
- package/docs/index.mdx +4 -1
- package/docs/meta.json +4 -0
- package/docs/posting-messages.mdx +3 -1
- package/docs/slack-primitives.mdx +320 -0
- package/docs/slash-commands.mdx +4 -4
- package/docs/streaming.mdx +4 -4
- package/docs/subject.mdx +1 -1
- package/docs/testing.mdx +142 -0
- package/docs/threads-messages-channels.mdx +1 -1
- package/docs/usage.mdx +8 -4
- package/package.json +23 -2
- package/resources/guides/create-a-discord-support-bot-with-nuxt-and-redis.md +5 -1
- package/resources/guides/how-to-build-a-slack-bot-with-next-js-and-redis.md +5 -1
- package/resources/guides/how-to-build-an-ai-agent-for-slack-with-chat-sdk-and-ai-sdk.md +1 -1
- package/resources/guides/human-in-the-loop-with-chat-sdk-and-workflow-sdk.md +176 -0
- package/resources/guides/liveblocks-chat-sdk-ai-sdk.md +165 -0
- package/resources/guides/run-and-track-deploys-from-slack.md +7 -5
- package/resources/guides/ship-a-github-code-review-bot-with-hono-and-redis.md +5 -1
- package/resources/guides/slack-bot-vercel-blob.md +254 -0
- package/resources/guides/triage-form-submissions-with-chat-sdk.md +3 -1
- package/resources/templates.json +5 -0
|
@@ -2,18 +2,31 @@
|
|
|
2
2
|
title: toAiMessages
|
|
3
3
|
description: Convert Chat SDK messages to AI SDK conversation format.
|
|
4
4
|
type: reference
|
|
5
|
+
related:
|
|
6
|
+
- /docs/ai
|
|
7
|
+
- /docs/ai/ai-sdk-tools
|
|
8
|
+
- /docs/ai/types
|
|
9
|
+
- /docs/streaming
|
|
5
10
|
---
|
|
6
11
|
|
|
7
|
-
Convert an array of `Message` objects into the `{ role, content }[]` format expected by AI
|
|
12
|
+
Convert an array of [`Message`](/docs/api/message) objects into the `{ role, content }[]` format expected by the AI SDK. The output is structurally compatible with AI SDK's `ModelMessage[]`.
|
|
8
13
|
|
|
9
14
|
```typescript
|
|
10
|
-
import { toAiMessages } from "chat";
|
|
15
|
+
import { toAiMessages } from "chat/ai";
|
|
11
16
|
```
|
|
12
17
|
|
|
18
|
+
<Callout>
|
|
19
|
+
`toAiMessages` is also re-exported from the main `chat` entrypoint
|
|
20
|
+
for backwards compatibility (with a `@deprecated` JSDoc hint), but
|
|
21
|
+
new code should import it from [`chat/ai`](/docs/ai) alongside
|
|
22
|
+
[`createChatTools`](/docs/ai/ai-sdk-tools) and the rest of the AI
|
|
23
|
+
utilities.
|
|
24
|
+
</Callout>
|
|
25
|
+
|
|
13
26
|
## Usage
|
|
14
27
|
|
|
15
28
|
```typescript title="lib/bot.ts" lineNumbers
|
|
16
|
-
import { toAiMessages } from "chat";
|
|
29
|
+
import { toAiMessages } from "chat/ai";
|
|
17
30
|
|
|
18
31
|
bot.onSubscribedMessage(async (thread, message) => {
|
|
19
32
|
const result = await thread.adapter.fetchMessages(thread.id, { limit: 20 });
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Types
|
|
3
|
+
description: TypeScript types exported from the chat/ai subpath.
|
|
4
|
+
type: reference
|
|
5
|
+
related:
|
|
6
|
+
- /docs/ai
|
|
7
|
+
- /docs/ai/ai-sdk-tools
|
|
8
|
+
- /docs/ai/to-ai-messages
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
Every type exported from `chat/ai`. Pulling these from the subpath keeps the optional `ai` and `zod` peer deps out of bundles that don't import them.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import type {
|
|
15
|
+
AiMessage,
|
|
16
|
+
AiUserMessage,
|
|
17
|
+
AiAssistantMessage,
|
|
18
|
+
AiMessagePart,
|
|
19
|
+
AiTextPart,
|
|
20
|
+
AiImagePart,
|
|
21
|
+
AiFilePart,
|
|
22
|
+
ToAiMessagesOptions,
|
|
23
|
+
ChatBinding,
|
|
24
|
+
ChatTools,
|
|
25
|
+
ChatToolName,
|
|
26
|
+
ChatToolPreset,
|
|
27
|
+
ChatWriteToolName,
|
|
28
|
+
ApprovalConfig,
|
|
29
|
+
ToolOptions,
|
|
30
|
+
ToolOverrides,
|
|
31
|
+
} from "chat/ai";
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Conversation messages
|
|
35
|
+
|
|
36
|
+
Used by [`toAiMessages`](/docs/ai/to-ai-messages) and any agent prompt you build by hand. The shapes are structurally compatible with AI SDK's `ModelMessage` so the result is directly assignable to `prompt` / `messages`.
|
|
37
|
+
|
|
38
|
+
### AiMessage
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
type AiMessage = AiUserMessage | AiAssistantMessage;
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
A single normalized turn in a conversation — the array form is what AI SDK calls expect.
|
|
45
|
+
|
|
46
|
+
### AiUserMessage
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
interface AiUserMessage {
|
|
50
|
+
role: "user";
|
|
51
|
+
content: string | AiMessagePart[];
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
User content can be plain text, or a multipart array when attachments are present.
|
|
56
|
+
|
|
57
|
+
### AiAssistantMessage
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
interface AiAssistantMessage {
|
|
61
|
+
role: "assistant";
|
|
62
|
+
content: string;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Assistant turns are always plain strings — `toAiMessages` produces this for any message authored by the bot itself (`author.isMe === true`).
|
|
67
|
+
|
|
68
|
+
### AiMessagePart
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
type AiMessagePart = AiTextPart | AiImagePart | AiFilePart;
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The discriminated union used inside multipart user messages.
|
|
75
|
+
|
|
76
|
+
### AiTextPart
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
interface AiTextPart {
|
|
80
|
+
type: "text";
|
|
81
|
+
text: string;
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### AiImagePart
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
interface AiImagePart {
|
|
89
|
+
type: "image";
|
|
90
|
+
image: DataContent | URL;
|
|
91
|
+
mediaType?: string;
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`DataContent` matches AI SDK's type — `string | Uint8Array | ArrayBuffer | Buffer`.
|
|
96
|
+
|
|
97
|
+
### AiFilePart
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
interface AiFilePart {
|
|
101
|
+
type: "file";
|
|
102
|
+
data: DataContent | URL;
|
|
103
|
+
filename?: string;
|
|
104
|
+
mediaType: string;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`toAiMessages` emits text-like attachments (JSON, XML, YAML, source files, etc.) as file parts.
|
|
109
|
+
|
|
110
|
+
### ToAiMessagesOptions
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
interface ToAiMessagesOptions {
|
|
114
|
+
includeNames?: boolean;
|
|
115
|
+
transformMessage?: (
|
|
116
|
+
aiMessage: AiMessage,
|
|
117
|
+
source: Message
|
|
118
|
+
) => AiMessage | null | Promise<AiMessage | null>;
|
|
119
|
+
onUnsupportedAttachment?: (
|
|
120
|
+
attachment: Attachment,
|
|
121
|
+
message: Message
|
|
122
|
+
) => void;
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
See [`toAiMessages`](/docs/ai/to-ai-messages) for behavior and examples.
|
|
127
|
+
|
|
128
|
+
## Tools
|
|
129
|
+
|
|
130
|
+
Returned by [`createChatTools`](/docs/ai/ai-sdk-tools) and used to configure it.
|
|
131
|
+
|
|
132
|
+
### ChatBinding
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
type ChatBinding = Chat<any, any>;
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Whatever [`Chat`](/docs/api/chat) instance the tools should dispatch operations against. The generics are intentionally loose so any strongly-typed `Chat<TAdapters, TState>` is assignable.
|
|
139
|
+
|
|
140
|
+
### ChatTools
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
type ChatTools = ReturnType<typeof createChatTools>;
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Convenience alias for the object returned by `createChatTools` — handy when you want to type a wrapper or pass the toolset around.
|
|
147
|
+
|
|
148
|
+
### ChatToolPreset
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
type ChatToolPreset = "reader" | "messenger" | "moderator";
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Predefined toolset scopes. See [Presets](/docs/ai/ai-sdk-tools#presets) for the exact tool list per preset.
|
|
155
|
+
|
|
156
|
+
### ChatToolName
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
type ChatToolName =
|
|
160
|
+
| "fetchMessages"
|
|
161
|
+
| "fetchChannelMessages"
|
|
162
|
+
| "fetchThread"
|
|
163
|
+
| "listThreads"
|
|
164
|
+
| "getThreadParticipants"
|
|
165
|
+
| "getChannelInfo"
|
|
166
|
+
| "getUser"
|
|
167
|
+
| "startTyping"
|
|
168
|
+
| "postMessage"
|
|
169
|
+
| "postChannelMessage"
|
|
170
|
+
| "sendDirectMessage"
|
|
171
|
+
| "editMessage"
|
|
172
|
+
| "deleteMessage"
|
|
173
|
+
| "addReaction"
|
|
174
|
+
| "removeReaction"
|
|
175
|
+
| "subscribeThread"
|
|
176
|
+
| "unsubscribeThread";
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The names of every generated tool. Useful when typing per-tool overrides.
|
|
180
|
+
|
|
181
|
+
### ChatWriteToolName
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
type ChatWriteToolName =
|
|
185
|
+
| "postMessage"
|
|
186
|
+
| "postChannelMessage"
|
|
187
|
+
| "sendDirectMessage"
|
|
188
|
+
| "editMessage"
|
|
189
|
+
| "deleteMessage"
|
|
190
|
+
| "addReaction"
|
|
191
|
+
| "removeReaction"
|
|
192
|
+
| "subscribeThread"
|
|
193
|
+
| "unsubscribeThread";
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The names of every mutating tool. Useful when wiring per-tool approval overrides.
|
|
197
|
+
|
|
198
|
+
### ApprovalConfig
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
type ApprovalConfig =
|
|
202
|
+
| boolean
|
|
203
|
+
| Partial<Record<ChatWriteToolName, boolean>>;
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Controls the `requireApproval` option:
|
|
207
|
+
|
|
208
|
+
- `true` (default) — every write tool needs approval.
|
|
209
|
+
- `false` — no write tool needs approval.
|
|
210
|
+
- object — per-tool override; unspecified write tools fall back to `true`.
|
|
211
|
+
|
|
212
|
+
### ToolOptions
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
interface ToolOptions {
|
|
216
|
+
needsApproval?: boolean;
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Common options accepted by every standalone write-tool factory (e.g. `postMessage(chat, { needsApproval: false })`).
|
|
221
|
+
|
|
222
|
+
### ToolOverrides
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
type ToolOverrides = Partial<
|
|
226
|
+
Pick<
|
|
227
|
+
Tool,
|
|
228
|
+
| "description"
|
|
229
|
+
| "inputExamples"
|
|
230
|
+
| "metadata"
|
|
231
|
+
| "needsApproval"
|
|
232
|
+
| "onInputAvailable"
|
|
233
|
+
| "onInputDelta"
|
|
234
|
+
| "onInputStart"
|
|
235
|
+
| "providerOptions"
|
|
236
|
+
| "strict"
|
|
237
|
+
| "title"
|
|
238
|
+
| "toModelOutput"
|
|
239
|
+
>
|
|
240
|
+
>;
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Per-tool overrides accepted by `createChatTools({ overrides })`. Core fields like `execute`, `inputSchema`, `outputSchema`, `type`, `id`, and `args` are intentionally excluded so tool semantics stay stable across upgrades.
|
package/docs/api/chat.mdx
CHANGED
|
@@ -74,9 +74,36 @@ bot.onNewMention(async (thread, message) => {
|
|
|
74
74
|
}}
|
|
75
75
|
/>
|
|
76
76
|
|
|
77
|
+
### onDirectMessage
|
|
78
|
+
|
|
79
|
+
Fires for every direct message when registered. Direct message handlers run before `onSubscribedMessage`, `onNewMention`, and pattern handlers. If no direct message handler is registered, unsubscribed DMs fall through to `onNewMention` for backward compatibility.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
bot.onDirectMessage(async (thread, message, channel) => {
|
|
83
|
+
await thread.post(`Got your DM in ${channel.id}: ${message.text}`);
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
<TypeTable
|
|
88
|
+
type={{
|
|
89
|
+
thread: {
|
|
90
|
+
description: 'The DM thread where the message occurred.',
|
|
91
|
+
type: 'Thread',
|
|
92
|
+
},
|
|
93
|
+
message: {
|
|
94
|
+
description: 'The direct message.',
|
|
95
|
+
type: 'Message',
|
|
96
|
+
},
|
|
97
|
+
channel: {
|
|
98
|
+
description: 'The DM channel.',
|
|
99
|
+
type: 'Channel',
|
|
100
|
+
},
|
|
101
|
+
}}
|
|
102
|
+
/>
|
|
103
|
+
|
|
77
104
|
### onSubscribedMessage
|
|
78
105
|
|
|
79
|
-
Fires for every new message in a subscribed thread. Once subscribed,
|
|
106
|
+
Fires for every new message in a subscribed non-DM thread. Once subscribed, messages (including @-mentions) route here instead of `onNewMention`. DM threads route to `onDirectMessage` first when a direct message handler is registered.
|
|
80
107
|
|
|
81
108
|
```typescript
|
|
82
109
|
bot.onSubscribedMessage(async (thread, message) => {
|
|
@@ -474,16 +501,20 @@ const slack = bot.getAdapter("slack");
|
|
|
474
501
|
|
|
475
502
|
#### Direct client access
|
|
476
503
|
|
|
477
|
-
|
|
504
|
+
Access the platform's typed native API client directly via an SDK-named getter — `.webClient` on Slack, `.linearClient` on Linear, `.octokit` on GitHub:
|
|
478
505
|
|
|
479
506
|
```typescript
|
|
507
|
+
// Slack - full WebClient from @slack/web-api
|
|
508
|
+
const slack = bot.getAdapter("slack").webClient;
|
|
509
|
+
await slack.pins.add({ channel: "C123ABC", timestamp: "1234567890.123456" });
|
|
510
|
+
|
|
480
511
|
// Linear - full LinearClient from @linear/sdk
|
|
481
|
-
const linear = bot.getAdapter("linear").
|
|
512
|
+
const linear = bot.getAdapter("linear").linearClient;
|
|
482
513
|
const issue = await linear.issue("ENG-123");
|
|
483
514
|
const project = await issue.project;
|
|
484
515
|
|
|
485
516
|
// GitHub - full Octokit from @octokit/rest
|
|
486
|
-
const github = bot.getAdapter("github").
|
|
517
|
+
const github = bot.getAdapter("github").octokit;
|
|
487
518
|
const { data: pulls } = await github.rest.pulls.list({
|
|
488
519
|
owner: "vercel",
|
|
489
520
|
repo: "chat",
|
|
@@ -491,16 +522,25 @@ const { data: pulls } = await github.rest.pulls.list({
|
|
|
491
522
|
});
|
|
492
523
|
```
|
|
493
524
|
|
|
494
|
-
The client uses the credentials from your adapter config. For multi-tenant adapters (Linear, GitHub), it returns the client for the current webhook request context.
|
|
525
|
+
The client uses the credentials from your adapter config. For multi-tenant / multi-workspace adapters (Slack, Linear, GitHub), it returns the client bound to the credentials for the current webhook request context.
|
|
526
|
+
|
|
527
|
+
<Callout type="info">
|
|
528
|
+
The previous `.client` getter still works on all three adapters as a deprecated alias for `.webClient` / `.linearClient` / `.octokit`.
|
|
529
|
+
</Callout>
|
|
495
530
|
|
|
496
531
|
<Callout type="warn">
|
|
497
|
-
|
|
532
|
+
Multi-tenant adapters (GitHub App without a fixed installation ID, Linear with per-org OAuth, Slack in multi-workspace mode) require a webhook handler context to resolve credentials when the native client getter is accessed. Calling it outside a handler throws.
|
|
533
|
+
|
|
534
|
+
For Slack, you can also bind a token explicitly outside a webhook with `adapter.withBotToken(token, () => adapter.webClient.…)` — useful for cron jobs or workflows. The same pattern is required when `botToken` is configured as an async resolver function, since `.webClient` resolves the token synchronously.
|
|
535
|
+
|
|
536
|
+
Single-tenant adapters (PAT, API key, static `botToken` string, or a synchronous `botToken` resolver) work anywhere.
|
|
498
537
|
</Callout>
|
|
499
538
|
|
|
500
|
-
| Adapter |
|
|
501
|
-
|
|
502
|
-
|
|
|
503
|
-
|
|
|
539
|
+
| Adapter | Getter | Type |
|
|
540
|
+
|---------|--------|------|
|
|
541
|
+
| Slack | `.webClient` | `WebClient` from `@slack/web-api` |
|
|
542
|
+
| Linear | `.linearClient` | `LinearClient` from `@linear/sdk` |
|
|
543
|
+
| GitHub | `.octokit` | `Octokit` from `@octokit/rest` |
|
|
504
544
|
|
|
505
545
|
### openDM
|
|
506
546
|
|
package/docs/api/index.mdx
CHANGED
|
@@ -20,12 +20,6 @@ import { Chat, root, paragraph, text, Card, Button, emoji } from "chat";
|
|
|
20
20
|
| [`Message`](/docs/api/message) | Normalized message with text, AST, author, and metadata |
|
|
21
21
|
| [`ScheduledMessage`](/docs/api/thread#scheduledmessage) | Returned by `thread.schedule()` / `channel.schedule()` with `cancel()` |
|
|
22
22
|
|
|
23
|
-
## Utilities
|
|
24
|
-
|
|
25
|
-
| Export | Description |
|
|
26
|
-
|--------|-------------|
|
|
27
|
-
| [`toAiMessages`](/docs/api/to-ai-messages) | Convert `Message[]` to AI SDK `{ role, content }[]` format |
|
|
28
|
-
|
|
29
23
|
## Message formats
|
|
30
24
|
|
|
31
25
|
| Export | Description |
|
|
@@ -36,3 +30,7 @@ import { Chat, root, paragraph, text, Card, Button, emoji } from "chat";
|
|
|
36
30
|
| [`Cards`](/docs/api/cards) | Rich card components — `Card`, `Text`, `Button`, `Actions`, etc. |
|
|
37
31
|
| [`Markdown`](/docs/api/markdown) | AST builder functions — `root`, `paragraph`, `text`, `strong`, etc. |
|
|
38
32
|
| [`Modals`](/docs/api/modals) | Modal form components — `Modal`, `TextInput`, `Select`, etc. |
|
|
33
|
+
|
|
34
|
+
## AI utilities
|
|
35
|
+
|
|
36
|
+
`toAiMessages`, `createChatTools`, and the supporting types live in the [`chat/ai`](/docs/ai) subpath — see the [AI section](/docs/ai) for the full reference.
|
package/docs/api/message.mdx
CHANGED
|
@@ -194,7 +194,7 @@ Links found in incoming messages are extracted and exposed as `LinkPreview` obje
|
|
|
194
194
|
/>
|
|
195
195
|
|
|
196
196
|
<Callout type="info">
|
|
197
|
-
When using [`toAiMessages()`](/docs/
|
|
197
|
+
When using [`toAiMessages()`](/docs/ai/to-ai-messages), link metadata is automatically appended to the message content. Embedded message links are labeled as `[Embedded message: ...]` so the AI model understands the context.
|
|
198
198
|
</Callout>
|
|
199
199
|
|
|
200
200
|
### Platform support
|
package/docs/api/meta.json
CHANGED
|
@@ -38,7 +38,7 @@ await thread.post({ raw: "Hello world" });
|
|
|
38
38
|
type: 'string',
|
|
39
39
|
},
|
|
40
40
|
attachments: {
|
|
41
|
-
description: '
|
|
41
|
+
description: 'Typed media attachments for adapters that support outgoing attachments.',
|
|
42
42
|
type: 'Attachment[]',
|
|
43
43
|
},
|
|
44
44
|
files: {
|
|
@@ -63,7 +63,7 @@ await thread.post({ markdown: "**Bold** and _italic_" });
|
|
|
63
63
|
type: 'string',
|
|
64
64
|
},
|
|
65
65
|
attachments: {
|
|
66
|
-
description: '
|
|
66
|
+
description: 'Typed media attachments for adapters that support outgoing attachments.',
|
|
67
67
|
type: 'Attachment[]',
|
|
68
68
|
},
|
|
69
69
|
files: {
|
|
@@ -92,7 +92,7 @@ await thread.post({
|
|
|
92
92
|
type: 'Root',
|
|
93
93
|
},
|
|
94
94
|
attachments: {
|
|
95
|
-
description: '
|
|
95
|
+
description: 'Typed media attachments for adapters that support outgoing attachments.',
|
|
96
96
|
type: 'Attachment[]',
|
|
97
97
|
},
|
|
98
98
|
files: {
|
package/docs/api/thread.mdx
CHANGED
|
@@ -146,7 +146,7 @@ if (participants.length > 1) {
|
|
|
146
146
|
|
|
147
147
|
## subscribe / unsubscribe
|
|
148
148
|
|
|
149
|
-
Manage thread subscriptions. Subscribed threads route all messages to `onSubscribedMessage` handlers.
|
|
149
|
+
Manage thread subscriptions. Subscribed non-DM threads route all messages to `onSubscribedMessage` handlers. DM threads route to `onDirectMessage` first when a direct message handler is registered.
|
|
150
150
|
|
|
151
151
|
```typescript
|
|
152
152
|
await thread.subscribe();
|
package/docs/concurrency.mdx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Overlapping Messages
|
|
3
|
-
description: Control how overlapping messages on the same thread are handled
|
|
3
|
+
description: Control how overlapping messages on the same thread are handled - burst, queue, debounce, drop, or process concurrently.
|
|
4
4
|
type: guide
|
|
5
5
|
prerequisites:
|
|
6
6
|
- /docs/handling-events
|
|
@@ -57,11 +57,49 @@ A done → drain: [B, C, D] → handler(D, { skipped: [B, C] })
|
|
|
57
57
|
D done → queue empty → release lock
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
### Burst
|
|
61
|
+
|
|
62
|
+
Waits for `debounceMs` before the first handler on an idle thread, then drains the collected burst like `queue`. The latest message is dispatched, and earlier messages in the burst are available as `context.skipped`.
|
|
63
|
+
|
|
64
|
+
Use this for assistant-style bots where users often send one logical turn as several short messages inside a small window and you want one response with full context. Compared to `debounce`, `burst` keeps the earlier messages in `context.skipped` instead of dropping them, and it flushes queued messages that arrive while the handler is running.
|
|
65
|
+
|
|
66
|
+
Choose `debounce` when the latest message replaces earlier ones, like rapid corrections. Choose `burst` when earlier messages still matter, like "hey", "quick question", and then the actual question. The tradeoff is that even a lone message waits for `debounceMs` before the handler runs.
|
|
67
|
+
|
|
68
|
+
```typescript title="lib/bot.ts" lineNumbers
|
|
69
|
+
const bot = new Chat({
|
|
70
|
+
concurrency: { strategy: "burst", debounceMs: 1000 },
|
|
71
|
+
// ...
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
bot.onNewMention(async (thread, message, context) => {
|
|
75
|
+
const turn = [...(context?.skipped ?? []), message]
|
|
76
|
+
.map((m) => m.text)
|
|
77
|
+
.join("\n\n");
|
|
78
|
+
|
|
79
|
+
const response = await generateAIResponse(turn);
|
|
80
|
+
await thread.post(response);
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Flow:**
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
A arrives → acquire lock → enqueue A → sleep(debounceMs)
|
|
88
|
+
B arrives → lock busy → enqueue B
|
|
89
|
+
C arrives → lock busy → enqueue C
|
|
90
|
+
... debounceMs elapses ...
|
|
91
|
+
→ drain: [A, B, C] → handler(C, { skipped: [A, B] })
|
|
92
|
+
D arrives while C is running → lock busy → enqueue D
|
|
93
|
+
E arrives while C is running → lock busy → enqueue E
|
|
94
|
+
C done → drain: [D, E] → handler(E, { skipped: [D] })
|
|
95
|
+
E done → queue empty → release lock
|
|
96
|
+
```
|
|
97
|
+
|
|
60
98
|
### Debounce
|
|
61
99
|
|
|
62
|
-
|
|
100
|
+
The first message waits for `debounceMs`. Messages that arrive during that window replace the pending message, so only the **final message in the burst window** is processed.
|
|
63
101
|
|
|
64
|
-
This is particularly useful for platforms like **WhatsApp** and **Telegram** where users tend to send a flurry of short messages in quick succession instead of composing a single message
|
|
102
|
+
This is particularly useful for platforms like **WhatsApp** and **Telegram** where users tend to send a flurry of short messages in quick succession instead of composing a single message - "hey", "quick question", "how do I reset my password?" arriving as three separate webhooks within a few seconds. Without debounce, the bot would respond to "hey" before the actual question even arrives. With debounce, the SDK waits briefly and processes only the final message in the window.
|
|
65
103
|
|
|
66
104
|
```typescript title="lib/bot.ts" lineNumbers
|
|
67
105
|
const bot = new Chat({
|
|
@@ -118,10 +156,10 @@ const bot = new Chat({
|
|
|
118
156
|
| Option | Strategies | Default | Description |
|
|
119
157
|
|--------|-----------|---------|-------------|
|
|
120
158
|
| `strategy` | all | `"drop"` | The concurrency strategy to use |
|
|
121
|
-
| `maxQueueSize` | queue,
|
|
122
|
-
| `onQueueFull` | queue,
|
|
123
|
-
| `queueEntryTtlMs` | queue, debounce | `90000` | TTL for queued entries in milliseconds. Expired entries are discarded on dequeue |
|
|
124
|
-
| `debounceMs` | debounce | `1500` | Debounce window in milliseconds |
|
|
159
|
+
| `maxQueueSize` | queue, burst | `10` | Maximum queued messages per thread |
|
|
160
|
+
| `onQueueFull` | queue, burst | `"drop-oldest"` | Whether to evict the oldest or reject the newest message when the queue is full |
|
|
161
|
+
| `queueEntryTtlMs` | queue, debounce, burst | `90000` | TTL for queued entries in milliseconds. Expired entries are discarded on dequeue |
|
|
162
|
+
| `debounceMs` | debounce, burst | `1500` | Debounce window in milliseconds |
|
|
125
163
|
| `maxConcurrent` | concurrent | `Infinity` | Max concurrent handlers per thread |
|
|
126
164
|
|
|
127
165
|
<Callout type="warn">
|
|
@@ -130,7 +168,7 @@ const bot = new Chat({
|
|
|
130
168
|
|
|
131
169
|
## MessageContext
|
|
132
170
|
|
|
133
|
-
All handler types (`onNewMention`, `onSubscribedMessage`, `onNewMessage`) accept an optional `MessageContext` as their last parameter. It is
|
|
171
|
+
All handler types (`onNewMention`, `onSubscribedMessage`, `onNewMessage`) accept an optional `MessageContext` as their last parameter. It is populated when using the `queue` strategy for queued messages, and when using `burst` for a collapsed turn. A lone `burst` message receives `skipped: []` and `totalSinceLastHandler: 1`.
|
|
134
172
|
|
|
135
173
|
```typescript
|
|
136
174
|
interface MessageContext {
|
|
@@ -186,7 +224,7 @@ const bot = new Chat({
|
|
|
186
224
|
|
|
187
225
|
## State adapter requirements
|
|
188
226
|
|
|
189
|
-
The `queue` and `
|
|
227
|
+
The `queue`, `debounce`, and `burst` strategies require three additional methods on your state adapter:
|
|
190
228
|
|
|
191
229
|
| Method | Description |
|
|
192
230
|
|--------|-------------|
|
|
@@ -202,12 +240,12 @@ All strategies emit structured log events at `info` level:
|
|
|
202
240
|
|
|
203
241
|
| Event | Strategy | Data |
|
|
204
242
|
|-------|----------|------|
|
|
205
|
-
| `message-queued` | queue | threadId, messageId, queueDepth |
|
|
206
|
-
| `message-dequeued` | queue, debounce | threadId, messageId, skippedCount |
|
|
207
|
-
| `message-dropped` | drop, queue | threadId, messageId, reason |
|
|
208
|
-
| `message-expired` | queue, debounce | threadId, messageId |
|
|
243
|
+
| `message-queued` | queue, burst | threadId, messageId, queueDepth |
|
|
244
|
+
| `message-dequeued` | queue, debounce, burst | threadId, messageId, skippedCount for queue/burst |
|
|
245
|
+
| `message-dropped` | drop, queue, burst | threadId, messageId, reason |
|
|
246
|
+
| `message-expired` | queue, debounce, burst | threadId, messageId |
|
|
209
247
|
| `message-superseded` | debounce | threadId, droppedId |
|
|
210
|
-
| `message-debouncing` | debounce | threadId, messageId, debounceMs |
|
|
248
|
+
| `message-debouncing` | debounce, burst | threadId, messageId, debounceMs |
|
|
211
249
|
| `message-debounce-reset` | debounce | threadId, messageId |
|
|
212
250
|
|
|
213
251
|
## Choosing a strategy
|
|
@@ -216,7 +254,8 @@ All strategies emit structured log events at `info` level:
|
|
|
216
254
|
|----------|----------|-----|
|
|
217
255
|
| Simple bots, one-shot commands | `drop` | No complexity, no queue overhead |
|
|
218
256
|
| AI chatbots, customer support | `queue` | Never lose messages; handler sees full conversation context |
|
|
219
|
-
|
|
|
257
|
+
| AI chatbots with multi-message user turns | `burst` | Wait for the idle burst window, then respond once with every message in that window |
|
|
258
|
+
| WhatsApp/Telegram bots, rapid corrections | `debounce` | Users send many short messages in quick succession; wait briefly and keep only the latest |
|
|
220
259
|
| Stateless lookups, translations | `concurrent` | Maximum throughput, no ordering needed |
|
|
221
260
|
|
|
222
261
|
## Backward compatibility
|
|
@@ -370,7 +370,7 @@ parseMessage(raw: unknown): Message<unknown> {
|
|
|
370
370
|
|
|
371
371
|
### Sending messages
|
|
372
372
|
|
|
373
|
-
Use `extractCard()` and `extractFiles()` from `@chat-adapter/shared` to check for rich content. Use your format converter's `renderPostable()` to convert the message to platform format.
|
|
373
|
+
Use `extractCard()` and `extractFiles()` from `@chat-adapter/shared` to check for rich content. Use `extractPostableAttachments()` if your adapter maps normalized `Attachment` objects to platform-native media uploads. Use your format converter's `renderPostable()` to convert the message to platform format.
|
|
374
374
|
|
|
375
375
|
```typescript title="src/adapter.ts" lineNumbers
|
|
376
376
|
async postMessage(
|
|
@@ -14,6 +14,10 @@ Chat SDK adapters are the trust boundary between your application and a platform
|
|
|
14
14
|
|
|
15
15
|
All adapters in this repo use [vitest](https://vitest.dev) with `@vitest/coverage-v8`. Community adapters should follow the same convention.
|
|
16
16
|
|
|
17
|
+
<Callout type="info">
|
|
18
|
+
This page covers the hand-rolled patterns used inside this repo's `packages/`. If you're testing a bot or a custom adapter as a **consumer** of Chat SDK, use [`@chat-adapter/tests`](/docs/testing) — it ships factories and Vitest matchers that cover most of these patterns in a few lines.
|
|
19
|
+
</Callout>
|
|
20
|
+
|
|
17
21
|
## Unit tests
|
|
18
22
|
|
|
19
23
|
### Factory function
|
package/docs/direct-messages.mdx
CHANGED
|
@@ -12,9 +12,18 @@ Open direct message conversations with users using `bot.openDM()`. For globally
|
|
|
12
12
|
|
|
13
13
|
DMs behave slightly differently from channel messages:
|
|
14
14
|
|
|
15
|
-
- **
|
|
15
|
+
- **Direct message handlers** — if you register `onDirectMessage`, every incoming DM routes there before `onSubscribedMessage`, `onNewMention`, and pattern handlers. This keeps DM-centric flows like WhatsApp conversations, Telegram DMs, and web chat on one consistent handler.
|
|
16
|
+
- **Mention fallback** — if no `onDirectMessage` handlers are registered, DMs continue through normal routing. Unsubscribed DMs are treated as mentions, so existing `onNewMention` bots keep working without requiring the user to @-mention the bot.
|
|
16
17
|
- **Per-conversation threading** — Each top-level DM starts a new conversation. Thread replies within a DM continue the same conversation, giving you the same per-thread isolation as channels.
|
|
17
18
|
|
|
19
|
+
## Handle incoming DMs
|
|
20
|
+
|
|
21
|
+
```typescript title="lib/bot.ts" lineNumbers
|
|
22
|
+
bot.onDirectMessage(async (thread, message) => {
|
|
23
|
+
await thread.post(`You said: ${message.text}`);
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
18
27
|
## Open a DM
|
|
19
28
|
|
|
20
29
|
### From an Author object
|
package/docs/files.mdx
CHANGED
|
@@ -25,6 +25,26 @@ await thread.post({
|
|
|
25
25
|
});
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
### Typed attachments
|
|
29
|
+
|
|
30
|
+
Use `attachments` when you already have normalized `Attachment` objects and the adapter supports typed outgoing media. Telegram supports one outgoing attachment per message and uses the native media method for the attachment type:
|
|
31
|
+
|
|
32
|
+
```typescript title="lib/bot.ts" lineNumbers
|
|
33
|
+
await thread.post({
|
|
34
|
+
markdown: "Here's the image:",
|
|
35
|
+
attachments: [
|
|
36
|
+
{
|
|
37
|
+
data: imageBuffer,
|
|
38
|
+
name: "diagram.png",
|
|
39
|
+
mimeType: "image/png",
|
|
40
|
+
type: "image",
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Outgoing `attachments` are available on `{ raw }`, `{ markdown }`, and `{ ast }` messages. Card messages use `files` for uploads. Use `files` for generic uploads. On Telegram, `files` always upload as documents, while `attachments` preserve image, audio, video, or file media type. Use `data` or `fetchData` for private/authenticated files; URL-only attachments must be public URLs Telegram can fetch directly.
|
|
47
|
+
|
|
28
48
|
### Multiple files
|
|
29
49
|
|
|
30
50
|
```typescript title="lib/bot.ts" lineNumbers
|
package/docs/getting-started.mdx
CHANGED
|
@@ -25,4 +25,8 @@ Connect your bot to chat platforms and persist state across restarts.
|
|
|
25
25
|
|
|
26
26
|
Browse all official and community adapters on the [Adapters](/adapters) page.
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
## Resources
|
|
29
|
+
|
|
30
|
+
- [The Complete Guide to Chat SDK](https://vercel.com/kb/guide/the-complete-guide-to-chat-sdk) — End-to-end walkthrough that takes you from zero to a deployed multi-platform bot, covering adapters, state, handlers, cards, and streaming.
|
|
31
|
+
|
|
32
|
+
See all guides and templates on the [resources](/resources) page.
|