@falai/agent 0.6.7 → 0.6.8

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 (60) hide show
  1. package/README.md +162 -30
  2. package/dist/cjs/core/Agent.d.ts +18 -0
  3. package/dist/cjs/core/Agent.d.ts.map +1 -1
  4. package/dist/cjs/core/Agent.js +218 -15
  5. package/dist/cjs/core/Agent.js.map +1 -1
  6. package/dist/cjs/core/Route.d.ts +12 -1
  7. package/dist/cjs/core/Route.d.ts.map +1 -1
  8. package/dist/cjs/core/Route.js +38 -1
  9. package/dist/cjs/core/Route.js.map +1 -1
  10. package/dist/cjs/core/RoutingEngine.d.ts.map +1 -1
  11. package/dist/cjs/core/RoutingEngine.js +3 -1
  12. package/dist/cjs/core/RoutingEngine.js.map +1 -1
  13. package/dist/cjs/core/State.js +1 -1
  14. package/dist/cjs/core/State.js.map +1 -1
  15. package/dist/cjs/index.d.ts +2 -2
  16. package/dist/cjs/index.d.ts.map +1 -1
  17. package/dist/cjs/index.js.map +1 -1
  18. package/dist/cjs/types/route.d.ts +51 -0
  19. package/dist/cjs/types/route.d.ts.map +1 -1
  20. package/dist/cjs/types/session.d.ts +17 -1
  21. package/dist/cjs/types/session.d.ts.map +1 -1
  22. package/dist/cjs/types/session.js.map +1 -1
  23. package/dist/core/Agent.d.ts +18 -0
  24. package/dist/core/Agent.d.ts.map +1 -1
  25. package/dist/core/Agent.js +219 -16
  26. package/dist/core/Agent.js.map +1 -1
  27. package/dist/core/Route.d.ts +12 -1
  28. package/dist/core/Route.d.ts.map +1 -1
  29. package/dist/core/Route.js +38 -1
  30. package/dist/core/Route.js.map +1 -1
  31. package/dist/core/RoutingEngine.d.ts.map +1 -1
  32. package/dist/core/RoutingEngine.js +3 -1
  33. package/dist/core/RoutingEngine.js.map +1 -1
  34. package/dist/core/State.js +1 -1
  35. package/dist/core/State.js.map +1 -1
  36. package/dist/index.d.ts +2 -2
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js.map +1 -1
  39. package/dist/types/route.d.ts +51 -0
  40. package/dist/types/route.d.ts.map +1 -1
  41. package/dist/types/session.d.ts +17 -1
  42. package/dist/types/session.d.ts.map +1 -1
  43. package/dist/types/session.js.map +1 -1
  44. package/docs/EXAMPLES.md +51 -2
  45. package/docs/ROUTES.md +345 -1
  46. package/docs/STATES.md +97 -7
  47. package/examples/business-onboarding.ts +10 -12
  48. package/examples/company-qna-agent.ts +4 -5
  49. package/examples/healthcare-agent.ts +63 -0
  50. package/examples/persistent-onboarding.ts +6 -8
  51. package/examples/route-transitions.ts +242 -0
  52. package/examples/travel-agent.ts +60 -0
  53. package/package.json +1 -1
  54. package/src/core/Agent.ts +338 -16
  55. package/src/core/Route.ts +53 -1
  56. package/src/core/RoutingEngine.ts +3 -1
  57. package/src/core/State.ts +1 -1
  58. package/src/index.ts +3 -1
  59. package/src/types/route.ts +57 -0
  60. package/src/types/session.ts +20 -2
package/docs/ROUTES.md CHANGED
@@ -9,6 +9,7 @@ A complete guide to creating and managing conversational routes in `@falai/agent
9
9
  - [What is a Route?](#what-is-a-route)
10
10
  - [Creating Routes](#creating-routes)
11
11
  - [Initial State Configuration](#initial-state-configuration)
12
+ - [End State Configuration](#end-state-configuration)
12
13
  - [Data Extraction](#data-extraction)
13
14
  - [Sequential Steps](#sequential-steps)
14
15
  - [Route Properties](#route-properties)
@@ -173,13 +174,140 @@ initialState: {
173
174
  // Skip this state if condition is met
174
175
  skipIf?: (extracted: Partial<TExtracted>) => boolean;
175
176
 
176
- // Prerequisites that must be met
177
+ // Prerequisites that must be metw
177
178
  requiredData?: string[];
178
179
  }
179
180
  ```
180
181
 
181
182
  ---
182
183
 
184
+ ## End State Configuration
185
+
186
+ Every route ends when it reaches `END_STATE`. You can configure what happens at route completion:
187
+
188
+ ### Configure End State at Route Creation
189
+
190
+ ```typescript
191
+ import { END_STATE } from "@falai/agent";
192
+
193
+ const bookingRoute = agent.createRoute<FlightData>({
194
+ title: "Book Flight",
195
+
196
+ // Configure what happens when route completes
197
+ endState: {
198
+ // Custom completion message
199
+ chatState: "Confirm the booking details and thank the user warmly!",
200
+
201
+ // Optional: Execute final actions (like sending confirmation emails)
202
+ toolState: sendConfirmationEmail,
203
+
204
+ // Optional: Gather final data before completing
205
+ gather: ["finalConfirmation"],
206
+
207
+ // Optional: Require certain data to be present
208
+ requiredData: ["destination", "departureDate"],
209
+
210
+ // Optional: Custom state ID
211
+ id: "booking_complete",
212
+ },
213
+
214
+ extractionSchema: {
215
+ // ... schema definition
216
+ },
217
+ });
218
+
219
+ // Then just transition to END_STATE
220
+ lastState.transitionTo({
221
+ state: END_STATE,
222
+ });
223
+ ```
224
+
225
+ ### Per-Transition Override
226
+
227
+ You can also override the endState configuration for specific transitions:
228
+
229
+ ```typescript
230
+ // Use route-level endState
231
+ lastState.transitionTo({
232
+ state: END_STATE,
233
+ });
234
+
235
+ // OR override with custom chatState for this specific transition
236
+ lastState.transitionTo({
237
+ chatState: "Special completion message for this path!",
238
+ state: END_STATE,
239
+ });
240
+ ```
241
+
242
+ ### End State Options
243
+
244
+ ```typescript
245
+ endState: {
246
+ // Completion message instruction (RECOMMENDED)
247
+ chatState?: string;
248
+
249
+ // Execute final tools/actions before completion
250
+ toolState?: ToolRef;
251
+
252
+ // Gather final data at completion
253
+ gather?: string[];
254
+
255
+ // Require specific data to be present
256
+ requiredData?: string[];
257
+
258
+ // Custom state ID for debugging
259
+ id?: string;
260
+ }
261
+ ```
262
+
263
+ ### Default Behavior
264
+
265
+ If you don't configure `endState`, a smart default is used:
266
+
267
+ ```typescript
268
+ // Default endState behavior:
269
+ {
270
+ chatState: "Summarize what was accomplished and confirm completion based on the conversation history and collected data"
271
+ }
272
+ ```
273
+
274
+ ### End State with Tools
275
+
276
+ Execute final actions when route completes:
277
+
278
+ ```typescript
279
+ import { defineTool, END_STATE } from "@falai/agent";
280
+
281
+ const sendConfirmation = defineTool(
282
+ "send_confirmation",
283
+ async ({ extracted }) => {
284
+ await emailService.send({
285
+ to: extracted.email,
286
+ subject: "Booking Confirmed",
287
+ body: `Your flight to ${extracted.destination} is confirmed!`,
288
+ });
289
+ return { data: "Confirmation sent" };
290
+ }
291
+ );
292
+
293
+ const bookingRoute = agent.createRoute({
294
+ title: "Book Flight",
295
+
296
+ endState: {
297
+ toolState: sendConfirmation, // Executes when route completes
298
+ chatState: "Your booking is complete! Confirmation email sent.",
299
+ },
300
+ });
301
+ ```
302
+
303
+ **Key Points:**
304
+ - āœ… `endState` is configured once at the route level (DRY principle)
305
+ - āœ… Can be overridden per-transition if needed
306
+ - āœ… Supports full state capabilities: `chatState`, `toolState`, `gather`, `requiredData`
307
+ - āœ… Falls back to smart default if not configured
308
+
309
+ ---
310
+
183
311
  ## Data Extraction
184
312
 
185
313
  ### Defining Extraction Schema
@@ -637,6 +765,222 @@ return response.message;
637
765
  - āœ… **Use `isRouteComplete`** for simplicity and clarity
638
766
  - āœ… **Use `END_STATE_ID`** if you want consistency with how you build routes (`END_STATE` symbol)
639
767
 
768
+ ---
769
+
770
+ ## Route Transitions with `onComplete`
771
+
772
+ **NEW:** You can now automatically transition to another route when a route completes using the `onComplete` option. This is perfect for chaining workflows like:
773
+
774
+ - šŸ“‹ Post-booking feedback collection
775
+ - šŸŽ Upsell offers after purchase
776
+ - šŸ“Š Satisfaction surveys after support
777
+ - ā†©ļø Error recovery flows
778
+
779
+ ### Simple String Transition
780
+
781
+ The simplest form - just specify the target route ID or title:
782
+
783
+ ```typescript
784
+ const bookingRoute = agent.createRoute<BookingData>({
785
+ title: "Book Hotel",
786
+ conditions: ["User wants to book a hotel"],
787
+ extractionSchema: BOOKING_SCHEMA,
788
+ // Automatically transition to feedback when booking completes
789
+ onComplete: "Collect Feedback",
790
+ });
791
+
792
+ const feedbackRoute = agent.createRoute<FeedbackData>({
793
+ title: "Collect Feedback",
794
+ conditions: ["Collect user feedback"],
795
+ extractionSchema: FEEDBACK_SCHEMA,
796
+ });
797
+ ```
798
+
799
+ ### With AI-Evaluated Condition
800
+
801
+ Add an optional condition that the AI evaluates to determine if transition should happen:
802
+
803
+ ```typescript
804
+ const bookingRoute = agent.createRoute<BookingData>({
805
+ title: "Book Hotel",
806
+ extractionSchema: BOOKING_SCHEMA,
807
+ onComplete: {
808
+ transitionTo: "Collect Feedback",
809
+ condition: "if booking was successful", // AI evaluates this
810
+ },
811
+ });
812
+ ```
813
+
814
+ ### Dynamic Function-Based Transition
815
+
816
+ Use a function for complex logic based on extracted data or context:
817
+
818
+ ```typescript
819
+ const bookingRoute = agent.createRoute<BookingData>({
820
+ title: "Book Hotel",
821
+ extractionSchema: BOOKING_SCHEMA,
822
+ // Function receives session and context
823
+ onComplete: (session, context) => {
824
+ // Conditional logic based on extracted data
825
+ if (session.extracted?.guests && session.extracted.guests > 5) {
826
+ return "VIP Feedback"; // Large groups get VIP treatment
827
+ }
828
+ if (session.extracted?.bookingFailed) {
829
+ return "Error Recovery"; // Handle failures gracefully
830
+ }
831
+ return "Collect Feedback"; // Standard feedback flow
832
+ },
833
+ });
834
+ ```
835
+
836
+ Function can also return a config object:
837
+
838
+ ```typescript
839
+ onComplete: (session) => ({
840
+ transitionTo: "Collect Feedback",
841
+ condition: session.extracted?.vip ? "if user is satisfied" : undefined,
842
+ })
843
+ ```
844
+
845
+ ### How It Works
846
+
847
+ 1. **Route completes** → reaches `END_STATE`
848
+ 2. **Agent evaluates** `onComplete` handler
849
+ 3. **Sets pending transition** in session state
850
+ 4. **Next `respond()` call** → automatically transitions to target route
851
+ 5. **User sees seamless flow** → no interruption in conversation
852
+
853
+ ```typescript
854
+ // User books hotel
855
+ const response1 = await agent.respond({ history, session });
856
+ console.log(response1.isRouteComplete); // true
857
+ console.log(response1.session?.pendingTransition); // { targetRouteId: "...", reason: "route_complete" }
858
+
859
+ // Next message automatically transitions to feedback route
860
+ history.push(createMessageEvent(EventSource.CUSTOMER, "User", "Yes!"));
861
+ const response2 = await agent.respond({ history, session: response1.session });
862
+ console.log(response2.session?.currentRoute?.title); // "Collect Feedback"
863
+ ```
864
+
865
+ ### Manual Transition Control
866
+
867
+ For more control, use `agent.transitionToRoute()` to manually set the transition:
868
+
869
+ ```typescript
870
+ const response = await agent.respond({ history, session });
871
+
872
+ if (response.isRouteComplete && shouldCollectFeedback) {
873
+ // Manually trigger transition instead of onComplete
874
+ const updatedSession = agent.transitionToRoute(
875
+ "Collect Feedback",
876
+ response.session
877
+ );
878
+
879
+ // Next respond() will transition automatically
880
+ const nextResponse = await agent.respond({
881
+ history,
882
+ session: updatedSession,
883
+ });
884
+ }
885
+ ```
886
+
887
+ ### Complete Example
888
+
889
+ ```typescript
890
+ interface BookingData {
891
+ hotelName: string;
892
+ date: string;
893
+ guests: number;
894
+ }
895
+
896
+ interface FeedbackData {
897
+ rating: number;
898
+ comments?: string;
899
+ }
900
+
901
+ // Booking route with automatic transition to feedback
902
+ const bookingRoute = agent.createRoute<BookingData>({
903
+ title: "Book Hotel",
904
+ conditions: ["User wants to book a hotel"],
905
+ extractionSchema: {
906
+ type: "object",
907
+ properties: {
908
+ hotelName: { type: "string" },
909
+ date: { type: "string" },
910
+ guests: { type: "number" },
911
+ },
912
+ required: ["hotelName", "date", "guests"],
913
+ },
914
+ onComplete: "Collect Feedback",
915
+ });
916
+
917
+ const askHotel = bookingRoute.initialState.transitionTo({
918
+ chatState: "Ask which hotel",
919
+ gather: ["hotelName"],
920
+ skipIf: (e) => !!e.hotelName,
921
+ });
922
+
923
+ const askDate = askHotel.transitionTo({
924
+ chatState: "Ask for date",
925
+ gather: ["date"],
926
+ skipIf: (e) => !!e.date,
927
+ });
928
+
929
+ const askGuests = askDate.transitionTo({
930
+ chatState: "Ask for guests",
931
+ gather: ["guests"],
932
+ skipIf: (e) => !!e.guests,
933
+ });
934
+
935
+ askGuests.transitionTo({
936
+ chatState: "Confirm booking",
937
+ state: END_STATE,
938
+ });
939
+
940
+ // Feedback route
941
+ const feedbackRoute = agent.createRoute<FeedbackData>({
942
+ title: "Collect Feedback",
943
+ conditions: ["Collect user feedback"],
944
+ extractionSchema: {
945
+ type: "object",
946
+ properties: {
947
+ rating: { type: "number" },
948
+ comments: { type: "string" },
949
+ },
950
+ required: ["rating"],
951
+ },
952
+ });
953
+
954
+ const askRating = feedbackRoute.initialState.transitionTo({
955
+ chatState: "Ask for rating 1-5",
956
+ gather: ["rating"],
957
+ });
958
+
959
+ askRating.transitionTo({
960
+ chatState: "Thank user",
961
+ state: END_STATE,
962
+ });
963
+
964
+ // Usage - seamless transition from booking to feedback
965
+ let session;
966
+ const response1 = await agent.respond({ history, session });
967
+ // Booking complete, pending transition set
968
+
969
+ history.push(createMessageEvent(EventSource.CUSTOMER, "User", "Yes!"));
970
+ const response2 = await agent.respond({ history, session: response1.session });
971
+ // Now in feedback route automatically
972
+ ```
973
+
974
+ ### Benefits
975
+
976
+ āœ… **Seamless user experience** - No awkward pauses between flows
977
+ āœ… **Predictable behavior** - Transitions defined at design time
978
+ āœ… **Flexible** - Simple strings, conditions, or complex functions
979
+ āœ… **Type-safe** - Full TypeScript inference for extracted data
980
+ āœ… **Non-breaking** - Existing routes without `onComplete` work as before
981
+
982
+ See [examples/route-transitions.ts](../examples/route-transitions.ts) for a complete working example.
983
+
640
984
  ### Immediate Completion
641
985
 
642
986
  Routes can complete **immediately** if all states are skipped due to `skipIf` conditions. This is useful when:
package/docs/STATES.md CHANGED
@@ -503,21 +503,111 @@ route.initialState.configure({
503
503
  - Set expectations
504
504
  - Pre-populate data
505
505
 
506
- ### 4. Terminal State
506
+ ### 4. End State (Terminal State)
507
507
 
508
- End of a route:
508
+ Every route ends when it reaches `END_STATE`. You can configure what happens at completion:
509
+
510
+ #### Option A: Route-Level Configuration (Recommended)
509
511
 
510
512
  ```typescript
511
- const end = finalStep.transitionTo({
513
+ import { END_STATE } from "@falai/agent";
514
+
515
+ const bookingRoute = agent.createRoute({
516
+ title: "Book Flight",
517
+
518
+ // Configure end state behavior
519
+ endState: {
520
+ chatState: "Confirm booking and thank the user!",
521
+ toolState: sendConfirmationEmail, // Execute final actions
522
+ gather: ["finalConfirmation"], // Gather last data
523
+ },
524
+ });
525
+
526
+ // Later, just transition to END_STATE
527
+ finalStep.transitionTo({
512
528
  state: END_STATE,
513
529
  });
514
530
  ```
515
531
 
516
- **When to use:**
532
+ #### Option B: Per-Transition Override
533
+
534
+ ```typescript
535
+ // Override endState for this specific path
536
+ finalStep.transitionTo({
537
+ chatState: "Special completion message for VIP users!",
538
+ state: END_STATE,
539
+ });
540
+ ```
541
+
542
+ #### Option C: Default Behavior
543
+
544
+ If you don't configure `endState`, a smart default completion message is generated:
545
+
546
+ ```typescript
547
+ finalStep.transitionTo({
548
+ state: END_STATE,
549
+ });
550
+ // Uses default: "Summarize what was accomplished and confirm completion..."
551
+ ```
552
+
553
+ **End State Capabilities:**
554
+
555
+ ```typescript
556
+ endState: {
557
+ // Completion message instruction
558
+ chatState?: string;
559
+
560
+ // Execute final actions (emails, database updates, etc.)
561
+ toolState?: ToolRef;
562
+
563
+ // Gather final data before completion
564
+ gather?: string[];
565
+
566
+ // Require certain data to be present
567
+ requiredData?: string[];
568
+
569
+ // Custom state ID for debugging
570
+ id?: string;
571
+ }
572
+ ```
573
+
574
+ **When to use END_STATE:**
575
+
576
+ - āœ… Route completion - all required data collected
577
+ - āœ… Success outcomes - action completed successfully
578
+ - āœ… Failure outcomes - error handling completed
579
+ - āœ… Before transition - handoff to another route via `onComplete`
580
+
581
+ **End State with Tools:**
582
+
583
+ Execute final actions when route completes:
584
+
585
+ ```typescript
586
+ import { defineTool, END_STATE } from "@falai/agent";
587
+
588
+ const notifyTeam = defineTool("notify_team", async ({ extracted }) => {
589
+ await slack.send({
590
+ channel: "#bookings",
591
+ message: `New booking: ${extracted.hotelName} for ${extracted.guests} guests`,
592
+ });
593
+ return { data: "Team notified" };
594
+ });
595
+
596
+ const bookingRoute = agent.createRoute({
597
+ endState: {
598
+ toolState: notifyTeam, // Runs when route completes
599
+ chatState: "Booking complete! Our team has been notified.",
600
+ },
601
+ });
602
+ ```
603
+
604
+ **Key Points:**
517
605
 
518
- - Route completion
519
- - Success/failure outcomes
520
- - Handoff to another route
606
+ - āœ… Configure once at route level (DRY principle)
607
+ - āœ… Can be overridden per-transition if needed
608
+ - āœ… Supports full state capabilities: `chatState`, `toolState`, `gather`, `requiredData`
609
+ - āœ… Automatically generates message if not configured
610
+ - āœ… Executes before `onComplete` route transitions
521
611
 
522
612
  ---
523
613
 
@@ -417,6 +417,9 @@ async function createBusinessOnboardingAgent(
417
417
  title: "Business Onboarding",
418
418
  description: "Complete onboarding process to configure personalized routes",
419
419
  conditions: ["User is starting the onboarding process"],
420
+ endState: {
421
+ chatState: "šŸŽ‰ Perfect! Setup complete! Your WhatsApp assistant is ready and will use all this information to automatically serve your customers. If you have any questions or need adjustments, just let me know!",
422
+ },
420
423
  });
421
424
 
422
425
  // ==================== Build the Flow ====================
@@ -556,15 +559,12 @@ async function createBusinessOnboardingAgent(
556
559
  // Loop back to summary after adding custom route
557
560
  saveCustomRoute.transitionTo({ state: summary });
558
561
 
559
- // Step 9b: Final confirmation
560
- const completion = summary.transitionTo({
561
- chatState:
562
- "šŸŽ‰ Perfect! Setup complete! Your WhatsApp assistant is ready and will use all this information to automatically serve your customers. If you have any questions or need adjustments, just let me know!",
562
+ // Step 9b: Final confirmation - transition to END_STATE (uses route-level endState)
563
+ summary.transitionTo({
564
+ state: END_STATE,
563
565
  condition: "User confirmed everything is okay",
564
566
  });
565
567
 
566
- completion.transitionTo({ state: END_STATE });
567
-
568
568
  // ==================== Alternative: Sequential Steps ====================
569
569
  // For simpler linear flows, you can use the new sequential steps approach:
570
570
 
@@ -598,6 +598,9 @@ async function createBusinessOnboardingAgent(
598
598
  title: "Manual Feedback Route",
599
599
  description: "Same flow using traditional chaining",
600
600
  conditions: ["User wants manual feedback flow"],
601
+ endState: {
602
+ chatState: "Thank you for your feedback! It helps us improve. šŸ™",
603
+ },
601
604
  });
602
605
 
603
606
  manualFeedbackRoute.initialState
@@ -614,12 +617,7 @@ async function createBusinessOnboardingAgent(
614
617
  chatState: "Is there anything we could improve?",
615
618
  condition: "User wants to provide feedback",
616
619
  })
617
- .transitionTo({
618
- id: "thank_you",
619
- chatState: "Thank you for your feedback! It helps us improve. šŸ™",
620
- condition: "User provided feedback",
621
- })
622
- .transitionTo({ state: END_STATE });
620
+ .transitionTo({ state: END_STATE }); // Uses route-level endState
623
621
 
624
622
  // ==================== Global Guidelines ====================
625
623
 
@@ -358,6 +358,9 @@ const feedbackRoute = agent.createRoute<FeedbackData>({
358
358
  },
359
359
  required: ["rating", "comments"],
360
360
  },
361
+ endState: {
362
+ chatState: "Thank the user warmly for their valuable feedback and let them know we appreciate their time",
363
+ },
361
364
  });
362
365
 
363
366
  feedbackRoute.initialState
@@ -381,11 +384,7 @@ feedbackRoute.initialState
381
384
  gather: ["contactPermission"],
382
385
  requiredData: ["comments"],
383
386
  })
384
- .transitionTo({
385
- id: "thank_you",
386
- chatState: "Thank you for your valuable feedback!",
387
- })
388
- .transitionTo({ state: END_STATE });
387
+ .transitionTo({ state: END_STATE }); // Uses route-level endState configuration
389
388
 
390
389
  // ==============================================================================
391
390
  // USAGE EXAMPLES: Three-Phase Pipeline Demonstration
@@ -34,6 +34,12 @@ interface LabResultsData {
34
34
  resultsNeeded?: boolean;
35
35
  }
36
36
 
37
+ interface SatisfactionData {
38
+ rating?: number;
39
+ easeOfScheduling?: number;
40
+ comments?: string;
41
+ }
42
+
37
43
  // Tools
38
44
  const getInsuranceProviders = defineTool<HealthcareContext, [], string[]>(
39
45
  "get_insurance_providers",
@@ -126,6 +132,7 @@ async function createHealthcareAgent() {
126
132
  });
127
133
 
128
134
  // Create scheduling route with data extraction schema
135
+ // NEW: Added onComplete to automatically transition to satisfaction survey after booking
129
136
  const schedulingRoute = agent.createRoute<AppointmentData>({
130
137
  title: "Schedule an Appointment",
131
138
  description: "Helps the patient find a time for their appointment.",
@@ -158,6 +165,8 @@ async function createHealthcareAgent() {
158
165
  },
159
166
  required: ["appointmentReason"],
160
167
  },
168
+ // NEW: Automatically collect feedback after successful scheduling
169
+ onComplete: "Satisfaction Survey",
161
170
  });
162
171
 
163
172
  // State 1: Gather appointment reason
@@ -300,6 +309,50 @@ async function createHealthcareAgent() {
300
309
  "Assertively tell them that you cannot help and they should call the office",
301
310
  });
302
311
 
312
+ // NEW: Satisfaction Survey route - collects feedback after appointment scheduling
313
+ const satisfactionRoute = agent.createRoute<SatisfactionData>({
314
+ title: "Satisfaction Survey",
315
+ description: "Quick satisfaction survey after scheduling",
316
+ conditions: ["Collect patient satisfaction feedback"],
317
+ extractionSchema: {
318
+ type: "object",
319
+ properties: {
320
+ rating: {
321
+ type: "number",
322
+ description: "Overall satisfaction rating 1-5",
323
+ },
324
+ easeOfScheduling: {
325
+ type: "number",
326
+ description: "Ease of scheduling process 1-5",
327
+ },
328
+ comments: {
329
+ type: "string",
330
+ description: "Optional feedback comments",
331
+ },
332
+ },
333
+ required: ["rating"],
334
+ },
335
+ });
336
+
337
+ const askRating = satisfactionRoute.initialState.transitionTo({
338
+ chatState:
339
+ "Ask for overall satisfaction rating from 1 to 5 with the scheduling experience",
340
+ gather: ["rating"],
341
+ skipIf: (extracted) => !!extracted.rating,
342
+ });
343
+
344
+ const askComments = askRating.transitionTo({
345
+ chatState: "Ask if they have any additional comments or feedback (optional)",
346
+ gather: ["comments"],
347
+ });
348
+
349
+ const thankYou = askComments.transitionTo({
350
+ chatState:
351
+ "Thank them for their feedback and confirm their appointment details one more time",
352
+ });
353
+
354
+ thankYou.transitionTo({ state: END_STATE });
355
+
303
356
  // Global guidelines
304
357
  agent.createGuideline({
305
358
  condition: "The patient asks about insurance",
@@ -384,6 +437,16 @@ async function main() {
384
437
  // Update session again
385
438
  session = response2.session!;
386
439
 
440
+ // NEW: Check if route is complete - will auto-transition to satisfaction survey
441
+ if (response2.isRouteComplete) {
442
+ console.log("\nāœ“ Appointment scheduling complete!");
443
+ console.log("Pending transition:", response2.session?.pendingTransition);
444
+ console.log(
445
+ "Next respond() will auto-transition to:",
446
+ response2.session?.pendingTransition?.targetRouteId
447
+ );
448
+ }
449
+
387
450
  // Turn 3: Patient provides final details
388
451
  const history3 = [
389
452
  ...history2,
@@ -8,7 +8,6 @@ import {
8
8
  defineTool,
9
9
  GeminiProvider,
10
10
  END_STATE,
11
- END_STATE_ID,
12
11
  EventSource,
13
12
  createMessageEvent,
14
13
  createSession,
@@ -102,7 +101,7 @@ async function createPersistentOnboardingAgent(sessionId: string) {
102
101
  // Define lifecycle hooks for automatic persistence
103
102
  const hooks = {
104
103
  // Called after data extraction - validate and enrich extracted data
105
- onExtractedUpdate: async (extracted, previousExtracted) => {
104
+ onExtractedUpdate: async (extracted: Partial<OnboardingData>) => {
106
105
  console.log("šŸ”„ Processing extracted data...");
107
106
 
108
107
  // Update completed steps based on what's been extracted
@@ -267,6 +266,9 @@ async function createPersistentOnboardingAgent(sessionId: string) {
267
266
  },
268
267
  required: ["businessName", "businessDescription"],
269
268
  },
269
+ endState: {
270
+ chatState: "Summarize all collected information warmly and confirm onboarding is complete",
271
+ },
270
272
  });
271
273
 
272
274
  // State 1: Gather business name and description
@@ -311,12 +313,8 @@ async function createPersistentOnboardingAgent(sessionId: string) {
311
313
  requiredData: ["contactEmail"],
312
314
  });
313
315
 
314
- // State 7: Confirmation
315
- const confirm = saveContact.transitionTo({
316
- chatState: "Summarize all collected information and ask for confirmation",
317
- });
318
-
319
- confirm.transitionTo({ state: END_STATE });
316
+ // State 7: Confirmation - uses route-level endState
317
+ saveContact.transitionTo({ state: END_STATE });
320
318
 
321
319
  // Guidelines
322
320
  onboardingRoute.createGuideline({