@falai/agent 3.4.4 → 4.0.0-alpha.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 (847) hide show
  1. package/README.md +41 -34
  2. package/dist/cjs/core/Agent.d.ts +22 -378
  3. package/dist/cjs/core/Agent.d.ts.map +1 -1
  4. package/dist/cjs/core/Agent.js +104 -1178
  5. package/dist/cjs/core/Agent.js.map +1 -1
  6. package/dist/cjs/core/CompactionEngine.d.ts.map +1 -1
  7. package/dist/cjs/core/CompactionEngine.js +5 -3
  8. package/dist/cjs/core/CompactionEngine.js.map +1 -1
  9. package/dist/cjs/core/FlowSpec.d.ts +136 -0
  10. package/dist/cjs/core/FlowSpec.d.ts.map +1 -0
  11. package/dist/cjs/core/FlowSpec.js +522 -0
  12. package/dist/cjs/core/FlowSpec.js.map +1 -0
  13. package/dist/cjs/core/Migrate.d.ts +38 -0
  14. package/dist/cjs/core/Migrate.d.ts.map +1 -0
  15. package/dist/cjs/core/Migrate.js +270 -0
  16. package/dist/cjs/core/Migrate.js.map +1 -0
  17. package/dist/cjs/core/Prompt.d.ts +54 -0
  18. package/dist/cjs/core/Prompt.d.ts.map +1 -0
  19. package/dist/cjs/core/Prompt.js +143 -0
  20. package/dist/cjs/core/Prompt.js.map +1 -0
  21. package/dist/cjs/core/Runner.d.ts +160 -0
  22. package/dist/cjs/core/Runner.d.ts.map +1 -0
  23. package/dist/cjs/core/Runner.js +1131 -0
  24. package/dist/cjs/core/Runner.js.map +1 -0
  25. package/dist/cjs/core/Speak.d.ts +37 -0
  26. package/dist/cjs/core/Speak.d.ts.map +1 -0
  27. package/dist/cjs/core/Speak.js +364 -0
  28. package/dist/cjs/core/Speak.js.map +1 -0
  29. package/dist/cjs/core/Understand.d.ts +28 -0
  30. package/dist/cjs/core/Understand.d.ts.map +1 -0
  31. package/dist/cjs/core/Understand.js +353 -0
  32. package/dist/cjs/core/Understand.js.map +1 -0
  33. package/dist/cjs/core/contracts.d.ts +122 -0
  34. package/dist/cjs/core/contracts.d.ts.map +1 -0
  35. package/dist/cjs/core/contracts.js +11 -0
  36. package/dist/cjs/core/contracts.js.map +1 -0
  37. package/dist/cjs/core/falai.d.ts +57 -0
  38. package/dist/cjs/core/falai.d.ts.map +1 -0
  39. package/dist/cjs/core/falai.js +43 -0
  40. package/dist/cjs/core/falai.js.map +1 -0
  41. package/dist/cjs/core/predicate.d.ts +9 -0
  42. package/dist/cjs/core/predicate.d.ts.map +1 -0
  43. package/dist/cjs/core/predicate.js +58 -0
  44. package/dist/cjs/core/predicate.js.map +1 -0
  45. package/dist/cjs/index.d.ts +26 -31
  46. package/dist/cjs/index.d.ts.map +1 -1
  47. package/dist/cjs/index.js +46 -68
  48. package/dist/cjs/index.js.map +1 -1
  49. package/dist/cjs/persistence/MemoryStore.d.ts +15 -0
  50. package/dist/cjs/persistence/MemoryStore.d.ts.map +1 -0
  51. package/dist/cjs/persistence/MemoryStore.js +39 -0
  52. package/dist/cjs/persistence/MemoryStore.js.map +1 -0
  53. package/dist/cjs/persistence/MongoStore.d.ts +42 -0
  54. package/dist/cjs/persistence/MongoStore.d.ts.map +1 -0
  55. package/dist/cjs/persistence/MongoStore.js +60 -0
  56. package/dist/cjs/persistence/MongoStore.js.map +1 -0
  57. package/dist/cjs/persistence/OpenSearchStore.d.ts +86 -0
  58. package/dist/cjs/persistence/OpenSearchStore.d.ts.map +1 -0
  59. package/dist/cjs/persistence/OpenSearchStore.js +120 -0
  60. package/dist/cjs/persistence/OpenSearchStore.js.map +1 -0
  61. package/dist/cjs/persistence/PostgresStore.d.ts +41 -0
  62. package/dist/cjs/persistence/PostgresStore.d.ts.map +1 -0
  63. package/dist/cjs/persistence/PostgresStore.js +58 -0
  64. package/dist/cjs/persistence/PostgresStore.js.map +1 -0
  65. package/dist/cjs/persistence/PrismaStore.d.ts +65 -0
  66. package/dist/cjs/persistence/PrismaStore.d.ts.map +1 -0
  67. package/dist/cjs/persistence/PrismaStore.js +95 -0
  68. package/dist/cjs/persistence/PrismaStore.js.map +1 -0
  69. package/dist/cjs/persistence/RedisStore.d.ts +34 -0
  70. package/dist/cjs/persistence/RedisStore.d.ts.map +1 -0
  71. package/dist/cjs/persistence/RedisStore.js +61 -0
  72. package/dist/cjs/persistence/RedisStore.js.map +1 -0
  73. package/dist/cjs/persistence/SQLiteStore.d.ts +45 -0
  74. package/dist/cjs/persistence/SQLiteStore.d.ts.map +1 -0
  75. package/dist/cjs/persistence/SQLiteStore.js +74 -0
  76. package/dist/cjs/persistence/SQLiteStore.js.map +1 -0
  77. package/dist/cjs/persistence/sessionRow.d.ts +14 -0
  78. package/dist/cjs/persistence/sessionRow.d.ts.map +1 -0
  79. package/dist/cjs/persistence/sessionRow.js +50 -0
  80. package/dist/cjs/persistence/sessionRow.js.map +1 -0
  81. package/dist/cjs/providers/DeepSeekProvider.d.ts.map +1 -1
  82. package/dist/cjs/providers/DeepSeekProvider.js +8 -3
  83. package/dist/cjs/providers/DeepSeekProvider.js.map +1 -1
  84. package/dist/cjs/providers/GeminiProvider.d.ts +4 -3
  85. package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
  86. package/dist/cjs/providers/GeminiProvider.js +4 -3
  87. package/dist/cjs/providers/GeminiProvider.js.map +1 -1
  88. package/dist/cjs/providers/OpenAICompatibleProvider.d.ts +4 -0
  89. package/dist/cjs/providers/OpenAICompatibleProvider.d.ts.map +1 -1
  90. package/dist/cjs/providers/OpenAICompatibleProvider.js +2 -0
  91. package/dist/cjs/providers/OpenAICompatibleProvider.js.map +1 -1
  92. package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -1
  93. package/dist/cjs/providers/OpenRouterProvider.js +2 -4
  94. package/dist/cjs/providers/OpenRouterProvider.js.map +1 -1
  95. package/dist/cjs/providers/ProviderAdapter.d.ts +1 -1
  96. package/dist/cjs/providers/ProviderAdapter.d.ts.map +1 -1
  97. package/dist/cjs/providers/ProviderAdapter.js +33 -10
  98. package/dist/cjs/providers/ProviderAdapter.js.map +1 -1
  99. package/dist/cjs/types/agent.d.ts +153 -383
  100. package/dist/cjs/types/agent.d.ts.map +1 -1
  101. package/dist/cjs/types/agent.js +1 -1
  102. package/dist/cjs/types/ai.d.ts +32 -1
  103. package/dist/cjs/types/ai.d.ts.map +1 -1
  104. package/dist/cjs/types/compaction.d.ts +3 -1
  105. package/dist/cjs/types/compaction.d.ts.map +1 -1
  106. package/dist/cjs/types/errors.d.ts +9 -12
  107. package/dist/cjs/types/errors.d.ts.map +1 -1
  108. package/dist/cjs/types/errors.js +14 -17
  109. package/dist/cjs/types/errors.js.map +1 -1
  110. package/dist/cjs/types/flow.d.ts +265 -513
  111. package/dist/cjs/types/flow.d.ts.map +1 -1
  112. package/dist/cjs/types/flow.js +7 -1
  113. package/dist/cjs/types/flow.js.map +1 -1
  114. package/dist/cjs/types/history.d.ts +7 -18
  115. package/dist/cjs/types/history.d.ts.map +1 -1
  116. package/dist/cjs/types/history.js.map +1 -1
  117. package/dist/cjs/types/index.d.ts +9 -15
  118. package/dist/cjs/types/index.d.ts.map +1 -1
  119. package/dist/cjs/types/index.js +4 -14
  120. package/dist/cjs/types/index.js.map +1 -1
  121. package/dist/cjs/types/session.d.ts +94 -64
  122. package/dist/cjs/types/session.d.ts.map +1 -1
  123. package/dist/cjs/types/session.js +5 -1
  124. package/dist/cjs/types/session.js.map +1 -1
  125. package/dist/cjs/types/tool.d.ts +37 -207
  126. package/dist/cjs/types/tool.d.ts.map +1 -1
  127. package/dist/cjs/types/tool.js +5 -14
  128. package/dist/cjs/types/tool.js.map +1 -1
  129. package/dist/cjs/utils/clock.d.ts +28 -0
  130. package/dist/cjs/utils/clock.d.ts.map +1 -0
  131. package/dist/cjs/utils/clock.js +64 -0
  132. package/dist/cjs/utils/clock.js.map +1 -0
  133. package/dist/cjs/utils/duration.d.ts +11 -0
  134. package/dist/cjs/utils/duration.d.ts.map +1 -0
  135. package/dist/cjs/utils/duration.js +31 -0
  136. package/dist/cjs/utils/duration.js.map +1 -0
  137. package/dist/cjs/utils/history.d.ts +4 -1
  138. package/dist/cjs/utils/history.d.ts.map +1 -1
  139. package/dist/cjs/utils/history.js +2 -2
  140. package/dist/cjs/utils/history.js.map +1 -1
  141. package/dist/cjs/utils/index.d.ts +4 -10
  142. package/dist/cjs/utils/index.d.ts.map +1 -1
  143. package/dist/cjs/utils/index.js +14 -61
  144. package/dist/cjs/utils/index.js.map +1 -1
  145. package/dist/cjs/utils/json.d.ts +2 -0
  146. package/dist/cjs/utils/json.d.ts.map +1 -1
  147. package/dist/cjs/utils/json.js +5 -0
  148. package/dist/cjs/utils/json.js.map +1 -1
  149. package/dist/cjs/utils/outcomes.d.ts +48 -0
  150. package/dist/cjs/utils/outcomes.d.ts.map +1 -0
  151. package/dist/cjs/utils/outcomes.js +51 -0
  152. package/dist/cjs/utils/outcomes.js.map +1 -0
  153. package/dist/cjs/utils/schema.d.ts +50 -0
  154. package/dist/cjs/utils/schema.d.ts.map +1 -0
  155. package/dist/cjs/utils/schema.js +138 -0
  156. package/dist/cjs/utils/schema.js.map +1 -0
  157. package/dist/cjs/utils/streamingMessage.d.ts +3 -2
  158. package/dist/cjs/utils/streamingMessage.d.ts.map +1 -1
  159. package/dist/cjs/utils/streamingMessage.js +38 -4
  160. package/dist/cjs/utils/streamingMessage.js.map +1 -1
  161. package/dist/cjs/utils/template.d.ts +13 -149
  162. package/dist/cjs/utils/template.d.ts.map +1 -1
  163. package/dist/cjs/utils/template.js +31 -363
  164. package/dist/cjs/utils/template.js.map +1 -1
  165. package/dist/cjs/utils/usage.d.ts +19 -0
  166. package/dist/cjs/utils/usage.d.ts.map +1 -0
  167. package/dist/cjs/utils/usage.js +35 -0
  168. package/dist/cjs/utils/usage.js.map +1 -0
  169. package/dist/core/Agent.d.ts +22 -378
  170. package/dist/core/Agent.d.ts.map +1 -1
  171. package/dist/core/Agent.js +107 -1181
  172. package/dist/core/Agent.js.map +1 -1
  173. package/dist/core/CompactionEngine.d.ts.map +1 -1
  174. package/dist/core/CompactionEngine.js +5 -3
  175. package/dist/core/CompactionEngine.js.map +1 -1
  176. package/dist/core/FlowSpec.d.ts +136 -0
  177. package/dist/core/FlowSpec.d.ts.map +1 -0
  178. package/dist/core/FlowSpec.js +516 -0
  179. package/dist/core/FlowSpec.js.map +1 -0
  180. package/dist/core/Migrate.d.ts +38 -0
  181. package/dist/core/Migrate.d.ts.map +1 -0
  182. package/dist/core/Migrate.js +264 -0
  183. package/dist/core/Migrate.js.map +1 -0
  184. package/dist/core/Prompt.d.ts +54 -0
  185. package/dist/core/Prompt.d.ts.map +1 -0
  186. package/dist/core/Prompt.js +133 -0
  187. package/dist/core/Prompt.js.map +1 -0
  188. package/dist/core/Runner.d.ts +160 -0
  189. package/dist/core/Runner.d.ts.map +1 -0
  190. package/dist/core/Runner.js +1127 -0
  191. package/dist/core/Runner.js.map +1 -0
  192. package/dist/core/Speak.d.ts +37 -0
  193. package/dist/core/Speak.d.ts.map +1 -0
  194. package/dist/core/Speak.js +360 -0
  195. package/dist/core/Speak.js.map +1 -0
  196. package/dist/core/Understand.d.ts +28 -0
  197. package/dist/core/Understand.d.ts.map +1 -0
  198. package/dist/core/Understand.js +349 -0
  199. package/dist/core/Understand.js.map +1 -0
  200. package/dist/core/contracts.d.ts +122 -0
  201. package/dist/core/contracts.d.ts.map +1 -0
  202. package/dist/core/contracts.js +10 -0
  203. package/dist/core/contracts.js.map +1 -0
  204. package/dist/core/falai.d.ts +57 -0
  205. package/dist/core/falai.d.ts.map +1 -0
  206. package/dist/core/falai.js +40 -0
  207. package/dist/core/falai.js.map +1 -0
  208. package/dist/core/predicate.d.ts +9 -0
  209. package/dist/core/predicate.d.ts.map +1 -0
  210. package/dist/core/predicate.js +54 -0
  211. package/dist/core/predicate.js.map +1 -0
  212. package/dist/index.d.ts +26 -31
  213. package/dist/index.d.ts.map +1 -1
  214. package/dist/index.js +19 -24
  215. package/dist/index.js.map +1 -1
  216. package/dist/persistence/MemoryStore.d.ts +15 -0
  217. package/dist/persistence/MemoryStore.d.ts.map +1 -0
  218. package/dist/persistence/MemoryStore.js +35 -0
  219. package/dist/persistence/MemoryStore.js.map +1 -0
  220. package/dist/persistence/MongoStore.d.ts +42 -0
  221. package/dist/persistence/MongoStore.d.ts.map +1 -0
  222. package/dist/persistence/MongoStore.js +56 -0
  223. package/dist/persistence/MongoStore.js.map +1 -0
  224. package/dist/persistence/OpenSearchStore.d.ts +86 -0
  225. package/dist/persistence/OpenSearchStore.d.ts.map +1 -0
  226. package/dist/persistence/OpenSearchStore.js +116 -0
  227. package/dist/persistence/OpenSearchStore.js.map +1 -0
  228. package/dist/persistence/PostgresStore.d.ts +41 -0
  229. package/dist/persistence/PostgresStore.d.ts.map +1 -0
  230. package/dist/persistence/PostgresStore.js +54 -0
  231. package/dist/persistence/PostgresStore.js.map +1 -0
  232. package/dist/persistence/PrismaStore.d.ts +65 -0
  233. package/dist/persistence/PrismaStore.d.ts.map +1 -0
  234. package/dist/persistence/PrismaStore.js +91 -0
  235. package/dist/persistence/PrismaStore.js.map +1 -0
  236. package/dist/persistence/RedisStore.d.ts +34 -0
  237. package/dist/persistence/RedisStore.d.ts.map +1 -0
  238. package/dist/persistence/RedisStore.js +57 -0
  239. package/dist/persistence/RedisStore.js.map +1 -0
  240. package/dist/persistence/SQLiteStore.d.ts +45 -0
  241. package/dist/persistence/SQLiteStore.d.ts.map +1 -0
  242. package/dist/persistence/SQLiteStore.js +70 -0
  243. package/dist/persistence/SQLiteStore.js.map +1 -0
  244. package/dist/persistence/sessionRow.d.ts +14 -0
  245. package/dist/persistence/sessionRow.d.ts.map +1 -0
  246. package/dist/persistence/sessionRow.js +45 -0
  247. package/dist/persistence/sessionRow.js.map +1 -0
  248. package/dist/providers/DeepSeekProvider.d.ts.map +1 -1
  249. package/dist/providers/DeepSeekProvider.js +8 -3
  250. package/dist/providers/DeepSeekProvider.js.map +1 -1
  251. package/dist/providers/GeminiProvider.d.ts +4 -3
  252. package/dist/providers/GeminiProvider.d.ts.map +1 -1
  253. package/dist/providers/GeminiProvider.js +4 -3
  254. package/dist/providers/GeminiProvider.js.map +1 -1
  255. package/dist/providers/OpenAICompatibleProvider.d.ts +4 -0
  256. package/dist/providers/OpenAICompatibleProvider.d.ts.map +1 -1
  257. package/dist/providers/OpenAICompatibleProvider.js +2 -0
  258. package/dist/providers/OpenAICompatibleProvider.js.map +1 -1
  259. package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
  260. package/dist/providers/OpenRouterProvider.js +2 -4
  261. package/dist/providers/OpenRouterProvider.js.map +1 -1
  262. package/dist/providers/ProviderAdapter.d.ts +1 -1
  263. package/dist/providers/ProviderAdapter.d.ts.map +1 -1
  264. package/dist/providers/ProviderAdapter.js +34 -11
  265. package/dist/providers/ProviderAdapter.js.map +1 -1
  266. package/dist/types/agent.d.ts +153 -383
  267. package/dist/types/agent.d.ts.map +1 -1
  268. package/dist/types/agent.js +1 -1
  269. package/dist/types/ai.d.ts +32 -1
  270. package/dist/types/ai.d.ts.map +1 -1
  271. package/dist/types/compaction.d.ts +3 -1
  272. package/dist/types/compaction.d.ts.map +1 -1
  273. package/dist/types/errors.d.ts +9 -12
  274. package/dist/types/errors.d.ts.map +1 -1
  275. package/dist/types/errors.js +12 -15
  276. package/dist/types/errors.js.map +1 -1
  277. package/dist/types/flow.d.ts +265 -513
  278. package/dist/types/flow.d.ts.map +1 -1
  279. package/dist/types/flow.js +7 -1
  280. package/dist/types/flow.js.map +1 -1
  281. package/dist/types/history.d.ts +7 -18
  282. package/dist/types/history.d.ts.map +1 -1
  283. package/dist/types/history.js.map +1 -1
  284. package/dist/types/index.d.ts +9 -15
  285. package/dist/types/index.d.ts.map +1 -1
  286. package/dist/types/index.js +2 -7
  287. package/dist/types/index.js.map +1 -1
  288. package/dist/types/session.d.ts +94 -64
  289. package/dist/types/session.d.ts.map +1 -1
  290. package/dist/types/session.js +5 -1
  291. package/dist/types/session.js.map +1 -1
  292. package/dist/types/tool.d.ts +37 -207
  293. package/dist/types/tool.d.ts.map +1 -1
  294. package/dist/types/tool.js +6 -13
  295. package/dist/types/tool.js.map +1 -1
  296. package/dist/utils/clock.d.ts +28 -0
  297. package/dist/utils/clock.d.ts.map +1 -0
  298. package/dist/utils/clock.js +59 -0
  299. package/dist/utils/clock.js.map +1 -0
  300. package/dist/utils/duration.d.ts +11 -0
  301. package/dist/utils/duration.d.ts.map +1 -0
  302. package/dist/utils/duration.js +26 -0
  303. package/dist/utils/duration.js.map +1 -0
  304. package/dist/utils/history.d.ts +4 -1
  305. package/dist/utils/history.d.ts.map +1 -1
  306. package/dist/utils/history.js +2 -2
  307. package/dist/utils/history.js.map +1 -1
  308. package/dist/utils/index.d.ts +4 -10
  309. package/dist/utils/index.d.ts.map +1 -1
  310. package/dist/utils/index.js +4 -21
  311. package/dist/utils/index.js.map +1 -1
  312. package/dist/utils/json.d.ts +2 -0
  313. package/dist/utils/json.d.ts.map +1 -1
  314. package/dist/utils/json.js +4 -0
  315. package/dist/utils/json.js.map +1 -1
  316. package/dist/utils/outcomes.d.ts +48 -0
  317. package/dist/utils/outcomes.d.ts.map +1 -0
  318. package/dist/utils/outcomes.js +48 -0
  319. package/dist/utils/outcomes.js.map +1 -0
  320. package/dist/utils/schema.d.ts +50 -0
  321. package/dist/utils/schema.d.ts.map +1 -0
  322. package/dist/utils/schema.js +129 -0
  323. package/dist/utils/schema.js.map +1 -0
  324. package/dist/utils/streamingMessage.d.ts +3 -2
  325. package/dist/utils/streamingMessage.d.ts.map +1 -1
  326. package/dist/utils/streamingMessage.js +38 -4
  327. package/dist/utils/streamingMessage.js.map +1 -1
  328. package/dist/utils/template.d.ts +13 -149
  329. package/dist/utils/template.d.ts.map +1 -1
  330. package/dist/utils/template.js +28 -355
  331. package/dist/utils/template.js.map +1 -1
  332. package/dist/utils/usage.d.ts +19 -0
  333. package/dist/utils/usage.d.ts.map +1 -0
  334. package/dist/utils/usage.js +31 -0
  335. package/dist/utils/usage.js.map +1 -0
  336. package/docs/README.md +37 -19
  337. package/docs/concepts/architecture.md +117 -239
  338. package/docs/concepts/collection.md +170 -0
  339. package/docs/concepts/pipeline.md +132 -378
  340. package/docs/concepts/runs-and-waits.md +192 -0
  341. package/docs/guides/actions-and-events.md +276 -0
  342. package/docs/guides/branching.md +119 -208
  343. package/docs/guides/compaction.md +63 -158
  344. package/docs/guides/conditions.md +164 -128
  345. package/docs/guides/error-handling.md +168 -164
  346. package/docs/guides/flow-control.md +210 -349
  347. package/docs/guides/flows-from-json.md +224 -0
  348. package/docs/guides/instructions.md +125 -161
  349. package/docs/guides/persistence.md +182 -206
  350. package/docs/guides/streaming.md +50 -114
  351. package/docs/guides/testing.md +284 -0
  352. package/docs/guides/triggers.md +401 -0
  353. package/docs/migration/README.md +8 -15
  354. package/docs/migration/v1-to-v2.md +1 -1
  355. package/docs/migration/v2-3-to-v2-4.md +2 -2
  356. package/docs/migration/v2-6-to-v2-7.md +4 -4
  357. package/docs/migration/v3-to-v4.md +452 -0
  358. package/docs/reference/actions-events-conditions.md +396 -0
  359. package/docs/reference/agent.md +244 -0
  360. package/docs/reference/branches.md +75 -203
  361. package/docs/reference/errors.md +188 -144
  362. package/docs/reference/fields.md +125 -0
  363. package/docs/reference/flow-spec.md +248 -0
  364. package/docs/reference/flow.md +104 -192
  365. package/docs/reference/instruction.md +83 -137
  366. package/docs/reference/outcomes.md +273 -0
  367. package/docs/reference/providers.md +525 -302
  368. package/docs/reference/session.md +210 -0
  369. package/docs/reference/step.md +194 -312
  370. package/docs/reference/stores.md +496 -0
  371. package/docs/reference/tool.md +162 -231
  372. package/docs/reference/trigger.md +180 -0
  373. package/docs/rfc/v4-one-flow.md +477 -0
  374. package/docs/start/01-install.md +59 -44
  375. package/docs/start/02-first-agent.md +97 -147
  376. package/docs/start/03-collect-data.md +78 -183
  377. package/docs/start/04-add-tools.md +159 -227
  378. package/docs/start/05-go-to-production.md +167 -164
  379. package/examples/01-quickstart.ts +26 -16
  380. package/examples/02-fields.ts +75 -0
  381. package/examples/03-tools.ts +79 -119
  382. package/examples/04-instructions.ts +60 -87
  383. package/examples/05-branches.ts +78 -0
  384. package/examples/06-triggers-and-waits.ts +148 -0
  385. package/examples/07-streaming.ts +34 -60
  386. package/examples/08-store-and-migration.ts +97 -0
  387. package/examples/09-flows-from-json.ts +107 -0
  388. package/package.json +9 -6
  389. package/src/core/Agent.ts +116 -1512
  390. package/src/core/CompactionEngine.ts +7 -4
  391. package/src/core/FlowSpec.ts +712 -0
  392. package/src/core/Migrate.ts +256 -0
  393. package/src/core/Prompt.ts +156 -0
  394. package/src/core/Runner.ts +1181 -0
  395. package/src/core/Speak.ts +451 -0
  396. package/src/core/Understand.ts +422 -0
  397. package/src/core/contracts.ts +111 -0
  398. package/src/core/falai.ts +86 -0
  399. package/src/core/predicate.ts +56 -0
  400. package/src/index.ts +119 -147
  401. package/src/persistence/MemoryStore.ts +37 -0
  402. package/src/persistence/MongoStore.ts +89 -0
  403. package/src/persistence/OpenSearchStore.ts +153 -0
  404. package/src/persistence/PostgresStore.ts +89 -0
  405. package/src/persistence/PrismaStore.ts +127 -0
  406. package/src/persistence/RedisStore.ts +90 -0
  407. package/src/persistence/SQLiteStore.ts +103 -0
  408. package/src/persistence/sessionRow.ts +45 -0
  409. package/src/providers/DeepSeekProvider.ts +8 -3
  410. package/src/providers/GeminiProvider.ts +4 -3
  411. package/src/providers/OpenAICompatibleProvider.ts +6 -0
  412. package/src/providers/OpenRouterProvider.ts +2 -4
  413. package/src/providers/ProviderAdapter.ts +36 -8
  414. package/src/types/agent.ts +124 -397
  415. package/src/types/ai.ts +33 -1
  416. package/src/types/compaction.ts +3 -1
  417. package/src/types/errors.ts +13 -16
  418. package/src/types/flow.ts +249 -550
  419. package/src/types/history.ts +7 -20
  420. package/src/types/index.ts +87 -139
  421. package/src/types/session.ts +135 -70
  422. package/src/types/tool.ts +42 -267
  423. package/src/utils/clock.ts +70 -0
  424. package/src/utils/duration.ts +33 -0
  425. package/src/utils/history.ts +3 -2
  426. package/src/utils/index.ts +8 -66
  427. package/src/utils/json.ts +5 -0
  428. package/src/utils/outcomes.ts +56 -0
  429. package/src/utils/schema.ts +145 -0
  430. package/src/utils/streamingMessage.ts +34 -4
  431. package/src/utils/template.ts +32 -423
  432. package/src/utils/usage.ts +37 -0
  433. package/dist/adapters/MemoryAdapter.d.ts +0 -47
  434. package/dist/adapters/MemoryAdapter.d.ts.map +0 -1
  435. package/dist/adapters/MemoryAdapter.js +0 -204
  436. package/dist/adapters/MemoryAdapter.js.map +0 -1
  437. package/dist/adapters/MongoAdapter.d.ts +0 -97
  438. package/dist/adapters/MongoAdapter.d.ts.map +0 -1
  439. package/dist/adapters/MongoAdapter.js +0 -196
  440. package/dist/adapters/MongoAdapter.js.map +0 -1
  441. package/dist/adapters/OpenSearchAdapter.d.ts +0 -169
  442. package/dist/adapters/OpenSearchAdapter.d.ts.map +0 -1
  443. package/dist/adapters/OpenSearchAdapter.js +0 -471
  444. package/dist/adapters/OpenSearchAdapter.js.map +0 -1
  445. package/dist/adapters/PostgreSQLAdapter.d.ts +0 -85
  446. package/dist/adapters/PostgreSQLAdapter.d.ts.map +0 -1
  447. package/dist/adapters/PostgreSQLAdapter.js +0 -308
  448. package/dist/adapters/PostgreSQLAdapter.js.map +0 -1
  449. package/dist/adapters/PrismaAdapter.d.ts +0 -115
  450. package/dist/adapters/PrismaAdapter.d.ts.map +0 -1
  451. package/dist/adapters/PrismaAdapter.js +0 -406
  452. package/dist/adapters/PrismaAdapter.js.map +0 -1
  453. package/dist/adapters/RedisAdapter.d.ts +0 -72
  454. package/dist/adapters/RedisAdapter.d.ts.map +0 -1
  455. package/dist/adapters/RedisAdapter.js +0 -286
  456. package/dist/adapters/RedisAdapter.js.map +0 -1
  457. package/dist/adapters/SQLiteAdapter.d.ts +0 -86
  458. package/dist/adapters/SQLiteAdapter.d.ts.map +0 -1
  459. package/dist/adapters/SQLiteAdapter.js +0 -337
  460. package/dist/adapters/SQLiteAdapter.js.map +0 -1
  461. package/dist/adapters/index.d.ts +0 -17
  462. package/dist/adapters/index.d.ts.map +0 -1
  463. package/dist/adapters/index.js +0 -11
  464. package/dist/adapters/index.js.map +0 -1
  465. package/dist/adapters/sessionRow.d.ts +0 -22
  466. package/dist/adapters/sessionRow.d.ts.map +0 -1
  467. package/dist/adapters/sessionRow.js +0 -48
  468. package/dist/adapters/sessionRow.js.map +0 -1
  469. package/dist/cjs/adapters/MemoryAdapter.d.ts +0 -47
  470. package/dist/cjs/adapters/MemoryAdapter.d.ts.map +0 -1
  471. package/dist/cjs/adapters/MemoryAdapter.js +0 -208
  472. package/dist/cjs/adapters/MemoryAdapter.js.map +0 -1
  473. package/dist/cjs/adapters/MongoAdapter.d.ts +0 -97
  474. package/dist/cjs/adapters/MongoAdapter.d.ts.map +0 -1
  475. package/dist/cjs/adapters/MongoAdapter.js +0 -200
  476. package/dist/cjs/adapters/MongoAdapter.js.map +0 -1
  477. package/dist/cjs/adapters/OpenSearchAdapter.d.ts +0 -169
  478. package/dist/cjs/adapters/OpenSearchAdapter.d.ts.map +0 -1
  479. package/dist/cjs/adapters/OpenSearchAdapter.js +0 -475
  480. package/dist/cjs/adapters/OpenSearchAdapter.js.map +0 -1
  481. package/dist/cjs/adapters/PostgreSQLAdapter.d.ts +0 -85
  482. package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +0 -1
  483. package/dist/cjs/adapters/PostgreSQLAdapter.js +0 -312
  484. package/dist/cjs/adapters/PostgreSQLAdapter.js.map +0 -1
  485. package/dist/cjs/adapters/PrismaAdapter.d.ts +0 -115
  486. package/dist/cjs/adapters/PrismaAdapter.d.ts.map +0 -1
  487. package/dist/cjs/adapters/PrismaAdapter.js +0 -410
  488. package/dist/cjs/adapters/PrismaAdapter.js.map +0 -1
  489. package/dist/cjs/adapters/RedisAdapter.d.ts +0 -72
  490. package/dist/cjs/adapters/RedisAdapter.d.ts.map +0 -1
  491. package/dist/cjs/adapters/RedisAdapter.js +0 -290
  492. package/dist/cjs/adapters/RedisAdapter.js.map +0 -1
  493. package/dist/cjs/adapters/SQLiteAdapter.d.ts +0 -86
  494. package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +0 -1
  495. package/dist/cjs/adapters/SQLiteAdapter.js +0 -341
  496. package/dist/cjs/adapters/SQLiteAdapter.js.map +0 -1
  497. package/dist/cjs/adapters/index.d.ts +0 -17
  498. package/dist/cjs/adapters/index.d.ts.map +0 -1
  499. package/dist/cjs/adapters/index.js +0 -21
  500. package/dist/cjs/adapters/index.js.map +0 -1
  501. package/dist/cjs/adapters/sessionRow.d.ts +0 -22
  502. package/dist/cjs/adapters/sessionRow.d.ts.map +0 -1
  503. package/dist/cjs/adapters/sessionRow.js +0 -52
  504. package/dist/cjs/adapters/sessionRow.js.map +0 -1
  505. package/dist/cjs/constants/index.d.ts +0 -1
  506. package/dist/cjs/constants/index.d.ts.map +0 -1
  507. package/dist/cjs/constants/index.js +0 -4
  508. package/dist/cjs/constants/index.js.map +0 -1
  509. package/dist/cjs/core/AutoChainExecutor.d.ts +0 -97
  510. package/dist/cjs/core/AutoChainExecutor.d.ts.map +0 -1
  511. package/dist/cjs/core/AutoChainExecutor.js +0 -288
  512. package/dist/cjs/core/AutoChainExecutor.js.map +0 -1
  513. package/dist/cjs/core/BranchEvaluator.d.ts +0 -55
  514. package/dist/cjs/core/BranchEvaluator.d.ts.map +0 -1
  515. package/dist/cjs/core/BranchEvaluator.js +0 -125
  516. package/dist/cjs/core/BranchEvaluator.js.map +0 -1
  517. package/dist/cjs/core/DirectiveChainTracker.d.ts +0 -49
  518. package/dist/cjs/core/DirectiveChainTracker.d.ts.map +0 -1
  519. package/dist/cjs/core/DirectiveChainTracker.js +0 -121
  520. package/dist/cjs/core/DirectiveChainTracker.js.map +0 -1
  521. package/dist/cjs/core/Events.d.ts +0 -26
  522. package/dist/cjs/core/Events.d.ts.map +0 -1
  523. package/dist/cjs/core/Events.js +0 -144
  524. package/dist/cjs/core/Events.js.map +0 -1
  525. package/dist/cjs/core/Flow.d.ts +0 -183
  526. package/dist/cjs/core/Flow.d.ts.map +0 -1
  527. package/dist/cjs/core/Flow.js +0 -551
  528. package/dist/cjs/core/Flow.js.map +0 -1
  529. package/dist/cjs/core/FlowRouter.d.ts +0 -183
  530. package/dist/cjs/core/FlowRouter.d.ts.map +0 -1
  531. package/dist/cjs/core/FlowRouter.js +0 -1047
  532. package/dist/cjs/core/FlowRouter.js.map +0 -1
  533. package/dist/cjs/core/PersistenceManager.d.ts +0 -114
  534. package/dist/cjs/core/PersistenceManager.d.ts.map +0 -1
  535. package/dist/cjs/core/PersistenceManager.js +0 -336
  536. package/dist/cjs/core/PersistenceManager.js.map +0 -1
  537. package/dist/cjs/core/PromptComposer.d.ts +0 -47
  538. package/dist/cjs/core/PromptComposer.d.ts.map +0 -1
  539. package/dist/cjs/core/PromptComposer.js +0 -397
  540. package/dist/cjs/core/PromptComposer.js.map +0 -1
  541. package/dist/cjs/core/PromptSectionCache.d.ts +0 -48
  542. package/dist/cjs/core/PromptSectionCache.d.ts.map +0 -1
  543. package/dist/cjs/core/PromptSectionCache.js +0 -108
  544. package/dist/cjs/core/PromptSectionCache.js.map +0 -1
  545. package/dist/cjs/core/ResponseEngine.d.ts +0 -43
  546. package/dist/cjs/core/ResponseEngine.d.ts.map +0 -1
  547. package/dist/cjs/core/ResponseEngine.js +0 -235
  548. package/dist/cjs/core/ResponseEngine.js.map +0 -1
  549. package/dist/cjs/core/ResponseGenerationError.d.ts +0 -30
  550. package/dist/cjs/core/ResponseGenerationError.d.ts.map +0 -1
  551. package/dist/cjs/core/ResponseGenerationError.js +0 -35
  552. package/dist/cjs/core/ResponseGenerationError.js.map +0 -1
  553. package/dist/cjs/core/ResponseModal.d.ts +0 -305
  554. package/dist/cjs/core/ResponseModal.d.ts.map +0 -1
  555. package/dist/cjs/core/ResponseModal.js +0 -1414
  556. package/dist/cjs/core/ResponseModal.js.map +0 -1
  557. package/dist/cjs/core/ResponsePipeline.d.ts +0 -220
  558. package/dist/cjs/core/ResponsePipeline.d.ts.map +0 -1
  559. package/dist/cjs/core/ResponsePipeline.js +0 -1040
  560. package/dist/cjs/core/ResponsePipeline.js.map +0 -1
  561. package/dist/cjs/core/SessionFinalizer.d.ts +0 -34
  562. package/dist/cjs/core/SessionFinalizer.d.ts.map +0 -1
  563. package/dist/cjs/core/SessionFinalizer.js +0 -88
  564. package/dist/cjs/core/SessionFinalizer.js.map +0 -1
  565. package/dist/cjs/core/SessionManager.d.ts +0 -112
  566. package/dist/cjs/core/SessionManager.d.ts.map +0 -1
  567. package/dist/cjs/core/SessionManager.js +0 -308
  568. package/dist/cjs/core/SessionManager.js.map +0 -1
  569. package/dist/cjs/core/SignalCoordinator.d.ts +0 -103
  570. package/dist/cjs/core/SignalCoordinator.d.ts.map +0 -1
  571. package/dist/cjs/core/SignalCoordinator.js +0 -207
  572. package/dist/cjs/core/SignalCoordinator.js.map +0 -1
  573. package/dist/cjs/core/SignalEvaluator.d.ts +0 -86
  574. package/dist/cjs/core/SignalEvaluator.d.ts.map +0 -1
  575. package/dist/cjs/core/SignalEvaluator.js +0 -319
  576. package/dist/cjs/core/SignalEvaluator.js.map +0 -1
  577. package/dist/cjs/core/SignalProcessor.d.ts +0 -152
  578. package/dist/cjs/core/SignalProcessor.d.ts.map +0 -1
  579. package/dist/cjs/core/SignalProcessor.js +0 -505
  580. package/dist/cjs/core/SignalProcessor.js.map +0 -1
  581. package/dist/cjs/core/Step.d.ts +0 -184
  582. package/dist/cjs/core/Step.d.ts.map +0 -1
  583. package/dist/cjs/core/Step.js +0 -599
  584. package/dist/cjs/core/Step.js.map +0 -1
  585. package/dist/cjs/core/StepLifecycle.d.ts +0 -43
  586. package/dist/cjs/core/StepLifecycle.d.ts.map +0 -1
  587. package/dist/cjs/core/StepLifecycle.js +0 -180
  588. package/dist/cjs/core/StepLifecycle.js.map +0 -1
  589. package/dist/cjs/core/StreamingToolExecutor.d.ts +0 -142
  590. package/dist/cjs/core/StreamingToolExecutor.d.ts.map +0 -1
  591. package/dist/cjs/core/StreamingToolExecutor.js +0 -490
  592. package/dist/cjs/core/StreamingToolExecutor.js.map +0 -1
  593. package/dist/cjs/core/ToolLoopExecutor.d.ts +0 -133
  594. package/dist/cjs/core/ToolLoopExecutor.d.ts.map +0 -1
  595. package/dist/cjs/core/ToolLoopExecutor.js +0 -568
  596. package/dist/cjs/core/ToolLoopExecutor.js.map +0 -1
  597. package/dist/cjs/core/ToolManager.d.ts +0 -250
  598. package/dist/cjs/core/ToolManager.d.ts.map +0 -1
  599. package/dist/cjs/core/ToolManager.js +0 -1104
  600. package/dist/cjs/core/ToolManager.js.map +0 -1
  601. package/dist/cjs/core/createAgent.d.ts +0 -35
  602. package/dist/cjs/core/createAgent.d.ts.map +0 -1
  603. package/dist/cjs/core/createAgent.js +0 -39
  604. package/dist/cjs/core/createAgent.js.map +0 -1
  605. package/dist/cjs/core/flow-namespace.d.ts +0 -64
  606. package/dist/cjs/core/flow-namespace.d.ts.map +0 -1
  607. package/dist/cjs/core/flow-namespace.js +0 -182
  608. package/dist/cjs/core/flow-namespace.js.map +0 -1
  609. package/dist/cjs/core/toolGates.d.ts +0 -24
  610. package/dist/cjs/core/toolGates.d.ts.map +0 -1
  611. package/dist/cjs/core/toolGates.js +0 -52
  612. package/dist/cjs/core/toolGates.js.map +0 -1
  613. package/dist/cjs/types/persistence.d.ts +0 -254
  614. package/dist/cjs/types/persistence.d.ts.map +0 -1
  615. package/dist/cjs/types/persistence.js +0 -7
  616. package/dist/cjs/types/persistence.js.map +0 -1
  617. package/dist/cjs/types/prompt-cache.d.ts +0 -15
  618. package/dist/cjs/types/prompt-cache.d.ts.map +0 -1
  619. package/dist/cjs/types/prompt-cache.js +0 -6
  620. package/dist/cjs/types/prompt-cache.js.map +0 -1
  621. package/dist/cjs/types/signals.d.ts +0 -263
  622. package/dist/cjs/types/signals.d.ts.map +0 -1
  623. package/dist/cjs/types/signals.js +0 -11
  624. package/dist/cjs/types/signals.js.map +0 -1
  625. package/dist/cjs/types/template.d.ts +0 -84
  626. package/dist/cjs/types/template.d.ts.map +0 -1
  627. package/dist/cjs/types/template.js +0 -3
  628. package/dist/cjs/types/template.js.map +0 -1
  629. package/dist/cjs/utils/condition.d.ts +0 -63
  630. package/dist/cjs/utils/condition.d.ts.map +0 -1
  631. package/dist/cjs/utils/condition.js +0 -239
  632. package/dist/cjs/utils/condition.js.map +0 -1
  633. package/dist/cjs/utils/event.d.ts +0 -6
  634. package/dist/cjs/utils/event.d.ts.map +0 -1
  635. package/dist/cjs/utils/event.js +0 -20
  636. package/dist/cjs/utils/event.js.map +0 -1
  637. package/dist/cjs/utils/id.d.ts +0 -33
  638. package/dist/cjs/utils/id.d.ts.map +0 -1
  639. package/dist/cjs/utils/id.js +0 -84
  640. package/dist/cjs/utils/id.js.map +0 -1
  641. package/dist/cjs/utils/serialize.d.ts +0 -36
  642. package/dist/cjs/utils/serialize.d.ts.map +0 -1
  643. package/dist/cjs/utils/serialize.js +0 -77
  644. package/dist/cjs/utils/serialize.js.map +0 -1
  645. package/dist/cjs/utils/session.d.ts +0 -124
  646. package/dist/cjs/utils/session.d.ts.map +0 -1
  647. package/dist/cjs/utils/session.js +0 -396
  648. package/dist/cjs/utils/session.js.map +0 -1
  649. package/dist/constants/index.d.ts +0 -2
  650. package/dist/constants/index.d.ts.map +0 -1
  651. package/dist/constants/index.js +0 -4
  652. package/dist/constants/index.js.map +0 -1
  653. package/dist/core/AutoChainExecutor.d.ts +0 -97
  654. package/dist/core/AutoChainExecutor.d.ts.map +0 -1
  655. package/dist/core/AutoChainExecutor.js +0 -284
  656. package/dist/core/AutoChainExecutor.js.map +0 -1
  657. package/dist/core/BranchEvaluator.d.ts +0 -55
  658. package/dist/core/BranchEvaluator.d.ts.map +0 -1
  659. package/dist/core/BranchEvaluator.js +0 -121
  660. package/dist/core/BranchEvaluator.js.map +0 -1
  661. package/dist/core/DirectiveChainTracker.d.ts +0 -49
  662. package/dist/core/DirectiveChainTracker.d.ts.map +0 -1
  663. package/dist/core/DirectiveChainTracker.js +0 -117
  664. package/dist/core/DirectiveChainTracker.js.map +0 -1
  665. package/dist/core/Events.d.ts +0 -26
  666. package/dist/core/Events.d.ts.map +0 -1
  667. package/dist/core/Events.js +0 -137
  668. package/dist/core/Events.js.map +0 -1
  669. package/dist/core/Flow.d.ts +0 -183
  670. package/dist/core/Flow.d.ts.map +0 -1
  671. package/dist/core/Flow.js +0 -547
  672. package/dist/core/Flow.js.map +0 -1
  673. package/dist/core/FlowRouter.d.ts +0 -183
  674. package/dist/core/FlowRouter.d.ts.map +0 -1
  675. package/dist/core/FlowRouter.js +0 -1043
  676. package/dist/core/FlowRouter.js.map +0 -1
  677. package/dist/core/PersistenceManager.d.ts +0 -114
  678. package/dist/core/PersistenceManager.d.ts.map +0 -1
  679. package/dist/core/PersistenceManager.js +0 -332
  680. package/dist/core/PersistenceManager.js.map +0 -1
  681. package/dist/core/PromptComposer.d.ts +0 -47
  682. package/dist/core/PromptComposer.d.ts.map +0 -1
  683. package/dist/core/PromptComposer.js +0 -393
  684. package/dist/core/PromptComposer.js.map +0 -1
  685. package/dist/core/PromptSectionCache.d.ts +0 -48
  686. package/dist/core/PromptSectionCache.d.ts.map +0 -1
  687. package/dist/core/PromptSectionCache.js +0 -104
  688. package/dist/core/PromptSectionCache.js.map +0 -1
  689. package/dist/core/ResponseEngine.d.ts +0 -43
  690. package/dist/core/ResponseEngine.d.ts.map +0 -1
  691. package/dist/core/ResponseEngine.js +0 -231
  692. package/dist/core/ResponseEngine.js.map +0 -1
  693. package/dist/core/ResponseGenerationError.d.ts +0 -30
  694. package/dist/core/ResponseGenerationError.d.ts.map +0 -1
  695. package/dist/core/ResponseGenerationError.js +0 -31
  696. package/dist/core/ResponseGenerationError.js.map +0 -1
  697. package/dist/core/ResponseModal.d.ts +0 -305
  698. package/dist/core/ResponseModal.d.ts.map +0 -1
  699. package/dist/core/ResponseModal.js +0 -1410
  700. package/dist/core/ResponseModal.js.map +0 -1
  701. package/dist/core/ResponsePipeline.d.ts +0 -220
  702. package/dist/core/ResponsePipeline.d.ts.map +0 -1
  703. package/dist/core/ResponsePipeline.js +0 -1035
  704. package/dist/core/ResponsePipeline.js.map +0 -1
  705. package/dist/core/SessionFinalizer.d.ts +0 -34
  706. package/dist/core/SessionFinalizer.d.ts.map +0 -1
  707. package/dist/core/SessionFinalizer.js +0 -84
  708. package/dist/core/SessionFinalizer.js.map +0 -1
  709. package/dist/core/SessionManager.d.ts +0 -112
  710. package/dist/core/SessionManager.d.ts.map +0 -1
  711. package/dist/core/SessionManager.js +0 -301
  712. package/dist/core/SessionManager.js.map +0 -1
  713. package/dist/core/SignalCoordinator.d.ts +0 -103
  714. package/dist/core/SignalCoordinator.d.ts.map +0 -1
  715. package/dist/core/SignalCoordinator.js +0 -203
  716. package/dist/core/SignalCoordinator.js.map +0 -1
  717. package/dist/core/SignalEvaluator.d.ts +0 -86
  718. package/dist/core/SignalEvaluator.d.ts.map +0 -1
  719. package/dist/core/SignalEvaluator.js +0 -312
  720. package/dist/core/SignalEvaluator.js.map +0 -1
  721. package/dist/core/SignalProcessor.d.ts +0 -152
  722. package/dist/core/SignalProcessor.d.ts.map +0 -1
  723. package/dist/core/SignalProcessor.js +0 -498
  724. package/dist/core/SignalProcessor.js.map +0 -1
  725. package/dist/core/Step.d.ts +0 -184
  726. package/dist/core/Step.d.ts.map +0 -1
  727. package/dist/core/Step.js +0 -594
  728. package/dist/core/Step.js.map +0 -1
  729. package/dist/core/StepLifecycle.d.ts +0 -43
  730. package/dist/core/StepLifecycle.d.ts.map +0 -1
  731. package/dist/core/StepLifecycle.js +0 -176
  732. package/dist/core/StepLifecycle.js.map +0 -1
  733. package/dist/core/StreamingToolExecutor.d.ts +0 -142
  734. package/dist/core/StreamingToolExecutor.d.ts.map +0 -1
  735. package/dist/core/StreamingToolExecutor.js +0 -483
  736. package/dist/core/StreamingToolExecutor.js.map +0 -1
  737. package/dist/core/ToolLoopExecutor.d.ts +0 -133
  738. package/dist/core/ToolLoopExecutor.d.ts.map +0 -1
  739. package/dist/core/ToolLoopExecutor.js +0 -564
  740. package/dist/core/ToolLoopExecutor.js.map +0 -1
  741. package/dist/core/ToolManager.d.ts +0 -250
  742. package/dist/core/ToolManager.d.ts.map +0 -1
  743. package/dist/core/ToolManager.js +0 -1098
  744. package/dist/core/ToolManager.js.map +0 -1
  745. package/dist/core/createAgent.d.ts +0 -35
  746. package/dist/core/createAgent.d.ts.map +0 -1
  747. package/dist/core/createAgent.js +0 -36
  748. package/dist/core/createAgent.js.map +0 -1
  749. package/dist/core/flow-namespace.d.ts +0 -64
  750. package/dist/core/flow-namespace.d.ts.map +0 -1
  751. package/dist/core/flow-namespace.js +0 -179
  752. package/dist/core/flow-namespace.js.map +0 -1
  753. package/dist/core/toolGates.d.ts +0 -24
  754. package/dist/core/toolGates.d.ts.map +0 -1
  755. package/dist/core/toolGates.js +0 -49
  756. package/dist/core/toolGates.js.map +0 -1
  757. package/dist/types/persistence.d.ts +0 -254
  758. package/dist/types/persistence.d.ts.map +0 -1
  759. package/dist/types/persistence.js +0 -6
  760. package/dist/types/persistence.js.map +0 -1
  761. package/dist/types/prompt-cache.d.ts +0 -15
  762. package/dist/types/prompt-cache.d.ts.map +0 -1
  763. package/dist/types/prompt-cache.js +0 -5
  764. package/dist/types/prompt-cache.js.map +0 -1
  765. package/dist/types/signals.d.ts +0 -263
  766. package/dist/types/signals.d.ts.map +0 -1
  767. package/dist/types/signals.js +0 -10
  768. package/dist/types/signals.js.map +0 -1
  769. package/dist/types/template.d.ts +0 -84
  770. package/dist/types/template.d.ts.map +0 -1
  771. package/dist/types/template.js +0 -2
  772. package/dist/types/template.js.map +0 -1
  773. package/dist/utils/condition.d.ts +0 -63
  774. package/dist/utils/condition.d.ts.map +0 -1
  775. package/dist/utils/condition.js +0 -230
  776. package/dist/utils/condition.js.map +0 -1
  777. package/dist/utils/event.d.ts +0 -6
  778. package/dist/utils/event.d.ts.map +0 -1
  779. package/dist/utils/event.js +0 -17
  780. package/dist/utils/event.js.map +0 -1
  781. package/dist/utils/id.d.ts +0 -33
  782. package/dist/utils/id.d.ts.map +0 -1
  783. package/dist/utils/id.js +0 -77
  784. package/dist/utils/id.js.map +0 -1
  785. package/dist/utils/serialize.d.ts +0 -36
  786. package/dist/utils/serialize.d.ts.map +0 -1
  787. package/dist/utils/serialize.js +0 -72
  788. package/dist/utils/serialize.js.map +0 -1
  789. package/dist/utils/session.d.ts +0 -124
  790. package/dist/utils/session.d.ts.map +0 -1
  791. package/dist/utils/session.js +0 -379
  792. package/dist/utils/session.js.map +0 -1
  793. package/docs/concepts/directives.md +0 -369
  794. package/docs/reference/adapters.md +0 -543
  795. package/docs/reference/create-agent.md +0 -216
  796. package/docs/reference/directive.md +0 -242
  797. package/docs/reference/signals.md +0 -368
  798. package/examples/02-data-extraction.ts +0 -90
  799. package/examples/05-branching.ts +0 -140
  800. package/examples/06-flow-control.ts +0 -103
  801. package/examples/08-persistence.ts +0 -98
  802. package/examples/09-signals.ts +0 -144
  803. package/src/adapters/MemoryAdapter.ts +0 -281
  804. package/src/adapters/MongoAdapter.ts +0 -341
  805. package/src/adapters/OpenSearchAdapter.ts +0 -693
  806. package/src/adapters/PostgreSQLAdapter.ts +0 -487
  807. package/src/adapters/PrismaAdapter.ts +0 -617
  808. package/src/adapters/RedisAdapter.ts +0 -439
  809. package/src/adapters/SQLiteAdapter.ts +0 -496
  810. package/src/adapters/index.ts +0 -43
  811. package/src/adapters/sessionRow.ts +0 -57
  812. package/src/constants/index.ts +0 -2
  813. package/src/core/AutoChainExecutor.ts +0 -397
  814. package/src/core/BranchEvaluator.ts +0 -161
  815. package/src/core/DirectiveChainTracker.ts +0 -144
  816. package/src/core/Events.ts +0 -164
  817. package/src/core/Flow.ts +0 -665
  818. package/src/core/FlowRouter.ts +0 -1540
  819. package/src/core/PersistenceManager.ts +0 -446
  820. package/src/core/PromptComposer.ts +0 -448
  821. package/src/core/PromptSectionCache.ts +0 -125
  822. package/src/core/ResponseEngine.ts +0 -338
  823. package/src/core/ResponseGenerationError.ts +0 -53
  824. package/src/core/ResponseModal.ts +0 -1902
  825. package/src/core/ResponsePipeline.ts +0 -1404
  826. package/src/core/SessionFinalizer.ts +0 -108
  827. package/src/core/SessionManager.ts +0 -372
  828. package/src/core/SignalCoordinator.ts +0 -263
  829. package/src/core/SignalEvaluator.ts +0 -404
  830. package/src/core/SignalProcessor.ts +0 -663
  831. package/src/core/Step.ts +0 -782
  832. package/src/core/StepLifecycle.ts +0 -242
  833. package/src/core/StreamingToolExecutor.ts +0 -609
  834. package/src/core/ToolLoopExecutor.ts +0 -749
  835. package/src/core/ToolManager.ts +0 -1379
  836. package/src/core/createAgent.ts +0 -40
  837. package/src/core/flow-namespace.ts +0 -227
  838. package/src/core/toolGates.ts +0 -72
  839. package/src/types/persistence.ts +0 -303
  840. package/src/types/prompt-cache.ts +0 -17
  841. package/src/types/signals.ts +0 -338
  842. package/src/types/template.ts +0 -98
  843. package/src/utils/condition.ts +0 -296
  844. package/src/utils/event.ts +0 -16
  845. package/src/utils/id.ts +0 -91
  846. package/src/utils/serialize.ts +0 -86
  847. package/src/utils/session.ts +0 -501
@@ -1,1902 +0,0 @@
1
- /**
2
- * ResponseModal handles all response generation logic for the Agent
3
- * Provides both streaming and non-streaming response generation with unified logic
4
- */
5
-
6
- import type {
7
- AgentOptions,
8
- AgentResponse,
9
- AgentResponseStreamChunk,
10
- EndedFlow,
11
- History,
12
- SessionState,
13
- StepRef,
14
- HistoryItem,
15
- Event,
16
- AgentStructuredResponse,
17
- GenerateMessageStreamChunk,
18
- StoppedReason,
19
- ScopedInstructions,
20
- AppliedInstruction,
21
- Directive,
22
- Instruction,
23
- Term,
24
- StructuredSchema,
25
- CompactionOptions,
26
- } from "../types/index.js";
27
- import type { SignalFiring } from "../types/signals.js";
28
- import type { SessionManager } from "./SessionManager.js";
29
- import type { SignalProcessor } from "./SignalProcessor.js";
30
- import type { PromptSectionCache } from "./PromptSectionCache.js";
31
- import type { FlowRouter } from "./FlowRouter.js";
32
- import type { PersistenceManager } from "./PersistenceManager.js";
33
- import type { Flow } from "./Flow.js";
34
- import { Step } from "./Step.js";
35
- import { ResponseEngine } from "./ResponseEngine.js";
36
- import { ResponsePipeline } from "./ResponsePipeline.js";
37
- import { AutoChainExecutor, type AutoChainResult } from "./AutoChainExecutor.js";
38
- import { StepLifecycle } from "./StepLifecycle.js";
39
- import { SessionFinalizer } from "./SessionFinalizer.js";
40
- import { ToolLoopExecutor } from "./ToolLoopExecutor.js";
41
- import { flow } from "./flow-namespace.js";
42
- import { SignalCoordinator } from "./SignalCoordinator.js";
43
- import { ResponseGenerationError } from "./ResponseGenerationError.js";
44
- import { ProviderError, SessionConflictError } from "../types/errors.js";
45
- import { cloneDeep, mergeCollected, logger, historyToEvents, completeCurrentFlow, render, userMessage, assistantMessage } from "../utils/index.js";
46
- import { createTemplateContext } from "../utils/template.js";
47
- import { StreamingMessageDecoder } from "../utils/streamingMessage.js";
48
- import { extractEmbeddedJSONObject, isJSONShaped, tryParseJSONResponse } from "../utils/json.js";
49
- import type { ToolManager } from "./ToolManager.js";
50
-
51
- /**
52
- * The narrow surface ResponseModal (and its collaborators) need from the
53
- * Agent. Agent implements this; the response layer is constructible and
54
- * testable against this interface without a full Agent.
55
- */
56
- export interface ResponseModalDeps<TContext = unknown, TData = unknown> {
57
- /** Session manager (history, live session, sync). */
58
- readonly session: SessionManager<TData>;
59
- /** Tool registry/resolver and single-tool executor. */
60
- readonly tool: ToolManager<TContext, TData>;
61
- readonly signalProcessor: SignalProcessor<TContext, TData> | undefined;
62
- readonly promptSectionCache: PromptSectionCache;
63
- readonly instructions: Instruction<TContext, TData>[];
64
- readonly schema: StructuredSchema | undefined;
65
- readonly maxAutoStepsPerTurn: number;
66
- /** The agent's live session reference (read and replaced at finalize). */
67
- currentSession: SessionState<TData> | undefined;
68
- getAgentOptions(): AgentOptions<TContext, TData>;
69
- getFlows(): Flow<TContext, TData>[];
70
- getTerms(): Term<TContext, TData>[];
71
- getFlowRouter(): FlowRouter<TContext, TData>;
72
- getContext(): Promise<TContext | undefined>;
73
- getCompactionOptions(): CompactionOptions | undefined;
74
- getPersistenceManager(): PersistenceManager<TData> | undefined;
75
- getUpdateDataMethod(): (
76
- session: SessionState<TData>,
77
- dataUpdate: Partial<TData>
78
- ) => Promise<SessionState<TData>>;
79
- updateContext(updates: Partial<TContext>): Promise<void>;
80
- updateCollectedData(updates: Partial<TData>): Promise<void>;
81
- /** Drain data staged before any session existed. */
82
- consumePendingData(): Partial<TData>;
83
- }
84
-
85
- /**
86
- * Configuration options for ResponseModal
87
- */
88
- export interface ResponseModalOptions {
89
- /** Maximum number of tool loops allowed during response generation */
90
- maxToolLoops?: number;
91
- /** Enable automatic session saving after response generation */
92
- enableAutoSave?: boolean;
93
- /** Enable debug mode for detailed logging */
94
- debugMode?: boolean;
95
- }
96
-
97
- /**
98
- * Parameters for respond and respondStream methods
99
- */
100
- export interface RespondParams<TContext = unknown, TData = unknown> extends Record<string, unknown> {
101
- history: History;
102
- /**
103
- * The user's message for this turn. When set, the engine appends it to the
104
- * generation history AND to the returned session's history, then appends
105
- * the assistant reply on top — callers who hold sessions no longer need to
106
- * maintain history arrays by hand.
107
- */
108
- message?: string;
109
- /**
110
- * Restrict this turn's ROUTING candidates to these flow ids/titles.
111
- * Directive targets (goTo etc.) still resolve against the full registry.
112
- * Use for entry-pin funnels instead of cloning/filtering agents.
113
- */
114
- allowedFlows?: string[];
115
- step?: StepRef;
116
- session?: SessionState<TData>;
117
- contextOverride?: Partial<TContext>;
118
- signal?: AbortSignal;
119
- }
120
-
121
- /**
122
- * Options for the modern stream() method
123
- */
124
- export interface StreamOptions<TContext = unknown> {
125
- contextOverride?: Partial<TContext>;
126
- signal?: AbortSignal;
127
- history?: History; // Optional: override session history
128
- }
129
-
130
- /**
131
- * Options for the modern generate() method
132
- */
133
- export interface GenerateOptions<TContext = unknown> {
134
- contextOverride?: Partial<TContext>;
135
- signal?: AbortSignal;
136
- history?: History; // Optional: override session history
137
- }
138
-
139
- /**
140
- * Common response context used across all response methods
141
- */
142
- interface ResponseContext<TContext = unknown, TData = unknown> {
143
- effectiveContext: TContext;
144
- session: SessionState<TData>;
145
- history: HistoryItem[]; // Keep as HistoryItem[] for external API compatibility
146
- selectedFlow?: Flow<TContext, TData>;
147
- selectedStep?: Step<TContext, TData>;
148
- responseDirectives?: string[];
149
- isFlowComplete: boolean;
150
- /** AbortSignal for cancellation propagation */
151
- signal?: AbortSignal;
152
- /** Signal firings accumulated across both phases (pre + post) for the response surface. */
153
- signalFirings?: SignalFiring<TContext, TData>[];
154
- /** Pre-phase merged directive from signals (non-position fields like appendPrompt, injectTools). */
155
- signalPreDirective?: Directive<TContext, TData>;
156
- /** Whether the pre-signal phase emitted a halt directive. */
157
- signalHalted?: boolean;
158
- /** Reply from a halt directive. */
159
- signalHaltReply?: string;
160
- /** Flows exited via pendingDirective redirects/resets during routing. */
161
- endedFlows?: EndedFlow[];
162
- /** The user turn supplied via params.message (drives history ownership). */
163
- turnMessage?: string;
164
- }
165
-
166
- /**
167
- * The terminal shape of a turn, decided once by {@link ResponseModal.planTurn}
168
- * and rendered by either the streaming or non-streaming path. Abstracting the
169
- * decision (rather than the rendering) is what keeps the two paths in lockstep.
170
- */
171
- type TurnOutcome<TContext, TData> =
172
- /** No LLM call — emit a verbatim message (signal halt or auto-chain halt). */
173
- | { kind: 'halt'; message: string; stoppedReason: StoppedReason; runPostPhase: boolean }
174
- /** Flow finished — pure state transition, no message of the framework's own. */
175
- | { kind: 'flowComplete'; selectedFlow: Flow<TContext, TData>; stoppedReason: StoppedReason }
176
- /** Render one interactive step via the LLM (the happy path). */
177
- | { kind: 'flowStep'; selectedFlow: Flow<TContext, TData>; step?: Step<TContext, TData>; responseDirectives?: string[]; signalPreDirective?: Directive<TContext, TData> }
178
- /** No flows defined — a simple unstructured response. */
179
- | { kind: 'fallback' };
180
-
181
- /** The output of {@link ResponseModal.planTurn}: the outcome plus shared turn state. */
182
- interface TurnPlan<TContext, TData> {
183
- outcome: TurnOutcome<TContext, TData>;
184
- /** Session after any auto-chain mutation. */
185
- session: SessionState<TData>;
186
- /** Live firing accumulator, seeded with pre-signal phase firings. */
187
- signalFirings: SignalFiring<TContext, TData>[];
188
- effectiveContext: TContext;
189
- history: HistoryItem[];
190
- historyEvents: Event[];
191
- signal?: AbortSignal;
192
- }
193
-
194
- /**
195
- * ResponseModal class that encapsulates all response generation logic
196
- * Uses unified approach for both streaming and non-streaming responses
197
- */
198
- export class ResponseModal<TContext = unknown, TData = unknown> {
199
- private readonly responseEngine: ResponseEngine<TContext, TData>;
200
- private readonly responsePipeline: ResponsePipeline<TContext, TData>;
201
- private readonly stepLifecycle: StepLifecycle<TContext, TData>;
202
- private readonly sessionFinalizer: SessionFinalizer<TContext, TData>;
203
- private readonly toolLoopExecutor: ToolLoopExecutor<TContext, TData>;
204
- private readonly signalCoordinator: SignalCoordinator<TContext, TData>;
205
-
206
- constructor(
207
- private readonly agent: ResponseModalDeps<TContext, TData>,
208
- private readonly options?: ResponseModalOptions
209
- ) {
210
- // Initialize response engine
211
- this.responseEngine = new ResponseEngine<TContext, TData>(this.agent.promptSectionCache);
212
-
213
- // Signal pre/post phase orchestration
214
- this.signalCoordinator = new SignalCoordinator<TContext, TData>({
215
- getFlows: () => this.agent.getFlows(),
216
- signalProcessor: this.agent.signalProcessor,
217
- });
218
-
219
- // Initialize response pipeline with agent dependencies
220
- this.responsePipeline = new ResponsePipeline<TContext, TData>(
221
- this.agent.getAgentOptions(),
222
- () => this.agent.getFlows(), // Pass a function to get flows dynamically
223
- this.agent.getFlowRouter(),
224
- this.signalCoordinator,
225
- this.agent.updateCollectedData.bind(this.agent),
226
- () => this.agent.schema
227
- );
228
-
229
- // Step prepare/finalize execution, shared by the prepare phase and finalizer
230
- this.stepLifecycle = new StepLifecycle<TContext, TData>({
231
- getFlows: () => this.agent.getFlows(),
232
- toolManager: this.getToolManager(),
233
- updateContext: this.agent.updateContext.bind(this.agent),
234
- updateData: this.agent.updateCollectedData.bind(this.agent),
235
- });
236
-
237
- // Single owner of end-of-turn finalization (compaction + persistence + sync)
238
- this.sessionFinalizer = new SessionFinalizer<TContext, TData>({
239
- getCompactionOptions: () => this.agent.getCompactionOptions(),
240
- getPersistenceManager: () => this.agent.getPersistenceManager(),
241
- getAgentOptions: () => this.agent.getAgentOptions(),
242
- getCurrentSession: () => this.agent.currentSession,
243
- setCurrentSession: (session) => { this.agent.currentSession = session; },
244
- stepLifecycle: this.stepLifecycle,
245
- enableAutoSave: this.options?.enableAutoSave,
246
- });
247
-
248
- // Tool follow-up loop (run tools, ask the LLM again) + streaming batch execution
249
- this.toolLoopExecutor = new ToolLoopExecutor<TContext, TData>({
250
- toolManager: this.getToolManager(),
251
- getAgentOptions: () => this.agent.getAgentOptions(),
252
- updateContext: this.agent.updateContext.bind(this.agent),
253
- updateCollectedData: this.agent.updateCollectedData.bind(this.agent),
254
- updateSessionData: this.agent.getUpdateDataMethod(),
255
- maxToolLoops: this.options?.maxToolLoops,
256
- });
257
-
258
- }
259
-
260
- /**
261
- * Generate a non-streaming response using unified logic
262
- */
263
- async respond(params: RespondParams<TContext, TData>): Promise<AgentResponse<TData>> {
264
- // Snapshot the managed session so a failed turn has no in-memory effect:
265
- // without this, mutations made before the failure leave the live session
266
- // diverged from persisted state
267
- const preTurnSession = this.agent.session.current
268
- ? cloneDeep(this.agent.session.current)
269
- : undefined;
270
- try {
271
- // Use unified response preparation and routing
272
- const responseContext = await this.prepareUnifiedResponseContext(params);
273
- // Generate response using unified logic
274
- const result = await this.generateUnifiedResponse(responseContext);
275
-
276
- // Finalize session — the non-streaming turn's single finalize
277
- await this.sessionFinalizer.finalize(result.session!, responseContext.effectiveContext);
278
-
279
- return result;
280
-
281
- } catch (error) {
282
- if (preTurnSession) {
283
- this.agent.session.syncSession(preTurnSession);
284
- }
285
- // Typed library errors carry their own semantics (ProviderError.code,
286
- // SessionConflictError) — rethrow bare so consumers can branch on
287
- // them instead of string-matching. Everything else wraps with the
288
- // original attached as `cause`.
289
- if (error instanceof ProviderError || error instanceof SessionConflictError) {
290
- throw error;
291
- }
292
- throw new ResponseGenerationError(
293
- `[ResponseGenerationError] Response generation failed: ${error instanceof Error ? error.message : String(error)}. ` +
294
- `Check provider configuration and network connectivity.`,
295
- { originalError: error, params, phase: 'response_generation' }
296
- );
297
- }
298
- }
299
-
300
- /**
301
- * Generate a streaming response using unified logic
302
- */
303
- async *respondStream(params: RespondParams<TContext, TData>): AsyncGenerator<AgentResponseStreamChunk<TData>> {
304
- // Same failed-turn rollback semantics as respond()
305
- const preTurnSession = this.agent.session.current
306
- ? cloneDeep(this.agent.session.current)
307
- : undefined;
308
- try {
309
- // Use unified response preparation and routing
310
- const responseContext = await this.prepareUnifiedResponseContext(params);
311
-
312
- // Generate streaming response using unified logic
313
- yield* this.generateUnifiedStreamingResponse(responseContext);
314
-
315
- } catch (error) {
316
- if (preTurnSession) {
317
- this.agent.session.syncSession(preTurnSession);
318
- }
319
- // Stream error to caller
320
- yield {
321
- delta: "",
322
- accumulated: "",
323
- done: true,
324
- session: params.session || await this.agent.session.getOrCreate(),
325
- error: new ResponseGenerationError(
326
- `Streaming response failed: ${error instanceof Error ? error.message : String(error)}`,
327
- { originalError: error, params, phase: 'streaming' }
328
- ),
329
- } as AgentResponseStreamChunk<TData>;
330
- }
331
- }
332
-
333
- /**
334
- * Modern streaming API - simple interface like chat()
335
- */
336
- async *stream(
337
- message?: string,
338
- options?: StreamOptions<TContext>
339
- ): AsyncGenerator<AgentResponseStreamChunk<TData>> {
340
- // Determine which history to use
341
- let history: History;
342
- if (options?.history) {
343
- // Use provided history for this response only
344
- history = options.history;
345
- } else {
346
- // Add user message to session history if provided
347
- if (message) {
348
- await this.agent.session.addMessage("user", message);
349
- }
350
- history = this.agent.session.getHistory();
351
- }
352
-
353
- // Get or create session — session.data is the single source of truth,
354
- // so no agent-side data merge is needed
355
- const session = await this.agent.session.getOrCreate();
356
-
357
- // Stream response using existing respondStream method
358
- let finalMessage = "";
359
- let finalizedSession: SessionState<TData> | undefined;
360
- for await (const chunk of this.respondStream({
361
- history,
362
- session,
363
- contextOverride: options?.contextOverride,
364
- signal: options?.signal,
365
- })) {
366
- // Accumulate the final message and capture finalized session
367
- if (chunk.done) {
368
- finalMessage = chunk.accumulated;
369
- finalizedSession = chunk.session;
370
- }
371
-
372
- yield chunk;
373
- }
374
-
375
- // Sync finalized session to agent.session.current (skip in override-history mode)
376
- // Must happen BEFORE addMessage so the assistant message is added on top of the synced session state
377
- if (!options?.history && finalizedSession) {
378
- this.agent.session.syncSession(finalizedSession);
379
- }
380
-
381
- // Add agent response to session history (only if not using override history)
382
- if (!options?.history && finalMessage) {
383
- await this.agent.session.addMessage("assistant", finalMessage);
384
- }
385
- }
386
-
387
- /**
388
- * Modern non-streaming API - equivalent to chat() but more explicit
389
- */
390
- async generate(
391
- message?: string,
392
- options?: GenerateOptions<TContext>
393
- ): Promise<AgentResponse<TData>> {
394
- // Determine which history to use
395
- let history: History;
396
- if (options?.history) {
397
- // Use provided history for this response only
398
- history = options.history;
399
- } else {
400
- // Add user message to session history if provided
401
- if (message) {
402
- await this.agent.session.addMessage("user", message);
403
- }
404
- history = this.agent.session.getHistory();
405
- }
406
-
407
- // Get or create session — session.data is the single source of truth,
408
- // so no agent-side data merge is needed
409
- const session = await this.agent.session.getOrCreate();
410
-
411
- // Generate response using existing respond method
412
- const result = await this.respond({
413
- history,
414
- session,
415
- contextOverride: options?.contextOverride,
416
- signal: options?.signal,
417
- });
418
-
419
- // Sync finalized session to agent.session.current (skip in override-history mode)
420
- // Must happen BEFORE addMessage so the assistant message is added on top of the synced session state
421
- if (!options?.history && result.session) {
422
- this.agent.session.syncSession(result.session);
423
- }
424
-
425
- // Add agent response to session history (only if not using override history)
426
- if (!options?.history) {
427
- await this.agent.session.addMessage("assistant", result.message);
428
- }
429
-
430
- // Ensure the result includes the current session
431
- return {
432
- ...result,
433
- session: result.session || this.agent.session.current,
434
- };
435
- }
436
-
437
- /**
438
- * Get the response engine instance
439
- * @internal
440
- */
441
- getResponseEngine(): ResponseEngine<TContext, TData> {
442
- return this.responseEngine;
443
- }
444
-
445
- /**
446
- * Get the response pipeline instance
447
- * @internal
448
- */
449
- getResponsePipeline(): ResponsePipeline<TContext, TData> {
450
- return this.responsePipeline;
451
- }
452
-
453
- /**
454
- * Get the ToolManager instance from the agent.
455
- * @private
456
- */
457
- private getToolManager(): ToolManager<TContext, TData> {
458
- return this.agent.tool;
459
- }
460
-
461
- /**
462
- * Recover a structured payload from a schema-mandated response the provider
463
- * could not parse. Raw protocol fragments must never surface as the
464
- * user-visible reply. Three shapes arrive here:
465
- * - a truncated or fence-wrapped envelope → one repair-parse;
466
- * - conversational prose FOLLOWED BY the envelope (the model answered
467
- * twice — the observed WhatsApp leak) → recover the embedded envelope,
468
- * whose "message" field is the complete intended reply;
469
- * - plain prose with nothing recoverable → `undefined`; the caller passes
470
- * it through untouched. (An envelope truncated mid-stream after prose
471
- * also lands here: the prose is user-worthy and the fragment carries
472
- * nothing recoverable.)
473
- * An unrecoverable JSON-SHAPED fragment throws so the turn fails LOUDLY
474
- * and the caller's rollback/retry path engages instead of leaking
475
- * `{"message": "…` to an end user.
476
- */
477
- private salvageStructuredOutput(
478
- raw: string,
479
- surface: "turn" | "stream"
480
- ): AgentStructuredResponse | undefined {
481
- const salvaged = tryParseJSONResponse(raw) as Partial<AgentStructuredResponse> | undefined;
482
- if (salvaged && typeof salvaged.message === "string") {
483
- logger.warn(`[ResponseModal] Salvaged malformed structured output from ${surface} via JSON repair parse.`);
484
- return { ...salvaged, message: salvaged.message };
485
- }
486
- const embedded = extractEmbeddedJSONObject(raw) as Partial<AgentStructuredResponse> | undefined;
487
- if (embedded && typeof embedded.message === "string") {
488
- logger.warn(`[ResponseModal] Salvaged structured output embedded after prose from ${surface}.`);
489
- return { ...embedded, message: embedded.message };
490
- }
491
- if (isJSONShaped(raw)) {
492
- throw ResponseGenerationError.fromError(
493
- new Error(
494
- "Model returned a schema-mandated response that could not be parsed as JSON. " +
495
- `The ${surface} was failed instead of delivering raw protocol output to the user.`
496
- ),
497
- 'structured_output_malformed',
498
- { responseSchemaName: 'response_output' }
499
- );
500
- }
501
- return undefined;
502
- }
503
-
504
- /**
505
- * Tool-emitted directives (ctx.dispatch / `{directive}` returns): state
506
- * writes apply now; control flow queues for the next turn's
507
- * pendingDirective applier (same deferred semantics as dispatch()).
508
- */
509
- private async applyToolEmittedDirectives(
510
- session: SessionState<TData>,
511
- d: Directive<TContext, TData>
512
- ): Promise<SessionState<TData>> {
513
- if (d.dataUpdate) {
514
- session = mergeCollected(session, d.dataUpdate);
515
- }
516
- if (d.contextUpdate) {
517
- await this.agent.updateContext(d.contextUpdate);
518
- }
519
- const control = { ...d };
520
- delete control.dataUpdate;
521
- delete control.contextUpdate;
522
- if (Object.keys(control).length > 0) {
523
- flow.queuePending(session, control);
524
- }
525
- return session;
526
- }
527
-
528
- /**
529
- * Collect scoped instructions from agent, flow, and step into a ScopedInstructions value.
530
- * @private
531
- */
532
- private collectScopedInstructions(
533
- flow?: Flow<TContext, TData>,
534
- step?: Step<TContext, TData>,
535
- ): ScopedInstructions<TContext, TData> {
536
- return {
537
- global: this.agent.instructions,
538
- flow: flow ? { flowTitle: flow.title, items: flow.instructions } : undefined,
539
- step: step ? { stepId: step.id, items: step.getInstructions() } : undefined,
540
- };
541
- }
542
-
543
- // UNIFIED RESPONSE LOGIC - Consolidates common logic between streaming and non-streaming
544
- // ============================================================================
545
-
546
- /**
547
- * Unified response preparation - handles context setup, session management, and routing
548
- * This consolidates common logic between streaming and non-streaming responses
549
- * @private
550
- */
551
- private async prepareUnifiedResponseContext(params: RespondParams<TContext, TData>): Promise<ResponseContext<TContext, TData>> {
552
- try {
553
- const { history: simpleHistory, contextOverride, signal, message: turnMessage, allowedFlows } = params;
554
-
555
- // Validate input parameters
556
- if (!simpleHistory) {
557
- throw new ResponseGenerationError(
558
- '[ResponseGenerationError] Missing history: history is required for response generation. ' +
559
- 'Pass a valid history array (or pass `message` alongside an existing history base).',
560
- { params, phase: 'validation' }
561
- );
562
- }
563
-
564
- // `message` is the user turn: appended to what the model sees this
565
- // turn AND recorded on the returned session's history.
566
- const history = turnMessage
567
- ? [...simpleHistory, userMessage(turnMessage)]
568
- : simpleHistory;
569
-
570
- // Convert HistoryItem[] to Event[] for internal processing
571
- const historyEvents = historyToEvents(history);
572
-
573
- // Use ResponsePipeline for context and session preparation; context
574
- // and session are passed explicitly — the pipeline holds no state
575
- let responseContext: {
576
- effectiveContext: TContext;
577
- session: SessionState<TData>;
578
- contextAfterHook?: TContext;
579
- };
580
- try {
581
- responseContext = await this.responsePipeline.prepareResponseContext({
582
- contextOverride,
583
- session: params.session ? cloneDeep(params.session) : undefined,
584
- currentContext: await this.agent.getContext(),
585
- currentSession: this.agent.currentSession,
586
- });
587
- } catch (error) {
588
- throw ResponseGenerationError.fromError(error, 'pipeline_context_preparation', params);
589
- }
590
-
591
- const { effectiveContext, contextAfterHook } = responseContext;
592
- let session = responseContext.session;
593
-
594
- // Sync the beforeRespond hook's context result back to the agent
595
- if (contextAfterHook !== undefined) {
596
- try {
597
- await this.agent.updateContext(contextAfterHook as Partial<TContext>);
598
- } catch (error) {
599
- throw ResponseGenerationError.fromError(error, 'context_update_from_pipeline', params, { contextAfterHook });
600
- }
601
- }
602
-
603
- // Apply data staged before any session existed (initialData,
604
- // pre-session updateCollectedData calls). Reading the live session's
605
- // data here would leak state across sessions when an explicit
606
- // session is passed, so only the staging buffer is merged.
607
- const stagedData = this.agent.consumePendingData();
608
- if (Object.keys(stagedData).length > 0) {
609
- try {
610
- session = mergeCollected(session, stagedData);
611
- logger.debug("[ResponseModal] Merged staged agent data into session:", stagedData);
612
- } catch (error) {
613
- throw ResponseGenerationError.fromError(error, 'data_merging', params, { stagedData });
614
- }
615
- }
616
-
617
- // Record the user turn on the session's own history so the returned
618
- // session carries the full exchange (respond owns session.history
619
- // when `message` is used).
620
- if (turnMessage) {
621
- session.history = [...(session.history ?? []), userMessage(turnMessage)];
622
- }
623
-
624
- // PHASE 1: PREPARE - Execute prepare function if current step has one
625
- try {
626
- const prepareDirective = await this.stepLifecycle.runPrepare(session, effectiveContext);
627
- // Queue the control-flow directive for THIS turn: routing
628
- // (handleRoutingAndStepSelection) consumes session.pendingDirective
629
- // before deciding flow/step, so a prepare-phase goTo/goToStep/
630
- // reset steers the current turn.
631
- if (prepareDirective) {
632
- flow.queuePending(session, prepareDirective);
633
- }
634
- } catch (error) {
635
- throw ResponseGenerationError.fromError(error, 'step_preparation', params, { session, effectiveContext });
636
- }
637
-
638
- // PHASE 2: ROUTING + STEP SELECTION - Determine which flow and step to use
639
- // Performs pre-extraction and step selection
640
- let routingResult: {
641
- selectedFlow?: Flow<TContext, TData>;
642
- selectedStep?: Step<TContext, TData>;
643
- responseDirectives?: string[];
644
- session: SessionState<TData>;
645
- isFlowComplete: boolean;
646
- signalFirings?: SignalFiring<TContext, TData>[];
647
- signalPreDirective?: Directive<TContext, TData>;
648
- signalHalted?: boolean;
649
- signalHaltReply?: string;
650
- endedFlows?: EndedFlow[];
651
- };
652
- try {
653
- routingResult = await this.responsePipeline.routeAndSelectStep({
654
- session,
655
- history: historyEvents,
656
- context: effectiveContext,
657
- signal,
658
- allowedFlows,
659
- });
660
- } catch (error) {
661
- throw ResponseGenerationError.fromError(error, 'routing_and_step_selection', params, { session, effectiveContext });
662
- }
663
-
664
- return {
665
- effectiveContext,
666
- session: routingResult.session,
667
- history,
668
- turnMessage,
669
- selectedFlow: routingResult.selectedFlow,
670
- selectedStep: routingResult.selectedStep,
671
- responseDirectives: routingResult.responseDirectives,
672
- isFlowComplete: routingResult.isFlowComplete,
673
- signal,
674
- signalFirings: routingResult.signalFirings,
675
- signalPreDirective: routingResult.signalPreDirective,
676
- signalHalted: routingResult.signalHalted,
677
- signalHaltReply: routingResult.signalHaltReply,
678
- endedFlows: routingResult.endedFlows,
679
- };
680
- } catch (error) {
681
- // Re-throw ResponseGenerationError as-is, wrap others
682
- if (ResponseGenerationError.isResponseGenerationError(error)) {
683
- throw error;
684
- }
685
- throw ResponseGenerationError.fromError(error, 'preparation', params);
686
- }
687
- }
688
-
689
- /**
690
- * Plan a turn: run signal-halt detection, the auto-chain walk, and flow/step
691
- * selection, collapsing them into a single {@link TurnOutcome}. This is the
692
- * shared decision spine for both the streaming and non-streaming paths — the
693
- * only logic that genuinely differs between them is how each *renders* the
694
- * outcome (await a value vs. yield chunks) and the leaf provider primitive it
695
- * uses. Centralizing the decision here is what keeps the two paths from
696
- * drifting (the class of bug behind the 2.4.x retry/empty fixes).
697
- *
698
- * The returned `session` reflects any auto-chain mutation; `signalFirings`
699
- * is seeded with the pre-signal phase firings and is the live accumulator the
700
- * post-phase tail appends to.
701
- * @private
702
- */
703
- private async planTurn(
704
- responseContext: ResponseContext<TContext, TData>
705
- ): Promise<TurnPlan<TContext, TData>> {
706
- const {
707
- effectiveContext,
708
- history,
709
- selectedFlow,
710
- selectedStep,
711
- responseDirectives,
712
- isFlowComplete,
713
- signal,
714
- signalFirings: preSignalFirings,
715
- signalPreDirective,
716
- signalHalted,
717
- signalHaltReply,
718
- } = responseContext;
719
- let session = responseContext.session;
720
-
721
- // Accumulator for signal firings across both phases (fire order)
722
- const signalFirings: SignalFiring<TContext, TData>[] = [...(preSignalFirings || [])];
723
- // Convert HistoryItem[] to Event[] for internal processing
724
- const historyEvents = historyToEvents(history);
725
-
726
- const base = { effectiveContext, history, historyEvents, signal, signalFirings };
727
-
728
- // ── SIGNAL HALT (Requirement 8.2) ─────────────────────────────────────
729
- // Pre-signal phase emitted halt → skip LLM call entirely. The post-signal
730
- // phase still runs (it sees the complete turn context).
731
- if (signalHalted) {
732
- const haltMessage = signalHaltReply || '';
733
- return {
734
- ...base, session,
735
- outcome: { kind: 'halt', message: haltMessage, stoppedReason: haltMessage ? 'reply' : 'halt', runPostPhase: true },
736
- };
737
- }
738
-
739
- if (selectedFlow && !isFlowComplete) {
740
- // AUTO-CHAIN: Walk consecutive auto-steps before any LLM work. If the
741
- // current step is auto, the executor advances through it (and any
742
- // subsequent auto-steps) until an interactive step or terminal condition.
743
- let resolvedStep = selectedStep;
744
- const currentStepInstance = session.currentStep
745
- ? selectedFlow.getStep(session.currentStep.id)
746
- : selectedStep;
747
-
748
- if (currentStepInstance?.auto) {
749
- const autoChainExecutor = new AutoChainExecutor<TContext, TData>({
750
- maxAutoStepsPerTurn: this.agent.maxAutoStepsPerTurn,
751
- });
752
- const autoResult: AutoChainResult<TContext, TData> = await autoChainExecutor.run({
753
- session,
754
- context: effectiveContext,
755
- flow: selectedFlow,
756
- });
757
-
758
- session = autoResult.session;
759
-
760
- // Halt: emit the verbatim reply, no LLM call. Unlike signal halt,
761
- // the auto-chain halt is a hard short-circuit that does NOT run the
762
- // post-signal phase (preserved across both paths).
763
- if (autoResult.stoppedReason === 'halt') {
764
- return {
765
- ...base, session,
766
- outcome: { kind: 'halt', message: autoResult.mergedDirective?.reply || '', stoppedReason: 'halt', runPostPhase: false },
767
- };
768
- }
769
-
770
- // Flow completion or cross-flow redirect from auto-chain: the chain
771
- // ended without resolving to an interactive step (last_step: no
772
- // successor; completed: explicit complete; goto: cross-flow redirect).
773
- if (autoResult.stoppedReason === 'last_step' || autoResult.stoppedReason === 'completed' || autoResult.stoppedReason === 'goto') {
774
- logger.debug(`[ResponseModal] Auto-chain ended with ${autoResult.stoppedReason}`);
775
- return {
776
- ...base, session,
777
- outcome: { kind: 'flowComplete', selectedFlow, stoppedReason: autoResult.stoppedReason },
778
- };
779
- }
780
-
781
- // Normal case: auto-chain resolved to an interactive step.
782
- resolvedStep = autoResult.resolvedStep;
783
- }
784
-
785
- return {
786
- ...base, session,
787
- outcome: { kind: 'flowStep', selectedFlow, step: resolvedStep, responseDirectives, signalPreDirective },
788
- };
789
- }
790
-
791
- if (isFlowComplete && selectedFlow) {
792
- // Flow completion path: pure state transition, no LLM call. The reason
793
- // is 'last_step' (implicit terminus — no successor or all skipped).
794
- logger.debug(`[ResponseModal] Releasing session to idle for completed flow: ${selectedFlow.title}`);
795
- return {
796
- ...base, session,
797
- outcome: { kind: 'flowComplete', selectedFlow, stoppedReason: 'last_step' },
798
- };
799
- }
800
-
801
- // Fallback: no flows defined, generate a simple response.
802
- return { ...base, session, outcome: { kind: 'fallback' } };
803
- }
804
-
805
- /**
806
- * The shared post-signal phase tail (Requirement 9.1–9.4). Runs after the
807
- * turn's message is known and before persistence, so post-phase signals see
808
- * the complete turn result (assistant message, collected data, tool results)
809
- * and can override the reply or wire a pendingDirective.
810
- *
811
- * `runPostPhase` is false only for the auto-chain halt short-circuit, which
812
- * deliberately bypasses the post-phase in both paths; that branch still
813
- * surfaces any pre-phase firings via `triggeredSignals`.
814
- * @private
815
- */
816
- private async applyTurnPostPhase(params: {
817
- session: SessionState<TData>;
818
- context: TContext;
819
- historyEvents: Event[];
820
- message: string;
821
- signalFirings: SignalFiring<TContext, TData>[];
822
- runPostPhase: boolean;
823
- }): Promise<{
824
- session: SessionState<TData>;
825
- message: string;
826
- replyOverridden: boolean;
827
- triggeredSignals?: SignalFiring<TContext, TData>[];
828
- }> {
829
- const { session, context, historyEvents, message, signalFirings, runPostPhase } = params;
830
-
831
- if (!runPostPhase) {
832
- return {
833
- session, message, replyOverridden: false,
834
- triggeredSignals: signalFirings.length > 0 ? signalFirings : undefined,
835
- };
836
- }
837
-
838
- const post = await this.signalCoordinator.applyPostPhase({ session, context, historyEvents, message });
839
- signalFirings.push(...post.firings);
840
- return {
841
- session: post.session,
842
- message: post.message,
843
- replyOverridden: post.replyOverridden ?? false,
844
- triggeredSignals: signalFirings.length > 0 ? signalFirings : undefined,
845
- };
846
- }
847
-
848
- /**
849
- * Unified response generation for non-streaming responses.
850
- * Renders the shared {@link planTurn} outcome by awaiting the leaf primitive
851
- * and running the shared post-phase tail; respond() owns the single finalize.
852
- * @private
853
- */
854
- private async generateUnifiedResponse(
855
- responseContext: ResponseContext<TContext, TData>
856
- ): Promise<AgentResponse<TData>> {
857
- const plan = await this.planTurn(responseContext);
858
- const { effectiveContext, history, historyEvents, signal, signalFirings } = plan;
859
- let session = plan.session;
860
-
861
- let message = '';
862
- let toolCalls: Array<{ toolName: string; arguments: Record<string, unknown> }> | undefined = undefined;
863
- let executedSteps: StepRef[] = [];
864
- let stoppedReason: StoppedReason | undefined;
865
- let isFlowComplete = false;
866
- let appliedInstructions: AppliedInstruction[] | undefined;
867
- let runPostPhase = true;
868
- let tokensUsed: number | undefined;
869
- const endedFlows: EndedFlow[] = [...(responseContext.endedFlows ?? [])];
870
-
871
- switch (plan.outcome.kind) {
872
- case 'halt': {
873
- message = plan.outcome.message;
874
- stoppedReason = plan.outcome.stoppedReason;
875
- runPostPhase = plan.outcome.runPostPhase;
876
- break;
877
- }
878
- case 'flowComplete': {
879
- session = await this.applyFlowCompletion({
880
- selectedFlow: plan.outcome.selectedFlow,
881
- session,
882
- context: effectiveContext,
883
- history,
884
- });
885
- isFlowComplete = true;
886
- stoppedReason = plan.outcome.stoppedReason;
887
- endedFlows.push({
888
- flowId: plan.outcome.selectedFlow.id,
889
- title: plan.outcome.selectedFlow.title,
890
- reason: plan.outcome.stoppedReason,
891
- });
892
- break;
893
- }
894
- case 'flowStep': {
895
- const result = await this.processFlowResponse({
896
- selectedFlow: plan.outcome.selectedFlow,
897
- selectedStep: plan.outcome.step,
898
- responseDirectives: plan.outcome.responseDirectives,
899
- session,
900
- history,
901
- context: effectiveContext,
902
- historyEvents,
903
- signal,
904
- // Propagate signal pre-directive's appendPrompt for this turn's LLM call (Requirement 8.4)
905
- transientAppendage: plan.outcome.signalPreDirective?.appendPrompt,
906
- // Merge signal pre-directive (halt/reply/injectTools) into the pre-LLM bus
907
- mergedPreDirective: plan.outcome.signalPreDirective,
908
- });
909
- message = result.message;
910
- toolCalls = result.toolCalls;
911
- session = result.session;
912
- appliedInstructions = result.appliedInstructions;
913
- tokensUsed = result.tokensUsed;
914
- if (plan.outcome.step) {
915
- executedSteps = [{ id: plan.outcome.step.id, flowId: plan.outcome.selectedFlow.id }];
916
- }
917
- // Use stoppedReason from processFlowResponse if set (halt/reply),
918
- // otherwise default to 'needs_input' for normal LLM responses.
919
- stoppedReason = result.stoppedReason || 'needs_input';
920
- break;
921
- }
922
- case 'fallback': {
923
- const fallbackResult = await this.generateFallbackResponse({
924
- history,
925
- context: effectiveContext,
926
- session,
927
- signal,
928
- });
929
- message = fallbackResult.message;
930
- appliedInstructions = fallbackResult.appliedInstructions;
931
- break;
932
- }
933
- }
934
-
935
- const tail = await this.applyTurnPostPhase({
936
- session, context: effectiveContext, historyEvents, message, signalFirings, runPostPhase,
937
- });
938
-
939
- // History ownership: with params.message the returned session carries
940
- // the full exchange — the user turn was recorded pre-routing, the
941
- // assistant tail lands here (post-phase, so overridden replies are the
942
- // ones recorded). Empty tails are skipped.
943
- if (responseContext.turnMessage && tail.message) {
944
- tail.session.history = [...(tail.session.history ?? []), assistantMessage(tail.message)];
945
- }
946
-
947
- return {
948
- message: tail.message,
949
- session: tail.session,
950
- toolCalls,
951
- isFlowComplete,
952
- executedSteps,
953
- stoppedReason,
954
- appliedInstructions,
955
- triggeredSignals: tail.triggeredSignals,
956
- ...(tokensUsed !== undefined ? { metadata: { tokensUsed } } : {}),
957
- ...(endedFlows.length > 0 ? { endedFlows } : {}),
958
- };
959
- }
960
-
961
- /**
962
- * Process flow response with unified tool execution and data collection
963
- * @private
964
- */
965
- private async processFlowResponse(params: {
966
- selectedFlow: Flow<TContext, TData>;
967
- selectedStep?: Step<TContext, TData>;
968
- responseDirectives?: string[];
969
- session: SessionState<TData>;
970
- history: HistoryItem[];
971
- context: TContext;
972
- historyEvents: Event[];
973
- signal?: AbortSignal;
974
- /**
975
- * Per-turn transient appendage from merged directive.appendPrompt.
976
- * Fresh every turn, never cached, never persisted.
977
- */
978
- transientAppendage?: string[];
979
- /**
980
- * Merged directive from the directive bus's pre-LLM phase drain.
981
- * When `halt: true`, the LLM call is skipped entirely.
982
- */
983
- mergedPreDirective?: Directive<TContext, TData>;
984
- }): Promise<{
985
- message: string;
986
- toolCalls?: Array<{ toolName: string; arguments: Record<string, unknown> }>;
987
- session: SessionState<TData>;
988
- appliedInstructions?: AppliedInstruction[];
989
- stoppedReason?: StoppedReason;
990
- /** Provider-reported usage for the primary generation call. */
991
- tokensUsed?: number;
992
- }> {
993
- const { selectedFlow, selectedStep, responseDirectives, history, context, historyEvents, signal, transientAppendage, mergedPreDirective } = params;
994
- let session = params.session;
995
-
996
- // Resolve the step to render (branches win over linear chain; requires enforced)
997
- const stepResolution = await this.responsePipeline.resolveRenderStep({
998
- selectedFlow,
999
- selectedStep,
1000
- session,
1001
- context,
1002
- });
1003
- if (stepResolution.flowTransition) {
1004
- // Flow transition or completion — no local step to render
1005
- // Return empty message with updated session; caller handles flow transition
1006
- return { message: '', session: stepResolution.session };
1007
- }
1008
- const nextStep = stepResolution.nextStep!;
1009
- session = stepResolution.session;
1010
-
1011
- // Build response schema for this flow (with collect fields from step)
1012
- const responseSchema = this.responseEngine.responseSchemaForFlow(selectedFlow, nextStep, this.agent.schema);
1013
-
1014
- // ── HALT SHORT-CIRCUIT (Requirement 2.5, 2.6, 2.7) ──────────────────────
1015
- // After pre-LLM emissions are merged, if `halt: true` then skip the LLM
1016
- // call entirely. The behavior depends on whether `reply` is also set.
1017
- if (mergedPreDirective?.halt) {
1018
- if (mergedPreDirective.reply) {
1019
- // halt + reply: emit the reply as the assistant message
1020
- logger.debug(`[ResponseModal] Halt with reply — skipping LLM call for step ${nextStep.id}`);
1021
- return { message: mergedPreDirective.reply, session, stoppedReason: 'reply' };
1022
- } else {
1023
- // halt without reply: emit empty assistant content
1024
- logger.debug(`[ResponseModal] Halt without reply — skipping LLM call for step ${nextStep.id}`);
1025
- return { message: '', session, stoppedReason: 'halt' };
1026
- }
1027
- }
1028
-
1029
- // ── STEP.REPLY SHORT-CIRCUIT (Requirement 25.1–25.7, 17.9) ──────────────
1030
- // A step with `reply` set emits a verbatim template response without LLM.
1031
- // onEnter and prepare have already fired normally at this point.
1032
- // If prepare returned a Directive with `reply`, that overrides
1033
- // the step-declared reply (last-emission-wins per Algorithm 4).
1034
- if (nextStep.reply != null) {
1035
- // Determine the effective reply: prepare-emitted reply wins over step-declared
1036
- const effectiveReply = mergedPreDirective?.reply ?? await render(
1037
- nextStep.reply,
1038
- createTemplateContext({ data: session.data || {}, context, session })
1039
- );
1040
- logger.debug(`[ResponseModal] Step.reply — skipping LLM call for step ${nextStep.id}`);
1041
- return { message: effectiveReply, session, stoppedReason: 'reply' };
1042
- }
1043
-
1044
- // Transient appendage: per-turn slot from Directive.appendPrompt.
1045
- // Fresh each turn, never cached, never persisted.
1046
- // Wrapped in try/finally to ensure cleanup even on abnormal termination.
1047
- let turnTransientAppendage: string[] | undefined = transientAppendage;
1048
- try {
1049
- // Build response prompt
1050
- const { prompt: responsePrompt, appliedInstructions } = await this.responseEngine.buildResponsePrompt({
1051
- flow: selectedFlow,
1052
- currentStep: nextStep,
1053
- rules: [],
1054
- prohibitions: [],
1055
- directives: responseDirectives,
1056
- history: historyEvents,
1057
- agentOptions: this.agent.getAgentOptions(),
1058
- instructions: this.collectScopedInstructions(selectedFlow, nextStep),
1059
- combinedTerms: this.agent.getTerms(),
1060
- context,
1061
- session,
1062
- agentSchema: this.agent.schema,
1063
- transientAppendage: turnTransientAppendage,
1064
- });
1065
-
1066
- // Collect available tools for AI
1067
- const availableTools = this.collectAvailableTools(selectedFlow, nextStep);
1068
-
1069
- // Generate message using AI provider
1070
- const agentOptions = this.agent.getAgentOptions();
1071
- const result = await agentOptions.provider.generateMessage({
1072
- prompt: responsePrompt,
1073
- history, // Use HistoryItem[] for AI provider
1074
- context,
1075
- tools: availableTools,
1076
- signal,
1077
- parameters: responseSchema ? { jsonSchema: responseSchema, schemaName: "response_output" } : undefined,
1078
- });
1079
-
1080
- let structuredData = result.structured;
1081
- let message = structuredData?.message || result.message;
1082
- const tokensUsed = result.metadata?.tokensUsed;
1083
-
1084
- // A schema was requested but the provider failed to parse the model's
1085
- // JSON (truncated output, fence-wrapped fragments). Raw protocol
1086
- // fragments must never surface as the user-visible reply: attempt one
1087
- // repair-parse, and if that fails fail the turn LOUDLY so the caller's
1088
- // rollback/retry path engages instead of leaking `{"message": "…` to
1089
- // an end user. Plain prose (not JSON-shaped at all) still passes
1090
- // through — it is not a protocol fragment.
1091
- if (!structuredData && responseSchema && message) {
1092
- const salvaged = this.salvageStructuredOutput(message, "turn");
1093
- if (salvaged) {
1094
- structuredData = salvaged;
1095
- message = salvaged.message;
1096
- }
1097
- }
1098
-
1099
- const effectiveResult = structuredData ? { ...result, structured: structuredData } : result;
1100
- let toolCalls = structuredData?.toolCalls;
1101
-
1102
- // Execute tools with unified loop handling
1103
- const toolResult = await this.toolLoopExecutor.runLoop({
1104
- toolCalls,
1105
- context,
1106
- session,
1107
- history,
1108
- selectedFlow,
1109
- responsePrompt,
1110
- availableTools,
1111
- responseSchema,
1112
- signal,
1113
- });
1114
-
1115
- session = toolResult.session;
1116
- toolCalls = toolResult.finalToolCalls;
1117
- let toolStructured = toolResult.structured;
1118
- if (toolResult.finalMessage) {
1119
- // The tool loop's follow-up calls carry the SAME response schema,
1120
- // and their message replaces the one the guard above already
1121
- // cleared — so an envelope produced after the tools ran reaches
1122
- // the user through a path that guard never sees. (Observed
1123
- // 2026-09-11: a catalog lookup answered, then the closing call
1124
- // returned `{"message": "…"}` raw to a WhatsApp customer.)
1125
- const salvaged = responseSchema
1126
- ? this.salvageStructuredOutput(toolResult.finalMessage, "turn")
1127
- : undefined;
1128
- message = salvaged?.message ?? toolResult.finalMessage;
1129
- if (salvaged) toolStructured = salvaged;
1130
- }
1131
-
1132
- // Tool-emitted directives (ctx.dispatch / `{directive}` returns):
1133
- // state writes apply now; control flow queues for the next turn's
1134
- // pendingDirective applier (same deferred semantics as dispatch()).
1135
- if (toolResult.directives) {
1136
- session = await this.applyToolEmittedDirectives(session, toolResult.directives);
1137
- }
1138
-
1139
- // Collect data from response
1140
- // Use follow-up structured data from tool loop when available, fall back to original result
1141
- const dataSource = toolStructured
1142
- ? { structured: toolStructured }
1143
- : effectiveResult;
1144
- session = await this.collectDataFromResponse({ result: dataSource, selectedFlow, nextStep, session });
1145
-
1146
- return { message, toolCalls, session, appliedInstructions, tokensUsed };
1147
- } finally {
1148
- // Drain the transient appendage at end of turn.
1149
- // This ensures Directive.appendPrompt does not leak to subsequent
1150
- // turns even when the turn terminates abnormally (error, abort, reject).
1151
- turnTransientAppendage = undefined;
1152
- }
1153
- }
1154
-
1155
- /**
1156
- * Unified streaming response generation.
1157
- * Renders the shared {@link planTurn} outcome as a chunk stream and runs the
1158
- * shared post-phase tail on the final chunk (finalizing exactly once).
1159
- * @private
1160
- */
1161
- private async *generateUnifiedStreamingResponse(
1162
- responseContext: ResponseContext<TContext, TData>
1163
- ): AsyncGenerator<AgentResponseStreamChunk<TData>> {
1164
- const plan = await this.planTurn(responseContext);
1165
- const { effectiveContext, history, historyEvents, signal, signalFirings } = plan;
1166
- const session = plan.session;
1167
-
1168
- // Build the inner chunk stream for the planned outcome. `runPostPhase` is
1169
- // the single post-phase gate (false only for auto-chain halt).
1170
- let innerStream: AsyncGenerator<AgentResponseStreamChunk<TData>>;
1171
- let runPostPhase = true;
1172
-
1173
- switch (plan.outcome.kind) {
1174
- case 'halt': {
1175
- runPostPhase = plan.outcome.runPostPhase;
1176
- innerStream = this.streamTerminalMessage({
1177
- message: plan.outcome.message,
1178
- stoppedReason: plan.outcome.stoppedReason,
1179
- session,
1180
- });
1181
- break;
1182
- }
1183
- case 'flowComplete': {
1184
- innerStream = this.streamFlowCompletion({
1185
- selectedFlow: plan.outcome.selectedFlow,
1186
- session,
1187
- context: effectiveContext,
1188
- history,
1189
- historyEvents,
1190
- stoppedReason: plan.outcome.stoppedReason,
1191
- });
1192
- break;
1193
- }
1194
- case 'flowStep': {
1195
- innerStream = this.processFlowStreamingResponse({
1196
- selectedFlow: plan.outcome.selectedFlow,
1197
- selectedStep: plan.outcome.step,
1198
- responseDirectives: plan.outcome.responseDirectives,
1199
- session,
1200
- history,
1201
- context: effectiveContext,
1202
- historyEvents,
1203
- signal,
1204
- transientAppendage: plan.outcome.signalPreDirective?.appendPrompt,
1205
- mergedPreDirective: plan.outcome.signalPreDirective,
1206
- });
1207
- break;
1208
- }
1209
- case 'fallback': {
1210
- innerStream = this.streamFallbackResponse({
1211
- history,
1212
- context: effectiveContext,
1213
- session,
1214
- signal,
1215
- });
1216
- break;
1217
- }
1218
- }
1219
-
1220
- // ── Intercept the inner stream on the final chunk ──────────────────────
1221
- // Mirrors the non-streaming tail: post-signal phase runs first (when
1222
- // applicable), then the session is finalized exactly once, attaching
1223
- // triggeredSignals to the final chunk (Requirement 11.2).
1224
- for await (const chunk of innerStream) {
1225
- if (chunk.done) {
1226
- const tail = await this.applyTurnPostPhase({
1227
- session: chunk.session || session,
1228
- context: effectiveContext,
1229
- historyEvents,
1230
- message: chunk.accumulated,
1231
- signalFirings,
1232
- runPostPhase,
1233
- });
1234
-
1235
- const accumulated = tail.message;
1236
- const delta = tail.replyOverridden ? accumulated : chunk.delta;
1237
-
1238
- // History ownership (parity with the sync tail): with
1239
- // params.message the returned session carries the full
1240
- // exchange — the user turn was recorded pre-routing, the
1241
- // assistant tail lands here, BEFORE finalize persists it.
1242
- if (responseContext.turnMessage && tail.message) {
1243
- tail.session.history = [...(tail.session.history ?? []), assistantMessage(tail.message)];
1244
- }
1245
-
1246
- // Single streaming exit: finalize the post-phase session so
1247
- // post-signal mutations (e.g. pendingDirective) are persisted.
1248
- await this.sessionFinalizer.finalize(tail.session, effectiveContext);
1249
-
1250
- yield {
1251
- ...chunk,
1252
- delta,
1253
- accumulated,
1254
- session: tail.session,
1255
- triggeredSignals: tail.triggeredSignals,
1256
- } as AgentResponseStreamChunk<TData>;
1257
- } else {
1258
- yield chunk;
1259
- }
1260
- }
1261
- }
1262
-
1263
- /**
1264
- * Emit a framework-authored message (a halt reply) as a single terminal
1265
- * chunk, to flow through the shared post-phase tail like any other inner
1266
- * stream. No LLM call, no provider text — so nothing to extract or finalize
1267
- * here; the caller's tail owns post-phase + finalize.
1268
- * @private
1269
- */
1270
- // eslint-disable-next-line @typescript-eslint/require-await -- yield-only async generator; must be `async *` to satisfy the AsyncGenerator return type the caller switches on
1271
- private async *streamTerminalMessage(params: {
1272
- message: string;
1273
- stoppedReason: StoppedReason;
1274
- session: SessionState<TData>;
1275
- }): AsyncGenerator<AgentResponseStreamChunk<TData>> {
1276
- yield {
1277
- delta: params.message,
1278
- accumulated: params.message,
1279
- done: true,
1280
- session: params.session,
1281
- toolCalls: undefined,
1282
- isFlowComplete: false,
1283
- stoppedReason: params.stoppedReason,
1284
- executedSteps: [],
1285
- } as AgentResponseStreamChunk<TData>;
1286
- }
1287
-
1288
- /**
1289
- * Wrap a provider message stream so each chunk's `delta`/`accumulated` carry
1290
- * clean message text instead of the raw structured-JSON wrapper. The single
1291
- * point where streamed JSON is unwrapped — every streaming response variant
1292
- * (flow step, fallback) consumes provider chunks through here, so consumers
1293
- * and stored history never see `{"message":...}` fragments. `structured`,
1294
- * `done`, and `metadata` pass through untouched.
1295
- * @private
1296
- */
1297
- private async *decodeMessageStream(
1298
- stream: AsyncGenerator<GenerateMessageStreamChunk<AgentStructuredResponse>>
1299
- ): AsyncGenerator<GenerateMessageStreamChunk<AgentStructuredResponse>> {
1300
- const decoder = new StreamingMessageDecoder();
1301
- for await (const chunk of stream) {
1302
- const clean = decoder.push(chunk.accumulated);
1303
- yield { ...chunk, delta: clean.delta, accumulated: clean.message };
1304
- }
1305
- }
1306
-
1307
- /**
1308
- * Process flow streaming response with unified tool execution and data collection
1309
- * @private
1310
- */
1311
- private async *processFlowStreamingResponse(params: {
1312
- selectedFlow: Flow<TContext, TData>;
1313
- selectedStep?: Step<TContext, TData>;
1314
- responseDirectives?: string[];
1315
- session: SessionState<TData>;
1316
- history: HistoryItem[];
1317
- context: TContext;
1318
- historyEvents: Event[];
1319
- signal?: AbortSignal;
1320
- /**
1321
- * Per-turn transient appendage from merged directive.appendPrompt.
1322
- * Fresh every turn, never cached, never persisted.
1323
- */
1324
- transientAppendage?: string[];
1325
- /**
1326
- * Merged directive from the directive bus's pre-LLM phase drain.
1327
- * When `halt: true`, the LLM call is skipped entirely.
1328
- */
1329
- mergedPreDirective?: Directive<TContext, TData>;
1330
- }): AsyncGenerator<AgentResponseStreamChunk<TData>> {
1331
- const { selectedFlow, selectedStep, responseDirectives, history, context, historyEvents, signal, transientAppendage, mergedPreDirective } = params;
1332
- let session = params.session;
1333
-
1334
- // Resolve the step to render (same logic as non-streaming)
1335
- const stepResolution = await this.responsePipeline.resolveRenderStep({
1336
- selectedFlow,
1337
- selectedStep,
1338
- session,
1339
- context,
1340
- });
1341
- if (stepResolution.flowTransition) {
1342
- // Flow transition or completion — no step to render
1343
- yield {
1344
- delta: '',
1345
- accumulated: '',
1346
- done: true,
1347
- session: stepResolution.session,
1348
- } as AgentResponseStreamChunk<TData>;
1349
- return;
1350
- }
1351
- const nextStep = stepResolution.nextStep!;
1352
- session = stepResolution.session;
1353
-
1354
- // Build response schema and prompt (same as non-streaming)
1355
- const responseSchema = this.responseEngine.responseSchemaForFlow(selectedFlow, nextStep, this.agent.schema);
1356
-
1357
- // ── HALT SHORT-CIRCUIT (Requirement 2.5, 2.6, 2.7) ──────────────────────
1358
- // After pre-LLM emissions are merged, if `halt: true` then skip the LLM
1359
- // call entirely. Emit a single done chunk with the appropriate content.
1360
- if (mergedPreDirective?.halt) {
1361
- const reply = mergedPreDirective.reply || '';
1362
- const reason: StoppedReason = mergedPreDirective.reply ? 'reply' : 'halt';
1363
- logger.debug(`[ResponseModal] Halt (streaming) — skipping LLM call for step ${nextStep.id}, stoppedReason: ${reason}`);
1364
- yield {
1365
- delta: reply,
1366
- accumulated: reply,
1367
- done: true,
1368
- session,
1369
- stoppedReason: reason,
1370
- executedSteps: [{ id: nextStep.id, flowId: selectedFlow.id }],
1371
- } as AgentResponseStreamChunk<TData>;
1372
- return;
1373
- }
1374
-
1375
- // ── STEP.REPLY SHORT-CIRCUIT (Requirement 25.1–25.7, 17.9) ──────────────
1376
- // A step with `reply` set emits a verbatim template response without LLM.
1377
- // onEnter and prepare have already fired normally. If prepare returned
1378
- // a Directive with `reply`, that overrides the step-declared reply.
1379
- if (nextStep.reply != null) {
1380
- const effectiveReply = mergedPreDirective?.reply ?? await render(
1381
- nextStep.reply,
1382
- createTemplateContext({ data: session.data || {}, context, session })
1383
- );
1384
- logger.debug(`[ResponseModal] Step.reply (streaming) — skipping LLM call for step ${nextStep.id}`);
1385
- yield {
1386
- delta: effectiveReply,
1387
- accumulated: effectiveReply,
1388
- done: true,
1389
- session,
1390
- stoppedReason: 'reply',
1391
- executedSteps: [{ id: nextStep.id, flowId: selectedFlow.id }],
1392
- } as AgentResponseStreamChunk<TData>;
1393
- return;
1394
- }
1395
-
1396
- // Transient appendage: per-turn slot from Directive.appendPrompt.
1397
- // Fresh each turn, never cached, never persisted.
1398
- // Wrapped in try/finally to ensure cleanup even on abnormal termination.
1399
- let turnTransientAppendage: string[] | undefined = transientAppendage;
1400
- try {
1401
- const { prompt: responsePrompt, appliedInstructions } = await this.responseEngine.buildResponsePrompt({
1402
- flow: selectedFlow,
1403
- currentStep: nextStep,
1404
- rules: [],
1405
- prohibitions: [],
1406
- directives: responseDirectives,
1407
- history: historyEvents,
1408
- agentOptions: this.agent.getAgentOptions(),
1409
- instructions: this.collectScopedInstructions(selectedFlow, nextStep),
1410
- combinedTerms: this.agent.getTerms(),
1411
- context,
1412
- session,
1413
- agentSchema: this.agent.schema,
1414
- transientAppendage: turnTransientAppendage,
1415
- });
1416
-
1417
- // Collect available tools for AI
1418
- const availableTools = this.collectAvailableTools(selectedFlow, nextStep);
1419
-
1420
- // Generate message stream using AI provider
1421
- const agentOptions = this.agent.getAgentOptions();
1422
- const stream = agentOptions.provider.generateMessageStream({
1423
- prompt: responsePrompt,
1424
- history, // Use HistoryItem[] for AI provider
1425
- context,
1426
- tools: availableTools,
1427
- signal,
1428
- parameters: { jsonSchema: responseSchema, schemaName: "response_stream_output" },
1429
- });
1430
-
1431
- // Stream chunks with unified tool handling. decodeMessageStream gives
1432
- // each chunk clean message text in delta/accumulated, so the non-done
1433
- // deltas, the final accumulated, the post-phase message input, and the
1434
- // assistant message stored by stream() are all clean — never the raw
1435
- // JSON wrapper (matching the non-streaming structured.message extraction).
1436
- for await (const chunk of this.decodeMessageStream(stream)) {
1437
- let toolCalls: Array<{ toolName: string; arguments: Record<string, unknown> }> | undefined = undefined;
1438
- // Final message/structured may be replaced by a forced post-tool
1439
- // response (see runStreamingBatch / gap: tools-ran-but-no-text).
1440
- let finalDelta = chunk.delta;
1441
- let finalAccumulated = chunk.accumulated;
1442
- let finalStructured = chunk.structured;
1443
-
1444
- // Extract tool calls from AI response on final chunk
1445
- if (chunk.done && chunk.structured?.toolCalls) {
1446
- toolCalls = chunk.structured.toolCalls;
1447
-
1448
- // Concurrent execution for the initial batch of tool calls,
1449
- // yielding tool-progress chunks as they arrive. The accumulated
1450
- // preamble is already clean text.
1451
- const batchResult = yield* this.toolLoopExecutor.runStreamingBatch({
1452
- toolCalls,
1453
- context,
1454
- session,
1455
- history,
1456
- selectedFlow,
1457
- step: nextStep,
1458
- accumulated: chunk.accumulated,
1459
- responsePrompt,
1460
- availableTools,
1461
- responseSchema,
1462
- signal,
1463
- });
1464
- session = batchResult.session;
1465
- toolCalls = batchResult.toolCalls;
1466
-
1467
- // Tool-emitted directives (ctx.dispatch / `{directive}`):
1468
- // state writes apply now; control flow queues for the next
1469
- // turn's pendingDirective applier (same deferred semantics
1470
- // as dispatch()). A verbatim tool reply already replaced the
1471
- // closing message inside runStreamingBatch.
1472
- if (batchResult.directives) {
1473
- session = await this.applyToolEmittedDirectives(session, batchResult.directives);
1474
- }
1475
-
1476
- // Prefer the post-tool follow-up structured for collection and
1477
- // emission whenever present — independent of whether a closing
1478
- // message was forced — matching the non-streaming path's
1479
- // `toolResult.structured ?? result` selection.
1480
- finalStructured = batchResult.structured ?? finalStructured;
1481
-
1482
- // Tools ran but the model produced no result-aware text — use
1483
- // the forced closing message (already clean) so we never emit the
1484
- // bare preamble (or an empty message) as the final response. Its
1485
- // delta is the portion not already streamed as the preamble.
1486
- if (batchResult.finalMessage) {
1487
- finalAccumulated = batchResult.finalMessage;
1488
- finalDelta = batchResult.finalMessage.startsWith(chunk.accumulated)
1489
- ? batchResult.finalMessage.slice(chunk.accumulated.length)
1490
- : batchResult.finalMessage;
1491
- }
1492
- }
1493
-
1494
- // Streaming twin of the non-streaming salvage guard: a schema was
1495
- // requested but no structured payload arrived, and the accumulated
1496
- // text is JSON-shaped (a protocol fragment) — repair-parse it or
1497
- // fail the turn rather than leaking raw output to the user.
1498
- if (chunk.done && !finalStructured && responseSchema && finalAccumulated) {
1499
- const salvaged = this.salvageStructuredOutput(finalAccumulated, "stream");
1500
- if (salvaged) {
1501
- finalDelta = salvaged.message.startsWith(finalAccumulated)
1502
- ? salvaged.message.slice(finalAccumulated.length)
1503
- : salvaged.message;
1504
- finalStructured = salvaged;
1505
- finalAccumulated = salvaged.message;
1506
- }
1507
- }
1508
-
1509
- // Collect data on the final chunk for any flow step — flow
1510
- // required/optional fields are valid targets even without a step
1511
- // `collect` — preferring the post-tool follow-up structured so a
1512
- // tool-driven turn harvests fields the model produced after tools.
1513
- if (chunk.done && finalStructured) {
1514
- session = await this.collectDataFromResponse({
1515
- result: { structured: finalStructured },
1516
- selectedFlow,
1517
- nextStep,
1518
- session,
1519
- });
1520
- }
1521
-
1522
- // Response structure completeness (Requirement 8.1, 8.2, 8.3)
1523
- // - executedSteps: single step executed in this response
1524
- // - stoppedReason: 'needs_input' for single-step execution (waiting for user input)
1525
- // - session.currentStep: reflects the executed step
1526
- yield {
1527
- delta: finalDelta,
1528
- accumulated: finalAccumulated,
1529
- done: chunk.done,
1530
- session,
1531
- toolCalls,
1532
- isFlowComplete: false,
1533
- executedSteps: chunk.done ? [{ id: nextStep.id, flowId: selectedFlow.id }] : undefined,
1534
- stoppedReason: chunk.done ? 'needs_input' : undefined,
1535
- metadata: chunk.metadata,
1536
- structured: finalStructured,
1537
- appliedInstructions: chunk.done ? appliedInstructions : undefined,
1538
- };
1539
- }
1540
- } finally {
1541
- // Drain the transient appendage at end of turn.
1542
- // This ensures Directive.appendPrompt does not leak to subsequent
1543
- // turns even when the turn terminates abnormally (error, abort, reject).
1544
- turnTransientAppendage = undefined;
1545
- }
1546
- }
1547
-
1548
- /**
1549
- * Unified data collection from AI response
1550
- * @private
1551
- */
1552
- private async collectDataFromResponse(params: {
1553
- result: { structured?: AgentStructuredResponse };
1554
- selectedFlow?: Flow<TContext, TData>;
1555
- nextStep?: Step<TContext, TData>;
1556
- session: SessionState<TData>;
1557
- }): Promise<SessionState<TData>> {
1558
- try {
1559
- const { result, selectedFlow, nextStep, session } = params;
1560
- let updatedSession = session;
1561
-
1562
- // Extract collected data from final response (only for flow-based interactions)
1563
- if (selectedFlow && result.structured) {
1564
- try {
1565
- const collectedData: Record<string, unknown> = {};
1566
- // AgentStructuredResponse extends Record<string, unknown>, so we can safely access properties
1567
- const structuredData = result.structured;
1568
-
1569
- // Collect ALL flow fields (required + optional) from structured response
1570
- const allFlowFields = new Set<string>();
1571
-
1572
- // Add flow required fields
1573
- if (selectedFlow.requiredFields) {
1574
- selectedFlow.requiredFields.forEach(field => allFlowFields.add(String(field)));
1575
- }
1576
-
1577
- // Add flow optional fields
1578
- if (selectedFlow.optionalFields) {
1579
- selectedFlow.optionalFields.forEach(field => allFlowFields.add(String(field)));
1580
- }
1581
-
1582
- // Also include current step's collect fields (in case they're not in flow fields)
1583
- if (nextStep?.collect) {
1584
- nextStep.collect.forEach(field => allFlowFields.add(String(field)));
1585
- }
1586
-
1587
- // Extract all available fields from structured response
1588
- for (const field of allFlowFields) {
1589
- const fieldKey = String(field);
1590
- if (fieldKey in structuredData && structuredData[fieldKey] !== undefined && structuredData[fieldKey] !== null) {
1591
- collectedData[fieldKey] = structuredData[fieldKey];
1592
- }
1593
- }
1594
-
1595
- // Merge collected data into session using agent-level data validation
1596
- if (Object.keys(collectedData).length > 0) {
1597
- try {
1598
- // Update agent-level collected data with validation
1599
- await this.agent.updateCollectedData(collectedData as Partial<TData>);
1600
-
1601
- // Update session with validated data
1602
- const updateDataMethod = this.agent.getUpdateDataMethod();
1603
- updatedSession = await updateDataMethod(updatedSession, collectedData as Partial<TData>);
1604
- logger.debug(`[ResponseModal] Collected data:`, collectedData);
1605
- } catch (error) {
1606
- logger.error(`[ResponseModal] Failed to update collected data:`, error);
1607
- // Continue without updating data rather than failing completely
1608
- }
1609
- }
1610
- } catch (error) {
1611
- logger.error(`[ResponseModal] Error during data collection:`, error);
1612
- // Continue without collecting data rather than failing completely
1613
- }
1614
- }
1615
-
1616
- // Extract any additional data from structured response
1617
- // Since AgentStructuredResponse extends Record<string, unknown>, we can safely check for additional properties
1618
- if (result.structured && "contextUpdate" in result.structured) {
1619
- try {
1620
- const contextUpdate = (result.structured as AgentStructuredResponse & { contextUpdate?: Partial<TContext> }).contextUpdate;
1621
- if (contextUpdate) {
1622
- await this.agent.updateContext(contextUpdate);
1623
- }
1624
- } catch (error) {
1625
- logger.error(`[ResponseModal] Failed to update context from structured response:`, error);
1626
- // Continue without updating context rather than failing completely
1627
- }
1628
- }
1629
-
1630
- return updatedSession;
1631
- } catch (error) {
1632
- logger.error(`[ResponseModal] Error in collectDataFromResponse:`, error);
1633
- // Return original session if data collection fails completely
1634
- return params.session;
1635
- }
1636
- }
1637
-
1638
- /**
1639
- * Apply flow completion: release the session to idle state.
1640
- *
1641
- * This is a pure state transition. The framework emits **no message of
1642
- * its own** at the completion boundary — every word delivered to the
1643
- * user comes from a developer-defined step prompt. If the dev wants a
1644
- * closing turn, they add a final interactive step with their own
1645
- * `prompt`; the framework respects that step's natural LLM output.
1646
- *
1647
- * Behavior:
1648
- * - Marks the active `flowHistory` entry as `completed: true` and
1649
- * stamps `exitedAt`.
1650
- * - Evaluates `flow.onComplete` for an explicit follow-up transition.
1651
- * When set, populates `session.pendingDirective` (the next turn's
1652
- * pipeline applies it). When absent, the session is fully idle.
1653
- * - Clears `currentFlow` and `currentStep` to `undefined`.
1654
- * - Clears owned fields when the flow is `reentrant` so subsequent
1655
- * re-selections start from a clean state.
1656
- *
1657
- * Returns the updated session. Callers compose any reply text from
1658
- * their own sources (an upstream LLM turn, a directive's `reply`, or
1659
- * an empty string for silent completion).
1660
- *
1661
- * @private
1662
- */
1663
- private async applyFlowCompletion(params: {
1664
- selectedFlow: Flow<TContext, TData>;
1665
- session: SessionState<TData>;
1666
- context: TContext;
1667
- history: HistoryItem[];
1668
- }): Promise<SessionState<TData>> {
1669
- const { selectedFlow, session, context } = params;
1670
-
1671
- // 1) Evaluate onComplete first — needs the still-active session shape.
1672
- const transitionConfig = await selectedFlow.evaluateOnComplete(
1673
- { data: session.data },
1674
- context,
1675
- );
1676
-
1677
- // 2) Release to idle. If the flow is reentrant, scrub its owned
1678
- // fields so re-selection on a future turn starts clean. When
1679
- // onComplete fires we still go idle here — the next turn's
1680
- // pipeline applies the pendingDirective before any routing.
1681
- const ownedFields = selectedFlow.reentrant
1682
- ? [
1683
- ...(selectedFlow.requiredFields ?? []),
1684
- ...(selectedFlow.optionalFields ?? []),
1685
- ]
1686
- : undefined;
1687
-
1688
- let nextSession = completeCurrentFlow(session, {
1689
- clearOwnedFields: ownedFields,
1690
- });
1691
-
1692
- // 3) Wire pendingDirective when onComplete returned a target.
1693
- if (transitionConfig) {
1694
- const goToTarget = typeof transitionConfig.goTo === 'string'
1695
- ? transitionConfig.goTo
1696
- : transitionConfig.goTo?.flow;
1697
-
1698
- const targetFlow = goToTarget ? this.agent.getFlows().find(
1699
- (r) =>
1700
- r.id === goToTarget ||
1701
- r.title === goToTarget,
1702
- ) : undefined;
1703
-
1704
- if (targetFlow) {
1705
- nextSession = {
1706
- ...nextSession,
1707
- pendingDirective: {
1708
- goTo: targetFlow.id,
1709
- },
1710
- };
1711
- logger.debug(
1712
- `[ResponseModal] Flow ${selectedFlow.title} completed with pending directive to: ${targetFlow.title}`,
1713
- );
1714
- } else if (goToTarget) {
1715
- logger.warn(
1716
- `[FlowConfigurationError] onComplete target not found: flow "${selectedFlow.title}" completed but onComplete target "${goToTarget}" does not match any flow. ` +
1717
- `Fix the onComplete value to reference an existing flow id/title, or remove onComplete to release the session to idle.`,
1718
- );
1719
- }
1720
- } else {
1721
- logger.debug(
1722
- `[ResponseModal] Flow ${selectedFlow.title} completed; session released to idle.`,
1723
- );
1724
- }
1725
-
1726
- return nextSession;
1727
- }
1728
-
1729
- /**
1730
- * Stream a flow completion as a single terminal chunk.
1731
- *
1732
- * No LLM call is made. The framework no longer authors a farewell — the
1733
- * completion path is a pure state transition. The chunk emits an empty
1734
- * `delta` and a `done: true` flag with the idle session attached so
1735
- * downstream consumers can finalize cleanly.
1736
- *
1737
- * If the developer wants closing copy in a streaming response, they
1738
- * should add a final interactive step whose own LLM turn delivers it.
1739
- *
1740
- * @private
1741
- */
1742
- private async *streamFlowCompletion(params: {
1743
- selectedFlow: Flow<TContext, TData>;
1744
- session: SessionState<TData>;
1745
- context: TContext;
1746
- history: HistoryItem[];
1747
- historyEvents: Event[];
1748
- stoppedReason?: StoppedReason;
1749
- signal?: AbortSignal;
1750
- }): AsyncGenerator<AgentResponseStreamChunk<TData>> {
1751
- const { selectedFlow, context, history } = params;
1752
-
1753
- const session = await this.applyFlowCompletion({
1754
- selectedFlow,
1755
- session: params.session,
1756
- context,
1757
- history,
1758
- });
1759
-
1760
- yield {
1761
- delta: '',
1762
- accumulated: '',
1763
- done: true,
1764
- session,
1765
- toolCalls: undefined,
1766
- isFlowComplete: true,
1767
- executedSteps: [],
1768
- stoppedReason: params.stoppedReason ?? 'completed',
1769
- };
1770
- }
1771
-
1772
- /**
1773
- * Generate fallback response when no flows are available
1774
- * @private
1775
- */
1776
- private async generateFallbackResponse(params: {
1777
- history: HistoryItem[];
1778
- context: TContext;
1779
- session: SessionState<TData>;
1780
- signal?: AbortSignal;
1781
- }): Promise<{ message: string; appliedInstructions?: AppliedInstruction[] }> {
1782
- const { history, context, session, signal } = params;
1783
-
1784
- logger.debug(`[ResponseModal] No flow selected, generating basic response`);
1785
-
1786
- // Build basic response prompt without flow context
1787
- const { prompt: fallbackPrompt, appliedInstructions } = await this.responseEngine.buildFallbackPrompt({
1788
- agentOptions: this.agent.getAgentOptions(),
1789
- terms: this.agent.getTerms(),
1790
- instructions: this.collectScopedInstructions(),
1791
- context,
1792
- session,
1793
- });
1794
-
1795
- const agentOptions = this.agent.getAgentOptions();
1796
- const result = await agentOptions.provider.generateMessage({
1797
- prompt: fallbackPrompt,
1798
- history,
1799
- context,
1800
- signal,
1801
- parameters: {
1802
- jsonSchema: {
1803
- type: "object",
1804
- properties: { message: { type: "string" } },
1805
- required: ["message"],
1806
- additionalProperties: false,
1807
- },
1808
- schemaName: "fallback_response",
1809
- },
1810
- });
1811
-
1812
- return { message: result.structured?.message || result.message, appliedInstructions };
1813
- }
1814
-
1815
- /**
1816
- * Stream fallback response when no flows are available
1817
- * @private
1818
- */
1819
- private async *streamFallbackResponse(params: {
1820
- history: HistoryItem[];
1821
- context: TContext;
1822
- session: SessionState<TData>;
1823
- signal?: AbortSignal;
1824
- }): AsyncGenerator<AgentResponseStreamChunk<TData>> {
1825
- const { history, context, session, signal } = params;
1826
-
1827
- const { prompt: fallbackPrompt, appliedInstructions } = await this.responseEngine.buildFallbackPrompt({
1828
- agentOptions: this.agent.getAgentOptions(),
1829
- terms: this.agent.getTerms(),
1830
- instructions: this.collectScopedInstructions(),
1831
- context,
1832
- session,
1833
- });
1834
-
1835
- const agentOptions = this.agent.getAgentOptions();
1836
- const stream = agentOptions.provider.generateMessageStream({
1837
- prompt: fallbackPrompt,
1838
- history,
1839
- context,
1840
- signal,
1841
- parameters: {
1842
- jsonSchema: {
1843
- type: "object",
1844
- properties: { message: { type: "string" } },
1845
- required: ["message"],
1846
- additionalProperties: false,
1847
- },
1848
- schemaName: "fallback_stream_response",
1849
- },
1850
- });
1851
-
1852
- // Decode the JSON wrapper to clean message text (same as the flow path).
1853
- for await (const chunk of this.decodeMessageStream(stream)) {
1854
- // Response structure completeness (Requirement 8.1, 8.2, 8.3)
1855
- // - executedSteps: empty for fallback (no flow/step execution)
1856
- // - stoppedReason: undefined for fallback (no flow context)
1857
- // - session.currentStep: unchanged (no step progression)
1858
- yield {
1859
- delta: chunk.delta,
1860
- accumulated: chunk.accumulated,
1861
- done: chunk.done,
1862
- session,
1863
- toolCalls: undefined,
1864
- isFlowComplete: false,
1865
- executedSteps: chunk.done ? [] : undefined,
1866
- stoppedReason: undefined,
1867
- metadata: chunk.metadata,
1868
- structured: chunk.structured,
1869
- appliedInstructions: chunk.done ? appliedInstructions : undefined,
1870
- };
1871
- }
1872
- }
1873
-
1874
- // ============================================================================
1875
- // UTILITY METHODS - Helper methods for tool management and other utilities
1876
- // ============================================================================
1877
-
1878
-
1879
- /**
1880
- * Collect all available tools for the given flow and step context.
1881
- * Delegates to ToolManager for unified tool resolution and deduplication.
1882
- * @private
1883
- */
1884
- private collectAvailableTools(
1885
- flow?: Flow<TContext, TData>,
1886
- step?: Step<TContext, TData>
1887
- ): Array<{
1888
- id: string;
1889
- name: string;
1890
- description?: string;
1891
- parameters?: unknown;
1892
- }> {
1893
- const availableTools = this.getToolManager().getAvailable(undefined, step, flow);
1894
- return availableTools.map((tool) => ({
1895
- id: tool.id,
1896
- name: tool.id,
1897
- description: tool.description,
1898
- parameters: tool.parameters,
1899
- }));
1900
- }
1901
-
1902
- }