@falai/agent 0.1.0-alpha2

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 (499) hide show
  1. package/README.md +797 -0
  2. package/dist/cjs/package.json +1 -0
  3. package/dist/cjs/src/adapters/MemoryAdapter.d.ts +47 -0
  4. package/dist/cjs/src/adapters/MemoryAdapter.d.ts.map +1 -0
  5. package/dist/cjs/src/adapters/MemoryAdapter.js +202 -0
  6. package/dist/cjs/src/adapters/MemoryAdapter.js.map +1 -0
  7. package/dist/cjs/src/adapters/MongoAdapter.d.ts +97 -0
  8. package/dist/cjs/src/adapters/MongoAdapter.d.ts.map +1 -0
  9. package/dist/cjs/src/adapters/MongoAdapter.js +168 -0
  10. package/dist/cjs/src/adapters/MongoAdapter.js.map +1 -0
  11. package/dist/cjs/src/adapters/OpenSearchAdapter.d.ts +169 -0
  12. package/dist/cjs/src/adapters/OpenSearchAdapter.d.ts.map +1 -0
  13. package/dist/cjs/src/adapters/OpenSearchAdapter.js +458 -0
  14. package/dist/cjs/src/adapters/OpenSearchAdapter.js.map +1 -0
  15. package/dist/cjs/src/adapters/PostgreSQLAdapter.d.ts +71 -0
  16. package/dist/cjs/src/adapters/PostgreSQLAdapter.d.ts.map +1 -0
  17. package/dist/cjs/src/adapters/PostgreSQLAdapter.js +260 -0
  18. package/dist/cjs/src/adapters/PostgreSQLAdapter.js.map +1 -0
  19. package/dist/cjs/src/adapters/PrismaAdapter.d.ts +115 -0
  20. package/dist/cjs/src/adapters/PrismaAdapter.d.ts.map +1 -0
  21. package/dist/cjs/src/adapters/PrismaAdapter.js +366 -0
  22. package/dist/cjs/src/adapters/PrismaAdapter.js.map +1 -0
  23. package/dist/cjs/src/adapters/RedisAdapter.d.ts +71 -0
  24. package/dist/cjs/src/adapters/RedisAdapter.d.ts.map +1 -0
  25. package/dist/cjs/src/adapters/RedisAdapter.js +231 -0
  26. package/dist/cjs/src/adapters/RedisAdapter.js.map +1 -0
  27. package/dist/cjs/src/adapters/SQLiteAdapter.d.ts +69 -0
  28. package/dist/cjs/src/adapters/SQLiteAdapter.d.ts.map +1 -0
  29. package/dist/cjs/src/adapters/SQLiteAdapter.js +312 -0
  30. package/dist/cjs/src/adapters/SQLiteAdapter.js.map +1 -0
  31. package/dist/cjs/src/adapters/index.d.ts +17 -0
  32. package/dist/cjs/src/adapters/index.d.ts.map +1 -0
  33. package/dist/cjs/src/adapters/index.js +21 -0
  34. package/dist/cjs/src/adapters/index.js.map +1 -0
  35. package/dist/cjs/src/constants/index.d.ts +10 -0
  36. package/dist/cjs/src/constants/index.d.ts.map +1 -0
  37. package/dist/cjs/src/constants/index.js +13 -0
  38. package/dist/cjs/src/constants/index.js.map +1 -0
  39. package/dist/cjs/src/core/Agent.d.ts +232 -0
  40. package/dist/cjs/src/core/Agent.d.ts.map +1 -0
  41. package/dist/cjs/src/core/Agent.js +741 -0
  42. package/dist/cjs/src/core/Agent.js.map +1 -0
  43. package/dist/cjs/src/core/Events.d.ts +26 -0
  44. package/dist/cjs/src/core/Events.d.ts.map +1 -0
  45. package/dist/cjs/src/core/Events.js +144 -0
  46. package/dist/cjs/src/core/Events.js.map +1 -0
  47. package/dist/cjs/src/core/PersistenceManager.d.ts +98 -0
  48. package/dist/cjs/src/core/PersistenceManager.d.ts.map +1 -0
  49. package/dist/cjs/src/core/PersistenceManager.js +261 -0
  50. package/dist/cjs/src/core/PersistenceManager.js.map +1 -0
  51. package/dist/cjs/src/core/PromptComposer.d.ts +27 -0
  52. package/dist/cjs/src/core/PromptComposer.d.ts.map +1 -0
  53. package/dist/cjs/src/core/PromptComposer.js +194 -0
  54. package/dist/cjs/src/core/PromptComposer.js.map +1 -0
  55. package/dist/cjs/src/core/ResponseEngine.d.ts +32 -0
  56. package/dist/cjs/src/core/ResponseEngine.d.ts.map +1 -0
  57. package/dist/cjs/src/core/ResponseEngine.js +202 -0
  58. package/dist/cjs/src/core/ResponseEngine.js.map +1 -0
  59. package/dist/cjs/src/core/ResponseModal.d.ts +222 -0
  60. package/dist/cjs/src/core/ResponseModal.d.ts.map +1 -0
  61. package/dist/cjs/src/core/ResponseModal.js +1588 -0
  62. package/dist/cjs/src/core/ResponseModal.js.map +1 -0
  63. package/dist/cjs/src/core/ResponsePipeline.d.ts +175 -0
  64. package/dist/cjs/src/core/ResponsePipeline.d.ts.map +1 -0
  65. package/dist/cjs/src/core/ResponsePipeline.js +549 -0
  66. package/dist/cjs/src/core/ResponsePipeline.js.map +1 -0
  67. package/dist/cjs/src/core/Route.d.ts +181 -0
  68. package/dist/cjs/src/core/Route.d.ts.map +1 -0
  69. package/dist/cjs/src/core/Route.js +541 -0
  70. package/dist/cjs/src/core/Route.js.map +1 -0
  71. package/dist/cjs/src/core/RoutingEngine.d.ts +159 -0
  72. package/dist/cjs/src/core/RoutingEngine.d.ts.map +1 -0
  73. package/dist/cjs/src/core/RoutingEngine.js +961 -0
  74. package/dist/cjs/src/core/RoutingEngine.js.map +1 -0
  75. package/dist/cjs/src/core/SessionManager.d.ts +94 -0
  76. package/dist/cjs/src/core/SessionManager.d.ts.map +1 -0
  77. package/dist/cjs/src/core/SessionManager.js +239 -0
  78. package/dist/cjs/src/core/SessionManager.js.map +1 -0
  79. package/dist/cjs/src/core/Step.d.ts +170 -0
  80. package/dist/cjs/src/core/Step.d.ts.map +1 -0
  81. package/dist/cjs/src/core/Step.js +448 -0
  82. package/dist/cjs/src/core/Step.js.map +1 -0
  83. package/dist/cjs/src/core/ToolManager.d.ts +234 -0
  84. package/dist/cjs/src/core/ToolManager.d.ts.map +1 -0
  85. package/dist/cjs/src/core/ToolManager.js +1117 -0
  86. package/dist/cjs/src/core/ToolManager.js.map +1 -0
  87. package/dist/cjs/src/index.d.ts +44 -0
  88. package/dist/cjs/src/index.d.ts.map +1 -0
  89. package/dist/cjs/src/index.js +88 -0
  90. package/dist/cjs/src/index.js.map +1 -0
  91. package/dist/cjs/src/providers/AnthropicProvider.d.ts +43 -0
  92. package/dist/cjs/src/providers/AnthropicProvider.d.ts.map +1 -0
  93. package/dist/cjs/src/providers/AnthropicProvider.js +377 -0
  94. package/dist/cjs/src/providers/AnthropicProvider.js.map +1 -0
  95. package/dist/cjs/src/providers/GeminiProvider.d.ts +58 -0
  96. package/dist/cjs/src/providers/GeminiProvider.d.ts.map +1 -0
  97. package/dist/cjs/src/providers/GeminiProvider.js +489 -0
  98. package/dist/cjs/src/providers/GeminiProvider.js.map +1 -0
  99. package/dist/cjs/src/providers/OpenAIProvider.d.ts +52 -0
  100. package/dist/cjs/src/providers/OpenAIProvider.d.ts.map +1 -0
  101. package/dist/cjs/src/providers/OpenAIProvider.js +395 -0
  102. package/dist/cjs/src/providers/OpenAIProvider.js.map +1 -0
  103. package/dist/cjs/src/providers/OpenRouterProvider.d.ts +56 -0
  104. package/dist/cjs/src/providers/OpenRouterProvider.d.ts.map +1 -0
  105. package/dist/cjs/src/providers/OpenRouterProvider.js +409 -0
  106. package/dist/cjs/src/providers/OpenRouterProvider.js.map +1 -0
  107. package/dist/cjs/src/providers/index.d.ts +13 -0
  108. package/dist/cjs/src/providers/index.d.ts.map +1 -0
  109. package/dist/cjs/src/providers/index.js +16 -0
  110. package/dist/cjs/src/providers/index.js.map +1 -0
  111. package/dist/cjs/src/types/agent.d.ts +181 -0
  112. package/dist/cjs/src/types/agent.d.ts.map +1 -0
  113. package/dist/cjs/src/types/agent.js +21 -0
  114. package/dist/cjs/src/types/agent.js.map +1 -0
  115. package/dist/cjs/src/types/ai.d.ts +143 -0
  116. package/dist/cjs/src/types/ai.d.ts.map +1 -0
  117. package/dist/cjs/src/types/ai.js +6 -0
  118. package/dist/cjs/src/types/ai.js.map +1 -0
  119. package/dist/cjs/src/types/history.d.ts +178 -0
  120. package/dist/cjs/src/types/history.d.ts.map +1 -0
  121. package/dist/cjs/src/types/history.js +33 -0
  122. package/dist/cjs/src/types/history.js.map +1 -0
  123. package/dist/cjs/src/types/index.d.ts +22 -0
  124. package/dist/cjs/src/types/index.d.ts.map +1 -0
  125. package/dist/cjs/src/types/index.js +37 -0
  126. package/dist/cjs/src/types/index.js.map +1 -0
  127. package/dist/cjs/src/types/persistence.d.ts +209 -0
  128. package/dist/cjs/src/types/persistence.d.ts.map +1 -0
  129. package/dist/cjs/src/types/persistence.js +7 -0
  130. package/dist/cjs/src/types/persistence.js.map +1 -0
  131. package/dist/cjs/src/types/route.d.ts +238 -0
  132. package/dist/cjs/src/types/route.d.ts.map +1 -0
  133. package/dist/cjs/src/types/route.js +6 -0
  134. package/dist/cjs/src/types/route.js.map +1 -0
  135. package/dist/cjs/src/types/routing.d.ts +16 -0
  136. package/dist/cjs/src/types/routing.d.ts.map +1 -0
  137. package/dist/cjs/src/types/routing.js +3 -0
  138. package/dist/cjs/src/types/routing.js.map +1 -0
  139. package/dist/cjs/src/types/schema.d.ts +22 -0
  140. package/dist/cjs/src/types/schema.d.ts.map +1 -0
  141. package/dist/cjs/src/types/schema.js +3 -0
  142. package/dist/cjs/src/types/schema.js.map +1 -0
  143. package/dist/cjs/src/types/session.d.ts +65 -0
  144. package/dist/cjs/src/types/session.d.ts.map +1 -0
  145. package/dist/cjs/src/types/session.js +6 -0
  146. package/dist/cjs/src/types/session.js.map +1 -0
  147. package/dist/cjs/src/types/template.d.ts +88 -0
  148. package/dist/cjs/src/types/template.d.ts.map +1 -0
  149. package/dist/cjs/src/types/template.js +3 -0
  150. package/dist/cjs/src/types/template.js.map +1 -0
  151. package/dist/cjs/src/types/tool.d.ts +130 -0
  152. package/dist/cjs/src/types/tool.d.ts.map +1 -0
  153. package/dist/cjs/src/types/tool.js +19 -0
  154. package/dist/cjs/src/types/tool.js.map +1 -0
  155. package/dist/cjs/src/utils/clone.d.ts +8 -0
  156. package/dist/cjs/src/utils/clone.d.ts.map +1 -0
  157. package/dist/cjs/src/utils/clone.js +32 -0
  158. package/dist/cjs/src/utils/clone.js.map +1 -0
  159. package/dist/cjs/src/utils/condition.d.ts +38 -0
  160. package/dist/cjs/src/utils/condition.d.ts.map +1 -0
  161. package/dist/cjs/src/utils/condition.js +168 -0
  162. package/dist/cjs/src/utils/condition.js.map +1 -0
  163. package/dist/cjs/src/utils/event.d.ts +6 -0
  164. package/dist/cjs/src/utils/event.d.ts.map +1 -0
  165. package/dist/cjs/src/utils/event.js +20 -0
  166. package/dist/cjs/src/utils/event.js.map +1 -0
  167. package/dist/cjs/src/utils/history.d.ts +60 -0
  168. package/dist/cjs/src/utils/history.d.ts.map +1 -0
  169. package/dist/cjs/src/utils/history.js +274 -0
  170. package/dist/cjs/src/utils/history.js.map +1 -0
  171. package/dist/cjs/src/utils/id.d.ts +25 -0
  172. package/dist/cjs/src/utils/id.d.ts.map +1 -0
  173. package/dist/cjs/src/utils/id.js +70 -0
  174. package/dist/cjs/src/utils/id.js.map +1 -0
  175. package/dist/cjs/src/utils/index.d.ts +15 -0
  176. package/dist/cjs/src/utils/index.d.ts.map +1 -0
  177. package/dist/cjs/src/utils/index.js +64 -0
  178. package/dist/cjs/src/utils/index.js.map +1 -0
  179. package/dist/cjs/src/utils/json.d.ts +16 -0
  180. package/dist/cjs/src/utils/json.d.ts.map +1 -0
  181. package/dist/cjs/src/utils/json.js +47 -0
  182. package/dist/cjs/src/utils/json.js.map +1 -0
  183. package/dist/cjs/src/utils/logger.d.ts +10 -0
  184. package/dist/cjs/src/utils/logger.d.ts.map +1 -0
  185. package/dist/cjs/src/utils/logger.js +23 -0
  186. package/dist/cjs/src/utils/logger.js.map +1 -0
  187. package/dist/cjs/src/utils/retry.d.ts +10 -0
  188. package/dist/cjs/src/utils/retry.d.ts.map +1 -0
  189. package/dist/cjs/src/utils/retry.js +76 -0
  190. package/dist/cjs/src/utils/retry.js.map +1 -0
  191. package/dist/cjs/src/utils/session.d.ts +51 -0
  192. package/dist/cjs/src/utils/session.d.ts.map +1 -0
  193. package/dist/cjs/src/utils/session.js +170 -0
  194. package/dist/cjs/src/utils/session.js.map +1 -0
  195. package/dist/cjs/src/utils/template.d.ts +155 -0
  196. package/dist/cjs/src/utils/template.d.ts.map +1 -0
  197. package/dist/cjs/src/utils/template.js +383 -0
  198. package/dist/cjs/src/utils/template.js.map +1 -0
  199. package/dist/src/adapters/MemoryAdapter.d.ts +47 -0
  200. package/dist/src/adapters/MemoryAdapter.d.ts.map +1 -0
  201. package/dist/src/adapters/MemoryAdapter.js +198 -0
  202. package/dist/src/adapters/MemoryAdapter.js.map +1 -0
  203. package/dist/src/adapters/MongoAdapter.d.ts +97 -0
  204. package/dist/src/adapters/MongoAdapter.d.ts.map +1 -0
  205. package/dist/src/adapters/MongoAdapter.js +164 -0
  206. package/dist/src/adapters/MongoAdapter.js.map +1 -0
  207. package/dist/src/adapters/OpenSearchAdapter.d.ts +169 -0
  208. package/dist/src/adapters/OpenSearchAdapter.d.ts.map +1 -0
  209. package/dist/src/adapters/OpenSearchAdapter.js +454 -0
  210. package/dist/src/adapters/OpenSearchAdapter.js.map +1 -0
  211. package/dist/src/adapters/PostgreSQLAdapter.d.ts +71 -0
  212. package/dist/src/adapters/PostgreSQLAdapter.d.ts.map +1 -0
  213. package/dist/src/adapters/PostgreSQLAdapter.js +256 -0
  214. package/dist/src/adapters/PostgreSQLAdapter.js.map +1 -0
  215. package/dist/src/adapters/PrismaAdapter.d.ts +115 -0
  216. package/dist/src/adapters/PrismaAdapter.d.ts.map +1 -0
  217. package/dist/src/adapters/PrismaAdapter.js +362 -0
  218. package/dist/src/adapters/PrismaAdapter.js.map +1 -0
  219. package/dist/src/adapters/RedisAdapter.d.ts +71 -0
  220. package/dist/src/adapters/RedisAdapter.d.ts.map +1 -0
  221. package/dist/src/adapters/RedisAdapter.js +227 -0
  222. package/dist/src/adapters/RedisAdapter.js.map +1 -0
  223. package/dist/src/adapters/SQLiteAdapter.d.ts +69 -0
  224. package/dist/src/adapters/SQLiteAdapter.d.ts.map +1 -0
  225. package/dist/src/adapters/SQLiteAdapter.js +308 -0
  226. package/dist/src/adapters/SQLiteAdapter.js.map +1 -0
  227. package/dist/src/adapters/index.d.ts +17 -0
  228. package/dist/src/adapters/index.d.ts.map +1 -0
  229. package/dist/src/adapters/index.js +11 -0
  230. package/dist/src/adapters/index.js.map +1 -0
  231. package/dist/src/constants/index.d.ts +10 -0
  232. package/dist/src/constants/index.d.ts.map +1 -0
  233. package/dist/src/constants/index.js +10 -0
  234. package/dist/src/constants/index.js.map +1 -0
  235. package/dist/src/core/Agent.d.ts +232 -0
  236. package/dist/src/core/Agent.d.ts.map +1 -0
  237. package/dist/src/core/Agent.js +737 -0
  238. package/dist/src/core/Agent.js.map +1 -0
  239. package/dist/src/core/Events.d.ts +26 -0
  240. package/dist/src/core/Events.d.ts.map +1 -0
  241. package/dist/src/core/Events.js +137 -0
  242. package/dist/src/core/Events.js.map +1 -0
  243. package/dist/src/core/PersistenceManager.d.ts +98 -0
  244. package/dist/src/core/PersistenceManager.d.ts.map +1 -0
  245. package/dist/src/core/PersistenceManager.js +257 -0
  246. package/dist/src/core/PersistenceManager.js.map +1 -0
  247. package/dist/src/core/PromptComposer.d.ts +27 -0
  248. package/dist/src/core/PromptComposer.d.ts.map +1 -0
  249. package/dist/src/core/PromptComposer.js +190 -0
  250. package/dist/src/core/PromptComposer.js.map +1 -0
  251. package/dist/src/core/ResponseEngine.d.ts +32 -0
  252. package/dist/src/core/ResponseEngine.d.ts.map +1 -0
  253. package/dist/src/core/ResponseEngine.js +198 -0
  254. package/dist/src/core/ResponseEngine.js.map +1 -0
  255. package/dist/src/core/ResponseModal.d.ts +222 -0
  256. package/dist/src/core/ResponseModal.d.ts.map +1 -0
  257. package/dist/src/core/ResponseModal.js +1583 -0
  258. package/dist/src/core/ResponseModal.js.map +1 -0
  259. package/dist/src/core/ResponsePipeline.d.ts +175 -0
  260. package/dist/src/core/ResponsePipeline.d.ts.map +1 -0
  261. package/dist/src/core/ResponsePipeline.js +545 -0
  262. package/dist/src/core/ResponsePipeline.js.map +1 -0
  263. package/dist/src/core/Route.d.ts +181 -0
  264. package/dist/src/core/Route.d.ts.map +1 -0
  265. package/dist/src/core/Route.js +537 -0
  266. package/dist/src/core/Route.js.map +1 -0
  267. package/dist/src/core/RoutingEngine.d.ts +159 -0
  268. package/dist/src/core/RoutingEngine.d.ts.map +1 -0
  269. package/dist/src/core/RoutingEngine.js +957 -0
  270. package/dist/src/core/RoutingEngine.js.map +1 -0
  271. package/dist/src/core/SessionManager.d.ts +94 -0
  272. package/dist/src/core/SessionManager.d.ts.map +1 -0
  273. package/dist/src/core/SessionManager.js +235 -0
  274. package/dist/src/core/SessionManager.js.map +1 -0
  275. package/dist/src/core/Step.d.ts +170 -0
  276. package/dist/src/core/Step.d.ts.map +1 -0
  277. package/dist/src/core/Step.js +444 -0
  278. package/dist/src/core/Step.js.map +1 -0
  279. package/dist/src/core/ToolManager.d.ts +234 -0
  280. package/dist/src/core/ToolManager.d.ts.map +1 -0
  281. package/dist/src/core/ToolManager.js +1111 -0
  282. package/dist/src/core/ToolManager.js.map +1 -0
  283. package/dist/src/index.d.ts +44 -0
  284. package/dist/src/index.d.ts.map +1 -0
  285. package/dist/src/index.js +37 -0
  286. package/dist/src/index.js.map +1 -0
  287. package/dist/src/providers/AnthropicProvider.d.ts +43 -0
  288. package/dist/src/providers/AnthropicProvider.d.ts.map +1 -0
  289. package/dist/src/providers/AnthropicProvider.js +370 -0
  290. package/dist/src/providers/AnthropicProvider.js.map +1 -0
  291. package/dist/src/providers/GeminiProvider.d.ts +58 -0
  292. package/dist/src/providers/GeminiProvider.d.ts.map +1 -0
  293. package/dist/src/providers/GeminiProvider.js +485 -0
  294. package/dist/src/providers/GeminiProvider.js.map +1 -0
  295. package/dist/src/providers/OpenAIProvider.d.ts +52 -0
  296. package/dist/src/providers/OpenAIProvider.d.ts.map +1 -0
  297. package/dist/src/providers/OpenAIProvider.js +388 -0
  298. package/dist/src/providers/OpenAIProvider.js.map +1 -0
  299. package/dist/src/providers/OpenRouterProvider.d.ts +56 -0
  300. package/dist/src/providers/OpenRouterProvider.d.ts.map +1 -0
  301. package/dist/src/providers/OpenRouterProvider.js +402 -0
  302. package/dist/src/providers/OpenRouterProvider.js.map +1 -0
  303. package/dist/src/providers/index.d.ts +13 -0
  304. package/dist/src/providers/index.d.ts.map +1 -0
  305. package/dist/src/providers/index.js +9 -0
  306. package/dist/src/providers/index.js.map +1 -0
  307. package/dist/src/types/agent.d.ts +181 -0
  308. package/dist/src/types/agent.d.ts.map +1 -0
  309. package/dist/src/types/agent.js +18 -0
  310. package/dist/src/types/agent.js.map +1 -0
  311. package/dist/src/types/ai.d.ts +143 -0
  312. package/dist/src/types/ai.d.ts.map +1 -0
  313. package/dist/src/types/ai.js +5 -0
  314. package/dist/src/types/ai.js.map +1 -0
  315. package/dist/src/types/history.d.ts +178 -0
  316. package/dist/src/types/history.d.ts.map +1 -0
  317. package/dist/src/types/history.js +30 -0
  318. package/dist/src/types/history.js.map +1 -0
  319. package/dist/src/types/index.d.ts +22 -0
  320. package/dist/src/types/index.d.ts.map +1 -0
  321. package/dist/src/types/index.js +12 -0
  322. package/dist/src/types/index.js.map +1 -0
  323. package/dist/src/types/persistence.d.ts +209 -0
  324. package/dist/src/types/persistence.d.ts.map +1 -0
  325. package/dist/src/types/persistence.js +6 -0
  326. package/dist/src/types/persistence.js.map +1 -0
  327. package/dist/src/types/route.d.ts +238 -0
  328. package/dist/src/types/route.d.ts.map +1 -0
  329. package/dist/src/types/route.js +5 -0
  330. package/dist/src/types/route.js.map +1 -0
  331. package/dist/src/types/routing.d.ts +16 -0
  332. package/dist/src/types/routing.d.ts.map +1 -0
  333. package/dist/src/types/routing.js +2 -0
  334. package/dist/src/types/routing.js.map +1 -0
  335. package/dist/src/types/schema.d.ts +22 -0
  336. package/dist/src/types/schema.d.ts.map +1 -0
  337. package/dist/src/types/schema.js +2 -0
  338. package/dist/src/types/schema.js.map +1 -0
  339. package/dist/src/types/session.d.ts +65 -0
  340. package/dist/src/types/session.d.ts.map +1 -0
  341. package/dist/src/types/session.js +5 -0
  342. package/dist/src/types/session.js.map +1 -0
  343. package/dist/src/types/template.d.ts +88 -0
  344. package/dist/src/types/template.d.ts.map +1 -0
  345. package/dist/src/types/template.js +2 -0
  346. package/dist/src/types/template.js.map +1 -0
  347. package/dist/src/types/tool.d.ts +130 -0
  348. package/dist/src/types/tool.d.ts.map +1 -0
  349. package/dist/src/types/tool.js +16 -0
  350. package/dist/src/types/tool.js.map +1 -0
  351. package/dist/src/utils/clone.d.ts +8 -0
  352. package/dist/src/utils/clone.d.ts.map +1 -0
  353. package/dist/src/utils/clone.js +29 -0
  354. package/dist/src/utils/clone.js.map +1 -0
  355. package/dist/src/utils/condition.d.ts +38 -0
  356. package/dist/src/utils/condition.d.ts.map +1 -0
  357. package/dist/src/utils/condition.js +161 -0
  358. package/dist/src/utils/condition.js.map +1 -0
  359. package/dist/src/utils/event.d.ts +6 -0
  360. package/dist/src/utils/event.d.ts.map +1 -0
  361. package/dist/src/utils/event.js +17 -0
  362. package/dist/src/utils/event.js.map +1 -0
  363. package/dist/src/utils/history.d.ts +60 -0
  364. package/dist/src/utils/history.d.ts.map +1 -0
  365. package/dist/src/utils/history.js +263 -0
  366. package/dist/src/utils/history.js.map +1 -0
  367. package/dist/src/utils/id.d.ts +25 -0
  368. package/dist/src/utils/id.d.ts.map +1 -0
  369. package/dist/src/utils/id.js +64 -0
  370. package/dist/src/utils/id.js.map +1 -0
  371. package/dist/src/utils/index.d.ts +15 -0
  372. package/dist/src/utils/index.d.ts.map +1 -0
  373. package/dist/src/utils/index.js +23 -0
  374. package/dist/src/utils/index.js.map +1 -0
  375. package/dist/src/utils/json.d.ts +16 -0
  376. package/dist/src/utils/json.d.ts.map +1 -0
  377. package/dist/src/utils/json.js +43 -0
  378. package/dist/src/utils/json.js.map +1 -0
  379. package/dist/src/utils/logger.d.ts +10 -0
  380. package/dist/src/utils/logger.d.ts.map +1 -0
  381. package/dist/src/utils/logger.js +17 -0
  382. package/dist/src/utils/logger.js.map +1 -0
  383. package/dist/src/utils/retry.d.ts +10 -0
  384. package/dist/src/utils/retry.d.ts.map +1 -0
  385. package/dist/src/utils/retry.js +71 -0
  386. package/dist/src/utils/retry.js.map +1 -0
  387. package/dist/src/utils/session.d.ts +51 -0
  388. package/dist/src/utils/session.d.ts.map +1 -0
  389. package/dist/src/utils/session.js +160 -0
  390. package/dist/src/utils/session.js.map +1 -0
  391. package/dist/src/utils/template.d.ts +155 -0
  392. package/dist/src/utils/template.d.ts.map +1 -0
  393. package/dist/src/utils/template.js +374 -0
  394. package/dist/src/utils/template.js.map +1 -0
  395. package/docs/CONTRIBUTING.md +521 -0
  396. package/docs/README.md +228 -0
  397. package/docs/api/README.md +3258 -0
  398. package/docs/api/overview.md +1134 -0
  399. package/docs/architecture/data-extraction-flow.md +363 -0
  400. package/docs/core/agent/README.md +902 -0
  401. package/docs/core/agent/context-management.md +796 -0
  402. package/docs/core/agent/session-management.md +641 -0
  403. package/docs/core/ai-integration/prompt-composition.md +220 -0
  404. package/docs/core/ai-integration/providers.md +515 -0
  405. package/docs/core/ai-integration/response-processing.md +287 -0
  406. package/docs/core/conversation-flows/data-collection.md +623 -0
  407. package/docs/core/conversation-flows/route-dsl.md +502 -0
  408. package/docs/core/conversation-flows/routes.md +247 -0
  409. package/docs/core/conversation-flows/step-transitions.md +595 -0
  410. package/docs/core/conversation-flows/steps.md +154 -0
  411. package/docs/core/error-handling.md +638 -0
  412. package/docs/core/persistence/adapters.md +255 -0
  413. package/docs/core/persistence/session-storage.md +644 -0
  414. package/docs/core/routing/intelligent-routing.md +466 -0
  415. package/docs/core/tools/tool-definition.md +970 -0
  416. package/docs/core/tools/tool-scoping.md +819 -0
  417. package/docs/guides/advanced-patterns/publishing.md +186 -0
  418. package/docs/guides/error-handling-patterns.md +578 -0
  419. package/docs/guides/getting-started/README.md +696 -0
  420. package/docs/guides/migration/README.md +72 -0
  421. package/docs/guides/migration/flexible-routing-conditions.md +375 -0
  422. package/docs/guides/migration/response-modal-refactor.md +518 -0
  423. package/examples/advanced-patterns/knowledge-based-agent.ts +735 -0
  424. package/examples/advanced-patterns/persistent-onboarding.ts +728 -0
  425. package/examples/advanced-patterns/route-lifecycle-hooks.ts +556 -0
  426. package/examples/advanced-patterns/streaming-responses.ts +578 -0
  427. package/examples/ai-providers/anthropic-integration.ts +388 -0
  428. package/examples/ai-providers/openai-integration.ts +228 -0
  429. package/examples/condition-patterns/function-only-conditions.ts +365 -0
  430. package/examples/condition-patterns/mixed-array-conditions.ts +477 -0
  431. package/examples/condition-patterns/route-skipif-patterns.ts +468 -0
  432. package/examples/condition-patterns/step-skipif-patterns.ts +0 -0
  433. package/examples/condition-patterns/string-only-conditions.ts +296 -0
  434. package/examples/conversation-flows/completion-transitions.ts +318 -0
  435. package/examples/core-concepts/basic-agent.ts +503 -0
  436. package/examples/core-concepts/modern-streaming-api.ts +309 -0
  437. package/examples/core-concepts/schema-driven-extraction.ts +332 -0
  438. package/examples/core-concepts/session-management.ts +494 -0
  439. package/examples/integrations/database-integration.ts +630 -0
  440. package/examples/integrations/healthcare-integration.ts +595 -0
  441. package/examples/integrations/search-integration.ts +530 -0
  442. package/examples/integrations/server-session-management.ts +307 -0
  443. package/examples/persistence/custom-adapter.ts +529 -0
  444. package/examples/persistence/database-persistence.ts +583 -0
  445. package/examples/persistence/memory-sessions.ts +495 -0
  446. package/examples/persistence/prisma-schema.example.prisma +74 -0
  447. package/examples/persistence/redis-persistence.ts +488 -0
  448. package/examples/tools/basic-tools.ts +765 -0
  449. package/examples/tools/data-enrichment-tools.ts +593 -0
  450. package/package.json +125 -0
  451. package/src/adapters/MemoryAdapter.ts +273 -0
  452. package/src/adapters/MongoAdapter.ts +304 -0
  453. package/src/adapters/OpenSearchAdapter.ts +670 -0
  454. package/src/adapters/PostgreSQLAdapter.ts +428 -0
  455. package/src/adapters/PrismaAdapter.ts +553 -0
  456. package/src/adapters/RedisAdapter.ts +377 -0
  457. package/src/adapters/SQLiteAdapter.ts +459 -0
  458. package/src/adapters/index.ts +43 -0
  459. package/src/constants/index.ts +10 -0
  460. package/src/core/Agent.ts +970 -0
  461. package/src/core/Events.ts +164 -0
  462. package/src/core/PersistenceManager.ts +353 -0
  463. package/src/core/PromptComposer.ts +253 -0
  464. package/src/core/ResponseEngine.ts +306 -0
  465. package/src/core/ResponseModal.ts +2050 -0
  466. package/src/core/ResponsePipeline.ts +864 -0
  467. package/src/core/Route.ts +677 -0
  468. package/src/core/RoutingEngine.ts +1396 -0
  469. package/src/core/SessionManager.ts +297 -0
  470. package/src/core/Step.ts +593 -0
  471. package/src/core/ToolManager.ts +1394 -0
  472. package/src/index.ts +155 -0
  473. package/src/providers/AnthropicProvider.ts +560 -0
  474. package/src/providers/GeminiProvider.ts +683 -0
  475. package/src/providers/OpenAIProvider.ts +602 -0
  476. package/src/providers/OpenRouterProvider.ts +613 -0
  477. package/src/providers/index.ts +16 -0
  478. package/src/types/agent.ts +196 -0
  479. package/src/types/ai.ts +158 -0
  480. package/src/types/history.ts +206 -0
  481. package/src/types/index.ts +119 -0
  482. package/src/types/persistence.ts +251 -0
  483. package/src/types/route.ts +272 -0
  484. package/src/types/routing.ts +18 -0
  485. package/src/types/schema.ts +23 -0
  486. package/src/types/session.ts +74 -0
  487. package/src/types/template.ts +104 -0
  488. package/src/types/tool.ts +174 -0
  489. package/src/utils/clone.ts +34 -0
  490. package/src/utils/condition.ts +190 -0
  491. package/src/utils/event.ts +16 -0
  492. package/src/utils/history.ts +306 -0
  493. package/src/utils/id.ts +73 -0
  494. package/src/utils/index.ts +69 -0
  495. package/src/utils/json.ts +46 -0
  496. package/src/utils/logger.ts +19 -0
  497. package/src/utils/retry.ts +97 -0
  498. package/src/utils/session.ts +204 -0
  499. package/src/utils/template.ts +444 -0
@@ -0,0 +1,2050 @@
1
+ /**
2
+ * ResponseModal handles all response generation logic for the Agent
3
+ * Provides both streaming and non-streaming response generation with unified logic
4
+ */
5
+
6
+ import type {
7
+ AgentResponse,
8
+ AgentResponseStreamChunk,
9
+ History,
10
+ SessionState,
11
+ StepRef,
12
+ HistoryItem,
13
+ Tool,
14
+ Event,
15
+ ToolEventData,
16
+ AgentStructuredResponse,
17
+ Term,
18
+ } from "../types";
19
+ import { EventKind, MessageRole } from "../types";
20
+ import type { Agent } from "./Agent";
21
+ import type { Route } from "./Route";
22
+ import { Step } from "./Step";
23
+ import { ResponseEngine } from "./ResponseEngine";
24
+ import { ResponsePipeline } from "./ResponsePipeline";
25
+ import { cloneDeep, mergeCollected, enterStep, getLastMessageFromHistory, render, logger, historyToEvents } from "../utils";
26
+ import { createTemplateContext } from "../utils/template";
27
+ import type { ToolManager } from "./ToolManager";
28
+ import { END_ROUTE_ID } from "../constants";
29
+
30
+ /**
31
+ * Configuration options for ResponseModal
32
+ */
33
+ export interface ResponseModalOptions {
34
+ /** Maximum number of tool loops allowed during response generation */
35
+ maxToolLoops?: number;
36
+ /** Enable automatic session saving after response generation */
37
+ enableAutoSave?: boolean;
38
+ /** Enable debug mode for detailed logging */
39
+ debugMode?: boolean;
40
+ }
41
+
42
+ /**
43
+ * Parameters for respond and respondStream methods
44
+ */
45
+ export interface RespondParams<TContext = unknown, TData = unknown> extends Record<string, unknown> {
46
+ history: History;
47
+ step?: StepRef;
48
+ session?: SessionState<TData>;
49
+ contextOverride?: Partial<TContext>;
50
+ signal?: AbortSignal;
51
+ }
52
+
53
+ /**
54
+ * Options for the modern stream() method
55
+ */
56
+ export interface StreamOptions<TContext = unknown> {
57
+ contextOverride?: Partial<TContext>;
58
+ signal?: AbortSignal;
59
+ history?: History; // Optional: override session history
60
+ }
61
+
62
+ /**
63
+ * Options for the modern generate() method
64
+ */
65
+ export interface GenerateOptions<TContext = unknown> {
66
+ contextOverride?: Partial<TContext>;
67
+ signal?: AbortSignal;
68
+ history?: History; // Optional: override session history
69
+ }
70
+
71
+ /**
72
+ * Error details for response generation failures
73
+ */
74
+ interface ResponseGenerationErrorDetails {
75
+ originalError?: unknown;
76
+ params?: Record<string, unknown>;
77
+ phase?: string;
78
+ context?: Record<string, unknown>;
79
+ }
80
+
81
+ /**
82
+ * Error class for response generation failures
83
+ */
84
+ export class ResponseGenerationError extends Error {
85
+ constructor(
86
+ message: string,
87
+ public readonly details?: ResponseGenerationErrorDetails
88
+ ) {
89
+ super(message);
90
+ this.name = 'ResponseGenerationError';
91
+
92
+ // Preserve stack trace from original error if available
93
+ if (details?.originalError instanceof Error && details.originalError.stack) {
94
+ this.stack = `${this.stack}\nCaused by: ${details.originalError.stack}`;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Create a ResponseGenerationError from an unknown error
100
+ */
101
+ static fromError(
102
+ error: unknown,
103
+ phase: string,
104
+ params?: Record<string, unknown>,
105
+ context?: Record<string, unknown>
106
+ ): ResponseGenerationError {
107
+ const message = error instanceof Error ? error.message : String(error);
108
+ return new ResponseGenerationError(
109
+ `Response generation failed in ${phase}: ${message}`,
110
+ { originalError: error, params, phase, context }
111
+ );
112
+ }
113
+
114
+ /**
115
+ * Check if an error is a ResponseGenerationError
116
+ */
117
+ static isResponseGenerationError(error: unknown): error is ResponseGenerationError {
118
+ return error instanceof ResponseGenerationError;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Common response context used across all response methods
124
+ */
125
+ interface ResponseContext<TContext = unknown, TData = unknown> {
126
+ effectiveContext: TContext;
127
+ session: SessionState<TData>;
128
+ history: HistoryItem[]; // Keep as HistoryItem[] for external API compatibility
129
+ selectedRoute?: Route<TContext, TData>;
130
+ selectedStep?: Step<TContext, TData>;
131
+ responseDirectives?: string[];
132
+ isRouteComplete: boolean;
133
+ }
134
+
135
+ /**
136
+ * ResponseModal class that encapsulates all response generation logic
137
+ * Uses unified approach for both streaming and non-streaming responses
138
+ */
139
+ export class ResponseModal<TContext = unknown, TData = unknown> {
140
+ private readonly responseEngine: ResponseEngine<TContext, TData>;
141
+ private readonly responsePipeline: ResponsePipeline<TContext, TData>;
142
+
143
+ constructor(
144
+ private readonly agent: Agent<TContext, TData>,
145
+ private readonly options?: ResponseModalOptions
146
+ ) {
147
+ // Initialize response engine
148
+ this.responseEngine = new ResponseEngine<TContext, TData>();
149
+
150
+ // Initialize response pipeline with agent dependencies
151
+ this.responsePipeline = new ResponsePipeline<TContext, TData>(
152
+ this.agent.getAgentOptions(),
153
+ () => this.agent.getRoutes(), // Pass a function to get routes dynamically
154
+ this.agent.getTools(),
155
+ this.agent.getRoutingEngine(),
156
+ this.agent.updateContext.bind(this.agent),
157
+ this.agent.getUpdateDataMethod(),
158
+ this.agent.updateCollectedData.bind(this.agent),
159
+ this.getToolManager()
160
+ );
161
+ }
162
+
163
+ /**
164
+ * Generate a non-streaming response using unified logic
165
+ */
166
+ async respond(params: RespondParams<TContext, TData>): Promise<AgentResponse<TData>> {
167
+ try {
168
+ // Use unified response preparation and routing
169
+ const responseContext = await this.prepareUnifiedResponseContext(params);
170
+ // Generate response using unified logic
171
+ const result = await this.generateUnifiedResponse(responseContext);
172
+
173
+ // Finalize session
174
+ await this.finalizeSession(result.session!, responseContext.effectiveContext);
175
+
176
+ return result;
177
+
178
+ } catch (error) {
179
+ throw new ResponseGenerationError(
180
+ `Failed to generate response: ${error instanceof Error ? error.message : String(error)}`,
181
+ { originalError: error, params, phase: 'response_generation' }
182
+ );
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Generate a streaming response using unified logic
188
+ */
189
+ async *respondStream(params: RespondParams<TContext, TData>): AsyncGenerator<AgentResponseStreamChunk<TData>> {
190
+ try {
191
+ // Use unified response preparation and routing
192
+ const responseContext = await this.prepareUnifiedResponseContext(params);
193
+
194
+ // Generate streaming response using unified logic
195
+ yield* this.generateUnifiedStreamingResponse(responseContext);
196
+
197
+ } catch (error) {
198
+ // Stream error to caller
199
+ yield {
200
+ delta: "",
201
+ accumulated: "",
202
+ done: true,
203
+ session: params.session || await this.agent.session.getOrCreate(),
204
+ error: new ResponseGenerationError(
205
+ `Streaming response failed: ${error instanceof Error ? error.message : String(error)}`,
206
+ { originalError: error, params, phase: 'streaming' }
207
+ ),
208
+ } as AgentResponseStreamChunk<TData>;
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Modern streaming API - simple interface like chat()
214
+ */
215
+ async *stream(
216
+ message?: string,
217
+ options?: StreamOptions<TContext>
218
+ ): AsyncGenerator<AgentResponseStreamChunk<TData>> {
219
+ // Determine which history to use
220
+ let history: History;
221
+ if (options?.history) {
222
+ // Use provided history for this response only
223
+ history = options.history;
224
+ } else {
225
+ // Add user message to session history if provided
226
+ if (message) {
227
+ await this.agent.session.addMessage("user", message);
228
+ }
229
+ history = this.agent.session.getHistory();
230
+ }
231
+
232
+ // Get or create session
233
+ let session = await this.agent.session.getOrCreate();
234
+
235
+ // Merge agent's collected data into session (agent data takes precedence)
236
+ const collectedData = this.agent.getCollectedData();
237
+ if (Object.keys(collectedData).length > 0) {
238
+ session = mergeCollected(session, collectedData);
239
+ // Update the session manager with the merged data
240
+ await this.agent.session.setData(collectedData);
241
+ logger.debug("[ResponseModal] Merged agent collected data into stream session:", collectedData);
242
+ }
243
+
244
+ // Stream response using existing respondStream method
245
+ let finalMessage = "";
246
+ for await (const chunk of this.respondStream({
247
+ history,
248
+ session,
249
+ contextOverride: options?.contextOverride,
250
+ signal: options?.signal,
251
+ })) {
252
+ // Accumulate the final message for session history
253
+ if (chunk.done) {
254
+ finalMessage = chunk.accumulated;
255
+ }
256
+
257
+ yield chunk;
258
+ }
259
+
260
+ // Add agent response to session history (only if not using override history)
261
+ if (!options?.history && finalMessage) {
262
+ await this.agent.session.addMessage("assistant", finalMessage);
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Modern non-streaming API - equivalent to chat() but more explicit
268
+ */
269
+ async generate(
270
+ message?: string,
271
+ options?: GenerateOptions<TContext>
272
+ ): Promise<AgentResponse<TData>> {
273
+ // Determine which history to use
274
+ let history: History;
275
+ if (options?.history) {
276
+ // Use provided history for this response only
277
+ history = options.history;
278
+ } else {
279
+ // Add user message to session history if provided
280
+ if (message) {
281
+ await this.agent.session.addMessage("user", message);
282
+ }
283
+ history = this.agent.session.getHistory();
284
+ }
285
+
286
+ // Get or create session
287
+ let session = await this.agent.session.getOrCreate();
288
+
289
+ // Merge agent's collected data into session (agent data takes precedence)
290
+ const collectedData = this.agent.getCollectedData();
291
+ if (Object.keys(collectedData).length > 0) {
292
+ session = mergeCollected(session, collectedData);
293
+ // Update the session manager with the merged data
294
+ await this.agent.session.setData(collectedData);
295
+ logger.debug("[ResponseModal] Merged agent collected data into generate session:", collectedData);
296
+ }
297
+
298
+ // Generate response using existing respond method
299
+ const result = await this.respond({
300
+ history,
301
+ session,
302
+ contextOverride: options?.contextOverride,
303
+ signal: options?.signal,
304
+ });
305
+
306
+ // Add agent response to session history (only if not using override history)
307
+ if (!options?.history) {
308
+ await this.agent.session.addMessage("assistant", result.message);
309
+ }
310
+
311
+ // Ensure the result includes the current session
312
+ return {
313
+ ...result,
314
+ session: result.session || this.agent.session.current,
315
+ };
316
+ }
317
+
318
+ /**
319
+ * Get the response engine instance
320
+ * @internal
321
+ */
322
+ getResponseEngine(): ResponseEngine<TContext, TData> {
323
+ return this.responseEngine;
324
+ }
325
+
326
+ /**
327
+ * Get the response pipeline instance
328
+ * @internal
329
+ */
330
+ getResponsePipeline(): ResponsePipeline<TContext, TData> {
331
+ return this.responsePipeline;
332
+ }
333
+
334
+ /**
335
+ * Get the ToolManager instance from the agent
336
+ * @private
337
+ */
338
+ private getToolManager(): ToolManager<TContext, TData> | undefined {
339
+ // Check if agent has a tool property (ToolManager)
340
+ if (this.agent && 'tool' in this.agent && this.agent.tool) {
341
+ return this.agent.tool;
342
+ }
343
+
344
+ // Log warning if ToolManager is not available
345
+ logger.warn(`[ResponseModal] ToolManager not available on agent - tool execution will use fallback methods`);
346
+ return undefined;
347
+ }
348
+
349
+ // UNIFIED RESPONSE LOGIC - Consolidates common logic between streaming and non-streaming
350
+ // ============================================================================
351
+
352
+ /**
353
+ * Unified response preparation - handles context setup, session management, and routing
354
+ * This consolidates common logic between streaming and non-streaming responses
355
+ * @private
356
+ */
357
+ private async prepareUnifiedResponseContext(params: RespondParams<TContext, TData>): Promise<ResponseContext<TContext, TData>> {
358
+ try {
359
+ const { history: simpleHistory, contextOverride, signal } = params;
360
+
361
+ // Validate input parameters
362
+ if (!simpleHistory) {
363
+ throw new ResponseGenerationError('History is required for response generation', { params, phase: 'validation' });
364
+ }
365
+
366
+ // Convert HistoryItem[] to Event[] for internal processing
367
+ const historyEvents = historyToEvents(simpleHistory);
368
+ // Keep original HistoryItem[] format for external APIs
369
+ const history = simpleHistory;
370
+
371
+ // Use ResponsePipeline for optimized context and session preparation
372
+ // This leverages existing optimizations and avoids code duplication
373
+ let responseContext: {
374
+ effectiveContext: TContext;
375
+ session: SessionState<TData>;
376
+ };
377
+ try {
378
+ // Set current context and session in pipeline for consistency
379
+ this.responsePipeline.setContext(await this.agent.getContext());
380
+ this.responsePipeline.setCurrentSession(this.agent.getCurrentSession());
381
+
382
+ responseContext = await this.responsePipeline.prepareResponseContext({
383
+ contextOverride,
384
+ session: params.session ? cloneDeep(params.session) : undefined,
385
+ });
386
+ } catch (error) {
387
+ throw ResponseGenerationError.fromError(error, 'pipeline_context_preparation', params);
388
+ }
389
+
390
+ const { effectiveContext } = responseContext;
391
+ let session = responseContext.session;
392
+
393
+ // Update our stored context if it was modified by beforeRespond hook
394
+ const storedContext = this.responsePipeline.getStoredContext();
395
+ if (storedContext !== undefined) {
396
+ try {
397
+ await this.agent.updateContext(storedContext as Partial<TContext>);
398
+ } catch (error) {
399
+ throw ResponseGenerationError.fromError(error, 'context_update_from_pipeline', params, { storedContext });
400
+ }
401
+ }
402
+
403
+ // Merge agent's collected data into session (agent data takes precedence)
404
+ const collectedData = this.agent.getCollectedData();
405
+ if (Object.keys(collectedData).length > 0) {
406
+ try {
407
+ session = mergeCollected(session, collectedData);
408
+ logger.debug("[ResponseModal] Merged agent collected data into session:", collectedData);
409
+ } catch (error) {
410
+ throw ResponseGenerationError.fromError(error, 'data_merging', params, { collectedData });
411
+ }
412
+ }
413
+
414
+ // PHASE 1: PREPARE - Execute prepare function if current step has one
415
+ try {
416
+ await this.executeStepPrepare(session, effectiveContext);
417
+ } catch (error) {
418
+ throw ResponseGenerationError.fromError(error, 'step_preparation', params, { session, effectiveContext });
419
+ }
420
+
421
+ // PHASE 2: ROUTING + STEP SELECTION - Determine which route and step to use
422
+ let routingResult: {
423
+ selectedRoute?: Route<TContext, TData>;
424
+ selectedStep?: Step<TContext, TData>;
425
+ responseDirectives?: string[];
426
+ session: SessionState<TData>;
427
+ isRouteComplete: boolean;
428
+ };
429
+ try {
430
+ routingResult = await this.handleUnifiedRoutingAndStepSelection({
431
+ session,
432
+ history: historyEvents,
433
+ context: effectiveContext,
434
+ signal,
435
+ });
436
+ } catch (error) {
437
+ throw ResponseGenerationError.fromError(error, 'routing_and_step_selection', params, { session, effectiveContext });
438
+ }
439
+
440
+ return {
441
+ effectiveContext,
442
+ session: routingResult.session,
443
+ history,
444
+ selectedRoute: routingResult.selectedRoute,
445
+ selectedStep: routingResult.selectedStep,
446
+ responseDirectives: routingResult.responseDirectives,
447
+ isRouteComplete: routingResult.isRouteComplete,
448
+ };
449
+ } catch (error) {
450
+ // Re-throw ResponseGenerationError as-is, wrap others
451
+ if (ResponseGenerationError.isResponseGenerationError(error)) {
452
+ throw error;
453
+ }
454
+ throw ResponseGenerationError.fromError(error, 'preparation', params);
455
+ }
456
+ }
457
+
458
+ /**
459
+ * Unified routing and step selection logic using ResponsePipeline for optimization
460
+ * @private
461
+ */
462
+ private async handleUnifiedRoutingAndStepSelection(params: {
463
+ session: SessionState<TData>;
464
+ history: Event[]; // Use Event[] for internal processing
465
+ context: TContext;
466
+ signal?: AbortSignal;
467
+ }): Promise<{
468
+ selectedRoute?: Route<TContext, TData>;
469
+ selectedStep?: Step<TContext, TData>;
470
+ responseDirectives?: string[];
471
+ session: SessionState<TData>;
472
+ isRouteComplete: boolean;
473
+ }> {
474
+ try {
475
+ // Use the ResponsePipeline for optimized routing and step selection
476
+ // This avoids duplicate logic and leverages existing optimizations
477
+ // ResponsePipeline expects Event[] for history
478
+ const routingResult = await this.responsePipeline.handleRoutingAndStepSelection({
479
+ session: params.session,
480
+ history: params.history, // Already Event[]
481
+ context: params.context,
482
+ signal: params.signal,
483
+ });
484
+
485
+ let updatedSession = routingResult.session;
486
+ let isRouteComplete = routingResult.isRouteComplete;
487
+
488
+ // PRE-EXTRACTION: If entering a new route that collects data, extract data from user message first
489
+ // This allows us to skip steps whose data is already provided
490
+ if (routingResult.selectedRoute && !isRouteComplete) {
491
+ const isEnteringNewRoute = !params.session.currentRoute ||
492
+ params.session.currentRoute.id !== routingResult.selectedRoute.id;
493
+
494
+ if (isEnteringNewRoute && this.shouldPreExtractData(routingResult.selectedRoute)) {
495
+ logger.debug(
496
+ `[ResponseModal] Pre-extracting data for route: ${routingResult.selectedRoute.title}`
497
+ );
498
+
499
+ const extractedData = await this.preExtractRouteData({
500
+ route: routingResult.selectedRoute,
501
+ history: params.history,
502
+ context: params.context,
503
+ session: updatedSession,
504
+ signal: params.signal,
505
+ });
506
+
507
+ if (extractedData && Object.keys(extractedData).length > 0) {
508
+ logger.debug(
509
+ `[ResponseModal] Pre-extracted data:`,
510
+ extractedData
511
+ );
512
+ // Update session with pre-extracted data
513
+ updatedSession = mergeCollected(updatedSession, extractedData);
514
+ // Also update agent's collected data
515
+ await this.agent.updateCollectedData(extractedData);
516
+
517
+ // Re-check route completion after pre-extraction
518
+ const allRequiredFieldsCollected = routingResult.selectedRoute.isComplete(updatedSession.data || {});
519
+ if (allRequiredFieldsCollected) {
520
+ logger.debug(
521
+ `[ResponseModal] Route ${routingResult.selectedRoute.title} completed after pre-extraction`
522
+ );
523
+ isRouteComplete = true;
524
+ }
525
+ }
526
+ }
527
+ }
528
+
529
+ // Determine next step using pipeline method for consistency
530
+ const stepResult = await this.responsePipeline.determineNextStep({
531
+ selectedRoute: routingResult.selectedRoute,
532
+ selectedStep: routingResult.selectedStep,
533
+ session: updatedSession, // Use updated session with pre-extracted data
534
+ isRouteComplete, // Use updated completion status
535
+ });
536
+
537
+ return {
538
+ selectedRoute: routingResult.selectedRoute,
539
+ selectedStep: stepResult.nextStep, // Use the determined next step
540
+ responseDirectives: routingResult.responseDirectives,
541
+ session: stepResult.session,
542
+ isRouteComplete, // Use updated completion status
543
+ };
544
+ } catch (error) {
545
+ throw ResponseGenerationError.fromError(error, 'routing_optimization', params);
546
+ }
547
+ }
548
+
549
+ /**
550
+ * Check if a route should pre-extract data before determining the initial step
551
+ * @private
552
+ */
553
+ private shouldPreExtractData(route: Route<TContext, TData>): boolean {
554
+ // Pre-extract if route has declared required or optional fields
555
+ if (route.requiredFields && route.requiredFields.length > 0) {
556
+ return true;
557
+ }
558
+ if (route.optionalFields && route.optionalFields.length > 0) {
559
+ return true;
560
+ }
561
+
562
+ // Pre-extract if any step in the route collects data
563
+ const steps = route.getAllSteps();
564
+ const hasDataCollectionSteps = steps.some(
565
+ step => step.collect && step.collect.length > 0
566
+ );
567
+
568
+ return hasDataCollectionSteps;
569
+ }
570
+
571
+ /**
572
+ * Pre-extract data from user message when entering a route
573
+ * This allows skipping steps whose data is already provided
574
+ * @private
575
+ */
576
+ private async preExtractRouteData(params: {
577
+ route: Route<TContext, TData>;
578
+ history: Event[];
579
+ context: TContext;
580
+ session: SessionState<TData>;
581
+ signal?: AbortSignal;
582
+ }): Promise<Partial<TData>> {
583
+ const { route, history, context, signal } = params;
584
+
585
+ // Build a schema for data extraction based on route's fields
586
+ const extractionSchema = this.agent.getSchema();
587
+ if (!extractionSchema) {
588
+ logger.warn(`[ResponseModal] No schema available for pre-extraction`);
589
+ return {};
590
+ }
591
+
592
+ // Get last user message
593
+ const lastMessage = getLastMessageFromHistory(history);
594
+
595
+ // Build extraction prompt
596
+ const extractionPrompt = [
597
+ `Extract any relevant information from the user's message that matches the following data fields.`,
598
+ `Only extract information that is explicitly stated or clearly implied.`,
599
+ ``,
600
+ `User's message: "${lastMessage}"`,
601
+ ``,
602
+ `Extract data for these fields if present:`,
603
+ ];
604
+
605
+ // Add field descriptions
606
+ if (route.requiredFields) {
607
+ extractionPrompt.push(`Required fields: ${route.requiredFields.join(', ')}`);
608
+ }
609
+ if (route.optionalFields) {
610
+ extractionPrompt.push(`Optional fields: ${route.optionalFields.join(', ')}`);
611
+ }
612
+
613
+ extractionPrompt.push(
614
+ ``,
615
+ `Return ONLY the extracted data as JSON. If no data can be extracted, return an empty object {}.`
616
+ );
617
+
618
+ // Call AI to extract data
619
+ const agentOptions = this.agent.getAgentOptions();
620
+ try {
621
+ const result = await agentOptions.provider.generateMessage<TContext, Partial<TData>>({
622
+ prompt: extractionPrompt.join('\n'),
623
+ history,
624
+ context,
625
+ signal,
626
+ parameters: {
627
+ jsonSchema: extractionSchema,
628
+ schemaName: 'data_extraction',
629
+ },
630
+ });
631
+
632
+ return result.structured || {};
633
+ } catch (error) {
634
+ logger.error(`[ResponseModal] Pre-extraction failed:`, error);
635
+ return {};
636
+ }
637
+ }
638
+
639
+ /**
640
+ * Unified response generation for non-streaming responses
641
+ * @private
642
+ */
643
+ private async generateUnifiedResponse(
644
+ responseContext: ResponseContext<TContext, TData>
645
+ ): Promise<AgentResponse<TData>> {
646
+ const { effectiveContext, session: initialSession, history, selectedRoute, selectedStep, responseDirectives, isRouteComplete } = responseContext;
647
+ let session = initialSession;
648
+
649
+ // Get last user message (needed for both route and completion handling)
650
+ // Convert HistoryItem[] to Event[] for internal processing
651
+ const historyEvents = historyToEvents(history);
652
+ const lastMessageText = getLastMessageFromHistory(historyEvents);
653
+
654
+ let message: string;
655
+ let toolCalls: Array<{ toolName: string; arguments: Record<string, unknown> }> | undefined = undefined;
656
+
657
+
658
+
659
+ if (selectedRoute && !isRouteComplete) {
660
+ // Handle normal route processing
661
+
662
+ const result = await this.processRouteResponse({
663
+ selectedRoute,
664
+ selectedStep,
665
+ responseDirectives,
666
+ session,
667
+ history,
668
+ context: effectiveContext,
669
+ lastMessageText,
670
+ historyEvents,
671
+ signal: responseContext.history ? undefined : undefined, // TODO: Fix signal passing
672
+ });
673
+
674
+ message = result.message;
675
+ toolCalls = result.toolCalls;
676
+ session = result.session;
677
+
678
+ } else if (isRouteComplete && selectedRoute) {
679
+ // Handle route completion
680
+ logger.debug(`[ResponseModal] Generating completion message for route: ${selectedRoute.title}`);
681
+
682
+ try {
683
+ message = await this.handleRouteCompletion({
684
+ selectedRoute,
685
+ session,
686
+ context: effectiveContext,
687
+ lastMessageText,
688
+ historyEvents,
689
+ signal: undefined, // TODO: Pass signal from responseContext
690
+ });
691
+
692
+ // Set step to END_ROUTE marker
693
+ session = enterStep(session, END_ROUTE_ID, "Route completed");
694
+ logger.debug(`[ResponseModal] Route ${selectedRoute.title} completed. Entered END_ROUTE step.`);
695
+ } catch (error) {
696
+ logger.error(`[ResponseModal] Error generating completion message:`, error);
697
+ // Fallback to simple completion message
698
+ message = `Thank you! I've recorded all the information for your ${selectedRoute.title.toLowerCase()}.`;
699
+ session = enterStep(session, END_ROUTE_ID, "Route completed");
700
+ }
701
+
702
+ } else {
703
+ // Fallback: No routes defined, generate a simple response
704
+
705
+ message = await this.generateFallbackResponse({
706
+ history: historyEvents, // Use Event[] for fallback response
707
+ context: effectiveContext,
708
+ session,
709
+ });
710
+ }
711
+
712
+ return {
713
+ message,
714
+ session,
715
+ toolCalls,
716
+ isRouteComplete,
717
+ };
718
+ }
719
+
720
+ /**
721
+ * Unified streaming response generation
722
+ * @private
723
+ */
724
+ private async *generateUnifiedStreamingResponse(
725
+ responseContext: ResponseContext<TContext, TData>
726
+ ): AsyncGenerator<AgentResponseStreamChunk<TData>> {
727
+ const { effectiveContext, session: initialSession, history, selectedRoute, selectedStep, responseDirectives, isRouteComplete } = responseContext;
728
+ const session = initialSession;
729
+
730
+ // Get last user message (needed for both route and completion handling)
731
+ // Convert HistoryItem[] to Event[] for internal processing
732
+ const historyEvents = historyToEvents(history);
733
+ const lastMessageText = getLastMessageFromHistory(historyEvents);
734
+
735
+ if (selectedRoute && !isRouteComplete) {
736
+ // Handle normal route processing with streaming
737
+ yield* this.processRouteStreamingResponse({
738
+ selectedRoute,
739
+ selectedStep,
740
+ responseDirectives,
741
+ session,
742
+ history,
743
+ context: effectiveContext,
744
+ lastMessageText,
745
+ historyEvents,
746
+ });
747
+
748
+ } else if (isRouteComplete && selectedRoute) {
749
+ // Handle route completion streaming
750
+ yield* this.streamRouteCompletion({
751
+ selectedRoute,
752
+ session,
753
+ context: effectiveContext,
754
+ lastMessageText,
755
+ historyEvents,
756
+ });
757
+
758
+ } else {
759
+ // Fallback: No routes defined, stream a simple response
760
+ yield* this.streamFallbackResponse({
761
+ history: historyEvents, // Use Event[] for fallback response
762
+ context: effectiveContext,
763
+ session,
764
+ });
765
+ }
766
+ }
767
+ /**
768
+ * Execute prepare function for current step if available
769
+ * @private
770
+ */
771
+ private async executeStepPrepare(session: SessionState<TData>, context: TContext): Promise<void> {
772
+ if (session.currentRoute && session.currentStep) {
773
+ const currentRoute = this.agent.getRoutes().find(
774
+ (r) => r.id === session.currentRoute?.id
775
+ );
776
+ if (currentRoute) {
777
+ const currentStep = currentRoute.getStep(session.currentStep.id);
778
+ if (currentStep?.prepare) {
779
+ logger.debug(`[ResponseModal] Executing prepare for step: ${currentStep.id}`);
780
+ await this.executePrepareFinalize(
781
+ currentStep.prepare,
782
+ context,
783
+ session.data,
784
+ currentRoute,
785
+ currentStep
786
+ );
787
+ }
788
+ }
789
+ }
790
+ }
791
+
792
+ /**
793
+ * Execute finalize function for current step if available
794
+ * @private
795
+ */
796
+ private async executeStepFinalize(session: SessionState<TData>, context: TContext): Promise<void> {
797
+ if (session.currentRoute && session.currentStep) {
798
+ const currentRoute = this.agent.getRoutes().find(
799
+ (r) => r.id === session.currentRoute?.id
800
+ );
801
+ if (currentRoute) {
802
+ const currentStep = currentRoute.getStep(session.currentStep.id);
803
+ if (currentStep?.finalize) {
804
+ logger.debug(
805
+ `[ResponseModal] Executing finalize for step: ${currentStep.id}`
806
+ );
807
+ await this.executePrepareFinalize(
808
+ currentStep.finalize,
809
+ context,
810
+ session.data,
811
+ currentRoute,
812
+ currentStep
813
+ );
814
+ }
815
+ }
816
+ }
817
+ }
818
+
819
+ /**
820
+ * Process route response with unified tool execution and data collection
821
+ * @private
822
+ */
823
+ private async processRouteResponse(params: {
824
+ selectedRoute: Route<TContext, TData>;
825
+ selectedStep?: Step<TContext, TData>;
826
+ responseDirectives?: string[];
827
+ session: SessionState<TData>;
828
+ history: HistoryItem[]; // Keep as HistoryItem[] for AI provider compatibility
829
+ context: TContext;
830
+ lastMessageText: string; // String version for buildResponsePrompt
831
+ historyEvents: Event[]; // Event[] version for buildResponsePrompt
832
+ signal?: AbortSignal;
833
+ }): Promise<{
834
+ message: string;
835
+ toolCalls?: Array<{ toolName: string; arguments: Record<string, unknown> }>;
836
+ session: SessionState<TData>;
837
+ }> {
838
+ const { selectedRoute, selectedStep, responseDirectives, history, context, lastMessageText, historyEvents, signal } = params;
839
+ let session = params.session;
840
+
841
+ // Determine next step
842
+ let nextStep: Step<TContext, TData>;
843
+ if (selectedStep) {
844
+ nextStep = selectedStep;
845
+ } else {
846
+ // Determine current step from session if we're already in this route
847
+ const isInSameRoute = session.currentRoute?.id === selectedRoute.id;
848
+ const currentStep = isInSameRoute && session.currentStep
849
+ ? selectedRoute.getStep(session.currentStep.id)
850
+ : undefined;
851
+
852
+ logger.debug(`[ResponseModal] Step determination: route match=${isInSameRoute}, currentRoute=${session.currentRoute?.id}, selectedRoute=${selectedRoute.id}, currentStep=${currentStep?.id || 'none'}`);
853
+
854
+ // Get candidate steps based on current position in the route
855
+ const routingEngine = this.agent.getRoutingEngine();
856
+ const candidates = await routingEngine.getCandidateStepsWithConditions(
857
+ selectedRoute,
858
+ currentStep, // Pass current step instead of undefined to maintain progression
859
+ createTemplateContext({ data: session.data, session, context })
860
+ );
861
+
862
+ logger.debug(`[ResponseModal] Found ${candidates.length} candidate steps${currentStep ? ' from current step ' + currentStep.id : ' (new route entry)'}`);
863
+
864
+ if (candidates.length > 0) {
865
+ nextStep = candidates[0].step;
866
+ logger.debug(`[ResponseModal] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new route'}`);
867
+ } else {
868
+ // Fallback to initial step even if it should be skipped
869
+ nextStep = selectedRoute.initialStep;
870
+ logger.warn(`[ResponseModal] No valid steps found, using initial step: ${nextStep.id}`);
871
+ }
872
+ }
873
+
874
+ // Update session with next step
875
+ session = enterStep(session, nextStep.id, nextStep.description);
876
+ logger.debug(`[ResponseModal] Entered step: ${nextStep.id}`);
877
+
878
+ // Build response schema for this route (with collect fields from step)
879
+ const responseSchema = this.responseEngine.responseSchemaForRoute(selectedRoute, nextStep, this.agent.getSchema());
880
+
881
+ // Build response prompt
882
+ const responsePrompt = await this.responseEngine.buildResponsePrompt({
883
+ route: selectedRoute,
884
+ currentStep: nextStep,
885
+ rules: selectedRoute.getRules(),
886
+ prohibitions: selectedRoute.getProhibitions(),
887
+ directives: responseDirectives,
888
+ history: historyEvents, // Use Event[] for buildResponsePrompt
889
+ lastMessage: lastMessageText, // Use string for buildResponsePrompt
890
+ agentOptions: this.agent.getAgentOptions(),
891
+ combinedGuidelines: [...this.agent.getGuidelines(), ...selectedRoute.getGuidelines()],
892
+ combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
893
+ context,
894
+ session,
895
+ agentSchema: this.agent.getSchema(),
896
+ });
897
+
898
+ // Collect available tools for AI
899
+ const availableTools = this.collectAvailableTools(selectedRoute, nextStep);
900
+
901
+ // Generate message using AI provider
902
+ const agentOptions = this.agent.getAgentOptions();
903
+ const result = await agentOptions.provider.generateMessage({
904
+ prompt: responsePrompt,
905
+ history: historyEvents, // Use Event[] for AI provider
906
+ context,
907
+ tools: availableTools,
908
+ signal,
909
+ parameters: responseSchema ? { jsonSchema: responseSchema, schemaName: "response_output" } : undefined,
910
+ });
911
+
912
+ let message = result.structured?.message || result.message;
913
+ let toolCalls = result.structured?.toolCalls;
914
+
915
+ // Debug: Log initial AI response
916
+ logger.debug(`[ResponseModal] Initial AI response:`, {
917
+ hasMessage: !!message,
918
+ messageLength: message?.length || 0,
919
+ hasToolCalls: !!toolCalls,
920
+ toolCallsCount: toolCalls?.length || 0,
921
+ toolNames: toolCalls?.map(tc => tc.toolName) || [],
922
+ });
923
+
924
+ // Execute tools with unified loop handling
925
+ const toolResult = await this.executeUnifiedToolLoop({
926
+ toolCalls,
927
+ context,
928
+ session,
929
+ history,
930
+ selectedRoute,
931
+ responsePrompt,
932
+ availableTools,
933
+ responseSchema,
934
+ signal,
935
+ });
936
+
937
+ session = toolResult.session;
938
+ toolCalls = toolResult.finalToolCalls;
939
+ if (toolResult.finalMessage) {
940
+ message = toolResult.finalMessage;
941
+ }
942
+
943
+ // Collect data from response
944
+ session = await this.collectDataFromResponse({ result, selectedRoute, nextStep, session });
945
+
946
+ return { message, toolCalls, session };
947
+ }
948
+
949
+ /**
950
+ * Process route streaming response with unified tool execution and data collection
951
+ * @private
952
+ */
953
+ private async *processRouteStreamingResponse(params: {
954
+ selectedRoute: Route<TContext, TData>;
955
+ selectedStep?: Step<TContext, TData>;
956
+ responseDirectives?: string[];
957
+ session: SessionState<TData>;
958
+ history: HistoryItem[];
959
+ context: TContext;
960
+ lastMessageText: string; // String version for buildResponsePrompt
961
+ historyEvents: Event[]; // Event[] version for buildResponsePrompt
962
+ signal?: AbortSignal;
963
+ }): AsyncGenerator<AgentResponseStreamChunk<TData>> {
964
+ const { selectedRoute, selectedStep, responseDirectives, history, context, lastMessageText, historyEvents, signal } = params;
965
+ let session = params.session;
966
+
967
+ // Determine next step (same logic as non-streaming)
968
+ let nextStep: Step<TContext, TData>;
969
+ if (selectedStep) {
970
+ nextStep = selectedStep;
971
+ } else {
972
+ // Determine current step from session if we're already in this route
973
+ const currentStep = session.currentRoute?.id === selectedRoute.id && session.currentStep
974
+ ? selectedRoute.getStep(session.currentStep.id)
975
+ : undefined;
976
+
977
+ // Get candidate steps based on current position in the route
978
+ const routingEngine = this.agent.getRoutingEngine();
979
+ const candidates = await routingEngine.getCandidateStepsWithConditions(
980
+ selectedRoute,
981
+ currentStep, // Pass current step instead of undefined to maintain progression
982
+ createTemplateContext({ data: session.data, session, context })
983
+ );
984
+
985
+ if (candidates.length > 0) {
986
+ nextStep = candidates[0].step;
987
+ logger.debug(`[ResponseModal] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new route'}`);
988
+ } else {
989
+ nextStep = selectedRoute.initialStep;
990
+ logger.warn(`[ResponseModal] No valid steps found, using initial step: ${nextStep.id}`);
991
+ }
992
+ }
993
+
994
+ // Update session with next step
995
+ session = enterStep(session, nextStep.id, nextStep.description);
996
+ logger.debug(`[ResponseModal] Entered step: ${nextStep.id}`);
997
+
998
+ // Build response schema and prompt (same as non-streaming)
999
+ const responseSchema = this.responseEngine.responseSchemaForRoute(selectedRoute, nextStep, this.agent.getSchema());
1000
+ const responsePrompt = await this.responseEngine.buildResponsePrompt({
1001
+ route: selectedRoute,
1002
+ currentStep: nextStep,
1003
+ rules: selectedRoute.getRules(),
1004
+ prohibitions: selectedRoute.getProhibitions(),
1005
+ directives: responseDirectives,
1006
+ history: historyEvents, // Use Event[] for buildResponsePrompt
1007
+ lastMessage: lastMessageText, // Use string for buildResponsePrompt
1008
+ agentOptions: this.agent.getAgentOptions(),
1009
+ combinedGuidelines: [...this.agent.getGuidelines(), ...selectedRoute.getGuidelines()],
1010
+ combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
1011
+ context,
1012
+ session,
1013
+ agentSchema: this.agent.getSchema(),
1014
+ });
1015
+
1016
+ // Collect available tools for AI
1017
+ const availableTools = this.collectAvailableTools(selectedRoute, nextStep);
1018
+
1019
+ // Generate message stream using AI provider
1020
+ const agentOptions = this.agent.getAgentOptions();
1021
+ const stream = agentOptions.provider.generateMessageStream({
1022
+ prompt: responsePrompt,
1023
+ history: historyEvents, // Use Event[] for AI provider
1024
+ context,
1025
+ tools: availableTools,
1026
+ signal,
1027
+ parameters: { jsonSchema: responseSchema, schemaName: "response_stream_output" },
1028
+ });
1029
+
1030
+ // Stream chunks with unified tool handling
1031
+ for await (const chunk of stream) {
1032
+ let toolCalls: Array<{ toolName: string; arguments: Record<string, unknown> }> | undefined = undefined;
1033
+
1034
+ // Extract tool calls from AI response on final chunk
1035
+ if (chunk.done && chunk.structured?.toolCalls) {
1036
+ toolCalls = chunk.structured.toolCalls;
1037
+
1038
+ // Execute tools with unified loop handling
1039
+ const toolResult = await this.executeUnifiedToolLoop({
1040
+ toolCalls,
1041
+ context,
1042
+ session,
1043
+ history,
1044
+ selectedRoute,
1045
+ responsePrompt,
1046
+ availableTools,
1047
+ responseSchema,
1048
+ signal,
1049
+ });
1050
+
1051
+ session = toolResult.session;
1052
+ toolCalls = toolResult.finalToolCalls;
1053
+ }
1054
+
1055
+ // Extract collected data on final chunk
1056
+ if (chunk.done && chunk.structured && nextStep.collect) {
1057
+ session = await this.collectDataFromResponse({
1058
+ result: { structured: chunk.structured },
1059
+ selectedRoute,
1060
+ nextStep,
1061
+ session,
1062
+ });
1063
+ }
1064
+
1065
+ // Handle session finalization on final chunk
1066
+ if (chunk.done) {
1067
+ await this.finalizeSession(session, context);
1068
+ }
1069
+
1070
+ yield {
1071
+ delta: chunk.delta,
1072
+ accumulated: chunk.accumulated,
1073
+ done: chunk.done,
1074
+ session,
1075
+ toolCalls,
1076
+ isRouteComplete: false,
1077
+ metadata: chunk.metadata,
1078
+ structured: chunk.structured,
1079
+ };
1080
+ }
1081
+ }
1082
+
1083
+ /**
1084
+ * Unified tool execution logic with loop handling
1085
+ * Consolidates the complex tool execution logic from both streaming and non-streaming responses
1086
+ * @private
1087
+ */
1088
+ private async executeUnifiedToolLoop(params: {
1089
+ toolCalls?: Array<{ toolName: string; arguments: Record<string, unknown> }>;
1090
+ context: TContext;
1091
+ session: SessionState<TData>;
1092
+ history: HistoryItem[];
1093
+ selectedRoute?: Route<TContext, TData>;
1094
+ responsePrompt: string;
1095
+ availableTools: Array<{
1096
+ id: string;
1097
+ name: string;
1098
+ description?: string;
1099
+ parameters?: unknown;
1100
+ }>;
1101
+ responseSchema?: Record<string, unknown>;
1102
+ signal?: AbortSignal;
1103
+ }): Promise<{
1104
+ session: SessionState<TData>;
1105
+ finalToolCalls?: Array<{ toolName: string; arguments: Record<string, unknown> }>;
1106
+ finalMessage?: string;
1107
+ }> {
1108
+ try {
1109
+ const { context, history, selectedRoute, responsePrompt, availableTools, responseSchema, signal } = params;
1110
+ let { toolCalls, session } = params;
1111
+
1112
+ // Convert HistoryItem[] to Event[] for internal processing
1113
+ const historyEvents = historyToEvents(history);
1114
+
1115
+ // Execute initial dynamic tool calls
1116
+ if (toolCalls && toolCalls.length > 0) {
1117
+ logger.debug(`[ResponseModal] Executing ${toolCalls.length} dynamic tool calls:`, toolCalls.map(tc => tc.toolName));
1118
+
1119
+ for (const toolCall of toolCalls) {
1120
+ const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
1121
+ if (!tool) {
1122
+ logger.warn(`[ResponseModal] Tool not found: ${toolCall.toolName}`);
1123
+ continue;
1124
+ }
1125
+
1126
+ try {
1127
+ // Use ToolManager for unified tool execution
1128
+ const toolManager = this.getToolManager();
1129
+ let toolResult;
1130
+
1131
+ if (toolManager) {
1132
+ toolResult = await toolManager.executeTool({
1133
+ tool: tool,
1134
+ context,
1135
+ updateContext: this.agent.updateContext.bind(this.agent),
1136
+ updateData: this.agent.updateCollectedData.bind(this.agent),
1137
+ history: historyEvents, // Use Event[] for tool execution
1138
+ data: session.data,
1139
+ toolArguments: toolCall.arguments,
1140
+ });
1141
+ } else {
1142
+ // Fallback: execute tool directly if ToolManager not available
1143
+ throw new Error(`ToolManager not available for tool execution: ${toolCall.toolName}`);
1144
+ }
1145
+
1146
+ // Check if tool execution was successful
1147
+ if (!toolResult.success) {
1148
+ logger.error(`[ResponseModal] Tool execution failed: ${toolCall.toolName} - ${toolResult.error}`);
1149
+ // Continue with other tools rather than failing completely
1150
+ continue;
1151
+ }
1152
+
1153
+ // Update context with tool results
1154
+ if (toolResult.contextUpdate) {
1155
+ try {
1156
+ await this.agent.updateContext(toolResult.contextUpdate as Partial<TContext>);
1157
+ } catch (error) {
1158
+ logger.error(`[ResponseModal] Failed to update context from tool ${toolCall.toolName}:`, error);
1159
+ // Continue execution but log the error
1160
+ }
1161
+ }
1162
+
1163
+ // Update collected data with tool results
1164
+ if (toolResult.dataUpdate) {
1165
+ try {
1166
+ const updateDataMethod = this.agent.getUpdateDataMethod();
1167
+ session = await updateDataMethod(session, toolResult.dataUpdate as Partial<TData>);
1168
+ logger.debug(`[ResponseModal] Tool updated collected data:`, toolResult.dataUpdate);
1169
+ } catch (error) {
1170
+ logger.error(`[ResponseModal] Failed to update data from tool ${toolCall.toolName}:`, error);
1171
+ // Continue execution but log the error
1172
+ }
1173
+ }
1174
+
1175
+ logger.debug(`[ResponseModal] Executed dynamic tool: ${toolCall.toolName} (success: ${toolResult.success})`);
1176
+ } catch (error) {
1177
+ logger.error(`[ResponseModal] Tool execution error for ${toolCall.toolName}:`, error);
1178
+ // Continue with other tools rather than failing the entire response
1179
+ continue;
1180
+ }
1181
+ }
1182
+ }
1183
+
1184
+ // TOOL LOOP: Allow AI to make follow-up tool calls after initial tool execution
1185
+ const MAX_TOOL_LOOPS = this.options?.maxToolLoops || 5;
1186
+ let toolLoopCount = 0;
1187
+ let hasToolCalls = toolCalls && toolCalls.length > 0;
1188
+ let finalMessage: string | undefined;
1189
+
1190
+ while (hasToolCalls && toolLoopCount < MAX_TOOL_LOOPS) {
1191
+ toolLoopCount++;
1192
+ logger.debug(`[ResponseModal] Starting tool loop ${toolLoopCount}/${MAX_TOOL_LOOPS} with ${toolCalls?.length || 0} tool calls`);
1193
+
1194
+ // Create tool result events with proper Event format structure
1195
+ const toolResultEvents: Event<ToolEventData>[] = [];
1196
+ for (const toolCall of toolCalls || []) {
1197
+ const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
1198
+ if (tool) {
1199
+ // Create proper Event format for tool results
1200
+ const toolResultEvent: Event<ToolEventData> = {
1201
+ kind: EventKind.TOOL,
1202
+ source: MessageRole.AGENT,
1203
+ timestamp: new Date().toISOString(),
1204
+ data: {
1205
+ tool_calls: [
1206
+ {
1207
+ tool_id: toolCall.toolName,
1208
+ arguments: toolCall.arguments,
1209
+ result: {
1210
+ data: "Tool executed successfully",
1211
+ },
1212
+ },
1213
+ ],
1214
+ },
1215
+ };
1216
+ toolResultEvents.push(toolResultEvent);
1217
+ }
1218
+ }
1219
+
1220
+ // Create updated history with tool results (combine Event arrays)
1221
+ const updatedHistoryEvents = [...historyEvents, ...toolResultEvents];
1222
+
1223
+ // Make follow-up AI call to see if more tools are needed
1224
+ // After first iteration, don't provide tools to force a text response
1225
+ const agentOptions = this.agent.getAgentOptions();
1226
+ const shouldProvideTools = toolLoopCount === 1;
1227
+
1228
+ logger.debug(`[ResponseModal] Making follow-up AI call (loop ${toolLoopCount}):`, {
1229
+ providingTools: shouldProvideTools,
1230
+ toolsCount: shouldProvideTools ? availableTools.length : 0,
1231
+ addingTextInstruction: toolLoopCount > 1,
1232
+ });
1233
+
1234
+ const followUpResult = await agentOptions.provider.generateMessage({
1235
+ prompt: responsePrompt + (toolLoopCount > 1 ? "\n\nProvide a text response to the user based on the tool results." : ""),
1236
+ history: updatedHistoryEvents, // Use Event[] for AI provider
1237
+ context,
1238
+ tools: shouldProvideTools ? availableTools : [], // Only provide tools on first iteration
1239
+ parameters: responseSchema ? {
1240
+ jsonSchema: responseSchema,
1241
+ schemaName: "tool_followup",
1242
+ } : undefined,
1243
+ signal,
1244
+ });
1245
+
1246
+ // Check if follow-up call has more tool calls
1247
+ const followUpToolCalls = followUpResult.structured?.toolCalls;
1248
+ hasToolCalls = followUpToolCalls && followUpToolCalls.length > 0;
1249
+
1250
+ logger.debug(`[ResponseModal] Follow-up AI response (loop ${toolLoopCount}):`, {
1251
+ hasMessage: !!followUpResult.message,
1252
+ messageLength: followUpResult.message?.length || 0,
1253
+ hasToolCalls,
1254
+ toolCallsCount: followUpToolCalls?.length || 0,
1255
+ toolNames: followUpToolCalls?.map(tc => tc.toolName) || [],
1256
+ });
1257
+
1258
+ if (hasToolCalls) {
1259
+ logger.debug(`[ResponseModal] Follow-up call produced ${followUpToolCalls!.length} additional tool calls`);
1260
+
1261
+ // Execute the follow-up tool calls
1262
+ for (const toolCall of followUpToolCalls!) {
1263
+ const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
1264
+ if (!tool) {
1265
+ logger.warn(`[ResponseModal] Tool not found in follow-up: ${toolCall.toolName}`);
1266
+ continue;
1267
+ }
1268
+
1269
+ try {
1270
+ // Use ToolManager for unified tool execution
1271
+ const toolManager = this.getToolManager();
1272
+ let toolResult;
1273
+
1274
+ if (toolManager) {
1275
+ toolResult = await toolManager.executeTool({
1276
+ tool: tool,
1277
+ context,
1278
+ updateContext: this.agent.updateContext.bind(this.agent),
1279
+ updateData: this.agent.updateCollectedData.bind(this.agent),
1280
+ history: updatedHistoryEvents, // Use Event[] for tool execution
1281
+ data: session.data,
1282
+ toolArguments: toolCall.arguments,
1283
+ });
1284
+ } else {
1285
+ // Fallback: execute tool directly if ToolManager not available
1286
+ throw new Error(`ToolManager not available for follow-up tool execution: ${toolCall.toolName}`);
1287
+ }
1288
+
1289
+ // Check if tool execution was successful
1290
+ if (!toolResult.success) {
1291
+ logger.error(`[ResponseModal] Follow-up tool execution failed: ${toolCall.toolName} - ${toolResult.error}`);
1292
+ continue;
1293
+ }
1294
+
1295
+ // Update context with follow-up tool results
1296
+ if (toolResult.contextUpdate) {
1297
+ try {
1298
+ await this.agent.updateContext(toolResult.contextUpdate as Partial<TContext>);
1299
+ } catch (error) {
1300
+ logger.error(`[ResponseModal] Failed to update context from follow-up tool ${toolCall.toolName}:`, error);
1301
+ }
1302
+ }
1303
+
1304
+ if (toolResult.dataUpdate) {
1305
+ try {
1306
+ const updateDataMethod = this.agent.getUpdateDataMethod();
1307
+ session = await updateDataMethod(session, toolResult.dataUpdate as Partial<TData>);
1308
+ logger.debug(`[ResponseModal] Follow-up tool updated collected data:`, toolResult.dataUpdate);
1309
+ } catch (error) {
1310
+ logger.error(`[ResponseModal] Failed to update data from follow-up tool ${toolCall.toolName}:`, error);
1311
+ }
1312
+ }
1313
+
1314
+ logger.debug(`[ResponseModal] Executed follow-up tool: ${toolCall.toolName} (success: ${toolResult.success})`);
1315
+ } catch (error) {
1316
+ logger.error(`[ResponseModal] Follow-up tool execution error for ${toolCall.toolName}:`, error);
1317
+ continue;
1318
+ }
1319
+ }
1320
+
1321
+ // Update toolCalls for next iteration or final response
1322
+ toolCalls = followUpToolCalls;
1323
+ } else {
1324
+ logger.debug(`[ResponseModal] Tool loop completed after ${toolLoopCount} iterations`);
1325
+ // Update final message and toolCalls from follow-up result if no more tools
1326
+ finalMessage = followUpResult.structured?.message || followUpResult.message;
1327
+ toolCalls = followUpToolCalls || [];
1328
+ break;
1329
+ }
1330
+ }
1331
+
1332
+ if (toolLoopCount >= MAX_TOOL_LOOPS) {
1333
+ logger.warn(`[ResponseModal] Tool loop limit reached (${MAX_TOOL_LOOPS}), stopping`);
1334
+ }
1335
+
1336
+ logger.debug(`[ResponseModal] Tool loop completed:`, {
1337
+ totalIterations: toolLoopCount,
1338
+ hasFinalMessage: !!finalMessage,
1339
+ finalMessageLength: finalMessage?.length || 0,
1340
+ finalToolCallsCount: toolCalls?.length || 0,
1341
+ });
1342
+
1343
+ return {
1344
+ session,
1345
+ finalToolCalls: toolCalls,
1346
+ finalMessage,
1347
+ };
1348
+ } catch (error) {
1349
+ throw ResponseGenerationError.fromError(error, 'tool_execution', params, {
1350
+ toolCallsCount: params.toolCalls?.length || 0,
1351
+ availableToolsCount: params.availableTools.length
1352
+ });
1353
+ }
1354
+ } /**
1355
+ * Unified data collection from AI response
1356
+ * @private
1357
+ */
1358
+ private async collectDataFromResponse(params: {
1359
+ result: { structured?: AgentStructuredResponse };
1360
+ selectedRoute?: Route<TContext, TData>;
1361
+ nextStep?: Step<TContext, TData>;
1362
+ session: SessionState<TData>;
1363
+ }): Promise<SessionState<TData>> {
1364
+ try {
1365
+ const { result, selectedRoute, nextStep, session } = params;
1366
+ let updatedSession = session;
1367
+
1368
+ // Extract collected data from final response (only for route-based interactions)
1369
+ if (selectedRoute && result.structured) {
1370
+ try {
1371
+ const collectedData: Record<string, unknown> = {};
1372
+ // AgentStructuredResponse extends Record<string, unknown>, so we can safely access properties
1373
+ const structuredData = result.structured;
1374
+
1375
+ // Collect ALL route fields (required + optional) from structured response
1376
+ const allRouteFields = new Set<string>();
1377
+
1378
+ // Add route required fields
1379
+ if (selectedRoute.requiredFields) {
1380
+ selectedRoute.requiredFields.forEach(field => allRouteFields.add(String(field)));
1381
+ }
1382
+
1383
+ // Add route optional fields
1384
+ if (selectedRoute.optionalFields) {
1385
+ selectedRoute.optionalFields.forEach(field => allRouteFields.add(String(field)));
1386
+ }
1387
+
1388
+ // Also include current step's collect fields (in case they're not in route fields)
1389
+ if (nextStep?.collect) {
1390
+ nextStep.collect.forEach(field => allRouteFields.add(String(field)));
1391
+ }
1392
+
1393
+ // Extract all available fields from structured response
1394
+ for (const field of allRouteFields) {
1395
+ const fieldKey = String(field);
1396
+ if (fieldKey in structuredData && structuredData[fieldKey] !== undefined && structuredData[fieldKey] !== null) {
1397
+ collectedData[fieldKey] = structuredData[fieldKey];
1398
+ }
1399
+ }
1400
+
1401
+ // Merge collected data into session using agent-level data validation
1402
+ if (Object.keys(collectedData).length > 0) {
1403
+ try {
1404
+ // Update agent-level collected data with validation
1405
+ await this.agent.updateCollectedData(collectedData as Partial<TData>);
1406
+
1407
+ // Update session with validated data
1408
+ const updateDataMethod = this.agent.getUpdateDataMethod();
1409
+ updatedSession = await updateDataMethod(updatedSession, collectedData as Partial<TData>);
1410
+ logger.debug(`[ResponseModal] Collected data:`, collectedData);
1411
+ } catch (error) {
1412
+ logger.error(`[ResponseModal] Failed to update collected data:`, error);
1413
+ // Continue without updating data rather than failing completely
1414
+ }
1415
+ }
1416
+ } catch (error) {
1417
+ logger.error(`[ResponseModal] Error during data collection:`, error);
1418
+ // Continue without collecting data rather than failing completely
1419
+ }
1420
+ }
1421
+
1422
+ // Extract any additional data from structured response
1423
+ // Since AgentStructuredResponse extends Record<string, unknown>, we can safely check for additional properties
1424
+ if (result.structured && "contextUpdate" in result.structured) {
1425
+ try {
1426
+ const contextUpdate = (result.structured as AgentStructuredResponse & { contextUpdate?: Partial<TContext> }).contextUpdate;
1427
+ if (contextUpdate) {
1428
+ await this.agent.updateContext(contextUpdate);
1429
+ }
1430
+ } catch (error) {
1431
+ logger.error(`[ResponseModal] Failed to update context from structured response:`, error);
1432
+ // Continue without updating context rather than failing completely
1433
+ }
1434
+ }
1435
+
1436
+ return updatedSession;
1437
+ } catch (error) {
1438
+ logger.error(`[ResponseModal] Error in collectDataFromResponse:`, error);
1439
+ // Return original session if data collection fails completely
1440
+ return params.session;
1441
+ }
1442
+ }
1443
+
1444
+ /**
1445
+ * Handle route completion logic
1446
+ * @private
1447
+ */
1448
+ private async handleRouteCompletion(params: {
1449
+ selectedRoute: Route<TContext, TData>;
1450
+ session: SessionState<TData>;
1451
+ context: TContext;
1452
+ lastMessageText: string; // String version for buildResponsePrompt
1453
+ historyEvents: Event[]; // Event[] version for buildResponsePrompt
1454
+ signal?: AbortSignal;
1455
+ }): Promise<string> {
1456
+ const { selectedRoute, session, context, lastMessageText, historyEvents, signal } = params;
1457
+
1458
+ // Get endStep spec from route
1459
+ const endStepSpec = selectedRoute.endStepSpec;
1460
+
1461
+ // Create a temporary step for completion message generation using endStep configuration
1462
+ const completionStep = new Step<TContext, TData>(selectedRoute.id, {
1463
+ description: endStepSpec.description,
1464
+ id: endStepSpec.id || END_ROUTE_ID,
1465
+ collect: endStepSpec.collect,
1466
+ requires: endStepSpec.requires,
1467
+ prompt: endStepSpec.prompt || "Summarize what was accomplished and confirm completion based on the conversation history and collected data",
1468
+ });
1469
+
1470
+ // Build response schema for completion (message only, no data collection)
1471
+ const completionSchema = {
1472
+ type: "object",
1473
+ properties: {
1474
+ message: {
1475
+ type: "string",
1476
+ description: "Completion message confirming what was accomplished",
1477
+ },
1478
+ },
1479
+ required: ["message"],
1480
+ additionalProperties: false,
1481
+ };
1482
+
1483
+ const templateContext = createTemplateContext({ context, session, history: historyEvents });
1484
+
1485
+ // Build completion response prompt using ResponseEngine
1486
+ // Filter out conditional guidelines - only include always-active ones
1487
+ const alwaysActiveGuidelines = [
1488
+ ...this.agent.getGuidelines().filter(g => !g.condition),
1489
+ ...selectedRoute.getGuidelines().filter(g => !g.condition),
1490
+ ];
1491
+ let completitionPrompt = "Summarize what was accomplished and confirm completion"
1492
+ if(endStepSpec.prompt){
1493
+ completitionPrompt = await render(endStepSpec.prompt, templateContext)
1494
+ }
1495
+
1496
+ const completionPrompt = await this.responseEngine.buildResponsePrompt({
1497
+ route: selectedRoute,
1498
+ currentStep: completionStep,
1499
+ rules: selectedRoute.getRules(),
1500
+ prohibitions: selectedRoute.getProhibitions(),
1501
+ directives: [
1502
+ `Task completed: ${selectedRoute.title}`,
1503
+ `Collected data: ${JSON.stringify(session.data, null, 2)}`,
1504
+ "Do NOT ask for more information - the task is complete",
1505
+ completitionPrompt,
1506
+ ],
1507
+ history: historyEvents,
1508
+ lastMessage: lastMessageText,
1509
+ agentOptions: this.agent.getAgentOptions(),
1510
+ combinedGuidelines: alwaysActiveGuidelines, // Only non-conditional guidelines
1511
+ combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
1512
+ context,
1513
+ session,
1514
+ agentSchema: undefined, // No data collection schema for completion
1515
+ });
1516
+
1517
+ // Generate completion message using AI provider
1518
+ const agentOptions = this.agent.getAgentOptions();
1519
+ logger.debug(`[ResponseModal] Calling AI provider for completion message...`);
1520
+
1521
+ const completionResult = await agentOptions.provider.generateMessage({
1522
+ prompt: completionPrompt,
1523
+ history: historyEvents,
1524
+ context,
1525
+ signal,
1526
+ parameters: { jsonSchema: completionSchema, schemaName: "completion_message" },
1527
+ });
1528
+
1529
+ logger.debug(`[ResponseModal] AI provider returned completion result`);
1530
+ const message = completionResult.structured?.message || completionResult.message;
1531
+ logger.debug(`[ResponseModal] Generated completion message for route: ${selectedRoute.title}`);
1532
+
1533
+ // Check for onComplete transition
1534
+ const transitionConfig = await selectedRoute.evaluateOnComplete({ data: session.data }, context);
1535
+
1536
+ if (transitionConfig) {
1537
+ // Find target route by ID or title
1538
+ const targetRoute = this.agent.getRoutes().find(
1539
+ (r) => r.id === transitionConfig.nextStep || r.title === transitionConfig.nextStep
1540
+ );
1541
+
1542
+ if (targetRoute) {
1543
+ const renderedCondition = await render(transitionConfig.condition, templateContext);
1544
+ // Set pending transition in session
1545
+ session.pendingTransition = {
1546
+ targetRouteId: targetRoute.id,
1547
+ condition: renderedCondition,
1548
+ reason: "route_complete",
1549
+ };
1550
+ logger.debug(`[ResponseModal] Route ${selectedRoute.title} completed with pending transition to: ${targetRoute.title}`);
1551
+ } else {
1552
+ logger.warn(`[ResponseModal] Route ${selectedRoute.title} completed but target route not found: ${transitionConfig.nextStep}`);
1553
+ }
1554
+ }
1555
+
1556
+ return message;
1557
+ }
1558
+
1559
+ /**
1560
+ * Stream route completion response
1561
+ * @private
1562
+ */
1563
+ private async *streamRouteCompletion(params: {
1564
+ selectedRoute: Route<TContext, TData>;
1565
+ session: SessionState<TData>;
1566
+ context: TContext;
1567
+ lastMessageText: string; // String version for buildResponsePrompt
1568
+ historyEvents: Event[]; // Event[] version for buildResponsePrompt
1569
+ signal?: AbortSignal;
1570
+ }): AsyncGenerator<AgentResponseStreamChunk<TData>> {
1571
+ const { selectedRoute, context, lastMessageText, historyEvents, signal } = params;
1572
+ let session = params.session;
1573
+
1574
+ // Get endStep spec from route
1575
+ const endStepSpec = selectedRoute.endStepSpec;
1576
+
1577
+ // Create a temporary step for completion message generation using endStep configuration
1578
+ const completionStep = new Step<TContext, TData>(selectedRoute.id, {
1579
+ description: endStepSpec.description,
1580
+ id: endStepSpec.id || END_ROUTE_ID,
1581
+ collect: endStepSpec.collect,
1582
+ requires: endStepSpec.requires,
1583
+ prompt: endStepSpec.prompt || "Summarize what was accomplished and confirm completion based on the conversation history and collected data",
1584
+ });
1585
+
1586
+ // Build response schema for completion
1587
+ const responseSchema = this.responseEngine.responseSchemaForRoute(selectedRoute, completionStep, this.agent.getSchema());
1588
+ const templateContext = createTemplateContext({ context, session, history: historyEvents }); // Use Event[] for template context
1589
+
1590
+ // Build completion response prompt
1591
+ const completionPrompt = await this.responseEngine.buildResponsePrompt({
1592
+ route: selectedRoute,
1593
+ currentStep: completionStep,
1594
+ rules: selectedRoute.getRules(),
1595
+ prohibitions: selectedRoute.getProhibitions(),
1596
+ directives: undefined, // No directives for completion
1597
+ history: historyEvents, // Use Event[] for buildResponsePrompt
1598
+ lastMessage: lastMessageText, // Use string for buildResponsePrompt
1599
+ agentOptions: this.agent.getAgentOptions(),
1600
+ combinedGuidelines: [...this.agent.getGuidelines(), ...selectedRoute.getGuidelines()],
1601
+ combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
1602
+ context,
1603
+ session,
1604
+ agentSchema: this.agent.getSchema(),
1605
+ });
1606
+
1607
+ // Stream completion message using AI provider
1608
+ const agentOptions = this.agent.getAgentOptions();
1609
+ const stream = agentOptions.provider.generateMessageStream({
1610
+ prompt: completionPrompt,
1611
+ history: historyEvents, // Use Event[] for AI provider
1612
+ context,
1613
+ signal,
1614
+ parameters: { jsonSchema: responseSchema, schemaName: "completion_message_stream" },
1615
+ });
1616
+
1617
+ logger.debug(`[ResponseModal] Streaming completion message for route: ${selectedRoute.title}`);
1618
+
1619
+ // Check for onComplete transition
1620
+ const transitionConfig = await selectedRoute.evaluateOnComplete({ data: session.data }, context);
1621
+
1622
+ if (transitionConfig) {
1623
+ // Find target route by ID or title
1624
+ const targetRoute = this.agent.getRoutes().find(
1625
+ (r) => r.id === transitionConfig.nextStep || r.title === transitionConfig.nextStep
1626
+ );
1627
+
1628
+ if (targetRoute) {
1629
+ const renderedCondition = await render(transitionConfig.condition, templateContext);
1630
+ // Set pending transition in session
1631
+ session = {
1632
+ ...session,
1633
+ pendingTransition: {
1634
+ targetRouteId: targetRoute.id,
1635
+ condition: renderedCondition,
1636
+ reason: "route_complete",
1637
+ },
1638
+ };
1639
+ logger.debug(`[ResponseModal] Route ${selectedRoute.title} completed with pending transition to: ${targetRoute.title}`);
1640
+ } else {
1641
+ logger.warn(`[ResponseModal] Route ${selectedRoute.title} completed but target route not found: ${transitionConfig.nextStep}`);
1642
+ }
1643
+ }
1644
+
1645
+ // Set step to END_ROUTE marker
1646
+ session = enterStep(session, END_ROUTE_ID, "Route completed");
1647
+ logger.debug(`[ResponseModal] Route ${selectedRoute.title} completed. Entered END_ROUTE step.`);
1648
+
1649
+ // Stream completion chunks
1650
+ for await (const chunk of stream) {
1651
+ // Update current session if we have one
1652
+ if (chunk.done) {
1653
+ await this.finalizeSession(session, context);
1654
+ }
1655
+
1656
+ yield {
1657
+ delta: chunk.delta,
1658
+ accumulated: chunk.accumulated,
1659
+ done: chunk.done,
1660
+ session,
1661
+ toolCalls: undefined,
1662
+ isRouteComplete: true,
1663
+ metadata: chunk.metadata,
1664
+ structured: chunk.structured,
1665
+ };
1666
+ }
1667
+ }
1668
+
1669
+ /**
1670
+ * Generate fallback response when no routes are available
1671
+ * @private
1672
+ */
1673
+ private async generateFallbackResponse(params: {
1674
+ history: Event[]; // Use Event[] for buildFallbackPrompt
1675
+ context: TContext;
1676
+ session: SessionState<TData>;
1677
+ signal?: AbortSignal;
1678
+ }): Promise<string> {
1679
+ const { history, context, session, signal } = params;
1680
+
1681
+ logger.debug(`[ResponseModal] No route selected, generating basic response`);
1682
+
1683
+ // Build basic response prompt without route context
1684
+ const fallbackPrompt = await this.responseEngine.buildFallbackPrompt({
1685
+ history,
1686
+ agentOptions: this.agent.getAgentOptions(),
1687
+ terms: this.agent.getTerms(),
1688
+ guidelines: this.agent.getGuidelines(),
1689
+ context,
1690
+ session,
1691
+ });
1692
+
1693
+ const agentOptions = this.agent.getAgentOptions();
1694
+ const result = await agentOptions.provider.generateMessage({
1695
+ prompt: fallbackPrompt,
1696
+ history,
1697
+ context,
1698
+ signal,
1699
+ parameters: {
1700
+ jsonSchema: {
1701
+ type: "object",
1702
+ properties: { message: { type: "string" } },
1703
+ required: ["message"],
1704
+ additionalProperties: false,
1705
+ },
1706
+ schemaName: "fallback_response",
1707
+ },
1708
+ });
1709
+
1710
+ return result.structured?.message || result.message;
1711
+ }
1712
+
1713
+ /**
1714
+ * Stream fallback response when no routes are available
1715
+ * @private
1716
+ */
1717
+ private async *streamFallbackResponse(params: {
1718
+ history: Event[]; // Use Event[] for buildFallbackPrompt
1719
+ context: TContext;
1720
+ session: SessionState<TData>;
1721
+ signal?: AbortSignal;
1722
+ }): AsyncGenerator<AgentResponseStreamChunk<TData>> {
1723
+ const { history, context, session, signal } = params;
1724
+
1725
+ const fallbackPrompt = await this.responseEngine.buildFallbackPrompt({
1726
+ history,
1727
+ agentOptions: this.agent.getAgentOptions(),
1728
+ terms: this.agent.getTerms(),
1729
+ guidelines: this.agent.getGuidelines(),
1730
+ context,
1731
+ session,
1732
+ });
1733
+
1734
+ const agentOptions = this.agent.getAgentOptions();
1735
+ const stream = agentOptions.provider.generateMessageStream({
1736
+ prompt: fallbackPrompt,
1737
+ history,
1738
+ context,
1739
+ signal,
1740
+ parameters: {
1741
+ jsonSchema: {
1742
+ type: "object",
1743
+ properties: { message: { type: "string" } },
1744
+ required: ["message"],
1745
+ additionalProperties: false,
1746
+ },
1747
+ schemaName: "fallback_stream_response",
1748
+ },
1749
+ });
1750
+
1751
+ for await (const chunk of stream) {
1752
+ // Update current session if we have one
1753
+ if (chunk.done) {
1754
+ await this.finalizeSession(session, context);
1755
+ }
1756
+
1757
+ yield {
1758
+ delta: chunk.delta,
1759
+ accumulated: chunk.accumulated,
1760
+ done: chunk.done,
1761
+ session,
1762
+ toolCalls: undefined,
1763
+ isRouteComplete: false,
1764
+ metadata: chunk.metadata,
1765
+ structured: chunk.structured,
1766
+ };
1767
+ }
1768
+ }
1769
+
1770
+ /**
1771
+ * Handle session persistence and finalization
1772
+ * @private
1773
+ */
1774
+ private async finalizeSession(session: SessionState<TData>, context: TContext): Promise<void> {
1775
+ // Auto-save session step to persistence if configured
1776
+ const persistenceManager = this.agent.getPersistenceManager();
1777
+ const agentOptions = this.agent.getAgentOptions();
1778
+ if (
1779
+ persistenceManager &&
1780
+ session.id &&
1781
+ (this.options?.enableAutoSave !== false && agentOptions.persistence?.autoSave !== false)
1782
+ ) {
1783
+ await persistenceManager.saveSessionState(session.id, session);
1784
+ logger.debug(`[ResponseModal] Auto-saved session step to persistence: ${session.id}`);
1785
+ }
1786
+
1787
+ // Execute finalize function
1788
+ await this.executeStepFinalize(session, context);
1789
+
1790
+ // Update current session if we have one
1791
+ const currentSession = this.agent.getCurrentSession();
1792
+ if (currentSession) {
1793
+ this.agent.setCurrentSession(session);
1794
+ }
1795
+ }
1796
+ // ============================================================================
1797
+ // UTILITY METHODS - Helper methods for tool management and other utilities
1798
+ // ============================================================================
1799
+
1800
+ /**
1801
+ * Find an available tool by name for the given route using ToolManager
1802
+ * Delegates to ToolManager for unified tool resolution
1803
+ * @private
1804
+ */
1805
+ private findAvailableTool(
1806
+ toolName: string,
1807
+ route?: Route<TContext, TData>
1808
+ ): Tool<TContext, TData> | undefined {
1809
+ // Use ToolManager for unified tool resolution
1810
+ const toolManager = this.getToolManager();
1811
+ if (toolManager) {
1812
+ return toolManager.find(toolName, undefined, undefined, route);
1813
+ }
1814
+
1815
+ // Fallback to legacy resolution if ToolManager not available
1816
+ logger.warn(`[ResponseModal] ToolManager not available, using legacy tool resolution for: ${toolName}`);
1817
+
1818
+ // Check route-level tools first (if route provided)
1819
+ if (route) {
1820
+ const routeTool = route
1821
+ .getTools()
1822
+ .find((tool: Tool<TContext, TData>) => tool.id === toolName || tool.name === toolName);
1823
+ if (routeTool) return routeTool;
1824
+ }
1825
+
1826
+ // Fall back to agent-level tools
1827
+ const agentTools = this.agent.getTools();
1828
+ return agentTools.find(
1829
+ (tool) => tool.id === toolName || tool.name === toolName
1830
+ );
1831
+ }
1832
+
1833
+ /**
1834
+ * Collect all available tools for the given route and step context using ToolManager
1835
+ * Delegates to ToolManager for unified tool resolution and deduplication
1836
+ * @private
1837
+ */
1838
+ private collectAvailableTools(
1839
+ route?: Route<TContext, TData>,
1840
+ step?: Step<TContext, TData>
1841
+ ): Array<{
1842
+ id: string;
1843
+ name: string;
1844
+ description?: string;
1845
+ parameters?: unknown;
1846
+ }> {
1847
+ // Use ToolManager for unified tool collection if available
1848
+ const toolManager = this.getToolManager();
1849
+ if (toolManager) {
1850
+ const availableTools = toolManager.getAvailable(undefined, step, route);
1851
+ return availableTools.map((tool) => ({
1852
+ id: tool.id,
1853
+ name: tool.name || tool.id,
1854
+ description: tool.description,
1855
+ parameters: tool.parameters,
1856
+ }));
1857
+ }
1858
+
1859
+ // Fallback to legacy collection logic if ToolManager not available
1860
+ logger.warn(`[ResponseModal] ToolManager not available, using legacy tool collection`);
1861
+
1862
+ const availableTools = new Map<string, Tool<TContext, TData>>();
1863
+
1864
+ // Add agent-level tools
1865
+ this.agent.getTools().forEach((tool) => {
1866
+ availableTools.set(tool.id, tool);
1867
+ });
1868
+
1869
+ // Add route-level tools (these take precedence)
1870
+ if (route) {
1871
+ route.getTools().forEach((tool: Tool<TContext, TData>) => {
1872
+ availableTools.set(tool.id, tool);
1873
+ });
1874
+ }
1875
+
1876
+ // Filter by step-level allowed tools if specified
1877
+ if (step?.tools) {
1878
+ const allowedToolIds = new Set<string>();
1879
+ const stepTools: Tool<TContext, TData>[] = [];
1880
+
1881
+ for (const toolRef of step.tools) {
1882
+ if (typeof toolRef === "string") {
1883
+ // Reference to registered tool
1884
+ allowedToolIds.add(toolRef);
1885
+ } else {
1886
+ // Inline tool definition
1887
+ if (toolRef.id) {
1888
+ allowedToolIds.add(toolRef.id);
1889
+ stepTools.push(toolRef);
1890
+ }
1891
+ }
1892
+ }
1893
+
1894
+ // If step specifies tools, only include those
1895
+ if (allowedToolIds.size > 0) {
1896
+ const filteredTools = new Map<string, Tool<TContext, TData>>();
1897
+ for (const toolId of Array.from(allowedToolIds)) {
1898
+ const tool = availableTools.get(toolId);
1899
+ if (tool) {
1900
+ filteredTools.set(toolId, tool);
1901
+ }
1902
+ }
1903
+ // Add inline tools
1904
+ stepTools.forEach((tool) => {
1905
+ if (tool.id) {
1906
+ filteredTools.set(tool.id, tool);
1907
+ }
1908
+ });
1909
+ availableTools.clear();
1910
+ filteredTools.forEach((tool, id) => availableTools.set(id, tool));
1911
+ }
1912
+ }
1913
+
1914
+ // Convert to the format expected by AI providers
1915
+ return Array.from(availableTools.values()).map((tool) => ({
1916
+ id: tool.id,
1917
+ name: tool.name || tool.id,
1918
+ description: tool.description,
1919
+ parameters: tool.parameters,
1920
+ }));
1921
+ }
1922
+
1923
+ /**
1924
+ * Execute a prepare or finalize function/tool
1925
+ * @private
1926
+ */
1927
+ private async executePrepareFinalize(
1928
+ prepareOrFinalize:
1929
+ | string
1930
+ | Tool<TContext, TData>
1931
+ | ((context: TContext, data?: Partial<TData>) => void | Promise<void>)
1932
+ | undefined,
1933
+ context: TContext,
1934
+ data?: Partial<TData>,
1935
+ route?: Route<TContext, TData>,
1936
+ step?: Step<TContext, TData>
1937
+ ): Promise<void> {
1938
+ if (!prepareOrFinalize) return;
1939
+
1940
+ if (typeof prepareOrFinalize === "function") {
1941
+ // It's a function - call it directly
1942
+ await prepareOrFinalize(context, data);
1943
+ } else {
1944
+ // It's a tool reference - find and execute the tool
1945
+ let tool: Tool<TContext, TData> | undefined;
1946
+
1947
+ if (typeof prepareOrFinalize === "string") {
1948
+ // Tool ID - use ToolManager for unified resolution
1949
+ const toolManager = this.getToolManager();
1950
+ if (toolManager) {
1951
+ tool = toolManager.find(prepareOrFinalize, undefined, step, route);
1952
+ } else {
1953
+ // Fallback to legacy resolution if ToolManager not available
1954
+ logger.warn(`[ResponseModal] ToolManager not available, using legacy tool resolution for prepare/finalize: ${prepareOrFinalize}`);
1955
+
1956
+ const availableTools = new Map<string, Tool<TContext, TData>>();
1957
+
1958
+ // Add agent-level tools
1959
+ this.agent.getTools().forEach((t) => {
1960
+ availableTools.set(t.id, t);
1961
+ });
1962
+
1963
+ // Add route-level tools
1964
+ if (route) {
1965
+ route.getTools().forEach((t: Tool<TContext, TData>) => {
1966
+ availableTools.set(t.id, t);
1967
+ });
1968
+ }
1969
+
1970
+ // Add step-level tools
1971
+ if (step?.tools) {
1972
+ for (const toolRef of step.tools) {
1973
+ if (typeof toolRef === "string") {
1974
+ // Keep as is
1975
+ } else if (typeof toolRef === 'object' && 'id' in toolRef && toolRef.id) {
1976
+ availableTools.set(toolRef.id, toolRef);
1977
+ }
1978
+ }
1979
+ }
1980
+
1981
+ tool = availableTools.get(prepareOrFinalize);
1982
+ }
1983
+ } else {
1984
+ // Tool object - use directly
1985
+ tool = prepareOrFinalize;
1986
+ }
1987
+
1988
+ if (tool) {
1989
+ // Use ToolManager for unified tool execution
1990
+ const toolManager = this.getToolManager();
1991
+ let result;
1992
+
1993
+ if (toolManager) {
1994
+ result = await toolManager.executeTool({
1995
+ tool,
1996
+ context,
1997
+ updateContext: this.agent.updateContext.bind(this.agent),
1998
+ updateData: this.agent.updateCollectedData.bind(this.agent),
1999
+ history: [], // Empty history for prepare/finalize
2000
+ data,
2001
+ });
2002
+ } else {
2003
+ // Fallback: execute tool directly if ToolManager not available
2004
+ throw new Error(`ToolManager not available for prepare/finalize tool execution: ${typeof prepareOrFinalize === "string" ? prepareOrFinalize : "inline tool"}`);
2005
+ }
2006
+
2007
+ if (!result.success) {
2008
+ logger.error(
2009
+ `[ResponseModal] Tool execution failed in prepare/finalize: ${result.error}`
2010
+ );
2011
+ throw new Error(`Tool execution failed: ${result.error}`);
2012
+ }
2013
+ } else {
2014
+ logger.warn(
2015
+ `[ResponseModal] Tool not found for prepare/finalize: ${typeof prepareOrFinalize === "string"
2016
+ ? prepareOrFinalize
2017
+ : "inline tool"
2018
+ }`
2019
+ );
2020
+ }
2021
+ }
2022
+ }
2023
+
2024
+ /**
2025
+ * Merge terms with route-specific taking precedence on conflicts
2026
+ * @private
2027
+ */
2028
+ private mergeTerms(
2029
+ agentTerms: Term<TContext, TData>[],
2030
+ routeTerms: Term<TContext, TData>[]
2031
+ ): Term<TContext, TData>[] {
2032
+ const merged = new Map<string, Term<TContext, TData>>();
2033
+
2034
+ // Add agent terms first
2035
+ agentTerms.forEach((term) => {
2036
+ const name =
2037
+ typeof term.name === "string" ? term.name : term.name.toString();
2038
+ merged.set(name, term);
2039
+ });
2040
+
2041
+ // Add route terms (these take precedence)
2042
+ routeTerms.forEach((term) => {
2043
+ const name =
2044
+ typeof term.name === "string" ? term.name : term.name.toString();
2045
+ merged.set(name, term);
2046
+ });
2047
+
2048
+ return Array.from(merged.values());
2049
+ }
2050
+ }