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
package/docs/handling-events.mdx
CHANGED
|
@@ -16,9 +16,10 @@ Chat SDK uses an event-driven architecture. You register handlers for different
|
|
|
16
16
|
|
|
17
17
|
When a message arrives, the SDK evaluates handlers in this order:
|
|
18
18
|
|
|
19
|
-
1. **
|
|
20
|
-
2. **
|
|
21
|
-
3. **
|
|
19
|
+
1. **Direct messages** — if the thread is a DM and any `onDirectMessage` handlers are registered, they fire before `onSubscribedMessage`, `onNewMention`, and pattern handlers.
|
|
20
|
+
2. **Subscribed threads** — if the thread is subscribed, `onSubscribedMessage` fires and no other message handler runs. DMs only reach this step when no `onDirectMessage` handlers are registered.
|
|
21
|
+
3. **Mentions** — if the bot is @-mentioned in an unsubscribed thread, `onNewMention` fires. Unsubscribed DMs without direct handlers are treated as mentions for backward compatibility.
|
|
22
|
+
4. **Pattern matches** — if the message text matches any `onNewMessage` regex patterns, those handlers fire.
|
|
22
23
|
|
|
23
24
|
Reactions, slash commands, actions, and modals have their own dedicated routing and are not affected by subscription state.
|
|
24
25
|
|
|
@@ -68,7 +69,9 @@ bot.onNewMention(async (thread, message) => {
|
|
|
68
69
|
|
|
69
70
|
## Handling subscribed messages
|
|
70
71
|
|
|
71
|
-
`onSubscribedMessage` fires for every new message in a thread your bot has subscribed to. Once subscribed,
|
|
72
|
+
`onSubscribedMessage` fires for every new message in a non-DM thread your bot has subscribed to. Once subscribed, messages (including @-mentions) route here instead of `onNewMention`.
|
|
73
|
+
|
|
74
|
+
If an `onDirectMessage` handler is registered, DM messages route there before subscription routing. Without a direct handler, subscribed DMs route to `onSubscribedMessage`.
|
|
72
75
|
|
|
73
76
|
```typescript title="lib/bot.ts" lineNumbers
|
|
74
77
|
bot.onSubscribedMessage(async (thread, message) => {
|
|
@@ -94,7 +97,7 @@ Messages sent by the bot itself do not trigger this handler. You don't need to f
|
|
|
94
97
|
### Example: Conversational AI with history
|
|
95
98
|
|
|
96
99
|
```typescript title="lib/bot.ts" lineNumbers
|
|
97
|
-
import { toAiMessages } from "chat";
|
|
100
|
+
import { toAiMessages } from "chat/ai";
|
|
98
101
|
|
|
99
102
|
bot.onSubscribedMessage(async (thread, message) => {
|
|
100
103
|
await thread.startTyping();
|
|
@@ -111,7 +114,7 @@ bot.onSubscribedMessage(async (thread, message) => {
|
|
|
111
114
|
});
|
|
112
115
|
```
|
|
113
116
|
|
|
114
|
-
See [`toAiMessages`](/docs/
|
|
117
|
+
See [`toAiMessages`](/docs/ai/to-ai-messages) for all options including multi-user name prefixing, message transforms, and attachment handling.
|
|
115
118
|
|
|
116
119
|
### Example: Unsubscribe on keyword
|
|
117
120
|
|
|
@@ -299,7 +302,7 @@ These handlers are specific to the Slack platform and require the Slack adapter.
|
|
|
299
302
|
|
|
300
303
|
### Handling assistant threads
|
|
301
304
|
|
|
302
|
-
`onAssistantThreadStarted` fires when a user opens a new assistant thread in Slack. Use it with the [Slack Assistants API](/adapters/slack#slack-assistants-api) to set suggested prompts and status indicators.
|
|
305
|
+
`onAssistantThreadStarted` fires when a user opens a new assistant thread in Slack. Use it with the [Slack Assistants API](/adapters/official/slack#slack-assistants-api) to set suggested prompts and status indicators.
|
|
303
306
|
|
|
304
307
|
```typescript title="lib/bot.ts" lineNumbers
|
|
305
308
|
bot.onAssistantThreadStarted(async (event) => {
|
package/docs/index.mdx
CHANGED
|
@@ -55,10 +55,11 @@ Each adapter factory auto-detects credentials from environment variables (`SLACK
|
|
|
55
55
|
| Microsoft Teams | `@chat-adapter/teams` | Yes | Read-only | Yes | Yes | Native (DMs) / Buffered | Yes |
|
|
56
56
|
| Google Chat | `@chat-adapter/gchat` | Yes | Yes | Yes | No | Post+Edit | Yes |
|
|
57
57
|
| Discord | `@chat-adapter/discord` | Yes | Yes | Yes | No | Post+Edit | Yes |
|
|
58
|
-
| Telegram | `@chat-adapter/telegram` | Yes | Yes | Partial | No | Post+Edit | Yes |
|
|
58
|
+
| Telegram | `@chat-adapter/telegram` | Yes | Yes | Partial | No | Private chat drafts / Post+Edit | Yes |
|
|
59
59
|
| GitHub | `@chat-adapter/github` | Yes | Yes | No | No | Buffered | No |
|
|
60
60
|
| Linear | `@chat-adapter/linear` | Yes | Yes | No | No | Agent sessions / Post+Edit | No |
|
|
61
61
|
| WhatsApp | `@chat-adapter/whatsapp` | N/A | Yes | Partial | No | Buffered | Yes |
|
|
62
|
+
| Twilio | `@chat-adapter/twilio` | N/A | No | Fallback | No | Buffered | Yes |
|
|
62
63
|
| Messenger | `@chat-adapter/messenger` | Yes | Receive-only | Partial | No | Buffered | Yes |
|
|
63
64
|
|
|
64
65
|
## AI coding agent support
|
|
@@ -78,6 +79,7 @@ The SDK is distributed as a set of packages you install based on your needs:
|
|
|
78
79
|
| Package | Description |
|
|
79
80
|
|---------|-------------|
|
|
80
81
|
| `chat` | Core SDK with `Chat` class, types, JSX runtime, and utilities |
|
|
82
|
+
| `chat/ai` | [AI utilities](/docs/ai) — [`createChatTools`](/docs/ai/ai-sdk-tools) for agent operations and [`toAiMessages`](/docs/ai/to-ai-messages) for converting chat history into AI SDK prompts |
|
|
81
83
|
| `@chat-adapter/slack` | Slack adapter |
|
|
82
84
|
| `@chat-adapter/teams` | Microsoft Teams adapter |
|
|
83
85
|
| `@chat-adapter/gchat` | Google Chat adapter |
|
|
@@ -86,6 +88,7 @@ The SDK is distributed as a set of packages you install based on your needs:
|
|
|
86
88
|
| `@chat-adapter/github` | GitHub Issues adapter |
|
|
87
89
|
| `@chat-adapter/linear` | Linear Issues adapter |
|
|
88
90
|
| `@chat-adapter/whatsapp` | WhatsApp Business adapter |
|
|
91
|
+
| `@chat-adapter/twilio` | Twilio SMS and MMS adapter |
|
|
89
92
|
| `@chat-adapter/messenger` | Facebook Messenger adapter |
|
|
90
93
|
| `@chat-adapter/state-redis` | Redis state adapter (production) |
|
|
91
94
|
| `@chat-adapter/state-ioredis` | ioredis state adapter (alternative) |
|
package/docs/meta.json
CHANGED
|
@@ -151,7 +151,7 @@ await thread.post(result.fullStream);
|
|
|
151
151
|
|
|
152
152
|
Both `fullStream` and `textStream` are supported. Use `fullStream` with multi-step agents — it preserves paragraph breaks between steps. Any `AsyncIterable<string>` also works for custom streams.
|
|
153
153
|
|
|
154
|
-
For multi-turn conversations, use [`toAiMessages()`](/docs/
|
|
154
|
+
For multi-turn conversations, use [`toAiMessages()`](/docs/ai/to-ai-messages) to convert thread history into the `{ role, content }[]` format expected by AI SDKs.
|
|
155
155
|
|
|
156
156
|
To pass platform-specific streaming options (e.g. Slack task grouping or stop blocks), wrap the stream in a [`StreamingPlan`](/docs/streaming#streaming-with-options) and post that.
|
|
157
157
|
|
|
@@ -168,6 +168,8 @@ await thread.post({
|
|
|
168
168
|
});
|
|
169
169
|
```
|
|
170
170
|
|
|
171
|
+
Use `attachments` on `{ raw }`, `{ markdown }`, or `{ ast }` when an adapter supports typed media uploads, such as Telegram's single image/audio/video/file upload support.
|
|
172
|
+
|
|
171
173
|
See the [Files](/docs/files) page for more on attachments.
|
|
172
174
|
|
|
173
175
|
## Choosing a format
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Slack Low-Level APIs
|
|
3
|
+
description: Use Slack request verification, formatting, Web API, and Block Kit helpers without the full Chat runtime.
|
|
4
|
+
type: guide
|
|
5
|
+
prerequisites:
|
|
6
|
+
- /adapters/official/slack
|
|
7
|
+
related:
|
|
8
|
+
- /docs/handling-events
|
|
9
|
+
- /docs/cards
|
|
10
|
+
- /docs/slash-commands
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
The Slack adapter is the right default for most bots. It verifies requests, resolves tokens, parses Slack payloads, stores thread state, and routes events through `Chat`.
|
|
14
|
+
|
|
15
|
+
Use the low-level Slack subpaths when your app already owns routing, state, sessions, or workflow execution and only needs the Slack-specific primitives.
|
|
16
|
+
|
|
17
|
+
| Subpath | Use for |
|
|
18
|
+
|---------|---------|
|
|
19
|
+
| `@chat-adapter/slack/webhook` | Request verification, body parsing, Events API payloads, slash commands, interactions, and continuation data |
|
|
20
|
+
| `@chat-adapter/slack/format` | Slack mrkdwn tokens, text objects, dates, links, mentions, and simple mrkdwn to Markdown conversion |
|
|
21
|
+
| `@chat-adapter/slack/api` | Fetch-based Slack Web API calls, thread replies, views, and files without `@slack/web-api` |
|
|
22
|
+
| `@chat-adapter/slack/blocks` | Runtime-free conversion from simple card objects and input requests to Slack Block Kit |
|
|
23
|
+
|
|
24
|
+
<Callout type="info">
|
|
25
|
+
These subpaths are for custom runtimes. If you want Chat SDK to handle webhook routing, state, subscriptions, and platform normalization, use `createSlackAdapter` from `@chat-adapter/slack`.
|
|
26
|
+
</Callout>
|
|
27
|
+
|
|
28
|
+
## Webhooks
|
|
29
|
+
|
|
30
|
+
[Slack signs incoming HTTP requests](https://docs.slack.dev/authentication/verifying-requests-from-slack/) with `x-slack-signature` and `x-slack-request-timestamp`. `verifySlackRequest` reads the request body, verifies the signature with your signing secret, and returns the raw body so you can parse it once.
|
|
31
|
+
|
|
32
|
+
```typescript title="app/api/slack/route.ts" lineNumbers
|
|
33
|
+
import {
|
|
34
|
+
parseSlackWebhookBody,
|
|
35
|
+
verifySlackRequest,
|
|
36
|
+
} from "@chat-adapter/slack/webhook";
|
|
37
|
+
import { postSlackMessage } from "@chat-adapter/slack/api";
|
|
38
|
+
|
|
39
|
+
export async function POST(request: Request) {
|
|
40
|
+
const body = await verifySlackRequest(request, {
|
|
41
|
+
signingSecret: process.env.SLACK_SIGNING_SECRET!,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const payload = parseSlackWebhookBody(body, {
|
|
45
|
+
contentType: request.headers.get("content-type"),
|
|
46
|
+
headers: request.headers,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (payload.kind === "url_verification") {
|
|
50
|
+
return Response.json({ challenge: payload.challenge });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (payload.kind === "app_mention") {
|
|
54
|
+
await postSlackMessage({
|
|
55
|
+
channel: payload.continuation.channelId,
|
|
56
|
+
markdownText: `received: ${payload.text}`,
|
|
57
|
+
threadTs: payload.continuation.threadTs,
|
|
58
|
+
token: process.env.SLACK_BOT_TOKEN!,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return new Response(null, { status: 200 });
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
[Slack slash commands](https://docs.slack.dev/interactivity/implementing-slash-commands/) and interactions should be acknowledged quickly. Slack documents a 3000 ms acknowledgement window for slash commands, so do slow work in your queue or workflow runtime after returning a 2xx response.
|
|
67
|
+
|
|
68
|
+
If you do not need direct access to the verified raw body, `readSlackWebhook` combines verification and parsing:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { readSlackWebhook } from "@chat-adapter/slack/webhook";
|
|
72
|
+
|
|
73
|
+
const payload = await readSlackWebhook(request, {
|
|
74
|
+
signingSecret: process.env.SLACK_SIGNING_SECRET!,
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
If your framework already buffered the request body, use `verifySlackSignature` with the raw body and headers, then pass that same body to `parseSlackWebhookBody`.
|
|
79
|
+
|
|
80
|
+
### Payloads
|
|
81
|
+
|
|
82
|
+
`parseSlackWebhookBody` returns typed payloads:
|
|
83
|
+
|
|
84
|
+
| Kind | Slack surface |
|
|
85
|
+
|------|---------------|
|
|
86
|
+
| `url_verification` | Events API URL verification |
|
|
87
|
+
| `app_mention` | App mention events |
|
|
88
|
+
| `direct_message` | Direct message events |
|
|
89
|
+
| `slash_command` | Slash command form posts |
|
|
90
|
+
| `block_actions` | Button, select, and Block Kit action payloads |
|
|
91
|
+
| `block_suggestion` | External select suggestion payloads |
|
|
92
|
+
| `view_submission` | Modal submissions |
|
|
93
|
+
| `view_closed` | Modal close events |
|
|
94
|
+
| `unsupported` | Valid Slack payloads not normalized by this helper yet |
|
|
95
|
+
|
|
96
|
+
Message-like payloads include `continuation`, which contains provider-native reply context:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
type SlackContinuation = {
|
|
100
|
+
channelId: string;
|
|
101
|
+
enterpriseId?: string;
|
|
102
|
+
teamId?: string;
|
|
103
|
+
threadTs: string;
|
|
104
|
+
};
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
This is not a Chat SDK `Thread`. It is the durable Slack data you need to reply later with `@chat-adapter/slack/api`.
|
|
108
|
+
|
|
109
|
+
App mention and direct message payloads also include typed `files` parsed from Slack file objects. Each file keeps the raw Slack object plus common fields like `id`, `name`, `mimeType`, `size`, `url`, and `downloadUrl`.
|
|
110
|
+
|
|
111
|
+
Interaction payloads expose convenience fields from Slack's raw payload:
|
|
112
|
+
|
|
113
|
+
- `block_actions` includes `actions`, `messageBlocks`, `messagePromptBlock`, `messagePromptText`, `messageTs`, `triggerId`, `responseUrl`, `user`, and `continuation`
|
|
114
|
+
- `view_submission` includes `callbackId`, `privateMetadata`, `values`, `responseUrls`, and `user`
|
|
115
|
+
|
|
116
|
+
## Formatting
|
|
117
|
+
|
|
118
|
+
Slack uses mrkdwn and special tokens for mentions, channels, dates, and links. The format subpath gives you small helpers for those strings.
|
|
119
|
+
|
|
120
|
+
The helper surface includes `escapeSlackText`, `unescapeSlackText`, `createSlackPlainText`, `createSlackMrkdwn`, `formatSlackUser`, `formatSlackChannel`, `formatSlackUserGroup`, `formatSlackSpecialMention`, `formatSlackLink`, `formatSlackDate`, and simple mrkdwn to Markdown normalization.
|
|
121
|
+
|
|
122
|
+
```typescript title="format.ts" lineNumbers
|
|
123
|
+
import {
|
|
124
|
+
createSlackMrkdwn,
|
|
125
|
+
formatSlackDate,
|
|
126
|
+
formatSlackLink,
|
|
127
|
+
formatSlackUser,
|
|
128
|
+
slackMrkdwnToMarkdown,
|
|
129
|
+
} from "@chat-adapter/slack/format";
|
|
130
|
+
|
|
131
|
+
const text = createSlackMrkdwn(
|
|
132
|
+
`${formatSlackUser("U123")} approved ${formatSlackLink("https://example.com", "the deploy")}`
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const when = formatSlackDate(
|
|
136
|
+
new Date("2026-05-27T12:00:00Z"),
|
|
137
|
+
"{date_short_pretty} at {time}",
|
|
138
|
+
"May 27 at 12:00"
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const markdown = slackMrkdwnToMarkdown("hello <@U123|jane>, see <https://example.com|this>");
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`linkBareSlackMentions` only links Slack user IDs like `@U123`. It does not resolve display names, because Slack mentions are ID-based.
|
|
145
|
+
|
|
146
|
+
## Web API
|
|
147
|
+
|
|
148
|
+
The API subpath calls [Slack Web API](https://docs.slack.dev/apis/web-api/) methods with `fetch`. It does not import `@slack/web-api`.
|
|
149
|
+
|
|
150
|
+
```typescript title="slack.ts" lineNumbers
|
|
151
|
+
import {
|
|
152
|
+
postSlackMessage,
|
|
153
|
+
sendSlackResponseUrl,
|
|
154
|
+
updateSlackMessage,
|
|
155
|
+
} from "@chat-adapter/slack/api";
|
|
156
|
+
|
|
157
|
+
const posted = await postSlackMessage({
|
|
158
|
+
channel: "C123",
|
|
159
|
+
markdownText: "**hello**",
|
|
160
|
+
token: process.env.SLACK_BOT_TOKEN!,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
await updateSlackMessage({
|
|
164
|
+
channel: "C123",
|
|
165
|
+
text: "updated",
|
|
166
|
+
token: process.env.SLACK_BOT_TOKEN!,
|
|
167
|
+
ts: posted.id,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
await sendSlackResponseUrl("https://hooks.slack.com/actions/T/1/abc", {
|
|
171
|
+
replaceOriginal: true,
|
|
172
|
+
text: "done",
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Use `callSlackApi` when you need a Slack method that does not have a helper yet:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { callSlackApi } from "@chat-adapter/slack/api";
|
|
180
|
+
|
|
181
|
+
const result = await callSlackApi(
|
|
182
|
+
"reactions.add",
|
|
183
|
+
{ channel: "C123", name: "white_check_mark", timestamp: "1710000000.000001" },
|
|
184
|
+
{ token: process.env.SLACK_BOT_TOKEN! }
|
|
185
|
+
);
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`markdownText` maps to the `markdown_text` field on [`chat.postMessage`](https://docs.slack.dev/reference/methods/chat.postMessage/) and cannot be combined with `text` or `blocks`. Use `text` with `blocks` when you need fallback text.
|
|
189
|
+
|
|
190
|
+
The subpath also includes `postSlackEphemeral`, `deleteSlackMessage`, `resolveSlackBotToken`, `encodeSlackApiBody`, and `assertSlackOk`.
|
|
191
|
+
|
|
192
|
+
Use `fetchSlackThreadReplies` when a custom runtime needs to refresh a thread with [`conversations.replies`](https://docs.slack.dev/reference/methods/conversations.replies/):
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { fetchSlackThreadReplies } from "@chat-adapter/slack/api";
|
|
196
|
+
|
|
197
|
+
const replies = await fetchSlackThreadReplies({
|
|
198
|
+
channel: payload.continuation.channelId,
|
|
199
|
+
limit: 50,
|
|
200
|
+
token: process.env.SLACK_BOT_TOKEN!,
|
|
201
|
+
ts: payload.continuation.threadTs,
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Use `openSlackView` to open a modal from an interaction `trigger_id`:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import { openSlackView } from "@chat-adapter/slack/api";
|
|
209
|
+
|
|
210
|
+
await openSlackView({
|
|
211
|
+
token: process.env.SLACK_BOT_TOKEN!,
|
|
212
|
+
triggerId: payload.triggerId,
|
|
213
|
+
view: {
|
|
214
|
+
type: "modal",
|
|
215
|
+
title: { type: "plain_text", text: "Answer" },
|
|
216
|
+
blocks: [],
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Files
|
|
222
|
+
|
|
223
|
+
[Slack's current external upload flow](https://docs.slack.dev/changelog/2024-04-a-better-way-to-upload-files-is-here-to-stay) uses `files.getUploadURLExternal`, then uploads bytes to the returned URL, then calls `files.completeUploadExternal`.
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
import { uploadSlackFiles } from "@chat-adapter/slack/api";
|
|
227
|
+
|
|
228
|
+
await uploadSlackFiles(
|
|
229
|
+
[{ data: new Uint8Array([1, 2, 3]), filename: "report.txt" }],
|
|
230
|
+
{
|
|
231
|
+
channelId: "C123",
|
|
232
|
+
initialComment: "report attached",
|
|
233
|
+
token: process.env.SLACK_BOT_TOKEN!,
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Use `fetchSlackFile` for private Slack file URLs that require bearer token authorization.
|
|
239
|
+
|
|
240
|
+
## Blocks
|
|
241
|
+
|
|
242
|
+
The blocks subpath converts simple card objects into Slack Block Kit without importing the full `chat` JSX runtime.
|
|
243
|
+
|
|
244
|
+
It exports `cardToSlackBlocks`, `cardToBlockKit`, `cardToSlackFallbackText`, `cardToFallbackText`, and `convertSlackEmojiPlaceholders`.
|
|
245
|
+
|
|
246
|
+
```typescript title="blocks.ts" lineNumbers
|
|
247
|
+
import {
|
|
248
|
+
cardToSlackBlocks,
|
|
249
|
+
cardToSlackFallbackText,
|
|
250
|
+
} from "@chat-adapter/slack/blocks";
|
|
251
|
+
import { postSlackMessage } from "@chat-adapter/slack/api";
|
|
252
|
+
|
|
253
|
+
const card = {
|
|
254
|
+
children: [
|
|
255
|
+
{ content: "deploy v2.4.1?", type: "text" },
|
|
256
|
+
{
|
|
257
|
+
children: [
|
|
258
|
+
{ id: "approve", label: "Approve", style: "primary", type: "button" },
|
|
259
|
+
{ id: "deny", label: "Deny", style: "danger", type: "button" },
|
|
260
|
+
],
|
|
261
|
+
type: "actions",
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
title: "Deployment",
|
|
265
|
+
type: "card",
|
|
266
|
+
} as const;
|
|
267
|
+
|
|
268
|
+
await postSlackMessage({
|
|
269
|
+
blocks: cardToSlackBlocks(card),
|
|
270
|
+
channel: "C123",
|
|
271
|
+
text: cardToSlackFallbackText(card),
|
|
272
|
+
token: process.env.SLACK_BOT_TOKEN!,
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Use the full Chat SDK card JSX when you want cross-platform rendering. Use `@chat-adapter/slack/blocks` when you are building a Slack-only runtime and want Block Kit output directly.
|
|
277
|
+
|
|
278
|
+
The blocks subpath also includes small input request helpers for Slack-only runtimes:
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
import {
|
|
282
|
+
inputRequestToSlackBlocks,
|
|
283
|
+
parseSlackInputResponse,
|
|
284
|
+
} from "@chat-adapter/slack/blocks";
|
|
285
|
+
import { postSlackMessage } from "@chat-adapter/slack/api";
|
|
286
|
+
|
|
287
|
+
await postSlackMessage({
|
|
288
|
+
blocks: inputRequestToSlackBlocks({
|
|
289
|
+
options: [
|
|
290
|
+
{ id: "approve", label: "Approve", style: "primary" },
|
|
291
|
+
{ id: "deny", label: "Deny", style: "danger" },
|
|
292
|
+
],
|
|
293
|
+
prompt: "Approve deploy?",
|
|
294
|
+
requestId: "deploy-1",
|
|
295
|
+
}),
|
|
296
|
+
channel: "C123",
|
|
297
|
+
text: "Approve deploy?",
|
|
298
|
+
token: process.env.SLACK_BOT_TOKEN!,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
if (payload.kind === "block_actions") {
|
|
302
|
+
const action = payload.actions[0];
|
|
303
|
+
const response = action ? parseSlackInputResponse(action) : null;
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Set `display: "radio"` for radio buttons, or `display: "select"` for a static select menu. Set `allowFreeform: true` to add a "Type your answer" button next to the provided options.
|
|
308
|
+
|
|
309
|
+
For freeform answers, use `buildSlackFreeformView` with `openSlackView`, then read the submitted value from `payload.values` with `parseSlackFreeformValue`.
|
|
310
|
+
|
|
311
|
+
## Import boundaries
|
|
312
|
+
|
|
313
|
+
The low-level Slack subpaths are designed to avoid the full runtime import graph:
|
|
314
|
+
|
|
315
|
+
- no `chat` import
|
|
316
|
+
- no `@chat-adapter/shared` import
|
|
317
|
+
- no `@slack/web-api` import
|
|
318
|
+
- no `@slack/socket-mode` import
|
|
319
|
+
|
|
320
|
+
The package still installs the full Slack adapter dependencies. The subpaths keep your source and bundle imports clean, but they are not a package-size split.
|
package/docs/slash-commands.mdx
CHANGED
|
@@ -6,13 +6,13 @@ prerequisites:
|
|
|
6
6
|
- /docs/getting-started
|
|
7
7
|
related:
|
|
8
8
|
- /docs/modals
|
|
9
|
-
- /adapters/slack
|
|
10
|
-
- /adapters/discord
|
|
9
|
+
- /adapters/official/slack
|
|
10
|
+
- /adapters/official/discord
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
Slash commands let users invoke your bot with `/command` syntax. Register handlers with `onSlashCommand` to respond.
|
|
14
14
|
|
|
15
|
-
Slash commands are supported on [Slack](/adapters/slack) and [Discord](/adapters/discord).
|
|
15
|
+
Slash commands are supported on [Slack](/adapters/official/slack) and [Discord](/adapters/official/discord).
|
|
16
16
|
|
|
17
17
|
## Handle a specific command
|
|
18
18
|
|
|
@@ -114,7 +114,7 @@ bot.onModalSubmit("feedback_form", async (event) => {
|
|
|
114
114
|
|
|
115
115
|
## Discord
|
|
116
116
|
|
|
117
|
-
Discord slash commands are received via [HTTP Interactions](/adapters/discord#
|
|
117
|
+
Discord slash commands are received via [HTTP Interactions](/adapters/official/discord#http-interactions-vs-gateway) — no Gateway connection is needed. The adapter automatically sends a deferred response to Discord, then resolves it when your handler calls `event.channel.post()`.
|
|
118
118
|
|
|
119
119
|
### Subcommands
|
|
120
120
|
|
package/docs/streaming.mdx
CHANGED
|
@@ -59,10 +59,10 @@ await thread.post(stream);
|
|
|
59
59
|
| Platform | Method | Description |
|
|
60
60
|
|----------|--------|-------------|
|
|
61
61
|
| Slack | Native streaming API | Uses Slack's `chatStream` for smooth, real-time updates |
|
|
62
|
+
| Telegram | Private chat draft previews | Uses Telegram's `sendMessageDraft` in private chats and falls back to post + edit elsewhere |
|
|
62
63
|
| Teams | Native (DMs) / Buffered (group chats) | Uses the Teams SDK's native `stream.emit()` for direct messages; accumulates chunks and posts one final message when no native streamer is active |
|
|
63
64
|
| Google Chat | Post + Edit | Posts a message then edits it as chunks arrive |
|
|
64
65
|
| Discord | Post + Edit | Posts a message then edits it as chunks arrive |
|
|
65
|
-
| Telegram | Post + Edit | Posts a message then edits it as chunks arrive |
|
|
66
66
|
| GitHub | Buffered | Accumulates chunks and posts one final comment |
|
|
67
67
|
| Linear | Agent sessions / Post + Edit | Uses agent session activities in agent-session threads; falls back to post+edit comments in issue threads |
|
|
68
68
|
| WhatsApp | Buffered | Accumulates chunks and sends one final message |
|
|
@@ -240,10 +240,10 @@ Adapters that don't support PostableObject editing (e.g. WhatsApp) render the pl
|
|
|
240
240
|
## Streaming with conversation history
|
|
241
241
|
|
|
242
242
|
Combine message history with streaming for multi-turn AI conversations.
|
|
243
|
-
Use [`toAiMessages()`](/docs/
|
|
243
|
+
Use [`toAiMessages()`](/docs/ai/to-ai-messages) to convert chat messages into the `{ role, content }` format expected by AI SDKs:
|
|
244
244
|
|
|
245
245
|
```typescript title="lib/bot.ts" lineNumbers
|
|
246
|
-
import { toAiMessages } from "chat";
|
|
246
|
+
import { toAiMessages } from "chat/ai";
|
|
247
247
|
|
|
248
248
|
bot.onSubscribedMessage(async (thread, message) => {
|
|
249
249
|
// Fetch recent messages for context
|
|
@@ -256,4 +256,4 @@ bot.onSubscribedMessage(async (thread, message) => {
|
|
|
256
256
|
});
|
|
257
257
|
```
|
|
258
258
|
|
|
259
|
-
See the [`toAiMessages`
|
|
259
|
+
See the [`toAiMessages` reference](/docs/ai/to-ai-messages) for all options including `includeNames`, `transformMessage`, and attachment handling.
|
package/docs/subject.mdx
CHANGED
|
@@ -50,4 +50,4 @@ bot.onNewMention(async (thread, message) => {
|
|
|
50
50
|
});
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
For anything beyond `message.subject`, access the platform's typed API client via [`bot.getAdapter(
|
|
53
|
+
For anything beyond `message.subject`, access the platform's typed API client via [`bot.getAdapter("github").octokit`](/docs/api/chat#getadapter) or [`bot.getAdapter("linear").linearClient`](/docs/api/chat#getadapter).
|
package/docs/testing.mdx
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Testing
|
|
3
|
+
description: Test your bot handlers and custom adapters with @chat-adapter/tests — Vitest factories, custom matchers, and a setup file.
|
|
4
|
+
type: guide
|
|
5
|
+
prerequisites:
|
|
6
|
+
- /docs/getting-started
|
|
7
|
+
related:
|
|
8
|
+
- /docs/state
|
|
9
|
+
- /docs/handling-events
|
|
10
|
+
- /docs/contributing/testing
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
The [`@chat-adapter/tests`](https://www.npmjs.com/package/@chat-adapter/tests) package gives you Vitest factories, custom matchers, and a setup file for testing bots and custom adapters built on Chat SDK.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add -D @chat-adapter/tests
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`chat` and `vitest` are peer dependencies — they should already be in your project.
|
|
22
|
+
|
|
23
|
+
## Setup file (recommended)
|
|
24
|
+
|
|
25
|
+
Auto-register all matchers by adding the package's setup file to your Vitest config:
|
|
26
|
+
|
|
27
|
+
```typescript title="vitest.config.ts" lineNumbers
|
|
28
|
+
import { defineConfig } from "vitest/config";
|
|
29
|
+
|
|
30
|
+
export default defineConfig({
|
|
31
|
+
test: {
|
|
32
|
+
setupFiles: ["@chat-adapter/tests/setup"],
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Without the setup file, register matchers manually:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { matchers } from "@chat-adapter/tests/matchers";
|
|
41
|
+
expect.extend(matchers);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Mock factories
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import {
|
|
48
|
+
createMockAdapter,
|
|
49
|
+
createMockChatInstance,
|
|
50
|
+
createMockState,
|
|
51
|
+
createTestMessage,
|
|
52
|
+
mockLogger,
|
|
53
|
+
} from "@chat-adapter/tests";
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
| Factory | Returns | Notes |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| `createMockAdapter(name?, overrides?)` | `Adapter` | Every method is `vi.fn()` with sensible defaults |
|
|
59
|
+
| `createMockChatInstance(options?)` | `ChatInstance` | Every `process*` handler is `vi.fn()`; `getState`/`getUserName`/`getLogger` wired up |
|
|
60
|
+
| `createMockState()` | `MockStateAdapter` | In-memory `Map`s for subscriptions, locks, KV, lists, queues; `cache` exposes the underlying map |
|
|
61
|
+
| `createTestMessage(id, text, overrides?)` | `Message` | Markdown text is parsed into the formatted AST |
|
|
62
|
+
| `mockLogger` / `createMockLogger()` | `Logger` | Shared default vs fresh-per-call |
|
|
63
|
+
|
|
64
|
+
## Matchers
|
|
65
|
+
|
|
66
|
+
| Matcher | Asserts |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `expect(adapter).toHavePosted(threadId, textPattern?)` | `adapter.postMessage` was called for this thread |
|
|
69
|
+
| `expect(adapter).toHaveEdited(threadId, messageId, textPattern?)` | `adapter.editMessage` was called for this message |
|
|
70
|
+
| `expect(adapter).toHaveDeleted(threadId, messageId)` | `adapter.deleteMessage` was called for this message |
|
|
71
|
+
| `expect(adapter).toHaveReactedWith(threadId, messageId, emoji)` | `adapter.addReaction` was called with the emoji (string or `EmojiValue.name`) |
|
|
72
|
+
| `expect(adapter).toHaveStartedTyping(threadId)` | `adapter.startTyping` was called for this thread |
|
|
73
|
+
| `expect(adapter).toHavePostedToChannel(channelId, textPattern?)` | `adapter.postChannelMessage` was called for this channel |
|
|
74
|
+
| `expect(chat).toHaveDispatched(handler)` | The named `process*` handler on the mock `ChatInstance` was called |
|
|
75
|
+
| `expect(state).toBeSubscribedTo(threadId)` | `state.isSubscribed(threadId)` resolves to `true` (async — `await expect(...)`) |
|
|
76
|
+
|
|
77
|
+
Text-pattern matchers extract a comparable string from `AdapterPostableMessage` — strings directly, `PostableMarkdown.markdown`, `PostableRaw.raw`, and `PostableCard.fallbackText`. AST-shaped messages and cards without `fallbackText` aren't text-matchable; assert without `textPattern` and inspect `mock.calls` directly.
|
|
78
|
+
|
|
79
|
+
## Bot authors: test your handlers
|
|
80
|
+
|
|
81
|
+
When you're building a bot on top of Chat SDK, the kit lets you exercise your handlers without a real Slack/Teams/etc. webhook on the wire:
|
|
82
|
+
|
|
83
|
+
```typescript title="bot.test.ts"
|
|
84
|
+
import { describe, expect, it } from "vitest";
|
|
85
|
+
import { Chat } from "chat";
|
|
86
|
+
import { createMockAdapter, createMockState } from "@chat-adapter/tests";
|
|
87
|
+
|
|
88
|
+
describe("bot handlers", () => {
|
|
89
|
+
it("replies with a greeting on mention", async () => {
|
|
90
|
+
const slack = createMockAdapter("slack");
|
|
91
|
+
const state = createMockState();
|
|
92
|
+
const bot = new Chat({
|
|
93
|
+
userName: "mybot",
|
|
94
|
+
adapters: { slack },
|
|
95
|
+
state,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
bot.onNewMention(async (thread) => {
|
|
99
|
+
await thread.post("hello there");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Drive a synthesized mention through the bot…
|
|
103
|
+
// (use your adapter's webhook path or a thread-level call)
|
|
104
|
+
|
|
105
|
+
expect(slack).toHavePosted("slack:C1:t1", /hello there/);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Adapter authors: test webhook → dispatch
|
|
111
|
+
|
|
112
|
+
When you're building a custom `Adapter`, the kit gives you a `ChatInstance` mock you can hand to your adapter and assert that webhooks route through the right `process*` hook with the right normalized payload:
|
|
113
|
+
|
|
114
|
+
```typescript title="adapter.test.ts"
|
|
115
|
+
import { describe, expect, it } from "vitest";
|
|
116
|
+
import { createMockChatInstance } from "@chat-adapter/tests";
|
|
117
|
+
import { MyAdapter } from "./adapter";
|
|
118
|
+
|
|
119
|
+
describe("MyAdapter.handleWebhook", () => {
|
|
120
|
+
it("dispatches incoming messages through processMessage", async () => {
|
|
121
|
+
const chat = createMockChatInstance();
|
|
122
|
+
const adapter = new MyAdapter({ /* config */ });
|
|
123
|
+
await adapter.initialize(chat);
|
|
124
|
+
|
|
125
|
+
const request = new Request("https://example.com/webhook", {
|
|
126
|
+
method: "POST",
|
|
127
|
+
body: JSON.stringify({ /* platform-specific payload */ }),
|
|
128
|
+
headers: { "content-type": "application/json" },
|
|
129
|
+
});
|
|
130
|
+
const response = await adapter.handleWebhook(request);
|
|
131
|
+
|
|
132
|
+
expect(response.status).toBe(200);
|
|
133
|
+
expect(chat).toHaveDispatched("processMessage");
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Adapter-specific helpers
|
|
139
|
+
|
|
140
|
+
Helpers that depend on a specific platform's wire format (signed Slack webhooks, Teams claim builders, etc.) live in each adapter's own `/testing` subpath rather than in this kit, so adopting `@chat-adapter/tests` doesn't pull in adapter dependencies you don't use.
|
|
141
|
+
|
|
142
|
+
If you're contributing adapters or core to this repo, see the [Testing adapters contributing guide](/docs/contributing/testing) for hand-rolled patterns used inside `packages/`.
|
|
@@ -40,7 +40,7 @@ await thread.post({
|
|
|
40
40
|
|
|
41
41
|
### Subscribe and unsubscribe
|
|
42
42
|
|
|
43
|
-
Subscriptions persist across restarts (stored in your state adapter). When a thread is subscribed, all messages route to `onSubscribedMessage`.
|
|
43
|
+
Subscriptions persist across restarts (stored in your state adapter). When a non-DM thread is subscribed, all messages route to `onSubscribedMessage`. DM threads route to `onDirectMessage` first when a direct message handler is registered.
|
|
44
44
|
|
|
45
45
|
```typescript title="lib/bot.ts" lineNumbers
|
|
46
46
|
await thread.subscribe();
|