@falai/agent 2.2.3 → 2.4.1

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