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,237 @@
1
+ ---
2
+ title: Threads, Messages, and Channels
3
+ description: Work with threads, messages, and channels across platforms.
4
+ type: guide
5
+ prerequisites:
6
+ - /docs/usage
7
+ related:
8
+ - /docs/handling-events
9
+ - /docs/posting-messages
10
+ ---
11
+
12
+ ## Threads
13
+
14
+ A `Thread` represents a conversation thread on any platform. It provides methods for posting messages, managing subscriptions, and accessing message history.
15
+
16
+ ### Post a message
17
+
18
+ ```typescript title="lib/bot.ts" lineNumbers
19
+ // Plain text
20
+ await thread.post("Hello world");
21
+
22
+ // Markdown (converted to each platform's format)
23
+ await thread.post("**Bold** and _italic_ text");
24
+
25
+ // Structured message with attachments
26
+ await thread.post({
27
+ markdown: "Here's a file:",
28
+ files: [{ data: buffer, filename: "report.pdf" }],
29
+ });
30
+ ```
31
+
32
+ ### Subscribe and unsubscribe
33
+
34
+ Subscriptions persist across restarts (stored in your state adapter). When a thread is subscribed, all messages route to `onSubscribedMessage`.
35
+
36
+ ```typescript title="lib/bot.ts" lineNumbers
37
+ await thread.subscribe();
38
+ await thread.unsubscribe();
39
+
40
+ const subscribed = await thread.isSubscribed();
41
+ ```
42
+
43
+ ### Typing indicator
44
+
45
+ ```typescript title="lib/bot.ts"
46
+ await thread.startTyping();
47
+ ```
48
+
49
+ <Callout type="info">
50
+ Not all platforms support typing indicators. The call is a no-op on unsupported platforms. See the [adapter feature matrix](/docs/adapters) for details.
51
+ </Callout>
52
+
53
+ ### Message history
54
+
55
+ Access recent messages or iterate through full history:
56
+
57
+ ```typescript title="lib/bot.ts" lineNumbers
58
+ // Cached messages from the webhook payload
59
+ const recent = thread.recentMessages;
60
+
61
+ // Newest first (auto-paginates)
62
+ for await (const msg of thread.messages) {
63
+ console.log(msg.text);
64
+ }
65
+
66
+ // Oldest first (auto-paginates)
67
+ for await (const msg of thread.allMessages) {
68
+ console.log(msg.text);
69
+ }
70
+ ```
71
+
72
+ ### Thread state
73
+
74
+ Store typed, per-thread state that persists across requests. Pass a generic type parameter to `Chat` to get typed thread state across all handlers:
75
+
76
+ ```typescript title="lib/bot.ts" lineNumbers
77
+ interface ThreadState {
78
+ aiMode?: boolean;
79
+ context?: string;
80
+ }
81
+
82
+ const bot = new Chat<typeof adapters, ThreadState>({
83
+ // ...config
84
+ });
85
+
86
+ bot.onNewMention(async (thread) => {
87
+ await thread.setState({ aiMode: true });
88
+
89
+ const state = await thread.state; // ThreadState | null
90
+ if (state?.aiMode) {
91
+ // AI mode is enabled
92
+ }
93
+ });
94
+ ```
95
+
96
+ State is stored in your state adapter with a 30-day TTL. Use `{ replace: true }` to replace state entirely instead of merging:
97
+
98
+ ```typescript title="lib/bot.ts"
99
+ await thread.setState({ aiMode: false }, { replace: true });
100
+ ```
101
+
102
+ ## Messages
103
+
104
+ Incoming messages are normalized across platforms into a consistent format:
105
+
106
+ | Property | Type | Description |
107
+ |----------|------|-------------|
108
+ | `id` | `string` | Platform message ID |
109
+ | `threadId` | `string` | Thread ID in `adapter:channel:thread` format |
110
+ | `text` | `string` | Plain text content |
111
+ | `formatted` | `Root` | mdast AST representation |
112
+ | `raw` | `unknown` | Original platform-specific payload |
113
+ | `author` | `Author` | Message author info |
114
+ | `metadata` | `MessageMetadata` | Timestamps and edit status |
115
+ | `attachments` | `Attachment[]` (optional) | File attachments |
116
+ | `isMention` | `boolean` (optional) | Whether the bot was @-mentioned |
117
+
118
+ ### Author
119
+
120
+ ```typescript lineNumbers
121
+ interface Author {
122
+ userId: string;
123
+ userName: string;
124
+ fullName: string;
125
+ isBot: boolean | "unknown";
126
+ isMe: boolean; // true if message is from the bot itself
127
+ }
128
+ ```
129
+
130
+ ### Sent messages
131
+
132
+ When you post a message, you get back a `SentMessage` with methods to edit, delete, and react:
133
+
134
+ ```typescript title="lib/bot.ts" lineNumbers
135
+ const sent = await thread.post("Processing...");
136
+ // Do some work...
137
+ await sent.edit("Done!");
138
+
139
+ // Or delete
140
+ await sent.delete();
141
+
142
+ // Add/remove reactions
143
+ await sent.addReaction(emoji.check);
144
+ await sent.removeReaction(emoji.check);
145
+ ```
146
+
147
+ ## Channels
148
+
149
+ A `Channel` represents the container that holds threads (e.g., a Slack channel, a Teams conversation). Navigate to a channel from a thread or get one directly:
150
+
151
+ ```typescript title="lib/bot.ts" lineNumbers
152
+ // From a thread
153
+ const channel = thread.channel;
154
+
155
+ // Directly by ID
156
+ const channel = bot.channel("slack:C123ABC");
157
+ ```
158
+
159
+ ### List threads
160
+
161
+ Iterate threads in a channel, most recently active first:
162
+
163
+ ```typescript title="lib/bot.ts" lineNumbers
164
+ for await (const thread of channel.threads()) {
165
+ console.log(thread.rootMessage.text, thread.replyCount);
166
+ }
167
+ ```
168
+
169
+ ### Channel messages
170
+
171
+ Iterate top-level messages (not thread replies):
172
+
173
+ ```typescript title="lib/bot.ts" lineNumbers
174
+ for await (const msg of channel.messages) {
175
+ console.log(msg.text);
176
+ }
177
+ ```
178
+
179
+ ### Post to a channel
180
+
181
+ Post a top-level message (not inside a thread):
182
+
183
+ ```typescript title="lib/bot.ts"
184
+ await channel.post("Hello channel!");
185
+ ```
186
+
187
+ ### Channel metadata
188
+
189
+ ```typescript title="lib/bot.ts"
190
+ const info = await channel.fetchMetadata();
191
+ console.log(info.name, info.memberCount);
192
+ ```
193
+
194
+ ## Thread ID format
195
+
196
+ All thread IDs follow the pattern `{adapter}:{channel}:{thread}`:
197
+
198
+ - **Slack**: `slack:C123ABC:1234567890.123456`
199
+ - **Teams**: `teams:{base64(conversationId)}:{base64(serviceUrl)}`
200
+ - **Google Chat**: `gchat:spaces/ABC123:{base64(threadName)}`
201
+ - **Discord**: `discord:{guildId}:{channelId}/{messageId}`
202
+
203
+ You typically don't need to construct these yourself — they're provided by the SDK in event handlers.
204
+
205
+ ## Logging
206
+
207
+ The `logger` option is optional — if omitted, Chat SDK uses `ConsoleLogger("info")` by default. Each adapter also creates its own child logger automatically.
208
+
209
+ ```typescript title="lib/bot.ts" lineNumbers
210
+ // Use defaults (ConsoleLogger at "info" level)
211
+ const bot = new Chat({
212
+ // ...
213
+ });
214
+
215
+ // Or set a specific log level
216
+ const bot = new Chat({
217
+ // ...
218
+ logger: "debug", // "debug" | "info" | "warn" | "error" | "silent"
219
+ });
220
+
221
+ // Or use a custom ConsoleLogger for child loggers
222
+ import { ConsoleLogger } from "chat";
223
+
224
+ const logger = new ConsoleLogger("info");
225
+ const bot = new Chat({
226
+ // ...
227
+ logger,
228
+ });
229
+ ```
230
+
231
+ You can pass child loggers to adapters for prefixed log output, but adapters create their own child loggers by default:
232
+
233
+ ```typescript title="lib/bot.ts"
234
+ createSlackAdapter({
235
+ logger: logger.child("slack"), // optional — auto-created if omitted
236
+ });
237
+ ```
package/docs/usage.mdx CHANGED
@@ -1,342 +1,148 @@
1
1
  ---
2
- title: Usage
3
- description: Event handlers, threads, channels, messages, and the core patterns of Chat SDK.
2
+ title: Creating a Chat Instance
3
+ description: Initialize the Chat class with adapters, state, and configuration options.
4
4
  type: guide
5
5
  prerequisites:
6
6
  - /docs/getting-started
7
+ related:
8
+ - /docs/handling-events
9
+ - /docs/adapters
10
+ - /docs/state
7
11
  ---
8
12
 
9
- Chat SDK uses an event-driven architecture. You register handlers for different event types, and the SDK routes incoming webhooks to the appropriate handler.
13
+ The `Chat` class is the main entry point for your bot. It coordinates adapters, routes events to your handlers, and manages thread state.
10
14
 
11
- ## Event handlers
12
-
13
- ### Mention handler
14
-
15
- `onNewMention` fires when your bot is @-mentioned in a thread it hasn't subscribed to yet. This is the primary entry point for new conversations.
15
+ ## Basic setup
16
16
 
17
17
  ```typescript title="lib/bot.ts" lineNumbers
18
- bot.onNewMention(async (thread, message) => {
19
- await thread.subscribe();
20
- await thread.post("Hello! I'm now listening to this thread.");
21
- });
22
- ```
23
-
24
- ### Subscribed message handler
18
+ import { Chat } from "chat";
19
+ import { createSlackAdapter } from "@chat-adapter/slack";
20
+ import { createRedisState } from "@chat-adapter/state-redis";
25
21
 
26
- `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`.
27
-
28
- ```typescript title="lib/bot.ts" lineNumbers
29
- bot.onSubscribedMessage(async (thread, message) => {
30
- // Check if the bot was @-mentioned specifically
31
- if (message.isMention) {
32
- await thread.post("You mentioned me!");
33
- return;
34
- }
35
-
36
- await thread.post(`Got your message: ${message.text}`);
22
+ const bot = new Chat({
23
+ userName: "mybot",
24
+ adapters: {
25
+ slack: createSlackAdapter(),
26
+ },
27
+ state: createRedisState(),
37
28
  });
38
- ```
39
-
40
- ### Pattern handler
41
29
 
42
- `onNewMessage` fires for messages matching a regex pattern in threads the bot is **not** subscribed to. Useful for keyword-triggered responses.
43
-
44
- ```typescript title="lib/bot.ts" lineNumbers
45
- bot.onNewMessage(/^help$/i, async (thread, message) => {
46
- await thread.post("Here's how I can help...");
30
+ bot.onNewMention(async (thread) => {
31
+ await thread.subscribe();
32
+ await thread.post("Hello! I'm listening to this thread.");
47
33
  });
48
34
  ```
49
35
 
50
- ### Slash command handler
36
+ 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.
51
37
 
52
- `onSlashCommand` fires when a user invokes a `/command` in the message composer. See [Slash Commands](/docs/slash-commands) for the full guide.
38
+ ## Multiple adapters
53
39
 
54
- ```typescript title="lib/bot.ts" lineNumbers
55
- bot.onSlashCommand("/status", async (event) => {
56
- await event.channel.post("All systems operational!");
57
- });
58
- ```
59
-
60
- ### Reaction handler
61
-
62
- `onReaction` fires when users add or remove emoji reactions to messages.
40
+ Register multiple adapters to deploy your bot across platforms simultaneously:
63
41
 
64
42
  ```typescript title="lib/bot.ts" lineNumbers
65
- import { emoji } from "chat";
43
+ import { Chat } from "chat";
44
+ import { createSlackAdapter } from "@chat-adapter/slack";
45
+ import { createTeamsAdapter } from "@chat-adapter/teams";
46
+ import { createDiscordAdapter } from "@chat-adapter/discord";
47
+ import { createRedisState } from "@chat-adapter/state-redis";
66
48
 
67
- bot.onReaction(["thumbs_up", "heart"], async (event) => {
68
- if (!event.added) return;
69
-
70
- await event.adapter.addReaction(
71
- event.threadId,
72
- event.messageId,
73
- emoji.raised_hands
74
- );
75
- });
76
- ```
77
-
78
- The `event` object includes:
79
-
80
- | Property | Type | Description |
81
- |----------|------|-------------|
82
- | `emoji` | `EmojiValue` | Normalized emoji for comparison |
83
- | `rawEmoji` | `string` | Platform-specific emoji string |
84
- | `added` | `boolean` | `true` if added, `false` if removed |
85
- | `user` | `Author` | The user who reacted |
86
- | `message` | `Message` (optional) | The message that was reacted to |
87
- | `thread` | `Thread` | Thread for posting replies |
88
- | `adapter` | `Adapter` | The platform adapter |
89
-
90
- ### Assistant thread handler
91
-
92
- `onAssistantThreadStarted` fires when a user opens a new assistant thread in Slack. Use it with the Slack Assistants API to set suggested prompts and status indicators. See [Slack Assistants API](/docs/adapters/slack#slack-assistants-api) for details.
93
-
94
- ```typescript title="lib/bot.ts" lineNumbers
95
- bot.onAssistantThreadStarted(async (event) => {
96
- const slack = bot.getAdapter("slack") as SlackAdapter;
97
- await slack.setSuggestedPrompts(event.channelId, event.threadTs, [
98
- { title: "Get started", message: "What can you help me with?" },
99
- ]);
49
+ const bot = new Chat({
50
+ userName: "mybot",
51
+ adapters: {
52
+ slack: createSlackAdapter(),
53
+ teams: createTeamsAdapter(),
54
+ discord: createDiscordAdapter(),
55
+ },
56
+ state: createRedisState(),
100
57
  });
101
58
  ```
102
59
 
103
- ### App Home handler
60
+ Your event handlers work identically across all registered adapters — the SDK normalizes messages, threads, and reactions into a consistent format.
104
61
 
105
- `onAppHomeOpened` fires when a user opens your bot's Home tab in Slack. Use it to publish a dynamic view.
62
+ ## Configuration options
106
63
 
107
- ```typescript title="lib/bot.ts" lineNumbers
108
- bot.onAppHomeOpened(async (event) => {
109
- const slack = bot.getAdapter("slack") as SlackAdapter;
110
- await slack.publishHomeView(event.userId, {
111
- type: "home",
112
- blocks: [{ type: "section", text: { type: "mrkdwn", text: "Welcome!" } }],
113
- });
114
- });
115
- ```
64
+ | Option | Type | Default | Description |
65
+ |--------|------|---------|-------------|
66
+ | `userName` | `string` | *required* | Default bot username across all adapters |
67
+ | `adapters` | `Record<string, Adapter>` | *required* | Map of adapter name to adapter instance |
68
+ | `state` | `StateAdapter` | *required* | State adapter for subscriptions and locking |
69
+ | `logger` | `Logger \| LogLevel` | `"info"` | Logger instance or log level (`"debug"`, `"info"`, `"warn"`, `"error"`, `"silent"`) |
70
+ | `dedupeTtlMs` | `number` | `300000` | TTL in ms for message deduplication (5 minutes) |
71
+ | `streamingUpdateIntervalMs` | `number` | `500` | Update interval in ms for post+edit streaming |
72
+ | `fallbackStreamingPlaceholderText` | `string \| null` | `"..."` | Placeholder text while streaming starts. Set to `null` to skip |
116
73
 
117
- ## Threads
74
+ ## Accessing adapters
118
75
 
119
- A `Thread` represents a conversation thread on any platform. It provides methods for posting messages, managing subscriptions, and accessing message history.
120
-
121
- ### Post a message
76
+ Use `getAdapter` to access platform-specific APIs when you need functionality beyond the unified interface:
122
77
 
123
78
  ```typescript title="lib/bot.ts" lineNumbers
124
- // Plain text
125
- await thread.post("Hello world");
126
-
127
- // Markdown (converted to each platform's format)
128
- await thread.post("**Bold** and _italic_ text");
79
+ import type { SlackAdapter } from "@chat-adapter/slack";
129
80
 
130
- // Structured message with attachments
131
- await thread.post({
132
- markdown: "Here's a file:",
133
- files: [{ data: buffer, filename: "report.pdf" }],
134
- });
81
+ const slack = bot.getAdapter("slack") as SlackAdapter;
82
+ await slack.setSuggestedPrompts(channelId, threadTs, [
83
+ { title: "Get started", message: "What can you help me with?" },
84
+ ]);
135
85
  ```
136
86
 
137
- ### Subscribe and unsubscribe
138
-
139
- Subscriptions persist across restarts (stored in your state adapter). When a thread is subscribed, all messages route to `onSubscribedMessage`.
140
-
141
- ```typescript title="lib/bot.ts" lineNumbers
142
- await thread.subscribe();
143
- await thread.unsubscribe();
87
+ ## Webhook routing
144
88
 
145
- const subscribed = await thread.isSubscribed();
146
- ```
89
+ The `webhooks` property provides type-safe handlers for each registered adapter. Wire these up to your HTTP framework's routes:
147
90
 
148
- ### Typing indicator
91
+ ```typescript title="app/api/webhooks/slack/route.ts" lineNumbers
92
+ import { bot } from "@/lib/bot";
149
93
 
150
- ```typescript title="lib/bot.ts"
151
- await thread.startTyping();
94
+ export const POST = bot.webhooks.slack;
152
95
  ```
153
96
 
154
- <Callout type="info">
155
- Not all platforms support typing indicators. The call is a no-op on unsupported platforms. See the [adapter feature matrix](/docs/adapters) for details.
156
- </Callout>
157
-
158
- ### Message history
97
+ ```typescript title="app/api/webhooks/teams/route.ts" lineNumbers
98
+ import { bot } from "@/lib/bot";
159
99
 
160
- Access recent messages or iterate through full history:
161
-
162
- ```typescript title="lib/bot.ts" lineNumbers
163
- // Cached messages from the webhook payload
164
- const recent = thread.recentMessages;
165
-
166
- // Newest first (auto-paginates)
167
- for await (const msg of thread.messages) {
168
- console.log(msg.text);
169
- }
170
-
171
- // Oldest first (auto-paginates)
172
- for await (const msg of thread.allMessages) {
173
- console.log(msg.text);
174
- }
100
+ export const POST = bot.webhooks.teams;
175
101
  ```
176
102
 
177
- ### Thread state
103
+ ## Lifecycle
178
104
 
179
- Store typed, per-thread state that persists across requests:
105
+ The Chat instance initializes lazily on the first webhook. You can also initialize manually:
180
106
 
181
107
  ```typescript title="lib/bot.ts" lineNumbers
182
- interface ThreadState {
183
- aiMode?: boolean;
184
- context?: string;
185
- }
186
-
187
- const bot = new Chat<typeof adapters, ThreadState>({
188
- // ...config
189
- });
190
-
191
- bot.onNewMention(async (thread) => {
192
- await thread.setState({ aiMode: true });
193
-
194
- const state = await thread.state; // ThreadState | null
195
- if (state?.aiMode) {
196
- // AI mode is enabled
197
- }
198
- });
199
- ```
200
-
201
- State is stored in your state adapter with a 30-day TTL. Use `{ replace: true }` to replace state entirely instead of merging:
202
-
203
- ```typescript title="lib/bot.ts"
204
- await thread.setState({ aiMode: false }, { replace: true });
205
- ```
206
-
207
- ## Messages
208
-
209
- Incoming messages are normalized across platforms into a consistent format:
210
-
211
- | Property | Type | Description |
212
- |----------|------|-------------|
213
- | `id` | `string` | Platform message ID |
214
- | `threadId` | `string` | Thread ID in `adapter:channel:thread` format |
215
- | `text` | `string` | Plain text content |
216
- | `formatted` | `Root` | mdast AST representation |
217
- | `raw` | `unknown` | Original platform-specific payload |
218
- | `author` | `Author` | Message author info |
219
- | `metadata` | `MessageMetadata` | Timestamps and edit status |
220
- | `attachments` | `Attachment[]` (optional) | File attachments |
221
- | `isMention` | `boolean` (optional) | Whether the bot was @-mentioned |
222
-
223
- ### Author
224
-
225
- ```typescript lineNumbers
226
- interface Author {
227
- userId: string;
228
- userName: string;
229
- fullName: string;
230
- isBot: boolean | "unknown";
231
- isMe: boolean; // true if message is from the bot itself
232
- }
108
+ await bot.initialize();
233
109
  ```
234
110
 
235
- ### Sent messages
236
-
237
- When you post a message, you get back a `SentMessage` with methods to edit, delete, and react:
111
+ For graceful shutdown (e.g. in serverless teardown), call `shutdown`:
238
112
 
239
113
  ```typescript title="lib/bot.ts" lineNumbers
240
- const sent = await thread.post("Processing...");
241
- // Do some work...
242
- await sent.edit("Done!");
243
-
244
- // Or delete
245
- await sent.delete();
246
-
247
- // Add/remove reactions
248
- await sent.addReaction(emoji.check);
249
- await sent.removeReaction(emoji.check);
114
+ await bot.shutdown();
250
115
  ```
251
116
 
252
- ## Channels
117
+ ## Singleton pattern
253
118
 
254
- A `Channel` represents the container that holds threads (e.g., a Slack channel, a Teams conversation). Navigate to a channel from a thread or get one directly:
119
+ Register a singleton when you need to access the Chat instance from multiple files:
255
120
 
256
121
  ```typescript title="lib/bot.ts" lineNumbers
257
- // From a thread
258
- const channel = thread.channel;
259
-
260
- // Directly by ID
261
- const channel = bot.channel("slack:C123ABC");
122
+ const bot = new Chat({ /* ...config */ }).registerSingleton();
123
+ export default bot;
262
124
  ```
263
125
 
264
- ### List threads
265
-
266
- Iterate threads in a channel, most recently active first:
126
+ ```typescript title="lib/utils.ts" lineNumbers
127
+ import { Chat } from "chat";
267
128
 
268
- ```typescript title="lib/bot.ts" lineNumbers
269
- for await (const thread of channel.threads()) {
270
- console.log(thread.rootMessage.text, thread.replyCount);
271
- }
129
+ const bot = Chat.getSingleton();
272
130
  ```
273
131
 
274
- ### Channel messages
132
+ ## Direct messaging
275
133
 
276
- Iterate top-level messages (not thread replies):
134
+ Open a DM thread with a user by passing their platform user ID or an `Author` object:
277
135
 
278
136
  ```typescript title="lib/bot.ts" lineNumbers
279
- for await (const msg of channel.messages) {
280
- console.log(msg.text);
281
- }
137
+ const dm = await bot.openDM("slack:U123ABC");
138
+ await dm.post("Hey! Just wanted to follow up on your request.");
282
139
  ```
283
140
 
284
- ### Post to a channel
285
-
286
- Post a top-level message (not inside a thread):
141
+ ## Channel access
287
142
 
288
- ```typescript title="lib/bot.ts"
289
- await channel.post("Hello channel!");
290
- ```
291
-
292
- ### Channel metadata
293
-
294
- ```typescript title="lib/bot.ts"
295
- const info = await channel.fetchMetadata();
296
- console.log(info.name, info.memberCount);
297
- ```
298
-
299
- ## Thread ID format
300
-
301
- All thread IDs follow the pattern `{adapter}:{channel}:{thread}`:
302
-
303
- - **Slack**: `slack:C123ABC:1234567890.123456`
304
- - **Teams**: `teams:{base64(conversationId)}:{base64(serviceUrl)}`
305
- - **Google Chat**: `gchat:spaces/ABC123:{base64(threadName)}`
306
- - **Discord**: `discord:{guildId}:{channelId}/{messageId}`
307
-
308
- You typically don't need to construct these yourself — they're provided by the SDK in event handlers.
309
-
310
- ## Logging
311
-
312
- The `logger` option is optional — if omitted, Chat SDK uses `ConsoleLogger("info")` by default. Each adapter also creates its own child logger automatically.
143
+ Get a channel directly by its ID:
313
144
 
314
145
  ```typescript title="lib/bot.ts" lineNumbers
315
- // Use defaults (ConsoleLogger at "info" level)
316
- const bot = new Chat({
317
- // ...
318
- });
319
-
320
- // Or set a specific log level
321
- const bot = new Chat({
322
- // ...
323
- logger: "debug", // "debug" | "info" | "warn" | "error" | "silent"
324
- });
325
-
326
- // Or use a custom ConsoleLogger for child loggers
327
- import { ConsoleLogger } from "chat";
328
-
329
- const logger = new ConsoleLogger("info");
330
- const bot = new Chat({
331
- // ...
332
- logger,
333
- });
334
- ```
335
-
336
- You can pass child loggers to adapters for prefixed log output, but adapters create their own child loggers by default:
337
-
338
- ```typescript title="lib/bot.ts"
339
- createSlackAdapter({
340
- logger: logger.child("slack"), // optional — auto-created if omitted
341
- });
146
+ const channel = bot.channel("slack:C123ABC");
147
+ await channel.post("Announcement: deploy complete!");
342
148
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chat",
3
- "version": "4.14.0",
3
+ "version": "4.16.0",
4
4
  "description": "Unified chat abstraction for Slack, Teams, Google Chat, and Discord",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -30,14 +30,15 @@
30
30
  "remark-gfm": "^4.0.0",
31
31
  "remark-parse": "^11.0.0",
32
32
  "remark-stringify": "^11.0.0",
33
+ "remend": "^1.2.1",
33
34
  "unified": "^11.0.5"
34
35
  },
35
36
  "devDependencies": {
36
37
  "@types/mdast": "^4.0.4",
37
- "@types/node": "^22.10.2",
38
+ "@types/node": "^25.3.2",
38
39
  "tsup": "^8.3.5",
39
40
  "typescript": "^5.7.2",
40
- "vitest": "^2.1.8"
41
+ "vitest": "^4.0.18"
41
42
  },
42
43
  "repository": {
43
44
  "type": "git",