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