@falai/agent 0.6.2 → 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 -56
  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
@@ -13,7 +13,7 @@
13
13
  import {
14
14
  Agent,
15
15
  defineTool,
16
- END_ROUTE,
16
+ END_STATE,
17
17
  EventSource,
18
18
  createMessageEvent,
19
19
  OpenAIProvider,
@@ -563,7 +563,7 @@ async function createBusinessOnboardingAgent(
563
563
  condition: "User confirmed everything is okay",
564
564
  });
565
565
 
566
- completion.transitionTo({ state: END_ROUTE });
566
+ completion.transitionTo({ state: END_STATE });
567
567
 
568
568
  // ==================== Alternative: Sequential Steps ====================
569
569
  // For simpler linear flows, you can use the new sequential steps approach:
@@ -619,7 +619,7 @@ async function createBusinessOnboardingAgent(
619
619
  chatState: "Thank you for your feedback! It helps us improve. 🙏",
620
620
  condition: "User provided feedback",
621
621
  })
622
- .transitionTo({ state: END_ROUTE });
622
+ .transitionTo({ state: END_STATE });
623
623
 
624
624
  // ==================== Global Guidelines ====================
625
625
 
@@ -722,15 +722,31 @@ async function main() {
722
722
  try {
723
723
  // Initialize session state for multi-turn conversation
724
724
  let session = createSession<OnboardingData>();
725
+ agent.setCurrentSession(session);
725
726
 
726
- const response = await agent.respond({ history, session });
727
+ const response = await agent.respond({ history });
727
728
  console.log("Agent:", response.message);
728
729
  console.log("\nRoute:", response.session?.currentRoute?.title);
729
- console.log("Extracted:", response.session?.extracted);
730
+
731
+ // After the conversation, you can get the extracted data
732
+ const extractedData = agent.getExtractedData<OnboardingData>();
733
+ console.log("Extracted:", extractedData);
730
734
 
731
735
  // Update session with progress
732
736
  session = response.session!;
733
737
 
738
+ // You can also pass the session to the agent constructor
739
+ // const agentWithSession = await createBusinessOnboardingAgent("user_123", "Alice", "session_456", {}, session);
740
+ // const response2 = await agentWithSession.respond({ history });
741
+
742
+ if (response.isRouteComplete) {
743
+ console.log("\n✅ Onboarding route complete!");
744
+ // Here you would typically save the complete data to your database
745
+ await processOnboardingData(agent.getExtractedData());
746
+ } else {
747
+ console.log("\n⏳ Onboarding route in progress...");
748
+ }
749
+
734
750
  console.log("\n✅ Session state benefits:");
735
751
  console.log(" - Data extraction tracked across turns");
736
752
  console.log(" - State progression managed automatically");
@@ -741,6 +757,31 @@ async function main() {
741
757
  }
742
758
  }
743
759
 
760
+ /**
761
+ * Mock function to process completed onboarding data
762
+ * @param data - The complete onboarding data
763
+ */
764
+ async function processOnboardingData(data: Partial<OnboardingData>) {
765
+ console.log("\n" + "=".repeat(60));
766
+ console.log("🚀 Processing Completed Onboarding Data...");
767
+ console.log("=".repeat(60));
768
+ console.log("Received data:", JSON.stringify(data, null, 2));
769
+
770
+ // Example: Save to a database
771
+ console.log("\n💾 Saving to database...");
772
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulate async operation
773
+ console.log(" - Business Info:", data.business?.businessName);
774
+ console.log(" - Contact Info:", data.contact?.website);
775
+ console.log(" - Location Info:", data.location?.address);
776
+
777
+ // Example: Triggering a welcome email
778
+ console.log("\n📧 Sending welcome email...");
779
+ await new Promise((resolve) => setTimeout(resolve, 1000));
780
+ console.log(` - Email sent to user associated with the account.`);
781
+
782
+ console.log("\n✨ Onboarding processing complete!");
783
+ }
784
+
744
785
  // Run if executed directly
745
786
  if (import.meta.url === `file://${process.argv[1]}`) {
746
787
  main().catch(console.error);
@@ -15,6 +15,8 @@ import {
15
15
  EventSource,
16
16
  createMessageEvent,
17
17
  EventKind,
18
+ END_STATE,
19
+ OpenAIProvider,
18
20
  } from "../src";
19
21
  import type { Event } from "../src/types";
20
22
  import type { ToolRef } from "../src/types/tool";
@@ -54,6 +56,12 @@ interface CompanyContext {
54
56
  }>;
55
57
  }
56
58
 
59
+ interface FeedbackData {
60
+ rating?: number;
61
+ comments?: string;
62
+ contactPermission?: boolean;
63
+ }
64
+
57
65
  // ==============================================================================
58
66
  // TOOLS: Context Enrichment (PREPARATION Phase)
59
67
  // ==============================================================================
@@ -138,7 +146,10 @@ const agent = new Agent<CompanyContext>({
138
146
  "I'm here to help you learn about Acme Corp, our products, and policies",
139
147
  personality:
140
148
  "Friendly, helpful, and knowledgeable. Always professional but approachable.",
141
- ai: null as any, // Replace with actual AI provider
149
+ ai: new OpenAIProvider({
150
+ apiKey: process.env.OPENAI_API_KEY || "test-key",
151
+ model: "gpt-5o-mini",
152
+ }),
142
153
 
143
154
  // Initialize with company knowledge
144
155
  context: {
@@ -325,6 +336,57 @@ const fallbackRoute = agent.createRoute({
325
336
 
326
337
  // Initial state is enough for fallback conversations
327
338
 
339
+ // Route 7: Collect Feedback (Stateful Example)
340
+ const feedbackRoute = agent.createRoute<FeedbackData>({
341
+ title: "Collect Feedback",
342
+ description: "Collect user feedback about their experience",
343
+ conditions: ["User wants to leave feedback", "User seems satisfied or upset"],
344
+ extractionSchema: {
345
+ type: "object",
346
+ properties: {
347
+ rating: {
348
+ type: "number",
349
+ description: "A rating from 1 to 5",
350
+ minimum: 1,
351
+ maximum: 5,
352
+ },
353
+ comments: { type: "string", description: "Open-ended feedback" },
354
+ contactPermission: {
355
+ type: "boolean",
356
+ description: "Permission to contact the user for more details",
357
+ },
358
+ },
359
+ required: ["rating", "comments"],
360
+ },
361
+ });
362
+
363
+ feedbackRoute.initialState
364
+ .transitionTo({
365
+ id: "ask_rating",
366
+ chatState:
367
+ "I'd love to hear your feedback. On a scale of 1 to 5, how would you rate your experience with me today?",
368
+ gather: ["rating"],
369
+ })
370
+ .transitionTo({
371
+ id: "ask_comments",
372
+ chatState:
373
+ "Thanks for the rating! Do you have any specific comments or suggestions?",
374
+ gather: ["comments"],
375
+ requiredData: ["rating"],
376
+ })
377
+ .transitionTo({
378
+ id: "ask_permission",
379
+ chatState:
380
+ "Thank you for the detailed feedback. Would it be okay if our team contacted you for more details?",
381
+ gather: ["contactPermission"],
382
+ requiredData: ["comments"],
383
+ })
384
+ .transitionTo({
385
+ id: "thank_you",
386
+ chatState: "Thank you for your valuable feedback!",
387
+ })
388
+ .transitionTo({ state: END_STATE });
389
+
328
390
  // ==============================================================================
329
391
  // USAGE EXAMPLES: Three-Phase Pipeline Demonstration
330
392
  // ==============================================================================
@@ -439,6 +501,50 @@ async function exampleConversations() {
439
501
  console.log("User: How much does it cost?");
440
502
  console.log("AI:", resp2.message);
441
503
  // AI understands "it" refers to Acme Widget from context
504
+
505
+ // =========================================================================
506
+ // Example 6: Stateful feedback collection
507
+ // =========================================================================
508
+ console.log("\n=== EXAMPLE 6: Stateful Feedback Collection ===");
509
+ const feedbackHistory: Event[] = [
510
+ createMessageEvent(
511
+ EventSource.CUSTOMER,
512
+ "User",
513
+ "This was very helpful, I want to leave some feedback."
514
+ ),
515
+ ];
516
+
517
+ const feedbackResponse = await agent.respond({
518
+ history: feedbackHistory,
519
+ session,
520
+ });
521
+ console.log("AI:", feedbackResponse.message);
522
+ console.log("Route:", feedbackResponse.session?.currentRoute?.title);
523
+
524
+ if (feedbackResponse.isRouteComplete) {
525
+ console.log("\n✅ Feedback collection complete!");
526
+ await processFeedback(agent.getExtractedData(feedbackResponse.session?.id));
527
+ } else {
528
+ console.log("\n⏳ Feedback collection in progress...");
529
+ }
530
+ }
531
+
532
+ /**
533
+ * Mock function to process collected feedback.
534
+ * @param data The feedback data collected from the user.
535
+ */
536
+ async function processFeedback(data: Partial<FeedbackData>) {
537
+ console.log("\n" + "=".repeat(60));
538
+ console.log("Processing user feedback...");
539
+ console.log("=".repeat(60));
540
+ console.log("Rating:", data.rating);
541
+ console.log("Comments:", data.comments);
542
+ console.log("Permission to contact:", data.contactPermission);
543
+
544
+ // Here you would typically save this to a database or send it to a support system.
545
+ await new Promise((resolve) => setTimeout(resolve, 500)); // Simulate async operation
546
+ console.log("Feedback logged successfully!");
547
+ console.log("=".repeat(60));
442
548
  }
443
549
 
444
550
  // ==============================================================================
@@ -19,6 +19,7 @@ import {
19
19
  SessionState,
20
20
  MessageEventData,
21
21
  Event,
22
+ END_STATE,
22
23
  } from "../src/index";
23
24
 
24
25
  /**
@@ -32,6 +33,7 @@ interface CustomDatabaseSession {
32
33
  currentState?: string;
33
34
  collectedData?: {
34
35
  extracted?: Record<string, unknown>;
36
+ extractedByRoute?: Record<string, Partial<unknown>>;
35
37
  routeHistory?: unknown[];
36
38
  currentRouteTitle?: string;
37
39
  currentStateDescription?: string;
@@ -193,7 +195,13 @@ async function example() {
193
195
  id: "confirm_details",
194
196
  chatState: "Confirm all details",
195
197
  requiredData: ["fullName", "email", "companyName"],
196
- });
198
+ })
199
+ .transitionTo({
200
+ id: "complete_onboarding",
201
+ chatState:
202
+ "Thank you! Your account is set up. You will receive a confirmation email shortly.",
203
+ })
204
+ .transitionTo({ state: END_STATE });
197
205
 
198
206
  /**
199
207
  * Create or load session from your custom database
@@ -234,6 +242,11 @@ async function example() {
234
242
  : undefined,
235
243
  extracted:
236
244
  (dbSession.collectedData?.extracted as Partial<OnboardingData>) || {},
245
+ extractedByRoute:
246
+ (dbSession.collectedData?.extractedByRoute as Record<
247
+ string,
248
+ Partial<OnboardingData>
249
+ >) || {},
237
250
  routeHistory:
238
251
  (dbSession.collectedData
239
252
  ?.routeHistory as SessionState<OnboardingData>["routeHistory"]) || [],
@@ -373,6 +386,14 @@ async function example() {
373
386
 
374
387
  console.log("💾 Session saved to database");
375
388
 
389
+ // Check for route completion
390
+ if (response2.isRouteComplete) {
391
+ console.log("\n✅ Onboarding Complete!");
392
+ // In a real app, you would now trigger the next steps,
393
+ // like sending a welcome email, creating an account, etc.
394
+ await processOnboarding(response2.session?.extracted);
395
+ }
396
+
376
397
  /**
377
398
  * Demonstrate session recovery
378
399
  */
@@ -404,6 +425,11 @@ async function example() {
404
425
  extracted:
405
426
  (reloadedDbSession.collectedData?.extracted as Partial<OnboardingData>) ||
406
427
  {},
428
+ extractedByRoute:
429
+ (reloadedDbSession.collectedData?.extractedByRoute as Record<
430
+ string,
431
+ Partial<OnboardingData>
432
+ >) || {},
407
433
  routeHistory:
408
434
  (reloadedDbSession.collectedData
409
435
  ?.routeHistory as SessionState<OnboardingData>["routeHistory"]) || [],
@@ -429,6 +455,23 @@ async function example() {
429
455
  console.log("\n✅ Example complete!");
430
456
  }
431
457
 
458
+ /**
459
+ * Mock function to simulate processing the completed onboarding data.
460
+ * @param data - The collected onboarding data.
461
+ */
462
+ async function processOnboarding(data: Partial<OnboardingData> | undefined) {
463
+ console.log("\n🚀 Processing onboarding data...");
464
+ // Simulate creating a user account
465
+ console.log(
466
+ ` - Creating account for ${data?.fullName} at ${data?.companyName}`
467
+ );
468
+ await new Promise((resolve) => setTimeout(resolve, 1000));
469
+ // Simulate sending a welcome email
470
+ console.log(` - Sending welcome email to ${data?.email}`);
471
+ await new Promise((resolve) => setTimeout(resolve, 1000));
472
+ console.log("✨ Onboarding processed successfully!");
473
+ }
474
+
432
475
  /**
433
476
  * Advanced Example: With validation hooks
434
477
  */
@@ -21,6 +21,7 @@ import {
21
21
  type Guideline,
22
22
  type Capability,
23
23
  type RouteOptions,
24
+ END_STATE,
24
25
  } from "../src/index";
25
26
 
26
27
  // Context type
@@ -45,24 +46,22 @@ interface LabData {
45
46
  }
46
47
 
47
48
  // Define tools with custom IDs (optional - IDs are deterministic by default)
48
- const getInsuranceProviders = defineTool<HealthcareContext, [], string[]>(
49
- "get_insurance_providers",
50
- async () => {
49
+ const getInsuranceProviders = defineTool<HealthcareContext, [], string[]>({
50
+ name: "get_insurance_providers",
51
+ handler: async () => {
51
52
  return { data: ["MegaCare Insurance", "HealthFirst", "WellnessPlus"] };
52
53
  },
53
- {
54
- id: "healthcare_insurance_providers", // Custom ID for persistence
55
- description: "Retrieves list of accepted insurance providers",
56
- }
57
- );
54
+ id: "healthcare_insurance_providers", // Custom ID for persistence
55
+ description: "Retrieves list of accepted insurance providers",
56
+ });
58
57
 
59
58
  const getAvailableSlots = defineTool<
60
59
  HealthcareContext,
61
60
  [],
62
61
  { date: string; time: string }[]
63
- >(
64
- "get_available_slots",
65
- async () => {
62
+ >({
63
+ name: "get_available_slots",
64
+ handler: async () => {
66
65
  return {
67
66
  data: [
68
67
  { date: "2025-10-20", time: "10:00 AM" },
@@ -71,19 +70,17 @@ const getAvailableSlots = defineTool<
71
70
  ],
72
71
  };
73
72
  },
74
- {
75
- id: "healthcare_available_slots", // Custom ID
76
- description: "Gets available appointment slots",
77
- }
78
- );
73
+ id: "healthcare_available_slots", // Custom ID
74
+ description: "Gets available appointment slots",
75
+ });
79
76
 
80
77
  const getLabResults = defineTool<
81
78
  HealthcareContext,
82
79
  [],
83
80
  { report: string; status: string }
84
- >(
85
- "get_lab_results",
86
- async ({ context, extracted }) => {
81
+ >({
82
+ name: "get_lab_results",
83
+ handler: async ({ context, extracted }) => {
87
84
  // Tools can now access extracted data
88
85
  const labData = extracted as Partial<LabData>;
89
86
  if (labData?.testType) {
@@ -102,19 +99,13 @@ const getLabResults = defineTool<
102
99
  },
103
100
  };
104
101
  },
105
- {
106
- id: "healthcare_lab_results", // Custom ID
107
- description: "Retrieves patient lab results",
108
- }
109
- );
102
+ id: "healthcare_lab_results", // Custom ID
103
+ description: "Retrieves patient lab results",
104
+ });
110
105
 
111
- const scheduleAppointment = defineTool<
112
- HealthcareContext,
113
- [],
114
- { confirmation: string }
115
- >(
116
- "schedule_appointment",
117
- async ({ context, extracted }) => {
106
+ const scheduleAppointment = defineTool<HealthcareContext>({
107
+ name: "schedule_appointment",
108
+ handler: async ({ context, extracted }) => {
118
109
  // Tools can access extracted appointment data
119
110
  const appointment = extracted as Partial<AppointmentData>;
120
111
  if (!appointment?.preferredDate || !appointment?.preferredTime) {
@@ -127,11 +118,9 @@ const scheduleAppointment = defineTool<
127
118
  },
128
119
  };
129
120
  },
130
- {
131
- id: "healthcare_schedule_appointment",
132
- description: "Schedules patient appointments",
133
- }
134
- );
121
+ id: "healthcare_schedule_appointment",
122
+ description: "Schedules patient appointments",
123
+ });
135
124
 
136
125
  // Declarative configuration
137
126
  const terms: Term[] = [
@@ -226,6 +215,36 @@ const routes: RouteOptions[] = [
226
215
  enabled: true,
227
216
  },
228
217
  ],
218
+ // NEW: Use sequential steps for a linear booking flow
219
+ steps: [
220
+ {
221
+ id: "ask_appointment_type",
222
+ chatState:
223
+ "What type of appointment do you need? (checkup, consultation, or followup)",
224
+ gather: ["appointmentType"],
225
+ },
226
+ {
227
+ id: "ask_date_time",
228
+ chatState: "When would you like to come in?",
229
+ gather: ["preferredDate", "preferredTime"],
230
+ requiredData: ["appointmentType"],
231
+ },
232
+ {
233
+ id: "ask_symptoms",
234
+ chatState: "Are you experiencing any symptoms?",
235
+ gather: ["symptoms"],
236
+ },
237
+ {
238
+ id: "confirm_appointment",
239
+ toolState: scheduleAppointment,
240
+ requiredData: ["preferredDate", "preferredTime"],
241
+ },
242
+ {
243
+ id: "final_confirmation",
244
+ chatState:
245
+ "Your appointment is confirmed. You will receive a notification shortly.",
246
+ },
247
+ ],
229
248
  },
230
249
  {
231
250
  id: "route_check_lab_results", // Custom ID
@@ -266,7 +285,7 @@ const routes: RouteOptions[] = [
266
285
  conditions: ["Patient asks general healthcare questions"],
267
286
  // No extractionSchema - stateless Q&A
268
287
  },
269
- ];
288
+ ] as RouteOptions[];
270
289
 
271
290
  // Create the fully configured agent
272
291
  const agent = new Agent<HealthcareContext>({
@@ -342,6 +361,14 @@ async function main() {
342
361
 
343
362
  // Session tracks the appointment booking progress
344
363
  console.log("Current state:", response2.session?.currentState?.id);
364
+
365
+ // Check for route completion
366
+ if (response2.isRouteComplete && response2.session) {
367
+ console.log("\n✅ Appointment scheduling complete!");
368
+ await sendAppointmentConfirmation(
369
+ agent.getExtractedData(response2.session.id) as AppointmentData
370
+ );
371
+ }
345
372
  }
346
373
 
347
374
  // Note: Custom IDs ensure consistency across server restarts
@@ -352,6 +379,22 @@ async function main() {
352
379
  // - State recovery for resuming conversations
353
380
  }
354
381
 
382
+ /**
383
+ * Mock function to send an appointment confirmation.
384
+ * @param data - The appointment data.
385
+ */
386
+ async function sendAppointmentConfirmation(data: AppointmentData) {
387
+ console.log("\n" + "=".repeat(60));
388
+ console.log("🚀 Sending Appointment Confirmation...");
389
+ console.log("=".repeat(60));
390
+ console.log("Appointment Details:", JSON.stringify(data, null, 2));
391
+ console.log(
392
+ ` - Sending confirmation to patient for ${data.preferredDate} at ${data.preferredTime}.`
393
+ );
394
+ await new Promise((resolve) => setTimeout(resolve, 1000));
395
+ console.log("✨ Confirmation sent!");
396
+ }
397
+
355
398
  // Uncomment to run:
356
399
  // main().catch(console.error);
357
400
 
@@ -17,6 +17,8 @@ import {
17
17
  createMessageEvent,
18
18
  EventSource,
19
19
  createSession,
20
+ END_STATE,
21
+ defineTool,
20
22
  } from "../src/index";
21
23
  import { OpenRouterProvider } from "../src/providers";
22
24
 
@@ -102,11 +104,55 @@ agent.createRoute({
102
104
  domains: ["scraping"], // ✅ Only scraping tools available
103
105
  });
104
106
 
105
- agent.createRoute({
107
+ const scheduleEventTool = defineTool({
108
+ id: "scheduleEvent",
109
+ name: "scheduleEvent",
110
+ description: "Schedules an event in the calendar",
111
+ handler: async ({ extracted }) => {
112
+ const { title, date, description } = extracted as {
113
+ title: string;
114
+ date: string;
115
+ description: string;
116
+ };
117
+ console.log(`[Calendar] Scheduling event: ${title} on ${date}`);
118
+ return { data: { eventId: "evt_123", success: true } };
119
+ },
120
+ });
121
+
122
+ agent.createRoute<{ title: string; date: string; description: string }>({
106
123
  title: "Schedule Meeting",
107
124
  description: "Book and manage appointments",
108
125
  conditions: ["User wants to schedule, view, or cancel events"],
109
126
  domains: ["calendar"], // ✅ Only calendar tools available
127
+ extractionSchema: {
128
+ type: "object",
129
+ properties: {
130
+ title: { type: "string" },
131
+ date: { type: "string" },
132
+ description: { type: "string" },
133
+ },
134
+ required: ["title", "date"],
135
+ },
136
+ steps: [
137
+ {
138
+ chatState: "What is the title of the meeting?",
139
+ gather: ["title"],
140
+ },
141
+ {
142
+ chatState: "When would you like to schedule it?",
143
+ gather: ["date"],
144
+ },
145
+ {
146
+ chatState: "Any description for the meeting?",
147
+ gather: ["description"],
148
+ },
149
+ {
150
+ toolState: scheduleEventTool,
151
+ },
152
+ {
153
+ chatState: "The meeting has been scheduled.",
154
+ },
155
+ ],
110
156
  });
111
157
 
112
158
  agent.createRoute({
@@ -137,11 +183,11 @@ async function demonstrateScoping() {
137
183
  // Example 1: Data Collection route - only scraping tools available
138
184
  console.log("1️⃣ Example: User wants to scrape data");
139
185
  const history1 = [
140
- createMessageEvent(
141
- EventSource.CUSTOMER,
142
- "Alice",
143
- "Can you scrape the homepage of example.com?"
144
- ),
186
+ createMessageEvent({
187
+ source: EventSource.CUSTOMER,
188
+ participantName: "Alice",
189
+ message: "Can you scrape the homepage of example.com?",
190
+ }),
145
191
  ];
146
192
 
147
193
  // Initialize session state for multi-turn conversation
@@ -158,11 +204,11 @@ async function demonstrateScoping() {
158
204
  // Example 2: Schedule Meeting route - only calendar tools available
159
205
  console.log("2️⃣ Example: User wants to schedule a meeting");
160
206
  const history2 = [
161
- createMessageEvent(
162
- EventSource.CUSTOMER,
163
- "Bob",
164
- "Schedule a meeting for tomorrow at 2pm"
165
- ),
207
+ createMessageEvent({
208
+ source: EventSource.CUSTOMER,
209
+ participantName: "Bob",
210
+ message: "Schedule a meeting for tomorrow at 2pm",
211
+ }),
166
212
  ];
167
213
 
168
214
  const response2 = await agent.respond({ history: history2, session });
@@ -170,17 +216,27 @@ async function demonstrateScoping() {
170
216
  console.log(`Available tools in this route: calendar only`);
171
217
  console.log(`Response: ${response2.message}\n`);
172
218
 
219
+ if (response2.isRouteComplete) {
220
+ console.log("\n✅ Meeting scheduling complete!");
221
+ await sendMeetingInvite(
222
+ agent.getExtractedData(response2.session?.id) as {
223
+ title: string;
224
+ date: string;
225
+ }
226
+ );
227
+ }
228
+
173
229
  // Update session again
174
230
  session = response2.session!;
175
231
 
176
232
  // Example 3: Customer Support route - NO tools available
177
233
  console.log("3️⃣ Example: User has a general question");
178
234
  const history3 = [
179
- createMessageEvent(
180
- EventSource.CUSTOMER,
181
- "Charlie",
182
- "What are your business hours?"
183
- ),
235
+ createMessageEvent({
236
+ source: EventSource.CUSTOMER,
237
+ participantName: "Charlie",
238
+ message: "What are your business hours?",
239
+ }),
184
240
  ];
185
241
 
186
242
  const response3 = await agent.respond({ history: history3, session });
@@ -194,11 +250,11 @@ async function demonstrateScoping() {
194
250
  // Example 4: Admin Support route - ALL tools available (for demo purposes)
195
251
  console.log("4️⃣ Example: Admin needs full access");
196
252
  const history4 = [
197
- createMessageEvent(
198
- EventSource.CUSTOMER,
199
- "Admin",
200
- "I need to generate a report and process a refund"
201
- ),
253
+ createMessageEvent({
254
+ source: EventSource.CUSTOMER,
255
+ participantName: "Admin",
256
+ message: "I need to generate a report and process a refund",
257
+ }),
202
258
  ];
203
259
 
204
260
  const response4 = await agent.respond({ history: history4, session });
@@ -288,6 +344,20 @@ console.log(
288
344
  )}`
289
345
  );
290
346
 
347
+ /**
348
+ * Mock function to send a meeting invite.
349
+ * @param data - The meeting data.
350
+ */
351
+ async function sendMeetingInvite(data: { title: string; date: string }) {
352
+ console.log("\n" + "=".repeat(60));
353
+ console.log("🚀 Sending Meeting Invite...");
354
+ console.log("=".repeat(60));
355
+ console.log("Meeting Details:", JSON.stringify(data, null, 2));
356
+ console.log(` - Sending invite for "${data.title}" on ${data.date}.`);
357
+ await new Promise((resolve) => setTimeout(resolve, 1000));
358
+ console.log("✨ Invite sent!");
359
+ }
360
+
291
361
  // Run demonstration
292
362
  if (import.meta.url === `file://${process.argv[1]}`) {
293
363
  demonstrateScoping().catch(console.error);