chat 4.15.0 → 4.16.1

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.
@@ -145,6 +145,8 @@ type ButtonStyle = "primary" | "danger" | "default";
145
145
  type TextStyle = "plain" | "bold" | "muted";
146
146
  /** Button element for interactive actions */
147
147
  interface ButtonElement {
148
+ /** If true, the button is displayed in an inactive state and doesn't respond to user actions */
149
+ disabled?: boolean;
148
150
  /** Unique action ID for callback routing */
149
151
  id: string;
150
152
  /** Button label text */
@@ -219,8 +221,20 @@ interface FieldsElement {
219
221
  children: FieldElement[];
220
222
  type: "fields";
221
223
  }
224
+ /** Column alignment for table elements */
225
+ type TableAlignment = "left" | "center" | "right";
226
+ /** Table element for structured data display */
227
+ interface TableElement {
228
+ /** Column alignment */
229
+ align?: TableAlignment[];
230
+ /** Column header labels */
231
+ headers: string[];
232
+ /** Data rows (each row is an array of cell strings) */
233
+ rows: string[][];
234
+ type: "table";
235
+ }
222
236
  /** Union of all card child element types */
223
- type CardChild = TextElement | ImageElement | DividerElement | ActionsElement | SectionElement | FieldsElement | LinkElement;
237
+ type CardChild = TextElement | ImageElement | DividerElement | ActionsElement | SectionElement | FieldsElement | LinkElement | TableElement;
224
238
  /** Union of all element types (including nested children) */
225
239
  type AnyCardElement = CardChild | CardElement | ButtonElement | LinkButtonElement | LinkElement | FieldElement | SelectElement | RadioSelectElement;
226
240
  /** Root card element */
@@ -318,6 +332,8 @@ declare function Section(children: CardChild[]): SectionElement;
318
332
  declare function Actions(children: (ButtonElement | LinkButtonElement | SelectElement | RadioSelectElement)[]): ActionsElement;
319
333
  /** Options for Button */
320
334
  interface ButtonOptions {
335
+ /** If true, the button is displayed in an inactive state and doesn't respond to user actions */
336
+ disabled?: boolean;
321
337
  /** Unique action ID for callback routing */
322
338
  id: string;
323
339
  /** Button label text */
@@ -380,6 +396,30 @@ declare function Field(options: {
380
396
  * ```
381
397
  */
382
398
  declare function Fields(children: FieldElement[]): FieldsElement;
399
+ /** Options for Table */
400
+ interface TableOptions {
401
+ /** Column alignment */
402
+ align?: TableAlignment[];
403
+ /** Column header labels */
404
+ headers: string[];
405
+ /** Data rows */
406
+ rows: string[][];
407
+ }
408
+ /**
409
+ * Create a Table element for structured data display.
410
+ *
411
+ * @example
412
+ * ```ts
413
+ * Table({
414
+ * headers: ["Name", "Age", "Role"],
415
+ * rows: [
416
+ * ["Alice", "30", "Engineer"],
417
+ * ["Bob", "25", "Designer"],
418
+ * ],
419
+ * })
420
+ * ```
421
+ */
422
+ declare function Table(options: TableOptions): TableElement;
383
423
  /**
384
424
  * Create a CardLink element for inline hyperlinks.
385
425
  *
@@ -412,6 +452,11 @@ declare function CardLink(options: {
412
452
  * ```
413
453
  */
414
454
  declare function fromReactElement(element: unknown): AnyCardElement | null;
455
+ /**
456
+ * Generate fallback text from a card child element.
457
+ * Exported so adapter card converters can call it for unknown types.
458
+ */
459
+ declare function cardChildToFallbackText(child: CardChild): string | null;
415
460
 
416
461
  /**
417
462
  * Custom JSX runtime for chat cards.
@@ -590,4 +635,4 @@ declare namespace JSX {
590
635
  }
591
636
  }
592
637
 
593
- export { type ModalOptions as $, Actions as A, Button as B, type CardElement as C, Divider as D, type LinkButtonOptions as E, Field as F, type LinkElement as G, type SectionElement as H, Image as I, type TextElement as J, type TextStyle as K, LinkButton as L, type ModalElement as M, type ButtonProps as N, type CardJSXProps as O, type CardLinkProps as P, type CardProps as Q, RadioSelect as R, Section as S, Text as T, type ContainerProps as U, type DividerProps as V, type FieldProps as W, type ImageProps as X, type LinkButtonProps as Y, type TextProps as Z, type ModalChild as _, type CardJSXElement as a, type RadioSelectElement as a0, type RadioSelectOptions as a1, type SelectElement as a2, type SelectOptionElement as a3, type SelectOptions as a4, type TextInputElement as a5, type TextInputOptions as a6, type ModalProps as a7, type TextInputProps as a8, type SelectProps as a9, type SelectOptionProps as aa, isCardLinkProps as ab, jsx as ac, jsxs as ad, jsxDEV as ae, Fragment as af, JSX as ag, type CardChild as b, Card as c, CardLink as d, Fields as e, fromReactElement as f, isJSX as g, toModalElement as h, isCardElement as i, fromReactModalElement as j, isModalElement as k, Modal as l, Select as m, SelectOption as n, TextInput as o, type ActionsElement as p, type ButtonElement as q, type ButtonOptions as r, type ButtonStyle as s, toCardElement as t, type CardOptions as u, type DividerElement as v, type FieldElement as w, type FieldsElement as x, type ImageElement as y, type LinkButtonElement as z };
638
+ export { type FieldProps as $, Actions as A, Button as B, type CardElement as C, Divider as D, type ImageElement as E, Field as F, type LinkButtonElement as G, type LinkButtonOptions as H, Image as I, type LinkElement as J, type SectionElement as K, LinkButton as L, type ModalElement as M, type TableAlignment as N, type TableElement as O, type TableOptions as P, type TextElement as Q, RadioSelect as R, Section as S, Text as T, type TextStyle as U, type ButtonProps as V, type CardJSXProps as W, type CardLinkProps as X, type CardProps as Y, type ContainerProps as Z, type DividerProps as _, type CardJSXElement as a, type ImageProps as a0, type LinkButtonProps as a1, type TextProps as a2, type ModalChild as a3, type ModalOptions as a4, type RadioSelectElement as a5, type RadioSelectOptions as a6, type SelectElement as a7, type SelectOptionElement as a8, type SelectOptions as a9, type TextInputElement as aa, type TextInputOptions as ab, type ModalProps as ac, type TextInputProps as ad, type SelectProps as ae, type SelectOptionProps as af, isCardLinkProps as ag, jsx as ah, jsxs as ai, jsxDEV as aj, Fragment as ak, JSX as al, type CardChild as b, Card as c, cardChildToFallbackText as d, CardLink as e, Fields as f, fromReactElement as g, isJSX as h, isCardElement as i, Table as j, toModalElement as k, fromReactModalElement as l, isModalElement as m, Modal as n, Select as o, SelectOption as p, TextInput as q, type ActionsElement as r, type ButtonElement as s, toCardElement as t, type ButtonOptions as u, type ButtonStyle as v, type CardOptions as w, type DividerElement as x, type FieldElement as y, type FieldsElement as z };
@@ -1 +1 @@
1
- export { N as ButtonProps, a as CardJSXElement, O as CardJSXProps, P as CardLinkProps, Q as CardProps, U as ContainerProps, V as DividerProps, W as FieldProps, af as Fragment, X as ImageProps, ag as JSX, Y as LinkButtonProps, a7 as ModalProps, aa as SelectOptionProps, a9 as SelectProps, a8 as TextInputProps, Z as TextProps, ab as isCardLinkProps, g as isJSX, ac as jsx, ae as jsxDEV, ad as jsxs, t as toCardElement, h as toModalElement } from './jsx-runtime-Wowykq7Z.js';
1
+ export { V as ButtonProps, a as CardJSXElement, W as CardJSXProps, X as CardLinkProps, Y as CardProps, Z as ContainerProps, _ as DividerProps, $ as FieldProps, ak as Fragment, a0 as ImageProps, al as JSX, a1 as LinkButtonProps, ac as ModalProps, af as SelectOptionProps, ae as SelectProps, ad as TextInputProps, a2 as TextProps, ag as isCardLinkProps, h as isJSX, ah as jsx, aj as jsxDEV, ai as jsxs, t as toCardElement, k as toModalElement } from './jsx-runtime-Bokk9xw5.js';
@@ -7,7 +7,7 @@ import {
7
7
  jsxs,
8
8
  toCardElement,
9
9
  toModalElement
10
- } from "./chunk-VGF42GJ2.js";
10
+ } from "./chunk-A2J5CUHD.js";
11
11
  export {
12
12
  Fragment,
13
13
  isCardLinkProps,
package/docs/actions.mdx CHANGED
@@ -46,7 +46,7 @@ The `event` object passed to action handlers:
46
46
  | `actionId` | `string` | The `id` from the Button or Select component |
47
47
  | `value` | `string` (optional) | The `value` from the Button or selected option |
48
48
  | `user` | `Author` | The user who clicked |
49
- | `thread` | `Thread` | The thread containing the card |
49
+ | `thread` | `Thread \| null` | The thread containing the card (null for view-based actions like home tab buttons) |
50
50
  | `messageId` | `string` | The message containing the card |
51
51
  | `threadId` | `string` | Thread ID |
52
52
  | `adapter` | `Adapter` | The platform adapter |
@@ -174,6 +174,7 @@ CRON_SECRET=your-random-secret # For Gateway cron
174
174
 
175
175
  | Feature | Supported |
176
176
  |---------|-----------|
177
+ | Slash Commands | Yes |
177
178
  | Mentions | Yes |
178
179
  | Reactions (add/remove) | Yes |
179
180
  | Cards (Embeds) | Yes |
@@ -28,6 +28,7 @@ Adapters handle webhook verification, message parsing, and API calls for each pl
28
28
  | Buttons | ✅ | ✅ | ✅ | ✅ | ⚠️ Inline keyboard callbacks | ❌ | ❌ |
29
29
  | Link buttons | ✅ | ✅ | ✅ | ✅ | ⚠️ Inline keyboard URLs | ❌ | ❌ |
30
30
  | Select menus | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
31
+ | Tables | ✅ Block Kit | ✅ GFM | ⚠️ ASCII | ✅ GFM | ⚠️ ASCII | ✅ GFM | ✅ GFM |
31
32
  | Fields | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
32
33
  | Images in cards | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ |
33
34
  | Modals | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
@@ -36,6 +37,7 @@ Adapters handle webhook verification, message parsing, and API calls for each pl
36
37
 
37
38
  | Feature | Slack | Teams | Google Chat | Discord | Telegram | GitHub | Linear |
38
39
  |---------|-------|-------|-------------|---------|----------|--------|--------|
40
+ | Slash commands | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
39
41
  | Mentions | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
40
42
  | Add reactions | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
41
43
  | Remove reactions | ✅ | ❌ | ✅ | ✅ | ✅ | ⚠️ | ⚠️ |
@@ -71,6 +73,20 @@ Adapters handle webhook verification, message parsing, and API calls for each pl
71
73
  | [GitHub](/docs/adapters/github) | `@chat-adapter/github` |
72
74
  | [Linear](/docs/adapters/linear) | `@chat-adapter/linear` |
73
75
 
76
+ ## Community adapters
77
+
78
+ Beyond the Vercel-maintained adapters listed above, you can build adapters for any messaging platform. Community adapters use the same `Adapter` interface and get full access to Chat SDK features like cards, actions, streaming, and state management.
79
+
80
+ | Tier | Description |
81
+ |------|-------------|
82
+ | Vercel-maintained | Published under `@chat-adapter/*` by Vercel |
83
+ | Community official | Built and maintained by the platform company itself (e.g., Resend building a Resend adapter) |
84
+ | Community | Built by third-party developers |
85
+
86
+ Community adapters are expected to meet the same testing bar as first-party adapters — unit tests for all public methods, integration tests for end-to-end message flows.
87
+
88
+ Ready to build one? Follow the [building](/docs/contributing/building) guide.
89
+
74
90
  ## How adapters work
75
91
 
76
92
  Each adapter implements a standard interface that the `Chat` class uses to route events and send messages. When a webhook arrives:
@@ -139,6 +139,7 @@ settings:
139
139
  request_url: https://your-domain.com/api/webhooks/slack
140
140
  bot_events:
141
141
  - app_mention
142
+ - member_joined_channel
142
143
  - message.channels
143
144
  - message.groups
144
145
  - message.im
@@ -187,6 +188,7 @@ All options are auto-detected from environment variables when not provided. You
187
188
  | `clientId` | No | App client ID for multi-workspace OAuth. Auto-detected from `SLACK_CLIENT_ID` |
188
189
  | `clientSecret` | No | App client secret for multi-workspace OAuth. Auto-detected from `SLACK_CLIENT_SECRET` |
189
190
  | `encryptionKey` | No | AES-256-GCM key for encrypting stored tokens. Auto-detected from `SLACK_ENCRYPTION_KEY` |
191
+ | `installationKeyPrefix` | No | Prefix for the state key used to store workspace installations. Defaults to `slack:installation`. The full key is `{prefix}:{teamId}` |
190
192
  | `logger` | No | Logger instance (defaults to `ConsoleLogger("info")`) |
191
193
 
192
194
  *`signingSecret` is required — either via config or `SLACK_SIGNING_SECRET` env var.
@@ -218,6 +220,7 @@ SLACK_ENCRYPTION_KEY=... # Optional, for token encryption
218
220
  | Message history | Yes |
219
221
  | Assistants API | Yes |
220
222
  | App Home tab | Yes |
223
+ | Member joined channel | Yes |
221
224
 
222
225
  ## Slack Assistants API
223
226
 
@@ -53,6 +53,60 @@ curl -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/setWebhook" \
53
53
  }'
54
54
  ```
55
55
 
56
+ ## Polling (local development)
57
+
58
+ When developing locally you typically can't expose a public URL for Telegram to deliver webhooks to. Polling mode uses `getUpdates` to fetch messages directly from Telegram instead — no public endpoint needed.
59
+
60
+ The `longPolling` option is entirely optional. Sensible defaults are applied when omitted.
61
+
62
+ ```typescript title="lib/bot.ts" lineNumbers
63
+ import { Chat } from "chat";
64
+ import { createTelegramAdapter } from "@chat-adapter/telegram";
65
+ import { createMemoryState } from "@chat-adapter/state-memory";
66
+
67
+ const telegram = createTelegramAdapter({
68
+ mode: "polling",
69
+ // Optional — fine-tune polling behavior:
70
+ // longPolling: { timeout: 30, dropPendingUpdates: false },
71
+ });
72
+
73
+ const bot = new Chat({
74
+ userName: "mybot",
75
+ adapters: { telegram },
76
+ state: createMemoryState(),
77
+ });
78
+
79
+ // Optional manual lifecycle control:
80
+ // await telegram.resetWebhook();
81
+ // await telegram.startPolling();
82
+ // await telegram.stopPolling();
83
+ ```
84
+
85
+ ### Auto mode
86
+
87
+ With `mode: "auto"` (the default), the adapter picks the right strategy for you. When deployed to a serverless environment like Vercel it uses webhooks; everywhere else (e.g. local dev) it falls back to polling automatically.
88
+
89
+ ```typescript title="lib/bot.ts" lineNumbers
90
+ import { Chat } from "chat";
91
+ import { createTelegramAdapter } from "@chat-adapter/telegram";
92
+ import { createMemoryState } from "@chat-adapter/state-memory";
93
+
94
+ const telegram = createTelegramAdapter({
95
+ mode: "auto", // default
96
+ });
97
+
98
+ export const bot = new Chat({
99
+ userName: "mybot",
100
+ adapters: { telegram },
101
+ state: createMemoryState(),
102
+ });
103
+
104
+ // Call initialize() so polling can start in long-running local processes:
105
+ void bot.initialize();
106
+
107
+ console.log(telegram.runtimeMode); // "webhook" | "polling"
108
+ ```
109
+
56
110
  ## Configuration
57
111
 
58
112
  All options are auto-detected from environment variables when not provided.
@@ -61,6 +115,8 @@ All options are auto-detected from environment variables when not provided.
61
115
  |--------|----------|-------------|
62
116
  | `botToken` | No* | Telegram bot token. Auto-detected from `TELEGRAM_BOT_TOKEN` |
63
117
  | `secretToken` | No | Optional webhook secret token. Auto-detected from `TELEGRAM_WEBHOOK_SECRET_TOKEN` |
118
+ | `mode` | No | Adapter mode: `auto` (default), `webhook`, or `polling` |
119
+ | `longPolling` | No | Optional long polling config for `getUpdates` (`timeout`, `limit`, `allowedUpdates`, `deleteWebhook`, `dropPendingUpdates`, `retryDelayMs`) |
64
120
  | `userName` | No | Bot username used for mention detection. Auto-detected from `TELEGRAM_BOT_USERNAME` or `getMe` |
65
121
  | `apiBaseUrl` | No | Telegram API base URL. Auto-detected from `TELEGRAM_API_BASE_URL` |
66
122
  | `logger` | No | Logger instance (defaults to `ConsoleLogger("info")`) |
@@ -96,6 +152,10 @@ TELEGRAM_API_BASE_URL=https://api.telegram.org
96
152
 
97
153
  - Telegram does not expose full historical message APIs to bots. `fetchMessages` / `fetchChannelMessages` return adapter-cached messages from the current process.
98
154
  - `listThreads` is not available for Telegram chats.
155
+ - Polling and webhooks are mutually exclusive in Telegram.
156
+ - `mode: "polling"` deletes webhook by default before calling `getUpdates`.
157
+ - `mode: "auto"` checks `getWebhookInfo`: if a webhook URL exists it uses webhook mode; if it is empty it falls back to polling on non-serverless runtimes without deleting webhook.
158
+ - If `getWebhookInfo` fails in `mode: "auto"`, the adapter stays in webhook mode (safe fallback).
99
159
  - `Button` and `LinkButton` in card `Actions` render as inline keyboard buttons.
100
160
  - Telegram callback data is limited to 64 bytes. Keep button `id`/`value` payloads short.
101
161
  - Other rich card elements (images/select menus/radios) render as fallback text only.
@@ -7,7 +7,7 @@ type: reference
7
7
  Card components render natively on each platform — Block Kit on Slack, Adaptive Cards on Teams, Embeds on Discord, and Google Chat Cards.
8
8
 
9
9
  ```typescript
10
- import { Card, Text, CardLink, Button, Actions, Section, Fields, Field, Divider, Image, LinkButton } from "chat";
10
+ import { Card, Text, CardLink, Button, Actions, Section, Fields, Field, Divider, Image, LinkButton, Table } from "chat";
11
11
  ```
12
12
 
13
13
  All components support both function-call and JSX syntax. Function-call syntax is recommended for better type inference.
@@ -216,6 +216,39 @@ Image({ url: "https://example.com/screenshot.png", alt: "Screenshot" })
216
216
  }}
217
217
  />
218
218
 
219
+ ## Table
220
+
221
+ Structured data display with column headers and rows.
222
+
223
+ ```typescript
224
+ Table({
225
+ headers: ["Name", "Age", "Role"],
226
+ rows: [
227
+ ["Alice", "30", "Engineer"],
228
+ ["Bob", "25", "Designer"],
229
+ ],
230
+ })
231
+ ```
232
+
233
+ <TypeTable
234
+ type={{
235
+ headers: {
236
+ description: 'Column header labels.',
237
+ type: 'string[]',
238
+ },
239
+ rows: {
240
+ description: 'Data rows (each row is an array of cell strings).',
241
+ type: 'string[][]',
242
+ },
243
+ align: {
244
+ description: 'Column alignment.',
245
+ type: '"left" | "center" | "right"[]',
246
+ },
247
+ }}
248
+ />
249
+
250
+ On platforms with native table support (Teams, GitHub, Linear), tables render as formatted tables. On other platforms (Slack, Google Chat, Discord, Telegram), tables render as padded ASCII text.
251
+
219
252
  ## Divider
220
253
 
221
254
  A visual separator between sections.
@@ -237,3 +270,4 @@ The `children` array in `Card` and `Section` accepts these element types:
237
270
  | `ActionsElement` | `Actions()` |
238
271
  | `SectionElement` | `Section()` |
239
272
  | `FieldsElement` | `Fields()` |
273
+ | `TableElement` | `Table()` |
@@ -161,10 +161,13 @@ await channel.postEphemeral(userId, "Only you can see this", {
161
161
 
162
162
  ## startTyping
163
163
 
164
- Show a typing indicator. No-op on platforms that don't support it.
164
+ Show a typing indicator. No-op on platforms that don't support it. On Slack, you can pass an optional `status` string to show a custom loading message (requires `assistant:write` scope).
165
165
 
166
166
  ```typescript
167
167
  await channel.startTyping();
168
+
169
+ // With custom status (Slack only)
170
+ await channel.startTyping("Searching documents...");
168
171
  ```
169
172
 
170
173
  ## mentionUser
package/docs/api/chat.mdx CHANGED
@@ -26,6 +26,11 @@ const bot = new Chat(config);
26
26
  description: 'Map of adapter name to adapter instance.',
27
27
  type: 'Record<string, Adapter>',
28
28
  },
29
+ dedupeTtlMs: {
30
+ description: 'TTL for message deduplication entries in milliseconds. Increase if webhook cold starts cause platform retries after the default window.',
31
+ type: 'number',
32
+ default: '300000',
33
+ },
29
34
  state: {
30
35
  description: 'State adapter for subscriptions, locking, and caching.',
31
36
  type: 'StateAdapter',
@@ -11,6 +11,7 @@ import {
11
11
  root, paragraph, text, strong, emphasis, strikethrough,
12
12
  inlineCode, codeBlock, link, blockquote,
13
13
  parseMarkdown, stringifyMarkdown, toPlainText, walkAst,
14
+ tableToAscii, tableElementToAscii,
14
15
  } from "chat";
15
16
  ```
16
17
 
@@ -204,6 +205,9 @@ Functions for checking node types:
204
205
  | `isBlockquoteNode(node)` | Blockquote |
205
206
  | `isListNode(node)` | List |
206
207
  | `isListItemNode(node)` | List item |
208
+ | `isTableNode(node)` | Table |
209
+ | `isTableRowNode(node)` | Table row |
210
+ | `isTableCellNode(node)` | Table cell |
207
211
 
208
212
  ### getNodeChildren / getNodeValue
209
213
 
@@ -214,6 +218,43 @@ const children = getNodeChildren(node); // Content[] | undefined
214
218
  const value = getNodeValue(node); // string | undefined
215
219
  ```
216
220
 
221
+ ## Table utilities
222
+
223
+ ### tableToAscii
224
+
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).
226
+
227
+ ```typescript
228
+ import { parseMarkdown, tableToAscii, isTableNode } from "chat";
229
+
230
+ const ast = parseMarkdown("| Name | Role |\n|------|------|\n| Alice | Engineer |");
231
+ // Find the table node and convert it
232
+ ```
233
+
234
+ Output:
235
+
236
+ ```
237
+ Name | Role
238
+ ------|--------
239
+ Alice | Engineer
240
+ ```
241
+
242
+ ### tableElementToAscii
243
+
244
+ Render a table from headers and string row arrays as a padded ASCII table. Used for card `TableElement` fallback rendering.
245
+
246
+ ```typescript
247
+ import { tableElementToAscii } from "chat";
248
+
249
+ const ascii = tableElementToAscii(
250
+ ["Name", "Age", "Role"],
251
+ [
252
+ ["Alice", "30", "Engineer"],
253
+ ["Bob", "25", "Designer"],
254
+ ]
255
+ );
256
+ ```
257
+
217
258
  ## Platform formatting
218
259
 
219
260
  The SDK uses mdast as the canonical format and each adapter converts it to the platform's native syntax. You write standard markdown and the SDK handles the translation — but it helps to know how each platform renders common formatting.
@@ -228,6 +269,7 @@ The SDK uses mdast as the canonical format and each adapter converts it to the p
228
269
  | Links | `<url\|text>` | `[text](url)` | `[text](url)` |
229
270
  | Lists | Supported | Supported | Supported |
230
271
  | Blockquotes | `>` | `>` | Simulated with `>` prefix |
272
+ | Tables | ASCII fallback | Native GFM | ASCII fallback |
231
273
  | Mentions | `<@USER>` | `<at>name</at>` | `<users/{id}>` |
232
274
 
233
275
  <Callout type="info">
@@ -7,7 +7,7 @@ type: reference
7
7
  `PostableMessage` is the union of all message formats accepted by `thread.post()` and `sent.edit()`.
8
8
 
9
9
  ```typescript
10
- type PostableMessage = AdapterPostableMessage | AsyncIterable<string>;
10
+ type PostableMessage = AdapterPostableMessage | AsyncIterable<string | StreamChunk | StreamEvent>;
11
11
  ```
12
12
 
13
13
  ## String
@@ -135,15 +135,23 @@ await thread.post({
135
135
 
136
136
  ## AsyncIterable (streaming)
137
137
 
138
- An async iterable of strings, like the AI SDK's `textStream`. The SDK streams the message in real time using platform-native APIs where available.
138
+ An async iterable of strings, `StreamChunk` objects, or stream events. The SDK streams the message in real time using platform-native APIs where available.
139
+
140
+ You can yield structured `StreamChunk` objects for rich content like task progress cards on platforms that support it (Slack). See [Streaming](/docs/streaming#structured-streaming-chunks-slack-only) for details.
141
+
142
+ Both AI SDK stream types are supported:
139
143
 
140
144
  ```typescript
141
- import { streamText } from "ai";
145
+ // fullStream (recommended) preserves step boundaries in multi-step agents
146
+ const result = await agent.stream({ prompt: message.text });
147
+ await thread.post(result.fullStream);
142
148
 
143
- const result = streamText({ model, prompt: message.text });
149
+ // textStream plain string chunks
144
150
  await thread.post(result.textStream);
145
151
  ```
146
152
 
153
+ When using `fullStream`, the SDK auto-detects `text-delta` and `step-finish` events, extracting text and inserting paragraph breaks between agent steps.
154
+
147
155
  ## FileUpload
148
156
 
149
157
  Used in the `files` field of any structured message format.
@@ -54,8 +54,8 @@ await thread.post({ ast: root([paragraph([text("Hello")])]) });
54
54
  // Card
55
55
  await thread.post(Card({ title: "Hi", children: [Text("Hello")] }));
56
56
 
57
- // Stream
58
- await thread.post(result.textStream);
57
+ // Stream (fullStream recommended for multi-step agents)
58
+ await thread.post(result.fullStream);
59
59
  ```
60
60
 
61
61
  **Parameters:** `message: string | PostableMessage | CardJSXElement`
@@ -122,10 +122,13 @@ await thread.setState({ aiMode: false }, { replace: true });
122
122
 
123
123
  ## startTyping
124
124
 
125
- Show a typing indicator in the thread. No-op on platforms that don't support it.
125
+ Show a typing indicator in the thread. No-op on platforms that don't support it. On Slack, you can pass an optional `status` string to show a custom loading message (requires `assistant:write` scope).
126
126
 
127
127
  ```typescript
128
128
  await thread.startTyping();
129
+
130
+ // With custom status (Slack only)
131
+ await thread.startTyping("Searching documents...");
129
132
  ```
130
133
 
131
134
  ## messages / allMessages
package/docs/cards.mdx CHANGED
@@ -177,6 +177,30 @@ Radio button group for mutually exclusive choices.
177
177
  </Actions>
178
178
  ```
179
179
 
180
+ ### Table
181
+
182
+ Structured data display with column headers and rows. Renders as a native table on platforms that support it (Teams, GitHub, Linear) and as padded ASCII text elsewhere.
183
+
184
+ ```tsx title="lib/bot.tsx" lineNumbers
185
+ <Table
186
+ headers={["Name", "Age", "Role"]}
187
+ rows={[
188
+ ["Alice", "30", "Engineer"],
189
+ ["Bob", "25", "Designer"],
190
+ ]}
191
+ />
192
+ ```
193
+
194
+ Optional column alignment:
195
+
196
+ ```tsx title="lib/bot.tsx"
197
+ <Table
198
+ headers={["Name", "Amount"]}
199
+ rows={[["Alice", "$100"], ["Bob", "$200"]]}
200
+ align={["left", "right"]}
201
+ />
202
+ ```
203
+
180
204
  ### Image
181
205
 
182
206
  Embeds an image in the card.