@falai/agent 0.6.3 → 0.6.4

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 (128) hide show
  1. package/README.md +89 -29
  2. package/dist/cjs/constants/index.d.ts +6 -1
  3. package/dist/cjs/constants/index.d.ts.map +1 -1
  4. package/dist/cjs/constants/index.js +8 -3
  5. package/dist/cjs/constants/index.js.map +1 -1
  6. package/dist/cjs/core/Agent.d.ts +22 -0
  7. package/dist/cjs/core/Agent.d.ts.map +1 -1
  8. package/dist/cjs/core/Agent.js +108 -21
  9. package/dist/cjs/core/Agent.js.map +1 -1
  10. package/dist/cjs/core/Events.d.ts +13 -0
  11. package/dist/cjs/core/Events.d.ts.map +1 -1
  12. package/dist/cjs/core/Events.js +28 -14
  13. package/dist/cjs/core/Events.js.map +1 -1
  14. package/dist/cjs/core/Route.d.ts.map +1 -1
  15. package/dist/cjs/core/Route.js +4 -4
  16. package/dist/cjs/core/Route.js.map +1 -1
  17. package/dist/cjs/core/RoutingEngine.d.ts +6 -1
  18. package/dist/cjs/core/RoutingEngine.d.ts.map +1 -1
  19. package/dist/cjs/core/RoutingEngine.js +112 -37
  20. package/dist/cjs/core/RoutingEngine.js.map +1 -1
  21. package/dist/cjs/core/State.d.ts +15 -5
  22. package/dist/cjs/core/State.d.ts.map +1 -1
  23. package/dist/cjs/core/State.js +24 -5
  24. package/dist/cjs/core/State.js.map +1 -1
  25. package/dist/cjs/core/Tool.d.ts +8 -1
  26. package/dist/cjs/core/Tool.d.ts.map +1 -1
  27. package/dist/cjs/core/Tool.js +25 -28
  28. package/dist/cjs/core/Tool.js.map +1 -1
  29. package/dist/cjs/core/Transition.js +1 -1
  30. package/dist/cjs/index.d.ts +1 -1
  31. package/dist/cjs/index.d.ts.map +1 -1
  32. package/dist/cjs/index.js +3 -2
  33. package/dist/cjs/index.js.map +1 -1
  34. package/dist/cjs/types/agent.d.ts +5 -0
  35. package/dist/cjs/types/agent.d.ts.map +1 -1
  36. package/dist/cjs/types/agent.js.map +1 -1
  37. package/dist/cjs/types/route.d.ts +7 -1
  38. package/dist/cjs/types/route.d.ts.map +1 -1
  39. package/dist/cjs/types/session.d.ts +12 -1
  40. package/dist/cjs/types/session.d.ts.map +1 -1
  41. package/dist/cjs/types/session.js +26 -5
  42. package/dist/cjs/types/session.js.map +1 -1
  43. package/dist/cjs/utils/logger.d.ts +10 -0
  44. package/dist/cjs/utils/logger.d.ts.map +1 -0
  45. package/dist/cjs/utils/logger.js +23 -0
  46. package/dist/cjs/utils/logger.js.map +1 -0
  47. package/dist/constants/index.d.ts +6 -1
  48. package/dist/constants/index.d.ts.map +1 -1
  49. package/dist/constants/index.js +6 -1
  50. package/dist/constants/index.js.map +1 -1
  51. package/dist/core/Agent.d.ts +22 -0
  52. package/dist/core/Agent.d.ts.map +1 -1
  53. package/dist/core/Agent.js +108 -21
  54. package/dist/core/Agent.js.map +1 -1
  55. package/dist/core/Events.d.ts +13 -0
  56. package/dist/core/Events.d.ts.map +1 -1
  57. package/dist/core/Events.js +28 -14
  58. package/dist/core/Events.js.map +1 -1
  59. package/dist/core/Route.d.ts.map +1 -1
  60. package/dist/core/Route.js +4 -4
  61. package/dist/core/Route.js.map +1 -1
  62. package/dist/core/RoutingEngine.d.ts +6 -1
  63. package/dist/core/RoutingEngine.d.ts.map +1 -1
  64. package/dist/core/RoutingEngine.js +112 -37
  65. package/dist/core/RoutingEngine.js.map +1 -1
  66. package/dist/core/State.d.ts +15 -5
  67. package/dist/core/State.d.ts.map +1 -1
  68. package/dist/core/State.js +25 -6
  69. package/dist/core/State.js.map +1 -1
  70. package/dist/core/Tool.d.ts +8 -1
  71. package/dist/core/Tool.d.ts.map +1 -1
  72. package/dist/core/Tool.js +25 -28
  73. package/dist/core/Tool.js.map +1 -1
  74. package/dist/core/Transition.js +1 -1
  75. package/dist/index.d.ts +1 -1
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +1 -1
  78. package/dist/index.js.map +1 -1
  79. package/dist/types/agent.d.ts +5 -0
  80. package/dist/types/agent.d.ts.map +1 -1
  81. package/dist/types/agent.js.map +1 -1
  82. package/dist/types/route.d.ts +7 -1
  83. package/dist/types/route.d.ts.map +1 -1
  84. package/dist/types/session.d.ts +12 -1
  85. package/dist/types/session.d.ts.map +1 -1
  86. package/dist/types/session.js +26 -5
  87. package/dist/types/session.js.map +1 -1
  88. package/dist/utils/logger.d.ts +10 -0
  89. package/dist/utils/logger.d.ts.map +1 -0
  90. package/dist/utils/logger.js +17 -0
  91. package/dist/utils/logger.js.map +1 -0
  92. package/docs/{CONSTRUCTOR_OPTIONS.md → AGENT.md} +79 -7
  93. package/docs/API_REFERENCE.md +309 -18
  94. package/docs/ARCHITECTURE.md +1 -1
  95. package/docs/DOCS.md +46 -22
  96. package/docs/GETTING_STARTED.md +1 -1
  97. package/docs/README.md +13 -5
  98. package/docs/ROUTES.md +743 -0
  99. package/docs/STATES.md +798 -0
  100. package/examples/business-onboarding.ts +46 -5
  101. package/examples/company-qna-agent.ts +107 -1
  102. package/examples/custom-database-persistence.ts +44 -1
  103. package/examples/declarative-agent.ts +80 -37
  104. package/examples/domain-scoping.ts +91 -21
  105. package/examples/extracted-data-modification.ts +64 -2
  106. package/examples/healthcare-agent.ts +61 -4
  107. package/examples/openai-agent.ts +24 -2
  108. package/examples/opensearch-persistence.ts +26 -1
  109. package/examples/persistent-onboarding.ts +84 -18
  110. package/examples/prisma-persistence.ts +90 -16
  111. package/examples/redis-persistence.ts +89 -17
  112. package/examples/rules-prohibitions.ts +300 -139
  113. package/examples/streaming-agent.ts +60 -0
  114. package/examples/travel-agent.ts +66 -24
  115. package/package.json +3 -2
  116. package/src/constants/index.ts +6 -1
  117. package/src/core/Agent.ts +135 -21
  118. package/src/core/Events.ts +73 -10
  119. package/src/core/Route.ts +8 -4
  120. package/src/core/RoutingEngine.ts +150 -39
  121. package/src/core/State.ts +35 -10
  122. package/src/core/Tool.ts +67 -10
  123. package/src/core/Transition.ts +1 -1
  124. package/src/index.ts +1 -1
  125. package/src/types/agent.ts +5 -0
  126. package/src/types/route.ts +10 -1
  127. package/src/types/session.ts +42 -6
  128. package/src/utils/logger.ts +19 -0
package/docs/STATES.md ADDED
@@ -0,0 +1,798 @@
1
+ # States Guide
2
+
3
+ A complete guide to creating and managing states in conversational flows.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ - [What is a State?](#what-is-a-state)
10
+ - [Creating States](#creating-states)
11
+ - [State Configuration](#state-configuration)
12
+ - [Transitions](#transitions)
13
+ - [Data Gathering](#data-gathering)
14
+ - [State Logic](#state-logic)
15
+ - [State Types](#state-types)
16
+ - [Advanced Patterns](#advanced-patterns)
17
+
18
+ ---
19
+
20
+ ## What is a State?
21
+
22
+ A **State** represents a specific step or moment in a conversation. Each state can:
23
+
24
+ - Display a message to the user (chat state)
25
+ - Execute a tool (tool state)
26
+ - Gather data from the conversation
27
+ - Make decisions about what to do next
28
+
29
+ ```typescript
30
+ // Example: A state that asks for user's name
31
+ const askName = route.initialState.transitionTo({
32
+ chatState: "What's your name?",
33
+ gather: ["firstName", "lastName"],
34
+ });
35
+ ```
36
+
37
+ **Key Concepts:**
38
+
39
+ - States form a **state machine** within a route
40
+ - Each state has a unique **ID** (auto-generated or custom)
41
+ - States can **gather data** from user responses
42
+ - States can be **skipped** based on conditions
43
+ - States can have **prerequisites** (required data)
44
+
45
+ ---
46
+
47
+ ## Creating States
48
+
49
+ ### Chat States
50
+
51
+ Chat states present a message and optionally gather data:
52
+
53
+ ```typescript
54
+ // Simple chat state
55
+ const welcome = route.initialState.transitionTo({
56
+ chatState: "Welcome! How can I help you today?",
57
+ });
58
+
59
+ // Chat state with data gathering
60
+ const askDestination = welcome.transitionTo({
61
+ chatState: "Where would you like to fly?",
62
+ gather: ["destination"],
63
+ });
64
+
65
+ // Chat state with custom ID
66
+ const askDates = askDestination.transitionTo({
67
+ id: "ask_travel_dates",
68
+ chatState: "When would you like to depart?",
69
+ gather: ["departureDate"],
70
+ });
71
+ ```
72
+
73
+ ### Tool States
74
+
75
+ Tool states execute functions and can update context or extracted data:
76
+
77
+ ```typescript
78
+ import { defineTool } from "@falai/agent";
79
+
80
+ const searchFlights = defineTool<Context, [], Results>(
81
+ "search_flights",
82
+ async ({ context, extracted }) => {
83
+ const results = await api.search(extracted.destination);
84
+ return {
85
+ data: results,
86
+ contextUpdate: { availableFlights: results },
87
+ };
88
+ }
89
+ );
90
+
91
+ // Tool state
92
+ const searchState = askDates.transitionTo({
93
+ toolState: searchFlights,
94
+ requiredData: ["destination", "departureDate"],
95
+ });
96
+ ```
97
+
98
+ ### Direct State References
99
+
100
+ Jump to specific states or end the route:
101
+
102
+ ```typescript
103
+ import { END_STATE } from "@falai/agent";
104
+
105
+ // Jump to another state
106
+ const confirm = processPayment.transitionTo({
107
+ state: previousState.getRef(), // Jump back
108
+ });
109
+
110
+ // End the route
111
+ const complete = confirm.transitionTo({
112
+ state: END_STATE,
113
+ });
114
+ ```
115
+
116
+ ---
117
+
118
+ ## State Configuration
119
+
120
+ ### Configuring Initial State
121
+
122
+ Every route has an initial state that can be configured:
123
+
124
+ ```typescript
125
+ // Option 1: Configure at route creation
126
+ const route = agent.createRoute({
127
+ title: "Booking",
128
+ initialState: {
129
+ id: "welcome",
130
+ chatState: "Welcome to our booking system!",
131
+ gather: ["intention"],
132
+ },
133
+ });
134
+
135
+ // Option 2: Configure after creation
136
+ route.initialState.configure({
137
+ description: "Welcome! Let's start booking",
138
+ gatherFields: ["destination"],
139
+ skipIf: (extracted) => !!extracted.destination,
140
+ requiredData: [],
141
+ });
142
+ ```
143
+
144
+ ### Configuring Any State
145
+
146
+ You can configure any state after creation:
147
+
148
+ ```typescript
149
+ const askName = route.initialState.transitionTo({
150
+ chatState: "What's your name?",
151
+ });
152
+
153
+ // Later, reconfigure it
154
+ askName.configure({
155
+ description: "Ask for user's full name",
156
+ gatherFields: ["firstName", "lastName"],
157
+ skipIf: (extracted) => !!extracted.firstName && !!extracted.lastName,
158
+ });
159
+
160
+ // Chaining is supported
161
+ askName
162
+ .configure({ description: "Updated description" })
163
+ .configure({ gatherFields: ["fullName"] });
164
+ ```
165
+
166
+ ### Configuration Options
167
+
168
+ ```typescript
169
+ state.configure({
170
+ // State description
171
+ description?: string;
172
+
173
+ // Fields to gather from conversation
174
+ gatherFields?: string[];
175
+
176
+ // Skip this state if condition is met
177
+ skipIf?: (extracted: Partial<TExtracted>) => boolean;
178
+
179
+ // Prerequisites that must be met before entering
180
+ requiredData?: string[];
181
+ });
182
+ ```
183
+
184
+ ---
185
+
186
+ ## Transitions
187
+
188
+ ### Transition Specification
189
+
190
+ Every transition from one state to another uses a `TransitionSpec`:
191
+
192
+ ```typescript
193
+ interface TransitionSpec<TExtracted = unknown> {
194
+ // Custom state ID (optional)
195
+ id?: string;
196
+
197
+ // Chat state description
198
+ chatState?: string;
199
+
200
+ // Tool to execute
201
+ toolState?: ToolRef;
202
+
203
+ // Direct state reference or END_STATE
204
+ state?: StateRef | symbol;
205
+
206
+ // Fields to gather in this state
207
+ gather?: string[];
208
+
209
+ // Skip condition (code-based)
210
+ skipIf?: (extracted: Partial<TExtracted>) => boolean;
211
+
212
+ // Prerequisites
213
+ requiredData?: string[];
214
+
215
+ // AI-evaluated condition (for state selection)
216
+ condition?: string;
217
+ }
218
+ ```
219
+
220
+ ### Transition Chaining
221
+
222
+ ```typescript
223
+ // Linear flow
224
+ route.initialState
225
+ .transitionTo({
226
+ chatState: "Step 1",
227
+ gather: ["field1"],
228
+ })
229
+ .transitionTo({
230
+ chatState: "Step 2",
231
+ gather: ["field2"],
232
+ })
233
+ .transitionTo({
234
+ chatState: "Step 3",
235
+ gather: ["field3"],
236
+ })
237
+ .transitionTo({ state: END_STATE });
238
+ ```
239
+
240
+ ### Branching Transitions
241
+
242
+ ```typescript
243
+ const askType = route.initialState.transitionTo({
244
+ chatState: "Are you booking a flight or hotel?",
245
+ gather: ["bookingType"],
246
+ });
247
+
248
+ // Branch 1: Flight booking
249
+ const flightFlow = askType.transitionTo({
250
+ chatState: "Let's book your flight",
251
+ condition: "User selected flight",
252
+ });
253
+
254
+ // Branch 2: Hotel booking
255
+ const hotelFlow = askType.transitionTo({
256
+ chatState: "Let's book your hotel",
257
+ condition: "User selected hotel",
258
+ });
259
+
260
+ // Both branches can converge later
261
+ const payment = flightFlow.transitionTo({
262
+ chatState: "Let's process payment",
263
+ });
264
+
265
+ hotelFlow.transitionTo({ state: payment }); // Converge to payment
266
+ ```
267
+
268
+ ---
269
+
270
+ ## Data Gathering
271
+
272
+ ### Basic Gathering
273
+
274
+ Specify which fields to extract in each state:
275
+
276
+ ```typescript
277
+ interface UserData {
278
+ firstName: string;
279
+ lastName: string;
280
+ email: string;
281
+ phone: string;
282
+ }
283
+
284
+ const route = agent.createRoute<UserData>({ ... });
285
+
286
+ // Gather single field
287
+ const askName = route.initialState.transitionTo({
288
+ chatState: "What's your first name?",
289
+ gather: ["firstName"],
290
+ });
291
+
292
+ // Gather multiple fields at once
293
+ const askContact = askName.transitionTo({
294
+ chatState: "Please provide your email and phone number",
295
+ gather: ["email", "phone"],
296
+ });
297
+ ```
298
+
299
+ ### Gathering with Schema Validation
300
+
301
+ The extraction schema validates gathered data:
302
+
303
+ ```typescript
304
+ const route = agent.createRoute<UserData>({
305
+ title: "User Registration",
306
+
307
+ extractionSchema: {
308
+ type: "object",
309
+ properties: {
310
+ firstName: {
311
+ type: "string",
312
+ minLength: 1,
313
+ },
314
+ email: {
315
+ type: "string",
316
+ format: "email", // Validates email format
317
+ },
318
+ age: {
319
+ type: "number",
320
+ minimum: 18,
321
+ maximum: 120,
322
+ },
323
+ },
324
+ required: ["firstName", "email"],
325
+ },
326
+ });
327
+
328
+ // AI will extract and validate according to schema
329
+ const askInfo = route.initialState.transitionTo({
330
+ chatState: "Please provide your name, email, and age",
331
+ gather: ["firstName", "email", "age"],
332
+ });
333
+ ```
334
+
335
+ ### Conditional Gathering
336
+
337
+ Use `skipIf` to avoid re-asking for data:
338
+
339
+ ```typescript
340
+ const askDestination = route.initialState.transitionTo({
341
+ chatState: "Where would you like to go?",
342
+ gather: ["destination"],
343
+ // Skip if we already have the destination
344
+ skipIf: (extracted) => !!extracted.destination,
345
+ });
346
+
347
+ const askDates = askDestination.transitionTo({
348
+ chatState: "When would you like to travel?",
349
+ gather: ["departureDate", "returnDate"],
350
+ // Skip if we have both dates
351
+ skipIf: (extracted) => !!extracted.departureDate && !!extracted.returnDate,
352
+ });
353
+ ```
354
+
355
+ ---
356
+
357
+ ## State Logic
358
+
359
+ ### Skip Conditions
360
+
361
+ Control when states should be bypassed:
362
+
363
+ ```typescript
364
+ // Skip if data already exists
365
+ const askEmail = route.initialState.transitionTo({
366
+ chatState: "What's your email?",
367
+ gather: ["email"],
368
+ skipIf: (extracted) => !!extracted.email,
369
+ });
370
+
371
+ // Skip based on business logic
372
+ const askShipping = askEmail.transitionTo({
373
+ chatState: "What's your shipping address?",
374
+ gather: ["shippingAddress"],
375
+ // Skip if user selected digital product
376
+ skipIf: (extracted) => extracted.productType === "digital",
377
+ });
378
+
379
+ // Skip based on multiple conditions
380
+ const askBilling = askShipping.transitionTo({
381
+ chatState: "What's your billing address?",
382
+ gather: ["billingAddress"],
383
+ // Skip if billing same as shipping, or already provided
384
+ skipIf: (extracted) =>
385
+ extracted.billingSameAsShipping === true || !!extracted.billingAddress,
386
+ });
387
+ ```
388
+
389
+ ### Required Data
390
+
391
+ Ensure prerequisites are met before entering a state:
392
+
393
+ ```typescript
394
+ // Can't search without destination and dates
395
+ const searchFlights = askDates.transitionTo({
396
+ toolState: searchFlightsTool,
397
+ requiredData: ["destination", "departureDate"],
398
+ });
399
+
400
+ // Can't checkout without all required fields
401
+ const processPayment = selectFlight.transitionTo({
402
+ toolState: processPaymentTool,
403
+ requiredData: [
404
+ "destination",
405
+ "departureDate",
406
+ "selectedFlight",
407
+ "paymentMethod",
408
+ ],
409
+ });
410
+
411
+ // Multiple prerequisites
412
+ const generateInvoice = processPayment.transitionTo({
413
+ chatState: "Here's your invoice",
414
+ requiredData: ["paymentConfirmation", "customerEmail", "bookingReference"],
415
+ });
416
+ ```
417
+
418
+ ### State Conditions
419
+
420
+ AI-evaluated conditions for state selection:
421
+
422
+ ```typescript
423
+ const askIssue = route.initialState.transitionTo({
424
+ chatState: "What seems to be the problem?",
425
+ gather: ["issueDescription"],
426
+ });
427
+
428
+ // Technical support path
429
+ const technicalHelp = askIssue.transitionTo({
430
+ chatState: "Let me help with your technical issue",
431
+ condition: "Issue is technical in nature",
432
+ });
433
+
434
+ // Billing support path
435
+ const billingHelp = askIssue.transitionTo({
436
+ chatState: "Let me help with your billing issue",
437
+ condition: "Issue is related to billing or payments",
438
+ });
439
+
440
+ // General inquiry path
441
+ const generalHelp = askIssue.transitionTo({
442
+ chatState: "Let me help with your question",
443
+ condition: "Issue is a general inquiry",
444
+ });
445
+ ```
446
+
447
+ ---
448
+
449
+ ## State Types
450
+
451
+ ### 1. Chat States
452
+
453
+ Present information and gather data:
454
+
455
+ ```typescript
456
+ const chat = route.initialState.transitionTo({
457
+ chatState: "What would you like to know?",
458
+ gather: ["question"],
459
+ });
460
+ ```
461
+
462
+ **When to use:**
463
+
464
+ - Ask questions
465
+ - Present information
466
+ - Gather user input
467
+ - Confirm actions
468
+
469
+ ### 2. Tool States
470
+
471
+ Execute functions:
472
+
473
+ ```typescript
474
+ const tool = route.initialState.transitionTo({
475
+ toolState: myTool,
476
+ requiredData: ["param1", "param2"],
477
+ });
478
+ ```
479
+
480
+ **When to use:**
481
+
482
+ - Call APIs
483
+ - Database operations
484
+ - Complex computations
485
+ - External integrations
486
+ - Data validation/enrichment
487
+
488
+ ### 3. Initial State
489
+
490
+ Every route's starting point:
491
+
492
+ ```typescript
493
+ route.initialState.configure({
494
+ description: "Welcome message",
495
+ gatherFields: ["initialInput"],
496
+ });
497
+ ```
498
+
499
+ **When to configure:**
500
+
501
+ - Set up welcome messages
502
+ - Gather initial context
503
+ - Set expectations
504
+ - Pre-populate data
505
+
506
+ ### 4. Terminal State
507
+
508
+ End of a route:
509
+
510
+ ```typescript
511
+ const end = finalStep.transitionTo({
512
+ state: END_STATE,
513
+ });
514
+ ```
515
+
516
+ **When to use:**
517
+
518
+ - Route completion
519
+ - Success/failure outcomes
520
+ - Handoff to another route
521
+
522
+ ---
523
+
524
+ ## Advanced Patterns
525
+
526
+ ### Pattern 1: State Loops
527
+
528
+ ```typescript
529
+ const askItems = route.initialState.transitionTo({
530
+ chatState: "What items would you like to add to your cart?",
531
+ gather: ["newItem"],
532
+ });
533
+
534
+ const confirmMore = askItems.transitionTo({
535
+ chatState: "Would you like to add more items?",
536
+ gather: ["addMore"],
537
+ });
538
+
539
+ // Loop back to askItems if user wants more
540
+ confirmMore.transitionTo({
541
+ state: askItems,
542
+ condition: "User wants to add more items",
543
+ });
544
+
545
+ // Or continue to checkout
546
+ const checkout = confirmMore.transitionTo({
547
+ chatState: "Let's proceed to checkout",
548
+ condition: "User is done adding items",
549
+ });
550
+ ```
551
+
552
+ ### Pattern 2: Error Handling States
553
+
554
+ ```typescript
555
+ const processPayment = route.initialState.transitionTo({
556
+ toolState: paymentTool,
557
+ requiredData: ["amount", "paymentMethod"],
558
+ });
559
+
560
+ // Success path
561
+ const paymentSuccess = processPayment.transitionTo({
562
+ chatState: "Payment successful! Here's your receipt",
563
+ condition: "Payment was successful",
564
+ });
565
+
566
+ // Failure path
567
+ const paymentFailed = processPayment.transitionTo({
568
+ chatState:
569
+ "Payment failed. Would you like to try a different payment method?",
570
+ gather: ["retryPayment"],
571
+ condition: "Payment failed",
572
+ });
573
+
574
+ // Retry logic
575
+ paymentFailed.transitionTo({
576
+ state: processPayment,
577
+ condition: "User wants to retry",
578
+ });
579
+ ```
580
+
581
+ ### Pattern 3: Progressive Disclosure
582
+
583
+ ```typescript
584
+ // Start with basic info
585
+ const askBasic = route.initialState.transitionTo({
586
+ chatState: "Let's start with the basics. What's your name?",
587
+ gather: ["name"],
588
+ });
589
+
590
+ // Reveal more options
591
+ const askPreferences = askBasic.transitionTo({
592
+ chatState: "Great! Now, would you like to customize your experience?",
593
+ gather: ["wantsCustomization"],
594
+ });
595
+
596
+ // Only ask detailed questions if user wants customization
597
+ const askDetailed = askPreferences.transitionTo({
598
+ chatState: "Tell me about your preferences...",
599
+ gather: ["theme", "notifications", "language"],
600
+ skipIf: (extracted) => extracted.wantsCustomization === false,
601
+ });
602
+
603
+ const finish = askDetailed.transitionTo({
604
+ chatState: "All set! Your account is ready",
605
+ });
606
+
607
+ // Direct path if no customization
608
+ askPreferences.transitionTo({
609
+ state: finish,
610
+ condition: "User doesn't want customization",
611
+ });
612
+ ```
613
+
614
+ ### Pattern 4: State Validation
615
+
616
+ ```typescript
617
+ const askAge = route.initialState.transitionTo({
618
+ chatState: "How old are you?",
619
+ gather: ["age"],
620
+ });
621
+
622
+ const validateAge = askAge.transitionTo({
623
+ toolState: validateAgeTool,
624
+ requiredData: ["age"],
625
+ });
626
+
627
+ // Valid age
628
+ const proceed = validateAge.transitionTo({
629
+ chatState: "Great! Let's continue",
630
+ condition: "Age is valid",
631
+ });
632
+
633
+ // Invalid age - loop back
634
+ validateAge.transitionTo({
635
+ state: askAge,
636
+ condition: "Age is invalid",
637
+ });
638
+ ```
639
+
640
+ ### Pattern 5: Context-Aware States
641
+
642
+ ```typescript
643
+ const askQuestion = route.initialState.transitionTo({
644
+ chatState: "What would you like to know?",
645
+ gather: ["question"],
646
+ });
647
+
648
+ // Different responses based on user type
649
+ const premiumResponse = askQuestion.transitionTo({
650
+ chatState: "As a premium member, here's detailed information...",
651
+ condition: "User has premium account",
652
+ });
653
+
654
+ const basicResponse = askQuestion.transitionTo({
655
+ chatState: "Here's the basic information. Upgrade for more details!",
656
+ condition: "User has basic account",
657
+ });
658
+ ```
659
+
660
+ ---
661
+
662
+ ## State Properties
663
+
664
+ ### Accessing State Properties
665
+
666
+ ```typescript
667
+ const state = route.getState("ask_name");
668
+
669
+ // State identification
670
+ console.log(state.id); // "state_ask_name_abc123"
671
+ console.log(state.routeId); // "route_onboarding_xyz789"
672
+ console.log(state.description); // "Ask for user's name"
673
+
674
+ // State configuration
675
+ console.log(state.gatherFields); // ["firstName", "lastName"]
676
+ console.log(state.requiredData); // ["userId"]
677
+
678
+ // State logic
679
+ if (state.skipIf) {
680
+ const shouldSkip = state.skipIf(extractedData);
681
+ console.log("Should skip:", shouldSkip);
682
+ }
683
+
684
+ // State guidelines
685
+ state.addGuideline({
686
+ condition: "User provides invalid name",
687
+ action: "Ask for valid name format",
688
+ });
689
+ console.log(state.getGuidelines());
690
+
691
+ // Transitions
692
+ console.log(state.getTransitions()); // [Transition, Transition, ...]
693
+ ```
694
+
695
+ ### State Reference
696
+
697
+ ```typescript
698
+ const stateRef = state.getRef();
699
+ console.log(stateRef);
700
+ // { id: "state_ask_name_abc123", routeId: "route_onboarding_xyz789" }
701
+
702
+ // Use reference in transitions
703
+ anotherState.transitionTo({ state: stateRef });
704
+ ```
705
+
706
+ ---
707
+
708
+ ## Best Practices
709
+
710
+ ### ✅ Do's
711
+
712
+ - **Use descriptive chatState** - Clear prompts for better UX
713
+ - **Define gather fields** - Explicit data extraction
714
+ - **Use skipIf for efficiency** - Avoid redundant questions
715
+ - **Set requiredData** - Prevent premature execution
716
+ - **Configure initial state** - Proper route entry point
717
+ - **Use tool states for logic** - Separate concerns
718
+ - **Add state-specific guidelines** - Context-aware behavior
719
+
720
+ ### ❌ Don'ts
721
+
722
+ - **Don't hardcode state IDs** - Let framework generate them
723
+ - **Don't skip validation** - Always validate with requiredData
724
+ - **Don't create circular loops** - Without exit conditions
725
+ - **Don't gather unrelated data** - One concept per state
726
+ - **Don't use vague conditions** - Be specific
727
+ - **Don't forget skipIf** - For pre-populated data
728
+ - **Don't mix chat and tool** - One type per state
729
+
730
+ ---
731
+
732
+ ## Troubleshooting
733
+
734
+ ### State Not Being Entered
735
+
736
+ ```typescript
737
+ // ❌ Problem: Required data missing
738
+ const state = prev.transitionTo({
739
+ chatState: "Do something",
740
+ requiredData: ["field1", "field2"], // Missing field2
741
+ });
742
+
743
+ // ✅ Solution: Ensure required data is gathered first
744
+ const gatherData = prev.transitionTo({
745
+ chatState: "Gather data",
746
+ gather: ["field1", "field2"],
747
+ });
748
+
749
+ const state = gatherData.transitionTo({
750
+ chatState: "Do something",
751
+ requiredData: ["field1", "field2"], // Now available
752
+ });
753
+ ```
754
+
755
+ ### State Always Skipped
756
+
757
+ ```typescript
758
+ // ❌ Problem: skipIf always returns true
759
+ const state = prev.transitionTo({
760
+ chatState: "Ask something",
761
+ skipIf: (extracted) => true, // Always skips!
762
+ });
763
+
764
+ // ✅ Solution: Use proper condition
765
+ const state = prev.transitionTo({
766
+ chatState: "Ask something",
767
+ skipIf: (extracted) => !!extracted.field, // Only skip if field exists
768
+ });
769
+ ```
770
+
771
+ ### Data Not Being Gathered
772
+
773
+ ```typescript
774
+ // ❌ Problem: Forgot to specify gather
775
+ const state = prev.transitionTo({
776
+ chatState: "What's your name?",
777
+ // Missing gather!
778
+ });
779
+
780
+ // ✅ Solution: Add gather fields
781
+ const state = prev.transitionTo({
782
+ chatState: "What's your name?",
783
+ gather: ["firstName", "lastName"],
784
+ });
785
+ ```
786
+
787
+ ---
788
+
789
+ ## See Also
790
+
791
+ - [Routes Guide](./ROUTES.md) - Understanding routes
792
+ - [API Reference - State](./API_REFERENCE.md#state) - Complete API docs
793
+ - [Examples](../examples/) - Real-world implementations
794
+ - [Architecture Guide](./ARCHITECTURE.md) - System overview
795
+
796
+ ---
797
+
798
+ **Made with ❤️ for the community**