chat 4.25.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.
Files changed (37) hide show
  1. package/dist/{chunk-OPV5U4WG.js → chunk-AN7MRAVW.js} +39 -0
  2. package/dist/index.d.ts +235 -6
  3. package/dist/index.js +353 -76
  4. package/dist/{jsx-runtime-DxATbnrP.d.ts → jsx-runtime-Co9uV6l7.d.ts} +39 -5
  5. package/dist/jsx-runtime.d.ts +1 -1
  6. package/dist/jsx-runtime.js +1 -1
  7. package/docs/adapters.mdx +30 -30
  8. package/docs/api/cards.mdx +5 -0
  9. package/docs/api/chat.mdx +95 -1
  10. package/docs/api/message.mdx +5 -1
  11. package/docs/api/modals.mdx +1 -1
  12. package/docs/api/thread.mdx +23 -1
  13. package/docs/cards.mdx +6 -0
  14. package/docs/contributing/publishing.mdx +33 -0
  15. package/docs/files.mdx +1 -0
  16. package/docs/getting-started.mdx +2 -12
  17. package/docs/meta.json +0 -2
  18. package/docs/modals.mdx +74 -2
  19. package/docs/state.mdx +1 -1
  20. package/docs/streaming.mdx +13 -5
  21. package/docs/threads-messages-channels.mdx +34 -0
  22. package/docs/usage.mdx +2 -2
  23. package/package.json +3 -2
  24. package/resources/guides/create-a-discord-support-bot-with-nuxt-and-redis.md +180 -0
  25. package/resources/guides/how-to-build-a-slack-bot-with-next-js-and-redis.md +134 -0
  26. package/resources/guides/how-to-build-an-ai-agent-for-slack-with-chat-sdk-and-ai-sdk.md +220 -0
  27. package/resources/guides/run-and-track-deploys-from-slack.md +270 -0
  28. package/resources/guides/ship-a-github-code-review-bot-with-hono-and-redis.md +147 -0
  29. package/resources/guides/triage-form-submissions-with-chat-sdk.md +178 -0
  30. package/resources/templates.json +19 -0
  31. package/docs/adapters/whatsapp.mdx +0 -222
  32. package/docs/guides/code-review-hono.mdx +0 -241
  33. package/docs/guides/discord-nuxt.mdx +0 -227
  34. package/docs/guides/durable-chat-sessions-nextjs.mdx +0 -331
  35. package/docs/guides/meta.json +0 -10
  36. package/docs/guides/scheduled-posts-neon.mdx +0 -447
  37. package/docs/guides/slack-nextjs.mdx +0 -234
@@ -371,7 +371,7 @@ declare function cardChildToFallbackText(child: CardChild): string | null;
371
371
  * Modal elements for form dialogs.
372
372
  */
373
373
 
374
- type ModalChild = TextInputElement | SelectElement | RadioSelectElement | TextElement | FieldsElement;
374
+ type ModalChild = TextInputElement | SelectElement | ExternalSelectElement | RadioSelectElement | TextElement | FieldsElement;
375
375
  interface ModalElement {
376
376
  callbackId: string;
377
377
  children: ModalChild[];
@@ -402,6 +402,15 @@ interface SelectElement {
402
402
  placeholder?: string;
403
403
  type: "select";
404
404
  }
405
+ interface ExternalSelectElement {
406
+ id: string;
407
+ initialOption?: SelectOptionElement;
408
+ label: string;
409
+ minQueryLength?: number;
410
+ optional?: boolean;
411
+ placeholder?: string;
412
+ type: "external_select";
413
+ }
405
414
  interface SelectOptionElement {
406
415
  description?: string;
407
416
  label: string;
@@ -446,6 +455,15 @@ interface SelectOptions {
446
455
  placeholder?: string;
447
456
  }
448
457
  declare function Select(options: SelectOptions): SelectElement;
458
+ interface ExternalSelectOptions {
459
+ id: string;
460
+ initialOption?: SelectOptionElement;
461
+ label: string;
462
+ minQueryLength?: number;
463
+ optional?: boolean;
464
+ placeholder?: string;
465
+ }
466
+ declare function ExternalSelect(options: ExternalSelectOptions): ExternalSelectElement;
449
467
  declare function SelectOption(options: {
450
468
  label: string;
451
469
  value: string;
@@ -574,6 +592,18 @@ interface SelectProps {
574
592
  optional?: boolean;
575
593
  placeholder?: string;
576
594
  }
595
+ /** Props for ExternalSelect component in JSX */
596
+ interface ExternalSelectProps {
597
+ id: string;
598
+ initialOption?: {
599
+ label: string;
600
+ value: string;
601
+ };
602
+ label: string;
603
+ minQueryLength?: number;
604
+ optional?: boolean;
605
+ placeholder?: string;
606
+ }
577
607
  /** Props for SelectOption component in JSX */
578
608
  interface SelectOptionProps {
579
609
  description?: string;
@@ -586,9 +616,9 @@ interface TableProps {
586
616
  rows: string[][];
587
617
  }
588
618
  /** Union of all valid JSX props */
589
- type CardJSXProps = CardProps | TextProps | ButtonProps | LinkButtonProps | CardLinkProps | ImageProps | FieldProps | ContainerProps | DividerProps | ModalProps | TextInputProps | SelectProps | SelectOptionProps | TableProps;
619
+ type CardJSXProps = CardProps | TextProps | ButtonProps | LinkButtonProps | CardLinkProps | ImageProps | FieldProps | ContainerProps | DividerProps | ModalProps | TextInputProps | SelectProps | ExternalSelectProps | SelectOptionProps | TableProps;
590
620
  /** Component function type with proper overloads */
591
- type CardComponentFunction = typeof Card | typeof Text | typeof Button | typeof LinkButton | typeof CardLink | typeof Image | typeof Field | typeof Divider | typeof Section | typeof Actions | typeof Fields | typeof Modal | typeof TextInput | typeof Select | typeof RadioSelect | typeof SelectOption | typeof Table;
621
+ type CardComponentFunction = typeof Card | typeof Text | typeof Button | typeof LinkButton | typeof CardLink | typeof Image | typeof Field | typeof Divider | typeof Section | typeof Actions | typeof Fields | typeof Modal | typeof TextInput | typeof Select | typeof ExternalSelect | typeof RadioSelect | typeof SelectOption | typeof Table;
592
622
  /**
593
623
  * Represents a JSX element from the chat JSX runtime.
594
624
  * This is the type returned when using JSX syntax with chat components.
@@ -600,7 +630,7 @@ interface CardJSXElement<P extends CardJSXProps = CardJSXProps> {
600
630
  type: CardComponentFunction;
601
631
  }
602
632
  /** Union of all element types that can be produced by chat components */
603
- type ChatElement = CardJSXElement | CardElement | TextElement | ButtonElement | LinkButtonElement | LinkElement | ImageElement | DividerElement | ActionsElement | SectionElement | FieldsElement | FieldElement | ModalElement | TextInputElement | SelectElement | SelectOptionElement | RadioSelectElement | TableElement;
633
+ type ChatElement = CardJSXElement | CardElement | TextElement | ButtonElement | LinkButtonElement | LinkElement | ImageElement | DividerElement | ActionsElement | SectionElement | FieldsElement | FieldElement | ModalElement | TextInputElement | SelectElement | ExternalSelectElement | SelectOptionElement | RadioSelectElement | TableElement;
604
634
  interface CardComponent {
605
635
  (options?: CardOptions): CardElement;
606
636
  (props: CardProps): ChatElement;
@@ -668,6 +698,10 @@ interface SelectComponent {
668
698
  (options: SelectOptions): SelectElement;
669
699
  (props: SelectProps): ChatElement;
670
700
  }
701
+ interface ExternalSelectComponent {
702
+ (options: ExternalSelectOptions): ExternalSelectElement;
703
+ (props: ExternalSelectProps): ChatElement;
704
+ }
671
705
  interface SelectOptionComponent {
672
706
  (options: {
673
707
  label: string;
@@ -735,4 +769,4 @@ declare namespace JSX {
735
769
  }
736
770
  }
737
771
 
738
- export { type DividerProps as $, type ActionsComponent as A, type ButtonComponent as B, type ChatElement as C, type DividerComponent as D, type ImageElement as E, type FieldComponent as F, type LinkButtonElement as G, type LinkButtonOptions as H, type ImageComponent as I, type LinkElement as J, type SectionElement as K, type LinkButtonComponent as L, type ModalElement as M, type TableAlignment as N, type TableElement as O, type TableOptions as P, type TextElement as Q, type RadioSelectComponent as R, type SectionComponent as S, type TextComponent as T, type TextStyle as U, type ButtonProps as V, type CardJSXElement as W, type CardJSXProps as X, type CardLinkProps as Y, type CardProps as Z, type ContainerProps as _, type CardElement as a, type FieldProps as a0, type ImageProps as a1, type LinkButtonProps as a2, type ModalProps as a3, type SelectOptionProps as a4, type SelectProps as a5, type TextInputProps as a6, type TextProps as a7, type ModalChild as a8, type ModalOptions as a9, type RadioSelectElement as aa, type RadioSelectOptions as ab, type SelectElement as ac, type SelectOptionElement as ad, type SelectOptions as ae, type TextInputElement as af, type TextInputOptions as ag, type TableProps as ah, type TableComponent as ai, isCardLinkProps as aj, jsx as ak, jsxs as al, jsxDEV as am, Fragment as an, JSX as ao, type CardChild as b, type CardComponent as c, cardChildToFallbackText as d, type CardLinkComponent as e, type FieldsComponent as f, fromReactElement as g, isJSX as h, isCardElement as i, Table as j, toModalElement as k, fromReactModalElement as l, isModalElement as m, type ModalComponent as n, type SelectComponent as o, type SelectOptionComponent as p, type TextInputComponent 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 };
772
+ export { type CardProps as $, type ActionsComponent as A, type ButtonComponent as B, type ChatElement as C, type DividerComponent as D, type ExternalSelectComponent as E, type FieldComponent as F, type FieldsElement as G, type ImageElement as H, type ImageComponent as I, type LinkButtonElement as J, type LinkButtonOptions as K, type LinkButtonComponent as L, type ModalElement as M, type LinkElement as N, type SectionElement as O, type TableAlignment as P, type TableElement as Q, type RadioSelectComponent as R, type SelectOptionElement as S, type TextComponent as T, type TableOptions as U, type TextElement as V, type TextStyle as W, type ButtonProps as X, type CardJSXElement as Y, type CardJSXProps as Z, type CardLinkProps as _, type CardElement as a, type ContainerProps as a0, type DividerProps as a1, type ExternalSelectProps as a2, type FieldProps as a3, type ImageProps as a4, type LinkButtonProps as a5, type ModalProps as a6, type SelectOptionProps as a7, type SelectProps as a8, type TextInputProps as a9, type TextProps as aa, type ExternalSelectElement as ab, type ExternalSelectOptions as ac, type ModalChild as ad, type ModalOptions as ae, type RadioSelectElement as af, type RadioSelectOptions as ag, type SelectElement as ah, type SelectOptions as ai, type TextInputElement as aj, type TextInputOptions as ak, type TableProps as al, type TableComponent as am, isCardLinkProps as an, jsx as ao, jsxs as ap, jsxDEV as aq, Fragment as ar, JSX as as, type CardChild as b, type CardComponent as c, cardChildToFallbackText as d, type CardLinkComponent as e, type FieldsComponent as f, fromReactElement as g, isJSX as h, isCardElement as i, type SectionComponent as j, Table as k, toModalElement as l, fromReactModalElement as m, isModalElement as n, type ModalComponent as o, type SelectComponent as p, type SelectOptionComponent as q, type TextInputComponent as r, type ActionsElement as s, toCardElement as t, type ButtonElement as u, type ButtonOptions as v, type ButtonStyle as w, type CardOptions as x, type DividerElement as y, type FieldElement as z };
@@ -1 +1 @@
1
- export { A as ActionsComponent, B as ButtonComponent, V as ButtonProps, c as CardComponent, W as CardJSXElement, X as CardJSXProps, e as CardLinkComponent, Y as CardLinkProps, Z as CardProps, C as ChatElement, _ as ContainerProps, D as DividerComponent, $ as DividerProps, F as FieldComponent, a0 as FieldProps, f as FieldsComponent, an as Fragment, I as ImageComponent, a1 as ImageProps, ao as JSX, L as LinkButtonComponent, a2 as LinkButtonProps, n as ModalComponent, a3 as ModalProps, R as RadioSelectComponent, S as SectionComponent, o as SelectComponent, p as SelectOptionComponent, a4 as SelectOptionProps, a5 as SelectProps, ai as TableComponent, ah as TableProps, T as TextComponent, q as TextInputComponent, a6 as TextInputProps, a7 as TextProps, aj as isCardLinkProps, h as isJSX, ak as jsx, am as jsxDEV, al as jsxs, t as toCardElement, k as toModalElement } from './jsx-runtime-DxATbnrP.js';
1
+ export { A as ActionsComponent, B as ButtonComponent, X as ButtonProps, c as CardComponent, Y as CardJSXElement, Z as CardJSXProps, e as CardLinkComponent, _ as CardLinkProps, $ as CardProps, C as ChatElement, a0 as ContainerProps, D as DividerComponent, a1 as DividerProps, E as ExternalSelectComponent, a2 as ExternalSelectProps, F as FieldComponent, a3 as FieldProps, f as FieldsComponent, ar as Fragment, I as ImageComponent, a4 as ImageProps, as as JSX, L as LinkButtonComponent, a5 as LinkButtonProps, o as ModalComponent, a6 as ModalProps, R as RadioSelectComponent, j as SectionComponent, p as SelectComponent, q as SelectOptionComponent, a7 as SelectOptionProps, a8 as SelectProps, am as TableComponent, al as TableProps, T as TextComponent, r as TextInputComponent, a9 as TextInputProps, aa as TextProps, an as isCardLinkProps, h as isJSX, ao as jsx, aq as jsxDEV, ap as jsxs, t as toCardElement, l as toModalElement } from './jsx-runtime-Co9uV6l7.js';
@@ -7,7 +7,7 @@ import {
7
7
  jsxs,
8
8
  toCardElement,
9
9
  toModalElement
10
- } from "./chunk-OPV5U4WG.js";
10
+ } from "./chunk-AN7MRAVW.js";
11
11
  export {
12
12
  Fragment,
13
13
  isCardLinkProps,
package/docs/adapters.mdx CHANGED
@@ -16,55 +16,55 @@ 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 | | | | | ⚠️ Single file | | | Images, audio, docs |
23
- | Streaming | Native | ⚠️ Post+Edit | ⚠️ Post+Edit | ⚠️ Post+Edit | ⚠️ Post+Edit | | | |
24
- | Scheduled messages | Native | | | | | | | |
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 | | | | | ⚠️ Inline keyboard callbacks | | | Interactive replies |
32
- | Link buttons | | | | | ⚠️ Inline keyboard URLs | | | |
33
- | Select menus | | | | | | | | |
34
- | Tables | Block Kit | GFM | ⚠️ ASCII | GFM | ⚠️ ASCII | GFM | GFM | |
35
- | Fields | | | | | | | | ⚠️ Template variables |
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 | Native | | Native | | | | | |
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 | | | | | ⚠️ Cached | | | ⚠️ Cached sent messages only |
56
- | Fetch single message | | | | | ⚠️ Cached | | | ⚠️ Cached sent messages only |
57
- | Fetch thread info | | | | | | | | |
58
- | Fetch channel messages | | | | | ⚠️ Cached | | | ⚠️ Cached sent messages only |
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
- ⚠️ indicates partial support — the feature works with limitations. See individual adapter pages for details.
64
+ <Warn /> indicates partial support — the feature works with limitations. See individual adapter pages for details.
65
65
  </Callout>
66
66
 
67
- ## How [adapters](/adapters) work
67
+ ## How adapters work
68
68
 
69
69
  Each adapter implements a standard interface that the `Chat` class uses to route events and send messages. When a webhook arrives:
70
70
 
@@ -73,7 +73,7 @@ Each adapter implements a standard interface that the `Chat` class uses to route
73
73
  3. Routes to your handlers via the `Chat` class
74
74
  4. Converts outgoing messages from markdown/AST/cards to the platform's native format
75
75
 
76
- ## Using multiple [adapters](/adapters)
76
+ ## Using multiple adapters
77
77
 
78
78
  Register multiple [adapters](/adapters) and your event handlers work across all of them:
79
79
 
@@ -95,6 +95,11 @@ Button({ id: "delete", label: "Delete", style: "danger", value: "item-123" })
95
95
  description: 'Optional payload sent with the action callback.',
96
96
  type: 'string',
97
97
  },
98
+ actionType: {
99
+ description: 'Hints to adapters like Teams that this button will open a modal via event.openModal().',
100
+ type: '"action" | "modal"',
101
+ default: '"action"',
102
+ },
98
103
  }}
99
104
  />
100
105
 
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 modal
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.
@@ -475,3 +559,13 @@ Get a `JSON.parse` reviver that deserializes `Thread` and `Message` objects from
475
559
  const data = JSON.parse(payload, bot.reviver());
476
560
  await data.thread.post("Hello from workflow!");
477
561
  ```
562
+
563
+ There is also a standalone `reviver` export that works without a `Chat` instance. This is useful in Vercel Workflow functions where importing the full Chat instance (with its adapter dependencies) is not possible:
564
+
565
+ ```typescript
566
+ import { reviver } from "chat";
567
+
568
+ const data = JSON.parse(payload, reviver) as { thread: Thread; message: Message };
569
+ ```
570
+
571
+ The standalone reviver uses lazy adapter resolution - the adapter is looked up from the Chat singleton when first accessed. Call `chat.registerSingleton()` before using thread methods like `post()` (typically inside a `"use step"` function).
@@ -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.
@@ -4,7 +4,7 @@ description: Modal form components for collecting user input.
4
4
  type: reference
5
5
  ---
6
6
 
7
- Modals display form dialogs that collect structured user input. Currently supported on Slack.
7
+ Modals display form dialogs that collect structured user input. Currently supported on Slack and Teams.
8
8
 
9
9
  ```typescript
10
10
  import { Modal, TextInput, Select, RadioSelect, SelectOption } from "chat";
@@ -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 don't create threads directly they come from handler callbacks or `chat.openDM()`.
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.
package/docs/cards.mdx CHANGED
@@ -109,6 +109,12 @@ The `id` maps to your `onAction` handler. Optional `value` passes extra data:
109
109
  <Button id="report" value="bug">Report Bug</Button>
110
110
  ```
111
111
 
112
+ Set `actionType="modal"` to indicate the button opens a [modal](/docs/modals). The button still triggers your `onAction` handler, where you call `event.openModal()` — this prop tells adapters like Teams to wire up the button for dialog opening:
113
+
114
+ ```tsx title="lib/bot.tsx"
115
+ <Button id="open-feedback" actionType="modal">Give Feedback</Button>
116
+ ```
117
+
112
118
  ### CardLink
113
119
 
114
120
  Inline hyperlink rendered as text. Unlike `LinkButton` (which must be inside `Actions`), `CardLink` can be placed directly in a card alongside other content.
@@ -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 |
@@ -14,7 +14,7 @@ Learn the core patterns for handling incoming events and posting messages back t
14
14
  <Card title="Posting Messages" description="Different ways to render and send messages with thread.post()." href="/docs/posting-messages" />
15
15
  </Cards>
16
16
 
17
- ## [Adapters](/adapters)
17
+ ## Adapters
18
18
 
19
19
  Connect your bot to chat platforms and persist state across restarts.
20
20
 
@@ -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
- ## Guides
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
@@ -15,8 +15,6 @@
15
15
  "concurrency",
16
16
  "...",
17
17
  "error-handling",
18
- "---Guides---",
19
- "...guides",
20
18
  "---API Reference---",
21
19
  "...api",
22
20
  "---Contributing---",
package/docs/modals.mdx CHANGED
@@ -6,7 +6,7 @@ prerequisites:
6
6
  - /docs/actions
7
7
  ---
8
8
 
9
- Modals open form dialogs in response to button clicks or [slash commands](/docs/slash-commands). They support text inputs, dropdowns, radio buttons, and server-side validation. Currently supported on Slack.
9
+ Modals open form dialogs in response to button clicks or [slash commands](/docs/slash-commands). They support text inputs, dropdowns, radio buttons, and server-side validation. Currently supported on Slack and Teams.
10
10
 
11
11
  ## Open a modal
12
12
 
@@ -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 modal |
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/state.mdx CHANGED
@@ -8,7 +8,7 @@ prerequisites:
8
8
 
9
9
  State adapters handle persistent storage for thread subscriptions, distributed locks (to prevent duplicate processing), and caching. You must provide a state adapter when creating a `Chat` instance. Browse all available state adapters on the [Adapters](/adapters) page.
10
10
 
11
- ## What state [adapters](/adapters) manage
11
+ ## What state adapters manage
12
12
 
13
13
  ### Thread subscriptions
14
14