You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
-`{ action: "push", modal: ModalElement }` — push a new modal view onto the stack
263
263
264
+
### onOptionsLoad
265
+
266
+
Fires when an `ExternalSelect` requests options dynamically. The handler is keyed on the select's `id` and must return options synchronously enough for Slack's 3-second budget (the adapter caps the loader at ~2.5s and substitutes an empty result on timeout). Slack-only.
267
+
268
+
```typescript
269
+
bot.onOptionsLoad("assignee", async (event) => {
270
+
const people =awaitpeopleService.search(event.query);
Return an array of `OptionsLoadGroup` (`{ label, options }[]`) instead of a flat array to render grouped headers (e.g. "Recent" / "All"). Slack limits: max 100 groups, max 100 options per group.
276
+
277
+
<TypeTable
278
+
type={{
279
+
'event.actionId': {
280
+
description: 'The id of the select requesting options (matches the id passed to bot.onOptionsLoad).',
281
+
type: 'string',
282
+
},
283
+
'event.query': {
284
+
description: 'The text the user has typed so far.',
285
+
type: 'string',
286
+
},
287
+
'event.user': {
288
+
description: 'The user requesting options.',
289
+
type: 'Author',
290
+
},
291
+
'event.adapter': {
292
+
description: 'The adapter that received this event.',
293
+
type: 'Adapter',
294
+
},
295
+
'event.raw': {
296
+
description: 'Raw platform-specific payload.',
297
+
type: 'unknown',
298
+
},
299
+
}}
300
+
/>
301
+
264
302
### onSlashCommand
265
303
266
304
Fires when a user invokes a `/command` in the message composer. Currently supported on Slack.
@@ -498,10 +536,16 @@ const user = await bot.getUser(message.author);
498
536
-**GitHub** — `email` is `null` unless the user made it public, or you authenticated with the `user:email` scope.
499
537
-**Linear** — full profile (incl. email + avatar) for any active workspace member.
500
538
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.
539
+
Fields that aren't available return `undefined`. Numeric user IDs (Discord/Telegram/GitHub) can be ambiguous when multiple of those adapters are registered — `bot.getUser` throws a `ChatError` with code `AMBIGUOUS_USER_ID` in that case. Pass an `Author` from a message handler (which already carries the adapter), or call the adapter directly (`adapter.getUser(userId)`).
502
540
</Callout>
503
541
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:
542
+
`bot.getUser` throws a `ChatError` in three cases. Handle them if your bot runs on multiple platforms:
543
+
544
+
| Code | When |
545
+
|------|------|
546
+
|`NOT_SUPPORTED`| The resolved adapter doesn't implement `getUser` (e.g. WhatsApp) |
547
+
|`AMBIGUOUS_USER_ID`| A numeric user ID could belong to more than one registered adapter (Discord/Telegram/GitHub) |
548
+
|`UNKNOWN_USER_ID_FORMAT`| The `userId` string doesn't match any registered platform's ID format |
505
549
506
550
```typescript
507
551
import { ChatError } from"chat";
@@ -512,8 +556,14 @@ try {
512
556
// User not found on this platform
513
557
}
514
558
} catch (error) {
515
-
if (errorinstanceofChatError&&error.code==="NOT_SUPPORTED") {
516
-
// This adapter doesn't support user lookups
559
+
if (errorinstanceofChatError) {
560
+
if (error.code==="NOT_SUPPORTED") {
561
+
// This adapter doesn't support user lookups
562
+
} elseif (error.code==="AMBIGUOUS_USER_ID") {
563
+
// Pass message.author or call adapter.getUser(userId) directly
Copy file name to clipboardExpand all lines: apps/docs/content/docs/api/markdown.mdx
+19Lines changed: 19 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,6 +15,25 @@ import {
15
15
} from"chat";
16
16
```
17
17
18
+
## Type re-exports
19
+
20
+
The chat package re-exports mdast's union and content types so adapters and downstream code can build exhaustively-typed AST walkers without depending on `mdast` directly:
Copy file name to clipboardExpand all lines: apps/docs/content/docs/api/modals.mdx
+46Lines changed: 46 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -167,6 +167,52 @@ Select({
167
167
}}
168
168
/>
169
169
170
+
## ExternalSelect
171
+
172
+
Dropdown that loads options dynamically from a handler as the user types. Slack-only. Pair with [`bot.onOptionsLoad`](/docs/api/chat#onoptionsload) to supply options. See [Modals → ExternalSelect](/docs/modals#externalselect) for a full example, grouped-options support, and Slack setup notes.
173
+
174
+
```typescript
175
+
ExternalSelect({
176
+
id: "assignee",
177
+
label: "Assignee",
178
+
placeholder: "Search people",
179
+
minQueryLength: 1,
180
+
initialOption: { label: "Alice", value: "U123" },
181
+
})
182
+
```
183
+
184
+
<TypeTable
185
+
type={{
186
+
id: {
187
+
description: 'Input ID — used as the key in event.values.',
188
+
type: 'string',
189
+
},
190
+
label: {
191
+
description: 'Label displayed above the select.',
192
+
type: 'string',
193
+
},
194
+
placeholder: {
195
+
description: 'Placeholder text.',
196
+
type: 'string',
197
+
},
198
+
minQueryLength: {
199
+
description: 'Minimum characters before the loader fires (Slack default: 3).',
200
+
type: 'number',
201
+
},
202
+
initialOption: {
203
+
description: 'Pre-selected option when the modal opens. Unlike static Select where initialOption is a value string, ExternalSelect needs the full label/value object since the loader has not run yet.',
204
+
type: '{ label: string, value: string }',
205
+
},
206
+
optional: {
207
+
description: 'Whether the field can be left empty.',
208
+
type: 'boolean',
209
+
default: 'false',
210
+
},
211
+
}}
212
+
/>
213
+
214
+
The loader registered via `bot.onOptionsLoad("assignee", handler)` returns either a flat `SelectOptionElement[]` or `OptionsLoadGroup[]` (`{ label, options }[]`) for grouped options.
215
+
170
216
## RadioSelect
171
217
172
218
Radio button group for mutually exclusive choices.
`PostableObject` covers `Plan` (mutable task lists) and `StreamingPlan` (streams with platform-specific options) — both documented below.
17
+
13
18
## String
14
19
15
20
Raw text passed through as-is to the platform.
@@ -133,6 +138,55 @@ await thread.post({
133
138
}}
134
139
/>
135
140
141
+
## Plan
142
+
143
+
A `Plan` is a step-by-step task list that mutates after posting. Each `addTask` / `updateTask` / `complete` call re-renders the same message in place. See [Plan API](/docs/streaming#plan-api) for full usage.
144
+
145
+
```typescript
146
+
import { Plan } from"chat";
147
+
148
+
const plan =newPlan({ initialMessage: "Researching options..." });
149
+
awaitthread.post(plan);
150
+
awaitplan.addTask({ title: "Look up records" });
151
+
awaitplan.complete({ completeMessage: "Done!" });
152
+
```
153
+
154
+
Adapters that don't support `PostableObject` editing render the plan as fallback text and ignore subsequent mutations.
155
+
156
+
## StreamingPlan
157
+
158
+
Wraps an async iterable with platform-specific streaming options. Use this when you need to pass options like task grouping or stop blocks through `thread.post()`. See [Streaming with options](/docs/streaming#streaming-with-options).
159
+
160
+
```typescript
161
+
import { StreamingPlan } from"chat";
162
+
163
+
const planned =newStreamingPlan(stream, {
164
+
groupTasks: "plan",
165
+
endWith: [feedbackBlock],
166
+
updateIntervalMs: 750,
167
+
});
168
+
169
+
awaitthread.post(planned);
170
+
```
171
+
172
+
<TypeTable
173
+
type={{
174
+
groupTasks: {
175
+
description: 'Slack: render task_update chunks as `"plan"` (single grouped block) or `"timeline"` (inline cards, default).',
176
+
type: '"plan" | "timeline" | undefined',
177
+
},
178
+
endWith: {
179
+
description: 'Slack: Block Kit elements appended when the stream stops (e.g. retry / feedback buttons).',
180
+
type: 'unknown[] | undefined',
181
+
},
182
+
updateIntervalMs: {
183
+
description: 'Fallback adapters: minimum interval between post+edit cycles in ms.',
184
+
type: 'number | undefined',
185
+
default: '500',
186
+
},
187
+
}}
188
+
/>
189
+
136
190
## AsyncIterable (streaming)
137
191
138
192
An async iterable of strings, `StreamChunk` objects, or stream events. The SDK streams the message in real time using platform-native APIs where available.
**Returns:**`Promise<SentMessage>` — the sent message with `edit()`, `delete()`, `addReaction()`, and `removeReaction()` methods.
71
+
**Returns:**`Promise<SentMessage | PostableObject>` — for plain messages and streams, a `SentMessage`with `edit()`, `delete()`, `addReaction()`, and `removeReaction()` methods; for `Plan` / `StreamingPlan` inputs, the same object is returned so you can keep mutating it.
64
72
65
73
See [Posting Messages](/docs/posting-messages) for details on each format.
Copy file name to clipboardExpand all lines: apps/docs/content/docs/concurrency.mdx
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -124,6 +124,10 @@ const bot = new Chat({
124
124
|`debounceMs`| debounce |`1500`| Debounce window in milliseconds |
125
125
|`maxConcurrent`| concurrent |`Infinity`| Max concurrent handlers per thread |
126
126
127
+
<Callouttype="warn">
128
+
`maxConcurrent` only applies to the `concurrent` strategy. Pairing it with any other strategy logs a warning and the value is ignored. Setting `maxConcurrent` to a value less than `1` throws at construction time — `0` would deadlock the strategy and is rejected up front.
129
+
</Callout>
130
+
127
131
## MessageContext
128
132
129
133
All handler types (`onNewMention`, `onSubscribedMessage`, `onNewMessage`) accept an optional `MessageContext` as their last parameter. It is only populated when using the `queue` strategy and messages were skipped.
Base error class for all SDK errors. Every error below extends `ChatError`.
19
+
Base error class for all SDK errors. Every error below extends `ChatError`. The `code` property carries a machine-readable identifier you can branch on:
20
+
21
+
| Code | Thrown by | Meaning |
22
+
|------|-----------|---------|
23
+
|`NOT_SUPPORTED`|`bot.openDM`, `bot.getUser`| The resolved adapter doesn't implement this method |
24
+
|`INVALID_THREAD_ID`|`bot.thread`, internal routing | Thread ID does not match the `adapter:channel:thread` shape |
25
+
|`INVALID_CHANNEL_ID`|`bot.channel`| Channel ID does not match the `adapter:channel` shape |
26
+
|`ADAPTER_NOT_FOUND`|`bot.thread`, `bot.channel`| Thread/channel ID references an adapter that wasn't registered on this `Chat` instance |
27
+
|`AMBIGUOUS_USER_ID`|`bot.getUser`, `bot.openDM`| Numeric user ID could match more than one registered adapter (Discord/Telegram/GitHub) |
28
+
|`UNKNOWN_USER_ID_FORMAT`|`bot.getUser`, `bot.openDM`| The `userId` doesn't match any platform's known ID format |
29
+
|`RATE_LIMITED`| Any platform call | Platform returned 429; see `RateLimitError` below |
30
+
|`NOT_IMPLEMENTED`| Any platform call | The adapter doesn't implement this feature; see `NotImplementedError` below |
31
+
|`LOCK_FAILED`| Inbound message routing | Distributed lock was busy; see `LockError` below |
20
32
21
33
<TypeTable
22
34
type={{
@@ -25,7 +37,7 @@ Base error class for all SDK errors. Every error below extends `ChatError`.
25
37
type: 'string',
26
38
},
27
39
code: {
28
-
description: 'Machine-readable error code.',
40
+
description: 'Machine-readable error code (see table above).',
0 commit comments