@falai/agent 2.2.4 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (397) hide show
  1. package/README.md +2 -2
  2. package/dist/adapters/MemoryAdapter.d.ts.map +1 -1
  3. package/dist/adapters/MemoryAdapter.js +10 -1
  4. package/dist/adapters/MemoryAdapter.js.map +1 -1
  5. package/dist/adapters/MongoAdapter.d.ts.map +1 -1
  6. package/dist/adapters/MongoAdapter.js +33 -2
  7. package/dist/adapters/MongoAdapter.js.map +1 -1
  8. package/dist/adapters/OpenSearchAdapter.d.ts.map +1 -1
  9. package/dist/adapters/OpenSearchAdapter.js +15 -1
  10. package/dist/adapters/OpenSearchAdapter.js.map +1 -1
  11. package/dist/adapters/PostgreSQLAdapter.d.ts.map +1 -1
  12. package/dist/adapters/PostgreSQLAdapter.js +35 -5
  13. package/dist/adapters/PostgreSQLAdapter.js.map +1 -1
  14. package/dist/adapters/PrismaAdapter.d.ts.map +1 -1
  15. package/dist/adapters/PrismaAdapter.js +58 -15
  16. package/dist/adapters/PrismaAdapter.js.map +1 -1
  17. package/dist/adapters/RedisAdapter.d.ts.map +1 -1
  18. package/dist/adapters/RedisAdapter.js +10 -1
  19. package/dist/adapters/RedisAdapter.js.map +1 -1
  20. package/dist/adapters/SQLiteAdapter.d.ts.map +1 -1
  21. package/dist/adapters/SQLiteAdapter.js +38 -6
  22. package/dist/adapters/SQLiteAdapter.js.map +1 -1
  23. package/dist/cjs/adapters/MemoryAdapter.d.ts.map +1 -1
  24. package/dist/cjs/adapters/MemoryAdapter.js +10 -1
  25. package/dist/cjs/adapters/MemoryAdapter.js.map +1 -1
  26. package/dist/cjs/adapters/MongoAdapter.d.ts.map +1 -1
  27. package/dist/cjs/adapters/MongoAdapter.js +33 -2
  28. package/dist/cjs/adapters/MongoAdapter.js.map +1 -1
  29. package/dist/cjs/adapters/OpenSearchAdapter.d.ts.map +1 -1
  30. package/dist/cjs/adapters/OpenSearchAdapter.js +15 -1
  31. package/dist/cjs/adapters/OpenSearchAdapter.js.map +1 -1
  32. package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +1 -1
  33. package/dist/cjs/adapters/PostgreSQLAdapter.js +35 -5
  34. package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -1
  35. package/dist/cjs/adapters/PrismaAdapter.d.ts.map +1 -1
  36. package/dist/cjs/adapters/PrismaAdapter.js +58 -15
  37. package/dist/cjs/adapters/PrismaAdapter.js.map +1 -1
  38. package/dist/cjs/adapters/RedisAdapter.d.ts.map +1 -1
  39. package/dist/cjs/adapters/RedisAdapter.js +10 -1
  40. package/dist/cjs/adapters/RedisAdapter.js.map +1 -1
  41. package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +1 -1
  42. package/dist/cjs/adapters/SQLiteAdapter.js +38 -6
  43. package/dist/cjs/adapters/SQLiteAdapter.js.map +1 -1
  44. package/dist/cjs/core/Agent.d.ts +28 -18
  45. package/dist/cjs/core/Agent.d.ts.map +1 -1
  46. package/dist/cjs/core/Agent.js +58 -68
  47. package/dist/cjs/core/Agent.js.map +1 -1
  48. package/dist/cjs/core/AutoChainExecutor.d.ts.map +1 -1
  49. package/dist/cjs/core/AutoChainExecutor.js +14 -20
  50. package/dist/cjs/core/AutoChainExecutor.js.map +1 -1
  51. package/dist/cjs/core/BranchEvaluator.d.ts +4 -3
  52. package/dist/cjs/core/BranchEvaluator.d.ts.map +1 -1
  53. package/dist/cjs/core/BranchEvaluator.js +18 -23
  54. package/dist/cjs/core/BranchEvaluator.js.map +1 -1
  55. package/dist/cjs/core/Flow.d.ts +2 -1
  56. package/dist/cjs/core/Flow.d.ts.map +1 -1
  57. package/dist/cjs/core/Flow.js +10 -3
  58. package/dist/cjs/core/Flow.js.map +1 -1
  59. package/dist/cjs/core/FlowRouter.d.ts +1 -0
  60. package/dist/cjs/core/FlowRouter.d.ts.map +1 -1
  61. package/dist/cjs/core/FlowRouter.js +28 -5
  62. package/dist/cjs/core/FlowRouter.js.map +1 -1
  63. package/dist/cjs/core/PersistenceManager.d.ts +3 -0
  64. package/dist/cjs/core/PersistenceManager.d.ts.map +1 -1
  65. package/dist/cjs/core/PersistenceManager.js +57 -5
  66. package/dist/cjs/core/PersistenceManager.js.map +1 -1
  67. package/dist/cjs/core/PromptComposer.d.ts.map +1 -1
  68. package/dist/cjs/core/PromptComposer.js +24 -10
  69. package/dist/cjs/core/PromptComposer.js.map +1 -1
  70. package/dist/cjs/core/ResponseGenerationError.d.ts +30 -0
  71. package/dist/cjs/core/ResponseGenerationError.d.ts.map +1 -0
  72. package/dist/cjs/core/ResponseGenerationError.js +37 -0
  73. package/dist/cjs/core/ResponseGenerationError.js.map +1 -0
  74. package/dist/cjs/core/ResponseModal.d.ts +43 -102
  75. package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
  76. package/dist/cjs/core/ResponseModal.js +178 -1194
  77. package/dist/cjs/core/ResponseModal.js.map +1 -1
  78. package/dist/cjs/core/ResponsePipeline.d.ts +58 -152
  79. package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -1
  80. package/dist/cjs/core/ResponsePipeline.js +408 -457
  81. package/dist/cjs/core/ResponsePipeline.js.map +1 -1
  82. package/dist/cjs/core/SessionFinalizer.d.ts +34 -0
  83. package/dist/cjs/core/SessionFinalizer.d.ts.map +1 -0
  84. package/dist/cjs/core/SessionFinalizer.js +61 -0
  85. package/dist/cjs/core/SessionFinalizer.js.map +1 -0
  86. package/dist/cjs/core/SessionManager.d.ts +1 -1
  87. package/dist/cjs/core/SessionManager.d.ts.map +1 -1
  88. package/dist/cjs/core/SessionManager.js +18 -7
  89. package/dist/cjs/core/SessionManager.js.map +1 -1
  90. package/dist/cjs/core/SignalCoordinator.d.ts +103 -0
  91. package/dist/cjs/core/SignalCoordinator.d.ts.map +1 -0
  92. package/dist/cjs/core/SignalCoordinator.js +207 -0
  93. package/dist/cjs/core/SignalCoordinator.js.map +1 -0
  94. package/dist/cjs/core/SignalEvaluator.d.ts +2 -2
  95. package/dist/cjs/core/SignalEvaluator.d.ts.map +1 -1
  96. package/dist/cjs/core/SignalEvaluator.js +11 -26
  97. package/dist/cjs/core/SignalEvaluator.js.map +1 -1
  98. package/dist/cjs/core/SignalProcessor.d.ts.map +1 -1
  99. package/dist/cjs/core/SignalProcessor.js +28 -11
  100. package/dist/cjs/core/SignalProcessor.js.map +1 -1
  101. package/dist/cjs/core/Step.d.ts +3 -1
  102. package/dist/cjs/core/Step.d.ts.map +1 -1
  103. package/dist/cjs/core/Step.js +10 -3
  104. package/dist/cjs/core/Step.js.map +1 -1
  105. package/dist/cjs/core/StepLifecycle.d.ts +33 -0
  106. package/dist/cjs/core/StepLifecycle.d.ts.map +1 -0
  107. package/dist/cjs/core/StepLifecycle.js +97 -0
  108. package/dist/cjs/core/StepLifecycle.js.map +1 -0
  109. package/dist/cjs/core/ToolLoopExecutor.d.ts +104 -0
  110. package/dist/cjs/core/ToolLoopExecutor.d.ts.map +1 -0
  111. package/dist/cjs/core/ToolLoopExecutor.js +391 -0
  112. package/dist/cjs/core/ToolLoopExecutor.js.map +1 -0
  113. package/dist/cjs/core/ToolManager.d.ts +1 -1
  114. package/dist/cjs/core/ToolManager.d.ts.map +1 -1
  115. package/dist/cjs/core/ToolManager.js.map +1 -1
  116. package/dist/cjs/index.d.ts +4 -5
  117. package/dist/cjs/index.d.ts.map +1 -1
  118. package/dist/cjs/index.js +7 -6
  119. package/dist/cjs/index.js.map +1 -1
  120. package/dist/cjs/providers/AnthropicProvider.d.ts +2 -0
  121. package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
  122. package/dist/cjs/providers/AnthropicProvider.js +28 -56
  123. package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
  124. package/dist/cjs/providers/DeepSeekProvider.d.ts +25 -21
  125. package/dist/cjs/providers/DeepSeekProvider.d.ts.map +1 -1
  126. package/dist/cjs/providers/DeepSeekProvider.js +48 -407
  127. package/dist/cjs/providers/DeepSeekProvider.js.map +1 -1
  128. package/dist/cjs/providers/GeminiProvider.d.ts +2 -0
  129. package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
  130. package/dist/cjs/providers/GeminiProvider.js +27 -56
  131. package/dist/cjs/providers/GeminiProvider.js.map +1 -1
  132. package/dist/cjs/providers/OpenAICompatibleProvider.d.ts +129 -0
  133. package/dist/cjs/providers/OpenAICompatibleProvider.d.ts.map +1 -0
  134. package/dist/cjs/providers/OpenAICompatibleProvider.js +485 -0
  135. package/dist/cjs/providers/OpenAICompatibleProvider.js.map +1 -0
  136. package/dist/cjs/providers/OpenAIProvider.d.ts +9 -28
  137. package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
  138. package/dist/cjs/providers/OpenAIProvider.js +23 -417
  139. package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
  140. package/dist/cjs/providers/OpenRouterProvider.d.ts +10 -27
  141. package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -1
  142. package/dist/cjs/providers/OpenRouterProvider.js +28 -417
  143. package/dist/cjs/providers/OpenRouterProvider.js.map +1 -1
  144. package/dist/cjs/providers/errorClassification.d.ts +61 -0
  145. package/dist/cjs/providers/errorClassification.d.ts.map +1 -0
  146. package/dist/cjs/providers/errorClassification.js +123 -0
  147. package/dist/cjs/providers/errorClassification.js.map +1 -0
  148. package/dist/cjs/providers/index.d.ts +4 -0
  149. package/dist/cjs/providers/index.d.ts.map +1 -1
  150. package/dist/cjs/providers/index.js +7 -1
  151. package/dist/cjs/providers/index.js.map +1 -1
  152. package/dist/cjs/types/agent.d.ts +2 -1
  153. package/dist/cjs/types/agent.d.ts.map +1 -1
  154. package/dist/cjs/types/ai.d.ts +20 -0
  155. package/dist/cjs/types/ai.d.ts.map +1 -1
  156. package/dist/cjs/types/errors.d.ts +33 -0
  157. package/dist/cjs/types/errors.d.ts.map +1 -1
  158. package/dist/cjs/types/errors.js +37 -1
  159. package/dist/cjs/types/errors.js.map +1 -1
  160. package/dist/cjs/types/flow.d.ts +10 -5
  161. package/dist/cjs/types/flow.d.ts.map +1 -1
  162. package/dist/cjs/types/history.d.ts +1 -1
  163. package/dist/cjs/types/history.d.ts.map +1 -1
  164. package/dist/cjs/types/index.d.ts +5 -4
  165. package/dist/cjs/types/index.d.ts.map +1 -1
  166. package/dist/cjs/types/index.js +3 -1
  167. package/dist/cjs/types/index.js.map +1 -1
  168. package/dist/cjs/types/persistence.d.ts +43 -2
  169. package/dist/cjs/types/persistence.d.ts.map +1 -1
  170. package/dist/cjs/types/session.d.ts +8 -0
  171. package/dist/cjs/types/session.d.ts.map +1 -1
  172. package/dist/cjs/types/signals.d.ts +18 -2
  173. package/dist/cjs/types/signals.d.ts.map +1 -1
  174. package/dist/cjs/types/template.d.ts +3 -1
  175. package/dist/cjs/types/template.d.ts.map +1 -1
  176. package/dist/cjs/types/tool.d.ts +4 -4
  177. package/dist/cjs/types/tool.d.ts.map +1 -1
  178. package/dist/cjs/utils/condition.d.ts +19 -0
  179. package/dist/cjs/utils/condition.d.ts.map +1 -1
  180. package/dist/cjs/utils/condition.js +86 -15
  181. package/dist/cjs/utils/condition.js.map +1 -1
  182. package/dist/cjs/utils/session.d.ts +1 -0
  183. package/dist/cjs/utils/session.d.ts.map +1 -1
  184. package/dist/cjs/utils/session.js +1 -0
  185. package/dist/cjs/utils/session.js.map +1 -1
  186. package/dist/core/Agent.d.ts +28 -18
  187. package/dist/core/Agent.d.ts.map +1 -1
  188. package/dist/core/Agent.js +58 -68
  189. package/dist/core/Agent.js.map +1 -1
  190. package/dist/core/AutoChainExecutor.d.ts.map +1 -1
  191. package/dist/core/AutoChainExecutor.js +14 -20
  192. package/dist/core/AutoChainExecutor.js.map +1 -1
  193. package/dist/core/BranchEvaluator.d.ts +4 -3
  194. package/dist/core/BranchEvaluator.d.ts.map +1 -1
  195. package/dist/core/BranchEvaluator.js +18 -23
  196. package/dist/core/BranchEvaluator.js.map +1 -1
  197. package/dist/core/Flow.d.ts +2 -1
  198. package/dist/core/Flow.d.ts.map +1 -1
  199. package/dist/core/Flow.js +10 -3
  200. package/dist/core/Flow.js.map +1 -1
  201. package/dist/core/FlowRouter.d.ts +1 -0
  202. package/dist/core/FlowRouter.d.ts.map +1 -1
  203. package/dist/core/FlowRouter.js +28 -5
  204. package/dist/core/FlowRouter.js.map +1 -1
  205. package/dist/core/PersistenceManager.d.ts +3 -0
  206. package/dist/core/PersistenceManager.d.ts.map +1 -1
  207. package/dist/core/PersistenceManager.js +58 -6
  208. package/dist/core/PersistenceManager.js.map +1 -1
  209. package/dist/core/PromptComposer.d.ts.map +1 -1
  210. package/dist/core/PromptComposer.js +24 -10
  211. package/dist/core/PromptComposer.js.map +1 -1
  212. package/dist/core/ResponseGenerationError.d.ts +30 -0
  213. package/dist/core/ResponseGenerationError.d.ts.map +1 -0
  214. package/dist/core/ResponseGenerationError.js +33 -0
  215. package/dist/core/ResponseGenerationError.js.map +1 -0
  216. package/dist/core/ResponseModal.d.ts +43 -102
  217. package/dist/core/ResponseModal.d.ts.map +1 -1
  218. package/dist/core/ResponseModal.js +171 -1186
  219. package/dist/core/ResponseModal.js.map +1 -1
  220. package/dist/core/ResponsePipeline.d.ts +58 -152
  221. package/dist/core/ResponsePipeline.d.ts.map +1 -1
  222. package/dist/core/ResponsePipeline.js +409 -458
  223. package/dist/core/ResponsePipeline.js.map +1 -1
  224. package/dist/core/SessionFinalizer.d.ts +34 -0
  225. package/dist/core/SessionFinalizer.d.ts.map +1 -0
  226. package/dist/core/SessionFinalizer.js +57 -0
  227. package/dist/core/SessionFinalizer.js.map +1 -0
  228. package/dist/core/SessionManager.d.ts +1 -1
  229. package/dist/core/SessionManager.d.ts.map +1 -1
  230. package/dist/core/SessionManager.js +18 -7
  231. package/dist/core/SessionManager.js.map +1 -1
  232. package/dist/core/SignalCoordinator.d.ts +103 -0
  233. package/dist/core/SignalCoordinator.d.ts.map +1 -0
  234. package/dist/core/SignalCoordinator.js +203 -0
  235. package/dist/core/SignalCoordinator.js.map +1 -0
  236. package/dist/core/SignalEvaluator.d.ts +2 -2
  237. package/dist/core/SignalEvaluator.d.ts.map +1 -1
  238. package/dist/core/SignalEvaluator.js +11 -26
  239. package/dist/core/SignalEvaluator.js.map +1 -1
  240. package/dist/core/SignalProcessor.d.ts.map +1 -1
  241. package/dist/core/SignalProcessor.js +28 -11
  242. package/dist/core/SignalProcessor.js.map +1 -1
  243. package/dist/core/Step.d.ts +3 -1
  244. package/dist/core/Step.d.ts.map +1 -1
  245. package/dist/core/Step.js +10 -3
  246. package/dist/core/Step.js.map +1 -1
  247. package/dist/core/StepLifecycle.d.ts +33 -0
  248. package/dist/core/StepLifecycle.d.ts.map +1 -0
  249. package/dist/core/StepLifecycle.js +93 -0
  250. package/dist/core/StepLifecycle.js.map +1 -0
  251. package/dist/core/ToolLoopExecutor.d.ts +104 -0
  252. package/dist/core/ToolLoopExecutor.d.ts.map +1 -0
  253. package/dist/core/ToolLoopExecutor.js +387 -0
  254. package/dist/core/ToolLoopExecutor.js.map +1 -0
  255. package/dist/core/ToolManager.d.ts +1 -1
  256. package/dist/core/ToolManager.d.ts.map +1 -1
  257. package/dist/core/ToolManager.js.map +1 -1
  258. package/dist/index.d.ts +4 -5
  259. package/dist/index.d.ts.map +1 -1
  260. package/dist/index.js +3 -3
  261. package/dist/index.js.map +1 -1
  262. package/dist/providers/AnthropicProvider.d.ts +2 -0
  263. package/dist/providers/AnthropicProvider.d.ts.map +1 -1
  264. package/dist/providers/AnthropicProvider.js +22 -50
  265. package/dist/providers/AnthropicProvider.js.map +1 -1
  266. package/dist/providers/DeepSeekProvider.d.ts +25 -21
  267. package/dist/providers/DeepSeekProvider.d.ts.map +1 -1
  268. package/dist/providers/DeepSeekProvider.js +49 -408
  269. package/dist/providers/DeepSeekProvider.js.map +1 -1
  270. package/dist/providers/GeminiProvider.d.ts +2 -0
  271. package/dist/providers/GeminiProvider.d.ts.map +1 -1
  272. package/dist/providers/GeminiProvider.js +21 -50
  273. package/dist/providers/GeminiProvider.js.map +1 -1
  274. package/dist/providers/OpenAICompatibleProvider.d.ts +129 -0
  275. package/dist/providers/OpenAICompatibleProvider.d.ts.map +1 -0
  276. package/dist/providers/OpenAICompatibleProvider.js +481 -0
  277. package/dist/providers/OpenAICompatibleProvider.js.map +1 -0
  278. package/dist/providers/OpenAIProvider.d.ts +9 -28
  279. package/dist/providers/OpenAIProvider.d.ts.map +1 -1
  280. package/dist/providers/OpenAIProvider.js +23 -417
  281. package/dist/providers/OpenAIProvider.js.map +1 -1
  282. package/dist/providers/OpenRouterProvider.d.ts +10 -27
  283. package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
  284. package/dist/providers/OpenRouterProvider.js +28 -417
  285. package/dist/providers/OpenRouterProvider.js.map +1 -1
  286. package/dist/providers/errorClassification.d.ts +61 -0
  287. package/dist/providers/errorClassification.d.ts.map +1 -0
  288. package/dist/providers/errorClassification.js +116 -0
  289. package/dist/providers/errorClassification.js.map +1 -0
  290. package/dist/providers/index.d.ts +4 -0
  291. package/dist/providers/index.d.ts.map +1 -1
  292. package/dist/providers/index.js +2 -0
  293. package/dist/providers/index.js.map +1 -1
  294. package/dist/types/agent.d.ts +2 -1
  295. package/dist/types/agent.d.ts.map +1 -1
  296. package/dist/types/ai.d.ts +20 -0
  297. package/dist/types/ai.d.ts.map +1 -1
  298. package/dist/types/errors.d.ts +33 -0
  299. package/dist/types/errors.d.ts.map +1 -1
  300. package/dist/types/errors.js +34 -0
  301. package/dist/types/errors.js.map +1 -1
  302. package/dist/types/flow.d.ts +10 -5
  303. package/dist/types/flow.d.ts.map +1 -1
  304. package/dist/types/history.d.ts +1 -1
  305. package/dist/types/history.d.ts.map +1 -1
  306. package/dist/types/index.d.ts +5 -4
  307. package/dist/types/index.d.ts.map +1 -1
  308. package/dist/types/index.js +1 -1
  309. package/dist/types/index.js.map +1 -1
  310. package/dist/types/persistence.d.ts +43 -2
  311. package/dist/types/persistence.d.ts.map +1 -1
  312. package/dist/types/session.d.ts +8 -0
  313. package/dist/types/session.d.ts.map +1 -1
  314. package/dist/types/signals.d.ts +18 -2
  315. package/dist/types/signals.d.ts.map +1 -1
  316. package/dist/types/template.d.ts +3 -1
  317. package/dist/types/template.d.ts.map +1 -1
  318. package/dist/types/tool.d.ts +4 -4
  319. package/dist/types/tool.d.ts.map +1 -1
  320. package/dist/utils/condition.d.ts +19 -0
  321. package/dist/utils/condition.d.ts.map +1 -1
  322. package/dist/utils/condition.js +84 -15
  323. package/dist/utils/condition.js.map +1 -1
  324. package/dist/utils/session.d.ts +1 -0
  325. package/dist/utils/session.d.ts.map +1 -1
  326. package/dist/utils/session.js +1 -0
  327. package/dist/utils/session.js.map +1 -1
  328. package/docs/README.md +1 -1
  329. package/docs/guides/branching.md +1 -1
  330. package/docs/guides/compaction.md +12 -5
  331. package/docs/guides/conditions.md +15 -2
  332. package/docs/guides/error-handling.md +59 -9
  333. package/docs/guides/instructions.md +2 -2
  334. package/docs/guides/persistence.md +69 -2
  335. package/docs/guides/streaming.md +2 -0
  336. package/docs/migration/README.md +5 -1
  337. package/docs/migration/v2-3-to-v2-4.md +316 -0
  338. package/docs/reference/adapters.md +55 -7
  339. package/docs/reference/branches.md +2 -2
  340. package/docs/reference/create-agent.md +3 -3
  341. package/docs/reference/errors.md +66 -1
  342. package/docs/reference/flow.md +1 -1
  343. package/docs/reference/instruction.md +4 -4
  344. package/docs/reference/providers.md +100 -3
  345. package/docs/reference/signals.md +14 -3
  346. package/docs/reference/step.md +2 -2
  347. package/docs/reference/tool.md +3 -1
  348. package/docs/start/05-go-to-production.md +3 -0
  349. package/package.json +1 -1
  350. package/src/adapters/MemoryAdapter.ts +15 -1
  351. package/src/adapters/MongoAdapter.ts +41 -2
  352. package/src/adapters/OpenSearchAdapter.ts +24 -1
  353. package/src/adapters/PostgreSQLAdapter.ts +45 -5
  354. package/src/adapters/PrismaAdapter.ts +82 -16
  355. package/src/adapters/RedisAdapter.ts +19 -1
  356. package/src/adapters/SQLiteAdapter.ts +47 -5
  357. package/src/core/Agent.ts +70 -85
  358. package/src/core/AutoChainExecutor.ts +27 -57
  359. package/src/core/BranchEvaluator.ts +24 -30
  360. package/src/core/Flow.ts +10 -3
  361. package/src/core/FlowRouter.ts +36 -4
  362. package/src/core/PersistenceManager.ts +79 -10
  363. package/src/core/PromptComposer.ts +25 -12
  364. package/src/core/ResponseGenerationError.ts +56 -0
  365. package/src/core/ResponseModal.ts +233 -1482
  366. package/src/core/ResponsePipeline.ts +492 -662
  367. package/src/core/SessionFinalizer.ts +78 -0
  368. package/src/core/SessionManager.ts +21 -9
  369. package/src/core/SignalCoordinator.ts +263 -0
  370. package/src/core/SignalEvaluator.ts +12 -29
  371. package/src/core/SignalProcessor.ts +34 -11
  372. package/src/core/Step.ts +11 -3
  373. package/src/core/StepLifecycle.ts +139 -0
  374. package/src/core/ToolLoopExecutor.ts +492 -0
  375. package/src/core/ToolManager.ts +2 -1
  376. package/src/index.ts +7 -5
  377. package/src/providers/AnthropicProvider.ts +30 -72
  378. package/src/providers/DeepSeekProvider.ts +74 -586
  379. package/src/providers/GeminiProvider.ts +29 -70
  380. package/src/providers/OpenAICompatibleProvider.ts +738 -0
  381. package/src/providers/OpenAIProvider.ts +29 -602
  382. package/src/providers/OpenRouterProvider.ts +38 -596
  383. package/src/providers/errorClassification.ts +172 -0
  384. package/src/providers/index.ts +13 -0
  385. package/src/types/agent.ts +2 -1
  386. package/src/types/ai.ts +22 -0
  387. package/src/types/errors.ts +57 -0
  388. package/src/types/flow.ts +10 -5
  389. package/src/types/history.ts +1 -2
  390. package/src/types/index.ts +5 -1
  391. package/src/types/persistence.ts +50 -2
  392. package/src/types/session.ts +9 -0
  393. package/src/types/signals.ts +20 -2
  394. package/src/types/template.ts +3 -1
  395. package/src/types/tool.ts +10 -10
  396. package/src/utils/condition.ts +115 -18
  397. package/src/utils/session.ts +2 -0
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Response processing utilities shared between respond() and respondStream() methods
3
3
  */
4
- import { createSession, enterStep, mergeCollected, completeCurrentFlow, logger, historyToEvents, serializeToolResult, } from "../utils";
4
+ import { createSession, enterStep, mergeCollected, logger, historyToEvents, eventsToHistory, getLastMessageFromHistory, } from "../utils";
5
5
  import { enterFlow } from "../utils/session";
6
6
  import { createTemplateContext } from "../utils/template";
7
7
  import { FlowConfigurationError } from "../core/Step";
8
8
  import { evaluateBranches, createAiConditionEvaluator } from "./BranchEvaluator";
9
9
  import { DirectiveChainTracker } from "./DirectiveChainTracker";
10
- import { DirectiveBus } from "./DirectiveBus";
10
+ import { ResponseGenerationError } from "./ResponseGenerationError";
11
11
  /**
12
12
  * Position fields on a Directive that represent a navigation decision.
13
13
  * When any of these is set, the directive "wins" the turn's position decision
@@ -29,16 +29,13 @@ export function hasDirectivePositionField(directive) {
29
29
  * Shared response processing logic between respond() and respondStream() methods
30
30
  */
31
31
  export class ResponsePipeline {
32
- constructor(options, getFlows, tools, flowRouter, updateContext, updateData, updateCollectedData, toolManager, signalProcessor) {
32
+ constructor(options, getFlows, flowRouter, signalCoordinator, updateCollectedData, getSchema) {
33
33
  this.options = options;
34
34
  this.getFlows = getFlows;
35
- this.tools = tools;
36
35
  this.flowRouter = flowRouter;
37
- this.updateContext = updateContext;
38
- this.updateData = updateData;
36
+ this.signalCoordinator = signalCoordinator;
39
37
  this.updateCollectedData = updateCollectedData;
40
- this.toolManager = toolManager;
41
- this.signalProcessor = signalProcessor;
38
+ this.getSchema = getSchema;
42
39
  }
43
40
  /**
44
41
  * Create a fresh chain tracker for the current turn.
@@ -58,87 +55,30 @@ export class ResponsePipeline {
58
55
  }
59
56
  return this._chainTracker;
60
57
  }
61
- /**
62
- * Create a fresh DirectiveBus for a new turn.
63
- * The bus collects directives from hooks, tools, and branches during the turn
64
- * and merges them at phase boundaries via Algorithm 4.
65
- */
66
- createDirectiveBus() {
67
- return new DirectiveBus();
68
- }
69
- // ──────────────────────────────────────────────────────────────────────────
70
- // Signal pipeline phases
71
- // ──────────────────────────────────────────────────────────────────────────
72
- /**
73
- * PRE-SIGNAL PHASE — Evaluates pre/both signals in parallel with routing.
74
- *
75
- * Delegates to `signalProcessor.runPreSignalPhase(...)` when configured.
76
- * When no signal processor is present (zero-cost path), returns a stable
77
- * empty shape so callers don't need to branch.
78
- *
79
- * @requirements 2.1, 2.3, 8.6, 13.3
80
- */
81
- async runPreSignalPhase(session, context, history) {
82
- if (!this.signalProcessor) {
83
- return { firings: [], updatedSession: session, mergedDirective: undefined };
84
- }
85
- const result = await this.signalProcessor.runPreSignalPhase({ session, history, context });
86
- return {
87
- firings: result.firings,
88
- updatedSession: result.updatedSession,
89
- mergedDirective: result.mergedDirective,
90
- };
91
- }
92
- /**
93
- * POST-SIGNAL PHASE — Evaluates post/both signals after finalize/onComplete.
94
- *
95
- * Delegates to `signalProcessor.runPostSignalPhase(...)` when configured.
96
- * When no signal processor is present, returns a stable empty shape.
97
- *
98
- * Post-phase signals see the complete turn result: assistant message in history,
99
- * collected data, tool results. Position directives from this phase set
100
- * `session.pendingDirective` (no mid-turn re-entry per D6 decision).
101
- *
102
- * Pre-LLM-only fields (`appendPrompt`, `injectTools`, `halt`) are already
103
- * dropped inside `runPostSignalPhase` per Phase 4.5 — this seam does NOT
104
- * re-introduce them.
105
- *
106
- * @requirements 9.1, 9.2, 9.3, 9.4
107
- */
108
- async runPostSignalPhase(session, context, history) {
109
- if (!this.signalProcessor) {
110
- return { firings: [], updatedSession: session, mergedDirective: undefined };
111
- }
112
- const result = await this.signalProcessor.runPostSignalPhase({ session, history, context });
113
- return {
114
- firings: result.firings,
115
- updatedSession: result.updatedSession,
116
- mergedDirective: result.mergedDirective,
117
- };
118
- }
119
58
  /**
120
59
  * Prepare context and session for response generation
121
60
  */
122
61
  async prepareResponseContext(params) {
123
62
  const { contextOverride, session } = params;
124
- // Get current context (may fetch from provider)
125
- let currentContext = await this.getContext();
63
+ let currentContext = params.currentContext;
64
+ let contextAfterHook;
126
65
  // Call beforeRespond hook if configured
127
66
  if (this.options.hooks?.beforeRespond && currentContext !== undefined) {
128
67
  currentContext = await this.options.hooks.beforeRespond(currentContext);
129
- // Update stored context with the result from beforeRespond
130
- this.context = currentContext;
68
+ // Surface the hook result so the caller can sync it back to the agent
69
+ contextAfterHook = currentContext;
131
70
  }
132
71
  // Merge context with override
133
72
  const effectiveContext = {
134
73
  ...currentContext,
135
74
  ...contextOverride,
136
75
  };
137
- // Initialize or get session (use current session if available)
138
- const targetSession = session || this.currentSession || createSession();
76
+ // Initialize or get session (use the live session if available)
77
+ const targetSession = session || params.currentSession || createSession();
139
78
  return {
140
79
  effectiveContext,
141
80
  session: targetSession,
81
+ contextAfterHook,
142
82
  };
143
83
  }
144
84
  /**
@@ -158,7 +98,7 @@ export class ResponsePipeline {
158
98
  // Check for pending directive from previous flow completion or external dispatch
159
99
  if (targetSession.pendingDirective) {
160
100
  const directive = targetSession.pendingDirective;
161
- logger.debug(`[ResponseHandler] Applying pending directive at start of turn`);
101
+ logger.debug(`[ResponsePipeline] Applying pending directive at start of turn`);
162
102
  // Track directive chain depth (Requirement 22.1)
163
103
  const tracker = this.chainTracker;
164
104
  tracker.record(directive, "pending");
@@ -182,7 +122,7 @@ export class ResponsePipeline {
182
122
  if (flowTarget) {
183
123
  const targetFlow = flows.find((r) => r.id === flowTarget || r.title === flowTarget);
184
124
  if (targetFlow) {
185
- logger.debug(`[ResponseHandler] Pending directive goTo → flow: ${targetFlow.title}`);
125
+ logger.debug(`[ResponsePipeline] Pending directive goTo → flow: ${targetFlow.title}`);
186
126
  targetSession = enterFlow(targetSession, targetFlow.id, targetFlow.title);
187
127
  // Merge initial data if available
188
128
  if (targetFlow.initialData) {
@@ -291,7 +231,7 @@ export class ResponsePipeline {
291
231
  completedFlows = orchestration.completedFlows || [];
292
232
  // Log if flow is complete
293
233
  if (isFlowComplete) {
294
- logger.debug(`[ResponseHandler] Flow complete: all required data collected or last step reached`);
234
+ logger.debug(`[ResponsePipeline] Flow complete: all required data collected or last step reached`);
295
235
  }
296
236
  }
297
237
  return {
@@ -307,7 +247,7 @@ export class ResponsePipeline {
307
247
  * Determine next step and update session
308
248
  */
309
249
  async determineNextStep(params) {
310
- const { selectedFlow, selectedStep, session, isFlowComplete, busDirective } = params;
250
+ const { selectedFlow, selectedStep, session, isFlowComplete, context, busDirective } = params;
311
251
  if (!selectedFlow) {
312
252
  return { nextStep: undefined, session };
313
253
  }
@@ -317,7 +257,7 @@ export class ResponsePipeline {
317
257
  // complete, abort, reset), that decision wins the turn. Do NOT evaluate
318
258
  // branches or linear/AI selection — the caller applies the bus directive.
319
259
  if (hasDirectivePositionField(busDirective)) {
320
- logger.debug(`[ResponseHandler] Directive bus winner has position field — skipping branch evaluation and linear/AI selection`);
260
+ logger.debug(`[ResponsePipeline] Directive bus winner has position field — skipping branch evaluation and linear/AI selection`);
321
261
  return { nextStep: undefined, session };
322
262
  }
323
263
  // STEP 1 (Algorithm 1): branches win over linear chain AND flow completion.
@@ -328,8 +268,7 @@ export class ResponsePipeline {
328
268
  ? selectedFlow.getStep(session.currentStep.id)
329
269
  : undefined;
330
270
  if (currentStep?.branches && currentStep.branches.length > 0) {
331
- const contextToUse = this.getStoredContext();
332
- const branchResult = await this.evaluateStepBranches(currentStep, selectedFlow, session, contextToUse);
271
+ const branchResult = await this.evaluateStepBranches(currentStep, selectedFlow, session, context);
333
272
  if (branchResult) {
334
273
  return branchResult;
335
274
  }
@@ -349,13 +288,12 @@ export class ResponsePipeline {
349
288
  const currentStep = session.currentFlow?.id === selectedFlow.id && session.currentStep
350
289
  ? selectedFlow.getStep(session.currentStep.id)
351
290
  : undefined;
352
- const contextToUse = this.getStoredContext();
353
291
  // Get candidate steps based on current position in the flow
354
292
  const candidates = await this.flowRouter.getCandidateStepsWithConditions(selectedFlow, currentStep, // Pass current step instead of undefined to maintain progression
355
- createTemplateContext({ data: session.data, session, context: contextToUse }));
293
+ createTemplateContext({ data: session.data, session, context }));
356
294
  if (candidates.length > 0) {
357
295
  nextStep = candidates[0].step;
358
- logger.debug(`[ResponseHandler] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new flow'}`);
296
+ logger.debug(`[ResponsePipeline] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new flow'}`);
359
297
  }
360
298
  else {
361
299
  // Fallback to initial step even if it should be skipped
@@ -368,7 +306,7 @@ export class ResponsePipeline {
368
306
  const sessionData = session.data || {};
369
307
  const missingRequires = nextStep.requires.filter(field => sessionData[String(field)] === undefined);
370
308
  if (missingRequires.length > 0) {
371
- logger.debug(`[ResponseHandler] Cannot enter step "${nextStep.id}": missing required fields [${missingRequires.join(', ')}]. Staying at current step.`);
309
+ logger.debug(`[ResponsePipeline] Cannot enter step "${nextStep.id}": missing required fields [${missingRequires.join(', ')}]. Staying at current step.`);
372
310
  // Stay at current step - don't enter the next one
373
311
  const currentStepId = session.currentStep?.id;
374
312
  if (currentStepId && selectedFlow) {
@@ -382,7 +320,7 @@ export class ResponsePipeline {
382
320
  }
383
321
  // Update session with next step
384
322
  const updatedSession = enterStep(session, nextStep.id, nextStep.description);
385
- logger.debug(`[ResponseHandler] Entered step: ${nextStep.id}`);
323
+ logger.debug(`[ResponsePipeline] Entered step: ${nextStep.id}`);
386
324
  return { nextStep, session: updatedSession };
387
325
  }
388
326
  /**
@@ -421,7 +359,7 @@ export class ResponsePipeline {
421
359
  const localStep = selectedFlow.getStep(result);
422
360
  if (localStep) {
423
361
  const updatedSession = enterStep(session, localStep.id, localStep.description);
424
- logger.debug(`[ResponseHandler] Branch resolved to local step: ${localStep.id}`);
362
+ logger.debug(`[ResponsePipeline] Branch resolved to local step: ${localStep.id}`);
425
363
  return { nextStep: localStep, session: updatedSession };
426
364
  }
427
365
  // 2. Look up in agent's flow registry
@@ -429,7 +367,7 @@ export class ResponsePipeline {
429
367
  const targetFlow = flows.find(f => f.id === result || f.title === result);
430
368
  if (targetFlow) {
431
369
  // Treat as applyDirective({ goTo: result }) — enter the target flow
432
- logger.debug(`[ResponseHandler] Branch resolved to flow: ${targetFlow.title}`);
370
+ logger.debug(`[ResponsePipeline] Branch resolved to flow: ${targetFlow.title}`);
433
371
  const updatedSession = enterFlow(session, targetFlow.id, targetFlow.title);
434
372
  return { nextStep: undefined, session: updatedSession, flowChanged: targetFlow };
435
373
  }
@@ -467,7 +405,7 @@ export class ResponsePipeline {
467
405
  updatedSession = enterStep(updatedSession, stepTarget);
468
406
  // Try to resolve the target step instance for the caller
469
407
  const targetStepInstance = targetFlow.getStep(stepTarget);
470
- logger.debug(`[ResponseHandler] Branch directive goToStep → ${flowTarget}.${stepTarget}`);
408
+ logger.debug(`[ResponsePipeline] Branch directive goToStep → ${flowTarget}.${stepTarget}`);
471
409
  return { nextStep: targetStepInstance || undefined, session: updatedSession, flowChanged: targetFlow };
472
410
  }
473
411
  throw new FlowConfigurationError(`[FlowConfigurationError] Branch directive goToStep targets unknown flow: "${flowTarget}" does not match any flow id or title. ` +
@@ -477,7 +415,7 @@ export class ResponsePipeline {
477
415
  const targetStep = selectedFlow.getStep(stepTarget);
478
416
  if (targetStep) {
479
417
  updatedSession = enterStep(updatedSession, targetStep.id, targetStep.description);
480
- logger.debug(`[ResponseHandler] Branch directive goToStep → ${targetStep.id}`);
418
+ logger.debug(`[ResponsePipeline] Branch directive goToStep → ${targetStep.id}`);
481
419
  return { nextStep: targetStep, session: updatedSession };
482
420
  }
483
421
  throw new FlowConfigurationError(`[FlowConfigurationError] Branch directive goToStep targets unknown step: "${stepTarget}" does not exist in flow "${selectedFlow.id}". ` +
@@ -496,7 +434,7 @@ export class ResponsePipeline {
496
434
  if (typeof directive.goTo === 'object' && directive.goTo.step) {
497
435
  updatedSession = enterStep(updatedSession, directive.goTo.step);
498
436
  }
499
- logger.debug(`[ResponseHandler] Branch directive goTo → ${targetFlow.title}`);
437
+ logger.debug(`[ResponsePipeline] Branch directive goTo → ${targetFlow.title}`);
500
438
  return { nextStep: undefined, session: updatedSession, flowChanged: targetFlow };
501
439
  }
502
440
  throw new FlowConfigurationError(`[FlowConfigurationError] Branch directive goTo targets unknown flow: "${flowTarget}" does not match any flow id or title. ` +
@@ -504,11 +442,11 @@ export class ResponsePipeline {
504
442
  }
505
443
  }
506
444
  if (directive.complete) {
507
- logger.debug(`[ResponseHandler] Branch directive complete`);
445
+ logger.debug(`[ResponsePipeline] Branch directive complete`);
508
446
  return { nextStep: undefined, session: updatedSession };
509
447
  }
510
448
  if (directive.abort) {
511
- logger.debug(`[ResponseHandler] Branch directive abort`);
449
+ logger.debug(`[ResponsePipeline] Branch directive abort`);
512
450
  return { nextStep: undefined, session: updatedSession };
513
451
  }
514
452
  if (directive.reset) {
@@ -525,7 +463,7 @@ export class ResponsePipeline {
525
463
  // Reset to initial step
526
464
  const initialStep = selectedFlow.initialStep;
527
465
  updatedSession = enterStep(updatedSession, initialStep.id, initialStep.description);
528
- logger.debug(`[ResponseHandler] Branch directive reset → ${initialStep.id}`);
466
+ logger.debug(`[ResponsePipeline] Branch directive reset → ${initialStep.id}`);
529
467
  return { nextStep: initialStep, session: updatedSession };
530
468
  }
531
469
  // Directive with only non-position fields (e.g., just dataUpdate/contextUpdate/reply)
@@ -533,407 +471,420 @@ export class ResponsePipeline {
533
471
  return { nextStep: undefined, session: updatedSession };
534
472
  }
535
473
  /**
536
- * Execute tool calls and handle results
474
+ * TURN ROUTING + STEP SELECTION — the single entry point for deciding which
475
+ * flow and step a turn renders. Runs the routing-skip optimization, the
476
+ * pre-signal phase (parallel with routing when a processor is configured),
477
+ * pre-extraction, and next-step determination.
537
478
  */
538
- async executeToolCalls(params) {
539
- const { toolCalls, selectedFlow, context, session, history, isStreaming = false, } = params;
540
- if (toolCalls.length === 0) {
541
- return { session, toolCalls: undefined };
542
- }
543
- logger.debug(`[ResponseHandler] Executing ${toolCalls.length} ${isStreaming ? "streaming " : ""}tool calls`);
544
- let updatedSession = session;
545
- const executedToolCalls = [];
546
- const toolResults = new Map();
547
- for (const toolCall of toolCalls) {
548
- const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
549
- if (!tool) {
550
- logger.warn(`[ToolExecutionError] Tool not found: "${toolCall.toolName}" is not registered in any scope (transient, step, flow, or agent). Skipping this tool call. Register the tool or check the tool name.`);
551
- continue;
479
+ async routeAndSelectStep(params) {
480
+ try {
481
+ // Create a fresh chain tracker for this turn (Requirement 22.1)
482
+ this.createChainTracker();
483
+ // ROUTING SKIP OPTIMIZATION (Requirements 20.1, 20.2, 20.3):
484
+ // When the current step has collect fields AND pre-extraction populates at least
485
+ // one of those fields, skip FlowRouter.decideFlowAndStep for this turn.
486
+ const routingSkipResult = await this.attemptRoutingSkipForCollect(params);
487
+ if (routingSkipResult) {
488
+ // Even when routing is skipped, run pre-signal phase if processor is present
489
+ if (this.signalCoordinator.enabled) {
490
+ const signalResult = await this.signalCoordinator.runPrePhase(params.session, params.context, params.history);
491
+ // If signal halts, override the routing skip result
492
+ if (signalResult.mergedDirective?.halt) {
493
+ return {
494
+ ...routingSkipResult,
495
+ session: signalResult.updatedSession,
496
+ signalFirings: signalResult.firings,
497
+ signalHalted: true,
498
+ signalHaltReply: signalResult.mergedDirective.reply,
499
+ };
500
+ }
501
+ // If signal has position fields, override routing skip result
502
+ if (hasDirectivePositionField(signalResult.mergedDirective)) {
503
+ return this.signalCoordinator.applyPositionDirective(signalResult);
504
+ }
505
+ // Non-position directive: propagate for pre-LLM augmentation
506
+ return {
507
+ ...routingSkipResult,
508
+ session: signalResult.updatedSession,
509
+ signalFirings: signalResult.firings,
510
+ signalPreDirective: signalResult.mergedDirective || undefined,
511
+ };
512
+ }
513
+ return routingSkipResult;
552
514
  }
553
- // Use ToolManager for unified tool execution
554
- let result;
555
- if (this.toolManager) {
556
- result = await this.toolManager.executeTool({
557
- tool,
558
- context,
559
- updateContext: this.updateContext,
560
- updateData: this.updateCollectedData || (async () => { }),
561
- history,
562
- data: updatedSession.data,
515
+ // ── PARALLEL PRE-SIGNAL PHASE + ROUTING (Algorithm 5) ────────────────
516
+ // When signalProcessor is present, run pre-signals in parallel with routing.
517
+ // When absent, call the router directly (zero overhead, preserve current behavior).
518
+ if (this.signalCoordinator.enabled) {
519
+ // Run pre-signal phase in parallel with routing (Requirement 8.1)
520
+ const [signalResult, routingResult] = await Promise.all([
521
+ this.signalCoordinator.runPrePhase(params.session, params.context, params.history),
522
+ this.handleRoutingAndStepSelection({
523
+ session: params.session,
524
+ history: params.history,
525
+ context: params.context,
526
+ signal: params.signal,
527
+ }),
528
+ ]);
529
+ // ── Requirement 8.2: halt → discard routing, skip LLM ────────────
530
+ if (signalResult.mergedDirective?.halt) {
531
+ return {
532
+ selectedFlow: undefined,
533
+ selectedStep: undefined,
534
+ session: signalResult.updatedSession,
535
+ isFlowComplete: false,
536
+ signalFirings: signalResult.firings,
537
+ signalHalted: true,
538
+ signalHaltReply: signalResult.mergedDirective.reply,
539
+ };
540
+ }
541
+ // ── Requirement 8.3: position directive → discard routing, apply signal position ──
542
+ if (hasDirectivePositionField(signalResult.mergedDirective)) {
543
+ return this.signalCoordinator.applyPositionDirective(signalResult);
544
+ }
545
+ // ── Requirement 8.4: non-position directive → use routing, propagate augmentation ──
546
+ // ── Requirement 8.5: no directive → use routing as-is ─────────────
547
+ let updatedSession = signalResult.updatedSession;
548
+ // Apply data/context updates from signal to the routed session
549
+ if (signalResult.mergedDirective?.dataUpdate) {
550
+ updatedSession = mergeCollected(updatedSession, signalResult.mergedDirective.dataUpdate);
551
+ }
552
+ // Use routing result for flow/step, but carry signal session state
553
+ // Merge routing session changes on top of signal session
554
+ const routingSession = routingResult.session;
555
+ updatedSession = {
556
+ ...updatedSession,
557
+ currentFlow: routingSession.currentFlow,
558
+ currentStep: routingSession.currentStep,
559
+ flowHistory: routingSession.flowHistory,
560
+ pendingDirective: routingSession.pendingDirective,
561
+ };
562
+ const isFlowComplete = routingResult.isFlowComplete;
563
+ // PRE-EXTRACTION: same logic as below — extract data from user message
564
+ if (routingResult.selectedFlow && !isFlowComplete) {
565
+ if (this.shouldPreExtractData(routingResult.selectedFlow)) {
566
+ logger.debug(`[ResponsePipeline] Pre-extracting data for flow: ${routingResult.selectedFlow.title}`);
567
+ const extractedData = await this.preExtractFlowData({
568
+ route: routingResult.selectedFlow,
569
+ history: params.history,
570
+ context: params.context,
571
+ session: updatedSession,
572
+ signal: params.signal,
573
+ });
574
+ if (extractedData && Object.keys(extractedData).length > 0) {
575
+ logger.debug(`[ResponsePipeline] Pre-extracted data:`, extractedData);
576
+ updatedSession = mergeCollected(updatedSession, extractedData);
577
+ await this.updateCollectedData(extractedData);
578
+ }
579
+ }
580
+ }
581
+ // Determine next step
582
+ const stepResult = await this.determineNextStep({
583
+ selectedFlow: routingResult.selectedFlow,
584
+ selectedStep: routingResult.selectedStep,
585
+ session: updatedSession,
586
+ isFlowComplete,
587
+ context: params.context,
563
588
  });
589
+ return {
590
+ selectedFlow: stepResult.flowChanged || routingResult.selectedFlow,
591
+ selectedStep: stepResult.nextStep,
592
+ responseDirectives: routingResult.responseDirectives,
593
+ session: stepResult.session,
594
+ isFlowComplete: stepResult.flowChanged ? false : isFlowComplete,
595
+ signalFirings: signalResult.firings,
596
+ signalPreDirective: signalResult.mergedDirective || undefined,
597
+ };
564
598
  }
565
- else {
566
- // Fallback: execute tool directly if ToolManager not available
567
- throw new Error(`ToolManager not available for tool execution: ${toolCall.toolName}`);
568
- }
569
- executedToolCalls.push(toolCall);
570
- // Store the actual tool result data for history
571
- toolResults.set(toolCall.toolName, serializeToolResult(result));
572
- // Update context with tool results
573
- if (result.contextUpdate) {
574
- await this.updateContext(result.contextUpdate);
575
- }
576
- // Update collected data with tool results
577
- if (result.dataUpdate) {
578
- updatedSession = await this.updateData(updatedSession, result.dataUpdate);
579
- logger.debug(`[ResponseHandler] ${isStreaming ? "Streaming " : ""}Tool updated collected data:`, result.dataUpdate);
580
- }
581
- logger.debug(`[ResponseHandler] Executed ${isStreaming ? "streaming " : ""}tool: ${String(tool.id || tool.id || 'unknown')} (success: ${result.success})`);
582
- }
583
- return {
584
- session: updatedSession,
585
- toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
586
- toolResults,
587
- };
588
- }
589
- /**
590
- * Execute tool loop for follow-up tool calls
591
- */
592
- async executeToolLoop(params) {
593
- const { initialToolCalls, selectedFlow, nextStep, responsePrompt, history, context, session, responseSchema, isStreaming = false, } = params;
594
- const MAX_TOOL_LOOPS = 5;
595
- let toolLoopCount = 0;
596
- let currentToolCalls = initialToolCalls;
597
- let hasToolCalls = currentToolCalls && currentToolCalls.length > 0;
598
- let currentSession = session;
599
- // Track tool results across loop iterations for history
600
- let currentToolResults = new Map();
601
- while (hasToolCalls && toolLoopCount < MAX_TOOL_LOOPS) {
602
- toolLoopCount++;
603
- logger.debug(`[ResponseHandler] Starting ${isStreaming ? "streaming " : ""}tool loop ${toolLoopCount}/${MAX_TOOL_LOOPS}`);
604
- // Add tool execution results to history so AI knows what happened
605
- const toolResultItems = [];
606
- for (const toolCall of currentToolCalls || []) {
607
- const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
608
- if (tool) {
609
- toolResultItems.push({
610
- role: "assistant",
611
- content: null,
612
- tool_calls: [{
613
- id: toolCall.toolName,
614
- name: toolCall.toolName,
615
- arguments: toolCall.arguments,
616
- }],
617
- });
618
- toolResultItems.push({
619
- role: "tool",
620
- tool_call_id: toolCall.toolName,
621
- name: toolCall.toolName,
622
- content: currentToolResults.get(toolCall.toolName) || "Tool executed successfully",
599
+ // ── No signal processor: existing behavior (zero overhead) ────────────
600
+ const routingResult = await this.handleRoutingAndStepSelection({
601
+ session: params.session,
602
+ history: params.history,
603
+ context: params.context,
604
+ signal: params.signal,
605
+ });
606
+ let updatedSession = routingResult.session;
607
+ const isFlowComplete = routingResult.isFlowComplete;
608
+ // PRE-EXTRACTION: If entering a flow that collects data, extract data from user message first
609
+ // This allows us to skip steps whose data is already provided
610
+ if (routingResult.selectedFlow && !isFlowComplete) {
611
+ // Always pre-extract when flow collects data (not just on new flow entry)
612
+ // This ensures step selection has the most up-to-date data
613
+ if (this.shouldPreExtractData(routingResult.selectedFlow)) {
614
+ logger.debug(`[ResponsePipeline] Pre-extracting data for flow: ${routingResult.selectedFlow.title}`);
615
+ const extractedData = await this.preExtractFlowData({
616
+ route: routingResult.selectedFlow,
617
+ history: params.history,
618
+ context: params.context,
619
+ session: updatedSession,
620
+ signal: params.signal,
623
621
  });
622
+ if (extractedData && Object.keys(extractedData).length > 0) {
623
+ logger.debug(`[ResponsePipeline] Pre-extracted data:`, extractedData);
624
+ // Merge pre-extracted data into session before step selection
625
+ updatedSession = mergeCollected(updatedSession, extractedData);
626
+ // Also update agent's collected data
627
+ await this.updateCollectedData(extractedData);
628
+ }
624
629
  }
625
630
  }
626
- // Create updated history with tool results
627
- const updatedHistory = [...history, ...toolResultItems];
628
- // Make follow-up AI call to see if more tools are needed
629
- const followUpResult = await this.options.provider.generateMessage({
630
- prompt: responsePrompt,
631
- history: updatedHistory,
632
- context,
633
- tools: this.collectAvailableTools(selectedFlow, nextStep),
634
- parameters: {
635
- jsonSchema: responseSchema,
636
- schemaName: isStreaming ? "tool_followup_streaming" : "tool_followup",
637
- },
631
+ // Determine next step using pipeline method for consistency
632
+ const stepResult = await this.determineNextStep({
633
+ selectedFlow: routingResult.selectedFlow,
634
+ selectedStep: routingResult.selectedStep,
635
+ session: updatedSession, // Use updated session with pre-extracted data
636
+ isFlowComplete, // Use updated completion status
637
+ context: params.context,
638
638
  });
639
- // Check if follow-up call has more tool calls
640
- const followUpToolCalls = followUpResult.structured?.toolCalls;
641
- hasToolCalls = followUpToolCalls && followUpToolCalls.length > 0;
642
- if (hasToolCalls) {
643
- logger.debug(`[ResponseHandler] Follow-up call produced ${followUpToolCalls.length} additional tool calls`);
644
- // Execute the follow-up tool calls
645
- const toolResult = await this.executeToolCalls({
646
- toolCalls: followUpToolCalls,
647
- selectedFlow,
648
- context,
649
- session: currentSession,
650
- history: historyToEvents(updatedHistory),
651
- isStreaming,
652
- });
653
- currentSession = toolResult.session;
654
- currentToolCalls = followUpToolCalls;
655
- currentToolResults = toolResult.toolResults || new Map();
656
- }
657
- else {
658
- logger.debug(`[ResponseHandler] ${isStreaming ? "Streaming " : ""}Tool loop completed after ${toolLoopCount} iterations`);
659
- // Update final toolCalls from follow-up result if no more tools
660
- currentToolCalls = followUpToolCalls || [];
661
- break;
662
- }
663
- }
664
- if (toolLoopCount >= MAX_TOOL_LOOPS) {
665
- logger.warn(`[ResponseGenerationError] ${isStreaming ? "Streaming t" : "T"}ool loop limit reached: ${toolLoopCount} iterations hit the cap (${MAX_TOOL_LOOPS}). Stopping tool execution. Increase MAX_TOOL_LOOPS or reduce recursive tool calls.`);
666
- }
667
- return {
668
- session: currentSession,
669
- toolCalls: currentToolCalls,
670
- };
671
- }
672
- /**
673
- * Handle data collection from structured response
674
- */
675
- async handleDataCollection(params) {
676
- const { structured, nextStep, session } = params;
677
- if (!structured || !nextStep.collect) {
678
- return { session };
679
- }
680
- const collectedData = {};
681
- // The structured response includes both base fields and collected extraction fields
682
- const structuredData = structured;
683
- for (const field of nextStep.collect) {
684
- const fieldKey = field;
685
- if (fieldKey in structuredData) {
686
- collectedData[fieldKey] = structuredData[fieldKey];
687
- }
688
- }
689
- let updatedSession = session;
690
- if (Object.keys(collectedData).length > 0) {
691
- // Update agent-level collected data with validation if available
692
- if (this.updateCollectedData) {
693
- await this.updateCollectedData(collectedData);
694
- }
695
- // Update session with validated data
696
- updatedSession = await this.updateData(session, collectedData);
697
- logger.debug(`[ResponseHandler] Collected data:`, collectedData);
639
+ return {
640
+ selectedFlow: stepResult.flowChanged || routingResult.selectedFlow,
641
+ selectedStep: stepResult.nextStep, // Use the determined next step
642
+ responseDirectives: routingResult.responseDirectives,
643
+ session: stepResult.session,
644
+ // If a branch changed the flow, the original isFlowComplete no longer applies
645
+ isFlowComplete: stepResult.flowChanged ? false : isFlowComplete,
646
+ };
698
647
  }
699
- return {
700
- session: updatedSession,
701
- collectedData,
702
- };
703
- }
704
- /**
705
- * Handle context updates from structured response
706
- */
707
- async handleContextUpdate(structured) {
708
- if (structured &&
709
- typeof structured === "object" &&
710
- "contextUpdate" in structured) {
711
- await this.updateContext(structured
712
- .contextUpdate);
648
+ catch (error) {
649
+ throw ResponseGenerationError.fromError(error, 'routing_optimization', params);
713
650
  }
714
651
  }
715
652
  /**
716
- * Handle flow completion: pure state transition.
653
+ * RENDER-STEP RESOLUTION — resolve the step a flow response will render,
654
+ * shared by the streaming and non-streaming paths. When no step was
655
+ * pre-selected: branches win over the linear chain, then candidate steps,
656
+ * then the initial-step fallback. Enforces `requires` (stays at the current
657
+ * step when required fields are missing) and enters the resolved step.
717
658
  *
718
- * Releases the session to idle (`currentFlow`/`currentStep` cleared),
719
- * marks the active `flowHistory` entry completed, and (when `onComplete`
720
- * is set) wires `pendingDirective` for the next turn. The framework
721
- * emits no message of its own at the completion boundary; callers that
722
- * need closing copy should add a final interactive step.
659
+ * Returns `flowTransition: true` when a branch resolved to a flow
660
+ * transition or completion there is no local step to render and the
661
+ * caller handles the transition.
723
662
  */
724
- async handleFlowCompletion(params) {
725
- const { selectedFlow, session, context } = params;
726
- // Check for onComplete transition
727
- const transitionConfig = await selectedFlow.evaluateOnComplete({ data: session.data }, context);
728
- // Release to idle. When the flow is reentrant, scrub its owned
729
- // fields so a future re-selection doesn't short-circuit on stale data.
730
- const ownedFields = selectedFlow.reentrant
731
- ? [
732
- ...(selectedFlow.requiredFields ?? []),
733
- ...(selectedFlow.optionalFields ?? []),
734
- ]
735
- : undefined;
736
- let updatedSession = completeCurrentFlow(session, {
737
- clearOwnedFields: ownedFields,
738
- });
739
- if (transitionConfig) {
740
- // Extract target from directive's goTo field
741
- const goToTarget = typeof transitionConfig.goTo === 'string'
742
- ? transitionConfig.goTo
743
- : transitionConfig.goTo?.flow;
744
- // Find target flow by ID or title
745
- const targetFlow = goToTarget ? this.getFlows().find((r) => r.id === goToTarget ||
746
- r.title === goToTarget) : undefined;
747
- if (targetFlow) {
748
- updatedSession = {
749
- ...updatedSession,
750
- pendingDirective: {
751
- goTo: targetFlow.id,
752
- },
753
- };
754
- logger.debug(`[ResponseHandler] Flow ${selectedFlow.title} completed with pending directive to: ${targetFlow.title}`);
755
- return { session: updatedSession, hasTransition: true };
756
- }
757
- else if (goToTarget) {
758
- logger.warn(`[FlowConfigurationError] onComplete target not found: flow "${selectedFlow.title}" completed but onComplete target "${goToTarget}" does not match any flow. ` +
759
- `Fix the onComplete value to reference an existing flow id/title, or remove onComplete to release the session to idle.`);
760
- }
761
- }
762
- logger.debug(`[ResponseHandler] Flow ${selectedFlow.title} completed; session released to idle.`);
763
- return { session: updatedSession, hasTransition: false };
764
- }
765
- /**
766
- * Find an available tool by name for the given flow using ToolManager
767
- * Delegates to ToolManager for unified tool resolution
768
- */
769
- findAvailableTool(toolName, flow) {
770
- // Use ToolManager for unified tool resolution if available
771
- if (this.toolManager) {
772
- return this.toolManager.find(toolName, undefined, undefined, flow);
773
- }
774
- // Fallback to legacy resolution if ToolManager not available
775
- logger.warn(`[ResponsePipeline] ToolManager not available, using legacy tool resolution for: ${toolName}`);
776
- // Check flow-level tools first (if flow provided)
777
- if (flow) {
778
- const flowTool = flow
779
- .getTools()
780
- .find((tool) => tool.id === toolName || tool.id === toolName);
781
- if (flowTool)
782
- return flowTool;
783
- }
784
- // Fall back to agent-level tools
785
- return this.tools.find((tool) => tool.id === toolName || tool.id === toolName);
786
- }
787
- /**
788
- * Collect all available tools for the given flow and step context using ToolManager
789
- * Delegates to ToolManager for unified tool resolution and deduplication
790
- */
791
- collectAvailableTools(flow, step) {
792
- // Use ToolManager for unified tool collection if available
793
- if (this.toolManager) {
794
- const availableTools = this.toolManager.getAvailable(undefined, step, flow);
795
- return availableTools.map((tool) => ({
796
- id: tool.id,
797
- description: tool.description,
798
- parameters: tool.parameters,
799
- }));
800
- }
801
- // Fallback to legacy collection logic if ToolManager not available
802
- logger.warn(`[ResponsePipeline] ToolManager not available, using legacy tool collection`);
803
- const availableTools = new Map();
804
- // Add agent-level tools
805
- this.tools.forEach((tool) => {
806
- availableTools.set(tool.id, tool);
807
- });
808
- // Add flow-level tools (these take precedence)
809
- if (flow) {
810
- flow.getTools().forEach((tool) => {
811
- availableTools.set(tool.id, tool);
812
- });
663
+ async resolveRenderStep(params) {
664
+ const { selectedFlow, selectedStep, context } = params;
665
+ let session = params.session;
666
+ // Determine next step
667
+ let nextStep;
668
+ if (selectedStep) {
669
+ nextStep = selectedStep;
813
670
  }
814
- // Filter by step-level allowed tools if specified
815
- if (step?.tools) {
816
- const allowedToolIds = new Set();
817
- const stepTools = [];
818
- for (const toolRef of step.tools) {
819
- if (typeof toolRef === "string") {
820
- // Reference to registered tool
821
- allowedToolIds.add(toolRef);
671
+ else {
672
+ // Determine current step from session if we're already in this flow
673
+ const isInSameFlow = session.currentFlow?.id === selectedFlow.id;
674
+ const currentStep = isInSameFlow && session.currentStep
675
+ ? selectedFlow.getStep(session.currentStep.id)
676
+ : undefined;
677
+ logger.debug(`[ResponsePipeline] Step determination: flow match=${isInSameFlow}, currentFlow=${session.currentFlow?.id}, selectedFlow=${selectedFlow.id}, currentStep=${currentStep?.id || 'none'}`);
678
+ // STEP 1 (Algorithm 1): branches win over linear chain
679
+ if (currentStep?.branches && currentStep.branches.length > 0) {
680
+ const branchResult = await this.evaluateStepBranches(currentStep, selectedFlow, session, context);
681
+ if (branchResult) {
682
+ if (branchResult.nextStep) {
683
+ nextStep = branchResult.nextStep;
684
+ session = branchResult.session;
685
+ }
686
+ else {
687
+ // Flow transition or completion — no local step to render
688
+ return { nextStep: undefined, session: branchResult.session, flowTransition: true };
689
+ }
690
+ }
691
+ }
692
+ if (!nextStep) {
693
+ // Get candidate steps based on current position in the flow
694
+ const candidates = await this.flowRouter.getCandidateStepsWithConditions(selectedFlow, currentStep, // Pass current step instead of undefined to maintain progression
695
+ createTemplateContext({ data: session.data, session, context }));
696
+ logger.debug(`[ResponsePipeline] Found ${candidates.length} candidate steps${currentStep ? ' from current step ' + currentStep.id : ' (new flow entry)'}`);
697
+ if (candidates.length > 0) {
698
+ nextStep = candidates[0].step;
699
+ logger.debug(`[ResponsePipeline] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new flow'}`);
822
700
  }
823
701
  else {
824
- // Inline tool definition
825
- if (toolRef.id) {
826
- allowedToolIds.add(toolRef.id);
827
- stepTools.push(toolRef);
828
- }
702
+ // Fallback to initial step even if it should be skipped
703
+ nextStep = selectedFlow.initialStep;
704
+ logger.warn(`[FlowConfigurationError] No valid steps found: all candidates were skipped in flow. Falling back to initial step "${nextStep.id}". Review step skip conditions.`);
829
705
  }
830
706
  }
831
- // If step specifies tools, only include those
832
- if (allowedToolIds.size > 0) {
833
- const filteredTools = new Map();
834
- for (const toolId of Array.from(allowedToolIds)) {
835
- const tool = availableTools.get(toolId);
836
- if (tool) {
837
- filteredTools.set(toolId, tool);
707
+ }
708
+ // Update session with next step
709
+ // If the next step has requires fields that are missing, stay at the previous step
710
+ if (nextStep.requires && nextStep.requires.length > 0) {
711
+ const sessionData = session.data || {};
712
+ const missingRequires = nextStep.requires.filter(field => sessionData[String(field)] === undefined);
713
+ if (missingRequires.length > 0) {
714
+ const warning = `[FlowConfigurationError] Cannot advance to step "${nextStep.description || nextStep.id}": ` +
715
+ `missing required fields [${missingRequires.join(', ')}]. Staying at current step. Ensure preceding steps collect these fields.`;
716
+ logger.warn(warning);
717
+ console.warn(warning);
718
+ // Stay at the current step - don't enter the next one
719
+ const currentStepId = session.currentStep?.id;
720
+ if (currentStepId) {
721
+ const currentStepInstance = selectedFlow.getStep(currentStepId);
722
+ if (currentStepInstance) {
723
+ nextStep = currentStepInstance;
724
+ logger.debug(`[ResponsePipeline] Staying at current step: ${nextStep.id} due to missing requires`);
838
725
  }
839
726
  }
840
- // Add inline tools
841
- stepTools.forEach((tool) => {
842
- if (tool.id) {
843
- filteredTools.set(tool.id, tool);
844
- }
845
- });
846
- availableTools.clear();
847
- filteredTools.forEach((tool, id) => availableTools.set(id, tool));
727
+ }
728
+ else {
729
+ session = enterStep(session, nextStep.id, nextStep.description);
730
+ logger.debug(`[ResponsePipeline] Entered step: ${nextStep.id}`);
848
731
  }
849
732
  }
850
- // Convert to the format expected by AI providers
851
- return Array.from(availableTools.values()).map((tool) => ({
852
- id: tool.id,
853
- description: tool.description,
854
- parameters: tool.parameters,
855
- }));
856
- }
857
- /**
858
- * Get current context (fetches from provider if configured)
859
- */
860
- async getContext() {
861
- // If context provider is configured, use it to fetch fresh context
862
- if (this.options.contextProvider) {
863
- return await this.options.contextProvider();
733
+ else {
734
+ session = enterStep(session, nextStep.id, nextStep.description);
735
+ logger.debug(`[ResponsePipeline] Entered step: ${nextStep.id}`);
864
736
  }
865
- // Otherwise return the stored context
866
- return this.context;
867
- }
868
- // Setters for context and current session (needed for beforeRespond hook)
869
- setContext(context) {
870
- this.context = context;
871
- }
872
- setCurrentSession(session) {
873
- this.currentSession = session;
874
- }
875
- getStoredContext() {
876
- return this.context;
877
- }
878
- getCurrentSession() {
879
- return this.currentSession;
737
+ return { nextStep, session, flowTransition: false };
880
738
  }
881
739
  /**
882
- * Handle cross-flow completion evaluation and notifications
883
- * This method evaluates all flows for completion and can trigger completion handlers
740
+ * Routing skip optimization (Requirements 20.1, 20.2, 20.3):
741
+ * When the current step declares `collect` fields AND pre-extraction populates
742
+ * at least one of those fields from the user's message, skip routing for this turn.
743
+ *
744
+ * Returns the routing result if the skip applies, or undefined to fall through
745
+ * to normal routing.
884
746
  */
885
- async handleCrossFlowCompletion(params) {
886
- const { flows, session, context } = params;
887
- // Evaluate all flows for completion
888
- const completedFlows = [];
889
- const pendingTransitions = [];
890
- for (const flow of flows) {
891
- if (flow.isComplete(session.data || {})) {
892
- completedFlows.push(flow);
893
- // Check for onComplete transitions
894
- const transitionConfig = await flow.evaluateOnComplete({ data: session.data }, context);
895
- if (transitionConfig) {
896
- pendingTransitions.push({ flow, transitionConfig });
897
- }
898
- logger.debug(`[ResponsePipeline] Flow completed: ${flow.title} ` +
899
- `(${Math.round(flow.getCompletionProgress(session.data || {}) * 100)}%)`);
747
+ async attemptRoutingSkipForCollect(params) {
748
+ const { session } = params;
749
+ // Only applies when we already have a current flow and step
750
+ if (!session.currentFlow || !session.currentStep) {
751
+ return undefined;
752
+ }
753
+ // Also skip this optimization if there's a pending directive (it takes priority)
754
+ if (session.pendingDirective) {
755
+ return undefined;
756
+ }
757
+ // Look up the actual Flow and Step objects to access `collect`
758
+ const currentFlow = this.getFlows().find((f) => f.id === session.currentFlow?.id);
759
+ if (!currentFlow) {
760
+ return undefined;
761
+ }
762
+ const currentStep = currentFlow.getStep(session.currentStep.id);
763
+ if (!currentStep || !currentStep.collect || currentStep.collect.length === 0) {
764
+ return undefined;
765
+ }
766
+ // We have a step with collect fields. Run pre-extraction to see if the
767
+ // user's message populates any of them.
768
+ const collectFields = currentStep.collect;
769
+ // Snapshot current data for comparison
770
+ const dataBefore = { ...session.data };
771
+ // Run pre-extraction against the current flow
772
+ const extractedData = await this.preExtractFlowData({
773
+ route: currentFlow,
774
+ history: params.history,
775
+ context: params.context,
776
+ session,
777
+ signal: params.signal,
778
+ });
779
+ if (!extractedData || Object.keys(extractedData).length === 0) {
780
+ return undefined;
781
+ }
782
+ // Determine which collect fields were newly populated by pre-extraction
783
+ const populatedCollectFields = [];
784
+ for (const field of collectFields) {
785
+ const key = field;
786
+ const hadValue = dataBefore[field] !== undefined && dataBefore[field] !== null;
787
+ const hasNewValue = extractedData[field] !== undefined && extractedData[field] !== null;
788
+ if (hasNewValue && !hadValue) {
789
+ populatedCollectFields.push(key);
900
790
  }
901
791
  }
902
- // Log completion status for all flows
903
- if (completedFlows.length > 0) {
904
- logger.debug(`[ResponsePipeline] Cross-flow completion evaluation: ` +
905
- `${completedFlows.length}/${flows.length} flows complete`);
906
- }
792
+ if (populatedCollectFields.length === 0) {
793
+ // Pre-extraction didn't populate any declared collect field — no skip
794
+ return undefined;
795
+ }
796
+ // ROUTING SKIP: pre-extraction populated collect fields → retain current flow/step
797
+ logger.debug(`[ResponsePipeline] Routing skip: pre-extraction populated collect fields [${populatedCollectFields.join(', ')}] for step "${currentStep.id}" — skipping FlowRouter`);
798
+ // Merge extracted data into session
799
+ const updatedSession = mergeCollected(session, extractedData);
800
+ await this.updateCollectedData(extractedData);
801
+ // Determine next step using pipeline method for consistency
802
+ // Pass the current flow/step as the routing result (retained)
803
+ const stepResult = await this.determineNextStep({
804
+ selectedFlow: currentFlow,
805
+ selectedStep: currentStep,
806
+ session: updatedSession,
807
+ isFlowComplete: false,
808
+ context: params.context,
809
+ });
907
810
  return {
908
- session,
909
- completedFlows,
910
- pendingTransitions,
811
+ selectedFlow: stepResult.flowChanged || currentFlow,
812
+ selectedStep: stepResult.nextStep,
813
+ responseDirectives: undefined,
814
+ session: stepResult.session,
815
+ isFlowComplete: false,
911
816
  };
912
817
  }
913
818
  /**
914
- * Update data flow to ensure agent-level data consistency
915
- * This method ensures that data updates are properly validated and propagated
819
+ * Check if a flow should pre-extract data before determining the initial step
916
820
  */
917
- async updateDataFlow(params) {
918
- const { session, dataUpdate, flows } = params;
919
- // Update session data
920
- const updatedSession = await this.updateData(session, dataUpdate);
921
- // Update agent-level data if handler is available
922
- if (this.updateCollectedData) {
923
- await this.updateCollectedData(dataUpdate);
924
- }
925
- // Evaluate flow completions after data update
926
- const completionResults = await this.handleCrossFlowCompletion({
927
- flows,
928
- session: updatedSession,
929
- context: this.context,
930
- history: [],
931
- });
932
- // Log any newly completed flows
933
- if (completionResults.completedFlows.length > 0) {
934
- logger.debug(`[ResponsePipeline] Data update resulted in ${completionResults.completedFlows.length} completed flows`);
821
+ shouldPreExtractData(flow) {
822
+ // Pre-extract if flow has declared required or optional fields
823
+ if (flow.requiredFields && flow.requiredFields.length > 0) {
824
+ return true;
825
+ }
826
+ if (flow.optionalFields && flow.optionalFields.length > 0) {
827
+ return true;
828
+ }
829
+ // Pre-extract if any step in the flow collects data
830
+ const steps = flow.getAllSteps();
831
+ const hasDataCollectionSteps = steps.some(step => step.collect && step.collect.length > 0);
832
+ return hasDataCollectionSteps;
833
+ }
834
+ /**
835
+ * Pre-extract data from user message when entering a flow
836
+ * This allows skipping steps whose data is already provided
837
+ */
838
+ async preExtractFlowData(params) {
839
+ const { route: flow, history, signal } = params;
840
+ // Build a schema for data extraction based on flow's fields
841
+ const extractionSchema = this.getSchema();
842
+ if (!extractionSchema) {
843
+ logger.warn(`[ResponsePipeline] No schema available for pre-extraction`);
844
+ return {};
845
+ }
846
+ // Get last user message
847
+ const lastMessage = getLastMessageFromHistory(history);
848
+ // Build extraction prompt
849
+ const extractionPrompt = [
850
+ `Extract any relevant information from the user's message that matches the following data fields.`,
851
+ `Only extract information that is explicitly stated or clearly implied.`,
852
+ ``,
853
+ `User's message: "${lastMessage}"`,
854
+ ``,
855
+ `Extract data for these fields if present:`,
856
+ ];
857
+ // Add field descriptions
858
+ if (flow.requiredFields) {
859
+ extractionPrompt.push(`Required fields: ${flow.requiredFields.join(', ')}`);
860
+ }
861
+ if (flow.optionalFields) {
862
+ extractionPrompt.push(`Optional fields: ${flow.optionalFields.join(', ')}`);
863
+ }
864
+ extractionPrompt.push(``, `Return ONLY the extracted data as JSON. If no data can be extracted, return an empty object {}.`);
865
+ // Convert Event[] to HistoryItem[] for provider call
866
+ const historyItems = eventsToHistory(history);
867
+ // Call AI to extract data
868
+ try {
869
+ const result = await this.options.provider.generateMessage({
870
+ prompt: extractionPrompt.join('\n'),
871
+ history: historyItems,
872
+ context: {}, // Passed as empty object so AI doesn't "extract" from context
873
+ // NOTE: context is intentionally NOT passed here.
874
+ // Passing context caused the AI to "extract" data from the lead's context
875
+ // (e.g., name, sector, city) instead of from what the user actually said.
876
+ signal,
877
+ parameters: {
878
+ jsonSchema: extractionSchema,
879
+ schemaName: 'data_extraction',
880
+ },
881
+ });
882
+ return result.structured || {};
883
+ }
884
+ catch (error) {
885
+ logger.error(`[ResponsePipeline] Pre-extraction failed:`, error);
886
+ return {};
935
887
  }
936
- return completionResults.session;
937
888
  }
938
889
  }
939
890
  //# sourceMappingURL=ResponsePipeline.js.map