@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
@@ -4,17 +4,13 @@
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Agent = void 0;
7
- const history_1 = require("../types/history");
8
7
  const utils_1 = require("../utils");
9
8
  const Route_1 = require("./Route");
10
- const Step_1 = require("./Step");
11
9
  const PersistenceManager_1 = require("./PersistenceManager");
12
10
  const SessionManager_1 = require("./SessionManager");
13
11
  const RoutingEngine_1 = require("./RoutingEngine");
14
- const ResponseEngine_1 = require("./ResponseEngine");
15
- const ToolExecutor_1 = require("./ToolExecutor");
16
- const ResponsePipeline_1 = require("./ResponsePipeline");
17
- const constants_1 = require("../constants");
12
+ const ResponseModal_1 = require("./ResponseModal");
13
+ const ToolManager_1 = require("./ToolManager");
18
14
  /**
19
15
  * Error thrown when data validation fails
20
16
  */
@@ -78,14 +74,14 @@ class Agent {
78
74
  }
79
75
  // Initialize current session if provided
80
76
  this.currentSession = options.session;
81
- // Initialize routing and response engines
77
+ // Initialize routing engine
82
78
  this.routingEngine = new RoutingEngine_1.RoutingEngine({
83
79
  maxCandidates: 5,
84
80
  allowRouteSwitch: true,
85
81
  switchThreshold: 70,
86
82
  });
87
- this.responseEngine = new ResponseEngine_1.ResponseEngine();
88
- this.responsePipeline = new ResponsePipeline_1.ResponsePipeline(options, this.routes, this.tools, this.routingEngine, this.updateContext.bind(this), this.updateData.bind(this), this.updateCollectedData.bind(this));
83
+ // Initialize ResponseModal for handling all response generation
84
+ this.responseModal = new ResponseModal_1.ResponseModal(this);
89
85
  // Initialize persistence if configured
90
86
  if (options.persistence) {
91
87
  try {
@@ -138,12 +134,21 @@ class Agent {
138
134
  if (options.knowledgeBase) {
139
135
  this.knowledgeBase = { ...options.knowledgeBase };
140
136
  }
141
- // Initialize session manager
142
- this.session = new SessionManager_1.SessionManager(this.persistenceManager);
137
+ // Initialize session manager with reference to this agent for bidirectional sync
138
+ this.session = new SessionManager_1.SessionManager(this.persistenceManager, this);
139
+ // Initialize tool manager with proper type inference
140
+ this.tool = new ToolManager_1.ToolManager(this);
143
141
  // Store sessionId for later use in getOrCreate calls
144
142
  if (options.sessionId) {
143
+ this.session.setDefaultSessionId(options.sessionId);
145
144
  // The session will be loaded on first getOrCreate call
146
- this.session.getOrCreate(options.sessionId).catch((err) => {
145
+ this.session.getOrCreate(options.sessionId).then((session) => {
146
+ // Sync session data to agent collected data
147
+ if (session.data && Object.keys(session.data).length > 0) {
148
+ this.collectedData = { ...session.data };
149
+ utils_1.logger.debug("[Agent] Synced session data to collected data:", this.collectedData);
150
+ }
151
+ }).catch((err) => {
147
152
  utils_1.logger.error("Failed to start session", err);
148
153
  });
149
154
  }
@@ -225,6 +230,8 @@ class Agent {
225
230
  * Get the current collected data
226
231
  */
227
232
  getCollectedData() {
233
+ // Ensure agent collected data is synced with session
234
+ this.syncSessionDataToCollectedData();
228
235
  return { ...this.collectedData };
229
236
  }
230
237
  /**
@@ -256,6 +263,12 @@ class Agent {
256
263
  if (this.currentSession) {
257
264
  this.currentSession = (0, utils_1.mergeCollected)(this.currentSession, this.collectedData);
258
265
  }
266
+ // Also update the session manager's session data (avoid circular call)
267
+ const sessionManagerSession = this.session.current;
268
+ if (sessionManagerSession) {
269
+ sessionManagerSession.data = { ...this.collectedData };
270
+ sessionManagerSession.metadata.lastUpdatedAt = new Date();
271
+ }
259
272
  utils_1.logger.debug("[Agent] Collected data updated:", updates);
260
273
  }
261
274
  /**
@@ -302,7 +315,7 @@ class Agent {
302
315
  `Must be valid keys from agent schema. Available fields: ${Object.keys(this.schema.properties).join(', ')}.`);
303
316
  }
304
317
  }
305
- const route = new Route_1.Route(options);
318
+ const route = new Route_1.Route(options, this);
306
319
  this.routes.push(route);
307
320
  return route;
308
321
  }
@@ -326,17 +339,47 @@ class Agent {
326
339
  return this;
327
340
  }
328
341
  /**
329
- * Register a tool at the agent level
342
+ * Add a tool to the agent using the unified Tool interface
343
+ * Creates and adds the tool to agent scope in one operation (BREAKING CHANGE: replaces createTool)
344
+ */
345
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
346
+ addTool(tool) {
347
+ // Validate tool before adding
348
+ if (!tool || !tool.id || !tool.handler) {
349
+ throw new Error('Invalid tool: must have id and handler properties');
350
+ }
351
+ // Add directly to agent's tools array, preserving the TResult type
352
+ this.tools.push(tool);
353
+ utils_1.logger.debug(`[Agent] Added tool to agent scope: ${tool.id}`);
354
+ return this;
355
+ }
356
+ /**
357
+ * Register a tool at the agent level (legacy method for backward compatibility)
358
+ * @deprecated Use addTool() with Tool interface instead
330
359
  */
360
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
331
361
  createTool(tool) {
362
+ // Validate tool before adding
363
+ if (!tool || !tool.id || !tool.handler) {
364
+ throw new Error('Invalid tool: must have id and handler properties');
365
+ }
332
366
  this.tools.push(tool);
367
+ utils_1.logger.debug(`[Agent] Created tool (legacy): ${tool.id}`);
333
368
  return this;
334
369
  }
335
370
  /**
336
371
  * Register multiple tools at the agent level
337
372
  */
373
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
338
374
  registerTools(tools) {
339
- tools.forEach((tool) => this.createTool(tool));
375
+ tools.forEach((tool) => {
376
+ // Validate each tool before adding
377
+ if (!tool || !tool.id || !tool.handler) {
378
+ throw new Error(`Invalid tool in batch: must have id and handler properties (tool: ${tool?.id || 'unknown'})`);
379
+ }
380
+ this.tools.push(tool);
381
+ });
382
+ utils_1.logger.debug(`[Agent] Registered ${tools.length} tools`);
340
383
  return this;
341
384
  }
342
385
  /**
@@ -412,916 +455,15 @@ class Agent {
412
455
  * Generate a response based on history and context as a stream
413
456
  */
414
457
  async *respondStream(params) {
415
- const { history: simpleHistory, signal } = params;
416
- const history = (0, utils_1.normalizeHistory)(simpleHistory);
417
- // Prepare context and session using the response pipeline
418
- this.responsePipeline.setContext(this.context);
419
- this.responsePipeline.setCurrentSession(this.currentSession);
420
- let session;
421
- const responseContext = await this.responsePipeline.prepareResponseContext({
422
- contextOverride: params.contextOverride,
423
- session: params.session ? (0, utils_1.cloneDeep)(params.session) : undefined,
424
- });
425
- const { effectiveContext } = responseContext;
426
- session = responseContext.session;
427
- // Merge agent's collected data into session (agent data takes precedence)
428
- if (Object.keys(this.collectedData).length > 0) {
429
- session = (0, utils_1.mergeCollected)(session, this.collectedData);
430
- utils_1.logger.debug("[Agent] Merged agent collected data into session:", this.collectedData);
431
- }
432
- // Update our stored context if it was modified by beforeRespond hook
433
- this.context = this.responsePipeline.getStoredContext();
434
- // PHASE 1: PREPARE - Execute prepare function if current step has one
435
- if (session.currentRoute && session.currentStep) {
436
- const currentRoute = this.routes.find((r) => r.id === session.currentRoute?.id);
437
- if (currentRoute) {
438
- const currentStep = currentRoute.getStep(session.currentStep.id);
439
- if (currentStep?.prepare) {
440
- utils_1.logger.debug(`[Agent] Executing prepare for step: ${currentStep.id}`);
441
- await this.executePrepareFinalize(currentStep.prepare, effectiveContext, session.data, currentRoute, currentStep);
442
- }
443
- }
444
- }
445
- // PHASE 2: ROUTING + STEP SELECTION - Use response pipeline
446
- const routingResult = await this.responsePipeline.handleRoutingAndStepSelection({
447
- session,
448
- history,
449
- context: effectiveContext,
450
- signal,
451
- });
452
- const selectedRoute = routingResult.selectedRoute;
453
- const selectedStep = routingResult.selectedStep;
454
- const responseDirectives = routingResult.responseDirectives;
455
- const isRouteComplete = routingResult.isRouteComplete;
456
- session = routingResult.session;
457
- // PHASE 3: DETERMINE NEXT STEP - Use pipeline method
458
- const stepResult = this.responsePipeline.determineNextStep({
459
- selectedRoute,
460
- selectedStep,
461
- session,
462
- isRouteComplete,
463
- });
464
- const nextStep = stepResult.nextStep;
465
- session = stepResult.session;
466
- if (selectedRoute && !isRouteComplete) {
467
- // PHASE 4: RESPONSE GENERATION - Stream message using selected route and step
468
- // Get last user message
469
- const lastUserMessage = (0, utils_1.getLastMessageFromHistory)(history);
470
- // Build response schema for this route (with collect fields from step)
471
- const responseSchema = this.responseEngine.responseSchemaForRoute(selectedRoute, nextStep, this.schema);
472
- // Check if selected route and next step are defined
473
- if (!selectedRoute || !nextStep) {
474
- utils_1.logger.error("[Agent] Selected route or next step is not defined", {
475
- selectedRoute,
476
- nextStep,
477
- });
478
- throw new Error("Selected route or next step is not defined");
479
- }
480
- // Build response prompt
481
- const responsePrompt = await this.responseEngine.buildResponsePrompt({
482
- route: selectedRoute,
483
- currentStep: nextStep,
484
- rules: selectedRoute.getRules(),
485
- prohibitions: selectedRoute.getProhibitions(),
486
- directives: responseDirectives,
487
- history,
488
- lastMessage: lastUserMessage,
489
- agentOptions: this.options,
490
- // Combine agent and route properties according to the specified logic
491
- combinedGuidelines: [
492
- ...this.getGuidelines(),
493
- ...selectedRoute.getGuidelines(),
494
- ],
495
- combinedTerms: this.mergeTerms(this.getTerms(), selectedRoute.getTerms()),
496
- context: effectiveContext,
497
- session,
498
- agentSchema: this.schema,
499
- });
500
- // Collect available tools for AI
501
- const availableTools = this.collectAvailableTools(selectedRoute, nextStep);
502
- // Generate message stream using AI provider
503
- const stream = this.options.provider.generateMessageStream({
504
- prompt: responsePrompt,
505
- history,
506
- context: effectiveContext,
507
- tools: availableTools,
508
- signal,
509
- parameters: {
510
- jsonSchema: responseSchema,
511
- schemaName: "response_stream_output",
512
- },
513
- });
514
- // Stream chunks to caller
515
- for await (const chunk of stream) {
516
- let toolCalls = undefined;
517
- // Extract tool calls from AI response on final chunk
518
- if (chunk.done && chunk.structured?.toolCalls) {
519
- toolCalls = chunk.structured.toolCalls;
520
- // Execute dynamic tool calls
521
- if (toolCalls.length > 0) {
522
- utils_1.logger.debug(`[Agent] Executing ${toolCalls.length} dynamic tool calls`);
523
- for (const toolCall of toolCalls) {
524
- const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
525
- if (!tool) {
526
- utils_1.logger.warn(`[Agent] Tool not found: ${toolCall.toolName}`);
527
- continue;
528
- }
529
- const toolExecutor = new ToolExecutor_1.ToolExecutor();
530
- const result = await toolExecutor.executeTool({
531
- tool: tool,
532
- context: effectiveContext,
533
- updateContext: this.updateContext.bind(this),
534
- updateData: this.updateCollectedData.bind(this),
535
- history,
536
- data: session.data,
537
- toolArguments: toolCall.arguments,
538
- });
539
- // Update context with tool results
540
- if (result.contextUpdate) {
541
- await this.updateContext(result.contextUpdate);
542
- }
543
- // Update collected data with tool results
544
- if (result.dataUpdate) {
545
- session = await this.updateData(session, result.dataUpdate);
546
- utils_1.logger.debug(`[Agent] Tool updated collected data:`, result.dataUpdate);
547
- }
548
- utils_1.logger.debug(`[Agent] Executed dynamic tool: ${result.toolName} (success: ${result.success})`);
549
- }
550
- }
551
- }
552
- // TOOL LOOP: Allow AI to make follow-up tool calls after initial tool execution (streaming)
553
- const MAX_TOOL_LOOPS = 5;
554
- let toolLoopCount = 0;
555
- let hasToolCalls = toolCalls && toolCalls.length > 0;
556
- while (hasToolCalls && toolLoopCount < MAX_TOOL_LOOPS) {
557
- toolLoopCount++;
558
- utils_1.logger.debug(`[Agent] Starting streaming tool loop ${toolLoopCount}/${MAX_TOOL_LOOPS}`);
559
- // Add tool execution results to history so AI knows what happened
560
- const toolResultsEvents = [];
561
- for (const toolCall of toolCalls || []) {
562
- const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
563
- if (tool) {
564
- toolResultsEvents.push({
565
- kind: history_1.EventKind.TOOL,
566
- source: history_1.MessageRole.AGENT,
567
- timestamp: new Date().toISOString(),
568
- data: {
569
- tool_calls: [
570
- {
571
- tool_id: toolCall.toolName,
572
- arguments: toolCall.arguments,
573
- result: {
574
- data: "Tool executed successfully",
575
- },
576
- },
577
- ],
578
- },
579
- });
580
- }
581
- }
582
- // Create updated history with tool results
583
- const updatedHistory = [...history, ...toolResultsEvents];
584
- // Make follow-up streaming AI call to see if more tools are needed
585
- const followUpStream = this.options.provider.generateMessageStream({
586
- prompt: responsePrompt,
587
- history: updatedHistory,
588
- context: effectiveContext,
589
- tools: availableTools,
590
- parameters: {
591
- jsonSchema: responseSchema,
592
- schemaName: "tool_followup",
593
- },
594
- signal,
595
- });
596
- let followUpToolCalls;
597
- for await (const followUpChunk of followUpStream) {
598
- // Extract tool calls from follow-up stream
599
- if (followUpChunk.done && followUpChunk.structured?.toolCalls) {
600
- followUpToolCalls = followUpChunk.structured.toolCalls;
601
- }
602
- }
603
- hasToolCalls = followUpToolCalls && followUpToolCalls.length > 0;
604
- if (hasToolCalls) {
605
- utils_1.logger.debug(`[Agent] Follow-up streaming call produced ${followUpToolCalls.length} additional tool calls`);
606
- // Execute the follow-up tool calls
607
- for (const toolCall of followUpToolCalls) {
608
- const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
609
- if (!tool) {
610
- utils_1.logger.warn(`[Agent] Tool not found in streaming follow-up: ${toolCall.toolName}`);
611
- continue;
612
- }
613
- const toolExecutor = new ToolExecutor_1.ToolExecutor();
614
- const result = await toolExecutor.executeTool({
615
- tool: tool,
616
- context: effectiveContext,
617
- updateContext: this.updateContext.bind(this),
618
- updateData: this.updateCollectedData.bind(this),
619
- history: updatedHistory,
620
- data: session.data,
621
- toolArguments: toolCall.arguments,
622
- });
623
- // Update context with follow-up tool results
624
- if (result.contextUpdate) {
625
- await this.updateContext(result.contextUpdate);
626
- }
627
- if (result.dataUpdate) {
628
- session = await this.updateData(session, result.dataUpdate);
629
- utils_1.logger.debug(`[Agent] Streaming follow-up tool updated collected data:`, result.dataUpdate);
630
- }
631
- utils_1.logger.debug(`[Agent] Executed streaming follow-up tool: ${result.toolName} (success: ${result.success})`);
632
- }
633
- // Update toolCalls for next iteration
634
- toolCalls = followUpToolCalls;
635
- }
636
- else {
637
- utils_1.logger.debug(`[Agent] Streaming tool loop completed after ${toolLoopCount} iterations`);
638
- // Update toolCalls for final response
639
- toolCalls = followUpToolCalls || [];
640
- break;
641
- }
642
- }
643
- if (toolLoopCount >= MAX_TOOL_LOOPS) {
644
- utils_1.logger.warn(`[Agent] Streaming tool loop limit reached (${MAX_TOOL_LOOPS}), stopping`);
645
- }
646
- // Extract collected data on final chunk
647
- if (chunk.done && chunk.structured && nextStep.collect) {
648
- const collectedData = {};
649
- // The structured response includes both base fields and collected extraction fields
650
- const structuredData = chunk.structured;
651
- for (const field of nextStep.collect) {
652
- const fieldKey = String(field);
653
- if (fieldKey in structuredData) {
654
- collectedData[fieldKey] = structuredData[fieldKey];
655
- }
656
- }
657
- // Merge collected data into session using agent-level data validation
658
- if (Object.keys(collectedData).length > 0) {
659
- // Update agent-level collected data with validation
660
- await this.updateCollectedData(collectedData);
661
- // Update session with validated data
662
- session = await this.updateData(session, collectedData);
663
- utils_1.logger.debug(`[Agent] Collected data:`, collectedData);
664
- }
665
- }
666
- // Extract any additional data from structured response on final chunk
667
- if (chunk.done &&
668
- chunk.structured &&
669
- typeof chunk.structured === "object" &&
670
- "contextUpdate" in chunk.structured) {
671
- await this.updateContext(chunk.structured
672
- .contextUpdate);
673
- }
674
- // Auto-save session step on final chunk
675
- if (chunk.done &&
676
- this.persistenceManager &&
677
- session.id &&
678
- this.options.persistence?.autoSave !== false) {
679
- await this.persistenceManager.saveSessionState(session.id, session);
680
- utils_1.logger.debug(`[Agent] Auto-saved session step to persistence: ${session.id}`);
681
- }
682
- // Execute finalize function on final chunk
683
- if (chunk.done && session.currentRoute && session.currentStep) {
684
- const currentRoute = this.routes.find((r) => r.id === session.currentRoute?.id);
685
- if (currentRoute) {
686
- const currentStep = currentRoute.getStep(session.currentStep.id);
687
- if (currentStep?.finalize) {
688
- utils_1.logger.debug(`[Agent] Executing finalize for step: ${currentStep.id}`);
689
- await this.executePrepareFinalize(currentStep.finalize, effectiveContext, session.data, currentRoute, currentStep);
690
- }
691
- }
692
- }
693
- // Update current session if we have one
694
- if (chunk.done && this.currentSession) {
695
- this.currentSession = session;
696
- }
697
- yield {
698
- delta: chunk.delta,
699
- accumulated: chunk.accumulated,
700
- done: chunk.done,
701
- session, // Return updated session
702
- toolCalls,
703
- isRouteComplete,
704
- metadata: chunk.metadata,
705
- structured: chunk.structured,
706
- };
707
- }
708
- }
709
- else if (isRouteComplete && selectedRoute) {
710
- // Route is complete - generate completion message then check for onComplete transition
711
- const lastUserMessage = (0, utils_1.getLastMessageFromHistory)(history);
712
- // Get endStep spec from route
713
- const endStepSpec = selectedRoute.endStepSpec;
714
- // Create a temporary step for completion message generation using endStep configuration
715
- const completionStep = new Step_1.Step(selectedRoute.id, {
716
- description: endStepSpec.description,
717
- id: endStepSpec.id || constants_1.END_ROUTE_ID,
718
- collect: endStepSpec.collect,
719
- requires: endStepSpec.requires,
720
- prompt: endStepSpec.prompt ||
721
- "Summarize what was accomplished and confirm completion based on the conversation history and collected data",
722
- });
723
- // Build response schema for completion
724
- const responseSchema = this.responseEngine.responseSchemaForRoute(selectedRoute, completionStep, this.schema);
725
- const templateContext = {
726
- context: effectiveContext,
727
- session,
728
- history,
729
- };
730
- // Build completion response prompt
731
- const completionPrompt = await this.responseEngine.buildResponsePrompt({
732
- route: selectedRoute,
733
- currentStep: completionStep,
734
- rules: selectedRoute.getRules(),
735
- prohibitions: selectedRoute.getProhibitions(),
736
- directives: undefined, // No directives for completion
737
- history,
738
- lastMessage: lastUserMessage,
739
- agentOptions: this.options,
740
- // Combine agent and route properties according to the specified logic
741
- combinedGuidelines: [
742
- ...this.getGuidelines(),
743
- ...selectedRoute.getGuidelines(),
744
- ],
745
- combinedTerms: this.mergeTerms(this.getTerms(), selectedRoute.getTerms()),
746
- context: effectiveContext,
747
- session,
748
- agentSchema: this.schema,
749
- });
750
- // Stream completion message using AI provider
751
- const stream = this.options.provider.generateMessageStream({
752
- prompt: completionPrompt,
753
- history,
754
- context: effectiveContext,
755
- signal,
756
- parameters: {
757
- jsonSchema: responseSchema,
758
- schemaName: "completion_message_stream",
759
- },
760
- });
761
- utils_1.logger.debug(`[Agent] Streaming completion message for route: ${selectedRoute.title}`);
762
- // Check for onComplete transition
763
- const transitionConfig = await selectedRoute.evaluateOnComplete({ data: session.data }, effectiveContext);
764
- if (transitionConfig) {
765
- // Find target route by ID or title
766
- const targetRoute = this.routes.find((r) => r.id === transitionConfig.nextStep ||
767
- r.title === transitionConfig.nextStep);
768
- if (targetRoute) {
769
- const renderedCondition = await (0, utils_1.render)(transitionConfig.condition, templateContext);
770
- // Set pending transition in session
771
- session = {
772
- ...session,
773
- pendingTransition: {
774
- targetRouteId: targetRoute.id,
775
- condition: renderedCondition,
776
- reason: "route_complete",
777
- },
778
- };
779
- utils_1.logger.debug(`[Agent] Route ${selectedRoute.title} completed with pending transition to: ${targetRoute.title}`);
780
- }
781
- else {
782
- utils_1.logger.warn(`[Agent] Route ${selectedRoute.title} completed but target route not found: ${transitionConfig.nextStep}`);
783
- }
784
- }
785
- // Set step to END_ROUTE marker
786
- session = (0, utils_1.enterStep)(session, constants_1.END_ROUTE_ID, "Route completed");
787
- utils_1.logger.debug(`[Agent] Route ${selectedRoute.title} completed. Entered END_ROUTE step.`);
788
- // Stream completion chunks
789
- for await (const chunk of stream) {
790
- // Update current session if we have one
791
- if (chunk.done && this.currentSession) {
792
- this.currentSession = session;
793
- }
794
- yield {
795
- delta: chunk.delta,
796
- accumulated: chunk.accumulated,
797
- done: chunk.done,
798
- session,
799
- toolCalls: undefined,
800
- isRouteComplete: true,
801
- metadata: chunk.metadata,
802
- structured: chunk.structured,
803
- };
804
- }
805
- }
806
- else {
807
- // Fallback: No routes defined, stream a simple response
808
- const fallbackPrompt = await this.responseEngine.buildFallbackPrompt({
809
- history,
810
- agentOptions: this.options,
811
- terms: this.terms,
812
- guidelines: this.guidelines,
813
- context: effectiveContext,
814
- session,
815
- });
816
- const stream = this.options.provider.generateMessageStream({
817
- prompt: fallbackPrompt,
818
- history,
819
- context: effectiveContext,
820
- signal,
821
- parameters: {
822
- jsonSchema: {
823
- type: "object",
824
- properties: {
825
- message: { type: "string" },
826
- },
827
- required: ["message"],
828
- additionalProperties: false,
829
- },
830
- schemaName: "fallback_stream_response",
831
- },
832
- });
833
- for await (const chunk of stream) {
834
- // Update current session if we have one
835
- if (chunk.done && this.currentSession) {
836
- this.currentSession = session;
837
- }
838
- yield {
839
- delta: chunk.delta,
840
- accumulated: chunk.accumulated,
841
- done: chunk.done,
842
- session, // Return updated session
843
- toolCalls: undefined,
844
- isRouteComplete: false,
845
- metadata: chunk.metadata,
846
- structured: chunk.structured,
847
- };
848
- }
849
- }
458
+ // Delegate to ResponseModal
459
+ yield* this.responseModal.respondStream(params);
850
460
  }
851
461
  /**
852
462
  * Generate a response based on history and context
853
463
  */
854
464
  async respond(params) {
855
- const { history: simpleHistory, contextOverride, signal } = params;
856
- const history = (0, utils_1.normalizeHistory)(simpleHistory);
857
- // Get current context (may fetch from provider)
858
- let currentContext = await this.getContext();
859
- // Call beforeRespond hook if configured
860
- if (this.options.hooks?.beforeRespond && currentContext !== undefined) {
861
- currentContext = await this.options.hooks.beforeRespond(currentContext);
862
- // Update stored context with the result from beforeRespond
863
- this.context = currentContext;
864
- }
865
- // Merge context with override
866
- const effectiveContext = {
867
- ...currentContext,
868
- ...contextOverride,
869
- };
870
- // Initialize or get session (use current session if available)
871
- let session = (0, utils_1.cloneDeep)(params.session) ||
872
- (0, utils_1.cloneDeep)(this.currentSession) ||
873
- (await this.session.getOrCreate());
874
- // Merge agent's collected data into session (agent data takes precedence)
875
- if (Object.keys(this.collectedData).length > 0) {
876
- session = (0, utils_1.mergeCollected)(session, this.collectedData);
877
- utils_1.logger.debug("[Agent] Merged agent collected data into session:", this.collectedData);
878
- }
879
- // PHASE 1: PREPARE - Execute prepare function if current step has one
880
- if (session.currentRoute && session.currentStep) {
881
- const currentRoute = this.routes.find((r) => r.id === session.currentRoute?.id);
882
- if (currentRoute) {
883
- const currentStep = currentRoute.getStep(session.currentStep.id);
884
- if (currentStep?.prepare) {
885
- utils_1.logger.debug(`[Agent] Executing prepare for step: ${currentStep.id}`);
886
- await this.executePrepareFinalize(currentStep.prepare, effectiveContext, session.data, currentRoute, currentStep);
887
- }
888
- }
889
- }
890
- // PHASE 2: ROUTING + STEP SELECTION - Determine which route and step to use (combined)
891
- let selectedRoute;
892
- let responseDirectives;
893
- let selectedStep;
894
- let isRouteComplete = false;
895
- // Check for pending transition from previous route completion
896
- if (session.pendingTransition) {
897
- const targetRoute = this.routes.find((r) => r.id === session.pendingTransition?.targetRouteId);
898
- if (targetRoute) {
899
- utils_1.logger.debug(`[Agent] Auto-transitioning from pending transition to route: ${targetRoute.title}`);
900
- // Clear pending transition and enter new route
901
- session = {
902
- ...session,
903
- pendingTransition: undefined,
904
- };
905
- session = (0, utils_1.enterRoute)(session, targetRoute.id, targetRoute.title);
906
- // Merge initial data if available
907
- if (targetRoute.initialData) {
908
- session = (0, utils_1.mergeCollected)(session, targetRoute.initialData);
909
- }
910
- selectedRoute = targetRoute;
911
- }
912
- else {
913
- utils_1.logger.warn(`[Agent] Pending transition target route not found: ${session.pendingTransition.targetRouteId}`);
914
- // Clear invalid transition
915
- session = {
916
- ...session,
917
- pendingTransition: undefined,
918
- };
919
- }
920
- }
921
- // If no pending transition or transition handled, do normal routing
922
- if (this.routes.length > 0 && !selectedRoute) {
923
- const orchestration = await this.routingEngine.decideRouteAndStep({
924
- routes: this.routes,
925
- session,
926
- history,
927
- agentOptions: this.options,
928
- provider: this.options.provider,
929
- context: effectiveContext,
930
- signal,
931
- });
932
- selectedRoute = orchestration.selectedRoute;
933
- selectedStep = orchestration.selectedStep;
934
- responseDirectives = orchestration.responseDirectives;
935
- session = orchestration.session;
936
- isRouteComplete = orchestration.isRouteComplete || false;
937
- // Log if route is complete
938
- if (isRouteComplete) {
939
- utils_1.logger.debug(`[Agent] Route complete: all required data collected, END_ROUTE reached`);
940
- }
941
- }
942
- // PHASE 3: DETERMINE NEXT STEP - Use step from combined decision or get initial step
943
- let message;
944
- let toolCalls = undefined;
945
- let responsePrompt;
946
- let availableTools = [];
947
- let responseSchema;
948
- let nextStep;
949
- // Get last user message (needed for both route and completion handling)
950
- const lastUserMessage = (0, utils_1.getLastMessageFromHistory)(history);
951
- if (selectedRoute && !isRouteComplete) {
952
- // If we have a selected step from the combined routing decision, use it
953
- if (selectedStep) {
954
- nextStep = selectedStep;
955
- }
956
- else {
957
- // New route or no step selected - get initial step or first valid step
958
- const candidates = this.routingEngine.getCandidateSteps(selectedRoute, undefined, session.data || {});
959
- if (candidates.length > 0) {
960
- nextStep = candidates[0].step;
961
- utils_1.logger.debug(`[Agent] Using first valid step: ${nextStep.id} for new route`);
962
- }
963
- else {
964
- // Fallback to initial step even if it should be skipped
965
- nextStep = selectedRoute.initialStep;
966
- utils_1.logger.warn(`[Agent] No valid steps found, using initial step: ${nextStep.id}`);
967
- }
968
- }
969
- // Update session with next step
970
- session = (0, utils_1.enterStep)(session, nextStep.id, nextStep.description);
971
- utils_1.logger.debug(`[Agent] Entered step: ${nextStep.id}`);
972
- // PHASE 4: RESPONSE GENERATION - Generate message using selected route and step
973
- // Get last user message
974
- const lastUserMessage = (0, utils_1.getLastMessageFromHistory)(history);
975
- // Build response schema for this route (with collect fields from step)
976
- responseSchema = this.responseEngine.responseSchemaForRoute(selectedRoute, nextStep, this.schema);
977
- // Build response prompt
978
- responsePrompt = await this.responseEngine.buildResponsePrompt({
979
- route: selectedRoute,
980
- currentStep: nextStep,
981
- rules: selectedRoute.getRules(),
982
- prohibitions: selectedRoute.getProhibitions(),
983
- directives: responseDirectives,
984
- history,
985
- lastMessage: lastUserMessage,
986
- agentOptions: this.options,
987
- // Combine agent and route properties according to the specified logic
988
- combinedGuidelines: [
989
- ...this.getGuidelines(),
990
- ...selectedRoute.getGuidelines(),
991
- ],
992
- combinedTerms: this.mergeTerms(this.getTerms(), selectedRoute.getTerms()),
993
- context: effectiveContext,
994
- session,
995
- agentSchema: this.schema,
996
- });
997
- // Collect available tools for AI
998
- availableTools = this.collectAvailableTools(selectedRoute, nextStep);
999
- }
1000
- else {
1001
- // No route selected - generate basic response without route context
1002
- utils_1.logger.debug(`[Agent] No route selected, generating basic response`);
1003
- // Build basic response prompt without route context
1004
- responsePrompt = await this.responseEngine.buildFallbackPrompt({
1005
- history,
1006
- agentOptions: this.options,
1007
- terms: this.getTerms(),
1008
- guidelines: this.getGuidelines(),
1009
- context: effectiveContext,
1010
- session,
1011
- });
1012
- // Use agent-level tools only
1013
- availableTools = this.collectAvailableTools();
1014
- responseSchema = undefined;
1015
- }
1016
- // Generate message using AI provider (common for both route and no-route cases)
1017
- const result = await this.options.provider.generateMessage({
1018
- prompt: responsePrompt,
1019
- history,
1020
- context: effectiveContext,
1021
- tools: availableTools,
1022
- signal,
1023
- parameters: responseSchema
1024
- ? {
1025
- jsonSchema: responseSchema,
1026
- schemaName: "response_output",
1027
- }
1028
- : undefined,
1029
- });
1030
- message = result.structured?.message || result.message;
1031
- // Process dynamic tool calls from AI response (common for both route and no-route cases)
1032
- if (result.structured?.toolCalls) {
1033
- toolCalls = result.structured.toolCalls;
1034
- // Execute dynamic tool calls
1035
- if (toolCalls.length > 0) {
1036
- utils_1.logger.debug(`[Agent] Executing ${toolCalls.length} dynamic tool calls`);
1037
- for (const toolCall of toolCalls) {
1038
- const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
1039
- if (!tool) {
1040
- utils_1.logger.warn(`[Agent] Tool not found: ${toolCall.toolName}`);
1041
- continue;
1042
- }
1043
- const toolExecutor = new ToolExecutor_1.ToolExecutor();
1044
- const toolResult = await toolExecutor.executeTool({
1045
- tool: tool,
1046
- context: effectiveContext,
1047
- updateContext: this.updateContext.bind(this),
1048
- updateData: this.updateCollectedData.bind(this),
1049
- history,
1050
- data: session.data,
1051
- toolArguments: toolCall.arguments,
1052
- });
1053
- // Update context with tool results
1054
- if (toolResult.contextUpdate) {
1055
- await this.updateContext(toolResult.contextUpdate);
1056
- }
1057
- // Update collected data with tool results
1058
- if (toolResult.dataUpdate) {
1059
- session = await this.updateData(session, toolResult.dataUpdate);
1060
- utils_1.logger.debug(`[Agent] Tool updated collected data:`, toolResult.dataUpdate);
1061
- }
1062
- utils_1.logger.debug(`[Agent] Executed dynamic tool: ${toolResult.toolName} (success: ${toolResult.success})`);
1063
- }
1064
- }
1065
- }
1066
- // TOOL LOOP: Allow AI to make follow-up tool calls after initial tool execution
1067
- const MAX_TOOL_LOOPS = 5;
1068
- let toolLoopCount = 0;
1069
- let hasToolCalls = toolCalls && toolCalls.length > 0;
1070
- while (hasToolCalls && toolLoopCount < MAX_TOOL_LOOPS) {
1071
- toolLoopCount++;
1072
- utils_1.logger.debug(`[Agent] Starting tool loop ${toolLoopCount}/${MAX_TOOL_LOOPS}`);
1073
- // Add tool execution results to history so AI knows what happened
1074
- const toolResultsEvents = [];
1075
- for (const toolCall of toolCalls || []) {
1076
- const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
1077
- if (tool) {
1078
- toolResultsEvents.push({
1079
- kind: history_1.EventKind.TOOL,
1080
- source: history_1.MessageRole.AGENT,
1081
- timestamp: new Date().toISOString(),
1082
- data: {
1083
- tool_calls: [
1084
- {
1085
- tool_id: toolCall.toolName,
1086
- arguments: toolCall.arguments,
1087
- result: {
1088
- data: "Tool executed successfully",
1089
- },
1090
- },
1091
- ],
1092
- },
1093
- });
1094
- }
1095
- }
1096
- // Create updated history with tool results
1097
- const updatedHistory = [...history, ...toolResultsEvents];
1098
- // Make follow-up AI call to see if more tools are needed
1099
- const followUpResult = await this.options.provider.generateMessage({
1100
- prompt: responsePrompt,
1101
- history: updatedHistory,
1102
- context: effectiveContext,
1103
- tools: availableTools,
1104
- parameters: {
1105
- jsonSchema: responseSchema,
1106
- schemaName: "tool_followup",
1107
- },
1108
- signal,
1109
- });
1110
- // Check if follow-up call has more tool calls
1111
- const followUpToolCalls = followUpResult.structured?.toolCalls;
1112
- hasToolCalls = followUpToolCalls && followUpToolCalls.length > 0;
1113
- if (hasToolCalls) {
1114
- utils_1.logger.debug(`[Agent] Follow-up call produced ${followUpToolCalls.length} additional tool calls`);
1115
- // Execute the follow-up tool calls
1116
- for (const toolCall of followUpToolCalls) {
1117
- const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
1118
- if (!tool) {
1119
- utils_1.logger.warn(`[Agent] Tool not found in follow-up: ${toolCall.toolName}`);
1120
- continue;
1121
- }
1122
- const toolExecutor = new ToolExecutor_1.ToolExecutor();
1123
- const toolResult = await toolExecutor.executeTool({
1124
- tool: tool,
1125
- context: effectiveContext,
1126
- updateContext: this.updateContext.bind(this),
1127
- updateData: this.updateCollectedData.bind(this),
1128
- history: updatedHistory,
1129
- data: session.data,
1130
- toolArguments: toolCall.arguments,
1131
- });
1132
- // Update context with follow-up tool results
1133
- if (toolResult.contextUpdate) {
1134
- await this.updateContext(toolResult.contextUpdate);
1135
- }
1136
- if (toolResult.dataUpdate) {
1137
- session = await this.updateData(session, toolResult.dataUpdate);
1138
- utils_1.logger.debug(`[Agent] Follow-up tool updated collected data:`, toolResult.dataUpdate);
1139
- }
1140
- utils_1.logger.debug(`[Agent] Executed follow-up tool: ${toolResult.toolName} (success: ${toolResult.success})`);
1141
- }
1142
- // Update toolCalls for next iteration or final response
1143
- toolCalls = followUpToolCalls;
1144
- }
1145
- else {
1146
- utils_1.logger.debug(`[Agent] Tool loop completed after ${toolLoopCount} iterations`);
1147
- // Update final message and toolCalls from follow-up result if no more tools
1148
- message = followUpResult.structured?.message || followUpResult.message;
1149
- toolCalls = followUpToolCalls || [];
1150
- break;
1151
- }
1152
- }
1153
- if (toolLoopCount >= MAX_TOOL_LOOPS) {
1154
- utils_1.logger.warn(`[Agent] Tool loop limit reached (${MAX_TOOL_LOOPS}), stopping`);
1155
- }
1156
- // Extract collected data from final response (only for route-based interactions)
1157
- if (selectedRoute && result.structured && nextStep?.collect) {
1158
- const collectedData = {};
1159
- // The structured response includes both base fields and collected extraction fields
1160
- const structuredData = result.structured;
1161
- for (const field of nextStep.collect) {
1162
- const fieldKey = String(field);
1163
- if (fieldKey in structuredData) {
1164
- collectedData[fieldKey] = structuredData[fieldKey];
1165
- }
1166
- }
1167
- // Merge collected data into session using agent-level data validation
1168
- if (Object.keys(collectedData).length > 0) {
1169
- // Update agent-level collected data with validation
1170
- await this.updateCollectedData(collectedData);
1171
- // Update session with validated data
1172
- session = await this.updateData(session, collectedData);
1173
- utils_1.logger.debug(`[Agent] Collected data:`, collectedData);
1174
- }
1175
- }
1176
- // Extract any additional data from structured response
1177
- if (result.structured &&
1178
- typeof result.structured === "object" &&
1179
- "contextUpdate" in result.structured) {
1180
- await this.updateContext(result.structured
1181
- .contextUpdate);
1182
- }
1183
- // Handle route completion if route is complete
1184
- if (isRouteComplete) {
1185
- // Route is complete - generate completion message then check for onComplete transition
1186
- // Get endStep spec from route
1187
- const endStepSpec = selectedRoute.endStepSpec;
1188
- // Create a temporary step for completion message generation using endStep configuration
1189
- const completionStep = new Step_1.Step(selectedRoute.id, {
1190
- description: endStepSpec.description,
1191
- id: endStepSpec.id || constants_1.END_ROUTE_ID,
1192
- collect: endStepSpec.collect,
1193
- requires: endStepSpec.requires,
1194
- prompt: endStepSpec.prompt ||
1195
- "Summarize what was accomplished and confirm completion based on the conversation history and collected data",
1196
- });
1197
- if (!selectedRoute) {
1198
- throw new Error("Selected route is not defined");
1199
- }
1200
- // Build response schema for completion
1201
- const responseSchema = this.responseEngine.responseSchemaForRoute(selectedRoute, completionStep, this.schema);
1202
- const templateContext = {
1203
- context: effectiveContext,
1204
- session,
1205
- history,
1206
- };
1207
- // Build completion response prompt
1208
- const completionPrompt = await this.responseEngine.buildResponsePrompt({
1209
- route: selectedRoute,
1210
- currentStep: completionStep,
1211
- rules: selectedRoute.getRules(),
1212
- prohibitions: selectedRoute.getProhibitions(),
1213
- directives: undefined, // No directives for completion
1214
- history,
1215
- lastMessage: lastUserMessage,
1216
- agentOptions: this.options,
1217
- // Combine agent and route properties according to the specified logic
1218
- combinedGuidelines: [
1219
- ...this.getGuidelines(),
1220
- ...selectedRoute.getGuidelines(),
1221
- ],
1222
- combinedTerms: this.mergeTerms(this.getTerms(), selectedRoute.getTerms()),
1223
- context: effectiveContext,
1224
- session,
1225
- agentSchema: this.schema,
1226
- });
1227
- // Generate completion message using AI provider
1228
- const completionResult = await this.options.provider.generateMessage({
1229
- prompt: completionPrompt,
1230
- history,
1231
- context: effectiveContext,
1232
- signal,
1233
- parameters: {
1234
- jsonSchema: responseSchema,
1235
- schemaName: "completion_message",
1236
- },
1237
- });
1238
- message =
1239
- completionResult.structured?.message || completionResult.message;
1240
- utils_1.logger.debug(`[Agent] Generated completion message for route: ${selectedRoute.title}`);
1241
- // Check for onComplete transition
1242
- const transitionConfig = await selectedRoute.evaluateOnComplete({ data: session.data }, effectiveContext);
1243
- if (transitionConfig) {
1244
- // Find target route by ID or title
1245
- const targetRoute = this.routes.find((r) => r.id === transitionConfig.nextStep ||
1246
- r.title === transitionConfig.nextStep);
1247
- if (targetRoute) {
1248
- const renderedCondition = await (0, utils_1.render)(transitionConfig.condition, templateContext);
1249
- // Set pending transition in session
1250
- session = {
1251
- ...session,
1252
- pendingTransition: {
1253
- targetRouteId: targetRoute.id,
1254
- condition: renderedCondition,
1255
- reason: "route_complete",
1256
- },
1257
- };
1258
- utils_1.logger.debug(`[Agent] Route ${selectedRoute.title} completed with pending transition to: ${targetRoute.title}`);
1259
- }
1260
- else {
1261
- utils_1.logger.warn(`[Agent] Route ${selectedRoute.title} completed but target route not found: ${transitionConfig.nextStep}`);
1262
- }
1263
- }
1264
- // Set step to END_ROUTE marker
1265
- session = (0, utils_1.enterStep)(session, constants_1.END_ROUTE_ID, "Route completed");
1266
- utils_1.logger.debug(`[Agent] Route ${selectedRoute.title} completed. Entered END_ROUTE step.`);
1267
- }
1268
- else {
1269
- // Fallback: No routes defined, generate a simple response
1270
- const fallbackPrompt = await this.responseEngine.buildFallbackPrompt({
1271
- history,
1272
- agentOptions: this.options,
1273
- terms: this.terms,
1274
- guidelines: this.guidelines,
1275
- context: effectiveContext,
1276
- session,
1277
- });
1278
- const result = await this.options.provider.generateMessage({
1279
- prompt: fallbackPrompt,
1280
- history,
1281
- context: effectiveContext,
1282
- signal,
1283
- parameters: {
1284
- jsonSchema: {
1285
- type: "object",
1286
- properties: {
1287
- message: { type: "string" },
1288
- },
1289
- required: ["message"],
1290
- additionalProperties: false,
1291
- },
1292
- schemaName: "fallback_response",
1293
- },
1294
- });
1295
- message = result.structured?.message || result.message;
1296
- }
1297
- // Auto-save session step to persistence if configured
1298
- if (this.persistenceManager &&
1299
- session.id &&
1300
- this.options.persistence?.autoSave !== false) {
1301
- await this.persistenceManager.saveSessionState(session.id, session);
1302
- utils_1.logger.debug(`[Agent] Auto-saved session step to persistence: ${session.id}`);
1303
- }
1304
- // Execute finalize function
1305
- if (session.currentRoute && session.currentStep) {
1306
- const currentRoute = this.routes.find((r) => r.id === session.currentRoute?.id);
1307
- if (currentRoute) {
1308
- const currentStep = currentRoute.getStep(session.currentStep.id);
1309
- if (currentStep?.finalize) {
1310
- utils_1.logger.debug(`[Agent] Executing finalize for step: ${currentStep.id}`);
1311
- await this.executePrepareFinalize(currentStep.finalize, effectiveContext, session.data, currentRoute, currentStep);
1312
- }
1313
- }
1314
- }
1315
- // Update current session if we have one
1316
- if (this.currentSession) {
1317
- this.currentSession = session;
1318
- }
1319
- return {
1320
- message,
1321
- session, // Return updated session with route/step info
1322
- toolCalls,
1323
- isRouteComplete, // Indicates if the route has reached END_ROUTE with all data collected
1324
- };
465
+ // Delegate to ResponseModal
466
+ return this.responseModal.respond(params);
1325
467
  }
1326
468
  /**
1327
469
  * Get all routes
@@ -1329,6 +471,27 @@ class Agent {
1329
471
  getRoutes() {
1330
472
  return [...this.routes];
1331
473
  }
474
+ /**
475
+ * Get agent options
476
+ * @internal Used by ResponseModal
477
+ */
478
+ getAgentOptions() {
479
+ return this.options;
480
+ }
481
+ /**
482
+ * Get routing engine
483
+ * @internal Used by ResponseModal
484
+ */
485
+ getRoutingEngine() {
486
+ return this.routingEngine;
487
+ }
488
+ /**
489
+ * Get the updateData method bound to this agent
490
+ * @internal Used by ResponseModal
491
+ */
492
+ getUpdateDataMethod() {
493
+ return this.updateData.bind(this);
494
+ }
1332
495
  /**
1333
496
  * Get all terms
1334
497
  */
@@ -1342,85 +505,45 @@ class Agent {
1342
505
  return [...this.tools];
1343
506
  }
1344
507
  /**
1345
- * Find an available tool by name for the given route
1346
- * Route-level tools take precedence over agent-level tools
1347
- * @private
508
+ * Get all guidelines
1348
509
  */
1349
- findAvailableTool(toolName, route) {
1350
- // Check route-level tools first (if route provided)
1351
- if (route) {
1352
- const routeTool = route
1353
- .getTools()
1354
- .find((tool) => tool.id === toolName || tool.name === toolName);
1355
- if (routeTool)
1356
- return routeTool;
1357
- }
1358
- // Fall back to agent-level tools
1359
- return this.tools.find((tool) => tool.id === toolName || tool.name === toolName);
510
+ getGuidelines() {
511
+ return [...this.guidelines];
1360
512
  }
1361
513
  /**
1362
- * Collect all available tools for the given route and step context
1363
- * @private
514
+ * Get the agent's knowledge base
1364
515
  */
1365
- collectAvailableTools(route, step) {
1366
- const availableTools = new Map();
1367
- // Add agent-level tools
1368
- this.tools.forEach((tool) => {
1369
- availableTools.set(tool.id, tool);
1370
- });
1371
- // Add route-level tools (these take precedence)
1372
- if (route) {
1373
- route.getTools().forEach((tool) => {
1374
- availableTools.set(tool.id, tool);
1375
- });
1376
- }
1377
- // Filter by step-level allowed tools if specified
1378
- if (step?.tools) {
1379
- const allowedToolIds = new Set();
1380
- const stepTools = [];
1381
- for (const toolRef of step.tools) {
1382
- if (typeof toolRef === "string") {
1383
- // Reference to registered tool
1384
- allowedToolIds.add(toolRef);
1385
- }
1386
- else {
1387
- // Inline tool definition
1388
- if (toolRef.id) {
1389
- allowedToolIds.add(toolRef.id);
1390
- stepTools.push(toolRef);
1391
- }
1392
- }
1393
- }
1394
- // If step specifies tools, only include those
1395
- if (allowedToolIds.size > 0) {
1396
- const filteredTools = new Map();
1397
- for (const toolId of allowedToolIds) {
1398
- const tool = availableTools.get(toolId);
1399
- if (tool) {
1400
- filteredTools.set(toolId, tool);
1401
- }
1402
- }
1403
- // Add inline tools
1404
- stepTools.forEach((tool) => {
1405
- if (tool.id) {
1406
- filteredTools.set(tool.id, tool);
1407
- }
1408
- });
1409
- availableTools.clear();
1410
- filteredTools.forEach((tool, id) => availableTools.set(id, tool));
1411
- }
1412
- }
1413
- // Convert to the format expected by AI providers
1414
- return Array.from(availableTools.values()).map((tool) => ({
1415
- id: tool.id,
1416
- name: tool.name || tool.id,
1417
- description: tool.description,
1418
- parameters: tool.parameters,
1419
- }));
516
+ getKnowledgeBase() {
517
+ return { ...this.knowledgeBase };
518
+ }
519
+ /**
520
+ * Get the persistence manager (if configured)
521
+ */
522
+ getPersistenceManager() {
523
+ return this.persistenceManager;
524
+ }
525
+ /**
526
+ * Check if persistence is enabled
527
+ */
528
+ hasPersistence() {
529
+ return this.persistenceManager !== undefined;
530
+ }
531
+ /**
532
+ * Set the current session for convenience methods
533
+ * @param session - Session step to use for subsequent calls
534
+ */
535
+ setCurrentSession(session) {
536
+ this.currentSession = session;
537
+ }
538
+ /**
539
+ * Get the current session (if set)
540
+ */
541
+ getCurrentSession() {
542
+ return this.currentSession;
1420
543
  }
1421
544
  /**
1422
545
  * Execute a prepare or finalize function/tool
1423
- * @private
546
+ * @internal Used by ResponseModal
1424
547
  */
1425
548
  async executePrepareFinalize(prepareOrFinalize, context, data, route, step) {
1426
549
  if (!prepareOrFinalize)
@@ -1433,38 +556,22 @@ class Agent {
1433
556
  // It's a tool reference - find and execute the tool
1434
557
  let tool;
1435
558
  if (typeof prepareOrFinalize === "string") {
1436
- // Tool ID - find it in available tools
1437
- const availableTools = new Map();
1438
- // Add agent-level tools
1439
- this.tools.forEach((t) => {
1440
- availableTools.set(t.id, t);
1441
- });
1442
- // Add route-level tools
1443
- if (route) {
1444
- route.getTools().forEach((t) => {
1445
- availableTools.set(t.id, t);
1446
- });
1447
- }
1448
- // Add step-level tools
1449
- if (step?.tools) {
1450
- for (const toolRef of step.tools) {
1451
- if (typeof toolRef === "string") {
1452
- // Keep as is
1453
- }
1454
- else if (toolRef.id) {
1455
- availableTools.set(toolRef.id, toolRef);
1456
- }
1457
- }
1458
- }
1459
- tool = availableTools.get(prepareOrFinalize);
559
+ // Tool ID - use ToolManager to find it across all scopes
560
+ tool = this.tool.find(prepareOrFinalize, undefined, step, route);
1460
561
  }
1461
562
  else {
1462
- // Tool object - use directly
1463
- tool = prepareOrFinalize;
563
+ // Tool object - validate it has required properties
564
+ if (prepareOrFinalize.id && typeof prepareOrFinalize.handler === 'function') {
565
+ tool = prepareOrFinalize;
566
+ }
567
+ else {
568
+ utils_1.logger.error(`[Agent] Invalid tool object for prepare/finalize: missing id or invalid handler`);
569
+ return;
570
+ }
1464
571
  }
1465
572
  if (tool) {
1466
- const toolExecutor = new ToolExecutor_1.ToolExecutor();
1467
- const result = await toolExecutor.executeTool({
573
+ // Use ToolManager for execution
574
+ const result = await this.tool.executeTool({
1468
575
  tool,
1469
576
  context,
1470
577
  updateContext: this.updateContext.bind(this),
@@ -1484,73 +591,31 @@ class Agent {
1484
591
  }
1485
592
  }
1486
593
  }
1487
- /**
1488
- * Get all guidelines
1489
- */
1490
- getGuidelines() {
1491
- return [...this.guidelines];
1492
- }
1493
- /**
1494
- * Get the agent's knowledge base
1495
- */
1496
- getKnowledgeBase() {
1497
- return { ...this.knowledgeBase };
1498
- }
1499
- /**
1500
- * Merge terms with route-specific taking precedence on conflicts
1501
- * @private
1502
- */
1503
- mergeTerms(agentTerms, routeTerms) {
1504
- const merged = new Map();
1505
- // Add agent terms first
1506
- agentTerms.forEach((term) => {
1507
- const name = typeof term.name === "string" ? term.name : term.name.toString();
1508
- merged.set(name, term);
1509
- });
1510
- // Add route terms (these take precedence)
1511
- routeTerms.forEach((term) => {
1512
- const name = typeof term.name === "string" ? term.name : term.name.toString();
1513
- merged.set(name, term);
1514
- });
1515
- return Array.from(merged.values());
1516
- }
1517
- /**
1518
- * Get the persistence manager (if configured)
1519
- */
1520
- getPersistenceManager() {
1521
- return this.persistenceManager;
1522
- }
1523
- /**
1524
- * Check if persistence is enabled
1525
- */
1526
- hasPersistence() {
1527
- return this.persistenceManager !== undefined;
1528
- }
1529
- /**
1530
- * Set the current session for convenience methods
1531
- * @param session - Session step to use for subsequent calls
1532
- */
1533
- setCurrentSession(session) {
1534
- this.currentSession = session;
1535
- }
1536
- /**
1537
- * Get the current session (if set)
1538
- */
1539
- getCurrentSession() {
1540
- return this.currentSession;
1541
- }
1542
594
  /**
1543
595
  * Clear the current session
1544
596
  */
1545
597
  clearCurrentSession() {
1546
598
  this.currentSession = undefined;
1547
599
  }
600
+ /**
601
+ * Sync session data to agent collected data
602
+ * @internal Used to keep agent and session data in sync
603
+ */
604
+ syncSessionDataToCollectedData() {
605
+ const sessionData = this.session.getData();
606
+ if (sessionData && Object.keys(sessionData).length > 0) {
607
+ this.collectedData = { ...sessionData };
608
+ utils_1.logger.debug("[Agent] Synced session data to collected data:", this.collectedData);
609
+ }
610
+ }
1548
611
  /**
1549
612
  * Get collected data from current session or agent-level collected data
1550
613
  * @param routeId - Optional route ID to get data for (uses current route if not provided)
1551
614
  * @returns The collected data from the current session or agent-level data
1552
615
  */
1553
616
  getData() {
617
+ // Ensure agent collected data is synced with session
618
+ this.syncSessionDataToCollectedData();
1554
619
  // If we have a current session, use session data
1555
620
  if (this.currentSession) {
1556
621
  // With agent-level data, all routes share the same data structure
@@ -1616,44 +681,20 @@ class Agent {
1616
681
  * Automatically manages conversation history through the session
1617
682
  */
1618
683
  async chat(message, options) {
1619
- // Determine which history to use
1620
- let history;
1621
- if (options?.history) {
1622
- // Use provided history for this response only
1623
- history = options.history;
1624
- }
1625
- else {
1626
- // Add user message to session history if provided
1627
- if (message) {
1628
- await this.session.addMessage("user", message);
1629
- }
1630
- history = this.session.getHistory();
1631
- }
1632
- // Get or create session
1633
- let session = await this.session.getOrCreate();
1634
- // Merge agent's collected data into session (agent data takes precedence)
1635
- if (Object.keys(this.collectedData).length > 0) {
1636
- session = (0, utils_1.mergeCollected)(session, this.collectedData);
1637
- // Update the session manager with the merged data
1638
- await this.session.setData(this.collectedData);
1639
- utils_1.logger.debug("[Agent] Merged agent collected data into chat session:", this.collectedData);
1640
- }
1641
- // Use existing respond method with session-managed history
1642
- const result = await this.respond({
1643
- history,
1644
- session,
684
+ // Delegate to ResponseModal.generate()
685
+ return this.responseModal.generate(message, options);
686
+ }
687
+ /**
688
+ * Modern streaming API - simple interface like chat() but returns a stream
689
+ * Automatically manages conversation history through the session
690
+ */
691
+ async *stream(message, options) {
692
+ // Delegate to ResponseModal with the same options structure as chat()
693
+ yield* this.responseModal.stream(message, {
694
+ history: options?.history,
1645
695
  contextOverride: options?.contextOverride,
1646
696
  signal: options?.signal,
1647
697
  });
1648
- // Add agent response to session history (only if not using override history)
1649
- if (!options?.history) {
1650
- await this.session.addMessage("assistant", result.message);
1651
- }
1652
- // Ensure the result includes the current session
1653
- return {
1654
- ...result,
1655
- session: result.session || this.session.current,
1656
- };
1657
698
  }
1658
699
  }
1659
700
  exports.Agent = Agent;