@falai/agent 0.1.0-alpha2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (499) hide show
  1. package/README.md +797 -0
  2. package/dist/cjs/package.json +1 -0
  3. package/dist/cjs/src/adapters/MemoryAdapter.d.ts +47 -0
  4. package/dist/cjs/src/adapters/MemoryAdapter.d.ts.map +1 -0
  5. package/dist/cjs/src/adapters/MemoryAdapter.js +202 -0
  6. package/dist/cjs/src/adapters/MemoryAdapter.js.map +1 -0
  7. package/dist/cjs/src/adapters/MongoAdapter.d.ts +97 -0
  8. package/dist/cjs/src/adapters/MongoAdapter.d.ts.map +1 -0
  9. package/dist/cjs/src/adapters/MongoAdapter.js +168 -0
  10. package/dist/cjs/src/adapters/MongoAdapter.js.map +1 -0
  11. package/dist/cjs/src/adapters/OpenSearchAdapter.d.ts +169 -0
  12. package/dist/cjs/src/adapters/OpenSearchAdapter.d.ts.map +1 -0
  13. package/dist/cjs/src/adapters/OpenSearchAdapter.js +458 -0
  14. package/dist/cjs/src/adapters/OpenSearchAdapter.js.map +1 -0
  15. package/dist/cjs/src/adapters/PostgreSQLAdapter.d.ts +71 -0
  16. package/dist/cjs/src/adapters/PostgreSQLAdapter.d.ts.map +1 -0
  17. package/dist/cjs/src/adapters/PostgreSQLAdapter.js +260 -0
  18. package/dist/cjs/src/adapters/PostgreSQLAdapter.js.map +1 -0
  19. package/dist/cjs/src/adapters/PrismaAdapter.d.ts +115 -0
  20. package/dist/cjs/src/adapters/PrismaAdapter.d.ts.map +1 -0
  21. package/dist/cjs/src/adapters/PrismaAdapter.js +366 -0
  22. package/dist/cjs/src/adapters/PrismaAdapter.js.map +1 -0
  23. package/dist/cjs/src/adapters/RedisAdapter.d.ts +71 -0
  24. package/dist/cjs/src/adapters/RedisAdapter.d.ts.map +1 -0
  25. package/dist/cjs/src/adapters/RedisAdapter.js +231 -0
  26. package/dist/cjs/src/adapters/RedisAdapter.js.map +1 -0
  27. package/dist/cjs/src/adapters/SQLiteAdapter.d.ts +69 -0
  28. package/dist/cjs/src/adapters/SQLiteAdapter.d.ts.map +1 -0
  29. package/dist/cjs/src/adapters/SQLiteAdapter.js +312 -0
  30. package/dist/cjs/src/adapters/SQLiteAdapter.js.map +1 -0
  31. package/dist/cjs/src/adapters/index.d.ts +17 -0
  32. package/dist/cjs/src/adapters/index.d.ts.map +1 -0
  33. package/dist/cjs/src/adapters/index.js +21 -0
  34. package/dist/cjs/src/adapters/index.js.map +1 -0
  35. package/dist/cjs/src/constants/index.d.ts +10 -0
  36. package/dist/cjs/src/constants/index.d.ts.map +1 -0
  37. package/dist/cjs/src/constants/index.js +13 -0
  38. package/dist/cjs/src/constants/index.js.map +1 -0
  39. package/dist/cjs/src/core/Agent.d.ts +232 -0
  40. package/dist/cjs/src/core/Agent.d.ts.map +1 -0
  41. package/dist/cjs/src/core/Agent.js +741 -0
  42. package/dist/cjs/src/core/Agent.js.map +1 -0
  43. package/dist/cjs/src/core/Events.d.ts +26 -0
  44. package/dist/cjs/src/core/Events.d.ts.map +1 -0
  45. package/dist/cjs/src/core/Events.js +144 -0
  46. package/dist/cjs/src/core/Events.js.map +1 -0
  47. package/dist/cjs/src/core/PersistenceManager.d.ts +98 -0
  48. package/dist/cjs/src/core/PersistenceManager.d.ts.map +1 -0
  49. package/dist/cjs/src/core/PersistenceManager.js +261 -0
  50. package/dist/cjs/src/core/PersistenceManager.js.map +1 -0
  51. package/dist/cjs/src/core/PromptComposer.d.ts +27 -0
  52. package/dist/cjs/src/core/PromptComposer.d.ts.map +1 -0
  53. package/dist/cjs/src/core/PromptComposer.js +194 -0
  54. package/dist/cjs/src/core/PromptComposer.js.map +1 -0
  55. package/dist/cjs/src/core/ResponseEngine.d.ts +32 -0
  56. package/dist/cjs/src/core/ResponseEngine.d.ts.map +1 -0
  57. package/dist/cjs/src/core/ResponseEngine.js +202 -0
  58. package/dist/cjs/src/core/ResponseEngine.js.map +1 -0
  59. package/dist/cjs/src/core/ResponseModal.d.ts +222 -0
  60. package/dist/cjs/src/core/ResponseModal.d.ts.map +1 -0
  61. package/dist/cjs/src/core/ResponseModal.js +1588 -0
  62. package/dist/cjs/src/core/ResponseModal.js.map +1 -0
  63. package/dist/cjs/src/core/ResponsePipeline.d.ts +175 -0
  64. package/dist/cjs/src/core/ResponsePipeline.d.ts.map +1 -0
  65. package/dist/cjs/src/core/ResponsePipeline.js +549 -0
  66. package/dist/cjs/src/core/ResponsePipeline.js.map +1 -0
  67. package/dist/cjs/src/core/Route.d.ts +181 -0
  68. package/dist/cjs/src/core/Route.d.ts.map +1 -0
  69. package/dist/cjs/src/core/Route.js +541 -0
  70. package/dist/cjs/src/core/Route.js.map +1 -0
  71. package/dist/cjs/src/core/RoutingEngine.d.ts +159 -0
  72. package/dist/cjs/src/core/RoutingEngine.d.ts.map +1 -0
  73. package/dist/cjs/src/core/RoutingEngine.js +961 -0
  74. package/dist/cjs/src/core/RoutingEngine.js.map +1 -0
  75. package/dist/cjs/src/core/SessionManager.d.ts +94 -0
  76. package/dist/cjs/src/core/SessionManager.d.ts.map +1 -0
  77. package/dist/cjs/src/core/SessionManager.js +239 -0
  78. package/dist/cjs/src/core/SessionManager.js.map +1 -0
  79. package/dist/cjs/src/core/Step.d.ts +170 -0
  80. package/dist/cjs/src/core/Step.d.ts.map +1 -0
  81. package/dist/cjs/src/core/Step.js +448 -0
  82. package/dist/cjs/src/core/Step.js.map +1 -0
  83. package/dist/cjs/src/core/ToolManager.d.ts +234 -0
  84. package/dist/cjs/src/core/ToolManager.d.ts.map +1 -0
  85. package/dist/cjs/src/core/ToolManager.js +1117 -0
  86. package/dist/cjs/src/core/ToolManager.js.map +1 -0
  87. package/dist/cjs/src/index.d.ts +44 -0
  88. package/dist/cjs/src/index.d.ts.map +1 -0
  89. package/dist/cjs/src/index.js +88 -0
  90. package/dist/cjs/src/index.js.map +1 -0
  91. package/dist/cjs/src/providers/AnthropicProvider.d.ts +43 -0
  92. package/dist/cjs/src/providers/AnthropicProvider.d.ts.map +1 -0
  93. package/dist/cjs/src/providers/AnthropicProvider.js +377 -0
  94. package/dist/cjs/src/providers/AnthropicProvider.js.map +1 -0
  95. package/dist/cjs/src/providers/GeminiProvider.d.ts +58 -0
  96. package/dist/cjs/src/providers/GeminiProvider.d.ts.map +1 -0
  97. package/dist/cjs/src/providers/GeminiProvider.js +489 -0
  98. package/dist/cjs/src/providers/GeminiProvider.js.map +1 -0
  99. package/dist/cjs/src/providers/OpenAIProvider.d.ts +52 -0
  100. package/dist/cjs/src/providers/OpenAIProvider.d.ts.map +1 -0
  101. package/dist/cjs/src/providers/OpenAIProvider.js +395 -0
  102. package/dist/cjs/src/providers/OpenAIProvider.js.map +1 -0
  103. package/dist/cjs/src/providers/OpenRouterProvider.d.ts +56 -0
  104. package/dist/cjs/src/providers/OpenRouterProvider.d.ts.map +1 -0
  105. package/dist/cjs/src/providers/OpenRouterProvider.js +409 -0
  106. package/dist/cjs/src/providers/OpenRouterProvider.js.map +1 -0
  107. package/dist/cjs/src/providers/index.d.ts +13 -0
  108. package/dist/cjs/src/providers/index.d.ts.map +1 -0
  109. package/dist/cjs/src/providers/index.js +16 -0
  110. package/dist/cjs/src/providers/index.js.map +1 -0
  111. package/dist/cjs/src/types/agent.d.ts +181 -0
  112. package/dist/cjs/src/types/agent.d.ts.map +1 -0
  113. package/dist/cjs/src/types/agent.js +21 -0
  114. package/dist/cjs/src/types/agent.js.map +1 -0
  115. package/dist/cjs/src/types/ai.d.ts +143 -0
  116. package/dist/cjs/src/types/ai.d.ts.map +1 -0
  117. package/dist/cjs/src/types/ai.js +6 -0
  118. package/dist/cjs/src/types/ai.js.map +1 -0
  119. package/dist/cjs/src/types/history.d.ts +178 -0
  120. package/dist/cjs/src/types/history.d.ts.map +1 -0
  121. package/dist/cjs/src/types/history.js +33 -0
  122. package/dist/cjs/src/types/history.js.map +1 -0
  123. package/dist/cjs/src/types/index.d.ts +22 -0
  124. package/dist/cjs/src/types/index.d.ts.map +1 -0
  125. package/dist/cjs/src/types/index.js +37 -0
  126. package/dist/cjs/src/types/index.js.map +1 -0
  127. package/dist/cjs/src/types/persistence.d.ts +209 -0
  128. package/dist/cjs/src/types/persistence.d.ts.map +1 -0
  129. package/dist/cjs/src/types/persistence.js +7 -0
  130. package/dist/cjs/src/types/persistence.js.map +1 -0
  131. package/dist/cjs/src/types/route.d.ts +238 -0
  132. package/dist/cjs/src/types/route.d.ts.map +1 -0
  133. package/dist/cjs/src/types/route.js +6 -0
  134. package/dist/cjs/src/types/route.js.map +1 -0
  135. package/dist/cjs/src/types/routing.d.ts +16 -0
  136. package/dist/cjs/src/types/routing.d.ts.map +1 -0
  137. package/dist/cjs/src/types/routing.js +3 -0
  138. package/dist/cjs/src/types/routing.js.map +1 -0
  139. package/dist/cjs/src/types/schema.d.ts +22 -0
  140. package/dist/cjs/src/types/schema.d.ts.map +1 -0
  141. package/dist/cjs/src/types/schema.js +3 -0
  142. package/dist/cjs/src/types/schema.js.map +1 -0
  143. package/dist/cjs/src/types/session.d.ts +65 -0
  144. package/dist/cjs/src/types/session.d.ts.map +1 -0
  145. package/dist/cjs/src/types/session.js +6 -0
  146. package/dist/cjs/src/types/session.js.map +1 -0
  147. package/dist/cjs/src/types/template.d.ts +88 -0
  148. package/dist/cjs/src/types/template.d.ts.map +1 -0
  149. package/dist/cjs/src/types/template.js +3 -0
  150. package/dist/cjs/src/types/template.js.map +1 -0
  151. package/dist/cjs/src/types/tool.d.ts +130 -0
  152. package/dist/cjs/src/types/tool.d.ts.map +1 -0
  153. package/dist/cjs/src/types/tool.js +19 -0
  154. package/dist/cjs/src/types/tool.js.map +1 -0
  155. package/dist/cjs/src/utils/clone.d.ts +8 -0
  156. package/dist/cjs/src/utils/clone.d.ts.map +1 -0
  157. package/dist/cjs/src/utils/clone.js +32 -0
  158. package/dist/cjs/src/utils/clone.js.map +1 -0
  159. package/dist/cjs/src/utils/condition.d.ts +38 -0
  160. package/dist/cjs/src/utils/condition.d.ts.map +1 -0
  161. package/dist/cjs/src/utils/condition.js +168 -0
  162. package/dist/cjs/src/utils/condition.js.map +1 -0
  163. package/dist/cjs/src/utils/event.d.ts +6 -0
  164. package/dist/cjs/src/utils/event.d.ts.map +1 -0
  165. package/dist/cjs/src/utils/event.js +20 -0
  166. package/dist/cjs/src/utils/event.js.map +1 -0
  167. package/dist/cjs/src/utils/history.d.ts +60 -0
  168. package/dist/cjs/src/utils/history.d.ts.map +1 -0
  169. package/dist/cjs/src/utils/history.js +274 -0
  170. package/dist/cjs/src/utils/history.js.map +1 -0
  171. package/dist/cjs/src/utils/id.d.ts +25 -0
  172. package/dist/cjs/src/utils/id.d.ts.map +1 -0
  173. package/dist/cjs/src/utils/id.js +70 -0
  174. package/dist/cjs/src/utils/id.js.map +1 -0
  175. package/dist/cjs/src/utils/index.d.ts +15 -0
  176. package/dist/cjs/src/utils/index.d.ts.map +1 -0
  177. package/dist/cjs/src/utils/index.js +64 -0
  178. package/dist/cjs/src/utils/index.js.map +1 -0
  179. package/dist/cjs/src/utils/json.d.ts +16 -0
  180. package/dist/cjs/src/utils/json.d.ts.map +1 -0
  181. package/dist/cjs/src/utils/json.js +47 -0
  182. package/dist/cjs/src/utils/json.js.map +1 -0
  183. package/dist/cjs/src/utils/logger.d.ts +10 -0
  184. package/dist/cjs/src/utils/logger.d.ts.map +1 -0
  185. package/dist/cjs/src/utils/logger.js +23 -0
  186. package/dist/cjs/src/utils/logger.js.map +1 -0
  187. package/dist/cjs/src/utils/retry.d.ts +10 -0
  188. package/dist/cjs/src/utils/retry.d.ts.map +1 -0
  189. package/dist/cjs/src/utils/retry.js +76 -0
  190. package/dist/cjs/src/utils/retry.js.map +1 -0
  191. package/dist/cjs/src/utils/session.d.ts +51 -0
  192. package/dist/cjs/src/utils/session.d.ts.map +1 -0
  193. package/dist/cjs/src/utils/session.js +170 -0
  194. package/dist/cjs/src/utils/session.js.map +1 -0
  195. package/dist/cjs/src/utils/template.d.ts +155 -0
  196. package/dist/cjs/src/utils/template.d.ts.map +1 -0
  197. package/dist/cjs/src/utils/template.js +383 -0
  198. package/dist/cjs/src/utils/template.js.map +1 -0
  199. package/dist/src/adapters/MemoryAdapter.d.ts +47 -0
  200. package/dist/src/adapters/MemoryAdapter.d.ts.map +1 -0
  201. package/dist/src/adapters/MemoryAdapter.js +198 -0
  202. package/dist/src/adapters/MemoryAdapter.js.map +1 -0
  203. package/dist/src/adapters/MongoAdapter.d.ts +97 -0
  204. package/dist/src/adapters/MongoAdapter.d.ts.map +1 -0
  205. package/dist/src/adapters/MongoAdapter.js +164 -0
  206. package/dist/src/adapters/MongoAdapter.js.map +1 -0
  207. package/dist/src/adapters/OpenSearchAdapter.d.ts +169 -0
  208. package/dist/src/adapters/OpenSearchAdapter.d.ts.map +1 -0
  209. package/dist/src/adapters/OpenSearchAdapter.js +454 -0
  210. package/dist/src/adapters/OpenSearchAdapter.js.map +1 -0
  211. package/dist/src/adapters/PostgreSQLAdapter.d.ts +71 -0
  212. package/dist/src/adapters/PostgreSQLAdapter.d.ts.map +1 -0
  213. package/dist/src/adapters/PostgreSQLAdapter.js +256 -0
  214. package/dist/src/adapters/PostgreSQLAdapter.js.map +1 -0
  215. package/dist/src/adapters/PrismaAdapter.d.ts +115 -0
  216. package/dist/src/adapters/PrismaAdapter.d.ts.map +1 -0
  217. package/dist/src/adapters/PrismaAdapter.js +362 -0
  218. package/dist/src/adapters/PrismaAdapter.js.map +1 -0
  219. package/dist/src/adapters/RedisAdapter.d.ts +71 -0
  220. package/dist/src/adapters/RedisAdapter.d.ts.map +1 -0
  221. package/dist/src/adapters/RedisAdapter.js +227 -0
  222. package/dist/src/adapters/RedisAdapter.js.map +1 -0
  223. package/dist/src/adapters/SQLiteAdapter.d.ts +69 -0
  224. package/dist/src/adapters/SQLiteAdapter.d.ts.map +1 -0
  225. package/dist/src/adapters/SQLiteAdapter.js +308 -0
  226. package/dist/src/adapters/SQLiteAdapter.js.map +1 -0
  227. package/dist/src/adapters/index.d.ts +17 -0
  228. package/dist/src/adapters/index.d.ts.map +1 -0
  229. package/dist/src/adapters/index.js +11 -0
  230. package/dist/src/adapters/index.js.map +1 -0
  231. package/dist/src/constants/index.d.ts +10 -0
  232. package/dist/src/constants/index.d.ts.map +1 -0
  233. package/dist/src/constants/index.js +10 -0
  234. package/dist/src/constants/index.js.map +1 -0
  235. package/dist/src/core/Agent.d.ts +232 -0
  236. package/dist/src/core/Agent.d.ts.map +1 -0
  237. package/dist/src/core/Agent.js +737 -0
  238. package/dist/src/core/Agent.js.map +1 -0
  239. package/dist/src/core/Events.d.ts +26 -0
  240. package/dist/src/core/Events.d.ts.map +1 -0
  241. package/dist/src/core/Events.js +137 -0
  242. package/dist/src/core/Events.js.map +1 -0
  243. package/dist/src/core/PersistenceManager.d.ts +98 -0
  244. package/dist/src/core/PersistenceManager.d.ts.map +1 -0
  245. package/dist/src/core/PersistenceManager.js +257 -0
  246. package/dist/src/core/PersistenceManager.js.map +1 -0
  247. package/dist/src/core/PromptComposer.d.ts +27 -0
  248. package/dist/src/core/PromptComposer.d.ts.map +1 -0
  249. package/dist/src/core/PromptComposer.js +190 -0
  250. package/dist/src/core/PromptComposer.js.map +1 -0
  251. package/dist/src/core/ResponseEngine.d.ts +32 -0
  252. package/dist/src/core/ResponseEngine.d.ts.map +1 -0
  253. package/dist/src/core/ResponseEngine.js +198 -0
  254. package/dist/src/core/ResponseEngine.js.map +1 -0
  255. package/dist/src/core/ResponseModal.d.ts +222 -0
  256. package/dist/src/core/ResponseModal.d.ts.map +1 -0
  257. package/dist/src/core/ResponseModal.js +1583 -0
  258. package/dist/src/core/ResponseModal.js.map +1 -0
  259. package/dist/src/core/ResponsePipeline.d.ts +175 -0
  260. package/dist/src/core/ResponsePipeline.d.ts.map +1 -0
  261. package/dist/src/core/ResponsePipeline.js +545 -0
  262. package/dist/src/core/ResponsePipeline.js.map +1 -0
  263. package/dist/src/core/Route.d.ts +181 -0
  264. package/dist/src/core/Route.d.ts.map +1 -0
  265. package/dist/src/core/Route.js +537 -0
  266. package/dist/src/core/Route.js.map +1 -0
  267. package/dist/src/core/RoutingEngine.d.ts +159 -0
  268. package/dist/src/core/RoutingEngine.d.ts.map +1 -0
  269. package/dist/src/core/RoutingEngine.js +957 -0
  270. package/dist/src/core/RoutingEngine.js.map +1 -0
  271. package/dist/src/core/SessionManager.d.ts +94 -0
  272. package/dist/src/core/SessionManager.d.ts.map +1 -0
  273. package/dist/src/core/SessionManager.js +235 -0
  274. package/dist/src/core/SessionManager.js.map +1 -0
  275. package/dist/src/core/Step.d.ts +170 -0
  276. package/dist/src/core/Step.d.ts.map +1 -0
  277. package/dist/src/core/Step.js +444 -0
  278. package/dist/src/core/Step.js.map +1 -0
  279. package/dist/src/core/ToolManager.d.ts +234 -0
  280. package/dist/src/core/ToolManager.d.ts.map +1 -0
  281. package/dist/src/core/ToolManager.js +1111 -0
  282. package/dist/src/core/ToolManager.js.map +1 -0
  283. package/dist/src/index.d.ts +44 -0
  284. package/dist/src/index.d.ts.map +1 -0
  285. package/dist/src/index.js +37 -0
  286. package/dist/src/index.js.map +1 -0
  287. package/dist/src/providers/AnthropicProvider.d.ts +43 -0
  288. package/dist/src/providers/AnthropicProvider.d.ts.map +1 -0
  289. package/dist/src/providers/AnthropicProvider.js +370 -0
  290. package/dist/src/providers/AnthropicProvider.js.map +1 -0
  291. package/dist/src/providers/GeminiProvider.d.ts +58 -0
  292. package/dist/src/providers/GeminiProvider.d.ts.map +1 -0
  293. package/dist/src/providers/GeminiProvider.js +485 -0
  294. package/dist/src/providers/GeminiProvider.js.map +1 -0
  295. package/dist/src/providers/OpenAIProvider.d.ts +52 -0
  296. package/dist/src/providers/OpenAIProvider.d.ts.map +1 -0
  297. package/dist/src/providers/OpenAIProvider.js +388 -0
  298. package/dist/src/providers/OpenAIProvider.js.map +1 -0
  299. package/dist/src/providers/OpenRouterProvider.d.ts +56 -0
  300. package/dist/src/providers/OpenRouterProvider.d.ts.map +1 -0
  301. package/dist/src/providers/OpenRouterProvider.js +402 -0
  302. package/dist/src/providers/OpenRouterProvider.js.map +1 -0
  303. package/dist/src/providers/index.d.ts +13 -0
  304. package/dist/src/providers/index.d.ts.map +1 -0
  305. package/dist/src/providers/index.js +9 -0
  306. package/dist/src/providers/index.js.map +1 -0
  307. package/dist/src/types/agent.d.ts +181 -0
  308. package/dist/src/types/agent.d.ts.map +1 -0
  309. package/dist/src/types/agent.js +18 -0
  310. package/dist/src/types/agent.js.map +1 -0
  311. package/dist/src/types/ai.d.ts +143 -0
  312. package/dist/src/types/ai.d.ts.map +1 -0
  313. package/dist/src/types/ai.js +5 -0
  314. package/dist/src/types/ai.js.map +1 -0
  315. package/dist/src/types/history.d.ts +178 -0
  316. package/dist/src/types/history.d.ts.map +1 -0
  317. package/dist/src/types/history.js +30 -0
  318. package/dist/src/types/history.js.map +1 -0
  319. package/dist/src/types/index.d.ts +22 -0
  320. package/dist/src/types/index.d.ts.map +1 -0
  321. package/dist/src/types/index.js +12 -0
  322. package/dist/src/types/index.js.map +1 -0
  323. package/dist/src/types/persistence.d.ts +209 -0
  324. package/dist/src/types/persistence.d.ts.map +1 -0
  325. package/dist/src/types/persistence.js +6 -0
  326. package/dist/src/types/persistence.js.map +1 -0
  327. package/dist/src/types/route.d.ts +238 -0
  328. package/dist/src/types/route.d.ts.map +1 -0
  329. package/dist/src/types/route.js +5 -0
  330. package/dist/src/types/route.js.map +1 -0
  331. package/dist/src/types/routing.d.ts +16 -0
  332. package/dist/src/types/routing.d.ts.map +1 -0
  333. package/dist/src/types/routing.js +2 -0
  334. package/dist/src/types/routing.js.map +1 -0
  335. package/dist/src/types/schema.d.ts +22 -0
  336. package/dist/src/types/schema.d.ts.map +1 -0
  337. package/dist/src/types/schema.js +2 -0
  338. package/dist/src/types/schema.js.map +1 -0
  339. package/dist/src/types/session.d.ts +65 -0
  340. package/dist/src/types/session.d.ts.map +1 -0
  341. package/dist/src/types/session.js +5 -0
  342. package/dist/src/types/session.js.map +1 -0
  343. package/dist/src/types/template.d.ts +88 -0
  344. package/dist/src/types/template.d.ts.map +1 -0
  345. package/dist/src/types/template.js +2 -0
  346. package/dist/src/types/template.js.map +1 -0
  347. package/dist/src/types/tool.d.ts +130 -0
  348. package/dist/src/types/tool.d.ts.map +1 -0
  349. package/dist/src/types/tool.js +16 -0
  350. package/dist/src/types/tool.js.map +1 -0
  351. package/dist/src/utils/clone.d.ts +8 -0
  352. package/dist/src/utils/clone.d.ts.map +1 -0
  353. package/dist/src/utils/clone.js +29 -0
  354. package/dist/src/utils/clone.js.map +1 -0
  355. package/dist/src/utils/condition.d.ts +38 -0
  356. package/dist/src/utils/condition.d.ts.map +1 -0
  357. package/dist/src/utils/condition.js +161 -0
  358. package/dist/src/utils/condition.js.map +1 -0
  359. package/dist/src/utils/event.d.ts +6 -0
  360. package/dist/src/utils/event.d.ts.map +1 -0
  361. package/dist/src/utils/event.js +17 -0
  362. package/dist/src/utils/event.js.map +1 -0
  363. package/dist/src/utils/history.d.ts +60 -0
  364. package/dist/src/utils/history.d.ts.map +1 -0
  365. package/dist/src/utils/history.js +263 -0
  366. package/dist/src/utils/history.js.map +1 -0
  367. package/dist/src/utils/id.d.ts +25 -0
  368. package/dist/src/utils/id.d.ts.map +1 -0
  369. package/dist/src/utils/id.js +64 -0
  370. package/dist/src/utils/id.js.map +1 -0
  371. package/dist/src/utils/index.d.ts +15 -0
  372. package/dist/src/utils/index.d.ts.map +1 -0
  373. package/dist/src/utils/index.js +23 -0
  374. package/dist/src/utils/index.js.map +1 -0
  375. package/dist/src/utils/json.d.ts +16 -0
  376. package/dist/src/utils/json.d.ts.map +1 -0
  377. package/dist/src/utils/json.js +43 -0
  378. package/dist/src/utils/json.js.map +1 -0
  379. package/dist/src/utils/logger.d.ts +10 -0
  380. package/dist/src/utils/logger.d.ts.map +1 -0
  381. package/dist/src/utils/logger.js +17 -0
  382. package/dist/src/utils/logger.js.map +1 -0
  383. package/dist/src/utils/retry.d.ts +10 -0
  384. package/dist/src/utils/retry.d.ts.map +1 -0
  385. package/dist/src/utils/retry.js +71 -0
  386. package/dist/src/utils/retry.js.map +1 -0
  387. package/dist/src/utils/session.d.ts +51 -0
  388. package/dist/src/utils/session.d.ts.map +1 -0
  389. package/dist/src/utils/session.js +160 -0
  390. package/dist/src/utils/session.js.map +1 -0
  391. package/dist/src/utils/template.d.ts +155 -0
  392. package/dist/src/utils/template.d.ts.map +1 -0
  393. package/dist/src/utils/template.js +374 -0
  394. package/dist/src/utils/template.js.map +1 -0
  395. package/docs/CONTRIBUTING.md +521 -0
  396. package/docs/README.md +228 -0
  397. package/docs/api/README.md +3258 -0
  398. package/docs/api/overview.md +1134 -0
  399. package/docs/architecture/data-extraction-flow.md +363 -0
  400. package/docs/core/agent/README.md +902 -0
  401. package/docs/core/agent/context-management.md +796 -0
  402. package/docs/core/agent/session-management.md +641 -0
  403. package/docs/core/ai-integration/prompt-composition.md +220 -0
  404. package/docs/core/ai-integration/providers.md +515 -0
  405. package/docs/core/ai-integration/response-processing.md +287 -0
  406. package/docs/core/conversation-flows/data-collection.md +623 -0
  407. package/docs/core/conversation-flows/route-dsl.md +502 -0
  408. package/docs/core/conversation-flows/routes.md +247 -0
  409. package/docs/core/conversation-flows/step-transitions.md +595 -0
  410. package/docs/core/conversation-flows/steps.md +154 -0
  411. package/docs/core/error-handling.md +638 -0
  412. package/docs/core/persistence/adapters.md +255 -0
  413. package/docs/core/persistence/session-storage.md +644 -0
  414. package/docs/core/routing/intelligent-routing.md +466 -0
  415. package/docs/core/tools/tool-definition.md +970 -0
  416. package/docs/core/tools/tool-scoping.md +819 -0
  417. package/docs/guides/advanced-patterns/publishing.md +186 -0
  418. package/docs/guides/error-handling-patterns.md +578 -0
  419. package/docs/guides/getting-started/README.md +696 -0
  420. package/docs/guides/migration/README.md +72 -0
  421. package/docs/guides/migration/flexible-routing-conditions.md +375 -0
  422. package/docs/guides/migration/response-modal-refactor.md +518 -0
  423. package/examples/advanced-patterns/knowledge-based-agent.ts +735 -0
  424. package/examples/advanced-patterns/persistent-onboarding.ts +728 -0
  425. package/examples/advanced-patterns/route-lifecycle-hooks.ts +556 -0
  426. package/examples/advanced-patterns/streaming-responses.ts +578 -0
  427. package/examples/ai-providers/anthropic-integration.ts +388 -0
  428. package/examples/ai-providers/openai-integration.ts +228 -0
  429. package/examples/condition-patterns/function-only-conditions.ts +365 -0
  430. package/examples/condition-patterns/mixed-array-conditions.ts +477 -0
  431. package/examples/condition-patterns/route-skipif-patterns.ts +468 -0
  432. package/examples/condition-patterns/step-skipif-patterns.ts +0 -0
  433. package/examples/condition-patterns/string-only-conditions.ts +296 -0
  434. package/examples/conversation-flows/completion-transitions.ts +318 -0
  435. package/examples/core-concepts/basic-agent.ts +503 -0
  436. package/examples/core-concepts/modern-streaming-api.ts +309 -0
  437. package/examples/core-concepts/schema-driven-extraction.ts +332 -0
  438. package/examples/core-concepts/session-management.ts +494 -0
  439. package/examples/integrations/database-integration.ts +630 -0
  440. package/examples/integrations/healthcare-integration.ts +595 -0
  441. package/examples/integrations/search-integration.ts +530 -0
  442. package/examples/integrations/server-session-management.ts +307 -0
  443. package/examples/persistence/custom-adapter.ts +529 -0
  444. package/examples/persistence/database-persistence.ts +583 -0
  445. package/examples/persistence/memory-sessions.ts +495 -0
  446. package/examples/persistence/prisma-schema.example.prisma +74 -0
  447. package/examples/persistence/redis-persistence.ts +488 -0
  448. package/examples/tools/basic-tools.ts +765 -0
  449. package/examples/tools/data-enrichment-tools.ts +593 -0
  450. package/package.json +125 -0
  451. package/src/adapters/MemoryAdapter.ts +273 -0
  452. package/src/adapters/MongoAdapter.ts +304 -0
  453. package/src/adapters/OpenSearchAdapter.ts +670 -0
  454. package/src/adapters/PostgreSQLAdapter.ts +428 -0
  455. package/src/adapters/PrismaAdapter.ts +553 -0
  456. package/src/adapters/RedisAdapter.ts +377 -0
  457. package/src/adapters/SQLiteAdapter.ts +459 -0
  458. package/src/adapters/index.ts +43 -0
  459. package/src/constants/index.ts +10 -0
  460. package/src/core/Agent.ts +970 -0
  461. package/src/core/Events.ts +164 -0
  462. package/src/core/PersistenceManager.ts +353 -0
  463. package/src/core/PromptComposer.ts +253 -0
  464. package/src/core/ResponseEngine.ts +306 -0
  465. package/src/core/ResponseModal.ts +2050 -0
  466. package/src/core/ResponsePipeline.ts +864 -0
  467. package/src/core/Route.ts +677 -0
  468. package/src/core/RoutingEngine.ts +1396 -0
  469. package/src/core/SessionManager.ts +297 -0
  470. package/src/core/Step.ts +593 -0
  471. package/src/core/ToolManager.ts +1394 -0
  472. package/src/index.ts +155 -0
  473. package/src/providers/AnthropicProvider.ts +560 -0
  474. package/src/providers/GeminiProvider.ts +683 -0
  475. package/src/providers/OpenAIProvider.ts +602 -0
  476. package/src/providers/OpenRouterProvider.ts +613 -0
  477. package/src/providers/index.ts +16 -0
  478. package/src/types/agent.ts +196 -0
  479. package/src/types/ai.ts +158 -0
  480. package/src/types/history.ts +206 -0
  481. package/src/types/index.ts +119 -0
  482. package/src/types/persistence.ts +251 -0
  483. package/src/types/route.ts +272 -0
  484. package/src/types/routing.ts +18 -0
  485. package/src/types/schema.ts +23 -0
  486. package/src/types/session.ts +74 -0
  487. package/src/types/template.ts +104 -0
  488. package/src/types/tool.ts +174 -0
  489. package/src/utils/clone.ts +34 -0
  490. package/src/utils/condition.ts +190 -0
  491. package/src/utils/event.ts +16 -0
  492. package/src/utils/history.ts +306 -0
  493. package/src/utils/id.ts +73 -0
  494. package/src/utils/index.ts +69 -0
  495. package/src/utils/json.ts +46 -0
  496. package/src/utils/logger.ts +19 -0
  497. package/src/utils/retry.ts +97 -0
  498. package/src/utils/session.ts +204 -0
  499. package/src/utils/template.ts +444 -0
@@ -0,0 +1,1396 @@
1
+ import type {
2
+ Event,
3
+ AgentOptions,
4
+ StructuredSchema,
5
+ RoutingDecision,
6
+ SessionState,
7
+ AiProvider,
8
+ TemplateContext,
9
+ } from "../types";
10
+ import { enterRoute, mergeCollected } from "../utils";
11
+ import type { Route } from "./Route";
12
+ import type { Step } from "./Step";
13
+ import { PromptComposer } from "./PromptComposer";
14
+ import { END_ROUTE_ID } from "../constants";
15
+ import { createTemplateContext, getLastMessageFromHistory, logger } from "../utils";
16
+
17
+ export interface CandidateStep<TContext = unknown, TData = unknown> {
18
+ step: Step<TContext, TData>;
19
+ isRouteComplete?: boolean;
20
+ }
21
+
22
+ export interface RoutingDecisionOutput {
23
+ context: string;
24
+ routes: Record<string, number>;
25
+ selectedStepId?: string; // For active route, which step to transition to
26
+ stepReasoning?: string; // Why this step was selected
27
+ responseDirectives?: string[];
28
+ extractions?: Array<{
29
+ name: string;
30
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
+ value: any;
32
+ confidence?: number;
33
+ source?: "message" | "history";
34
+ }>;
35
+ contextUpdate?: Record<string, unknown>;
36
+ }
37
+
38
+ export interface RoutingEngineOptions {
39
+ allowRouteSwitch?: boolean;
40
+ switchThreshold?: number; // 0-100
41
+ maxCandidates?: number;
42
+ }
43
+
44
+ export interface BuildStepSelectionPromptParams<
45
+ TContext = unknown,
46
+ TData = unknown
47
+ > {
48
+ route: Route<TContext, TData>;
49
+ currentStep: Step<TContext, TData> | undefined;
50
+ candidates: CandidateStep<TContext, TData>[];
51
+ data: Partial<TData>;
52
+ history: Event[];
53
+ lastMessage: string;
54
+ agentOptions?: AgentOptions<TContext, TData>;
55
+ context?: TContext;
56
+ session?: SessionState<TData>;
57
+ stepConditionContext?: string[]; // AI context strings from step conditions
58
+ }
59
+
60
+ export interface BuildRoutingPromptParams<TContext = unknown, TData = unknown> {
61
+ history: Event[];
62
+ routes: Route<TContext, TData>[];
63
+ lastMessage: string;
64
+ agentOptions?: AgentOptions<TContext, TData>;
65
+ session?: SessionState<TData>;
66
+ activeRouteSteps?: Step<TContext, TData>[];
67
+ context?: TContext;
68
+ routeConditionContext?: string[]; // AI context strings from route conditions
69
+ }
70
+
71
+ export class RoutingEngine<TContext = unknown, TData = unknown> {
72
+ constructor(private readonly options?: RoutingEngineOptions) { }
73
+
74
+ /**
75
+ * Enter a route if not already in it, merging initial data
76
+ * @private
77
+ */
78
+ private enterRouteIfNeeded(
79
+ session: SessionState<TData>,
80
+ route: Route<TContext, TData>
81
+ ): SessionState<TData> {
82
+ if (!session.currentRoute || session.currentRoute.id !== route.id) {
83
+ let updatedSession = enterRoute(session, route.id, route.title);
84
+ if (route.initialData) {
85
+ updatedSession = mergeCollected(updatedSession, route.initialData);
86
+ logger.debug(
87
+ `[RoutingEngine] Merged initial data for route ${route.title}:`,
88
+ route.initialData
89
+ );
90
+ }
91
+ logger.debug(`[RoutingEngine] Entered route: ${route.title}`);
92
+ return updatedSession;
93
+ }
94
+ return session;
95
+ }
96
+
97
+ /**
98
+ * Optimized decision for single-route scenarios
99
+ * Skips route scoring and only does step selection
100
+ * @private
101
+ */
102
+ private async decideSingleRouteStep(params: {
103
+ route: Route<TContext, TData>;
104
+ session: SessionState<TData>;
105
+ history: Event[];
106
+ agentOptions?: AgentOptions<TContext, TData>;
107
+ provider: AiProvider;
108
+ context: TContext;
109
+ signal?: AbortSignal;
110
+ }): Promise<{
111
+ selectedRoute?: Route<TContext, TData>;
112
+ selectedStep?: Step<TContext, TData>;
113
+ responseDirectives?: string[];
114
+ session: SessionState<TData>;
115
+ isRouteComplete?: boolean;
116
+ completedRoutes?: Route<TContext, TData>[];
117
+ }> {
118
+ const { route, session, history, agentOptions, provider, context, signal } =
119
+ params;
120
+
121
+ const selectedRoute = route;
122
+
123
+ // Enter route if not already in it (this may merge initial data)
124
+ const updatedSession = this.enterRouteIfNeeded(session, route);
125
+
126
+ // Check if this single route is complete (use updated session data)
127
+ const completedRoutes = route.isComplete(updatedSession.data || {}) ? [route] : [];
128
+
129
+ // Get candidate steps using new condition evaluation
130
+ const templateContext = createTemplateContext({
131
+ context,
132
+ session: updatedSession,
133
+ history,
134
+ data: updatedSession.data
135
+ });
136
+ const currentStep = updatedSession.currentStep
137
+ ? route.getStep(updatedSession.currentStep.id)
138
+ : undefined;
139
+ const candidates = await this.getCandidateStepsWithConditions(
140
+ route,
141
+ currentStep,
142
+ templateContext
143
+ );
144
+
145
+ if (candidates.length === 0) {
146
+ logger.warn(`[RoutingEngine] Single-route: No valid steps found`);
147
+ return { selectedRoute, session: updatedSession };
148
+ }
149
+
150
+ // If only one candidate, check if it's a completion marker
151
+ if (candidates.length === 1) {
152
+ const candidate = candidates[0];
153
+
154
+ if (candidate.isRouteComplete) {
155
+ logger.debug(
156
+ `[RoutingEngine] Single-route: Route complete - all required fields collected or END_ROUTE reached`
157
+ );
158
+ // Don't return a selectedStep when route is complete - there's no step to enter
159
+ return {
160
+ selectedRoute,
161
+ selectedStep: undefined,
162
+ session: updatedSession,
163
+ isRouteComplete: true,
164
+ completedRoutes,
165
+ };
166
+ } else {
167
+ logger.debug(
168
+ `[RoutingEngine] Single-route: Only one valid step: ${candidate.step.id}`
169
+ );
170
+ return {
171
+ selectedRoute,
172
+ selectedStep: candidate.step,
173
+ session: updatedSession,
174
+ isRouteComplete: false,
175
+ completedRoutes,
176
+ };
177
+ }
178
+ }
179
+
180
+ // No candidates means route is likely complete or has no valid next steps
181
+ if (candidates.length === 0) {
182
+ const dataComplete = route.isComplete(updatedSession.data || {});
183
+ logger.debug(
184
+ `[RoutingEngine] Single-route: No valid steps found - ` +
185
+ `(data: ${dataComplete ? 'complete' : 'incomplete'}, marking as ${dataComplete ? 'complete' : 'incomplete'})`
186
+ );
187
+ return {
188
+ selectedRoute,
189
+ selectedStep: undefined,
190
+ session: updatedSession,
191
+ isRouteComplete: dataComplete,
192
+ completedRoutes,
193
+ };
194
+ }
195
+
196
+ // Multiple candidates - use AI to select best step
197
+ const lastUserMessage = getLastMessageFromHistory(history);
198
+
199
+ // Collect AI context strings from step conditions
200
+ const stepConditionContext: string[] = [];
201
+ for (const candidate of candidates) {
202
+ const whenResult = await candidate.step.evaluateWhen(templateContext);
203
+ stepConditionContext.push(...whenResult.aiContextStrings);
204
+ }
205
+
206
+ // Check if any candidate is a completion marker (isRouteComplete = true)
207
+ const hasCompletionOption = candidates.some(c => c.isRouteComplete);
208
+
209
+ const stepPrompt = await this.buildStepSelectionPrompt({
210
+ route,
211
+ currentStep,
212
+ candidates,
213
+ data: updatedSession.data || {},
214
+ history,
215
+ lastMessage: lastUserMessage,
216
+ agentOptions,
217
+ context,
218
+ session: updatedSession,
219
+ stepConditionContext,
220
+ includeEndRoute: hasCompletionOption,
221
+ });
222
+
223
+ const stepSchema = this.buildStepSelectionSchema(
224
+ candidates.filter(c => !c.isRouteComplete).map((c) => c.step),
225
+ hasCompletionOption
226
+ );
227
+
228
+ const stepResult = await provider.generateMessage<
229
+ TContext,
230
+ {
231
+ reasoning: string;
232
+ selectedStepId: string;
233
+ responseDirectives?: string[];
234
+ }
235
+ >({
236
+ prompt: stepPrompt,
237
+ history,
238
+ context,
239
+ signal,
240
+ parameters: {
241
+ jsonSchema: stepSchema,
242
+ schemaName: "step_selection",
243
+ },
244
+ });
245
+
246
+ const selectedStepId = stepResult.structured?.selectedStepId;
247
+
248
+ // Check if AI selected END_ROUTE
249
+ if (selectedStepId === END_ROUTE_ID) {
250
+ logger.debug(
251
+ `[RoutingEngine] Single-route: AI selected END_ROUTE - completing route`
252
+ );
253
+ logger.debug(
254
+ `[RoutingEngine] Single-route: Reasoning: ${stepResult.structured?.reasoning}`
255
+ );
256
+ return {
257
+ selectedRoute,
258
+ selectedStep: undefined,
259
+ responseDirectives: stepResult.structured?.responseDirectives,
260
+ session: updatedSession,
261
+ isRouteComplete: true,
262
+ completedRoutes,
263
+ };
264
+ }
265
+
266
+ const selectedStep = candidates.find((c) => c.step.id === selectedStepId);
267
+
268
+ if (selectedStep) {
269
+ logger.debug(
270
+ `[RoutingEngine] Single-route: AI selected step: ${selectedStep.step.id}`
271
+ );
272
+ logger.debug(
273
+ `[RoutingEngine] Single-route: Reasoning: ${stepResult.structured?.reasoning}`
274
+ );
275
+ } else {
276
+ logger.warn(
277
+ `[RoutingEngine] Single-route: Invalid step ID returned, using first candidate`
278
+ );
279
+ }
280
+
281
+ return {
282
+ selectedRoute,
283
+ selectedStep: selectedStep?.step || candidates[0].step,
284
+ responseDirectives: stepResult.structured?.responseDirectives,
285
+ session: updatedSession,
286
+ completedRoutes,
287
+ };
288
+ }
289
+
290
+ /**
291
+ * Recursively traverse step chain to find first non-skipped step or END_ROUTE using new condition evaluation
292
+ * @private
293
+ */
294
+ private async findFirstValidStepRecursiveWithConditions(
295
+ currentStep: Step<TContext, TData>,
296
+ templateContext: TemplateContext<TContext, TData>,
297
+ visited: Set<string>
298
+ ): Promise<{
299
+ step?: Step<TContext, TData>;
300
+ isRouteComplete?: boolean;
301
+ aiContextStrings?: string[];
302
+ }> {
303
+ // Prevent infinite loops
304
+ if (visited.has(currentStep.id)) {
305
+ return { aiContextStrings: [] };
306
+ }
307
+ visited.add(currentStep.id);
308
+
309
+ const transitions = currentStep.getTransitions();
310
+ const allAiContextStrings: string[] = [];
311
+
312
+ for (const transition of transitions) {
313
+ const target = transition;
314
+
315
+ // Check for END_ROUTE transition
316
+ if (target && target.id === END_ROUTE_ID) {
317
+ // Found END_ROUTE - route is complete
318
+ return {
319
+ isRouteComplete: true,
320
+ aiContextStrings: allAiContextStrings,
321
+ };
322
+ }
323
+
324
+ if (!target) continue;
325
+
326
+ // Evaluate skipIf condition using new system
327
+ const skipResult = await target.evaluateSkipIf(templateContext);
328
+ allAiContextStrings.push(...skipResult.aiContextStrings);
329
+
330
+ // If target should NOT be skipped, we found our step
331
+ if (!skipResult.shouldSkip) {
332
+ logger.debug(
333
+ `[RoutingEngine] Found valid step after skipping: ${target.id}`
334
+ );
335
+ return {
336
+ step: target,
337
+ isRouteComplete: false,
338
+ aiContextStrings: allAiContextStrings,
339
+ };
340
+ }
341
+
342
+ // Target should be skipped too - recurse deeper
343
+ logger.debug(
344
+ `[RoutingEngine] Skipping step ${target.id} (skipIf condition met), continuing traversal...`
345
+ );
346
+ const result = await this.findFirstValidStepRecursiveWithConditions(target, templateContext, visited);
347
+
348
+ // Collect AI context from recursive call
349
+ if (result.aiContextStrings) {
350
+ allAiContextStrings.push(...result.aiContextStrings);
351
+ }
352
+
353
+ // If we found something (a valid step or END_ROUTE), return it
354
+ if (result.step || result.isRouteComplete) {
355
+ return {
356
+ ...result,
357
+ aiContextStrings: allAiContextStrings,
358
+ };
359
+ }
360
+ }
361
+
362
+ // No valid steps or END_ROUTE found in this branch
363
+ return { aiContextStrings: allAiContextStrings };
364
+ }
365
+
366
+
367
+
368
+ /**
369
+ * Identify valid next candidate steps using new condition evaluation system
370
+ * Returns step with isRouteComplete flag if route is complete (all steps skipped + has END_ROUTE transition)
371
+ *
372
+ * NEW: Automatically completes route when all required fields are collected
373
+ */
374
+ async getCandidateStepsWithConditions(
375
+ route: Route<TContext, TData>,
376
+ currentStep: Step<TContext, TData> | undefined,
377
+ templateContext: TemplateContext<TContext, TData>
378
+ ): Promise<CandidateStep<TContext, TData>[]> {
379
+ const candidates: CandidateStep<TContext, TData>[] = [];
380
+ const data = templateContext.data || {};
381
+
382
+ // Check if all required fields are collected
383
+ const allRequiredFieldsCollected = route.isComplete(data);
384
+
385
+ if (!currentStep) {
386
+ // Entering route for the first time
387
+
388
+ // If all required fields already collected, route is immediately complete
389
+ if (allRequiredFieldsCollected) {
390
+ logger.debug(
391
+ `[RoutingEngine] Route ${route.title} complete on entry: all required fields already collected`
392
+ );
393
+ // Return a completion marker - use initial step with completion flag
394
+ candidates.push({
395
+ step: route.initialStep,
396
+ isRouteComplete: true,
397
+ });
398
+ return candidates;
399
+ }
400
+
401
+ const initialStep = route.initialStep;
402
+ const skipResult = await initialStep.evaluateSkipIf(templateContext);
403
+
404
+ if (skipResult.shouldSkip) {
405
+ // Initial step should be skipped - recursively traverse to find first non-skipped step or END_ROUTE
406
+ const result = await this.findFirstValidStepRecursiveWithConditions(
407
+ initialStep,
408
+ templateContext,
409
+ new Set<string>()
410
+ );
411
+
412
+ if (result.isRouteComplete) {
413
+ // All steps are skipped and we reached END_ROUTE
414
+ logger.debug(
415
+ `[RoutingEngine] Route complete on entry: all steps skipped, END_ROUTE reached`
416
+ );
417
+ candidates.push({
418
+ step: initialStep,
419
+ isRouteComplete: true,
420
+ });
421
+ } else if (result.step) {
422
+ // Found a non-skipped step
423
+ candidates.push({
424
+ step: result.step,
425
+ isRouteComplete: result.isRouteComplete || false,
426
+ });
427
+ }
428
+ // If no step found and not complete, fall through to return empty candidates
429
+ } else {
430
+ candidates.push({
431
+ step: initialStep,
432
+ isRouteComplete: false,
433
+ });
434
+ }
435
+ return candidates;
436
+ }
437
+
438
+ // Check if all required fields are now collected (may have been collected during this step)
439
+ if (allRequiredFieldsCollected) {
440
+ // Required fields are complete - check if we should continue for optional fields
441
+ const transitions = currentStep.getTransitions();
442
+ const optionalFieldCandidates: CandidateStep<TContext, TData>[] = [];
443
+
444
+ for (const transition of transitions) {
445
+ const target = transition;
446
+
447
+ // Check for END_ROUTE transition
448
+ if (target && target.id === END_ROUTE_ID) {
449
+ continue;
450
+ }
451
+
452
+ if (!target) continue;
453
+
454
+ // Check if this step collects only optional fields
455
+ const collectsOnlyOptional = target.collect && target.collect.length > 0 &&
456
+ target.collect.every(field =>
457
+ route.optionalFields?.includes(field)
458
+ );
459
+
460
+ if (collectsOnlyOptional) {
461
+ // This step collects optional fields - it's a candidate
462
+ const skipResult = await target.evaluateSkipIf(templateContext);
463
+ if (!skipResult.shouldSkip) {
464
+ optionalFieldCandidates.push({
465
+ step: target,
466
+ isRouteComplete: false,
467
+ });
468
+ }
469
+ }
470
+ }
471
+
472
+ // If we have optional field candidates, include them along with END_ROUTE option
473
+ if (optionalFieldCandidates.length > 0) {
474
+ logger.debug(
475
+ `[RoutingEngine] Required fields complete, but ${optionalFieldCandidates.length} optional field steps available`
476
+ );
477
+ // Add optional field steps as candidates
478
+ candidates.push(...optionalFieldCandidates);
479
+ // Also add END_ROUTE as a candidate (AI can choose to skip optional fields)
480
+ candidates.push({
481
+ step: currentStep,
482
+ isRouteComplete: true,
483
+ });
484
+ return candidates;
485
+ }
486
+
487
+ // No optional fields to collect - route is complete
488
+ logger.debug(
489
+ `[RoutingEngine] Route ${route.title} complete: all required fields collected, no optional fields remain`
490
+ );
491
+ return [
492
+ {
493
+ step: currentStep,
494
+ isRouteComplete: true,
495
+ },
496
+ ];
497
+ }
498
+
499
+ // Required fields not yet complete - continue normal step progression
500
+ const transitions = currentStep.getTransitions();
501
+ let hasEndRoute = false;
502
+
503
+ for (const transition of transitions) {
504
+ const target = transition;
505
+
506
+ // Check for END_ROUTE transition (no target step)
507
+ if (target && target.id === END_ROUTE_ID) {
508
+ hasEndRoute = true;
509
+ continue;
510
+ }
511
+
512
+ if (!target) continue;
513
+
514
+ const skipResult = await target.evaluateSkipIf(templateContext);
515
+
516
+ if (skipResult.shouldSkip) {
517
+ logger.debug(
518
+ `[RoutingEngine] Skipping step ${target.id} (skipIf condition met)`
519
+ );
520
+
521
+ // Recursively traverse to find next valid step or END_ROUTE
522
+ const result = await this.findFirstValidStepRecursiveWithConditions(
523
+ target,
524
+ templateContext,
525
+ new Set<string>([currentStep.id]) // Already visited current step
526
+ );
527
+
528
+ if (result.isRouteComplete) {
529
+ hasEndRoute = true;
530
+ } else if (result.step) {
531
+ // Found a non-skipped step deeper in the chain
532
+ candidates.push({
533
+ step: result.step,
534
+ isRouteComplete: result.isRouteComplete || false,
535
+ });
536
+ }
537
+ continue;
538
+ }
539
+
540
+ candidates.push({
541
+ step: target,
542
+ isRouteComplete: hasEndRoute || false,
543
+ });
544
+ }
545
+
546
+ // If no valid candidates found
547
+ if (candidates.length === 0) {
548
+ // If current step has END_ROUTE transition, the route is complete
549
+ if (hasEndRoute) {
550
+ logger.debug(
551
+ `[RoutingEngine] Route complete: all steps processed, END_ROUTE reached`
552
+ );
553
+ // Return current step with completion flag
554
+ return [
555
+ {
556
+ step: currentStep,
557
+ isRouteComplete: true,
558
+ },
559
+ ];
560
+ }
561
+
562
+ // Otherwise, stay in current step if it's still valid
563
+ const currentSkipResult = await currentStep.evaluateSkipIf(templateContext);
564
+ if (!currentSkipResult.shouldSkip) {
565
+ candidates.push({
566
+ step: currentStep,
567
+ isRouteComplete: hasEndRoute || false,
568
+ });
569
+ }
570
+ }
571
+
572
+ return candidates;
573
+ }
574
+
575
+ /**
576
+ * Full routing orchestration: builds prompt and schema, calls AI, selects route/step,
577
+ * and updates the session (including initialData merge when entering a new route).
578
+ *
579
+ * OPTIMIZATION: If there's only 1 route, skips route scoring and only does step selection.
580
+ * CROSS-ROUTE COMPLETION: Evaluates all routes for completion based on collected data.
581
+ */
582
+ async decideRouteAndStep(params: {
583
+ routes: Route<TContext, TData>[];
584
+ session: SessionState<TData>;
585
+ history: Event[];
586
+ agentOptions?: AgentOptions<TContext, TData>;
587
+ provider: AiProvider;
588
+ context: TContext;
589
+ signal?: AbortSignal;
590
+ }): Promise<{
591
+ selectedRoute?: Route<TContext, TData>;
592
+ selectedStep?: Step<TContext, TData>;
593
+ responseDirectives?: string[];
594
+ session: SessionState<TData>;
595
+ isRouteComplete?: boolean;
596
+ completedRoutes?: Route<TContext, TData>[];
597
+ }> {
598
+ const {
599
+ routes,
600
+ session,
601
+ history,
602
+ agentOptions,
603
+ provider,
604
+ context,
605
+ signal,
606
+ } = params;
607
+
608
+ if (routes.length === 0) {
609
+ return { session };
610
+ }
611
+
612
+ // CROSS-ROUTE COMPLETION EVALUATION: Check all routes for completion
613
+ const completedRoutes = this.evaluateRouteCompletions(routes, session.data || {});
614
+
615
+ // Log completed routes
616
+ if (completedRoutes.length > 0) {
617
+ logger.debug(
618
+ `[RoutingEngine] Found ${completedRoutes.length} completed routes: ${completedRoutes.map(r => r.title).join(', ')}`
619
+ );
620
+ }
621
+
622
+ // OPTIMIZATION: Single route - skip route scoring, only do step selection
623
+ if (routes.length === 1) {
624
+ const result = await this.decideSingleRouteStep({
625
+ route: routes[0],
626
+ session,
627
+ history,
628
+ agentOptions,
629
+ provider,
630
+ context,
631
+ signal,
632
+ });
633
+ return {
634
+ ...result,
635
+ completedRoutes,
636
+ };
637
+ }
638
+
639
+ const lastUserMessage = getLastMessageFromHistory(history);
640
+ const templateContext = createTemplateContext({
641
+ context,
642
+ session,
643
+ history,
644
+ data: session.data
645
+ });
646
+
647
+ // Apply route filtering with new condition evaluation system
648
+ const skipIfResult = await this.filterRoutesBySkipIf(routes, templateContext);
649
+ const whenResult = await this.filterRoutesByWhen(skipIfResult.eligibleRoutes, templateContext);
650
+
651
+ // Collect all AI context strings from route conditions
652
+ const routeConditionContext = [...skipIfResult.aiContextStrings, ...whenResult.aiContextStrings];
653
+
654
+ // Use filtered routes for further processing
655
+ const eligibleRoutes = whenResult.eligibleRoutes;
656
+
657
+ logger.debug(`[RoutingEngine] Route filtering: ${routes.length} total → ${skipIfResult.eligibleRoutes.length} after skipIf → ${eligibleRoutes.length} after when`);
658
+
659
+ let activeRouteSteps: Step<TContext, TData>[] | undefined;
660
+ let activeRoute: Route<TContext, TData> | undefined;
661
+ let isRouteComplete = false;
662
+ let updatedSession = session;
663
+
664
+ if (session.currentRoute) {
665
+ activeRoute = eligibleRoutes.find((r) => r.id === session.currentRoute?.id);
666
+ if (activeRoute) {
667
+ const currentStep = session.currentStep
668
+ ? activeRoute.getStep(session.currentStep.id)
669
+ : undefined;
670
+ const activeTemplateContext = createTemplateContext({
671
+ ...templateContext,
672
+ session: updatedSession,
673
+ data: updatedSession.data
674
+ });
675
+ const candidates = await this.getCandidateStepsWithConditions(
676
+ activeRoute,
677
+ currentStep,
678
+ activeTemplateContext
679
+ );
680
+
681
+ // Check if route is complete
682
+ // getCandidateStepsWithConditions now automatically handles completion when required fields are collected
683
+ if (candidates.length === 1 && candidates[0].isRouteComplete) {
684
+ isRouteComplete = true;
685
+ logger.debug(
686
+ `[RoutingEngine] Route ${activeRoute.title} is complete - all required fields collected or END_ROUTE reached`
687
+ );
688
+ // Don't include steps in routing if route is complete
689
+ activeRouteSteps = undefined;
690
+ } else if (candidates.length === 0) {
691
+ // No candidates - check if data is complete
692
+ const dataComplete = activeRoute.isComplete(updatedSession.data || {});
693
+ isRouteComplete = dataComplete;
694
+ logger.debug(
695
+ `[RoutingEngine] Route ${activeRoute.title} has no valid steps - ` +
696
+ `marking as ${isRouteComplete ? 'complete' : 'incomplete'}`
697
+ );
698
+ activeRouteSteps = undefined;
699
+ } else {
700
+ // Multiple candidates or single non-complete candidate
701
+ activeRouteSteps = candidates.map((c) => c.step);
702
+ logger.debug(
703
+ `[RoutingEngine] Found ${activeRouteSteps.length} candidate steps for active route`
704
+ );
705
+ }
706
+ }
707
+ }
708
+
709
+ const routingSchema = this.buildDynamicRoutingSchema(
710
+ eligibleRoutes,
711
+ undefined,
712
+ activeRouteSteps
713
+ );
714
+
715
+ const routingPrompt = await this.buildRoutingPrompt({
716
+ history,
717
+ routes: eligibleRoutes,
718
+ lastMessage: lastUserMessage,
719
+ agentOptions,
720
+ session,
721
+ activeRouteSteps,
722
+ context,
723
+ routeConditionContext, // Pass AI context strings from route conditions
724
+ });
725
+
726
+ const routingResult = await provider.generateMessage<
727
+ TContext,
728
+ RoutingDecisionOutput
729
+ >({
730
+ prompt: routingPrompt,
731
+ history,
732
+ context,
733
+ signal,
734
+ parameters: {
735
+ jsonSchema: routingSchema,
736
+ schemaName: "routing_output",
737
+ },
738
+ });
739
+
740
+ let selectedRoute: Route<TContext, TData> | undefined;
741
+ let selectedStep: Step<TContext, TData> | undefined;
742
+ let responseDirectives: string[] | undefined;
743
+
744
+ if (routingResult.structured?.routes) {
745
+ // Use cross-route completion evaluation to select optimal route
746
+ const optimalRoute = this.selectOptimalRoute(
747
+ eligibleRoutes,
748
+ updatedSession.data || {},
749
+ routingResult.structured.routes
750
+ );
751
+
752
+ // If no optimal route found, check why
753
+ if (!optimalRoute) {
754
+ if (eligibleRoutes.length === 0) {
755
+ // No routes passed filtering
756
+ logger.debug(
757
+ `[RoutingEngine] No eligible routes available - all routes filtered out`
758
+ );
759
+ selectedRoute = undefined;
760
+ } else {
761
+ // Routes exist but selectOptimalRoute returned undefined
762
+ // This means all routes are 100% complete
763
+ logger.debug(
764
+ `[RoutingEngine] No optimal route found - all ${eligibleRoutes.length} eligible routes are complete`
765
+ );
766
+ selectedRoute = undefined;
767
+ }
768
+ } else {
769
+ selectedRoute = optimalRoute;
770
+ }
771
+
772
+ responseDirectives = routingResult.structured.responseDirectives;
773
+
774
+ if (
775
+ selectedRoute === activeRoute &&
776
+ routingResult.structured.selectedStepId &&
777
+ activeRoute
778
+ ) {
779
+ selectedStep = activeRoute.getStep(
780
+ routingResult.structured.selectedStepId
781
+ );
782
+ if (selectedStep) {
783
+ logger.debug(
784
+ `[RoutingEngine] AI selected step: ${selectedStep.id} in active route`
785
+ );
786
+ logger.debug(
787
+ `[RoutingEngine] Step reasoning: ${routingResult.structured.stepReasoning}`
788
+ );
789
+ }
790
+ }
791
+
792
+ if (selectedRoute) {
793
+ logger.debug(`[RoutingEngine] Selected route: ${selectedRoute.title}`);
794
+ updatedSession = this.enterRouteIfNeeded(updatedSession, selectedRoute);
795
+ }
796
+ }
797
+
798
+ return {
799
+ selectedRoute,
800
+ selectedStep,
801
+ responseDirectives,
802
+ session: updatedSession,
803
+ isRouteComplete,
804
+ completedRoutes,
805
+ };
806
+ }
807
+
808
+ /**
809
+ * Filter routes based on skipIf conditions
810
+ * @param routes - All available routes
811
+ * @param templateContext - Context for condition evaluation
812
+ * @returns Object with eligible routes and collected AI context strings
813
+ */
814
+ async filterRoutesBySkipIf(
815
+ routes: Route<TContext, TData>[],
816
+ templateContext: TemplateContext<TContext, TData>
817
+ ): Promise<{
818
+ eligibleRoutes: Route<TContext, TData>[];
819
+ aiContextStrings: string[];
820
+ }> {
821
+ const eligibleRoutes: Route<TContext, TData>[] = [];
822
+ const aiContextStrings: string[] = [];
823
+
824
+ for (const route of routes) {
825
+ const skipResult = await route.evaluateSkipIf(templateContext);
826
+
827
+ // Collect AI context strings from skipIf conditions
828
+ aiContextStrings.push(...skipResult.aiContextStrings);
829
+
830
+ // If route should not be skipped, it's eligible
831
+ if (!skipResult.programmaticResult) {
832
+ eligibleRoutes.push(route);
833
+ } else {
834
+ logger.debug(`[RoutingEngine] Skipping route ${route.title} (skipIf condition met)`);
835
+ }
836
+ }
837
+
838
+ return { eligibleRoutes, aiContextStrings };
839
+ }
840
+
841
+ /**
842
+ * Filter routes based on when conditions
843
+ * @param routes - Routes that passed skipIf filtering
844
+ * @param templateContext - Context for condition evaluation
845
+ * @returns Object with eligible routes and collected AI context strings
846
+ */
847
+ async filterRoutesByWhen(
848
+ routes: Route<TContext, TData>[],
849
+ templateContext: TemplateContext<TContext, TData>
850
+ ): Promise<{
851
+ eligibleRoutes: Route<TContext, TData>[];
852
+ aiContextStrings: string[];
853
+ }> {
854
+ const eligibleRoutes: Route<TContext, TData>[] = [];
855
+ const aiContextStrings: string[] = [];
856
+
857
+ for (const route of routes) {
858
+ const whenResult = await route.evaluateWhen(templateContext);
859
+
860
+ // Collect AI context strings from when conditions
861
+ aiContextStrings.push(...whenResult.aiContextStrings);
862
+
863
+ // If route has no programmatic conditions or they evaluate to true, it's eligible
864
+ if (!whenResult.hasProgrammaticConditions || whenResult.programmaticResult) {
865
+ eligibleRoutes.push(route);
866
+ } else {
867
+ logger.debug(`[RoutingEngine] Route ${route.title} not eligible (when condition not met)`);
868
+ }
869
+ }
870
+
871
+ return { eligibleRoutes, aiContextStrings };
872
+ }
873
+
874
+ /**
875
+ * Evaluate all routes for completion based on collected data
876
+ * @param routes - All available routes
877
+ * @param data - Currently collected agent-level data
878
+ * @returns Array of routes that are complete
879
+ */
880
+ evaluateRouteCompletions(routes: Route<TContext, TData>[], data: Partial<TData>): Route<TContext, TData>[] {
881
+ return routes.filter(route => route.isComplete(data));
882
+ }
883
+
884
+ /**
885
+ * Get completion status for all routes
886
+ * @param routes - All available routes
887
+ * @param data - Currently collected agent-level data
888
+ * @returns Map of route ID to completion progress (0-1)
889
+ */
890
+ getRouteCompletionStatus(routes: Route<TContext, TData>[], data: Partial<TData>): Map<string, number> {
891
+ const completionStatus = new Map<string, number>();
892
+
893
+ for (const route of routes) {
894
+ const progress = route.getCompletionProgress(data);
895
+ completionStatus.set(route.id, progress);
896
+ }
897
+
898
+ return completionStatus;
899
+ }
900
+
901
+ /**
902
+ * Find the best route to continue based on completion status and user intent
903
+ * Prioritizes routes that are partially complete but not finished
904
+ * IMPORTANT: Completed routes are excluded to prevent re-entering finished tasks
905
+ * @param routes - All available routes
906
+ * @param data - Currently collected agent-level data
907
+ * @param routeScores - AI-generated route scores from routing decision
908
+ * @returns Route that should be prioritized for continuation
909
+ */
910
+ selectOptimalRoute(
911
+ routes: Route<TContext, TData>[],
912
+ data: Partial<TData>,
913
+ routeScores: Record<string, number>
914
+ ): Route<TContext, TData> | undefined {
915
+ const completionStatus = this.getRouteCompletionStatus(routes, data);
916
+
917
+ // Create weighted scores combining AI intent scores with completion progress
918
+ const weightedScores: Array<{ route: Route<TContext, TData>; score: number }> = [];
919
+
920
+ for (const route of routes) {
921
+ const aiScore = routeScores[route.id] || 0;
922
+ const completionProgress = completionStatus.get(route.id) || 0;
923
+
924
+ // ALWAYS skip fully completed routes to prevent re-entering finished tasks
925
+ // Users should not be forced back into completed routes
926
+ if (completionProgress >= 1.0) {
927
+ logger.debug(
928
+ `[RoutingEngine] Excluding completed route: ${route.title} (100% complete)`
929
+ );
930
+ continue;
931
+ }
932
+
933
+ // Boost partially complete routes that match user intent
934
+ let weightedScore = aiScore;
935
+ if (completionProgress > 0 && completionProgress < 1.0) {
936
+ // Boost score for partially complete routes
937
+ weightedScore += (completionProgress * 20); // Up to 20 point boost
938
+ }
939
+
940
+ weightedScores.push({ route, score: weightedScore });
941
+ }
942
+
943
+ // Sort by weighted score and return the best option
944
+ weightedScores.sort((a, b) => b.score - a.score);
945
+
946
+ if (weightedScores.length > 0) {
947
+ logger.debug(
948
+ `[RoutingEngine] Selected optimal route: ${weightedScores[0].route.title} ` +
949
+ `(AI: ${routeScores[weightedScores[0].route.id]}, ` +
950
+ `Completion: ${(completionStatus.get(weightedScores[0].route.id) || 0) * 100}%, ` +
951
+ `Weighted: ${weightedScores[0].score})`
952
+ );
953
+ return weightedScores[0].route;
954
+ }
955
+
956
+ return undefined;
957
+ }
958
+
959
+ /**
960
+ * Build prompt for step selection within a single route
961
+ * @private
962
+ */
963
+ private async buildStepSelectionPrompt(
964
+ params: BuildStepSelectionPromptParams<TContext, TData> & { includeEndRoute?: boolean }
965
+ ): Promise<string> {
966
+ const {
967
+ route,
968
+ currentStep,
969
+ candidates,
970
+ data,
971
+ history,
972
+ lastMessage,
973
+ agentOptions,
974
+ context,
975
+ session,
976
+ stepConditionContext,
977
+ includeEndRoute = false,
978
+ } = params;
979
+ const templateContext = createTemplateContext({ context, session, history });
980
+ const pc = new PromptComposer<TContext, TData>(templateContext);
981
+
982
+ // Add agent metadata
983
+ if (agentOptions) {
984
+ await pc.addAgentMeta(agentOptions);
985
+ }
986
+
987
+ // Add route context
988
+ await pc.addInstruction(
989
+ `Active Route: ${route.title}\nDescription: ${route.description || "N/A"}`
990
+ );
991
+
992
+ // Add current step context
993
+ if (currentStep) {
994
+ await pc.addInstruction(
995
+ `Current Step: ${currentStep.id}\nDescription: ${currentStep.description || "N/A"
996
+ }`
997
+ );
998
+ } else {
999
+ await pc.addInstruction("Current Step: None (entering route)");
1000
+ }
1001
+
1002
+ // Add collected data context
1003
+ if (Object.keys(data).length > 0) {
1004
+ await pc.addInstruction(
1005
+ `Collected Data So Far:\n${JSON.stringify(data, null, 2)}`
1006
+ );
1007
+ } else {
1008
+ await pc.addInstruction("Collected Data: None yet");
1009
+ }
1010
+
1011
+ // Add conversation history
1012
+ await pc.addInteractionHistory(history);
1013
+ await pc.addLastMessage(lastMessage);
1014
+
1015
+ // Add candidate steps with condition context
1016
+ const stepDescriptions = [];
1017
+ for (const candidate of candidates) {
1018
+ const idx = candidates.indexOf(candidate);
1019
+ const parts = [
1020
+ `${idx + 1}. Step ID: ${candidate.step.id}`,
1021
+ ` Description: ${candidate.step.description || "N/A"}`,
1022
+ ];
1023
+
1024
+ // Add when condition context
1025
+ if (candidate.step.when) {
1026
+ const whenResult = await candidate.step.evaluateWhen(templateContext);
1027
+ if (whenResult.aiContextStrings.length > 0) {
1028
+ parts.push(` When conditions: ${whenResult.aiContextStrings.join(", ")}`);
1029
+ } else if (typeof candidate.step.when === 'string') {
1030
+ parts.push(` When this step should be completed: ${candidate.step.when}`);
1031
+ }
1032
+ }
1033
+
1034
+ if (candidate.step.requires && candidate.step.requires.length > 0) {
1035
+ parts.push(` Required Data: ${candidate.step.requires.join(", ")}`);
1036
+ }
1037
+
1038
+ if (candidate.step.collect && candidate.step.collect.length > 0) {
1039
+ parts.push(` Collects: ${candidate.step.collect.join(", ")}`);
1040
+ }
1041
+
1042
+ stepDescriptions.push(parts.join("\n"));
1043
+ }
1044
+
1045
+ await pc.addInstruction(
1046
+ `Available Steps to Transition To:\n${stepDescriptions.join("\n\n")}`
1047
+ );
1048
+
1049
+ // Add step condition context if available
1050
+ if (stepConditionContext && stepConditionContext.length > 0) {
1051
+ await pc.addInstruction(
1052
+ [
1053
+ "",
1054
+ "Additional step context from conditions:",
1055
+ ...stepConditionContext.map(ctx => `- ${ctx}`),
1056
+ "",
1057
+ "Consider this context when selecting the most appropriate step.",
1058
+ ].join("\n")
1059
+ );
1060
+ }
1061
+
1062
+ // Add decision prompt
1063
+ const decisionRules = [
1064
+ "Task: Decide which step to transition to based on:",
1065
+ "1. The user's current message and intent",
1066
+ "2. The conversation history and context",
1067
+ "3. The collected data we already have",
1068
+ "4. The conditions and requirements of each step",
1069
+ "5. The logical flow of the conversation",
1070
+ "",
1071
+ "Rules:",
1072
+ "- If a step has a condition, evaluate whether it's met based on context",
1073
+ "- If a step requires data we don't have, consider if we should collect it now",
1074
+ "- Choose the step that makes the most sense for moving the conversation forward",
1075
+ "- Steps with skipIf conditions that are met have already been filtered out",
1076
+ ];
1077
+
1078
+ if (includeEndRoute) {
1079
+ decisionRules.push(
1080
+ "",
1081
+ `- You can select '${END_ROUTE_ID}' to complete this route if:`,
1082
+ " * All required data has been collected",
1083
+ " * The user's intent suggests they're done with this task",
1084
+ " * No further steps are needed to fulfill the user's request"
1085
+ );
1086
+ }
1087
+
1088
+ decisionRules.push(
1089
+ "",
1090
+ "Return ONLY JSON matching the provided schema."
1091
+ );
1092
+
1093
+ await pc.addInstruction(decisionRules.join("\n"));
1094
+
1095
+ return pc.build();
1096
+ }
1097
+
1098
+ /**
1099
+ * Build schema for step selection
1100
+ * @private
1101
+ */
1102
+ private buildStepSelectionSchema(
1103
+ validSteps: Step<TContext, TData>[],
1104
+ includeEndRoute: boolean = false
1105
+ ): StructuredSchema {
1106
+ const stepIds = validSteps.map((s) => s.id);
1107
+
1108
+ // Add END_ROUTE as an option if requested (when required fields are complete)
1109
+ if (includeEndRoute) {
1110
+ stepIds.push(END_ROUTE_ID);
1111
+ }
1112
+
1113
+ return {
1114
+ description:
1115
+ "Step transition decision based on conversation context and collected data",
1116
+ type: "object",
1117
+ properties: {
1118
+ reasoning: {
1119
+ type: "string",
1120
+ nullable: false,
1121
+ description: "Brief explanation of why this step was selected",
1122
+ },
1123
+ selectedStepId: {
1124
+ type: "string",
1125
+ nullable: false,
1126
+ description: includeEndRoute
1127
+ ? `The ID of the selected step to transition to, or '${END_ROUTE_ID}' to complete the route`
1128
+ : "The ID of the selected step to transition to",
1129
+ enum: stepIds,
1130
+ },
1131
+ responseDirectives: {
1132
+ type: "array",
1133
+ items: { type: "string" },
1134
+ description:
1135
+ "Optional bullet points the response should address (concise)",
1136
+ },
1137
+ },
1138
+ required: ["reasoning", "selectedStepId"],
1139
+ additionalProperties: false,
1140
+ };
1141
+ }
1142
+
1143
+ buildDynamicRoutingSchema(
1144
+ routes: Route<TContext, TData>[],
1145
+ extrasSchema?: StructuredSchema,
1146
+ activeRouteSteps?: Step<TContext, TData>[]
1147
+ ): StructuredSchema {
1148
+ const routeIds = routes.map((r) => r.id);
1149
+ const routeProperties: Record<string, StructuredSchema> = {};
1150
+ for (const id of routeIds) {
1151
+ routeProperties[id] = {
1152
+ type: "number",
1153
+ nullable: false,
1154
+ description: `Score for route ${id} based on direct evidence, context and semantic fit (0-100)`,
1155
+ minimum: 0,
1156
+ maximum: 100,
1157
+ } as StructuredSchema;
1158
+ }
1159
+
1160
+ const base: StructuredSchema = {
1161
+ description:
1162
+ "Full intent analysis: score ALL available routes (0-100) using evidence and context",
1163
+ type: "object",
1164
+ properties: {
1165
+ context: {
1166
+ type: "string",
1167
+ nullable: false,
1168
+ description: "Brief summary of the user's intent/context",
1169
+ },
1170
+ routes: {
1171
+ type: "object",
1172
+ properties: routeProperties,
1173
+ required: routeIds,
1174
+ nullable: false,
1175
+ description: "Mapping of routeId to score (0-100)",
1176
+ },
1177
+ responseDirectives: {
1178
+ type: "array",
1179
+ items: { type: "string" },
1180
+ description:
1181
+ "Optional bullet points the response should address (concise)",
1182
+ },
1183
+ },
1184
+ required: ["context", "routes"],
1185
+ additionalProperties: false,
1186
+ };
1187
+
1188
+ // Add step selection fields if there's an active route with steps
1189
+ if (activeRouteSteps && activeRouteSteps.length > 0) {
1190
+ base.properties = base.properties || {};
1191
+ base.properties.selectedStepId = {
1192
+ type: "string",
1193
+ nullable: false,
1194
+ description:
1195
+ "The step ID to transition to within the active route (required if continuing in current route)",
1196
+ enum: activeRouteSteps.map((s) => s.id),
1197
+ };
1198
+ base.properties.stepReasoning = {
1199
+ type: "string",
1200
+ nullable: false,
1201
+ description: "Brief explanation of why this step was selected",
1202
+ };
1203
+ base.required = [
1204
+ ...(base.required || []),
1205
+ "selectedStepId",
1206
+ "stepReasoning",
1207
+ ];
1208
+ }
1209
+
1210
+ if (extrasSchema) {
1211
+ base.properties = base.properties || {};
1212
+ base.properties.extractions = extrasSchema;
1213
+ }
1214
+
1215
+ return base;
1216
+ }
1217
+
1218
+ async buildRoutingPrompt(
1219
+ params: BuildRoutingPromptParams<TContext, TData>
1220
+ ): Promise<string> {
1221
+ const {
1222
+ history,
1223
+ routes,
1224
+ lastMessage,
1225
+ agentOptions,
1226
+ session,
1227
+ activeRouteSteps,
1228
+ context,
1229
+ routeConditionContext,
1230
+ } = params;
1231
+ const templateContext = createTemplateContext({ context, session, history });
1232
+ const pc = new PromptComposer<TContext, TData>(templateContext);
1233
+ if (agentOptions) {
1234
+ await pc.addAgentMeta(agentOptions);
1235
+ }
1236
+ await pc.addInstruction(
1237
+ "Task: Intent analysis and route scoring (0-100). Score ALL listed routes."
1238
+ );
1239
+
1240
+ // Add session context if available
1241
+ if (session?.currentRoute) {
1242
+ const sessionInfo = [
1243
+ "Current conversation context:",
1244
+ `- Active route: ${session.currentRoute.title} (${session.currentRoute.id})`,
1245
+ ];
1246
+ if (session.currentStep) {
1247
+ sessionInfo.push(`- Current step: ${session.currentStep.id}`);
1248
+ if (session.currentStep.description) {
1249
+ sessionInfo.push(` "${session.currentStep.description}"`);
1250
+ }
1251
+ }
1252
+ if (session.data && Object.keys(session.data).length > 0) {
1253
+ sessionInfo.push(`- Collected data: ${JSON.stringify(session.data)}`);
1254
+ }
1255
+ sessionInfo.push(
1256
+ "Note: User is mid-conversation. They may want to continue current route or switch to a new one based on their intent."
1257
+ );
1258
+ await pc.addInstruction(sessionInfo.join("\n"));
1259
+
1260
+ // Add cross-route completion status
1261
+ const completionStatus = this.getRouteCompletionStatus(routes, session.data || {});
1262
+ const completedRoutes = this.evaluateRouteCompletions(routes, session.data || {});
1263
+
1264
+ if (completionStatus.size > 0) {
1265
+ const statusInfo = [
1266
+ "",
1267
+ "Route completion status based on collected data:",
1268
+ ];
1269
+
1270
+ for (const route of routes) {
1271
+ const progress = completionStatus.get(route.id) || 0;
1272
+ const isComplete = completedRoutes.includes(route);
1273
+ const progressPercent = Math.round(progress * 100);
1274
+
1275
+ statusInfo.push(
1276
+ `- ${route.title}: ${progressPercent}% complete${isComplete ? ' ✓ COMPLETE' : ''}`
1277
+ );
1278
+
1279
+ if (!isComplete && route.requiredFields) {
1280
+ const missingFields = route.getMissingRequiredFields(session.data || {});
1281
+ if (missingFields.length > 0) {
1282
+ statusInfo.push(` Missing: ${missingFields.join(', ')}`);
1283
+ }
1284
+ }
1285
+ }
1286
+
1287
+ statusInfo.push(
1288
+ "",
1289
+ "Consider route completion status when scoring. Partially complete routes may be good candidates for continuation."
1290
+ );
1291
+
1292
+ await pc.addInstruction(statusInfo.join("\n"));
1293
+ }
1294
+
1295
+ // Add available steps for the active route
1296
+ if (activeRouteSteps && activeRouteSteps.length > 0) {
1297
+ const stepInfo = [
1298
+ "",
1299
+ "Available steps in active route (choose one to transition to):",
1300
+ ];
1301
+ const activeStepConditionContext: string[] = [];
1302
+
1303
+ for (const step of activeRouteSteps) {
1304
+ const idx = activeRouteSteps.indexOf(step);
1305
+ stepInfo.push(`${idx + 1}. Step: ${step.id}`);
1306
+ if (step.description) {
1307
+ stepInfo.push(` Description: ${step.description}`);
1308
+ }
1309
+
1310
+ // Collect AI context from step conditions
1311
+ if (step.when) {
1312
+ const whenResult = await step.evaluateWhen(templateContext);
1313
+ if (whenResult.aiContextStrings.length > 0) {
1314
+ stepInfo.push(` When conditions: ${whenResult.aiContextStrings.join(", ")}`);
1315
+ activeStepConditionContext.push(...whenResult.aiContextStrings);
1316
+ } else if (typeof step.when === 'string') {
1317
+ stepInfo.push(` When this step should be completed: ${step.when}`);
1318
+ }
1319
+ }
1320
+
1321
+ if (step.requires && step.requires.length > 0) {
1322
+ stepInfo.push(` Required data: ${step.requires.join(", ")}`);
1323
+ }
1324
+ if (step.collect && step.collect.length > 0) {
1325
+ stepInfo.push(` Will collect: ${step.collect.join(", ")}`);
1326
+ }
1327
+ }
1328
+ stepInfo.push("");
1329
+ stepInfo.push(
1330
+ "IMPORTANT: You MUST select a step to transition to. Evaluate which step makes the most sense based on:"
1331
+ );
1332
+ stepInfo.push("- The conversation flow and what's been collected");
1333
+ stepInfo.push("- What data is still needed vs already present");
1334
+ stepInfo.push("- The logical next step in the conversation");
1335
+ stepInfo.push("- Whether conditions for steps are met");
1336
+ await pc.addInstruction(stepInfo.join("\n"));
1337
+
1338
+ // Add active step condition context if available
1339
+ if (activeStepConditionContext.length > 0) {
1340
+ await pc.addInstruction(
1341
+ [
1342
+ "",
1343
+ "Additional context from step conditions:",
1344
+ ...activeStepConditionContext.map(ctx => `- ${ctx}`),
1345
+ "",
1346
+ "Use this context to inform your step selection decision.",
1347
+ ].join("\n")
1348
+ );
1349
+ }
1350
+ }
1351
+ }
1352
+
1353
+ await pc.addInteractionHistory(history);
1354
+ await pc.addLastMessage(lastMessage);
1355
+ await pc.addRoutingOverview(routes);
1356
+
1357
+ // Add route condition context if available
1358
+ if (routeConditionContext && routeConditionContext.length > 0) {
1359
+ await pc.addInstruction(
1360
+ [
1361
+ "",
1362
+ "Additional routing context from route conditions:",
1363
+ ...routeConditionContext.map(ctx => `- ${ctx}`),
1364
+ "",
1365
+ "Consider this context when scoring routes for relevance.",
1366
+ ].join("\n")
1367
+ );
1368
+ }
1369
+ await pc.addInstruction(
1370
+ [
1371
+ "Scoring rules:",
1372
+ "- 90-100: explicit keywords + clear intent",
1373
+ "- 70-89: strong contextual evidence + relevant keywords",
1374
+ "- 50-69: moderate relevance",
1375
+ "- 30-49: weak connection or ambiguous",
1376
+ "- 0-29: minimal/none",
1377
+ "Return ONLY JSON matching the provided schema. Include scores for ALL routes.",
1378
+ ].join("\n")
1379
+ );
1380
+ return pc.build();
1381
+ }
1382
+
1383
+ decideRouteFromScores(output: RoutingDecision): {
1384
+ routeId: string;
1385
+ maxScore: number;
1386
+ } {
1387
+ // Optionally limit candidates and apply switching threshold
1388
+ const entries = Object.entries(output.routes).sort((a, b) => b[1] - a[1]);
1389
+ const limited = this.options?.maxCandidates
1390
+ ? entries.slice(0, this.options.maxCandidates)
1391
+ : entries;
1392
+ const [topId, topScore] = limited[0] || ["", 0];
1393
+ // switchThreshold is enforced by caller when a current route exists
1394
+ return { routeId: topId, maxScore: topScore };
1395
+ }
1396
+ }