@falai/agent 0.8.1 → 0.9.0-alpha-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (665) hide show
  1. package/README.md +332 -147
  2. package/dist/{adapters → cjs/src/adapters}/MemoryAdapter.d.ts +4 -4
  3. package/dist/cjs/src/adapters/MemoryAdapter.d.ts.map +1 -0
  4. package/dist/cjs/{adapters → src/adapters}/MemoryAdapter.js +41 -21
  5. package/dist/cjs/src/adapters/MemoryAdapter.js.map +1 -0
  6. package/dist/{adapters → cjs/src/adapters}/MongoAdapter.d.ts +3 -3
  7. package/dist/cjs/src/adapters/MongoAdapter.d.ts.map +1 -0
  8. package/dist/cjs/{adapters → src/adapters}/MongoAdapter.js +2 -1
  9. package/dist/cjs/src/adapters/MongoAdapter.js.map +1 -0
  10. package/dist/cjs/{adapters → src/adapters}/OpenSearchAdapter.d.ts +3 -3
  11. package/dist/cjs/src/adapters/OpenSearchAdapter.d.ts.map +1 -0
  12. package/dist/cjs/{adapters → src/adapters}/OpenSearchAdapter.js +10 -13
  13. package/dist/cjs/src/adapters/OpenSearchAdapter.js.map +1 -0
  14. package/dist/cjs/{adapters → src/adapters}/PostgreSQLAdapter.d.ts +3 -3
  15. package/dist/cjs/src/adapters/PostgreSQLAdapter.d.ts.map +1 -0
  16. package/dist/cjs/{adapters → src/adapters}/PostgreSQLAdapter.js +1 -1
  17. package/dist/cjs/src/adapters/PostgreSQLAdapter.js.map +1 -0
  18. package/dist/cjs/{adapters → src/adapters}/PrismaAdapter.d.ts +3 -3
  19. package/dist/cjs/src/adapters/PrismaAdapter.d.ts.map +1 -0
  20. package/dist/cjs/{adapters → src/adapters}/PrismaAdapter.js +35 -5
  21. package/dist/cjs/src/adapters/PrismaAdapter.js.map +1 -0
  22. package/dist/cjs/{adapters → src/adapters}/RedisAdapter.d.ts +3 -3
  23. package/dist/cjs/src/adapters/RedisAdapter.d.ts.map +1 -0
  24. package/dist/cjs/{adapters → src/adapters}/RedisAdapter.js +3 -2
  25. package/dist/cjs/src/adapters/RedisAdapter.js.map +1 -0
  26. package/dist/{adapters → cjs/src/adapters}/SQLiteAdapter.d.ts +3 -3
  27. package/dist/cjs/src/adapters/SQLiteAdapter.d.ts.map +1 -0
  28. package/dist/cjs/{adapters → src/adapters}/SQLiteAdapter.js +2 -1
  29. package/dist/cjs/src/adapters/SQLiteAdapter.js.map +1 -0
  30. package/dist/cjs/src/adapters/index.d.ts.map +1 -0
  31. package/dist/cjs/src/adapters/index.js.map +1 -0
  32. package/dist/cjs/src/constants/index.d.ts.map +1 -0
  33. package/dist/cjs/src/constants/index.js.map +1 -0
  34. package/dist/cjs/src/core/Agent.d.ts +223 -0
  35. package/dist/cjs/src/core/Agent.d.ts.map +1 -0
  36. package/dist/cjs/src/core/Agent.js +1660 -0
  37. package/dist/cjs/src/core/Agent.js.map +1 -0
  38. package/dist/cjs/src/core/Events.d.ts +26 -0
  39. package/dist/cjs/src/core/Events.d.ts.map +1 -0
  40. package/dist/cjs/src/core/Events.js +144 -0
  41. package/dist/cjs/src/core/Events.js.map +1 -0
  42. package/dist/{core → cjs/src/core}/PersistenceManager.d.ts +21 -19
  43. package/dist/cjs/src/core/PersistenceManager.d.ts.map +1 -0
  44. package/dist/cjs/{core → src/core}/PersistenceManager.js +73 -20
  45. package/dist/cjs/src/core/PersistenceManager.js.map +1 -0
  46. package/dist/cjs/src/core/PromptComposer.d.ts +27 -0
  47. package/dist/cjs/src/core/PromptComposer.d.ts.map +1 -0
  48. package/dist/cjs/src/core/PromptComposer.js +157 -0
  49. package/dist/cjs/src/core/PromptComposer.js.map +1 -0
  50. package/dist/cjs/src/core/ResponseEngine.d.ts +32 -0
  51. package/dist/cjs/src/core/ResponseEngine.d.ts.map +1 -0
  52. package/dist/cjs/src/core/ResponseEngine.js +84 -0
  53. package/dist/cjs/src/core/ResponseEngine.js.map +1 -0
  54. package/dist/cjs/src/core/ResponsePipeline.d.ts +171 -0
  55. package/dist/cjs/src/core/ResponsePipeline.d.ts.map +1 -0
  56. package/dist/cjs/src/core/ResponsePipeline.js +514 -0
  57. package/dist/cjs/src/core/ResponsePipeline.js.map +1 -0
  58. package/dist/cjs/src/core/Route.d.ts +145 -0
  59. package/dist/cjs/src/core/Route.d.ts.map +1 -0
  60. package/dist/cjs/src/core/Route.js +343 -0
  61. package/dist/cjs/src/core/Route.js.map +1 -0
  62. package/dist/cjs/src/core/RoutingEngine.d.ts +129 -0
  63. package/dist/cjs/src/core/RoutingEngine.d.ts.map +1 -0
  64. package/dist/cjs/{core → src/core}/RoutingEngine.js +215 -117
  65. package/dist/cjs/src/core/RoutingEngine.js.map +1 -0
  66. package/dist/cjs/src/core/SessionManager.d.ts +86 -0
  67. package/dist/cjs/src/core/SessionManager.d.ts.map +1 -0
  68. package/dist/cjs/src/core/SessionManager.js +217 -0
  69. package/dist/cjs/src/core/SessionManager.js.map +1 -0
  70. package/dist/cjs/src/core/Step.d.ts +96 -0
  71. package/dist/cjs/src/core/Step.d.ts.map +1 -0
  72. package/dist/cjs/src/core/Step.js +206 -0
  73. package/dist/cjs/src/core/Step.js.map +1 -0
  74. package/dist/cjs/src/core/ToolExecutor.d.ts +45 -0
  75. package/dist/cjs/src/core/ToolExecutor.d.ts.map +1 -0
  76. package/dist/cjs/{core → src/core}/ToolExecutor.js +30 -19
  77. package/dist/cjs/src/core/ToolExecutor.js.map +1 -0
  78. package/dist/{index.d.ts → cjs/src/index.d.ts} +7 -15
  79. package/dist/cjs/src/index.d.ts.map +1 -0
  80. package/dist/cjs/{index.js → src/index.js} +21 -19
  81. package/dist/cjs/src/index.js.map +1 -0
  82. package/dist/cjs/{providers → src/providers}/AnthropicProvider.d.ts +1 -1
  83. package/dist/cjs/src/providers/AnthropicProvider.d.ts.map +1 -0
  84. package/dist/cjs/{providers → src/providers}/AnthropicProvider.js +54 -2
  85. package/dist/cjs/src/providers/AnthropicProvider.js.map +1 -0
  86. package/dist/{providers → cjs/src/providers}/GeminiProvider.d.ts +1 -1
  87. package/dist/cjs/src/providers/GeminiProvider.d.ts.map +1 -0
  88. package/dist/cjs/{providers → src/providers}/GeminiProvider.js +65 -0
  89. package/dist/cjs/src/providers/GeminiProvider.js.map +1 -0
  90. package/dist/cjs/{providers → src/providers}/OpenAIProvider.d.ts +1 -1
  91. package/dist/cjs/src/providers/OpenAIProvider.d.ts.map +1 -0
  92. package/dist/cjs/{providers → src/providers}/OpenAIProvider.js +70 -1
  93. package/dist/cjs/src/providers/OpenAIProvider.js.map +1 -0
  94. package/dist/{providers → cjs/src/providers}/OpenRouterProvider.d.ts +1 -1
  95. package/dist/cjs/src/providers/OpenRouterProvider.d.ts.map +1 -0
  96. package/dist/cjs/{providers → src/providers}/OpenRouterProvider.js +76 -0
  97. package/dist/cjs/src/providers/OpenRouterProvider.js.map +1 -0
  98. package/dist/cjs/src/providers/index.d.ts.map +1 -0
  99. package/dist/cjs/src/providers/index.js.map +1 -0
  100. package/dist/cjs/{types → src/types}/agent.d.ts +80 -41
  101. package/dist/cjs/src/types/agent.d.ts.map +1 -0
  102. package/dist/cjs/src/types/agent.js.map +1 -0
  103. package/dist/cjs/{types → src/types}/ai.d.ts +7 -0
  104. package/dist/cjs/src/types/ai.d.ts.map +1 -0
  105. package/dist/cjs/src/types/ai.js.map +1 -0
  106. package/dist/{types → cjs/src/types}/history.d.ts +76 -18
  107. package/dist/cjs/src/types/history.d.ts.map +1 -0
  108. package/dist/cjs/src/types/history.js +33 -0
  109. package/dist/cjs/src/types/history.js.map +1 -0
  110. package/dist/cjs/src/types/index.d.ts +20 -0
  111. package/dist/cjs/src/types/index.d.ts.map +1 -0
  112. package/dist/cjs/src/types/index.js +30 -0
  113. package/dist/cjs/src/types/index.js.map +1 -0
  114. package/dist/{types → cjs/src/types}/persistence.d.ts +38 -23
  115. package/dist/cjs/src/types/persistence.d.ts.map +1 -0
  116. package/dist/cjs/src/types/persistence.js.map +1 -0
  117. package/dist/cjs/src/types/route.d.ts +235 -0
  118. package/dist/cjs/src/types/route.d.ts.map +1 -0
  119. package/dist/cjs/{types → src/types}/route.js.map +1 -1
  120. package/dist/cjs/src/types/routing.d.ts.map +1 -0
  121. package/dist/{types → cjs/src/types}/routing.js.map +1 -1
  122. package/dist/cjs/src/types/schema.d.ts.map +1 -0
  123. package/dist/{types → cjs/src/types}/schema.js.map +1 -1
  124. package/dist/cjs/src/types/session.d.ts +65 -0
  125. package/dist/cjs/src/types/session.d.ts.map +1 -0
  126. package/dist/cjs/src/types/session.js +6 -0
  127. package/dist/cjs/src/types/session.js.map +1 -0
  128. package/dist/cjs/src/types/template.d.ts +30 -0
  129. package/dist/cjs/src/types/template.d.ts.map +1 -0
  130. package/dist/cjs/src/types/template.js +3 -0
  131. package/dist/cjs/src/types/template.js.map +1 -0
  132. package/dist/{types → cjs/src/types}/tool.d.ts +17 -13
  133. package/dist/cjs/src/types/tool.d.ts.map +1 -0
  134. package/dist/cjs/{types → src/types}/tool.js.map +1 -1
  135. package/dist/cjs/src/utils/clone.d.ts +8 -0
  136. package/dist/cjs/src/utils/clone.d.ts.map +1 -0
  137. package/dist/cjs/src/utils/clone.js +36 -0
  138. package/dist/cjs/src/utils/clone.js.map +1 -0
  139. package/dist/{utils → cjs/src/utils}/event.d.ts +1 -1
  140. package/dist/cjs/src/utils/event.d.ts.map +1 -0
  141. package/dist/cjs/{utils → src/utils}/event.js +2 -2
  142. package/dist/cjs/src/utils/event.js.map +1 -0
  143. package/dist/cjs/src/utils/history.d.ts +31 -0
  144. package/dist/cjs/src/utils/history.d.ts.map +1 -0
  145. package/dist/cjs/src/utils/history.js +128 -0
  146. package/dist/cjs/src/utils/history.js.map +1 -0
  147. package/dist/cjs/src/utils/id.d.ts.map +1 -0
  148. package/dist/cjs/src/utils/id.js.map +1 -0
  149. package/dist/cjs/src/utils/index.d.ts +13 -0
  150. package/dist/cjs/src/utils/index.d.ts.map +1 -0
  151. package/dist/cjs/src/utils/index.js +49 -0
  152. package/dist/cjs/src/utils/index.js.map +1 -0
  153. package/dist/cjs/src/utils/logger.d.ts.map +1 -0
  154. package/dist/cjs/src/utils/logger.js.map +1 -0
  155. package/dist/cjs/src/utils/retry.d.ts.map +1 -0
  156. package/dist/cjs/src/utils/retry.js.map +1 -0
  157. package/dist/cjs/src/utils/session.d.ts +51 -0
  158. package/dist/cjs/src/utils/session.d.ts.map +1 -0
  159. package/dist/cjs/{types → src/utils}/session.js +35 -32
  160. package/dist/cjs/src/utils/session.js.map +1 -0
  161. package/dist/cjs/src/utils/template.d.ts +107 -0
  162. package/dist/cjs/src/utils/template.d.ts.map +1 -0
  163. package/dist/cjs/src/utils/template.js +283 -0
  164. package/dist/cjs/src/utils/template.js.map +1 -0
  165. package/dist/{cjs → src}/adapters/MemoryAdapter.d.ts +4 -4
  166. package/dist/src/adapters/MemoryAdapter.d.ts.map +1 -0
  167. package/dist/{adapters → src/adapters}/MemoryAdapter.js +41 -21
  168. package/dist/src/adapters/MemoryAdapter.js.map +1 -0
  169. package/dist/{cjs → src}/adapters/MongoAdapter.d.ts +3 -3
  170. package/dist/src/adapters/MongoAdapter.d.ts.map +1 -0
  171. package/dist/{adapters → src/adapters}/MongoAdapter.js +2 -1
  172. package/dist/src/adapters/MongoAdapter.js.map +1 -0
  173. package/dist/{adapters → src/adapters}/OpenSearchAdapter.d.ts +3 -3
  174. package/dist/src/adapters/OpenSearchAdapter.d.ts.map +1 -0
  175. package/dist/{adapters → src/adapters}/OpenSearchAdapter.js +10 -13
  176. package/dist/src/adapters/OpenSearchAdapter.js.map +1 -0
  177. package/dist/{adapters → src/adapters}/PostgreSQLAdapter.d.ts +3 -3
  178. package/dist/src/adapters/PostgreSQLAdapter.d.ts.map +1 -0
  179. package/dist/{adapters → src/adapters}/PostgreSQLAdapter.js +1 -1
  180. package/dist/src/adapters/PostgreSQLAdapter.js.map +1 -0
  181. package/dist/{adapters → src/adapters}/PrismaAdapter.d.ts +3 -3
  182. package/dist/src/adapters/PrismaAdapter.d.ts.map +1 -0
  183. package/dist/{adapters → src/adapters}/PrismaAdapter.js +35 -5
  184. package/dist/src/adapters/PrismaAdapter.js.map +1 -0
  185. package/dist/{adapters → src/adapters}/RedisAdapter.d.ts +3 -3
  186. package/dist/src/adapters/RedisAdapter.d.ts.map +1 -0
  187. package/dist/{adapters → src/adapters}/RedisAdapter.js +3 -2
  188. package/dist/src/adapters/RedisAdapter.js.map +1 -0
  189. package/dist/{cjs → src}/adapters/SQLiteAdapter.d.ts +3 -3
  190. package/dist/src/adapters/SQLiteAdapter.d.ts.map +1 -0
  191. package/dist/{adapters → src/adapters}/SQLiteAdapter.js +2 -1
  192. package/dist/src/adapters/SQLiteAdapter.js.map +1 -0
  193. package/dist/src/adapters/index.js.map +1 -0
  194. package/dist/src/constants/index.js.map +1 -0
  195. package/dist/src/core/Agent.d.ts +223 -0
  196. package/dist/src/core/Agent.d.ts.map +1 -0
  197. package/dist/src/core/Agent.js +1656 -0
  198. package/dist/src/core/Agent.js.map +1 -0
  199. package/dist/src/core/Events.d.ts +26 -0
  200. package/dist/src/core/Events.d.ts.map +1 -0
  201. package/dist/src/core/Events.js +137 -0
  202. package/dist/src/core/Events.js.map +1 -0
  203. package/dist/{cjs → src}/core/PersistenceManager.d.ts +21 -19
  204. package/dist/src/core/PersistenceManager.d.ts.map +1 -0
  205. package/dist/{core → src/core}/PersistenceManager.js +71 -18
  206. package/dist/src/core/PersistenceManager.js.map +1 -0
  207. package/dist/src/core/PromptComposer.d.ts +27 -0
  208. package/dist/src/core/PromptComposer.d.ts.map +1 -0
  209. package/dist/src/core/PromptComposer.js +153 -0
  210. package/dist/src/core/PromptComposer.js.map +1 -0
  211. package/dist/src/core/ResponseEngine.d.ts +32 -0
  212. package/dist/src/core/ResponseEngine.d.ts.map +1 -0
  213. package/dist/src/core/ResponseEngine.js +80 -0
  214. package/dist/src/core/ResponseEngine.js.map +1 -0
  215. package/dist/src/core/ResponsePipeline.d.ts +171 -0
  216. package/dist/src/core/ResponsePipeline.d.ts.map +1 -0
  217. package/dist/src/core/ResponsePipeline.js +510 -0
  218. package/dist/src/core/ResponsePipeline.js.map +1 -0
  219. package/dist/src/core/Route.d.ts +145 -0
  220. package/dist/src/core/Route.d.ts.map +1 -0
  221. package/dist/src/core/Route.js +339 -0
  222. package/dist/src/core/Route.js.map +1 -0
  223. package/dist/src/core/RoutingEngine.d.ts +129 -0
  224. package/dist/src/core/RoutingEngine.d.ts.map +1 -0
  225. package/dist/{core → src/core}/RoutingEngine.js +211 -113
  226. package/dist/src/core/RoutingEngine.js.map +1 -0
  227. package/dist/src/core/SessionManager.d.ts +86 -0
  228. package/dist/src/core/SessionManager.d.ts.map +1 -0
  229. package/dist/src/core/SessionManager.js +213 -0
  230. package/dist/src/core/SessionManager.js.map +1 -0
  231. package/dist/src/core/Step.d.ts +96 -0
  232. package/dist/src/core/Step.d.ts.map +1 -0
  233. package/dist/src/core/Step.js +202 -0
  234. package/dist/src/core/Step.js.map +1 -0
  235. package/dist/src/core/ToolExecutor.d.ts +45 -0
  236. package/dist/src/core/ToolExecutor.d.ts.map +1 -0
  237. package/dist/src/core/ToolExecutor.js +80 -0
  238. package/dist/src/core/ToolExecutor.js.map +1 -0
  239. package/dist/{cjs → src}/index.d.ts +7 -15
  240. package/dist/src/index.d.ts.map +1 -0
  241. package/dist/{index.js → src/index.js} +6 -7
  242. package/dist/src/index.js.map +1 -0
  243. package/dist/{providers → src/providers}/AnthropicProvider.d.ts +1 -1
  244. package/dist/src/providers/AnthropicProvider.d.ts.map +1 -0
  245. package/dist/{providers → src/providers}/AnthropicProvider.js +54 -2
  246. package/dist/src/providers/AnthropicProvider.js.map +1 -0
  247. package/dist/{cjs → src}/providers/GeminiProvider.d.ts +1 -1
  248. package/dist/{cjs → src}/providers/GeminiProvider.d.ts.map +1 -1
  249. package/dist/{providers → src/providers}/GeminiProvider.js +65 -0
  250. package/dist/src/providers/GeminiProvider.js.map +1 -0
  251. package/dist/{providers → src/providers}/OpenAIProvider.d.ts +1 -1
  252. package/dist/{cjs → src}/providers/OpenAIProvider.d.ts.map +1 -1
  253. package/dist/{providers → src/providers}/OpenAIProvider.js +70 -1
  254. package/dist/src/providers/OpenAIProvider.js.map +1 -0
  255. package/dist/{cjs → src}/providers/OpenRouterProvider.d.ts +1 -1
  256. package/dist/{cjs → src}/providers/OpenRouterProvider.d.ts.map +1 -1
  257. package/dist/{providers → src/providers}/OpenRouterProvider.js +76 -0
  258. package/dist/src/providers/OpenRouterProvider.js.map +1 -0
  259. package/dist/src/providers/index.js.map +1 -0
  260. package/dist/{types → src/types}/agent.d.ts +80 -41
  261. package/dist/src/types/agent.d.ts.map +1 -0
  262. package/dist/src/types/agent.js.map +1 -0
  263. package/dist/{types → src/types}/ai.d.ts +7 -0
  264. package/dist/src/types/ai.d.ts.map +1 -0
  265. package/dist/{cjs → src}/types/ai.js.map +1 -1
  266. package/dist/{cjs → src}/types/history.d.ts +76 -18
  267. package/dist/src/types/history.d.ts.map +1 -0
  268. package/dist/src/types/history.js +30 -0
  269. package/dist/src/types/history.js.map +1 -0
  270. package/dist/src/types/index.d.ts +20 -0
  271. package/dist/src/types/index.d.ts.map +1 -0
  272. package/dist/src/types/index.js +10 -0
  273. package/dist/src/types/index.js.map +1 -0
  274. package/dist/{cjs → src}/types/persistence.d.ts +38 -23
  275. package/dist/src/types/persistence.d.ts.map +1 -0
  276. package/dist/src/types/persistence.js.map +1 -0
  277. package/dist/src/types/route.d.ts +235 -0
  278. package/dist/src/types/route.d.ts.map +1 -0
  279. package/dist/{types → src/types}/route.js.map +1 -1
  280. package/dist/src/types/session.d.ts +65 -0
  281. package/dist/src/types/session.d.ts.map +1 -0
  282. package/dist/src/types/session.js +5 -0
  283. package/dist/src/types/session.js.map +1 -0
  284. package/dist/src/types/template.d.ts +30 -0
  285. package/dist/src/types/template.d.ts.map +1 -0
  286. package/dist/src/types/template.js +2 -0
  287. package/dist/src/types/template.js.map +1 -0
  288. package/dist/{cjs → src}/types/tool.d.ts +17 -13
  289. package/dist/src/types/tool.d.ts.map +1 -0
  290. package/dist/{types → src/types}/tool.js.map +1 -1
  291. package/dist/src/utils/clone.d.ts +8 -0
  292. package/dist/src/utils/clone.d.ts.map +1 -0
  293. package/dist/src/utils/clone.js +33 -0
  294. package/dist/src/utils/clone.js.map +1 -0
  295. package/dist/{cjs → src}/utils/event.d.ts +1 -1
  296. package/dist/{cjs → src}/utils/event.d.ts.map +1 -1
  297. package/dist/{utils → src/utils}/event.js +1 -1
  298. package/dist/src/utils/event.js.map +1 -0
  299. package/dist/src/utils/history.d.ts +31 -0
  300. package/dist/src/utils/history.d.ts.map +1 -0
  301. package/dist/src/utils/history.js +121 -0
  302. package/dist/src/utils/history.js.map +1 -0
  303. package/dist/src/utils/id.js.map +1 -0
  304. package/dist/src/utils/index.d.ts +13 -0
  305. package/dist/src/utils/index.d.ts.map +1 -0
  306. package/dist/src/utils/index.js +19 -0
  307. package/dist/src/utils/index.js.map +1 -0
  308. package/dist/src/utils/logger.js.map +1 -0
  309. package/dist/src/utils/retry.js.map +1 -0
  310. package/dist/src/utils/session.d.ts +51 -0
  311. package/dist/src/utils/session.d.ts.map +1 -0
  312. package/dist/{types → src/utils}/session.js +33 -32
  313. package/dist/src/utils/session.js.map +1 -0
  314. package/dist/src/utils/template.d.ts +107 -0
  315. package/dist/src/utils/template.d.ts.map +1 -0
  316. package/dist/src/utils/template.js +276 -0
  317. package/dist/src/utils/template.js.map +1 -0
  318. package/docs/README.md +174 -68
  319. package/docs/{API_REFERENCE.md → api/README.md} +925 -255
  320. package/docs/api/overview.md +952 -0
  321. package/docs/core/agent/README.md +787 -0
  322. package/docs/{CONTEXT_MANAGEMENT.md → core/agent/context-management.md} +175 -102
  323. package/docs/{ARCHITECTURE.md → core/agent/session-management.md} +117 -69
  324. package/docs/core/ai-integration/prompt-composition.md +220 -0
  325. package/docs/core/ai-integration/providers.md +515 -0
  326. package/docs/core/ai-integration/response-processing.md +176 -0
  327. package/docs/core/conversation-flows/data-collection.md +623 -0
  328. package/docs/core/conversation-flows/route-dsl.md +502 -0
  329. package/docs/core/conversation-flows/routes.md +117 -0
  330. package/docs/core/conversation-flows/step-transitions.md +595 -0
  331. package/docs/core/conversation-flows/steps.md +154 -0
  332. package/docs/{ADAPTERS.md → core/persistence/adapters.md} +1 -1
  333. package/docs/core/persistence/session-storage.md +644 -0
  334. package/docs/core/routing/intelligent-routing.md +348 -0
  335. package/docs/core/tools/tool-definition.md +346 -0
  336. package/docs/core/tools/tool-execution.md +815 -0
  337. package/docs/core/tools/tool-scoping.md +628 -0
  338. package/docs/guides/getting-started/README.md +406 -0
  339. package/examples/{company-qna-agent.ts → advanced-patterns/knowledge-based-agent.ts} +139 -95
  340. package/examples/{persistent-onboarding.ts → advanced-patterns/persistent-onboarding.ts} +244 -137
  341. package/examples/{rules-prohibitions.ts → advanced-patterns/route-lifecycle-hooks.ts} +130 -84
  342. package/examples/{streaming-agent.ts → advanced-patterns/streaming-responses.ts} +116 -90
  343. package/examples/ai-providers/anthropic-integration.ts +384 -0
  344. package/examples/{openai-agent.ts → ai-providers/openai-integration.ts} +57 -63
  345. package/examples/conversation-flows/completion-transitions.ts +277 -0
  346. package/examples/core-concepts/basic-agent.ts +443 -0
  347. package/examples/core-concepts/schema-driven-extraction.ts +305 -0
  348. package/examples/core-concepts/session-management.ts +406 -0
  349. package/examples/integrations/database-integration.ts +630 -0
  350. package/examples/integrations/healthcare-integration.ts +609 -0
  351. package/examples/{opensearch-persistence.ts → integrations/search-integration.ts} +199 -171
  352. package/examples/integrations/server-session-management.ts +307 -0
  353. package/examples/persistence/custom-adapter.ts +529 -0
  354. package/examples/{prisma-persistence.ts → persistence/database-persistence.ts} +215 -272
  355. package/examples/persistence/memory-sessions.ts +495 -0
  356. package/examples/{prisma-schema.example.prisma → persistence/prisma-schema.example.prisma} +1 -1
  357. package/examples/persistence/redis-persistence.ts +490 -0
  358. package/examples/tools/basic-tools.ts +561 -0
  359. package/examples/{extracted-data-modification.ts → tools/data-enrichment-tools.ts} +128 -117
  360. package/package.json +14 -10
  361. package/src/adapters/MemoryAdapter.ts +74 -46
  362. package/src/adapters/MongoAdapter.ts +33 -24
  363. package/src/adapters/OpenSearchAdapter.ts +41 -37
  364. package/src/adapters/PostgreSQLAdapter.ts +35 -24
  365. package/src/adapters/PrismaAdapter.ts +69 -27
  366. package/src/adapters/RedisAdapter.ts +38 -26
  367. package/src/adapters/SQLiteAdapter.ts +32 -22
  368. package/src/core/Agent.ts +1431 -526
  369. package/src/core/Events.ts +100 -112
  370. package/src/core/PersistenceManager.ts +103 -49
  371. package/src/core/PromptComposer.ts +158 -85
  372. package/src/core/ResponseEngine.ts +128 -46
  373. package/src/core/ResponsePipeline.ts +830 -0
  374. package/src/core/Route.ts +222 -53
  375. package/src/core/RoutingEngine.ts +345 -229
  376. package/src/core/SessionManager.ts +265 -0
  377. package/src/core/Step.ts +157 -67
  378. package/src/core/ToolExecutor.ts +52 -43
  379. package/src/index.ts +31 -37
  380. package/src/providers/AnthropicProvider.ts +71 -5
  381. package/src/providers/GeminiProvider.ts +83 -2
  382. package/src/providers/OpenAIProvider.ts +95 -3
  383. package/src/providers/OpenRouterProvider.ts +102 -2
  384. package/src/types/agent.ts +81 -46
  385. package/src/types/ai.ts +7 -0
  386. package/src/types/history.ts +91 -18
  387. package/src/types/index.ts +45 -7
  388. package/src/types/persistence.ts +45 -28
  389. package/src/types/route.ts +122 -57
  390. package/src/types/session.ts +20 -220
  391. package/src/types/template.ts +36 -0
  392. package/src/types/tool.ts +23 -19
  393. package/src/utils/clone.ts +36 -0
  394. package/src/utils/event.ts +1 -1
  395. package/src/utils/history.ts +143 -0
  396. package/src/utils/index.ts +53 -0
  397. package/src/utils/session.ts +204 -0
  398. package/src/utils/template.ts +335 -0
  399. package/dist/adapters/MemoryAdapter.d.ts.map +0 -1
  400. package/dist/adapters/MemoryAdapter.js.map +0 -1
  401. package/dist/adapters/MongoAdapter.d.ts.map +0 -1
  402. package/dist/adapters/MongoAdapter.js.map +0 -1
  403. package/dist/adapters/OpenSearchAdapter.d.ts.map +0 -1
  404. package/dist/adapters/OpenSearchAdapter.js.map +0 -1
  405. package/dist/adapters/PostgreSQLAdapter.d.ts.map +0 -1
  406. package/dist/adapters/PostgreSQLAdapter.js.map +0 -1
  407. package/dist/adapters/PrismaAdapter.d.ts.map +0 -1
  408. package/dist/adapters/PrismaAdapter.js.map +0 -1
  409. package/dist/adapters/RedisAdapter.d.ts.map +0 -1
  410. package/dist/adapters/RedisAdapter.js.map +0 -1
  411. package/dist/adapters/SQLiteAdapter.d.ts.map +0 -1
  412. package/dist/adapters/SQLiteAdapter.js.map +0 -1
  413. package/dist/adapters/index.d.ts.map +0 -1
  414. package/dist/adapters/index.js.map +0 -1
  415. package/dist/cjs/adapters/MemoryAdapter.d.ts.map +0 -1
  416. package/dist/cjs/adapters/MemoryAdapter.js.map +0 -1
  417. package/dist/cjs/adapters/MongoAdapter.d.ts.map +0 -1
  418. package/dist/cjs/adapters/MongoAdapter.js.map +0 -1
  419. package/dist/cjs/adapters/OpenSearchAdapter.d.ts.map +0 -1
  420. package/dist/cjs/adapters/OpenSearchAdapter.js.map +0 -1
  421. package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +0 -1
  422. package/dist/cjs/adapters/PostgreSQLAdapter.js.map +0 -1
  423. package/dist/cjs/adapters/PrismaAdapter.d.ts.map +0 -1
  424. package/dist/cjs/adapters/PrismaAdapter.js.map +0 -1
  425. package/dist/cjs/adapters/RedisAdapter.d.ts.map +0 -1
  426. package/dist/cjs/adapters/RedisAdapter.js.map +0 -1
  427. package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +0 -1
  428. package/dist/cjs/adapters/SQLiteAdapter.js.map +0 -1
  429. package/dist/cjs/adapters/index.js.map +0 -1
  430. package/dist/cjs/constants/index.js.map +0 -1
  431. package/dist/cjs/core/Agent.d.ts +0 -197
  432. package/dist/cjs/core/Agent.d.ts.map +0 -1
  433. package/dist/cjs/core/Agent.js +0 -966
  434. package/dist/cjs/core/Agent.js.map +0 -1
  435. package/dist/cjs/core/DomainRegistry.d.ts +0 -36
  436. package/dist/cjs/core/DomainRegistry.d.ts.map +0 -1
  437. package/dist/cjs/core/DomainRegistry.js +0 -72
  438. package/dist/cjs/core/DomainRegistry.js.map +0 -1
  439. package/dist/cjs/core/Events.d.ts +0 -41
  440. package/dist/cjs/core/Events.d.ts.map +0 -1
  441. package/dist/cjs/core/Events.js +0 -99
  442. package/dist/cjs/core/Events.js.map +0 -1
  443. package/dist/cjs/core/PersistenceManager.d.ts.map +0 -1
  444. package/dist/cjs/core/PersistenceManager.js.map +0 -1
  445. package/dist/cjs/core/PromptComposer.d.ts +0 -24
  446. package/dist/cjs/core/PromptComposer.d.ts.map +0 -1
  447. package/dist/cjs/core/PromptComposer.js +0 -127
  448. package/dist/cjs/core/PromptComposer.js.map +0 -1
  449. package/dist/cjs/core/ResponseEngine.d.ts +0 -14
  450. package/dist/cjs/core/ResponseEngine.d.ts.map +0 -1
  451. package/dist/cjs/core/ResponseEngine.js +0 -56
  452. package/dist/cjs/core/ResponseEngine.js.map +0 -1
  453. package/dist/cjs/core/Route.d.ts +0 -90
  454. package/dist/cjs/core/Route.d.ts.map +0 -1
  455. package/dist/cjs/core/Route.js +0 -203
  456. package/dist/cjs/core/Route.js.map +0 -1
  457. package/dist/cjs/core/RoutingEngine.d.ts +0 -109
  458. package/dist/cjs/core/RoutingEngine.d.ts.map +0 -1
  459. package/dist/cjs/core/RoutingEngine.js.map +0 -1
  460. package/dist/cjs/core/Step.d.ts +0 -72
  461. package/dist/cjs/core/Step.d.ts.map +0 -1
  462. package/dist/cjs/core/Step.js +0 -150
  463. package/dist/cjs/core/Step.js.map +0 -1
  464. package/dist/cjs/core/Tool.d.ts +0 -39
  465. package/dist/cjs/core/Tool.d.ts.map +0 -1
  466. package/dist/cjs/core/Tool.js +0 -34
  467. package/dist/cjs/core/Tool.js.map +0 -1
  468. package/dist/cjs/core/ToolExecutor.d.ts +0 -29
  469. package/dist/cjs/core/ToolExecutor.d.ts.map +0 -1
  470. package/dist/cjs/core/ToolExecutor.js.map +0 -1
  471. package/dist/cjs/core/Transition.d.ts +0 -32
  472. package/dist/cjs/core/Transition.d.ts.map +0 -1
  473. package/dist/cjs/core/Transition.js +0 -89
  474. package/dist/cjs/core/Transition.js.map +0 -1
  475. package/dist/cjs/index.d.ts.map +0 -1
  476. package/dist/cjs/index.js.map +0 -1
  477. package/dist/cjs/providers/AnthropicProvider.d.ts.map +0 -1
  478. package/dist/cjs/providers/AnthropicProvider.js.map +0 -1
  479. package/dist/cjs/providers/GeminiProvider.js.map +0 -1
  480. package/dist/cjs/providers/OpenAIProvider.js.map +0 -1
  481. package/dist/cjs/providers/OpenRouterProvider.js.map +0 -1
  482. package/dist/cjs/providers/index.js.map +0 -1
  483. package/dist/cjs/types/agent.d.ts.map +0 -1
  484. package/dist/cjs/types/agent.js.map +0 -1
  485. package/dist/cjs/types/ai.d.ts.map +0 -1
  486. package/dist/cjs/types/history.d.ts.map +0 -1
  487. package/dist/cjs/types/history.js +0 -37
  488. package/dist/cjs/types/history.js.map +0 -1
  489. package/dist/cjs/types/index.d.ts +0 -12
  490. package/dist/cjs/types/index.d.ts.map +0 -1
  491. package/dist/cjs/types/index.js +0 -12
  492. package/dist/cjs/types/index.js.map +0 -1
  493. package/dist/cjs/types/persistence.d.ts.map +0 -1
  494. package/dist/cjs/types/persistence.js.map +0 -1
  495. package/dist/cjs/types/route.d.ts +0 -175
  496. package/dist/cjs/types/route.d.ts.map +0 -1
  497. package/dist/cjs/types/session.d.ts +0 -104
  498. package/dist/cjs/types/session.d.ts.map +0 -1
  499. package/dist/cjs/types/session.js.map +0 -1
  500. package/dist/cjs/types/tool.d.ts.map +0 -1
  501. package/dist/cjs/utils/event.js.map +0 -1
  502. package/dist/cjs/utils/id.js.map +0 -1
  503. package/dist/cjs/utils/logger.js.map +0 -1
  504. package/dist/cjs/utils/retry.js.map +0 -1
  505. package/dist/constants/index.d.ts.map +0 -1
  506. package/dist/constants/index.js.map +0 -1
  507. package/dist/core/Agent.d.ts +0 -197
  508. package/dist/core/Agent.d.ts.map +0 -1
  509. package/dist/core/Agent.js +0 -962
  510. package/dist/core/Agent.js.map +0 -1
  511. package/dist/core/DomainRegistry.d.ts +0 -36
  512. package/dist/core/DomainRegistry.d.ts.map +0 -1
  513. package/dist/core/DomainRegistry.js +0 -68
  514. package/dist/core/DomainRegistry.js.map +0 -1
  515. package/dist/core/Events.d.ts +0 -41
  516. package/dist/core/Events.d.ts.map +0 -1
  517. package/dist/core/Events.js +0 -94
  518. package/dist/core/Events.js.map +0 -1
  519. package/dist/core/PersistenceManager.d.ts.map +0 -1
  520. package/dist/core/PersistenceManager.js.map +0 -1
  521. package/dist/core/PromptComposer.d.ts +0 -24
  522. package/dist/core/PromptComposer.d.ts.map +0 -1
  523. package/dist/core/PromptComposer.js +0 -123
  524. package/dist/core/PromptComposer.js.map +0 -1
  525. package/dist/core/ResponseEngine.d.ts +0 -14
  526. package/dist/core/ResponseEngine.d.ts.map +0 -1
  527. package/dist/core/ResponseEngine.js +0 -52
  528. package/dist/core/ResponseEngine.js.map +0 -1
  529. package/dist/core/Route.d.ts +0 -90
  530. package/dist/core/Route.d.ts.map +0 -1
  531. package/dist/core/Route.js +0 -199
  532. package/dist/core/Route.js.map +0 -1
  533. package/dist/core/RoutingEngine.d.ts +0 -109
  534. package/dist/core/RoutingEngine.d.ts.map +0 -1
  535. package/dist/core/RoutingEngine.js.map +0 -1
  536. package/dist/core/Step.d.ts +0 -72
  537. package/dist/core/Step.d.ts.map +0 -1
  538. package/dist/core/Step.js +0 -146
  539. package/dist/core/Step.js.map +0 -1
  540. package/dist/core/Tool.d.ts +0 -39
  541. package/dist/core/Tool.d.ts.map +0 -1
  542. package/dist/core/Tool.js +0 -31
  543. package/dist/core/Tool.js.map +0 -1
  544. package/dist/core/ToolExecutor.d.ts +0 -29
  545. package/dist/core/ToolExecutor.d.ts.map +0 -1
  546. package/dist/core/ToolExecutor.js +0 -69
  547. package/dist/core/ToolExecutor.js.map +0 -1
  548. package/dist/core/Transition.d.ts +0 -32
  549. package/dist/core/Transition.d.ts.map +0 -1
  550. package/dist/core/Transition.js +0 -85
  551. package/dist/core/Transition.js.map +0 -1
  552. package/dist/index.d.ts.map +0 -1
  553. package/dist/index.js.map +0 -1
  554. package/dist/providers/AnthropicProvider.d.ts.map +0 -1
  555. package/dist/providers/AnthropicProvider.js.map +0 -1
  556. package/dist/providers/GeminiProvider.d.ts.map +0 -1
  557. package/dist/providers/GeminiProvider.js.map +0 -1
  558. package/dist/providers/OpenAIProvider.d.ts.map +0 -1
  559. package/dist/providers/OpenAIProvider.js.map +0 -1
  560. package/dist/providers/OpenRouterProvider.d.ts.map +0 -1
  561. package/dist/providers/OpenRouterProvider.js.map +0 -1
  562. package/dist/providers/index.d.ts.map +0 -1
  563. package/dist/providers/index.js.map +0 -1
  564. package/dist/types/agent.d.ts.map +0 -1
  565. package/dist/types/agent.js.map +0 -1
  566. package/dist/types/ai.d.ts.map +0 -1
  567. package/dist/types/ai.js.map +0 -1
  568. package/dist/types/history.d.ts.map +0 -1
  569. package/dist/types/history.js +0 -34
  570. package/dist/types/history.js.map +0 -1
  571. package/dist/types/index.d.ts +0 -12
  572. package/dist/types/index.d.ts.map +0 -1
  573. package/dist/types/index.js +0 -6
  574. package/dist/types/index.js.map +0 -1
  575. package/dist/types/persistence.d.ts.map +0 -1
  576. package/dist/types/persistence.js.map +0 -1
  577. package/dist/types/route.d.ts +0 -175
  578. package/dist/types/route.d.ts.map +0 -1
  579. package/dist/types/routing.d.ts.map +0 -1
  580. package/dist/types/schema.d.ts.map +0 -1
  581. package/dist/types/session.d.ts +0 -104
  582. package/dist/types/session.d.ts.map +0 -1
  583. package/dist/types/session.js.map +0 -1
  584. package/dist/types/tool.d.ts.map +0 -1
  585. package/dist/utils/event.d.ts.map +0 -1
  586. package/dist/utils/event.js.map +0 -1
  587. package/dist/utils/id.d.ts.map +0 -1
  588. package/dist/utils/id.js.map +0 -1
  589. package/dist/utils/logger.d.ts.map +0 -1
  590. package/dist/utils/logger.js.map +0 -1
  591. package/dist/utils/retry.d.ts.map +0 -1
  592. package/dist/utils/retry.js.map +0 -1
  593. package/docs/AGENT.md +0 -535
  594. package/docs/DOCS.md +0 -263
  595. package/docs/DOMAINS.md +0 -735
  596. package/docs/EXAMPLES.md +0 -467
  597. package/docs/GETTING_STARTED.md +0 -424
  598. package/docs/PERSISTENCE.md +0 -815
  599. package/docs/PROVIDERS.md +0 -612
  600. package/docs/ROUTES.md +0 -1085
  601. package/docs/STEPS.md +0 -883
  602. package/examples/business-onboarding.ts +0 -791
  603. package/examples/custom-database-persistence.ts +0 -574
  604. package/examples/declarative-agent.ts +0 -401
  605. package/examples/domain-scoping.ts +0 -366
  606. package/examples/healthcare-agent.ts +0 -511
  607. package/examples/redis-persistence.ts +0 -525
  608. package/examples/route-transitions.ts +0 -266
  609. package/examples/travel-agent.ts +0 -584
  610. package/src/core/DomainRegistry.ts +0 -80
  611. package/src/core/Tool.ts +0 -112
  612. package/src/core/Transition.ts +0 -115
  613. /package/dist/{adapters → cjs/src/adapters}/index.d.ts +0 -0
  614. /package/dist/cjs/{adapters → src/adapters}/index.js +0 -0
  615. /package/dist/cjs/{constants → src/constants}/index.d.ts +0 -0
  616. /package/dist/cjs/{constants → src/constants}/index.js +0 -0
  617. /package/dist/cjs/{providers → src/providers}/index.d.ts +0 -0
  618. /package/dist/cjs/{providers → src/providers}/index.js +0 -0
  619. /package/dist/cjs/{types → src/types}/agent.js +0 -0
  620. /package/dist/cjs/{types → src/types}/ai.js +0 -0
  621. /package/dist/cjs/{types → src/types}/persistence.js +0 -0
  622. /package/dist/cjs/{types → src/types}/route.js +0 -0
  623. /package/dist/cjs/{types → src/types}/routing.d.ts +0 -0
  624. /package/dist/cjs/{types → src/types}/routing.js +0 -0
  625. /package/dist/cjs/{types → src/types}/schema.d.ts +0 -0
  626. /package/dist/cjs/{types → src/types}/schema.js +0 -0
  627. /package/dist/cjs/{types → src/types}/tool.js +0 -0
  628. /package/dist/cjs/{utils → src/utils}/id.d.ts +0 -0
  629. /package/dist/cjs/{utils → src/utils}/id.js +0 -0
  630. /package/dist/cjs/{utils → src/utils}/logger.d.ts +0 -0
  631. /package/dist/cjs/{utils → src/utils}/logger.js +0 -0
  632. /package/dist/cjs/{utils → src/utils}/retry.d.ts +0 -0
  633. /package/dist/cjs/{utils → src/utils}/retry.js +0 -0
  634. /package/dist/{cjs → src}/adapters/index.d.ts +0 -0
  635. /package/dist/{cjs → src}/adapters/index.d.ts.map +0 -0
  636. /package/dist/{adapters → src/adapters}/index.js +0 -0
  637. /package/dist/{constants → src/constants}/index.d.ts +0 -0
  638. /package/dist/{cjs → src}/constants/index.d.ts.map +0 -0
  639. /package/dist/{constants → src/constants}/index.js +0 -0
  640. /package/dist/{providers → src/providers}/index.d.ts +0 -0
  641. /package/dist/{cjs → src}/providers/index.d.ts.map +0 -0
  642. /package/dist/{providers → src/providers}/index.js +0 -0
  643. /package/dist/{types → src/types}/agent.js +0 -0
  644. /package/dist/{types → src/types}/ai.js +0 -0
  645. /package/dist/{types → src/types}/persistence.js +0 -0
  646. /package/dist/{types → src/types}/route.js +0 -0
  647. /package/dist/{types → src/types}/routing.d.ts +0 -0
  648. /package/dist/{cjs → src}/types/routing.d.ts.map +0 -0
  649. /package/dist/{types → src/types}/routing.js +0 -0
  650. /package/dist/{cjs → src}/types/routing.js.map +0 -0
  651. /package/dist/{types → src/types}/schema.d.ts +0 -0
  652. /package/dist/{cjs → src}/types/schema.d.ts.map +0 -0
  653. /package/dist/{types → src/types}/schema.js +0 -0
  654. /package/dist/{cjs → src}/types/schema.js.map +0 -0
  655. /package/dist/{types → src/types}/tool.js +0 -0
  656. /package/dist/{utils → src/utils}/id.d.ts +0 -0
  657. /package/dist/{cjs → src}/utils/id.d.ts.map +0 -0
  658. /package/dist/{utils → src/utils}/id.js +0 -0
  659. /package/dist/{utils → src/utils}/logger.d.ts +0 -0
  660. /package/dist/{cjs → src}/utils/logger.d.ts.map +0 -0
  661. /package/dist/{utils → src/utils}/logger.js +0 -0
  662. /package/dist/{utils → src/utils}/retry.d.ts +0 -0
  663. /package/dist/{cjs → src}/utils/retry.d.ts.map +0 -0
  664. /package/dist/{utils → src/utils}/retry.js +0 -0
  665. /package/docs/{PUBLISHING.md → guides/advanced-patterns/publishing.md} +0 -0
package/src/core/Agent.ts CHANGED
@@ -2,54 +2,90 @@
2
2
  * Core Agent implementation
3
3
  */
4
4
 
5
- import type { AgentOptions, Term, Guideline, Capability } from "../types/agent";
6
- import type { Event, StepRef } from "../types/index";
7
- import type { RouteOptions } from "../types/route";
8
-
9
- import type { SessionState } from "../types/session";
10
- import type { AgentStructuredResponse } from "../types/ai";
5
+ import type {
6
+ AgentOptions,
7
+ Term,
8
+ Guideline,
9
+ Tool,
10
+ Event,
11
+ RouteOptions,
12
+ SessionState,
13
+ AgentStructuredResponse,
14
+ Template,
15
+ StepRef,
16
+ History,
17
+ AgentResponseStreamChunk,
18
+ AgentResponse,
19
+ StructuredSchema,
20
+ ValidationError,
21
+ ValidationResult,
22
+ } from "../types";
23
+ import { EventKind, MessageRole } from "../types/history";
11
24
  import {
12
- createSession,
13
25
  enterRoute,
14
26
  enterStep,
15
27
  mergeCollected,
16
- } from "../types/session";
17
- import { PromptComposer } from "./PromptComposer";
18
- import { logger, LoggerLevel } from "../utils/logger";
28
+ logger,
29
+ LoggerLevel,
30
+ render,
31
+ getLastMessageFromHistory,
32
+ normalizeHistory,
33
+ cloneDeep,
34
+ } from "../utils";
19
35
 
20
36
  import { Route } from "./Route";
21
37
  import { Step } from "./Step";
22
- import { DomainRegistry } from "./DomainRegistry";
23
38
  import { PersistenceManager } from "./PersistenceManager";
39
+ import { SessionManager } from "./SessionManager";
24
40
  import { RoutingEngine } from "./RoutingEngine";
25
41
  import { ResponseEngine } from "./ResponseEngine";
26
42
  import { ToolExecutor } from "./ToolExecutor";
27
- import { getLastMessageFromHistory } from "../utils/event";
43
+ import { ResponsePipeline } from "./ResponsePipeline";
28
44
  import { END_ROUTE_ID } from "../constants";
29
- import { ToolRef } from "../types";
30
45
 
31
46
  /**
32
- * Main Agent class with generic context support
47
+ * Error thrown when data validation fails
33
48
  */
34
- export class Agent<TContext = unknown> {
35
- private terms: Term[] = [];
36
- private guidelines: Guideline[] = [];
37
- private capabilities: Capability[] = [];
38
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
- private routes: Route<TContext, any>[] = [];
40
- private domainRegistry = new DomainRegistry();
41
- private context: TContext | undefined;
42
- private persistenceManager: PersistenceManager | undefined;
43
- private routingEngine: RoutingEngine<TContext>;
44
- private responseEngine: ResponseEngine<TContext>;
45
- private currentSession?: SessionState;
49
+ class DataValidationError extends Error {
50
+ constructor(public errors: ValidationError[], message?: string) {
51
+ super(message || "Data validation failed");
52
+ this.name = "DataValidationError";
53
+ }
54
+ }
46
55
 
47
- /**
48
- * Dynamic domain property - populated via addDomain
49
- */
50
- public readonly domain: Record<string, Record<string, unknown>> = {};
56
+ /**
57
+ * Error thrown when route configuration is invalid
58
+ */
59
+ class RouteConfigurationError extends Error {
60
+ constructor(public routeTitle: string, public invalidFields: string[], message?: string) {
61
+ super(message || `Route configuration error in '${routeTitle}'`);
62
+ this.name = "RouteConfigurationError";
63
+ }
64
+ }
51
65
 
52
- constructor(private readonly options: AgentOptions<TContext>) {
66
+ /**
67
+ * Main Agent class with generic context and data support
68
+ */
69
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
+ export class Agent<TContext = any, TData = any> {
71
+ private terms: Term<TContext, TData>[] = [];
72
+ private guidelines: Guideline<TContext, TData>[] = [];
73
+ private tools: Tool<TContext, TData, unknown[], unknown>[] = [];
74
+ private routes: Route<TContext, TData>[] = [];
75
+ private context: TContext | undefined;
76
+ private persistenceManager: PersistenceManager<TData> | undefined;
77
+ private routingEngine: RoutingEngine<TContext, TData>;
78
+ private responseEngine: ResponseEngine<TContext, TData>;
79
+ private responsePipeline: ResponsePipeline<TContext, TData>;
80
+ private currentSession?: SessionState<TData>;
81
+ private knowledgeBase: Record<string, unknown> = {};
82
+ private schema?: StructuredSchema;
83
+ private collectedData: Partial<TData> = {};
84
+
85
+ /** Public session manager for easy session management */
86
+ public session: SessionManager<TData>;
87
+
88
+ constructor(private readonly options: AgentOptions<TContext, TData>) {
53
89
  // Set log level based on debug option
54
90
  if (options.debug) {
55
91
  logger.setLevel(LoggerLevel.DEBUG);
@@ -62,38 +98,89 @@ export class Agent<TContext = unknown> {
62
98
  );
63
99
  }
64
100
 
101
+ // Initialize and validate agent-level schema if provided
102
+ if (options.schema) {
103
+ this.schema = options.schema;
104
+ this.validateSchema(this.schema);
105
+ logger.debug("[Agent] Agent-level schema initialized and validated");
106
+ }
107
+
65
108
  // Initialize context if provided
66
109
  this.context = options.context;
67
110
 
111
+ // Initialize collected data with initial data if provided
112
+ if (options.initialData) {
113
+ if (this.schema) {
114
+ const validation = this.validateData(options.initialData);
115
+ if (!validation.valid) {
116
+ throw new Error(
117
+ `Initial data validation failed: ${validation.errors.map(e => e.message).join(', ')}`
118
+ );
119
+ }
120
+ }
121
+ this.collectedData = { ...options.initialData };
122
+ logger.debug("[Agent] Initial data set:", this.collectedData);
123
+ }
124
+
68
125
  // Initialize current session if provided
69
126
  this.currentSession = options.session;
70
127
 
71
128
  // Initialize routing and response engines
72
- this.routingEngine = new RoutingEngine<TContext>({
129
+ this.routingEngine = new RoutingEngine<TContext, TData>({
73
130
  maxCandidates: 5,
74
131
  allowRouteSwitch: true,
75
132
  switchThreshold: 70,
76
133
  });
77
- this.responseEngine = new ResponseEngine<TContext>();
134
+ this.responseEngine = new ResponseEngine<TContext, TData>();
135
+ this.responsePipeline = new ResponsePipeline<TContext, TData>(
136
+ options,
137
+ this.routes,
138
+ this.tools,
139
+ this.routingEngine,
140
+ this.updateContext.bind(this),
141
+ this.updateData.bind(this),
142
+ this.updateCollectedData.bind(this)
143
+ );
78
144
 
79
145
  // Initialize persistence if configured
80
146
  if (options.persistence) {
81
- this.persistenceManager = new PersistenceManager(options.persistence);
147
+ try {
148
+ // Validate persistence configuration
149
+ if (!options.persistence.adapter) {
150
+ throw new Error("Persistence adapter is required when persistence is configured");
151
+ }
82
152
 
83
- // Initialize the adapter if it has an initialize method
84
- if (options.persistence.adapter.initialize) {
85
- options.persistence.adapter.initialize().catch((error) => {
86
- logger.error(
87
- "[Agent] Persistence adapter initialization failed:",
88
- error
89
- );
90
- });
153
+ if (!options.persistence.adapter.sessionRepository) {
154
+ throw new Error("Persistence adapter must provide a sessionRepository");
155
+ }
156
+
157
+ if (!options.persistence.adapter.messageRepository) {
158
+ throw new Error("Persistence adapter must provide a messageRepository");
159
+ }
160
+
161
+ this.persistenceManager = new PersistenceManager<TData>(options.persistence);
162
+
163
+ // Initialize the adapter if it has an initialize method
164
+ if (options.persistence.adapter.initialize) {
165
+ options.persistence.adapter.initialize().catch((error) => {
166
+ logger.error(
167
+ "[Agent] Persistence adapter initialization failed:",
168
+ error instanceof Error ? error.message : String(error)
169
+ );
170
+ });
171
+ }
172
+ } catch (error) {
173
+ const errorMessage = error instanceof Error ? error.message : String(error);
174
+ logger.error("[Agent] Failed to initialize persistence:", errorMessage);
175
+ throw new Error(`Failed to initialize persistence: ${errorMessage}`);
91
176
  }
92
177
  }
93
178
 
94
- // Initialize from options
179
+ // Initialize from options - use create methods for consistency
95
180
  if (options.terms) {
96
- this.terms = [...options.terms];
181
+ options.terms.forEach((term) => {
182
+ this.createTerm(term);
183
+ });
97
184
  }
98
185
 
99
186
  if (options.guidelines) {
@@ -102,19 +189,172 @@ export class Agent<TContext = unknown> {
102
189
  });
103
190
  }
104
191
 
105
- if (options.capabilities) {
106
- options.capabilities.forEach((capability) => {
107
- this.createCapability(capability);
192
+ if (options.tools) {
193
+ options.tools.forEach((tool) => {
194
+ this.createTool(tool);
108
195
  });
109
196
  }
110
197
 
111
198
  if (options.routes) {
112
199
  options.routes.forEach((routeOptions) => {
113
- this.createRoute<unknown>(routeOptions);
200
+ this.createRoute(routeOptions);
201
+ });
202
+ }
203
+
204
+ // Initialize knowledge base
205
+ if (options.knowledgeBase) {
206
+ this.knowledgeBase = { ...options.knowledgeBase };
207
+ }
208
+
209
+ // Initialize session manager
210
+ this.session = new SessionManager<TData>(this.persistenceManager);
211
+
212
+ // Store sessionId for later use in getOrCreate calls
213
+ if (options.sessionId) {
214
+ // The session will be loaded on first getOrCreate call
215
+ this.session.getOrCreate(options.sessionId).catch((err) => {
216
+ logger.error("Failed to start session", err);
114
217
  });
115
218
  }
116
219
  }
117
220
 
221
+ /**
222
+ * Validate the agent-level schema structure
223
+ * @private
224
+ */
225
+ private validateSchema(schema: StructuredSchema): void {
226
+ if (!schema || typeof schema !== 'object') {
227
+ throw new Error(
228
+ "Agent schema must be a valid JSON Schema object. " +
229
+ "Provide a schema with 'type': 'object' and 'properties' to define the data structure."
230
+ );
231
+ }
232
+
233
+ if (schema.type !== 'object') {
234
+ throw new Error(
235
+ `Agent schema must be of type 'object', but received '${String(schema.type)}'. ` +
236
+ "Agent-level schemas must define object structures for data collection."
237
+ );
238
+ }
239
+
240
+ if (!schema.properties || typeof schema.properties !== 'object') {
241
+ throw new Error(
242
+ "Agent schema must have a 'properties' field defining the data fields. " +
243
+ "Example: { type: 'object', properties: { name: { type: 'string' }, email: { type: 'string' } } }"
244
+ );
245
+ }
246
+
247
+ logger.debug("[Agent] Schema validation passed");
248
+ }
249
+
250
+ /**
251
+ * Validate data against the agent-level schema
252
+ */
253
+ validateData(data: Partial<TData>): ValidationResult {
254
+ if (!this.schema) {
255
+ // No schema defined, consider all data valid
256
+ return { valid: true, errors: [], warnings: [] };
257
+ }
258
+
259
+ const errors: ValidationError[] = [];
260
+ const warnings: ValidationError[] = [];
261
+
262
+ // Basic validation - check if provided fields exist in schema
263
+ if (this.schema.properties) {
264
+ for (const [key, value] of Object.entries(data)) {
265
+ if (!(key in this.schema.properties)) {
266
+ errors.push({
267
+ field: key,
268
+ value,
269
+ message: `Field '${key}' is not defined in agent schema`,
270
+ schemaPath: `properties.${key}`
271
+ });
272
+ }
273
+ }
274
+ }
275
+
276
+ // Check required fields if specified
277
+ if (this.schema.required && Array.isArray(this.schema.required)) {
278
+ for (const requiredField of this.schema.required) {
279
+ if (!(requiredField in data) || data[requiredField as keyof TData] === undefined) {
280
+ warnings.push({
281
+ field: requiredField,
282
+ value: undefined,
283
+ message: `Required field '${requiredField}' is missing`,
284
+ schemaPath: `required`
285
+ });
286
+ }
287
+ }
288
+ }
289
+
290
+ return {
291
+ valid: errors.length === 0,
292
+ errors,
293
+ warnings
294
+ };
295
+ }
296
+
297
+ /**
298
+ * Check if a field is valid according to the agent schema
299
+ * @param field - The field key to validate
300
+ * @returns true if field exists in schema or no schema is defined, false otherwise
301
+ */
302
+ isValidSchemaField(field: keyof TData): boolean {
303
+ if (!this.schema || !this.schema.properties) {
304
+ // No schema defined, consider all fields valid
305
+ return true;
306
+ }
307
+
308
+ return field as string in this.schema.properties;
309
+ }
310
+
311
+ /**
312
+ * Get the current collected data
313
+ */
314
+ getCollectedData(): Partial<TData> {
315
+ return { ...this.collectedData };
316
+ }
317
+
318
+ /**
319
+ * Update collected data with validation
320
+ */
321
+ async updateCollectedData(updates: Partial<TData>): Promise<void> {
322
+ // Validate the updates
323
+ const validation = this.validateData(updates);
324
+ if (!validation.valid) {
325
+ const errorMessages = validation.errors.map(e => e.message).join(', ');
326
+ throw new DataValidationError(validation.errors, `Data validation failed: ${errorMessages}`);
327
+ }
328
+
329
+ // Log warnings if any
330
+ if (validation.warnings.length > 0) {
331
+ const warningMessages = validation.warnings.map(w => w.message).join(', ');
332
+ logger.warn(`[Agent] Data validation warnings: ${warningMessages}`);
333
+ }
334
+
335
+ // Merge updates with current data
336
+ const previousData = { ...this.collectedData };
337
+ this.collectedData = {
338
+ ...this.collectedData,
339
+ ...updates
340
+ };
341
+
342
+ // Trigger agent-level lifecycle hook if configured
343
+ if (this.options.hooks?.onDataUpdate) {
344
+ this.collectedData = await this.options.hooks.onDataUpdate(
345
+ this.collectedData,
346
+ previousData
347
+ );
348
+ }
349
+
350
+ // Update current session if it exists to keep it in sync
351
+ if (this.currentSession) {
352
+ this.currentSession = mergeCollected(this.currentSession, this.collectedData);
353
+ }
354
+
355
+ logger.debug("[Agent] Collected data updated:", updates);
356
+ }
357
+
118
358
  /**
119
359
  * Get agent name
120
360
  */
@@ -137,12 +377,48 @@ export class Agent<TContext = unknown> {
137
377
  }
138
378
 
139
379
  /**
140
- * Create a new route (journey)
141
- * @template TData - Type of data collected throughout the route
380
+ * Get agent identity
142
381
  */
143
- createRoute<TData = unknown>(
382
+ get identity(): Template<TContext> | undefined {
383
+ return this.options.identity;
384
+ }
385
+
386
+ /**
387
+ * Create a new route (journey) using agent-level data type
388
+ */
389
+ createRoute(
144
390
  options: RouteOptions<TContext, TData>
145
391
  ): Route<TContext, TData> {
392
+ // Validate that requiredFields exist in agent schema
393
+ if (options.requiredFields && this.schema?.properties) {
394
+ const invalidRequiredFields = options.requiredFields.filter(
395
+ field => !(String(field) in this.schema!.properties!)
396
+ );
397
+ if (invalidRequiredFields.length > 0) {
398
+ throw new RouteConfigurationError(
399
+ options.title,
400
+ invalidRequiredFields.map(f => String(f)),
401
+ `Invalid required fields in route '${options.title}': ${invalidRequiredFields.join(', ')}. ` +
402
+ `Must be valid keys from agent schema. Available fields: ${Object.keys(this.schema.properties).join(', ')}.`
403
+ );
404
+ }
405
+ }
406
+
407
+ // Validate that optionalFields exist in agent schema
408
+ if (options.optionalFields && this.schema?.properties) {
409
+ const invalidOptionalFields = options.optionalFields.filter(
410
+ field => !(String(field) in this.schema!.properties!)
411
+ );
412
+ if (invalidOptionalFields.length > 0) {
413
+ throw new RouteConfigurationError(
414
+ options.title,
415
+ invalidOptionalFields.map(f => String(f)),
416
+ `Invalid optional fields in route '${options.title}': ${invalidOptionalFields.join(', ')}. ` +
417
+ `Must be valid keys from agent schema. Available fields: ${Object.keys(this.schema.properties).join(', ')}.`
418
+ );
419
+ }
420
+ }
421
+
146
422
  const route = new Route<TContext, TData>(options);
147
423
  this.routes.push(route);
148
424
  return route;
@@ -151,7 +427,7 @@ export class Agent<TContext = unknown> {
151
427
  /**
152
428
  * Create a domain term for the glossary
153
429
  */
154
- createTerm(term: Term): this {
430
+ createTerm(term: Term<TContext, TData>): this {
155
431
  this.terms.push(term);
156
432
  return this;
157
433
  }
@@ -159,7 +435,7 @@ export class Agent<TContext = unknown> {
159
435
  /**
160
436
  * Create a behavioral guideline
161
437
  */
162
- createGuideline(guideline: Guideline): this {
438
+ createGuideline(guideline: Guideline<TContext, TData>): this {
163
439
  const guidelineWithId = {
164
440
  ...guideline,
165
441
  id: guideline.id || `guideline_${this.guidelines.length}`,
@@ -170,50 +446,24 @@ export class Agent<TContext = unknown> {
170
446
  }
171
447
 
172
448
  /**
173
- * Add a capability
449
+ * Register a tool at the agent level
174
450
  */
175
- createCapability(capability: Capability): this {
176
- const capabilityWithId = {
177
- ...capability,
178
- id: capability.id || `capability_${this.capabilities.length}`,
179
- };
180
- this.capabilities.push(capabilityWithId);
451
+ createTool(tool: Tool<TContext, TData, unknown[], unknown>): this {
452
+ this.tools.push(tool);
181
453
  return this;
182
454
  }
183
455
 
184
456
  /**
185
- * Add a domain with its tools/methods
186
- * Automatically tags all ToolRef objects with their domain name for security enforcement
457
+ * Register multiple tools at the agent level
187
458
  */
188
- addDomain<TName extends string, TDomain extends Record<string, unknown>>(
189
- name: TName,
190
- domainObject: TDomain
191
- ): void {
192
- // Tag all tools in this domain with the domain name for security enforcement
193
- const taggedDomain = { ...domainObject };
194
- for (const key in taggedDomain) {
195
- const value = taggedDomain[key];
196
- // Check if value is a ToolRef (has handler, id, name properties)
197
- if (
198
- value &&
199
- typeof value === "object" &&
200
- "handler" in value &&
201
- "id" in value &&
202
- "name" in value
203
- ) {
204
- // Tag the tool with its domain name
205
- (value as Record<string, unknown>).domainName = name;
206
- }
207
- }
208
-
209
- this.domainRegistry.register(name, taggedDomain);
210
- // Attach to the domain property for easy access
211
- this.domain[name] = taggedDomain;
459
+ registerTools(tools: Tool<TContext, TData, unknown[], unknown>[]): this {
460
+ tools.forEach((tool) => this.createTool(tool));
461
+ return this;
212
462
  }
213
463
 
214
464
  /**
215
465
  * Update the agent's context
216
- * Triggers the onContextUpdate lifecycle hook if configured
466
+ * Triggers both agent-level and route-specific onContextUpdate lifecycle hooks if configured
217
467
  */
218
468
  async updateContext(updates: Partial<TContext>): Promise<void> {
219
469
  const previousContext = this.context;
@@ -224,7 +474,20 @@ export class Agent<TContext = unknown> {
224
474
  ...(updates as Record<string, unknown>),
225
475
  } as TContext;
226
476
 
227
- // Trigger lifecycle hook if configured
477
+ // Trigger route-specific lifecycle hook if configured and session has current route
478
+ if (this.currentSession?.currentRoute) {
479
+ const currentRoute = this.routes.find(
480
+ (r) => r.id === this.currentSession!.currentRoute?.id
481
+ );
482
+ if (
483
+ currentRoute?.hooks?.onContextUpdate &&
484
+ previousContext !== undefined
485
+ ) {
486
+ await currentRoute.handleContextUpdate(this.context, previousContext);
487
+ }
488
+ }
489
+
490
+ // Trigger agent-level lifecycle hook if configured
228
491
  if (this.options.hooks?.onContextUpdate && previousContext !== undefined) {
229
492
  await this.options.hooks.onContextUpdate(this.context, previousContext);
230
493
  }
@@ -232,38 +495,53 @@ export class Agent<TContext = unknown> {
232
495
 
233
496
  /**
234
497
  * Update collected data in session with lifecycle hook support
235
- * Triggers the onDataUpdate lifecycle hook if configured
498
+ * Triggers both agent-level and route-specific onDataUpdate lifecycle hooks if configured
236
499
  * @internal
237
500
  */
238
- private async updateData<TData = unknown>(
501
+ private async updateData(
239
502
  session: SessionState<TData>,
240
- collectedUpdate: Partial<TData>
503
+ dataUpdate: Partial<TData>
241
504
  ): Promise<SessionState<TData>> {
242
505
  const previousCollected = { ...session.data };
243
506
 
244
507
  // Merge new collected data
245
508
  let newCollected = {
246
509
  ...session.data,
247
- ...collectedUpdate,
510
+ ...dataUpdate,
248
511
  };
249
512
 
250
- // Trigger lifecycle hook if configured
513
+ // Trigger route-specific lifecycle hook if configured and session has a current route
514
+ if (session.currentRoute) {
515
+ const currentRoute = this.routes.find(
516
+ (r) => r.id === session.currentRoute?.id
517
+ );
518
+ if (currentRoute?.hooks?.onDataUpdate) {
519
+ newCollected = await currentRoute.handleDataUpdate(
520
+ newCollected,
521
+ previousCollected
522
+ );
523
+ }
524
+ }
525
+
526
+ // Trigger agent-level lifecycle hook if configured
251
527
  if (this.options.hooks?.onDataUpdate) {
252
528
  newCollected = (await this.options.hooks.onDataUpdate(
253
529
  newCollected,
254
530
  previousCollected
255
- )) as Partial<TData>;
531
+ ));
256
532
  }
257
533
 
534
+ // Update agent's collected data to stay in sync
535
+ this.collectedData = { ...newCollected };
536
+
258
537
  // Return updated session
259
538
  return mergeCollected(session, newCollected);
260
539
  }
261
540
 
262
541
  /**
263
542
  * Get current context (fetches from provider if configured)
264
- * @internal
265
543
  */
266
- private async getContext(): Promise<TContext | undefined> {
544
+ async getContext(): Promise<TContext | undefined> {
267
545
  // If context provider is configured, use it to fetch fresh context
268
546
  if (this.options.contextProvider) {
269
547
  return await this.options.contextProvider();
@@ -272,197 +550,91 @@ export class Agent<TContext = unknown> {
272
550
  // Otherwise return the stored context
273
551
  return this.context;
274
552
  }
553
+ /**
554
+ * Get current schema
555
+ */
556
+ getSchema(): StructuredSchema | undefined {
557
+ return this.schema;
558
+ }
275
559
 
276
560
  /**
277
561
  * Generate a response based on history and context as a stream
278
562
  */
279
563
  async *respondStream(params: {
280
- history: Event[];
564
+ history: History;
281
565
  step?: StepRef;
282
- session?: SessionState;
566
+ session?: SessionState<TData>;
283
567
  contextOverride?: Partial<TContext>;
284
568
  signal?: AbortSignal;
285
- }): AsyncGenerator<{
286
- delta: string;
287
- accumulated: string;
288
- done: boolean;
289
- session?: SessionState;
290
- toolCalls?: Array<{ toolName: string; arguments: Record<string, unknown> }>;
291
- isRouteComplete?: boolean;
292
- }> {
293
- const { history, contextOverride, signal } = params;
294
-
295
- // Get current context (may fetch from provider)
296
- let currentContext = await this.getContext();
297
-
298
- // Call beforeRespond hook if configured
299
- if (this.options.hooks?.beforeRespond && currentContext !== undefined) {
300
- currentContext = await this.options.hooks.beforeRespond(currentContext);
301
- // Update stored context with the result from beforeRespond
302
- this.context = currentContext;
569
+ }): AsyncGenerator<AgentResponseStreamChunk<TData>> {
570
+ const { history: simpleHistory, signal } = params;
571
+ const history = normalizeHistory(simpleHistory);
572
+
573
+ // Prepare context and session using the response pipeline
574
+ this.responsePipeline.setContext(this.context);
575
+ this.responsePipeline.setCurrentSession(this.currentSession);
576
+ let session: SessionState;
577
+ const responseContext = await this.responsePipeline.prepareResponseContext({
578
+ contextOverride: params.contextOverride,
579
+ session: params.session ? cloneDeep(params.session) : undefined,
580
+ });
581
+ const { effectiveContext } = responseContext;
582
+ session = responseContext.session;
583
+
584
+ // Merge agent's collected data into session (agent data takes precedence)
585
+ if (Object.keys(this.collectedData).length > 0) {
586
+ session = mergeCollected(session, this.collectedData);
587
+ logger.debug("[Agent] Merged agent collected data into session:", this.collectedData);
303
588
  }
589
+
590
+ // Update our stored context if it was modified by beforeRespond hook
591
+ this.context = this.responsePipeline.getStoredContext();
304
592
 
305
- // Merge context with override
306
- const effectiveContext = {
307
- ...(currentContext as Record<string, unknown>),
308
- ...(contextOverride as Record<string, unknown>),
309
- } as TContext;
310
-
311
- // Initialize or get session (use current session if available)
312
- let session = params.session || this.currentSession || createSession();
313
-
314
- // PHASE 1: TOOL EXECUTION - Execute tools if current step has tool
593
+ // PHASE 1: PREPARE - Execute prepare function if current step has one
315
594
  if (session.currentRoute && session.currentStep) {
316
595
  const currentRoute = this.routes.find(
317
596
  (r) => r.id === session.currentRoute?.id
318
597
  );
319
598
  if (currentRoute) {
320
599
  const currentStep = currentRoute.getStep(session.currentStep.id);
321
- if (currentStep) {
322
- const transitions = currentStep.getTransitions();
323
- const toolTransition = transitions.find((t) => t.spec.tool);
324
-
325
- if (toolTransition?.spec.tool) {
326
- const toolExecutor = new ToolExecutor<TContext, unknown>();
327
- // Get allowed domains from current route for security enforcement
328
- const allowedDomains = currentRoute.getDomains();
329
- const result = await toolExecutor.executeTool(
330
- toolTransition.spec.tool as ToolRef<TContext, unknown[], unknown>,
331
- effectiveContext,
332
- this.updateContext.bind(this),
333
- history,
334
- session.data,
335
- allowedDomains
336
- );
337
-
338
- // Update context with tool results
339
- if (result.contextUpdate) {
340
- await this.updateContext(
341
- result.contextUpdate as Partial<TContext>
342
- );
343
- }
344
-
345
- // Update collected data with tool results
346
- if (result.collectedUpdate) {
347
- session = await this.updateData(session, result.collectedUpdate);
348
- logger.debug(
349
- `[Agent] Tool updated collected data:`,
350
- result.collectedUpdate
351
- );
352
- }
353
-
354
- logger.debug(
355
- `[Agent] Executed tool: ${result.toolName} (success: ${result.success})`
356
- );
357
- }
358
- }
359
- }
360
- }
361
-
362
- // PHASE 2: ROUTING + STEP SELECTION - Determine which route and step to use (combined)
363
- let selectedRoute: Route<TContext> | undefined;
364
- let responseDirectives: string[] | undefined;
365
- let selectedStep: Step<TContext> | undefined;
366
- let isRouteComplete = false;
367
-
368
- // Check for pending transition from previous route completion
369
- if (session.pendingTransition) {
370
- const targetRoute = this.routes.find(
371
- (r) => r.id === session.pendingTransition?.targetRouteId
372
- );
373
-
374
- if (targetRoute) {
375
- logger.debug(
376
- `[Agent] Auto-transitioning from pending transition to route: ${targetRoute.title}`
377
- );
378
- // Clear pending transition and enter new route
379
- session = {
380
- ...session,
381
- pendingTransition: undefined,
382
- };
383
- session = enterRoute(session, targetRoute.id, targetRoute.title);
384
-
385
- // Merge initial data if available
386
- if (targetRoute.initialData) {
387
- session = mergeCollected(session, targetRoute.initialData);
600
+ if (currentStep?.prepare) {
601
+ logger.debug(`[Agent] Executing prepare for step: ${currentStep.id}`);
602
+ await this.executePrepareFinalize(
603
+ currentStep.prepare,
604
+ effectiveContext,
605
+ session.data,
606
+ currentRoute,
607
+ currentStep
608
+ );
388
609
  }
389
-
390
- selectedRoute = targetRoute;
391
- } else {
392
- logger.warn(
393
- `[Agent] Pending transition target route not found: ${session.pendingTransition.targetRouteId}`
394
- );
395
- // Clear invalid transition
396
- session = {
397
- ...session,
398
- pendingTransition: undefined,
399
- };
400
610
  }
401
611
  }
402
612
 
403
- // If no pending transition or transition handled, do normal routing
404
- if (this.routes.length > 0 && !selectedRoute) {
405
- const orchestration = await this.routingEngine.decideRouteAndStep({
406
- routes: this.routes,
613
+ // PHASE 2: ROUTING + STEP SELECTION - Use response pipeline
614
+ const routingResult =
615
+ await this.responsePipeline.handleRoutingAndStepSelection({
407
616
  session,
408
617
  history,
409
- agentMeta: {
410
- name: this.options.name,
411
- goal: this.options.goal,
412
- description: this.options.description,
413
- personality: this.options.personality,
414
- },
415
- provider: this.options.provider,
416
618
  context: effectiveContext,
417
619
  signal,
418
620
  });
621
+ const selectedRoute = routingResult.selectedRoute;
622
+ const selectedStep = routingResult.selectedStep;
623
+ const responseDirectives = routingResult.responseDirectives;
624
+ const isRouteComplete = routingResult.isRouteComplete;
625
+ session = routingResult.session;
626
+
627
+ // PHASE 3: DETERMINE NEXT STEP - Use pipeline method
628
+ const stepResult = this.responsePipeline.determineNextStep({
629
+ selectedRoute,
630
+ selectedStep,
631
+ session,
632
+ isRouteComplete,
633
+ });
634
+ const nextStep = stepResult.nextStep;
635
+ session = stepResult.session;
419
636
 
420
- selectedRoute = orchestration.selectedRoute;
421
- selectedStep = orchestration.selectedStep;
422
- responseDirectives = orchestration.responseDirectives;
423
- session = orchestration.session;
424
- isRouteComplete = orchestration.isRouteComplete || false;
425
-
426
- // Log if route is complete
427
- if (isRouteComplete) {
428
- logger.debug(
429
- `[Agent] Route complete: all required data collected, END_ROUTE reached`
430
- );
431
- }
432
- }
433
-
434
- // PHASE 3: DETERMINE NEXT STEP - Use step from combined decision or get initial step
435
637
  if (selectedRoute && !isRouteComplete) {
436
- let nextStep: Step<TContext>;
437
-
438
- // If we have a selected step from the combined routing decision, use it
439
- if (selectedStep) {
440
- nextStep = selectedStep;
441
- } else {
442
- // New route or no step selected - get initial step or first valid step
443
- const candidates = this.routingEngine.getCandidateSteps(
444
- selectedRoute,
445
- undefined,
446
- session.data || {}
447
- );
448
- if (candidates.length > 0) {
449
- nextStep = candidates[0].step;
450
- logger.debug(
451
- `[Agent] Using first valid step: ${nextStep.id} for new route`
452
- );
453
- } else {
454
- // Fallback to initial step even if it should be skipped
455
- nextStep = selectedRoute.initialStep;
456
- logger.warn(
457
- `[Agent] No valid steps found, using initial step: ${nextStep.id}`
458
- );
459
- }
460
- }
461
-
462
- // Update session with next step
463
- session = enterStep(session, nextStep.id, nextStep.description);
464
- logger.debug(`[Agent] Entered step: ${nextStep.id}`);
465
-
466
638
  // PHASE 4: RESPONSE GENERATION - Stream message using selected route and step
467
639
  // Get last user message
468
640
  const lastUserMessage = getLastMessageFromHistory(history);
@@ -470,24 +642,47 @@ export class Agent<TContext = unknown> {
470
642
  // Build response schema for this route (with collect fields from step)
471
643
  const responseSchema = this.responseEngine.responseSchemaForRoute(
472
644
  selectedRoute,
473
- nextStep
645
+ nextStep,
646
+ this.schema
474
647
  );
475
648
 
649
+ // Check if selected route and next step are defined
650
+ if (!selectedRoute || !nextStep) {
651
+ logger.error("[Agent] Selected route or next step is not defined", {
652
+ selectedRoute,
653
+ nextStep,
654
+ });
655
+ throw new Error("Selected route or next step is not defined");
656
+ }
657
+
476
658
  // Build response prompt
477
- const responsePrompt = this.responseEngine.buildResponsePrompt(
478
- selectedRoute,
479
- nextStep,
480
- selectedRoute.getRules(),
481
- selectedRoute.getProhibitions(),
482
- responseDirectives,
659
+ const responsePrompt = await this.responseEngine.buildResponsePrompt({
660
+ route: selectedRoute,
661
+ currentStep: nextStep,
662
+ rules: selectedRoute.getRules(),
663
+ prohibitions: selectedRoute.getProhibitions(),
664
+ directives: responseDirectives,
483
665
  history,
484
- lastUserMessage,
485
- {
486
- name: this.options.name,
487
- goal: this.options.goal,
488
- description: this.options.description,
489
- personality: this.options.personality,
490
- }
666
+ lastMessage: lastUserMessage,
667
+ agentOptions: this.options,
668
+ // Combine agent and route properties according to the specified logic
669
+ combinedGuidelines: [
670
+ ...this.getGuidelines(),
671
+ ...selectedRoute.getGuidelines(),
672
+ ],
673
+ combinedTerms: this.mergeTerms(
674
+ this.getTerms(),
675
+ selectedRoute.getTerms()
676
+ ),
677
+ context: effectiveContext,
678
+ session,
679
+ agentSchema: this.schema,
680
+ });
681
+
682
+ // Collect available tools for AI
683
+ const availableTools = this.collectAvailableTools(
684
+ selectedRoute,
685
+ nextStep
491
686
  );
492
687
 
493
688
  // Generate message stream using AI provider
@@ -495,6 +690,7 @@ export class Agent<TContext = unknown> {
495
690
  prompt: responsePrompt,
496
691
  history,
497
692
  context: effectiveContext,
693
+ tools: availableTools,
498
694
  signal,
499
695
  parameters: {
500
696
  jsonSchema: responseSchema,
@@ -504,26 +700,220 @@ export class Agent<TContext = unknown> {
504
700
 
505
701
  // Stream chunks to caller
506
702
  for await (const chunk of stream) {
507
- const toolCalls:
703
+ let toolCalls:
508
704
  | Array<{ toolName: string; arguments: Record<string, unknown> }>
509
705
  | undefined = undefined;
510
706
 
707
+ // Extract tool calls from AI response on final chunk
708
+ if (chunk.done && chunk.structured?.toolCalls) {
709
+ toolCalls = chunk.structured.toolCalls;
710
+
711
+ // Execute dynamic tool calls
712
+ if (toolCalls.length > 0) {
713
+ logger.debug(
714
+ `[Agent] Executing ${toolCalls.length} dynamic tool calls`
715
+ );
716
+
717
+ for (const toolCall of toolCalls) {
718
+ const tool = this.findAvailableTool(
719
+ toolCall.toolName,
720
+ selectedRoute
721
+ );
722
+ if (!tool) {
723
+ logger.warn(`[Agent] Tool not found: ${toolCall.toolName}`);
724
+ continue;
725
+ }
726
+
727
+ const toolExecutor = new ToolExecutor<TContext, TData>();
728
+ const result = await toolExecutor.executeTool({
729
+ tool: tool,
730
+ context: effectiveContext,
731
+ updateContext: this.updateContext.bind(this),
732
+ updateData: this.updateCollectedData.bind(this),
733
+ history,
734
+ data: session.data,
735
+ toolArguments: toolCall.arguments,
736
+ });
737
+
738
+ // Update context with tool results
739
+ if (result.contextUpdate) {
740
+ await this.updateContext(
741
+ result.contextUpdate as Partial<TContext>
742
+ );
743
+ }
744
+
745
+ // Update collected data with tool results
746
+ if (result.dataUpdate) {
747
+ session = await this.updateData(session, result.dataUpdate as Partial<TData>);
748
+ logger.debug(
749
+ `[Agent] Tool updated collected data:`,
750
+ result.dataUpdate
751
+ );
752
+ }
753
+
754
+ logger.debug(
755
+ `[Agent] Executed dynamic tool: ${result.toolName} (success: ${result.success})`
756
+ );
757
+ }
758
+ }
759
+ }
760
+
761
+ // TOOL LOOP: Allow AI to make follow-up tool calls after initial tool execution (streaming)
762
+ const MAX_TOOL_LOOPS = 5;
763
+ let toolLoopCount = 0;
764
+ let hasToolCalls = toolCalls && toolCalls.length > 0;
765
+
766
+ while (hasToolCalls && toolLoopCount < MAX_TOOL_LOOPS) {
767
+ toolLoopCount++;
768
+ logger.debug(
769
+ `[Agent] Starting streaming tool loop ${toolLoopCount}/${MAX_TOOL_LOOPS}`
770
+ );
771
+
772
+ // Add tool execution results to history so AI knows what happened
773
+ const toolResultsEvents: Event[] = [];
774
+ for (const toolCall of toolCalls || []) {
775
+ const tool = this.findAvailableTool(
776
+ toolCall.toolName,
777
+ selectedRoute
778
+ );
779
+ if (tool) {
780
+ toolResultsEvents.push({
781
+ kind: EventKind.TOOL,
782
+ source: MessageRole.AGENT,
783
+ timestamp: new Date().toISOString(),
784
+ data: {
785
+ tool_calls: [
786
+ {
787
+ tool_id: toolCall.toolName,
788
+ arguments: toolCall.arguments,
789
+ result: {
790
+ data: "Tool executed successfully",
791
+ },
792
+ },
793
+ ],
794
+ },
795
+ });
796
+ }
797
+ }
798
+
799
+ // Create updated history with tool results
800
+ const updatedHistory = [...history, ...toolResultsEvents];
801
+
802
+ // Make follow-up streaming AI call to see if more tools are needed
803
+ const followUpStream = this.options.provider.generateMessageStream({
804
+ prompt: responsePrompt,
805
+ history: updatedHistory,
806
+ context: effectiveContext,
807
+ tools: availableTools,
808
+ parameters: {
809
+ jsonSchema: responseSchema,
810
+ schemaName: "tool_followup",
811
+ },
812
+ signal,
813
+ });
814
+
815
+ let followUpToolCalls:
816
+ | Array<{ toolName: string; arguments: Record<string, unknown> }>
817
+ | undefined;
818
+
819
+ for await (const followUpChunk of followUpStream) {
820
+ // Extract tool calls from follow-up stream
821
+ if (followUpChunk.done && followUpChunk.structured?.toolCalls) {
822
+ followUpToolCalls = followUpChunk.structured.toolCalls;
823
+ }
824
+ }
825
+
826
+ hasToolCalls = followUpToolCalls && followUpToolCalls.length > 0;
827
+
828
+ if (hasToolCalls) {
829
+ logger.debug(
830
+ `[Agent] Follow-up streaming call produced ${followUpToolCalls!.length
831
+ } additional tool calls`
832
+ );
833
+
834
+ // Execute the follow-up tool calls
835
+ for (const toolCall of followUpToolCalls!) {
836
+ const tool = this.findAvailableTool(
837
+ toolCall.toolName,
838
+ selectedRoute
839
+ );
840
+ if (!tool) {
841
+ logger.warn(
842
+ `[Agent] Tool not found in streaming follow-up: ${toolCall.toolName}`
843
+ );
844
+ continue;
845
+ }
846
+
847
+ const toolExecutor = new ToolExecutor<TContext, TData>();
848
+ const result = await toolExecutor.executeTool({
849
+ tool: tool,
850
+ context: effectiveContext,
851
+ updateContext: this.updateContext.bind(this),
852
+ updateData: this.updateCollectedData.bind(this),
853
+ history: updatedHistory,
854
+ data: session.data,
855
+ toolArguments: toolCall.arguments,
856
+ });
857
+
858
+ // Update context with follow-up tool results
859
+ if (result.contextUpdate) {
860
+ await this.updateContext(
861
+ result.contextUpdate as Partial<TContext>
862
+ );
863
+ }
864
+
865
+ if (result.dataUpdate) {
866
+ session = await this.updateData(session, result.dataUpdate as Partial<TData>);
867
+ logger.debug(
868
+ `[Agent] Streaming follow-up tool updated collected data:`,
869
+ result.dataUpdate
870
+ );
871
+ }
872
+
873
+ logger.debug(
874
+ `[Agent] Executed streaming follow-up tool: ${result.toolName} (success: ${result.success})`
875
+ );
876
+ }
877
+
878
+ // Update toolCalls for next iteration
879
+ toolCalls = followUpToolCalls;
880
+ } else {
881
+ logger.debug(
882
+ `[Agent] Streaming tool loop completed after ${toolLoopCount} iterations`
883
+ );
884
+ // Update toolCalls for final response
885
+ toolCalls = followUpToolCalls || [];
886
+ break;
887
+ }
888
+ }
889
+
890
+ if (toolLoopCount >= MAX_TOOL_LOOPS) {
891
+ logger.warn(
892
+ `[Agent] Streaming tool loop limit reached (${MAX_TOOL_LOOPS}), stopping`
893
+ );
894
+ }
895
+
511
896
  // Extract collected data on final chunk
512
- if (chunk.done && chunk.structured && nextStep.collectFields) {
897
+ if (chunk.done && chunk.structured && nextStep.collect) {
513
898
  const collectedData: Record<string, unknown> = {};
514
899
  // The structured response includes both base fields and collected extraction fields
515
900
  const structuredData = chunk.structured as AgentStructuredResponse &
516
901
  Record<string, unknown>;
517
902
 
518
- for (const field of nextStep.collectFields) {
519
- if (field in structuredData) {
520
- collectedData[field] = structuredData[field];
903
+ for (const field of nextStep.collect) {
904
+ const fieldKey = String(field);
905
+ if (fieldKey in structuredData) {
906
+ collectedData[fieldKey] = structuredData[fieldKey];
521
907
  }
522
908
  }
523
909
 
524
- // Merge collected data into session
910
+ // Merge collected data into session using agent-level data validation
525
911
  if (Object.keys(collectedData).length > 0) {
526
- session = await this.updateData(session, collectedData);
912
+ // Update agent-level collected data with validation
913
+ await this.updateCollectedData(collectedData as Partial<TData>);
914
+
915
+ // Update session with validated data
916
+ session = await this.updateData(session, collectedData as Partial<TData>);
527
917
  logger.debug(`[Agent] Collected data:`, collectedData);
528
918
  }
529
919
  }
@@ -554,6 +944,28 @@ export class Agent<TContext = unknown> {
554
944
  );
555
945
  }
556
946
 
947
+ // Execute finalize function on final chunk
948
+ if (chunk.done && session.currentRoute && session.currentStep) {
949
+ const currentRoute = this.routes.find(
950
+ (r) => r.id === session.currentRoute?.id
951
+ );
952
+ if (currentRoute) {
953
+ const currentStep = currentRoute.getStep(session.currentStep.id);
954
+ if (currentStep?.finalize) {
955
+ logger.debug(
956
+ `[Agent] Executing finalize for step: ${currentStep.id}`
957
+ );
958
+ await this.executePrepareFinalize(
959
+ currentStep.finalize,
960
+ effectiveContext,
961
+ session.data,
962
+ currentRoute,
963
+ currentStep
964
+ );
965
+ }
966
+ }
967
+ }
968
+
557
969
  // Update current session if we have one
558
970
  if (chunk.done && this.currentSession) {
559
971
  this.currentSession = session;
@@ -566,6 +978,8 @@ export class Agent<TContext = unknown> {
566
978
  session, // Return updated session
567
979
  toolCalls,
568
980
  isRouteComplete,
981
+ metadata: chunk.metadata,
982
+ structured: chunk.structured,
569
983
  };
570
984
  }
571
985
  } else if (isRouteComplete && selectedRoute) {
@@ -576,40 +990,51 @@ export class Agent<TContext = unknown> {
576
990
  const endStepSpec = selectedRoute.endStepSpec;
577
991
 
578
992
  // Create a temporary step for completion message generation using endStep configuration
579
- const completionStep = new Step<TContext>(
580
- selectedRoute.id,
581
- endStepSpec.instructions ||
582
- "Summarize what was accomplished and confirm completion",
583
- endStepSpec.id || END_ROUTE_ID,
584
- endStepSpec.collect,
585
- undefined,
586
- endStepSpec.requires,
587
- endStepSpec.instructions ||
588
- "Summarize what was accomplished and confirm completion based on the conversation history and collected data"
589
- );
993
+ const completionStep = new Step<TContext, TData>(selectedRoute.id, {
994
+ description: endStepSpec.description,
995
+ id: endStepSpec.id || END_ROUTE_ID,
996
+ collect: endStepSpec.collect,
997
+ requires: endStepSpec.requires,
998
+ prompt:
999
+ endStepSpec.prompt ||
1000
+ "Summarize what was accomplished and confirm completion based on the conversation history and collected data",
1001
+ });
590
1002
 
591
1003
  // Build response schema for completion
592
1004
  const responseSchema = this.responseEngine.responseSchemaForRoute(
593
1005
  selectedRoute,
594
- completionStep
1006
+ completionStep,
1007
+ this.schema
595
1008
  );
1009
+ const templateContext = {
1010
+ context: effectiveContext,
1011
+ session,
1012
+ history,
1013
+ };
596
1014
 
597
1015
  // Build completion response prompt
598
- const completionPrompt = this.responseEngine.buildResponsePrompt(
599
- selectedRoute,
600
- completionStep,
601
- selectedRoute.getRules(),
602
- selectedRoute.getProhibitions(),
603
- undefined, // No directives for completion
1016
+ const completionPrompt = await this.responseEngine.buildResponsePrompt({
1017
+ route: selectedRoute,
1018
+ currentStep: completionStep,
1019
+ rules: selectedRoute.getRules(),
1020
+ prohibitions: selectedRoute.getProhibitions(),
1021
+ directives: undefined, // No directives for completion
604
1022
  history,
605
- lastUserMessage,
606
- {
607
- name: this.options.name,
608
- goal: this.options.goal,
609
- description: this.options.description,
610
- personality: this.options.personality,
611
- }
612
- );
1023
+ lastMessage: lastUserMessage,
1024
+ agentOptions: this.options,
1025
+ // Combine agent and route properties according to the specified logic
1026
+ combinedGuidelines: [
1027
+ ...this.getGuidelines(),
1028
+ ...selectedRoute.getGuidelines(),
1029
+ ],
1030
+ combinedTerms: this.mergeTerms(
1031
+ this.getTerms(),
1032
+ selectedRoute.getTerms()
1033
+ ),
1034
+ context: effectiveContext,
1035
+ session,
1036
+ agentSchema: this.schema,
1037
+ });
613
1038
 
614
1039
  // Stream completion message using AI provider
615
1040
  const stream = this.options.provider.generateMessageStream({
@@ -642,12 +1067,16 @@ export class Agent<TContext = unknown> {
642
1067
  );
643
1068
 
644
1069
  if (targetRoute) {
1070
+ const renderedCondition = await render(
1071
+ transitionConfig.condition,
1072
+ templateContext
1073
+ );
645
1074
  // Set pending transition in session
646
1075
  session = {
647
1076
  ...session,
648
1077
  pendingTransition: {
649
1078
  targetRouteId: targetRoute.id,
650
- condition: transitionConfig.condition,
1079
+ condition: renderedCondition,
651
1080
  reason: "route_complete",
652
1081
  },
653
1082
  };
@@ -681,22 +1110,20 @@ export class Agent<TContext = unknown> {
681
1110
  session,
682
1111
  toolCalls: undefined,
683
1112
  isRouteComplete: true,
1113
+ metadata: chunk.metadata,
1114
+ structured: chunk.structured,
684
1115
  };
685
1116
  }
686
1117
  } else {
687
1118
  // Fallback: No routes defined, stream a simple response
688
- const fallbackPrompt = new PromptComposer<TContext>()
689
- .addAgentMeta({
690
- name: this.options.name,
691
- goal: this.options.goal,
692
- description: this.options.description,
693
- })
694
- .addPersonality(this.options.personality)
695
- .addInteractionHistory(history)
696
- .addGlossary(this.terms)
697
- .addGuidelines(this.guidelines)
698
- .addCapabilities(this.capabilities)
699
- .build();
1119
+ const fallbackPrompt = await this.responseEngine.buildFallbackPrompt({
1120
+ history,
1121
+ agentOptions: this.options,
1122
+ terms: this.terms,
1123
+ guidelines: this.guidelines,
1124
+ context: effectiveContext,
1125
+ session,
1126
+ });
700
1127
 
701
1128
  const stream = this.options.provider.generateMessageStream({
702
1129
  prompt: fallbackPrompt,
@@ -729,6 +1156,8 @@ export class Agent<TContext = unknown> {
729
1156
  session, // Return updated session
730
1157
  toolCalls: undefined,
731
1158
  isRouteComplete: false,
1159
+ metadata: chunk.metadata,
1160
+ structured: chunk.structured,
732
1161
  };
733
1162
  }
734
1163
  }
@@ -738,18 +1167,14 @@ export class Agent<TContext = unknown> {
738
1167
  * Generate a response based on history and context
739
1168
  */
740
1169
  async respond(params: {
741
- history: Event[];
1170
+ history: History;
742
1171
  step?: StepRef;
743
- session?: SessionState;
1172
+ session?: SessionState<TData>;
744
1173
  contextOverride?: Partial<TContext>;
745
1174
  signal?: AbortSignal;
746
- }): Promise<{
747
- message: string;
748
- session?: SessionState;
749
- toolCalls?: Array<{ toolName: string; arguments: Record<string, unknown> }>;
750
- isRouteComplete?: boolean;
751
- }> {
752
- const { history, contextOverride, signal } = params;
1175
+ }): Promise<AgentResponse<TData>> {
1176
+ const { history: simpleHistory, contextOverride, signal } = params;
1177
+ const history = normalizeHistory(simpleHistory);
753
1178
 
754
1179
  // Get current context (may fetch from provider)
755
1180
  let currentContext = await this.getContext();
@@ -769,60 +1194,40 @@ export class Agent<TContext = unknown> {
769
1194
 
770
1195
  // Initialize or get session (use current session if available)
771
1196
  let session =
772
- params.session || this.currentSession || createSession<TContext>();
1197
+ cloneDeep(params.session) ||
1198
+ cloneDeep(this.currentSession) ||
1199
+ (await this.session.getOrCreate());
1200
+
1201
+ // Merge agent's collected data into session (agent data takes precedence)
1202
+ if (Object.keys(this.collectedData).length > 0) {
1203
+ session = mergeCollected(session, this.collectedData);
1204
+ logger.debug("[Agent] Merged agent collected data into session:", this.collectedData);
1205
+ }
773
1206
 
774
- // PHASE 1: TOOL EXECUTION - Execute tools if current step has tool
1207
+ // PHASE 1: PREPARE - Execute prepare function if current step has one
775
1208
  if (session.currentRoute && session.currentStep) {
776
1209
  const currentRoute = this.routes.find(
777
1210
  (r) => r.id === session.currentRoute?.id
778
1211
  );
779
1212
  if (currentRoute) {
780
1213
  const currentStep = currentRoute.getStep(session.currentStep.id);
781
- if (currentStep) {
782
- const transitions = currentStep.getTransitions();
783
- const toolTransition = transitions.find((t) => t.spec.tool);
784
-
785
- if (toolTransition?.spec.tool) {
786
- const toolExecutor = new ToolExecutor<TContext, unknown>();
787
- // Get allowed domains from current route for security enforcement
788
- const allowedDomains = currentRoute.getDomains();
789
- const result = await toolExecutor.executeTool(
790
- toolTransition.spec.tool as ToolRef<TContext, unknown[], unknown>,
791
- effectiveContext,
792
- this.updateContext.bind(this),
793
- history,
794
- session.data,
795
- allowedDomains
796
- );
797
-
798
- // Update context with tool results
799
- if (result.contextUpdate) {
800
- await this.updateContext(
801
- result.contextUpdate as Partial<TContext>
802
- );
803
- }
804
-
805
- // Update collected data with tool results
806
- if (result.collectedUpdate) {
807
- session = await this.updateData(session, result.collectedUpdate);
808
- logger.debug(
809
- `[Agent] Tool updated collected data:`,
810
- result.collectedUpdate
811
- );
812
- }
813
-
814
- logger.debug(
815
- `[Agent] Executed tool: ${result.toolName} (success: ${result.success})`
816
- );
817
- }
1214
+ if (currentStep?.prepare) {
1215
+ logger.debug(`[Agent] Executing prepare for step: ${currentStep.id}`);
1216
+ await this.executePrepareFinalize(
1217
+ currentStep.prepare,
1218
+ effectiveContext,
1219
+ session.data,
1220
+ currentRoute,
1221
+ currentStep
1222
+ );
818
1223
  }
819
1224
  }
820
1225
  }
821
1226
 
822
1227
  // PHASE 2: ROUTING + STEP SELECTION - Determine which route and step to use (combined)
823
- let selectedRoute: Route<TContext> | undefined;
1228
+ let selectedRoute: Route<TContext, TData> | undefined;
824
1229
  let responseDirectives: string[] | undefined;
825
- let selectedStep: Step<TContext> | undefined;
1230
+ let selectedStep: Step<TContext, TData> | undefined;
826
1231
  let isRouteComplete = false;
827
1232
 
828
1233
  // Check for pending transition from previous route completion
@@ -866,12 +1271,7 @@ export class Agent<TContext = unknown> {
866
1271
  routes: this.routes,
867
1272
  session,
868
1273
  history,
869
- agentMeta: {
870
- name: this.options.name,
871
- goal: this.options.goal,
872
- description: this.options.description,
873
- personality: this.options.personality,
874
- },
1274
+ agentOptions: this.options,
875
1275
  provider: this.options.provider,
876
1276
  context: effectiveContext,
877
1277
  signal,
@@ -893,13 +1293,23 @@ export class Agent<TContext = unknown> {
893
1293
 
894
1294
  // PHASE 3: DETERMINE NEXT STEP - Use step from combined decision or get initial step
895
1295
  let message: string;
896
- const toolCalls:
1296
+ let toolCalls:
897
1297
  | Array<{ toolName: string; arguments: Record<string, unknown> }>
898
1298
  | undefined = undefined;
1299
+ let responsePrompt: string;
1300
+ let availableTools: Array<{
1301
+ id: string;
1302
+ name: string;
1303
+ description?: string;
1304
+ parameters?: unknown;
1305
+ }> = [];
1306
+ let responseSchema: StructuredSchema | undefined;
1307
+ let nextStep: Step<TContext, TData> | undefined;
1308
+
1309
+ // Get last user message (needed for both route and completion handling)
1310
+ const lastUserMessage = getLastMessageFromHistory(history);
899
1311
 
900
1312
  if (selectedRoute && !isRouteComplete) {
901
- let nextStep: Step<TContext>;
902
-
903
1313
  // If we have a selected step from the combined routing decision, use it
904
1314
  if (selectedStep) {
905
1315
  nextStep = selectedStep;
@@ -933,115 +1343,343 @@ export class Agent<TContext = unknown> {
933
1343
  const lastUserMessage = getLastMessageFromHistory(history);
934
1344
 
935
1345
  // Build response schema for this route (with collect fields from step)
936
- const responseSchema = this.responseEngine.responseSchemaForRoute(
1346
+ responseSchema = this.responseEngine.responseSchemaForRoute(
937
1347
  selectedRoute,
938
- nextStep
1348
+ nextStep,
1349
+ this.schema
939
1350
  );
940
1351
 
941
1352
  // Build response prompt
942
- const responsePrompt = this.responseEngine.buildResponsePrompt(
1353
+ responsePrompt = await this.responseEngine.buildResponsePrompt({
1354
+ route: selectedRoute,
1355
+ currentStep: nextStep,
1356
+ rules: selectedRoute.getRules(),
1357
+ prohibitions: selectedRoute.getProhibitions(),
1358
+ directives: responseDirectives,
1359
+ history,
1360
+ lastMessage: lastUserMessage,
1361
+ agentOptions: this.options,
1362
+ // Combine agent and route properties according to the specified logic
1363
+ combinedGuidelines: [
1364
+ ...this.getGuidelines(),
1365
+ ...selectedRoute.getGuidelines(),
1366
+ ],
1367
+ combinedTerms: this.mergeTerms(
1368
+ this.getTerms(),
1369
+ selectedRoute.getTerms()
1370
+ ),
1371
+ context: effectiveContext,
1372
+ session,
1373
+ agentSchema: this.schema,
1374
+ });
1375
+
1376
+ // Collect available tools for AI
1377
+ availableTools = this.collectAvailableTools(
943
1378
  selectedRoute,
944
- nextStep,
945
- selectedRoute.getRules(),
946
- selectedRoute.getProhibitions(),
947
- responseDirectives,
1379
+ nextStep
1380
+ );
1381
+ } else {
1382
+ // No route selected - generate basic response without route context
1383
+ logger.debug(`[Agent] No route selected, generating basic response`);
1384
+
1385
+ // Build basic response prompt without route context
1386
+ responsePrompt = await this.responseEngine.buildFallbackPrompt({
948
1387
  history,
949
- lastUserMessage,
950
- {
951
- name: this.options.name,
952
- goal: this.options.goal,
953
- description: this.options.description,
954
- personality: this.options.personality,
1388
+ agentOptions: this.options,
1389
+ terms: this.getTerms(),
1390
+ guidelines: this.getGuidelines(),
1391
+ context: effectiveContext,
1392
+ session,
1393
+ });
1394
+
1395
+ // Use agent-level tools only
1396
+ availableTools = this.collectAvailableTools();
1397
+ responseSchema = undefined;
1398
+ }
1399
+
1400
+ // Generate message using AI provider (common for both route and no-route cases)
1401
+ const result = await this.options.provider.generateMessage({
1402
+ prompt: responsePrompt,
1403
+ history,
1404
+ context: effectiveContext,
1405
+ tools: availableTools,
1406
+ signal,
1407
+ parameters: responseSchema
1408
+ ? {
1409
+ jsonSchema: responseSchema,
1410
+ schemaName: "response_output",
1411
+ }
1412
+ : undefined,
1413
+ });
1414
+
1415
+ message = result.structured?.message || result.message;
1416
+
1417
+ // Process dynamic tool calls from AI response (common for both route and no-route cases)
1418
+ if (result.structured?.toolCalls) {
1419
+ toolCalls = result.structured.toolCalls;
1420
+
1421
+ // Execute dynamic tool calls
1422
+ if (toolCalls.length > 0) {
1423
+ logger.debug(
1424
+ `[Agent] Executing ${toolCalls.length} dynamic tool calls`
1425
+ );
1426
+
1427
+ for (const toolCall of toolCalls) {
1428
+ const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
1429
+ if (!tool) {
1430
+ logger.warn(`[Agent] Tool not found: ${toolCall.toolName}`);
1431
+ continue;
1432
+ }
1433
+
1434
+ const toolExecutor = new ToolExecutor<TContext, TData>();
1435
+ const toolResult = await toolExecutor.executeTool({
1436
+ tool: tool,
1437
+ context: effectiveContext,
1438
+ updateContext: this.updateContext.bind(this),
1439
+ updateData: this.updateCollectedData.bind(this),
1440
+ history,
1441
+ data: session.data,
1442
+ toolArguments: toolCall.arguments,
1443
+ });
1444
+
1445
+ // Update context with tool results
1446
+ if (toolResult.contextUpdate) {
1447
+ await this.updateContext(
1448
+ toolResult.contextUpdate as Partial<TContext>
1449
+ );
1450
+ }
1451
+
1452
+ // Update collected data with tool results
1453
+ if (toolResult.dataUpdate) {
1454
+ session = await this.updateData(session, toolResult.dataUpdate as Partial<TData>);
1455
+ logger.debug(
1456
+ `[Agent] Tool updated collected data:`,
1457
+ toolResult.dataUpdate
1458
+ );
1459
+ }
1460
+
1461
+ logger.debug(
1462
+ `[Agent] Executed dynamic tool: ${toolResult.toolName} (success: ${toolResult.success})`
1463
+ );
955
1464
  }
1465
+ }
1466
+ }
1467
+
1468
+ // TOOL LOOP: Allow AI to make follow-up tool calls after initial tool execution
1469
+ const MAX_TOOL_LOOPS = 5;
1470
+ let toolLoopCount = 0;
1471
+ let hasToolCalls = toolCalls && toolCalls.length > 0;
1472
+
1473
+ while (hasToolCalls && toolLoopCount < MAX_TOOL_LOOPS) {
1474
+ toolLoopCount++;
1475
+ logger.debug(
1476
+ `[Agent] Starting tool loop ${toolLoopCount}/${MAX_TOOL_LOOPS}`
956
1477
  );
957
1478
 
958
- // Generate message using AI provider
959
- const result = await this.options.provider.generateMessage({
1479
+ // Add tool execution results to history so AI knows what happened
1480
+ const toolResultsEvents: Event[] = [];
1481
+ for (const toolCall of toolCalls || []) {
1482
+ const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
1483
+ if (tool) {
1484
+ toolResultsEvents.push({
1485
+ kind: EventKind.TOOL,
1486
+ source: MessageRole.AGENT,
1487
+ timestamp: new Date().toISOString(),
1488
+ data: {
1489
+ tool_calls: [
1490
+ {
1491
+ tool_id: toolCall.toolName,
1492
+ arguments: toolCall.arguments,
1493
+ result: {
1494
+ data: "Tool executed successfully",
1495
+ },
1496
+ },
1497
+ ],
1498
+ },
1499
+ });
1500
+ }
1501
+ }
1502
+
1503
+ // Create updated history with tool results
1504
+ const updatedHistory = [...history, ...toolResultsEvents];
1505
+
1506
+ // Make follow-up AI call to see if more tools are needed
1507
+ const followUpResult = await this.options.provider.generateMessage({
960
1508
  prompt: responsePrompt,
961
- history,
1509
+ history: updatedHistory,
962
1510
  context: effectiveContext,
963
- signal,
1511
+ tools: availableTools,
964
1512
  parameters: {
965
- jsonSchema: responseSchema,
966
- schemaName: "response_output",
1513
+ jsonSchema: responseSchema as StructuredSchema,
1514
+ schemaName: "tool_followup",
967
1515
  },
1516
+ signal,
968
1517
  });
969
1518
 
970
- message = result.structured?.message || result.message;
1519
+ // Check if follow-up call has more tool calls
1520
+ const followUpToolCalls = followUpResult.structured?.toolCalls;
1521
+ hasToolCalls = followUpToolCalls && followUpToolCalls.length > 0;
1522
+
1523
+ if (hasToolCalls) {
1524
+ logger.debug(
1525
+ `[Agent] Follow-up call produced ${followUpToolCalls!.length
1526
+ } additional tool calls`
1527
+ );
1528
+
1529
+ // Execute the follow-up tool calls
1530
+ for (const toolCall of followUpToolCalls!) {
1531
+ const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
1532
+ if (!tool) {
1533
+ logger.warn(
1534
+ `[Agent] Tool not found in follow-up: ${toolCall.toolName}`
1535
+ );
1536
+ continue;
1537
+ }
971
1538
 
972
- // Extract collected data from response
973
- if (result.structured && nextStep.collectFields) {
974
- const collectedData: Record<string, unknown> = {};
975
- // The structured response includes both base fields and collected extraction fields
976
- const structuredData = result.structured as AgentStructuredResponse &
977
- Record<string, unknown>;
1539
+ const toolExecutor = new ToolExecutor<TContext, TData>();
1540
+ const toolResult = await toolExecutor.executeTool({
1541
+ tool: tool,
1542
+ context: effectiveContext,
1543
+ updateContext: this.updateContext.bind(this),
1544
+ updateData: this.updateCollectedData.bind(this),
1545
+ history: updatedHistory,
1546
+ data: session.data,
1547
+ toolArguments: toolCall.arguments,
1548
+ });
1549
+
1550
+ // Update context with follow-up tool results
1551
+ if (toolResult.contextUpdate) {
1552
+ await this.updateContext(
1553
+ toolResult.contextUpdate as Partial<TContext>
1554
+ );
1555
+ }
978
1556
 
979
- for (const field of nextStep.collectFields) {
980
- if (field in structuredData) {
981
- collectedData[field] = structuredData[field];
1557
+ if (toolResult.dataUpdate) {
1558
+ session = await this.updateData(session, toolResult.dataUpdate as Partial<TData>);
1559
+ logger.debug(
1560
+ `[Agent] Follow-up tool updated collected data:`,
1561
+ toolResult.dataUpdate
1562
+ );
982
1563
  }
1564
+
1565
+ logger.debug(
1566
+ `[Agent] Executed follow-up tool: ${toolResult.toolName} (success: ${toolResult.success})`
1567
+ );
983
1568
  }
984
1569
 
985
- // Merge collected data into session
986
- if (Object.keys(collectedData).length > 0) {
987
- session = await this.updateData(session, collectedData);
988
- logger.debug(`[Agent] Collected data:`, collectedData);
1570
+ // Update toolCalls for next iteration or final response
1571
+ toolCalls = followUpToolCalls;
1572
+ } else {
1573
+ logger.debug(
1574
+ `[Agent] Tool loop completed after ${toolLoopCount} iterations`
1575
+ );
1576
+ // Update final message and toolCalls from follow-up result if no more tools
1577
+ message = followUpResult.structured?.message || followUpResult.message;
1578
+ toolCalls = followUpToolCalls || [];
1579
+ break;
1580
+ }
1581
+ }
1582
+
1583
+ if (toolLoopCount >= MAX_TOOL_LOOPS) {
1584
+ logger.warn(
1585
+ `[Agent] Tool loop limit reached (${MAX_TOOL_LOOPS}), stopping`
1586
+ );
1587
+ }
1588
+
1589
+ // Extract collected data from final response (only for route-based interactions)
1590
+ if (selectedRoute && result.structured && nextStep?.collect) {
1591
+ const collectedData: Record<string, unknown> = {};
1592
+ // The structured response includes both base fields and collected extraction fields
1593
+ const structuredData = result.structured as AgentStructuredResponse &
1594
+ Record<string, unknown>;
1595
+
1596
+ for (const field of nextStep.collect) {
1597
+ const fieldKey = String(field);
1598
+ if (fieldKey in structuredData) {
1599
+ collectedData[fieldKey] = structuredData[fieldKey];
989
1600
  }
990
1601
  }
991
1602
 
992
- // Extract any additional data from structured response
993
- if (
994
- result.structured &&
995
- typeof result.structured === "object" &&
996
- "contextUpdate" in result.structured
997
- ) {
998
- await this.updateContext(
999
- (result.structured as { contextUpdate?: Partial<TContext> })
1000
- .contextUpdate as Partial<TContext>
1001
- );
1603
+ // Merge collected data into session using agent-level data validation
1604
+ if (Object.keys(collectedData).length > 0) {
1605
+ // Update agent-level collected data with validation
1606
+ await this.updateCollectedData(collectedData as Partial<TData>);
1607
+
1608
+ // Update session with validated data
1609
+ session = await this.updateData(session, collectedData as Partial<TData>);
1610
+ logger.debug(`[Agent] Collected data:`, collectedData);
1002
1611
  }
1003
- } else if (isRouteComplete && selectedRoute) {
1612
+ }
1613
+
1614
+ // Extract any additional data from structured response
1615
+ if (
1616
+ result.structured &&
1617
+ typeof result.structured === "object" &&
1618
+ "contextUpdate" in result.structured
1619
+ ) {
1620
+ await this.updateContext(
1621
+ (result.structured as { contextUpdate?: Partial<TContext> })
1622
+ .contextUpdate as Partial<TContext>
1623
+ );
1624
+ }
1625
+
1626
+ // Handle route completion if route is complete
1627
+ if (isRouteComplete) {
1004
1628
  // Route is complete - generate completion message then check for onComplete transition
1005
- const lastUserMessage = getLastMessageFromHistory(history);
1006
1629
 
1007
1630
  // Get endStep spec from route
1008
- const endStepSpec = selectedRoute.endStepSpec;
1631
+ const endStepSpec = selectedRoute!.endStepSpec;
1009
1632
 
1010
1633
  // Create a temporary step for completion message generation using endStep configuration
1011
- const completionStep = new Step<TContext>(
1012
- selectedRoute.id,
1013
- endStepSpec.instructions ||
1014
- "Summarize what was accomplished and confirm completion",
1015
- endStepSpec.id || END_ROUTE_ID,
1016
- endStepSpec.collect,
1017
- undefined,
1018
- endStepSpec.requires,
1019
- endStepSpec.instructions ||
1020
- "Summarize what was accomplished and confirm completion based on the conversation history and collected data"
1021
- );
1634
+ const completionStep = new Step<TContext, TData>(selectedRoute!.id, {
1635
+ description: endStepSpec.description,
1636
+ id: endStepSpec.id || END_ROUTE_ID,
1637
+ collect: endStepSpec.collect,
1638
+ requires: endStepSpec.requires,
1639
+ prompt:
1640
+ endStepSpec.prompt ||
1641
+ "Summarize what was accomplished and confirm completion based on the conversation history and collected data",
1642
+ });
1643
+
1644
+ if (!selectedRoute) {
1645
+ throw new Error("Selected route is not defined");
1646
+ }
1022
1647
 
1023
1648
  // Build response schema for completion
1024
1649
  const responseSchema = this.responseEngine.responseSchemaForRoute(
1025
1650
  selectedRoute,
1026
- completionStep
1651
+ completionStep,
1652
+ this.schema
1027
1653
  );
1654
+ const templateContext = {
1655
+ context: effectiveContext,
1656
+ session,
1657
+ history,
1658
+ };
1028
1659
 
1029
1660
  // Build completion response prompt
1030
- const completionPrompt = this.responseEngine.buildResponsePrompt(
1031
- selectedRoute,
1032
- completionStep,
1033
- selectedRoute.getRules(),
1034
- selectedRoute.getProhibitions(),
1035
- undefined, // No directives for completion
1661
+ const completionPrompt = await this.responseEngine.buildResponsePrompt({
1662
+ route: selectedRoute,
1663
+ currentStep: completionStep,
1664
+ rules: selectedRoute.getRules(),
1665
+ prohibitions: selectedRoute.getProhibitions(),
1666
+ directives: undefined, // No directives for completion
1036
1667
  history,
1037
- lastUserMessage,
1038
- {
1039
- name: this.options.name,
1040
- goal: this.options.goal,
1041
- description: this.options.description,
1042
- personality: this.options.personality,
1043
- }
1044
- );
1668
+ lastMessage: lastUserMessage,
1669
+ agentOptions: this.options,
1670
+ // Combine agent and route properties according to the specified logic
1671
+ combinedGuidelines: [
1672
+ ...this.getGuidelines(),
1673
+ ...selectedRoute.getGuidelines(),
1674
+ ],
1675
+ combinedTerms: this.mergeTerms(
1676
+ this.getTerms(),
1677
+ selectedRoute.getTerms()
1678
+ ),
1679
+ context: effectiveContext,
1680
+ session,
1681
+ agentSchema: this.schema,
1682
+ });
1045
1683
 
1046
1684
  // Generate completion message using AI provider
1047
1685
  const completionResult = await this.options.provider.generateMessage({
@@ -1076,12 +1714,16 @@ export class Agent<TContext = unknown> {
1076
1714
  );
1077
1715
 
1078
1716
  if (targetRoute) {
1717
+ const renderedCondition = await render(
1718
+ transitionConfig.condition,
1719
+ templateContext
1720
+ );
1079
1721
  // Set pending transition in session
1080
1722
  session = {
1081
1723
  ...session,
1082
1724
  pendingTransition: {
1083
1725
  targetRouteId: targetRoute.id,
1084
- condition: transitionConfig.condition,
1726
+ condition: renderedCondition,
1085
1727
  reason: "route_complete",
1086
1728
  },
1087
1729
  };
@@ -1102,18 +1744,14 @@ export class Agent<TContext = unknown> {
1102
1744
  );
1103
1745
  } else {
1104
1746
  // Fallback: No routes defined, generate a simple response
1105
- const fallbackPrompt = new PromptComposer<TContext>()
1106
- .addAgentMeta({
1107
- name: this.options.name,
1108
- goal: this.options.goal,
1109
- description: this.options.description,
1110
- })
1111
- .addPersonality(this.options.personality)
1112
- .addInteractionHistory(history)
1113
- .addGlossary(this.terms)
1114
- .addGuidelines(this.guidelines)
1115
- .addCapabilities(this.capabilities)
1116
- .build();
1747
+ const fallbackPrompt = await this.responseEngine.buildFallbackPrompt({
1748
+ history,
1749
+ agentOptions: this.options,
1750
+ terms: this.terms,
1751
+ guidelines: this.guidelines,
1752
+ context: effectiveContext,
1753
+ session,
1754
+ });
1117
1755
 
1118
1756
  const result = await this.options.provider.generateMessage({
1119
1757
  prompt: fallbackPrompt,
@@ -1148,6 +1786,28 @@ export class Agent<TContext = unknown> {
1148
1786
  );
1149
1787
  }
1150
1788
 
1789
+ // Execute finalize function
1790
+ if (session.currentRoute && session.currentStep) {
1791
+ const currentRoute = this.routes.find(
1792
+ (r) => r.id === session.currentRoute?.id
1793
+ );
1794
+ if (currentRoute) {
1795
+ const currentStep = currentRoute.getStep(session.currentStep.id);
1796
+ if (currentStep?.finalize) {
1797
+ logger.debug(
1798
+ `[Agent] Executing finalize for step: ${currentStep.id}`
1799
+ );
1800
+ await this.executePrepareFinalize(
1801
+ currentStep.finalize,
1802
+ effectiveContext,
1803
+ session.data,
1804
+ currentRoute,
1805
+ currentStep
1806
+ );
1807
+ }
1808
+ }
1809
+ }
1810
+
1151
1811
  // Update current session if we have one
1152
1812
  if (this.currentSession) {
1153
1813
  this.currentSession = session;
@@ -1164,86 +1824,268 @@ export class Agent<TContext = unknown> {
1164
1824
  /**
1165
1825
  * Get all routes
1166
1826
  */
1167
- getRoutes(): Route<TContext, unknown>[] {
1827
+ getRoutes(): Route<TContext, TData>[] {
1168
1828
  return [...this.routes];
1169
1829
  }
1170
1830
 
1171
1831
  /**
1172
1832
  * Get all terms
1173
1833
  */
1174
- getTerms(): Term[] {
1834
+ getTerms(): Term<TContext, TData>[] {
1175
1835
  return [...this.terms];
1176
1836
  }
1177
1837
 
1178
1838
  /**
1179
- * Get all guidelines
1839
+ * Get all tools
1180
1840
  */
1181
- getGuidelines(): Guideline[] {
1182
- return [...this.guidelines];
1841
+ getTools(): Tool<TContext, TData, unknown[], unknown>[] {
1842
+ return [...this.tools];
1183
1843
  }
1184
1844
 
1185
1845
  /**
1186
- * Get all capabilities
1846
+ * Find an available tool by name for the given route
1847
+ * Route-level tools take precedence over agent-level tools
1848
+ * @private
1187
1849
  */
1188
- getCapabilities(): Capability[] {
1189
- return [...this.capabilities];
1850
+ private findAvailableTool(
1851
+ toolName: string,
1852
+ route?: Route<TContext, TData>
1853
+ ): Tool<TContext, TData, unknown[], unknown> | undefined {
1854
+ // Check route-level tools first (if route provided)
1855
+ if (route) {
1856
+ const routeTool = route
1857
+ .getTools()
1858
+ .find((tool) => tool.id === toolName || tool.name === toolName);
1859
+ if (routeTool) return routeTool;
1860
+ }
1861
+
1862
+ // Fall back to agent-level tools
1863
+ return this.tools.find(
1864
+ (tool) => tool.id === toolName || tool.name === toolName
1865
+ );
1190
1866
  }
1191
1867
 
1192
1868
  /**
1193
- * Get the domain registry
1869
+ * Collect all available tools for the given route and step context
1870
+ * @private
1194
1871
  */
1195
- getDomainRegistry(): DomainRegistry {
1196
- return this.domainRegistry;
1872
+ private collectAvailableTools(
1873
+ route?: Route<TContext, TData>,
1874
+ step?: Step<TContext, TData>
1875
+ ): Array<{
1876
+ id: string;
1877
+ name: string;
1878
+ description?: string;
1879
+ parameters?: unknown;
1880
+ }> {
1881
+ const availableTools = new Map<
1882
+ string,
1883
+ Tool<TContext, TData, unknown[], unknown>
1884
+ >();
1885
+
1886
+ // Add agent-level tools
1887
+ this.tools.forEach((tool) => {
1888
+ availableTools.set(tool.id, tool);
1889
+ });
1890
+
1891
+ // Add route-level tools (these take precedence)
1892
+ if (route) {
1893
+ route.getTools().forEach((tool) => {
1894
+ availableTools.set(tool.id, tool);
1895
+ });
1896
+ }
1897
+
1898
+ // Filter by step-level allowed tools if specified
1899
+ if (step?.tools) {
1900
+ const allowedToolIds = new Set<string>();
1901
+ const stepTools: Tool<TContext, TData, unknown[], unknown>[] = [];
1902
+
1903
+ for (const toolRef of step.tools) {
1904
+ if (typeof toolRef === "string") {
1905
+ // Reference to registered tool
1906
+ allowedToolIds.add(toolRef);
1907
+ } else {
1908
+ // Inline tool definition
1909
+ if (toolRef.id) {
1910
+ allowedToolIds.add(toolRef.id);
1911
+ stepTools.push(toolRef);
1912
+ }
1913
+ }
1914
+ }
1915
+
1916
+ // If step specifies tools, only include those
1917
+ if (allowedToolIds.size > 0) {
1918
+ const filteredTools = new Map<
1919
+ string,
1920
+ Tool<TContext, TData, unknown[], unknown>
1921
+ >();
1922
+ for (const toolId of allowedToolIds) {
1923
+ const tool = availableTools.get(toolId);
1924
+ if (tool) {
1925
+ filteredTools.set(toolId, tool);
1926
+ }
1927
+ }
1928
+ // Add inline tools
1929
+ stepTools.forEach((tool) => {
1930
+ if (tool.id) {
1931
+ filteredTools.set(tool.id, tool);
1932
+ }
1933
+ });
1934
+ availableTools.clear();
1935
+ filteredTools.forEach((tool, id) => availableTools.set(id, tool));
1936
+ }
1937
+ }
1938
+
1939
+ // Convert to the format expected by AI providers
1940
+ return Array.from(availableTools.values()).map((tool) => ({
1941
+ id: tool.id,
1942
+ name: tool.name || tool.id,
1943
+ description: tool.description,
1944
+ parameters: tool.parameters,
1945
+ }));
1197
1946
  }
1198
1947
 
1199
1948
  /**
1200
- * Get the persistence manager (if configured)
1949
+ * Execute a prepare or finalize function/tool
1950
+ * @private
1201
1951
  */
1202
- getPersistenceManager(): PersistenceManager | undefined {
1203
- return this.persistenceManager;
1952
+ private async executePrepareFinalize(
1953
+ prepareOrFinalize:
1954
+ | string
1955
+ | Tool<TContext, TData, unknown[], unknown>
1956
+ | ((context: TContext, data?: Partial<TData>) => void | Promise<void>)
1957
+ | undefined,
1958
+ context: TContext,
1959
+ data?: Partial<TData>,
1960
+ route?: Route<TContext, TData>,
1961
+ step?: Step<TContext, TData>
1962
+ ): Promise<void> {
1963
+ if (!prepareOrFinalize) return;
1964
+
1965
+ if (typeof prepareOrFinalize === "function") {
1966
+ // It's a function - call it directly
1967
+ await prepareOrFinalize(context, data);
1968
+ } else {
1969
+ // It's a tool reference - find and execute the tool
1970
+ let tool: Tool<TContext, TData, unknown[], unknown> | undefined;
1971
+
1972
+ if (typeof prepareOrFinalize === "string") {
1973
+ // Tool ID - find it in available tools
1974
+ const availableTools = new Map<
1975
+ string,
1976
+ Tool<TContext, TData, unknown[], unknown>
1977
+ >();
1978
+
1979
+ // Add agent-level tools
1980
+ this.tools.forEach((t) => {
1981
+ availableTools.set(t.id, t);
1982
+ });
1983
+
1984
+ // Add route-level tools
1985
+ if (route) {
1986
+ route.getTools().forEach((t) => {
1987
+ availableTools.set(t.id, t);
1988
+ });
1989
+ }
1990
+
1991
+ // Add step-level tools
1992
+ if (step?.tools) {
1993
+ for (const toolRef of step.tools) {
1994
+ if (typeof toolRef === "string") {
1995
+ // Keep as is
1996
+ } else if (toolRef.id) {
1997
+ availableTools.set(toolRef.id, toolRef);
1998
+ }
1999
+ }
2000
+ }
2001
+
2002
+ tool = availableTools.get(prepareOrFinalize);
2003
+ } else {
2004
+ // Tool object - use directly
2005
+ tool = prepareOrFinalize;
2006
+ }
2007
+
2008
+ if (tool) {
2009
+ const toolExecutor = new ToolExecutor<TContext, TData>();
2010
+ const result = await toolExecutor.executeTool({
2011
+ tool,
2012
+ context,
2013
+ updateContext: this.updateContext.bind(this),
2014
+ updateData: this.updateCollectedData.bind(this),
2015
+ history: [], // Empty history for prepare/finalize
2016
+ data,
2017
+ });
2018
+
2019
+ if (!result.success) {
2020
+ logger.error(
2021
+ `[Agent] Tool execution failed in prepare/finalize: ${result.error}`
2022
+ );
2023
+ throw new Error(`Tool execution failed: ${result.error}`);
2024
+ }
2025
+ } else {
2026
+ logger.warn(
2027
+ `[Agent] Tool not found for prepare/finalize: ${typeof prepareOrFinalize === "string"
2028
+ ? prepareOrFinalize
2029
+ : "inline tool"
2030
+ }`
2031
+ );
2032
+ }
2033
+ }
1204
2034
  }
1205
2035
 
1206
2036
  /**
1207
- * Check if persistence is enabled
2037
+ * Get all guidelines
1208
2038
  */
1209
- hasPersistence(): boolean {
1210
- return this.persistenceManager !== undefined;
2039
+ getGuidelines(): Guideline<TContext, TData>[] {
2040
+ return [...this.guidelines];
1211
2041
  }
1212
2042
 
1213
2043
  /**
1214
- * Get allowed domains for a specific route
1215
- * @param routeId - Route ID to check
1216
- * @returns Filtered domains object, or all domains if route has no restrictions
2044
+ * Get the agent's knowledge base
1217
2045
  */
1218
- getDomainsForRoute(routeId: string): Record<string, Record<string, unknown>> {
1219
- const route = this.routes.find((r) => r.id === routeId);
2046
+ getKnowledgeBase(): Record<string, unknown> {
2047
+ return { ...this.knowledgeBase };
2048
+ }
1220
2049
 
1221
- if (!route) {
1222
- // Route not found, return all domains
1223
- return this.domainRegistry.all();
1224
- }
2050
+ /**
2051
+ * Merge terms with route-specific taking precedence on conflicts
2052
+ * @private
2053
+ */
2054
+ private mergeTerms(
2055
+ agentTerms: Term<TContext, TData>[],
2056
+ routeTerms: Term<TContext, TData>[]
2057
+ ): Term<TContext, TData>[] {
2058
+ const merged = new Map<string, Term<TContext, TData>>();
2059
+
2060
+ // Add agent terms first
2061
+ agentTerms.forEach((term) => {
2062
+ const name =
2063
+ typeof term.name === "string" ? term.name : term.name.toString();
2064
+ merged.set(name, term);
2065
+ });
2066
+
2067
+ // Add route terms (these take precedence)
2068
+ routeTerms.forEach((term) => {
2069
+ const name =
2070
+ typeof term.name === "string" ? term.name : term.name.toString();
2071
+ merged.set(name, term);
2072
+ });
1225
2073
 
1226
- const allowedDomains = route.getDomains();
1227
- return this.domainRegistry.getFiltered(allowedDomains);
2074
+ return Array.from(merged.values());
1228
2075
  }
1229
2076
 
1230
2077
  /**
1231
- * Get allowed domains for a specific route by title
1232
- * @param routeTitle - Route title to check
1233
- * @returns Filtered domains object, or all domains if route has no restrictions
2078
+ * Get the persistence manager (if configured)
1234
2079
  */
1235
- getDomainsForRouteByTitle(
1236
- routeTitle: string
1237
- ): Record<string, Record<string, unknown>> {
1238
- const route = this.routes.find((r) => r.title === routeTitle);
1239
-
1240
- if (!route) {
1241
- // Route not found, return all domains
1242
- return this.domainRegistry.all();
1243
- }
2080
+ getPersistenceManager(): PersistenceManager<TData> | undefined {
2081
+ return this.persistenceManager;
2082
+ }
1244
2083
 
1245
- const allowedDomains = route.getDomains();
1246
- return this.domainRegistry.getFiltered(allowedDomains);
2084
+ /**
2085
+ * Check if persistence is enabled
2086
+ */
2087
+ hasPersistence(): boolean {
2088
+ return this.persistenceManager !== undefined;
1247
2089
  }
1248
2090
 
1249
2091
  /**
@@ -1269,21 +2111,20 @@ export class Agent<TContext = unknown> {
1269
2111
  }
1270
2112
 
1271
2113
  /**
1272
- * Get collected data from current session
2114
+ * Get collected data from current session or agent-level collected data
1273
2115
  * @param routeId - Optional route ID to get data for (uses current route if not provided)
1274
- * @returns The collected data from the current session
2116
+ * @returns The collected data from the current session or agent-level data
1275
2117
  */
1276
- getData<TData = unknown>(routeId?: string): Partial<TData> {
1277
- if (!this.currentSession) {
1278
- return {} as Partial<TData>;
1279
- }
1280
- if (routeId) {
1281
- return (
1282
- (this.currentSession.dataByRoute?.[routeId] as Partial<TData>) ||
1283
- ({} as Partial<TData>)
1284
- );
2118
+ getData(): Partial<TData> {
2119
+ // If we have a current session, use session data
2120
+ if (this.currentSession) {
2121
+ // With agent-level data, all routes share the same data structure
2122
+ // No need for route-specific data access
2123
+ return (this.currentSession.data) || {};
1285
2124
  }
1286
- return (this.currentSession.data as Partial<TData>) || {};
2125
+
2126
+ // Otherwise, return agent-level collected data
2127
+ return this.getCollectedData();
1287
2128
  }
1288
2129
 
1289
2130
  /**
@@ -1303,11 +2144,12 @@ export class Agent<TContext = unknown> {
1303
2144
  * const nextResponse = await agent.respond({ history, session: updatedSession });
1304
2145
  * }
1305
2146
  */
1306
- nextStepRoute(
2147
+ async nextStepRoute(
1307
2148
  routeIdOrTitle: string,
1308
- session?: SessionState,
1309
- condition?: string
1310
- ): SessionState {
2149
+ session?: SessionState<TData>,
2150
+ condition?: Template<TContext, TData>,
2151
+ history?: Event[]
2152
+ ): Promise<SessionState<TData>> {
1311
2153
  const targetSession = session || this.currentSession;
1312
2154
 
1313
2155
  if (!targetSession) {
@@ -1328,13 +2170,20 @@ export class Agent<TContext = unknown> {
1328
2170
  .join(", ")}`
1329
2171
  );
1330
2172
  }
2173
+ const templateContext = {
2174
+ context: this.context,
2175
+ session,
2176
+ history,
2177
+ data: this.currentSession?.data,
2178
+ };
2179
+ const renderedCondition = await render(condition, templateContext);
1331
2180
 
1332
- const updatedSession: SessionState = {
2181
+ const updatedSession: SessionState<TData> = {
1333
2182
  ...targetSession,
1334
2183
  pendingTransition: {
1335
2184
  targetRouteId: targetRoute.id,
1336
- condition,
1337
- reason: "manual",
2185
+ condition: renderedCondition,
2186
+ reason: "route_complete",
1338
2187
  },
1339
2188
  };
1340
2189
 
@@ -1344,9 +2193,65 @@ export class Agent<TContext = unknown> {
1344
2193
  }
1345
2194
 
1346
2195
  logger.debug(
1347
- `[Agent] Set pending manual transition to route: ${targetRoute.title}`
2196
+ `[Agent] Set pending transition to route: ${targetRoute.title}`
1348
2197
  );
1349
2198
 
1350
2199
  return updatedSession;
1351
2200
  }
2201
+
2202
+ /**
2203
+ * Simplified respond method using SessionManager
2204
+ * Automatically manages conversation history through the session
2205
+ */
2206
+ async chat(
2207
+ message?: string,
2208
+ options?: {
2209
+ history?: History; // Optional: override session history for this response
2210
+ contextOverride?: Partial<TContext>;
2211
+ signal?: AbortSignal;
2212
+ }
2213
+ ): Promise<AgentResponse<TData>> {
2214
+ // Determine which history to use
2215
+ let history: History;
2216
+ if (options?.history) {
2217
+ // Use provided history for this response only
2218
+ history = options.history;
2219
+ } else {
2220
+ // Add user message to session history if provided
2221
+ if (message) {
2222
+ await this.session.addMessage("user", message);
2223
+ }
2224
+ history = this.session.getHistory();
2225
+ }
2226
+
2227
+ // Get or create session
2228
+ let session = await this.session.getOrCreate();
2229
+
2230
+ // Merge agent's collected data into session (agent data takes precedence)
2231
+ if (Object.keys(this.collectedData).length > 0) {
2232
+ session = mergeCollected(session, this.collectedData);
2233
+ // Update the session manager with the merged data
2234
+ await this.session.setData(this.collectedData);
2235
+ logger.debug("[Agent] Merged agent collected data into chat session:", this.collectedData);
2236
+ }
2237
+
2238
+ // Use existing respond method with session-managed history
2239
+ const result = await this.respond({
2240
+ history,
2241
+ session,
2242
+ contextOverride: options?.contextOverride,
2243
+ signal: options?.signal,
2244
+ });
2245
+
2246
+ // Add agent response to session history (only if not using override history)
2247
+ if (!options?.history) {
2248
+ await this.session.addMessage("assistant", result.message);
2249
+ }
2250
+
2251
+ // Ensure the result includes the current session
2252
+ return {
2253
+ ...result,
2254
+ session: result.session || this.session.current,
2255
+ };
2256
+ }
1352
2257
  }