@falai/agent 0.9.0-alpha-1 → 0.9.0-alpha-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 (166) hide show
  1. package/README.md +34 -22
  2. package/dist/cjs/src/core/Agent.d.ts +52 -24
  3. package/dist/cjs/src/core/Agent.d.ts.map +1 -1
  4. package/dist/cjs/src/core/Agent.js +266 -39
  5. package/dist/cjs/src/core/Agent.js.map +1 -1
  6. package/dist/cjs/src/core/PersistenceManager.d.ts.map +1 -1
  7. package/dist/cjs/src/core/PersistenceManager.js +48 -25
  8. package/dist/cjs/src/core/PersistenceManager.js.map +1 -1
  9. package/dist/cjs/src/core/PromptComposer.d.ts +1 -1
  10. package/dist/cjs/src/core/PromptComposer.d.ts.map +1 -1
  11. package/dist/cjs/src/core/PromptComposer.js.map +1 -1
  12. package/dist/cjs/src/core/ResponseEngine.d.ts +13 -12
  13. package/dist/cjs/src/core/ResponseEngine.d.ts.map +1 -1
  14. package/dist/cjs/src/core/ResponseEngine.js +4 -4
  15. package/dist/cjs/src/core/ResponseEngine.js.map +1 -1
  16. package/dist/cjs/src/core/ResponsePipeline.d.ts +66 -38
  17. package/dist/cjs/src/core/ResponsePipeline.d.ts.map +1 -1
  18. package/dist/cjs/src/core/ResponsePipeline.js +71 -3
  19. package/dist/cjs/src/core/ResponsePipeline.js.map +1 -1
  20. package/dist/cjs/src/core/Route.d.ts +24 -5
  21. package/dist/cjs/src/core/Route.d.ts.map +1 -1
  22. package/dist/cjs/src/core/Route.js +45 -1
  23. package/dist/cjs/src/core/Route.js.map +1 -1
  24. package/dist/cjs/src/core/RoutingEngine.d.ts +31 -6
  25. package/dist/cjs/src/core/RoutingEngine.d.ts.map +1 -1
  26. package/dist/cjs/src/core/RoutingEngine.js +113 -9
  27. package/dist/cjs/src/core/RoutingEngine.js.map +1 -1
  28. package/dist/cjs/src/core/SessionManager.d.ts +14 -4
  29. package/dist/cjs/src/core/SessionManager.d.ts.map +1 -1
  30. package/dist/cjs/src/core/SessionManager.js +25 -5
  31. package/dist/cjs/src/core/SessionManager.js.map +1 -1
  32. package/dist/cjs/src/core/Step.d.ts +10 -10
  33. package/dist/cjs/src/core/Step.d.ts.map +1 -1
  34. package/dist/cjs/src/core/Step.js.map +1 -1
  35. package/dist/cjs/src/core/ToolExecutor.d.ts +4 -2
  36. package/dist/cjs/src/core/ToolExecutor.d.ts.map +1 -1
  37. package/dist/cjs/src/core/ToolExecutor.js +13 -3
  38. package/dist/cjs/src/core/ToolExecutor.js.map +1 -1
  39. package/dist/cjs/src/types/agent.d.ts +41 -21
  40. package/dist/cjs/src/types/agent.d.ts.map +1 -1
  41. package/dist/cjs/src/types/agent.js.map +1 -1
  42. package/dist/cjs/src/types/index.d.ts +1 -1
  43. package/dist/cjs/src/types/index.d.ts.map +1 -1
  44. package/dist/cjs/src/types/index.js.map +1 -1
  45. package/dist/cjs/src/types/persistence.d.ts +0 -1
  46. package/dist/cjs/src/types/persistence.d.ts.map +1 -1
  47. package/dist/cjs/src/types/route.d.ts +22 -16
  48. package/dist/cjs/src/types/route.d.ts.map +1 -1
  49. package/dist/cjs/src/types/session.d.ts +6 -11
  50. package/dist/cjs/src/types/session.d.ts.map +1 -1
  51. package/dist/cjs/src/types/tool.d.ts +12 -6
  52. package/dist/cjs/src/types/tool.d.ts.map +1 -1
  53. package/dist/cjs/src/utils/session.d.ts +2 -2
  54. package/dist/cjs/src/utils/session.d.ts.map +1 -1
  55. package/dist/cjs/src/utils/session.js +6 -26
  56. package/dist/cjs/src/utils/session.js.map +1 -1
  57. package/dist/src/core/Agent.d.ts +52 -24
  58. package/dist/src/core/Agent.d.ts.map +1 -1
  59. package/dist/src/core/Agent.js +266 -39
  60. package/dist/src/core/Agent.js.map +1 -1
  61. package/dist/src/core/PersistenceManager.d.ts.map +1 -1
  62. package/dist/src/core/PersistenceManager.js +48 -25
  63. package/dist/src/core/PersistenceManager.js.map +1 -1
  64. package/dist/src/core/PromptComposer.d.ts +1 -1
  65. package/dist/src/core/PromptComposer.d.ts.map +1 -1
  66. package/dist/src/core/PromptComposer.js.map +1 -1
  67. package/dist/src/core/ResponseEngine.d.ts +13 -12
  68. package/dist/src/core/ResponseEngine.d.ts.map +1 -1
  69. package/dist/src/core/ResponseEngine.js +4 -4
  70. package/dist/src/core/ResponseEngine.js.map +1 -1
  71. package/dist/src/core/ResponsePipeline.d.ts +66 -38
  72. package/dist/src/core/ResponsePipeline.d.ts.map +1 -1
  73. package/dist/src/core/ResponsePipeline.js +71 -3
  74. package/dist/src/core/ResponsePipeline.js.map +1 -1
  75. package/dist/src/core/Route.d.ts +24 -5
  76. package/dist/src/core/Route.d.ts.map +1 -1
  77. package/dist/src/core/Route.js +45 -1
  78. package/dist/src/core/Route.js.map +1 -1
  79. package/dist/src/core/RoutingEngine.d.ts +31 -6
  80. package/dist/src/core/RoutingEngine.d.ts.map +1 -1
  81. package/dist/src/core/RoutingEngine.js +113 -9
  82. package/dist/src/core/RoutingEngine.js.map +1 -1
  83. package/dist/src/core/SessionManager.d.ts +14 -4
  84. package/dist/src/core/SessionManager.d.ts.map +1 -1
  85. package/dist/src/core/SessionManager.js +25 -5
  86. package/dist/src/core/SessionManager.js.map +1 -1
  87. package/dist/src/core/Step.d.ts +10 -10
  88. package/dist/src/core/Step.d.ts.map +1 -1
  89. package/dist/src/core/Step.js.map +1 -1
  90. package/dist/src/core/ToolExecutor.d.ts +4 -2
  91. package/dist/src/core/ToolExecutor.d.ts.map +1 -1
  92. package/dist/src/core/ToolExecutor.js +13 -3
  93. package/dist/src/core/ToolExecutor.js.map +1 -1
  94. package/dist/src/types/agent.d.ts +41 -21
  95. package/dist/src/types/agent.d.ts.map +1 -1
  96. package/dist/src/types/agent.js.map +1 -1
  97. package/dist/src/types/index.d.ts +1 -1
  98. package/dist/src/types/index.d.ts.map +1 -1
  99. package/dist/src/types/index.js.map +1 -1
  100. package/dist/src/types/persistence.d.ts +0 -1
  101. package/dist/src/types/persistence.d.ts.map +1 -1
  102. package/dist/src/types/route.d.ts +22 -16
  103. package/dist/src/types/route.d.ts.map +1 -1
  104. package/dist/src/types/session.d.ts +6 -11
  105. package/dist/src/types/session.d.ts.map +1 -1
  106. package/dist/src/types/tool.d.ts +12 -6
  107. package/dist/src/types/tool.d.ts.map +1 -1
  108. package/dist/src/utils/session.d.ts +2 -2
  109. package/dist/src/utils/session.d.ts.map +1 -1
  110. package/dist/src/utils/session.js +6 -26
  111. package/dist/src/utils/session.js.map +1 -1
  112. package/docs/README.md +3 -3
  113. package/docs/api/README.md +35 -4
  114. package/docs/api/overview.md +166 -12
  115. package/docs/core/agent/README.md +162 -17
  116. package/docs/core/agent/context-management.md +39 -15
  117. package/docs/core/agent/session-management.md +49 -16
  118. package/docs/core/ai-integration/prompt-composition.md +38 -14
  119. package/docs/core/ai-integration/response-processing.md +28 -17
  120. package/docs/core/conversation-flows/data-collection.md +103 -25
  121. package/docs/core/conversation-flows/route-dsl.md +45 -22
  122. package/docs/core/conversation-flows/routes.md +74 -18
  123. package/docs/core/conversation-flows/step-transitions.md +3 -3
  124. package/docs/core/conversation-flows/steps.md +39 -15
  125. package/docs/core/routing/intelligent-routing.md +18 -9
  126. package/docs/core/tools/tool-definition.md +8 -8
  127. package/docs/core/tools/tool-execution.md +26 -26
  128. package/docs/core/tools/tool-scoping.md +5 -5
  129. package/docs/guides/getting-started/README.md +54 -32
  130. package/examples/advanced-patterns/knowledge-based-agent.ts +37 -28
  131. package/examples/advanced-patterns/persistent-onboarding.ts +70 -41
  132. package/examples/advanced-patterns/route-lifecycle-hooks.ts +28 -2
  133. package/examples/advanced-patterns/streaming-responses.ts +28 -23
  134. package/examples/ai-providers/anthropic-integration.ts +40 -33
  135. package/examples/ai-providers/openai-integration.ts +25 -25
  136. package/examples/conversation-flows/completion-transitions.ts +36 -32
  137. package/examples/core-concepts/basic-agent.ts +76 -78
  138. package/examples/core-concepts/schema-driven-extraction.ts +20 -16
  139. package/examples/core-concepts/session-management.ts +65 -53
  140. package/examples/integrations/database-integration.ts +49 -34
  141. package/examples/integrations/healthcare-integration.ts +96 -91
  142. package/examples/integrations/search-integration.ts +79 -82
  143. package/examples/integrations/server-session-management.ts +25 -17
  144. package/examples/persistence/database-persistence.ts +61 -45
  145. package/examples/persistence/memory-sessions.ts +52 -63
  146. package/examples/persistence/redis-persistence.ts +81 -95
  147. package/examples/tools/basic-tools.ts +73 -62
  148. package/examples/tools/data-enrichment-tools.ts +52 -44
  149. package/package.json +1 -1
  150. package/src/core/Agent.ts +418 -128
  151. package/src/core/PersistenceManager.ts +51 -27
  152. package/src/core/PromptComposer.ts +1 -1
  153. package/src/core/ResponseEngine.ts +21 -19
  154. package/src/core/ResponsePipeline.ts +174 -59
  155. package/src/core/Route.ts +58 -6
  156. package/src/core/RoutingEngine.ts +174 -27
  157. package/src/core/SessionManager.ts +32 -8
  158. package/src/core/Step.ts +20 -12
  159. package/src/core/ToolExecutor.ts +19 -5
  160. package/src/types/agent.ts +46 -23
  161. package/src/types/index.ts +2 -0
  162. package/src/types/persistence.ts +0 -1
  163. package/src/types/route.ts +22 -16
  164. package/src/types/session.ts +6 -12
  165. package/src/types/tool.ts +15 -9
  166. package/src/utils/session.ts +6 -31
@@ -53,7 +53,6 @@ export class PersistenceManager<TData = Record<string, unknown>> {
53
53
  status: "active",
54
54
  collectedData: {
55
55
  data: options.initialData || {},
56
- dataByRoute: {},
57
56
  routeHistory: [],
58
57
  metadata: {},
59
58
  },
@@ -235,32 +234,57 @@ export class PersistenceManager<TData = Record<string, unknown>> {
235
234
  sessionId: string,
236
235
  sessionStep: SessionState<TData>
237
236
  ): Promise<SessionData<TData> | null> {
238
- const persistenceData = sessionStepToData(sessionStep);
239
-
240
- // First try to find existing session
241
- const existingSession = await this.sessionRepository.findById(sessionId);
242
-
243
- if (existingSession) {
244
- // Update existing session
245
- return await this.sessionRepository.update(sessionId, {
246
- currentRoute: persistenceData.currentRoute,
247
- currentStep: persistenceData.currentStep,
248
- collectedData: persistenceData.collectedData,
249
- lastMessageAt: new Date(),
250
- });
251
- } else {
252
- // Create new session if it doesn't exist
253
- return await this.sessionRepository.create({
254
- id: sessionId,
255
- userId: persistenceData.collectedData.metadata?.userId
256
- ? JSON.stringify(persistenceData.collectedData.metadata?.userId)
257
- : this.config.userId,
258
- status: "active",
259
- currentRoute: persistenceData.currentRoute,
260
- currentStep: persistenceData.currentStep,
261
- collectedData: persistenceData.collectedData,
262
- messageCount: 0,
263
- });
237
+ // Validate input parameters
238
+ if (!sessionId || typeof sessionId !== 'string') {
239
+ throw new Error('Session ID must be a non-empty string');
240
+ }
241
+
242
+ if (!sessionStep || typeof sessionStep !== 'object') {
243
+ throw new Error('Session step must be a valid object');
244
+ }
245
+
246
+ // Validate session data structure
247
+ if (sessionStep.data && typeof sessionStep.data !== 'object') {
248
+ throw new Error('Session data must be an object');
249
+ }
250
+
251
+ let persistenceData;
252
+ try {
253
+ persistenceData = sessionStepToData(sessionStep);
254
+ } catch (error) {
255
+ const errorMessage = error instanceof Error ? error.message : String(error);
256
+ throw new Error(`Failed to convert session step to persistence data: ${errorMessage}`);
257
+ }
258
+
259
+ try {
260
+ // First try to find existing session
261
+ const existingSession = await this.sessionRepository.findById(sessionId);
262
+
263
+ if (existingSession) {
264
+ // Update existing session
265
+ return await this.sessionRepository.update(sessionId, {
266
+ currentRoute: persistenceData.currentRoute,
267
+ currentStep: persistenceData.currentStep,
268
+ collectedData: persistenceData.collectedData,
269
+ lastMessageAt: new Date(),
270
+ });
271
+ } else {
272
+ // Create new session if it doesn't exist
273
+ return await this.sessionRepository.create({
274
+ id: sessionId,
275
+ userId: persistenceData.collectedData.metadata?.userId
276
+ ? JSON.stringify(persistenceData.collectedData.metadata?.userId)
277
+ : this.config.userId,
278
+ status: "active",
279
+ currentRoute: persistenceData.currentRoute,
280
+ currentStep: persistenceData.currentStep,
281
+ collectedData: persistenceData.collectedData,
282
+ messageCount: 0,
283
+ });
284
+ }
285
+ } catch (error) {
286
+ const errorMessage = error instanceof Error ? error.message : String(error);
287
+ throw new Error(`Failed to save session state to persistence: ${errorMessage}`);
264
288
  }
265
289
  }
266
290
 
@@ -13,7 +13,7 @@ export class PromptComposer<TContext = unknown, TData = unknown> {
13
13
 
14
14
  // Specific, typed sections tailored to the framework
15
15
 
16
- async addAgentMeta(agent: AgentOptions<TContext>): Promise<this> {
16
+ async addAgentMeta(agent: AgentOptions<TContext, TData>): Promise<this> {
17
17
  const lines: string[] = [];
18
18
  lines.push("## Agent");
19
19
  lines.push(`**Name:** ${agent.name}`);
@@ -23,27 +23,30 @@ export interface BuildResponsePromptParams<
23
23
  directives: string[] | undefined;
24
24
  history: Event[];
25
25
  lastMessage: string;
26
- agentOptions?: AgentOptions<TContext>;
26
+ agentOptions?: AgentOptions<TContext, TData>;
27
27
  // Combined properties from agent and route
28
- combinedGuidelines?: Guideline<TContext>[];
29
- combinedTerms?: Term<TContext>[];
28
+ combinedGuidelines?: Guideline<TContext, TData>[];
29
+ combinedTerms?: Term<TContext, TData>[];
30
30
  context?: TContext;
31
31
  session?: SessionState<TData>;
32
+ // NEW: Agent-level schema for data validation
33
+ agentSchema?: StructuredSchema;
32
34
  }
33
35
 
34
- export interface BuildFallbackPromptParams<TContext = unknown> {
36
+ export interface BuildFallbackPromptParams<TContext = unknown, TData = unknown> {
35
37
  history: Event[];
36
- agentOptions: AgentOptions<TContext>;
37
- terms: Term<TContext>[];
38
- guidelines: Guideline<TContext>[];
38
+ agentOptions: AgentOptions<TContext, TData>;
39
+ terms: Term<TContext, TData>[];
40
+ guidelines: Guideline<TContext, TData>[];
39
41
  context?: TContext;
40
- session?: SessionState;
42
+ session?: SessionState<TData>;
41
43
  }
42
44
 
43
- export class ResponseEngine<TContext = unknown> {
44
- responseSchemaForRoute<TData = unknown>(
45
+ export class ResponseEngine<TContext = unknown, TData = unknown> {
46
+ responseSchemaForRoute(
45
47
  route: Route<TContext, TData>,
46
- currentStep?: Step<TContext, TData>
48
+ currentStep?: Step<TContext, TData>,
49
+ agentSchema?: StructuredSchema
47
50
  ): StructuredSchema {
48
51
  const base: StructuredSchema = {
49
52
  type: "object",
@@ -59,12 +62,12 @@ export class ResponseEngine<TContext = unknown> {
59
62
  base.properties!.data = route.responseOutputSchema;
60
63
  }
61
64
 
62
- // Add collect fields from current step
63
- if (currentStep?.collect && route.schema?.properties) {
65
+ // Add collect fields from current step using agent-level schema
66
+ if (currentStep?.collect && agentSchema?.properties) {
64
67
  for (const field of currentStep.collect) {
65
- const fieldSchema = route.schema.properties[field];
68
+ const fieldSchema = agentSchema.properties[field as string];
66
69
  if (fieldSchema) {
67
- base.properties![field] = fieldSchema;
70
+ base.properties![field as string] = fieldSchema;
68
71
  }
69
72
  }
70
73
  }
@@ -73,7 +76,7 @@ export class ResponseEngine<TContext = unknown> {
73
76
  }
74
77
 
75
78
  async buildResponsePrompt(
76
- params: BuildResponsePromptParams<TContext>
79
+ params: BuildResponsePromptParams<TContext, TData>
77
80
  ): Promise<string> {
78
81
  const {
79
82
  route,
@@ -107,8 +110,7 @@ export class ResponseEngine<TContext = unknown> {
107
110
  await pc.addAgentMeta(effectiveAgentOptions);
108
111
  }
109
112
  await pc.addInstruction(
110
- `Route: ${route.title}${
111
- route.description ? ` — ${route.description}` : ""
113
+ `Route: ${route.title}${route.description ? ` — ${route.description}` : ""
112
114
  }`
113
115
  );
114
116
  if (currentStep.prompt) {
@@ -148,7 +150,7 @@ export class ResponseEngine<TContext = unknown> {
148
150
  }
149
151
 
150
152
  async buildFallbackPrompt(
151
- params: BuildFallbackPromptParams<TContext>
153
+ params: BuildFallbackPromptParams<TContext, TData>
152
154
  ): Promise<string> {
153
155
  const { history, agentOptions, terms, guidelines, context, session } =
154
156
  params;
@@ -8,6 +8,7 @@ import type {
8
8
  SessionState,
9
9
  AgentStructuredResponse,
10
10
  Tool,
11
+ RouteTransitionConfig,
11
12
  } from "../types";
12
13
  import { EventKind, MessageRole } from "../types/history";
13
14
  import {
@@ -24,47 +25,51 @@ import { RoutingEngine } from "../core/RoutingEngine";
24
25
  import { ToolExecutor } from "../core/ToolExecutor";
25
26
  import { END_ROUTE_ID } from "../constants";
26
27
 
27
- export interface ResponsePreparationResult<TContext> {
28
+ export interface ResponsePreparationResult<TContext, TData = unknown> {
28
29
  effectiveContext: TContext;
29
- session: SessionState;
30
+ session: SessionState<TData>;
30
31
  }
31
32
 
32
- export interface RoutingResult<TContext> {
33
- selectedRoute: Route<TContext, unknown> | undefined;
34
- selectedStep: Step<TContext, unknown> | undefined;
33
+ export interface RoutingResult<TContext, TData = unknown> {
34
+ selectedRoute: Route<TContext, TData> | undefined;
35
+ selectedStep: Step<TContext, TData> | undefined;
35
36
  responseDirectives: string[] | undefined;
36
- session: SessionState;
37
+ session: SessionState<TData>;
37
38
  isRouteComplete: boolean;
39
+ completedRoutes?: Route<TContext, TData>[];
38
40
  }
39
41
 
40
- export interface ToolExecutionResult {
41
- session: SessionState;
42
+ export interface ToolExecutionResult<TData = unknown> {
43
+ session: SessionState<TData>;
42
44
  toolCalls:
43
45
  | Array<{ toolName: string; arguments: Record<string, unknown> }>
44
46
  | undefined;
45
47
  }
46
48
 
47
- export interface DataCollectionResult {
48
- session: SessionState;
49
- collectedData?: Record<string, unknown>;
49
+ export interface DataCollectionResult<TData = unknown> {
50
+ session: SessionState<TData>;
51
+ collectedData?: Partial<TData>;
50
52
  }
51
53
 
52
54
  /**
53
55
  * Shared response processing logic between respond() and respondStream() methods
54
56
  */
55
- export class ResponsePipeline<TContext = unknown> {
57
+ export class ResponsePipeline<TContext = unknown, TData = unknown> {
56
58
  constructor(
57
- private readonly options: AgentOptions<TContext>,
58
- private readonly routes: Route<TContext, unknown>[],
59
- private readonly tools: Tool<TContext, unknown[], unknown, unknown>[],
60
- private readonly routingEngine: RoutingEngine<TContext>,
59
+ private readonly options: AgentOptions<TContext, TData>,
60
+ private readonly routes: Route<TContext, TData>[],
61
+ private readonly tools: Tool<TContext, TData, unknown[], unknown>[],
62
+ private readonly routingEngine: RoutingEngine<TContext, TData>,
61
63
  private readonly updateContext: (
62
64
  updates: Partial<TContext>
63
65
  ) => Promise<void>,
64
66
  private readonly updateData: (
65
- session: SessionState,
66
- dataUpdate: Partial<unknown>
67
- ) => Promise<SessionState>
67
+ session: SessionState<TData>,
68
+ dataUpdate: Partial<TData>
69
+ ) => Promise<SessionState<TData>>,
70
+ private readonly updateCollectedData?: (
71
+ updates: Partial<TData>
72
+ ) => Promise<void>
68
73
  ) {}
69
74
 
70
75
  /**
@@ -72,8 +77,8 @@ export class ResponsePipeline<TContext = unknown> {
72
77
  */
73
78
  async prepareResponseContext(params: {
74
79
  contextOverride?: Partial<TContext>;
75
- session?: SessionState;
76
- }): Promise<ResponsePreparationResult<TContext>> {
80
+ session?: SessionState<TData>;
81
+ }): Promise<ResponsePreparationResult<TContext, TData>> {
77
82
  const { contextOverride, session } = params;
78
83
 
79
84
  // Get current context (may fetch from provider)
@@ -93,7 +98,7 @@ export class ResponsePipeline<TContext = unknown> {
93
98
  } as TContext;
94
99
 
95
100
  // Initialize or get session (use current session if available)
96
- const targetSession = session || this.currentSession || createSession();
101
+ const targetSession = session || this.currentSession || createSession<TData>();
97
102
 
98
103
  return {
99
104
  effectiveContext,
@@ -105,18 +110,19 @@ export class ResponsePipeline<TContext = unknown> {
105
110
  * Handle routing and step selection logic
106
111
  */
107
112
  async handleRoutingAndStepSelection(params: {
108
- session: SessionState;
113
+ session: SessionState<TData>;
109
114
  history: Event[];
110
115
  context: TContext;
111
116
  signal?: AbortSignal;
112
- }): Promise<RoutingResult<TContext>> {
117
+ }): Promise<RoutingResult<TContext, TData>> {
113
118
  const { session, history, context, signal } = params;
114
119
 
115
120
  // PHASE 2: ROUTING + STEP SELECTION - Determine which route and step to use (combined)
116
- let selectedRoute: Route<TContext, unknown> | undefined;
121
+ let selectedRoute: Route<TContext, TData> | undefined;
117
122
  let responseDirectives: string[] | undefined;
118
- let selectedStep: Step<TContext, unknown> | undefined;
123
+ let selectedStep: Step<TContext, TData> | undefined;
119
124
  let isRouteComplete = false;
125
+ let completedRoutes: Route<TContext, TData>[] = [];
120
126
  let targetSession = session;
121
127
 
122
128
  // Check for pending transition from previous route completion
@@ -178,6 +184,7 @@ export class ResponsePipeline<TContext = unknown> {
178
184
  responseDirectives = orchestration.responseDirectives;
179
185
  targetSession = orchestration.session;
180
186
  isRouteComplete = orchestration.isRouteComplete || false;
187
+ completedRoutes = orchestration.completedRoutes || [];
181
188
 
182
189
  // Log if route is complete
183
190
  if (isRouteComplete) {
@@ -193,6 +200,7 @@ export class ResponsePipeline<TContext = unknown> {
193
200
  responseDirectives,
194
201
  session: targetSession,
195
202
  isRouteComplete,
203
+ completedRoutes,
196
204
  };
197
205
  }
198
206
 
@@ -200,18 +208,18 @@ export class ResponsePipeline<TContext = unknown> {
200
208
  * Determine next step and update session
201
209
  */
202
210
  determineNextStep(params: {
203
- selectedRoute: Route<TContext, unknown> | undefined;
204
- selectedStep: Step<TContext, unknown> | undefined;
205
- session: SessionState;
211
+ selectedRoute: Route<TContext, TData> | undefined;
212
+ selectedStep: Step<TContext, TData> | undefined;
213
+ session: SessionState<TData>;
206
214
  isRouteComplete: boolean;
207
- }): { nextStep: Step<TContext, unknown> | undefined; session: SessionState } {
215
+ }): { nextStep: Step<TContext, TData> | undefined; session: SessionState<TData> } {
208
216
  const { selectedRoute, selectedStep, session, isRouteComplete } = params;
209
217
 
210
218
  if (!selectedRoute || isRouteComplete) {
211
219
  return { nextStep: undefined, session };
212
220
  }
213
221
 
214
- let nextStep: Step<TContext, unknown>;
222
+ let nextStep: Step<TContext, TData>;
215
223
 
216
224
  // If we have a selected step from the combined routing decision, use it
217
225
  if (selectedStep) {
@@ -253,12 +261,12 @@ export class ResponsePipeline<TContext = unknown> {
253
261
  */
254
262
  async executeToolCalls(params: {
255
263
  toolCalls: Array<{ toolName: string; arguments: Record<string, unknown> }>;
256
- selectedRoute?: Route<TContext, unknown>;
264
+ selectedRoute?: Route<TContext, TData>;
257
265
  context: TContext;
258
- session: SessionState;
266
+ session: SessionState<TData>;
259
267
  history: Event[];
260
268
  isStreaming?: boolean;
261
- }): Promise<ToolExecutionResult> {
269
+ }): Promise<ToolExecutionResult<TData>> {
262
270
  const {
263
271
  toolCalls,
264
272
  selectedRoute,
@@ -291,11 +299,12 @@ export class ResponsePipeline<TContext = unknown> {
291
299
  continue;
292
300
  }
293
301
 
294
- const toolExecutor = new ToolExecutor<TContext, unknown>();
302
+ const toolExecutor = new ToolExecutor<TContext, TData>();
295
303
  const result = await toolExecutor.executeTool({
296
304
  tool,
297
305
  context,
298
306
  updateContext: this.updateContext,
307
+ updateData: this.updateCollectedData || (async () => {}),
299
308
  history,
300
309
  data: updatedSession.data,
301
310
  });
@@ -311,7 +320,7 @@ export class ResponsePipeline<TContext = unknown> {
311
320
  if (result.dataUpdate) {
312
321
  updatedSession = await this.updateData(
313
322
  updatedSession,
314
- result.dataUpdate
323
+ result.dataUpdate as Partial<TData>
315
324
  );
316
325
  logger.debug(
317
326
  `[ResponseHandler] ${
@@ -341,15 +350,15 @@ export class ResponsePipeline<TContext = unknown> {
341
350
  initialToolCalls:
342
351
  | Array<{ toolName: string; arguments: Record<string, unknown> }>
343
352
  | undefined;
344
- selectedRoute?: Route<TContext, unknown>;
345
- nextStep: Step<TContext, unknown>;
353
+ selectedRoute?: Route<TContext, TData>;
354
+ nextStep: Step<TContext, TData>;
346
355
  responsePrompt: string;
347
356
  history: Event[];
348
357
  context: TContext;
349
- session: SessionState;
358
+ session: SessionState<TData>;
350
359
  responseSchema: Record<string, unknown>;
351
360
  isStreaming?: boolean;
352
- }): Promise<ToolExecutionResult> {
361
+ }): Promise<ToolExecutionResult<TData>> {
353
362
  const {
354
363
  initialToolCalls,
355
364
  selectedRoute,
@@ -469,28 +478,35 @@ export class ResponsePipeline<TContext = unknown> {
469
478
  */
470
479
  async handleDataCollection(params: {
471
480
  structured: AgentStructuredResponse | undefined;
472
- nextStep: Step<TContext, unknown>;
473
- session: SessionState;
474
- }): Promise<DataCollectionResult> {
481
+ nextStep: Step<TContext, TData>;
482
+ session: SessionState<TData>;
483
+ }): Promise<DataCollectionResult<TData>> {
475
484
  const { structured, nextStep, session } = params;
476
485
 
477
486
  if (!structured || !nextStep.collect) {
478
487
  return { session };
479
488
  }
480
489
 
481
- const collectedData: Record<string, unknown> = {};
490
+ const collectedData: Partial<TData> = {};
482
491
  // The structured response includes both base fields and collected extraction fields
483
492
  const structuredData = structured as AgentStructuredResponse &
484
493
  Record<string, unknown>;
485
494
 
486
495
  for (const field of nextStep.collect) {
487
- if (field in structuredData) {
488
- collectedData[field] = structuredData[field];
496
+ const fieldKey = field as string;
497
+ if (fieldKey in structuredData) {
498
+ (collectedData as Record<string, unknown>)[fieldKey] = structuredData[fieldKey];
489
499
  }
490
500
  }
491
501
 
492
502
  let updatedSession = session;
493
503
  if (Object.keys(collectedData).length > 0) {
504
+ // Update agent-level collected data with validation if available
505
+ if (this.updateCollectedData) {
506
+ await this.updateCollectedData(collectedData);
507
+ }
508
+
509
+ // Update session with validated data
494
510
  updatedSession = await this.updateData(session, collectedData);
495
511
  logger.debug(`[ResponseHandler] Collected data:`, collectedData);
496
512
  }
@@ -523,11 +539,11 @@ export class ResponsePipeline<TContext = unknown> {
523
539
  * Handle route completion logic
524
540
  */
525
541
  async handleRouteCompletion(params: {
526
- selectedRoute: Route<TContext, unknown>;
527
- session: SessionState;
542
+ selectedRoute: Route<TContext, TData>;
543
+ session: SessionState<TData>;
528
544
  context: TContext;
529
545
  history: Event[];
530
- }): Promise<{ session: SessionState; hasTransition: boolean }> {
546
+ }): Promise<{ session: SessionState<TData>; hasTransition: boolean }> {
531
547
  const { selectedRoute, session, context, history } = params;
532
548
 
533
549
  // Check for onComplete transition
@@ -590,8 +606,8 @@ export class ResponsePipeline<TContext = unknown> {
590
606
  */
591
607
  private findAvailableTool(
592
608
  toolName: string,
593
- route?: Route<TContext, unknown>
594
- ): Tool<TContext, unknown[], unknown, unknown> | undefined {
609
+ route?: Route<TContext, TData>
610
+ ): Tool<TContext, TData, unknown[], unknown> | undefined {
595
611
  // Check route-level tools first (if route provided)
596
612
  if (route) {
597
613
  const routeTool = route
@@ -610,12 +626,12 @@ export class ResponsePipeline<TContext = unknown> {
610
626
  * Collect all available tools for the given route and step context
611
627
  */
612
628
  private collectAvailableTools(
613
- route?: Route<TContext, unknown>,
614
- step?: Step<TContext, unknown>
629
+ route?: Route<TContext, TData>,
630
+ step?: Step<TContext, TData>
615
631
  ): Array<{ id: string; description?: string; parameters?: unknown }> {
616
632
  const availableTools = new Map<
617
633
  string,
618
- Tool<TContext, unknown[], unknown, unknown>
634
+ Tool<TContext, TData, unknown[], unknown>
619
635
  >();
620
636
 
621
637
  // Add agent-level tools
@@ -633,7 +649,7 @@ export class ResponsePipeline<TContext = unknown> {
633
649
  // Filter by step-level allowed tools if specified
634
650
  if (step?.tools) {
635
651
  const allowedToolIds = new Set<string>();
636
- const stepTools: Tool<TContext, unknown[], unknown, unknown>[] = [];
652
+ const stepTools: Tool<TContext, TData, unknown[], unknown>[] = [];
637
653
 
638
654
  for (const toolRef of step.tools) {
639
655
  if (typeof toolRef === "string") {
@@ -652,7 +668,7 @@ export class ResponsePipeline<TContext = unknown> {
652
668
  if (allowedToolIds.size > 0) {
653
669
  const filteredTools = new Map<
654
670
  string,
655
- Tool<TContext, unknown[], unknown, unknown>
671
+ Tool<TContext, TData, unknown[], unknown>
656
672
  >();
657
673
  for (const toolId of allowedToolIds) {
658
674
  const tool = availableTools.get(toolId);
@@ -694,14 +710,14 @@ export class ResponsePipeline<TContext = unknown> {
694
710
 
695
711
  // These need to be passed in or accessed differently since ResponseHandler is not part of Agent
696
712
  private context?: TContext;
697
- private currentSession?: SessionState;
713
+ private currentSession?: SessionState<TData>;
698
714
 
699
715
  // Setters for context and current session (needed for beforeRespond hook)
700
716
  setContext(context: TContext | undefined): void {
701
717
  this.context = context;
702
718
  }
703
719
 
704
- setCurrentSession(session: SessionState | undefined): void {
720
+ setCurrentSession(session: SessionState<TData> | undefined): void {
705
721
  this.currentSession = session;
706
722
  }
707
723
 
@@ -709,7 +725,106 @@ export class ResponsePipeline<TContext = unknown> {
709
725
  return this.context;
710
726
  }
711
727
 
712
- public getCurrentSession(): SessionState | undefined {
728
+ public getCurrentSession(): SessionState<TData> | undefined {
713
729
  return this.currentSession;
714
730
  }
731
+
732
+ /**
733
+ * Handle cross-route completion evaluation and notifications
734
+ * This method evaluates all routes for completion and can trigger completion handlers
735
+ */
736
+ async handleCrossRouteCompletion(params: {
737
+ routes: Route<TContext, TData>[];
738
+ session: SessionState<TData>;
739
+ context: TContext;
740
+ history: Event[];
741
+ }): Promise<{
742
+ session: SessionState<TData>;
743
+ completedRoutes: Route<TContext, TData>[];
744
+ pendingTransitions: Array<{
745
+ route: Route<TContext, TData>;
746
+ transitionConfig: RouteTransitionConfig<TContext, TData>;
747
+ }>;
748
+ }> {
749
+ const { routes, session, context } = params;
750
+
751
+ // Evaluate all routes for completion
752
+ const completedRoutes: Route<TContext, TData>[] = [];
753
+ const pendingTransitions: Array<{
754
+ route: Route<TContext, TData>;
755
+ transitionConfig: RouteTransitionConfig<TContext, TData>;
756
+ }> = [];
757
+
758
+ for (const route of routes) {
759
+ if (route.isComplete(session.data || {})) {
760
+ completedRoutes.push(route);
761
+
762
+ // Check for onComplete transitions
763
+ const transitionConfig = await route.evaluateOnComplete(
764
+ { data: session.data },
765
+ context
766
+ );
767
+
768
+ if (transitionConfig) {
769
+ pendingTransitions.push({ route, transitionConfig });
770
+ }
771
+
772
+ logger.debug(
773
+ `[ResponsePipeline] Route completed: ${route.title} ` +
774
+ `(${Math.round(route.getCompletionProgress(session.data || {}) * 100)}%)`
775
+ );
776
+ }
777
+ }
778
+
779
+ // Log completion status for all routes
780
+ if (completedRoutes.length > 0) {
781
+ logger.debug(
782
+ `[ResponsePipeline] Cross-route completion evaluation: ` +
783
+ `${completedRoutes.length}/${routes.length} routes complete`
784
+ );
785
+ }
786
+
787
+ return {
788
+ session,
789
+ completedRoutes,
790
+ pendingTransitions,
791
+ };
792
+ }
793
+
794
+ /**
795
+ * Update data flow to ensure agent-level data consistency
796
+ * This method ensures that data updates are properly validated and propagated
797
+ */
798
+ async updateDataFlow(params: {
799
+ session: SessionState<TData>;
800
+ dataUpdate: Partial<TData>;
801
+ routes: Route<TContext, TData>[];
802
+ }): Promise<SessionState<TData>> {
803
+ const { session, dataUpdate, routes } = params;
804
+
805
+ // Update session data
806
+ const updatedSession = await this.updateData(session, dataUpdate);
807
+
808
+ // Update agent-level data if handler is available
809
+ if (this.updateCollectedData) {
810
+ await this.updateCollectedData(dataUpdate);
811
+ }
812
+
813
+ // Evaluate route completions after data update
814
+ const completionResults = await this.handleCrossRouteCompletion({
815
+ routes,
816
+ session: updatedSession,
817
+ context: this.context!,
818
+ history: [],
819
+ });
820
+
821
+ // Log any newly completed routes
822
+ if (completionResults.completedRoutes.length > 0) {
823
+ logger.debug(
824
+ `[ResponsePipeline] Data update resulted in ${completionResults.completedRoutes.length} completed routes`
825
+ );
826
+ }
827
+
828
+ return completionResults.session;
829
+ }
715
830
  }