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.
- package/dist/{chunk-OPV5U4WG.js → chunk-AN7MRAVW.js} +39 -0
- package/dist/index.d.ts +220 -6
- package/dist/index.js +321 -50
- package/dist/{jsx-runtime-DxATbnrP.d.ts → jsx-runtime-Co9uV6l7.d.ts} +39 -5
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +1 -1
- package/docs/adapters.mdx +28 -28
- package/docs/api/chat.mdx +85 -1
- package/docs/api/message.mdx +5 -1
- package/docs/api/thread.mdx +23 -1
- package/docs/contributing/publishing.mdx +33 -0
- package/docs/files.mdx +1 -0
- package/docs/getting-started.mdx +1 -11
- package/docs/meta.json +0 -2
- package/docs/modals.mdx +73 -1
- package/docs/streaming.mdx +13 -5
- package/docs/threads-messages-channels.mdx +34 -0
- package/package.json +3 -2
- package/resources/guides/create-a-discord-support-bot-with-nuxt-and-redis.md +180 -0
- package/resources/guides/how-to-build-a-slack-bot-with-next-js-and-redis.md +134 -0
- package/resources/guides/how-to-build-an-ai-agent-for-slack-with-chat-sdk-and-ai-sdk.md +220 -0
- package/resources/guides/run-and-track-deploys-from-slack.md +270 -0
- package/resources/guides/ship-a-github-code-review-bot-with-hono-and-redis.md +147 -0
- package/resources/guides/triage-form-submissions-with-chat-sdk.md +178 -0
- package/resources/templates.json +19 -0
- package/docs/guides/code-review-hono.mdx +0 -241
- package/docs/guides/discord-nuxt.mdx +0 -227
- package/docs/guides/durable-chat-sessions-nextjs.mdx +0 -337
- package/docs/guides/meta.json +0 -10
- package/docs/guides/scheduled-posts-neon.mdx +0 -447
- package/docs/guides/slack-nextjs.mdx +0 -234
package/dist/jsx-runtime.js
CHANGED
package/docs/adapters.mdx
CHANGED
|
@@ -16,52 +16,52 @@ Ready to build your own? Follow the [building](/docs/contributing/building) guid
|
|
|
16
16
|
|
|
17
17
|
| Feature | [Slack](/adapters/slack) | [Teams](/adapters/teams) | [Google Chat](/adapters/google-chat) | [Discord](/adapters/discord) | [Telegram](/adapters/telegram) | [GitHub](/adapters/github) | [Linear](/adapters/linear) | [WhatsApp](/adapters/whatsapp) |
|
|
18
18
|
|---------|-------|-------|-------------|---------|---------|--------|--------|-----------|
|
|
19
|
-
| Post message |
|
|
20
|
-
| Edit message |
|
|
21
|
-
| Delete message |
|
|
22
|
-
| File uploads |
|
|
23
|
-
| Streaming |
|
|
24
|
-
| Scheduled messages |
|
|
19
|
+
| Post message | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
20
|
+
| Edit message | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
21
|
+
| Delete message | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Cross /> |
|
|
22
|
+
| File uploads | <Check /> | <Check /> | <Cross /> | <Check /> | <Warn /> Single file | <Cross /> | <Cross /> | <Check /> Images, audio, docs |
|
|
23
|
+
| Streaming | <Check /> Native | <Warn /> Post+Edit | <Warn /> Post+Edit | <Warn /> Post+Edit | <Warn /> Post+Edit | <Cross /> | <Cross /> | <Cross /> |
|
|
24
|
+
| Scheduled messages | <Check /> Native | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> |
|
|
25
25
|
|
|
26
26
|
### Rich content
|
|
27
27
|
|
|
28
28
|
| Feature | Slack | Teams | Google Chat | Discord | Telegram | GitHub | Linear | WhatsApp |
|
|
29
29
|
|---------|-------|-------|-------------|---------|----------|--------|--------|-----------|
|
|
30
30
|
| Card format | Block Kit | Adaptive Cards | Google Chat Cards | Embeds | Markdown + inline keyboard buttons | GFM Markdown | Markdown | WhatsApp templates |
|
|
31
|
-
| Buttons |
|
|
32
|
-
| Link buttons |
|
|
33
|
-
| Select menus |
|
|
34
|
-
| Tables |
|
|
35
|
-
| Fields |
|
|
36
|
-
| Images in cards |
|
|
37
|
-
| Modals |
|
|
31
|
+
| Buttons | <Check /> | <Check /> | <Check /> | <Check /> | <Warn /> Inline keyboard callbacks | <Cross /> | <Cross /> | <Check /> Interactive replies |
|
|
32
|
+
| Link buttons | <Check /> | <Check /> | <Check /> | <Check /> | <Warn /> Inline keyboard URLs | <Cross /> | <Cross /> | <Cross /> |
|
|
33
|
+
| Select menus | <Check /> | <Cross /> | <Check /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> |
|
|
34
|
+
| Tables | <Check /> Block Kit | <Check /> GFM | <Warn /> ASCII | <Check /> GFM | <Warn /> ASCII | <Check /> GFM | <Check /> GFM | <Cross /> |
|
|
35
|
+
| Fields | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Warn /> Template variables |
|
|
36
|
+
| Images in cards | <Check /> | <Check /> | <Check /> | <Check /> | <Cross /> | <Check /> | <Cross /> | <Check /> |
|
|
37
|
+
| Modals | <Check /> | <Check /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> |
|
|
38
38
|
|
|
39
39
|
### Conversations
|
|
40
40
|
|
|
41
41
|
| Feature | Slack | Teams | Google Chat | Discord | Telegram | GitHub | Linear | WhatsApp |
|
|
42
42
|
|---------|-------|-------|-------------|---------|----------|--------|--------|-----------|
|
|
43
|
-
| Slash commands |
|
|
44
|
-
| Mentions |
|
|
45
|
-
| Add reactions |
|
|
46
|
-
| Remove reactions |
|
|
47
|
-
| Typing indicator |
|
|
48
|
-
| DMs |
|
|
49
|
-
| Ephemeral messages |
|
|
43
|
+
| Slash commands | <Check /> | <Cross /> | <Cross /> | <Check /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> |
|
|
44
|
+
| Mentions | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Cross /> |
|
|
45
|
+
| Add reactions | <Check /> | <Cross /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Cross /> |
|
|
46
|
+
| Remove reactions | <Check /> | <Cross /> | <Check /> | <Check /> | <Check /> | <Warn /> | <Warn /> | <Cross /> |
|
|
47
|
+
| Typing indicator | <Cross /> | <Check /> | <Cross /> | <Check /> | <Check /> | <Cross /> | <Cross /> | <Cross /> |
|
|
48
|
+
| DMs | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Cross /> | <Cross /> | <Check /> |
|
|
49
|
+
| Ephemeral messages | <Check /> Native | <Cross /> | <Check /> Native | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> |
|
|
50
50
|
|
|
51
51
|
### Message history
|
|
52
52
|
|
|
53
53
|
| Feature | Slack | Teams | Google Chat | Discord | Telegram | GitHub | Linear | WhatsApp |
|
|
54
54
|
|---------|-------|-------|-------------|---------|----------|--------|--------|-----------|
|
|
55
|
-
| Fetch messages |
|
|
56
|
-
| Fetch single message |
|
|
57
|
-
| Fetch thread info |
|
|
58
|
-
| Fetch channel messages |
|
|
59
|
-
| List threads |
|
|
60
|
-
| Fetch channel info |
|
|
61
|
-
| Post channel message |
|
|
55
|
+
| Fetch messages | <Check /> | <Check /> | <Check /> | <Check /> | <Warn /> Cached | <Check /> | <Check /> | <Warn /> Cached sent messages only |
|
|
56
|
+
| Fetch single message | <Check /> | <Cross /> | <Cross /> | <Cross /> | <Warn /> Cached | <Cross /> | <Cross /> | <Warn /> Cached sent messages only |
|
|
57
|
+
| Fetch thread info | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Cross /> |
|
|
58
|
+
| Fetch channel messages | <Check /> | <Check /> | <Check /> | <Check /> | <Warn /> Cached | <Check /> | <Cross /> | <Warn /> Cached sent messages only |
|
|
59
|
+
| List threads | <Check /> | <Check /> | <Check /> | <Check /> | <Cross /> | <Check /> | <Cross /> | <Cross /> |
|
|
60
|
+
| Fetch channel info | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Cross /> | <Cross /> |
|
|
61
|
+
| Post channel message | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Cross /> | <Cross /> | <Check /> |
|
|
62
62
|
|
|
63
63
|
<Callout type="info">
|
|
64
|
-
|
|
64
|
+
<Warn /> indicates partial support — the feature works with limitations. See individual adapter pages for details.
|
|
65
65
|
</Callout>
|
|
66
66
|
|
|
67
67
|
## How adapters work
|
package/docs/api/chat.mdx
CHANGED
|
@@ -255,7 +255,8 @@ bot.onModalSubmit("feedback", async (event) => {
|
|
|
255
255
|
|
|
256
256
|
Returns `ModalResponse | undefined` to control the modal after submission:
|
|
257
257
|
|
|
258
|
-
- `{ action: "close" }` — close the
|
|
258
|
+
- `{ action: "close" }` — close the current view (goes back one level in the stack)
|
|
259
|
+
- `{ action: "clear" }` — close all views and dismiss the modal entirely
|
|
259
260
|
- `{ action: "errors", errors: { fieldId: "message" } }` — show validation errors
|
|
260
261
|
- `{ action: "update", modal: ModalElement }` — replace the modal content
|
|
261
262
|
- `{ action: "push", modal: ModalElement }` — push a new modal view onto the stack
|
|
@@ -443,6 +444,89 @@ await dm.post("Hello via DM!");
|
|
|
443
444
|
const dm = await bot.openDM(message.author);
|
|
444
445
|
```
|
|
445
446
|
|
|
447
|
+
### getUser
|
|
448
|
+
|
|
449
|
+
Look up user information by user ID. Returns a `UserInfo` object with name, email, avatar, and bot status, or `null` if the user was not found. Supported on Slack, Microsoft Teams, Discord, Google Chat, GitHub, Linear, and Telegram. Other adapters will throw `NOT_SUPPORTED`.
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
const user = await bot.getUser("U123456");
|
|
453
|
+
console.log(user?.email); // "alice@company.com"
|
|
454
|
+
console.log(user?.fullName); // "Alice Smith"
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
// Or with an Author object from a message handler
|
|
459
|
+
const user = await bot.getUser(message.author);
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
<TypeTable
|
|
463
|
+
type={{
|
|
464
|
+
userId: {
|
|
465
|
+
description: 'Platform-specific user ID.',
|
|
466
|
+
type: 'string',
|
|
467
|
+
},
|
|
468
|
+
userName: {
|
|
469
|
+
description: 'Username/handle.',
|
|
470
|
+
type: 'string',
|
|
471
|
+
},
|
|
472
|
+
fullName: {
|
|
473
|
+
description: 'Display name / full name.',
|
|
474
|
+
type: 'string',
|
|
475
|
+
},
|
|
476
|
+
isBot: {
|
|
477
|
+
description: 'Whether the user is a bot.',
|
|
478
|
+
type: 'boolean',
|
|
479
|
+
},
|
|
480
|
+
email: {
|
|
481
|
+
description: 'Email address (requires scopes on some platforms).',
|
|
482
|
+
type: 'string | undefined',
|
|
483
|
+
},
|
|
484
|
+
avatarUrl: {
|
|
485
|
+
description: 'Profile image URL.',
|
|
486
|
+
type: 'string | undefined',
|
|
487
|
+
},
|
|
488
|
+
}}
|
|
489
|
+
/>
|
|
490
|
+
|
|
491
|
+
<Callout type="info">
|
|
492
|
+
**Per-platform constraints:**
|
|
493
|
+
- **Slack** — requires both `users:read` and `users:read.email` scopes (the email scope must be granted at OAuth install time).
|
|
494
|
+
- **Discord** — bot tokens never see email (the `email` OAuth scope only applies in user-context auth).
|
|
495
|
+
- **Telegram** — bots can only look up users who have previously messaged them.
|
|
496
|
+
- **Microsoft Teams** — only works for users who previously interacted with the bot (cached from webhook activity). `avatarUrl` is not returned (Graph API requires a separate photo call).
|
|
497
|
+
- **Google Chat** — same caching constraint as Teams: only users seen in prior webhooks.
|
|
498
|
+
- **GitHub** — `email` is `null` unless the user made it public, or you authenticated with the `user:email` scope.
|
|
499
|
+
- **Linear** — full profile (incl. email + avatar) for any active workspace member.
|
|
500
|
+
|
|
501
|
+
Fields that aren't available return `undefined`. Numeric user IDs (Discord/Telegram/GitHub) can be ambiguous when multiple of those adapters are registered — call the platform's adapter directly (`adapter.getUser(userId)`) in that case.
|
|
502
|
+
</Callout>
|
|
503
|
+
|
|
504
|
+
Adapters that don't support user lookups will throw a `ChatError` with code `NOT_SUPPORTED`. Handle both cases if your bot runs on multiple platforms:
|
|
505
|
+
|
|
506
|
+
```typescript
|
|
507
|
+
import { ChatError } from "chat";
|
|
508
|
+
|
|
509
|
+
try {
|
|
510
|
+
const user = await bot.getUser(userId);
|
|
511
|
+
if (!user) {
|
|
512
|
+
// User not found on this platform
|
|
513
|
+
}
|
|
514
|
+
} catch (error) {
|
|
515
|
+
if (error instanceof ChatError && error.code === "NOT_SUPPORTED") {
|
|
516
|
+
// This adapter doesn't support user lookups
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### thread
|
|
522
|
+
|
|
523
|
+
Get a Thread handle by its thread ID. Useful for posting to threads outside of webhook contexts (e.g. cron jobs, external triggers).
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
const thread = bot.thread("slack:C123ABC:1234567890.123456");
|
|
527
|
+
await thread.post("Hello from a cron job!");
|
|
528
|
+
```
|
|
529
|
+
|
|
446
530
|
### channel
|
|
447
531
|
|
|
448
532
|
Get a Channel by its channel ID.
|
package/docs/api/message.mdx
CHANGED
|
@@ -149,6 +149,10 @@ All adapters return `false` if the bot ID isn't known yet. This is a safe defaul
|
|
|
149
149
|
description: 'Fetch the attachment data. Handles platform auth automatically.',
|
|
150
150
|
type: '() => Promise<Buffer> | undefined',
|
|
151
151
|
},
|
|
152
|
+
fetchMetadata: {
|
|
153
|
+
description: 'Platform-specific IDs for reconstructing fetchData after serialization (e.g. WhatsApp mediaId, Telegram fileId).',
|
|
154
|
+
type: 'Record<string, string> | undefined',
|
|
155
|
+
},
|
|
152
156
|
}}
|
|
153
157
|
/>
|
|
154
158
|
|
|
@@ -208,4 +212,4 @@ const json = message.toJSON();
|
|
|
208
212
|
const restored = Message.fromJSON(json);
|
|
209
213
|
```
|
|
210
214
|
|
|
211
|
-
The serialized format converts `Date` fields to ISO strings and omits non-serializable fields like `data` buffers and `fetchData` functions.
|
|
215
|
+
The serialized format converts `Date` fields to ISO strings and omits non-serializable fields like `data` buffers and `fetchData` functions. The `fetchMetadata` field is preserved so that adapters can reconstruct `fetchData` when the message is rehydrated from a queue.
|
package/docs/api/thread.mdx
CHANGED
|
@@ -4,7 +4,7 @@ description: Represents a conversation thread with methods for posting, subscrib
|
|
|
4
4
|
type: reference
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
A `Thread` is provided to your event handlers and represents a conversation thread on any platform. You
|
|
7
|
+
A `Thread` is provided to your event handlers and represents a conversation thread on any platform. You can also create thread handles directly using `chat.thread()` or `chat.openDM()`.
|
|
8
8
|
|
|
9
9
|
## Properties
|
|
10
10
|
|
|
@@ -114,6 +114,28 @@ await scheduled.cancel();
|
|
|
114
114
|
Streaming and file uploads are not supported in scheduled messages.
|
|
115
115
|
</Callout>
|
|
116
116
|
|
|
117
|
+
## getParticipants
|
|
118
|
+
|
|
119
|
+
Get the unique human participants in a thread. Returns deduplicated authors, excluding all bots. Useful for subscribing only to 1:1 conversations and unsubscribing when others join.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
const participants = await thread.getParticipants();
|
|
123
|
+
|
|
124
|
+
// Subscribe only when one person is talking to the bot
|
|
125
|
+
if (participants.length === 1) {
|
|
126
|
+
await thread.subscribe();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Unsubscribe when the thread becomes a group conversation
|
|
130
|
+
if (participants.length > 1) {
|
|
131
|
+
await thread.unsubscribe();
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
<Callout type="warn">
|
|
136
|
+
Each call fetches the full message history to find all participants. On threads with long history this makes multiple API calls to the platform. Consider checking `message.author` against a known set before calling `getParticipants()` on every incoming message.
|
|
137
|
+
</Callout>
|
|
138
|
+
|
|
117
139
|
## subscribe / unsubscribe
|
|
118
140
|
|
|
119
141
|
Manage thread subscriptions. Subscribed threads route all messages to `onSubscribedMessage` handlers.
|
|
@@ -159,3 +159,36 @@ You should see your exported symbols (`createMatrixAdapter`, `MatrixAdapter`, et
|
|
|
159
159
|
- Watch the [Chat SDK changelog](https://github.com/vercel/chat/releases) for new features and breaking changes
|
|
160
160
|
- Run your test suite against new Chat SDK releases before they ship to catch compatibility issues early
|
|
161
161
|
- When the `Adapter` interface adds new optional methods, consider implementing them to keep your adapter feature-complete
|
|
162
|
+
|
|
163
|
+
## Listing on chat-sdk.dev
|
|
164
|
+
|
|
165
|
+
Community adapters can be listed on the [Adapters](https://chat-sdk.dev/adapters) page by opening a PR that adds an entry to `apps/docs/adapters.json` in the [Chat SDK repo](https://github.com/vercel/chat). Your adapter's README is fetched from GitHub at build time and rendered on its dedicated page.
|
|
166
|
+
|
|
167
|
+
### Pin your README to a commit or tag
|
|
168
|
+
|
|
169
|
+
<Callout type="warn">
|
|
170
|
+
The `readme` field **must** reference a specific commit SHA or tag — not a branch name like `main`.
|
|
171
|
+
</Callout>
|
|
172
|
+
|
|
173
|
+
The docs site re-renders on every deploy, so an unpinned `readme` would serve whatever currently sits at your default branch — including edits made after the listing PR was reviewed. Pinning freezes the rendered content at the state we approved; new content goes live through a follow-up PR that bumps the ref.
|
|
174
|
+
|
|
175
|
+
```json title="apps/docs/adapters.json"
|
|
176
|
+
{
|
|
177
|
+
"name": "My Adapter",
|
|
178
|
+
"slug": "my-adapter",
|
|
179
|
+
"type": "platform",
|
|
180
|
+
"community": true,
|
|
181
|
+
"packageName": "chat-adapter-my-thing",
|
|
182
|
+
"readme": "https://github.com/your-org/chat-adapter-my-thing/tree/v1.2.0"
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Accepted `readme` formats:
|
|
187
|
+
|
|
188
|
+
| Format | Example |
|
|
189
|
+
|--------|---------|
|
|
190
|
+
| Repo root at a tag | `https://github.com/owner/repo/tree/v1.0.0` |
|
|
191
|
+
| Repo root at a commit | `https://github.com/owner/repo/tree/abc1234...` |
|
|
192
|
+
| Subpath in a monorepo | `https://github.com/owner/repo/tree/<ref>/packages/adapter` |
|
|
193
|
+
|
|
194
|
+
Unpinned refs (e.g., `tree/main`, or omitting `/tree/<ref>` entirely) will emit a build warning and are rejected during PR review.
|
package/docs/files.mdx
CHANGED
|
@@ -75,3 +75,4 @@ bot.onSubscribedMessage(async (thread, message) => {
|
|
|
75
75
|
| `width` | `number` (optional) | Image width |
|
|
76
76
|
| `height` | `number` (optional) | Image height |
|
|
77
77
|
| `fetchData` | `() => Promise<Buffer>` (optional) | Download the file data |
|
|
78
|
+
| `fetchMetadata` | `Record<string, string>` (optional) | Platform-specific IDs for reconstructing `fetchData` after serialization |
|
package/docs/getting-started.mdx
CHANGED
|
@@ -25,14 +25,4 @@ 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
|
-
|
|
29
|
-
|
|
30
|
-
Step-by-step tutorials to get up and running on your platform of choice.
|
|
31
|
-
|
|
32
|
-
<Cards>
|
|
33
|
-
<Card title="Slack bot with Next.js and Redis" description="Build a Slack bot from scratch using Chat SDK, Next.js, and Redis." href="/docs/guides/slack-nextjs" />
|
|
34
|
-
<Card title="Durable chat sessions with Next.js, Workflow, and Redis" description="Build a bot whose thread sessions survive restarts by combining Chat SDK with Workflow." href="/docs/guides/durable-chat-sessions-nextjs" />
|
|
35
|
-
<Card title="Schedule Slack posts with Next.js, Workflow, and Neon" description="Build durable scheduled posts backed by Neon and Workflow timers." href="/docs/guides/scheduled-posts-neon" />
|
|
36
|
-
<Card title="Code review GitHub bot with Hono and Redis" description="Build a GitHub bot that reviews pull requests using AI SDK, Vercel Sandbox, and Chat SDK." href="/docs/guides/code-review-hono" />
|
|
37
|
-
<Card title="Discord support bot with Nuxt and Redis" description="Build a Discord support bot using Chat SDK, Nuxt, and AI SDK." href="/docs/guides/discord-nuxt" />
|
|
38
|
-
</Cards>
|
|
28
|
+
Step-by-step guides and starter templates are available on the [Resources](/resources) page.
|
package/docs/meta.json
CHANGED
package/docs/modals.mdx
CHANGED
|
@@ -87,6 +87,77 @@ A dropdown for selecting a single option.
|
|
|
87
87
|
| `initialOption` | `string` (optional) | Pre-selected value |
|
|
88
88
|
| `optional` | `boolean` (optional) | Allow empty submission |
|
|
89
89
|
|
|
90
|
+
### ExternalSelect
|
|
91
|
+
|
|
92
|
+
A dropdown that loads its options dynamically from a handler as the user types. Useful for large or remote-backed option sets (people, tickets, records) where a static `<Select>` would be impractical. Slack-only.
|
|
93
|
+
|
|
94
|
+
| Prop | Type | Description |
|
|
95
|
+
|------|------|-------------|
|
|
96
|
+
| `id` | `string` | Field identifier (key in `event.values`) |
|
|
97
|
+
| `label` | `string` | Field label |
|
|
98
|
+
| `placeholder` | `string` (optional) | Placeholder text |
|
|
99
|
+
| `minQueryLength` | `number` (optional) | Minimum characters before the loader fires (Slack default: 3) |
|
|
100
|
+
| `initialOption` | `{ label, value }` (optional) | Pre-selected option when the modal opens (must match an option returned by the loader). For static `<Select>`, `initialOption` is just the value string — for `<ExternalSelect>` it's the full `{ label, value }` object since the loader hasn't run yet. |
|
|
101
|
+
| `optional` | `boolean` (optional) | Allow empty submission |
|
|
102
|
+
|
|
103
|
+
Register the loader with `onOptionsLoad`:
|
|
104
|
+
|
|
105
|
+
```tsx title="lib/bot.tsx" lineNumbers
|
|
106
|
+
import { ExternalSelect, Modal } from "chat";
|
|
107
|
+
|
|
108
|
+
bot.onAction("assign", async (event) => {
|
|
109
|
+
await event.openModal(
|
|
110
|
+
<Modal callbackId="assign_form" title="Assign to…">
|
|
111
|
+
<ExternalSelect
|
|
112
|
+
id="assignee"
|
|
113
|
+
label="Assignee"
|
|
114
|
+
placeholder="Search people"
|
|
115
|
+
minQueryLength={1}
|
|
116
|
+
/>
|
|
117
|
+
</Modal>
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
bot.onOptionsLoad("assignee", async (event) => {
|
|
122
|
+
const people = await peopleService.search(event.query);
|
|
123
|
+
return people.map((p) => ({ label: p.fullName, value: p.id }));
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
bot.onModalSubmit("assign_form", async (event) => {
|
|
127
|
+
const assigneeId = event.values.assignee;
|
|
128
|
+
// …
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The selected value arrives in `event.values` on submit just like a static `<Select>`.
|
|
133
|
+
|
|
134
|
+
#### Grouped options
|
|
135
|
+
|
|
136
|
+
Return an array of groups instead of a flat options array to render headers between sections (e.g. "Recent" / "All"):
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
bot.onOptionsLoad("assignee", async (event) => {
|
|
140
|
+
const [recent, all] = await Promise.all([
|
|
141
|
+
peopleService.recent(event.user.userId),
|
|
142
|
+
peopleService.search(event.query),
|
|
143
|
+
]);
|
|
144
|
+
return [
|
|
145
|
+
{ label: "Recent", options: recent.map((p) => ({ label: p.fullName, value: p.id })) },
|
|
146
|
+
{ label: "All", options: all.map((p) => ({ label: p.fullName, value: p.id })) },
|
|
147
|
+
];
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Slack limits: max 100 groups, max 100 options per group, group label max 75 characters.
|
|
152
|
+
|
|
153
|
+
<Callout type="warn">
|
|
154
|
+
Slack requires a response within 3 seconds for options requests. The adapter caps the loader at ~2.5s and returns an empty result on timeout — keep your loader fast (cache, prefetch, or narrow the query server-side).
|
|
155
|
+
</Callout>
|
|
156
|
+
|
|
157
|
+
<Callout type="info">
|
|
158
|
+
**Slack setup:** `ExternalSelect` uses Slack's `block_suggestion` payload, which is dispatched to the **Options Load URL**. In your [Slack app settings](https://api.slack.com/apps) go to **Interactivity & Shortcuts** → **Select Menus** and set the **Options Load URL** to the same endpoint as your Interactivity Request URL (e.g. `https://your-domain.com/api/webhooks/slack`). Without this, typing into an external select will silently return no results.
|
|
159
|
+
</Callout>
|
|
160
|
+
|
|
90
161
|
### RadioSelect
|
|
91
162
|
|
|
92
163
|
A radio button group for mutually exclusive options.
|
|
@@ -142,7 +213,8 @@ bot.onModalSubmit("feedback_form", async (event) => {
|
|
|
142
213
|
|
|
143
214
|
| Response | Description |
|
|
144
215
|
|----------|-------------|
|
|
145
|
-
| `undefined` or `{ action: "close" }` | Close the
|
|
216
|
+
| `undefined` or `{ action: "close" }` | Close the current view (goes back one level in the stack) |
|
|
217
|
+
| `{ action: "clear" }` | Close all views and dismiss the modal entirely |
|
|
146
218
|
| `{ action: "errors", errors: { fieldId: "message" } }` | Show validation errors on specific fields |
|
|
147
219
|
| `{ action: "update", modal: ModalElement }` | Replace the modal content |
|
|
148
220
|
| `{ action: "push", modal: ModalElement }` | Push a new modal view onto the stack |
|
package/docs/streaming.mdx
CHANGED
|
@@ -118,6 +118,7 @@ const stream = (async function* () {
|
|
|
118
118
|
type: "task_update",
|
|
119
119
|
id: "search-1",
|
|
120
120
|
title: "Searching documents",
|
|
121
|
+
details: "Querying internal docs and ranking the best matches",
|
|
121
122
|
status: "in_progress",
|
|
122
123
|
} satisfies StreamChunk;
|
|
123
124
|
|
|
@@ -127,6 +128,7 @@ const stream = (async function* () {
|
|
|
127
128
|
type: "task_update",
|
|
128
129
|
id: "search-1",
|
|
129
130
|
title: "Searching documents",
|
|
131
|
+
details: "Ranked 3 relevant results",
|
|
130
132
|
status: "complete",
|
|
131
133
|
output: "Found 3 results",
|
|
132
134
|
} satisfies StreamChunk;
|
|
@@ -142,16 +144,19 @@ await thread.post(stream);
|
|
|
142
144
|
| Type | Fields | Description |
|
|
143
145
|
|------|--------|-------------|
|
|
144
146
|
| `markdown_text` | `text` | Streamed text content |
|
|
145
|
-
| `task_update` | `id`, `title`, `status`, `output?` | Tool/step progress cards (`pending`, `in_progress`, `complete`, `error`) |
|
|
147
|
+
| `task_update` | `id`, `title`, `status`, `details?`, `output?` | Tool/step progress cards (`pending`, `in_progress`, `complete`, `error`) with optional extra task context |
|
|
146
148
|
| `plan_update` | `title` | Plan title updates |
|
|
147
149
|
|
|
148
150
|
### Task display mode
|
|
149
151
|
|
|
150
|
-
Control how `task_update` chunks render in Slack by passing `taskDisplayMode`
|
|
152
|
+
Control how `task_update` chunks render in Slack by passing `taskDisplayMode` via the adapter's streaming API. These options are currently only available through `adapter.stream()` directly:
|
|
151
153
|
|
|
152
154
|
```typescript
|
|
153
|
-
|
|
154
|
-
|
|
155
|
+
const raw = message.raw as { team_id?: string; team?: string };
|
|
156
|
+
await thread.adapter.stream(thread.id, stream, {
|
|
157
|
+
recipientUserId: message.author.userId,
|
|
158
|
+
recipientTeamId: raw.team_id ?? raw.team,
|
|
159
|
+
taskDisplayMode: "plan",
|
|
155
160
|
});
|
|
156
161
|
```
|
|
157
162
|
|
|
@@ -167,7 +172,10 @@ Adapters without structured chunk support extract text from `markdown_text` chun
|
|
|
167
172
|
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:
|
|
168
173
|
|
|
169
174
|
```typescript title="lib/bot.ts" lineNumbers
|
|
170
|
-
|
|
175
|
+
const raw = message.raw as { team_id?: string; team?: string };
|
|
176
|
+
await thread.adapter.stream(thread.id, textStream, {
|
|
177
|
+
recipientUserId: message.author.userId,
|
|
178
|
+
recipientTeamId: raw.team_id ?? raw.team,
|
|
171
179
|
stopBlocks: [
|
|
172
180
|
{
|
|
173
181
|
type: "actions",
|
|
@@ -40,6 +40,33 @@ await thread.unsubscribe();
|
|
|
40
40
|
const subscribed = await thread.isSubscribed();
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
### Participants
|
|
44
|
+
|
|
45
|
+
Get the unique human participants in a thread. Returns deduplicated authors, excluding all bots. Useful for deciding whether to subscribe based on how many humans are in the conversation.
|
|
46
|
+
|
|
47
|
+
```typescript title="lib/bot.ts" lineNumbers
|
|
48
|
+
bot.onNewMention(async (thread) => {
|
|
49
|
+
const participants = await thread.getParticipants();
|
|
50
|
+
if (participants.length === 1) {
|
|
51
|
+
await thread.subscribe();
|
|
52
|
+
await thread.post("I'm here to help!");
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
bot.onSubscribedMessage(async (thread) => {
|
|
57
|
+
const participants = await thread.getParticipants();
|
|
58
|
+
if (participants.length > 1) {
|
|
59
|
+
await thread.unsubscribe();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// respond...
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
<Callout type="warn">
|
|
67
|
+
Each call fetches the full message history to find all participants. On threads with long history this makes multiple API calls to the platform. Consider checking `message.author` against a known set before calling `getParticipants()` on every incoming message.
|
|
68
|
+
</Callout>
|
|
69
|
+
|
|
43
70
|
### Typing indicator
|
|
44
71
|
|
|
45
72
|
```typescript title="lib/bot.ts"
|
|
@@ -144,6 +171,13 @@ interface Author {
|
|
|
144
171
|
}
|
|
145
172
|
```
|
|
146
173
|
|
|
174
|
+
For richer user info (email, avatar), use [`chat.getUser()`](/docs/api/chat#getuser):
|
|
175
|
+
|
|
176
|
+
```typescript title="lib/bot.ts"
|
|
177
|
+
const user = await bot.getUser(message.author);
|
|
178
|
+
console.log(user?.email); // "alice@company.com"
|
|
179
|
+
```
|
|
180
|
+
|
|
147
181
|
### Sent messages
|
|
148
182
|
|
|
149
183
|
When you post a message, you get back a `SentMessage` with methods to edit, delete, and react:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chat",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.27.0",
|
|
4
4
|
"description": "Unified chat abstraction for Slack, Teams, Google Chat, and Discord",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"dist",
|
|
25
|
-
"docs"
|
|
25
|
+
"docs",
|
|
26
|
+
"resources"
|
|
26
27
|
],
|
|
27
28
|
"dependencies": {
|
|
28
29
|
"@workflow/serde": "4.1.0-beta.2",
|