@falai/agent 0.9.0-alpha-2 → 0.9.2

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 (179) hide show
  1. package/README.md +42 -34
  2. package/dist/cjs/src/core/Agent.d.ts +48 -44
  3. package/dist/cjs/src/core/Agent.d.ts.map +1 -1
  4. package/dist/cjs/src/core/Agent.js +151 -1110
  5. package/dist/cjs/src/core/Agent.js.map +1 -1
  6. package/dist/cjs/src/core/ResponseModal.d.ts +211 -0
  7. package/dist/cjs/src/core/ResponseModal.d.ts.map +1 -0
  8. package/dist/cjs/src/core/ResponseModal.js +1394 -0
  9. package/dist/cjs/src/core/ResponseModal.js.map +1 -0
  10. package/dist/cjs/src/core/ResponsePipeline.d.ts +8 -4
  11. package/dist/cjs/src/core/ResponsePipeline.d.ts.map +1 -1
  12. package/dist/cjs/src/core/ResponsePipeline.js +48 -20
  13. package/dist/cjs/src/core/ResponsePipeline.js.map +1 -1
  14. package/dist/cjs/src/core/Route.d.ts +12 -5
  15. package/dist/cjs/src/core/Route.d.ts.map +1 -1
  16. package/dist/cjs/src/core/Route.js +26 -5
  17. package/dist/cjs/src/core/Route.js.map +1 -1
  18. package/dist/cjs/src/core/RoutingEngine.d.ts +5 -0
  19. package/dist/cjs/src/core/RoutingEngine.d.ts.map +1 -1
  20. package/dist/cjs/src/core/RoutingEngine.js +37 -25
  21. package/dist/cjs/src/core/RoutingEngine.js.map +1 -1
  22. package/dist/cjs/src/core/SessionManager.d.ts +9 -1
  23. package/dist/cjs/src/core/SessionManager.d.ts.map +1 -1
  24. package/dist/cjs/src/core/SessionManager.js +27 -5
  25. package/dist/cjs/src/core/SessionManager.js.map +1 -1
  26. package/dist/cjs/src/core/Step.d.ts +60 -7
  27. package/dist/cjs/src/core/Step.d.ts.map +1 -1
  28. package/dist/cjs/src/core/Step.js +151 -4
  29. package/dist/cjs/src/core/Step.js.map +1 -1
  30. package/dist/cjs/src/core/ToolManager.d.ts +234 -0
  31. package/dist/cjs/src/core/ToolManager.d.ts.map +1 -0
  32. package/dist/cjs/src/core/ToolManager.js +1117 -0
  33. package/dist/cjs/src/core/ToolManager.js.map +1 -0
  34. package/dist/cjs/src/index.d.ts +5 -4
  35. package/dist/cjs/src/index.d.ts.map +1 -1
  36. package/dist/cjs/src/index.js +11 -3
  37. package/dist/cjs/src/index.js.map +1 -1
  38. package/dist/cjs/src/types/agent.d.ts +2 -1
  39. package/dist/cjs/src/types/agent.d.ts.map +1 -1
  40. package/dist/cjs/src/types/ai.d.ts +1 -1
  41. package/dist/cjs/src/types/ai.d.ts.map +1 -1
  42. package/dist/cjs/src/types/index.d.ts +3 -2
  43. package/dist/cjs/src/types/index.d.ts.map +1 -1
  44. package/dist/cjs/src/types/index.js +3 -1
  45. package/dist/cjs/src/types/index.js.map +1 -1
  46. package/dist/cjs/src/types/route.d.ts +6 -4
  47. package/dist/cjs/src/types/route.d.ts.map +1 -1
  48. package/dist/cjs/src/types/tool.d.ts +84 -14
  49. package/dist/cjs/src/types/tool.d.ts.map +1 -1
  50. package/dist/cjs/src/types/tool.js +13 -0
  51. package/dist/cjs/src/types/tool.js.map +1 -1
  52. package/dist/cjs/src/utils/clone.d.ts.map +1 -1
  53. package/dist/cjs/src/utils/clone.js +0 -4
  54. package/dist/cjs/src/utils/clone.js.map +1 -1
  55. package/dist/cjs/src/utils/history.d.ts +30 -1
  56. package/dist/cjs/src/utils/history.d.ts.map +1 -1
  57. package/dist/cjs/src/utils/history.js +169 -23
  58. package/dist/cjs/src/utils/history.js.map +1 -1
  59. package/dist/cjs/src/utils/index.d.ts +1 -1
  60. package/dist/cjs/src/utils/index.d.ts.map +1 -1
  61. package/dist/cjs/src/utils/index.js +5 -1
  62. package/dist/cjs/src/utils/index.js.map +1 -1
  63. package/dist/src/core/Agent.d.ts +48 -44
  64. package/dist/src/core/Agent.d.ts.map +1 -1
  65. package/dist/src/core/Agent.js +152 -1111
  66. package/dist/src/core/Agent.js.map +1 -1
  67. package/dist/src/core/ResponseModal.d.ts +211 -0
  68. package/dist/src/core/ResponseModal.d.ts.map +1 -0
  69. package/dist/src/core/ResponseModal.js +1389 -0
  70. package/dist/src/core/ResponseModal.js.map +1 -0
  71. package/dist/src/core/ResponsePipeline.d.ts +8 -4
  72. package/dist/src/core/ResponsePipeline.d.ts.map +1 -1
  73. package/dist/src/core/ResponsePipeline.js +48 -20
  74. package/dist/src/core/ResponsePipeline.js.map +1 -1
  75. package/dist/src/core/Route.d.ts +12 -5
  76. package/dist/src/core/Route.d.ts.map +1 -1
  77. package/dist/src/core/Route.js +26 -5
  78. package/dist/src/core/Route.js.map +1 -1
  79. package/dist/src/core/RoutingEngine.d.ts +5 -0
  80. package/dist/src/core/RoutingEngine.d.ts.map +1 -1
  81. package/dist/src/core/RoutingEngine.js +37 -25
  82. package/dist/src/core/RoutingEngine.js.map +1 -1
  83. package/dist/src/core/SessionManager.d.ts +9 -1
  84. package/dist/src/core/SessionManager.d.ts.map +1 -1
  85. package/dist/src/core/SessionManager.js +27 -5
  86. package/dist/src/core/SessionManager.js.map +1 -1
  87. package/dist/src/core/Step.d.ts +60 -7
  88. package/dist/src/core/Step.d.ts.map +1 -1
  89. package/dist/src/core/Step.js +151 -4
  90. package/dist/src/core/Step.js.map +1 -1
  91. package/dist/src/core/ToolManager.d.ts +234 -0
  92. package/dist/src/core/ToolManager.d.ts.map +1 -0
  93. package/dist/src/core/ToolManager.js +1111 -0
  94. package/dist/src/core/ToolManager.js.map +1 -0
  95. package/dist/src/index.d.ts +5 -4
  96. package/dist/src/index.d.ts.map +1 -1
  97. package/dist/src/index.js +3 -2
  98. package/dist/src/index.js.map +1 -1
  99. package/dist/src/types/agent.d.ts +2 -1
  100. package/dist/src/types/agent.d.ts.map +1 -1
  101. package/dist/src/types/ai.d.ts +1 -1
  102. package/dist/src/types/ai.d.ts.map +1 -1
  103. package/dist/src/types/index.d.ts +3 -2
  104. package/dist/src/types/index.d.ts.map +1 -1
  105. package/dist/src/types/index.js +1 -0
  106. package/dist/src/types/index.js.map +1 -1
  107. package/dist/src/types/route.d.ts +6 -4
  108. package/dist/src/types/route.d.ts.map +1 -1
  109. package/dist/src/types/tool.d.ts +84 -14
  110. package/dist/src/types/tool.d.ts.map +1 -1
  111. package/dist/src/types/tool.js +12 -1
  112. package/dist/src/types/tool.js.map +1 -1
  113. package/dist/src/utils/clone.d.ts.map +1 -1
  114. package/dist/src/utils/clone.js +0 -4
  115. package/dist/src/utils/clone.js.map +1 -1
  116. package/dist/src/utils/history.d.ts +30 -1
  117. package/dist/src/utils/history.d.ts.map +1 -1
  118. package/dist/src/utils/history.js +165 -23
  119. package/dist/src/utils/history.js.map +1 -1
  120. package/dist/src/utils/index.d.ts +1 -1
  121. package/dist/src/utils/index.d.ts.map +1 -1
  122. package/dist/src/utils/index.js +1 -1
  123. package/dist/src/utils/index.js.map +1 -1
  124. package/docs/CONTRIBUTING.md +40 -0
  125. package/docs/README.md +14 -6
  126. package/docs/api/README.md +235 -45
  127. package/docs/api/overview.md +140 -33
  128. package/docs/core/agent/session-management.md +152 -5
  129. package/docs/core/ai-integration/response-processing.md +115 -4
  130. package/docs/core/conversation-flows/routes.md +130 -0
  131. package/docs/core/error-handling.md +638 -0
  132. package/docs/core/tools/tool-definition.md +684 -60
  133. package/docs/core/tools/tool-scoping.md +244 -53
  134. package/docs/guides/error-handling-patterns.md +578 -0
  135. package/docs/guides/getting-started/README.md +139 -28
  136. package/docs/guides/migration/README.md +72 -0
  137. package/docs/guides/migration/response-modal-refactor.md +518 -0
  138. package/examples/advanced-patterns/knowledge-based-agent.ts +6 -6
  139. package/examples/advanced-patterns/persistent-onboarding.ts +30 -43
  140. package/examples/advanced-patterns/streaming-responses.ts +169 -96
  141. package/examples/ai-providers/anthropic-integration.ts +9 -5
  142. package/examples/ai-providers/openai-integration.ts +11 -7
  143. package/examples/core-concepts/basic-agent.ts +106 -67
  144. package/examples/core-concepts/modern-streaming-api.ts +309 -0
  145. package/examples/core-concepts/schema-driven-extraction.ts +10 -7
  146. package/examples/core-concepts/session-management.ts +71 -18
  147. package/examples/integrations/healthcare-integration.ts +15 -29
  148. package/examples/integrations/server-session-management.ts +3 -3
  149. package/examples/persistence/memory-sessions.ts +3 -3
  150. package/examples/tools/basic-tools.ts +293 -89
  151. package/examples/tools/data-enrichment-tools.ts +185 -75
  152. package/package.json +1 -1
  153. package/src/core/Agent.ts +190 -1529
  154. package/src/core/ResponseModal.ts +1798 -0
  155. package/src/core/ResponsePipeline.ts +83 -57
  156. package/src/core/Route.ts +39 -12
  157. package/src/core/RoutingEngine.ts +46 -42
  158. package/src/core/SessionManager.ts +39 -7
  159. package/src/core/Step.ts +198 -20
  160. package/src/core/ToolManager.ts +1394 -0
  161. package/src/index.ts +19 -3
  162. package/src/types/agent.ts +2 -1
  163. package/src/types/ai.ts +1 -1
  164. package/src/types/index.ts +13 -2
  165. package/src/types/route.ts +6 -4
  166. package/src/types/tool.ts +116 -25
  167. package/src/utils/clone.ts +6 -8
  168. package/src/utils/history.ts +190 -27
  169. package/src/utils/index.ts +4 -0
  170. package/dist/cjs/src/core/ToolExecutor.d.ts +0 -45
  171. package/dist/cjs/src/core/ToolExecutor.d.ts.map +0 -1
  172. package/dist/cjs/src/core/ToolExecutor.js +0 -84
  173. package/dist/cjs/src/core/ToolExecutor.js.map +0 -1
  174. package/dist/src/core/ToolExecutor.d.ts +0 -45
  175. package/dist/src/core/ToolExecutor.d.ts.map +0 -1
  176. package/dist/src/core/ToolExecutor.js +0 -80
  177. package/dist/src/core/ToolExecutor.js.map +0 -1
  178. package/docs/core/tools/tool-execution.md +0 -815
  179. package/src/core/ToolExecutor.ts +0 -126
@@ -22,7 +22,7 @@ import {
22
22
  import { Route } from "../core/Route";
23
23
  import { Step } from "../core/Step";
24
24
  import { RoutingEngine } from "../core/RoutingEngine";
25
- import { ToolExecutor } from "../core/ToolExecutor";
25
+ import type { ToolManager } from "../core/ToolManager";
26
26
  import { END_ROUTE_ID } from "../constants";
27
27
 
28
28
  export interface ResponsePreparationResult<TContext, TData = unknown> {
@@ -42,8 +42,8 @@ export interface RoutingResult<TContext, TData = unknown> {
42
42
  export interface ToolExecutionResult<TData = unknown> {
43
43
  session: SessionState<TData>;
44
44
  toolCalls:
45
- | Array<{ toolName: string; arguments: Record<string, unknown> }>
46
- | undefined;
45
+ | Array<{ toolName: string; arguments: Record<string, unknown> }>
46
+ | undefined;
47
47
  }
48
48
 
49
49
  export interface DataCollectionResult<TData = unknown> {
@@ -57,8 +57,8 @@ export interface DataCollectionResult<TData = unknown> {
57
57
  export class ResponsePipeline<TContext = unknown, TData = unknown> {
58
58
  constructor(
59
59
  private readonly options: AgentOptions<TContext, TData>,
60
- private readonly routes: Route<TContext, TData>[],
61
- private readonly tools: Tool<TContext, TData, unknown[], unknown>[],
60
+ private readonly getRoutes: () => Route<TContext, TData>[],
61
+ private readonly tools: Tool<TContext, TData>[],
62
62
  private readonly routingEngine: RoutingEngine<TContext, TData>,
63
63
  private readonly updateContext: (
64
64
  updates: Partial<TContext>
@@ -69,8 +69,9 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
69
69
  ) => Promise<SessionState<TData>>,
70
70
  private readonly updateCollectedData?: (
71
71
  updates: Partial<TData>
72
- ) => Promise<void>
73
- ) {}
72
+ ) => Promise<void>,
73
+ private readonly toolManager?: ToolManager<TContext, TData>
74
+ ) { }
74
75
 
75
76
  /**
76
77
  * Prepare context and session for response generation
@@ -125,9 +126,12 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
125
126
  let completedRoutes: Route<TContext, TData>[] = [];
126
127
  let targetSession = session;
127
128
 
129
+ // Get routes early since we need them for pending transitions
130
+ const routes = this.getRoutes();
131
+
128
132
  // Check for pending transition from previous route completion
129
133
  if (targetSession.pendingTransition) {
130
- const targetRoute = this.routes.find(
134
+ const targetRoute = routes.find(
131
135
  (r) => r.id === targetSession.pendingTransition?.targetRouteId
132
136
  );
133
137
 
@@ -166,11 +170,11 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
166
170
  };
167
171
  }
168
172
  }
169
-
173
+
170
174
  // If no pending transition or transition handled, do normal routing
171
- if (this.routes.length > 0 && !selectedRoute) {
175
+ if (routes.length > 0 && !selectedRoute) {
172
176
  const orchestration = await this.routingEngine.decideRouteAndStep({
173
- routes: this.routes,
177
+ routes: routes,
174
178
  session: targetSession,
175
179
  history,
176
180
  agentOptions: this.options,
@@ -281,8 +285,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
281
285
  }
282
286
 
283
287
  logger.debug(
284
- `[ResponseHandler] Executing ${toolCalls.length} ${
285
- isStreaming ? "streaming " : ""
288
+ `[ResponseHandler] Executing ${toolCalls.length} ${isStreaming ? "streaming " : ""
286
289
  }tool calls`
287
290
  );
288
291
 
@@ -299,15 +302,21 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
299
302
  continue;
300
303
  }
301
304
 
302
- const toolExecutor = new ToolExecutor<TContext, TData>();
303
- const result = await toolExecutor.executeTool({
304
- tool,
305
- context,
306
- updateContext: this.updateContext,
307
- updateData: this.updateCollectedData || (async () => {}),
308
- history,
309
- data: updatedSession.data,
310
- });
305
+ // Use ToolManager for unified tool execution
306
+ let result;
307
+ if (this.toolManager) {
308
+ result = await this.toolManager.executeTool({
309
+ tool,
310
+ context,
311
+ updateContext: this.updateContext,
312
+ updateData: this.updateCollectedData || (async () => { }),
313
+ history,
314
+ data: updatedSession.data,
315
+ });
316
+ } else {
317
+ // Fallback: execute tool directly if ToolManager not available
318
+ throw new Error(`ToolManager not available for tool execution: ${toolCall.toolName}`);
319
+ }
311
320
 
312
321
  executedToolCalls.push(toolCall);
313
322
 
@@ -323,16 +332,14 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
323
332
  result.dataUpdate as Partial<TData>
324
333
  );
325
334
  logger.debug(
326
- `[ResponseHandler] ${
327
- isStreaming ? "Streaming " : ""
335
+ `[ResponseHandler] ${isStreaming ? "Streaming " : ""
328
336
  }Tool updated collected data:`,
329
337
  result.dataUpdate
330
338
  );
331
339
  }
332
340
 
333
341
  logger.debug(
334
- `[ResponseHandler] Executed ${isStreaming ? "streaming " : ""}tool: ${
335
- result.toolName
342
+ `[ResponseHandler] Executed ${isStreaming ? "streaming " : ""}tool: ${String(tool.id || tool.name || 'unknown')
336
343
  } (success: ${result.success})`
337
344
  );
338
345
  }
@@ -348,8 +355,8 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
348
355
  */
349
356
  async executeToolLoop(params: {
350
357
  initialToolCalls:
351
- | Array<{ toolName: string; arguments: Record<string, unknown> }>
352
- | undefined;
358
+ | Array<{ toolName: string; arguments: Record<string, unknown> }>
359
+ | undefined;
353
360
  selectedRoute?: Route<TContext, TData>;
354
361
  nextStep: Step<TContext, TData>;
355
362
  responsePrompt: string;
@@ -380,8 +387,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
380
387
  while (hasToolCalls && toolLoopCount < MAX_TOOL_LOOPS) {
381
388
  toolLoopCount++;
382
389
  logger.debug(
383
- `[ResponseHandler] Starting ${
384
- isStreaming ? "streaming " : ""
390
+ `[ResponseHandler] Starting ${isStreaming ? "streaming " : ""
385
391
  }tool loop ${toolLoopCount}/${MAX_TOOL_LOOPS}`
386
392
  );
387
393
 
@@ -430,8 +436,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
430
436
 
431
437
  if (hasToolCalls) {
432
438
  logger.debug(
433
- `[ResponseHandler] Follow-up call produced ${
434
- followUpToolCalls!.length
439
+ `[ResponseHandler] Follow-up call produced ${followUpToolCalls!.length
435
440
  } additional tool calls`
436
441
  );
437
442
 
@@ -449,8 +454,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
449
454
  currentToolCalls = followUpToolCalls;
450
455
  } else {
451
456
  logger.debug(
452
- `[ResponseHandler] ${
453
- isStreaming ? "Streaming " : ""
457
+ `[ResponseHandler] ${isStreaming ? "Streaming " : ""
454
458
  }Tool loop completed after ${toolLoopCount} iterations`
455
459
  );
456
460
  // Update final toolCalls from follow-up result if no more tools
@@ -461,8 +465,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
461
465
 
462
466
  if (toolLoopCount >= MAX_TOOL_LOOPS) {
463
467
  logger.warn(
464
- `[ResponseHandler] ${
465
- isStreaming ? "Streaming " : ""
468
+ `[ResponseHandler] ${isStreaming ? "Streaming " : ""
466
469
  }Tool loop limit reached (${MAX_TOOL_LOOPS}), stopping`
467
470
  );
468
471
  }
@@ -505,7 +508,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
505
508
  if (this.updateCollectedData) {
506
509
  await this.updateCollectedData(collectedData);
507
510
  }
508
-
511
+
509
512
  // Update session with validated data
510
513
  updatedSession = await this.updateData(session, collectedData);
511
514
  logger.debug(`[ResponseHandler] Collected data:`, collectedData);
@@ -554,7 +557,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
554
557
 
555
558
  if (transitionConfig) {
556
559
  // Find target route by ID or title
557
- const targetRoute = this.routes.find(
560
+ const targetRoute = this.getRoutes().find(
558
561
  (r) =>
559
562
  r.id === transitionConfig.nextStep ||
560
563
  r.title === transitionConfig.nextStep
@@ -602,12 +605,21 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
602
605
  }
603
606
 
604
607
  /**
605
- * Find an available tool by name for the given route
608
+ * Find an available tool by name for the given route using ToolManager
609
+ * Delegates to ToolManager for unified tool resolution
606
610
  */
607
611
  private findAvailableTool(
608
612
  toolName: string,
609
613
  route?: Route<TContext, TData>
610
- ): Tool<TContext, TData, unknown[], unknown> | undefined {
614
+ ): Tool<TContext, TData> | undefined {
615
+ // Use ToolManager for unified tool resolution if available
616
+ if (this.toolManager) {
617
+ return this.toolManager.find(toolName, undefined, undefined, route);
618
+ }
619
+
620
+ // Fallback to legacy resolution if ToolManager not available
621
+ logger.warn(`[ResponsePipeline] ToolManager not available, using legacy tool resolution for: ${toolName}`);
622
+
611
623
  // Check route-level tools first (if route provided)
612
624
  if (route) {
613
625
  const routeTool = route
@@ -623,15 +635,29 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
623
635
  }
624
636
 
625
637
  /**
626
- * Collect all available tools for the given route and step context
638
+ * Collect all available tools for the given route and step context using ToolManager
639
+ * Delegates to ToolManager for unified tool resolution and deduplication
627
640
  */
628
641
  private collectAvailableTools(
629
642
  route?: Route<TContext, TData>,
630
643
  step?: Step<TContext, TData>
631
644
  ): Array<{ id: string; description?: string; parameters?: unknown }> {
645
+ // Use ToolManager for unified tool collection if available
646
+ if (this.toolManager) {
647
+ const availableTools = this.toolManager.getAvailable(undefined, step, route);
648
+ return availableTools.map((tool) => ({
649
+ id: tool.id,
650
+ description: tool.description,
651
+ parameters: tool.parameters,
652
+ }));
653
+ }
654
+
655
+ // Fallback to legacy collection logic if ToolManager not available
656
+ logger.warn(`[ResponsePipeline] ToolManager not available, using legacy tool collection`);
657
+
632
658
  const availableTools = new Map<
633
659
  string,
634
- Tool<TContext, TData, unknown[], unknown>
660
+ Tool<TContext, TData>
635
661
  >();
636
662
 
637
663
  // Add agent-level tools
@@ -649,7 +675,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
649
675
  // Filter by step-level allowed tools if specified
650
676
  if (step?.tools) {
651
677
  const allowedToolIds = new Set<string>();
652
- const stepTools: Tool<TContext, TData, unknown[], unknown>[] = [];
678
+ const stepTools: Tool<TContext, TData>[] = [];
653
679
 
654
680
  for (const toolRef of step.tools) {
655
681
  if (typeof toolRef === "string") {
@@ -668,9 +694,9 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
668
694
  if (allowedToolIds.size > 0) {
669
695
  const filteredTools = new Map<
670
696
  string,
671
- Tool<TContext, TData, unknown[], unknown>
697
+ Tool<TContext, TData>
672
698
  >();
673
- for (const toolId of allowedToolIds) {
699
+ for (const toolId of Array.from(allowedToolIds)) {
674
700
  const tool = availableTools.get(toolId);
675
701
  if (tool) {
676
702
  filteredTools.set(toolId, tool);
@@ -747,35 +773,35 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
747
773
  }>;
748
774
  }> {
749
775
  const { routes, session, context } = params;
750
-
776
+
751
777
  // Evaluate all routes for completion
752
778
  const completedRoutes: Route<TContext, TData>[] = [];
753
779
  const pendingTransitions: Array<{
754
780
  route: Route<TContext, TData>;
755
781
  transitionConfig: RouteTransitionConfig<TContext, TData>;
756
782
  }> = [];
757
-
783
+
758
784
  for (const route of routes) {
759
785
  if (route.isComplete(session.data || {})) {
760
786
  completedRoutes.push(route);
761
-
787
+
762
788
  // Check for onComplete transitions
763
789
  const transitionConfig = await route.evaluateOnComplete(
764
790
  { data: session.data },
765
791
  context
766
792
  );
767
-
793
+
768
794
  if (transitionConfig) {
769
795
  pendingTransitions.push({ route, transitionConfig });
770
796
  }
771
-
797
+
772
798
  logger.debug(
773
799
  `[ResponsePipeline] Route completed: ${route.title} ` +
774
800
  `(${Math.round(route.getCompletionProgress(session.data || {}) * 100)}%)`
775
801
  );
776
802
  }
777
803
  }
778
-
804
+
779
805
  // Log completion status for all routes
780
806
  if (completedRoutes.length > 0) {
781
807
  logger.debug(
@@ -783,7 +809,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
783
809
  `${completedRoutes.length}/${routes.length} routes complete`
784
810
  );
785
811
  }
786
-
812
+
787
813
  return {
788
814
  session,
789
815
  completedRoutes,
@@ -801,15 +827,15 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
801
827
  routes: Route<TContext, TData>[];
802
828
  }): Promise<SessionState<TData>> {
803
829
  const { session, dataUpdate, routes } = params;
804
-
830
+
805
831
  // Update session data
806
832
  const updatedSession = await this.updateData(session, dataUpdate);
807
-
833
+
808
834
  // Update agent-level data if handler is available
809
835
  if (this.updateCollectedData) {
810
836
  await this.updateCollectedData(dataUpdate);
811
837
  }
812
-
838
+
813
839
  // Evaluate route completions after data update
814
840
  const completionResults = await this.handleCrossRouteCompletion({
815
841
  routes,
@@ -817,14 +843,14 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
817
843
  context: this.context!,
818
844
  history: [],
819
845
  });
820
-
846
+
821
847
  // Log any newly completed routes
822
848
  if (completionResults.completedRoutes.length > 0) {
823
849
  logger.debug(
824
850
  `[ResponsePipeline] Data update resulted in ${completionResults.completedRoutes.length} completed routes`
825
851
  );
826
852
  }
827
-
853
+
828
854
  return completionResults.session;
829
855
  }
830
856
  }
package/src/core/Route.ts CHANGED
@@ -18,6 +18,7 @@ import type {
18
18
  } from "../types";
19
19
 
20
20
  import { Step } from "./Step";
21
+ import { Agent } from './Agent'
21
22
  import { generateRouteId } from "../utils/id";
22
23
  import { END_ROUTE } from "../constants";
23
24
 
@@ -50,10 +51,13 @@ export class Route<TContext = unknown, TData = unknown> {
50
51
  public routingExtrasSchema?: StructuredSchema;
51
52
  public guidelines: Guideline<TContext>[] = [];
52
53
  public terms: Term<TContext>[] = [];
53
- public tools: Tool<TContext, TData, unknown[], unknown>[] = [];
54
+ public tools: Tool<TContext, TData>[] = [];
54
55
  public knowledgeBase: Record<string, unknown> = {};
55
56
 
56
- constructor(options: RouteOptions<TContext, TData>) {
57
+ // Reference to parent agent for ToolManager access
58
+ private parentAgent?: Agent<TContext, TData>;
59
+
60
+ constructor(options: RouteOptions<TContext, TData>, parentAgent?: Agent<TContext, TData>) {
57
61
  // Use provided ID or generate a deterministic one from the title
58
62
  this.id = options.id || generateRouteId(options.title);
59
63
  this.title = options.title;
@@ -64,6 +68,9 @@ export class Route<TContext = unknown, TData = unknown> {
64
68
  this.rules = options.rules || [];
65
69
  this.prohibitions = options.prohibitions || [];
66
70
 
71
+ // Store reference to parent agent for ToolManager access
72
+ this.parentAgent = parentAgent;
73
+
67
74
  // Handle initial step logic
68
75
  let initialStepOptions = options.initialStep;
69
76
  let stepsToChain: StepOptions<TContext, TData>[] = [];
@@ -79,7 +86,7 @@ export class Route<TContext = unknown, TData = unknown> {
79
86
  }
80
87
  }
81
88
 
82
- this.initialStep = new Step<TContext, TData>(this.id, initialStepOptions);
89
+ this.initialStep = new Step<TContext, TData>(this.id, initialStepOptions, this.parentAgent);
83
90
 
84
91
  // Store endStep spec (will be used when route completes)
85
92
  this.endStepSpec = options.endStep || {
@@ -133,14 +140,13 @@ export class Route<TContext = unknown, TData = unknown> {
133
140
  private buildSequentialSteps(
134
141
  steps: Array<StepOptions<TContext, TData> | typeof END_ROUTE>
135
142
  ): void {
136
- let currentStep: StepResult<TContext, TData> =
137
- this.initialStep.asStepResult();
143
+ let currentStepResult: StepResult<TContext, TData> = this.initialStep.asStepResult();
138
144
 
139
145
  for (const step of steps) {
140
146
  if (step === END_ROUTE) {
141
- currentStep.nextStep({ step: END_ROUTE });
147
+ currentStepResult.nextStep({ step: END_ROUTE });
142
148
  } else {
143
- currentStep = currentStep.nextStep(step);
149
+ currentStepResult = currentStepResult.nextStep(step);
144
150
  }
145
151
  }
146
152
  }
@@ -168,7 +174,12 @@ export class Route<TContext = unknown, TData = unknown> {
168
174
  /**
169
175
  * Register a tool for this route
170
176
  */
171
- createTool(tool: Tool<TContext, TData, unknown[], unknown>): this {
177
+ createTool(tool: Tool<TContext, TData>): this {
178
+ // Validate tool before adding
179
+ if (!tool || !tool.id || !tool.handler) {
180
+ throw new Error(`Invalid tool: must have id and handler properties`);
181
+ }
182
+
172
183
  this.tools.push(tool);
173
184
  return this;
174
185
  }
@@ -176,11 +187,28 @@ export class Route<TContext = unknown, TData = unknown> {
176
187
  /**
177
188
  * Register multiple tools for this route
178
189
  */
179
- registerTools(tools: Tool<TContext, TData, unknown[], unknown>[]): this {
190
+ registerTools(tools: Tool<TContext, TData>[]): this {
180
191
  tools.forEach((tool) => this.createTool(tool));
181
192
  return this;
182
193
  }
183
194
 
195
+ /**
196
+ * Add a tool to this route using the ToolManager API
197
+ * Creates and adds the tool to route scope in one operation
198
+ */
199
+ addTool(
200
+ tool: Tool<TContext, TData>
201
+ ): this {
202
+ if (this.parentAgent && this.parentAgent.tool) {
203
+ // Use ToolManager to add to route scope - no casting needed with unified interface
204
+ this.parentAgent.tool.addToRoute(this, tool);
205
+ } else {
206
+ // Fallback: add tool directly to route tools
207
+ this.createTool(tool);
208
+ }
209
+ return this;
210
+ }
211
+
184
212
  /**
185
213
  * Get all guidelines for this route
186
214
  */
@@ -198,7 +226,7 @@ export class Route<TContext = unknown, TData = unknown> {
198
226
  /**
199
227
  * Get all tools for this route
200
228
  */
201
- getTools(): Tool<TContext, TData, unknown[], unknown>[] {
229
+ getTools(): Tool<TContext, TData>[] {
202
230
  return [...this.tools];
203
231
  }
204
232
 
@@ -311,8 +339,7 @@ export class Route<TContext = unknown, TData = unknown> {
311
339
  const transitions = step.getTransitions();
312
340
  for (const transition of transitions) {
313
341
  lines.push(
314
- ` -> ${transition.id}${
315
- transition.description ? `: ${transition.description}` : ""
342
+ ` -> ${transition.id}${transition.description ? `: ${transition.description}` : ""
316
343
  }`
317
344
  );
318
345
  }
@@ -70,6 +70,29 @@ export interface BuildRoutingPromptParams<TContext = unknown, TData = unknown> {
70
70
  export class RoutingEngine<TContext = unknown, TData = unknown> {
71
71
  constructor(private readonly options?: RoutingEngineOptions) { }
72
72
 
73
+ /**
74
+ * Enter a route if not already in it, merging initial data
75
+ * @private
76
+ */
77
+ private enterRouteIfNeeded(
78
+ session: SessionState<TData>,
79
+ route: Route<TContext, TData>
80
+ ): SessionState<TData> {
81
+ if (!session.currentRoute || session.currentRoute.id !== route.id) {
82
+ let updatedSession = enterRoute(session, route.id, route.title);
83
+ if (route.initialData) {
84
+ updatedSession = mergeCollected(updatedSession, route.initialData);
85
+ logger.debug(
86
+ `[RoutingEngine] Merged initial data for route ${route.title}:`,
87
+ route.initialData
88
+ );
89
+ }
90
+ logger.debug(`[RoutingEngine] Entered route: ${route.title}`);
91
+ return updatedSession;
92
+ }
93
+ return session;
94
+ }
95
+
73
96
  /**
74
97
  * Optimized decision for single-route scenarios
75
98
  * Skips route scoring and only does step selection
@@ -94,26 +117,13 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
94
117
  const { route, session, history, agentOptions, provider, context, signal } =
95
118
  params;
96
119
 
97
- let updatedSession = session;
98
120
  const selectedRoute = route;
99
121
 
100
- // Check if this single route is complete
101
- const completedRoutes = route.isComplete(session.data || {}) ? [route] : [];
122
+ // Enter route if not already in it (this may merge initial data)
123
+ const updatedSession = this.enterRouteIfNeeded(session, route);
102
124
 
103
- // Enter route if not already in it
104
- if (!session.currentRoute || session.currentRoute.id !== route.id) {
105
- updatedSession = enterRoute(session, route.id, route.title);
106
- if (route.initialData) {
107
- updatedSession = mergeCollected(updatedSession, route.initialData);
108
- logger.debug(
109
- `[RoutingEngine] Single-route: Merged initial data:`,
110
- route.initialData
111
- );
112
- }
113
- logger.debug(
114
- `[RoutingEngine] Single-route: Entered route: ${route.title}`
115
- );
116
- }
125
+ // Check if this single route is complete (use updated session data)
126
+ const completedRoutes = route.isComplete(updatedSession.data || {}) ? [route] : [];
117
127
 
118
128
  // Get candidate steps
119
129
  const currentStep = updatedSession.currentStep
@@ -130,9 +140,11 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
130
140
  return { selectedRoute, session: updatedSession };
131
141
  }
132
142
 
133
- // If only one candidate, check if route is complete
143
+ // If only one candidate, no need for AI selection
134
144
  if (candidates.length === 1) {
135
- const isRouteComplete = candidates[0].isRouteComplete;
145
+ const candidate = candidates[0];
146
+ const isRouteComplete = candidate.isRouteComplete;
147
+
136
148
  if (isRouteComplete) {
137
149
  logger.debug(
138
150
  `[RoutingEngine] Single-route: Route complete - all data collected, END_ROUTE reached`
@@ -147,11 +159,11 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
147
159
  };
148
160
  } else {
149
161
  logger.debug(
150
- `[RoutingEngine] Single-route: Only one valid step: ${candidates[0].step.id}`
162
+ `[RoutingEngine] Single-route: Only one valid step: ${candidate.step.id}`
151
163
  );
152
164
  return {
153
165
  selectedRoute,
154
- selectedStep: candidates[0].step,
166
+ selectedStep: candidate.step,
155
167
  session: updatedSession,
156
168
  isRouteComplete: false,
157
169
  completedRoutes,
@@ -159,6 +171,18 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
159
171
  }
160
172
  }
161
173
 
174
+ // No candidates means route is likely complete or has no valid next steps
175
+ if (candidates.length === 0) {
176
+ logger.debug(`[RoutingEngine] Single-route: No valid steps found, route may be complete`);
177
+ return {
178
+ selectedRoute,
179
+ selectedStep: undefined,
180
+ session: updatedSession,
181
+ isRouteComplete: true, // Assume complete if no valid steps
182
+ completedRoutes,
183
+ };
184
+ }
185
+
162
186
  // Multiple candidates - use AI to select best step
163
187
  const lastUserMessage = getLastMessageFromHistory(history);
164
188
  const stepPrompt = await this.buildStepSelectionPrompt({
@@ -573,27 +597,7 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
573
597
 
574
598
  if (selectedRoute) {
575
599
  logger.debug(`[RoutingEngine] Selected route: ${selectedRoute.title}`);
576
- if (
577
- !session.currentRoute ||
578
- session.currentRoute.id !== selectedRoute.id
579
- ) {
580
- updatedSession = enterRoute(
581
- session,
582
- selectedRoute.id,
583
- selectedRoute.title
584
- );
585
- if (selectedRoute.initialData) {
586
- updatedSession = mergeCollected(
587
- updatedSession,
588
- selectedRoute.initialData
589
- );
590
- logger.debug(
591
- `[RoutingEngine] Merged initial data:`,
592
- selectedRoute.initialData
593
- );
594
- }
595
- logger.debug(`[RoutingEngine] Entered route: ${selectedRoute.title}`);
596
- }
600
+ updatedSession = this.enterRouteIfNeeded(updatedSession, selectedRoute);
597
601
  }
598
602
  }
599
603