@falai/agent 2.2.3 → 2.4.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 (397) hide show
  1. package/README.md +2 -2
  2. package/dist/adapters/MemoryAdapter.d.ts.map +1 -1
  3. package/dist/adapters/MemoryAdapter.js +10 -1
  4. package/dist/adapters/MemoryAdapter.js.map +1 -1
  5. package/dist/adapters/MongoAdapter.d.ts.map +1 -1
  6. package/dist/adapters/MongoAdapter.js +33 -2
  7. package/dist/adapters/MongoAdapter.js.map +1 -1
  8. package/dist/adapters/OpenSearchAdapter.d.ts.map +1 -1
  9. package/dist/adapters/OpenSearchAdapter.js +15 -1
  10. package/dist/adapters/OpenSearchAdapter.js.map +1 -1
  11. package/dist/adapters/PostgreSQLAdapter.d.ts.map +1 -1
  12. package/dist/adapters/PostgreSQLAdapter.js +35 -5
  13. package/dist/adapters/PostgreSQLAdapter.js.map +1 -1
  14. package/dist/adapters/PrismaAdapter.d.ts.map +1 -1
  15. package/dist/adapters/PrismaAdapter.js +58 -15
  16. package/dist/adapters/PrismaAdapter.js.map +1 -1
  17. package/dist/adapters/RedisAdapter.d.ts.map +1 -1
  18. package/dist/adapters/RedisAdapter.js +10 -1
  19. package/dist/adapters/RedisAdapter.js.map +1 -1
  20. package/dist/adapters/SQLiteAdapter.d.ts.map +1 -1
  21. package/dist/adapters/SQLiteAdapter.js +38 -6
  22. package/dist/adapters/SQLiteAdapter.js.map +1 -1
  23. package/dist/cjs/adapters/MemoryAdapter.d.ts.map +1 -1
  24. package/dist/cjs/adapters/MemoryAdapter.js +10 -1
  25. package/dist/cjs/adapters/MemoryAdapter.js.map +1 -1
  26. package/dist/cjs/adapters/MongoAdapter.d.ts.map +1 -1
  27. package/dist/cjs/adapters/MongoAdapter.js +33 -2
  28. package/dist/cjs/adapters/MongoAdapter.js.map +1 -1
  29. package/dist/cjs/adapters/OpenSearchAdapter.d.ts.map +1 -1
  30. package/dist/cjs/adapters/OpenSearchAdapter.js +15 -1
  31. package/dist/cjs/adapters/OpenSearchAdapter.js.map +1 -1
  32. package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +1 -1
  33. package/dist/cjs/adapters/PostgreSQLAdapter.js +35 -5
  34. package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -1
  35. package/dist/cjs/adapters/PrismaAdapter.d.ts.map +1 -1
  36. package/dist/cjs/adapters/PrismaAdapter.js +58 -15
  37. package/dist/cjs/adapters/PrismaAdapter.js.map +1 -1
  38. package/dist/cjs/adapters/RedisAdapter.d.ts.map +1 -1
  39. package/dist/cjs/adapters/RedisAdapter.js +10 -1
  40. package/dist/cjs/adapters/RedisAdapter.js.map +1 -1
  41. package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +1 -1
  42. package/dist/cjs/adapters/SQLiteAdapter.js +38 -6
  43. package/dist/cjs/adapters/SQLiteAdapter.js.map +1 -1
  44. package/dist/cjs/core/Agent.d.ts +28 -18
  45. package/dist/cjs/core/Agent.d.ts.map +1 -1
  46. package/dist/cjs/core/Agent.js +58 -68
  47. package/dist/cjs/core/Agent.js.map +1 -1
  48. package/dist/cjs/core/AutoChainExecutor.d.ts.map +1 -1
  49. package/dist/cjs/core/AutoChainExecutor.js +14 -20
  50. package/dist/cjs/core/AutoChainExecutor.js.map +1 -1
  51. package/dist/cjs/core/BranchEvaluator.d.ts +4 -3
  52. package/dist/cjs/core/BranchEvaluator.d.ts.map +1 -1
  53. package/dist/cjs/core/BranchEvaluator.js +18 -23
  54. package/dist/cjs/core/BranchEvaluator.js.map +1 -1
  55. package/dist/cjs/core/Flow.d.ts +2 -1
  56. package/dist/cjs/core/Flow.d.ts.map +1 -1
  57. package/dist/cjs/core/Flow.js +10 -3
  58. package/dist/cjs/core/Flow.js.map +1 -1
  59. package/dist/cjs/core/FlowRouter.d.ts +1 -0
  60. package/dist/cjs/core/FlowRouter.d.ts.map +1 -1
  61. package/dist/cjs/core/FlowRouter.js +28 -5
  62. package/dist/cjs/core/FlowRouter.js.map +1 -1
  63. package/dist/cjs/core/PersistenceManager.d.ts +3 -0
  64. package/dist/cjs/core/PersistenceManager.d.ts.map +1 -1
  65. package/dist/cjs/core/PersistenceManager.js +57 -5
  66. package/dist/cjs/core/PersistenceManager.js.map +1 -1
  67. package/dist/cjs/core/PromptComposer.d.ts.map +1 -1
  68. package/dist/cjs/core/PromptComposer.js +24 -10
  69. package/dist/cjs/core/PromptComposer.js.map +1 -1
  70. package/dist/cjs/core/ResponseGenerationError.d.ts +30 -0
  71. package/dist/cjs/core/ResponseGenerationError.d.ts.map +1 -0
  72. package/dist/cjs/core/ResponseGenerationError.js +37 -0
  73. package/dist/cjs/core/ResponseGenerationError.js.map +1 -0
  74. package/dist/cjs/core/ResponseModal.d.ts +43 -96
  75. package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
  76. package/dist/cjs/core/ResponseModal.js +184 -1183
  77. package/dist/cjs/core/ResponseModal.js.map +1 -1
  78. package/dist/cjs/core/ResponsePipeline.d.ts +58 -152
  79. package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -1
  80. package/dist/cjs/core/ResponsePipeline.js +408 -457
  81. package/dist/cjs/core/ResponsePipeline.js.map +1 -1
  82. package/dist/cjs/core/SessionFinalizer.d.ts +34 -0
  83. package/dist/cjs/core/SessionFinalizer.d.ts.map +1 -0
  84. package/dist/cjs/core/SessionFinalizer.js +61 -0
  85. package/dist/cjs/core/SessionFinalizer.js.map +1 -0
  86. package/dist/cjs/core/SessionManager.d.ts +1 -1
  87. package/dist/cjs/core/SessionManager.d.ts.map +1 -1
  88. package/dist/cjs/core/SessionManager.js +18 -7
  89. package/dist/cjs/core/SessionManager.js.map +1 -1
  90. package/dist/cjs/core/SignalCoordinator.d.ts +103 -0
  91. package/dist/cjs/core/SignalCoordinator.d.ts.map +1 -0
  92. package/dist/cjs/core/SignalCoordinator.js +207 -0
  93. package/dist/cjs/core/SignalCoordinator.js.map +1 -0
  94. package/dist/cjs/core/SignalEvaluator.d.ts +2 -2
  95. package/dist/cjs/core/SignalEvaluator.d.ts.map +1 -1
  96. package/dist/cjs/core/SignalEvaluator.js +13 -27
  97. package/dist/cjs/core/SignalEvaluator.js.map +1 -1
  98. package/dist/cjs/core/SignalProcessor.d.ts.map +1 -1
  99. package/dist/cjs/core/SignalProcessor.js +1 -3
  100. package/dist/cjs/core/SignalProcessor.js.map +1 -1
  101. package/dist/cjs/core/Step.d.ts +3 -1
  102. package/dist/cjs/core/Step.d.ts.map +1 -1
  103. package/dist/cjs/core/Step.js +10 -3
  104. package/dist/cjs/core/Step.js.map +1 -1
  105. package/dist/cjs/core/StepLifecycle.d.ts +33 -0
  106. package/dist/cjs/core/StepLifecycle.d.ts.map +1 -0
  107. package/dist/cjs/core/StepLifecycle.js +97 -0
  108. package/dist/cjs/core/StepLifecycle.js.map +1 -0
  109. package/dist/cjs/core/ToolLoopExecutor.d.ts +104 -0
  110. package/dist/cjs/core/ToolLoopExecutor.d.ts.map +1 -0
  111. package/dist/cjs/core/ToolLoopExecutor.js +391 -0
  112. package/dist/cjs/core/ToolLoopExecutor.js.map +1 -0
  113. package/dist/cjs/core/ToolManager.d.ts +1 -1
  114. package/dist/cjs/core/ToolManager.d.ts.map +1 -1
  115. package/dist/cjs/core/ToolManager.js.map +1 -1
  116. package/dist/cjs/index.d.ts +4 -5
  117. package/dist/cjs/index.d.ts.map +1 -1
  118. package/dist/cjs/index.js +7 -6
  119. package/dist/cjs/index.js.map +1 -1
  120. package/dist/cjs/providers/AnthropicProvider.d.ts +2 -0
  121. package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
  122. package/dist/cjs/providers/AnthropicProvider.js +28 -56
  123. package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
  124. package/dist/cjs/providers/DeepSeekProvider.d.ts +25 -21
  125. package/dist/cjs/providers/DeepSeekProvider.d.ts.map +1 -1
  126. package/dist/cjs/providers/DeepSeekProvider.js +48 -407
  127. package/dist/cjs/providers/DeepSeekProvider.js.map +1 -1
  128. package/dist/cjs/providers/GeminiProvider.d.ts +2 -0
  129. package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
  130. package/dist/cjs/providers/GeminiProvider.js +27 -56
  131. package/dist/cjs/providers/GeminiProvider.js.map +1 -1
  132. package/dist/cjs/providers/OpenAICompatibleProvider.d.ts +129 -0
  133. package/dist/cjs/providers/OpenAICompatibleProvider.d.ts.map +1 -0
  134. package/dist/cjs/providers/OpenAICompatibleProvider.js +485 -0
  135. package/dist/cjs/providers/OpenAICompatibleProvider.js.map +1 -0
  136. package/dist/cjs/providers/OpenAIProvider.d.ts +9 -28
  137. package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
  138. package/dist/cjs/providers/OpenAIProvider.js +23 -417
  139. package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
  140. package/dist/cjs/providers/OpenRouterProvider.d.ts +10 -27
  141. package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -1
  142. package/dist/cjs/providers/OpenRouterProvider.js +28 -417
  143. package/dist/cjs/providers/OpenRouterProvider.js.map +1 -1
  144. package/dist/cjs/providers/errorClassification.d.ts +61 -0
  145. package/dist/cjs/providers/errorClassification.d.ts.map +1 -0
  146. package/dist/cjs/providers/errorClassification.js +123 -0
  147. package/dist/cjs/providers/errorClassification.js.map +1 -0
  148. package/dist/cjs/providers/index.d.ts +4 -0
  149. package/dist/cjs/providers/index.d.ts.map +1 -1
  150. package/dist/cjs/providers/index.js +7 -1
  151. package/dist/cjs/providers/index.js.map +1 -1
  152. package/dist/cjs/types/agent.d.ts +2 -1
  153. package/dist/cjs/types/agent.d.ts.map +1 -1
  154. package/dist/cjs/types/ai.d.ts +20 -0
  155. package/dist/cjs/types/ai.d.ts.map +1 -1
  156. package/dist/cjs/types/errors.d.ts +33 -0
  157. package/dist/cjs/types/errors.d.ts.map +1 -1
  158. package/dist/cjs/types/errors.js +37 -1
  159. package/dist/cjs/types/errors.js.map +1 -1
  160. package/dist/cjs/types/flow.d.ts +10 -5
  161. package/dist/cjs/types/flow.d.ts.map +1 -1
  162. package/dist/cjs/types/history.d.ts +1 -1
  163. package/dist/cjs/types/history.d.ts.map +1 -1
  164. package/dist/cjs/types/index.d.ts +5 -4
  165. package/dist/cjs/types/index.d.ts.map +1 -1
  166. package/dist/cjs/types/index.js +3 -1
  167. package/dist/cjs/types/index.js.map +1 -1
  168. package/dist/cjs/types/persistence.d.ts +43 -2
  169. package/dist/cjs/types/persistence.d.ts.map +1 -1
  170. package/dist/cjs/types/session.d.ts +8 -0
  171. package/dist/cjs/types/session.d.ts.map +1 -1
  172. package/dist/cjs/types/signals.d.ts +21 -11
  173. package/dist/cjs/types/signals.d.ts.map +1 -1
  174. package/dist/cjs/types/template.d.ts +3 -1
  175. package/dist/cjs/types/template.d.ts.map +1 -1
  176. package/dist/cjs/types/tool.d.ts +4 -4
  177. package/dist/cjs/types/tool.d.ts.map +1 -1
  178. package/dist/cjs/utils/condition.d.ts +19 -0
  179. package/dist/cjs/utils/condition.d.ts.map +1 -1
  180. package/dist/cjs/utils/condition.js +86 -15
  181. package/dist/cjs/utils/condition.js.map +1 -1
  182. package/dist/cjs/utils/session.d.ts +1 -0
  183. package/dist/cjs/utils/session.d.ts.map +1 -1
  184. package/dist/cjs/utils/session.js +1 -0
  185. package/dist/cjs/utils/session.js.map +1 -1
  186. package/dist/core/Agent.d.ts +28 -18
  187. package/dist/core/Agent.d.ts.map +1 -1
  188. package/dist/core/Agent.js +58 -68
  189. package/dist/core/Agent.js.map +1 -1
  190. package/dist/core/AutoChainExecutor.d.ts.map +1 -1
  191. package/dist/core/AutoChainExecutor.js +14 -20
  192. package/dist/core/AutoChainExecutor.js.map +1 -1
  193. package/dist/core/BranchEvaluator.d.ts +4 -3
  194. package/dist/core/BranchEvaluator.d.ts.map +1 -1
  195. package/dist/core/BranchEvaluator.js +18 -23
  196. package/dist/core/BranchEvaluator.js.map +1 -1
  197. package/dist/core/Flow.d.ts +2 -1
  198. package/dist/core/Flow.d.ts.map +1 -1
  199. package/dist/core/Flow.js +10 -3
  200. package/dist/core/Flow.js.map +1 -1
  201. package/dist/core/FlowRouter.d.ts +1 -0
  202. package/dist/core/FlowRouter.d.ts.map +1 -1
  203. package/dist/core/FlowRouter.js +28 -5
  204. package/dist/core/FlowRouter.js.map +1 -1
  205. package/dist/core/PersistenceManager.d.ts +3 -0
  206. package/dist/core/PersistenceManager.d.ts.map +1 -1
  207. package/dist/core/PersistenceManager.js +58 -6
  208. package/dist/core/PersistenceManager.js.map +1 -1
  209. package/dist/core/PromptComposer.d.ts.map +1 -1
  210. package/dist/core/PromptComposer.js +24 -10
  211. package/dist/core/PromptComposer.js.map +1 -1
  212. package/dist/core/ResponseGenerationError.d.ts +30 -0
  213. package/dist/core/ResponseGenerationError.d.ts.map +1 -0
  214. package/dist/core/ResponseGenerationError.js +33 -0
  215. package/dist/core/ResponseGenerationError.js.map +1 -0
  216. package/dist/core/ResponseModal.d.ts +43 -96
  217. package/dist/core/ResponseModal.d.ts.map +1 -1
  218. package/dist/core/ResponseModal.js +177 -1175
  219. package/dist/core/ResponseModal.js.map +1 -1
  220. package/dist/core/ResponsePipeline.d.ts +58 -152
  221. package/dist/core/ResponsePipeline.d.ts.map +1 -1
  222. package/dist/core/ResponsePipeline.js +409 -458
  223. package/dist/core/ResponsePipeline.js.map +1 -1
  224. package/dist/core/SessionFinalizer.d.ts +34 -0
  225. package/dist/core/SessionFinalizer.d.ts.map +1 -0
  226. package/dist/core/SessionFinalizer.js +57 -0
  227. package/dist/core/SessionFinalizer.js.map +1 -0
  228. package/dist/core/SessionManager.d.ts +1 -1
  229. package/dist/core/SessionManager.d.ts.map +1 -1
  230. package/dist/core/SessionManager.js +18 -7
  231. package/dist/core/SessionManager.js.map +1 -1
  232. package/dist/core/SignalCoordinator.d.ts +103 -0
  233. package/dist/core/SignalCoordinator.d.ts.map +1 -0
  234. package/dist/core/SignalCoordinator.js +203 -0
  235. package/dist/core/SignalCoordinator.js.map +1 -0
  236. package/dist/core/SignalEvaluator.d.ts +2 -2
  237. package/dist/core/SignalEvaluator.d.ts.map +1 -1
  238. package/dist/core/SignalEvaluator.js +13 -27
  239. package/dist/core/SignalEvaluator.js.map +1 -1
  240. package/dist/core/SignalProcessor.d.ts.map +1 -1
  241. package/dist/core/SignalProcessor.js +1 -3
  242. package/dist/core/SignalProcessor.js.map +1 -1
  243. package/dist/core/Step.d.ts +3 -1
  244. package/dist/core/Step.d.ts.map +1 -1
  245. package/dist/core/Step.js +10 -3
  246. package/dist/core/Step.js.map +1 -1
  247. package/dist/core/StepLifecycle.d.ts +33 -0
  248. package/dist/core/StepLifecycle.d.ts.map +1 -0
  249. package/dist/core/StepLifecycle.js +93 -0
  250. package/dist/core/StepLifecycle.js.map +1 -0
  251. package/dist/core/ToolLoopExecutor.d.ts +104 -0
  252. package/dist/core/ToolLoopExecutor.d.ts.map +1 -0
  253. package/dist/core/ToolLoopExecutor.js +387 -0
  254. package/dist/core/ToolLoopExecutor.js.map +1 -0
  255. package/dist/core/ToolManager.d.ts +1 -1
  256. package/dist/core/ToolManager.d.ts.map +1 -1
  257. package/dist/core/ToolManager.js.map +1 -1
  258. package/dist/index.d.ts +4 -5
  259. package/dist/index.d.ts.map +1 -1
  260. package/dist/index.js +3 -3
  261. package/dist/index.js.map +1 -1
  262. package/dist/providers/AnthropicProvider.d.ts +2 -0
  263. package/dist/providers/AnthropicProvider.d.ts.map +1 -1
  264. package/dist/providers/AnthropicProvider.js +22 -50
  265. package/dist/providers/AnthropicProvider.js.map +1 -1
  266. package/dist/providers/DeepSeekProvider.d.ts +25 -21
  267. package/dist/providers/DeepSeekProvider.d.ts.map +1 -1
  268. package/dist/providers/DeepSeekProvider.js +49 -408
  269. package/dist/providers/DeepSeekProvider.js.map +1 -1
  270. package/dist/providers/GeminiProvider.d.ts +2 -0
  271. package/dist/providers/GeminiProvider.d.ts.map +1 -1
  272. package/dist/providers/GeminiProvider.js +21 -50
  273. package/dist/providers/GeminiProvider.js.map +1 -1
  274. package/dist/providers/OpenAICompatibleProvider.d.ts +129 -0
  275. package/dist/providers/OpenAICompatibleProvider.d.ts.map +1 -0
  276. package/dist/providers/OpenAICompatibleProvider.js +481 -0
  277. package/dist/providers/OpenAICompatibleProvider.js.map +1 -0
  278. package/dist/providers/OpenAIProvider.d.ts +9 -28
  279. package/dist/providers/OpenAIProvider.d.ts.map +1 -1
  280. package/dist/providers/OpenAIProvider.js +23 -417
  281. package/dist/providers/OpenAIProvider.js.map +1 -1
  282. package/dist/providers/OpenRouterProvider.d.ts +10 -27
  283. package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
  284. package/dist/providers/OpenRouterProvider.js +28 -417
  285. package/dist/providers/OpenRouterProvider.js.map +1 -1
  286. package/dist/providers/errorClassification.d.ts +61 -0
  287. package/dist/providers/errorClassification.d.ts.map +1 -0
  288. package/dist/providers/errorClassification.js +116 -0
  289. package/dist/providers/errorClassification.js.map +1 -0
  290. package/dist/providers/index.d.ts +4 -0
  291. package/dist/providers/index.d.ts.map +1 -1
  292. package/dist/providers/index.js +2 -0
  293. package/dist/providers/index.js.map +1 -1
  294. package/dist/types/agent.d.ts +2 -1
  295. package/dist/types/agent.d.ts.map +1 -1
  296. package/dist/types/ai.d.ts +20 -0
  297. package/dist/types/ai.d.ts.map +1 -1
  298. package/dist/types/errors.d.ts +33 -0
  299. package/dist/types/errors.d.ts.map +1 -1
  300. package/dist/types/errors.js +34 -0
  301. package/dist/types/errors.js.map +1 -1
  302. package/dist/types/flow.d.ts +10 -5
  303. package/dist/types/flow.d.ts.map +1 -1
  304. package/dist/types/history.d.ts +1 -1
  305. package/dist/types/history.d.ts.map +1 -1
  306. package/dist/types/index.d.ts +5 -4
  307. package/dist/types/index.d.ts.map +1 -1
  308. package/dist/types/index.js +1 -1
  309. package/dist/types/index.js.map +1 -1
  310. package/dist/types/persistence.d.ts +43 -2
  311. package/dist/types/persistence.d.ts.map +1 -1
  312. package/dist/types/session.d.ts +8 -0
  313. package/dist/types/session.d.ts.map +1 -1
  314. package/dist/types/signals.d.ts +21 -11
  315. package/dist/types/signals.d.ts.map +1 -1
  316. package/dist/types/template.d.ts +3 -1
  317. package/dist/types/template.d.ts.map +1 -1
  318. package/dist/types/tool.d.ts +4 -4
  319. package/dist/types/tool.d.ts.map +1 -1
  320. package/dist/utils/condition.d.ts +19 -0
  321. package/dist/utils/condition.d.ts.map +1 -1
  322. package/dist/utils/condition.js +84 -15
  323. package/dist/utils/condition.js.map +1 -1
  324. package/dist/utils/session.d.ts +1 -0
  325. package/dist/utils/session.d.ts.map +1 -1
  326. package/dist/utils/session.js +1 -0
  327. package/dist/utils/session.js.map +1 -1
  328. package/docs/README.md +1 -1
  329. package/docs/guides/branching.md +1 -1
  330. package/docs/guides/compaction.md +12 -5
  331. package/docs/guides/conditions.md +15 -2
  332. package/docs/guides/error-handling.md +59 -9
  333. package/docs/guides/instructions.md +2 -2
  334. package/docs/guides/persistence.md +69 -2
  335. package/docs/guides/streaming.md +3 -1
  336. package/docs/migration/README.md +5 -1
  337. package/docs/migration/v2-3-to-v2-4.md +316 -0
  338. package/docs/reference/adapters.md +55 -7
  339. package/docs/reference/branches.md +2 -2
  340. package/docs/reference/create-agent.md +3 -3
  341. package/docs/reference/errors.md +66 -1
  342. package/docs/reference/flow.md +1 -1
  343. package/docs/reference/instruction.md +4 -4
  344. package/docs/reference/providers.md +100 -3
  345. package/docs/reference/signals.md +18 -8
  346. package/docs/reference/step.md +2 -2
  347. package/docs/reference/tool.md +3 -1
  348. package/docs/start/05-go-to-production.md +3 -0
  349. package/package.json +1 -1
  350. package/src/adapters/MemoryAdapter.ts +15 -1
  351. package/src/adapters/MongoAdapter.ts +41 -2
  352. package/src/adapters/OpenSearchAdapter.ts +24 -1
  353. package/src/adapters/PostgreSQLAdapter.ts +45 -5
  354. package/src/adapters/PrismaAdapter.ts +82 -16
  355. package/src/adapters/RedisAdapter.ts +19 -1
  356. package/src/adapters/SQLiteAdapter.ts +47 -5
  357. package/src/core/Agent.ts +70 -85
  358. package/src/core/AutoChainExecutor.ts +27 -57
  359. package/src/core/BranchEvaluator.ts +24 -30
  360. package/src/core/Flow.ts +10 -3
  361. package/src/core/FlowRouter.ts +36 -4
  362. package/src/core/PersistenceManager.ts +79 -10
  363. package/src/core/PromptComposer.ts +25 -12
  364. package/src/core/ResponseGenerationError.ts +56 -0
  365. package/src/core/ResponseModal.ts +241 -1465
  366. package/src/core/ResponsePipeline.ts +492 -662
  367. package/src/core/SessionFinalizer.ts +78 -0
  368. package/src/core/SessionManager.ts +21 -9
  369. package/src/core/SignalCoordinator.ts +263 -0
  370. package/src/core/SignalEvaluator.ts +14 -30
  371. package/src/core/SignalProcessor.ts +1 -4
  372. package/src/core/Step.ts +11 -3
  373. package/src/core/StepLifecycle.ts +139 -0
  374. package/src/core/ToolLoopExecutor.ts +492 -0
  375. package/src/core/ToolManager.ts +2 -1
  376. package/src/index.ts +7 -5
  377. package/src/providers/AnthropicProvider.ts +30 -72
  378. package/src/providers/DeepSeekProvider.ts +74 -586
  379. package/src/providers/GeminiProvider.ts +29 -70
  380. package/src/providers/OpenAICompatibleProvider.ts +738 -0
  381. package/src/providers/OpenAIProvider.ts +29 -602
  382. package/src/providers/OpenRouterProvider.ts +38 -596
  383. package/src/providers/errorClassification.ts +172 -0
  384. package/src/providers/index.ts +13 -0
  385. package/src/types/agent.ts +2 -1
  386. package/src/types/ai.ts +22 -0
  387. package/src/types/errors.ts +57 -0
  388. package/src/types/flow.ts +10 -5
  389. package/src/types/history.ts +1 -2
  390. package/src/types/index.ts +5 -1
  391. package/src/types/persistence.ts +50 -2
  392. package/src/types/session.ts +9 -0
  393. package/src/types/signals.ts +23 -11
  394. package/src/types/template.ts +3 -1
  395. package/src/types/tool.ts +10 -10
  396. package/src/utils/condition.ts +115 -18
  397. package/src/utils/session.ts +2 -0
@@ -31,6 +31,17 @@ class ToolExecutionError extends Error {
31
31
  cause?: Error;
32
32
  }
33
33
  class NotImplementedError extends Error { /* name = "NotImplementedError" */ }
34
+ class ProviderError extends Error {
35
+ code: ProviderErrorCode; // 'rate_limited' | 'overloaded' | 'auth' | 'invalid_request'
36
+ // | 'schema_rejected' | 'timeout' | 'network' | 'unknown'
37
+ provider: string; // e.g. "openai"
38
+ cause?: unknown; // original SDK/HTTP error
39
+ }
40
+ class SessionConflictError extends Error {
41
+ sessionId: string;
42
+ expectedVersion: number;
43
+ actualVersion: number | undefined;
44
+ }
34
45
 
35
46
  // Internal — match by `error.name` (not exported from the package barrel)
36
47
  class DataValidationError extends Error { errors: ValidationError[] }
@@ -53,6 +64,8 @@ class ResponseGenerationError extends Error {
53
64
  | `ToolExecutionError` | A handler throws, all retries fail, or `validateInput` cannot correct invalid args. | `toolId`, `executionContext`, `cause` | Surface a user-friendly message; optionally `agent.dispatch({ goTo: '<recovery-flow>' })`. |
54
65
  | `DataValidationError` | `agent.respond(...)` collects values that violate the declared `schema`. | `errors: ValidationError[]` | Re-prompt for the offending fields, then retry. |
55
66
  | `ResponseGenerationError` | The provider call fails or the response cannot be parsed. | `details.phase`, `details.originalError` | Retry with backoff, fall back to a different provider, or surface a soft failure to the user. |
67
+ | `ProviderError` | A provider call fails terminally — retries and `backupModels` exhausted. Normalized across all vendors. | `code`, `provider`, `cause` (original SDK error) | Match on `code`: backoff for `rate_limited`/`overloaded`, fix credentials for `auth`, fail fast otherwise. Inside a turn it surfaces on `ResponseGenerationError.details.originalError`. |
68
+ | `SessionConflictError` | A session save carries a stale `version` — another writer persisted the session after this one loaded it (concurrent `respond()` calls, parallel webhooks, two tabs). | `sessionId`, `expectedVersion`, `actualVersion` | Reload the session and retry the operation, or surface the conflict. |
56
69
  | `NotImplementedError` | A reserved option is set to a value this version does not support (e.g. `routerMode: 'embedding'` in v2.0). | `message` | Use a supported value. |
57
70
 
58
71
  ## Examples
@@ -87,7 +100,57 @@ try {
87
100
  }
88
101
  ```
89
102
 
90
- ### 2. The format contract in practice
103
+ ### 2. Matching provider failures by normalized code
104
+
105
+ Terminal provider failures throw `ProviderError` with a vendor-agnostic `code`. Inside a turn, the agent wraps it in `ResponseGenerationError` — unwrap via `details.originalError`.
106
+
107
+ ```typescript
108
+ import { ProviderError } from "@falai/agent";
109
+
110
+ function asProviderError(err: unknown): ProviderError | undefined {
111
+ if (err instanceof ProviderError) return err;
112
+ if (err instanceof Error && err.name === "ResponseGenerationError") {
113
+ const original = (err as { details?: { originalError?: unknown } }).details?.originalError;
114
+ if (original instanceof ProviderError) return original;
115
+ }
116
+ return undefined;
117
+ }
118
+
119
+ const providerError = asProviderError(err);
120
+ if (providerError) {
121
+ switch (providerError.code) {
122
+ case "rate_limited":
123
+ case "overloaded":
124
+ return retryWithBackoff(); // transient — wait and retry
125
+ case "auth":
126
+ throw providerError; // config bug — crash loudly
127
+ default:
128
+ log.error({ cause: providerError.cause }, providerError.message);
129
+ return "I'm having trouble reaching the model. Please retry.";
130
+ }
131
+ }
132
+ ```
133
+
134
+ ### 3. Recovering from a session conflict
135
+
136
+ `SessionConflictError` means another writer persisted the session between your load and your save. Reload, then retry.
137
+
138
+ ```typescript
139
+ import { SessionConflictError } from "@falai/agent";
140
+
141
+ try {
142
+ await agent.respond({ history, session });
143
+ } catch (err) {
144
+ const original = (err as { details?: { originalError?: unknown } }).details?.originalError;
145
+ if (err instanceof SessionConflictError || original instanceof SessionConflictError) {
146
+ const fresh = await agent.session.getOrCreate(sessionId);
147
+ return agent.respond({ history, session: fresh });
148
+ }
149
+ throw err;
150
+ }
151
+ ```
152
+
153
+ ### 4. The format contract in practice
91
154
 
92
155
  Every thrown message is parseable. The leading `[<ErrorClass>]` token mirrors the class name, the colon separates `<what>` from `<why>`, and the trailing sentence is `<how to fix>`.
93
156
 
@@ -120,3 +183,5 @@ Tool input validation, permission denials, and missing-tool warnings are reporte
120
183
  - [createAgent](./create-agent.md) — construction-time errors thrown from `new Agent(...)`.
121
184
  - [Tool](./tool.md) — handler return shape and the `ToolExecutionError` triggers.
122
185
  - [Directive](./directive.md) — the validation rules that surface as `FlowConfigurationError`.
186
+ - [Providers](./providers.md) — the retry/backup pipeline that ends in `ProviderError`.
187
+ - [Persistence adapters](./adapters.md) — the optimistic locking that throws `SessionConflictError`.
@@ -77,7 +77,7 @@ class Flow<TContext = unknown, TData = unknown> {
77
77
  | `id` | `string` | no | derived from `title` | Stable identifier. Auto-generated deterministically from the title when omitted. |
78
78
  | `title` | `string` | yes | — | Human-readable name. Shown to the router and used as the default flow id. |
79
79
  | `description` | `string` | no | — | One-line summary surfaced to the router prompt. |
80
- | `when` | `string \| string[]` | no | — | AI-evaluated activation condition(s). Strings only — functions belong on `if`. Multiple strings combine with OR semantics. |
80
+ | `when` | `string \| string[]` | no | — | AI-evaluated activation condition(s). Strings only — functions belong on `if`. Non-`!` strings are OR alternatives; `!` strings are OR exclusions where any match inhibits activation. |
81
81
  | `if` | `(ctx) => boolean \| Promise<boolean>` or array | no | — | Code-evaluated activation condition(s). Free to evaluate. When both are set, `if` runs first; `when` only evaluates if `if` passes. |
82
82
  | `instructions` | `Instruction<TContext, TData>[]` | no | `[]` | Flow-scoped instructions. Apply only while this flow is active. See [Instruction](./instruction.md). |
83
83
  | `tools` | `(string \| Tool)[]` | no | `[]` | Tool ids (resolved via the agent's tool registry) or inline `Tool` objects. Available only while this flow is active. |
@@ -19,7 +19,7 @@ The set of instructions actually rendered into a given turn's prompt is reported
19
19
  interface Instruction<TContext = unknown, TData = unknown> {
20
20
  id?: string;
21
21
  kind?: 'must' | 'never' | 'should'; // default: 'should'
22
- when?: ConditionWhen; // AI-evaluated string(s), OR semantics
22
+ when?: ConditionWhen; // AI strings: positives OR, ! exclusions inhibit
23
23
  if?: ConditionIf<TContext, TData>; // code-evaluated function(s), AND semantics
24
24
  prompt: Template<TContext, TData>;
25
25
  enabled?: boolean; // default: true
@@ -48,7 +48,7 @@ interface AppliedInstruction {
48
48
  |-------|------|----------|---------|-------|
49
49
  | `prompt` | `Template<TContext, TData>` | yes | — | Behavioral text rendered into the prompt under the `## Instructions` section. |
50
50
  | `kind` | `'must' \| 'never' \| 'should'` | no | `'should'` | Severity. `'must'` = absolute do, `'never'` = absolute don't, `'should'` = conditional nudge. |
51
- | `when` | `ConditionWhen` | no | — | AI-evaluated activation string (or array, OR semantics). Functions are not allowed here; use `if`. |
51
+ | `when` | `ConditionWhen` | no | — | AI-evaluated activation string or array. Non-`!` entries are OR alternatives. `!` entries are OR exclusions; any matching exclusion inhibits the instruction. Functions are not allowed here; use `if`. |
52
52
  | `if` | `ConditionIf<TContext, TData>` | no | — | Code-evaluated activation function (or array). Free to evaluate. When both `when` and `if` are set, `if` runs first; `when` is only evaluated if `if` passes. |
53
53
  | `id` | `string` | no | auto | Stable identifier used in `AppliedInstruction.id`. Auto-generated when omitted. |
54
54
  | `enabled` | `boolean` | no | `true` | Set `false` to skip the instruction without removing it from configuration. |
@@ -74,10 +74,10 @@ The same `Instruction` shape attaches at three positions:
74
74
  At prompt-build time the composer renders each eligible instruction as a single bullet:
75
75
 
76
76
  ```
77
- - [<kind>] [<scope-caption>] <prompt> (apply only when: <when-clause> OR <when-clause>)
77
+ - [<kind>] [<scope-caption>] <prompt> (apply only when: <when-clause> OR <when-clause>; do not apply when: <exclusion-clause>)
78
78
  ```
79
79
 
80
- The parenthesized condition is omitted when `when` is not set. Code-evaluated `if` predicates run first; a failing predicate removes the entire bullet before the prompt reaches the model.
80
+ The parenthesized condition is omitted when `when` is not set. If `when` contains only `!` exclusions, the suffix uses only `do not apply when: ...`. Code-evaluated `if` predicates run first; a failing predicate removes the entire bullet before the prompt reaches the model.
81
81
 
82
82
  Scope captions are fixed by where the instruction was declared:
83
83
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: "Providers"
3
- description: "Strategy classes that connect an Agent to Gemini, OpenAI, Anthropic, or OpenRouter."
3
+ description: "Strategy classes that connect an Agent to Gemini, OpenAI, Anthropic, OpenRouter, or DeepSeek — plus the base class for building your own."
4
4
  type: reference
5
5
  order: 10
6
6
  ---
@@ -21,6 +21,32 @@ Providers are the strategy plug between an `Agent` and a model vendor. Every pro
21
21
  | OpenRouter | `OpenRouterProvider` | `OpenRouterProviderOptions` | `openai` (compat) |
22
22
  | DeepSeek | `DeepSeekProvider` | `DeepSeekProviderOptions` | `openai` (compat) |
23
23
 
24
+ ## Capabilities
25
+
26
+ Every provider declares a required `capabilities: ProviderCapabilities` field — five static flags the engine reads to decide how to drive the vendor (e.g., whether structured output is schema-enforced or prompt-instructed). Custom `AiProvider` implementations **must** declare it.
27
+
28
+ ```typescript
29
+ interface ProviderCapabilities {
30
+ supportsTools: boolean; // tool/function calling
31
+ supportsNativeJsonSchema: boolean; // native JSON-schema-enforced output (vs. prompt-based JSON instruction)
32
+ supportsStreaming: boolean; // streaming responses
33
+ supportsStreamingToolCalls: boolean; // tool calls surfaced during streaming
34
+ supportsPromptCaching: boolean; // prompt caching
35
+ }
36
+ ```
37
+
38
+ The five built-ins:
39
+
40
+ | Capability | Gemini | OpenAI | Anthropic | OpenRouter | DeepSeek |
41
+ |------------|--------|--------|-----------|------------|----------|
42
+ | `supportsTools` | ✅ | ✅ | ✅ | ✅ | ✅ |
43
+ | `supportsNativeJsonSchema` | ✅ | ✅ | ❌ | ✅ | ✅ |
44
+ | `supportsStreaming` | ✅ | ✅ | ✅ | ✅ | ✅ |
45
+ | `supportsStreamingToolCalls` | ✅ | ✅ | ✅ | ✅ | ✅ |
46
+ | `supportsPromptCaching` | ❌ | ❌ | ✅ | ❌ | ❌ |
47
+
48
+ The two asymmetries: Anthropic reports `supportsNativeJsonSchema: false` because its JSON output is enforced via a prompt instruction, not a native schema mode — and it is the only built-in that reports `supportsPromptCaching: true`.
49
+
24
50
  ## Use with createAgent
25
51
 
26
52
  `createAgent({ provider })` accepts any class that implements `AiProvider`. Swap providers by changing the constructor; nothing else in your agent has to move.
@@ -250,6 +276,47 @@ const deepseek = new DeepSeekProvider({
250
276
  });
251
277
  ```
252
278
 
279
+ ## Building a custom OpenAI-compatible provider
280
+
281
+ Many vendors (Groq, Together, Fireworks, …) expose OpenAI-compatible chat-completions APIs. Instead of implementing `AiProvider` from scratch, subclass the exported `OpenAICompatibleProvider` base class — it handles message/history building, tool-call parsing, streaming chunks, backup-model fallback, retries, schema passthrough, and normalized `ProviderError` wrapping. `OpenAIProvider`, `OpenRouterProvider`, and `DeepSeekProvider` are themselves thin subclasses.
282
+
283
+ A minimal subclass supplies the configured client, naming, and capabilities:
284
+
285
+ ```typescript
286
+ import OpenAI from "openai";
287
+ import {
288
+ OpenAICompatibleProvider,
289
+ type ProviderCapabilities,
290
+ } from "@falai/agent";
291
+
292
+ export class GroqProvider extends OpenAICompatibleProvider {
293
+ public readonly name = "groq";
294
+ public readonly capabilities: ProviderCapabilities = {
295
+ supportsTools: true,
296
+ supportsNativeJsonSchema: true,
297
+ supportsStreaming: true,
298
+ supportsStreamingToolCalls: true,
299
+ supportsPromptCaching: false,
300
+ };
301
+
302
+ protected readonly logLabel = "GROQ"; // tag in log lines
303
+ protected readonly displayName = "Groq"; // name in retry/error messages
304
+
305
+ constructor(options: { apiKey: string; model: string; backupModels?: string[] }) {
306
+ super({
307
+ client: new OpenAI({
308
+ apiKey: options.apiKey,
309
+ baseURL: "https://api.groq.com/openai/v1",
310
+ }),
311
+ model: options.model,
312
+ backupModels: options.backupModels,
313
+ });
314
+ }
315
+ }
316
+ ```
317
+
318
+ That is a complete, working provider. For genuinely vendor-specific behavior, override the protected hooks — `DeepSeekProvider` is the reference pattern: it overrides `executeStructuredGenerate` (no `responses.parse` API), `structuredResponseFormat` (native `json_schema` enforcement), `configureStreamParams` (usage in stream chunks), and `onStreamDelta` (reasoning content on the delta).
319
+
253
320
  ## Errors
254
321
 
255
322
  All five providers share the same construction-time guards and runtime failure modes.
@@ -259,15 +326,45 @@ All five providers share the same construction-time guards and runtime failure m
259
326
  | `apiKey` is empty or missing | `Error("<vendor> API key is required")` | Thrown from the constructor. |
260
327
  | `model` is empty or missing | `Error("Model is required. ...")` | Thrown from the constructor. |
261
328
  | Vendor returns no text and no tool calls | `Error("No response from <vendor>")` | Surfaces as a `ResponseGenerationError` once it bubbles through the agent. |
262
- | Primary and every backup model fail | The last backup error is rethrown | After exhausting `backupModels`. The agent wraps it in `ResponseGenerationError`. |
329
+ | Primary and every backup model fail | `ProviderError` with a normalized `code` | After exhausting retries and `backupModels`. The agent wraps it in `ResponseGenerationError`. |
263
330
  | Anthropic streaming with `system: undefined` | Vendor 400 | Set `config.system` or rely on history-derived system messages. |
264
331
 
265
332
  The retry/backup logic only kicks in for transient errors: HTTP 429 / 500 / 503 (and 529 for Anthropic), `overloaded`-style codes, or messages containing `overloaded`, `unavailable`, `internal error`, or (OpenRouter only) `capacity`. Other errors fail fast.
266
333
 
334
+ ### `ProviderError`
335
+
336
+ Terminal failures — after retries and backup models are exhausted — throw the exported `ProviderError` with a normalized `code`, so callers handle failures uniformly regardless of which vendor is configured. The original SDK/HTTP error is preserved as `cause`.
337
+
338
+ ```typescript
339
+ import { ProviderError } from "@falai/agent";
340
+
341
+ type ProviderErrorCode =
342
+ | 'rate_limited' // 429-style throttling
343
+ | 'overloaded' // capacity / 503 / 529
344
+ | 'auth' // invalid or missing credentials
345
+ | 'invalid_request' // vendor rejected the request shape
346
+ | 'schema_rejected' // structured-output schema rejected
347
+ | 'timeout' // per-attempt timeout exhausted
348
+ | 'network' // connection-level failure
349
+ | 'unknown'; // anything unclassified
350
+
351
+ try {
352
+ await provider.generateMessage(input);
353
+ } catch (err) {
354
+ if (err instanceof ProviderError) {
355
+ console.error(err.provider, err.code); // e.g. "openai" "rate_limited"
356
+ console.error(err.cause); // original SDK error
357
+ }
358
+ }
359
+ ```
360
+
361
+ When the failure bubbles through `agent.respond(...)`, it is wrapped in `ResponseGenerationError` like every other turn failure — the `ProviderError` is then on `details.originalError`. See [Errors](./errors.md).
362
+
267
363
  ## Related
268
364
 
269
365
  - [Install](../start/01-install.md) — provider signup and env keys
270
366
  - [Architecture](../concepts/architecture.md) — where the provider sits in the engine
271
367
  - [createAgent](./create-agent.md) — the `provider` field
272
368
  - [Persistence adapters](./adapters.md) — the other strategy plug
273
- - [Errors](./errors.md) — `ResponseGenerationError` and friends
369
+ - [Errors](./errors.md) — `ProviderError`, `ResponseGenerationError`, and friends
370
+ - [v2.3 → v2.4 migration](../migration/v2-3-to-v2-4.md) — required `capabilities` and the `ProviderError` change
@@ -88,12 +88,16 @@ interface SignalDirective<TContext = unknown, TData = unknown>
88
88
  replyWith?: string | ((ctx: SignalContext<TContext, TData>) => string);
89
89
  }
90
90
 
91
+ // As reported on the response surface: replyWith already resolved onto reply
92
+ type ResolvedSignalDirective<TContext = unknown, TData = unknown> =
93
+ Directive<TContext, TData> & { stopOtherSignals?: boolean };
94
+
91
95
  interface SignalFiring<TContext = unknown, TData = unknown> {
92
96
  id: string;
93
97
  phase: 'pre' | 'post';
94
98
  reason?: string;
95
99
  extracted?: unknown;
96
- directive?: SignalDirective<TContext, TData>;
100
+ directive?: ResolvedSignalDirective<TContext, TData>;
97
101
  handlerError?: string;
98
102
  durationMs?: number;
99
103
  }
@@ -130,7 +134,7 @@ interface AgentOptions<TContext, TData> {
130
134
  | `id` | `string` | no | auto-generated | Stable identifier within a session. Used for `SignalsState.triggers` keying and on `SignalFiring`. Must be unique across the agent's signals. |
131
135
  | `title` | `string` | no | — | Display title shown in logs and traces. |
132
136
  | `description` | `string` | no | — | Free-text description; rendered into the classifier prompt. |
133
- | `when` | `string \| string[]` | no | — | AI-evaluated condition(s). Entries prefixed with `!` are exclusion conditions rendered under "DO NOT TRIGGER WHEN" (OR semantics — any match inhibits firing). Non-prefixed entries render under "TRIGGER WHEN" (AND semantics — all must match). |
137
+ | `when` | `string \| string[]` | no | — | AI-evaluated condition(s), using the same `ConditionWhen` include/exclude syntax as flows, steps, branches, and instructions. Non-prefixed entries render under "TRIGGER WHEN" (OR semantics — any match can trigger). Entries prefixed with `!` are exclusion conditions rendered under "DO NOT TRIGGER WHEN" (OR semantics — any match inhibits firing). |
134
138
  | `if` | `SignalPredicate \| SignalPredicate[]` | no | — | Code predicate(s). AND semantics. Free to evaluate. Runs before `when`; if any returns `false`, `when` is skipped (no token cost). |
135
139
  | `extract` | `SignalSchema<TExtract>` | no | — | When set, the signal operates in extraction mode. JSON Schema object describing the per-signal `extracted` field merged into the classifier response. The `TExtract` generic carries the resulting type onto `ctx.extracted`. |
136
140
  | `phase` | `'pre' \| 'post' \| 'both'` | yes | — | When the signal evaluates. `'pre'` runs in parallel with routing. `'post'` runs after the LLM call, before persistence. `'both'` evaluates in both phases. |
@@ -185,6 +189,10 @@ Extends [`Directive`](./directive.md). All position fields (`goTo`, `goToStep`,
185
189
 
186
190
  **Post-phase drop rules.** When a signal runs in the post-phase, `appendPrompt`, `injectTools`, and `halt` are dropped with a debug warning — they have no meaning after the LLM call has already completed. Position directives in the post-phase set `session.pendingDirective` for the *next* turn (no mid-turn re-entry).
187
191
 
192
+ ### `ResolvedSignalDirective`
193
+
194
+ The directive shape as reported on the response surface (`SignalFiring.directive`). The signal processor resolves `replyWith` onto `reply` and strips it before firings reach `AgentResponse` — so a `ResolvedSignalDirective` is a plain [`Directive`](./directive.md) plus `stopOtherSignals`, with the reply text (if any) always on `reply`. Exported from the package barrel.
195
+
188
196
  ### `SignalFiring`
189
197
 
190
198
  One entry per signal that fired this turn. Populated in fire order across both phases on `AgentResponse.triggeredSignals` (and on the final chunk of `AgentResponseStreamChunk`). Mirrors the observability framing of `executedSteps` and `appliedInstructions`.
@@ -195,7 +203,7 @@ One entry per signal that fired this turn. Populated in fire order across both p
195
203
  | `phase` | `'pre' \| 'post'` | Phase the signal fired in. |
196
204
  | `reason` | `string \| undefined` | AI rationale, `'code-only'`, or `'unconditional'`. |
197
205
  | `extracted` | `unknown` | Extracted payload when in extraction mode. |
198
- | `directive` | `SignalDirective \| undefined` | The directive returned (or dispatched) by the handler. |
206
+ | `directive` | `ResolvedSignalDirective \| undefined` | The directive returned (or dispatched) by the handler, with `replyWith` already resolved onto `reply`. |
199
207
  | `handlerError` | `string \| undefined` | Error message if the handler threw. The turn continues — handler errors never break a turn. |
200
208
  | `durationMs` | `number \| undefined` | Wall-clock duration of the handler invocation. |
201
209
 
@@ -207,11 +215,13 @@ Persisted on `session.signals`. Adapters preserve this shape bit-identical.
207
215
 
208
216
  | `SignalTriggerState` field | Type | Notes |
209
217
  |----------------------------|------|-------|
210
- | `firstTriggeredAt` | `Date` | When this signal first fired in the session. Never updated on subsequent fires. |
211
- | `lastTriggeredAt` | `Date` | When this signal last fired. Drives `cooldown` arithmetic. |
212
- | `count` | `number` | Total fires for this signal in this session. Monotonically increasing. |
213
- | `lastReason` | `string \| undefined` | The `reason` from the most recent firing. |
214
- | `lastPhase` | `'pre' \| 'post' \| undefined` | The phase of the most recent firing. |
218
+ | `firstTriggeredAt` | `Date` | When this signal first completed its handler successfully in the session. Never updated on subsequent successful triggers. |
219
+ | `lastTriggeredAt` | `Date` | When this signal last completed its handler successfully. Drives `cooldown` arithmetic. |
220
+ | `count` | `number` | Total successful handler completions for this signal in this session. Monotonically increasing. |
221
+ | `lastReason` | `string \| undefined` | The `reason` from the most recent successful trigger. |
222
+ | `lastPhase` | `'pre' \| 'post' \| undefined` | The phase of the most recent successful trigger. |
223
+
224
+ Handler errors are still surfaced on `SignalFiring.handlerError`, but they do not update `SignalsState.triggers`. A `behavior: 'once'` or cooldown signal can therefore retry after an app-side handler failure.
215
225
 
216
226
  ### `signalBatchSize`
217
227
 
@@ -96,7 +96,7 @@ interface StepLifecycleHooks<TContext = unknown, TData = unknown> {
96
96
  | `auto` | `boolean` | no | `false` | When `true`, the step runs without an LLM call — only `onEnter`, `prepare`, and `branches` execute. Cannot coexist with `prompt`, `collect`, `tools`, or `finalize`. Counts against `maxAutoStepsPerTurn`. |
97
97
  | `collect` | `(keyof TData)[]` | no | `[]` | Schema field keys this step is responsible for extracting from the user message. Every key must exist in the agent's `schema`. The engine skips the step automatically when every listed key is already present in `session.data` (pre-extraction). |
98
98
  | `requires` | `(keyof TData)[]` | no | `[]` | Prerequisite field keys. The engine refuses to enter the step until every key is present in `session.data`. When fields covered by `requires` are read inside `branches[].if` predicates, they are guaranteed to be defined. |
99
- | `when` | `string \| string[]` | no | — | AI-evaluated activation strings (OR semantics). Evaluated by the LLM at routing time. Functions are not allowed here — the constructor throws `FlowConfigurationError` if a function is found. |
99
+ | `when` | `string \| string[]` | no | — | AI-evaluated activation strings. Non-`!` strings are OR alternatives; `!` strings are OR exclusions where any match inhibits activation. Evaluated by the LLM at routing time. Functions are not allowed here — the constructor throws `FlowConfigurationError` if a function is found. |
100
100
  | `if` | `(ctx) => boolean \| Promise<boolean>` or array | no | — | Code-evaluated activation predicates (AND semantics). Evaluated locally — no LLM cost. When both `when` and `if` are set, `if` runs first; `when` is only evaluated if every `if` predicate passes. |
101
101
  | `skip` | `(ctx) => boolean \| Promise<boolean>` or array | no | — | Code-evaluated skip predicates (OR semantics). When any predicate returns `true`, the step is bypassed. Only code predicates — no AI strings. |
102
102
  | `tools` | `(string \| Tool<TContext, TData>)[]` | no | `[]` | Tools available during this step. Strings are resolved against the agent's tool registry; objects are inline tools. Stacked on top of agent and flow scopes. |
@@ -130,7 +130,7 @@ or the full `Directive` surface (`appendPrompt`,
130
130
 
131
131
  For one step, the engine walks this sequence per turn:
132
132
 
133
- 1. Evaluate `if` (code, AND) and `when` (AI, OR) — fails skip the step entirely.
133
+ 1. Evaluate `if` (code, AND) and `when` (AI: positive OR, `!` exclusions inhibit) — fails skip the step entirely.
134
134
  2. Evaluate `skip` (code, OR) — true means bypass and fall through.
135
135
  3. Check `requires` — refuse entry if any required field is missing.
136
136
  4. Run `onEnter`, then `prepare` / `hooks.prepare`. May emit a `Directive` (pre-LLM fields honored).
@@ -13,10 +13,12 @@ A `Tool` is a function the agent can invoke during a turn. v2 unifies tools into
13
13
 
14
14
  `Tool.id` is the sole identifier.
15
15
 
16
+ Since v2.4 the generic defaults are `unknown` (previously `any`) on `Tool`, `ToolContext`, `ToolResult`, and `ToolHandler`. Pass explicit type parameters — or let inference flow from `createAgent`'s `schema` — to get typed `ctx.data` and `ctx.context`; untyped tool code that relied on implicit `any` needs explicit generics or a type guard.
17
+
16
18
  ## Signature
17
19
 
18
20
  ```typescript
19
- interface Tool<TContext = any, TData = any, TResult = any> {
21
+ interface Tool<TContext = unknown, TData = unknown, TResult = unknown> {
20
22
  // Identity
21
23
  id: string;
22
24
  description?: string;
@@ -54,6 +54,7 @@ model AgentSession {
54
54
  collectedData Json?
55
55
  pendingDirective Json?
56
56
  signals Json?
57
+ version Int?
57
58
  messageCount Int @default(0)
58
59
  lastMessageAt DateTime?
59
60
  completedAt DateTime?
@@ -115,6 +116,8 @@ const response = await agent.respond({
115
116
 
116
117
  Unknown ids start fresh against that id; there is no "not found" error path.
117
118
 
119
+ The `version Int?` column enables optimistic locking: when two writers race on one session (parallel webhooks, a double-send), the stale save throws `SessionConflictError` instead of silently overwriting — reload the session and retry. See [Concurrent writers](../guides/persistence.md#concurrent-writers-optimistic-locking).
120
+
118
121
  For the full schema migration story (renaming `pending_transition` to `pendingDirective`, adding the `signals` column) see [persistence adapters reference](../reference/adapters.md#prismaadapter).
119
122
 
120
123
  ## 2. Stream responses
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@falai/agent",
3
- "version": "2.2.3",
3
+ "version": "2.4.1",
4
4
  "description": "Conversational state engine for TypeScript where the AI understands, but the code is in control",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -11,8 +11,10 @@ import type {
11
11
  SessionData,
12
12
  SessionRepository,
13
13
  SessionStatus,
14
+ SessionUpdateOptions,
14
15
  CreateSessionData,
15
16
  } from "../types";
17
+ import { SessionConflictError } from "../types/errors";
16
18
  import { cloneDeep } from "../utils/clone";
17
19
  import { createSessionId } from "../utils";
18
20
 
@@ -92,6 +94,7 @@ class MemorySessionRepository<TData = Record<string, unknown>>
92
94
  id,
93
95
  status: data.status || "active",
94
96
  messageCount: data.messageCount || 0,
97
+ version: data.version ?? 1,
95
98
  createdAt: now,
96
99
  updatedAt: now,
97
100
  };
@@ -126,14 +129,25 @@ class MemorySessionRepository<TData = Record<string, unknown>>
126
129
 
127
130
  async update(
128
131
  id: string,
129
- data: Partial<Omit<SessionData<TData>, "id" | "createdAt">>
132
+ data: Partial<Omit<SessionData<TData>, "id" | "createdAt">>,
133
+ options?: SessionUpdateOptions
130
134
  ): Promise<SessionData<TData> | null> {
131
135
  const existing = this.sessions.get(id);
132
136
  if (!existing) return null;
133
137
 
138
+ // Compare-and-swap: rows without a stored version (pre-2.4) are accepted
139
+ if (
140
+ options?.expectedVersion !== undefined &&
141
+ existing.version !== undefined &&
142
+ existing.version !== options.expectedVersion
143
+ ) {
144
+ throw new SessionConflictError(id, options.expectedVersion, existing.version);
145
+ }
146
+
134
147
  const updated: SessionData<TData> = {
135
148
  ...existing,
136
149
  ...data,
150
+ version: (existing.version ?? options?.expectedVersion ?? 0) + 1,
137
151
  updatedAt: new Date(),
138
152
  };
139
153
 
@@ -12,7 +12,9 @@ import type {
12
12
  SessionStatus,
13
13
  CollectedStateData,
14
14
  CreateSessionData,
15
+ SessionUpdateOptions,
15
16
  } from "../types";
17
+ import { SessionConflictError } from "../types/errors";
16
18
  import { createSessionId } from "../utils";
17
19
 
18
20
  /**
@@ -144,6 +146,7 @@ class MongoSessionRepository<TData = Record<string, unknown>>
144
146
  createSessionId(),
145
147
  status: data.status || "active",
146
148
  messageCount: data.messageCount || 0,
149
+ version: data.version ?? 1,
147
150
  createdAt: now,
148
151
  updatedAt: now,
149
152
  };
@@ -173,11 +176,47 @@ class MongoSessionRepository<TData = Record<string, unknown>>
173
176
 
174
177
  async update(
175
178
  id: string,
176
- data: Partial<Omit<SessionData<TData>, "id" | "createdAt">>
179
+ data: Partial<Omit<SessionData<TData>, "id" | "createdAt">>,
180
+ options?: SessionUpdateOptions
177
181
  ): Promise<SessionData<TData> | null> {
182
+ if (options?.expectedVersion !== undefined) {
183
+ const expectedVersion = options.expectedVersion;
184
+
185
+ // Compare-and-swap via the filter: docs without a stored version
186
+ // (pre-2.4) are accepted and adopt expectedVersion as their base
187
+ const result = await this.collection.updateOne(
188
+ {
189
+ id,
190
+ $or: [
191
+ { version: { $exists: false } },
192
+ { version: null },
193
+ { version: expectedVersion },
194
+ ],
195
+ },
196
+ { $set: { ...data, version: expectedVersion + 1, updatedAt: new Date() } }
197
+ );
198
+
199
+ if (result.matchedCount === 0) {
200
+ const existing = await this.collection.findOne({ id });
201
+ if (!existing) return null;
202
+ throw new SessionConflictError(id, expectedVersion, existing.version);
203
+ }
204
+
205
+ return await this.findById(id);
206
+ }
207
+
208
+ const existing = await this.collection.findOne({ id });
209
+ if (!existing) return null;
210
+
178
211
  const result = await this.collection.updateOne(
179
212
  { id },
180
- { $set: { ...data, updatedAt: new Date() } }
213
+ {
214
+ $set: {
215
+ ...data,
216
+ version: (existing.version ?? 0) + 1,
217
+ updatedAt: new Date(),
218
+ },
219
+ }
181
220
  );
182
221
 
183
222
  if (result.matchedCount === 0) return null;
@@ -42,7 +42,9 @@ import type {
42
42
  MessageData,
43
43
  CollectedStateData,
44
44
  CreateSessionData,
45
+ SessionUpdateOptions,
45
46
  } from "../types";
47
+ import { SessionConflictError } from "../types/errors";
46
48
 
47
49
  /**
48
50
  * OpenSearch Client interface (minimal typing for the official client)
@@ -200,6 +202,7 @@ export class OpenSearchAdapter<TData = Record<string, unknown>> implements Persi
200
202
  pendingDirective: { type: "object", enabled: false },
201
203
  signals: { type: "object", enabled: false },
202
204
  messageCount: { type: "integer" },
205
+ version: { type: "integer" },
203
206
  createdAt: { type: "date" },
204
207
  updatedAt: { type: "date" },
205
208
  lastMessageAt: { type: "date" },
@@ -266,6 +269,7 @@ class OpenSearchSessionRepository<TData = Record<string, unknown>>
266
269
  id,
267
270
  status: data.status || "active",
268
271
  messageCount: data.messageCount || 0,
272
+ version: data.version ?? 1,
269
273
  createdAt: now,
270
274
  updatedAt: now,
271
275
  };
@@ -341,10 +345,28 @@ class OpenSearchSessionRepository<TData = Record<string, unknown>>
341
345
 
342
346
  async update(
343
347
  id: string,
344
- updates: Partial<Omit<SessionData<TData>, "id" | "createdAt">>
348
+ updates: Partial<Omit<SessionData<TData>, "id" | "createdAt">>,
349
+ options?: SessionUpdateOptions
345
350
  ): Promise<SessionData<TData> | null> {
351
+ // Check-then-set on the stored document — not fully atomic across writers
352
+ const existing = await this.findById(id);
353
+ if (!existing) return null;
354
+
355
+ if (
356
+ options?.expectedVersion !== undefined &&
357
+ existing.version !== undefined &&
358
+ existing.version !== options.expectedVersion
359
+ ) {
360
+ throw new SessionConflictError(
361
+ id,
362
+ options.expectedVersion,
363
+ existing.version
364
+ );
365
+ }
366
+
346
367
  const doc: Record<string, unknown> = {
347
368
  ...updates,
369
+ version: (existing.version ?? options?.expectedVersion ?? 0) + 1,
348
370
  updatedAt: new Date().toISOString(),
349
371
  };
350
372
 
@@ -485,6 +507,7 @@ class OpenSearchSessionRepository<TData = Record<string, unknown>>
485
507
  currentStep: doc.currentStep as string | undefined,
486
508
  collectedData: doc.collectedData as CollectedStateData<TData> | undefined,
487
509
  messageCount: (doc.messageCount as number) || 0,
510
+ version: (doc.version as number | null) ?? undefined,
488
511
  createdAt: new Date(doc.createdAt as string),
489
512
  updatedAt: new Date(doc.updatedAt as string),
490
513
  lastMessageAt: doc.lastMessageAt