chat 4.27.0 → 4.29.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.
Files changed (49) hide show
  1. package/dist/ai/index.d.ts +501 -0
  2. package/dist/ai/index.js +500 -0
  3. package/dist/chat-D9UYaaNO.d.ts +3156 -0
  4. package/dist/chunk-HD375J7S.js +128 -0
  5. package/dist/{chunk-AN7MRAVW.js → chunk-V25FKIIL.js} +5 -1
  6. package/dist/index.d.ts +35 -2934
  7. package/dist/index.js +567 -210
  8. package/dist/{jsx-runtime-Co9uV6l7.d.ts → jsx-runtime-CFq1K_Ve.d.ts} +11 -1
  9. package/dist/jsx-runtime.d.ts +1 -1
  10. package/dist/jsx-runtime.js +1 -1
  11. package/docs/actions.mdx +52 -1
  12. package/docs/adapters.mdx +72 -36
  13. package/docs/ai/ai-sdk-tools.mdx +227 -0
  14. package/docs/ai/index.mdx +63 -0
  15. package/docs/ai/meta.json +4 -0
  16. package/docs/{api → ai}/to-ai-messages.mdx +16 -3
  17. package/docs/ai/types.mdx +243 -0
  18. package/docs/api/cards.mdx +4 -0
  19. package/docs/api/chat.mdx +132 -10
  20. package/docs/api/index.mdx +6 -6
  21. package/docs/api/markdown.mdx +28 -5
  22. package/docs/api/message.mdx +54 -1
  23. package/docs/api/meta.json +1 -0
  24. package/docs/api/modals.mdx +50 -0
  25. package/docs/api/postable-message.mdx +58 -4
  26. package/docs/api/thread.mdx +11 -3
  27. package/docs/api/transcripts.mdx +220 -0
  28. package/docs/cards.mdx +6 -0
  29. package/docs/concurrency.mdx +58 -15
  30. package/docs/contributing/building.mdx +74 -2
  31. package/docs/contributing/testing.mdx +4 -0
  32. package/docs/conversation-history.mdx +137 -0
  33. package/docs/direct-messages.mdx +23 -5
  34. package/docs/ephemeral-messages.mdx +1 -1
  35. package/docs/error-handling.mdx +15 -3
  36. package/docs/files.mdx +21 -1
  37. package/docs/handling-events.mdx +10 -7
  38. package/docs/index.mdx +8 -5
  39. package/docs/meta.json +17 -3
  40. package/docs/modals.mdx +24 -0
  41. package/docs/posting-messages.mdx +10 -4
  42. package/docs/slash-commands.mdx +4 -4
  43. package/docs/streaming.mdx +75 -27
  44. package/docs/subject.mdx +53 -0
  45. package/docs/testing.mdx +142 -0
  46. package/docs/threads-messages-channels.mdx +10 -1
  47. package/docs/usage.mdx +15 -2
  48. package/package.json +23 -2
  49. package/resources/guides/how-to-build-an-ai-agent-for-slack-with-chat-sdk-and-ai-sdk.md +1 -1
@@ -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.
@@ -100,6 +100,10 @@ Button({ id: "delete", label: "Delete", style: "danger", value: "item-123" })
100
100
  type: '"action" | "modal"',
101
101
  default: '"action"',
102
102
  },
103
+ callbackUrl: {
104
+ description: 'URL to POST action data to when this button is clicked.',
105
+ type: 'string',
106
+ },
103
107
  }}
104
108
  />
105
109
 
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, all messages (including @-mentions) route here instead of `onNewMention`.
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) => {
@@ -168,7 +195,9 @@ Fires when a user clicks a button or selects an option in a card.
168
195
  ```typescript
169
196
  // Single action
170
197
  bot.onAction("approve", async (event) => {
171
- await event.thread.post("Approved!");
198
+ if (event.thread) {
199
+ await event.thread.post("Approved!");
200
+ }
172
201
  });
173
202
 
174
203
  // Multiple actions
@@ -193,8 +222,8 @@ bot.onAction(async (event) => { /* ... */ });
193
222
  type: 'Author',
194
223
  },
195
224
  'event.thread': {
196
- description: 'The thread containing the card.',
197
- type: 'Thread',
225
+ description: 'The thread containing the card, or null for view-based actions.',
226
+ type: 'Thread | null',
198
227
  },
199
228
  'event.triggerId': {
200
229
  description: 'Trigger ID for opening modals (platform-specific, may expire quickly).',
@@ -261,9 +290,47 @@ Returns `ModalResponse | undefined` to control the modal after submission:
261
290
  - `{ action: "update", modal: ModalElement }` — replace the modal content
262
291
  - `{ action: "push", modal: ModalElement }` — push a new modal view onto the stack
263
292
 
293
+ ### onOptionsLoad
294
+
295
+ 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.
296
+
297
+ ```typescript
298
+ bot.onOptionsLoad("assignee", async (event) => {
299
+ const people = await peopleService.search(event.query);
300
+ return people.map((p) => ({ label: p.fullName, value: p.id }));
301
+ });
302
+ ```
303
+
304
+ 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.
305
+
306
+ <TypeTable
307
+ type={{
308
+ 'event.actionId': {
309
+ description: 'The id of the select requesting options (matches the id passed to bot.onOptionsLoad).',
310
+ type: 'string',
311
+ },
312
+ 'event.query': {
313
+ description: 'The text the user has typed so far.',
314
+ type: 'string',
315
+ },
316
+ 'event.user': {
317
+ description: 'The user requesting options.',
318
+ type: 'Author',
319
+ },
320
+ 'event.adapter': {
321
+ description: 'The adapter that received this event.',
322
+ type: 'Adapter',
323
+ },
324
+ 'event.raw': {
325
+ description: 'Raw platform-specific payload.',
326
+ type: 'unknown',
327
+ },
328
+ }}
329
+ />
330
+
264
331
  ### onSlashCommand
265
332
 
266
- Fires when a user invokes a `/command` in the message composer. Currently supported on Slack.
333
+ Fires when a user invokes a `/command` in the message composer. Currently supported on Slack and Discord.
267
334
 
268
335
  ```typescript
269
336
  // Specific command
@@ -426,12 +493,55 @@ bot.webhooks.teams(request, { waitUntil });
426
493
 
427
494
  ### getAdapter
428
495
 
429
- Get an adapter instance by name.
496
+ Get a typed adapter instance by name.
430
497
 
431
498
  ```typescript
432
499
  const slack = bot.getAdapter("slack");
433
500
  ```
434
501
 
502
+ #### Direct client access
503
+
504
+ Access the platform's typed native API client directly via an SDK-named getter — `.webClient` on Slack, `.linearClient` on Linear, `.octokit` on GitHub:
505
+
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
+
511
+ // Linear - full LinearClient from @linear/sdk
512
+ const linear = bot.getAdapter("linear").linearClient;
513
+ const issue = await linear.issue("ENG-123");
514
+ const project = await issue.project;
515
+
516
+ // GitHub - full Octokit from @octokit/rest
517
+ const github = bot.getAdapter("github").octokit;
518
+ const { data: pulls } = await github.rest.pulls.list({
519
+ owner: "vercel",
520
+ repo: "chat",
521
+ state: "open",
522
+ });
523
+ ```
524
+
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>
530
+
531
+ <Callout type="warn">
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.
537
+ </Callout>
538
+
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` |
544
+
435
545
  ### openDM
436
546
 
437
547
  Open a direct message thread with a user.
@@ -498,10 +608,16 @@ const user = await bot.getUser(message.author);
498
608
  - **GitHub** — `email` is `null` unless the user made it public, or you authenticated with the `user:email` scope.
499
609
  - **Linear** — full profile (incl. email + avatar) for any active workspace member.
500
610
 
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.
611
+ 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
612
  </Callout>
503
613
 
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:
614
+ `bot.getUser` throws a `ChatError` in three cases. Handle them if your bot runs on multiple platforms:
615
+
616
+ | Code | When |
617
+ |------|------|
618
+ | `NOT_SUPPORTED` | The resolved adapter doesn't implement `getUser` (e.g. WhatsApp) |
619
+ | `AMBIGUOUS_USER_ID` | A numeric user ID could belong to more than one registered adapter (Discord/Telegram/GitHub) |
620
+ | `UNKNOWN_USER_ID_FORMAT` | The `userId` string doesn't match any registered platform's ID format |
505
621
 
506
622
  ```typescript
507
623
  import { ChatError } from "chat";
@@ -512,8 +628,14 @@ try {
512
628
  // User not found on this platform
513
629
  }
514
630
  } catch (error) {
515
- if (error instanceof ChatError && error.code === "NOT_SUPPORTED") {
516
- // This adapter doesn't support user lookups
631
+ if (error instanceof ChatError) {
632
+ if (error.code === "NOT_SUPPORTED") {
633
+ // This adapter doesn't support user lookups
634
+ } else if (error.code === "AMBIGUOUS_USER_ID") {
635
+ // Pass message.author or call adapter.getUser(userId) directly
636
+ } else if (error.code === "UNKNOWN_USER_ID_FORMAT") {
637
+ // userId doesn't match any known platform format
638
+ }
517
639
  }
518
640
  }
519
641
  ```
@@ -20,17 +20,17 @@ 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 |
32
26
  |--------|-------------|
33
27
  | [`PostableMessage`](/docs/api/postable-message) | Union type accepted by `thread.post()` |
28
+ | [`Plan`](/docs/api/postable-message#plan) | Step-by-step task list that mutates after posting |
29
+ | [`StreamingPlan`](/docs/api/postable-message#streamingplan) | Wraps an async iterable with platform-specific streaming options |
34
30
  | [`Cards`](/docs/api/cards) | Rich card components — `Card`, `Text`, `Button`, `Actions`, etc. |
35
31
  | [`Markdown`](/docs/api/markdown) | AST builder functions — `root`, `paragraph`, `text`, `strong`, etc. |
36
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.
@@ -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:
21
+
22
+ ```typescript
23
+ import type { Nodes, Root, Content } from "chat";
24
+
25
+ function render(node: Nodes): string {
26
+ switch (node.type) {
27
+ case "text": return node.value;
28
+ case "strong": return node.children.map(render).join("");
29
+ // ...
30
+ default: throw new Error(`Unhandled: ${node satisfies never}`);
31
+ }
32
+ }
33
+ ```
34
+
35
+ Adapters use this pattern to make the type checker reject the build when a new mdast node type is introduced upstream.
36
+
18
37
  ## Node builders
19
38
 
20
39
  ### root
@@ -222,7 +241,7 @@ const value = getNodeValue(node); // string | undefined
222
241
 
223
242
  ### tableToAscii
224
243
 
225
- Render an mdast `Table` node as a padded ASCII table string. Used by adapters that lack native table support (Slack, Google Chat, Discord, Telegram).
244
+ Render an mdast `Table` node as a padded ASCII table string. Used by adapters that lack native table support (Google Chat, Discord, Telegram).
226
245
 
227
246
  ```typescript
228
247
  import { parseMarkdown, tableToAscii, isTableNode } from "chat";
@@ -261,17 +280,21 @@ The SDK uses mdast as the canonical format and each adapter converts it to the p
261
280
 
262
281
  | Feature | Slack | Teams | Google Chat |
263
282
  |---------|-------|-------|-------------|
264
- | Bold | `*text*` | `**text**` | `*text*` |
283
+ | Bold | `**text**` | `**text**` | `*text*` |
265
284
  | Italic | `_text_` | `_text_` | `_text_` |
266
- | Strikethrough | `~text~` | `~~text~~` | `~text~` |
285
+ | Strikethrough | `~~text~~` | `~~text~~` | `~text~` |
267
286
  | Code | `` `code` `` | `` `code` `` | `` `code` `` |
268
287
  | Code blocks | ```` ``` ```` | ```` ``` ```` | ```` ``` ```` |
269
- | Links | `<url\|text>` | `[text](url)` | `[text](url)` |
288
+ | Links | `[text](url)` | `[text](url)` | `[text](url)` |
270
289
  | Lists | Supported | Supported | Supported |
271
290
  | Blockquotes | `>` | `>` | Simulated with `>` prefix |
272
- | Tables | ASCII fallback | Native GFM | ASCII fallback |
291
+ | Tables | Native (markdown_text) | Native GFM | ASCII fallback |
273
292
  | Mentions | `<@USER>` | `<at>name</at>` | `<users/{id}>` |
274
293
 
294
+ <Callout type="info">
295
+ Slack accepts standard markdown via the `markdown_text` field on `chat.postMessage` and friends, so the SDK passes markdown through directly. Incoming Slack messages still arrive as legacy mrkdwn (`*bold*`, `<url|text>`) and are parsed transparently. If you need to send mrkdwn yourself, use `{ raw: "..." }`.
296
+ </Callout>
297
+
275
298
  <Callout type="info">
276
299
  You don't need to worry about these differences when using the SDK — the AST builders and `parseMarkdown` handle conversion automatically. This table is useful if you're working with `raw` platform payloads or debugging formatting issues.
277
300
  </Callout>
@@ -54,6 +54,10 @@ import { Message } from "chat";
54
54
  description: 'Whether the bot was @-mentioned in this message.',
55
55
  type: 'boolean | undefined',
56
56
  },
57
+ subject: {
58
+ description: 'Resolves the parent resource (issue, PR) this message is about. Returns null on chat platforms. See [Message Subject](/docs/subject).',
59
+ type: 'Promise<MessageSubject | null>',
60
+ },
57
61
  }}
58
62
  />
59
63
 
@@ -190,7 +194,7 @@ Links found in incoming messages are extracted and exposed as `LinkPreview` obje
190
194
  />
191
195
 
192
196
  <Callout type="info">
193
- When using [`toAiMessages()`](/docs/api/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.
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.
194
198
  </Callout>
195
199
 
196
200
  ### Platform support
@@ -200,6 +204,55 @@ When using [`toAiMessages()`](/docs/api/to-ai-messages), link metadata is automa
200
204
  | Slack | URLs from `rich_text` blocks or `<url>` text patterns | Slack message links (`*.slack.com/archives/...`) |
201
205
  | Others | Not yet — `links` is always `[]` | — |
202
206
 
207
+ ## MessageSubject
208
+
209
+ Returned by `message.subject` on platforms with parent resources. See [Message Subject](/docs/subject) for usage.
210
+
211
+ <TypeTable
212
+ type={{
213
+ type: {
214
+ description: 'Resource kind, e.g. "issue" or "pull_request".',
215
+ type: 'string',
216
+ },
217
+ id: {
218
+ description: 'Resource identifier (e.g. "ENG-123" or "42").',
219
+ type: 'string',
220
+ },
221
+ title: {
222
+ description: 'Resource title.',
223
+ type: 'string | undefined',
224
+ },
225
+ description: {
226
+ description: 'Full description/body in markdown.',
227
+ type: 'string | undefined',
228
+ },
229
+ status: {
230
+ description: 'Current status (e.g. "In Progress", "open").',
231
+ type: 'string | undefined',
232
+ },
233
+ url: {
234
+ description: 'Web URL to the resource.',
235
+ type: 'string | undefined',
236
+ },
237
+ author: {
238
+ description: 'Resource creator.',
239
+ type: '{ id: string; name: string } | undefined',
240
+ },
241
+ assignee: {
242
+ description: 'Current assignee.',
243
+ type: '{ id: string; name: string } | undefined',
244
+ },
245
+ labels: {
246
+ description: 'Labels/tags.',
247
+ type: 'string[] | undefined',
248
+ },
249
+ raw: {
250
+ description: 'Full platform API response.',
251
+ type: 'unknown',
252
+ },
253
+ }}
254
+ />
255
+
203
256
  ## Serialization
204
257
 
205
258
  Messages can be serialized for workflow engines and external systems.
@@ -7,6 +7,7 @@
7
7
  "channel",
8
8
  "message",
9
9
  "postable-message",
10
+ "transcripts",
10
11
  "cards",
11
12
  "markdown",
12
13
  "modals"
@@ -54,6 +54,10 @@ bot.onAction("open-form", async (event) => {
54
54
  type: 'boolean',
55
55
  default: 'false',
56
56
  },
57
+ callbackUrl: {
58
+ description: 'URL to POST form values to when the modal is submitted.',
59
+ type: 'string',
60
+ },
57
61
  privateMetadata: {
58
62
  description: 'Arbitrary string passed through the modal lifecycle (e.g., JSON context).',
59
63
  type: 'string',
@@ -167,6 +171,52 @@ Select({
167
171
  }}
168
172
  />
169
173
 
174
+ ## ExternalSelect
175
+
176
+ 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.
177
+
178
+ ```typescript
179
+ ExternalSelect({
180
+ id: "assignee",
181
+ label: "Assignee",
182
+ placeholder: "Search people",
183
+ minQueryLength: 1,
184
+ initialOption: { label: "Alice", value: "U123" },
185
+ })
186
+ ```
187
+
188
+ <TypeTable
189
+ type={{
190
+ id: {
191
+ description: 'Input ID — used as the key in event.values.',
192
+ type: 'string',
193
+ },
194
+ label: {
195
+ description: 'Label displayed above the select.',
196
+ type: 'string',
197
+ },
198
+ placeholder: {
199
+ description: 'Placeholder text.',
200
+ type: 'string',
201
+ },
202
+ minQueryLength: {
203
+ description: 'Minimum characters before the loader fires (Slack default: 3).',
204
+ type: 'number',
205
+ },
206
+ initialOption: {
207
+ 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.',
208
+ type: '{ label: string, value: string }',
209
+ },
210
+ optional: {
211
+ description: 'Whether the field can be left empty.',
212
+ type: 'boolean',
213
+ default: 'false',
214
+ },
215
+ }}
216
+ />
217
+
218
+ The loader registered via `bot.onOptionsLoad("assignee", handler)` returns either a flat `SelectOptionElement[]` or `OptionsLoadGroup[]` (`{ label, options }[]`) for grouped options.
219
+
170
220
  ## RadioSelect
171
221
 
172
222
  Radio button group for mutually exclusive choices.