chat 4.27.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.
package/docs/index.mdx CHANGED
@@ -4,7 +4,7 @@ description: A unified SDK for building chat bots across Slack, Microsoft Teams,
4
4
  type: overview
5
5
  ---
6
6
 
7
- Chat SDK is a TypeScript library for building chat bots that work across multiple platforms with a single codebase. Write your bot logic once and deploy it to Slack, Microsoft Teams, Google Chat, Discord, Telegram, GitHub, Linear, and WhatsApp.
7
+ Chat SDK is a TypeScript library for building chat bots that work across multiple platforms with a single codebase. Write your bot logic once and deploy it to Slack, Microsoft Teams, Google Chat, Discord, Telegram, GitHub, Linear, WhatsApp, and Messenger.
8
8
 
9
9
  ## Why Chat SDK?
10
10
 
@@ -52,13 +52,14 @@ Each adapter factory auto-detects credentials from environment variables (`SLACK
52
52
  | Platform | Package | Mentions | Reactions | Cards | Modals | Streaming | DMs |
53
53
  |----------|---------|----------|-----------|-------|--------|-----------|-----|
54
54
  | Slack | `@chat-adapter/slack` | Yes | Yes | Yes | Yes | Native | Yes |
55
- | Microsoft Teams | `@chat-adapter/teams` | Yes | Read-only | Yes | No | Post+Edit | Yes |
55
+ | Microsoft Teams | `@chat-adapter/teams` | Yes | Read-only | Yes | Yes | Native (DMs) / Buffered | Yes |
56
56
  | Google Chat | `@chat-adapter/gchat` | Yes | Yes | Yes | No | Post+Edit | Yes |
57
57
  | Discord | `@chat-adapter/discord` | Yes | Yes | Yes | No | Post+Edit | Yes |
58
58
  | Telegram | `@chat-adapter/telegram` | Yes | Yes | Partial | No | Post+Edit | Yes |
59
- | GitHub | `@chat-adapter/github` | Yes | Yes | No | No | No | No |
60
- | Linear | `@chat-adapter/linear` | Yes | Yes | No | No | No | No |
61
- | WhatsApp | `@chat-adapter/whatsapp` | N/A | Yes | Partial | No | No | Yes |
59
+ | GitHub | `@chat-adapter/github` | Yes | Yes | No | No | Buffered | No |
60
+ | Linear | `@chat-adapter/linear` | Yes | Yes | No | No | Agent sessions / Post+Edit | No |
61
+ | WhatsApp | `@chat-adapter/whatsapp` | N/A | Yes | Partial | No | Buffered | Yes |
62
+ | Messenger | `@chat-adapter/messenger` | Yes | Receive-only | Partial | No | Buffered | Yes |
62
63
 
63
64
  ## AI coding agent support
64
65
 
@@ -85,6 +86,7 @@ The SDK is distributed as a set of packages you install based on your needs:
85
86
  | `@chat-adapter/github` | GitHub Issues adapter |
86
87
  | `@chat-adapter/linear` | Linear Issues adapter |
87
88
  | `@chat-adapter/whatsapp` | WhatsApp Business adapter |
89
+ | `@chat-adapter/messenger` | Facebook Messenger adapter |
88
90
  | `@chat-adapter/state-redis` | Redis state adapter (production) |
89
91
  | `@chat-adapter/state-ioredis` | ioredis state adapter (alternative) |
90
92
  | `@chat-adapter/state-pg` | PostgreSQL state adapter (production) |
package/docs/meta.json CHANGED
@@ -8,13 +8,24 @@
8
8
  "threads-messages-channels",
9
9
  "handling-events",
10
10
  "posting-messages",
11
+ "error-handling",
11
12
  "---Adapters---",
12
13
  "adapters",
13
14
  "state",
14
- "---Features---",
15
+ "---Messaging---",
16
+ "streaming",
17
+ "direct-messages",
18
+ "ephemeral-messages",
19
+ "files",
20
+ "conversation-history",
21
+ "subject",
15
22
  "concurrency",
16
- "...",
17
- "error-handling",
23
+ "---Interactivity---",
24
+ "cards",
25
+ "modals",
26
+ "actions",
27
+ "slash-commands",
28
+ "emoji",
18
29
  "---API Reference---",
19
30
  "...api",
20
31
  "---Contributing---",
package/docs/modals.mdx CHANGED
@@ -59,6 +59,7 @@ The top-level container for the form.
59
59
  | `submitLabel` | `string` (optional) | Submit button text (defaults to "Submit") |
60
60
  | `closeLabel` | `string` (optional) | Cancel button text (defaults to "Cancel") |
61
61
  | `notifyOnClose` | `boolean` (optional) | Fire `onModalClose` when user cancels |
62
+ | `callbackUrl` | `string` (optional) | URL to POST form values to on submit |
62
63
  | `privateMetadata` | `string` (optional) | Custom context passed through to handlers |
63
64
 
64
65
  ### TextInput
@@ -248,6 +249,29 @@ bot.onModalClose("feedback_form", async (event) => {
248
249
  });
249
250
  ```
250
251
 
252
+ ## Callback URLs
253
+
254
+ Like buttons, modals accept a `callbackUrl`. When the modal is submitted, the form values are POSTed to the URL:
255
+
256
+ ```tsx title="lib/bot.tsx" lineNumbers
257
+ await event.openModal(
258
+ <Modal callbackUrl={webhook.url} callbackId="intake" title="Request Access" submitLabel="Submit">
259
+ <TextInput id="reason" label="Reason" multiline />
260
+ </Modal>
261
+ );
262
+ ```
263
+
264
+ The POST body for modal submissions:
265
+
266
+ ```json
267
+ {
268
+ "type": "modal_submit",
269
+ "callbackId": "intake",
270
+ "values": { "reason": "Need access to production logs" },
271
+ "user": { "id": "U123", "name": "alice" }
272
+ }
273
+ ```
274
+
251
275
  ## Pass context with privateMetadata
252
276
 
253
277
  Use `privateMetadata` to carry context from the button click through to the submit handler:
@@ -24,7 +24,7 @@ This sends the string directly without any formatting conversion.
24
24
 
25
25
  ## Markdown
26
26
 
27
- Pass a `{ markdown }` object to have the SDK convert standard markdown to each platform's native format mrkdwn for Slack, HTML for Teams, and so on.
27
+ Pass a `{ markdown }` object to have the SDK render standard markdown on each platform — passed through to Slack's native `markdown_text` field, converted to HTML for Teams, and so on.
28
28
 
29
29
  ```typescript title="lib/bot.ts" lineNumbers
30
30
  await thread.post({
@@ -32,7 +32,7 @@ await thread.post({
32
32
  });
33
33
  ```
34
34
 
35
- Under the hood, the SDK parses the markdown into an mdast AST, then each adapter converts it to the platform's format.
35
+ Under the hood, the SDK parses the markdown into an mdast AST, then each adapter handles it natively or converts it to the platform's format.
36
36
 
37
37
  ## AST builders
38
38
 
@@ -139,7 +139,7 @@ See the [Cards](/docs/cards) page for the full list of card components.
139
139
 
140
140
  ## Streaming
141
141
 
142
- Pass an AI SDK stream to `thread.post()` to stream a message in real time. The SDK uses platform-native streaming where available and falls back to post-then-edit on other platforms.
142
+ Pass an AI SDK stream to `thread.post()` to stream a message in real time. The SDK uses platform-native streaming where available and falls back to post-then-edit or buffered delivery depending on the platform.
143
143
 
144
144
  ```typescript title="lib/bot.ts" lineNumbers
145
145
  import { ToolLoopAgent } from "ai";
@@ -153,6 +153,8 @@ Both `fullStream` and `textStream` are supported. Use `fullStream` with multi-st
153
153
 
154
154
  For multi-turn conversations, use [`toAiMessages()`](/docs/api/to-ai-messages) to convert thread history into the `{ role, content }[]` format expected by AI SDKs.
155
155
 
156
+ To pass platform-specific streaming options (e.g. Slack task grouping or stop blocks), wrap the stream in a [`StreamingPlan`](/docs/streaming#streaming-with-options) and post that.
157
+
156
158
  See the [Streaming](/docs/streaming) page for details on platform behavior and configuration.
157
159
 
158
160
  ## Attachments and files
@@ -178,5 +180,7 @@ See the [Files](/docs/files) page for more on attachments.
178
180
  | Card (function) | You need buttons, fields, or structured layouts | Approval flows, dashboards |
179
181
  | Card (JSX) | Same as above, with JSX syntax preference | Same use cases as function cards |
180
182
  | `AsyncIterable` | Streaming AI responses | Chat with LLMs |
183
+ | [`Plan`](/docs/streaming#plan-api) | Step-by-step tasks that mutate after posting | Multi-step agents, deploy progress |
184
+ | [`StreamingPlan`](/docs/streaming#streaming-with-options) | Streaming with platform-specific options | Slack streaming with grouped tasks or stop blocks |
181
185
 
182
186
  For most cases, **AST builders** give the best balance of control and simplicity. Reach for **cards** when you need interactive elements like buttons or dropdowns.
@@ -6,7 +6,7 @@ prerequisites:
6
6
  - /docs/usage
7
7
  ---
8
8
 
9
- Chat SDK accepts any `AsyncIterable<string>` as a message, enabling real-time streaming of AI responses and other incremental content to chat platforms. For platforms with native streaming support (Slack), you can also stream structured `StreamChunk` objects for rich content like task progress cards and plan updates.
9
+ Chat SDK accepts any `AsyncIterable<string>` as a message, enabling real-time streaming of AI responses and other incremental content to chat platforms. For platforms with native or structured streaming support, you can also stream `StreamChunk` objects for rich content like task progress cards and plan updates.
10
10
 
11
11
  ## AI SDK integration
12
12
 
@@ -59,9 +59,14 @@ await thread.post(stream);
59
59
  | Platform | Method | Description |
60
60
  |----------|--------|-------------|
61
61
  | Slack | Native streaming API | Uses Slack's `chatStream` for smooth, real-time updates |
62
- | Teams | Post + Edit | Posts a message then edits it as chunks arrive |
62
+ | Teams | Native (DMs) / Buffered (group chats) | Uses the Teams SDK's native `stream.emit()` for direct messages; accumulates chunks and posts one final message when no native streamer is active |
63
63
  | Google Chat | Post + Edit | Posts a message then edits it as chunks arrive |
64
64
  | Discord | Post + Edit | Posts a message then edits it as chunks arrive |
65
+ | Telegram | Post + Edit | Posts a message then edits it as chunks arrive |
66
+ | GitHub | Buffered | Accumulates chunks and posts one final comment |
67
+ | Linear | Agent sessions / Post + Edit | Uses agent session activities in agent-session threads; falls back to post+edit comments in issue threads |
68
+ | WhatsApp | Buffered | Accumulates chunks and sends one final message |
69
+ | Messenger | Buffered | Accumulates chunks and sends one final message |
65
70
 
66
71
  The post+edit fallback throttles edits to avoid rate limits. Configure the update interval when creating your `Chat` instance:
67
72
 
@@ -104,9 +109,9 @@ When streaming content that contains GFM tables (e.g. from an LLM), the SDK auto
104
109
 
105
110
  This happens transparently — no configuration needed.
106
111
 
107
- ## Structured streaming chunks (Slack only)
112
+ ## Structured streaming chunks
108
113
 
109
- For Slack's native streaming API, you can yield `StreamChunk` objects alongside plain text for rich content:
114
+ For Slack native streams and Linear agent-session streams, you can yield `StreamChunk` objects alongside plain text for rich progress updates:
110
115
 
111
116
  ```typescript title="lib/bot.ts" lineNumbers
112
117
  import type { StreamChunk } from "chat";
@@ -144,39 +149,42 @@ await thread.post(stream);
144
149
  | Type | Fields | Description |
145
150
  |------|--------|-------------|
146
151
  | `markdown_text` | `text` | Streamed text content |
147
- | `task_update` | `id`, `title`, `status`, `details?`, `output?` | Tool/step progress cards (`pending`, `in_progress`, `complete`, `error`) with optional extra task context |
148
- | `plan_update` | `title` | Plan title updates |
152
+ | `task_update` | `id`, `title`, `status`, `details?`, `output?` | Tool/step progress updates (`pending`, `in_progress`, `complete`, `error`) with optional extra task context |
153
+ | `plan_update` | `title` | Plan title updates on supported platforms |
149
154
 
150
- ### Task display mode
155
+ ### Streaming with options
151
156
 
152
- Control how `task_update` chunks render in Slack by passing `taskDisplayMode` via the adapter's streaming API. These options are currently only available through `adapter.stream()` directly:
157
+ Wrap a stream in a `StreamingPlan` to pass platform-specific options through `thread.post()` without dropping down to `adapter.stream()` directly:
153
158
 
154
159
  ```typescript
155
- const raw = message.raw as { team_id?: string; team?: string };
156
- await thread.adapter.stream(thread.id, stream, {
157
- recipientUserId: message.author.userId,
158
- recipientTeamId: raw.team_id ?? raw.team,
159
- taskDisplayMode: "plan",
160
+ import { StreamingPlan } from "chat";
161
+
162
+ const planned = new StreamingPlan(stream, {
163
+ groupTasks: "plan", // Slack: render task cards as a single grouped block
164
+ endWith: [feedbackBlock], // Slack: Block Kit elements appended after stream stops
165
+ updateIntervalMs: 750, // Post+edit cadence on supported adapters
160
166
  });
167
+
168
+ await thread.post(planned);
161
169
  ```
162
170
 
163
- | Mode | Description |
164
- |------|-------------|
165
- | `"timeline"` | Individual task cards shown inline with text (default) |
166
- | `"plan"` | All tasks grouped into a single plan block |
171
+ | Option | Platform | Description |
172
+ |--------|----------|-------------|
173
+ | `groupTasks` | Slack | `"timeline"` (default) renders task cards inline; `"plan"` groups them into one plan block |
174
+ | `endWith` | Slack | Block Kit elements attached when the stream stops (e.g. retry / feedback buttons) |
175
+ | `updateIntervalMs` | Post+edit adapters | Minimum interval between post+edit cycles in ms (default `500`) |
167
176
 
168
- Adapters without structured chunk support extract text from `markdown_text` chunks and ignore other types.
177
+ Adapters without structured chunk support extract text from `markdown_text` chunks and ignore other types. Slack-only options are silently ignored on other platforms.
169
178
 
170
179
  ## Stop blocks (Slack only)
171
180
 
172
- When streaming in Slack, you can attach Block Kit elements to the final message using `stopBlocks`. This is useful for adding action buttons after a streamed response completes:
181
+ Use `endWith` on `StreamingPlan` to attach Block Kit elements to the final message. This is useful for adding action buttons after a streamed response completes:
173
182
 
174
183
  ```typescript title="lib/bot.ts" lineNumbers
175
- const raw = message.raw as { team_id?: string; team?: string };
176
- await thread.adapter.stream(thread.id, textStream, {
177
- recipientUserId: message.author.userId,
178
- recipientTeamId: raw.team_id ?? raw.team,
179
- stopBlocks: [
184
+ import { StreamingPlan } from "chat";
185
+
186
+ const planned = new StreamingPlan(textStream, {
187
+ endWith: [
180
188
  {
181
189
  type: "actions",
182
190
  elements: [{
@@ -187,8 +195,48 @@ await thread.adapter.stream(thread.id, textStream, {
187
195
  },
188
196
  ],
189
197
  });
198
+
199
+ await thread.post(planned);
190
200
  ```
191
201
 
202
+ ## Plan API
203
+
204
+ For step-by-step task progress that lives outside an LLM stream, post a `Plan` directly. `Plan` is a `PostableObject` you can mutate after posting — every mutation re-renders the block in place.
205
+
206
+ ```typescript title="lib/bot.ts" lineNumbers
207
+ import { Plan } from "chat";
208
+
209
+ const plan = new Plan({ initialMessage: "Researching options..." });
210
+ await thread.post(plan);
211
+
212
+ const lookup = await plan.addTask({ title: "Look up customer record" });
213
+ // ...do work...
214
+ await plan.updateTask("Found 3 matches");
215
+
216
+ await plan.addTask({ title: "Summarize findings" });
217
+ await plan.complete({ completeMessage: "Done!" });
218
+ ```
219
+
220
+ By default `updateTask()` mutates the most recent `in_progress` task. Pass `{ id }` to target a specific task — useful when work runs in parallel or out of order:
221
+
222
+ ```typescript
223
+ const fetchTask = await plan.addTask({ title: "Fetch data" });
224
+ const transformTask = await plan.addTask({ title: "Transform" });
225
+
226
+ // Update a specific task by id, even if it isn't the most recent in_progress one.
227
+ await plan.updateTask({ id: fetchTask.id, output: "Got 42 rows" });
228
+ await plan.updateTask({ id: transformTask.id, status: "complete" });
229
+ ```
230
+
231
+ Adapters that don't support PostableObject editing (e.g. WhatsApp) render the plan as a fallback emoji-list message; the plan still posts, but mutations are no-ops.
232
+
233
+ | Method | Description |
234
+ |--------|-------------|
235
+ | `addTask({ title, children? })` | Append a new task. The previous in-progress task is auto-completed |
236
+ | `updateTask(input)` | Mutate the current (or `{ id }`-targeted) task's `output`, `status`, or `title` |
237
+ | `complete({ completeMessage })` | Mark all in-progress tasks complete and update the plan title |
238
+ | `reset({ initialMessage })` | Discard all tasks and start fresh with a new initial message — useful when re-using a plan handle for a new run |
239
+
192
240
  ## Streaming with conversation history
193
241
 
194
242
  Combine message history with streaming for multi-turn AI conversations.
@@ -0,0 +1,53 @@
1
+ ---
2
+ title: Message Subject
3
+ description: Fetch the parent resource that a message is about.
4
+ type: guide
5
+ prerequisites:
6
+ - /docs/handling-events
7
+ related:
8
+ - /docs/conversation-history
9
+ ---
10
+
11
+ When your bot receives a comment on a Linear issue or GitHub PR, `message.subject` resolves the parent resource so your handler knows what the conversation is about.
12
+
13
+ ## Usage
14
+
15
+ ```typescript title="lib/bot.ts" lineNumbers
16
+ bot.onNewMention(async (thread, message) => {
17
+ const subject = await message.subject;
18
+
19
+ if (subject) {
20
+ await thread.post(
21
+ `This is about: ${subject.title} (${subject.status})\n${subject.url}`
22
+ );
23
+ }
24
+ });
25
+ ```
26
+
27
+ On Linear and GitHub, comment webhooks deliver the comment text but not the parent issue or pull request — `message.subject` fetches it from the platform API on first access. The result is cached on the message instance. On chat platforms (which have no parent-resource concept), or if the API call fails, it returns `null`.
28
+
29
+ See [`MessageSubject`](/docs/api/message#messagesubject) for the full type shape.
30
+
31
+ ### Platform support
32
+
33
+ | Platform | `message.subject` returns |
34
+ |----------|--------------------------|
35
+ | Linear | Parent issue (from comment webhooks) |
36
+ | GitHub | Parent issue or PR (from comment webhooks) |
37
+
38
+ All other platforms return `null`.
39
+
40
+ ## User info
41
+
42
+ For user profile details, use [`bot.getUser`](/docs/api/chat#getuser):
43
+
44
+ ```typescript title="lib/bot.ts" lineNumbers
45
+ bot.onNewMention(async (thread, message) => {
46
+ const user = await bot.getUser(message.author);
47
+ if (user) {
48
+ await thread.post(`Hi ${user.fullName} (${user.email})`);
49
+ }
50
+ });
51
+ ```
52
+
53
+ For anything beyond `message.subject`, access the platform's typed API client via [`bot.getAdapter(...).client`](/docs/api/chat#getadapter).
@@ -13,6 +13,15 @@ related:
13
13
 
14
14
  A `Thread` represents a conversation thread on any platform. It provides methods for posting messages, managing subscriptions, and accessing message history.
15
15
 
16
+ Thread instances are most often supplied by the SDK to your event handlers. You can also construct one explicitly from a thread ID — useful for cron jobs, workflow steps, or any other context outside an inbound webhook:
17
+
18
+ ```typescript title="lib/bot.ts" lineNumbers
19
+ const thread = bot.thread("slack:C123ABC:1234567890.123456");
20
+ await thread.post("Reminder from a cron job");
21
+ ```
22
+
23
+ For DM-style conversations, use [`bot.openDM(userIdOrAuthor)`](/docs/direct-messages) instead. It resolves the right channel and thread for user ID formats the SDK can infer.
24
+
16
25
  ### Post a message
17
26
 
18
27
  ```typescript title="lib/bot.ts" lineNumbers
package/docs/usage.mdx CHANGED
@@ -34,7 +34,7 @@ bot.onNewMention(async (thread) => {
34
34
  ```
35
35
 
36
36
  <Callout type="info">
37
- This example uses Redis. Chat SDK also supports [PostgreSQL](/docs/state/postgres) and [ioredis](/docs/state/ioredis) as production state adapters. See [State Adapters](/docs/state) for all options.
37
+ This example uses Redis. Chat SDK also supports [PostgreSQL](/adapters/postgres) and [ioredis](/adapters/ioredis) as production state adapters. See [State Adapters](/docs/state) for all options.
38
38
  </Callout>
39
39
 
40
40
  Each adapter factory auto-detects credentials from environment variables (`SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET`, `REDIS_URL`, etc.), so you can get started with zero config. Pass explicit values to override.
@@ -89,6 +89,15 @@ await slack.setSuggestedPrompts(channelId, threadTs, [
89
89
  ]);
90
90
  ```
91
91
 
92
+ For typed access to the platform's native API client (Linear, GitHub), use `.client`:
93
+
94
+ ```typescript title="lib/bot.ts" lineNumbers
95
+ const linear = bot.getAdapter("linear").client; // LinearClient
96
+ const github = bot.getAdapter("github").client; // Octokit
97
+ ```
98
+
99
+ See [`getAdapter`](/docs/api/chat#getadapter) for multi-tenant constraints.
100
+
92
101
  ## Webhook routing
93
102
 
94
103
  The `webhooks` property provides type-safe handlers for each registered adapter. Wire these up to your HTTP framework's routes:
@@ -139,7 +148,7 @@ const bot = Chat.getSingleton();
139
148
  Open a DM thread with a user by passing their platform user ID or an `Author` object:
140
149
 
141
150
  ```typescript title="lib/bot.ts" lineNumbers
142
- const dm = await bot.openDM("slack:U123ABC");
151
+ const dm = await bot.openDM("U123ABC");
143
152
  await dm.post("Hey! Just wanted to follow up on your request.");
144
153
  ```
145
154
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chat",
3
- "version": "4.27.0",
3
+ "version": "4.28.1",
4
4
  "description": "Unified chat abstraction for Slack, Teams, Google Chat, and Discord",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",