@botpress/adk-cli 1.19.0-beta.4 → 1.19.0-beta.6

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 (87) hide show
  1. package/{dist/index-a3ZL0_Jr-q145h35p.js → assets/ui-dist/assets/index-BC_iUF2d.js} +2 -2
  2. package/assets/ui-dist/assets/index-BxpvDT_K.css +1 -0
  3. package/{dist/index-BESZeTO5-2rpmcefa.js → assets/ui-dist/assets/index-C7gAokRv.js} +2 -2
  4. package/assets/ui-dist/assets/{index-B3b7PPgV.js → index-exyr_Djl.js} +424 -424
  5. package/assets/ui-dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  6. package/assets/ui-dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  7. package/assets/ui-dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  8. package/assets/ui-dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  9. package/assets/ui-dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  10. package/assets/ui-dist/index.html +2 -2
  11. package/dist/{README-wjb7z7f0.md → README-824wa4jj.md} +1 -1
  12. package/dist/SKILL-75ym6tcj.md +257 -0
  13. package/dist/SKILL-chnwzmt1.md +548 -0
  14. package/dist/SKILL-d4bs53y3.md +244 -0
  15. package/dist/SKILL-h4b2f1n9.md +114 -0
  16. package/dist/SKILL-hbw5nt95.md +111 -0
  17. package/dist/actions-h7hxxvxr.md +823 -0
  18. package/dist/adk-build-emhwd0tg.md +39 -0
  19. package/dist/adk-debug-yrrdtpmb.md +23 -0
  20. package/dist/adk-dev-console-7z9rtjpy.md +17 -0
  21. package/dist/adk-document-ras6zzzh.md +68 -0
  22. package/dist/adk-eval-vx76dwq0.md +17 -0
  23. package/dist/adk-explain-fcsaw852.md +22 -0
  24. package/dist/adk-init-9kmxmbbe.md +30 -0
  25. package/dist/adk-ship-dybzn884.md +29 -0
  26. package/dist/adk-test-xq67savs.md +27 -0
  27. package/dist/adk-validate-1p4skv4m.md +31 -0
  28. package/dist/advanced-patterns-z86m9h1a.md +669 -0
  29. package/dist/agent-config-1aqvkrgf.md +450 -0
  30. package/dist/agent-steps-zj4c1n9t.md +115 -0
  31. package/dist/assets-q7jt8r2q.md +237 -0
  32. package/dist/autonomous-execution-fymc12e5.md +747 -0
  33. package/dist/cli-c31e06yr.md +1334 -0
  34. package/dist/cli.js +4241 -6449
  35. package/dist/commands.json +25 -4
  36. package/dist/common-failures-ht29w2db.md +231 -0
  37. package/dist/component-registry-bbpw0see.md +43 -0
  38. package/dist/context-api-760cvh0h.md +461 -0
  39. package/dist/conversation-analysis-8ka4nrm4.md +420 -0
  40. package/dist/conversation-lifecycle-4seqqjc1.md +311 -0
  41. package/dist/conversations-gwfvxaee.md +1111 -0
  42. package/dist/custom-components-y5wdgpey.md +167 -0
  43. package/dist/debug-workflow-vz84q80v.md +239 -0
  44. package/dist/desk-k54ktvsd.md +852 -0
  45. package/dist/doc-standards-ck6ax2jd.md +124 -0
  46. package/dist/eval-format-7n9brja4.md +354 -0
  47. package/dist/evals-y1zcr1mc.md +11 -0
  48. package/dist/explain-config-1yaq3zzd.md +424 -0
  49. package/dist/files-dzt0wxn3.md +918 -0
  50. package/dist/{index-3t354nbw.html → index-0qts7wfk.html} +2 -2
  51. package/{assets/ui-dist/assets/index-a3ZL0_Jr.js → dist/index-BC_iUF2d-3h9mqazs.js} +2 -2
  52. package/dist/index-BxpvDT_K-62864175.css +1 -0
  53. package/{assets/ui-dist/assets/index-BESZeTO5.js → dist/index-C7gAokRv-zr7dzkba.js} +2 -2
  54. package/dist/{index-B3b7PPgV-zetkb01w.js → index-exyr_Djl-ksys08wc.js} +424 -424
  55. package/dist/integration-actions-xang7tma.md +659 -0
  56. package/dist/integrations-k5ss07f4.md +266 -0
  57. package/dist/interfaces-6t0bpxs4.md +97 -0
  58. package/dist/jetbrains-mono-cyrillic-wght-normal-D73BlboJ-6vpq1jfh.woff2 +0 -0
  59. package/dist/jetbrains-mono-greek-wght-normal-Bw9x6K1M-jv5dqe09.woff2 +0 -0
  60. package/dist/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a-h2ysfc3f.woff2 +0 -0
  61. package/dist/jetbrains-mono-latin-wght-normal-B9CIFXIH-hj2e42wt.woff2 +0 -0
  62. package/dist/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq-wttk1pwp.woff2 +0 -0
  63. package/dist/knowledge-bases-20rg8p5c.md +456 -0
  64. package/dist/llm-debugging-76ng8rzm.md +209 -0
  65. package/dist/mcp-server-n1asyzhv.md +112 -0
  66. package/dist/messages-gkkg0b54.md +769 -0
  67. package/dist/model-configuration-f273kgd0.md +201 -0
  68. package/dist/multi-agent-dashboard-1mdfjdts.md +70 -0
  69. package/dist/pages-xn18xn73.md +273 -0
  70. package/dist/patterns-mistakes-5vb99dhf.md +469 -0
  71. package/dist/plugins-ttnx8f49.md +280 -0
  72. package/dist/tables-x45t36dd.md +1136 -0
  73. package/dist/tags-46td3qrg.md +516 -0
  74. package/dist/test-patterns-hj47w4nc.md +355 -0
  75. package/dist/testing-workflow-2vkp932f.md +225 -0
  76. package/dist/tools-wk7bq24z.md +581 -0
  77. package/dist/trace-summarization-hdyx1c2e.md +313 -0
  78. package/dist/traces-and-logs-f2kjfh19.md +203 -0
  79. package/dist/triggers-4j2cc86x.md +719 -0
  80. package/dist/ui-server-entry.js +1839 -791
  81. package/dist/workflow-steps-mrdrf1c5.md +851 -0
  82. package/dist/workflows-gadx3s69.md +1408 -0
  83. package/dist/zai-agent-reference-e6644aad.md +454 -0
  84. package/dist/zai-complete-guide-mnna5d6f.md +1408 -0
  85. package/package.json +4 -4
  86. package/assets/ui-dist/assets/index-DIFGtVmI.css +0 -1
  87. package/dist/index-DIFGtVmI-d4dhjg97.css +0 -1
@@ -0,0 +1,1111 @@
1
+ # Conversations
2
+
3
+ Conversations handle incoming messages and route them based on channel. They're the primary interface between users and your bot, providing AI-powered responses and integration with workflows.
4
+
5
+ ## Basic Concepts
6
+
7
+ ### What are Conversations?
8
+ - **Channel-specific**: Each conversation handles a specific channel (chat, Slack, etc.)
9
+ - **Stateful**: Maintain state per conversation
10
+ - **AI-powered**: Built-in support for AI execution with tools and knowledge
11
+ - **Interactive**: Can trigger workflows and handle workflow requests
12
+
13
+ ### File Location
14
+ - **Location**: `src/conversations/*.ts`
15
+ - **Auto-registration**: Files automatically register for their specified channel
16
+
17
+ ## Creating Conversations
18
+
19
+ ### Basic Structure
20
+
21
+ ```typescript
22
+ import { Conversation, z } from "@botpress/runtime";
23
+
24
+ export const Chat = new Conversation({
25
+ channel: "chat.channel", // Required: channel ID
26
+
27
+ // Optional: Per-conversation state
28
+ state: z.object({
29
+ count: z.number().default(0),
30
+ activeWorkflowId: z.string().optional()
31
+ }),
32
+
33
+ async handler({ message, state, conversation, execute, type, event, request, completion }) {
34
+ // Access conversation state via the state parameter
35
+ state.count += 1;
36
+
37
+ // Send messages via conversation instance
38
+ await conversation.send({
39
+ type: "text",
40
+ payload: { text: "Hello!" }
41
+ });
42
+
43
+ // AI-powered response
44
+ await execute({
45
+ instructions: "You are a helpful assistant",
46
+ tools: [searchTool],
47
+ knowledge: [MyKnowledgeBase]
48
+ });
49
+ }
50
+ });
51
+ ```
52
+
53
+ ### Handler Parameters
54
+
55
+ ```typescript
56
+ async handler({ message, state, conversation, execute, type, event, request, completion, client }) {
57
+ // message - Incoming message object
58
+ // state - Conversation state (mutable, automatically tracked)
59
+ // conversation - Conversation instance with send() and other methods
60
+ // execute - AI execution function
61
+ // type - Event type: "message" | "event" | "workflow_request" | "workflow_callback"
62
+ // event - Raw event (typed per handler type — see workflow sections below)
63
+ // request - Workflow request object (when type === "workflow_request")
64
+ // completion - Workflow callback object (when type === "workflow_callback")
65
+ // client - BotClient for API calls
66
+ }
67
+ ```
68
+
69
+ > **Important:** Inside conversation handlers, always use the `conversation` parameter provided by the handler. Do **not** use `context.get("conversation")` — that pattern is for Actions, Tools, and Triggers that may optionally run within a conversation context. The handler parameter is already properly scoped and typed.
70
+
71
+ ## Common Channel IDs
72
+
73
+ Different platforms have different channel IDs:
74
+
75
+ ```typescript
76
+ // Botpress Chat
77
+ channel: "chat.channel"
78
+
79
+ // Slack
80
+ channel: "slack.dm" // Direct messages
81
+ channel: "slack.channel" // Channel messages
82
+
83
+ // Discord
84
+ channel: "discord.dm"
85
+ channel: "discord.channel"
86
+
87
+ // WhatsApp
88
+ channel: "whatsapp.channel"
89
+
90
+ // Webchat
91
+ channel: "webchat.channel"
92
+
93
+ // Teams
94
+ channel: "teams.dm"
95
+ channel: "teams.channel"
96
+ ```
97
+
98
+ ## Message Handling
99
+
100
+ ### Basic Message Processing
101
+
102
+ ```typescript
103
+ export const Chat = new Conversation({
104
+ channel: "chat.channel",
105
+
106
+ async handler({ message, conversation, execute }) {
107
+ // Check message type
108
+ if (message?.type === "text") {
109
+ const text = message.payload.text;
110
+
111
+ // Command handling
112
+ if (text.startsWith("/help")) {
113
+ await conversation.send({
114
+ type: "text",
115
+ payload: { text: "Available commands:\n/help - Show help\n/status - Check status" }
116
+ });
117
+ return;
118
+ }
119
+
120
+ // Regular message - use AI
121
+ await execute({
122
+ instructions: "Help the user with their request"
123
+ });
124
+ }
125
+
126
+ // Handle other message types
127
+ if (message?.type === "image") {
128
+ await conversation.send({
129
+ type: "text",
130
+ payload: { text: "I received your image!" }
131
+ });
132
+ }
133
+ }
134
+ });
135
+ ```
136
+
137
+ See **[Messages](./messages.md)** for complete guide on all message types, metadata, and sending patterns.
138
+
139
+ ## Conversation Instance Methods
140
+
141
+ The `conversation` object provides methods for interacting with the current conversation.
142
+
143
+ ### conversation.send()
144
+
145
+ Send messages to the conversation:
146
+
147
+ ```typescript
148
+ // Send text message
149
+ await conversation.send({
150
+ type: "text",
151
+ payload: { text: "Hello!" }
152
+ });
153
+
154
+ // Send choice message
155
+ await conversation.send({
156
+ type: "choice",
157
+ payload: {
158
+ text: "Choose an option:",
159
+ options: [
160
+ { label: "Option 1", value: "opt1" },
161
+ { label: "Option 2", value: "opt2" }
162
+ ]
163
+ }
164
+ });
165
+ ```
166
+
167
+ See **[Messages](./messages.md)** for all message types and payloads.
168
+
169
+ ### conversation.startTyping()
170
+
171
+ Show typing indicator to the user:
172
+
173
+ ```typescript
174
+ export const Chat = new Conversation({
175
+ channel: "chat.channel",
176
+
177
+ async handler({ conversation }) {
178
+ // Start typing indicator
179
+ await conversation.startTyping();
180
+
181
+ // Do some work (API call, processing, etc.)
182
+ await fetchData();
183
+
184
+ // Send message (stops typing automatically)
185
+ await conversation.send({
186
+ type: "text",
187
+ payload: { text: "Here's the data!" }
188
+ });
189
+ }
190
+ });
191
+ ```
192
+
193
+ ### conversation.stopTyping()
194
+
195
+ Manually stop typing indicator:
196
+
197
+ ```typescript
198
+ export const Chat = new Conversation({
199
+ channel: "chat.channel",
200
+
201
+ async handler({ conversation }) {
202
+ await conversation.startTyping();
203
+
204
+ try {
205
+ const result = await riskyOperation();
206
+ await conversation.send({
207
+ type: "text",
208
+ payload: { text: result }
209
+ });
210
+ } catch (error) {
211
+ // Stop typing if sending fails
212
+ await conversation.stopTyping();
213
+ throw error;
214
+ }
215
+ }
216
+ });
217
+ ```
218
+
219
+ ### conversation.tags
220
+
221
+ Access and modify conversation tags:
222
+
223
+ ```typescript
224
+ // Read tags
225
+ const priority = conversation.tags.priority;
226
+ const category = conversation.tags.category;
227
+
228
+ // Set tags
229
+ conversation.tags.priority = "high";
230
+ conversation.tags.category = "support";
231
+ ```
232
+
233
+ See **[Tags](./tags.md)** for complete tag documentation.
234
+
235
+ ### Conversation Properties
236
+
237
+ ```typescript
238
+ // Conversation ID
239
+ const id = conversation.id;
240
+
241
+ // Channel information
242
+ const channel = conversation.channel; // e.g., "webchat.channel"
243
+ const integration = conversation.integration; // e.g., "webchat"
244
+ const alias = conversation.alias; // Integration alias
245
+ ```
246
+
247
+ ## AI Execution
248
+
249
+ ### Basic AI Integration
250
+
251
+ ```typescript
252
+ export const Chat = new Conversation({
253
+ channel: "chat.channel",
254
+
255
+ async handler({ message, execute }) {
256
+ await execute({
257
+ instructions: "You are a helpful customer support assistant",
258
+
259
+ // Optional: Add tools
260
+ tools: [searchTool, createTicketTool],
261
+
262
+ // Optional: Add knowledge bases
263
+ knowledge: [DocsKnowledgeBase, FAQKnowledgeBase],
264
+
265
+ // Optional: Model configuration
266
+ model: "openai:gpt-4o",
267
+ temperature: 0.7,
268
+
269
+ // Optional: Execution hooks
270
+ hooks: {
271
+ onTrace: ({ trace }) => console.log(trace),
272
+ onIterationEnd: async (iteration) => {
273
+ if (iteration.isFailed()) {
274
+ console.log("Iteration failed:", iteration.error);
275
+ }
276
+ }
277
+ }
278
+ });
279
+ }
280
+ });
281
+ ```
282
+
283
+ See **[Actions](./actions.md#converting-actions-to-tools)** for converting actions to tools with `.asTool()`.
284
+
285
+ ## State Management
286
+
287
+ ### Conversation State
288
+
289
+ Each conversation maintains its own state:
290
+
291
+ ```typescript
292
+ export const Chat = new Conversation({
293
+ channel: "chat.channel",
294
+
295
+ state: z.object({
296
+ messageCount: z.number().default(0),
297
+ userName: z.string().optional(),
298
+ preferences: z.object({
299
+ language: z.string().default("en"),
300
+ theme: z.string().default("light")
301
+ }).default({})
302
+ }),
303
+
304
+ async handler({ message, state, conversation }) {
305
+ // Access and modify state via the state parameter
306
+ state.messageCount += 1;
307
+
308
+ if (!state.userName && message?.type === "text") {
309
+ state.userName = message.payload.text;
310
+ await conversation.send({
311
+ type: "text",
312
+ payload: { text: `Nice to meet you, ${state.userName}!` }
313
+ });
314
+ }
315
+
316
+ // State persists across messages
317
+ if (state.messageCount > 10) {
318
+ await conversation.send({
319
+ type: "text",
320
+ payload: { text: "Thanks for being an active user!" }
321
+ });
322
+ }
323
+ }
324
+ });
325
+ ```
326
+
327
+ ## Workflow Integration
328
+
329
+ ### Starting Workflows from Conversations
330
+
331
+ ```typescript
332
+ export const Chat = new Conversation({
333
+ channel: "chat.channel",
334
+ state: z.object({
335
+ activeWorkflowId: z.string().optional()
336
+ }),
337
+
338
+ async handler({ message, state, conversation }) {
339
+ const text = message?.payload.text || "";
340
+
341
+ if (text.startsWith("/process")) {
342
+ // Start workflow with conversation context
343
+ const instance = await ProcessWorkflow.start({
344
+ conversationId: conversation.id, // Critical for communication!
345
+ userId: user.id,
346
+ data: text.substring(8)
347
+ });
348
+
349
+ // Store workflow ID
350
+ state.activeWorkflowId = instance.id;
351
+
352
+ await conversation.send({
353
+ type: "text",
354
+ payload: { text: `Started processing: ${instance.id}` }
355
+ });
356
+ return;
357
+ }
358
+
359
+ if (text === "/status" && state.activeWorkflowId) {
360
+ // Check workflow status
361
+ const instance = await ProcessWorkflow.getOrCreate({
362
+ key: state.activeWorkflowId,
363
+ input: { /* required input */ }
364
+ });
365
+
366
+ await conversation.send({
367
+ type: "text",
368
+ payload: {
369
+ text: `Status: ${instance?.status || "Not found"}`
370
+ }
371
+ });
372
+ return;
373
+ }
374
+ }
375
+ });
376
+ ```
377
+
378
+ ### Handling Workflow Requests
379
+
380
+ Workflows can request data from conversations using `step.request()`. The conversation receives a `workflow_request` type with the request object.
381
+
382
+ **Request Object Structure:**
383
+ ```typescript
384
+ type WorkflowRequest = {
385
+ type: `${WorkflowName}:${RequestName}`; // e.g., "processOrder:email"
386
+ workflow: BaseWorkflowInstance; // Workflow instance
387
+ step: string; // Step name
388
+ }
389
+ ```
390
+
391
+ **Using the Type Discriminant (Recommended)**
392
+
393
+ When `type === "workflow_request"`, both `request` and `event` are properly typed:
394
+
395
+ ```typescript
396
+ export const Chat = new Conversation({
397
+ channel: "chat.channel",
398
+
399
+ async handler({ type, request, event, conversation, message }) {
400
+ if (type === "workflow_request") {
401
+ // request is typed as WorkflowRequest
402
+ // event is typed as WorkflowDataRequestEventType
403
+ // event.payload.message contains the prompt from step.request()
404
+
405
+ await conversation.send({
406
+ type: "text",
407
+ payload: { text: event.payload.message }
408
+ });
409
+
410
+ // Check specific request type
411
+ if (request.type === "processOrder:email") {
412
+ const email = message?.payload.text || "user@example.com";
413
+ await request.workflow.provide("email", { email });
414
+ }
415
+ return;
416
+ }
417
+ }
418
+ });
419
+ ```
420
+
421
+ **Legacy: Using `isWorkflowDataRequest` Type Guard (Deprecated)**
422
+
423
+ > **Deprecated:** Use the `type === "workflow_request"` discriminant instead. It provides the same typed event access plus the `request` object for additional context.
424
+
425
+ ```typescript
426
+ import { isWorkflowDataRequest } from "@botpress/runtime";
427
+
428
+ export const Chat = new Conversation({
429
+ channel: "chat.channel",
430
+
431
+ async handler({ event, conversation }) {
432
+ // ⚠️ Deprecated — prefer type === "workflow_request"
433
+ if (isWorkflowDataRequest(event)) {
434
+ await conversation.send({
435
+ type: "text",
436
+ payload: { text: event.payload.message }
437
+ });
438
+
439
+ const userInput = getUserInput();
440
+ await MyWorkflow.provide(event, { data: userInput });
441
+ return;
442
+ }
443
+ }
444
+ });
445
+ ```
446
+
447
+ ### Handling Workflow Callbacks (Completion Events)
448
+
449
+ When a workflow completes, fails, is canceled, or times out, the conversation receives a `workflow_callback` event. Use the `completion` object to inspect the result.
450
+
451
+ **Completion Object Structure:**
452
+ ```typescript
453
+ type WorkflowCallback = {
454
+ type: string; // Workflow name
455
+ workflow: BaseWorkflowInstance; // Workflow instance
456
+ status: "completed" | "failed" | "canceled" | "timed_out";
457
+ output?: z.infer<WorkflowOutput>; // Workflow output (if completed)
458
+ error?: string; // Error message (if failed)
459
+ }
460
+ ```
461
+
462
+ **Using the Type Discriminant (Recommended)**
463
+
464
+ ```typescript
465
+ export const Chat = new Conversation({
466
+ channel: "chat.channel",
467
+
468
+ async handler({ type, completion, event, conversation }) {
469
+ if (type === "workflow_callback") {
470
+ // completion is typed as WorkflowCallback
471
+ // event is typed as WorkflowCallbackEventType
472
+
473
+ switch (completion.status) {
474
+ case "completed":
475
+ await conversation.send({
476
+ type: "text",
477
+ payload: {
478
+ text: `Workflow "${completion.type}" completed successfully!`
479
+ }
480
+ });
481
+ // Access workflow output
482
+ if (completion.output) {
483
+ console.log("Output:", completion.output);
484
+ }
485
+ break;
486
+
487
+ case "failed":
488
+ await conversation.send({
489
+ type: "text",
490
+ payload: {
491
+ text: `Workflow "${completion.type}" failed: ${completion.error}`
492
+ }
493
+ });
494
+ break;
495
+
496
+ case "canceled":
497
+ await conversation.send({
498
+ type: "text",
499
+ payload: { text: `Workflow "${completion.type}" was canceled.` }
500
+ });
501
+ break;
502
+
503
+ case "timed_out":
504
+ await conversation.send({
505
+ type: "text",
506
+ payload: { text: `Workflow "${completion.type}" timed out.` }
507
+ });
508
+ break;
509
+ }
510
+ return;
511
+ }
512
+ }
513
+ });
514
+ ```
515
+
516
+ **Legacy: Using `isWorkflowCallback` Type Guard (Deprecated)**
517
+
518
+ > **Deprecated:** Use the `type === "workflow_callback"` discriminant instead. It provides a typed `completion` object with workflow instance, status, output, and error information.
519
+
520
+ ```typescript
521
+ import { isWorkflowCallback } from "@botpress/runtime";
522
+
523
+ export const Chat = new Conversation({
524
+ channel: "chat.channel",
525
+
526
+ async handler({ event, conversation }) {
527
+ // ⚠️ Deprecated — prefer type === "workflow_callback"
528
+ if (isWorkflowCallback(event)) {
529
+ console.log("Workflow completed:", event.payload);
530
+ }
531
+ }
532
+ });
533
+ ```
534
+
535
+ ### Combined Workflow Event Handling
536
+
537
+ A single conversation handler can handle all workflow event types:
538
+
539
+ ```typescript
540
+ export const Chat = new Conversation({
541
+ channel: "chat.channel",
542
+ state: z.object({
543
+ activeWorkflowId: z.string().optional()
544
+ }),
545
+
546
+ async handler({ type, message, request, completion, event, conversation, execute }) {
547
+ // Handle workflow data requests
548
+ if (type === "workflow_request") {
549
+ await conversation.send({
550
+ type: "text",
551
+ payload: { text: event.payload.message }
552
+ });
553
+ // Wait for user input, then provide it back
554
+ return;
555
+ }
556
+
557
+ // Handle workflow completion/failure
558
+ if (type === "workflow_callback") {
559
+ if (completion.status === "completed") {
560
+ await conversation.send({
561
+ type: "text",
562
+ payload: { text: `Done! Result: ${JSON.stringify(completion.output)}` }
563
+ });
564
+ } else {
565
+ await conversation.send({
566
+ type: "text",
567
+ payload: { text: `Workflow ended with status: ${completion.status}` }
568
+ });
569
+ }
570
+ return;
571
+ }
572
+
573
+ // Handle regular messages
574
+ if (type === "message") {
575
+ await execute({
576
+ instructions: "You are a helpful assistant"
577
+ });
578
+ }
579
+ }
580
+ });
581
+ ```
582
+
583
+ See **[Workflows](./workflows.md#conversation-communication)** for complete workflow request patterns including state management and multiple request types.
584
+
585
+ ## Command Handling
586
+
587
+ ### Implementing Commands
588
+
589
+ ```typescript
590
+ export const Chat = new Conversation({
591
+ channel: "chat.channel",
592
+
593
+ async handler({ message, conversation, execute }) {
594
+ if (message?.type !== "text") return;
595
+
596
+ const text = message.payload.text;
597
+ const command = text.split(" ")[0].toLowerCase();
598
+ const args = text.substring(command.length).trim();
599
+
600
+ switch (command) {
601
+ case "/help":
602
+ await conversation.send({
603
+ type: "text",
604
+ payload: {
605
+ text: `Available commands:
606
+ /help - Show this help
607
+ /search <query> - Search knowledge base
608
+ /refresh - Refresh knowledge base
609
+ /workflow <data> - Start workflow`
610
+ }
611
+ });
612
+ break;
613
+
614
+ case "/search":
615
+ const results = await MyKnowledgeBase.search({ query: args, limit: 5 });
616
+ await conversation.send({
617
+ type: "text",
618
+ payload: {
619
+ text: results.length > 0
620
+ ? `Found ${results.length} results:\n${results.join("\n")}`
621
+ : "No results found"
622
+ }
623
+ });
624
+ break;
625
+
626
+ case "/refresh":
627
+ await MyKnowledgeBase.refresh();
628
+ await conversation.send({
629
+ type: "text",
630
+ payload: { text: "Knowledge base refreshed!" }
631
+ });
632
+ break;
633
+
634
+ case "/workflow":
635
+ const instance = await MyWorkflow.getOrCreate({
636
+ key: user.id,
637
+ input: { userId: user.id, data: args }
638
+ });
639
+ await conversation.send({
640
+ type: "text",
641
+ payload: { text: `Workflow ${instance.id} started` }
642
+ });
643
+ break;
644
+
645
+ default:
646
+ // Not a command, use AI
647
+ await execute({
648
+ instructions: "You are a helpful assistant",
649
+ knowledge: [MyKnowledgeBase]
650
+ });
651
+ }
652
+ }
653
+ });
654
+ ```
655
+
656
+ ## Advanced Patterns
657
+
658
+ ### Multi-Channel Support
659
+
660
+ Create separate conversations for different channels:
661
+
662
+ ```typescript
663
+ // src/conversations/slack-dm.ts
664
+ export const SlackDM = new Conversation({
665
+ channel: "slack.dm",
666
+
667
+ async handler({ message, execute }) {
668
+ // Slack-specific handling
669
+ if (message?.payload.thread_ts) {
670
+ // Handle threaded messages
671
+ }
672
+
673
+ await execute({
674
+ instructions: "You are a Slack assistant. Use Slack markdown formatting."
675
+ });
676
+ }
677
+ });
678
+
679
+ // src/conversations/slack-channel.ts
680
+ export const SlackChannel = new Conversation({
681
+ channel: "slack.channel",
682
+
683
+ async handler({ message, execute }) {
684
+ // Only respond when mentioned
685
+ if (!message?.payload.text?.includes("@bot")) {
686
+ return; // Don't respond
687
+ }
688
+
689
+ await execute({
690
+ instructions: "You are in a public Slack channel. Be concise."
691
+ });
692
+ }
693
+ });
694
+ ```
695
+
696
+ ### Conversation Context
697
+
698
+ Maintain context across messages:
699
+
700
+ ```typescript
701
+ export const Chat = new Conversation({
702
+ channel: "chat.channel",
703
+ state: z.object({
704
+ context: z.array(z.object({
705
+ role: z.string(),
706
+ content: z.string()
707
+ })).default([])
708
+ }),
709
+
710
+ async handler({ message, state, execute }) {
711
+ // Add user message to context
712
+ if (message?.type === "text") {
713
+ state.context.push({
714
+ role: "user",
715
+ content: message.payload.text
716
+ });
717
+ }
718
+
719
+ // Keep last 10 messages
720
+ if (state.context.length > 10) {
721
+ state.context = state.context.slice(-10);
722
+ }
723
+
724
+ // Use context in AI execution
725
+ await execute({
726
+ instructions: `You are a helpful assistant.
727
+ Previous context:
728
+ ${state.context.map(m => `${m.role}: ${m.content}`).join("\n")}`
729
+ });
730
+ }
731
+ });
732
+ ```
733
+
734
+ ### Context API & Chat Instance
735
+
736
+ Access the chat instance to read conversation history, register custom components, and build adaptive experiences.
737
+
738
+ #### Accessing the Chat Instance
739
+
740
+ ```typescript
741
+ import { context, Conversation } from "@botpress/runtime";
742
+
743
+ export const Chat = new Conversation({
744
+ channel: "webchat.channel",
745
+
746
+ async handler({ message, execute, conversation }) {
747
+ // Get the chat instance
748
+ const chat = context.get("chat");
749
+
750
+ // Fetch conversation transcript
751
+ const transcript = await chat.fetchTranscript();
752
+
753
+ // Analyze conversation history
754
+ const messageCount = transcript.length;
755
+ const hasImages = transcript.some(
756
+ (msg) => msg.role === "user" && msg.attachments && msg.attachments.length > 0
757
+ );
758
+
759
+ console.log(`Conversation has ${messageCount} messages, includes images: ${hasImages}`);
760
+ }
761
+ });
762
+ ```
763
+
764
+ #### Adaptive Model Selection
765
+
766
+ Choose models dynamically based on conversation content (e.g., handle images with vision models):
767
+
768
+ ```typescript
769
+ import { context, Conversation } from "@botpress/runtime";
770
+
771
+ export const Chat = new Conversation({
772
+ channel: "webchat.channel",
773
+
774
+ async handler({ message, execute, conversation }) {
775
+ const chat = context.get("chat");
776
+ const transcript = await chat.fetchTranscript();
777
+
778
+ // Check if any user messages contain images
779
+ const hasImages = transcript.some(
780
+ (msg) => msg.role === "user" && msg.attachments?.length > 0
781
+ );
782
+
783
+ // Select appropriate model
784
+ const model = hasImages
785
+ ? "openai:gpt-4.1" // Vision-capable model for images
786
+ : "cerebras:gpt-oss-120b"; // Fast, cost-effective for text-only
787
+
788
+ await execute({
789
+ instructions: "You are a helpful assistant...",
790
+ model,
791
+ tools: [myTool]
792
+ });
793
+ }
794
+ });
795
+ ```
796
+
797
+ **Use Cases:**
798
+ - **Cost Optimization**: Use cheaper/faster models for text-only conversations
799
+ - **Capability Matching**: Switch to vision models only when images are present
800
+ - **Context-Aware Responses**: Adapt behavior based on conversation history
801
+ - **Quality Control**: Use more powerful models for complex or lengthy conversations
802
+
803
+ #### Custom UI Components
804
+
805
+ Custom components are React `.bp.tsx` files that render rich UI in webchat. Register them in `src/components/index.ts` with `CustomComponent`, then list them in a conversation's `components` array:
806
+
807
+ ```typescript
808
+ import { Conversation } from "@botpress/runtime";
809
+ import { TicketCardComponent } from "../components";
810
+
811
+ export const Chat = new Conversation({
812
+ channel: "webchat.channel",
813
+
814
+ components: [TicketCardComponent],
815
+
816
+ async handler({ execute, conversation }) {
817
+ // The LLM can yield TicketCard autonomously
818
+ await execute({
819
+ instructions: "Always use the TicketCard component to display ticket details.",
820
+ tools: [lookupTicket]
821
+ });
822
+ }
823
+ });
824
+ ```
825
+
826
+ You can also send a component explicitly:
827
+
828
+ ```typescript
829
+ import { WelcomeBannerComponent } from "../components";
830
+
831
+ await conversation.send({
832
+ type: "customComponent",
833
+ payload: {
834
+ component: WelcomeBannerComponent,
835
+ props: {},
836
+ }
837
+ });
838
+ ```
839
+
840
+ See the component registry reference for the full component creation flow (`.bp.tsx` files, metadata, and registration).
841
+
842
+ **Component Use Cases:**
843
+ - **Rich Answers**: Formatted responses with citations and styling
844
+ - **Interactive Elements**: Buttons, forms, and action cards
845
+ - **Data Visualization**: Charts, tables, and structured data display
846
+ - **Custom Workflows**: Multi-step interactions with state
847
+
848
+ #### Chat Instance Methods
849
+
850
+ The chat instance provides methods for managing conversation transcripts and components.
851
+
852
+ **Transcript Management:**
853
+
854
+ ```typescript
855
+ import { context } from "@botpress/runtime";
856
+
857
+ const chat = context.get("chat");
858
+
859
+ // Fetch transcript (loads from API)
860
+ const transcript = await chat.fetchTranscript();
861
+
862
+ // Get transcript (returns cached copy)
863
+ const cached = await chat.getTranscript();
864
+
865
+ // Set/replace entire transcript
866
+ await chat.setTranscript([
867
+ { id: "1", role: "user", content: "Hello" },
868
+ { id: "2", role: "assistant", content: "Hi there!" }
869
+ ]);
870
+
871
+ // Clear all messages
872
+ await chat.clearTranscript();
873
+
874
+ // Prepend messages to beginning
875
+ await chat.prependToTranscript([
876
+ { id: "0", role: "summary", content: "Previous conversation summary..." }
877
+ ]);
878
+
879
+ // Remove specific message by ID
880
+ const removed = await chat.removeMessage("msg-123");
881
+
882
+ // Remove messages by predicate
883
+ const count = await chat.removeMessages(
884
+ (item) => item.role === "assistant" && item.content.includes("error")
885
+ );
886
+
887
+ // Compact transcript (summarize/compress)
888
+ await chat.compactTranscript();
889
+
890
+ // Save transcript changes
891
+ await chat.saveTranscript();
892
+ ```
893
+
894
+ **Component Management:**
895
+
896
+ ```typescript
897
+ const chat = context.get("chat");
898
+
899
+ // Register component with handler
900
+ chat.registerComponent({
901
+ component: myComponent,
902
+ handler: async (props) => {
903
+ // Handle component rendering
904
+ }
905
+ });
906
+
907
+ // Remove component by name
908
+ chat.removeComponent("Answer");
909
+
910
+ // Get all registered components
911
+ const components = await chat.getComponents();
912
+ ```
913
+
914
+ **Transcript Item Types:**
915
+
916
+ ```typescript
917
+ type TranscriptUserMessage = {
918
+ id: string;
919
+ role: "user";
920
+ content: string;
921
+ name?: string;
922
+ createdAt?: string;
923
+ attachments?: Array<{ type: "image"; url: string }>;
924
+ };
925
+
926
+ type TranscriptAssistantMessage = {
927
+ id: string;
928
+ role: "assistant";
929
+ content: string;
930
+ name?: string;
931
+ createdAt?: string;
932
+ };
933
+
934
+ type TranscriptEventMessage = {
935
+ id: string;
936
+ role: "event";
937
+ name: string;
938
+ payload: unknown;
939
+ createdAt?: string;
940
+ };
941
+
942
+ type TranscriptSummaryMessage = {
943
+ id: string;
944
+ role: "summary";
945
+ content: string;
946
+ };
947
+ ```
948
+
949
+ #### Transcript Analysis Patterns
950
+
951
+ ```typescript
952
+ import { context, Conversation } from "@botpress/runtime";
953
+
954
+ export const Chat = new Conversation({
955
+ channel: "chat.channel",
956
+
957
+ async handler({ message, execute }) {
958
+ const chat = context.get("chat");
959
+ const transcript = await chat.fetchTranscript();
960
+
961
+ // Detect conversation patterns
962
+ const userMessages = transcript.filter(msg => msg.role === "user");
963
+ const hasRepeatedQuestion = userMessages.some((msg, idx) =>
964
+ idx > 0 && msg.content === userMessages[idx - 1].content
965
+ );
966
+
967
+ if (hasRepeatedQuestion) {
968
+ // User asked the same question twice - may need clarification
969
+ await execute({
970
+ instructions: `The user seems frustrated or confused.
971
+ Provide extra clarification and ask if they need more help.`,
972
+ tools: [searchTool]
973
+ });
974
+ } else {
975
+ // Normal conversation flow
976
+ await execute({
977
+ instructions: "You are a helpful assistant",
978
+ tools: [searchTool]
979
+ });
980
+ }
981
+ }
982
+ });
983
+ ```
984
+
985
+ **Analysis Patterns:**
986
+ - **Detect frustration**: Repeated questions, negative sentiment
987
+ - **Identify complexity**: Long conversations may need escalation
988
+ - **Track engagement**: Message frequency and length analysis
989
+ - **Content-based routing**: Images, files, code snippets
990
+
991
+ ### Error Handling
992
+
993
+ ```typescript
994
+ export const Chat = new Conversation({
995
+ channel: "chat.channel",
996
+
997
+ async handler({ message, conversation, execute }) {
998
+ try {
999
+ await execute({
1000
+ instructions: "You are a helpful assistant",
1001
+ tools: [riskyTool]
1002
+ });
1003
+ } catch (error) {
1004
+ console.error("Execution failed:", error);
1005
+
1006
+ // Send error message to user
1007
+ await conversation.send({
1008
+ type: "text",
1009
+ payload: {
1010
+ text: "I apologize, but I encountered an error. Please try again later."
1011
+ }
1012
+ });
1013
+
1014
+ // Optionally notify admins
1015
+ await notifyAdmins({
1016
+ error: error.message,
1017
+ conversationId: conversation.id
1018
+ });
1019
+ }
1020
+ }
1021
+ });
1022
+ ```
1023
+
1024
+ ## Best Practices
1025
+
1026
+ ### 1. Handle All Message Types
1027
+ ```typescript
1028
+ async handler({ message, conversation }) {
1029
+ switch (message?.type) {
1030
+ case "text":
1031
+ // Handle text
1032
+ break;
1033
+ case "image":
1034
+ // Handle image
1035
+ break;
1036
+ case "choice":
1037
+ // Handle choice selection
1038
+ break;
1039
+ default:
1040
+ // Handle unknown types
1041
+ }
1042
+ }
1043
+ ```
1044
+
1045
+ ### 2. Validate Input
1046
+ ```typescript
1047
+ if (message?.type === "text") {
1048
+ const text = message.payload.text?.trim();
1049
+ if (!text || text.length > 1000) {
1050
+ await conversation.send({
1051
+ type: "text",
1052
+ payload: { text: "Please provide a valid message (1-1000 characters)" }
1053
+ });
1054
+ return;
1055
+ }
1056
+ }
1057
+ ```
1058
+
1059
+ ### 3. Use Early Returns
1060
+ ```typescript
1061
+ async handler({ message, state, conversation, execute }) {
1062
+ // Handle commands first
1063
+ if (message?.payload.text?.startsWith("/")) {
1064
+ // Handle command inline
1065
+ return; // Early return
1066
+ }
1067
+
1068
+ // Handle special cases
1069
+ if (state.waitingForInput) {
1070
+ // Handle input
1071
+ return; // Early return
1072
+ }
1073
+
1074
+ // Default AI handling
1075
+ await execute({ instructions: "..." });
1076
+ }
1077
+ ```
1078
+
1079
+ ### 4. Provide Clear Instructions
1080
+ ```typescript
1081
+ await execute({
1082
+ instructions: `You are a customer support assistant for ACME Corp.
1083
+
1084
+ Guidelines:
1085
+ - Be helpful and professional
1086
+ - Keep responses concise (under 100 words)
1087
+ - Always verify customer identity before sharing sensitive info
1088
+ - Escalate to human agent if customer seems frustrated
1089
+
1090
+ Current user: ${user.name}
1091
+ Account type: ${user.accountType}`
1092
+ });
1093
+ ```
1094
+
1095
+ ### 5. Clean Up State
1096
+ ```typescript
1097
+ // Reset state when conversation ends
1098
+ async handler({ message, state, conversation }) {
1099
+ if (message?.payload.text === "/end") {
1100
+ // Reset state properties
1101
+ state.count = 0;
1102
+ state.userName = undefined;
1103
+ state.activeWorkflowId = undefined;
1104
+
1105
+ await conversation.send({
1106
+ type: "text",
1107
+ payload: { text: "Conversation ended. State cleared." }
1108
+ });
1109
+ }
1110
+ }
1111
+ ```