@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.
- 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/assets-q7jt8r2q.md +237 -0
- package/dist/autonomous-execution-fymc12e5.md +747 -0
- package/dist/cli-c31e06yr.md +1334 -0
- package/dist/cli.js +970 -566
- 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/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 +952 -137
- 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,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
|
+
```
|