@falai/agent 2.2.3 → 2.4.1

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 (397) hide show
  1. package/README.md +2 -2
  2. package/dist/adapters/MemoryAdapter.d.ts.map +1 -1
  3. package/dist/adapters/MemoryAdapter.js +10 -1
  4. package/dist/adapters/MemoryAdapter.js.map +1 -1
  5. package/dist/adapters/MongoAdapter.d.ts.map +1 -1
  6. package/dist/adapters/MongoAdapter.js +33 -2
  7. package/dist/adapters/MongoAdapter.js.map +1 -1
  8. package/dist/adapters/OpenSearchAdapter.d.ts.map +1 -1
  9. package/dist/adapters/OpenSearchAdapter.js +15 -1
  10. package/dist/adapters/OpenSearchAdapter.js.map +1 -1
  11. package/dist/adapters/PostgreSQLAdapter.d.ts.map +1 -1
  12. package/dist/adapters/PostgreSQLAdapter.js +35 -5
  13. package/dist/adapters/PostgreSQLAdapter.js.map +1 -1
  14. package/dist/adapters/PrismaAdapter.d.ts.map +1 -1
  15. package/dist/adapters/PrismaAdapter.js +58 -15
  16. package/dist/adapters/PrismaAdapter.js.map +1 -1
  17. package/dist/adapters/RedisAdapter.d.ts.map +1 -1
  18. package/dist/adapters/RedisAdapter.js +10 -1
  19. package/dist/adapters/RedisAdapter.js.map +1 -1
  20. package/dist/adapters/SQLiteAdapter.d.ts.map +1 -1
  21. package/dist/adapters/SQLiteAdapter.js +38 -6
  22. package/dist/adapters/SQLiteAdapter.js.map +1 -1
  23. package/dist/cjs/adapters/MemoryAdapter.d.ts.map +1 -1
  24. package/dist/cjs/adapters/MemoryAdapter.js +10 -1
  25. package/dist/cjs/adapters/MemoryAdapter.js.map +1 -1
  26. package/dist/cjs/adapters/MongoAdapter.d.ts.map +1 -1
  27. package/dist/cjs/adapters/MongoAdapter.js +33 -2
  28. package/dist/cjs/adapters/MongoAdapter.js.map +1 -1
  29. package/dist/cjs/adapters/OpenSearchAdapter.d.ts.map +1 -1
  30. package/dist/cjs/adapters/OpenSearchAdapter.js +15 -1
  31. package/dist/cjs/adapters/OpenSearchAdapter.js.map +1 -1
  32. package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +1 -1
  33. package/dist/cjs/adapters/PostgreSQLAdapter.js +35 -5
  34. package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -1
  35. package/dist/cjs/adapters/PrismaAdapter.d.ts.map +1 -1
  36. package/dist/cjs/adapters/PrismaAdapter.js +58 -15
  37. package/dist/cjs/adapters/PrismaAdapter.js.map +1 -1
  38. package/dist/cjs/adapters/RedisAdapter.d.ts.map +1 -1
  39. package/dist/cjs/adapters/RedisAdapter.js +10 -1
  40. package/dist/cjs/adapters/RedisAdapter.js.map +1 -1
  41. package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +1 -1
  42. package/dist/cjs/adapters/SQLiteAdapter.js +38 -6
  43. package/dist/cjs/adapters/SQLiteAdapter.js.map +1 -1
  44. package/dist/cjs/core/Agent.d.ts +28 -18
  45. package/dist/cjs/core/Agent.d.ts.map +1 -1
  46. package/dist/cjs/core/Agent.js +58 -68
  47. package/dist/cjs/core/Agent.js.map +1 -1
  48. package/dist/cjs/core/AutoChainExecutor.d.ts.map +1 -1
  49. package/dist/cjs/core/AutoChainExecutor.js +14 -20
  50. package/dist/cjs/core/AutoChainExecutor.js.map +1 -1
  51. package/dist/cjs/core/BranchEvaluator.d.ts +4 -3
  52. package/dist/cjs/core/BranchEvaluator.d.ts.map +1 -1
  53. package/dist/cjs/core/BranchEvaluator.js +18 -23
  54. package/dist/cjs/core/BranchEvaluator.js.map +1 -1
  55. package/dist/cjs/core/Flow.d.ts +2 -1
  56. package/dist/cjs/core/Flow.d.ts.map +1 -1
  57. package/dist/cjs/core/Flow.js +10 -3
  58. package/dist/cjs/core/Flow.js.map +1 -1
  59. package/dist/cjs/core/FlowRouter.d.ts +1 -0
  60. package/dist/cjs/core/FlowRouter.d.ts.map +1 -1
  61. package/dist/cjs/core/FlowRouter.js +28 -5
  62. package/dist/cjs/core/FlowRouter.js.map +1 -1
  63. package/dist/cjs/core/PersistenceManager.d.ts +3 -0
  64. package/dist/cjs/core/PersistenceManager.d.ts.map +1 -1
  65. package/dist/cjs/core/PersistenceManager.js +57 -5
  66. package/dist/cjs/core/PersistenceManager.js.map +1 -1
  67. package/dist/cjs/core/PromptComposer.d.ts.map +1 -1
  68. package/dist/cjs/core/PromptComposer.js +24 -10
  69. package/dist/cjs/core/PromptComposer.js.map +1 -1
  70. package/dist/cjs/core/ResponseGenerationError.d.ts +30 -0
  71. package/dist/cjs/core/ResponseGenerationError.d.ts.map +1 -0
  72. package/dist/cjs/core/ResponseGenerationError.js +37 -0
  73. package/dist/cjs/core/ResponseGenerationError.js.map +1 -0
  74. package/dist/cjs/core/ResponseModal.d.ts +43 -96
  75. package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
  76. package/dist/cjs/core/ResponseModal.js +184 -1183
  77. package/dist/cjs/core/ResponseModal.js.map +1 -1
  78. package/dist/cjs/core/ResponsePipeline.d.ts +58 -152
  79. package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -1
  80. package/dist/cjs/core/ResponsePipeline.js +408 -457
  81. package/dist/cjs/core/ResponsePipeline.js.map +1 -1
  82. package/dist/cjs/core/SessionFinalizer.d.ts +34 -0
  83. package/dist/cjs/core/SessionFinalizer.d.ts.map +1 -0
  84. package/dist/cjs/core/SessionFinalizer.js +61 -0
  85. package/dist/cjs/core/SessionFinalizer.js.map +1 -0
  86. package/dist/cjs/core/SessionManager.d.ts +1 -1
  87. package/dist/cjs/core/SessionManager.d.ts.map +1 -1
  88. package/dist/cjs/core/SessionManager.js +18 -7
  89. package/dist/cjs/core/SessionManager.js.map +1 -1
  90. package/dist/cjs/core/SignalCoordinator.d.ts +103 -0
  91. package/dist/cjs/core/SignalCoordinator.d.ts.map +1 -0
  92. package/dist/cjs/core/SignalCoordinator.js +207 -0
  93. package/dist/cjs/core/SignalCoordinator.js.map +1 -0
  94. package/dist/cjs/core/SignalEvaluator.d.ts +2 -2
  95. package/dist/cjs/core/SignalEvaluator.d.ts.map +1 -1
  96. package/dist/cjs/core/SignalEvaluator.js +13 -27
  97. package/dist/cjs/core/SignalEvaluator.js.map +1 -1
  98. package/dist/cjs/core/SignalProcessor.d.ts.map +1 -1
  99. package/dist/cjs/core/SignalProcessor.js +1 -3
  100. package/dist/cjs/core/SignalProcessor.js.map +1 -1
  101. package/dist/cjs/core/Step.d.ts +3 -1
  102. package/dist/cjs/core/Step.d.ts.map +1 -1
  103. package/dist/cjs/core/Step.js +10 -3
  104. package/dist/cjs/core/Step.js.map +1 -1
  105. package/dist/cjs/core/StepLifecycle.d.ts +33 -0
  106. package/dist/cjs/core/StepLifecycle.d.ts.map +1 -0
  107. package/dist/cjs/core/StepLifecycle.js +97 -0
  108. package/dist/cjs/core/StepLifecycle.js.map +1 -0
  109. package/dist/cjs/core/ToolLoopExecutor.d.ts +104 -0
  110. package/dist/cjs/core/ToolLoopExecutor.d.ts.map +1 -0
  111. package/dist/cjs/core/ToolLoopExecutor.js +391 -0
  112. package/dist/cjs/core/ToolLoopExecutor.js.map +1 -0
  113. package/dist/cjs/core/ToolManager.d.ts +1 -1
  114. package/dist/cjs/core/ToolManager.d.ts.map +1 -1
  115. package/dist/cjs/core/ToolManager.js.map +1 -1
  116. package/dist/cjs/index.d.ts +4 -5
  117. package/dist/cjs/index.d.ts.map +1 -1
  118. package/dist/cjs/index.js +7 -6
  119. package/dist/cjs/index.js.map +1 -1
  120. package/dist/cjs/providers/AnthropicProvider.d.ts +2 -0
  121. package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
  122. package/dist/cjs/providers/AnthropicProvider.js +28 -56
  123. package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
  124. package/dist/cjs/providers/DeepSeekProvider.d.ts +25 -21
  125. package/dist/cjs/providers/DeepSeekProvider.d.ts.map +1 -1
  126. package/dist/cjs/providers/DeepSeekProvider.js +48 -407
  127. package/dist/cjs/providers/DeepSeekProvider.js.map +1 -1
  128. package/dist/cjs/providers/GeminiProvider.d.ts +2 -0
  129. package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
  130. package/dist/cjs/providers/GeminiProvider.js +27 -56
  131. package/dist/cjs/providers/GeminiProvider.js.map +1 -1
  132. package/dist/cjs/providers/OpenAICompatibleProvider.d.ts +129 -0
  133. package/dist/cjs/providers/OpenAICompatibleProvider.d.ts.map +1 -0
  134. package/dist/cjs/providers/OpenAICompatibleProvider.js +485 -0
  135. package/dist/cjs/providers/OpenAICompatibleProvider.js.map +1 -0
  136. package/dist/cjs/providers/OpenAIProvider.d.ts +9 -28
  137. package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
  138. package/dist/cjs/providers/OpenAIProvider.js +23 -417
  139. package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
  140. package/dist/cjs/providers/OpenRouterProvider.d.ts +10 -27
  141. package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -1
  142. package/dist/cjs/providers/OpenRouterProvider.js +28 -417
  143. package/dist/cjs/providers/OpenRouterProvider.js.map +1 -1
  144. package/dist/cjs/providers/errorClassification.d.ts +61 -0
  145. package/dist/cjs/providers/errorClassification.d.ts.map +1 -0
  146. package/dist/cjs/providers/errorClassification.js +123 -0
  147. package/dist/cjs/providers/errorClassification.js.map +1 -0
  148. package/dist/cjs/providers/index.d.ts +4 -0
  149. package/dist/cjs/providers/index.d.ts.map +1 -1
  150. package/dist/cjs/providers/index.js +7 -1
  151. package/dist/cjs/providers/index.js.map +1 -1
  152. package/dist/cjs/types/agent.d.ts +2 -1
  153. package/dist/cjs/types/agent.d.ts.map +1 -1
  154. package/dist/cjs/types/ai.d.ts +20 -0
  155. package/dist/cjs/types/ai.d.ts.map +1 -1
  156. package/dist/cjs/types/errors.d.ts +33 -0
  157. package/dist/cjs/types/errors.d.ts.map +1 -1
  158. package/dist/cjs/types/errors.js +37 -1
  159. package/dist/cjs/types/errors.js.map +1 -1
  160. package/dist/cjs/types/flow.d.ts +10 -5
  161. package/dist/cjs/types/flow.d.ts.map +1 -1
  162. package/dist/cjs/types/history.d.ts +1 -1
  163. package/dist/cjs/types/history.d.ts.map +1 -1
  164. package/dist/cjs/types/index.d.ts +5 -4
  165. package/dist/cjs/types/index.d.ts.map +1 -1
  166. package/dist/cjs/types/index.js +3 -1
  167. package/dist/cjs/types/index.js.map +1 -1
  168. package/dist/cjs/types/persistence.d.ts +43 -2
  169. package/dist/cjs/types/persistence.d.ts.map +1 -1
  170. package/dist/cjs/types/session.d.ts +8 -0
  171. package/dist/cjs/types/session.d.ts.map +1 -1
  172. package/dist/cjs/types/signals.d.ts +21 -11
  173. package/dist/cjs/types/signals.d.ts.map +1 -1
  174. package/dist/cjs/types/template.d.ts +3 -1
  175. package/dist/cjs/types/template.d.ts.map +1 -1
  176. package/dist/cjs/types/tool.d.ts +4 -4
  177. package/dist/cjs/types/tool.d.ts.map +1 -1
  178. package/dist/cjs/utils/condition.d.ts +19 -0
  179. package/dist/cjs/utils/condition.d.ts.map +1 -1
  180. package/dist/cjs/utils/condition.js +86 -15
  181. package/dist/cjs/utils/condition.js.map +1 -1
  182. package/dist/cjs/utils/session.d.ts +1 -0
  183. package/dist/cjs/utils/session.d.ts.map +1 -1
  184. package/dist/cjs/utils/session.js +1 -0
  185. package/dist/cjs/utils/session.js.map +1 -1
  186. package/dist/core/Agent.d.ts +28 -18
  187. package/dist/core/Agent.d.ts.map +1 -1
  188. package/dist/core/Agent.js +58 -68
  189. package/dist/core/Agent.js.map +1 -1
  190. package/dist/core/AutoChainExecutor.d.ts.map +1 -1
  191. package/dist/core/AutoChainExecutor.js +14 -20
  192. package/dist/core/AutoChainExecutor.js.map +1 -1
  193. package/dist/core/BranchEvaluator.d.ts +4 -3
  194. package/dist/core/BranchEvaluator.d.ts.map +1 -1
  195. package/dist/core/BranchEvaluator.js +18 -23
  196. package/dist/core/BranchEvaluator.js.map +1 -1
  197. package/dist/core/Flow.d.ts +2 -1
  198. package/dist/core/Flow.d.ts.map +1 -1
  199. package/dist/core/Flow.js +10 -3
  200. package/dist/core/Flow.js.map +1 -1
  201. package/dist/core/FlowRouter.d.ts +1 -0
  202. package/dist/core/FlowRouter.d.ts.map +1 -1
  203. package/dist/core/FlowRouter.js +28 -5
  204. package/dist/core/FlowRouter.js.map +1 -1
  205. package/dist/core/PersistenceManager.d.ts +3 -0
  206. package/dist/core/PersistenceManager.d.ts.map +1 -1
  207. package/dist/core/PersistenceManager.js +58 -6
  208. package/dist/core/PersistenceManager.js.map +1 -1
  209. package/dist/core/PromptComposer.d.ts.map +1 -1
  210. package/dist/core/PromptComposer.js +24 -10
  211. package/dist/core/PromptComposer.js.map +1 -1
  212. package/dist/core/ResponseGenerationError.d.ts +30 -0
  213. package/dist/core/ResponseGenerationError.d.ts.map +1 -0
  214. package/dist/core/ResponseGenerationError.js +33 -0
  215. package/dist/core/ResponseGenerationError.js.map +1 -0
  216. package/dist/core/ResponseModal.d.ts +43 -96
  217. package/dist/core/ResponseModal.d.ts.map +1 -1
  218. package/dist/core/ResponseModal.js +177 -1175
  219. package/dist/core/ResponseModal.js.map +1 -1
  220. package/dist/core/ResponsePipeline.d.ts +58 -152
  221. package/dist/core/ResponsePipeline.d.ts.map +1 -1
  222. package/dist/core/ResponsePipeline.js +409 -458
  223. package/dist/core/ResponsePipeline.js.map +1 -1
  224. package/dist/core/SessionFinalizer.d.ts +34 -0
  225. package/dist/core/SessionFinalizer.d.ts.map +1 -0
  226. package/dist/core/SessionFinalizer.js +57 -0
  227. package/dist/core/SessionFinalizer.js.map +1 -0
  228. package/dist/core/SessionManager.d.ts +1 -1
  229. package/dist/core/SessionManager.d.ts.map +1 -1
  230. package/dist/core/SessionManager.js +18 -7
  231. package/dist/core/SessionManager.js.map +1 -1
  232. package/dist/core/SignalCoordinator.d.ts +103 -0
  233. package/dist/core/SignalCoordinator.d.ts.map +1 -0
  234. package/dist/core/SignalCoordinator.js +203 -0
  235. package/dist/core/SignalCoordinator.js.map +1 -0
  236. package/dist/core/SignalEvaluator.d.ts +2 -2
  237. package/dist/core/SignalEvaluator.d.ts.map +1 -1
  238. package/dist/core/SignalEvaluator.js +13 -27
  239. package/dist/core/SignalEvaluator.js.map +1 -1
  240. package/dist/core/SignalProcessor.d.ts.map +1 -1
  241. package/dist/core/SignalProcessor.js +1 -3
  242. package/dist/core/SignalProcessor.js.map +1 -1
  243. package/dist/core/Step.d.ts +3 -1
  244. package/dist/core/Step.d.ts.map +1 -1
  245. package/dist/core/Step.js +10 -3
  246. package/dist/core/Step.js.map +1 -1
  247. package/dist/core/StepLifecycle.d.ts +33 -0
  248. package/dist/core/StepLifecycle.d.ts.map +1 -0
  249. package/dist/core/StepLifecycle.js +93 -0
  250. package/dist/core/StepLifecycle.js.map +1 -0
  251. package/dist/core/ToolLoopExecutor.d.ts +104 -0
  252. package/dist/core/ToolLoopExecutor.d.ts.map +1 -0
  253. package/dist/core/ToolLoopExecutor.js +387 -0
  254. package/dist/core/ToolLoopExecutor.js.map +1 -0
  255. package/dist/core/ToolManager.d.ts +1 -1
  256. package/dist/core/ToolManager.d.ts.map +1 -1
  257. package/dist/core/ToolManager.js.map +1 -1
  258. package/dist/index.d.ts +4 -5
  259. package/dist/index.d.ts.map +1 -1
  260. package/dist/index.js +3 -3
  261. package/dist/index.js.map +1 -1
  262. package/dist/providers/AnthropicProvider.d.ts +2 -0
  263. package/dist/providers/AnthropicProvider.d.ts.map +1 -1
  264. package/dist/providers/AnthropicProvider.js +22 -50
  265. package/dist/providers/AnthropicProvider.js.map +1 -1
  266. package/dist/providers/DeepSeekProvider.d.ts +25 -21
  267. package/dist/providers/DeepSeekProvider.d.ts.map +1 -1
  268. package/dist/providers/DeepSeekProvider.js +49 -408
  269. package/dist/providers/DeepSeekProvider.js.map +1 -1
  270. package/dist/providers/GeminiProvider.d.ts +2 -0
  271. package/dist/providers/GeminiProvider.d.ts.map +1 -1
  272. package/dist/providers/GeminiProvider.js +21 -50
  273. package/dist/providers/GeminiProvider.js.map +1 -1
  274. package/dist/providers/OpenAICompatibleProvider.d.ts +129 -0
  275. package/dist/providers/OpenAICompatibleProvider.d.ts.map +1 -0
  276. package/dist/providers/OpenAICompatibleProvider.js +481 -0
  277. package/dist/providers/OpenAICompatibleProvider.js.map +1 -0
  278. package/dist/providers/OpenAIProvider.d.ts +9 -28
  279. package/dist/providers/OpenAIProvider.d.ts.map +1 -1
  280. package/dist/providers/OpenAIProvider.js +23 -417
  281. package/dist/providers/OpenAIProvider.js.map +1 -1
  282. package/dist/providers/OpenRouterProvider.d.ts +10 -27
  283. package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
  284. package/dist/providers/OpenRouterProvider.js +28 -417
  285. package/dist/providers/OpenRouterProvider.js.map +1 -1
  286. package/dist/providers/errorClassification.d.ts +61 -0
  287. package/dist/providers/errorClassification.d.ts.map +1 -0
  288. package/dist/providers/errorClassification.js +116 -0
  289. package/dist/providers/errorClassification.js.map +1 -0
  290. package/dist/providers/index.d.ts +4 -0
  291. package/dist/providers/index.d.ts.map +1 -1
  292. package/dist/providers/index.js +2 -0
  293. package/dist/providers/index.js.map +1 -1
  294. package/dist/types/agent.d.ts +2 -1
  295. package/dist/types/agent.d.ts.map +1 -1
  296. package/dist/types/ai.d.ts +20 -0
  297. package/dist/types/ai.d.ts.map +1 -1
  298. package/dist/types/errors.d.ts +33 -0
  299. package/dist/types/errors.d.ts.map +1 -1
  300. package/dist/types/errors.js +34 -0
  301. package/dist/types/errors.js.map +1 -1
  302. package/dist/types/flow.d.ts +10 -5
  303. package/dist/types/flow.d.ts.map +1 -1
  304. package/dist/types/history.d.ts +1 -1
  305. package/dist/types/history.d.ts.map +1 -1
  306. package/dist/types/index.d.ts +5 -4
  307. package/dist/types/index.d.ts.map +1 -1
  308. package/dist/types/index.js +1 -1
  309. package/dist/types/index.js.map +1 -1
  310. package/dist/types/persistence.d.ts +43 -2
  311. package/dist/types/persistence.d.ts.map +1 -1
  312. package/dist/types/session.d.ts +8 -0
  313. package/dist/types/session.d.ts.map +1 -1
  314. package/dist/types/signals.d.ts +21 -11
  315. package/dist/types/signals.d.ts.map +1 -1
  316. package/dist/types/template.d.ts +3 -1
  317. package/dist/types/template.d.ts.map +1 -1
  318. package/dist/types/tool.d.ts +4 -4
  319. package/dist/types/tool.d.ts.map +1 -1
  320. package/dist/utils/condition.d.ts +19 -0
  321. package/dist/utils/condition.d.ts.map +1 -1
  322. package/dist/utils/condition.js +84 -15
  323. package/dist/utils/condition.js.map +1 -1
  324. package/dist/utils/session.d.ts +1 -0
  325. package/dist/utils/session.d.ts.map +1 -1
  326. package/dist/utils/session.js +1 -0
  327. package/dist/utils/session.js.map +1 -1
  328. package/docs/README.md +1 -1
  329. package/docs/guides/branching.md +1 -1
  330. package/docs/guides/compaction.md +12 -5
  331. package/docs/guides/conditions.md +15 -2
  332. package/docs/guides/error-handling.md +59 -9
  333. package/docs/guides/instructions.md +2 -2
  334. package/docs/guides/persistence.md +69 -2
  335. package/docs/guides/streaming.md +3 -1
  336. package/docs/migration/README.md +5 -1
  337. package/docs/migration/v2-3-to-v2-4.md +316 -0
  338. package/docs/reference/adapters.md +55 -7
  339. package/docs/reference/branches.md +2 -2
  340. package/docs/reference/create-agent.md +3 -3
  341. package/docs/reference/errors.md +66 -1
  342. package/docs/reference/flow.md +1 -1
  343. package/docs/reference/instruction.md +4 -4
  344. package/docs/reference/providers.md +100 -3
  345. package/docs/reference/signals.md +18 -8
  346. package/docs/reference/step.md +2 -2
  347. package/docs/reference/tool.md +3 -1
  348. package/docs/start/05-go-to-production.md +3 -0
  349. package/package.json +1 -1
  350. package/src/adapters/MemoryAdapter.ts +15 -1
  351. package/src/adapters/MongoAdapter.ts +41 -2
  352. package/src/adapters/OpenSearchAdapter.ts +24 -1
  353. package/src/adapters/PostgreSQLAdapter.ts +45 -5
  354. package/src/adapters/PrismaAdapter.ts +82 -16
  355. package/src/adapters/RedisAdapter.ts +19 -1
  356. package/src/adapters/SQLiteAdapter.ts +47 -5
  357. package/src/core/Agent.ts +70 -85
  358. package/src/core/AutoChainExecutor.ts +27 -57
  359. package/src/core/BranchEvaluator.ts +24 -30
  360. package/src/core/Flow.ts +10 -3
  361. package/src/core/FlowRouter.ts +36 -4
  362. package/src/core/PersistenceManager.ts +79 -10
  363. package/src/core/PromptComposer.ts +25 -12
  364. package/src/core/ResponseGenerationError.ts +56 -0
  365. package/src/core/ResponseModal.ts +241 -1465
  366. package/src/core/ResponsePipeline.ts +492 -662
  367. package/src/core/SessionFinalizer.ts +78 -0
  368. package/src/core/SessionManager.ts +21 -9
  369. package/src/core/SignalCoordinator.ts +263 -0
  370. package/src/core/SignalEvaluator.ts +14 -30
  371. package/src/core/SignalProcessor.ts +1 -4
  372. package/src/core/Step.ts +11 -3
  373. package/src/core/StepLifecycle.ts +139 -0
  374. package/src/core/ToolLoopExecutor.ts +492 -0
  375. package/src/core/ToolManager.ts +2 -1
  376. package/src/index.ts +7 -5
  377. package/src/providers/AnthropicProvider.ts +30 -72
  378. package/src/providers/DeepSeekProvider.ts +74 -586
  379. package/src/providers/GeminiProvider.ts +29 -70
  380. package/src/providers/OpenAICompatibleProvider.ts +738 -0
  381. package/src/providers/OpenAIProvider.ts +29 -602
  382. package/src/providers/OpenRouterProvider.ts +38 -596
  383. package/src/providers/errorClassification.ts +172 -0
  384. package/src/providers/index.ts +13 -0
  385. package/src/types/agent.ts +2 -1
  386. package/src/types/ai.ts +22 -0
  387. package/src/types/errors.ts +57 -0
  388. package/src/types/flow.ts +10 -5
  389. package/src/types/history.ts +1 -2
  390. package/src/types/index.ts +5 -1
  391. package/src/types/persistence.ts +50 -2
  392. package/src/types/session.ts +9 -0
  393. package/src/types/signals.ts +23 -11
  394. package/src/types/template.ts +3 -1
  395. package/src/types/tool.ts +10 -10
  396. package/src/utils/condition.ts +115 -18
  397. package/src/utils/session.ts +2 -0
@@ -3,38 +3,15 @@
3
3
  * Provides both streaming and non-streaming response generation with unified logic
4
4
  */
5
5
  import { ResponseEngine } from "./ResponseEngine";
6
- import { ResponsePipeline, hasDirectivePositionField } from "./ResponsePipeline";
6
+ import { ResponsePipeline } from "./ResponsePipeline";
7
7
  import { AutoChainExecutor } from "./AutoChainExecutor";
8
- import { cloneDeep, mergeCollected, enterStep, enterFlow, getLastMessageFromHistory, logger, historyToEvents, eventsToHistory, serializeToolResult, completeCurrentFlow, render } from "../utils";
8
+ import { StepLifecycle } from "./StepLifecycle";
9
+ import { SessionFinalizer } from "./SessionFinalizer";
10
+ import { ToolLoopExecutor } from "./ToolLoopExecutor";
11
+ import { SignalCoordinator } from "./SignalCoordinator";
12
+ import { ResponseGenerationError } from "./ResponseGenerationError";
13
+ import { cloneDeep, mergeCollected, logger, historyToEvents, completeCurrentFlow, render } from "../utils";
9
14
  import { createTemplateContext } from "../utils/template";
10
- /**
11
- * Error class for response generation failures
12
- */
13
- export class ResponseGenerationError extends Error {
14
- constructor(message, details) {
15
- super(message);
16
- this.details = details;
17
- this.name = 'ResponseGenerationError';
18
- // Preserve stack trace from original error if available
19
- if (details?.originalError instanceof Error && details.originalError.stack) {
20
- this.stack = `${this.stack}\nCaused by: ${details.originalError.stack}`;
21
- }
22
- }
23
- /**
24
- * Create a ResponseGenerationError from an unknown error
25
- */
26
- static fromError(error, phase, params, context) {
27
- const message = error instanceof Error ? error.message : String(error);
28
- return new ResponseGenerationError(`[ResponseGenerationError] Response generation failed in ${phase}: ${message}. ` +
29
- `Check provider configuration and the ${phase} phase handler.`, { originalError: error, params, phase, context });
30
- }
31
- /**
32
- * Check if an error is a ResponseGenerationError
33
- */
34
- static isResponseGenerationError(error) {
35
- return error instanceof ResponseGenerationError;
36
- }
37
- }
38
15
  /**
39
16
  * ResponseModal class that encapsulates all response generation logic
40
17
  * Uses unified approach for both streaming and non-streaming responses
@@ -45,24 +22,64 @@ export class ResponseModal {
45
22
  this.options = options;
46
23
  // Initialize response engine
47
24
  this.responseEngine = new ResponseEngine(this.agent.promptSectionCache);
25
+ // Signal pre/post phase orchestration
26
+ this.signalCoordinator = new SignalCoordinator({
27
+ getFlows: () => this.agent.getFlows(),
28
+ signalProcessor: this.agent.signalProcessor,
29
+ });
48
30
  // Initialize response pipeline with agent dependencies
49
31
  this.responsePipeline = new ResponsePipeline(this.agent.getAgentOptions(), () => this.agent.getFlows(), // Pass a function to get flows dynamically
50
- this.agent.getTools(), this.agent.getFlowRouter(), this.agent.updateContext.bind(this.agent), this.agent.getUpdateDataMethod(), this.agent.updateCollectedData.bind(this.agent), this.getToolManager(), this.agent.signalProcessor);
32
+ this.agent.getFlowRouter(), this.signalCoordinator, this.agent.updateCollectedData.bind(this.agent), () => this.agent.schema);
33
+ // Step prepare/finalize execution, shared by the prepare phase and finalizer
34
+ this.stepLifecycle = new StepLifecycle({
35
+ getFlows: () => this.agent.getFlows(),
36
+ toolManager: this.getToolManager(),
37
+ updateContext: this.agent.updateContext.bind(this.agent),
38
+ updateData: this.agent.updateCollectedData.bind(this.agent),
39
+ });
40
+ // Single owner of end-of-turn finalization (compaction + persistence + sync)
41
+ this.sessionFinalizer = new SessionFinalizer({
42
+ getCompactionOptions: () => this.agent.getCompactionOptions(),
43
+ getPersistenceManager: () => this.agent.getPersistenceManager(),
44
+ getAgentOptions: () => this.agent.getAgentOptions(),
45
+ getCurrentSession: () => this.agent.currentSession,
46
+ setCurrentSession: (session) => { this.agent.currentSession = session; },
47
+ stepLifecycle: this.stepLifecycle,
48
+ enableAutoSave: this.options?.enableAutoSave,
49
+ });
50
+ // Tool follow-up loop (run tools, ask the LLM again) + streaming batch execution
51
+ this.toolLoopExecutor = new ToolLoopExecutor({
52
+ toolManager: this.getToolManager(),
53
+ getAgentOptions: () => this.agent.getAgentOptions(),
54
+ updateContext: this.agent.updateContext.bind(this.agent),
55
+ updateCollectedData: this.agent.updateCollectedData.bind(this.agent),
56
+ updateSessionData: this.agent.getUpdateDataMethod(),
57
+ maxToolLoops: this.options?.maxToolLoops,
58
+ });
51
59
  }
52
60
  /**
53
61
  * Generate a non-streaming response using unified logic
54
62
  */
55
63
  async respond(params) {
64
+ // Snapshot the managed session so a failed turn has no in-memory effect:
65
+ // without this, mutations made before the failure leave the live session
66
+ // diverged from persisted state
67
+ const preTurnSession = this.agent.session.current
68
+ ? cloneDeep(this.agent.session.current)
69
+ : undefined;
56
70
  try {
57
71
  // Use unified response preparation and routing
58
72
  const responseContext = await this.prepareUnifiedResponseContext(params);
59
73
  // Generate response using unified logic
60
74
  const result = await this.generateUnifiedResponse(responseContext);
61
- // Finalize session
62
- await this.finalizeSession(result.session, responseContext.effectiveContext);
75
+ // Finalize session — the non-streaming turn's single finalize
76
+ await this.sessionFinalizer.finalize(result.session, responseContext.effectiveContext);
63
77
  return result;
64
78
  }
65
79
  catch (error) {
80
+ if (preTurnSession) {
81
+ this.agent.session.syncSession(preTurnSession);
82
+ }
66
83
  throw new ResponseGenerationError(`[ResponseGenerationError] Response generation failed: ${error instanceof Error ? error.message : String(error)}. ` +
67
84
  `Check provider configuration and network connectivity.`, { originalError: error, params, phase: 'response_generation' });
68
85
  }
@@ -71,6 +88,10 @@ export class ResponseModal {
71
88
  * Generate a streaming response using unified logic
72
89
  */
73
90
  async *respondStream(params) {
91
+ // Same failed-turn rollback semantics as respond()
92
+ const preTurnSession = this.agent.session.current
93
+ ? cloneDeep(this.agent.session.current)
94
+ : undefined;
74
95
  try {
75
96
  // Use unified response preparation and routing
76
97
  const responseContext = await this.prepareUnifiedResponseContext(params);
@@ -78,6 +99,9 @@ export class ResponseModal {
78
99
  yield* this.generateUnifiedStreamingResponse(responseContext);
79
100
  }
80
101
  catch (error) {
102
+ if (preTurnSession) {
103
+ this.agent.session.syncSession(preTurnSession);
104
+ }
81
105
  // Stream error to caller
82
106
  yield {
83
107
  delta: "",
@@ -105,16 +129,9 @@ export class ResponseModal {
105
129
  }
106
130
  history = this.agent.session.getHistory();
107
131
  }
108
- // Get or create session
109
- let session = await this.agent.session.getOrCreate();
110
- // Merge agent's collected data into session (agent data takes precedence)
111
- const collectedData = this.agent.getCollectedData();
112
- if (Object.keys(collectedData).length > 0) {
113
- session = mergeCollected(session, collectedData);
114
- // Update the session manager with the merged data
115
- await this.agent.session.setData(collectedData);
116
- logger.debug("[ResponseModal] Merged agent collected data into stream session:", collectedData);
117
- }
132
+ // Get or create session — session.data is the single source of truth,
133
+ // so no agent-side data merge is needed
134
+ const session = await this.agent.session.getOrCreate();
118
135
  // Stream response using existing respondStream method
119
136
  let finalMessage = "";
120
137
  let finalizedSession;
@@ -158,16 +175,9 @@ export class ResponseModal {
158
175
  }
159
176
  history = this.agent.session.getHistory();
160
177
  }
161
- // Get or create session
162
- let session = await this.agent.session.getOrCreate();
163
- // Merge agent's collected data into session (agent data takes precedence)
164
- const collectedData = this.agent.getCollectedData();
165
- if (Object.keys(collectedData).length > 0) {
166
- session = mergeCollected(session, collectedData);
167
- // Update the session manager with the merged data
168
- await this.agent.session.setData(collectedData);
169
- logger.debug("[ResponseModal] Merged agent collected data into generate session:", collectedData);
170
- }
178
+ // Get or create session — session.data is the single source of truth,
179
+ // so no agent-side data merge is needed
180
+ const session = await this.agent.session.getOrCreate();
171
181
  // Generate response using existing respond method
172
182
  const result = await this.respond({
173
183
  history,
@@ -241,47 +251,48 @@ export class ResponseModal {
241
251
  const historyEvents = historyToEvents(simpleHistory);
242
252
  // Keep original HistoryItem[] format for external APIs
243
253
  const history = simpleHistory;
244
- // Use ResponsePipeline for optimized context and session preparation
245
- // This leverages existing optimizations and avoids code duplication
254
+ // Use ResponsePipeline for context and session preparation; context
255
+ // and session are passed explicitly the pipeline holds no state
246
256
  let responseContext;
247
257
  try {
248
- // Set current context and session in pipeline for consistency
249
- this.responsePipeline.setContext(await this.agent.getContext());
250
- this.responsePipeline.setCurrentSession(this.agent.currentSession);
251
258
  responseContext = await this.responsePipeline.prepareResponseContext({
252
259
  contextOverride,
253
260
  session: params.session ? cloneDeep(params.session) : undefined,
261
+ currentContext: await this.agent.getContext(),
262
+ currentSession: this.agent.currentSession,
254
263
  });
255
264
  }
256
265
  catch (error) {
257
266
  throw ResponseGenerationError.fromError(error, 'pipeline_context_preparation', params);
258
267
  }
259
- const { effectiveContext } = responseContext;
268
+ const { effectiveContext, contextAfterHook } = responseContext;
260
269
  let session = responseContext.session;
261
- // Update our stored context if it was modified by beforeRespond hook
262
- const storedContext = this.responsePipeline.getStoredContext();
263
- if (storedContext !== undefined) {
270
+ // Sync the beforeRespond hook's context result back to the agent
271
+ if (contextAfterHook !== undefined) {
264
272
  try {
265
- await this.agent.updateContext(storedContext);
273
+ await this.agent.updateContext(contextAfterHook);
266
274
  }
267
275
  catch (error) {
268
- throw ResponseGenerationError.fromError(error, 'context_update_from_pipeline', params, { storedContext });
276
+ throw ResponseGenerationError.fromError(error, 'context_update_from_pipeline', params, { contextAfterHook });
269
277
  }
270
278
  }
271
- // Merge agent's collected data into session (agent data takes precedence)
272
- const collectedData = this.agent.getCollectedData();
273
- if (Object.keys(collectedData).length > 0) {
279
+ // Apply data staged before any session existed (initialData,
280
+ // pre-session updateCollectedData calls). Reading the live session's
281
+ // data here would leak state across sessions when an explicit
282
+ // session is passed, so only the staging buffer is merged.
283
+ const stagedData = this.agent.consumePendingData();
284
+ if (Object.keys(stagedData).length > 0) {
274
285
  try {
275
- session = mergeCollected(session, collectedData);
276
- logger.debug("[ResponseModal] Merged agent collected data into session:", collectedData);
286
+ session = mergeCollected(session, stagedData);
287
+ logger.debug("[ResponseModal] Merged staged agent data into session:", stagedData);
277
288
  }
278
289
  catch (error) {
279
- throw ResponseGenerationError.fromError(error, 'data_merging', params, { collectedData });
290
+ throw ResponseGenerationError.fromError(error, 'data_merging', params, { stagedData });
280
291
  }
281
292
  }
282
293
  // PHASE 1: PREPARE - Execute prepare function if current step has one
283
294
  try {
284
- await this.executeStepPrepare(session, effectiveContext);
295
+ await this.stepLifecycle.runPrepare(session, effectiveContext);
285
296
  }
286
297
  catch (error) {
287
298
  throw ResponseGenerationError.fromError(error, 'step_preparation', params, { session, effectiveContext });
@@ -290,7 +301,7 @@ export class ResponseModal {
290
301
  // Performs pre-extraction and step selection
291
302
  let routingResult;
292
303
  try {
293
- routingResult = await this.handleUnifiedRoutingAndStepSelection({
304
+ routingResult = await this.responsePipeline.routeAndSelectStep({
294
305
  session,
295
306
  history: historyEvents,
296
307
  context: effectiveContext,
@@ -323,443 +334,6 @@ export class ResponseModal {
323
334
  throw ResponseGenerationError.fromError(error, 'preparation', params);
324
335
  }
325
336
  }
326
- /**
327
- * Unified routing and step selection logic using ResponsePipeline for optimization
328
- * @private
329
- */
330
- async handleUnifiedRoutingAndStepSelection(params) {
331
- try {
332
- // Create a fresh chain tracker for this turn (Requirement 22.1)
333
- this.responsePipeline.createChainTracker();
334
- // ROUTING SKIP OPTIMIZATION (Requirements 20.1, 20.2, 20.3):
335
- // When the current step has collect fields AND pre-extraction populates at least
336
- // one of those fields, skip FlowRouter.decideFlowAndStep for this turn.
337
- const routingSkipResult = await this.attemptRoutingSkipForCollect(params);
338
- if (routingSkipResult) {
339
- // Even when routing is skipped, run pre-signal phase if processor is present
340
- if (this.agent.signalProcessor) {
341
- const signalResult = await this.responsePipeline.runPreSignalPhase(params.session, params.context, params.history);
342
- // If signal halts, override the routing skip result
343
- if (signalResult.mergedDirective?.halt) {
344
- return {
345
- ...routingSkipResult,
346
- session: signalResult.updatedSession,
347
- signalFirings: signalResult.firings,
348
- signalHalted: true,
349
- signalHaltReply: signalResult.mergedDirective.reply,
350
- };
351
- }
352
- // If signal has position fields, override routing skip result
353
- if (hasDirectivePositionField(signalResult.mergedDirective)) {
354
- return this.applySignalPositionDirective(signalResult, params);
355
- }
356
- // Non-position directive: propagate for pre-LLM augmentation
357
- return {
358
- ...routingSkipResult,
359
- session: signalResult.updatedSession,
360
- signalFirings: signalResult.firings,
361
- signalPreDirective: signalResult.mergedDirective || undefined,
362
- };
363
- }
364
- return routingSkipResult;
365
- }
366
- // ── PARALLEL PRE-SIGNAL PHASE + ROUTING (Algorithm 5) ────────────────
367
- // When signalProcessor is present, run pre-signals in parallel with routing.
368
- // When absent, call the router directly (zero overhead, preserve current behavior).
369
- if (this.agent.signalProcessor) {
370
- // Run pre-signal phase in parallel with routing (Requirement 8.1)
371
- const [signalResult, routingResult] = await Promise.all([
372
- this.responsePipeline.runPreSignalPhase(params.session, params.context, params.history),
373
- this.responsePipeline.handleRoutingAndStepSelection({
374
- session: params.session,
375
- history: params.history,
376
- context: params.context,
377
- signal: params.signal,
378
- }),
379
- ]);
380
- // ── Requirement 8.2: halt → discard routing, skip LLM ────────────
381
- if (signalResult.mergedDirective?.halt) {
382
- return {
383
- selectedFlow: undefined,
384
- selectedStep: undefined,
385
- session: signalResult.updatedSession,
386
- isFlowComplete: false,
387
- signalFirings: signalResult.firings,
388
- signalHalted: true,
389
- signalHaltReply: signalResult.mergedDirective.reply,
390
- };
391
- }
392
- // ── Requirement 8.3: position directive → discard routing, apply signal position ──
393
- if (hasDirectivePositionField(signalResult.mergedDirective)) {
394
- return this.applySignalPositionDirective(signalResult, params);
395
- }
396
- // ── Requirement 8.4: non-position directive → use routing, propagate augmentation ──
397
- // ── Requirement 8.5: no directive → use routing as-is ─────────────
398
- let updatedSession = signalResult.updatedSession;
399
- // Apply data/context updates from signal to the routed session
400
- if (signalResult.mergedDirective?.dataUpdate) {
401
- updatedSession = mergeCollected(updatedSession, signalResult.mergedDirective.dataUpdate);
402
- }
403
- // Use routing result for flow/step, but carry signal session state
404
- // Merge routing session changes on top of signal session
405
- const routingSession = routingResult.session;
406
- updatedSession = {
407
- ...updatedSession,
408
- currentFlow: routingSession.currentFlow,
409
- currentStep: routingSession.currentStep,
410
- flowHistory: routingSession.flowHistory,
411
- pendingDirective: routingSession.pendingDirective,
412
- };
413
- const isFlowComplete = routingResult.isFlowComplete;
414
- // PRE-EXTRACTION: same logic as below — extract data from user message
415
- if (routingResult.selectedFlow && !isFlowComplete) {
416
- if (this.shouldPreExtractData(routingResult.selectedFlow)) {
417
- logger.debug(`[ResponseModal] Pre-extracting data for flow: ${routingResult.selectedFlow.title}`);
418
- const extractedData = await this.preExtractFlowData({
419
- route: routingResult.selectedFlow,
420
- history: params.history,
421
- context: params.context,
422
- session: updatedSession,
423
- signal: params.signal,
424
- });
425
- if (extractedData && Object.keys(extractedData).length > 0) {
426
- logger.debug(`[ResponseModal] Pre-extracted data:`, extractedData);
427
- updatedSession = mergeCollected(updatedSession, extractedData);
428
- await this.agent.updateCollectedData(extractedData);
429
- }
430
- }
431
- }
432
- // Determine next step
433
- const stepResult = await this.responsePipeline.determineNextStep({
434
- selectedFlow: routingResult.selectedFlow,
435
- selectedStep: routingResult.selectedStep,
436
- session: updatedSession,
437
- isFlowComplete,
438
- });
439
- return {
440
- selectedFlow: stepResult.flowChanged || routingResult.selectedFlow,
441
- selectedStep: stepResult.nextStep,
442
- responseDirectives: routingResult.responseDirectives,
443
- session: stepResult.session,
444
- isFlowComplete: stepResult.flowChanged ? false : isFlowComplete,
445
- signalFirings: signalResult.firings,
446
- signalPreDirective: signalResult.mergedDirective || undefined,
447
- };
448
- }
449
- // ── No signal processor: existing behavior (zero overhead) ────────────
450
- const routingResult = await this.responsePipeline.handleRoutingAndStepSelection({
451
- session: params.session,
452
- history: params.history,
453
- context: params.context,
454
- signal: params.signal,
455
- });
456
- let updatedSession = routingResult.session;
457
- const isFlowComplete = routingResult.isFlowComplete;
458
- // PRE-EXTRACTION: If entering a flow that collects data, extract data from user message first
459
- // This allows us to skip steps whose data is already provided
460
- if (routingResult.selectedFlow && !isFlowComplete) {
461
- // Always pre-extract when flow collects data (not just on new flow entry)
462
- // This ensures step selection has the most up-to-date data
463
- if (this.shouldPreExtractData(routingResult.selectedFlow)) {
464
- logger.debug(`[ResponseModal] Pre-extracting data for flow: ${routingResult.selectedFlow.title}`);
465
- const extractedData = await this.preExtractFlowData({
466
- route: routingResult.selectedFlow,
467
- history: params.history,
468
- context: params.context,
469
- session: updatedSession,
470
- signal: params.signal,
471
- });
472
- if (extractedData && Object.keys(extractedData).length > 0) {
473
- logger.debug(`[ResponseModal] Pre-extracted data:`, extractedData);
474
- // Merge pre-extracted data into session before step selection
475
- updatedSession = mergeCollected(updatedSession, extractedData);
476
- // Also update agent's collected data
477
- await this.agent.updateCollectedData(extractedData);
478
- }
479
- }
480
- }
481
- // Determine next step using pipeline method for consistency
482
- const stepResult = await this.responsePipeline.determineNextStep({
483
- selectedFlow: routingResult.selectedFlow,
484
- selectedStep: routingResult.selectedStep,
485
- session: updatedSession, // Use updated session with pre-extracted data
486
- isFlowComplete, // Use updated completion status
487
- });
488
- return {
489
- selectedFlow: stepResult.flowChanged || routingResult.selectedFlow,
490
- selectedStep: stepResult.nextStep, // Use the determined next step
491
- responseDirectives: routingResult.responseDirectives,
492
- session: stepResult.session,
493
- // If a branch changed the flow, the original isFlowComplete no longer applies
494
- isFlowComplete: stepResult.flowChanged ? false : isFlowComplete,
495
- };
496
- }
497
- catch (error) {
498
- throw ResponseGenerationError.fromError(error, 'routing_optimization', params);
499
- }
500
- }
501
- /**
502
- * Apply a signal's position directive (goTo, goToStep, complete, abort, reset).
503
- * Discards routing result and uses the signal's position decision.
504
- * @private
505
- * @requirements 8.3
506
- */
507
- applySignalPositionDirective(signalResult, _params) {
508
- const directive = signalResult.mergedDirective;
509
- let session = signalResult.updatedSession;
510
- const flows = this.agent.getFlows();
511
- let selectedFlow;
512
- let selectedStep;
513
- let isFlowComplete = false;
514
- // Apply data updates if present alongside position
515
- if (directive.dataUpdate) {
516
- session = mergeCollected(session, directive.dataUpdate);
517
- }
518
- if (directive.goTo) {
519
- const flowTarget = typeof directive.goTo === 'string'
520
- ? directive.goTo
521
- : directive.goTo.flow ?? directive.goTo.step;
522
- if (flowTarget) {
523
- const targetFlow = flows.find(f => f.id === flowTarget || f.title === flowTarget);
524
- if (targetFlow) {
525
- session = enterFlow(session, targetFlow.id, targetFlow.title);
526
- selectedFlow = targetFlow;
527
- if (typeof directive.goTo === 'object' && directive.goTo.step) {
528
- const targetStep = targetFlow.getStep(directive.goTo.step);
529
- if (targetStep) {
530
- session = enterStep(session, targetStep.id, targetStep.description);
531
- selectedStep = targetStep;
532
- }
533
- }
534
- }
535
- else {
536
- logger.warn(`[Signals] Pre-phase goTo target not found: "${flowTarget}". Falling back to no flow.`);
537
- }
538
- }
539
- }
540
- else if (directive.goToStep) {
541
- const stepTarget = typeof directive.goToStep === 'string'
542
- ? directive.goToStep
543
- : directive.goToStep.step;
544
- const flowTarget = typeof directive.goToStep === 'object'
545
- ? directive.goToStep.flow
546
- : undefined;
547
- if (flowTarget) {
548
- const targetFlow = flows.find(f => f.id === flowTarget || f.title === flowTarget);
549
- if (targetFlow) {
550
- session = enterFlow(session, targetFlow.id, targetFlow.title);
551
- selectedFlow = targetFlow;
552
- const targetStep = targetFlow.getStep(stepTarget);
553
- if (targetStep) {
554
- session = enterStep(session, targetStep.id, targetStep.description);
555
- selectedStep = targetStep;
556
- }
557
- }
558
- }
559
- else if (session.currentFlow) {
560
- const currentFlow = flows.find(f => f.id === session.currentFlow?.id);
561
- if (currentFlow) {
562
- selectedFlow = currentFlow;
563
- const targetStep = currentFlow.getStep(stepTarget);
564
- if (targetStep) {
565
- session = enterStep(session, targetStep.id, targetStep.description);
566
- selectedStep = targetStep;
567
- }
568
- }
569
- }
570
- }
571
- else if (directive.complete) {
572
- isFlowComplete = true;
573
- }
574
- else if (directive.abort) {
575
- // Abort — no flow, session cleared or marked
576
- isFlowComplete = true;
577
- }
578
- else if (directive.reset) {
579
- if (session.currentFlow) {
580
- const currentFlow = flows.find(f => f.id === session.currentFlow?.id);
581
- if (currentFlow) {
582
- selectedFlow = currentFlow;
583
- const resetStep = typeof directive.reset === 'object' && directive.reset.step
584
- ? directive.reset.step
585
- : undefined;
586
- if (resetStep) {
587
- const targetStep = currentFlow.getStep(resetStep);
588
- if (targetStep) {
589
- session = enterStep(session, targetStep.id, targetStep.description);
590
- selectedStep = targetStep;
591
- }
592
- }
593
- else {
594
- const initialStep = currentFlow.initialStep;
595
- session = enterStep(session, initialStep.id, initialStep.description);
596
- selectedStep = initialStep;
597
- }
598
- }
599
- }
600
- }
601
- return {
602
- selectedFlow,
603
- selectedStep,
604
- session,
605
- isFlowComplete,
606
- signalFirings: signalResult.firings,
607
- signalPreDirective: signalResult.mergedDirective || undefined,
608
- };
609
- }
610
- /**
611
- * Routing skip optimization (Requirements 20.1, 20.2, 20.3):
612
- * When the current step declares `collect` fields AND pre-extraction populates
613
- * at least one of those fields from the user's message, skip routing for this turn.
614
- *
615
- * Returns the routing result if the skip applies, or undefined to fall through
616
- * to normal routing.
617
- * @private
618
- */
619
- async attemptRoutingSkipForCollect(params) {
620
- const { session } = params;
621
- // Only applies when we already have a current flow and step
622
- if (!session.currentFlow || !session.currentStep) {
623
- return undefined;
624
- }
625
- // Also skip this optimization if there's a pending directive (it takes priority)
626
- if (session.pendingDirective) {
627
- return undefined;
628
- }
629
- // Look up the actual Flow and Step objects to access `collect`
630
- const currentFlow = this.agent.getFlows().find((f) => f.id === session.currentFlow?.id);
631
- if (!currentFlow) {
632
- return undefined;
633
- }
634
- const currentStep = currentFlow.getStep(session.currentStep.id);
635
- if (!currentStep || !currentStep.collect || currentStep.collect.length === 0) {
636
- return undefined;
637
- }
638
- // We have a step with collect fields. Run pre-extraction to see if the
639
- // user's message populates any of them.
640
- const collectFields = currentStep.collect;
641
- // Snapshot current data for comparison
642
- const dataBefore = { ...session.data };
643
- // Run pre-extraction against the current flow
644
- const extractedData = await this.preExtractFlowData({
645
- route: currentFlow,
646
- history: params.history,
647
- context: params.context,
648
- session,
649
- signal: params.signal,
650
- });
651
- if (!extractedData || Object.keys(extractedData).length === 0) {
652
- return undefined;
653
- }
654
- // Determine which collect fields were newly populated by pre-extraction
655
- const populatedCollectFields = [];
656
- for (const field of collectFields) {
657
- const key = field;
658
- const hadValue = dataBefore[field] !== undefined && dataBefore[field] !== null;
659
- const hasNewValue = extractedData[field] !== undefined && extractedData[field] !== null;
660
- if (hasNewValue && !hadValue) {
661
- populatedCollectFields.push(key);
662
- }
663
- }
664
- if (populatedCollectFields.length === 0) {
665
- // Pre-extraction didn't populate any declared collect field — no skip
666
- return undefined;
667
- }
668
- // ROUTING SKIP: pre-extraction populated collect fields → retain current flow/step
669
- logger.debug(`[ResponseModal] Routing skip: pre-extraction populated collect fields [${populatedCollectFields.join(', ')}] for step "${currentStep.id}" — skipping FlowRouter`);
670
- // Merge extracted data into session
671
- const updatedSession = mergeCollected(session, extractedData);
672
- await this.agent.updateCollectedData(extractedData);
673
- // Determine next step using pipeline method for consistency
674
- // Pass the current flow/step as the routing result (retained)
675
- const stepResult = await this.responsePipeline.determineNextStep({
676
- selectedFlow: currentFlow,
677
- selectedStep: currentStep,
678
- session: updatedSession,
679
- isFlowComplete: false,
680
- });
681
- return {
682
- selectedFlow: stepResult.flowChanged || currentFlow,
683
- selectedStep: stepResult.nextStep,
684
- responseDirectives: undefined,
685
- session: stepResult.session,
686
- isFlowComplete: stepResult.flowChanged ? false : false,
687
- };
688
- }
689
- /**
690
- * Check if a flow should pre-extract data before determining the initial step
691
- * @private
692
- */
693
- shouldPreExtractData(flow) {
694
- // Pre-extract if flow has declared required or optional fields
695
- if (flow.requiredFields && flow.requiredFields.length > 0) {
696
- return true;
697
- }
698
- if (flow.optionalFields && flow.optionalFields.length > 0) {
699
- return true;
700
- }
701
- // Pre-extract if any step in the flow collects data
702
- const steps = flow.getAllSteps();
703
- const hasDataCollectionSteps = steps.some(step => step.collect && step.collect.length > 0);
704
- return hasDataCollectionSteps;
705
- }
706
- /**
707
- * Pre-extract data from user message when entering a flow
708
- * This allows skipping steps whose data is already provided
709
- * @private
710
- */
711
- async preExtractFlowData(params) {
712
- const { route: flow, history, signal } = params;
713
- // Build a schema for data extraction based on flow's fields
714
- const extractionSchema = this.agent.schema;
715
- if (!extractionSchema) {
716
- logger.warn(`[ResponseModal] No schema available for pre-extraction`);
717
- return {};
718
- }
719
- // Get last user message
720
- const lastMessage = getLastMessageFromHistory(history);
721
- // Build extraction prompt
722
- const extractionPrompt = [
723
- `Extract any relevant information from the user's message that matches the following data fields.`,
724
- `Only extract information that is explicitly stated or clearly implied.`,
725
- ``,
726
- `User's message: "${lastMessage}"`,
727
- ``,
728
- `Extract data for these fields if present:`,
729
- ];
730
- // Add field descriptions
731
- if (flow.requiredFields) {
732
- extractionPrompt.push(`Required fields: ${flow.requiredFields.join(', ')}`);
733
- }
734
- if (flow.optionalFields) {
735
- extractionPrompt.push(`Optional fields: ${flow.optionalFields.join(', ')}`);
736
- }
737
- extractionPrompt.push(``, `Return ONLY the extracted data as JSON. If no data can be extracted, return an empty object {}.`);
738
- // Convert Event[] to HistoryItem[] for provider call
739
- const historyItems = eventsToHistory(history);
740
- // Call AI to extract data
741
- const agentOptions = this.agent.getAgentOptions();
742
- try {
743
- const result = await agentOptions.provider.generateMessage({
744
- prompt: extractionPrompt.join('\n'),
745
- history: historyItems,
746
- context: {}, // Passed as empty object so AI doesn't "extract" from context
747
- // NOTE: context is intentionally NOT passed here.
748
- // Passing context caused the AI to "extract" data from the lead's context
749
- // (e.g., name, sector, city) instead of from what the user actually said.
750
- signal,
751
- parameters: {
752
- jsonSchema: extractionSchema,
753
- schemaName: 'data_extraction',
754
- },
755
- });
756
- return result.structured || {};
757
- }
758
- catch (error) {
759
- logger.error(`[ResponseModal] Pre-extraction failed:`, error);
760
- return {};
761
- }
762
- }
763
337
  /**
764
338
  * Unified response generation for non-streaming responses
765
339
  * @private
@@ -777,21 +351,19 @@ export class ResponseModal {
777
351
  if (signalHalted) {
778
352
  const haltMessage = signalHaltReply || '';
779
353
  // Run post-signal phase even on halt (post-phase sees complete turn context)
780
- const postResult = await this.responsePipeline.runPostSignalPhase(session, effectiveContext, historyEvents);
781
- session = postResult.updatedSession;
782
- signalFirings.push(...postResult.firings);
783
- // Apply post-phase position directive as pendingDirective (Requirement 9.3)
784
- if (postResult.mergedDirective && hasDirectivePositionField(postResult.mergedDirective)) {
785
- session = { ...session, pendingDirective: postResult.mergedDirective };
786
- }
787
- await this.finalizeSession(session, effectiveContext);
354
+ const post = await this.signalCoordinator.applyPostPhase({
355
+ session, context: effectiveContext, historyEvents, message: haltMessage,
356
+ });
357
+ session = post.session;
358
+ signalFirings.push(...post.firings);
359
+ const message = post.message;
788
360
  return {
789
- message: haltMessage,
361
+ message,
790
362
  session,
791
363
  toolCalls: undefined,
792
364
  isFlowComplete: false,
793
365
  executedSteps: [],
794
- stoppedReason: signalHaltReply ? 'reply' : 'halt',
366
+ stoppedReason: haltMessage ? 'reply' : 'halt',
795
367
  triggeredSignals: signalFirings.length > 0 ? signalFirings : undefined,
796
368
  };
797
369
  }
@@ -818,12 +390,12 @@ export class ResponseModal {
818
390
  flow: selectedFlow,
819
391
  });
820
392
  session = autoResult.session;
821
- // Handle halt: emit verbatim reply, persist, return — no LLM call.
393
+ // Handle halt: emit verbatim reply, return — no LLM call.
394
+ // respond() finalizes the returned session exactly once.
822
395
  if (autoResult.stoppedReason === 'halt') {
823
396
  message = autoResult.mergedDirective?.reply || '';
824
397
  stoppedReason = 'halt';
825
398
  executedSteps = [];
826
- await this.finalizeSession(session, effectiveContext);
827
399
  return {
828
400
  message,
829
401
  session,
@@ -845,7 +417,6 @@ export class ResponseModal {
845
417
  context: effectiveContext,
846
418
  history,
847
419
  });
848
- await this.finalizeSession(session, effectiveContext);
849
420
  return {
850
421
  message: '',
851
422
  session,
@@ -925,16 +496,13 @@ export class ResponseModal {
925
496
  // Runs after finalize/onComplete and before session persistence.
926
497
  // Post-phase signals see the complete turn result: assistant message in
927
498
  // history, collected data, tool results.
928
- const postResult = await this.responsePipeline.runPostSignalPhase(session, effectiveContext, historyEvents);
929
- session = postResult.updatedSession;
499
+ const post = await this.signalCoordinator.applyPostPhase({
500
+ session, context: effectiveContext, historyEvents, message,
501
+ });
502
+ session = post.session;
930
503
  // Append post-phase firings to the accumulator (preserves fire order)
931
- signalFirings.push(...postResult.firings);
932
- // Requirement 9.3: Post-phase position directive sets session.pendingDirective
933
- // (no mid-turn re-entry per D6 decision). Pre-LLM-only fields are already
934
- // dropped inside runPostSignalPhase per Phase 4.5.
935
- if (postResult.mergedDirective && hasDirectivePositionField(postResult.mergedDirective)) {
936
- session = { ...session, pendingDirective: postResult.mergedDirective };
937
- }
504
+ signalFirings.push(...post.firings);
505
+ message = post.message;
938
506
  // Ensure response structure completeness (Requirement 8.1, 8.2, 8.3)
939
507
  // - executedSteps: array of steps executed (empty array if none)
940
508
  // - stoppedReason: why execution stopped (undefined for fallback)
@@ -950,43 +518,6 @@ export class ResponseModal {
950
518
  triggeredSignals: signalFirings.length > 0 ? signalFirings : undefined,
951
519
  };
952
520
  }
953
- /**
954
- * Execute prepare function for current step if available
955
- * @private
956
- */
957
- async executeStepPrepare(session, context) {
958
- if (session.currentFlow && session.currentStep) {
959
- const currentFlow = this.agent.getFlows().find((r) => r.id === session.currentFlow?.id);
960
- if (currentFlow) {
961
- const currentStep = currentFlow.getStep(session.currentStep.id);
962
- // Skip auto-steps — their prepare is handled by AutoChainExecutor
963
- if (currentStep?.auto) {
964
- logger.debug(`[ResponseModal] Skipping pre-routing prepare for auto-step: ${currentStep.id}`);
965
- return;
966
- }
967
- if (currentStep?.prepare) {
968
- logger.debug(`[ResponseModal] Executing prepare for step: ${currentStep.id}`);
969
- await this.executePrepareFinalize(currentStep.prepare, context, session.data, currentFlow, currentStep);
970
- }
971
- }
972
- }
973
- }
974
- /**
975
- * Execute finalize function for current step if available
976
- * @private
977
- */
978
- async executeStepFinalize(session, context) {
979
- if (session.currentFlow && session.currentStep) {
980
- const currentFlow = this.agent.getFlows().find((r) => r.id === session.currentFlow?.id);
981
- if (currentFlow) {
982
- const currentStep = currentFlow.getStep(session.currentStep.id);
983
- if (currentStep?.finalize) {
984
- logger.debug(`[ResponseModal] Executing finalize for step: ${currentStep.id}`);
985
- await this.executePrepareFinalize(currentStep.finalize, context, session.data, currentFlow, currentStep);
986
- }
987
- }
988
- }
989
- }
990
521
  /**
991
522
  * Process flow response with unified tool execution and data collection
992
523
  * @private
@@ -994,79 +525,20 @@ export class ResponseModal {
994
525
  async processFlowResponse(params) {
995
526
  const { selectedFlow, selectedStep, responseDirectives, history, context, historyEvents, signal, transientAppendage, mergedPreDirective } = params;
996
527
  let session = params.session;
997
- // Determine next step
998
- let nextStep;
999
- if (selectedStep) {
1000
- nextStep = selectedStep;
1001
- }
1002
- else {
1003
- // Determine current step from session if we're already in this flow
1004
- const isInSameFlow = session.currentFlow?.id === selectedFlow.id;
1005
- const currentStep = isInSameFlow && session.currentStep
1006
- ? selectedFlow.getStep(session.currentStep.id)
1007
- : undefined;
1008
- logger.debug(`[ResponseModal] Step determination: flow match=${isInSameFlow}, currentFlow=${session.currentFlow?.id}, selectedFlow=${selectedFlow.id}, currentStep=${currentStep?.id || 'none'}`);
1009
- // STEP 1 (Algorithm 1): branches win over linear chain
1010
- if (currentStep?.branches && currentStep.branches.length > 0) {
1011
- const branchResult = await this.responsePipeline.evaluateStepBranches(currentStep, selectedFlow, session, context);
1012
- if (branchResult) {
1013
- if (branchResult.nextStep) {
1014
- nextStep = branchResult.nextStep;
1015
- session = branchResult.session;
1016
- }
1017
- else {
1018
- // Flow transition or completion — no local step to render
1019
- // Return empty message with updated session; caller handles flow transition
1020
- return { message: '', session: branchResult.session };
1021
- }
1022
- }
1023
- }
1024
- if (!nextStep) {
1025
- // Get candidate steps based on current position in the flow
1026
- const flowRouter = this.agent.getFlowRouter();
1027
- const candidates = await flowRouter.getCandidateStepsWithConditions(selectedFlow, currentStep, // Pass current step instead of undefined to maintain progression
1028
- createTemplateContext({ data: session.data, session, context }));
1029
- logger.debug(`[ResponseModal] Found ${candidates.length} candidate steps${currentStep ? ' from current step ' + currentStep.id : ' (new flow entry)'}`);
1030
- if (candidates.length > 0) {
1031
- nextStep = candidates[0].step;
1032
- logger.debug(`[ResponseModal] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new flow'}`);
1033
- }
1034
- else {
1035
- // Fallback to initial step even if it should be skipped
1036
- nextStep = selectedFlow.initialStep;
1037
- logger.warn(`[FlowConfigurationError] No valid steps found: all candidates were skipped in flow. Falling back to initial step "${nextStep.id}". Review step skip conditions.`);
1038
- }
1039
- }
1040
- }
1041
- // Update session with next step
1042
- // If the next step has requires fields that are missing, stay at the previous step
1043
- if (nextStep.requires && nextStep.requires.length > 0) {
1044
- const sessionData = session.data || {};
1045
- const missingRequires = nextStep.requires.filter(field => sessionData[String(field)] === undefined);
1046
- if (missingRequires.length > 0) {
1047
- const warning = `[FlowConfigurationError] Cannot advance to step "${nextStep.description || nextStep.id}": ` +
1048
- `missing required fields [${missingRequires.join(', ')}]. Staying at current step. Ensure preceding steps collect these fields.`;
1049
- logger.warn(warning);
1050
- console.warn(warning);
1051
- // Stay at the current step - don't enter the next one
1052
- const currentStepId = session.currentStep?.id;
1053
- if (currentStepId) {
1054
- const currentStepInstance = selectedFlow.getStep(currentStepId);
1055
- if (currentStepInstance) {
1056
- nextStep = currentStepInstance;
1057
- logger.debug(`[ResponseModal] Staying at current step: ${nextStep.id} due to missing requires`);
1058
- }
1059
- }
1060
- }
1061
- else {
1062
- session = enterStep(session, nextStep.id, nextStep.description);
1063
- logger.debug(`[ResponseModal] Entered step: ${nextStep.id}`);
1064
- }
1065
- }
1066
- else {
1067
- session = enterStep(session, nextStep.id, nextStep.description);
1068
- logger.debug(`[ResponseModal] Entered step: ${nextStep.id}`);
528
+ // Resolve the step to render (branches win over linear chain; requires enforced)
529
+ const stepResolution = await this.responsePipeline.resolveRenderStep({
530
+ selectedFlow,
531
+ selectedStep,
532
+ session,
533
+ context,
534
+ });
535
+ if (stepResolution.flowTransition) {
536
+ // Flow transition or completion — no local step to render
537
+ // Return empty message with updated session; caller handles flow transition
538
+ return { message: '', session: stepResolution.session };
1069
539
  }
540
+ const nextStep = stepResolution.nextStep;
541
+ session = stepResolution.session;
1070
542
  // Build response schema for this flow (with collect fields from step)
1071
543
  const responseSchema = this.responseEngine.responseSchemaForFlow(selectedFlow, nextStep, this.agent.schema);
1072
544
  // ── HALT SHORT-CIRCUIT (Requirement 2.5, 2.6, 2.7) ──────────────────────
@@ -1139,7 +611,7 @@ export class ResponseModal {
1139
611
  toolNames: toolCalls?.map(tc => tc.toolName) || [],
1140
612
  });
1141
613
  // Execute tools with unified loop handling
1142
- const toolResult = await this.executeUnifiedToolLoop({
614
+ const toolResult = await this.toolLoopExecutor.runLoop({
1143
615
  toolCalls,
1144
616
  context,
1145
617
  session,
@@ -1185,16 +657,16 @@ export class ResponseModal {
1185
657
  if (signalHalted) {
1186
658
  const haltMessage = signalHaltReply || '';
1187
659
  // Run post-signal phase even on halt
1188
- const postResult = await this.responsePipeline.runPostSignalPhase(session, effectiveContext, historyEvents);
1189
- session = postResult.updatedSession;
1190
- signalFirings.push(...postResult.firings);
1191
- if (postResult.mergedDirective && hasDirectivePositionField(postResult.mergedDirective)) {
1192
- session = { ...session, pendingDirective: postResult.mergedDirective };
1193
- }
1194
- await this.finalizeSession(session, effectiveContext);
660
+ const post = await this.signalCoordinator.applyPostPhase({
661
+ session, context: effectiveContext, historyEvents, message: haltMessage,
662
+ });
663
+ session = post.session;
664
+ signalFirings.push(...post.firings);
665
+ const message = post.message;
666
+ await this.sessionFinalizer.finalize(session, effectiveContext);
1195
667
  yield {
1196
- delta: haltMessage,
1197
- accumulated: haltMessage,
668
+ delta: message,
669
+ accumulated: message,
1198
670
  done: true,
1199
671
  session,
1200
672
  stoppedReason: haltMessage ? 'reply' : 'halt',
@@ -1224,7 +696,7 @@ export class ResponseModal {
1224
696
  // Handle halt: emit verbatim reply as a single chunk, done.
1225
697
  if (autoResult.stoppedReason === 'halt') {
1226
698
  const reply = autoResult.mergedDirective?.reply || '';
1227
- await this.finalizeSession(session, effectiveContext);
699
+ await this.sessionFinalizer.finalize(session, effectiveContext);
1228
700
  yield {
1229
701
  delta: reply,
1230
702
  accumulated: reply,
@@ -1301,22 +773,30 @@ export class ResponseModal {
1301
773
  session,
1302
774
  });
1303
775
  }
1304
- // ── Intercept the inner stream to run post-signal phase on the final chunk ──
1305
- // This mirrors the non-streaming path: post-phase runs after finalize/onComplete
1306
- // and before session persistence, attaching triggeredSignals to the final chunk
1307
- // (Requirement 11.2).
776
+ // ── Intercept the inner stream on the final chunk ──────────────────────
777
+ // Mirrors the non-streaming path: post-signal phase runs first, then the
778
+ // session (including post-phase mutations) is finalized exactly once,
779
+ // attaching triggeredSignals to the final chunk (Requirement 11.2).
1308
780
  for await (const chunk of innerStream) {
1309
781
  if (chunk.done) {
1310
782
  // Run post-signal phase on final chunk (Requirement 9.1, 9.2)
1311
- const postResult = await this.responsePipeline.runPostSignalPhase(chunk.session || session, effectiveContext, historyEvents);
1312
- let finalSession = postResult.updatedSession;
1313
- signalFirings.push(...postResult.firings);
1314
- // Requirement 9.3: Post-phase position directive sets session.pendingDirective
1315
- if (postResult.mergedDirective && hasDirectivePositionField(postResult.mergedDirective)) {
1316
- finalSession = { ...finalSession, pendingDirective: postResult.mergedDirective };
1317
- }
783
+ const post = await this.signalCoordinator.applyPostPhase({
784
+ session: chunk.session || session,
785
+ context: effectiveContext,
786
+ historyEvents,
787
+ message: chunk.accumulated,
788
+ });
789
+ const finalSession = post.session;
790
+ signalFirings.push(...post.firings);
791
+ const accumulated = post.message;
792
+ const delta = post.replyOverridden ? accumulated : chunk.delta;
793
+ // Single streaming exit: finalize the post-phase session so
794
+ // post-signal mutations (e.g. pendingDirective) are persisted
795
+ await this.sessionFinalizer.finalize(finalSession, effectiveContext);
1318
796
  yield {
1319
797
  ...chunk,
798
+ delta,
799
+ accumulated,
1320
800
  session: finalSession,
1321
801
  triggeredSignals: signalFirings.length > 0 ? signalFirings : undefined,
1322
802
  };
@@ -1333,80 +813,25 @@ export class ResponseModal {
1333
813
  async *processFlowStreamingResponse(params) {
1334
814
  const { selectedFlow, selectedStep, responseDirectives, history, context, historyEvents, signal, transientAppendage, mergedPreDirective } = params;
1335
815
  let session = params.session;
1336
- // Determine next step (same logic as non-streaming)
1337
- let nextStep;
1338
- if (selectedStep) {
1339
- nextStep = selectedStep;
1340
- }
1341
- else {
1342
- // Determine current step from session if we're already in this flow
1343
- const currentStep = session.currentFlow?.id === selectedFlow.id && session.currentStep
1344
- ? selectedFlow.getStep(session.currentStep.id)
1345
- : undefined;
1346
- // STEP 1 (Algorithm 1): branches win over linear chain
1347
- if (currentStep?.branches && currentStep.branches.length > 0) {
1348
- const branchResult = await this.responsePipeline.evaluateStepBranches(currentStep, selectedFlow, session, context);
1349
- if (branchResult) {
1350
- // Branch resolved — yield a final chunk with the updated session and return
1351
- if (branchResult.nextStep) {
1352
- session = branchResult.session;
1353
- nextStep = branchResult.nextStep;
1354
- }
1355
- else {
1356
- // Flow transition or completion — no step to render
1357
- yield {
1358
- delta: '',
1359
- accumulated: '',
1360
- done: true,
1361
- session: branchResult.session,
1362
- };
1363
- return;
1364
- }
1365
- }
1366
- }
1367
- if (!nextStep) {
1368
- // Get candidate steps based on current position in the flow
1369
- const flowRouter = this.agent.getFlowRouter();
1370
- const candidates = await flowRouter.getCandidateStepsWithConditions(selectedFlow, currentStep, // Pass current step instead of undefined to maintain progression
1371
- createTemplateContext({ data: session.data, session, context }));
1372
- if (candidates.length > 0) {
1373
- nextStep = candidates[0].step;
1374
- logger.debug(`[ResponseModal] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new flow'}`);
1375
- }
1376
- else {
1377
- nextStep = selectedFlow.initialStep;
1378
- logger.warn(`[FlowConfigurationError] No valid steps found: all candidates were skipped in flow. Falling back to initial step "${nextStep.id}". Review step skip conditions.`);
1379
- }
1380
- }
1381
- }
1382
- // Update session with next step
1383
- // If the next step has requires fields that are missing, stay at the previous step
1384
- if (nextStep.requires && nextStep.requires.length > 0) {
1385
- const sessionData = session.data || {};
1386
- const missingRequires = nextStep.requires.filter(field => sessionData[String(field)] === undefined);
1387
- if (missingRequires.length > 0) {
1388
- const warning = `[FlowConfigurationError] Cannot advance to step "${nextStep.description || nextStep.id}": ` +
1389
- `missing required fields [${missingRequires.join(', ')}]. Staying at current step. Ensure preceding steps collect these fields.`;
1390
- logger.warn(warning);
1391
- console.warn(warning);
1392
- const currentStepId = session.currentStep?.id;
1393
- if (currentStepId) {
1394
- const currentStepInstance = selectedFlow.getStep(currentStepId);
1395
- if (currentStepInstance) {
1396
- nextStep = currentStepInstance;
1397
- logger.debug(`[ResponseModal] Staying at current step: ${nextStep.id} due to missing requires`);
1398
- }
1399
- }
1400
- }
1401
- else {
1402
- session = enterStep(session, nextStep.id, nextStep.description);
1403
- logger.debug(`[ResponseModal] Entered step: ${nextStep.id}`);
1404
- }
1405
- }
1406
- else {
1407
- session = enterStep(session, nextStep.id, nextStep.description);
1408
- logger.debug(`[ResponseModal] Entered step: ${nextStep.id}`);
816
+ // Resolve the step to render (same logic as non-streaming)
817
+ const stepResolution = await this.responsePipeline.resolveRenderStep({
818
+ selectedFlow,
819
+ selectedStep,
820
+ session,
821
+ context,
822
+ });
823
+ if (stepResolution.flowTransition) {
824
+ // Flow transition or completion — no step to render
825
+ yield {
826
+ delta: '',
827
+ accumulated: '',
828
+ done: true,
829
+ session: stepResolution.session,
830
+ };
831
+ return;
1409
832
  }
833
+ const nextStep = stepResolution.nextStep;
834
+ session = stepResolution.session;
1410
835
  // Build response schema and prompt (same as non-streaming)
1411
836
  const responseSchema = this.responseEngine.responseSchemaForFlow(selectedFlow, nextStep, this.agent.schema);
1412
837
  // ── HALT SHORT-CIRCUIT (Requirement 2.5, 2.6, 2.7) ──────────────────────
@@ -1416,7 +841,6 @@ export class ResponseModal {
1416
841
  const reply = mergedPreDirective.reply || '';
1417
842
  const reason = mergedPreDirective.reply ? 'reply' : 'halt';
1418
843
  logger.debug(`[ResponseModal] Halt (streaming) — skipping LLM call for step ${nextStep.id}, stoppedReason: ${reason}`);
1419
- await this.finalizeSession(session, context);
1420
844
  yield {
1421
845
  delta: reply,
1422
846
  accumulated: reply,
@@ -1434,7 +858,6 @@ export class ResponseModal {
1434
858
  if (nextStep.reply != null) {
1435
859
  const effectiveReply = mergedPreDirective?.reply ?? await render(nextStep.reply, createTemplateContext({ data: session.data || {}, context, session }));
1436
860
  logger.debug(`[ResponseModal] Step.reply (streaming) — skipping LLM call for step ${nextStep.id}`);
1437
- await this.finalizeSession(session, context);
1438
861
  yield {
1439
862
  delta: effectiveReply,
1440
863
  accumulated: effectiveReply,
@@ -1483,70 +906,23 @@ export class ResponseModal {
1483
906
  // Extract tool calls from AI response on final chunk
1484
907
  if (chunk.done && chunk.structured?.toolCalls) {
1485
908
  toolCalls = chunk.structured.toolCalls;
1486
- const toolManager = this.getToolManager();
1487
- // Use concurrent execution for the initial batch of tool calls
1488
- {
1489
- const toolCallRequests = toolCalls.map((tc, i) => ({
1490
- id: `${tc.toolName}-${i}-${Date.now()}`,
1491
- toolName: tc.toolName,
1492
- arguments: tc.arguments,
1493
- }));
1494
- const historyEvents = historyToEvents(history);
1495
- try {
1496
- for await (const update of toolManager.executeWithConcurrency({
1497
- toolCalls: toolCallRequests,
1498
- context,
1499
- data: session.data,
1500
- history: historyEvents,
1501
- signal,
1502
- flow: selectedFlow,
1503
- step: nextStep,
1504
- })) {
1505
- // Apply context updates
1506
- if (update.contextUpdate) {
1507
- try {
1508
- await this.agent.updateContext(update.contextUpdate);
1509
- }
1510
- catch (error) {
1511
- logger.error(`[ResponseModal] Failed to update context from concurrent tool:`, error);
1512
- }
1513
- }
1514
- // Apply data updates
1515
- if (update.dataUpdate) {
1516
- try {
1517
- const updateDataMethod = this.agent.getUpdateDataMethod();
1518
- session = await updateDataMethod(session, update.dataUpdate);
1519
- }
1520
- catch (error) {
1521
- logger.error(`[ResponseModal] Failed to update data from concurrent tool:`, error);
1522
- }
1523
- }
1524
- // Yield progress updates immediately
1525
- if (update.progress) {
1526
- yield {
1527
- delta: '',
1528
- accumulated: chunk.accumulated,
1529
- done: false,
1530
- session,
1531
- toolCalls: undefined,
1532
- isFlowComplete: false,
1533
- metadata: { toolProgress: update.progress, toolCallId: update.toolCallId },
1534
- };
1535
- }
1536
- }
1537
- logger.debug(`[ResponseModal] Concurrent tool execution completed for ${toolCallRequests.length} tools`);
1538
- }
1539
- catch (error) {
1540
- logger.error(`[ResponseModal] Concurrent tool execution failed, falling back to sequential:`, error);
1541
- // Fall back to the unified tool loop on failure
1542
- const toolResult = await this.executeUnifiedToolLoop({
1543
- toolCalls, context, session, history, selectedFlow,
1544
- responsePrompt, availableTools, responseSchema, signal,
1545
- });
1546
- session = toolResult.session;
1547
- toolCalls = toolResult.finalToolCalls;
1548
- }
1549
- }
909
+ // Concurrent execution for the initial batch of tool calls,
910
+ // yielding tool-progress chunks as they arrive
911
+ const batchResult = yield* this.toolLoopExecutor.runStreamingBatch({
912
+ toolCalls,
913
+ context,
914
+ session,
915
+ history,
916
+ selectedFlow,
917
+ step: nextStep,
918
+ accumulated: chunk.accumulated,
919
+ responsePrompt,
920
+ availableTools,
921
+ responseSchema,
922
+ signal,
923
+ });
924
+ session = batchResult.session;
925
+ toolCalls = batchResult.toolCalls;
1550
926
  }
1551
927
  // Extract collected data on final chunk
1552
928
  if (chunk.done && chunk.structured && nextStep.collect) {
@@ -1557,10 +933,6 @@ export class ResponseModal {
1557
933
  session,
1558
934
  });
1559
935
  }
1560
- // Handle session finalization on final chunk
1561
- if (chunk.done) {
1562
- await this.finalizeSession(session, context);
1563
- }
1564
936
  // Response structure completeness (Requirement 8.1, 8.2, 8.3)
1565
937
  // - executedSteps: single step executed in this response
1566
938
  // - stoppedReason: 'needs_input' for single-step execution (waiting for user input)
@@ -1588,293 +960,6 @@ export class ResponseModal {
1588
960
  }
1589
961
  }
1590
962
  /**
1591
- * Unified tool execution logic with loop handling
1592
- * Consolidates the complex tool execution logic from both streaming and non-streaming responses
1593
- * @private
1594
- */
1595
- async executeUnifiedToolLoop(params) {
1596
- try {
1597
- const { context, history, selectedFlow, responsePrompt, availableTools, responseSchema, signal } = params;
1598
- let { toolCalls, session } = params;
1599
- // Convert HistoryItem[] to Event[] for internal processing
1600
- const historyEvents = historyToEvents(history);
1601
- // Map to store tool execution results for history
1602
- const toolResultsMap = new Map();
1603
- // Map to store tool call arguments for history reconstruction
1604
- const toolArgsMap = new Map();
1605
- // Execute initial dynamic tool calls
1606
- if (toolCalls && toolCalls.length > 0) {
1607
- logger.debug(`[ResponseModal] Executing ${toolCalls.length} dynamic tool calls:`, toolCalls.map(tc => tc.toolName));
1608
- for (const toolCall of toolCalls) {
1609
- const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
1610
- if (!tool) {
1611
- logger.warn(`[ToolExecutionError] Tool not found: "${toolCall.toolName}" is not registered in any scope. Skipping this tool call. Register the tool or check the tool name.`);
1612
- continue;
1613
- }
1614
- try {
1615
- // Use ToolManager for unified tool execution
1616
- const toolManager = this.getToolManager();
1617
- const toolResult = await toolManager.executeTool({
1618
- tool: tool,
1619
- context,
1620
- updateContext: this.agent.updateContext.bind(this.agent),
1621
- updateData: this.agent.updateCollectedData.bind(this.agent),
1622
- history: historyEvents, // Use Event[] for tool execution
1623
- data: session.data,
1624
- toolArguments: toolCall.arguments,
1625
- });
1626
- // Store the actual tool result data for history
1627
- toolResultsMap.set(toolCall.toolName, serializeToolResult(toolResult));
1628
- toolArgsMap.set(toolCall.toolName, toolCall.arguments);
1629
- // Check if tool execution was successful
1630
- if (!toolResult.success) {
1631
- logger.error(`[ResponseModal] Tool execution failed: ${toolCall.toolName} - ${toolResult.error}`);
1632
- // Continue with other tools rather than failing completely
1633
- continue;
1634
- }
1635
- // Update context with tool results
1636
- if (toolResult.contextUpdate) {
1637
- try {
1638
- await this.agent.updateContext(toolResult.contextUpdate);
1639
- }
1640
- catch (error) {
1641
- logger.error(`[ResponseModal] Failed to update context from tool ${toolCall.toolName}:`, error);
1642
- // Continue execution but log the error
1643
- }
1644
- }
1645
- // Update collected data with tool results
1646
- if (toolResult.dataUpdate) {
1647
- try {
1648
- const updateDataMethod = this.agent.getUpdateDataMethod();
1649
- session = await updateDataMethod(session, toolResult.dataUpdate);
1650
- logger.debug(`[ResponseModal] Tool updated collected data:`, toolResult.dataUpdate);
1651
- }
1652
- catch (error) {
1653
- logger.error(`[ResponseModal] Failed to update data from tool ${toolCall.toolName}:`, error);
1654
- // Continue execution but log the error
1655
- }
1656
- }
1657
- logger.debug(`[ResponseModal] Executed dynamic tool: ${toolCall.toolName} (success: ${toolResult.success})`);
1658
- }
1659
- catch (error) {
1660
- logger.error(`[ResponseModal] Tool execution error for ${toolCall.toolName}:`, error);
1661
- // Continue with other tools rather than failing the entire response
1662
- continue;
1663
- }
1664
- }
1665
- }
1666
- // TOOL LOOP: Allow AI to make follow-up tool calls after initial tool execution
1667
- const MAX_TOOL_LOOPS = this.options?.maxToolLoops || 5;
1668
- let toolLoopCount = 0;
1669
- let hasToolCalls = toolCalls && toolCalls.length > 0;
1670
- let finalMessage;
1671
- let followUpStructured;
1672
- while (hasToolCalls && toolLoopCount < MAX_TOOL_LOOPS) {
1673
- toolLoopCount++;
1674
- logger.debug(`[ResponseModal] Starting tool loop ${toolLoopCount}/${MAX_TOOL_LOOPS} with ${toolCalls?.length || 0} tool calls`);
1675
- // Create tool result history items
1676
- const toolResultHistoryItems = [];
1677
- for (const toolCall of toolCalls || []) {
1678
- const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
1679
- if (tool) {
1680
- // Create HistoryItem format for tool results
1681
- // Add assistant message with tool_calls
1682
- toolResultHistoryItems.push({
1683
- role: "assistant",
1684
- content: null,
1685
- tool_calls: [{
1686
- id: toolCall.toolName,
1687
- name: toolCall.toolName,
1688
- arguments: toolCall.arguments,
1689
- }],
1690
- });
1691
- // Add tool result
1692
- toolResultHistoryItems.push({
1693
- role: "tool",
1694
- tool_call_id: toolCall.toolName,
1695
- name: toolCall.toolName,
1696
- content: toolResultsMap.get(toolCall.toolName) || "Tool executed successfully",
1697
- });
1698
- }
1699
- }
1700
- // Create updated history with tool results
1701
- const updatedHistory = [...history, ...toolResultHistoryItems];
1702
- // Make follow-up AI call to see if more tools are needed
1703
- // After first iteration, don't provide tools to force a text response
1704
- const agentOptions = this.agent.getAgentOptions();
1705
- const shouldProvideTools = toolLoopCount === 1;
1706
- logger.debug(`[ResponseModal] Making follow-up AI call (loop ${toolLoopCount}):`, {
1707
- providingTools: shouldProvideTools,
1708
- toolsCount: shouldProvideTools ? availableTools.length : 0,
1709
- addingTextInstruction: toolLoopCount > 1,
1710
- });
1711
- const followUpResult = await agentOptions.provider.generateMessage({
1712
- prompt: responsePrompt + (toolLoopCount > 1 ? "\n\nProvide a text response to the user based on the tool results." : ""),
1713
- history: updatedHistory, // Use HistoryItem[] for AI provider
1714
- context,
1715
- tools: shouldProvideTools ? availableTools : [], // Only provide tools on first iteration
1716
- parameters: responseSchema ? {
1717
- jsonSchema: responseSchema,
1718
- schemaName: "tool_followup",
1719
- } : undefined,
1720
- signal,
1721
- });
1722
- // Check if follow-up call has more tool calls
1723
- const followUpToolCalls = followUpResult.structured?.toolCalls;
1724
- hasToolCalls = followUpToolCalls && followUpToolCalls.length > 0;
1725
- logger.debug(`[ResponseModal] Follow-up AI response (loop ${toolLoopCount}):`, {
1726
- hasMessage: !!followUpResult.message,
1727
- messageLength: followUpResult.message?.length || 0,
1728
- hasToolCalls,
1729
- toolCallsCount: followUpToolCalls?.length || 0,
1730
- toolNames: followUpToolCalls?.map(tc => tc.toolName) || [],
1731
- });
1732
- if (hasToolCalls) {
1733
- logger.debug(`[ResponseModal] Follow-up call produced ${followUpToolCalls.length} additional tool calls`);
1734
- // Execute the follow-up tool calls
1735
- for (const toolCall of followUpToolCalls) {
1736
- const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
1737
- if (!tool) {
1738
- logger.warn(`[ToolExecutionError] Tool not found in follow-up: "${toolCall.toolName}" is not registered in any scope. Skipping this tool call. Register the tool or check the tool name.`);
1739
- continue;
1740
- }
1741
- try {
1742
- // Use ToolManager for unified tool execution
1743
- const toolManager = this.getToolManager();
1744
- const toolResult = await toolManager.executeTool({
1745
- tool: tool,
1746
- context,
1747
- updateContext: this.agent.updateContext.bind(this.agent),
1748
- updateData: this.agent.updateCollectedData.bind(this.agent),
1749
- history: historyToEvents(updatedHistory), // Convert to Event[] for tool execution
1750
- data: session.data,
1751
- toolArguments: toolCall.arguments,
1752
- });
1753
- // Check if tool execution was successful
1754
- if (!toolResult.success) {
1755
- logger.error(`[ResponseModal] Follow-up tool execution failed: ${toolCall.toolName} - ${toolResult.error}`);
1756
- continue;
1757
- }
1758
- // Update context with follow-up tool results
1759
- if (toolResult.contextUpdate) {
1760
- try {
1761
- await this.agent.updateContext(toolResult.contextUpdate);
1762
- }
1763
- catch (error) {
1764
- logger.error(`[ResponseModal] Failed to update context from follow-up tool ${toolCall.toolName}:`, error);
1765
- }
1766
- }
1767
- if (toolResult.dataUpdate) {
1768
- try {
1769
- const updateDataMethod = this.agent.getUpdateDataMethod();
1770
- session = await updateDataMethod(session, toolResult.dataUpdate);
1771
- logger.debug(`[ResponseModal] Follow-up tool updated collected data:`, toolResult.dataUpdate);
1772
- }
1773
- catch (error) {
1774
- logger.error(`[ResponseModal] Failed to update data from follow-up tool ${toolCall.toolName}:`, error);
1775
- }
1776
- }
1777
- // Store the follow-up tool result for potential next loop iteration
1778
- toolResultsMap.set(toolCall.toolName, serializeToolResult(toolResult));
1779
- toolArgsMap.set(toolCall.toolName, toolCall.arguments);
1780
- logger.debug(`[ResponseModal] Executed follow-up tool: ${toolCall.toolName} (success: ${toolResult.success})`);
1781
- }
1782
- catch (error) {
1783
- logger.error(`[ResponseModal] Follow-up tool execution error for ${toolCall.toolName}:`, error);
1784
- continue;
1785
- }
1786
- }
1787
- // Update toolCalls for next iteration or final response
1788
- toolCalls = followUpToolCalls;
1789
- }
1790
- else {
1791
- logger.debug(`[ResponseModal] Tool loop completed after ${toolLoopCount} iterations`);
1792
- // Update final message and toolCalls from follow-up result if no more tools
1793
- finalMessage = followUpResult.structured?.message || followUpResult.message;
1794
- followUpStructured = followUpResult.structured;
1795
- toolCalls = followUpToolCalls || [];
1796
- break;
1797
- }
1798
- }
1799
- if (toolLoopCount >= MAX_TOOL_LOOPS) {
1800
- logger.warn(`[ResponseGenerationError] Tool loop limit reached: ${toolLoopCount} iterations hit the cap (${MAX_TOOL_LOOPS}). Stopping tool execution. Increase MAX_TOOL_LOOPS or reduce recursive tool calls.`);
1801
- }
1802
- // If tools were executed but no final text message was produced,
1803
- // make one more LLM call to generate a proper text response from tool results.
1804
- // This prevents the original tool-invocation message (e.g. "Let me check...")
1805
- // from being returned as the final user-facing response.
1806
- if (!finalMessage && toolLoopCount > 0) {
1807
- logger.debug(`[ResponseModal] No final message after tool loop, making additional LLM call for text response`);
1808
- // Build tool result history from toolResultsMap which contains ALL
1809
- // tool executions (initial + follow-up). We can't use `toolCalls` here
1810
- // because it was reassigned to the (empty) follow-up tool calls when
1811
- // the while loop broke out.
1812
- const finalToolResultHistoryItems = [];
1813
- for (const [toolName, toolResult] of toolResultsMap) {
1814
- finalToolResultHistoryItems.push({
1815
- role: "assistant",
1816
- content: null,
1817
- tool_calls: [{
1818
- id: toolName,
1819
- name: toolName,
1820
- arguments: toolArgsMap.get(toolName) || {},
1821
- }],
1822
- });
1823
- finalToolResultHistoryItems.push({
1824
- role: "tool",
1825
- tool_call_id: toolName,
1826
- name: toolName,
1827
- content: toolResult,
1828
- });
1829
- }
1830
- const finalHistory = [...history, ...finalToolResultHistoryItems];
1831
- const agentOptions = this.agent.getAgentOptions();
1832
- try {
1833
- const textResult = await agentOptions.provider.generateMessage({
1834
- prompt: responsePrompt + "\n\nProvide a text response to the user based on the tool results. Do not call any tools.",
1835
- history: finalHistory,
1836
- context,
1837
- tools: [], // No tools - force text response
1838
- parameters: responseSchema ? {
1839
- jsonSchema: responseSchema,
1840
- schemaName: "tool_final_text",
1841
- } : undefined,
1842
- signal,
1843
- });
1844
- finalMessage = textResult.structured?.message || textResult.message;
1845
- if (textResult.structured) {
1846
- followUpStructured = textResult.structured;
1847
- }
1848
- logger.debug(`[ResponseModal] Generated final text response after tool loop:`, {
1849
- hasMessage: !!finalMessage,
1850
- messageLength: finalMessage?.length || 0,
1851
- });
1852
- }
1853
- catch (error) {
1854
- logger.error(`[ResponseModal] Failed to generate final text response after tool loop:`, error);
1855
- // finalMessage remains undefined; caller will use original message as fallback
1856
- }
1857
- }
1858
- logger.debug(`[ResponseModal] Tool loop completed:`, {
1859
- totalIterations: toolLoopCount,
1860
- hasFinalMessage: !!finalMessage,
1861
- finalMessageLength: finalMessage?.length || 0,
1862
- finalToolCallsCount: toolCalls?.length || 0,
1863
- });
1864
- return {
1865
- session,
1866
- finalToolCalls: toolCalls,
1867
- finalMessage,
1868
- structured: followUpStructured,
1869
- };
1870
- }
1871
- catch (error) {
1872
- throw ResponseGenerationError.fromError(error, 'tool_execution', params, {
1873
- toolCallsCount: params.toolCalls?.length || 0,
1874
- availableToolsCount: params.availableTools.length
1875
- });
1876
- }
1877
- } /**
1878
963
  * Unified data collection from AI response
1879
964
  * @private
1880
965
  */
@@ -2020,10 +1105,6 @@ export class ResponseModal {
2020
1105
  }
2021
1106
  return nextSession;
2022
1107
  }
2023
- /**
2024
- * Stream flow completion response
2025
- * @private
2026
- */
2027
1108
  /**
2028
1109
  * Stream a flow completion as a single terminal chunk.
2029
1110
  *
@@ -2045,7 +1126,6 @@ export class ResponseModal {
2045
1126
  context,
2046
1127
  history,
2047
1128
  });
2048
- await this.finalizeSession(session, context);
2049
1129
  yield {
2050
1130
  delta: '',
2051
1131
  accumulated: '',
@@ -2120,10 +1200,6 @@ export class ResponseModal {
2120
1200
  },
2121
1201
  });
2122
1202
  for await (const chunk of stream) {
2123
- // Update current session if we have one
2124
- if (chunk.done) {
2125
- await this.finalizeSession(session, context);
2126
- }
2127
1203
  // Response structure completeness (Requirement 8.1, 8.2, 8.3)
2128
1204
  // - executedSteps: empty for fallback (no flow/step execution)
2129
1205
  // - stoppedReason: undefined for fallback (no flow context)
@@ -2143,39 +1219,9 @@ export class ResponseModal {
2143
1219
  };
2144
1220
  }
2145
1221
  }
2146
- /**
2147
- * Handle session persistence and finalization
2148
- * @private
2149
- */
2150
- async finalizeSession(session, context) {
2151
- // Auto-save session step to persistence if configured
2152
- const persistenceManager = this.agent.getPersistenceManager();
2153
- const agentOptions = this.agent.getAgentOptions();
2154
- if (persistenceManager &&
2155
- session.id &&
2156
- (this.options?.enableAutoSave !== false && agentOptions.persistence?.autoSave !== false)) {
2157
- await persistenceManager.saveSessionState(session.id, session);
2158
- logger.debug(`[ResponseModal] Auto-saved session step to persistence: ${session.id}`);
2159
- }
2160
- // Execute finalize function
2161
- await this.executeStepFinalize(session, context);
2162
- // Update current session if we have one
2163
- const currentSession = this.agent.currentSession;
2164
- if (currentSession) {
2165
- this.agent.currentSession = session;
2166
- }
2167
- }
2168
1222
  // ============================================================================
2169
1223
  // UTILITY METHODS - Helper methods for tool management and other utilities
2170
1224
  // ============================================================================
2171
- /**
2172
- * Find an available tool by name for the given flow.
2173
- * Delegates to ToolManager for unified tool resolution.
2174
- * @private
2175
- */
2176
- findAvailableTool(toolName, flow) {
2177
- return this.getToolManager().find(toolName, undefined, undefined, flow);
2178
- }
2179
1225
  /**
2180
1226
  * Collect all available tools for the given flow and step context.
2181
1227
  * Delegates to ToolManager for unified tool resolution and deduplication.
@@ -2190,49 +1236,5 @@ export class ResponseModal {
2190
1236
  parameters: tool.parameters,
2191
1237
  }));
2192
1238
  }
2193
- /**
2194
- * Execute a prepare or finalize function/tool
2195
- * @private
2196
- */
2197
- async executePrepareFinalize(prepareOrFinalize, context, data, flow, step) {
2198
- if (!prepareOrFinalize)
2199
- return;
2200
- if (typeof prepareOrFinalize === "function") {
2201
- // It's a function - call it directly
2202
- await prepareOrFinalize(context, data);
2203
- }
2204
- else {
2205
- // It's a tool reference - find and execute the tool
2206
- let tool;
2207
- if (typeof prepareOrFinalize === "string") {
2208
- // Tool ID - use ToolManager for unified resolution
2209
- tool = this.getToolManager().find(prepareOrFinalize, undefined, step, flow);
2210
- }
2211
- else {
2212
- // Tool object - use directly
2213
- tool = prepareOrFinalize;
2214
- }
2215
- if (tool) {
2216
- // Use ToolManager for unified tool execution
2217
- const result = await this.getToolManager().executeTool({
2218
- tool,
2219
- context,
2220
- updateContext: this.agent.updateContext.bind(this.agent),
2221
- updateData: this.agent.updateCollectedData.bind(this.agent),
2222
- history: [], // Empty history for prepare/finalize
2223
- data,
2224
- });
2225
- if (!result.success) {
2226
- logger.error(`[ResponseModal] Tool execution failed in prepare/finalize: ${result.error}`);
2227
- throw new Error(`Tool execution failed: ${result.error}`);
2228
- }
2229
- }
2230
- else {
2231
- logger.warn(`[ResponseModal] Tool not found for prepare/finalize: ${typeof prepareOrFinalize === "string"
2232
- ? prepareOrFinalize
2233
- : "inline tool"}`);
2234
- }
2235
- }
2236
- }
2237
1239
  }
2238
1240
  //# sourceMappingURL=ResponseModal.js.map