@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,823 @@
1
+ # Actions
2
+
3
+ Actions are callable, strongly-typed functions that encapsulate reusable logic accessible throughout your agent. They can be invoked from workflows, conversations, other actions, or exposed as tools for AI.
4
+
5
+ ## Overview
6
+
7
+ Actions enable you to:
8
+ - Create reusable business logic across your bot
9
+ - Call integration APIs with type safety
10
+ - Expose functionality as AI-callable tools
11
+ - Compose complex operations from simple functions
12
+ - Test logic independently from conversational flows
13
+
14
+ ## File Location
15
+
16
+ - **Location**: `src/actions/*.ts`
17
+ - **Auto-registration**: Files in this directory automatically become available as `actions.{actionName}`
18
+ - **Export pattern**: Both `export const` and `export default` work
19
+ - **`export const`** (Recommended): Enables direct imports and passing to `execute()` as tools
20
+ - **`export default`**: Simpler for single-action files
21
+
22
+ ## Basic Action Structure
23
+
24
+ ```typescript
25
+ import { Action, z } from "@botpress/runtime";
26
+
27
+ // Option 1: Named export (recommended)
28
+ export const myAction = new Action({
29
+ name: "myAction",
30
+ input: z.object({
31
+ userId: z.string(),
32
+ }),
33
+ output: z.object({
34
+ data: z.string(),
35
+ timestamp: z.string()
36
+ }),
37
+ handler: async ({ input, client }) {
38
+ // Your business logic here
39
+ const result = await fetchData(input.userId);
40
+
41
+ return {
42
+ data: result,
43
+ timestamp: new Date().toISOString()
44
+ };
45
+ },
46
+ });
47
+
48
+ // Option 2: Default export (also valid)
49
+ export default new Action({
50
+ name: "myAction",
51
+ // ... same configuration
52
+ });
53
+ ```
54
+
55
+ **Why use `export const`?**
56
+ - Enables direct imports: `import { myAction } from "./actions/myAction"`
57
+ - Can pass to `execute()`: `tools: [myAction.asTool()]`
58
+ - Better for action composition and reusability
59
+
60
+ ## Action Properties
61
+
62
+ | Property | Type | Required | Description |
63
+ |----------|------|----------|-------------|
64
+ | **name** | `string` | Yes | Unique alphanumeric identifier (no spaces or special characters) |
65
+ | **input** | `z.ZodTypeAny` | Yes | Zod schema defining input parameters |
66
+ | **output** | `z.ZodTypeAny` | Yes | Zod schema defining return type |
67
+ | **handler** | `function` | Yes | Async function implementing the action logic |
68
+ | **title** | `string` | No | Human-readable display name |
69
+ | **description** | `string` | No | Description of the action's functionality |
70
+ | **attributes** | `Record<string, string>` | No | Custom metadata for categorization |
71
+ | **cached** | `boolean` | No | Enable caching for identical inputs (defaults to `false`) |
72
+
73
+ ## Handler Syntax
74
+
75
+ Action handlers receive a props object containing `input` and `client`. Both method syntax and arrow functions are supported.
76
+
77
+ ### Method Syntax (Recommended)
78
+
79
+ ```typescript
80
+ export const fetchUser = new Action({
81
+ name: "fetchUser",
82
+ input: z.object({ userId: z.string() }),
83
+ output: z.object({
84
+ name: z.string(),
85
+ email: z.string()
86
+ }),
87
+
88
+ async handler({ input, client }) {
89
+ // input: validated input matching your input schema
90
+ // client: Botpress API client for making API calls
91
+ const { user } = await client.getUser({ id: input.userId });
92
+ return {
93
+ name: user.name,
94
+ email: user.tags.email
95
+ };
96
+ }
97
+ });
98
+ ```
99
+
100
+ ### Arrow Function Syntax
101
+
102
+ ```typescript
103
+ export const updateUser = new Action({
104
+ name: "updateUser",
105
+ input: z.object({
106
+ userId: z.string(),
107
+ name: z.string()
108
+ }),
109
+ output: z.object({ success: z.boolean() }),
110
+
111
+ handler: async ({ input, client }) => {
112
+ await client.updateUser({
113
+ id: input.userId,
114
+ name: input.name
115
+ });
116
+ return { success: true };
117
+ }
118
+ });
119
+ ```
120
+
121
+ ### Common Mistakes
122
+
123
+ ```typescript
124
+ // ❌ WRONG - Destructuring input fields directly
125
+ export const fetchUser = new Action({
126
+ name: "fetchUser",
127
+ input: z.object({ userId: z.string() }),
128
+ output: z.object({ name: z.string() }),
129
+
130
+ async handler({ userId }) { // ❌ Wrong! Must be { input }
131
+ return { name: userId };
132
+ }
133
+ });
134
+
135
+ // ✅ CORRECT - Use { input } wrapper
136
+ export const fetchUser = new Action({
137
+ name: "fetchUser",
138
+ input: z.object({ userId: z.string() }),
139
+ output: z.object({ name: z.string() }),
140
+
141
+ async handler({ input }) { // ✅ Correct
142
+ const { userId } = input; // Destructure inside handler
143
+ return { name: userId };
144
+ }
145
+ });
146
+ ```
147
+
148
+ **Key points:**
149
+ - Handler receives `{ input, client }` props object
150
+ - Both method syntax and arrow functions work
151
+ - Destructure input fields **inside** the handler body, not in the parameter
152
+ - **Actions CANNOT destructure input fields directly** (unlike Tools)
153
+ - Omit `client` if not needed: `async handler({ input })`
154
+
155
+ ## Calling Actions
156
+
157
+ ### From Workflows
158
+
159
+ ```typescript
160
+ import { Workflow, actions } from "@botpress/runtime";
161
+
162
+ export default new Workflow({
163
+ name: "processOrder",
164
+ input: z.object({ orderId: z.string() }),
165
+
166
+ handler: async ({ input }) => {
167
+ // Call your custom actions
168
+ const order = await actions.fetchOrder({ orderId: input.orderId });
169
+ const result = await actions.processPayment({ order });
170
+
171
+ return result;
172
+ }
173
+ });
174
+ ```
175
+
176
+ ### From Other Actions
177
+
178
+ ```typescript
179
+ import { Action, actions, z } from "@botpress/runtime";
180
+
181
+ export default new Action({
182
+ name: "compositeAction",
183
+ input: z.object({ userId: z.string() }),
184
+ output: z.object({ success: z.boolean() }),
185
+
186
+ async handler({ input }) {
187
+ // Compose multiple actions
188
+ const userData = await actions.fetchUser({ userId: input.userId });
189
+ const profile = await actions.buildProfile({ user: userData });
190
+ return { success: true };
191
+ }
192
+ });
193
+ ```
194
+
195
+ ### Integration Actions
196
+
197
+ Call integration actions from within your actions:
198
+
199
+ ```typescript
200
+ import { actions } from "@botpress/runtime";
201
+
202
+ // Inside action handler
203
+ await actions.slack.sendMessage({
204
+ channel: "#general",
205
+ text: "Message from bot"
206
+ });
207
+
208
+ await actions.linear.issueList({ first: 10 });
209
+ ```
210
+
211
+ See **[Integration Actions](./integration-actions.md)** for complete guide.
212
+
213
+ ## Accessing Additional Context
214
+
215
+ Actions can access additional runtime context through **direct imports** or the **Context API**.
216
+
217
+ Direct imports are best for the global proxies that actually exist at runtime, such as `user`, `bot`, `configuration`, and `adk`. For optional per-request values like `conversation` and `message`, use the Context API.
218
+
219
+ ### Method 1: Direct Imports (Global Context Proxies)
220
+
221
+ ```typescript
222
+ import { Action, z, user, bot, adk } from "@botpress/runtime";
223
+ import { context } from "@botpress/runtime";
224
+
225
+ export default new Action({
226
+ name: "updateUserProfile",
227
+ input: z.object({ name: z.string() }),
228
+ output: z.object({ success: z.boolean() }),
229
+
230
+ async handler({ input }) {
231
+ // Access user state directly (when in conversation context)
232
+ user.state.profileName = input.name;
233
+ user.tags.profileComplete = "true";
234
+
235
+ // Access bot state
236
+ bot.state.totalUsers += 1;
237
+
238
+ // Access conversation (when in conversation context)
239
+ const conversation = context.get("conversation", { optional: true });
240
+ console.log(conversation?.id);
241
+
242
+ // Use ADK utilities
243
+ const extracted = await adk.zai.extract(
244
+ input.name,
245
+ z.object({ firstName: z.string(), lastName: z.string() })
246
+ );
247
+
248
+ return { success: true };
249
+ }
250
+ });
251
+ ```
252
+
253
+ ### Method 2: Context API (Explicit Retrieval)
254
+
255
+ ```typescript
256
+ import { Action, z, context } from "@botpress/runtime";
257
+
258
+ export default new Action({
259
+ name: "checkContext",
260
+ input: z.object({}),
261
+ output: z.object({ hasUser: z.boolean() }),
262
+
263
+ async handler({ input }) {
264
+ // Optional context (may not be available in all scenarios)
265
+ const user = context.get("user", { optional: true });
266
+ const conversation = context.get("conversation", { optional: true });
267
+ const message = context.get("message", { optional: true });
268
+
269
+ // Always available
270
+ const client = context.get("client");
271
+ const citations = context.get("citations");
272
+
273
+ if (user) {
274
+ console.log(`User: ${user.id}`);
275
+ }
276
+
277
+ return { hasUser: !!user };
278
+ }
279
+ });
280
+ ```
281
+
282
+ **Which method to use?**
283
+
284
+ - **Direct imports**: Use for `user`, `bot`, `adk` when you know they're available (simpler syntax)
285
+ - **Context API**: Use for optional context (`user`, `conversation`, `message`) or when you need `client` and `citations`
286
+
287
+ **Always available:** `bot`, `adk`, `client`, `context`
288
+ **Conditionally available:** `user`, `conversation`, `message` (only in conversation context)
289
+
290
+ See **[Context API](./context-api.md)** for complete details on all context keys and availability.
291
+
292
+ ## Advanced Action Patterns
293
+
294
+ ### Pattern 1: Integration Wrapper Pattern
295
+
296
+ **Purpose:** Wrap integration actions with bot-specific logic, data transformation, and local persistence.
297
+
298
+ **When to use:**
299
+ - Syncing external resources (products, issues, orders) to local tables
300
+ - Normalizing data from different integrations
301
+ - Adding business logic or validation
302
+ - Maintaining local cache of external data
303
+
304
+ **Abstract Structure:**
305
+
306
+ ```typescript
307
+ export default new Action({
308
+ name: "syncResource",
309
+ description: "Fetch and sync external resource to local storage",
310
+
311
+ input: z.object({
312
+ resourceId: z.string().describe("External resource identifier"),
313
+ }),
314
+
315
+ output: BotResourceSchema, // Your normalized schema
316
+
317
+ async handler({ input }) {
318
+ // 1. Call integration action
319
+ const externalData = await actions.integration.getResource({
320
+ id: input.resourceId
321
+ });
322
+
323
+ // 2. Transform to bot schema
324
+ const normalizedData = transformToBotSchema(externalData);
325
+
326
+ // 3. Persist to table (prevents duplicates)
327
+ await ResourceTable.upsertRows({
328
+ rows: [normalizedData],
329
+ keyColumn: "id" // Unique identifier
330
+ });
331
+
332
+ // 4. Return normalized data
333
+ return normalizedData;
334
+ }
335
+ });
336
+ ```
337
+
338
+ **Real Example - Shopify: Sync Products**
339
+
340
+ ```typescript
341
+ import { Action, actions, z } from "@botpress/runtime";
342
+ import { ProductsTable } from "../tables/shopify";
343
+
344
+ export default new Action({
345
+ name: "syncProduct",
346
+ description: "Sync a Shopify product to local database",
347
+
348
+ input: z.object({
349
+ productId: z.string().describe("Shopify product ID"),
350
+ includeVariants: z.boolean().default(true)
351
+ }),
352
+
353
+ output: z.object({
354
+ id: z.string(),
355
+ title: z.string(),
356
+ price: z.number(),
357
+ inventory: z.number(),
358
+ lastSynced: z.string()
359
+ }),
360
+
361
+ async handler({ input }) {
362
+ // 1. Fetch from Shopify
363
+ const shopifyProduct = await actions.shopify.getProduct({
364
+ id: input.productId,
365
+ fields: ["id", "title", "variants", "inventory"]
366
+ });
367
+
368
+ // 2. Transform to bot schema
369
+ const product = {
370
+ id: shopifyProduct.id,
371
+ title: shopifyProduct.title,
372
+ price: shopifyProduct.variants[0].price,
373
+ inventory: shopifyProduct.variants.reduce((sum, v) => sum + v.inventory_quantity, 0),
374
+ lastSynced: new Date().toISOString()
375
+ };
376
+
377
+ // 3. Upsert to table
378
+ await ProductsTable.upsertRows({
379
+ rows: [product],
380
+ keyColumn: "id"
381
+ });
382
+
383
+ return product;
384
+ }
385
+ });
386
+ ```
387
+
388
+ **Key Benefits:**
389
+ - **Consistency**: All external data follows your bot's schema
390
+ - **Performance**: Local table queries are faster than API calls
391
+ - **Reliability**: Works even if integration is temporarily unavailable
392
+ - **Extensibility**: Easy to add business logic or validation
393
+
394
+ ### Pattern 2: Resource State Management
395
+
396
+ **Purpose:** Update external resource state and keep local cache in sync.
397
+
398
+ **Real Example - Linear: Update Issue Status**
399
+
400
+ ```typescript
401
+ import { Action, actions, z } from "@botpress/runtime";
402
+ import { IssuesTable } from "../tables/linear";
403
+
404
+ export default new Action({
405
+ name: "updateIssueStatus",
406
+
407
+ input: z.object({
408
+ issueId: z.string(),
409
+ stateId: z.string(),
410
+ comment: z.string().optional()
411
+ }),
412
+
413
+ output: z.object({
414
+ id: z.string(),
415
+ title: z.string(),
416
+ state: z.string(),
417
+ updatedAt: z.string()
418
+ }),
419
+
420
+ async handler({ input }) {
421
+ // 1. Update in Linear
422
+ const issue = await actions.linear.updateIssue({
423
+ id: input.issueId,
424
+ stateId: input.stateId,
425
+ ...(input.comment && {
426
+ commentBody: input.comment
427
+ })
428
+ });
429
+
430
+ // 2. Transform and sync to table
431
+ const normalized = {
432
+ id: issue.id,
433
+ title: issue.title,
434
+ state: issue.state.name,
435
+ updatedAt: new Date().toISOString()
436
+ };
437
+
438
+ await IssuesTable.upsertRows({
439
+ rows: [normalized],
440
+ keyColumn: "id"
441
+ });
442
+
443
+ return normalized;
444
+ }
445
+ });
446
+ ```
447
+
448
+ **When to use this pattern:**
449
+ - Tracking order fulfillment status
450
+ - Managing ticket/issue lifecycle
451
+ - Handling conversation states
452
+ - Any resource with state transitions
453
+
454
+ ## Schema Design Best Practices
455
+
456
+ **Core Principles:**
457
+
458
+ ```typescript
459
+ import { z } from "@botpress/runtime";
460
+
461
+ // 1. Always export both type and schema
462
+ export type Product = z.infer<typeof ProductSchema>;
463
+ export const ProductSchema = z.object({
464
+ id: z.string(),
465
+ name: z.string(),
466
+ price: z.number()
467
+ });
468
+
469
+ // 2. Use .describe() on every field (helps AI tools)
470
+ const schema = z.object({
471
+ productId: z.string().describe("Unique product identifier"),
472
+ price: z.number().describe("Price in cents"),
473
+ inStock: z.boolean().describe("Whether product is available")
474
+ });
475
+
476
+ // 3. ISO strings for dates (NOT z.date())
477
+ const timeFields = z.object({
478
+ createdAt: z.string().describe("Creation date as ISO 8601 string"),
479
+ updatedAt: z.string().optional().describe("Last update date")
480
+ });
481
+
482
+ // 4. Use .optional() for optional fields
483
+ const optionalFields = z.object({
484
+ name: z.string(), // Required
485
+ description: z.string().optional(), // Optional
486
+ metadata: z.record(z.unknown()).optional()
487
+ });
488
+
489
+ // 5. Use .passthrough() for external API data
490
+ const externalData = z.object({
491
+ id: z.string(),
492
+ // Allow unknown fields from external API
493
+ }).passthrough();
494
+
495
+ // 6. Define nested objects explicitly
496
+ const structured = z.object({
497
+ product: z.object({ // ✅ Explicit structure
498
+ id: z.string(),
499
+ name: z.string()
500
+ }),
501
+ metadata: z.record(z.unknown()) // ❌ Use only when structure is truly unknown
502
+ });
503
+ ```
504
+
505
+ **Key Patterns:**
506
+ 1. **Export both type and schema** - TypeScript type from `z.infer<>`
507
+ 2. **Describe every field** - Helps AI tools and documentation
508
+ 3. **ISO strings for dates** - Use `z.string()` not `z.date()`
509
+ 4. **Optional vs nullable** - Use `.optional()` for optional fields
510
+ 5. **Passthrough for external APIs** - Use `.passthrough()` when structure varies
511
+ 6. **Separate schemas file** - Keep complex schemas in `src/schemas.ts`
512
+ 7. **Explicit nested objects** - Define structure instead of `z.unknown()`
513
+
514
+ ## Converting Actions to Tools
515
+
516
+ Actions can be converted to AI-callable tools using the `.asTool()` method. This allows AI agents to autonomously call your actions during conversations.
517
+
518
+ ### Basic Usage
519
+
520
+ ```typescript
521
+ import { actions, Conversation } from "@botpress/runtime";
522
+
523
+ export default new Conversation({
524
+ handler: async ({ execute }) => {
525
+ await execute({
526
+ instructions: "Help the user with their request",
527
+ tools: [
528
+ // Convert existing actions to tools
529
+ actions.fetchUser.asTool(),
530
+ actions.sendEmail.asTool(),
531
+ actions.searchDatabase.asTool()
532
+ ]
533
+ });
534
+ }
535
+ });
536
+ ```
537
+
538
+ ### Tool Patterns: Blocking vs Non-Blocking
539
+
540
+ #### Pattern Comparison
541
+
542
+ | Pattern | Returns | Execution | Example |
543
+ |---------|---------|-----------|---------|
544
+ | **Direct Action** | Final result | Synchronous | `fetchUser()` |
545
+ | **Workflow Wrapper** | Workflow ID | Asynchronous | `startIndexing()` |
546
+
547
+ #### 1. Direct Actions
548
+
549
+ Actions that complete and return results immediately:
550
+
551
+ ```typescript
552
+ import { Action, z } from "@botpress/runtime";
553
+
554
+ export default new Action({
555
+ name: "calculateShipping",
556
+ description: "Calculate shipping cost for an order",
557
+
558
+ input: z.object({
559
+ weight: z.number(),
560
+ destination: z.string()
561
+ }),
562
+
563
+ output: z.object({
564
+ cost: z.number(),
565
+ estimatedDays: z.number()
566
+ }),
567
+
568
+ async handler({ input }) {
569
+ // Business logic that completes immediately
570
+ const baseRate = 5.99;
571
+ const perKg = 2.50;
572
+ const cost = baseRate + (input.weight * perKg);
573
+
574
+ const estimatedDays = input.destination === "domestic" ? 3 : 7;
575
+
576
+ return { cost, estimatedDays };
577
+ }
578
+ });
579
+
580
+ // Use as tool - AI can call this during conversation
581
+ await execute({
582
+ instructions: "Help the user with shipping",
583
+ tools: [actions.calculateShipping.asTool()]
584
+ });
585
+ ```
586
+
587
+ #### 2. Workflow Wrappers
588
+
589
+ Actions that start workflows and return workflow ID:
590
+
591
+ ```typescript
592
+ import { Action, z } from "@botpress/runtime";
593
+ import { DataAnalysisWorkflow } from "../workflows/data-analysis";
594
+
595
+ export default new Action({
596
+ name: "startDataAnalysis",
597
+ description: "Start data analysis workflow",
598
+
599
+ input: z.object({
600
+ datasetId: z.string()
601
+ }),
602
+
603
+ output: z.object({
604
+ workflowId: z.string()
605
+ }),
606
+
607
+ async handler({ input }) {
608
+ // Calls workflow.start() - returns immediately
609
+ const instance = await DataAnalysisWorkflow.start({
610
+ datasetId: input.datasetId
611
+ });
612
+ return { workflowId: instance.id };
613
+ }
614
+ });
615
+
616
+ // Companion action to check status
617
+ export const checkAnalysisStatus = new Action({
618
+ name: "checkAnalysisStatus",
619
+ description: "Check workflow status",
620
+
621
+ input: z.object({
622
+ workflowId: z.string()
623
+ }),
624
+
625
+ output: z.object({
626
+ status: z.string()
627
+ }),
628
+
629
+ async handler({ input, client }) {
630
+ const { workflow } = await client.getWorkflow({ id: input.workflowId });
631
+ return { status: workflow.status };
632
+ }
633
+ });
634
+
635
+ // Use both as tools
636
+ await execute({
637
+ instructions: "Help the user",
638
+ tools: [
639
+ actions.startDataAnalysis.asTool(),
640
+ actions.checkAnalysisStatus.asTool()
641
+ ]
642
+ });
643
+ ```
644
+
645
+ ### Custom Tool Conversion
646
+
647
+ ```typescript
648
+ // Basic conversion
649
+ const shippingTool = actions.calculateShipping.asTool();
650
+
651
+ // With custom description
652
+ const analysisTool = actions.startDataAnalysis.asTool({
653
+ description: "Analyze dataset and return workflow ID"
654
+ });
655
+ ```
656
+
657
+ > **See Also:** [Exposing Workflows as Tools](./workflows.md#exposing-workflows-as-tools-non-blocking-pattern) for comprehensive workflow wrapper patterns and tracking examples.
658
+
659
+ ## Best Practices
660
+
661
+ 1. **Use descriptive names**: `fetchUserProfile` not `getUser`
662
+ 2. **Validate inputs thoroughly**: Use Zod's validation features
663
+ 3. **Handle errors gracefully**: Catch and wrap errors with context
664
+ 4. **Keep actions focused**: One action = one responsibility
665
+ 5. **Document complex logic**: Add comments for non-obvious operations
666
+ 6. **Use caching wisely**: Enable `cached: true` for deterministic operations
667
+
668
+ ```typescript
669
+ export default new Action({
670
+ name: "fetchUserProfile",
671
+ description: "Fetches complete user profile with preferences",
672
+
673
+ input: z.object({
674
+ userId: z.string().uuid(), // Validate UUID format
675
+ includePreferences: z.boolean().default(false)
676
+ }),
677
+
678
+ output: z.object({
679
+ user: z.object({
680
+ id: z.string(),
681
+ name: z.string(),
682
+ email: z.string().email()
683
+ }),
684
+ preferences: z.object({
685
+ language: z.string(),
686
+ timezone: z.string()
687
+ }).optional()
688
+ }),
689
+
690
+ cached: true, // Cache for performance
691
+
692
+ async handler({ input, client }) {
693
+ // Validate user exists
694
+ const { user } = await client.getUser({ id: input.userId });
695
+ if (!user) {
696
+ throw new Error(`User ${input.userId} not found`);
697
+ }
698
+
699
+ // Conditionally fetch preferences
700
+ const preferences = input.includePreferences
701
+ ? await fetchUserPreferences(input.userId)
702
+ : undefined;
703
+
704
+ return {
705
+ user: {
706
+ id: user.id,
707
+ name: user.name,
708
+ email: user.tags.email
709
+ },
710
+ preferences
711
+ };
712
+ }
713
+ });
714
+ ```
715
+
716
+ ## Common Patterns
717
+
718
+ ### Composition Pattern
719
+
720
+ Combine multiple actions for complex operations:
721
+
722
+ ```typescript
723
+ export default new Action({
724
+ name: "processOrder",
725
+ input: z.object({ orderId: z.string() }),
726
+ output: z.object({ success: z.boolean() }),
727
+
728
+ async handler({ input }) {
729
+ // Compose multiple actions
730
+ const order = await actions.fetchOrder({ id: input.orderId });
731
+ const validated = await actions.validateOrder({ order });
732
+
733
+ if (validated.isValid) {
734
+ await actions.chargePayment({
735
+ amount: order.total,
736
+ customerId: order.customerId
737
+ });
738
+ await actions.sendConfirmation({ order });
739
+ await actions.updateInventory({ items: order.items });
740
+ }
741
+
742
+ return { success: validated.isValid };
743
+ }
744
+ });
745
+ ```
746
+
747
+ ### Error Recovery Pattern
748
+
749
+ Handle failures gracefully with retries:
750
+
751
+ ```typescript
752
+ export default new Action({
753
+ name: "resilientFetch",
754
+ input: z.object({ url: z.string().url() }),
755
+ output: z.object({ data: z.unknown() }),
756
+
757
+ async handler({ input }) {
758
+ const maxRetries = 3;
759
+ let lastError;
760
+
761
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
762
+ try {
763
+ const response = await fetch(input.url);
764
+ if (!response.ok) {
765
+ throw new Error(`HTTP ${response.status}`);
766
+ }
767
+ return { data: await response.json() };
768
+ } catch (error) {
769
+ lastError = error;
770
+ if (attempt < maxRetries) {
771
+ // Exponential backoff
772
+ await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt)));
773
+ }
774
+ }
775
+ }
776
+
777
+ throw new Error(`Failed after ${maxRetries} attempts: ${lastError.message}`);
778
+ }
779
+ });
780
+ ```
781
+
782
+ ## Troubleshooting
783
+
784
+ ### Common Issues
785
+
786
+ 1. **"Cannot destructure property" error**
787
+ - **Cause**: Destructuring input fields directly in Action handler parameter
788
+ - **Wrong**: `async handler({ userId }) { ... }`
789
+ - **Correct**: `async handler({ input }) { const { userId } = input; ... }`
790
+ - **Note**: Tools can destructure directly, but Actions cannot
791
+
792
+ 2. **"user is not defined" or context errors**
793
+ - **Cause**: Accessing conversation context outside of conversation context
794
+ - **Solution**: Use `context.get("user", { optional: true })` to check availability
795
+ - **Note**: `user`, `conversation`, `message` are only available in conversation contexts
796
+
797
+ 3. **Action not found**
798
+ - Verify file is in `src/actions/` directory
799
+ - Check import: `import { actions } from "@botpress/runtime"`
800
+ - Ensure action name matches file export
801
+
802
+ 4. **Type errors with inputs/outputs**
803
+ - Verify Zod schemas match actual data
804
+ - Use `.optional()` for optional fields
805
+ - Check for typos in property names
806
+
807
+ 5. **State properties undefined**
808
+ - **Cause**: State properties must be defined in `agent.config.ts` schemas
809
+ - **Solution**: Add properties to `user.state` or `bot.state` schema in config
810
+ - **Example**: Can't use `user.state.characterName` unless defined in config
811
+
812
+ 6. **Caching issues**
813
+ - **Issue**: Cached results not updating when expected
814
+ - **Solution**: Only use `cached: true` for deterministic functions
815
+ - **Tip**: Avoid caching for time-sensitive or user-specific data
816
+
817
+ ## See Also
818
+
819
+ - [Tools](./tools.md) - AI-callable tools for autonomous execution
820
+ - [Workflows](./workflows.md) - Multi-step processes with state management
821
+ - [Context API](./context-api.md) - Access runtime context and services
822
+ - [Integration Actions](./integration-actions.md) - Using integration actions
823
+ - [Patterns & Common Mistakes](./patterns-mistakes.md) - Best practices for Zod schemas