@botpress/adk-cli 1.19.0-beta.3 → 1.19.0-beta.5
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/assets/ui-dist/assets/{index-CSTz-oTK.js → index-B3b7PPgV.js} +4 -4
- package/{dist/index-DNqXHLNi-27nvpkee.js → assets/ui-dist/assets/index-BESZeTO5.js} +2 -2
- package/{dist/index-DAztRXeA-vr85xnxs.css → assets/ui-dist/assets/index-DIFGtVmI.css} +1 -1
- package/{dist/index-DfjXY3Oo-szsanhnt.js → assets/ui-dist/assets/index-a3ZL0_Jr.js} +2 -2
- package/assets/ui-dist/index.html +2 -2
- package/dist/{README-wjb7z7f0.md → README-824wa4jj.md} +1 -1
- package/dist/SKILL-75ym6tcj.md +257 -0
- package/dist/SKILL-chnwzmt1.md +548 -0
- package/dist/SKILL-d4bs53y3.md +244 -0
- package/dist/SKILL-h4b2f1n9.md +114 -0
- package/dist/SKILL-hbw5nt95.md +111 -0
- package/dist/actions-h7hxxvxr.md +823 -0
- package/dist/adk-build-emhwd0tg.md +39 -0
- package/dist/adk-debug-yrrdtpmb.md +23 -0
- package/dist/adk-dev-console-7z9rtjpy.md +17 -0
- package/dist/adk-document-ras6zzzh.md +68 -0
- package/dist/adk-eval-vx76dwq0.md +17 -0
- package/dist/adk-explain-fcsaw852.md +22 -0
- package/dist/adk-init-9kmxmbbe.md +30 -0
- package/dist/adk-ship-dybzn884.md +29 -0
- package/dist/adk-test-xq67savs.md +27 -0
- package/dist/adk-validate-1p4skv4m.md +31 -0
- package/dist/advanced-patterns-z86m9h1a.md +669 -0
- package/dist/agent-config-1aqvkrgf.md +450 -0
- package/dist/agent-steps-zj4c1n9t.md +115 -0
- package/dist/agent0-assets/skills/adk/SKILL.md +5 -5
- package/dist/agent0-assets/skills/adk/references/cli.md +265 -24
- package/dist/agent0-assets/skills/adk-debugger/SKILL.md +5 -5
- package/dist/agent0-assets/skills/adk-debugger/references/common-failures.md +1 -1
- package/dist/agent0-assets/skills/adk-debugger/references/debug-workflow.md +3 -3
- package/dist/agent0-assets/skills/adk-debugger/references/traces-and-logs.md +15 -7
- package/dist/assets-q7jt8r2q.md +237 -0
- package/dist/autonomous-execution-fymc12e5.md +747 -0
- package/dist/cli-c31e06yr.md +1334 -0
- package/dist/cli.js +963 -548
- package/dist/commands.json +24 -3
- package/dist/common-failures-ht29w2db.md +231 -0
- package/dist/component-registry-bbpw0see.md +43 -0
- package/dist/context-api-760cvh0h.md +461 -0
- package/dist/conversation-analysis-8ka4nrm4.md +420 -0
- package/dist/conversation-lifecycle-4seqqjc1.md +311 -0
- package/dist/conversations-gwfvxaee.md +1111 -0
- package/dist/custom-components-y5wdgpey.md +167 -0
- package/dist/debug-workflow-vz84q80v.md +239 -0
- package/dist/desk-k54ktvsd.md +852 -0
- package/dist/doc-standards-ck6ax2jd.md +124 -0
- package/dist/eval-format-7n9brja4.md +354 -0
- package/dist/evals-y1zcr1mc.md +11 -0
- package/dist/explain-config-1yaq3zzd.md +424 -0
- package/dist/files-dzt0wxn3.md +918 -0
- package/dist/{index-gqxgqnyz.html → index-3t354nbw.html} +2 -2
- package/dist/{index-CSTz-oTK-mdfh47r8.js → index-B3b7PPgV-zetkb01w.js} +4 -4
- package/{assets/ui-dist/assets/index-DNqXHLNi.js → dist/index-BESZeTO5-2rpmcefa.js} +2 -2
- package/{assets/ui-dist/assets/index-DAztRXeA.css → dist/index-DIFGtVmI-d4dhjg97.css} +1 -1
- package/{assets/ui-dist/assets/index-DfjXY3Oo.js → dist/index-a3ZL0_Jr-q145h35p.js} +2 -2
- package/dist/integration-actions-xang7tma.md +659 -0
- package/dist/integrations-k5ss07f4.md +266 -0
- package/dist/interfaces-6t0bpxs4.md +97 -0
- package/dist/knowledge-bases-20rg8p5c.md +456 -0
- package/dist/llm-debugging-76ng8rzm.md +209 -0
- package/dist/mcp-server-n1asyzhv.md +112 -0
- package/dist/messages-gkkg0b54.md +769 -0
- package/dist/model-configuration-f273kgd0.md +201 -0
- package/dist/multi-agent-dashboard-1mdfjdts.md +70 -0
- package/dist/pages-xn18xn73.md +273 -0
- package/dist/patterns-mistakes-5vb99dhf.md +469 -0
- package/dist/plugins-ttnx8f49.md +280 -0
- package/dist/tables-x45t36dd.md +1136 -0
- package/dist/tags-46td3qrg.md +516 -0
- package/dist/test-patterns-hj47w4nc.md +355 -0
- package/dist/testing-workflow-2vkp932f.md +225 -0
- package/dist/tools-wk7bq24z.md +581 -0
- package/dist/trace-summarization-hdyx1c2e.md +313 -0
- package/dist/traces-and-logs-f2kjfh19.md +203 -0
- package/dist/triggers-4j2cc86x.md +719 -0
- package/dist/ui-server-entry.js +977 -162
- package/dist/workflow-steps-mrdrf1c5.md +851 -0
- package/dist/workflows-gadx3s69.md +1408 -0
- package/dist/zai-agent-reference-e6644aad.md +454 -0
- package/dist/zai-complete-guide-mnna5d6f.md +1408 -0
- package/package.json +4 -4
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
# Messages & Events
|
|
2
|
+
|
|
3
|
+
Understanding how to send messages and events is critical for building conversational AI agents. This guide covers all message types, when to use them, and common pitfalls.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
**Quick Links:**
|
|
8
|
+
- [Core Concepts](#core-concepts) - Messages vs Events, Agnostic vs Channel-Specific
|
|
9
|
+
- [Sending Messages in Conversations](#sending-messages-in-conversations) - Using `conversation.send()`
|
|
10
|
+
- [Message Metadata](#message-metadata-chat-integration) - Adding custom data to messages
|
|
11
|
+
- [All Message Types](#all-message-types) - Complete reference (text, image, file, card, etc.)
|
|
12
|
+
- [File Messages & Metadata](#file-message-metadata) - File-specific metadata usage
|
|
13
|
+
- [Custom Component (Webchat)](#custom-component-webchat) - Webchat-only custom UI components
|
|
14
|
+
- [Accessing Messages on Frontend](#accessing-messages-on-the-frontend) - Webchat SDK integration
|
|
15
|
+
- [Sending from Workflows](#sending-messages-from-workflows) - Using `client.createMessage()`
|
|
16
|
+
- [Sending Events (Ephemeral)](#sending-events-ephemeral) - Non-persistent notifications
|
|
17
|
+
- [Channel Compatibility](#channel-compatibility) - What works where
|
|
18
|
+
- [Common Mistakes](#common-mistakes) - Pitfalls to avoid
|
|
19
|
+
- [Best Practices](#best-practices) - Recommended patterns
|
|
20
|
+
|
|
21
|
+
## Core Concepts
|
|
22
|
+
|
|
23
|
+
### Messages vs Events
|
|
24
|
+
|
|
25
|
+
**Messages** are persistent conversation history items that users can see:
|
|
26
|
+
- Appear in chat history
|
|
27
|
+
- Survive page reloads
|
|
28
|
+
- Part of conversation context
|
|
29
|
+
- Sent using `conversation.send()` in conversations
|
|
30
|
+
|
|
31
|
+
**Events** are ephemeral signals that don't persist:
|
|
32
|
+
- NOT in conversation history
|
|
33
|
+
- Lost on page reload
|
|
34
|
+
- Used for real-time notifications
|
|
35
|
+
- Sent using integration actions (e.g., `bp.webchat.actions.customEvent()`)
|
|
36
|
+
|
|
37
|
+
### Agnostic vs Channel-Specific
|
|
38
|
+
|
|
39
|
+
**Agnostic Messages** work across multiple channels (defined in `@botpress/sdk`):
|
|
40
|
+
- `text`, `image`, `audio`, `video`, `file`, `location`
|
|
41
|
+
- `card`, `carousel`, `choice`, `dropdown`, `bloc`, `markdown`
|
|
42
|
+
|
|
43
|
+
**Channel-Specific Messages** only work in specific integrations:
|
|
44
|
+
- `customComponent` (webchat only)
|
|
45
|
+
- Platform-specific fields (e.g., Slack threads)
|
|
46
|
+
|
|
47
|
+
## Sending Messages in Conversations
|
|
48
|
+
|
|
49
|
+
### Basic Syntax
|
|
50
|
+
|
|
51
|
+
In conversation handlers, use `conversation.send()`:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { Conversation } from "@botpress/runtime";
|
|
55
|
+
|
|
56
|
+
export const Chat = new Conversation({
|
|
57
|
+
channel: "chat.channel",
|
|
58
|
+
|
|
59
|
+
async handler({ message, conversation }) {
|
|
60
|
+
// Send a message
|
|
61
|
+
await conversation.send({
|
|
62
|
+
type: "text",
|
|
63
|
+
payload: { text: "Hello!" }
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**CRITICAL**: Never use `client.createMessage()` directly in conversations. Always use `conversation.send()`.
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// ❌ WRONG - Don't use client directly
|
|
73
|
+
await client.createMessage({
|
|
74
|
+
conversationId: conversation.id,
|
|
75
|
+
type: "text",
|
|
76
|
+
payload: { text: "Hello" }
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// ✅ CORRECT - Use conversation.send()
|
|
80
|
+
await conversation.send({
|
|
81
|
+
type: "text",
|
|
82
|
+
payload: { text: "Hello" }
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Why Use `conversation.send()`?
|
|
87
|
+
|
|
88
|
+
- Automatically handles conversation context
|
|
89
|
+
- Type-safe for the channel
|
|
90
|
+
- Manages message tracking
|
|
91
|
+
- Integrates with ADK runtime
|
|
92
|
+
|
|
93
|
+
## Message Metadata (Chat Integration)
|
|
94
|
+
|
|
95
|
+
The **chat** integration supports an optional `metadata` field on **all message types**. This allows you to attach arbitrary custom data that will be available to the frontend.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
await conversation.send({
|
|
99
|
+
type: "text",
|
|
100
|
+
payload: {
|
|
101
|
+
text: "Your order has been confirmed",
|
|
102
|
+
metadata: {
|
|
103
|
+
orderId: "order_123",
|
|
104
|
+
customerId: "cust_456",
|
|
105
|
+
trackingNumber: "TRACK789",
|
|
106
|
+
internalRef: "ref_abc",
|
|
107
|
+
// Any custom data you need
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Metadata Schema**:
|
|
114
|
+
- Type: `Record<string, any>` (key-value pairs)
|
|
115
|
+
- Keys: strings
|
|
116
|
+
- Values: any type (string, number, boolean, object, array)
|
|
117
|
+
- Optional on all message types
|
|
118
|
+
|
|
119
|
+
**Use Cases**:
|
|
120
|
+
- File identifiers for tracking
|
|
121
|
+
- Unique keys for deduplication
|
|
122
|
+
- Custom display properties
|
|
123
|
+
- Analytics tracking data
|
|
124
|
+
- Frontend state management
|
|
125
|
+
|
|
126
|
+
**Channel Support**:
|
|
127
|
+
- ✅ **chat**: Full metadata support on all message types
|
|
128
|
+
- ❓ **webchat**: Check generated types after adding dependency
|
|
129
|
+
- ❓ **Other channels**: Varies by integration
|
|
130
|
+
|
|
131
|
+
## All Message Types
|
|
132
|
+
|
|
133
|
+
### Text
|
|
134
|
+
|
|
135
|
+
Basic text message - works everywhere.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
await conversation.send({
|
|
139
|
+
type: "text",
|
|
140
|
+
payload: {
|
|
141
|
+
text: "Hello, world!"
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Markdown
|
|
147
|
+
|
|
148
|
+
Formatted text with markdown - **channel support varies**.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
await conversation.send({
|
|
152
|
+
type: "markdown",
|
|
153
|
+
payload: {
|
|
154
|
+
markdown: "**Bold** _italic_ `code` [link](https://example.com)"
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Note**: Markdown support depends on the channel. Use `text` for guaranteed cross-channel compatibility.
|
|
160
|
+
|
|
161
|
+
### Image
|
|
162
|
+
|
|
163
|
+
Display an image from a URL.
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
await conversation.send({
|
|
167
|
+
type: "image",
|
|
168
|
+
payload: {
|
|
169
|
+
imageUrl: "https://example.com/image.png"
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Audio
|
|
175
|
+
|
|
176
|
+
Play an audio file.
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
await conversation.send({
|
|
180
|
+
type: "audio",
|
|
181
|
+
payload: {
|
|
182
|
+
audioUrl: "https://example.com/audio.mp3"
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Video
|
|
188
|
+
|
|
189
|
+
Display a video player.
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
await conversation.send({
|
|
193
|
+
type: "video",
|
|
194
|
+
payload: {
|
|
195
|
+
videoUrl: "https://example.com/video.mp4"
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### File
|
|
201
|
+
|
|
202
|
+
Share a downloadable file with optional metadata.
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
await conversation.send({
|
|
206
|
+
type: "file",
|
|
207
|
+
payload: {
|
|
208
|
+
fileUrl: "https://example.com/document.pdf",
|
|
209
|
+
title: "Invoice", // optional
|
|
210
|
+
metadata: { // optional - see Message Metadata section
|
|
211
|
+
fileId: "file_123",
|
|
212
|
+
uniqueKey: "invoice-2024-001"
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Location
|
|
219
|
+
|
|
220
|
+
Share a geographic location.
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
await conversation.send({
|
|
224
|
+
type: "location",
|
|
225
|
+
payload: {
|
|
226
|
+
latitude: 40.7128,
|
|
227
|
+
longitude: -74.0060,
|
|
228
|
+
address: "New York, NY", // optional
|
|
229
|
+
title: "Meeting Location" // optional
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Card
|
|
235
|
+
|
|
236
|
+
Rich card with image, title, and action buttons.
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
await conversation.send({
|
|
240
|
+
type: "card",
|
|
241
|
+
payload: {
|
|
242
|
+
title: "Product Name",
|
|
243
|
+
subtitle: "Product description", // optional
|
|
244
|
+
imageUrl: "https://example.com/product.jpg", // optional
|
|
245
|
+
actions: [
|
|
246
|
+
{
|
|
247
|
+
action: "postback", // or "url" or "say"
|
|
248
|
+
label: "Buy Now",
|
|
249
|
+
value: "buy_product_123"
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
action: "url",
|
|
253
|
+
label: "Learn More",
|
|
254
|
+
value: "https://example.com/product"
|
|
255
|
+
}
|
|
256
|
+
]
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Action Types**:
|
|
262
|
+
- `postback`: Sends a value back to the bot (triggers new message)
|
|
263
|
+
- `url`: Opens a URL in browser
|
|
264
|
+
- `say`: Makes user "say" text (sends as user message)
|
|
265
|
+
|
|
266
|
+
### Carousel
|
|
267
|
+
|
|
268
|
+
Multiple cards in a scrollable carousel.
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
await conversation.send({
|
|
272
|
+
type: "carousel",
|
|
273
|
+
payload: {
|
|
274
|
+
items: [
|
|
275
|
+
{
|
|
276
|
+
title: "Product 1",
|
|
277
|
+
subtitle: "Description 1",
|
|
278
|
+
imageUrl: "https://example.com/product1.jpg",
|
|
279
|
+
actions: [
|
|
280
|
+
{ action: "postback", label: "Select", value: "product_1" }
|
|
281
|
+
]
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
title: "Product 2",
|
|
285
|
+
subtitle: "Description 2",
|
|
286
|
+
imageUrl: "https://example.com/product2.jpg",
|
|
287
|
+
actions: [
|
|
288
|
+
{ action: "postback", label: "Select", value: "product_2" }
|
|
289
|
+
]
|
|
290
|
+
}
|
|
291
|
+
]
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Choice (Buttons)
|
|
297
|
+
|
|
298
|
+
Quick reply buttons for user selection.
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
await conversation.send({
|
|
302
|
+
type: "choice",
|
|
303
|
+
payload: {
|
|
304
|
+
text: "What would you like to do?",
|
|
305
|
+
options: [
|
|
306
|
+
{ label: "Option 1", value: "opt1" },
|
|
307
|
+
{ label: "Option 2", value: "opt2" },
|
|
308
|
+
{ label: "Option 3", value: "opt3" }
|
|
309
|
+
],
|
|
310
|
+
disableFreeText: false // optional - prevent typing
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Dropdown
|
|
316
|
+
|
|
317
|
+
Dropdown/select menu for user selection.
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
await conversation.send({
|
|
321
|
+
type: "dropdown",
|
|
322
|
+
payload: {
|
|
323
|
+
text: "Select your country:",
|
|
324
|
+
options: [
|
|
325
|
+
{ label: "United States", value: "us" },
|
|
326
|
+
{ label: "Canada", value: "ca" },
|
|
327
|
+
{ label: "United Kingdom", value: "uk" }
|
|
328
|
+
],
|
|
329
|
+
disableFreeText: true // optional
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Bloc
|
|
335
|
+
|
|
336
|
+
Multiple messages grouped together (composite message).
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
await conversation.send({
|
|
340
|
+
type: "bloc",
|
|
341
|
+
payload: {
|
|
342
|
+
items: [
|
|
343
|
+
{
|
|
344
|
+
type: "text",
|
|
345
|
+
payload: { text: "Here's your order summary:" }
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
type: "image",
|
|
349
|
+
payload: { imageUrl: "https://example.com/product.jpg" }
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
type: "text",
|
|
353
|
+
payload: { text: "Total: $99.99" }
|
|
354
|
+
}
|
|
355
|
+
]
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**Allowed item types in bloc**:
|
|
361
|
+
- `text`, `markdown`, `image`, `audio`, `video`, `file`, `location`
|
|
362
|
+
|
|
363
|
+
### Custom Component (Webchat)
|
|
364
|
+
|
|
365
|
+
Send a custom React component in webchat conversations using the `customComponent` message type:
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
import { WelcomeBannerComponent } from '../components'
|
|
369
|
+
|
|
370
|
+
await conversation.send({
|
|
371
|
+
type: "customComponent",
|
|
372
|
+
payload: {
|
|
373
|
+
component: WelcomeBannerComponent,
|
|
374
|
+
props: {},
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Components are `.bp.tsx` files registered in `src/components/index.ts` with `new CustomComponent()`. See the component registry reference for the full creation flow.
|
|
380
|
+
|
|
381
|
+
When components are listed in a `Conversation`'s `components` array, the LLM can also yield them autonomously during `execute()` — no explicit `send()` needed.
|
|
382
|
+
|
|
383
|
+
**Note**: Custom components only work in webchat channels. For other channels, use agnostic message types like `card` or `carousel`.
|
|
384
|
+
|
|
385
|
+
## Accessing Messages on the Frontend
|
|
386
|
+
|
|
387
|
+
Messages (including metadata) are available to frontend clients via the webchat SDK.
|
|
388
|
+
|
|
389
|
+
### Listening to New Messages
|
|
390
|
+
|
|
391
|
+
```javascript
|
|
392
|
+
// React to new messages as they arrive
|
|
393
|
+
window.botpressWebChat.onMessage((message) => {
|
|
394
|
+
console.log('New message:', message);
|
|
395
|
+
|
|
396
|
+
if (message.type === 'file') {
|
|
397
|
+
console.log('File URL:', message.payload.fileUrl);
|
|
398
|
+
console.log('File title:', message.payload.title);
|
|
399
|
+
console.log('Metadata:', message.payload.metadata);
|
|
400
|
+
|
|
401
|
+
// Access custom metadata
|
|
402
|
+
const { fileId, uniqueKey, customData } = message.payload.metadata || {};
|
|
403
|
+
|
|
404
|
+
// Fetch file contents if needed
|
|
405
|
+
fetch(message.payload.fileUrl)
|
|
406
|
+
.then(response => response.text())
|
|
407
|
+
.then(content => {
|
|
408
|
+
console.log('File content:', content);
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Reconstructing State from Message History
|
|
415
|
+
|
|
416
|
+
On page reload, iterate through conversation history to reconstruct application state:
|
|
417
|
+
|
|
418
|
+
```javascript
|
|
419
|
+
// Get all messages from conversation history
|
|
420
|
+
window.botpressWebChat.getMessages().then(messages => {
|
|
421
|
+
// Filter for file messages with specific metadata
|
|
422
|
+
const componentFiles = messages.filter(msg =>
|
|
423
|
+
msg.type === 'file' &&
|
|
424
|
+
msg.payload.metadata?.fileType === 'component'
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
// Reconstruct state from persisted messages
|
|
428
|
+
componentFiles.forEach(async (msg) => {
|
|
429
|
+
const { fileUrl, metadata } = msg.payload;
|
|
430
|
+
const { componentId, stepId } = metadata;
|
|
431
|
+
|
|
432
|
+
// Fetch and restore component
|
|
433
|
+
const content = await fetch(fileUrl).then(r => r.text());
|
|
434
|
+
restoreComponent(componentId, stepId, content);
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Message Persistence Benefits
|
|
440
|
+
|
|
441
|
+
Messages (unlike events) are:
|
|
442
|
+
- ✅ Persisted in conversation history
|
|
443
|
+
- ✅ Replayed on page reload
|
|
444
|
+
- ✅ Available via `getMessages()` API
|
|
445
|
+
- ✅ Include all metadata
|
|
446
|
+
|
|
447
|
+
This makes messages ideal for:
|
|
448
|
+
- State reconstruction after page reload
|
|
449
|
+
- File tracking and retrieval
|
|
450
|
+
- Workflow progress indicators
|
|
451
|
+
- Audit trails
|
|
452
|
+
|
|
453
|
+
## Sending Messages from Workflows
|
|
454
|
+
|
|
455
|
+
Workflows can't use `conversation.send()` - they must use `client.createMessage()`:
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
import { Workflow } from "@botpress/runtime";
|
|
459
|
+
|
|
460
|
+
export const NotifyWorkflow = new Workflow({
|
|
461
|
+
name: "notify",
|
|
462
|
+
input: z.object({
|
|
463
|
+
conversationId: z.string(), // REQUIRED for messaging!
|
|
464
|
+
message: z.string()
|
|
465
|
+
}),
|
|
466
|
+
|
|
467
|
+
handler: async ({ input, client, step }) => {
|
|
468
|
+
await step("send-notification", async () => {
|
|
469
|
+
await client.createMessage({
|
|
470
|
+
conversationId: input.conversationId,
|
|
471
|
+
type: "text",
|
|
472
|
+
payload: { text: input.message }
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
**CRITICAL**: Always pass `conversationId` to workflows that need to send messages (see Common Mistakes section for details).
|
|
480
|
+
|
|
481
|
+
## Sending Events (Ephemeral)
|
|
482
|
+
|
|
483
|
+
Events are **NOT messages** - they don't persist in conversation history. Use integration actions to send ephemeral events (see [Integration Actions for Messages](#integration-actions-for-messages) section for examples).
|
|
484
|
+
|
|
485
|
+
### When to Use Events vs Messages
|
|
486
|
+
|
|
487
|
+
**Use Messages** (persistent) when:
|
|
488
|
+
- User needs to see it in history
|
|
489
|
+
- It's part of the conversation flow
|
|
490
|
+
- You want it to survive page reloads
|
|
491
|
+
- Examples: chat responses, confirmations, results
|
|
492
|
+
|
|
493
|
+
**Use Events** (ephemeral) when:
|
|
494
|
+
- Real-time notifications (typing indicators, progress)
|
|
495
|
+
- UI state changes (show/hide widget, config updates)
|
|
496
|
+
- Temporary feedback (loading, processing)
|
|
497
|
+
- Examples: "Agent is typing...", "File uploading..."
|
|
498
|
+
|
|
499
|
+
## Integration Actions for Messages
|
|
500
|
+
|
|
501
|
+
Some integrations provide actions for special message types and events:
|
|
502
|
+
|
|
503
|
+
```typescript
|
|
504
|
+
// Webchat - Send custom event (ephemeral, not persisted)
|
|
505
|
+
await client.callAction({
|
|
506
|
+
type: "webchat:customEvent",
|
|
507
|
+
input: {
|
|
508
|
+
conversationId: conversation.id,
|
|
509
|
+
event: JSON.stringify({
|
|
510
|
+
type: "notification",
|
|
511
|
+
message: "Processing..."
|
|
512
|
+
})
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// Webchat - Show/hide widget
|
|
517
|
+
await client.callAction({
|
|
518
|
+
type: "webchat:showWebchat",
|
|
519
|
+
input: { conversationId: conversation.id }
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
await client.callAction({
|
|
523
|
+
type: "webchat:hideWebchat",
|
|
524
|
+
input: { conversationId: conversation.id }
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
// Webchat - Update configuration
|
|
528
|
+
await client.callAction({
|
|
529
|
+
type: "webchat:configWebchat",
|
|
530
|
+
input: {
|
|
531
|
+
conversationId: conversation.id,
|
|
532
|
+
config: JSON.stringify({
|
|
533
|
+
theme: { primaryColor: "#ff0000" }
|
|
534
|
+
})
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
**Frontend event handling**:
|
|
540
|
+
|
|
541
|
+
```javascript
|
|
542
|
+
window.botpressWebChat.onEvent((event) => {
|
|
543
|
+
if (event.type === 'notification') {
|
|
544
|
+
showToast(event.message);
|
|
545
|
+
}
|
|
546
|
+
}, ['TRIGGER']);
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
## Channel Compatibility
|
|
550
|
+
|
|
551
|
+
Not all message types work in all channels:
|
|
552
|
+
|
|
553
|
+
| Message Type | Chat | Webchat | Slack | WhatsApp | Teams |
|
|
554
|
+
|-------------|------|---------|-------|----------|-------|
|
|
555
|
+
| text | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
556
|
+
| markdown | ✅ | ✅ | ✅ | ❌ | ✅ |
|
|
557
|
+
| image | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
558
|
+
| audio | ✅ | ✅ | ❌ | ✅ | ❌ |
|
|
559
|
+
| video | ✅ | ✅ | ❌ | ✅ | ❌ |
|
|
560
|
+
| file | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
561
|
+
| location | ✅ | ✅ | ❌ | ✅ | ❌ |
|
|
562
|
+
| card | ✅ | ✅ | ✅ | ❌ | ✅ |
|
|
563
|
+
| carousel | ✅ | ✅ | ❌ | ❌ | ❌ |
|
|
564
|
+
| choice | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
565
|
+
| dropdown | ✅ | ✅ | ❌ | ✅ | ❌ |
|
|
566
|
+
| bloc | ✅ | ✅ | ❌ | ❌ | ❌ |
|
|
567
|
+
| customComponent | ❌ | ✅ (webchat only) | ❌ | ❌ | ❌ |
|
|
568
|
+
|
|
569
|
+
**Tip**: Use `text` and `image` for maximum compatibility across channels.
|
|
570
|
+
|
|
571
|
+
## Common Mistakes
|
|
572
|
+
|
|
573
|
+
### 1. Using client.createMessage() in Conversations
|
|
574
|
+
|
|
575
|
+
```typescript
|
|
576
|
+
// ❌ WRONG
|
|
577
|
+
export const Chat = new Conversation({
|
|
578
|
+
async handler({ client }) {
|
|
579
|
+
await client.createMessage({ /* ... */ });
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// ✅ CORRECT
|
|
584
|
+
export const Chat = new Conversation({
|
|
585
|
+
async handler({ conversation }) {
|
|
586
|
+
await conversation.send({ /* ... */ });
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### 2. Using client.createEvent() for Messages
|
|
592
|
+
|
|
593
|
+
```typescript
|
|
594
|
+
// ❌ WRONG - Events don't persist!
|
|
595
|
+
await client.createEvent({
|
|
596
|
+
conversationId: conversation.id,
|
|
597
|
+
type: "text",
|
|
598
|
+
payload: { text: "Hello" }
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
// ✅ CORRECT - Use messages
|
|
602
|
+
await conversation.send({
|
|
603
|
+
type: "text",
|
|
604
|
+
payload: { text: "Hello" }
|
|
605
|
+
});
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### 3. Missing conversationId in Workflows
|
|
609
|
+
|
|
610
|
+
```typescript
|
|
611
|
+
// ❌ WRONG - Can't send messages without conversationId
|
|
612
|
+
export const MyWorkflow = new Workflow({
|
|
613
|
+
handler: async ({ input, client }) => {
|
|
614
|
+
await client.createMessage({
|
|
615
|
+
// Where's conversationId? This will fail!
|
|
616
|
+
type: "text",
|
|
617
|
+
payload: { text: "Hello" }
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
// ✅ CORRECT - Pass conversationId in input
|
|
623
|
+
export const MyWorkflow = new Workflow({
|
|
624
|
+
input: z.object({
|
|
625
|
+
conversationId: z.string()
|
|
626
|
+
}),
|
|
627
|
+
handler: async ({ input, client }) => {
|
|
628
|
+
await client.createMessage({
|
|
629
|
+
conversationId: input.conversationId,
|
|
630
|
+
type: "text",
|
|
631
|
+
payload: { text: "Hello" }
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
### 4. Using Custom Components in Non-Webchat Channels
|
|
638
|
+
|
|
639
|
+
```typescript
|
|
640
|
+
// ❌ WRONG - customComponent only works in webchat
|
|
641
|
+
export const SlackChat = new Conversation({
|
|
642
|
+
channel: "slack.dm",
|
|
643
|
+
async handler({ conversation }) {
|
|
644
|
+
await conversation.send({
|
|
645
|
+
type: "customComponent", // Will fail in Slack!
|
|
646
|
+
payload: { component: TicketCardComponent, props: { /* ... */ } }
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
// ✅ CORRECT - Use agnostic message types for non-webchat channels
|
|
652
|
+
export const SlackChat = new Conversation({
|
|
653
|
+
channel: "slack.dm",
|
|
654
|
+
async handler({ conversation }) {
|
|
655
|
+
await conversation.send({
|
|
656
|
+
type: "card", // Works in Slack
|
|
657
|
+
payload: { title: "Ticket", subtitle: "Details here", actions: [] }
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
## Best Practices
|
|
664
|
+
|
|
665
|
+
### 1. Type Safety
|
|
666
|
+
|
|
667
|
+
TypeScript will help you with type-safe payloads:
|
|
668
|
+
|
|
669
|
+
```typescript
|
|
670
|
+
// TypeScript knows the payload structure based on type
|
|
671
|
+
await conversation.send({
|
|
672
|
+
type: "card",
|
|
673
|
+
payload: {
|
|
674
|
+
title: "Required",
|
|
675
|
+
subtitle: "Optional",
|
|
676
|
+
// IntelliSense will suggest all valid fields!
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
### 2. Graceful Degradation
|
|
682
|
+
|
|
683
|
+
When supporting multiple channels:
|
|
684
|
+
|
|
685
|
+
```typescript
|
|
686
|
+
import { TicketCardComponent } from "../components";
|
|
687
|
+
|
|
688
|
+
export const Chat = new Conversation({
|
|
689
|
+
async handler({ conversation }) {
|
|
690
|
+
if (conversation.channel === "webchat.channel") {
|
|
691
|
+
// Webchat supports custom components
|
|
692
|
+
await conversation.send({
|
|
693
|
+
type: "customComponent",
|
|
694
|
+
payload: { component: TicketCardComponent, props: { ticketId: "TKT-001", title: "VPN issue", priority: "high", status: "open" } }
|
|
695
|
+
});
|
|
696
|
+
} else {
|
|
697
|
+
// Fall back to card for other channels
|
|
698
|
+
await conversation.send({
|
|
699
|
+
type: "card",
|
|
700
|
+
payload: {
|
|
701
|
+
title: "TKT-001: VPN issue",
|
|
702
|
+
subtitle: "Priority: high · Status: open",
|
|
703
|
+
actions: []
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
### 3. Error Handling
|
|
712
|
+
|
|
713
|
+
Always wrap message sending in try-catch:
|
|
714
|
+
|
|
715
|
+
```typescript
|
|
716
|
+
try {
|
|
717
|
+
await conversation.send({
|
|
718
|
+
type: "image",
|
|
719
|
+
payload: { imageUrl: maybeInvalidUrl }
|
|
720
|
+
});
|
|
721
|
+
} catch (error) {
|
|
722
|
+
console.error("Failed to send image:", error);
|
|
723
|
+
// Fall back to text
|
|
724
|
+
await conversation.send({
|
|
725
|
+
type: "text",
|
|
726
|
+
payload: { text: "Image unavailable" }
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
### 4. Message Composition
|
|
732
|
+
|
|
733
|
+
Break complex messages into parts:
|
|
734
|
+
|
|
735
|
+
```typescript
|
|
736
|
+
// Instead of one giant text
|
|
737
|
+
await conversation.send({
|
|
738
|
+
type: "text",
|
|
739
|
+
payload: { text: "Title\n\nBody text here\n\nFooter" }
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
// Use bloc for better structure
|
|
743
|
+
await conversation.send({
|
|
744
|
+
type: "bloc",
|
|
745
|
+
payload: {
|
|
746
|
+
items: [
|
|
747
|
+
{ type: "markdown", payload: { markdown: "**Title**" } },
|
|
748
|
+
{ type: "text", payload: { text: "Body text here" } },
|
|
749
|
+
{ type: "text", payload: { text: "Footer" } }
|
|
750
|
+
]
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
## Summary
|
|
756
|
+
|
|
757
|
+
**In Conversations**: Use `conversation.send()` for messages (persistent, type-safe)
|
|
758
|
+
|
|
759
|
+
**In Workflows**: Use `client.createMessage()` with `conversationId` in input
|
|
760
|
+
|
|
761
|
+
**Events**: Use integration actions for ephemeral notifications (don't persist)
|
|
762
|
+
|
|
763
|
+
**Key Rules**:
|
|
764
|
+
1. ✅ Use `conversation.send()` in conversations, `client.createMessage()` in workflows
|
|
765
|
+
2. ✅ Always pass `conversationId` to workflows that send messages
|
|
766
|
+
3. ✅ Use messages for persistent content, events for ephemeral notifications
|
|
767
|
+
4. ✅ In webchat conversations, use `customComponent` message type with a `CustomComponent` reference
|
|
768
|
+
5. ❌ Never use `client.createMessage()` in conversations
|
|
769
|
+
6. ❌ Check channel compatibility for message types (see table above)
|