chat 4.14.0 → 4.16.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 (38) hide show
  1. package/README.md +12 -0
  2. package/dist/{chunk-THM4ACIE.js → chunk-7S5DLTN2.js} +372 -9
  3. package/dist/chunk-7S5DLTN2.js.map +1 -0
  4. package/dist/index.d.ts +122 -5
  5. package/dist/index.js +367 -235
  6. package/dist/index.js.map +1 -1
  7. package/dist/{jsx-runtime-Bdt1Dwzf.d.ts → jsx-runtime-Bokk9xw5.d.ts} +80 -5
  8. package/dist/jsx-runtime.d.ts +1 -1
  9. package/dist/jsx-runtime.js +3 -1
  10. package/docs/adapters/discord.mdx +1 -0
  11. package/docs/adapters/index.mdx +51 -34
  12. package/docs/adapters/meta.json +1 -0
  13. package/docs/adapters/slack.mdx +16 -0
  14. package/docs/adapters/telegram.mdx +161 -0
  15. package/docs/api/cards.mdx +57 -1
  16. package/docs/api/channel.mdx +4 -1
  17. package/docs/api/chat.mdx +5 -0
  18. package/docs/api/markdown.mdx +42 -0
  19. package/docs/api/thread.mdx +4 -1
  20. package/docs/cards.mdx +44 -1
  21. package/docs/contributing/building.mdx +626 -0
  22. package/docs/contributing/documenting.mdx +218 -0
  23. package/docs/contributing/meta.json +4 -0
  24. package/docs/contributing/publishing.mdx +161 -0
  25. package/docs/contributing/testing.mdx +494 -0
  26. package/docs/error-handling.mdx +1 -1
  27. package/docs/getting-started.mdx +23 -1
  28. package/docs/handling-events.mdx +375 -0
  29. package/docs/index.mdx +4 -2
  30. package/docs/meta.json +6 -1
  31. package/docs/posting-messages.mdx +7 -7
  32. package/docs/slash-commands.mdx +23 -0
  33. package/docs/state/meta.json +1 -6
  34. package/docs/streaming.mdx +32 -0
  35. package/docs/threads-messages-channels.mdx +237 -0
  36. package/docs/usage.mdx +82 -276
  37. package/package.json +4 -3
  38. package/dist/chunk-THM4ACIE.js.map +0 -1
@@ -0,0 +1,375 @@
1
+ ---
2
+ title: Handling Events
3
+ description: Register handlers for mentions, messages, reactions, and platform-specific events.
4
+ type: guide
5
+ prerequisites:
6
+ - /docs/getting-started
7
+ related:
8
+ - /docs/slash-commands
9
+ - /docs/actions
10
+ - /docs/modals
11
+ ---
12
+
13
+ Chat SDK uses an event-driven architecture. You register handlers for different event types, and the SDK routes incoming webhooks to the appropriate handler.
14
+
15
+ ## How routing works
16
+
17
+ When a message arrives, the SDK evaluates handlers in this order:
18
+
19
+ 1. **Subscribed threads** — if the thread is subscribed, `onSubscribedMessage` fires and no other message handler runs.
20
+ 2. **Mentions** — if the bot is @-mentioned in an unsubscribed thread, `onNewMention` fires.
21
+ 3. **Pattern matches** — if the message text matches any `onNewMessage` regex patterns, those handlers fire.
22
+
23
+ Reactions, slash commands, actions, and modals have their own dedicated routing and are not affected by subscription state.
24
+
25
+ ## Handling @-mentions
26
+
27
+ `onNewMention` fires when your bot is @-mentioned in a thread it hasn't subscribed to. This is the primary entry point for new conversations.
28
+
29
+ ```typescript title="lib/bot.ts" lineNumbers
30
+ bot.onNewMention(async (thread, message) => {
31
+ await thread.subscribe();
32
+ await thread.post("Hello! I'm now listening to this thread.");
33
+ });
34
+ ```
35
+
36
+ The handler receives a [`Thread`](/docs/api/thread) and a [`Message`](/docs/api/message). Once you call `thread.subscribe()`, future messages in that thread route to `onSubscribedMessage` instead.
37
+
38
+ ### When to use
39
+
40
+ - **AI assistants** — subscribe on first mention, then respond to all follow-up messages in the thread.
41
+ - **Ticket bots** — create a ticket when mentioned, then track the conversation.
42
+ - **One-shot commands** — respond to a mention without subscribing for bots that don't need ongoing context.
43
+
44
+ ### Example: AI assistant with context
45
+
46
+ ```typescript title="lib/bot.ts" lineNumbers
47
+ bot.onNewMention(async (thread, message) => {
48
+ await thread.subscribe();
49
+ await thread.startTyping();
50
+
51
+ const response = await generateAIResponse(message.text);
52
+ await thread.post(response);
53
+ });
54
+ ```
55
+
56
+ ### Example: Triage bot
57
+
58
+ ```typescript title="lib/bot.ts" lineNumbers
59
+ bot.onNewMention(async (thread, message) => {
60
+ const ticket = await createTicket({
61
+ title: message.text.slice(0, 100),
62
+ reporter: message.author.fullName,
63
+ });
64
+
65
+ await thread.post(`Ticket created: ${ticket.url}`);
66
+ });
67
+ ```
68
+
69
+ ## Handling subscribed messages
70
+
71
+ `onSubscribedMessage` fires for every new message in a thread your bot has subscribed to. Once subscribed, all messages (including @-mentions) route here instead of `onNewMention`.
72
+
73
+ ```typescript title="lib/bot.ts" lineNumbers
74
+ bot.onSubscribedMessage(async (thread, message) => {
75
+ if (message.isMention) {
76
+ await thread.post("You mentioned me!");
77
+ return;
78
+ }
79
+
80
+ await thread.post(`Got your message: ${message.text}`);
81
+ });
82
+ ```
83
+
84
+ <Callout type="info">
85
+ Messages sent by the bot itself do not trigger this handler. You don't need to filter out your own messages.
86
+ </Callout>
87
+
88
+ ### When to use
89
+
90
+ - **Conversational AI** — maintain a back-and-forth conversation with message history.
91
+ - **Thread monitoring** — watch a thread for updates and react to specific keywords or patterns.
92
+ - **Collaborative workflows** — track all messages in a thread to update external systems.
93
+
94
+ ### Example: Conversational AI with history
95
+
96
+ ```typescript title="lib/bot.ts" lineNumbers
97
+ bot.onSubscribedMessage(async (thread, message) => {
98
+ await thread.startTyping();
99
+
100
+ // Build conversation history from thread messages
101
+ const history = [];
102
+ for await (const msg of thread.allMessages) {
103
+ history.push({
104
+ role: msg.author.isMe ? "assistant" : "user",
105
+ content: msg.text,
106
+ });
107
+ }
108
+
109
+ const response = await generateAIResponse(history);
110
+ await thread.post(response);
111
+ });
112
+ ```
113
+
114
+ ### Example: Unsubscribe on keyword
115
+
116
+ ```typescript title="lib/bot.ts" lineNumbers
117
+ bot.onSubscribedMessage(async (thread, message) => {
118
+ if (message.text.toLowerCase().includes("stop")) {
119
+ await thread.unsubscribe();
120
+ await thread.post("Got it, I'll stop watching this thread.");
121
+ return;
122
+ }
123
+
124
+ // Handle other messages...
125
+ });
126
+ ```
127
+
128
+ ### Example: Thread state for multi-step flows
129
+
130
+ ```typescript title="lib/bot.ts" lineNumbers
131
+ interface ThreadState {
132
+ step: "awaiting_name" | "awaiting_email" | "done";
133
+ }
134
+
135
+ const bot = new Chat<typeof adapters, ThreadState>({ /* ...config */ });
136
+
137
+ bot.onNewMention(async (thread) => {
138
+ await thread.subscribe();
139
+ await thread.setState({ step: "awaiting_name" });
140
+ await thread.post("Let's get you set up. What's your name?");
141
+ });
142
+
143
+ bot.onSubscribedMessage(async (thread, message) => {
144
+ const state = await thread.state;
145
+
146
+ switch (state?.step) {
147
+ case "awaiting_name":
148
+ await thread.setState({ step: "awaiting_email" });
149
+ await thread.post(`Thanks, ${message.text}! What's your email?`);
150
+ break;
151
+ case "awaiting_email":
152
+ await thread.setState({ step: "done" });
153
+ await thread.post("All set! Your account has been created.");
154
+ await thread.unsubscribe();
155
+ break;
156
+ }
157
+ });
158
+ ```
159
+
160
+ ## Handling pattern matches
161
+
162
+ `onNewMessage` fires for messages matching a regex pattern in threads the bot is **not** subscribed to. Use it for keyword-triggered responses without requiring an @-mention.
163
+
164
+ ```typescript title="lib/bot.ts" lineNumbers
165
+ bot.onNewMessage(/^help$/i, async (thread, message) => {
166
+ await thread.post("Here's how I can help...");
167
+ });
168
+ ```
169
+
170
+ The first argument is a `RegExp` that's tested against the message text. Only messages in unsubscribed threads are evaluated.
171
+
172
+ ### When to use
173
+
174
+ - **Keyword triggers** — respond to specific words or phrases without requiring a mention.
175
+ - **Auto-responders** — detect common questions and provide instant answers.
176
+ - **Escalation detection** — watch for urgent language and alert the right people.
177
+
178
+ ### Example: FAQ auto-responder
179
+
180
+ ```typescript title="lib/bot.ts" lineNumbers
181
+ bot.onNewMessage(/\b(deploy|deployment|ship)\b/i, async (thread, message) => {
182
+ await thread.post(
183
+ "Deployments run automatically on push to `main`. " +
184
+ "Check status at https://dashboard.example.com/deploys"
185
+ );
186
+ });
187
+ ```
188
+
189
+ ### Example: Incident detection
190
+
191
+ ```typescript title="lib/bot.ts" lineNumbers
192
+ bot.onNewMessage(/\b(outage|down|incident|p[01])\b/i, async (thread, message) => {
193
+ await thread.subscribe();
194
+ await thread.post(
195
+ "I've flagged this as a potential incident and I'm monitoring this thread."
196
+ );
197
+
198
+ await notifyOnCallTeam({
199
+ channel: thread.channel.id,
200
+ reporter: message.author.fullName,
201
+ text: message.text,
202
+ });
203
+ });
204
+ ```
205
+
206
+ ## Handling reactions
207
+
208
+ `onReaction` fires when users add or remove emoji reactions to messages. You can handle all reactions or filter by specific emoji.
209
+
210
+ ```typescript title="lib/bot.ts" lineNumbers
211
+ import { emoji } from "chat";
212
+
213
+ // Handle specific emoji
214
+ bot.onReaction(["thumbs_up", "heart"], async (event) => {
215
+ if (!event.added) return;
216
+
217
+ await event.adapter.addReaction(
218
+ event.threadId,
219
+ event.messageId,
220
+ emoji.raised_hands
221
+ );
222
+ });
223
+
224
+ // Handle all reactions
225
+ bot.onReaction(async (event) => {
226
+ console.log(`${event.user.userName} ${event.added ? "added" : "removed"} ${event.emoji}`);
227
+ });
228
+ ```
229
+
230
+ ### ReactionEvent
231
+
232
+ | Property | Type | Description |
233
+ |----------|------|-------------|
234
+ | `emoji` | `EmojiValue` | Normalized emoji name for cross-platform comparison |
235
+ | `rawEmoji` | `string` | Platform-specific emoji string |
236
+ | `added` | `boolean` | `true` if added, `false` if removed |
237
+ | `user` | `Author` | The user who reacted |
238
+ | `message` | `Message` (optional) | The message that was reacted to |
239
+ | `thread` | `Thread` | Thread for posting replies |
240
+ | `messageId` | `string` | ID of the message that was reacted to |
241
+ | `threadId` | `string` | Thread ID |
242
+ | `adapter` | `Adapter` | The platform adapter |
243
+ | `raw` | `unknown` | Platform-specific event payload |
244
+
245
+ ### When to use
246
+
247
+ - **Approval workflows** — use thumbs up/down as lightweight approve/reject signals.
248
+ - **Bookmarking** — save messages to an external system when a specific emoji is added.
249
+ - **Polls and voting** — count reactions as votes.
250
+
251
+ ### Example: Approval workflow
252
+
253
+ ```typescript title="lib/bot.ts" lineNumbers
254
+ bot.onReaction(["thumbs_up", "thumbs_down"], async (event) => {
255
+ if (!event.added) return;
256
+
257
+ const approved = event.emoji === "thumbs_up";
258
+ const status = approved ? "approved" : "rejected";
259
+
260
+ await event.thread.post(
261
+ `Request ${status} by ${event.user.fullName}`
262
+ );
263
+
264
+ await updateRequestStatus(event.messageId, status, event.user.userId);
265
+ });
266
+ ```
267
+
268
+ ### Example: Save to external system
269
+
270
+ ```typescript title="lib/bot.ts" lineNumbers
271
+ bot.onReaction(["bookmark"], async (event) => {
272
+ if (!event.added || !event.message) return;
273
+
274
+ await saveToDatabase({
275
+ text: event.message.text,
276
+ savedBy: event.user.userId,
277
+ source: event.threadId,
278
+ });
279
+
280
+ await event.thread.post(
281
+ `Bookmarked by ${event.user.fullName}`
282
+ );
283
+ });
284
+ ```
285
+
286
+ ## Handling interactions
287
+
288
+ For button clicks, slash commands, and modal forms, see the dedicated guides:
289
+
290
+ - **[Slash Commands](/docs/slash-commands)** — handle `/command` invocations from the message composer.
291
+ - **[Actions](/docs/actions)** — handle button clicks and interactive card events.
292
+ - **[Modals](/docs/modals)** — collect structured input through modal dialogs with validation.
293
+
294
+ ## Handling Slack-specific events
295
+
296
+ These handlers are specific to the Slack platform and require the Slack adapter.
297
+
298
+ ### Handling assistant threads
299
+
300
+ `onAssistantThreadStarted` fires when a user opens a new assistant thread in Slack. Use it with the [Slack Assistants API](/adapters/slack#slack-assistants-api) to set suggested prompts and status indicators.
301
+
302
+ ```typescript title="lib/bot.ts" lineNumbers
303
+ bot.onAssistantThreadStarted(async (event) => {
304
+ const slack = bot.getAdapter("slack") as SlackAdapter;
305
+ await slack.setSuggestedPrompts(event.channelId, event.threadTs, [
306
+ { title: "Get started", message: "What can you help me with?" },
307
+ { title: "Summarize", message: "Summarize the current channel" },
308
+ ]);
309
+ });
310
+ ```
311
+
312
+ The `event` object includes:
313
+
314
+ | Property | Type | Description |
315
+ |----------|------|-------------|
316
+ | `channelId` | `string` | The assistant thread's channel |
317
+ | `threadTs` | `string` | Thread timestamp |
318
+ | `threadId` | `string` | Thread ID |
319
+ | `userId` | `string` | User who started the thread |
320
+ | `context` | `object` | Assistant context (channelId, teamId, enterpriseId, threadEntryPoint) |
321
+ | `adapter` | `Adapter` | The Slack adapter |
322
+
323
+ ### Handling assistant context changes
324
+
325
+ `onAssistantContextChanged` fires when the assistant context changes, for example when a user navigates to a different channel while the assistant thread is open.
326
+
327
+ ```typescript title="lib/bot.ts" lineNumbers
328
+ bot.onAssistantContextChanged(async (event) => {
329
+ const slack = bot.getAdapter("slack") as SlackAdapter;
330
+ await slack.setStatus(event.channelId, event.threadTs, "Updating context...");
331
+
332
+ // Update prompts based on new context
333
+ const channelName = event.context.channelId ?? "general";
334
+ await slack.setSuggestedPrompts(event.channelId, event.threadTs, [
335
+ { title: "Summarize", message: `Summarize #${channelName}` },
336
+ ]);
337
+ });
338
+ ```
339
+
340
+ ### Handling App Home opens
341
+
342
+ `onAppHomeOpened` fires when a user opens your bot's Home tab in Slack. Use it to publish a dynamic view.
343
+
344
+ ```typescript title="lib/bot.ts" lineNumbers
345
+ bot.onAppHomeOpened(async (event) => {
346
+ const slack = bot.getAdapter("slack") as SlackAdapter;
347
+ await slack.publishHomeView(event.userId, {
348
+ type: "home",
349
+ blocks: [
350
+ {
351
+ type: "section",
352
+ text: { type: "mrkdwn", text: `Welcome, <@${event.userId}>!` },
353
+ },
354
+ {
355
+ type: "actions",
356
+ elements: [
357
+ {
358
+ type: "button",
359
+ text: { type: "plain_text", text: "Open Dashboard" },
360
+ url: "https://dashboard.example.com",
361
+ },
362
+ ],
363
+ },
364
+ ],
365
+ });
366
+ });
367
+ ```
368
+
369
+ The `event` object includes:
370
+
371
+ | Property | Type | Description |
372
+ |----------|------|-------------|
373
+ | `userId` | `string` | User who opened the Home tab |
374
+ | `channelId` | `string` | Channel context |
375
+ | `adapter` | `Adapter` | The Slack adapter |
package/docs/index.mdx CHANGED
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  title: Introduction
3
- description: A unified SDK for building chat bots across Slack, Microsoft Teams, Google Chat, Discord, and more.
3
+ description: A unified SDK for building chat bots across Slack, Microsoft Teams, Google Chat, Discord, Telegram, and more.
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, GitHub, and Linear.
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, and Linear.
8
8
 
9
9
  ## Why Chat SDK?
10
10
 
@@ -55,6 +55,7 @@ Each adapter factory auto-detects credentials from environment variables (`SLACK
55
55
  | Microsoft Teams | `@chat-adapter/teams` | Yes | Read-only | Yes | No | Post+Edit | 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
+ | Telegram | `@chat-adapter/telegram` | Yes | Yes | Partial | No | Post+Edit | Yes |
58
59
  | GitHub | `@chat-adapter/github` | Yes | Yes | No | No | No | No |
59
60
  | Linear | `@chat-adapter/linear` | Yes | Yes | No | No | No | No |
60
61
 
@@ -79,6 +80,7 @@ The SDK is distributed as a set of packages you install based on your needs:
79
80
  | `@chat-adapter/teams` | Microsoft Teams adapter |
80
81
  | `@chat-adapter/gchat` | Google Chat adapter |
81
82
  | `@chat-adapter/discord` | Discord adapter |
83
+ | `@chat-adapter/telegram` | Telegram adapter |
82
84
  | `@chat-adapter/github` | GitHub Issues adapter |
83
85
  | `@chat-adapter/linear` | Linear Issues adapter |
84
86
  | `@chat-adapter/state-redis` | Redis state adapter (production) |
package/docs/meta.json CHANGED
@@ -3,7 +3,10 @@
3
3
  "pages": [
4
4
  "index",
5
5
  "getting-started",
6
+ "---Usage---",
6
7
  "usage",
8
+ "threads-messages-channels",
9
+ "handling-events",
7
10
  "posting-messages",
8
11
  "---Features---",
9
12
  "...",
@@ -15,6 +18,8 @@
15
18
  "---Guides---",
16
19
  "...guides",
17
20
  "---API Reference---",
18
- "...api"
21
+ "...api",
22
+ "---Contributing---",
23
+ "...contributing"
19
24
  ]
20
25
  }
@@ -16,7 +16,7 @@ related:
16
16
 
17
17
  The simplest option. Pass a string and it goes through as-is to the platform.
18
18
 
19
- ```typescript title="lib/bot.ts"
19
+ ```typescript title="lib/bot.ts" lineNumbers
20
20
  await thread.post("Hello world");
21
21
  ```
22
22
 
@@ -26,7 +26,7 @@ This sends the string directly without any formatting conversion.
26
26
 
27
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.
28
28
 
29
- ```typescript title="lib/bot.ts"
29
+ ```typescript title="lib/bot.ts" lineNumbers
30
30
  await thread.post({
31
31
  markdown: "**Bold**, _italic_, and `code`",
32
32
  });
@@ -38,7 +38,7 @@ Under the hood, the SDK parses the markdown into an mdast AST, then each adapter
38
38
 
39
39
  For programmatic control over message formatting, use the mdast AST builder functions exported from `chat`. This is the recommended approach for most use cases — it gives you fine-grained control without the overhead of card rendering.
40
40
 
41
- ```typescript title="lib/bot.ts"
41
+ ```typescript title="lib/bot.ts" lineNumbers
42
42
  import { root, paragraph, text, strong, link } from "chat";
43
43
 
44
44
  await thread.post({
@@ -71,7 +71,7 @@ await thread.post({
71
71
 
72
72
  You can also parse a markdown string into an AST, manipulate it, then send it:
73
73
 
74
- ```typescript title="lib/bot.ts"
74
+ ```typescript title="lib/bot.ts" lineNumbers
75
75
  import { parseMarkdown, stringifyMarkdown } from "chat";
76
76
 
77
77
  const ast = parseMarkdown("**Hello** world");
@@ -87,7 +87,7 @@ When you need interactive elements like buttons, dropdowns, or structured layout
87
87
 
88
88
  Use the function-call API for type-safe card construction:
89
89
 
90
- ```typescript title="lib/bot.ts"
90
+ ```typescript title="lib/bot.ts" lineNumbers
91
91
  import { Card, Text, Actions, Button } from "chat";
92
92
 
93
93
  await thread.post(
@@ -141,7 +141,7 @@ See the [Cards](/docs/cards) page for the full list of card components.
141
141
 
142
142
  Pass an `AsyncIterable<string>` (like the AI SDK's `textStream`) 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.
143
143
 
144
- ```typescript title="lib/bot.ts"
144
+ ```typescript title="lib/bot.ts" lineNumbers
145
145
  import { streamText } from "ai";
146
146
 
147
147
  const result = streamText({ model, prompt: message.text });
@@ -154,7 +154,7 @@ See the [Streaming](/docs/streaming) page for details on platform behavior and c
154
154
 
155
155
  Any structured message format (`markdown`, `ast`, or `card`) supports `files` for uploading attachments alongside the message:
156
156
 
157
- ```typescript title="lib/bot.ts"
157
+ ```typescript title="lib/bot.ts" lineNumbers
158
158
  await thread.post({
159
159
  markdown: "Here's the report:",
160
160
  files: [{ data: buffer, filename: "report.pdf" }],
@@ -7,10 +7,13 @@ prerequisites:
7
7
  related:
8
8
  - /docs/modals
9
9
  - /docs/adapters/slack
10
+ - /docs/adapters/discord
10
11
  ---
11
12
 
12
13
  Slash commands let users invoke your bot with `/command` syntax. Register handlers with `onSlashCommand` to respond.
13
14
 
15
+ Slash commands are supported on [Slack](/docs/adapters/slack) and [Discord](/docs/adapters/discord).
16
+
14
17
  ## Handle a specific command
15
18
 
16
19
  ```typescript title="lib/bot.ts" lineNumbers
@@ -108,3 +111,23 @@ bot.onModalSubmit("feedback_form", async (event) => {
108
111
  }
109
112
  });
110
113
  ```
114
+
115
+ ## Discord
116
+
117
+ Discord slash commands are received via [HTTP Interactions](/docs/adapters/discord#architecture-http-interactions-vs-gateway) — no Gateway connection is needed. The adapter automatically sends a deferred response to Discord, then resolves it when your handler calls `event.channel.post()`.
118
+
119
+ ### Subcommands
120
+
121
+ Discord supports subcommand groups and subcommands. The adapter flattens these into the `event.command` path:
122
+
123
+ | Discord command | `event.command` | `event.text` |
124
+ |-----------------|-----------------|--------------|
125
+ | `/status` | `/status` | `""` |
126
+ | `/project create --name="Acme"` | `/project create` | `Acme` |
127
+ | `/project issue list --status="open"` | `/project issue list` | `open` |
128
+
129
+ For full option details (names, types), use `event.raw` to access the original Discord interaction payload.
130
+
131
+ ### Registering commands with Discord
132
+
133
+ You need to register slash commands with the [Discord API](https://discord.com/developers/docs/interactions/application-commands) before they appear in the client. The Chat SDK handles incoming commands but does not register them for you.
@@ -1,9 +1,4 @@
1
1
  {
2
2
  "title": "State",
3
- "pages": [
4
- "index",
5
- "redis",
6
- "ioredis",
7
- "memory"
8
- ]
3
+ "pages": ["index", "redis", "ioredis", "memory"]
9
4
  }
@@ -58,6 +58,38 @@ const bot = new Chat({
58
58
  });
59
59
  ```
60
60
 
61
+ ### Disabling the placeholder message
62
+
63
+ By default, post+edit adapters send an initial `"..."` placeholder message before the first chunk arrives. You can disable this to wait for real content before posting:
64
+
65
+ ```typescript title="lib/bot.ts" lineNumbers
66
+ const bot = new Chat({
67
+ // ...
68
+ fallbackStreamingPlaceholderText: null,
69
+ });
70
+ ```
71
+
72
+ You can also customize the placeholder text:
73
+
74
+ ```typescript title="lib/bot.ts"
75
+ const bot = new Chat({
76
+ // ...
77
+ fallbackStreamingPlaceholderText: "Thinking...",
78
+ });
79
+ ```
80
+
81
+ ## Markdown healing
82
+
83
+ During streaming, chunks often arrive mid-word or mid-syntax — for example, `**bold` before the closing `**` arrives. The SDK automatically heals incomplete markdown in intermediate renders using [remend](https://www.npmjs.com/package/remend), so messages always display with correct formatting while streaming.
84
+
85
+ The final message uses the raw accumulated text without healing, so the original markdown is preserved.
86
+
87
+ ## Table buffering
88
+
89
+ When streaming content that contains GFM tables (e.g. from an LLM), the SDK automatically buffers potential table headers until a separator line (`|---|---|`) confirms them. This prevents tables from briefly flashing as raw pipe-delimited text before the table structure is complete.
90
+
91
+ This happens transparently — no configuration needed.
92
+
61
93
  ## Stop blocks (Slack only)
62
94
 
63
95
  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: