@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/ROUTES.md ADDED
@@ -0,0 +1,743 @@
1
+ # Routes Guide
2
+
3
+ A complete guide to creating and managing conversational routes in `@falai/agent`.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ - [What is a Route?](#what-is-a-route)
10
+ - [Creating Routes](#creating-routes)
11
+ - [Initial State Configuration](#initial-state-configuration)
12
+ - [Data Extraction](#data-extraction)
13
+ - [Sequential Steps](#sequential-steps)
14
+ - [Route Properties](#route-properties)
15
+ - [Route Security](#route-security)
16
+ - [Advanced Patterns](#advanced-patterns)
17
+
18
+ ---
19
+
20
+ ## What is a Route?
21
+
22
+ A **Route** (also called a "Journey") represents a specific conversational flow in your agent. Think of it as a state machine that guides the conversation through a series of steps to accomplish a specific goal.
23
+
24
+ ```typescript
25
+ // Example: A route for booking flights
26
+ const bookingRoute = agent.createRoute<FlightData>({
27
+ title: "Book Flight",
28
+ description: "Help user book a flight",
29
+ conditions: ["User wants to book a flight"],
30
+ });
31
+ ```
32
+
33
+ **Key Concepts:**
34
+
35
+ - Each route has a **goal** (e.g., "book a flight", "answer FAQ", "collect feedback")
36
+ - Routes contain **states** that represent conversation steps
37
+ - Routes can extract and track **typed data** throughout the conversation
38
+ - Routes have their own **rules**, **prohibitions**, and **guidelines**
39
+
40
+ ---
41
+
42
+ ## Creating Routes
43
+
44
+ ### Basic Route
45
+
46
+ ```typescript
47
+ interface FlightData {
48
+ destination: string;
49
+ departureDate: string;
50
+ passengers: number;
51
+ }
52
+
53
+ const bookingRoute = agent.createRoute<FlightData>({
54
+ title: "Book Flight",
55
+ description: "Help user book a flight",
56
+ conditions: [
57
+ "User wants to book a flight",
58
+ "User mentions flying or traveling",
59
+ ],
60
+ });
61
+ ```
62
+
63
+ ### Route with Data Extraction Schema
64
+
65
+ ```typescript
66
+ const bookingRoute = agent.createRoute<FlightData>({
67
+ title: "Book Flight",
68
+ description: "Help user book a flight",
69
+ conditions: ["User wants to book a flight"],
70
+
71
+ // Define what data to extract
72
+ extractionSchema: {
73
+ type: "object",
74
+ properties: {
75
+ destination: {
76
+ type: "string",
77
+ description: "Where the user wants to fly",
78
+ },
79
+ departureDate: {
80
+ type: "string",
81
+ description: "When they want to depart",
82
+ },
83
+ passengers: {
84
+ type: "number",
85
+ minimum: 1,
86
+ maximum: 9,
87
+ },
88
+ },
89
+ required: ["destination", "departureDate", "passengers"],
90
+ },
91
+ });
92
+ ```
93
+
94
+ ### Route with Rules and Prohibitions
95
+
96
+ ```typescript
97
+ const paymentRoute = agent.createRoute({
98
+ title: "Process Payment",
99
+ conditions: ["User wants to make a payment"],
100
+
101
+ // Absolute rules the agent MUST follow
102
+ rules: [
103
+ "Always confirm the payment amount before processing",
104
+ "Verify payment method is supported",
105
+ "Provide a receipt after successful payment",
106
+ ],
107
+
108
+ // Things the agent must NEVER do
109
+ prohibitions: [
110
+ "Never store credit card numbers",
111
+ "Never process payments without explicit confirmation",
112
+ "Never share payment details with third parties",
113
+ ],
114
+ });
115
+ ```
116
+
117
+ ---
118
+
119
+ ## Initial State Configuration
120
+
121
+ Every route starts with an initial state. You can now configure it in two ways:
122
+
123
+ ### Option 1: Configure at Route Creation
124
+
125
+ ```typescript
126
+ const bookingRoute = agent.createRoute<FlightData>({
127
+ title: "Book Flight",
128
+
129
+ // Configure the initial state
130
+ initialState: {
131
+ id: "welcome_state",
132
+ chatState:
133
+ "Welcome! I'll help you book a flight. Where would you like to go?",
134
+ gather: ["destination"],
135
+ skipIf: (extracted) => !!extracted.destination,
136
+ },
137
+
138
+ extractionSchema: {
139
+ // ... schema definition
140
+ },
141
+ });
142
+ ```
143
+
144
+ ### Option 2: Configure After Route Creation
145
+
146
+ ```typescript
147
+ const bookingRoute = agent.createRoute<FlightData>({
148
+ title: "Book Flight",
149
+ // ... other options
150
+ });
151
+
152
+ // Configure initial state later
153
+ bookingRoute.initialState.configure({
154
+ description: "Welcome! Let's book your flight",
155
+ gatherFields: ["destination"],
156
+ skipIf: (extracted) => !!extracted.destination,
157
+ });
158
+ ```
159
+
160
+ ### Initial State Options
161
+
162
+ ```typescript
163
+ initialState: {
164
+ // Custom ID for the initial state
165
+ id?: string;
166
+
167
+ // Description/prompt for the initial state
168
+ chatState?: string;
169
+
170
+ // Fields to extract in this state
171
+ gather?: string[];
172
+
173
+ // Skip this state if condition is met
174
+ skipIf?: (extracted: Partial<TExtracted>) => boolean;
175
+
176
+ // Prerequisites that must be met
177
+ requiredData?: string[];
178
+ }
179
+ ```
180
+
181
+ ---
182
+
183
+ ## Data Extraction
184
+
185
+ ### Defining Extraction Schema
186
+
187
+ The extraction schema defines what data your route will collect:
188
+
189
+ ```typescript
190
+ const onboardingRoute = agent.createRoute<OnboardingData>({
191
+ title: "User Onboarding",
192
+
193
+ extractionSchema: {
194
+ type: "object",
195
+ properties: {
196
+ firstName: { type: "string" },
197
+ lastName: { type: "string" },
198
+ email: { type: "string", format: "email" },
199
+ company: { type: "string" },
200
+ role: {
201
+ type: "string",
202
+ enum: ["developer", "designer", "manager", "other"],
203
+ },
204
+ },
205
+ required: ["firstName", "lastName", "email"],
206
+ },
207
+ });
208
+ ```
209
+
210
+ ### Getting Extracted Data
211
+
212
+ ```typescript
213
+ // Get extracted data from the route
214
+ const extracted = bookingRoute.getExtractedData(session);
215
+
216
+ console.log(extracted);
217
+ // { destination: "Paris", departureDate: "2025-06-15", passengers: 2 }
218
+
219
+ // Only returns data if session is in this route
220
+ const otherRouteData = otherRoute.getExtractedData(session);
221
+ // {} - empty if session is in a different route
222
+ ```
223
+
224
+ ### Pre-populating Data
225
+
226
+ You can pre-populate data when entering a route:
227
+
228
+ ```typescript
229
+ const bookingRoute = agent.createRoute<FlightData>({
230
+ title: "Book Flight",
231
+
232
+ // Pre-fill known information
233
+ initialData: {
234
+ destination: "Paris", // User mentioned this earlier
235
+ passengers: 1, // Default value
236
+ },
237
+
238
+ extractionSchema: {
239
+ // ... schema
240
+ },
241
+ });
242
+
243
+ // States with skipIf will automatically bypass if data exists
244
+ ```
245
+
246
+ ---
247
+
248
+ ## Sequential Steps
249
+
250
+ For simple linear flows, use the `steps` option:
251
+
252
+ ```typescript
253
+ const feedbackRoute = agent.createRoute({
254
+ title: "Collect Feedback",
255
+
256
+ steps: [
257
+ {
258
+ id: "ask_rating",
259
+ chatState: "How would you rate your experience? (1-5 stars)",
260
+ gather: ["rating"],
261
+ },
262
+ {
263
+ id: "ask_liked",
264
+ chatState: "What did you like most?",
265
+ gather: ["likedMost"],
266
+ },
267
+ {
268
+ id: "ask_improve",
269
+ chatState: "What could we improve?",
270
+ gather: ["improvements"],
271
+ },
272
+ {
273
+ id: "thank_you",
274
+ chatState: "Thank you for your feedback! 🙏",
275
+ },
276
+ ],
277
+ });
278
+
279
+ // Automatically chains: initialState → ask_rating → ask_liked → ask_improve → thank_you → END_STATE
280
+ ```
281
+
282
+ **When to use steps vs manual chaining:**
283
+
284
+ - ✅ Use `steps` for: Linear flows, simple wizards, sequential data collection
285
+ - ✅ Use manual chaining for: Branching logic, conditional flows, complex state machines
286
+
287
+ ---
288
+
289
+ ## Route Properties
290
+
291
+ ### Accessing Route Properties
292
+
293
+ ```typescript
294
+ const route = agent.createRoute({ ... });
295
+
296
+ // Route identification
297
+ console.log(route.id); // "route_book_flight_abc123"
298
+ console.log(route.title); // "Book Flight"
299
+ console.log(route.description); // "Help user book a flight"
300
+
301
+ // Activation conditions
302
+ console.log(route.conditions); // ["User wants to book a flight"]
303
+
304
+ // Rules and guidelines
305
+ console.log(route.getRules()); // ["Always confirm...", ...]
306
+ console.log(route.getProhibitions()); // ["Never store...", ...]
307
+ console.log(route.getGuidelines()); // [{ condition: "...", action: "..." }]
308
+
309
+ // Domain restrictions
310
+ console.log(route.getDomains()); // ["payment", "booking"] or undefined
311
+ ```
312
+
313
+ ### Route Reference
314
+
315
+ ```typescript
316
+ const routeRef = route.getRef();
317
+ console.log(routeRef); // { id: "route_book_flight_abc123" }
318
+
319
+ // Use reference to jump to specific routes
320
+ state.transitionTo({ state: routeRef });
321
+ ```
322
+
323
+ ### Route Structure
324
+
325
+ ```typescript
326
+ // Get all states in the route
327
+ const states = route.getAllStates();
328
+ console.log(states); // [State, State, State, ...]
329
+
330
+ // Get specific state by ID
331
+ const state = route.getState("ask_destination");
332
+
333
+ // Describe route structure
334
+ console.log(route.describe());
335
+ // Output:
336
+ // Route: Book Flight
337
+ // ID: route_book_flight_abc123
338
+ // Description: Help user book a flight
339
+ // Conditions: User wants to book a flight
340
+ //
341
+ // States:
342
+ // - initial_state: Initial state
343
+ // -> ask_destination
344
+ // - ask_destination: Ask where they want to fly
345
+ // -> ask_dates
346
+ // ...
347
+ ```
348
+
349
+ ---
350
+
351
+ ## Route Security
352
+
353
+ ### Domain Scoping
354
+
355
+ Restrict which tools/domains a route can access:
356
+
357
+ ```typescript
358
+ // Define domains
359
+ agent.addDomain("payment", {
360
+ processPayment: async (amount: number) => {
361
+ /* ... */
362
+ },
363
+ refund: async (transactionId: string) => {
364
+ /* ... */
365
+ },
366
+ });
367
+
368
+ agent.addDomain("booking", {
369
+ searchFlights: async (dest: string) => {
370
+ /* ... */
371
+ },
372
+ createReservation: async (data: any) => {
373
+ /* ... */
374
+ },
375
+ });
376
+
377
+ agent.addDomain("database", {
378
+ saveUser: async (user: any) => {
379
+ /* ... */
380
+ },
381
+ deleteUser: async (userId: string) => {
382
+ /* ... */
383
+ },
384
+ });
385
+
386
+ // Payment route: only access payment tools
387
+ const paymentRoute = agent.createRoute({
388
+ title: "Process Payment",
389
+ domains: ["payment"], // ONLY payment domain
390
+ });
391
+
392
+ // Booking route: access booking AND payment
393
+ const bookingRoute = agent.createRoute({
394
+ title: "Book Flight",
395
+ domains: ["booking", "payment"], // Both domains
396
+ });
397
+
398
+ // Admin route: access everything
399
+ const adminRoute = agent.createRoute({
400
+ title: "Admin Actions",
401
+ // domains: undefined = all domains allowed
402
+ });
403
+ ```
404
+
405
+ **Security Benefits:**
406
+
407
+ - ✅ Prevents accidental tool execution in wrong context
408
+ - ✅ Reduces attack surface for each route
409
+ - ✅ Makes permissions explicit and auditable
410
+
411
+ ---
412
+
413
+ ## Advanced Patterns
414
+
415
+ ### Pattern 1: Branching Logic
416
+
417
+ ```typescript
418
+ const supportRoute = agent.createRoute<SupportData>({
419
+ title: "Customer Support",
420
+ });
421
+
422
+ // Ask issue type
423
+ const askIssueType = supportRoute.initialState.transitionTo({
424
+ chatState: "What type of issue are you experiencing?",
425
+ gather: ["issueType"],
426
+ });
427
+
428
+ // Branch 1: Technical issues
429
+ const technicalFlow = askIssueType.transitionTo({
430
+ chatState: "Let me help with your technical issue...",
431
+ condition: "Issue type is technical",
432
+ });
433
+
434
+ // Branch 2: Billing issues
435
+ const billingFlow = askIssueType.transitionTo({
436
+ chatState: "Let me help with your billing issue...",
437
+ condition: "Issue type is billing",
438
+ });
439
+
440
+ // Branch 3: General inquiries
441
+ const generalFlow = askIssueType.transitionTo({
442
+ chatState: "Let me help with your inquiry...",
443
+ condition: "Issue type is general",
444
+ });
445
+ ```
446
+
447
+ ### Pattern 2: Conditional Skip States
448
+
449
+ ```typescript
450
+ const checkoutRoute = agent.createRoute<CheckoutData>({
451
+ title: "Checkout",
452
+ extractionSchema: {
453
+ properties: {
454
+ hasAccount: { type: "boolean" },
455
+ email: { type: "string" },
456
+ shippingAddress: { type: "object" },
457
+ billingAddress: { type: "object" },
458
+ },
459
+ },
460
+ });
461
+
462
+ // Skip login if user already has account
463
+ const login = checkoutRoute.initialState.transitionTo({
464
+ chatState: "Please log in or continue as guest",
465
+ gather: ["hasAccount", "email"],
466
+ skipIf: (extracted) => extracted.hasAccount === true,
467
+ });
468
+
469
+ // Skip billing address if same as shipping
470
+ const shippingAddress = login.transitionTo({
471
+ chatState: "What's your shipping address?",
472
+ gather: ["shippingAddress"],
473
+ });
474
+
475
+ const billingAddress = shippingAddress.transitionTo({
476
+ chatState: "Is your billing address the same as shipping?",
477
+ gather: ["billingAddress"],
478
+ skipIf: (extracted) => extracted.billingAddress !== undefined,
479
+ });
480
+ ```
481
+
482
+ ### Pattern 3: Route-Specific Guidelines
483
+
484
+ ```typescript
485
+ const bookingRoute = agent.createRoute({
486
+ title: "Book Flight",
487
+ });
488
+
489
+ // Add guidelines specific to this route
490
+ bookingRoute.createGuideline({
491
+ condition: "User asks about cancellation policy",
492
+ action: "Explain that cancellations must be made 24 hours in advance",
493
+ tags: ["policy"],
494
+ });
495
+
496
+ bookingRoute.createGuideline({
497
+ condition: "User provides invalid date",
498
+ action: "Politely ask for a valid future date in YYYY-MM-DD format",
499
+ tags: ["validation"],
500
+ });
501
+ ```
502
+
503
+ ### Pattern 4: Tool Integration in Routes
504
+
505
+ ```typescript
506
+ import { defineTool } from "@falai/agent";
507
+
508
+ const searchFlights = defineTool<MyContext, [], FlightResults>(
509
+ "search_flights",
510
+ async ({ context, extracted }) => {
511
+ const flights = await api.searchFlights({
512
+ destination: extracted.destination,
513
+ date: extracted.departureDate,
514
+ });
515
+
516
+ return {
517
+ data: flights,
518
+ contextUpdate: { availableFlights: flights },
519
+ };
520
+ }
521
+ );
522
+
523
+ const bookingRoute = agent.createRoute<FlightData>({
524
+ title: "Book Flight",
525
+ domains: ["booking"], // Ensure tool is in this domain
526
+ });
527
+
528
+ // Gather data
529
+ const gatherDetails = bookingRoute.initialState.transitionTo({
530
+ chatState: "Where and when would you like to fly?",
531
+ gather: ["destination", "departureDate", "passengers"],
532
+ });
533
+
534
+ // Execute tool
535
+ const searchState = gatherDetails.transitionTo({
536
+ toolState: searchFlights,
537
+ requiredData: ["destination", "departureDate", "passengers"],
538
+ });
539
+
540
+ // Present results
541
+ const presentFlights = searchState.transitionTo({
542
+ chatState: "Here are available flights based on your search",
543
+ });
544
+ ```
545
+
546
+ ---
547
+
548
+ ## Route Completion
549
+
550
+ When a route reaches its final state and transitions to `END_STATE`, the agent returns `isRouteComplete: true` to signal that all required data has been collected.
551
+
552
+ ### Ending a Route
553
+
554
+ Use the `END_STATE` symbol to mark the end of a route:
555
+
556
+ ```typescript
557
+ import { END_STATE } from "@falai/agent";
558
+
559
+ const onboardingRoute = agent.createRoute<OnboardingData>({
560
+ title: "User Onboarding",
561
+ extractionSchema: ONBOARDING_SCHEMA,
562
+ });
563
+
564
+ const askName = onboardingRoute.initialState.transitionTo({
565
+ chatState: "What's your name?",
566
+ gather: ["name"],
567
+ });
568
+
569
+ const askEmail = askName.transitionTo({
570
+ chatState: "What's your email?",
571
+ gather: ["email"],
572
+ });
573
+
574
+ const thankYou = askEmail.transitionTo({
575
+ chatState: "Thank you! Your profile is complete.",
576
+ });
577
+
578
+ // End the route
579
+ thankYou.transitionTo({ state: END_STATE });
580
+ ```
581
+
582
+ ### Handling Completion
583
+
584
+ There are **two ways** to check if a route has completed:
585
+
586
+ #### Method 1: Using `isRouteComplete` (Recommended)
587
+
588
+ ```typescript
589
+ const response = await agent.respond({
590
+ history,
591
+ session,
592
+ });
593
+
594
+ if (response.isRouteComplete) {
595
+ // ✅ Route is complete! All data has been collected
596
+
597
+ // Get the collected data
598
+ const data = agent.getExtractedData(response.session!);
599
+ console.log("Collected data:", data);
600
+
601
+ // Process the data
602
+ await saveToDatabase(data);
603
+ await sendConfirmationEmail(data.email);
604
+
605
+ // Show custom completion message
606
+ return "Thank you! Your information has been saved.";
607
+ } else {
608
+ // ⏳ Route still in progress
609
+ return response.message;
610
+ }
611
+ ```
612
+
613
+ #### Method 2: Using `END_STATE_ID` Constant
614
+
615
+ For users who prefer a symbol-based pattern consistent with building routes:
616
+
617
+ ```typescript
618
+ import { END_STATE_ID } from "@falai/agent";
619
+
620
+ const response = await agent.respond({
621
+ history,
622
+ session,
623
+ });
624
+
625
+ if (response.session?.currentState?.id === END_STATE_ID) {
626
+ // ✅ Route completed - currentState is now END_STATE
627
+ const data = agent.getExtractedData(response.session!);
628
+ await handleCompletion(data);
629
+ return "Complete!";
630
+ }
631
+
632
+ return response.message;
633
+ ```
634
+
635
+ **Which method should you use?**
636
+
637
+ - ✅ **Use `isRouteComplete`** for simplicity and clarity
638
+ - ✅ **Use `END_STATE_ID`** if you want consistency with how you build routes (`END_STATE` symbol)
639
+
640
+ ### Immediate Completion
641
+
642
+ Routes can complete **immediately** if all states are skipped due to `skipIf` conditions. This is useful when:
643
+
644
+ - Resuming a partially completed route
645
+ - Pre-filling data from an existing session
646
+ - User provides all information upfront
647
+
648
+ ```typescript
649
+ const onboardingRoute = agent.createRoute<OnboardingData>({
650
+ title: "User Onboarding",
651
+ extractionSchema: ONBOARDING_SCHEMA,
652
+ initialData: existingUserData, // Pre-fill with existing data
653
+ });
654
+
655
+ const askName = onboardingRoute.initialState.transitionTo({
656
+ chatState: "What's your name?",
657
+ gather: ["name"],
658
+ skipIf: (data) => !!data.name, // Skip if name exists
659
+ });
660
+
661
+ const askEmail = askName.transitionTo({
662
+ chatState: "What's your email?",
663
+ gather: ["email"],
664
+ skipIf: (data) => !!data.email, // Skip if email exists
665
+ });
666
+
667
+ const complete = askEmail.transitionTo({
668
+ chatState: "All done!",
669
+ });
670
+
671
+ complete.transitionTo({ state: END_STATE });
672
+
673
+ // In your handler:
674
+ const response = await agent.respond({ history, session });
675
+
676
+ if (response.isRouteComplete) {
677
+ // If existingUserData had all fields, route completes immediately!
678
+ // The routing engine recursively skips all states and reaches END_STATE
679
+ console.log("Profile already complete!");
680
+ }
681
+ ```
682
+
683
+ ### Important Notes
684
+
685
+ - **`isRouteComplete: true`** indicates the route has reached `END_STATE`
686
+ - **`currentState.id`** is set to `END_STATE_ID` when the route completes
687
+ - **`response.message`** will be empty (`""`) when route is complete
688
+ - **The routing engine** recursively traverses skipped states to detect completion
689
+ - **You can check either** `isRouteComplete` or `currentState.id === END_STATE_ID`
690
+ - **Session state** still contains all the collected data via `agent.getExtractedData()`
691
+ - **The route** (`currentRoute.id`) remains the completed route (e.g., "onboarding"), not END_STATE
692
+
693
+ ### With Streaming
694
+
695
+ ```typescript
696
+ for await (const chunk of agent.respondStream({ history, session })) {
697
+ if (chunk.delta) {
698
+ process.stdout.write(chunk.delta);
699
+ }
700
+
701
+ if (chunk.done && chunk.isRouteComplete) {
702
+ console.log("\n🎉 Route completed!");
703
+ const data = agent.getExtractedData(chunk.session!);
704
+ await processCompletedData(data);
705
+ }
706
+ }
707
+ ```
708
+
709
+ ---
710
+
711
+ ## Best Practices
712
+
713
+ ### ✅ Do's
714
+
715
+ - **Use descriptive titles and descriptions** - Makes routing more accurate
716
+ - **Define extraction schemas** - Type-safe data collection
717
+ - **Configure initial state** - Set up proper entry point
718
+ - **Use skipIf for known data** - Avoid redundant questions
719
+ - **Scope domains** - Limit tool access per route
720
+ - **Add route-specific guidelines** - Context-aware behavior
721
+ - **Use steps for linear flows** - Cleaner code for simple paths
722
+
723
+ ### ❌ Don'ts
724
+
725
+ - **Don't use vague conditions** - Be specific about when to activate
726
+ - **Don't skip extraction schemas** - Loses type safety
727
+ - **Don't create overly complex routes** - Split into multiple routes
728
+ - **Don't forget requiredData** - Prevent states from executing too early
729
+ - **Don't mix concerns** - One route = one goal
730
+ - **Don't hardcode state IDs** - Let framework generate deterministic IDs
731
+
732
+ ---
733
+
734
+ ## See Also
735
+
736
+ - [States Guide](./STATES.md) - Deep dive into state management
737
+ - [API Reference - Route](./API_REFERENCE.md#route) - Complete API docs
738
+ - [Examples](../examples/) - Real-world route implementations
739
+ - [Architecture Guide](./ARCHITECTURE.md) - How routes fit in the system
740
+
741
+ ---
742
+
743
+ **Made with ❤️ for the community**