chat 4.26.0 → 4.28.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.
Files changed (50) hide show
  1. package/dist/{chunk-OPV5U4WG.js → chunk-V25FKIIL.js} +44 -1
  2. package/dist/index.d.ts +485 -33
  3. package/dist/index.js +862 -135
  4. package/dist/{jsx-runtime-DxATbnrP.d.ts → jsx-runtime-DxGwoLu2.d.ts} +49 -5
  5. package/dist/jsx-runtime.d.ts +1 -1
  6. package/dist/jsx-runtime.js +1 -1
  7. package/docs/actions.mdx +52 -1
  8. package/docs/adapters.mdx +43 -37
  9. package/docs/api/cards.mdx +4 -0
  10. package/docs/api/chat.mdx +172 -6
  11. package/docs/api/index.mdx +2 -0
  12. package/docs/api/markdown.mdx +28 -5
  13. package/docs/api/message.mdx +58 -1
  14. package/docs/api/meta.json +2 -0
  15. package/docs/api/modals.mdx +50 -0
  16. package/docs/api/postable-message.mdx +55 -1
  17. package/docs/api/thread.mdx +33 -3
  18. package/docs/api/transcripts.mdx +220 -0
  19. package/docs/cards.mdx +6 -0
  20. package/docs/concurrency.mdx +4 -0
  21. package/docs/contributing/building.mdx +73 -1
  22. package/docs/contributing/publishing.mdx +33 -0
  23. package/docs/conversation-history.mdx +137 -0
  24. package/docs/direct-messages.mdx +13 -4
  25. package/docs/ephemeral-messages.mdx +1 -1
  26. package/docs/error-handling.mdx +15 -3
  27. package/docs/files.mdx +2 -1
  28. package/docs/getting-started.mdx +1 -11
  29. package/docs/index.mdx +7 -5
  30. package/docs/meta.json +14 -5
  31. package/docs/modals.mdx +97 -1
  32. package/docs/posting-messages.mdx +7 -3
  33. package/docs/streaming.mdx +74 -18
  34. package/docs/subject.mdx +53 -0
  35. package/docs/threads-messages-channels.mdx +43 -0
  36. package/docs/usage.mdx +11 -2
  37. package/package.json +3 -2
  38. package/resources/guides/create-a-discord-support-bot-with-nuxt-and-redis.md +180 -0
  39. package/resources/guides/how-to-build-a-slack-bot-with-next-js-and-redis.md +134 -0
  40. package/resources/guides/how-to-build-an-ai-agent-for-slack-with-chat-sdk-and-ai-sdk.md +220 -0
  41. package/resources/guides/run-and-track-deploys-from-slack.md +270 -0
  42. package/resources/guides/ship-a-github-code-review-bot-with-hono-and-redis.md +147 -0
  43. package/resources/guides/triage-form-submissions-with-chat-sdk.md +178 -0
  44. package/resources/templates.json +19 -0
  45. package/docs/guides/code-review-hono.mdx +0 -241
  46. package/docs/guides/discord-nuxt.mdx +0 -227
  47. package/docs/guides/durable-chat-sessions-nextjs.mdx +0 -337
  48. package/docs/guides/meta.json +0 -10
  49. package/docs/guides/scheduled-posts-neon.mdx +0 -447
  50. package/docs/guides/slack-nextjs.mdx +0 -234
@@ -52,6 +52,8 @@ type TextStyle = "plain" | "bold" | "muted";
52
52
  interface ButtonElement {
53
53
  /** Whether this button triggers a regular action or opens a modal dialog. Default: "action" */
54
54
  actionType?: "action" | "modal";
55
+ /** URL to POST action data to when this button is clicked */
56
+ callbackUrl?: string;
55
57
  /** If true, the button is displayed in an inactive state and doesn't respond to user actions */
56
58
  disabled?: boolean;
57
59
  /** Unique action ID for callback routing */
@@ -241,6 +243,8 @@ declare function Actions(children: (ButtonElement | LinkButtonElement | SelectEl
241
243
  interface ButtonOptions {
242
244
  /** Whether this button triggers a regular action or opens a modal dialog. Default: "action" */
243
245
  actionType?: "action" | "modal";
246
+ /** URL to POST action data to when this button is clicked */
247
+ callbackUrl?: string;
244
248
  /** If true, the button is displayed in an inactive state and doesn't respond to user actions */
245
249
  disabled?: boolean;
246
250
  /** Unique action ID for callback routing */
@@ -371,9 +375,11 @@ declare function cardChildToFallbackText(child: CardChild): string | null;
371
375
  * Modal elements for form dialogs.
372
376
  */
373
377
 
374
- type ModalChild = TextInputElement | SelectElement | RadioSelectElement | TextElement | FieldsElement;
378
+ type ModalChild = TextInputElement | SelectElement | ExternalSelectElement | RadioSelectElement | TextElement | FieldsElement;
375
379
  interface ModalElement {
376
380
  callbackId: string;
381
+ /** URL to POST form values to when this modal is submitted */
382
+ callbackUrl?: string;
377
383
  children: ModalChild[];
378
384
  closeLabel?: string;
379
385
  notifyOnClose?: boolean;
@@ -402,6 +408,15 @@ interface SelectElement {
402
408
  placeholder?: string;
403
409
  type: "select";
404
410
  }
411
+ interface ExternalSelectElement {
412
+ id: string;
413
+ initialOption?: SelectOptionElement;
414
+ label: string;
415
+ minQueryLength?: number;
416
+ optional?: boolean;
417
+ placeholder?: string;
418
+ type: "external_select";
419
+ }
405
420
  interface SelectOptionElement {
406
421
  description?: string;
407
422
  label: string;
@@ -418,6 +433,8 @@ interface RadioSelectElement {
418
433
  declare function isModalElement(value: unknown): value is ModalElement;
419
434
  interface ModalOptions {
420
435
  callbackId: string;
436
+ /** URL to POST form values to when this modal is submitted */
437
+ callbackUrl?: string;
421
438
  children?: ModalChild[];
422
439
  closeLabel?: string;
423
440
  notifyOnClose?: boolean;
@@ -446,6 +463,15 @@ interface SelectOptions {
446
463
  placeholder?: string;
447
464
  }
448
465
  declare function Select(options: SelectOptions): SelectElement;
466
+ interface ExternalSelectOptions {
467
+ id: string;
468
+ initialOption?: SelectOptionElement;
469
+ label: string;
470
+ minQueryLength?: number;
471
+ optional?: boolean;
472
+ placeholder?: string;
473
+ }
474
+ declare function ExternalSelect(options: ExternalSelectOptions): ExternalSelectElement;
449
475
  declare function SelectOption(options: {
450
476
  label: string;
451
477
  value: string;
@@ -509,6 +535,7 @@ interface TextProps {
509
535
  /** Props for Button component in JSX */
510
536
  interface ButtonProps {
511
537
  actionType?: "action" | "modal";
538
+ callbackUrl?: string;
512
539
  children?: string | number | (string | number | undefined)[];
513
540
  disabled?: boolean;
514
541
  id: string;
@@ -548,6 +575,7 @@ type DividerProps = Record<string, never>;
548
575
  /** Props for Modal component in JSX */
549
576
  interface ModalProps {
550
577
  callbackId: string;
578
+ callbackUrl?: string;
551
579
  children?: unknown;
552
580
  closeLabel?: string;
553
581
  notifyOnClose?: boolean;
@@ -574,6 +602,18 @@ interface SelectProps {
574
602
  optional?: boolean;
575
603
  placeholder?: string;
576
604
  }
605
+ /** Props for ExternalSelect component in JSX */
606
+ interface ExternalSelectProps {
607
+ id: string;
608
+ initialOption?: {
609
+ label: string;
610
+ value: string;
611
+ };
612
+ label: string;
613
+ minQueryLength?: number;
614
+ optional?: boolean;
615
+ placeholder?: string;
616
+ }
577
617
  /** Props for SelectOption component in JSX */
578
618
  interface SelectOptionProps {
579
619
  description?: string;
@@ -586,9 +626,9 @@ interface TableProps {
586
626
  rows: string[][];
587
627
  }
588
628
  /** Union of all valid JSX props */
589
- type CardJSXProps = CardProps | TextProps | ButtonProps | LinkButtonProps | CardLinkProps | ImageProps | FieldProps | ContainerProps | DividerProps | ModalProps | TextInputProps | SelectProps | SelectOptionProps | TableProps;
629
+ type CardJSXProps = CardProps | TextProps | ButtonProps | LinkButtonProps | CardLinkProps | ImageProps | FieldProps | ContainerProps | DividerProps | ModalProps | TextInputProps | SelectProps | ExternalSelectProps | SelectOptionProps | TableProps;
590
630
  /** 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;
631
+ 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
632
  /**
593
633
  * Represents a JSX element from the chat JSX runtime.
594
634
  * This is the type returned when using JSX syntax with chat components.
@@ -600,7 +640,7 @@ interface CardJSXElement<P extends CardJSXProps = CardJSXProps> {
600
640
  type: CardComponentFunction;
601
641
  }
602
642
  /** 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;
643
+ type ChatElement = CardJSXElement | CardElement | TextElement | ButtonElement | LinkButtonElement | LinkElement | ImageElement | DividerElement | ActionsElement | SectionElement | FieldsElement | FieldElement | ModalElement | TextInputElement | SelectElement | ExternalSelectElement | SelectOptionElement | RadioSelectElement | TableElement;
604
644
  interface CardComponent {
605
645
  (options?: CardOptions): CardElement;
606
646
  (props: CardProps): ChatElement;
@@ -668,6 +708,10 @@ interface SelectComponent {
668
708
  (options: SelectOptions): SelectElement;
669
709
  (props: SelectProps): ChatElement;
670
710
  }
711
+ interface ExternalSelectComponent {
712
+ (options: ExternalSelectOptions): ExternalSelectElement;
713
+ (props: ExternalSelectProps): ChatElement;
714
+ }
671
715
  interface SelectOptionComponent {
672
716
  (options: {
673
717
  label: string;
@@ -735,4 +779,4 @@ declare namespace JSX {
735
779
  }
736
780
  }
737
781
 
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 };
782
+ 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-DxGwoLu2.js';
@@ -7,7 +7,7 @@ import {
7
7
  jsxs,
8
8
  toCardElement,
9
9
  toModalElement
10
- } from "./chunk-OPV5U4WG.js";
10
+ } from "./chunk-V25FKIIL.js";
11
11
  export {
12
12
  Fragment,
13
13
  isCardLinkProps,
package/docs/actions.mdx CHANGED
@@ -94,5 +94,56 @@ bot.onAction("feedback", async (event) => {
94
94
  ```
95
95
 
96
96
  <Callout type="info">
97
- Modals are currently supported on Slack. Other platforms will receive a no-op or fallback behavior.
97
+ Modals are currently supported on Slack and Teams. Other platforms will receive a no-op
98
+ or fallback behavior.
98
99
  </Callout>
100
+
101
+ ## Callback URLs
102
+
103
+ Buttons accept a `callbackUrl` prop. When clicked, the action data is POSTed to that URL in addition to firing any `onAction` handler. This pairs naturally with webhook-based workflow engines to build approval flows without any `onAction` handler at all:
104
+
105
+ ```tsx title="lib/bot.tsx" lineNumbers
106
+ bot.onNewMention(async (thread) => {
107
+ const approveUrl = "https://example.com/webhook/approve";
108
+ const denyUrl = "https://example.com/webhook/deny";
109
+
110
+ await thread.post(
111
+ <Card title="Deploy v2.4.1?">
112
+ <Actions>
113
+ <Button callbackUrl={approveUrl} id="approve" style="primary">
114
+ Approve
115
+ </Button>
116
+ <Button callbackUrl={denyUrl} id="deny" style="danger">
117
+ Deny
118
+ </Button>
119
+ </Actions>
120
+ </Card>
121
+ );
122
+ });
123
+ ```
124
+
125
+ ### Callback payload
126
+
127
+ The POST body sent to the `callbackUrl`:
128
+
129
+ ```json
130
+ {
131
+ "type": "action",
132
+ "actionId": "approve",
133
+ "user": { "id": "U123", "name": "alice" },
134
+ "threadId": "slack:C123:1234567890.123",
135
+ "messageId": "1234567890.456"
136
+ }
137
+ ```
138
+
139
+ If the button also has a `value` prop, it is included in the payload as `"value"`.
140
+
141
+ <Callout type="info">
142
+ Platform limits apply to encoded button data. Discord's `custom_id` has a 100
143
+ character limit - if the action ID plus callback token exceed this, posting
144
+ the card throws a `ValidationError`. Telegram's `callback_data` has a 64 byte
145
+ limit - buttons that exceed this will throw a `ValidationError`. Keep action
146
+ IDs short when using `callbackUrl` on these platforms.
147
+ </Callout>
148
+
149
+ For modals, see [callbackUrl on modals](/docs/modals#callback-urls).
package/docs/adapters.mdx CHANGED
@@ -8,60 +8,66 @@ prerequisites:
8
8
 
9
9
  Adapters handle webhook verification, message parsing, and API calls for each platform. Install only the adapters you need. Browse all available adapters — including community-built ones — on the [Adapters](/adapters) page.
10
10
 
11
+ Need a browser chat UI? See the [Web adapter](/adapters/web) — it speaks the AI SDK `useChat` protocol so the same bot serves Slack, Teams, **and** a `<Conversation>` from `ai-elements` out of the box.
12
+
11
13
  Ready to build your own? Follow the [building](/docs/contributing/building) guide.
12
14
 
13
15
  ## Feature matrix
14
16
 
15
17
  ### Messaging
16
18
 
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
- |---------|-------|-------|-------------|---------|---------|--------|--------|-----------|
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
+ | 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) | [Messenger](/adapters/messenger) |
20
+ |---------|-------|-------|-------------|---------|---------|--------|--------|-----------|-----------|
21
+ | Post message | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
22
+ | Edit message | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Warn /> Partial | <Cross /> | <Cross /> |
23
+ | Delete message | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Warn /> Partial | <Cross /> | <Cross /> |
24
+ | File uploads | <Check /> | <Check /> | <Cross /> | <Check /> | <Warn /> Single file | <Cross /> | <Cross /> | <Check /> Images, audio, docs | <Cross /> |
25
+ | Streaming | <Check /> Native | <Warn /> Native (DMs) / Buffered | <Warn /> Post+Edit | <Warn /> Post+Edit | <Warn /> Post+Edit | <Warn /> Buffered | <Warn /> Agent sessions / Post+Edit | <Warn /> Buffered | <Warn /> Buffered |
26
+ | Scheduled messages | <Check /> Native | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> |
25
27
 
26
28
  ### Rich content
27
29
 
28
- | Feature | Slack | Teams | Google Chat | Discord | Telegram | GitHub | Linear | WhatsApp |
29
- |---------|-------|-------|-------------|---------|----------|--------|--------|-----------|
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 | | | | | | | | |
30
+ | Feature | Slack | Teams | Google Chat | Discord | Telegram | GitHub | Linear | WhatsApp | Messenger |
31
+ |---------|-------|-------|-------------|---------|----------|--------|--------|-----------|-----------|
32
+ | Card format | Block Kit | Adaptive Cards | Google Chat Cards | Embeds | Markdown + inline keyboard buttons | GFM Markdown | Markdown | WhatsApp templates | Generic/Button Templates |
33
+ | Buttons | <Check /> | <Check /> | <Check /> | <Check /> | <Warn /> Inline keyboard callbacks | <Cross /> | <Cross /> | <Check /> Interactive replies | <Warn /> Max 3, postback |
34
+ | Link buttons | <Check /> | <Check /> | <Check /> | <Check /> | <Warn /> Inline keyboard URLs | <Cross /> | <Cross /> | <Cross /> | <Check /> |
35
+ | Select menus | <Check /> | <Cross /> | <Check /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> |
36
+ | Tables | <Check /> Block Kit | <Check /> GFM | <Warn /> ASCII | <Check /> GFM | <Warn /> ASCII | <Check /> GFM | <Check /> GFM | <Cross /> | <Warn /> ASCII |
37
+ | Fields | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Warn /> Template variables | <Warn /> ASCII |
38
+ | Images in cards | <Check /> | <Check /> | <Check /> | <Check /> | <Cross /> | <Check /> | <Cross /> | <Check /> | <Check /> |
39
+ | Modals | <Check /> | <Check /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> |
38
40
 
39
41
  ### Conversations
40
42
 
41
- | Feature | Slack | Teams | Google Chat | Discord | Telegram | GitHub | Linear | WhatsApp |
42
- |---------|-------|-------|-------------|---------|----------|--------|--------|-----------|
43
- | Slash commands | | | | | | | | |
44
- | Mentions | | | | | | | | |
45
- | Add reactions | | | | | | | | |
46
- | Remove reactions | | | | | | ⚠️ | ⚠️ | |
47
- | Typing indicator | | | | | | | | |
48
- | DMs | | | | | | | | |
49
- | Ephemeral messages | Native | | Native | | | | | |
43
+ | Feature | Slack | Teams | Google Chat | Discord | Telegram | GitHub | Linear | WhatsApp | Messenger |
44
+ |---------|-------|-------|-------------|---------|----------|--------|--------|-----------|-----------|
45
+ | Slash commands | <Check /> | <Cross /> | <Cross /> | <Check /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> |
46
+ | Mentions | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Cross /> | <Check /> |
47
+ | Add reactions | <Check /> | <Cross /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Cross /> |
48
+ | Remove reactions | <Check /> | <Cross /> | <Check /> | <Check /> | <Check /> | <Warn /> | <Warn /> | <Check /> | <Cross /> |
49
+ | Typing indicator | <Check /> | <Check /> | <Cross /> | <Check /> | <Check /> | <Cross /> | <Warn /> Agent sessions | <Cross /> | <Check /> |
50
+ | DMs | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Cross /> | <Cross /> | <Check /> | <Check /> |
51
+ | Ephemeral messages | <Check /> Native | <Cross /> | <Check /> Native | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> |
52
+ | User lookup ([`getUser`](/docs/api/chat#getuser)) | <Check /> | <Warn /> Cached | <Warn /> Cached | <Check /> | <Warn /> Seen users | <Check /> | <Check /> | <Cross /> | <Cross /> |
53
+ | Parent subject ([`message.subject`](/docs/subject)) | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Check /> | <Check /> | <Cross /> | <Cross /> |
54
+ | Native client ([`.client`](/docs/api/chat#getadapter)) | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Check /> | <Check /> | <Cross /> | <Cross /> |
55
+ | Custom API endpoint (`apiUrl`) | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
50
56
 
51
57
  ### Message history
52
58
 
53
- | Feature | Slack | Teams | Google Chat | Discord | Telegram | GitHub | Linear | WhatsApp |
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 | | | | | | | | |
59
+ | Feature | Slack | Teams | Google Chat | Discord | Telegram | GitHub | Linear | WhatsApp | Messenger |
60
+ |---------|-------|-------|-------------|---------|----------|--------|--------|-----------|-----------|
61
+ | Fetch messages | <Check /> | <Check /> | <Check /> | <Check /> | <Warn /> Cached | <Check /> | <Check /> | <Warn /> Cached sent messages only | <Warn /> Cached sent messages only |
62
+ | Fetch single message | <Check /> | <Cross /> | <Cross /> | <Cross /> | <Warn /> Cached | <Cross /> | <Cross /> | <Cross /> | <Warn /> Cached |
63
+ | Fetch thread info | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
64
+ | Fetch channel messages | <Check /> | <Check /> | <Check /> | <Check /> | <Warn /> Cached | <Check /> | <Cross /> | <Cross /> | <Warn /> Cached |
65
+ | List threads | <Check /> | <Check /> | <Check /> | <Check /> | <Cross /> | <Check /> | <Cross /> | <Cross /> | <Cross /> |
66
+ | Fetch channel info | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Cross /> | <Cross /> | <Check /> |
67
+ | Post channel message | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Cross /> | <Cross /> | <Check /> | <Check /> |
62
68
 
63
69
  <Callout type="info">
64
- ⚠️ indicates partial support — the feature works with limitations. See individual adapter pages for details.
70
+ <Warn /> indicates partial support — the feature works with limitations. See individual adapter pages for details.
65
71
  </Callout>
66
72
 
67
73
  ## How adapters work
@@ -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
@@ -168,7 +168,9 @@ Fires when a user clicks a button or selects an option in a card.
168
168
  ```typescript
169
169
  // Single action
170
170
  bot.onAction("approve", async (event) => {
171
- await event.thread.post("Approved!");
171
+ if (event.thread) {
172
+ await event.thread.post("Approved!");
173
+ }
172
174
  });
173
175
 
174
176
  // Multiple actions
@@ -193,8 +195,8 @@ bot.onAction(async (event) => { /* ... */ });
193
195
  type: 'Author',
194
196
  },
195
197
  'event.thread': {
196
- description: 'The thread containing the card.',
197
- type: 'Thread',
198
+ description: 'The thread containing the card, or null for view-based actions.',
199
+ type: 'Thread | null',
198
200
  },
199
201
  'event.triggerId': {
200
202
  description: 'Trigger ID for opening modals (platform-specific, may expire quickly).',
@@ -255,14 +257,53 @@ bot.onModalSubmit("feedback", async (event) => {
255
257
 
256
258
  Returns `ModalResponse | undefined` to control the modal after submission:
257
259
 
258
- - `{ action: "close" }` — close the modal
260
+ - `{ action: "close" }` — close the current view (goes back one level in the stack)
261
+ - `{ action: "clear" }` — close all views and dismiss the modal entirely
259
262
  - `{ action: "errors", errors: { fieldId: "message" } }` — show validation errors
260
263
  - `{ action: "update", modal: ModalElement }` — replace the modal content
261
264
  - `{ action: "push", modal: ModalElement }` — push a new modal view onto the stack
262
265
 
266
+ ### onOptionsLoad
267
+
268
+ 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.
269
+
270
+ ```typescript
271
+ bot.onOptionsLoad("assignee", async (event) => {
272
+ const people = await peopleService.search(event.query);
273
+ return people.map((p) => ({ label: p.fullName, value: p.id }));
274
+ });
275
+ ```
276
+
277
+ 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.
278
+
279
+ <TypeTable
280
+ type={{
281
+ 'event.actionId': {
282
+ description: 'The id of the select requesting options (matches the id passed to bot.onOptionsLoad).',
283
+ type: 'string',
284
+ },
285
+ 'event.query': {
286
+ description: 'The text the user has typed so far.',
287
+ type: 'string',
288
+ },
289
+ 'event.user': {
290
+ description: 'The user requesting options.',
291
+ type: 'Author',
292
+ },
293
+ 'event.adapter': {
294
+ description: 'The adapter that received this event.',
295
+ type: 'Adapter',
296
+ },
297
+ 'event.raw': {
298
+ description: 'Raw platform-specific payload.',
299
+ type: 'unknown',
300
+ },
301
+ }}
302
+ />
303
+
263
304
  ### onSlashCommand
264
305
 
265
- Fires when a user invokes a `/command` in the message composer. Currently supported on Slack.
306
+ Fires when a user invokes a `/command` in the message composer. Currently supported on Slack and Discord.
266
307
 
267
308
  ```typescript
268
309
  // Specific command
@@ -425,12 +466,42 @@ bot.webhooks.teams(request, { waitUntil });
425
466
 
426
467
  ### getAdapter
427
468
 
428
- Get an adapter instance by name.
469
+ Get a typed adapter instance by name.
429
470
 
430
471
  ```typescript
431
472
  const slack = bot.getAdapter("slack");
432
473
  ```
433
474
 
475
+ #### Direct client access
476
+
477
+ Use `.client` to access the platform's typed native API client directly — available on Linear and GitHub:
478
+
479
+ ```typescript
480
+ // Linear - full LinearClient from @linear/sdk
481
+ const linear = bot.getAdapter("linear").client;
482
+ const issue = await linear.issue("ENG-123");
483
+ const project = await issue.project;
484
+
485
+ // GitHub - full Octokit from @octokit/rest
486
+ const github = bot.getAdapter("github").client;
487
+ const { data: pulls } = await github.rest.pulls.list({
488
+ owner: "vercel",
489
+ repo: "chat",
490
+ state: "open",
491
+ });
492
+ ```
493
+
494
+ The client uses the credentials from your adapter config. For multi-tenant adapters (Linear, GitHub), it returns the client for the current webhook request context.
495
+
496
+ <Callout type="warn">
497
+ For multi-tenant adapters (GitHub App without a fixed installation ID, Linear with per-org OAuth), `client` requires webhook handler context to resolve credentials. Calling it outside a handler throws. Single-tenant adapters (PAT, API key) work anywhere.
498
+ </Callout>
499
+
500
+ | Adapter | `client` type |
501
+ |---------|---------------|
502
+ | Linear | `LinearClient` from `@linear/sdk` |
503
+ | GitHub | `Octokit` from `@octokit/rest` |
504
+
434
505
  ### openDM
435
506
 
436
507
  Open a direct message thread with a user.
@@ -443,6 +514,101 @@ await dm.post("Hello via DM!");
443
514
  const dm = await bot.openDM(message.author);
444
515
  ```
445
516
 
517
+ ### getUser
518
+
519
+ 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`.
520
+
521
+ ```typescript
522
+ const user = await bot.getUser("U123456");
523
+ console.log(user?.email); // "alice@company.com"
524
+ console.log(user?.fullName); // "Alice Smith"
525
+ ```
526
+
527
+ ```typescript
528
+ // Or with an Author object from a message handler
529
+ const user = await bot.getUser(message.author);
530
+ ```
531
+
532
+ <TypeTable
533
+ type={{
534
+ userId: {
535
+ description: 'Platform-specific user ID.',
536
+ type: 'string',
537
+ },
538
+ userName: {
539
+ description: 'Username/handle.',
540
+ type: 'string',
541
+ },
542
+ fullName: {
543
+ description: 'Display name / full name.',
544
+ type: 'string',
545
+ },
546
+ isBot: {
547
+ description: 'Whether the user is a bot.',
548
+ type: 'boolean',
549
+ },
550
+ email: {
551
+ description: 'Email address (requires scopes on some platforms).',
552
+ type: 'string | undefined',
553
+ },
554
+ avatarUrl: {
555
+ description: 'Profile image URL.',
556
+ type: 'string | undefined',
557
+ },
558
+ }}
559
+ />
560
+
561
+ <Callout type="info">
562
+ **Per-platform constraints:**
563
+ - **Slack** — requires both `users:read` and `users:read.email` scopes (the email scope must be granted at OAuth install time).
564
+ - **Discord** — bot tokens never see email (the `email` OAuth scope only applies in user-context auth).
565
+ - **Telegram** — bots can only look up users who have previously messaged them.
566
+ - **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).
567
+ - **Google Chat** — same caching constraint as Teams: only users seen in prior webhooks.
568
+ - **GitHub** — `email` is `null` unless the user made it public, or you authenticated with the `user:email` scope.
569
+ - **Linear** — full profile (incl. email + avatar) for any active workspace member.
570
+
571
+ 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)`).
572
+ </Callout>
573
+
574
+ `bot.getUser` throws a `ChatError` in three cases. Handle them if your bot runs on multiple platforms:
575
+
576
+ | Code | When |
577
+ |------|------|
578
+ | `NOT_SUPPORTED` | The resolved adapter doesn't implement `getUser` (e.g. WhatsApp) |
579
+ | `AMBIGUOUS_USER_ID` | A numeric user ID could belong to more than one registered adapter (Discord/Telegram/GitHub) |
580
+ | `UNKNOWN_USER_ID_FORMAT` | The `userId` string doesn't match any registered platform's ID format |
581
+
582
+ ```typescript
583
+ import { ChatError } from "chat";
584
+
585
+ try {
586
+ const user = await bot.getUser(userId);
587
+ if (!user) {
588
+ // User not found on this platform
589
+ }
590
+ } catch (error) {
591
+ if (error instanceof ChatError) {
592
+ if (error.code === "NOT_SUPPORTED") {
593
+ // This adapter doesn't support user lookups
594
+ } else if (error.code === "AMBIGUOUS_USER_ID") {
595
+ // Pass message.author or call adapter.getUser(userId) directly
596
+ } else if (error.code === "UNKNOWN_USER_ID_FORMAT") {
597
+ // userId doesn't match any known platform format
598
+ }
599
+ }
600
+ }
601
+ ```
602
+
603
+ ### thread
604
+
605
+ Get a Thread handle by its thread ID. Useful for posting to threads outside of webhook contexts (e.g. cron jobs, external triggers).
606
+
607
+ ```typescript
608
+ const thread = bot.thread("slack:C123ABC:1234567890.123456");
609
+ await thread.post("Hello from a cron job!");
610
+ ```
611
+
446
612
  ### channel
447
613
 
448
614
  Get a Channel by its channel ID.
@@ -31,6 +31,8 @@ import { Chat, root, paragraph, text, Card, Button, emoji } from "chat";
31
31
  | Export | Description |
32
32
  |--------|-------------|
33
33
  | [`PostableMessage`](/docs/api/postable-message) | Union type accepted by `thread.post()` |
34
+ | [`Plan`](/docs/api/postable-message#plan) | Step-by-step task list that mutates after posting |
35
+ | [`StreamingPlan`](/docs/api/postable-message#streamingplan) | Wraps an async iterable with platform-specific streaming options |
34
36
  | [`Cards`](/docs/api/cards) | Rich card components — `Card`, `Text`, `Button`, `Actions`, etc. |
35
37
  | [`Markdown`](/docs/api/markdown) | AST builder functions — `root`, `paragraph`, `text`, `strong`, etc. |
36
38
  | [`Modals`](/docs/api/modals) | Modal form components — `Modal`, `TextInput`, `Select`, etc. |