@falai/agent 2.6.0 → 2.7.0

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 (320) hide show
  1. package/README.md +1 -1
  2. package/dist/adapters/MemoryAdapter.js +29 -33
  3. package/dist/adapters/MemoryAdapter.js.map +1 -1
  4. package/dist/adapters/PostgreSQLAdapter.d.ts.map +1 -1
  5. package/dist/adapters/PostgreSQLAdapter.js +16 -11
  6. package/dist/adapters/PostgreSQLAdapter.js.map +1 -1
  7. package/dist/adapters/RedisAdapter.d.ts +1 -0
  8. package/dist/adapters/RedisAdapter.d.ts.map +1 -1
  9. package/dist/adapters/RedisAdapter.js +75 -25
  10. package/dist/adapters/RedisAdapter.js.map +1 -1
  11. package/dist/adapters/SQLiteAdapter.d.ts.map +1 -1
  12. package/dist/adapters/SQLiteAdapter.js +7 -29
  13. package/dist/adapters/SQLiteAdapter.js.map +1 -1
  14. package/dist/adapters/sessionRow.d.ts +22 -0
  15. package/dist/adapters/sessionRow.d.ts.map +1 -0
  16. package/dist/adapters/sessionRow.js +48 -0
  17. package/dist/adapters/sessionRow.js.map +1 -0
  18. package/dist/cjs/adapters/MemoryAdapter.js +29 -33
  19. package/dist/cjs/adapters/MemoryAdapter.js.map +1 -1
  20. package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +1 -1
  21. package/dist/cjs/adapters/PostgreSQLAdapter.js +16 -11
  22. package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -1
  23. package/dist/cjs/adapters/RedisAdapter.d.ts +1 -0
  24. package/dist/cjs/adapters/RedisAdapter.d.ts.map +1 -1
  25. package/dist/cjs/adapters/RedisAdapter.js +75 -25
  26. package/dist/cjs/adapters/RedisAdapter.js.map +1 -1
  27. package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +1 -1
  28. package/dist/cjs/adapters/SQLiteAdapter.js +7 -29
  29. package/dist/cjs/adapters/SQLiteAdapter.js.map +1 -1
  30. package/dist/cjs/adapters/sessionRow.d.ts +22 -0
  31. package/dist/cjs/adapters/sessionRow.d.ts.map +1 -0
  32. package/dist/cjs/adapters/sessionRow.js +52 -0
  33. package/dist/cjs/adapters/sessionRow.js.map +1 -0
  34. package/dist/cjs/core/Agent.d.ts +8 -0
  35. package/dist/cjs/core/Agent.d.ts.map +1 -1
  36. package/dist/cjs/core/Agent.js +43 -3
  37. package/dist/cjs/core/Agent.js.map +1 -1
  38. package/dist/cjs/core/AutoChainExecutor.d.ts +8 -18
  39. package/dist/cjs/core/AutoChainExecutor.d.ts.map +1 -1
  40. package/dist/cjs/core/AutoChainExecutor.js +23 -26
  41. package/dist/cjs/core/AutoChainExecutor.js.map +1 -1
  42. package/dist/cjs/core/CompactionEngine.d.ts +14 -1
  43. package/dist/cjs/core/CompactionEngine.d.ts.map +1 -1
  44. package/dist/cjs/core/CompactionEngine.js +30 -6
  45. package/dist/cjs/core/CompactionEngine.js.map +1 -1
  46. package/dist/cjs/core/FlowRouter.d.ts.map +1 -1
  47. package/dist/cjs/core/FlowRouter.js +32 -10
  48. package/dist/cjs/core/FlowRouter.js.map +1 -1
  49. package/dist/cjs/core/ResponseGenerationError.d.ts.map +1 -1
  50. package/dist/cjs/core/ResponseGenerationError.js +3 -5
  51. package/dist/cjs/core/ResponseGenerationError.js.map +1 -1
  52. package/dist/cjs/core/ResponseModal.d.ts +29 -0
  53. package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
  54. package/dist/cjs/core/ResponseModal.js +141 -18
  55. package/dist/cjs/core/ResponseModal.js.map +1 -1
  56. package/dist/cjs/core/ResponsePipeline.d.ts +36 -6
  57. package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -1
  58. package/dist/cjs/core/ResponsePipeline.js +208 -73
  59. package/dist/cjs/core/ResponsePipeline.js.map +1 -1
  60. package/dist/cjs/core/SessionFinalizer.d.ts.map +1 -1
  61. package/dist/cjs/core/SessionFinalizer.js +30 -3
  62. package/dist/cjs/core/SessionFinalizer.js.map +1 -1
  63. package/dist/cjs/core/SessionManager.d.ts +10 -1
  64. package/dist/cjs/core/SessionManager.d.ts.map +1 -1
  65. package/dist/cjs/core/SessionManager.js +43 -16
  66. package/dist/cjs/core/SessionManager.js.map +1 -1
  67. package/dist/cjs/core/SignalProcessor.d.ts.map +1 -1
  68. package/dist/cjs/core/SignalProcessor.js +5 -77
  69. package/dist/cjs/core/SignalProcessor.js.map +1 -1
  70. package/dist/cjs/core/Step.d.ts.map +1 -1
  71. package/dist/cjs/core/Step.js +50 -2
  72. package/dist/cjs/core/Step.js.map +1 -1
  73. package/dist/cjs/core/StepLifecycle.d.ts +16 -6
  74. package/dist/cjs/core/StepLifecycle.d.ts.map +1 -1
  75. package/dist/cjs/core/StepLifecycle.js +97 -14
  76. package/dist/cjs/core/StepLifecycle.js.map +1 -1
  77. package/dist/cjs/core/StreamingToolExecutor.d.ts.map +1 -1
  78. package/dist/cjs/core/StreamingToolExecutor.js +28 -4
  79. package/dist/cjs/core/StreamingToolExecutor.js.map +1 -1
  80. package/dist/cjs/core/ToolLoopExecutor.d.ts +5 -1
  81. package/dist/cjs/core/ToolLoopExecutor.d.ts.map +1 -1
  82. package/dist/cjs/core/ToolLoopExecutor.js +156 -64
  83. package/dist/cjs/core/ToolLoopExecutor.js.map +1 -1
  84. package/dist/cjs/core/ToolManager.d.ts +1 -1
  85. package/dist/cjs/core/ToolManager.d.ts.map +1 -1
  86. package/dist/cjs/core/ToolManager.js +40 -17
  87. package/dist/cjs/core/ToolManager.js.map +1 -1
  88. package/dist/cjs/core/flow-namespace.d.ts +15 -0
  89. package/dist/cjs/core/flow-namespace.d.ts.map +1 -1
  90. package/dist/cjs/core/flow-namespace.js +22 -0
  91. package/dist/cjs/core/flow-namespace.js.map +1 -1
  92. package/dist/cjs/index.d.ts +4 -1
  93. package/dist/cjs/index.d.ts.map +1 -1
  94. package/dist/cjs/index.js +5 -2
  95. package/dist/cjs/index.js.map +1 -1
  96. package/dist/cjs/providers/AnthropicProvider.d.ts +10 -3
  97. package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
  98. package/dist/cjs/providers/AnthropicProvider.js +54 -73
  99. package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
  100. package/dist/cjs/providers/GeminiProvider.d.ts +9 -3
  101. package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
  102. package/dist/cjs/providers/GeminiProvider.js +24 -68
  103. package/dist/cjs/providers/GeminiProvider.js.map +1 -1
  104. package/dist/cjs/providers/OpenAICompatibleProvider.d.ts +9 -0
  105. package/dist/cjs/providers/OpenAICompatibleProvider.d.ts.map +1 -1
  106. package/dist/cjs/providers/OpenAICompatibleProvider.js +101 -74
  107. package/dist/cjs/providers/OpenAICompatibleProvider.js.map +1 -1
  108. package/dist/cjs/providers/OpenAIProvider.d.ts +1 -1
  109. package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
  110. package/dist/cjs/providers/OpenAIProvider.js +1 -1
  111. package/dist/cjs/providers/errorClassification.d.ts +7 -0
  112. package/dist/cjs/providers/errorClassification.d.ts.map +1 -1
  113. package/dist/cjs/providers/errorClassification.js +27 -0
  114. package/dist/cjs/providers/errorClassification.js.map +1 -1
  115. package/dist/cjs/types/agent.d.ts +35 -0
  116. package/dist/cjs/types/agent.d.ts.map +1 -1
  117. package/dist/cjs/types/flow.d.ts +49 -32
  118. package/dist/cjs/types/flow.d.ts.map +1 -1
  119. package/dist/cjs/types/index.d.ts +1 -1
  120. package/dist/cjs/types/index.d.ts.map +1 -1
  121. package/dist/cjs/types/index.js.map +1 -1
  122. package/dist/cjs/types/tool.d.ts +2 -0
  123. package/dist/cjs/types/tool.d.ts.map +1 -1
  124. package/dist/cjs/types/tool.js.map +1 -1
  125. package/dist/cjs/utils/index.d.ts +4 -4
  126. package/dist/cjs/utils/index.d.ts.map +1 -1
  127. package/dist/cjs/utils/index.js +10 -1
  128. package/dist/cjs/utils/index.js.map +1 -1
  129. package/dist/cjs/utils/retry.d.ts +99 -1
  130. package/dist/cjs/utils/retry.d.ts.map +1 -1
  131. package/dist/cjs/utils/retry.js +140 -3
  132. package/dist/cjs/utils/retry.js.map +1 -1
  133. package/dist/cjs/utils/serialize.d.ts +17 -0
  134. package/dist/cjs/utils/serialize.d.ts.map +1 -1
  135. package/dist/cjs/utils/serialize.js +33 -0
  136. package/dist/cjs/utils/serialize.js.map +1 -1
  137. package/dist/cjs/utils/session.d.ts +21 -1
  138. package/dist/cjs/utils/session.d.ts.map +1 -1
  139. package/dist/cjs/utils/session.js +36 -4
  140. package/dist/cjs/utils/session.js.map +1 -1
  141. package/dist/core/Agent.d.ts +8 -0
  142. package/dist/core/Agent.d.ts.map +1 -1
  143. package/dist/core/Agent.js +43 -3
  144. package/dist/core/Agent.js.map +1 -1
  145. package/dist/core/AutoChainExecutor.d.ts +8 -18
  146. package/dist/core/AutoChainExecutor.d.ts.map +1 -1
  147. package/dist/core/AutoChainExecutor.js +23 -26
  148. package/dist/core/AutoChainExecutor.js.map +1 -1
  149. package/dist/core/CompactionEngine.d.ts +14 -1
  150. package/dist/core/CompactionEngine.d.ts.map +1 -1
  151. package/dist/core/CompactionEngine.js +30 -6
  152. package/dist/core/CompactionEngine.js.map +1 -1
  153. package/dist/core/FlowRouter.d.ts.map +1 -1
  154. package/dist/core/FlowRouter.js +32 -10
  155. package/dist/core/FlowRouter.js.map +1 -1
  156. package/dist/core/ResponseGenerationError.d.ts.map +1 -1
  157. package/dist/core/ResponseGenerationError.js +3 -5
  158. package/dist/core/ResponseGenerationError.js.map +1 -1
  159. package/dist/core/ResponseModal.d.ts +29 -0
  160. package/dist/core/ResponseModal.d.ts.map +1 -1
  161. package/dist/core/ResponseModal.js +142 -19
  162. package/dist/core/ResponseModal.js.map +1 -1
  163. package/dist/core/ResponsePipeline.d.ts +36 -6
  164. package/dist/core/ResponsePipeline.d.ts.map +1 -1
  165. package/dist/core/ResponsePipeline.js +208 -73
  166. package/dist/core/ResponsePipeline.js.map +1 -1
  167. package/dist/core/SessionFinalizer.d.ts.map +1 -1
  168. package/dist/core/SessionFinalizer.js +31 -4
  169. package/dist/core/SessionFinalizer.js.map +1 -1
  170. package/dist/core/SessionManager.d.ts +10 -1
  171. package/dist/core/SessionManager.d.ts.map +1 -1
  172. package/dist/core/SessionManager.js +44 -17
  173. package/dist/core/SessionManager.js.map +1 -1
  174. package/dist/core/SignalProcessor.d.ts.map +1 -1
  175. package/dist/core/SignalProcessor.js +5 -77
  176. package/dist/core/SignalProcessor.js.map +1 -1
  177. package/dist/core/Step.d.ts.map +1 -1
  178. package/dist/core/Step.js +50 -2
  179. package/dist/core/Step.js.map +1 -1
  180. package/dist/core/StepLifecycle.d.ts +16 -6
  181. package/dist/core/StepLifecycle.d.ts.map +1 -1
  182. package/dist/core/StepLifecycle.js +97 -14
  183. package/dist/core/StepLifecycle.js.map +1 -1
  184. package/dist/core/StreamingToolExecutor.d.ts.map +1 -1
  185. package/dist/core/StreamingToolExecutor.js +28 -4
  186. package/dist/core/StreamingToolExecutor.js.map +1 -1
  187. package/dist/core/ToolLoopExecutor.d.ts +5 -1
  188. package/dist/core/ToolLoopExecutor.d.ts.map +1 -1
  189. package/dist/core/ToolLoopExecutor.js +156 -64
  190. package/dist/core/ToolLoopExecutor.js.map +1 -1
  191. package/dist/core/ToolManager.d.ts +1 -1
  192. package/dist/core/ToolManager.d.ts.map +1 -1
  193. package/dist/core/ToolManager.js +41 -18
  194. package/dist/core/ToolManager.js.map +1 -1
  195. package/dist/core/flow-namespace.d.ts +15 -0
  196. package/dist/core/flow-namespace.d.ts.map +1 -1
  197. package/dist/core/flow-namespace.js +22 -0
  198. package/dist/core/flow-namespace.js.map +1 -1
  199. package/dist/index.d.ts +4 -1
  200. package/dist/index.d.ts.map +1 -1
  201. package/dist/index.js +2 -1
  202. package/dist/index.js.map +1 -1
  203. package/dist/providers/AnthropicProvider.d.ts +10 -3
  204. package/dist/providers/AnthropicProvider.d.ts.map +1 -1
  205. package/dist/providers/AnthropicProvider.js +56 -75
  206. package/dist/providers/AnthropicProvider.js.map +1 -1
  207. package/dist/providers/GeminiProvider.d.ts +9 -3
  208. package/dist/providers/GeminiProvider.d.ts.map +1 -1
  209. package/dist/providers/GeminiProvider.js +26 -70
  210. package/dist/providers/GeminiProvider.js.map +1 -1
  211. package/dist/providers/OpenAICompatibleProvider.d.ts +9 -0
  212. package/dist/providers/OpenAICompatibleProvider.d.ts.map +1 -1
  213. package/dist/providers/OpenAICompatibleProvider.js +103 -76
  214. package/dist/providers/OpenAICompatibleProvider.js.map +1 -1
  215. package/dist/providers/OpenAIProvider.d.ts +1 -1
  216. package/dist/providers/OpenAIProvider.d.ts.map +1 -1
  217. package/dist/providers/OpenAIProvider.js +1 -1
  218. package/dist/providers/errorClassification.d.ts +7 -0
  219. package/dist/providers/errorClassification.d.ts.map +1 -1
  220. package/dist/providers/errorClassification.js +26 -0
  221. package/dist/providers/errorClassification.js.map +1 -1
  222. package/dist/types/agent.d.ts +35 -0
  223. package/dist/types/agent.d.ts.map +1 -1
  224. package/dist/types/flow.d.ts +49 -32
  225. package/dist/types/flow.d.ts.map +1 -1
  226. package/dist/types/index.d.ts +1 -1
  227. package/dist/types/index.d.ts.map +1 -1
  228. package/dist/types/index.js.map +1 -1
  229. package/dist/types/tool.d.ts +2 -0
  230. package/dist/types/tool.d.ts.map +1 -1
  231. package/dist/types/tool.js.map +1 -1
  232. package/dist/utils/index.d.ts +4 -4
  233. package/dist/utils/index.d.ts.map +1 -1
  234. package/dist/utils/index.js +3 -3
  235. package/dist/utils/index.js.map +1 -1
  236. package/dist/utils/retry.d.ts +99 -1
  237. package/dist/utils/retry.d.ts.map +1 -1
  238. package/dist/utils/retry.js +137 -3
  239. package/dist/utils/retry.js.map +1 -1
  240. package/dist/utils/serialize.d.ts +17 -0
  241. package/dist/utils/serialize.d.ts.map +1 -1
  242. package/dist/utils/serialize.js +31 -0
  243. package/dist/utils/serialize.js.map +1 -1
  244. package/dist/utils/session.d.ts +21 -1
  245. package/dist/utils/session.d.ts.map +1 -1
  246. package/dist/utils/session.js +33 -4
  247. package/dist/utils/session.js.map +1 -1
  248. package/docs/concepts/architecture.md +3 -3
  249. package/docs/concepts/directives.md +1 -1
  250. package/docs/guides/error-handling.md +46 -45
  251. package/docs/guides/flow-control.md +8 -1
  252. package/docs/guides/instructions.md +15 -6
  253. package/docs/guides/persistence.md +12 -5
  254. package/docs/guides/streaming.md +10 -0
  255. package/docs/migration/README.md +4 -0
  256. package/docs/migration/v2-3-to-v2-4.md +4 -0
  257. package/docs/migration/v2-6-to-v2-7.md +246 -0
  258. package/docs/reference/adapters.md +15 -1
  259. package/docs/reference/create-agent.md +30 -0
  260. package/docs/reference/directive.md +1 -1
  261. package/docs/reference/errors.md +29 -31
  262. package/docs/reference/providers.md +23 -19
  263. package/docs/reference/step.md +28 -21
  264. package/docs/reference/tool.md +14 -5
  265. package/docs/start/02-first-agent.md +8 -4
  266. package/docs/start/03-collect-data.md +19 -10
  267. package/examples/01-quickstart.ts +1 -1
  268. package/examples/02-data-extraction.ts +1 -1
  269. package/examples/03-tools.ts +1 -1
  270. package/examples/04-instructions.ts +1 -1
  271. package/examples/05-branching.ts +1 -1
  272. package/examples/06-flow-control.ts +3 -3
  273. package/examples/07-streaming.ts +1 -1
  274. package/examples/08-persistence.ts +1 -1
  275. package/examples/09-signals.ts +1 -1
  276. package/package.json +2 -2
  277. package/src/adapters/MemoryAdapter.ts +29 -33
  278. package/src/adapters/PostgreSQLAdapter.ts +23 -18
  279. package/src/adapters/RedisAdapter.ts +81 -34
  280. package/src/adapters/SQLiteAdapter.ts +10 -31
  281. package/src/adapters/sessionRow.ts +57 -0
  282. package/src/core/Agent.ts +51 -3
  283. package/src/core/AutoChainExecutor.ts +37 -50
  284. package/src/core/CompactionEngine.ts +40 -6
  285. package/src/core/FlowRouter.ts +38 -12
  286. package/src/core/ResponseGenerationError.ts +3 -6
  287. package/src/core/ResponseModal.ts +185 -19
  288. package/src/core/ResponsePipeline.ts +259 -85
  289. package/src/core/SessionFinalizer.ts +35 -5
  290. package/src/core/SessionManager.ts +55 -21
  291. package/src/core/SignalProcessor.ts +5 -85
  292. package/src/core/Step.ts +79 -2
  293. package/src/core/StepLifecycle.ts +125 -22
  294. package/src/core/StreamingToolExecutor.ts +31 -6
  295. package/src/core/ToolLoopExecutor.ts +201 -75
  296. package/src/core/ToolManager.ts +45 -18
  297. package/src/core/flow-namespace.ts +29 -0
  298. package/src/index.ts +4 -1
  299. package/src/providers/AnthropicProvider.ts +83 -131
  300. package/src/providers/GeminiProvider.ts +42 -116
  301. package/src/providers/OpenAICompatibleProvider.ts +132 -133
  302. package/src/providers/OpenAIProvider.ts +2 -2
  303. package/src/providers/errorClassification.ts +34 -0
  304. package/src/types/agent.ts +34 -0
  305. package/src/types/flow.ts +37 -39
  306. package/src/types/index.ts +1 -0
  307. package/src/types/tool.ts +2 -0
  308. package/src/utils/index.ts +22 -3
  309. package/src/utils/retry.ts +251 -2
  310. package/src/utils/serialize.ts +38 -0
  311. package/src/utils/session.ts +41 -4
  312. package/dist/cjs/core/DirectiveBus.d.ts +0 -88
  313. package/dist/cjs/core/DirectiveBus.d.ts.map +0 -1
  314. package/dist/cjs/core/DirectiveBus.js +0 -196
  315. package/dist/cjs/core/DirectiveBus.js.map +0 -1
  316. package/dist/core/DirectiveBus.d.ts +0 -88
  317. package/dist/core/DirectiveBus.d.ts.map +0 -1
  318. package/dist/core/DirectiveBus.js +0 -192
  319. package/dist/core/DirectiveBus.js.map +0 -1
  320. package/src/core/DirectiveBus.ts +0 -248
@@ -18,7 +18,7 @@ import type {
18
18
  } from "../types";
19
19
  import type { Directive } from "../types/flow";
20
20
  import { ToolScope } from "../types";
21
- import { logger } from "../utils";
21
+ import { logger, isToolResultLike, extractResultDirectives } from "../utils";
22
22
  // Type-only import: ToolManager must not create a runtime cycle with Agent
23
23
  import type { Agent } from "./Agent";
24
24
  import { Flow } from "./Flow";
@@ -81,7 +81,7 @@ export class ToolManager<TContext = unknown, TData = unknown> {
81
81
  * Set the transient tool layer for the current turn.
82
82
  *
83
83
  * Accepts the merged `injectTools` array from pre-LLM emitters
84
- * (already concatenated in outer-to-inner order by the DirectiveBus).
84
+ * (concatenated in outer-to-inner order).
85
85
  * Deduplicates by tool `id` with last-definition-wins.
86
86
  *
87
87
  * Must be paired with `clearTransientTools()` in a try/finally guard
@@ -678,6 +678,7 @@ export class ToolManager<TContext = unknown, TData = unknown> {
678
678
  }): Promise<ToolExecutionResult> {
679
679
  const { tool, context, updateContext, updateData, history, data, toolArguments } = params;
680
680
  const startTime = Date.now();
681
+ const executionTimeout = 30000; // 30 seconds default timeout
681
682
 
682
683
  try {
683
684
  // Validate tool before execution
@@ -725,24 +726,40 @@ export class ToolManager<TContext = unknown, TData = unknown> {
725
726
  return gateDenial;
726
727
  }
727
728
 
728
- // Execute tool with timeout protection
729
- const executionTimeout = 30000; // 30 seconds default timeout
729
+ // Execute tool with timeout protection. The timer is cleared when the
730
+ // race settles AND unref'd so a pending timer never delays process
731
+ // shutdown; a handler that loses the race keeps running detached (its
732
+ // result is discarded), which we surface in a warn.
733
+ let timeoutTimer: ReturnType<typeof setTimeout> | undefined;
730
734
  const timeoutPromise = new Promise<never>((_, reject) => {
731
- setTimeout(() => reject(new Error(`Tool execution timeout after ${executionTimeout}ms`)), executionTimeout);
735
+ timeoutTimer = setTimeout(
736
+ () => reject(new Error(`Tool execution timeout after ${executionTimeout}ms`)),
737
+ executionTimeout
738
+ );
739
+ timeoutTimer.unref?.();
732
740
  });
733
741
 
734
- const result = await Promise.race([
735
- tool.handler(toolContext, toolArguments),
736
- timeoutPromise
737
- ]);
742
+ let result: unknown;
743
+ try {
744
+ // Attach a no-op catch to the losing handler promise so a post-timeout
745
+ // rejection can't surface as an unhandled rejection and crash the process.
746
+ const handlerPromise = Promise.resolve(tool.handler(toolContext, toolArguments));
747
+ handlerPromise.catch(() => undefined);
748
+ result = await Promise.race([handlerPromise, timeoutPromise]);
749
+ } finally {
750
+ clearTimeout(timeoutTimer);
751
+ }
738
752
 
739
753
  const executionTime = Date.now() - startTime;
740
754
  logger.debug(`[ToolManager] Tool ${tool.id} completed in ${executionTime}ms`);
741
755
 
742
- // Handle different result types
756
+ // Handle different result types. Only SEMANTIC markers identify a
757
+ // ToolResult — bare `{data}` / `{error}` shapes are indistinguishable
758
+ // from ordinary business payloads (e.g. upstream API envelopes) and are
759
+ // wrapped as raw results instead.
743
760
  let toolResult: ToolResult<unknown, TContext, TData>;
744
761
 
745
- if (result && typeof result === 'object' && ('data' in result || 'success' in result || 'error' in result || 'directive' in result)) {
762
+ if (isToolResultLike(result)) {
746
763
  // It's already a ToolResult-like object
747
764
  toolResult = result as ToolResult<unknown, TContext, TData>;
748
765
  } else {
@@ -753,10 +770,9 @@ export class ToolManager<TContext = unknown, TData = unknown> {
753
770
  };
754
771
  }
755
772
 
756
- // Collect directive from ToolResult.directive (if present)
757
- if (toolResult.directive) {
758
- collectedDirectives.push(toolResult.directive);
759
- }
773
+ // Collect directives from the result — singular and plural forms both
774
+ // count, ordered identically to the concurrent executor.
775
+ collectedDirectives.push(...extractResultDirectives(toolResult));
760
776
 
761
777
  // Apply data updates from tool result with validation
762
778
  if (toolResult.dataUpdate) {
@@ -813,6 +829,15 @@ export class ToolManager<TContext = unknown, TData = unknown> {
813
829
  const executionTime = Date.now() - startTime;
814
830
 
815
831
  logger.error(`[ToolManager] Tool execution error for ${tool.id} after ${executionTime}ms:`, error);
832
+ if (error instanceof Error && error.message.includes("Tool execution timeout")) {
833
+ // The losing handler may still be running with captured callbacks.
834
+ // Its writes are NOT rolled back here — tools should stay idempotent
835
+ // or accept their own cancellation via interruptBehavior/abort.
836
+ logger.warn(
837
+ `[ToolManager] Handler for "${tool.id}" exceeded ${executionTimeout}ms and was abandoned while still running. ` +
838
+ `Its eventual side effects are not cancelled or rolled back.`
839
+ );
840
+ }
816
841
 
817
842
  // Re-throw the error so callers can handle it
818
843
  throw error;
@@ -871,9 +896,11 @@ export class ToolManager<TContext = unknown, TData = unknown> {
871
896
  return data != null && key in (data as Record<string, unknown>);
872
897
  },
873
898
  dispatch: (_directive: Directive<TContext, TData>): void => {
874
- // Directives from concurrent execution are collected by the StreamingToolExecutor
875
- // and surfaced via ToolExecutionUpdate. This is a no-op placeholder;
876
- // full directive bus integration happens in ResponsePipeline (task 1.6).
899
+ // Handlers never see THIS context StreamingToolExecutor.executeTool
900
+ // wraps each call in a per-call context whose dispatch attributes the
901
+ // directive to that tool. Reaching here means a gate or future caller
902
+ // dispatched outside a tool execution; surface it instead of dropping.
903
+ logger.warn("[ToolManager] ctx.dispatch called outside tool execution has no turn to attach to; directive dropped.");
877
904
  },
878
905
  };
879
906
 
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { Directive } from "../types/flow";
9
+ import type { SessionState } from "../types/session";
9
10
  import { FlowConfigurationError } from "./Step";
10
11
 
11
12
  // ─── Position field metadata ─────────────────────────────────────────────────
@@ -181,6 +182,32 @@ function validate<TContext, TData>(d: Directive<TContext, TData>): void {
181
182
  }
182
183
  }
183
184
 
185
+ /**
186
+ * Reduce an array of directives into one via the canonical Algorithm 4 merge
187
+ * (position precedence, reply last-wins, state shallow-merge, halt OR).
188
+ * Empty arrays yield undefined.
189
+ */
190
+ function mergeAll<TContext, TData>(
191
+ directives: Directive<TContext, TData>[]
192
+ ): Directive<TContext, TData> | undefined {
193
+ if (directives.length === 0) return undefined;
194
+ return directives.reduce((a, b) => merge(a, b));
195
+ }
196
+
197
+ /**
198
+ * Queue a control directive on the session for the pendingDirective applier at
199
+ * the START of the next turn, merging with anything already queued via the
200
+ * canonical Algorithm 4 merge.
201
+ */
202
+ function queuePending<TData>(
203
+ session: SessionState<TData>,
204
+ directive: Directive<unknown, TData>
205
+ ): void {
206
+ session.pendingDirective = session.pendingDirective
207
+ ? merge(session.pendingDirective, directive)
208
+ : directive;
209
+ }
210
+
184
211
  /**
185
212
  * The `flow` namespace object. Exported as a single const for ergonomic usage:
186
213
  *
@@ -194,5 +221,7 @@ function validate<TContext, TData>(d: Directive<TContext, TData>): void {
194
221
  export const flow = {
195
222
  isDirective,
196
223
  merge,
224
+ mergeAll,
225
+ queuePending,
197
226
  validate,
198
227
  } as const;
package/src/index.ts CHANGED
@@ -6,6 +6,9 @@
6
6
 
7
7
  // Core
8
8
  export { Agent } from "./core/Agent";
9
+ export type { RespondParams, StreamOptions, GenerateOptions } from "./core/ResponseModal";
10
+ export { ResponseGenerationError } from "./core/ResponseGenerationError";
11
+ export type { EndedFlow } from "./types/agent";
9
12
  export { createAgent } from "./core/createAgent";
10
13
  export { Flow } from "./core/Flow";
11
14
  export { Step, FlowConfigurationError } from "./core/Step";
@@ -188,4 +191,4 @@ export type {
188
191
  ConditionWhen,
189
192
  } from "./types";
190
193
  export { EventKind, MessageRole } from "./types";
191
- export { createSession, createSessionId, createPersistedState, enterFlow, enterStep, completeCurrentFlow, isFlowCompletedThisSession, mergeCollected } from "./utils";
194
+ export { restoreSession, createSession, createSessionId, createPersistedState, enterFlow, enterStep, completeCurrentFlow, isFlowCompletedThisSession, mergeCollected } from "./utils";
@@ -17,11 +17,11 @@ import type {
17
17
  } from "../types";
18
18
  import type { ProviderCapabilities } from "../types/ai";
19
19
  import type { HistoryItem } from "../types/history";
20
- import { withTimeoutAndRetry, withStreamRetry, resolveRetryConfig, logger, assertUsableCompletion, combineAbortSignals } from "../utils";
20
+ import { withTimeoutAndRetry, withStreamRetry, withBackupFallback, streamWithBackupFallback, backupFallbackLogging, resolveRetryConfig, logger, assertUsableCompletion, combineAbortSignals } from "../utils";
21
21
  import {
22
22
  classifyProviderError,
23
- getErrorMessage,
24
23
  isBackupEligible,
24
+ isRetriableProviderError,
25
25
  toProviderError,
26
26
  type ErrorClassificationOptions,
27
27
  } from "./errorClassification";
@@ -31,9 +31,9 @@ import {
31
31
  * Uses types from @anthropic-ai/sdk package
32
32
  */
33
33
  export interface AnthropicProviderOptions {
34
- /** Anthropic API key */
35
- apiKey: string;
36
- /** Model to use (required) - e.g., "claude-sonnet-4-6", "claude-opus-4-7" */
34
+ /** Anthropic API key. Optional when `client` is injected (tests). */
35
+ apiKey?: string;
36
+ /** Model to use (required) - e.g., "claude-sonnet-5", "claude-opus-5" */
37
37
  model: string;
38
38
  /** Backup models to try if primary fails (default: []) */
39
39
  backupModels?: string[];
@@ -44,6 +44,12 @@ export interface AnthropicProviderOptions {
44
44
  timeout?: number;
45
45
  retries?: number;
46
46
  };
47
+ /**
48
+ * Pre-configured SDK client. Overrides the internally-constructed one.
49
+ * Intended for tests injecting scripted transports; production callers
50
+ * should pass `apiKey` instead.
51
+ */
52
+ client?: Anthropic;
47
53
  }
48
54
 
49
55
  /**
@@ -73,7 +79,10 @@ export class AnthropicProvider implements AiProvider {
73
79
  supportsNativeJsonSchema: false, // JSON output is enforced via a prompt instruction, not a native schema mode
74
80
  supportsStreaming: true,
75
81
  supportsStreamingToolCalls: true,
76
- supportsPromptCaching: true,
82
+ // No cache_control is attached to any request yet — reporting true would
83
+ // make consumers believe repeated system prompts are cached when they are
84
+ // not. Flip this when caching is actually implemented.
85
+ supportsPromptCaching: false,
77
86
  };
78
87
  private client: Anthropic;
79
88
  private primaryModel: string;
@@ -84,17 +93,17 @@ export class AnthropicProvider implements AiProvider {
84
93
  private retryConfig: { timeout: number; retries: number };
85
94
 
86
95
  constructor(options: AnthropicProviderOptions) {
87
- const { apiKey, model, backupModels = [], config, retryConfig } = options;
96
+ const { apiKey, model, backupModels = [], config, retryConfig, client } = options;
88
97
 
89
- if (!apiKey) {
98
+ if (!client && !apiKey) {
90
99
  throw new Error("Anthropic API key is required");
91
100
  }
92
101
 
93
102
  if (!model) {
94
- throw new Error("Model is required. Example: 'claude-sonnet-4-6'");
103
+ throw new Error("Model is required. Example: 'claude-sonnet-5'");
95
104
  }
96
105
 
97
- this.client = new Anthropic({
106
+ this.client = client ?? new Anthropic({
98
107
  apiKey,
99
108
  });
100
109
  this.primaryModel = model;
@@ -188,64 +197,19 @@ export class AnthropicProvider implements AiProvider {
188
197
  >(
189
198
  input: GenerateMessageInput<TContext>
190
199
  ): Promise<GenerateMessageOutput<TStructured>> {
191
- // Try primary model first
192
- try {
193
- return await this.generateWithModel<TContext, TStructured>(
194
- this.primaryModel,
195
- input
196
- );
197
- } catch (primaryError: unknown) {
198
- const primaryErrMsg = getErrorMessage(primaryError);
199
- logger.warn(
200
- `[ANTHROPIC] Primary model ${this.primaryModel} failed: ${primaryErrMsg}`
201
- );
202
-
203
- if (!shouldUseBackupModel(primaryError)) {
204
- throw toProviderError(primaryError, this.name, CLASSIFICATION_OPTIONS);
205
- }
206
-
207
- logger.debug(`[ANTHROPIC] Trying backup models`);
208
-
209
- let lastBackupError: unknown = primaryError;
210
-
211
- for (let i = 0; i < this.backupModels.length; i++) {
212
- const backupModel = this.backupModels[i];
213
- logger.debug(
214
- `[ANTHROPIC] Trying backup model ${i + 1}/${this.backupModels.length
215
- }: ${backupModel}`
216
- );
217
-
218
- try {
219
- const result = await this.generateWithModel<TContext, TStructured>(
220
- backupModel,
221
- input
222
- );
223
- logger.debug(`[ANTHROPIC] Backup model ${backupModel} succeeded`);
224
- return result;
225
- } catch (backupError: unknown) {
226
- const backupErrMsg = getErrorMessage(backupError);
227
- logger.warn(
228
- `[ANTHROPIC] Backup model ${backupModel} failed: ${backupErrMsg}`
229
- );
230
- lastBackupError = backupError;
231
-
232
- if (
233
- !shouldUseBackupModel(backupError) &&
234
- i < this.backupModels.length - 1
235
- ) {
236
- logger.debug(
237
- `[ANTHROPIC] Backup model error doesn't qualify for further attempts`
238
- );
239
- break;
240
- }
241
- }
242
- }
200
+ const observer = backupFallbackLogging("[ANTHROPIC]", shouldUseBackupModel);
243
201
 
244
- const lastBackupErrMsg = getErrorMessage(lastBackupError);
245
- logger.error(
246
- `[ANTHROPIC] All models failed. Primary: ${primaryErrMsg}, Last backup: ${lastBackupErrMsg}`
247
- );
248
- throw toProviderError(lastBackupError, this.name, CLASSIFICATION_OPTIONS);
202
+ try {
203
+ return await withBackupFallback({
204
+ models: [this.primaryModel, ...this.backupModels],
205
+ attempt: (model) =>
206
+ this.generateWithModel<TContext, TStructured>(model, input),
207
+ shouldTryBackup: shouldUseBackupModel,
208
+ ...observer.callbacks,
209
+ });
210
+ } catch (error: unknown) {
211
+ observer.logExhausted(error);
212
+ throw toProviderError(error, this.name, CLASSIFICATION_OPTIONS);
249
213
  }
250
214
  }
251
215
 
@@ -394,7 +358,8 @@ export class AnthropicProvider implements AiProvider {
394
358
  operation,
395
359
  this.retryConfig.timeout,
396
360
  this.retryConfig.retries,
397
- `Anthropic ${model}`
361
+ `Anthropic ${model}`,
362
+ (error) => isRetriableProviderError(error, CLASSIFICATION_OPTIONS)
398
363
  ) as Promise<GenerateMessageOutput<TStructured>>;
399
364
  }
400
365
 
@@ -404,64 +369,22 @@ export class AnthropicProvider implements AiProvider {
404
369
  >(
405
370
  input: GenerateMessageInput<TContext>
406
371
  ): AsyncGenerator<GenerateMessageStreamChunk<TStructured>> {
407
- // Try primary model first
408
- try {
409
- yield* withStreamRetry(
410
- (signal) => this.generateStreamWithModel<TContext, TStructured>(this.primaryModel, input, signal),
411
- { maxRetries: this.retryConfig.retries, firstChunkTimeoutMs: this.retryConfig.timeout, operationName: `Anthropic ${this.primaryModel} stream` }
412
- );
413
- } catch (primaryError: unknown) {
414
- const primaryErrMsg = getErrorMessage(primaryError);
415
- logger.warn(
416
- `[ANTHROPIC] Primary model ${this.primaryModel} failed: ${primaryErrMsg}`
417
- );
418
-
419
- if (!shouldUseBackupModel(primaryError)) {
420
- throw toProviderError(primaryError, this.name, CLASSIFICATION_OPTIONS);
421
- }
422
-
423
- logger.debug(`[ANTHROPIC] Trying backup models for streaming`);
424
-
425
- let lastBackupError: unknown = primaryError;
426
-
427
- for (let i = 0; i < this.backupModels.length; i++) {
428
- const backupModel = this.backupModels[i];
429
- logger.debug(
430
- `[ANTHROPIC] Trying backup model ${i + 1}/${this.backupModels.length
431
- }: ${backupModel}`
432
- );
372
+ const observer = backupFallbackLogging("[ANTHROPIC]", shouldUseBackupModel, { streaming: true });
433
373
 
434
- try {
435
- yield* withStreamRetry(
436
- (signal) => this.generateStreamWithModel<TContext, TStructured>(backupModel, input, signal),
437
- { maxRetries: this.retryConfig.retries, firstChunkTimeoutMs: this.retryConfig.timeout, operationName: `Anthropic ${backupModel} stream` }
438
- );
439
- logger.debug(`[ANTHROPIC] Backup model ${backupModel} succeeded`);
440
- return;
441
- } catch (backupError: unknown) {
442
- const backupErrMsg = getErrorMessage(backupError);
443
- logger.warn(
444
- `[ANTHROPIC] Backup model ${backupModel} failed: ${backupErrMsg}`
445
- );
446
- lastBackupError = backupError;
447
-
448
- if (
449
- !shouldUseBackupModel(backupError) &&
450
- i < this.backupModels.length - 1
451
- ) {
452
- logger.debug(
453
- `[ANTHROPIC] Backup model error doesn't qualify for further attempts`
454
- );
455
- break;
456
- }
457
- }
458
- }
459
-
460
- const lastBackupErrMsg = getErrorMessage(lastBackupError);
461
- logger.error(
462
- `[ANTHROPIC] All models failed. Primary: ${primaryErrMsg}, Last backup: ${lastBackupErrMsg}`
463
- );
464
- throw toProviderError(lastBackupError, this.name, CLASSIFICATION_OPTIONS);
374
+ try {
375
+ yield* streamWithBackupFallback({
376
+ models: [this.primaryModel, ...this.backupModels],
377
+ attempt: (model) =>
378
+ withStreamRetry(
379
+ (signal) => this.generateStreamWithModel<TContext, TStructured>(model, input, signal),
380
+ { maxRetries: this.retryConfig.retries, firstChunkTimeoutMs: this.retryConfig.timeout, operationName: `Anthropic ${model} stream`, isRetriable: (error) => isRetriableProviderError(error, CLASSIFICATION_OPTIONS) }
381
+ ),
382
+ shouldTryBackup: shouldUseBackupModel,
383
+ ...observer.callbacks,
384
+ });
385
+ } catch (error: unknown) {
386
+ observer.logExhausted(error);
387
+ throw toProviderError(error, this.name, CLASSIFICATION_OPTIONS);
465
388
  }
466
389
  }
467
390
 
@@ -545,10 +468,13 @@ export class AnthropicProvider implements AiProvider {
545
468
  let stopReason: string | undefined;
546
469
  let inputTokens = 0;
547
470
  let outputTokens = 0;
548
- const toolCalls: Array<{
549
- toolName: string;
550
- arguments: Record<string, unknown>;
551
- }> = [];
471
+ // Tool_use blocks arrive empty: content_block_start carries `input: {}`,
472
+ // the real arguments stream in later as input_json_delta partial_json
473
+ // fragments. Accumulate per block index and parse once at stream end.
474
+ const pendingToolBlocks = new Map<
475
+ number,
476
+ { toolName: string; argumentsBuffer: string }
477
+ >();
552
478
 
553
479
  for await (const chunk of stream) {
554
480
  if (chunk.type === "message_start") {
@@ -556,9 +482,9 @@ export class AnthropicProvider implements AiProvider {
556
482
  inputTokens = chunk.message.usage.input_tokens;
557
483
  } else if (chunk.type === "content_block_start") {
558
484
  if (chunk.content_block.type === "tool_use") {
559
- toolCalls.push({
485
+ pendingToolBlocks.set(chunk.index, {
560
486
  toolName: chunk.content_block.name,
561
- arguments: chunk.content_block.input as Record<string, unknown>,
487
+ argumentsBuffer: "",
562
488
  });
563
489
  }
564
490
  } else if (chunk.type === "content_block_delta") {
@@ -570,6 +496,11 @@ export class AnthropicProvider implements AiProvider {
570
496
  accumulated,
571
497
  done: false,
572
498
  } as GenerateMessageStreamChunk<TStructured>;
499
+ } else if (chunk.delta.type === "input_json_delta") {
500
+ const block = pendingToolBlocks.get(chunk.index);
501
+ if (block) {
502
+ block.argumentsBuffer += chunk.delta.partial_json;
503
+ }
573
504
  }
574
505
  } else if (chunk.type === "message_delta") {
575
506
  stopReason = chunk.delta.stop_reason || undefined;
@@ -577,6 +508,27 @@ export class AnthropicProvider implements AiProvider {
577
508
  }
578
509
  }
579
510
 
511
+ const toolCalls: Array<{
512
+ toolName: string;
513
+ arguments: Record<string, unknown>;
514
+ }> = [];
515
+ for (const [, block] of [...pendingToolBlocks.entries()].sort(
516
+ ([a], [b]) => a - b
517
+ )) {
518
+ let args: Record<string, unknown> = {};
519
+ if (block.argumentsBuffer) {
520
+ try {
521
+ args = JSON.parse(block.argumentsBuffer) as Record<string, unknown>;
522
+ } catch (error) {
523
+ logger.warn(
524
+ "[ANTHROPIC] Failed to parse streamed tool call arguments:",
525
+ error
526
+ );
527
+ }
528
+ }
529
+ toolCalls.push({ toolName: block.toolName, arguments: args });
530
+ }
531
+
580
532
  // Parse JSON response if schema was provided
581
533
  let structured: AgentStructuredResponse | undefined;
582
534
  if (input.parameters?.jsonSchema && accumulated) {