@falai/agent 0.6.3 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) 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 +113 -26
  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/ResponseEngine.d.ts +1 -1
  15. package/dist/cjs/core/ResponseEngine.d.ts.map +1 -1
  16. package/dist/cjs/core/ResponseEngine.js +4 -1
  17. package/dist/cjs/core/ResponseEngine.js.map +1 -1
  18. package/dist/cjs/core/Route.d.ts.map +1 -1
  19. package/dist/cjs/core/Route.js +4 -4
  20. package/dist/cjs/core/Route.js.map +1 -1
  21. package/dist/cjs/core/RoutingEngine.d.ts +6 -1
  22. package/dist/cjs/core/RoutingEngine.d.ts.map +1 -1
  23. package/dist/cjs/core/RoutingEngine.js +116 -41
  24. package/dist/cjs/core/RoutingEngine.js.map +1 -1
  25. package/dist/cjs/core/State.d.ts +18 -6
  26. package/dist/cjs/core/State.d.ts.map +1 -1
  27. package/dist/cjs/core/State.js +30 -7
  28. package/dist/cjs/core/State.js.map +1 -1
  29. package/dist/cjs/core/Tool.d.ts +8 -1
  30. package/dist/cjs/core/Tool.d.ts.map +1 -1
  31. package/dist/cjs/core/Tool.js +25 -28
  32. package/dist/cjs/core/Tool.js.map +1 -1
  33. package/dist/cjs/core/Transition.js +1 -1
  34. package/dist/cjs/index.d.ts +1 -1
  35. package/dist/cjs/index.d.ts.map +1 -1
  36. package/dist/cjs/index.js +3 -2
  37. package/dist/cjs/index.js.map +1 -1
  38. package/dist/cjs/types/agent.d.ts +5 -0
  39. package/dist/cjs/types/agent.d.ts.map +1 -1
  40. package/dist/cjs/types/agent.js.map +1 -1
  41. package/dist/cjs/types/route.d.ts +7 -1
  42. package/dist/cjs/types/route.d.ts.map +1 -1
  43. package/dist/cjs/types/session.d.ts +13 -2
  44. package/dist/cjs/types/session.d.ts.map +1 -1
  45. package/dist/cjs/types/session.js +28 -5
  46. package/dist/cjs/types/session.js.map +1 -1
  47. package/dist/cjs/utils/logger.d.ts +10 -0
  48. package/dist/cjs/utils/logger.d.ts.map +1 -0
  49. package/dist/cjs/utils/logger.js +23 -0
  50. package/dist/cjs/utils/logger.js.map +1 -0
  51. package/dist/constants/index.d.ts +6 -1
  52. package/dist/constants/index.d.ts.map +1 -1
  53. package/dist/constants/index.js +6 -1
  54. package/dist/constants/index.js.map +1 -1
  55. package/dist/core/Agent.d.ts +22 -0
  56. package/dist/core/Agent.d.ts.map +1 -1
  57. package/dist/core/Agent.js +113 -26
  58. package/dist/core/Agent.js.map +1 -1
  59. package/dist/core/Events.d.ts +13 -0
  60. package/dist/core/Events.d.ts.map +1 -1
  61. package/dist/core/Events.js +28 -14
  62. package/dist/core/Events.js.map +1 -1
  63. package/dist/core/ResponseEngine.d.ts +1 -1
  64. package/dist/core/ResponseEngine.d.ts.map +1 -1
  65. package/dist/core/ResponseEngine.js +4 -1
  66. package/dist/core/ResponseEngine.js.map +1 -1
  67. package/dist/core/Route.d.ts.map +1 -1
  68. package/dist/core/Route.js +4 -4
  69. package/dist/core/Route.js.map +1 -1
  70. package/dist/core/RoutingEngine.d.ts +6 -1
  71. package/dist/core/RoutingEngine.d.ts.map +1 -1
  72. package/dist/core/RoutingEngine.js +116 -41
  73. package/dist/core/RoutingEngine.js.map +1 -1
  74. package/dist/core/State.d.ts +18 -6
  75. package/dist/core/State.d.ts.map +1 -1
  76. package/dist/core/State.js +31 -8
  77. package/dist/core/State.js.map +1 -1
  78. package/dist/core/Tool.d.ts +8 -1
  79. package/dist/core/Tool.d.ts.map +1 -1
  80. package/dist/core/Tool.js +25 -28
  81. package/dist/core/Tool.js.map +1 -1
  82. package/dist/core/Transition.js +1 -1
  83. package/dist/index.d.ts +1 -1
  84. package/dist/index.d.ts.map +1 -1
  85. package/dist/index.js +1 -1
  86. package/dist/index.js.map +1 -1
  87. package/dist/types/agent.d.ts +5 -0
  88. package/dist/types/agent.d.ts.map +1 -1
  89. package/dist/types/agent.js.map +1 -1
  90. package/dist/types/route.d.ts +7 -1
  91. package/dist/types/route.d.ts.map +1 -1
  92. package/dist/types/session.d.ts +13 -2
  93. package/dist/types/session.d.ts.map +1 -1
  94. package/dist/types/session.js +28 -5
  95. package/dist/types/session.js.map +1 -1
  96. package/dist/utils/logger.d.ts +10 -0
  97. package/dist/utils/logger.d.ts.map +1 -0
  98. package/dist/utils/logger.js +17 -0
  99. package/dist/utils/logger.js.map +1 -0
  100. package/docs/{CONSTRUCTOR_OPTIONS.md → AGENT.md} +79 -7
  101. package/docs/API_REFERENCE.md +309 -18
  102. package/docs/ARCHITECTURE.md +1 -1
  103. package/docs/DOCS.md +46 -22
  104. package/docs/GETTING_STARTED.md +1 -1
  105. package/docs/README.md +13 -5
  106. package/docs/ROUTES.md +743 -0
  107. package/docs/STATES.md +798 -0
  108. package/examples/business-onboarding.ts +46 -5
  109. package/examples/company-qna-agent.ts +107 -1
  110. package/examples/custom-database-persistence.ts +44 -1
  111. package/examples/declarative-agent.ts +80 -37
  112. package/examples/domain-scoping.ts +91 -21
  113. package/examples/extracted-data-modification.ts +64 -2
  114. package/examples/healthcare-agent.ts +61 -4
  115. package/examples/openai-agent.ts +24 -2
  116. package/examples/opensearch-persistence.ts +26 -1
  117. package/examples/persistent-onboarding.ts +84 -18
  118. package/examples/prisma-persistence.ts +90 -16
  119. package/examples/redis-persistence.ts +89 -17
  120. package/examples/rules-prohibitions.ts +300 -139
  121. package/examples/streaming-agent.ts +60 -0
  122. package/examples/travel-agent.ts +66 -24
  123. package/package.json +3 -2
  124. package/src/constants/index.ts +6 -1
  125. package/src/core/Agent.ts +140 -24
  126. package/src/core/Events.ts +73 -10
  127. package/src/core/ResponseEngine.ts +6 -0
  128. package/src/core/Route.ts +8 -4
  129. package/src/core/RoutingEngine.ts +154 -43
  130. package/src/core/State.ts +45 -12
  131. package/src/core/Tool.ts +67 -10
  132. package/src/core/Transition.ts +1 -1
  133. package/src/index.ts +1 -1
  134. package/src/types/agent.ts +5 -0
  135. package/src/types/route.ts +10 -1
  136. package/src/types/session.ts +47 -7
  137. package/src/utils/logger.ts +19 -0
@@ -14,6 +14,7 @@ import {
14
14
  OpenAIProvider,
15
15
  GeminiProvider,
16
16
  createSession,
17
+ END_STATE,
17
18
  } from "../src/index";
18
19
 
19
20
  // Custom context type
@@ -257,6 +258,37 @@ async function streamingWithRoutes() {
257
258
  chatState: "Understand the user's product question",
258
259
  });
259
260
 
261
+ // Create a feedback route
262
+ const feedbackRoute = agent.createRoute<{
263
+ rating: number;
264
+ comments: string;
265
+ }>({
266
+ title: "Collect Feedback",
267
+ description: "Collect user feedback on their support experience",
268
+ conditions: ["User wants to provide feedback"],
269
+ extractionSchema: {
270
+ type: "object",
271
+ properties: {
272
+ rating: { type: "number", minimum: 1, maximum: 5 },
273
+ comments: { type: "string" },
274
+ },
275
+ required: ["rating"],
276
+ },
277
+ steps: [
278
+ {
279
+ chatState: "How would you rate your support experience from 1 to 5?",
280
+ gather: ["rating"],
281
+ },
282
+ {
283
+ chatState: "Thanks for the rating! Any other comments?",
284
+ gather: ["comments"],
285
+ },
286
+ {
287
+ chatState: "We appreciate your feedback!",
288
+ },
289
+ ],
290
+ });
291
+
260
292
  const history = [
261
293
  createMessageEvent(
262
294
  EventSource.CUSTOMER,
@@ -285,6 +317,19 @@ async function streamingWithRoutes() {
285
317
  );
286
318
  console.log(` - Extracted:`, chunk.session?.extracted || "None");
287
319
 
320
+ // Check for route completion
321
+ if (chunk.isRouteComplete) {
322
+ console.log("\n✅ Route complete!");
323
+ if (chunk.session?.currentRoute?.title === "Collect Feedback") {
324
+ await logFeedback(
325
+ agent.getExtractedData(chunk.session?.id) as {
326
+ rating: number;
327
+ comments: string;
328
+ }
329
+ );
330
+ }
331
+ }
332
+
288
333
  // Update session with progress
289
334
  session = chunk.session!;
290
335
  }
@@ -372,6 +417,21 @@ async function streamingWithAbortSignal() {
372
417
  }
373
418
  }
374
419
 
420
+ /**
421
+ * Mock function to log feedback.
422
+ * @param data - The feedback data.
423
+ */
424
+ async function logFeedback(data: { rating: number; comments: string }) {
425
+ console.log("\n" + "=".repeat(60));
426
+ console.log("📝 Logging Feedback...");
427
+ console.log("=".repeat(60));
428
+ console.log("Feedback Details:", JSON.stringify(data, null, 2));
429
+ console.log(` - Rating: ${data.rating}`);
430
+ console.log(` - Comments: ${data.comments}`);
431
+ await new Promise((resolve) => setTimeout(resolve, 1000));
432
+ console.log("✨ Feedback logged successfully!");
433
+ }
434
+
375
435
  async function main() {
376
436
  console.log("🚀 Starting Streaming Examples\n");
377
437
  console.log("=".repeat(60));
@@ -7,7 +7,7 @@ import {
7
7
  Agent,
8
8
  defineTool,
9
9
  OpenRouterProvider,
10
- END_ROUTE,
10
+ END_STATE,
11
11
  EventSource,
12
12
  createMessageEvent,
13
13
  createSession,
@@ -200,6 +200,7 @@ async function createTravelAgent() {
200
200
  customerId: "test-123",
201
201
  customerName: "Test Customer",
202
202
  },
203
+ debug: true,
203
204
  });
204
205
 
205
206
  // Add domain glossary
@@ -328,7 +329,7 @@ async function createTravelAgent() {
328
329
  });
329
330
 
330
331
  provideConfirmation.transitionTo({
331
- state: END_ROUTE,
332
+ state: END_STATE,
332
333
  condition: "Customer has confirmation, booking flow complete",
333
334
  });
334
335
 
@@ -388,7 +389,7 @@ async function createTravelAgent() {
388
389
  });
389
390
 
390
391
  provideStatus.transitionTo({
391
- state: END_ROUTE,
392
+ state: END_STATE,
392
393
  condition: "Booking information provided to customer",
393
394
  });
394
395
 
@@ -431,16 +432,16 @@ async function main() {
431
432
  ),
432
433
  ];
433
434
 
434
- console.log("Agent:", agent.name);
435
- console.log("Description:", agent.description);
435
+ console.info("Agent:", agent.name);
436
+ console.info("Description:", agent.description);
436
437
 
437
438
  // Turn 1 - Agent extracts data and starts booking flow
438
439
  const response1 = await agent.respond({ history, session });
439
- console.log("\n=== TURN 1 ===");
440
- console.log("Agent:", response1.message);
441
- console.log("Route:", response1.session?.currentRoute?.title);
442
- console.log("State:", response1.session?.currentState?.id);
443
- console.log("Extracted:", response1.session?.extracted);
440
+ console.info("\n=== TURN 1 ===");
441
+ console.info("Agent:", response1.message);
442
+ console.info("Route:", response1.session?.currentRoute?.title);
443
+ console.info("State:", response1.session?.currentState?.id);
444
+ console.info("Extracted:", response1.session?.extracted);
444
445
 
445
446
  // Session state updated with progress
446
447
  session = response1.session!;
@@ -454,10 +455,10 @@ async function main() {
454
455
  ];
455
456
 
456
457
  const response2 = await agent.respond({ history: history2, session });
457
- console.log("\n=== TURN 2 ===");
458
- console.log("Agent:", response2.message);
459
- console.log("Updated extracted:", response2.session?.extracted);
460
- console.log("Current state:", response2.session?.currentState?.id);
458
+ console.info("\n=== TURN 2 ===");
459
+ console.info("Agent:", response2.message);
460
+ console.info("Updated extracted:", response2.session?.extracted);
461
+ console.info("Current state:", response2.session?.currentState?.id);
461
462
  }
462
463
 
463
464
  // Demonstrate booking status check
@@ -474,24 +475,65 @@ async function main() {
474
475
  history: statusHistory,
475
476
  session: statusSession,
476
477
  });
477
- console.log("\n=== BOOKING STATUS CHECK ===");
478
- console.log("Agent:", statusResponse.message);
479
- console.log("Route:", statusResponse.session?.currentRoute?.title);
480
- console.log("Extracted:", statusResponse.session?.extracted);
478
+ console.info("\n=== BOOKING STATUS CHECK ===");
479
+ console.info("Agent:", statusResponse.message);
480
+ console.info("Route:", statusResponse.session?.currentRoute?.title);
481
+ console.info("Extracted:", statusResponse.session?.extracted);
481
482
 
482
483
  // Show session state management benefits
483
- console.log("\n=== SESSION STATE BENEFITS ===");
484
- console.log("✅ Always-on routing - respects user intent changes");
485
- console.log("✅ Data persistence - extracted data survives across turns");
486
- console.log(
484
+ console.info("\n=== SESSION STATE BENEFITS ===");
485
+ console.info("✅ Always-on routing - respects user intent changes");
486
+ console.info("✅ Data persistence - extracted data survives across turns");
487
+ console.info(
487
488
  "✅ State progression - intelligent flow based on collected data"
488
489
  );
489
- console.log("✅ Context awareness - router sees current progress");
490
+ console.info("✅ Context awareness - router sees current progress");
491
+
492
+ if (statusResponse.isRouteComplete) {
493
+ console.info("\n✅ Booking status check complete!");
494
+ await logBookingStatusCheck(
495
+ agent.getExtractedData(
496
+ statusResponse.session?.id
497
+ ) as unknown as BookingStatusData
498
+ );
499
+ }
500
+ }
501
+
502
+ /**
503
+ * Mock function to send a booking confirmation.
504
+ * @param data - The flight booking data.
505
+ */
506
+ async function sendBookingConfirmation(data: FlightBookingData) {
507
+ console.info("\n" + "=".repeat(60));
508
+ console.info("🚀 Sending Booking Confirmation...");
509
+ console.info("=".repeat(60));
510
+ console.info("Booking Details:", JSON.stringify(data, null, 2));
511
+ console.info(
512
+ ` - Sending confirmation for ${data.passengers} passengers to ${data.destination}.`
513
+ );
514
+ await new Promise((resolve) => setTimeout(resolve, 1000));
515
+ console.info("✨ Confirmation sent!");
516
+ }
517
+
518
+ /**
519
+ * Mock function to log a booking status check.
520
+ * @param data - The booking status data.
521
+ */
522
+ async function logBookingStatusCheck(data: BookingStatusData) {
523
+ console.info("\n" + "=".repeat(60));
524
+ console.info("📝 Logging Booking Status Check...");
525
+ console.info("=".repeat(60));
526
+ console.info("Check Details:", JSON.stringify(data, null, 2));
527
+ console.info(
528
+ ` - Logging status check for confirmation #${data.confirmationNumber}.`
529
+ );
530
+ await new Promise((resolve) => setTimeout(resolve, 500));
531
+ console.info("✨ Status check logged!");
490
532
  }
491
533
 
492
534
  // Run if executed directly
493
535
  if (import.meta.url === `file://${process.argv[1]}`) {
494
- main().catch(console.error);
536
+ main().catch((err) => console.error(err));
495
537
  }
496
538
 
497
539
  export { createTravelAgent };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@falai/agent",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "description": "Standalone, strongly-typed AI Agent framework with route DSL and AI provider strategy",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -79,12 +79,13 @@
79
79
  "dependencies": {
80
80
  "@anthropic-ai/sdk": "^0.65.0",
81
81
  "@google/genai": "^0.3.0",
82
+ "@types/redis": "^4.0.11",
83
+ "loglevel": "^1.9.2",
82
84
  "openai": "^6.3.0"
83
85
  },
84
86
  "peerDependencies": {
85
87
  "@prisma/client": "^6.0.0",
86
88
  "ioredis": "^5.7.0",
87
- "redis": "^4.6.0 || ^5.0.0",
88
89
  "mongodb": "^6.0.0 || ^7.0.0",
89
90
  "pg": "^8.11.0",
90
91
  "mysql2": "^3.2.0",
@@ -1,5 +1,10 @@
1
1
  /**
2
2
  * Special marker to end a route/journey
3
3
  */
4
+ export const END_STATE = Symbol("END_STATE");
4
5
 
5
- export const END_ROUTE = Symbol("END_ROUTE");
6
+ /**
7
+ * String constant for END_STATE comparisons
8
+ * Use this when checking if currentState.id has reached END_STATE
9
+ */
10
+ export const END_STATE_ID = "END_STATE";
package/src/core/Agent.ts CHANGED
@@ -10,6 +10,7 @@ import type { SessionState } from "../types/session";
10
10
  import type { AgentStructuredResponse } from "../types/ai";
11
11
  import { createSession, enterState, mergeExtracted } from "../types/session";
12
12
  import { PromptComposer } from "./PromptComposer";
13
+ import { logger, LoggerLevel } from "../utils/logger";
13
14
 
14
15
  import { Route } from "./Route";
15
16
  import { State } from "./State";
@@ -19,6 +20,7 @@ import { RoutingEngine } from "./RoutingEngine";
19
20
  import { ResponseEngine } from "./ResponseEngine";
20
21
  import { ToolExecutor } from "./ToolExecutor";
21
22
  import { getLastMessageFromHistory } from "../utils/event";
23
+ import { END_STATE_ID } from "../constants";
22
24
 
23
25
  /**
24
26
  * Main Agent class with generic context support
@@ -34,6 +36,7 @@ export class Agent<TContext = unknown> {
34
36
  private persistenceManager: PersistenceManager | undefined;
35
37
  private routingEngine: RoutingEngine<TContext>;
36
38
  private responseEngine: ResponseEngine<TContext>;
39
+ private currentSession?: SessionState;
37
40
 
38
41
  /**
39
42
  * Dynamic domain property - populated via addDomain
@@ -41,6 +44,11 @@ export class Agent<TContext = unknown> {
41
44
  public readonly domain: Record<string, Record<string, unknown>> = {};
42
45
 
43
46
  constructor(private readonly options: AgentOptions<TContext>) {
47
+ // Set log level based on debug option
48
+ if (options.debug) {
49
+ logger.setLevel(LoggerLevel.DEBUG);
50
+ }
51
+
44
52
  // Validate context configuration
45
53
  if (options.context !== undefined && options.contextProvider) {
46
54
  throw new Error(
@@ -51,6 +59,9 @@ export class Agent<TContext = unknown> {
51
59
  // Initialize context if provided
52
60
  this.context = options.context;
53
61
 
62
+ // Initialize current session if provided
63
+ this.currentSession = options.session;
64
+
54
65
  // Initialize routing and response engines
55
66
  this.routingEngine = new RoutingEngine<TContext>({
56
67
  maxCandidates: 5,
@@ -66,7 +77,7 @@ export class Agent<TContext = unknown> {
66
77
  // Initialize the adapter if it has an initialize method
67
78
  if (options.persistence.adapter.initialize) {
68
79
  options.persistence.adapter.initialize().catch((error) => {
69
- console.error(
80
+ logger.error(
70
81
  "[Agent] Persistence adapter initialization failed:",
71
82
  error
72
83
  );
@@ -272,6 +283,7 @@ export class Agent<TContext = unknown> {
272
283
  done: boolean;
273
284
  session?: SessionState;
274
285
  toolCalls?: Array<{ toolName: string; arguments: Record<string, unknown> }>;
286
+ isRouteComplete?: boolean;
275
287
  }> {
276
288
  const { history, contextOverride, signal } = params;
277
289
 
@@ -291,8 +303,8 @@ export class Agent<TContext = unknown> {
291
303
  ...(contextOverride as Record<string, unknown>),
292
304
  } as TContext;
293
305
 
294
- // Initialize or get session
295
- let session = params.session || createSession();
306
+ // Initialize or get session (use current session if available)
307
+ let session = params.session || this.currentSession || createSession();
296
308
 
297
309
  // PHASE 1: TOOL EXECUTION - Execute tools if current state has toolState
298
310
  if (session.currentRoute && session.currentState) {
@@ -331,13 +343,13 @@ export class Agent<TContext = unknown> {
331
343
  session,
332
344
  result.extractedUpdate
333
345
  );
334
- console.log(
346
+ logger.debug(
335
347
  `[Agent] Tool updated extracted data:`,
336
348
  result.extractedUpdate
337
349
  );
338
350
  }
339
351
 
340
- console.log(
352
+ logger.debug(
341
353
  `[Agent] Executed tool: ${result.toolName} (success: ${result.success})`
342
354
  );
343
355
  }
@@ -349,6 +361,7 @@ export class Agent<TContext = unknown> {
349
361
  let selectedRoute: Route<TContext> | undefined;
350
362
  let responseDirectives: string[] | undefined;
351
363
  let selectedState: State<TContext> | undefined;
364
+ let isRouteComplete = false;
352
365
 
353
366
  if (this.routes.length > 0) {
354
367
  const orchestration = await this.routingEngine.decideRouteAndState({
@@ -370,10 +383,18 @@ export class Agent<TContext = unknown> {
370
383
  selectedState = orchestration.selectedState;
371
384
  responseDirectives = orchestration.responseDirectives;
372
385
  session = orchestration.session;
386
+ isRouteComplete = orchestration.isRouteComplete || false;
387
+
388
+ // Log if route is complete
389
+ if (isRouteComplete) {
390
+ logger.debug(
391
+ `[Agent] Route complete: all required data collected, END_STATE reached`
392
+ );
393
+ }
373
394
  }
374
395
 
375
396
  // PHASE 3: DETERMINE NEXT STATE - Use state from combined decision or get initial state
376
- if (selectedRoute) {
397
+ if (selectedRoute && !isRouteComplete) {
377
398
  let nextState: State<TContext>;
378
399
 
379
400
  // If we have a selected state from the combined routing decision, use it
@@ -384,17 +405,17 @@ export class Agent<TContext = unknown> {
384
405
  const candidates = this.routingEngine.getCandidateStates(
385
406
  selectedRoute,
386
407
  undefined,
387
- session.extracted
408
+ session.extracted || {}
388
409
  );
389
410
  if (candidates.length > 0) {
390
411
  nextState = candidates[0].state;
391
- console.log(
412
+ logger.debug(
392
413
  `[Agent] Using first valid state: ${nextState.id} for new route`
393
414
  );
394
415
  } else {
395
416
  // Fallback to initial state even if it should be skipped
396
417
  nextState = selectedRoute.initialState;
397
- console.warn(
418
+ logger.warn(
398
419
  `[Agent] No valid states found, using initial state: ${nextState.id}`
399
420
  );
400
421
  }
@@ -402,7 +423,7 @@ export class Agent<TContext = unknown> {
402
423
 
403
424
  // Update session with next state
404
425
  session = enterState(session, nextState.id, nextState.description);
405
- console.log(`[Agent] Entered state: ${nextState.id}`);
426
+ logger.debug(`[Agent] Entered state: ${nextState.id}`);
406
427
 
407
428
  // PHASE 4: RESPONSE GENERATION - Stream message using selected route and state
408
429
  // Get last user message
@@ -417,6 +438,7 @@ export class Agent<TContext = unknown> {
417
438
  // Build response prompt
418
439
  const responsePrompt = this.responseEngine.buildResponsePrompt(
419
440
  selectedRoute,
441
+ nextState,
420
442
  selectedRoute.getRules(),
421
443
  selectedRoute.getProhibitions(),
422
444
  responseDirectives,
@@ -464,7 +486,7 @@ export class Agent<TContext = unknown> {
464
486
  // Merge gathered data into session
465
487
  if (Object.keys(gatheredData).length > 0) {
466
488
  session = await this.updateExtracted(session, gatheredData);
467
- console.log(`[Agent] Extracted data:`, gatheredData);
489
+ logger.debug(`[Agent] Extracted data:`, gatheredData);
468
490
  }
469
491
  }
470
492
 
@@ -489,19 +511,39 @@ export class Agent<TContext = unknown> {
489
511
  this.options.persistence?.autoSave !== false
490
512
  ) {
491
513
  await this.persistenceManager.saveSessionState(session.id, session);
492
- console.log(
514
+ logger.debug(
493
515
  `[Agent] Auto-saved session state to persistence: ${session.id}`
494
516
  );
495
517
  }
496
518
 
519
+ // Update current session if we have one
520
+ if (chunk.done && this.currentSession) {
521
+ this.currentSession = session;
522
+ }
523
+
497
524
  yield {
498
525
  delta: chunk.delta,
499
526
  accumulated: chunk.accumulated,
500
527
  done: chunk.done,
501
528
  session, // Return updated session
502
529
  toolCalls,
530
+ isRouteComplete,
503
531
  };
504
532
  }
533
+ } else if (isRouteComplete && selectedRoute) {
534
+ // Route is complete - set state to END_STATE marker and yield completion signal
535
+ session = enterState(session, END_STATE_ID, "Route completed");
536
+ logger.debug(
537
+ `[Agent] Route ${selectedRoute.title} completed. Entered END_STATE state.`
538
+ );
539
+ yield {
540
+ delta: "",
541
+ accumulated: "",
542
+ done: true,
543
+ session,
544
+ toolCalls: undefined,
545
+ isRouteComplete: true,
546
+ };
505
547
  } else {
506
548
  // Fallback: No routes defined, stream a simple response
507
549
  const fallbackPrompt = new PromptComposer<TContext>()
@@ -536,12 +578,18 @@ export class Agent<TContext = unknown> {
536
578
  });
537
579
 
538
580
  for await (const chunk of stream) {
581
+ // Update current session if we have one
582
+ if (chunk.done && this.currentSession) {
583
+ this.currentSession = session;
584
+ }
585
+
539
586
  yield {
540
587
  delta: chunk.delta,
541
588
  accumulated: chunk.accumulated,
542
589
  done: chunk.done,
543
590
  session, // Return updated session
544
591
  toolCalls: undefined,
592
+ isRouteComplete: false,
545
593
  };
546
594
  }
547
595
  }
@@ -560,6 +608,7 @@ export class Agent<TContext = unknown> {
560
608
  message: string;
561
609
  session?: SessionState;
562
610
  toolCalls?: Array<{ toolName: string; arguments: Record<string, unknown> }>;
611
+ isRouteComplete?: boolean;
563
612
  }> {
564
613
  const { history, contextOverride, signal } = params;
565
614
 
@@ -579,8 +628,9 @@ export class Agent<TContext = unknown> {
579
628
  ...(contextOverride as Record<string, unknown>),
580
629
  } as TContext;
581
630
 
582
- // Initialize or get session
583
- let session = params.session || createSession();
631
+ // Initialize or get session (use current session if available)
632
+ let session =
633
+ params.session || this.currentSession || createSession<TContext>();
584
634
 
585
635
  // PHASE 1: TOOL EXECUTION - Execute tools if current state has toolState
586
636
  if (session.currentRoute && session.currentState) {
@@ -619,13 +669,13 @@ export class Agent<TContext = unknown> {
619
669
  session,
620
670
  result.extractedUpdate
621
671
  );
622
- console.log(
672
+ logger.debug(
623
673
  `[Agent] Tool updated extracted data:`,
624
674
  result.extractedUpdate
625
675
  );
626
676
  }
627
677
 
628
- console.log(
678
+ logger.debug(
629
679
  `[Agent] Executed tool: ${result.toolName} (success: ${result.success})`
630
680
  );
631
681
  }
@@ -637,6 +687,7 @@ export class Agent<TContext = unknown> {
637
687
  let selectedRoute: Route<TContext> | undefined;
638
688
  let responseDirectives: string[] | undefined;
639
689
  let selectedState: State<TContext> | undefined;
690
+ let isRouteComplete = false;
640
691
 
641
692
  if (this.routes.length > 0) {
642
693
  const orchestration = await this.routingEngine.decideRouteAndState({
@@ -658,6 +709,14 @@ export class Agent<TContext = unknown> {
658
709
  selectedState = orchestration.selectedState;
659
710
  responseDirectives = orchestration.responseDirectives;
660
711
  session = orchestration.session;
712
+ isRouteComplete = orchestration.isRouteComplete || false;
713
+
714
+ // Log if route is complete
715
+ if (isRouteComplete) {
716
+ logger.debug(
717
+ `[Agent] Route complete: all required data collected, END_STATE reached`
718
+ );
719
+ }
661
720
  }
662
721
 
663
722
  // PHASE 3: DETERMINE NEXT STATE - Use state from combined decision or get initial state
@@ -666,7 +725,7 @@ export class Agent<TContext = unknown> {
666
725
  | Array<{ toolName: string; arguments: Record<string, unknown> }>
667
726
  | undefined = undefined;
668
727
 
669
- if (selectedRoute) {
728
+ if (selectedRoute && !isRouteComplete) {
670
729
  let nextState: State<TContext>;
671
730
 
672
731
  // If we have a selected state from the combined routing decision, use it
@@ -677,17 +736,17 @@ export class Agent<TContext = unknown> {
677
736
  const candidates = this.routingEngine.getCandidateStates(
678
737
  selectedRoute,
679
738
  undefined,
680
- session.extracted
739
+ session.extracted || {}
681
740
  );
682
741
  if (candidates.length > 0) {
683
742
  nextState = candidates[0].state;
684
- console.log(
743
+ logger.debug(
685
744
  `[Agent] Using first valid state: ${nextState.id} for new route`
686
745
  );
687
746
  } else {
688
747
  // Fallback to initial state even if it should be skipped
689
748
  nextState = selectedRoute.initialState;
690
- console.warn(
749
+ logger.warn(
691
750
  `[Agent] No valid states found, using initial state: ${nextState.id}`
692
751
  );
693
752
  }
@@ -695,7 +754,7 @@ export class Agent<TContext = unknown> {
695
754
 
696
755
  // Update session with next state
697
756
  session = enterState(session, nextState.id, nextState.description);
698
- console.log(`[Agent] Entered state: ${nextState.id}`);
757
+ logger.debug(`[Agent] Entered state: ${nextState.id}`);
699
758
 
700
759
  // PHASE 4: RESPONSE GENERATION - Generate message using selected route and state
701
760
  // Get last user message
@@ -710,6 +769,7 @@ export class Agent<TContext = unknown> {
710
769
  // Build response prompt
711
770
  const responsePrompt = this.responseEngine.buildResponsePrompt(
712
771
  selectedRoute,
772
+ nextState,
713
773
  selectedRoute.getRules(),
714
774
  selectedRoute.getProhibitions(),
715
775
  responseDirectives,
@@ -752,8 +812,8 @@ export class Agent<TContext = unknown> {
752
812
 
753
813
  // Merge gathered data into session
754
814
  if (Object.keys(gatheredData).length > 0) {
755
- session = mergeExtracted(session, gatheredData);
756
- console.log(`[Agent] Extracted data:`, gatheredData);
815
+ session = await this.updateExtracted(session, gatheredData);
816
+ logger.debug(`[Agent] Extracted data:`, gatheredData);
757
817
  }
758
818
  }
759
819
 
@@ -768,6 +828,13 @@ export class Agent<TContext = unknown> {
768
828
  .contextUpdate as Partial<TContext>
769
829
  );
770
830
  }
831
+ } else if (isRouteComplete && selectedRoute) {
832
+ // Route is complete - set state to END_STATE marker and return completion signal
833
+ session = enterState(session, END_STATE_ID, "Route completed");
834
+ message = "";
835
+ logger.debug(
836
+ `[Agent] Route ${selectedRoute.title} completed. Entered END_STATE state.`
837
+ );
771
838
  } else {
772
839
  // Fallback: No routes defined, generate a simple response
773
840
  const fallbackPrompt = new PromptComposer<TContext>()
@@ -811,15 +878,21 @@ export class Agent<TContext = unknown> {
811
878
  this.options.persistence?.autoSave !== false
812
879
  ) {
813
880
  await this.persistenceManager.saveSessionState(session.id, session);
814
- console.log(
881
+ logger.debug(
815
882
  `[Agent] Auto-saved session state to persistence: ${session.id}`
816
883
  );
817
884
  }
818
885
 
886
+ // Update current session if we have one
887
+ if (this.currentSession) {
888
+ this.currentSession = session;
889
+ }
890
+
819
891
  return {
820
892
  message,
821
893
  session, // Return updated session with route/state info
822
894
  toolCalls,
895
+ isRouteComplete: !isRouteComplete, // Indicates if the route has reached END_STATE with all data collected
823
896
  };
824
897
  }
825
898
 
@@ -907,4 +980,47 @@ export class Agent<TContext = unknown> {
907
980
  const allowedDomains = route.getDomains();
908
981
  return this.domainRegistry.getFiltered(allowedDomains);
909
982
  }
983
+
984
+ /**
985
+ * Set the current session for convenience methods
986
+ * @param session - Session state to use for subsequent calls
987
+ */
988
+ setCurrentSession(session: SessionState): void {
989
+ this.currentSession = session;
990
+ }
991
+
992
+ /**
993
+ * Get the current session (if set)
994
+ */
995
+ getCurrentSession(): SessionState | undefined {
996
+ return this.currentSession;
997
+ }
998
+
999
+ /**
1000
+ * Clear the current session
1001
+ */
1002
+ clearCurrentSession(): void {
1003
+ this.currentSession = undefined;
1004
+ }
1005
+
1006
+ /**
1007
+ * Get extracted data from current session
1008
+ * @param routeId - Optional route ID to get data for (uses current route if not provided)
1009
+ * @returns The extracted data from the current session
1010
+ */
1011
+ getExtractedData<TExtracted = unknown>(
1012
+ routeId?: string
1013
+ ): Partial<TExtracted> {
1014
+ if (!this.currentSession) {
1015
+ return {} as Partial<TExtracted>;
1016
+ }
1017
+ if (routeId) {
1018
+ return (
1019
+ (this.currentSession.extractedByRoute?.[
1020
+ routeId
1021
+ ] as Partial<TExtracted>) || ({} as Partial<TExtracted>)
1022
+ );
1023
+ }
1024
+ return (this.currentSession.extracted as Partial<TExtracted>) || {};
1025
+ }
910
1026
  }