@falai/agent 0.9.2 → 1.0.1

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 (628) hide show
  1. package/README.md +281 -42
  2. package/dist/adapters/MemoryAdapter.d.ts.map +1 -0
  3. package/dist/adapters/MemoryAdapter.js.map +1 -0
  4. package/dist/adapters/MongoAdapter.d.ts.map +1 -0
  5. package/dist/adapters/MongoAdapter.js.map +1 -0
  6. package/dist/adapters/OpenSearchAdapter.d.ts.map +1 -0
  7. package/dist/adapters/OpenSearchAdapter.js.map +1 -0
  8. package/dist/adapters/PostgreSQLAdapter.d.ts.map +1 -0
  9. package/dist/adapters/PostgreSQLAdapter.js.map +1 -0
  10. package/dist/adapters/PrismaAdapter.d.ts.map +1 -0
  11. package/dist/{src/adapters → adapters}/PrismaAdapter.js +3 -2
  12. package/dist/adapters/PrismaAdapter.js.map +1 -0
  13. package/dist/adapters/RedisAdapter.d.ts.map +1 -0
  14. package/dist/{src/adapters → adapters}/RedisAdapter.js +3 -3
  15. package/dist/adapters/RedisAdapter.js.map +1 -0
  16. package/dist/adapters/SQLiteAdapter.d.ts.map +1 -0
  17. package/dist/adapters/SQLiteAdapter.js.map +1 -0
  18. package/dist/adapters/index.d.ts.map +1 -0
  19. package/dist/adapters/index.js.map +1 -0
  20. package/dist/cjs/adapters/MemoryAdapter.js.map +1 -0
  21. package/dist/cjs/adapters/MongoAdapter.js.map +1 -0
  22. package/dist/cjs/adapters/OpenSearchAdapter.js.map +1 -0
  23. package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -0
  24. package/dist/{src → cjs}/adapters/PrismaAdapter.d.ts.map +1 -1
  25. package/dist/cjs/{src/adapters → adapters}/PrismaAdapter.js +3 -2
  26. package/dist/cjs/adapters/PrismaAdapter.js.map +1 -0
  27. package/dist/cjs/{src/adapters → adapters}/RedisAdapter.js +2 -2
  28. package/dist/cjs/adapters/RedisAdapter.js.map +1 -0
  29. package/dist/cjs/adapters/SQLiteAdapter.js.map +1 -0
  30. package/dist/cjs/adapters/index.js.map +1 -0
  31. package/dist/cjs/constants/index.js.map +1 -0
  32. package/dist/cjs/{src/core → core}/Agent.d.ts +16 -1
  33. package/dist/cjs/core/Agent.d.ts.map +1 -0
  34. package/dist/cjs/{src/core → core}/Agent.js +63 -2
  35. package/dist/cjs/core/Agent.js.map +1 -0
  36. package/dist/cjs/core/BatchExecutor.d.ts +353 -0
  37. package/dist/cjs/core/BatchExecutor.d.ts.map +1 -0
  38. package/dist/cjs/core/BatchExecutor.js +850 -0
  39. package/dist/cjs/core/BatchExecutor.js.map +1 -0
  40. package/dist/cjs/core/BatchPromptBuilder.d.ts +86 -0
  41. package/dist/cjs/core/BatchPromptBuilder.d.ts.map +1 -0
  42. package/dist/cjs/core/BatchPromptBuilder.js +217 -0
  43. package/dist/cjs/core/BatchPromptBuilder.js.map +1 -0
  44. package/dist/cjs/core/Events.js.map +1 -0
  45. package/dist/cjs/core/PersistenceManager.js.map +1 -0
  46. package/dist/{src → cjs}/core/PromptComposer.d.ts +1 -1
  47. package/dist/cjs/core/PromptComposer.d.ts.map +1 -0
  48. package/dist/cjs/{src/core → core}/PromptComposer.js +44 -7
  49. package/dist/cjs/core/PromptComposer.js.map +1 -0
  50. package/dist/{src → cjs}/core/ResponseEngine.d.ts.map +1 -1
  51. package/dist/cjs/core/ResponseEngine.js +211 -0
  52. package/dist/cjs/core/ResponseEngine.js.map +1 -0
  53. package/dist/{src → cjs}/core/ResponseModal.d.ts +45 -0
  54. package/dist/cjs/core/ResponseModal.d.ts.map +1 -0
  55. package/dist/cjs/{src/core → core}/ResponseModal.js +752 -74
  56. package/dist/cjs/core/ResponseModal.js.map +1 -0
  57. package/dist/{src → cjs}/core/ResponsePipeline.d.ts +2 -2
  58. package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -0
  59. package/dist/cjs/{src/core → core}/ResponsePipeline.js +13 -6
  60. package/dist/cjs/core/ResponsePipeline.js.map +1 -0
  61. package/dist/{src → cjs}/core/Route.d.ts +34 -5
  62. package/dist/cjs/core/Route.d.ts.map +1 -0
  63. package/dist/cjs/{src/core → core}/Route.js +196 -19
  64. package/dist/cjs/core/Route.js.map +1 -0
  65. package/dist/cjs/{src/core → core}/RoutingEngine.d.ts +30 -5
  66. package/dist/cjs/core/RoutingEngine.d.ts.map +1 -0
  67. package/dist/cjs/{src/core → core}/RoutingEngine.js +330 -80
  68. package/dist/cjs/core/RoutingEngine.js.map +1 -0
  69. package/dist/cjs/core/SessionManager.js.map +1 -0
  70. package/dist/{src → cjs}/core/Step.d.ts +31 -10
  71. package/dist/cjs/core/Step.d.ts.map +1 -0
  72. package/dist/cjs/{src/core → core}/Step.js +105 -10
  73. package/dist/cjs/core/Step.js.map +1 -0
  74. package/dist/cjs/core/ToolManager.js.map +1 -0
  75. package/dist/{src → cjs}/index.d.ts +4 -1
  76. package/dist/cjs/index.d.ts.map +1 -0
  77. package/dist/cjs/{src/index.js → index.js} +12 -1
  78. package/dist/cjs/index.js.map +1 -0
  79. package/dist/cjs/{src/providers → providers}/AnthropicProvider.js +18 -18
  80. package/dist/cjs/providers/AnthropicProvider.js.map +1 -0
  81. package/dist/{src → cjs}/providers/GeminiProvider.d.ts.map +1 -1
  82. package/dist/cjs/{src/providers → providers}/GeminiProvider.js +123 -51
  83. package/dist/cjs/providers/GeminiProvider.js.map +1 -0
  84. package/dist/cjs/{src/providers → providers}/OpenAIProvider.js +19 -19
  85. package/dist/cjs/providers/OpenAIProvider.js.map +1 -0
  86. package/dist/cjs/{src/providers → providers}/OpenRouterProvider.js +19 -19
  87. package/dist/cjs/providers/OpenRouterProvider.js.map +1 -0
  88. package/dist/cjs/providers/index.js.map +1 -0
  89. package/dist/cjs/{src/types → types}/agent.d.ts +15 -3
  90. package/dist/cjs/types/agent.d.ts.map +1 -0
  91. package/dist/cjs/types/agent.js.map +1 -0
  92. package/dist/{src → cjs}/types/ai.js.map +1 -1
  93. package/dist/cjs/types/history.js.map +1 -0
  94. package/dist/cjs/{src/types → types}/index.d.ts +2 -1
  95. package/dist/{src → cjs}/types/index.d.ts.map +1 -1
  96. package/dist/cjs/{src/types → types}/index.js +6 -1
  97. package/dist/cjs/types/index.js.map +1 -0
  98. package/dist/cjs/types/persistence.js.map +1 -0
  99. package/dist/cjs/{src/types → types}/route.d.ts +111 -12
  100. package/dist/cjs/types/route.d.ts.map +1 -0
  101. package/dist/cjs/{src/types → types}/route.js.map +1 -1
  102. package/dist/cjs/types/session.js.map +1 -0
  103. package/dist/cjs/types/template.d.ts +88 -0
  104. package/dist/cjs/types/template.d.ts.map +1 -0
  105. package/dist/cjs/types/tool.js.map +1 -0
  106. package/dist/cjs/utils/clone.js.map +1 -0
  107. package/dist/cjs/utils/condition.d.ts +38 -0
  108. package/dist/cjs/utils/condition.d.ts.map +1 -0
  109. package/dist/cjs/utils/condition.js +168 -0
  110. package/dist/cjs/utils/condition.js.map +1 -0
  111. package/dist/cjs/utils/event.js.map +1 -0
  112. package/dist/cjs/utils/history.js.map +1 -0
  113. package/dist/cjs/utils/id.js.map +1 -0
  114. package/dist/cjs/{src/utils → utils}/index.d.ts +3 -1
  115. package/dist/cjs/utils/index.d.ts.map +1 -0
  116. package/dist/cjs/{src/utils → utils}/index.js +12 -1
  117. package/dist/cjs/utils/index.js.map +1 -0
  118. package/dist/cjs/utils/json.d.ts +16 -0
  119. package/dist/cjs/utils/json.d.ts.map +1 -0
  120. package/dist/cjs/utils/json.js +47 -0
  121. package/dist/cjs/utils/json.js.map +1 -0
  122. package/dist/cjs/utils/logger.js.map +1 -0
  123. package/dist/{src → cjs}/utils/retry.d.ts +0 -3
  124. package/dist/cjs/utils/retry.d.ts.map +1 -0
  125. package/dist/cjs/{src/utils → utils}/retry.js +8 -7
  126. package/dist/cjs/utils/retry.js.map +1 -0
  127. package/dist/cjs/utils/session.js.map +1 -0
  128. package/dist/{src → cjs}/utils/template.d.ts +48 -0
  129. package/dist/cjs/utils/template.d.ts.map +1 -0
  130. package/dist/cjs/{src/utils → utils}/template.js +100 -0
  131. package/dist/cjs/utils/template.js.map +1 -0
  132. package/dist/constants/index.d.ts.map +1 -0
  133. package/dist/constants/index.js.map +1 -0
  134. package/dist/{src/core → core}/Agent.d.ts +16 -1
  135. package/dist/core/Agent.d.ts.map +1 -0
  136. package/dist/{src/core → core}/Agent.js +64 -3
  137. package/dist/core/Agent.js.map +1 -0
  138. package/dist/core/BatchExecutor.d.ts +353 -0
  139. package/dist/core/BatchExecutor.d.ts.map +1 -0
  140. package/dist/core/BatchExecutor.js +845 -0
  141. package/dist/core/BatchExecutor.js.map +1 -0
  142. package/dist/core/BatchPromptBuilder.d.ts +86 -0
  143. package/dist/core/BatchPromptBuilder.d.ts.map +1 -0
  144. package/dist/core/BatchPromptBuilder.js +213 -0
  145. package/dist/core/BatchPromptBuilder.js.map +1 -0
  146. package/dist/core/Events.d.ts.map +1 -0
  147. package/dist/core/Events.js.map +1 -0
  148. package/dist/core/PersistenceManager.d.ts.map +1 -0
  149. package/dist/core/PersistenceManager.js.map +1 -0
  150. package/dist/{cjs/src/core → core}/PromptComposer.d.ts +1 -1
  151. package/dist/core/PromptComposer.d.ts.map +1 -0
  152. package/dist/{src/core → core}/PromptComposer.js +45 -8
  153. package/dist/core/PromptComposer.js.map +1 -0
  154. package/dist/core/ResponseEngine.d.ts.map +1 -0
  155. package/dist/core/ResponseEngine.js +207 -0
  156. package/dist/core/ResponseEngine.js.map +1 -0
  157. package/dist/{cjs/src/core → core}/ResponseModal.d.ts +45 -0
  158. package/dist/core/ResponseModal.d.ts.map +1 -0
  159. package/dist/{src/core → core}/ResponseModal.js +752 -74
  160. package/dist/core/ResponseModal.js.map +1 -0
  161. package/dist/{cjs/src/core → core}/ResponsePipeline.d.ts +2 -2
  162. package/dist/core/ResponsePipeline.d.ts.map +1 -0
  163. package/dist/{src/core → core}/ResponsePipeline.js +13 -6
  164. package/dist/core/ResponsePipeline.js.map +1 -0
  165. package/dist/{cjs/src/core → core}/Route.d.ts +34 -5
  166. package/dist/core/Route.d.ts.map +1 -0
  167. package/dist/{src/core → core}/Route.js +195 -18
  168. package/dist/core/Route.js.map +1 -0
  169. package/dist/{src/core → core}/RoutingEngine.d.ts +30 -5
  170. package/dist/core/RoutingEngine.d.ts.map +1 -0
  171. package/dist/{src/core → core}/RoutingEngine.js +310 -60
  172. package/dist/core/RoutingEngine.js.map +1 -0
  173. package/dist/core/SessionManager.d.ts.map +1 -0
  174. package/dist/core/SessionManager.js.map +1 -0
  175. package/dist/{cjs/src/core → core}/Step.d.ts +31 -10
  176. package/dist/core/Step.d.ts.map +1 -0
  177. package/dist/{src/core → core}/Step.js +104 -9
  178. package/dist/core/Step.js.map +1 -0
  179. package/dist/core/ToolManager.d.ts.map +1 -0
  180. package/dist/core/ToolManager.js.map +1 -0
  181. package/dist/{cjs/src/index.d.ts → index.d.ts} +4 -1
  182. package/dist/index.d.ts.map +1 -0
  183. package/dist/{src/index.js → index.js} +3 -0
  184. package/dist/index.js.map +1 -0
  185. package/dist/providers/AnthropicProvider.d.ts.map +1 -0
  186. package/dist/{src/providers → providers}/AnthropicProvider.js +17 -17
  187. package/dist/providers/AnthropicProvider.js.map +1 -0
  188. package/dist/providers/GeminiProvider.d.ts.map +1 -0
  189. package/dist/{src/providers → providers}/GeminiProvider.js +123 -51
  190. package/dist/providers/GeminiProvider.js.map +1 -0
  191. package/dist/providers/OpenAIProvider.d.ts.map +1 -0
  192. package/dist/{src/providers → providers}/OpenAIProvider.js +18 -18
  193. package/dist/providers/OpenAIProvider.js.map +1 -0
  194. package/dist/providers/OpenRouterProvider.d.ts.map +1 -0
  195. package/dist/{src/providers → providers}/OpenRouterProvider.js +18 -18
  196. package/dist/providers/OpenRouterProvider.js.map +1 -0
  197. package/dist/providers/index.d.ts.map +1 -0
  198. package/dist/providers/index.js.map +1 -0
  199. package/dist/{src/types → types}/agent.d.ts +15 -3
  200. package/dist/types/agent.d.ts.map +1 -0
  201. package/dist/types/agent.js.map +1 -0
  202. package/dist/types/ai.d.ts.map +1 -0
  203. package/dist/types/ai.js.map +1 -0
  204. package/dist/types/history.d.ts.map +1 -0
  205. package/dist/types/history.js.map +1 -0
  206. package/dist/{src/types → types}/index.d.ts +2 -1
  207. package/dist/types/index.d.ts.map +1 -0
  208. package/dist/{src/types → types}/index.js +1 -0
  209. package/dist/types/index.js.map +1 -0
  210. package/dist/types/persistence.d.ts.map +1 -0
  211. package/dist/types/persistence.js.map +1 -0
  212. package/dist/{src/types → types}/route.d.ts +111 -12
  213. package/dist/types/route.d.ts.map +1 -0
  214. package/dist/{src/types → types}/route.js.map +1 -1
  215. package/dist/types/routing.d.ts.map +1 -0
  216. package/dist/{cjs/src/types → types}/routing.js.map +1 -1
  217. package/dist/types/schema.d.ts.map +1 -0
  218. package/dist/{cjs/src/types → types}/schema.js.map +1 -1
  219. package/dist/types/session.d.ts.map +1 -0
  220. package/dist/{src/types → types}/session.js.map +1 -1
  221. package/dist/types/template.d.ts +88 -0
  222. package/dist/types/template.d.ts.map +1 -0
  223. package/dist/{cjs/src/types → types}/template.js.map +1 -1
  224. package/dist/types/tool.d.ts.map +1 -0
  225. package/dist/types/tool.js.map +1 -0
  226. package/dist/utils/clone.d.ts.map +1 -0
  227. package/dist/utils/clone.js.map +1 -0
  228. package/dist/utils/condition.d.ts +38 -0
  229. package/dist/utils/condition.d.ts.map +1 -0
  230. package/dist/utils/condition.js +161 -0
  231. package/dist/utils/condition.js.map +1 -0
  232. package/dist/utils/event.d.ts.map +1 -0
  233. package/dist/utils/event.js.map +1 -0
  234. package/dist/utils/history.d.ts.map +1 -0
  235. package/dist/utils/history.js.map +1 -0
  236. package/dist/utils/id.d.ts.map +1 -0
  237. package/dist/utils/id.js.map +1 -0
  238. package/dist/{src/utils → utils}/index.d.ts +3 -1
  239. package/dist/utils/index.d.ts.map +1 -0
  240. package/dist/{src/utils → utils}/index.js +5 -1
  241. package/dist/utils/index.js.map +1 -0
  242. package/dist/utils/json.d.ts +16 -0
  243. package/dist/utils/json.d.ts.map +1 -0
  244. package/dist/utils/json.js +43 -0
  245. package/dist/utils/json.js.map +1 -0
  246. package/dist/utils/logger.d.ts.map +1 -0
  247. package/dist/utils/logger.js.map +1 -0
  248. package/dist/{cjs/src/utils → utils}/retry.d.ts +0 -3
  249. package/dist/utils/retry.d.ts.map +1 -0
  250. package/dist/{src/utils → utils}/retry.js +5 -4
  251. package/dist/utils/retry.js.map +1 -0
  252. package/dist/utils/session.d.ts.map +1 -0
  253. package/dist/utils/session.js.map +1 -0
  254. package/dist/{cjs/src/utils → utils}/template.d.ts +48 -0
  255. package/dist/utils/template.d.ts.map +1 -0
  256. package/dist/{src/utils → utils}/template.js +98 -0
  257. package/dist/utils/template.js.map +1 -0
  258. package/docs/README.md +1 -0
  259. package/docs/api/README.md +237 -12
  260. package/docs/api/overview.md +206 -3
  261. package/docs/architecture/data-extraction-flow.md +363 -0
  262. package/docs/architecture/multi-step-execution.md +243 -0
  263. package/docs/core/agent/README.md +156 -5
  264. package/docs/core/agent/rules-and-prohibitions.md +113 -0
  265. package/docs/core/agent/session-management.md +1 -1
  266. package/docs/core/ai-integration/prompt-composition.md +135 -0
  267. package/docs/core/ai-integration/response-processing.md +146 -0
  268. package/docs/core/conversation-flows/data-collection.md +143 -0
  269. package/docs/core/conversation-flows/routes.md +2 -2
  270. package/docs/core/conversation-flows/step-transitions.md +132 -0
  271. package/docs/core/conversation-flows/steps.md +112 -0
  272. package/docs/core/error-handling.md +193 -0
  273. package/docs/core/routing/intelligent-routing.md +118 -0
  274. package/docs/guides/getting-started/README.md +284 -3
  275. package/docs/guides/migration/README.md +27 -0
  276. package/docs/guides/migration/flexible-routing-conditions.md +375 -0
  277. package/docs/guides/migration/multi-step-execution.md +373 -0
  278. package/examples/advanced-patterns/knowledge-based-agent.ts +101 -24
  279. package/examples/advanced-patterns/persistent-onboarding.ts +40 -5
  280. package/examples/advanced-patterns/route-lifecycle-hooks.ts +82 -12
  281. package/examples/advanced-patterns/streaming-responses.ts +2 -2
  282. package/examples/ai-providers/anthropic-integration.ts +4 -4
  283. package/examples/ai-providers/openai-integration.ts +1 -1
  284. package/examples/condition-patterns/function-only-conditions.ts +365 -0
  285. package/examples/condition-patterns/mixed-array-conditions.ts +477 -0
  286. package/examples/condition-patterns/route-skipif-patterns.ts +468 -0
  287. package/examples/condition-patterns/step-skipif-patterns.ts +0 -0
  288. package/examples/condition-patterns/string-only-conditions.ts +296 -0
  289. package/examples/conversation-flows/completion-transitions.ts +48 -7
  290. package/examples/core-concepts/basic-agent.ts +54 -33
  291. package/examples/core-concepts/schema-driven-extraction.ts +33 -9
  292. package/examples/core-concepts/session-management.ts +51 -16
  293. package/examples/integrations/database-integration.ts +6 -6
  294. package/examples/integrations/healthcare-integration.ts +10 -10
  295. package/examples/integrations/search-integration.ts +8 -8
  296. package/examples/integrations/server-session-management.ts +8 -8
  297. package/examples/persistence/database-persistence.ts +15 -15
  298. package/examples/persistence/memory-sessions.ts +3 -3
  299. package/examples/persistence/redis-persistence.ts +7 -9
  300. package/examples/tools/data-enrichment-tools.ts +4 -4
  301. package/package.json +6 -4
  302. package/src/adapters/PrismaAdapter.ts +3 -2
  303. package/src/adapters/RedisAdapter.ts +3 -3
  304. package/src/core/Agent.ts +78 -2
  305. package/src/core/BatchExecutor.ts +1166 -0
  306. package/src/core/BatchPromptBuilder.ts +293 -0
  307. package/src/core/PromptComposer.ts +53 -16
  308. package/src/core/ResponseEngine.ts +168 -29
  309. package/src/core/ResponseModal.ts +954 -74
  310. package/src/core/ResponsePipeline.ts +17 -9
  311. package/src/core/Route.ts +223 -22
  312. package/src/core/RoutingEngine.ts +426 -83
  313. package/src/core/Step.ts +144 -16
  314. package/src/index.ts +19 -0
  315. package/src/providers/AnthropicProvider.ts +17 -17
  316. package/src/providers/GeminiProvider.ts +129 -60
  317. package/src/providers/OpenAIProvider.ts +18 -18
  318. package/src/providers/OpenRouterProvider.ts +18 -18
  319. package/src/types/agent.ts +15 -3
  320. package/src/types/index.ts +12 -1
  321. package/src/types/route.ts +131 -12
  322. package/src/types/template.ts +70 -2
  323. package/src/utils/condition.ts +190 -0
  324. package/src/utils/index.ts +12 -0
  325. package/src/utils/json.ts +46 -0
  326. package/src/utils/retry.ts +5 -4
  327. package/src/utils/template.ts +109 -0
  328. package/dist/cjs/src/adapters/MemoryAdapter.d.ts.map +0 -1
  329. package/dist/cjs/src/adapters/MemoryAdapter.js.map +0 -1
  330. package/dist/cjs/src/adapters/MongoAdapter.d.ts.map +0 -1
  331. package/dist/cjs/src/adapters/MongoAdapter.js.map +0 -1
  332. package/dist/cjs/src/adapters/OpenSearchAdapter.d.ts.map +0 -1
  333. package/dist/cjs/src/adapters/OpenSearchAdapter.js.map +0 -1
  334. package/dist/cjs/src/adapters/PostgreSQLAdapter.d.ts.map +0 -1
  335. package/dist/cjs/src/adapters/PostgreSQLAdapter.js.map +0 -1
  336. package/dist/cjs/src/adapters/PrismaAdapter.d.ts.map +0 -1
  337. package/dist/cjs/src/adapters/PrismaAdapter.js.map +0 -1
  338. package/dist/cjs/src/adapters/RedisAdapter.d.ts.map +0 -1
  339. package/dist/cjs/src/adapters/RedisAdapter.js.map +0 -1
  340. package/dist/cjs/src/adapters/SQLiteAdapter.d.ts.map +0 -1
  341. package/dist/cjs/src/adapters/SQLiteAdapter.js.map +0 -1
  342. package/dist/cjs/src/adapters/index.d.ts.map +0 -1
  343. package/dist/cjs/src/adapters/index.js.map +0 -1
  344. package/dist/cjs/src/constants/index.d.ts.map +0 -1
  345. package/dist/cjs/src/constants/index.js.map +0 -1
  346. package/dist/cjs/src/core/Agent.d.ts.map +0 -1
  347. package/dist/cjs/src/core/Agent.js.map +0 -1
  348. package/dist/cjs/src/core/Events.d.ts.map +0 -1
  349. package/dist/cjs/src/core/Events.js.map +0 -1
  350. package/dist/cjs/src/core/PersistenceManager.d.ts.map +0 -1
  351. package/dist/cjs/src/core/PersistenceManager.js.map +0 -1
  352. package/dist/cjs/src/core/PromptComposer.d.ts.map +0 -1
  353. package/dist/cjs/src/core/PromptComposer.js.map +0 -1
  354. package/dist/cjs/src/core/ResponseEngine.d.ts.map +0 -1
  355. package/dist/cjs/src/core/ResponseEngine.js +0 -84
  356. package/dist/cjs/src/core/ResponseEngine.js.map +0 -1
  357. package/dist/cjs/src/core/ResponseModal.d.ts.map +0 -1
  358. package/dist/cjs/src/core/ResponseModal.js.map +0 -1
  359. package/dist/cjs/src/core/ResponsePipeline.d.ts.map +0 -1
  360. package/dist/cjs/src/core/ResponsePipeline.js.map +0 -1
  361. package/dist/cjs/src/core/Route.d.ts.map +0 -1
  362. package/dist/cjs/src/core/Route.js.map +0 -1
  363. package/dist/cjs/src/core/RoutingEngine.d.ts.map +0 -1
  364. package/dist/cjs/src/core/RoutingEngine.js.map +0 -1
  365. package/dist/cjs/src/core/SessionManager.d.ts.map +0 -1
  366. package/dist/cjs/src/core/SessionManager.js.map +0 -1
  367. package/dist/cjs/src/core/Step.d.ts.map +0 -1
  368. package/dist/cjs/src/core/Step.js.map +0 -1
  369. package/dist/cjs/src/core/ToolManager.d.ts.map +0 -1
  370. package/dist/cjs/src/core/ToolManager.js.map +0 -1
  371. package/dist/cjs/src/index.d.ts.map +0 -1
  372. package/dist/cjs/src/index.js.map +0 -1
  373. package/dist/cjs/src/providers/AnthropicProvider.d.ts.map +0 -1
  374. package/dist/cjs/src/providers/AnthropicProvider.js.map +0 -1
  375. package/dist/cjs/src/providers/GeminiProvider.d.ts.map +0 -1
  376. package/dist/cjs/src/providers/GeminiProvider.js.map +0 -1
  377. package/dist/cjs/src/providers/OpenAIProvider.d.ts.map +0 -1
  378. package/dist/cjs/src/providers/OpenAIProvider.js.map +0 -1
  379. package/dist/cjs/src/providers/OpenRouterProvider.d.ts.map +0 -1
  380. package/dist/cjs/src/providers/OpenRouterProvider.js.map +0 -1
  381. package/dist/cjs/src/providers/index.d.ts.map +0 -1
  382. package/dist/cjs/src/providers/index.js.map +0 -1
  383. package/dist/cjs/src/types/agent.d.ts.map +0 -1
  384. package/dist/cjs/src/types/agent.js.map +0 -1
  385. package/dist/cjs/src/types/ai.d.ts.map +0 -1
  386. package/dist/cjs/src/types/ai.js.map +0 -1
  387. package/dist/cjs/src/types/history.d.ts.map +0 -1
  388. package/dist/cjs/src/types/history.js.map +0 -1
  389. package/dist/cjs/src/types/index.d.ts.map +0 -1
  390. package/dist/cjs/src/types/index.js.map +0 -1
  391. package/dist/cjs/src/types/persistence.d.ts.map +0 -1
  392. package/dist/cjs/src/types/persistence.js.map +0 -1
  393. package/dist/cjs/src/types/route.d.ts.map +0 -1
  394. package/dist/cjs/src/types/routing.d.ts.map +0 -1
  395. package/dist/cjs/src/types/schema.d.ts.map +0 -1
  396. package/dist/cjs/src/types/session.d.ts.map +0 -1
  397. package/dist/cjs/src/types/session.js.map +0 -1
  398. package/dist/cjs/src/types/template.d.ts +0 -30
  399. package/dist/cjs/src/types/template.d.ts.map +0 -1
  400. package/dist/cjs/src/types/tool.d.ts.map +0 -1
  401. package/dist/cjs/src/types/tool.js.map +0 -1
  402. package/dist/cjs/src/utils/clone.d.ts.map +0 -1
  403. package/dist/cjs/src/utils/clone.js.map +0 -1
  404. package/dist/cjs/src/utils/event.d.ts.map +0 -1
  405. package/dist/cjs/src/utils/event.js.map +0 -1
  406. package/dist/cjs/src/utils/history.d.ts.map +0 -1
  407. package/dist/cjs/src/utils/history.js.map +0 -1
  408. package/dist/cjs/src/utils/id.d.ts.map +0 -1
  409. package/dist/cjs/src/utils/id.js.map +0 -1
  410. package/dist/cjs/src/utils/index.d.ts.map +0 -1
  411. package/dist/cjs/src/utils/index.js.map +0 -1
  412. package/dist/cjs/src/utils/logger.d.ts.map +0 -1
  413. package/dist/cjs/src/utils/logger.js.map +0 -1
  414. package/dist/cjs/src/utils/retry.d.ts.map +0 -1
  415. package/dist/cjs/src/utils/retry.js.map +0 -1
  416. package/dist/cjs/src/utils/session.d.ts.map +0 -1
  417. package/dist/cjs/src/utils/session.js.map +0 -1
  418. package/dist/cjs/src/utils/template.d.ts.map +0 -1
  419. package/dist/cjs/src/utils/template.js.map +0 -1
  420. package/dist/src/adapters/MemoryAdapter.js.map +0 -1
  421. package/dist/src/adapters/MongoAdapter.js.map +0 -1
  422. package/dist/src/adapters/OpenSearchAdapter.js.map +0 -1
  423. package/dist/src/adapters/PostgreSQLAdapter.js.map +0 -1
  424. package/dist/src/adapters/PrismaAdapter.js.map +0 -1
  425. package/dist/src/adapters/RedisAdapter.js.map +0 -1
  426. package/dist/src/adapters/SQLiteAdapter.js.map +0 -1
  427. package/dist/src/adapters/index.js.map +0 -1
  428. package/dist/src/constants/index.js.map +0 -1
  429. package/dist/src/core/Agent.d.ts.map +0 -1
  430. package/dist/src/core/Agent.js.map +0 -1
  431. package/dist/src/core/Events.js.map +0 -1
  432. package/dist/src/core/PersistenceManager.js.map +0 -1
  433. package/dist/src/core/PromptComposer.d.ts.map +0 -1
  434. package/dist/src/core/PromptComposer.js.map +0 -1
  435. package/dist/src/core/ResponseEngine.js +0 -80
  436. package/dist/src/core/ResponseEngine.js.map +0 -1
  437. package/dist/src/core/ResponseModal.d.ts.map +0 -1
  438. package/dist/src/core/ResponseModal.js.map +0 -1
  439. package/dist/src/core/ResponsePipeline.d.ts.map +0 -1
  440. package/dist/src/core/ResponsePipeline.js.map +0 -1
  441. package/dist/src/core/Route.d.ts.map +0 -1
  442. package/dist/src/core/Route.js.map +0 -1
  443. package/dist/src/core/RoutingEngine.d.ts.map +0 -1
  444. package/dist/src/core/RoutingEngine.js.map +0 -1
  445. package/dist/src/core/SessionManager.js.map +0 -1
  446. package/dist/src/core/Step.d.ts.map +0 -1
  447. package/dist/src/core/Step.js.map +0 -1
  448. package/dist/src/core/ToolManager.js.map +0 -1
  449. package/dist/src/index.d.ts.map +0 -1
  450. package/dist/src/index.js.map +0 -1
  451. package/dist/src/providers/AnthropicProvider.js.map +0 -1
  452. package/dist/src/providers/GeminiProvider.js.map +0 -1
  453. package/dist/src/providers/OpenAIProvider.js.map +0 -1
  454. package/dist/src/providers/OpenRouterProvider.js.map +0 -1
  455. package/dist/src/providers/index.js.map +0 -1
  456. package/dist/src/types/agent.d.ts.map +0 -1
  457. package/dist/src/types/agent.js.map +0 -1
  458. package/dist/src/types/history.js.map +0 -1
  459. package/dist/src/types/index.js.map +0 -1
  460. package/dist/src/types/persistence.js.map +0 -1
  461. package/dist/src/types/route.d.ts.map +0 -1
  462. package/dist/src/types/template.d.ts +0 -30
  463. package/dist/src/types/template.d.ts.map +0 -1
  464. package/dist/src/types/tool.js.map +0 -1
  465. package/dist/src/utils/clone.js.map +0 -1
  466. package/dist/src/utils/event.js.map +0 -1
  467. package/dist/src/utils/history.js.map +0 -1
  468. package/dist/src/utils/id.js.map +0 -1
  469. package/dist/src/utils/index.d.ts.map +0 -1
  470. package/dist/src/utils/index.js.map +0 -1
  471. package/dist/src/utils/logger.js.map +0 -1
  472. package/dist/src/utils/retry.d.ts.map +0 -1
  473. package/dist/src/utils/retry.js.map +0 -1
  474. package/dist/src/utils/session.js.map +0 -1
  475. package/dist/src/utils/template.d.ts.map +0 -1
  476. package/dist/src/utils/template.js.map +0 -1
  477. /package/dist/{cjs/src/adapters → adapters}/MemoryAdapter.d.ts +0 -0
  478. /package/dist/{src/adapters → adapters}/MemoryAdapter.js +0 -0
  479. /package/dist/{cjs/src/adapters → adapters}/MongoAdapter.d.ts +0 -0
  480. /package/dist/{src/adapters → adapters}/MongoAdapter.js +0 -0
  481. /package/dist/{cjs/src/adapters → adapters}/OpenSearchAdapter.d.ts +0 -0
  482. /package/dist/{src/adapters → adapters}/OpenSearchAdapter.js +0 -0
  483. /package/dist/{cjs/src/adapters → adapters}/PostgreSQLAdapter.d.ts +0 -0
  484. /package/dist/{src/adapters → adapters}/PostgreSQLAdapter.js +0 -0
  485. /package/dist/{cjs/src/adapters → adapters}/PrismaAdapter.d.ts +0 -0
  486. /package/dist/{cjs/src/adapters → adapters}/RedisAdapter.d.ts +0 -0
  487. /package/dist/{cjs/src/adapters → adapters}/SQLiteAdapter.d.ts +0 -0
  488. /package/dist/{src/adapters → adapters}/SQLiteAdapter.js +0 -0
  489. /package/dist/{cjs/src/adapters → adapters}/index.d.ts +0 -0
  490. /package/dist/{src/adapters → adapters}/index.js +0 -0
  491. /package/dist/{src → cjs}/adapters/MemoryAdapter.d.ts +0 -0
  492. /package/dist/{src → cjs}/adapters/MemoryAdapter.d.ts.map +0 -0
  493. /package/dist/cjs/{src/adapters → adapters}/MemoryAdapter.js +0 -0
  494. /package/dist/{src → cjs}/adapters/MongoAdapter.d.ts +0 -0
  495. /package/dist/{src → cjs}/adapters/MongoAdapter.d.ts.map +0 -0
  496. /package/dist/cjs/{src/adapters → adapters}/MongoAdapter.js +0 -0
  497. /package/dist/{src → cjs}/adapters/OpenSearchAdapter.d.ts +0 -0
  498. /package/dist/{src → cjs}/adapters/OpenSearchAdapter.d.ts.map +0 -0
  499. /package/dist/cjs/{src/adapters → adapters}/OpenSearchAdapter.js +0 -0
  500. /package/dist/{src → cjs}/adapters/PostgreSQLAdapter.d.ts +0 -0
  501. /package/dist/{src → cjs}/adapters/PostgreSQLAdapter.d.ts.map +0 -0
  502. /package/dist/cjs/{src/adapters → adapters}/PostgreSQLAdapter.js +0 -0
  503. /package/dist/{src → cjs}/adapters/PrismaAdapter.d.ts +0 -0
  504. /package/dist/{src → cjs}/adapters/RedisAdapter.d.ts +0 -0
  505. /package/dist/{src → cjs}/adapters/RedisAdapter.d.ts.map +0 -0
  506. /package/dist/{src → cjs}/adapters/SQLiteAdapter.d.ts +0 -0
  507. /package/dist/{src → cjs}/adapters/SQLiteAdapter.d.ts.map +0 -0
  508. /package/dist/cjs/{src/adapters → adapters}/SQLiteAdapter.js +0 -0
  509. /package/dist/{src → cjs}/adapters/index.d.ts +0 -0
  510. /package/dist/{src → cjs}/adapters/index.d.ts.map +0 -0
  511. /package/dist/cjs/{src/adapters → adapters}/index.js +0 -0
  512. /package/dist/cjs/{src/constants → constants}/index.d.ts +0 -0
  513. /package/dist/{src → cjs}/constants/index.d.ts.map +0 -0
  514. /package/dist/cjs/{src/constants → constants}/index.js +0 -0
  515. /package/dist/cjs/{src/core → core}/Events.d.ts +0 -0
  516. /package/dist/{src → cjs}/core/Events.d.ts.map +0 -0
  517. /package/dist/cjs/{src/core → core}/Events.js +0 -0
  518. /package/dist/cjs/{src/core → core}/PersistenceManager.d.ts +0 -0
  519. /package/dist/{src → cjs}/core/PersistenceManager.d.ts.map +0 -0
  520. /package/dist/cjs/{src/core → core}/PersistenceManager.js +0 -0
  521. /package/dist/cjs/{src/core → core}/ResponseEngine.d.ts +0 -0
  522. /package/dist/cjs/{src/core → core}/SessionManager.d.ts +0 -0
  523. /package/dist/{src → cjs}/core/SessionManager.d.ts.map +0 -0
  524. /package/dist/cjs/{src/core → core}/SessionManager.js +0 -0
  525. /package/dist/cjs/{src/core → core}/ToolManager.d.ts +0 -0
  526. /package/dist/{src → cjs}/core/ToolManager.d.ts.map +0 -0
  527. /package/dist/cjs/{src/core → core}/ToolManager.js +0 -0
  528. /package/dist/cjs/{src/providers → providers}/AnthropicProvider.d.ts +0 -0
  529. /package/dist/{src → cjs}/providers/AnthropicProvider.d.ts.map +0 -0
  530. /package/dist/cjs/{src/providers → providers}/GeminiProvider.d.ts +0 -0
  531. /package/dist/cjs/{src/providers → providers}/OpenAIProvider.d.ts +0 -0
  532. /package/dist/{src → cjs}/providers/OpenAIProvider.d.ts.map +0 -0
  533. /package/dist/cjs/{src/providers → providers}/OpenRouterProvider.d.ts +0 -0
  534. /package/dist/{src → cjs}/providers/OpenRouterProvider.d.ts.map +0 -0
  535. /package/dist/cjs/{src/providers → providers}/index.d.ts +0 -0
  536. /package/dist/{src → cjs}/providers/index.d.ts.map +0 -0
  537. /package/dist/cjs/{src/providers → providers}/index.js +0 -0
  538. /package/dist/cjs/{src/types → types}/agent.js +0 -0
  539. /package/dist/cjs/{src/types → types}/ai.d.ts +0 -0
  540. /package/dist/{src → cjs}/types/ai.d.ts.map +0 -0
  541. /package/dist/cjs/{src/types → types}/ai.js +0 -0
  542. /package/dist/cjs/{src/types → types}/history.d.ts +0 -0
  543. /package/dist/{src → cjs}/types/history.d.ts.map +0 -0
  544. /package/dist/cjs/{src/types → types}/history.js +0 -0
  545. /package/dist/cjs/{src/types → types}/persistence.d.ts +0 -0
  546. /package/dist/{src → cjs}/types/persistence.d.ts.map +0 -0
  547. /package/dist/cjs/{src/types → types}/persistence.js +0 -0
  548. /package/dist/cjs/{src/types → types}/route.js +0 -0
  549. /package/dist/cjs/{src/types → types}/routing.d.ts +0 -0
  550. /package/dist/{src → cjs}/types/routing.d.ts.map +0 -0
  551. /package/dist/cjs/{src/types → types}/routing.js +0 -0
  552. /package/dist/{src → cjs}/types/routing.js.map +0 -0
  553. /package/dist/cjs/{src/types → types}/schema.d.ts +0 -0
  554. /package/dist/{src → cjs}/types/schema.d.ts.map +0 -0
  555. /package/dist/cjs/{src/types → types}/schema.js +0 -0
  556. /package/dist/{src → cjs}/types/schema.js.map +0 -0
  557. /package/dist/cjs/{src/types → types}/session.d.ts +0 -0
  558. /package/dist/{src → cjs}/types/session.d.ts.map +0 -0
  559. /package/dist/cjs/{src/types → types}/session.js +0 -0
  560. /package/dist/cjs/{src/types → types}/template.js +0 -0
  561. /package/dist/{src → cjs}/types/template.js.map +0 -0
  562. /package/dist/cjs/{src/types → types}/tool.d.ts +0 -0
  563. /package/dist/{src → cjs}/types/tool.d.ts.map +0 -0
  564. /package/dist/cjs/{src/types → types}/tool.js +0 -0
  565. /package/dist/cjs/{src/utils → utils}/clone.d.ts +0 -0
  566. /package/dist/{src → cjs}/utils/clone.d.ts.map +0 -0
  567. /package/dist/cjs/{src/utils → utils}/clone.js +0 -0
  568. /package/dist/cjs/{src/utils → utils}/event.d.ts +0 -0
  569. /package/dist/{src → cjs}/utils/event.d.ts.map +0 -0
  570. /package/dist/cjs/{src/utils → utils}/event.js +0 -0
  571. /package/dist/cjs/{src/utils → utils}/history.d.ts +0 -0
  572. /package/dist/{src → cjs}/utils/history.d.ts.map +0 -0
  573. /package/dist/cjs/{src/utils → utils}/history.js +0 -0
  574. /package/dist/cjs/{src/utils → utils}/id.d.ts +0 -0
  575. /package/dist/{src → cjs}/utils/id.d.ts.map +0 -0
  576. /package/dist/cjs/{src/utils → utils}/id.js +0 -0
  577. /package/dist/cjs/{src/utils → utils}/logger.d.ts +0 -0
  578. /package/dist/{src → cjs}/utils/logger.d.ts.map +0 -0
  579. /package/dist/cjs/{src/utils → utils}/logger.js +0 -0
  580. /package/dist/cjs/{src/utils → utils}/session.d.ts +0 -0
  581. /package/dist/{src → cjs}/utils/session.d.ts.map +0 -0
  582. /package/dist/cjs/{src/utils → utils}/session.js +0 -0
  583. /package/dist/{src/constants → constants}/index.d.ts +0 -0
  584. /package/dist/{src/constants → constants}/index.js +0 -0
  585. /package/dist/{src/core → core}/Events.d.ts +0 -0
  586. /package/dist/{src/core → core}/Events.js +0 -0
  587. /package/dist/{src/core → core}/PersistenceManager.d.ts +0 -0
  588. /package/dist/{src/core → core}/PersistenceManager.js +0 -0
  589. /package/dist/{src/core → core}/ResponseEngine.d.ts +0 -0
  590. /package/dist/{src/core → core}/SessionManager.d.ts +0 -0
  591. /package/dist/{src/core → core}/SessionManager.js +0 -0
  592. /package/dist/{src/core → core}/ToolManager.d.ts +0 -0
  593. /package/dist/{src/core → core}/ToolManager.js +0 -0
  594. /package/dist/{src/providers → providers}/AnthropicProvider.d.ts +0 -0
  595. /package/dist/{src/providers → providers}/GeminiProvider.d.ts +0 -0
  596. /package/dist/{src/providers → providers}/OpenAIProvider.d.ts +0 -0
  597. /package/dist/{src/providers → providers}/OpenRouterProvider.d.ts +0 -0
  598. /package/dist/{src/providers → providers}/index.d.ts +0 -0
  599. /package/dist/{src/providers → providers}/index.js +0 -0
  600. /package/dist/{src/types → types}/agent.js +0 -0
  601. /package/dist/{src/types → types}/ai.d.ts +0 -0
  602. /package/dist/{src/types → types}/ai.js +0 -0
  603. /package/dist/{src/types → types}/history.d.ts +0 -0
  604. /package/dist/{src/types → types}/history.js +0 -0
  605. /package/dist/{src/types → types}/persistence.d.ts +0 -0
  606. /package/dist/{src/types → types}/persistence.js +0 -0
  607. /package/dist/{src/types → types}/route.js +0 -0
  608. /package/dist/{src/types → types}/routing.d.ts +0 -0
  609. /package/dist/{src/types → types}/routing.js +0 -0
  610. /package/dist/{src/types → types}/schema.d.ts +0 -0
  611. /package/dist/{src/types → types}/schema.js +0 -0
  612. /package/dist/{src/types → types}/session.d.ts +0 -0
  613. /package/dist/{src/types → types}/session.js +0 -0
  614. /package/dist/{src/types → types}/template.js +0 -0
  615. /package/dist/{src/types → types}/tool.d.ts +0 -0
  616. /package/dist/{src/types → types}/tool.js +0 -0
  617. /package/dist/{src/utils → utils}/clone.d.ts +0 -0
  618. /package/dist/{src/utils → utils}/clone.js +0 -0
  619. /package/dist/{src/utils → utils}/event.d.ts +0 -0
  620. /package/dist/{src/utils → utils}/event.js +0 -0
  621. /package/dist/{src/utils → utils}/history.d.ts +0 -0
  622. /package/dist/{src/utils → utils}/history.js +0 -0
  623. /package/dist/{src/utils → utils}/id.d.ts +0 -0
  624. /package/dist/{src/utils → utils}/id.js +0 -0
  625. /package/dist/{src/utils → utils}/logger.d.ts +0 -0
  626. /package/dist/{src/utils → utils}/logger.js +0 -0
  627. /package/dist/{src/utils → utils}/session.d.ts +0 -0
  628. /package/dist/{src/utils → utils}/session.js +0 -0
@@ -15,6 +15,7 @@ import type {
15
15
  ToolEventData,
16
16
  AgentStructuredResponse,
17
17
  Term,
18
+ StoppedReason,
18
19
  } from "../types";
19
20
  import { EventKind, MessageRole } from "../types";
20
21
  import type { Agent } from "./Agent";
@@ -22,9 +23,13 @@ import type { Route } from "./Route";
22
23
  import { Step } from "./Step";
23
24
  import { ResponseEngine } from "./ResponseEngine";
24
25
  import { ResponsePipeline } from "./ResponsePipeline";
26
+ import { BatchExecutor, type HookFunction } from "./BatchExecutor";
27
+ import { BatchPromptBuilder } from "./BatchPromptBuilder";
25
28
  import { cloneDeep, mergeCollected, enterStep, getLastMessageFromHistory, render, logger, historyToEvents } from "../utils";
29
+ import { createTemplateContext } from "../utils/template";
26
30
  import type { ToolManager } from "./ToolManager";
27
31
  import { END_ROUTE_ID } from "../constants";
32
+ import type { StepOptions } from "../types/route";
28
33
 
29
34
  /**
30
35
  * Configuration options for ResponseModal
@@ -129,6 +134,12 @@ interface ResponseContext<TContext = unknown, TData = unknown> {
129
134
  selectedStep?: Step<TContext, TData>;
130
135
  responseDirectives?: string[];
131
136
  isRouteComplete: boolean;
137
+ /** Batch of steps to execute (for multi-step execution) */
138
+ batchSteps?: StepOptions<TContext, TData>[];
139
+ /** Reason why batch determination stopped */
140
+ batchStoppedReason?: StoppedReason;
141
+ /** Step that caused batch to stop (if applicable) */
142
+ batchStoppedAtStep?: StepOptions<TContext, TData>;
132
143
  }
133
144
 
134
145
  /**
@@ -138,6 +149,8 @@ interface ResponseContext<TContext = unknown, TData = unknown> {
138
149
  export class ResponseModal<TContext = unknown, TData = unknown> {
139
150
  private readonly responseEngine: ResponseEngine<TContext, TData>;
140
151
  private readonly responsePipeline: ResponsePipeline<TContext, TData>;
152
+ private readonly batchExecutor: BatchExecutor<TContext, TData>;
153
+ private readonly batchPromptBuilder: BatchPromptBuilder<TContext, TData>;
141
154
 
142
155
  constructor(
143
156
  private readonly agent: Agent<TContext, TData>,
@@ -157,6 +170,12 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
157
170
  this.agent.updateCollectedData.bind(this.agent),
158
171
  this.getToolManager()
159
172
  );
173
+
174
+ // Initialize batch executor for multi-step execution
175
+ this.batchExecutor = new BatchExecutor<TContext, TData>();
176
+
177
+ // Initialize batch prompt builder for combined prompts
178
+ this.batchPromptBuilder = new BatchPromptBuilder<TContext, TData>();
160
179
  }
161
180
 
162
181
  /**
@@ -418,12 +437,16 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
418
437
  }
419
438
 
420
439
  // PHASE 2: ROUTING + STEP SELECTION - Determine which route and step to use
440
+ // Also performs pre-extraction and batch determination
421
441
  let routingResult: {
422
442
  selectedRoute?: Route<TContext, TData>;
423
443
  selectedStep?: Step<TContext, TData>;
424
444
  responseDirectives?: string[];
425
445
  session: SessionState<TData>;
426
446
  isRouteComplete: boolean;
447
+ batchSteps?: StepOptions<TContext, TData>[];
448
+ batchStoppedReason?: StoppedReason;
449
+ batchStoppedAtStep?: StepOptions<TContext, TData>;
427
450
  };
428
451
  try {
429
452
  routingResult = await this.handleUnifiedRoutingAndStepSelection({
@@ -444,6 +467,9 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
444
467
  selectedStep: routingResult.selectedStep,
445
468
  responseDirectives: routingResult.responseDirectives,
446
469
  isRouteComplete: routingResult.isRouteComplete,
470
+ batchSteps: routingResult.batchSteps,
471
+ batchStoppedReason: routingResult.batchStoppedReason,
472
+ batchStoppedAtStep: routingResult.batchStoppedAtStep,
447
473
  };
448
474
  } catch (error) {
449
475
  // Re-throw ResponseGenerationError as-is, wrap others
@@ -469,6 +495,12 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
469
495
  responseDirectives?: string[];
470
496
  session: SessionState<TData>;
471
497
  isRouteComplete: boolean;
498
+ /** Batch of steps to execute (for multi-step execution) */
499
+ batchSteps?: StepOptions<TContext, TData>[];
500
+ /** Reason why batch determination stopped */
501
+ batchStoppedReason?: StoppedReason;
502
+ /** Step that caused batch to stop (if applicable) */
503
+ batchStoppedAtStep?: StepOptions<TContext, TData>;
472
504
  }> {
473
505
  try {
474
506
  // Use the ResponsePipeline for optimized routing and step selection
@@ -480,12 +512,84 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
480
512
  context: params.context,
481
513
  signal: params.signal,
482
514
  });
515
+
516
+ let updatedSession = routingResult.session;
517
+ let isRouteComplete = routingResult.isRouteComplete;
518
+
519
+ // PRE-EXTRACTION: If entering a route that collects data, extract data from user message first
520
+ // This allows us to skip steps whose data is already provided
521
+ // Requirement 3.1: Perform Pre_Extraction before determining the Batch
522
+ if (routingResult.selectedRoute && !isRouteComplete) {
523
+ // Always pre-extract when route collects data (not just on new route entry)
524
+ // This ensures batch determination has the most up-to-date data
525
+ if (this.shouldPreExtractData(routingResult.selectedRoute)) {
526
+ logger.debug(
527
+ `[ResponseModal] Pre-extracting data for route: ${routingResult.selectedRoute.title}`
528
+ );
529
+
530
+ const extractedData = await this.preExtractRouteData({
531
+ route: routingResult.selectedRoute,
532
+ history: params.history,
533
+ context: params.context,
534
+ session: updatedSession,
535
+ signal: params.signal,
536
+ });
537
+
538
+ if (extractedData && Object.keys(extractedData).length > 0) {
539
+ logger.debug(
540
+ `[ResponseModal] Pre-extracted data:`,
541
+ extractedData
542
+ );
543
+ // Requirement 3.3: Merge pre-extracted data into session before batch determination
544
+ updatedSession = mergeCollected(updatedSession, extractedData);
545
+ // Also update agent's collected data
546
+ await this.agent.updateCollectedData(extractedData);
547
+
548
+ // Re-check route completion after pre-extraction
549
+ const allRequiredFieldsCollected = routingResult.selectedRoute.isComplete(updatedSession.data || {});
550
+ if (allRequiredFieldsCollected) {
551
+ logger.debug(
552
+ `[ResponseModal] Route ${routingResult.selectedRoute.title} completed after pre-extraction`
553
+ );
554
+ isRouteComplete = true;
555
+ }
556
+ }
557
+ }
558
+ }
559
+
560
+ // BATCH DETERMINATION: Use BatchExecutor to determine which steps can execute together
561
+ // Requirement 3.4: Pre-extraction results affect batch determination
562
+ let batchSteps: StepOptions<TContext, TData>[] | undefined;
563
+ let batchStoppedReason: StoppedReason | undefined;
564
+ let batchStoppedAtStep: StepOptions<TContext, TData> | undefined;
565
+
566
+ if (routingResult.selectedRoute && !isRouteComplete) {
567
+ // Determine current step position for batch determination
568
+ const currentStep = routingResult.selectedStep ||
569
+ (updatedSession.currentStep ? routingResult.selectedRoute.getStep(updatedSession.currentStep.id) : undefined);
570
+
571
+ logger.debug(`[ResponseModal] Determining batch starting from step: ${currentStep?.id || 'initial'}`);
572
+
573
+ const batchResult = await this.batchExecutor.determineBatch({
574
+ route: routingResult.selectedRoute,
575
+ currentStep,
576
+ sessionData: updatedSession.data || {},
577
+ context: params.context,
578
+ });
579
+
580
+ batchSteps = batchResult.steps;
581
+ batchStoppedReason = batchResult.stoppedReason;
582
+ batchStoppedAtStep = batchResult.stoppedAtStep;
583
+
584
+ logger.debug(`[ResponseModal] Batch determined: ${batchSteps.length} steps, stopped reason: ${batchStoppedReason}`);
585
+ }
586
+
483
587
  // Determine next step using pipeline method for consistency
484
- const stepResult = this.responsePipeline.determineNextStep({
588
+ const stepResult = await this.responsePipeline.determineNextStep({
485
589
  selectedRoute: routingResult.selectedRoute,
486
590
  selectedStep: routingResult.selectedStep,
487
- session: routingResult.session,
488
- isRouteComplete: routingResult.isRouteComplete,
591
+ session: updatedSession, // Use updated session with pre-extracted data
592
+ isRouteComplete, // Use updated completion status
489
593
  });
490
594
 
491
595
  return {
@@ -493,13 +597,106 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
493
597
  selectedStep: stepResult.nextStep, // Use the determined next step
494
598
  responseDirectives: routingResult.responseDirectives,
495
599
  session: stepResult.session,
496
- isRouteComplete: routingResult.isRouteComplete,
600
+ isRouteComplete, // Use updated completion status
601
+ batchSteps,
602
+ batchStoppedReason,
603
+ batchStoppedAtStep,
497
604
  };
498
605
  } catch (error) {
499
606
  throw ResponseGenerationError.fromError(error, 'routing_optimization', params);
500
607
  }
501
608
  }
502
609
 
610
+ /**
611
+ * Check if a route should pre-extract data before determining the initial step
612
+ * @private
613
+ */
614
+ private shouldPreExtractData(route: Route<TContext, TData>): boolean {
615
+ // Pre-extract if route has declared required or optional fields
616
+ if (route.requiredFields && route.requiredFields.length > 0) {
617
+ return true;
618
+ }
619
+ if (route.optionalFields && route.optionalFields.length > 0) {
620
+ return true;
621
+ }
622
+
623
+ // Pre-extract if any step in the route collects data
624
+ const steps = route.getAllSteps();
625
+ const hasDataCollectionSteps = steps.some(
626
+ step => step.collect && step.collect.length > 0
627
+ );
628
+
629
+ return hasDataCollectionSteps;
630
+ }
631
+
632
+ /**
633
+ * Pre-extract data from user message when entering a route
634
+ * This allows skipping steps whose data is already provided
635
+ * @private
636
+ */
637
+ private async preExtractRouteData(params: {
638
+ route: Route<TContext, TData>;
639
+ history: Event[];
640
+ context: TContext;
641
+ session: SessionState<TData>;
642
+ signal?: AbortSignal;
643
+ }): Promise<Partial<TData>> {
644
+ const { route, history, context, signal } = params;
645
+
646
+ // Build a schema for data extraction based on route's fields
647
+ const extractionSchema = this.agent.getSchema();
648
+ if (!extractionSchema) {
649
+ logger.warn(`[ResponseModal] No schema available for pre-extraction`);
650
+ return {};
651
+ }
652
+
653
+ // Get last user message
654
+ const lastMessage = getLastMessageFromHistory(history);
655
+
656
+ // Build extraction prompt
657
+ const extractionPrompt = [
658
+ `Extract any relevant information from the user's message that matches the following data fields.`,
659
+ `Only extract information that is explicitly stated or clearly implied.`,
660
+ ``,
661
+ `User's message: "${lastMessage}"`,
662
+ ``,
663
+ `Extract data for these fields if present:`,
664
+ ];
665
+
666
+ // Add field descriptions
667
+ if (route.requiredFields) {
668
+ extractionPrompt.push(`Required fields: ${route.requiredFields.join(', ')}`);
669
+ }
670
+ if (route.optionalFields) {
671
+ extractionPrompt.push(`Optional fields: ${route.optionalFields.join(', ')}`);
672
+ }
673
+
674
+ extractionPrompt.push(
675
+ ``,
676
+ `Return ONLY the extracted data as JSON. If no data can be extracted, return an empty object {}.`
677
+ );
678
+
679
+ // Call AI to extract data
680
+ const agentOptions = this.agent.getAgentOptions();
681
+ try {
682
+ const result = await agentOptions.provider.generateMessage<TContext, Partial<TData>>({
683
+ prompt: extractionPrompt.join('\n'),
684
+ history,
685
+ context,
686
+ signal,
687
+ parameters: {
688
+ jsonSchema: extractionSchema,
689
+ schemaName: 'data_extraction',
690
+ },
691
+ });
692
+
693
+ return result.structured || {};
694
+ } catch (error) {
695
+ logger.error(`[ResponseModal] Pre-extraction failed:`, error);
696
+ return {};
697
+ }
698
+ }
699
+
503
700
  /**
504
701
  * Unified response generation for non-streaming responses
505
702
  * @private
@@ -507,7 +704,17 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
507
704
  private async generateUnifiedResponse(
508
705
  responseContext: ResponseContext<TContext, TData>
509
706
  ): Promise<AgentResponse<TData>> {
510
- const { effectiveContext, session: initialSession, history, selectedRoute, selectedStep, responseDirectives, isRouteComplete } = responseContext;
707
+ const {
708
+ effectiveContext,
709
+ session: initialSession,
710
+ history,
711
+ selectedRoute,
712
+ selectedStep,
713
+ responseDirectives,
714
+ isRouteComplete,
715
+ batchSteps,
716
+ batchStoppedReason,
717
+ } = responseContext;
511
718
  let session = initialSession;
512
719
 
513
720
  // Get last user message (needed for both route and completion handling)
@@ -517,42 +724,87 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
517
724
 
518
725
  let message: string;
519
726
  let toolCalls: Array<{ toolName: string; arguments: Record<string, unknown> }> | undefined = undefined;
727
+ let executedSteps: StepRef[] | undefined;
728
+ let stoppedReason: StoppedReason | undefined;
520
729
 
521
730
 
522
731
 
523
732
  if (selectedRoute && !isRouteComplete) {
524
- // Handle normal route processing
733
+ // Check if we have batch steps to execute
734
+ if (batchSteps && batchSteps.length > 0) {
735
+ // BATCH EXECUTION: Execute multiple steps in a single LLM call
736
+ logger.debug(`[ResponseModal] Executing batch of ${batchSteps.length} steps`);
525
737
 
526
- const result = await this.processRouteResponse({
527
- selectedRoute,
528
- selectedStep,
529
- responseDirectives,
530
- session,
531
- history,
532
- context: effectiveContext,
533
- lastMessageText,
534
- historyEvents,
535
- signal: responseContext.history ? undefined : undefined, // TODO: Fix signal passing
536
- });
738
+ const batchResult = await this.executeBatchResponse({
739
+ selectedRoute,
740
+ batchSteps,
741
+ responseDirectives,
742
+ session,
743
+ history,
744
+ context: effectiveContext,
745
+ historyEvents,
746
+ });
747
+
748
+ message = batchResult.message;
749
+ toolCalls = batchResult.toolCalls;
750
+ session = batchResult.session;
751
+ executedSteps = batchResult.executedSteps;
752
+ stoppedReason = batchStoppedReason;
753
+
754
+ } else {
755
+ // SINGLE STEP EXECUTION: Fall back to single-step processing
756
+ // This happens when batch determination returns empty (first step needs input)
757
+ const result = await this.processRouteResponse({
758
+ selectedRoute,
759
+ selectedStep,
760
+ responseDirectives,
761
+ session,
762
+ history,
763
+ context: effectiveContext,
764
+ lastMessageText,
765
+ historyEvents,
766
+ signal: undefined,
767
+ });
537
768
 
538
- message = result.message;
539
- toolCalls = result.toolCalls;
540
- session = result.session;
769
+ message = result.message;
770
+ toolCalls = result.toolCalls;
771
+ session = result.session;
772
+
773
+ // Track executed step for single-step execution
774
+ if (selectedStep) {
775
+ executedSteps = [{
776
+ id: selectedStep.id,
777
+ routeId: selectedRoute.id,
778
+ }];
779
+ }
780
+ stoppedReason = batchStoppedReason || 'needs_input';
781
+ }
541
782
 
542
783
  } else if (isRouteComplete && selectedRoute) {
543
784
  // Handle route completion
785
+ logger.debug(`[ResponseModal] Generating completion message for route: ${selectedRoute.title}`);
544
786
 
545
- message = await this.handleRouteCompletion({
546
- selectedRoute,
547
- session,
548
- context: effectiveContext,
549
- lastMessageText,
550
- historyEvents,
551
- });
787
+ try {
788
+ message = await this.handleRouteCompletion({
789
+ selectedRoute,
790
+ session,
791
+ context: effectiveContext,
792
+ lastMessageText,
793
+ historyEvents,
794
+ signal: undefined,
795
+ });
552
796
 
553
- // Set step to END_ROUTE marker
554
- session = enterStep(session, END_ROUTE_ID, "Route completed");
555
- logger.debug(`[ResponseModal] Route ${selectedRoute.title} completed. Entered END_ROUTE step.`);
797
+ // Set step to END_ROUTE marker
798
+ session = enterStep(session, END_ROUTE_ID, "Route completed");
799
+ stoppedReason = 'route_complete';
800
+ logger.debug(`[ResponseModal] Route ${selectedRoute.title} completed. Entered END_ROUTE step.`);
801
+ } catch (error) {
802
+ logger.error(`[ResponseModal] Error generating completion message:`, error);
803
+ // Fallback to simple completion message
804
+ message = `Thank you! I've recorded all the information for your ${selectedRoute.title.toLowerCase()}.`;
805
+ session = enterStep(session, END_ROUTE_ID, "Route completed");
806
+ stoppedReason = 'route_complete';
807
+ }
556
808
 
557
809
  } else {
558
810
  // Fallback: No routes defined, generate a simple response
@@ -562,16 +814,298 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
562
814
  context: effectiveContext,
563
815
  session,
564
816
  });
817
+
818
+ // For fallback responses, set empty executedSteps and no stoppedReason
819
+ // since there's no route/step execution happening
820
+ executedSteps = [];
821
+ stoppedReason = undefined;
565
822
  }
566
823
 
824
+ // Ensure response structure completeness (Requirement 8.1, 8.2, 8.3)
825
+ // - executedSteps: array of steps executed (empty array if none)
826
+ // - stoppedReason: why execution stopped (undefined for fallback)
827
+ // - session.currentStep: reflects final step position
567
828
  return {
568
829
  message,
569
830
  session,
570
831
  toolCalls,
571
832
  isRouteComplete,
833
+ executedSteps: executedSteps || [],
834
+ stoppedReason,
835
+ };
836
+ }
837
+
838
+ /**
839
+ * Execute a batch of steps with a single LLM call
840
+ *
841
+ * This method:
842
+ * 1. Executes all prepare hooks for steps in the batch (in order)
843
+ * 2. Builds a combined prompt using BatchPromptBuilder
844
+ * 3. Makes a single LLM call
845
+ * 4. Collects data from the response for all steps
846
+ * 5. Executes all finalize hooks for steps in the batch (in order)
847
+ *
848
+ * @private
849
+ * **Validates: Requirements 1.1, 4.4, 5.1, 5.2**
850
+ */
851
+ private async executeBatchResponse(params: {
852
+ selectedRoute: Route<TContext, TData>;
853
+ batchSteps: StepOptions<TContext, TData>[];
854
+ responseDirectives?: string[];
855
+ session: SessionState<TData>;
856
+ history: HistoryItem[];
857
+ context: TContext;
858
+ historyEvents: Event[];
859
+ signal?: AbortSignal;
860
+ }): Promise<{
861
+ message: string;
862
+ toolCalls?: Array<{ toolName: string; arguments: Record<string, unknown> }>;
863
+ session: SessionState<TData>;
864
+ executedSteps: StepRef[];
865
+ }> {
866
+ const { selectedRoute, batchSteps, history, context, historyEvents, signal } = params;
867
+ let session = params.session;
868
+
869
+ logger.debug(`[ResponseModal] Starting batch execution for ${batchSteps.length} steps`);
870
+
871
+ // Create hook executor function
872
+ const executeHook = async (
873
+ hook: HookFunction<TContext, TData>,
874
+ hookContext: TContext,
875
+ data?: Partial<TData>,
876
+ step?: StepOptions<TContext, TData>
877
+ ): Promise<void> => {
878
+ // Find the route for this step
879
+ const route = selectedRoute;
880
+ // Convert StepOptions to Step if needed for executePrepareFinalize
881
+ const stepInstance = step?.id ? route.getStep(step.id) : undefined;
882
+ await this.executePrepareFinalize(hook, hookContext, data, route, stepInstance);
883
+ };
884
+
885
+ // PHASE 1: Execute all prepare hooks (Requirement 5.1)
886
+ logger.debug(`[ResponseModal] Executing prepare hooks for batch`);
887
+ const prepareResult = await this.batchExecutor.executePrepareHooks({
888
+ steps: batchSteps,
889
+ context,
890
+ data: session.data,
891
+ executeHook,
892
+ });
893
+
894
+ if (!prepareResult.success) {
895
+ // Prepare hook failed - return error response
896
+ logger.error(`[ResponseModal] Prepare hook failed:`, prepareResult.error);
897
+ throw new ResponseGenerationError(
898
+ `Prepare hook failed: ${prepareResult.error?.message}`,
899
+ {
900
+ phase: 'prepare_hooks',
901
+ context: {
902
+ stepId: prepareResult.error?.stepId,
903
+ executedSteps: prepareResult.executedSteps,
904
+ }
905
+ }
906
+ );
907
+ }
908
+
909
+ // PHASE 2: Build combined prompt using BatchPromptBuilder (Requirement 4.4)
910
+ logger.debug(`[ResponseModal] Building batch prompt`);
911
+ const batchPromptResult = await this.batchPromptBuilder.buildBatchPrompt({
912
+ steps: batchSteps,
913
+ route: selectedRoute,
914
+ history: historyEvents,
915
+ context,
916
+ session,
917
+ agentOptions: this.agent.getAgentOptions(),
918
+ });
919
+
920
+ logger.debug(`[ResponseModal] Batch prompt built with ${batchPromptResult.stepCount} steps, collecting: ${batchPromptResult.collectFields.join(', ')}`);
921
+
922
+ // Build response schema for batch (includes all collect fields)
923
+ const responseSchema = this.buildBatchResponseSchema(batchPromptResult.collectFields);
924
+
925
+ // Collect available tools for AI (from all steps in batch)
926
+ const availableTools = this.collectBatchAvailableTools(selectedRoute, batchSteps);
927
+
928
+ // PHASE 3: Make single LLM call (Requirement 4.4)
929
+ logger.debug(`[ResponseModal] Making LLM call for batch`);
930
+ const agentOptions = this.agent.getAgentOptions();
931
+ const result = await agentOptions.provider.generateMessage({
932
+ prompt: batchPromptResult.prompt,
933
+ history: historyEvents,
934
+ context,
935
+ tools: availableTools,
936
+ signal,
937
+ parameters: responseSchema ? { jsonSchema: responseSchema, schemaName: "batch_response" } : undefined,
938
+ });
939
+
940
+ let message = result.structured?.message || result.message;
941
+ let toolCalls = result.structured?.toolCalls;
942
+
943
+ logger.debug(`[ResponseModal] LLM response received for batch`);
944
+
945
+ // Execute tools if any
946
+ if (toolCalls && toolCalls.length > 0) {
947
+ const toolResult = await this.executeUnifiedToolLoop({
948
+ toolCalls,
949
+ context,
950
+ session,
951
+ history,
952
+ selectedRoute,
953
+ responsePrompt: batchPromptResult.prompt,
954
+ availableTools,
955
+ responseSchema,
956
+ signal,
957
+ });
958
+
959
+ session = toolResult.session;
960
+ toolCalls = toolResult.finalToolCalls;
961
+ if (toolResult.finalMessage) {
962
+ message = toolResult.finalMessage;
963
+ }
964
+ }
965
+
966
+ // PHASE 4: Collect data from response for all steps (Requirement 6.1, 6.2, 6.3)
967
+ logger.debug(`[ResponseModal] Collecting batch data`);
968
+ const collectResult = this.batchExecutor.collectBatchData({
969
+ steps: batchSteps,
970
+ llmResponse: result.structured || {},
971
+ session,
972
+ schema: this.agent.getSchema(),
973
+ });
974
+
975
+ session = collectResult.session;
976
+
977
+ if (collectResult.collectedData && Object.keys(collectResult.collectedData).length > 0) {
978
+ // Update agent's collected data
979
+ await this.agent.updateCollectedData(collectResult.collectedData);
980
+ logger.debug(`[ResponseModal] Batch collected data:`, collectResult.collectedData);
981
+ }
982
+
983
+ if (collectResult.validationErrors && collectResult.validationErrors.length > 0) {
984
+ logger.warn(`[ResponseModal] Batch data validation errors:`, collectResult.validationErrors);
985
+ }
986
+
987
+ // Update session to final step position
988
+ const lastStep = batchSteps[batchSteps.length - 1];
989
+ if (lastStep?.id) {
990
+ session = enterStep(session, lastStep.id, lastStep.description);
991
+ logger.debug(`[ResponseModal] Updated session to final batch step: ${lastStep.id}`);
992
+ }
993
+
994
+ // PHASE 5: Execute all finalize hooks (Requirement 5.2)
995
+ logger.debug(`[ResponseModal] Executing finalize hooks for batch`);
996
+ const finalizeResult = await this.batchExecutor.executeFinalizeHooks({
997
+ steps: batchSteps,
998
+ context,
999
+ data: session.data,
1000
+ executeHook,
1001
+ });
1002
+
1003
+ if (finalizeResult.errors && finalizeResult.errors.length > 0) {
1004
+ // Log finalize errors but don't fail (Requirement 5.5)
1005
+ logger.warn(`[ResponseModal] Some finalize hooks failed:`, finalizeResult.errors);
1006
+ }
1007
+
1008
+ // Build executed steps list
1009
+ const executedSteps: StepRef[] = batchSteps
1010
+ .filter(step => step.id)
1011
+ .map(step => ({
1012
+ id: step.id!,
1013
+ routeId: selectedRoute.id,
1014
+ }));
1015
+
1016
+ logger.debug(`[ResponseModal] Batch execution complete. Executed ${executedSteps.length} steps`);
1017
+
1018
+ return {
1019
+ message,
1020
+ toolCalls,
1021
+ session,
1022
+ executedSteps,
572
1023
  };
573
1024
  }
574
1025
 
1026
+ /**
1027
+ * Build response schema for batch execution
1028
+ * @private
1029
+ */
1030
+ private buildBatchResponseSchema(collectFields: string[]): Record<string, unknown> {
1031
+ const properties: Record<string, unknown> = {
1032
+ message: {
1033
+ type: "string",
1034
+ description: "Your response to the user",
1035
+ },
1036
+ };
1037
+
1038
+ const agentSchema = this.agent.getSchema();
1039
+
1040
+ // Add collect fields to schema, using agent schema definitions when available
1041
+ for (const field of collectFields) {
1042
+ if (agentSchema?.properties && agentSchema.properties[field]) {
1043
+ properties[field] = agentSchema.properties[field];
1044
+ } else {
1045
+ // Dynamic fallback when no agent schema is defined
1046
+ properties[field] = {
1047
+ type: "string",
1048
+ description: `Collected value for ${field}`,
1049
+ };
1050
+ }
1051
+ }
1052
+
1053
+ return {
1054
+ type: "object",
1055
+ properties,
1056
+ required: ["message"],
1057
+ additionalProperties: true,
1058
+ };
1059
+ }
1060
+
1061
+ /**
1062
+ * Collect available tools from all steps in the batch
1063
+ * @private
1064
+ */
1065
+ private collectBatchAvailableTools(
1066
+ route: Route<TContext, TData>,
1067
+ batchSteps: StepOptions<TContext, TData>[]
1068
+ ): Array<{
1069
+ id: string;
1070
+ name: string;
1071
+ description?: string;
1072
+ parameters?: unknown;
1073
+ }> {
1074
+ const availableTools = new Map<string, Tool<TContext, TData>>();
1075
+
1076
+ // Add agent-level tools
1077
+ this.agent.getTools().forEach((tool) => {
1078
+ availableTools.set(tool.id, tool);
1079
+ });
1080
+
1081
+ // Add route-level tools
1082
+ route.getTools().forEach((tool: Tool<TContext, TData>) => {
1083
+ availableTools.set(tool.id, tool);
1084
+ });
1085
+
1086
+ // Add step-level tools from all batch steps
1087
+ for (const step of batchSteps) {
1088
+ if (step.tools) {
1089
+ for (const toolRef of step.tools) {
1090
+ if (typeof toolRef === "string") {
1091
+ // Reference to registered tool - already in availableTools
1092
+ } else if (typeof toolRef === 'object' && 'id' in toolRef && toolRef.id) {
1093
+ // Inline tool definition
1094
+ availableTools.set(toolRef.id, toolRef);
1095
+ }
1096
+ }
1097
+ }
1098
+ }
1099
+
1100
+ // Convert to the format expected by AI providers
1101
+ return Array.from(availableTools.values()).map((tool) => ({
1102
+ id: tool.id,
1103
+ name: tool.name || tool.id,
1104
+ description: tool.description,
1105
+ parameters: tool.parameters,
1106
+ }));
1107
+ }
1108
+
575
1109
  /**
576
1110
  * Unified streaming response generation
577
1111
  * @private
@@ -579,7 +1113,17 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
579
1113
  private async *generateUnifiedStreamingResponse(
580
1114
  responseContext: ResponseContext<TContext, TData>
581
1115
  ): AsyncGenerator<AgentResponseStreamChunk<TData>> {
582
- const { effectiveContext, session: initialSession, history, selectedRoute, selectedStep, responseDirectives, isRouteComplete } = responseContext;
1116
+ const {
1117
+ effectiveContext,
1118
+ session: initialSession,
1119
+ history,
1120
+ selectedRoute,
1121
+ selectedStep,
1122
+ responseDirectives,
1123
+ isRouteComplete,
1124
+ batchSteps,
1125
+ batchStoppedReason,
1126
+ } = responseContext;
583
1127
  const session = initialSession;
584
1128
 
585
1129
  // Get last user message (needed for both route and completion handling)
@@ -588,17 +1132,35 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
588
1132
  const lastMessageText = getLastMessageFromHistory(historyEvents);
589
1133
 
590
1134
  if (selectedRoute && !isRouteComplete) {
591
- // Handle normal route processing with streaming
592
- yield* this.processRouteStreamingResponse({
593
- selectedRoute,
594
- selectedStep,
595
- responseDirectives,
596
- session,
597
- history,
598
- context: effectiveContext,
599
- lastMessageText,
600
- historyEvents,
601
- });
1135
+ // Check if we have batch steps to execute
1136
+ if (batchSteps && batchSteps.length > 0) {
1137
+ // BATCH EXECUTION: Execute multiple steps with streaming
1138
+ // Note: For streaming, we still use batch execution but stream the response
1139
+ logger.debug(`[ResponseModal] Streaming batch execution for ${batchSteps.length} steps`);
1140
+
1141
+ yield* this.streamBatchResponse({
1142
+ selectedRoute,
1143
+ batchSteps,
1144
+ responseDirectives,
1145
+ session,
1146
+ history,
1147
+ context: effectiveContext,
1148
+ historyEvents,
1149
+ batchStoppedReason,
1150
+ });
1151
+ } else {
1152
+ // SINGLE STEP EXECUTION: Fall back to single-step streaming
1153
+ yield* this.processRouteStreamingResponse({
1154
+ selectedRoute,
1155
+ selectedStep,
1156
+ responseDirectives,
1157
+ session,
1158
+ history,
1159
+ context: effectiveContext,
1160
+ lastMessageText,
1161
+ historyEvents,
1162
+ });
1163
+ }
602
1164
 
603
1165
  } else if (isRouteComplete && selectedRoute) {
604
1166
  // Handle route completion streaming
@@ -619,6 +1181,148 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
619
1181
  });
620
1182
  }
621
1183
  }
1184
+
1185
+ /**
1186
+ * Stream a batch response with multiple steps
1187
+ *
1188
+ * Similar to executeBatchResponse but streams the LLM response.
1189
+ *
1190
+ * @private
1191
+ */
1192
+ private async *streamBatchResponse(params: {
1193
+ selectedRoute: Route<TContext, TData>;
1194
+ batchSteps: StepOptions<TContext, TData>[];
1195
+ responseDirectives?: string[];
1196
+ session: SessionState<TData>;
1197
+ history: HistoryItem[];
1198
+ context: TContext;
1199
+ historyEvents: Event[];
1200
+ batchStoppedReason?: StoppedReason;
1201
+ signal?: AbortSignal;
1202
+ }): AsyncGenerator<AgentResponseStreamChunk<TData>> {
1203
+ const { selectedRoute, batchSteps, context, historyEvents, batchStoppedReason, signal } = params;
1204
+ let session = params.session;
1205
+
1206
+ // Create hook executor function
1207
+ const executeHook = async (
1208
+ hook: HookFunction<TContext, TData>,
1209
+ hookContext: TContext,
1210
+ data?: Partial<TData>,
1211
+ step?: StepOptions<TContext, TData>
1212
+ ): Promise<void> => {
1213
+ const route = selectedRoute;
1214
+ const stepInstance = step?.id ? route.getStep(step.id) : undefined;
1215
+ await this.executePrepareFinalize(hook, hookContext, data, route, stepInstance);
1216
+ };
1217
+
1218
+ // PHASE 1: Execute all prepare hooks
1219
+ const prepareResult = await this.batchExecutor.executePrepareHooks({
1220
+ steps: batchSteps,
1221
+ context,
1222
+ data: session.data,
1223
+ executeHook,
1224
+ });
1225
+
1226
+ if (!prepareResult.success) {
1227
+ // Yield error chunk
1228
+ yield {
1229
+ delta: "",
1230
+ accumulated: "",
1231
+ done: true,
1232
+ session,
1233
+ error: new ResponseGenerationError(
1234
+ `Prepare hook failed: ${prepareResult.error?.message}`,
1235
+ { phase: 'prepare_hooks' }
1236
+ ),
1237
+ };
1238
+ return;
1239
+ }
1240
+
1241
+ // PHASE 2: Build combined prompt
1242
+ const batchPromptResult = await this.batchPromptBuilder.buildBatchPrompt({
1243
+ steps: batchSteps,
1244
+ route: selectedRoute,
1245
+ history: historyEvents,
1246
+ context,
1247
+ session,
1248
+ agentOptions: this.agent.getAgentOptions(),
1249
+ });
1250
+
1251
+ const responseSchema = this.buildBatchResponseSchema(batchPromptResult.collectFields);
1252
+ const availableTools = this.collectBatchAvailableTools(selectedRoute, batchSteps);
1253
+
1254
+ // PHASE 3: Stream LLM response
1255
+ const agentOptions = this.agent.getAgentOptions();
1256
+ const stream = agentOptions.provider.generateMessageStream({
1257
+ prompt: batchPromptResult.prompt,
1258
+ history: historyEvents,
1259
+ context,
1260
+ tools: availableTools,
1261
+ signal,
1262
+ parameters: responseSchema ? { jsonSchema: responseSchema, schemaName: "batch_stream_response" } : undefined,
1263
+ });
1264
+
1265
+ // Build executed steps list
1266
+ const executedSteps: StepRef[] = batchSteps
1267
+ .filter(step => step.id)
1268
+ .map(step => ({
1269
+ id: step.id!,
1270
+ routeId: selectedRoute.id,
1271
+ }));
1272
+
1273
+ // Stream chunks
1274
+ for await (const chunk of stream) {
1275
+ // On final chunk, collect data and execute finalize hooks
1276
+ if (chunk.done) {
1277
+ // Collect data from response
1278
+ if (chunk.structured) {
1279
+ const collectResult = this.batchExecutor.collectBatchData({
1280
+ steps: batchSteps,
1281
+ llmResponse: chunk.structured,
1282
+ session,
1283
+ schema: this.agent.getSchema(),
1284
+ });
1285
+
1286
+ session = collectResult.session;
1287
+
1288
+ if (collectResult.collectedData && Object.keys(collectResult.collectedData).length > 0) {
1289
+ await this.agent.updateCollectedData(collectResult.collectedData);
1290
+ }
1291
+ }
1292
+
1293
+ // Update session to final step position
1294
+ const lastStep = batchSteps[batchSteps.length - 1];
1295
+ if (lastStep?.id) {
1296
+ session = enterStep(session, lastStep.id, lastStep.description);
1297
+ }
1298
+
1299
+ // Execute finalize hooks
1300
+ await this.batchExecutor.executeFinalizeHooks({
1301
+ steps: batchSteps,
1302
+ context,
1303
+ data: session.data,
1304
+ executeHook,
1305
+ });
1306
+
1307
+ // Finalize session
1308
+ await this.finalizeSession(session, context);
1309
+ }
1310
+
1311
+ yield {
1312
+ delta: chunk.delta,
1313
+ accumulated: chunk.accumulated,
1314
+ done: chunk.done,
1315
+ session,
1316
+ toolCalls: chunk.structured?.toolCalls,
1317
+ isRouteComplete: false,
1318
+ executedSteps: chunk.done ? executedSteps : undefined,
1319
+ stoppedReason: chunk.done ? batchStoppedReason : undefined,
1320
+ metadata: chunk.metadata,
1321
+ structured: chunk.structured,
1322
+ };
1323
+ }
1324
+ }
1325
+
622
1326
  /**
623
1327
  * Execute prepare function for current step if available
624
1328
  * @private
@@ -698,12 +1402,27 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
698
1402
  if (selectedStep) {
699
1403
  nextStep = selectedStep;
700
1404
  } else {
701
- // New route or no step selected - get initial step or first valid step
1405
+ // Determine current step from session if we're already in this route
1406
+ const isInSameRoute = session.currentRoute?.id === selectedRoute.id;
1407
+ const currentStep = isInSameRoute && session.currentStep
1408
+ ? selectedRoute.getStep(session.currentStep.id)
1409
+ : undefined;
1410
+
1411
+ logger.debug(`[ResponseModal] Step determination: route match=${isInSameRoute}, currentRoute=${session.currentRoute?.id}, selectedRoute=${selectedRoute.id}, currentStep=${currentStep?.id || 'none'}`);
1412
+
1413
+ // Get candidate steps based on current position in the route
702
1414
  const routingEngine = this.agent.getRoutingEngine();
703
- const candidates = routingEngine.getCandidateSteps(selectedRoute, undefined, session.data || {});
1415
+ const candidates = await routingEngine.getCandidateStepsWithConditions(
1416
+ selectedRoute,
1417
+ currentStep, // Pass current step instead of undefined to maintain progression
1418
+ createTemplateContext({ data: session.data, session, context })
1419
+ );
1420
+
1421
+ logger.debug(`[ResponseModal] Found ${candidates.length} candidate steps${currentStep ? ' from current step ' + currentStep.id : ' (new route entry)'}`);
1422
+
704
1423
  if (candidates.length > 0) {
705
1424
  nextStep = candidates[0].step;
706
- logger.debug(`[ResponseModal] Using first valid step: ${nextStep.id} for new route`);
1425
+ logger.debug(`[ResponseModal] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new route'}`);
707
1426
  } else {
708
1427
  // Fallback to initial step even if it should be skipped
709
1428
  nextStep = selectedRoute.initialStep;
@@ -712,8 +1431,34 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
712
1431
  }
713
1432
 
714
1433
  // Update session with next step
715
- session = enterStep(session, nextStep.id, nextStep.description);
716
- logger.debug(`[ResponseModal] Entered step: ${nextStep.id}`);
1434
+ // If the next step has requires fields that are missing, stay at the previous step
1435
+ if (nextStep.requires && nextStep.requires.length > 0) {
1436
+ const sessionData = session.data || {};
1437
+ const missingRequires = nextStep.requires.filter(
1438
+ field => (sessionData as Record<string, unknown>)[String(field)] === undefined
1439
+ );
1440
+ if (missingRequires.length > 0) {
1441
+ const warning = `[Agent] Cannot advance to step "${nextStep.description || nextStep.id}": ` +
1442
+ `missing required fields [${missingRequires.join(', ')}]. Staying at current step.`;
1443
+ logger.warn(warning);
1444
+ console.warn(warning);
1445
+ // Stay at the current step - don't enter the next one
1446
+ const currentStepId = session.currentStep?.id;
1447
+ if (currentStepId) {
1448
+ const currentStepInstance = selectedRoute.getStep(currentStepId);
1449
+ if (currentStepInstance) {
1450
+ nextStep = currentStepInstance;
1451
+ logger.debug(`[ResponseModal] Staying at current step: ${nextStep.id} due to missing requires`);
1452
+ }
1453
+ }
1454
+ } else {
1455
+ session = enterStep(session, nextStep.id, nextStep.description);
1456
+ logger.debug(`[ResponseModal] Entered step: ${nextStep.id}`);
1457
+ }
1458
+ } else {
1459
+ session = enterStep(session, nextStep.id, nextStep.description);
1460
+ logger.debug(`[ResponseModal] Entered step: ${nextStep.id}`);
1461
+ }
717
1462
 
718
1463
  // Build response schema for this route (with collect fields from step)
719
1464
  const responseSchema = this.responseEngine.responseSchemaForRoute(selectedRoute, nextStep, this.agent.getSchema());
@@ -722,8 +1467,8 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
722
1467
  const responsePrompt = await this.responseEngine.buildResponsePrompt({
723
1468
  route: selectedRoute,
724
1469
  currentStep: nextStep,
725
- rules: selectedRoute.getRules(),
726
- prohibitions: selectedRoute.getProhibitions(),
1470
+ rules: [...this.agent.getRules(), ...selectedRoute.getRules()],
1471
+ prohibitions: [...this.agent.getProhibitions(), ...selectedRoute.getProhibitions()],
727
1472
  directives: responseDirectives,
728
1473
  history: historyEvents, // Use Event[] for buildResponsePrompt
729
1474
  lastMessage: lastMessageText, // Use string for buildResponsePrompt
@@ -752,6 +1497,15 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
752
1497
  let message = result.structured?.message || result.message;
753
1498
  let toolCalls = result.structured?.toolCalls;
754
1499
 
1500
+ // Debug: Log initial AI response
1501
+ logger.debug(`[ResponseModal] Initial AI response:`, {
1502
+ hasMessage: !!message,
1503
+ messageLength: message?.length || 0,
1504
+ hasToolCalls: !!toolCalls,
1505
+ toolCallsCount: toolCalls?.length || 0,
1506
+ toolNames: toolCalls?.map(tc => tc.toolName) || [],
1507
+ });
1508
+
755
1509
  // Execute tools with unified loop handling
756
1510
  const toolResult = await this.executeUnifiedToolLoop({
757
1511
  toolCalls,
@@ -800,11 +1554,22 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
800
1554
  if (selectedStep) {
801
1555
  nextStep = selectedStep;
802
1556
  } else {
1557
+ // Determine current step from session if we're already in this route
1558
+ const currentStep = session.currentRoute?.id === selectedRoute.id && session.currentStep
1559
+ ? selectedRoute.getStep(session.currentStep.id)
1560
+ : undefined;
1561
+
1562
+ // Get candidate steps based on current position in the route
803
1563
  const routingEngine = this.agent.getRoutingEngine();
804
- const candidates = routingEngine.getCandidateSteps(selectedRoute, undefined, session.data || {});
1564
+ const candidates = await routingEngine.getCandidateStepsWithConditions(
1565
+ selectedRoute,
1566
+ currentStep, // Pass current step instead of undefined to maintain progression
1567
+ createTemplateContext({ data: session.data, session, context })
1568
+ );
1569
+
805
1570
  if (candidates.length > 0) {
806
1571
  nextStep = candidates[0].step;
807
- logger.debug(`[ResponseModal] Using first valid step: ${nextStep.id} for new route`);
1572
+ logger.debug(`[ResponseModal] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new route'}`);
808
1573
  } else {
809
1574
  nextStep = selectedRoute.initialStep;
810
1575
  logger.warn(`[ResponseModal] No valid steps found, using initial step: ${nextStep.id}`);
@@ -812,16 +1577,41 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
812
1577
  }
813
1578
 
814
1579
  // Update session with next step
815
- session = enterStep(session, nextStep.id, nextStep.description);
816
- logger.debug(`[ResponseModal] Entered step: ${nextStep.id}`);
1580
+ // If the next step has requires fields that are missing, stay at the previous step
1581
+ if (nextStep.requires && nextStep.requires.length > 0) {
1582
+ const sessionData = session.data || {};
1583
+ const missingRequires = nextStep.requires.filter(
1584
+ field => (sessionData as Record<string, unknown>)[String(field)] === undefined
1585
+ );
1586
+ if (missingRequires.length > 0) {
1587
+ const warning = `[Agent] Cannot advance to step "${nextStep.description || nextStep.id}": ` +
1588
+ `missing required fields [${missingRequires.join(', ')}]. Staying at current step.`;
1589
+ logger.warn(warning);
1590
+ console.warn(warning);
1591
+ const currentStepId = session.currentStep?.id;
1592
+ if (currentStepId) {
1593
+ const currentStepInstance = selectedRoute.getStep(currentStepId);
1594
+ if (currentStepInstance) {
1595
+ nextStep = currentStepInstance;
1596
+ logger.debug(`[ResponseModal] Staying at current step: ${nextStep.id} due to missing requires`);
1597
+ }
1598
+ }
1599
+ } else {
1600
+ session = enterStep(session, nextStep.id, nextStep.description);
1601
+ logger.debug(`[ResponseModal] Entered step: ${nextStep.id}`);
1602
+ }
1603
+ } else {
1604
+ session = enterStep(session, nextStep.id, nextStep.description);
1605
+ logger.debug(`[ResponseModal] Entered step: ${nextStep.id}`);
1606
+ }
817
1607
 
818
1608
  // Build response schema and prompt (same as non-streaming)
819
1609
  const responseSchema = this.responseEngine.responseSchemaForRoute(selectedRoute, nextStep, this.agent.getSchema());
820
1610
  const responsePrompt = await this.responseEngine.buildResponsePrompt({
821
1611
  route: selectedRoute,
822
1612
  currentStep: nextStep,
823
- rules: selectedRoute.getRules(),
824
- prohibitions: selectedRoute.getProhibitions(),
1613
+ rules: [...this.agent.getRules(), ...selectedRoute.getRules()],
1614
+ prohibitions: [...this.agent.getProhibitions(), ...selectedRoute.getProhibitions()],
825
1615
  directives: responseDirectives,
826
1616
  history: historyEvents, // Use Event[] for buildResponsePrompt
827
1617
  lastMessage: lastMessageText, // Use string for buildResponsePrompt
@@ -887,6 +1677,10 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
887
1677
  await this.finalizeSession(session, context);
888
1678
  }
889
1679
 
1680
+ // Response structure completeness (Requirement 8.1, 8.2, 8.3)
1681
+ // - executedSteps: single step executed in this response
1682
+ // - stoppedReason: 'needs_input' for single-step execution (waiting for user input)
1683
+ // - session.currentStep: reflects the executed step
890
1684
  yield {
891
1685
  delta: chunk.delta,
892
1686
  accumulated: chunk.accumulated,
@@ -894,6 +1688,8 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
894
1688
  session,
895
1689
  toolCalls,
896
1690
  isRouteComplete: false,
1691
+ executedSteps: chunk.done ? [{ id: nextStep.id, routeId: selectedRoute.id }] : undefined,
1692
+ stoppedReason: chunk.done ? 'needs_input' : undefined,
897
1693
  metadata: chunk.metadata,
898
1694
  structured: chunk.structured,
899
1695
  };
@@ -934,7 +1730,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
934
1730
 
935
1731
  // Execute initial dynamic tool calls
936
1732
  if (toolCalls && toolCalls.length > 0) {
937
- logger.debug(`[ResponseModal] Executing ${toolCalls.length} dynamic tool calls`);
1733
+ logger.debug(`[ResponseModal] Executing ${toolCalls.length} dynamic tool calls:`, toolCalls.map(tc => tc.toolName));
938
1734
 
939
1735
  for (const toolCall of toolCalls) {
940
1736
  const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
@@ -1009,7 +1805,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1009
1805
 
1010
1806
  while (hasToolCalls && toolLoopCount < MAX_TOOL_LOOPS) {
1011
1807
  toolLoopCount++;
1012
- logger.debug(`[ResponseModal] Starting tool loop ${toolLoopCount}/${MAX_TOOL_LOOPS}`);
1808
+ logger.debug(`[ResponseModal] Starting tool loop ${toolLoopCount}/${MAX_TOOL_LOOPS} with ${toolCalls?.length || 0} tool calls`);
1013
1809
 
1014
1810
  // Create tool result events with proper Event format structure
1015
1811
  const toolResultEvents: Event<ToolEventData>[] = [];
@@ -1041,12 +1837,21 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1041
1837
  const updatedHistoryEvents = [...historyEvents, ...toolResultEvents];
1042
1838
 
1043
1839
  // Make follow-up AI call to see if more tools are needed
1840
+ // After first iteration, don't provide tools to force a text response
1044
1841
  const agentOptions = this.agent.getAgentOptions();
1842
+ const shouldProvideTools = toolLoopCount === 1;
1843
+
1844
+ logger.debug(`[ResponseModal] Making follow-up AI call (loop ${toolLoopCount}):`, {
1845
+ providingTools: shouldProvideTools,
1846
+ toolsCount: shouldProvideTools ? availableTools.length : 0,
1847
+ addingTextInstruction: toolLoopCount > 1,
1848
+ });
1849
+
1045
1850
  const followUpResult = await agentOptions.provider.generateMessage({
1046
- prompt: responsePrompt,
1851
+ prompt: responsePrompt + (toolLoopCount > 1 ? "\n\nProvide a text response to the user based on the tool results." : ""),
1047
1852
  history: updatedHistoryEvents, // Use Event[] for AI provider
1048
1853
  context,
1049
- tools: availableTools,
1854
+ tools: shouldProvideTools ? availableTools : [], // Only provide tools on first iteration
1050
1855
  parameters: responseSchema ? {
1051
1856
  jsonSchema: responseSchema,
1052
1857
  schemaName: "tool_followup",
@@ -1058,6 +1863,14 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1058
1863
  const followUpToolCalls = followUpResult.structured?.toolCalls;
1059
1864
  hasToolCalls = followUpToolCalls && followUpToolCalls.length > 0;
1060
1865
 
1866
+ logger.debug(`[ResponseModal] Follow-up AI response (loop ${toolLoopCount}):`, {
1867
+ hasMessage: !!followUpResult.message,
1868
+ messageLength: followUpResult.message?.length || 0,
1869
+ hasToolCalls,
1870
+ toolCallsCount: followUpToolCalls?.length || 0,
1871
+ toolNames: followUpToolCalls?.map(tc => tc.toolName) || [],
1872
+ });
1873
+
1061
1874
  if (hasToolCalls) {
1062
1875
  logger.debug(`[ResponseModal] Follow-up call produced ${followUpToolCalls!.length} additional tool calls`);
1063
1876
 
@@ -1136,6 +1949,13 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1136
1949
  logger.warn(`[ResponseModal] Tool loop limit reached (${MAX_TOOL_LOOPS}), stopping`);
1137
1950
  }
1138
1951
 
1952
+ logger.debug(`[ResponseModal] Tool loop completed:`, {
1953
+ totalIterations: toolLoopCount,
1954
+ hasFinalMessage: !!finalMessage,
1955
+ finalMessageLength: finalMessage?.length || 0,
1956
+ finalToolCallsCount: toolCalls?.length || 0,
1957
+ });
1958
+
1139
1959
  return {
1140
1960
  session,
1141
1961
  finalToolCalls: toolCalls,
@@ -1162,15 +1982,34 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1162
1982
  let updatedSession = session;
1163
1983
 
1164
1984
  // Extract collected data from final response (only for route-based interactions)
1165
- if (selectedRoute && result.structured && nextStep?.collect) {
1985
+ if (selectedRoute && result.structured) {
1166
1986
  try {
1167
1987
  const collectedData: Record<string, unknown> = {};
1168
1988
  // AgentStructuredResponse extends Record<string, unknown>, so we can safely access properties
1169
1989
  const structuredData = result.structured;
1170
1990
 
1171
- for (const field of nextStep.collect) {
1991
+ // Collect ALL route fields (required + optional) from structured response
1992
+ const allRouteFields = new Set<string>();
1993
+
1994
+ // Add route required fields
1995
+ if (selectedRoute.requiredFields) {
1996
+ selectedRoute.requiredFields.forEach(field => allRouteFields.add(String(field)));
1997
+ }
1998
+
1999
+ // Add route optional fields
2000
+ if (selectedRoute.optionalFields) {
2001
+ selectedRoute.optionalFields.forEach(field => allRouteFields.add(String(field)));
2002
+ }
2003
+
2004
+ // Also include current step's collect fields (in case they're not in route fields)
2005
+ if (nextStep?.collect) {
2006
+ nextStep.collect.forEach(field => allRouteFields.add(String(field)));
2007
+ }
2008
+
2009
+ // Extract all available fields from structured response
2010
+ for (const field of allRouteFields) {
1172
2011
  const fieldKey = String(field);
1173
- if (fieldKey in structuredData) {
2012
+ if (fieldKey in structuredData && structuredData[fieldKey] !== undefined && structuredData[fieldKey] !== null) {
1174
2013
  collectedData[fieldKey] = structuredData[fieldKey];
1175
2014
  }
1176
2015
  }
@@ -1244,37 +2083,66 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1244
2083
  prompt: endStepSpec.prompt || "Summarize what was accomplished and confirm completion based on the conversation history and collected data",
1245
2084
  });
1246
2085
 
1247
- // Build response schema for completion
1248
- const responseSchema = this.responseEngine.responseSchemaForRoute(selectedRoute, completionStep, this.agent.getSchema());
1249
- const templateContext = { context, session, history: historyEvents }; // Use Event[] for template context
2086
+ // Build response schema for completion (message only, no data collection)
2087
+ const completionSchema = {
2088
+ type: "object",
2089
+ properties: {
2090
+ message: {
2091
+ type: "string",
2092
+ description: "Completion message confirming what was accomplished",
2093
+ },
2094
+ },
2095
+ required: ["message"],
2096
+ additionalProperties: false,
2097
+ };
2098
+
2099
+ const templateContext = createTemplateContext({ context, session, history: historyEvents });
2100
+
2101
+ // Build completion response prompt using ResponseEngine
2102
+ // Filter out conditional guidelines - only include always-active ones
2103
+ const alwaysActiveGuidelines = [
2104
+ ...this.agent.getGuidelines().filter(g => !g.condition),
2105
+ ...selectedRoute.getGuidelines().filter(g => !g.condition),
2106
+ ];
2107
+ let completitionPrompt = "Summarize what was accomplished and confirm completion"
2108
+ if(endStepSpec.prompt){
2109
+ completitionPrompt = await render(endStepSpec.prompt, templateContext)
2110
+ }
1250
2111
 
1251
- // Build completion response prompt
1252
2112
  const completionPrompt = await this.responseEngine.buildResponsePrompt({
1253
2113
  route: selectedRoute,
1254
2114
  currentStep: completionStep,
1255
2115
  rules: selectedRoute.getRules(),
1256
2116
  prohibitions: selectedRoute.getProhibitions(),
1257
- directives: undefined, // No directives for completion
1258
- history: historyEvents, // Use Event[] for buildResponsePrompt
1259
- lastMessage: lastMessageText, // Use string for buildResponsePrompt
2117
+ directives: [
2118
+ `Task completed: ${selectedRoute.title}`,
2119
+ `Collected data: ${JSON.stringify(session.data, null, 2)}`,
2120
+ "Do NOT ask for more information - the task is complete",
2121
+ completitionPrompt,
2122
+ ],
2123
+ history: historyEvents,
2124
+ lastMessage: lastMessageText,
1260
2125
  agentOptions: this.agent.getAgentOptions(),
1261
- combinedGuidelines: [...this.agent.getGuidelines(), ...selectedRoute.getGuidelines()],
2126
+ combinedGuidelines: alwaysActiveGuidelines, // Only non-conditional guidelines
1262
2127
  combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
1263
2128
  context,
1264
2129
  session,
1265
- agentSchema: this.agent.getSchema(),
2130
+ agentSchema: undefined, // No data collection schema for completion
1266
2131
  });
1267
2132
 
1268
2133
  // Generate completion message using AI provider
1269
2134
  const agentOptions = this.agent.getAgentOptions();
2135
+ logger.debug(`[ResponseModal] Calling AI provider for completion message...`);
2136
+
1270
2137
  const completionResult = await agentOptions.provider.generateMessage({
1271
2138
  prompt: completionPrompt,
1272
- history: historyEvents, // Use Event[] for AI provider
2139
+ history: historyEvents,
1273
2140
  context,
1274
2141
  signal,
1275
- parameters: { jsonSchema: responseSchema, schemaName: "completion_message" },
2142
+ parameters: { jsonSchema: completionSchema, schemaName: "completion_message" },
1276
2143
  });
1277
2144
 
2145
+ logger.debug(`[ResponseModal] AI provider returned completion result`);
1278
2146
  const message = completionResult.structured?.message || completionResult.message;
1279
2147
  logger.debug(`[ResponseModal] Generated completion message for route: ${selectedRoute.title}`);
1280
2148
 
@@ -1333,7 +2201,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1333
2201
 
1334
2202
  // Build response schema for completion
1335
2203
  const responseSchema = this.responseEngine.responseSchemaForRoute(selectedRoute, completionStep, this.agent.getSchema());
1336
- const templateContext = { context, session, history: historyEvents }; // Use Event[] for template context
2204
+ const templateContext = createTemplateContext({ context, session, history: historyEvents }); // Use Event[] for template context
1337
2205
 
1338
2206
  // Build completion response prompt
1339
2207
  const completionPrompt = await this.responseEngine.buildResponsePrompt({
@@ -1401,6 +2269,10 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1401
2269
  await this.finalizeSession(session, context);
1402
2270
  }
1403
2271
 
2272
+ // Response structure completeness (Requirement 8.1, 8.2, 8.3)
2273
+ // - executedSteps: empty for route completion (no new steps executed)
2274
+ // - stoppedReason: 'route_complete' for completed routes
2275
+ // - session.currentStep: set to END_ROUTE
1404
2276
  yield {
1405
2277
  delta: chunk.delta,
1406
2278
  accumulated: chunk.accumulated,
@@ -1408,6 +2280,8 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1408
2280
  session,
1409
2281
  toolCalls: undefined,
1410
2282
  isRouteComplete: true,
2283
+ executedSteps: chunk.done ? [] : undefined,
2284
+ stoppedReason: chunk.done ? 'route_complete' : undefined,
1411
2285
  metadata: chunk.metadata,
1412
2286
  structured: chunk.structured,
1413
2287
  };
@@ -1502,6 +2376,10 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1502
2376
  await this.finalizeSession(session, context);
1503
2377
  }
1504
2378
 
2379
+ // Response structure completeness (Requirement 8.1, 8.2, 8.3)
2380
+ // - executedSteps: empty for fallback (no route/step execution)
2381
+ // - stoppedReason: undefined for fallback (no route context)
2382
+ // - session.currentStep: unchanged (no step progression)
1505
2383
  yield {
1506
2384
  delta: chunk.delta,
1507
2385
  accumulated: chunk.accumulated,
@@ -1509,6 +2387,8 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1509
2387
  session,
1510
2388
  toolCalls: undefined,
1511
2389
  isRouteComplete: false,
2390
+ executedSteps: chunk.done ? [] : undefined,
2391
+ stoppedReason: undefined,
1512
2392
  metadata: chunk.metadata,
1513
2393
  structured: chunk.structured,
1514
2394
  };