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.
- package/dist/{chunk-OPV5U4WG.js → chunk-V25FKIIL.js} +44 -1
- package/dist/index.d.ts +485 -33
- package/dist/index.js +862 -135
- package/dist/{jsx-runtime-DxATbnrP.d.ts → jsx-runtime-DxGwoLu2.d.ts} +49 -5
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +1 -1
- package/docs/actions.mdx +52 -1
- package/docs/adapters.mdx +43 -37
- package/docs/api/cards.mdx +4 -0
- package/docs/api/chat.mdx +172 -6
- package/docs/api/index.mdx +2 -0
- package/docs/api/markdown.mdx +28 -5
- package/docs/api/message.mdx +58 -1
- package/docs/api/meta.json +2 -0
- package/docs/api/modals.mdx +50 -0
- package/docs/api/postable-message.mdx +55 -1
- package/docs/api/thread.mdx +33 -3
- package/docs/api/transcripts.mdx +220 -0
- package/docs/cards.mdx +6 -0
- package/docs/concurrency.mdx +4 -0
- package/docs/contributing/building.mdx +73 -1
- package/docs/contributing/publishing.mdx +33 -0
- package/docs/conversation-history.mdx +137 -0
- package/docs/direct-messages.mdx +13 -4
- package/docs/ephemeral-messages.mdx +1 -1
- package/docs/error-handling.mdx +15 -3
- package/docs/files.mdx +2 -1
- package/docs/getting-started.mdx +1 -11
- package/docs/index.mdx +7 -5
- package/docs/meta.json +14 -5
- package/docs/modals.mdx +97 -1
- package/docs/posting-messages.mdx +7 -3
- package/docs/streaming.mdx +74 -18
- package/docs/subject.mdx +53 -0
- package/docs/threads-messages-channels.mdx +43 -0
- package/docs/usage.mdx +11 -2
- package/package.json +3 -2
- package/resources/guides/create-a-discord-support-bot-with-nuxt-and-redis.md +180 -0
- package/resources/guides/how-to-build-a-slack-bot-with-next-js-and-redis.md +134 -0
- package/resources/guides/how-to-build-an-ai-agent-for-slack-with-chat-sdk-and-ai-sdk.md +220 -0
- package/resources/guides/run-and-track-deploys-from-slack.md +270 -0
- package/resources/guides/ship-a-github-code-review-bot-with-hono-and-redis.md +147 -0
- package/resources/guides/triage-form-submissions-with-chat-sdk.md +178 -0
- package/resources/templates.json +19 -0
- package/docs/guides/code-review-hono.mdx +0 -241
- package/docs/guides/discord-nuxt.mdx +0 -227
- package/docs/guides/durable-chat-sessions-nextjs.mdx +0 -337
- package/docs/guides/meta.json +0 -10
- package/docs/guides/scheduled-posts-neon.mdx +0 -447
- package/docs/guides/slack-nextjs.mdx +0 -234
package/docs/api/markdown.mdx
CHANGED
|
@@ -15,6 +15,25 @@ import {
|
|
|
15
15
|
} from "chat";
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
## Type re-exports
|
|
19
|
+
|
|
20
|
+
The chat package re-exports mdast's union and content types so adapters and downstream code can build exhaustively-typed AST walkers without depending on `mdast` directly:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import type { Nodes, Root, Content } from "chat";
|
|
24
|
+
|
|
25
|
+
function render(node: Nodes): string {
|
|
26
|
+
switch (node.type) {
|
|
27
|
+
case "text": return node.value;
|
|
28
|
+
case "strong": return node.children.map(render).join("");
|
|
29
|
+
// ...
|
|
30
|
+
default: throw new Error(`Unhandled: ${node satisfies never}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Adapters use this pattern to make the type checker reject the build when a new mdast node type is introduced upstream.
|
|
36
|
+
|
|
18
37
|
## Node builders
|
|
19
38
|
|
|
20
39
|
### root
|
|
@@ -222,7 +241,7 @@ const value = getNodeValue(node); // string | undefined
|
|
|
222
241
|
|
|
223
242
|
### tableToAscii
|
|
224
243
|
|
|
225
|
-
Render an mdast `Table` node as a padded ASCII table string. Used by adapters that lack native table support (
|
|
244
|
+
Render an mdast `Table` node as a padded ASCII table string. Used by adapters that lack native table support (Google Chat, Discord, Telegram).
|
|
226
245
|
|
|
227
246
|
```typescript
|
|
228
247
|
import { parseMarkdown, tableToAscii, isTableNode } from "chat";
|
|
@@ -261,17 +280,21 @@ The SDK uses mdast as the canonical format and each adapter converts it to the p
|
|
|
261
280
|
|
|
262
281
|
| Feature | Slack | Teams | Google Chat |
|
|
263
282
|
|---------|-------|-------|-------------|
|
|
264
|
-
| Bold |
|
|
283
|
+
| Bold | `**text**` | `**text**` | `*text*` |
|
|
265
284
|
| Italic | `_text_` | `_text_` | `_text_` |
|
|
266
|
-
| Strikethrough |
|
|
285
|
+
| Strikethrough | `~~text~~` | `~~text~~` | `~text~` |
|
|
267
286
|
| Code | `` `code` `` | `` `code` `` | `` `code` `` |
|
|
268
287
|
| Code blocks | ```` ``` ```` | ```` ``` ```` | ```` ``` ```` |
|
|
269
|
-
| Links |
|
|
288
|
+
| Links | `[text](url)` | `[text](url)` | `[text](url)` |
|
|
270
289
|
| Lists | Supported | Supported | Supported |
|
|
271
290
|
| Blockquotes | `>` | `>` | Simulated with `>` prefix |
|
|
272
|
-
| Tables |
|
|
291
|
+
| Tables | Native (markdown_text) | Native GFM | ASCII fallback |
|
|
273
292
|
| Mentions | `<@USER>` | `<at>name</at>` | `<users/{id}>` |
|
|
274
293
|
|
|
294
|
+
<Callout type="info">
|
|
295
|
+
Slack accepts standard markdown via the `markdown_text` field on `chat.postMessage` and friends, so the SDK passes markdown through directly. Incoming Slack messages still arrive as legacy mrkdwn (`*bold*`, `<url|text>`) and are parsed transparently. If you need to send mrkdwn yourself, use `{ raw: "..." }`.
|
|
296
|
+
</Callout>
|
|
297
|
+
|
|
275
298
|
<Callout type="info">
|
|
276
299
|
You don't need to worry about these differences when using the SDK — the AST builders and `parseMarkdown` handle conversion automatically. This table is useful if you're working with `raw` platform payloads or debugging formatting issues.
|
|
277
300
|
</Callout>
|
package/docs/api/message.mdx
CHANGED
|
@@ -54,6 +54,10 @@ import { Message } from "chat";
|
|
|
54
54
|
description: 'Whether the bot was @-mentioned in this message.',
|
|
55
55
|
type: 'boolean | undefined',
|
|
56
56
|
},
|
|
57
|
+
subject: {
|
|
58
|
+
description: 'Resolves the parent resource (issue, PR) this message is about. Returns null on chat platforms. See [Message Subject](/docs/subject).',
|
|
59
|
+
type: 'Promise<MessageSubject | null>',
|
|
60
|
+
},
|
|
57
61
|
}}
|
|
58
62
|
/>
|
|
59
63
|
|
|
@@ -149,6 +153,10 @@ All adapters return `false` if the bot ID isn't known yet. This is a safe defaul
|
|
|
149
153
|
description: 'Fetch the attachment data. Handles platform auth automatically.',
|
|
150
154
|
type: '() => Promise<Buffer> | undefined',
|
|
151
155
|
},
|
|
156
|
+
fetchMetadata: {
|
|
157
|
+
description: 'Platform-specific IDs for reconstructing fetchData after serialization (e.g. WhatsApp mediaId, Telegram fileId).',
|
|
158
|
+
type: 'Record<string, string> | undefined',
|
|
159
|
+
},
|
|
152
160
|
}}
|
|
153
161
|
/>
|
|
154
162
|
|
|
@@ -196,6 +204,55 @@ When using [`toAiMessages()`](/docs/api/to-ai-messages), link metadata is automa
|
|
|
196
204
|
| Slack | URLs from `rich_text` blocks or `<url>` text patterns | Slack message links (`*.slack.com/archives/...`) |
|
|
197
205
|
| Others | Not yet — `links` is always `[]` | — |
|
|
198
206
|
|
|
207
|
+
## MessageSubject
|
|
208
|
+
|
|
209
|
+
Returned by `message.subject` on platforms with parent resources. See [Message Subject](/docs/subject) for usage.
|
|
210
|
+
|
|
211
|
+
<TypeTable
|
|
212
|
+
type={{
|
|
213
|
+
type: {
|
|
214
|
+
description: 'Resource kind, e.g. "issue" or "pull_request".',
|
|
215
|
+
type: 'string',
|
|
216
|
+
},
|
|
217
|
+
id: {
|
|
218
|
+
description: 'Resource identifier (e.g. "ENG-123" or "42").',
|
|
219
|
+
type: 'string',
|
|
220
|
+
},
|
|
221
|
+
title: {
|
|
222
|
+
description: 'Resource title.',
|
|
223
|
+
type: 'string | undefined',
|
|
224
|
+
},
|
|
225
|
+
description: {
|
|
226
|
+
description: 'Full description/body in markdown.',
|
|
227
|
+
type: 'string | undefined',
|
|
228
|
+
},
|
|
229
|
+
status: {
|
|
230
|
+
description: 'Current status (e.g. "In Progress", "open").',
|
|
231
|
+
type: 'string | undefined',
|
|
232
|
+
},
|
|
233
|
+
url: {
|
|
234
|
+
description: 'Web URL to the resource.',
|
|
235
|
+
type: 'string | undefined',
|
|
236
|
+
},
|
|
237
|
+
author: {
|
|
238
|
+
description: 'Resource creator.',
|
|
239
|
+
type: '{ id: string; name: string } | undefined',
|
|
240
|
+
},
|
|
241
|
+
assignee: {
|
|
242
|
+
description: 'Current assignee.',
|
|
243
|
+
type: '{ id: string; name: string } | undefined',
|
|
244
|
+
},
|
|
245
|
+
labels: {
|
|
246
|
+
description: 'Labels/tags.',
|
|
247
|
+
type: 'string[] | undefined',
|
|
248
|
+
},
|
|
249
|
+
raw: {
|
|
250
|
+
description: 'Full platform API response.',
|
|
251
|
+
type: 'unknown',
|
|
252
|
+
},
|
|
253
|
+
}}
|
|
254
|
+
/>
|
|
255
|
+
|
|
199
256
|
## Serialization
|
|
200
257
|
|
|
201
258
|
Messages can be serialized for workflow engines and external systems.
|
|
@@ -208,4 +265,4 @@ const json = message.toJSON();
|
|
|
208
265
|
const restored = Message.fromJSON(json);
|
|
209
266
|
```
|
|
210
267
|
|
|
211
|
-
The serialized format converts `Date` fields to ISO strings and omits non-serializable fields like `data` buffers and `fetchData` functions.
|
|
268
|
+
The serialized format converts `Date` fields to ISO strings and omits non-serializable fields like `data` buffers and `fetchData` functions. The `fetchMetadata` field is preserved so that adapters can reconstruct `fetchData` when the message is rehydrated from a queue.
|
package/docs/api/meta.json
CHANGED
package/docs/api/modals.mdx
CHANGED
|
@@ -54,6 +54,10 @@ bot.onAction("open-form", async (event) => {
|
|
|
54
54
|
type: 'boolean',
|
|
55
55
|
default: 'false',
|
|
56
56
|
},
|
|
57
|
+
callbackUrl: {
|
|
58
|
+
description: 'URL to POST form values to when the modal is submitted.',
|
|
59
|
+
type: 'string',
|
|
60
|
+
},
|
|
57
61
|
privateMetadata: {
|
|
58
62
|
description: 'Arbitrary string passed through the modal lifecycle (e.g., JSON context).',
|
|
59
63
|
type: 'string',
|
|
@@ -167,6 +171,52 @@ Select({
|
|
|
167
171
|
}}
|
|
168
172
|
/>
|
|
169
173
|
|
|
174
|
+
## ExternalSelect
|
|
175
|
+
|
|
176
|
+
Dropdown that loads options dynamically from a handler as the user types. Slack-only. Pair with [`bot.onOptionsLoad`](/docs/api/chat#onoptionsload) to supply options. See [Modals → ExternalSelect](/docs/modals#externalselect) for a full example, grouped-options support, and Slack setup notes.
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
ExternalSelect({
|
|
180
|
+
id: "assignee",
|
|
181
|
+
label: "Assignee",
|
|
182
|
+
placeholder: "Search people",
|
|
183
|
+
minQueryLength: 1,
|
|
184
|
+
initialOption: { label: "Alice", value: "U123" },
|
|
185
|
+
})
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
<TypeTable
|
|
189
|
+
type={{
|
|
190
|
+
id: {
|
|
191
|
+
description: 'Input ID — used as the key in event.values.',
|
|
192
|
+
type: 'string',
|
|
193
|
+
},
|
|
194
|
+
label: {
|
|
195
|
+
description: 'Label displayed above the select.',
|
|
196
|
+
type: 'string',
|
|
197
|
+
},
|
|
198
|
+
placeholder: {
|
|
199
|
+
description: 'Placeholder text.',
|
|
200
|
+
type: 'string',
|
|
201
|
+
},
|
|
202
|
+
minQueryLength: {
|
|
203
|
+
description: 'Minimum characters before the loader fires (Slack default: 3).',
|
|
204
|
+
type: 'number',
|
|
205
|
+
},
|
|
206
|
+
initialOption: {
|
|
207
|
+
description: 'Pre-selected option when the modal opens. Unlike static Select where initialOption is a value string, ExternalSelect needs the full label/value object since the loader has not run yet.',
|
|
208
|
+
type: '{ label: string, value: string }',
|
|
209
|
+
},
|
|
210
|
+
optional: {
|
|
211
|
+
description: 'Whether the field can be left empty.',
|
|
212
|
+
type: 'boolean',
|
|
213
|
+
default: 'false',
|
|
214
|
+
},
|
|
215
|
+
}}
|
|
216
|
+
/>
|
|
217
|
+
|
|
218
|
+
The loader registered via `bot.onOptionsLoad("assignee", handler)` returns either a flat `SelectOptionElement[]` or `OptionsLoadGroup[]` (`{ label, options }[]`) for grouped options.
|
|
219
|
+
|
|
170
220
|
## RadioSelect
|
|
171
221
|
|
|
172
222
|
Radio button group for mutually exclusive choices.
|
|
@@ -7,9 +7,14 @@ type: reference
|
|
|
7
7
|
`PostableMessage` is the union of all message formats accepted by `thread.post()` and `sent.edit()`.
|
|
8
8
|
|
|
9
9
|
```typescript
|
|
10
|
-
type PostableMessage =
|
|
10
|
+
type PostableMessage =
|
|
11
|
+
| AdapterPostableMessage
|
|
12
|
+
| AsyncIterable<string | StreamChunk | StreamEvent>
|
|
13
|
+
| PostableObject;
|
|
11
14
|
```
|
|
12
15
|
|
|
16
|
+
`PostableObject` covers `Plan` (mutable task lists) and `StreamingPlan` (streams with platform-specific options) — both documented below.
|
|
17
|
+
|
|
13
18
|
## String
|
|
14
19
|
|
|
15
20
|
Raw text passed through as-is to the platform.
|
|
@@ -133,6 +138,55 @@ await thread.post({
|
|
|
133
138
|
}}
|
|
134
139
|
/>
|
|
135
140
|
|
|
141
|
+
## Plan
|
|
142
|
+
|
|
143
|
+
A `Plan` is a step-by-step task list that mutates after posting. Each `addTask` / `updateTask` / `complete` call re-renders the same message in place. See [Plan API](/docs/streaming#plan-api) for full usage.
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import { Plan } from "chat";
|
|
147
|
+
|
|
148
|
+
const plan = new Plan({ initialMessage: "Researching options..." });
|
|
149
|
+
await thread.post(plan);
|
|
150
|
+
await plan.addTask({ title: "Look up records" });
|
|
151
|
+
await plan.complete({ completeMessage: "Done!" });
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Adapters that don't support `PostableObject` editing render the plan as fallback text and ignore subsequent mutations.
|
|
155
|
+
|
|
156
|
+
## StreamingPlan
|
|
157
|
+
|
|
158
|
+
Wraps an async iterable with platform-specific streaming options. Use this when you need to pass options like task grouping or stop blocks through `thread.post()`. See [Streaming with options](/docs/streaming#streaming-with-options).
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { StreamingPlan } from "chat";
|
|
162
|
+
|
|
163
|
+
const planned = new StreamingPlan(stream, {
|
|
164
|
+
groupTasks: "plan",
|
|
165
|
+
endWith: [feedbackBlock],
|
|
166
|
+
updateIntervalMs: 750,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
await thread.post(planned);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
<TypeTable
|
|
173
|
+
type={{
|
|
174
|
+
groupTasks: {
|
|
175
|
+
description: 'Slack: render task_update chunks as `"plan"` (single grouped block) or `"timeline"` (inline cards, default).',
|
|
176
|
+
type: '"plan" | "timeline" | undefined',
|
|
177
|
+
},
|
|
178
|
+
endWith: {
|
|
179
|
+
description: 'Slack: Block Kit elements appended when the stream stops (e.g. retry / feedback buttons).',
|
|
180
|
+
type: 'unknown[] | undefined',
|
|
181
|
+
},
|
|
182
|
+
updateIntervalMs: {
|
|
183
|
+
description: 'Post+edit adapters: minimum interval between update cycles in ms.',
|
|
184
|
+
type: 'number | undefined',
|
|
185
|
+
default: '500',
|
|
186
|
+
},
|
|
187
|
+
}}
|
|
188
|
+
/>
|
|
189
|
+
|
|
136
190
|
## AsyncIterable (streaming)
|
|
137
191
|
|
|
138
192
|
An async iterable of strings, `StreamChunk` objects, or stream events. The SDK streams the message in real time using platform-native APIs where available.
|
package/docs/api/thread.mdx
CHANGED
|
@@ -4,7 +4,7 @@ description: Represents a conversation thread with methods for posting, subscrib
|
|
|
4
4
|
type: reference
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
A `Thread` is provided to your event handlers and represents a conversation thread on any platform. You
|
|
7
|
+
A `Thread` is provided to your event handlers and represents a conversation thread on any platform. You can also create thread handles directly using `chat.thread()` or `chat.openDM()`.
|
|
8
8
|
|
|
9
9
|
## Properties
|
|
10
10
|
|
|
@@ -39,7 +39,7 @@ A `Thread` is provided to your event handlers and represents a conversation thre
|
|
|
39
39
|
|
|
40
40
|
## post
|
|
41
41
|
|
|
42
|
-
Post a message to the thread. Accepts strings, structured messages, cards, and
|
|
42
|
+
Post a message to the thread. Accepts strings, structured messages, cards, streams, and `PostableObject` instances (`Plan`, `StreamingPlan`).
|
|
43
43
|
|
|
44
44
|
```typescript
|
|
45
45
|
// Plain text
|
|
@@ -56,11 +56,19 @@ await thread.post(Card({ title: "Hi", children: [Text("Hello")] }));
|
|
|
56
56
|
|
|
57
57
|
// Stream (fullStream recommended for multi-step agents)
|
|
58
58
|
await thread.post(result.fullStream);
|
|
59
|
+
|
|
60
|
+
// Plan (mutable task list)
|
|
61
|
+
const plan = new Plan({ initialMessage: "Working..." });
|
|
62
|
+
await thread.post(plan);
|
|
63
|
+
await plan.addTask({ title: "Step 1" });
|
|
64
|
+
|
|
65
|
+
// Streaming with platform options
|
|
66
|
+
await thread.post(new StreamingPlan(stream, { groupTasks: "plan" }));
|
|
59
67
|
```
|
|
60
68
|
|
|
61
69
|
**Parameters:** `message: string | PostableMessage | CardJSXElement`
|
|
62
70
|
|
|
63
|
-
**Returns:** `Promise<SentMessage>` —
|
|
71
|
+
**Returns:** `Promise<SentMessage | PostableObject>` — for plain messages and streams, a `SentMessage` with `edit()`, `delete()`, `addReaction()`, and `removeReaction()` methods; for `Plan` / `StreamingPlan` inputs, the same object is returned so you can keep mutating it.
|
|
64
72
|
|
|
65
73
|
See [Posting Messages](/docs/posting-messages) for details on each format.
|
|
66
74
|
|
|
@@ -114,6 +122,28 @@ await scheduled.cancel();
|
|
|
114
122
|
Streaming and file uploads are not supported in scheduled messages.
|
|
115
123
|
</Callout>
|
|
116
124
|
|
|
125
|
+
## getParticipants
|
|
126
|
+
|
|
127
|
+
Get the unique human participants in a thread. Returns deduplicated authors, excluding all bots. Useful for subscribing only to 1:1 conversations and unsubscribing when others join.
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
const participants = await thread.getParticipants();
|
|
131
|
+
|
|
132
|
+
// Subscribe only when one person is talking to the bot
|
|
133
|
+
if (participants.length === 1) {
|
|
134
|
+
await thread.subscribe();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Unsubscribe when the thread becomes a group conversation
|
|
138
|
+
if (participants.length > 1) {
|
|
139
|
+
await thread.unsubscribe();
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
<Callout type="warn">
|
|
144
|
+
Each call fetches the full message history to find all participants. On threads with long history this makes multiple API calls to the platform. Consider checking `message.author` against a known set before calling `getParticipants()` on every incoming message.
|
|
145
|
+
</Callout>
|
|
146
|
+
|
|
117
147
|
## subscribe / unsubscribe
|
|
118
148
|
|
|
119
149
|
Manage thread subscriptions. Subscribed threads route all messages to `onSubscribedMessage` handlers.
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Transcripts
|
|
3
|
+
description: Cross-platform per-user transcript persistence — configuration, methods, and entry shape.
|
|
4
|
+
type: reference
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
`bot.transcripts` provides per-user message persistence keyed by a stable cross-platform identifier. See the [Conversation history](/docs/conversation-history) guide for usage patterns.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { Chat } from "chat";
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Configuration
|
|
14
|
+
|
|
15
|
+
`transcripts` and `identity` are configured on `ChatConfig`. Both must be set together — passing `transcripts` without `identity` throws at construction.
|
|
16
|
+
|
|
17
|
+
### ChatConfig.transcripts
|
|
18
|
+
|
|
19
|
+
<TypeTable
|
|
20
|
+
type={{
|
|
21
|
+
retention: {
|
|
22
|
+
description: 'List TTL, refreshed on every append. Accepts ms or a duration string ("45s", "30m", "6h", "7d"). Omit for no expiry.',
|
|
23
|
+
type: 'number | DurationString | undefined',
|
|
24
|
+
},
|
|
25
|
+
maxPerUser: {
|
|
26
|
+
description: 'Hard cap per user. Older entries are evicted on append.',
|
|
27
|
+
type: 'number',
|
|
28
|
+
default: '200',
|
|
29
|
+
},
|
|
30
|
+
storeFormatted: {
|
|
31
|
+
description: 'Persist the mdast `formatted` field alongside `text`. Off by default to keep storage small.',
|
|
32
|
+
type: 'boolean',
|
|
33
|
+
default: 'false',
|
|
34
|
+
},
|
|
35
|
+
}}
|
|
36
|
+
/>
|
|
37
|
+
|
|
38
|
+
### ChatConfig.identity
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
identity: (context: IdentityContext) => string | null | Promise<string | null>;
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Called once per inbound message during dispatch. The result is attached to the `Message` instance as `message.userKey`. Return `null` to skip persistence for an event.
|
|
45
|
+
|
|
46
|
+
#### IdentityContext
|
|
47
|
+
|
|
48
|
+
<TypeTable
|
|
49
|
+
type={{
|
|
50
|
+
adapter: {
|
|
51
|
+
description: 'Adapter name (e.g. "slack", "discord").',
|
|
52
|
+
type: 'string',
|
|
53
|
+
},
|
|
54
|
+
author: {
|
|
55
|
+
description: 'Message author info.',
|
|
56
|
+
type: 'Author',
|
|
57
|
+
},
|
|
58
|
+
message: {
|
|
59
|
+
description: 'The inbound message.',
|
|
60
|
+
type: 'Message',
|
|
61
|
+
},
|
|
62
|
+
}}
|
|
63
|
+
/>
|
|
64
|
+
|
|
65
|
+
## Methods
|
|
66
|
+
|
|
67
|
+
Access via `bot.transcripts`. Throws if `transcripts` was not configured on the `Chat` instance.
|
|
68
|
+
|
|
69
|
+
### append
|
|
70
|
+
|
|
71
|
+
Persist a `Message` (typically the inbound user message) or an `AppendInput` (typically a bot reply you just posted).
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
append(
|
|
75
|
+
thread: Postable,
|
|
76
|
+
message: Message | AppendInput,
|
|
77
|
+
options?: AppendOptions,
|
|
78
|
+
): Promise<TranscriptEntry | null>;
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
When `message` is a `Message`, `userKey` is read from the instance. If it's `undefined` (the resolver returned `null`), the call is a no-op and returns `null`. When `message` is an `AppendInput`, `options.userKey` is required.
|
|
82
|
+
|
|
83
|
+
#### AppendInput
|
|
84
|
+
|
|
85
|
+
<TypeTable
|
|
86
|
+
type={{
|
|
87
|
+
role: {
|
|
88
|
+
description: 'Role tag for the entry.',
|
|
89
|
+
type: '"user" | "assistant" | "system"',
|
|
90
|
+
},
|
|
91
|
+
text: {
|
|
92
|
+
description: 'Plain-text body.',
|
|
93
|
+
type: 'string',
|
|
94
|
+
},
|
|
95
|
+
formatted: {
|
|
96
|
+
description: 'Optional mdast AST. Only stored when `transcripts.storeFormatted` is true.',
|
|
97
|
+
type: 'FormattedContent | undefined',
|
|
98
|
+
},
|
|
99
|
+
platformMessageId: {
|
|
100
|
+
description: 'Platform-native message ID, when known.',
|
|
101
|
+
type: 'string | undefined',
|
|
102
|
+
},
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
|
|
106
|
+
#### AppendOptions
|
|
107
|
+
|
|
108
|
+
<TypeTable
|
|
109
|
+
type={{
|
|
110
|
+
userKey: {
|
|
111
|
+
description: 'Required when appending an `AppendInput` (assistant or system role); ignored when appending a `Message`.',
|
|
112
|
+
type: 'string | undefined',
|
|
113
|
+
},
|
|
114
|
+
}}
|
|
115
|
+
/>
|
|
116
|
+
|
|
117
|
+
### list
|
|
118
|
+
|
|
119
|
+
Returns entries in chronological order (oldest first). When `limit` is set, returns the newest `N` entries — still chronologically.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
list(query: ListQuery): Promise<TranscriptEntry[]>;
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
#### ListQuery
|
|
126
|
+
|
|
127
|
+
<TypeTable
|
|
128
|
+
type={{
|
|
129
|
+
userKey: {
|
|
130
|
+
description: 'Cross-platform user key.',
|
|
131
|
+
type: 'string',
|
|
132
|
+
},
|
|
133
|
+
limit: {
|
|
134
|
+
description: 'Maximum entries returned. Cannot exceed `maxPerUser` because that is the storage cap.',
|
|
135
|
+
type: 'number',
|
|
136
|
+
default: '50',
|
|
137
|
+
},
|
|
138
|
+
platforms: {
|
|
139
|
+
description: 'Filter to a subset of adapter names.',
|
|
140
|
+
type: 'string[] | undefined',
|
|
141
|
+
},
|
|
142
|
+
threadId: {
|
|
143
|
+
description: 'Filter to a single thread.',
|
|
144
|
+
type: 'string | undefined',
|
|
145
|
+
},
|
|
146
|
+
roles: {
|
|
147
|
+
description: 'Filter to specific roles.',
|
|
148
|
+
type: '("user" | "assistant" | "system")[] | undefined',
|
|
149
|
+
},
|
|
150
|
+
}}
|
|
151
|
+
/>
|
|
152
|
+
|
|
153
|
+
### count
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
count(query: CountQuery): Promise<number>;
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Returns the total number of entries stored under the user key. `CountQuery` has a single field, `userKey: string`.
|
|
160
|
+
|
|
161
|
+
### delete
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
delete(target: { userKey: string }): Promise<{ deleted: number }>;
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Wipes every entry stored under the user key. Returns the count that was removed. Single-entry and time-range deletes are not supported — the underlying `appendToList` primitive can't support them safely under concurrent writes.
|
|
168
|
+
|
|
169
|
+
## TranscriptEntry
|
|
170
|
+
|
|
171
|
+
Returned by `append` and `list`.
|
|
172
|
+
|
|
173
|
+
<TypeTable
|
|
174
|
+
type={{
|
|
175
|
+
id: {
|
|
176
|
+
description: 'UUID assigned by the SDK at append time.',
|
|
177
|
+
type: 'string',
|
|
178
|
+
},
|
|
179
|
+
userKey: {
|
|
180
|
+
description: 'Cross-platform user key from the IdentityResolver.',
|
|
181
|
+
type: 'string',
|
|
182
|
+
},
|
|
183
|
+
role: {
|
|
184
|
+
description: 'Role tag.',
|
|
185
|
+
type: '"user" | "assistant" | "system"',
|
|
186
|
+
},
|
|
187
|
+
text: {
|
|
188
|
+
description: 'Plain-text body — canonical for prompt building.',
|
|
189
|
+
type: 'string',
|
|
190
|
+
},
|
|
191
|
+
formatted: {
|
|
192
|
+
description: 'mdast AST. Only present when `transcripts.storeFormatted` is true.',
|
|
193
|
+
type: 'FormattedContent | undefined',
|
|
194
|
+
},
|
|
195
|
+
platform: {
|
|
196
|
+
description: 'Originating adapter name.',
|
|
197
|
+
type: 'string',
|
|
198
|
+
},
|
|
199
|
+
threadId: {
|
|
200
|
+
description: 'Originating thread ID.',
|
|
201
|
+
type: 'string',
|
|
202
|
+
},
|
|
203
|
+
platformMessageId: {
|
|
204
|
+
description: 'Platform-native message ID, when known.',
|
|
205
|
+
type: 'string | undefined',
|
|
206
|
+
},
|
|
207
|
+
timestamp: {
|
|
208
|
+
description: 'ms-since-epoch, set at append time on the SDK side.',
|
|
209
|
+
type: 'number',
|
|
210
|
+
},
|
|
211
|
+
}}
|
|
212
|
+
/>
|
|
213
|
+
|
|
214
|
+
## Storage
|
|
215
|
+
|
|
216
|
+
Backed by `StateAdapter.appendToList` / `getList` / `delete`. Every built-in state adapter (`memory`, `redis`, `ioredis`, `pg`) supports these primitives.
|
|
217
|
+
|
|
218
|
+
Entries are stored under `transcripts:user:{userKey}` as a capped list. `appendToList` is atomic, so concurrent inbound messages don't race.
|
|
219
|
+
|
|
220
|
+
The `retention` value is applied as the list TTL and refreshed on every append. With `retention: "30d"`, a user who hasn't talked to the bot in 30 days has their transcript expire automatically.
|
package/docs/cards.mdx
CHANGED
|
@@ -115,6 +115,12 @@ Set `actionType="modal"` to indicate the button opens a [modal](/docs/modals). T
|
|
|
115
115
|
<Button id="open-feedback" actionType="modal">Give Feedback</Button>
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
+
Optional `callbackUrl` causes the action data to be POSTed to a URL when clicked. See [Callback URLs](/docs/actions#callback-urls) for details.
|
|
119
|
+
|
|
120
|
+
```tsx title="lib/bot.tsx"
|
|
121
|
+
<Button callbackUrl={webhook.url} id="approve" style="primary">Approve</Button>
|
|
122
|
+
```
|
|
123
|
+
|
|
118
124
|
### CardLink
|
|
119
125
|
|
|
120
126
|
Inline hyperlink rendered as text. Unlike `LinkButton` (which must be inside `Actions`), `CardLink` can be placed directly in a card alongside other content.
|
package/docs/concurrency.mdx
CHANGED
|
@@ -124,6 +124,10 @@ const bot = new Chat({
|
|
|
124
124
|
| `debounceMs` | debounce | `1500` | Debounce window in milliseconds |
|
|
125
125
|
| `maxConcurrent` | concurrent | `Infinity` | Max concurrent handlers per thread |
|
|
126
126
|
|
|
127
|
+
<Callout type="warn">
|
|
128
|
+
`maxConcurrent` only applies to the `concurrent` strategy. Pairing it with any other strategy logs a warning and the value is ignored. Setting `maxConcurrent` to a value less than `1` throws at construction time — `0` would deadlock the strategy and is rejected up front.
|
|
129
|
+
</Callout>
|
|
130
|
+
|
|
127
131
|
## MessageContext
|
|
128
132
|
|
|
129
133
|
All handler types (`onNewMention`, `onSubscribedMessage`, `onNewMessage`) accept an optional `MessageContext` as their last parameter. It is only populated when using the `queue` strategy and messages were skipped.
|