@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
@@ -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.4",
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
@@ -388,13 +409,13 @@ export class Agent<TContext = unknown> {
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
@@ -464,7 +485,7 @@ export class Agent<TContext = unknown> {
464
485
  // Merge gathered data into session
465
486
  if (Object.keys(gatheredData).length > 0) {
466
487
  session = await this.updateExtracted(session, gatheredData);
467
- console.log(`[Agent] Extracted data:`, gatheredData);
488
+ logger.debug(`[Agent] Extracted data:`, gatheredData);
468
489
  }
469
490
  }
470
491
 
@@ -489,19 +510,39 @@ export class Agent<TContext = unknown> {
489
510
  this.options.persistence?.autoSave !== false
490
511
  ) {
491
512
  await this.persistenceManager.saveSessionState(session.id, session);
492
- console.log(
513
+ logger.debug(
493
514
  `[Agent] Auto-saved session state to persistence: ${session.id}`
494
515
  );
495
516
  }
496
517
 
518
+ // Update current session if we have one
519
+ if (chunk.done && this.currentSession) {
520
+ this.currentSession = session;
521
+ }
522
+
497
523
  yield {
498
524
  delta: chunk.delta,
499
525
  accumulated: chunk.accumulated,
500
526
  done: chunk.done,
501
527
  session, // Return updated session
502
528
  toolCalls,
529
+ isRouteComplete,
503
530
  };
504
531
  }
532
+ } else if (isRouteComplete && selectedRoute) {
533
+ // Route is complete - set state to END_STATE marker and yield completion signal
534
+ session = enterState(session, END_STATE_ID, "Route completed");
535
+ logger.debug(
536
+ `[Agent] Route ${selectedRoute.title} completed. Entered END_STATE state.`
537
+ );
538
+ yield {
539
+ delta: "",
540
+ accumulated: "",
541
+ done: true,
542
+ session,
543
+ toolCalls: undefined,
544
+ isRouteComplete: true,
545
+ };
505
546
  } else {
506
547
  // Fallback: No routes defined, stream a simple response
507
548
  const fallbackPrompt = new PromptComposer<TContext>()
@@ -536,12 +577,18 @@ export class Agent<TContext = unknown> {
536
577
  });
537
578
 
538
579
  for await (const chunk of stream) {
580
+ // Update current session if we have one
581
+ if (chunk.done && this.currentSession) {
582
+ this.currentSession = session;
583
+ }
584
+
539
585
  yield {
540
586
  delta: chunk.delta,
541
587
  accumulated: chunk.accumulated,
542
588
  done: chunk.done,
543
589
  session, // Return updated session
544
590
  toolCalls: undefined,
591
+ isRouteComplete: false,
545
592
  };
546
593
  }
547
594
  }
@@ -560,6 +607,7 @@ export class Agent<TContext = unknown> {
560
607
  message: string;
561
608
  session?: SessionState;
562
609
  toolCalls?: Array<{ toolName: string; arguments: Record<string, unknown> }>;
610
+ isRouteComplete?: boolean;
563
611
  }> {
564
612
  const { history, contextOverride, signal } = params;
565
613
 
@@ -579,8 +627,9 @@ export class Agent<TContext = unknown> {
579
627
  ...(contextOverride as Record<string, unknown>),
580
628
  } as TContext;
581
629
 
582
- // Initialize or get session
583
- let session = params.session || createSession();
630
+ // Initialize or get session (use current session if available)
631
+ let session =
632
+ params.session || this.currentSession || createSession<TContext>();
584
633
 
585
634
  // PHASE 1: TOOL EXECUTION - Execute tools if current state has toolState
586
635
  if (session.currentRoute && session.currentState) {
@@ -619,13 +668,13 @@ export class Agent<TContext = unknown> {
619
668
  session,
620
669
  result.extractedUpdate
621
670
  );
622
- console.log(
671
+ logger.debug(
623
672
  `[Agent] Tool updated extracted data:`,
624
673
  result.extractedUpdate
625
674
  );
626
675
  }
627
676
 
628
- console.log(
677
+ logger.debug(
629
678
  `[Agent] Executed tool: ${result.toolName} (success: ${result.success})`
630
679
  );
631
680
  }
@@ -637,6 +686,7 @@ export class Agent<TContext = unknown> {
637
686
  let selectedRoute: Route<TContext> | undefined;
638
687
  let responseDirectives: string[] | undefined;
639
688
  let selectedState: State<TContext> | undefined;
689
+ let isRouteComplete = false;
640
690
 
641
691
  if (this.routes.length > 0) {
642
692
  const orchestration = await this.routingEngine.decideRouteAndState({
@@ -658,6 +708,14 @@ export class Agent<TContext = unknown> {
658
708
  selectedState = orchestration.selectedState;
659
709
  responseDirectives = orchestration.responseDirectives;
660
710
  session = orchestration.session;
711
+ isRouteComplete = orchestration.isRouteComplete || false;
712
+
713
+ // Log if route is complete
714
+ if (isRouteComplete) {
715
+ logger.debug(
716
+ `[Agent] Route complete: all required data collected, END_STATE reached`
717
+ );
718
+ }
661
719
  }
662
720
 
663
721
  // PHASE 3: DETERMINE NEXT STATE - Use state from combined decision or get initial state
@@ -666,7 +724,7 @@ export class Agent<TContext = unknown> {
666
724
  | Array<{ toolName: string; arguments: Record<string, unknown> }>
667
725
  | undefined = undefined;
668
726
 
669
- if (selectedRoute) {
727
+ if (selectedRoute && !isRouteComplete) {
670
728
  let nextState: State<TContext>;
671
729
 
672
730
  // If we have a selected state from the combined routing decision, use it
@@ -681,13 +739,13 @@ export class Agent<TContext = unknown> {
681
739
  );
682
740
  if (candidates.length > 0) {
683
741
  nextState = candidates[0].state;
684
- console.log(
742
+ logger.debug(
685
743
  `[Agent] Using first valid state: ${nextState.id} for new route`
686
744
  );
687
745
  } else {
688
746
  // Fallback to initial state even if it should be skipped
689
747
  nextState = selectedRoute.initialState;
690
- console.warn(
748
+ logger.warn(
691
749
  `[Agent] No valid states found, using initial state: ${nextState.id}`
692
750
  );
693
751
  }
@@ -695,7 +753,7 @@ export class Agent<TContext = unknown> {
695
753
 
696
754
  // Update session with next state
697
755
  session = enterState(session, nextState.id, nextState.description);
698
- console.log(`[Agent] Entered state: ${nextState.id}`);
756
+ logger.debug(`[Agent] Entered state: ${nextState.id}`);
699
757
 
700
758
  // PHASE 4: RESPONSE GENERATION - Generate message using selected route and state
701
759
  // Get last user message
@@ -753,7 +811,7 @@ export class Agent<TContext = unknown> {
753
811
  // Merge gathered data into session
754
812
  if (Object.keys(gatheredData).length > 0) {
755
813
  session = mergeExtracted(session, gatheredData);
756
- console.log(`[Agent] Extracted data:`, gatheredData);
814
+ logger.debug(`[Agent] Extracted data:`, gatheredData);
757
815
  }
758
816
  }
759
817
 
@@ -768,6 +826,13 @@ export class Agent<TContext = unknown> {
768
826
  .contextUpdate as Partial<TContext>
769
827
  );
770
828
  }
829
+ } else if (isRouteComplete && selectedRoute) {
830
+ // Route is complete - set state to END_STATE marker and return completion signal
831
+ session = enterState(session, END_STATE_ID, "Route completed");
832
+ message = "";
833
+ logger.debug(
834
+ `[Agent] Route ${selectedRoute.title} completed. Entered END_STATE state.`
835
+ );
771
836
  } else {
772
837
  // Fallback: No routes defined, generate a simple response
773
838
  const fallbackPrompt = new PromptComposer<TContext>()
@@ -811,15 +876,21 @@ export class Agent<TContext = unknown> {
811
876
  this.options.persistence?.autoSave !== false
812
877
  ) {
813
878
  await this.persistenceManager.saveSessionState(session.id, session);
814
- console.log(
879
+ logger.debug(
815
880
  `[Agent] Auto-saved session state to persistence: ${session.id}`
816
881
  );
817
882
  }
818
883
 
884
+ // Update current session if we have one
885
+ if (this.currentSession) {
886
+ this.currentSession = session;
887
+ }
888
+
819
889
  return {
820
890
  message,
821
891
  session, // Return updated session with route/state info
822
892
  toolCalls,
893
+ isRouteComplete: !isRouteComplete, // Indicates if the route has reached END_STATE with all data collected
823
894
  };
824
895
  }
825
896
 
@@ -907,4 +978,47 @@ export class Agent<TContext = unknown> {
907
978
  const allowedDomains = route.getDomains();
908
979
  return this.domainRegistry.getFiltered(allowedDomains);
909
980
  }
981
+
982
+ /**
983
+ * Set the current session for convenience methods
984
+ * @param session - Session state to use for subsequent calls
985
+ */
986
+ setCurrentSession(session: SessionState): void {
987
+ this.currentSession = session;
988
+ }
989
+
990
+ /**
991
+ * Get the current session (if set)
992
+ */
993
+ getCurrentSession(): SessionState | undefined {
994
+ return this.currentSession;
995
+ }
996
+
997
+ /**
998
+ * Clear the current session
999
+ */
1000
+ clearCurrentSession(): void {
1001
+ this.currentSession = undefined;
1002
+ }
1003
+
1004
+ /**
1005
+ * Get extracted data from current session
1006
+ * @param routeId - Optional route ID to get data for (uses current route if not provided)
1007
+ * @returns The extracted data from the current session
1008
+ */
1009
+ getExtractedData<TExtracted = unknown>(
1010
+ routeId?: string
1011
+ ): Partial<TExtracted> {
1012
+ if (!this.currentSession) {
1013
+ return {} as Partial<TExtracted>;
1014
+ }
1015
+ if (routeId) {
1016
+ return (
1017
+ (this.currentSession.extractedByRoute[
1018
+ routeId
1019
+ ] as Partial<TExtracted>) || ({} as Partial<TExtracted>)
1020
+ );
1021
+ }
1022
+ return this.currentSession.extracted as Partial<TExtracted>;
1023
+ }
910
1024
  }