@falai/agent 2.2.4 → 2.4.2

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