@botpress/adk-cli 1.19.0-beta.4 → 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.
Files changed (65) hide show
  1. package/dist/{README-wjb7z7f0.md → README-824wa4jj.md} +1 -1
  2. package/dist/SKILL-75ym6tcj.md +257 -0
  3. package/dist/SKILL-chnwzmt1.md +548 -0
  4. package/dist/SKILL-d4bs53y3.md +244 -0
  5. package/dist/SKILL-h4b2f1n9.md +114 -0
  6. package/dist/SKILL-hbw5nt95.md +111 -0
  7. package/dist/actions-h7hxxvxr.md +823 -0
  8. package/dist/adk-build-emhwd0tg.md +39 -0
  9. package/dist/adk-debug-yrrdtpmb.md +23 -0
  10. package/dist/adk-dev-console-7z9rtjpy.md +17 -0
  11. package/dist/adk-document-ras6zzzh.md +68 -0
  12. package/dist/adk-eval-vx76dwq0.md +17 -0
  13. package/dist/adk-explain-fcsaw852.md +22 -0
  14. package/dist/adk-init-9kmxmbbe.md +30 -0
  15. package/dist/adk-ship-dybzn884.md +29 -0
  16. package/dist/adk-test-xq67savs.md +27 -0
  17. package/dist/adk-validate-1p4skv4m.md +31 -0
  18. package/dist/advanced-patterns-z86m9h1a.md +669 -0
  19. package/dist/agent-config-1aqvkrgf.md +450 -0
  20. package/dist/agent-steps-zj4c1n9t.md +115 -0
  21. package/dist/assets-q7jt8r2q.md +237 -0
  22. package/dist/autonomous-execution-fymc12e5.md +747 -0
  23. package/dist/cli-c31e06yr.md +1334 -0
  24. package/dist/cli.js +970 -566
  25. package/dist/commands.json +24 -3
  26. package/dist/common-failures-ht29w2db.md +231 -0
  27. package/dist/component-registry-bbpw0see.md +43 -0
  28. package/dist/context-api-760cvh0h.md +461 -0
  29. package/dist/conversation-analysis-8ka4nrm4.md +420 -0
  30. package/dist/conversation-lifecycle-4seqqjc1.md +311 -0
  31. package/dist/conversations-gwfvxaee.md +1111 -0
  32. package/dist/custom-components-y5wdgpey.md +167 -0
  33. package/dist/debug-workflow-vz84q80v.md +239 -0
  34. package/dist/desk-k54ktvsd.md +852 -0
  35. package/dist/doc-standards-ck6ax2jd.md +124 -0
  36. package/dist/eval-format-7n9brja4.md +354 -0
  37. package/dist/evals-y1zcr1mc.md +11 -0
  38. package/dist/explain-config-1yaq3zzd.md +424 -0
  39. package/dist/files-dzt0wxn3.md +918 -0
  40. package/dist/integration-actions-xang7tma.md +659 -0
  41. package/dist/integrations-k5ss07f4.md +266 -0
  42. package/dist/interfaces-6t0bpxs4.md +97 -0
  43. package/dist/knowledge-bases-20rg8p5c.md +456 -0
  44. package/dist/llm-debugging-76ng8rzm.md +209 -0
  45. package/dist/mcp-server-n1asyzhv.md +112 -0
  46. package/dist/messages-gkkg0b54.md +769 -0
  47. package/dist/model-configuration-f273kgd0.md +201 -0
  48. package/dist/multi-agent-dashboard-1mdfjdts.md +70 -0
  49. package/dist/pages-xn18xn73.md +273 -0
  50. package/dist/patterns-mistakes-5vb99dhf.md +469 -0
  51. package/dist/plugins-ttnx8f49.md +280 -0
  52. package/dist/tables-x45t36dd.md +1136 -0
  53. package/dist/tags-46td3qrg.md +516 -0
  54. package/dist/test-patterns-hj47w4nc.md +355 -0
  55. package/dist/testing-workflow-2vkp932f.md +225 -0
  56. package/dist/tools-wk7bq24z.md +581 -0
  57. package/dist/trace-summarization-hdyx1c2e.md +313 -0
  58. package/dist/traces-and-logs-f2kjfh19.md +203 -0
  59. package/dist/triggers-4j2cc86x.md +719 -0
  60. package/dist/ui-server-entry.js +952 -137
  61. package/dist/workflow-steps-mrdrf1c5.md +851 -0
  62. package/dist/workflows-gadx3s69.md +1408 -0
  63. package/dist/zai-agent-reference-e6644aad.md +454 -0
  64. package/dist/zai-complete-guide-mnna5d6f.md +1408 -0
  65. 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)