@falai/agent 2.6.1 → 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 +40 -2
  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 +155 -63
  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 +26 -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 +40 -2
  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 +155 -63
  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 +26 -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 +28 -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 +48 -2
  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 +200 -74
  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 +25 -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
@@ -2,7 +2,7 @@
2
2
  * Response processing utilities shared between respond() and respondStream() methods
3
3
  */
4
4
 
5
- import type {
5
+ import type { EndedFlow,
6
6
  AgentOptions,
7
7
  Event,
8
8
  SessionState,
@@ -51,6 +51,27 @@ export function hasDirectivePositionField<TContext = unknown, TData = unknown>(
51
51
  );
52
52
  }
53
53
 
54
+ /**
55
+ * Compute the per-key data writes the signal phase applied to a session.
56
+ * The signal phase and routing start from the same input session, so any
57
+ * key whose value differs between the input and the signal output was
58
+ * written by the signal phase alone (handler `updateData` calls).
59
+ */
60
+ function diffSignalDataWrites<TData>(
61
+ before: Partial<TData> | undefined,
62
+ after: Partial<TData> | undefined,
63
+ ): Partial<TData> {
64
+ const beforeData = (before ?? {}) as Record<string, unknown>;
65
+ const afterData = (after ?? {}) as Record<string, unknown>;
66
+ const writes: Record<string, unknown> = {};
67
+ for (const key of Object.keys(afterData)) {
68
+ if (afterData[key] !== beforeData[key]) {
69
+ writes[key] = afterData[key];
70
+ }
71
+ }
72
+ return writes as Partial<TData>;
73
+ }
74
+
54
75
  export interface ResponsePreparationResult<TContext, TData = unknown> {
55
76
  effectiveContext: TContext;
56
77
  session: SessionState<TData>;
@@ -65,6 +86,8 @@ export interface RoutingResult<TContext, TData = unknown> {
65
86
  session: SessionState<TData>;
66
87
  isFlowComplete: boolean;
67
88
  completedFlows?: Flow<TContext, TData>[];
89
+ /** Flows exited via pendingDirective redirects/resets this turn. */
90
+ endedFlows?: EndedFlow[];
68
91
  }
69
92
 
70
93
  /**
@@ -154,8 +177,10 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
154
177
  history: Event[];
155
178
  context: TContext;
156
179
  signal?: AbortSignal;
180
+ /** Restrict ROUTING candidates; directive targets still use the full registry. */
181
+ allowedFlows?: string[];
157
182
  }): Promise<RoutingResult<TContext, TData>> {
158
- const { session, history, context, signal } = params;
183
+ const { session, history, context, signal, allowedFlows } = params;
159
184
 
160
185
  // PHASE 2: ROUTING + STEP SELECTION - Determine which flow and step to use (combined)
161
186
  let selectedFlow: Flow<TContext, TData> | undefined;
@@ -164,9 +189,23 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
164
189
  let isFlowComplete = false;
165
190
  let completedFlows: Flow<TContext, TData>[] = [];
166
191
  let targetSession = session;
192
+ // Flows exited this turn via pendingDirective redirect/reset
193
+ const endedFlows: EndedFlow[] = [];
194
+ let exitPrevFlowId: string | undefined;
195
+ let exitPrevFlowTitle: string | undefined;
196
+ let exitReason: EndedFlow['reason'] | undefined;
167
197
 
168
198
  // Get flows early since we need them for pending directives
169
199
  const flows = this.getFlows();
200
+ // Routing candidates may be narrowed for this turn (entry pins); the
201
+ // pendingDirective applier below still resolves targets against the FULL
202
+ // registry so declared redirects always resolve.
203
+ const routingFlows =
204
+ allowedFlows && allowedFlows.length > 0
205
+ ? flows.filter(
206
+ (f) => allowedFlows.includes(f.id) || allowedFlows.includes(f.title),
207
+ )
208
+ : flows;
170
209
 
171
210
  // Check for pending directive from previous flow completion or external dispatch
172
211
  if (targetSession.pendingDirective) {
@@ -195,8 +234,16 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
195
234
  pendingDirective: nextDirective,
196
235
  };
197
236
 
237
+ // Capture the flow we're leaving so directive-driven exits surface on
238
+ // the response (endedFlows).
239
+ exitPrevFlowId = targetSession.currentFlow?.id;
240
+ exitPrevFlowTitle = exitPrevFlowId
241
+ ? flows.find((f) => f.id === exitPrevFlowId)?.title
242
+ : undefined;
243
+
198
244
  // Apply the directive: resolve position field to a flow/step
199
245
  if (directive.goTo) {
246
+ exitReason = 'goto';
200
247
  const flowTarget = typeof directive.goTo === 'string'
201
248
  ? directive.goTo
202
249
  : directive.goTo.flow;
@@ -283,6 +330,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
283
330
  }
284
331
  }
285
332
  } else if (directive.reset) {
333
+ exitReason = 'reset';
286
334
  // Reset current flow
287
335
  if (targetSession.currentFlow) {
288
336
  const currentFlow = flows.find(r => r.id === targetSession.currentFlow?.id);
@@ -316,10 +364,19 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
316
364
  // Skip FlowRouter.decideFlowAndStep — the directive resolved the position
317
365
  }
318
366
 
319
- // If no pending transition or transition handled, do normal routing
320
- if (flows.length > 0 && !selectedFlow) {
367
+ if (
368
+ exitReason &&
369
+ exitPrevFlowId &&
370
+ (!selectedFlow || selectedFlow.id !== exitPrevFlowId)
371
+ ) {
372
+ endedFlows.push({ flowId: exitPrevFlowId, title: exitPrevFlowTitle, reason: exitReason });
373
+ }
374
+
375
+ // If no pending transition or transition handled, do normal routing.
376
+ // Candidates honor allowedFlows; completion scoring runs over the same set.
377
+ if (routingFlows.length > 0 && !selectedFlow) {
321
378
  const orchestration = await this.flowRouter.decideFlowAndStep({
322
- flows: flows,
379
+ flows: routingFlows,
323
380
  session: targetSession,
324
381
  history,
325
382
  agentOptions: this.options,
@@ -350,9 +407,41 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
350
407
  session: targetSession,
351
408
  isFlowComplete,
352
409
  completedFlows,
410
+ ...(endedFlows.length > 0 ? { endedFlows } : {}),
353
411
  };
354
412
  }
355
413
 
414
+ /**
415
+ * Combine a routing-derived session with the signal phase's non-position
416
+ * mutations. The routing session is the base — it owns position state AND
417
+ * data merges such as flow initialData and directive-carried writes; the
418
+ * signal phase's mutations (trigger recording under `signals`, handler
419
+ * data writes) are re-applied on top. Both phases start from the same
420
+ * input session, so their mutations cannot silently collide.
421
+ */
422
+ private combineRoutingAndSignalSessions(params: {
423
+ inputSession: SessionState<TData>;
424
+ signalSession: SessionState<TData>;
425
+ routingSession: SessionState<TData>;
426
+ }): SessionState<TData> {
427
+ const { inputSession, signalSession, routingSession } = params;
428
+ let combined = routingSession;
429
+
430
+ if (signalSession.signals !== inputSession.signals) {
431
+ combined = { ...combined, signals: signalSession.signals };
432
+ }
433
+
434
+ const signalWrites = diffSignalDataWrites(
435
+ inputSession.data,
436
+ signalSession.data,
437
+ );
438
+ if (Object.keys(signalWrites).length > 0) {
439
+ combined = mergeCollected(combined, signalWrites);
440
+ }
441
+
442
+ return combined;
443
+ }
444
+
356
445
  /**
357
446
  * Determine next step and update session
358
447
  */
@@ -679,6 +768,8 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
679
768
  history: Event[]; // Use Event[] for internal processing
680
769
  context: TContext;
681
770
  signal?: AbortSignal;
771
+ /** Restrict this turn's routing candidates (entry pins). */
772
+ allowedFlows?: string[];
682
773
  }): Promise<{
683
774
  selectedFlow?: Flow<TContext, TData>;
684
775
  selectedStep?: Step<TContext, TData>;
@@ -693,16 +784,21 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
693
784
  signalHalted?: boolean;
694
785
  /** Reply text from the halt directive. */
695
786
  signalHaltReply?: string;
787
+ /** Flows exited via pendingDirective redirects/resets this turn. */
788
+ endedFlows?: EndedFlow[];
696
789
  }> {
697
790
  try {
698
791
  // Create a fresh chain tracker for this turn (Requirement 22.1)
699
792
  this.createChainTracker();
700
793
 
701
794
  // ROUTING SKIP OPTIMIZATION (Requirements 20.1, 20.2, 20.3):
702
- // When the current step has collect fields AND pre-extraction populates at least
703
- // one of those fields, skip FlowRouter.decideFlowAndStep for this turn.
704
- const routingSkipResult = await this.attemptRoutingSkipForCollect(params);
705
- if (routingSkipResult) {
795
+ // When the current step has collect fields AND step-scoped pre-extraction
796
+ // populates at least one of those fields, skip FlowRouter.decideFlowAndStep
797
+ // for this turn. The check's extraction result is reused by the fallthrough
798
+ // paths below so a turn costs at most ONE extraction LLM call.
799
+ const routingSkipCheck = await this.attemptRoutingSkipForCollect(params);
800
+ if (routingSkipCheck.skipResult) {
801
+ const skipResult = routingSkipCheck.skipResult;
706
802
  // Even when routing is skipped, run pre-signal phase if processor is present
707
803
  if (this.signalCoordinator.enabled) {
708
804
  const signalResult = await this.signalCoordinator.runPrePhase(
@@ -711,7 +807,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
711
807
  // If signal halts, override the routing skip result
712
808
  if (signalResult.mergedDirective?.halt) {
713
809
  return {
714
- ...routingSkipResult,
810
+ ...skipResult,
715
811
  session: signalResult.updatedSession,
716
812
  signalFirings: signalResult.firings,
717
813
  signalHalted: true,
@@ -722,15 +818,20 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
722
818
  if (hasDirectivePositionField(signalResult.mergedDirective)) {
723
819
  return this.signalCoordinator.applyPositionDirective(signalResult);
724
820
  }
725
- // Non-position directive: propagate for pre-LLM augmentation
821
+ // Non-position directive: keep the skip result (extraction merges,
822
+ // step entry) and re-apply the signal phase's non-position mutations
726
823
  return {
727
- ...routingSkipResult,
728
- session: signalResult.updatedSession,
824
+ ...skipResult,
825
+ session: this.combineRoutingAndSignalSessions({
826
+ inputSession: params.session,
827
+ signalSession: signalResult.updatedSession,
828
+ routingSession: skipResult.session,
829
+ }),
729
830
  signalFirings: signalResult.firings,
730
831
  signalPreDirective: signalResult.mergedDirective || undefined,
731
832
  };
732
833
  }
733
- return routingSkipResult;
834
+ return skipResult;
734
835
  }
735
836
 
736
837
  // ── PARALLEL PRE-SIGNAL PHASE + ROUTING (Algorithm 5) ────────────────
@@ -747,6 +848,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
747
848
  history: params.history,
748
849
  context: params.context,
749
850
  signal: params.signal,
851
+ allowedFlows: params.allowedFlows,
750
852
  }),
751
853
  ]);
752
854
 
@@ -770,44 +872,45 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
770
872
 
771
873
  // ── Requirement 8.4: non-position directive → use routing, propagate augmentation ──
772
874
  // ── Requirement 8.5: no directive → use routing as-is ─────────────
773
- let updatedSession = signalResult.updatedSession;
875
+ // Base = routed session (position state AND router data merges such as
876
+ // flow initialData survive); the signal phase's non-position mutations
877
+ // (trigger state, handler data writes) are re-applied on top.
878
+ let updatedSession = this.combineRoutingAndSignalSessions({
879
+ inputSession: params.session,
880
+ signalSession: signalResult.updatedSession,
881
+ routingSession: routingResult.session,
882
+ });
774
883
 
775
- // Apply data/context updates from signal to the routed session
884
+ // Explicit dataUpdate from the signal's merged directive lands last
776
885
  if (signalResult.mergedDirective?.dataUpdate) {
777
886
  updatedSession = mergeCollected(updatedSession, signalResult.mergedDirective.dataUpdate);
778
887
  }
779
888
 
780
- // Use routing result for flow/step, but carry signal session state
781
- // Merge routing session changes on top of signal session
782
- const routingSession = routingResult.session;
783
- updatedSession = {
784
- ...updatedSession,
785
- currentFlow: routingSession.currentFlow,
786
- currentStep: routingSession.currentStep,
787
- flowHistory: routingSession.flowHistory,
788
- pendingDirective: routingSession.pendingDirective,
789
- };
790
-
791
889
  const isFlowComplete = routingResult.isFlowComplete;
792
890
 
793
- // PRE-EXTRACTION: same logic as below — extract data from user message
891
+ // PRE-EXTRACTION: same logic as below — extract data from user message.
892
+ // Reuses the routing-skip check's step-scoped extraction when it already
893
+ // ran this turn (at most ONE extraction LLM call per turn).
794
894
  if (routingResult.selectedFlow && !isFlowComplete) {
795
- if (this.shouldPreExtractData(routingResult.selectedFlow)) {
895
+ let extractedData: Partial<TData> | undefined;
896
+ if (routingSkipCheck.extractionRan) {
897
+ extractedData = routingSkipCheck.extractedData;
898
+ } else if (this.shouldPreExtractData(routingResult.selectedFlow)) {
796
899
  logger.debug(
797
900
  `[ResponsePipeline] Pre-extracting data for flow: ${routingResult.selectedFlow.title}`
798
901
  );
799
- const extractedData = await this.preExtractFlowData({
902
+ extractedData = await this.preExtractFlowData({
800
903
  route: routingResult.selectedFlow,
801
904
  history: params.history,
802
905
  context: params.context,
803
906
  session: updatedSession,
804
907
  signal: params.signal,
805
908
  });
806
- if (extractedData && Object.keys(extractedData).length > 0) {
807
- logger.debug(`[ResponsePipeline] Pre-extracted data:`, extractedData);
808
- updatedSession = mergeCollected(updatedSession, extractedData);
809
- await this.updateCollectedData(extractedData);
810
- }
909
+ }
910
+ if (extractedData && Object.keys(extractedData).length > 0) {
911
+ logger.debug(`[ResponsePipeline] Pre-extracted data:`, extractedData);
912
+ updatedSession = mergeCollected(updatedSession, extractedData);
913
+ await this.updateCollectedData(extractedData);
811
914
  }
812
915
  }
813
916
 
@@ -828,6 +931,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
828
931
  isFlowComplete: stepResult.flowChanged ? false : isFlowComplete,
829
932
  signalFirings: signalResult.firings,
830
933
  signalPreDirective: signalResult.mergedDirective || undefined,
934
+ endedFlows: routingResult.endedFlows,
831
935
  };
832
936
  }
833
937
 
@@ -837,39 +941,45 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
837
941
  history: params.history,
838
942
  context: params.context,
839
943
  signal: params.signal,
944
+ allowedFlows: params.allowedFlows,
840
945
  });
841
946
 
842
947
  let updatedSession = routingResult.session;
843
948
  const isFlowComplete = routingResult.isFlowComplete;
844
949
 
845
950
  // PRE-EXTRACTION: If entering a flow that collects data, extract data from user message first
846
- // This allows us to skip steps whose data is already provided
951
+ // This allows us to skip steps whose data is already provided.
952
+ // Reuses the routing-skip check's step-scoped extraction when it already
953
+ // ran this turn — at most ONE extraction LLM call per turn.
847
954
  if (routingResult.selectedFlow && !isFlowComplete) {
848
955
  // Always pre-extract when flow collects data (not just on new flow entry)
849
956
  // This ensures step selection has the most up-to-date data
850
- if (this.shouldPreExtractData(routingResult.selectedFlow)) {
957
+ let extractedData: Partial<TData> | undefined;
958
+ if (routingSkipCheck.extractionRan) {
959
+ extractedData = routingSkipCheck.extractedData;
960
+ } else if (this.shouldPreExtractData(routingResult.selectedFlow)) {
851
961
  logger.debug(
852
962
  `[ResponsePipeline] Pre-extracting data for flow: ${routingResult.selectedFlow.title}`
853
963
  );
854
964
 
855
- const extractedData = await this.preExtractFlowData({
965
+ extractedData = await this.preExtractFlowData({
856
966
  route: routingResult.selectedFlow,
857
967
  history: params.history,
858
968
  context: params.context,
859
969
  session: updatedSession,
860
970
  signal: params.signal,
861
971
  });
972
+ }
862
973
 
863
- if (extractedData && Object.keys(extractedData).length > 0) {
864
- logger.debug(
865
- `[ResponsePipeline] Pre-extracted data:`,
866
- extractedData
867
- );
868
- // Merge pre-extracted data into session before step selection
869
- updatedSession = mergeCollected(updatedSession, extractedData);
870
- // Also update agent's collected data
871
- await this.updateCollectedData(extractedData);
872
- }
974
+ if (extractedData && Object.keys(extractedData).length > 0) {
975
+ logger.debug(
976
+ `[ResponsePipeline] Pre-extracted data:`,
977
+ extractedData
978
+ );
979
+ // Merge pre-extracted data into session before step selection
980
+ updatedSession = mergeCollected(updatedSession, extractedData);
981
+ // Also update agent's collected data
982
+ await this.updateCollectedData(extractedData);
873
983
  }
874
984
  }
875
985
 
@@ -889,6 +999,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
889
999
  session: stepResult.session,
890
1000
  // If a branch changed the flow, the original isFlowComplete no longer applies
891
1001
  isFlowComplete: stepResult.flowChanged ? false : isFlowComplete,
1002
+ endedFlows: routingResult.endedFlows,
892
1003
  };
893
1004
  } catch (error) {
894
1005
  throw ResponseGenerationError.fromError(error, 'routing_optimization', params);
@@ -1003,12 +1114,19 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
1003
1114
  }
1004
1115
 
1005
1116
  /**
1006
- * Routing skip optimization (Requirements 20.1, 20.2, 20.3):
1007
- * When the current step declares `collect` fields AND pre-extraction populates
1008
- * at least one of those fields from the user's message, skip routing for this turn.
1117
+ * Routing skip check (Requirements 20.1, 20.2, 20.3):
1118
+ * When the current step declares `collect` fields AND step-scoped extraction
1119
+ * populates at least one of those fields from the user's message, the caller
1120
+ * skips routing for this turn (`skipResult`).
1009
1121
  *
1010
- * Returns the routing result if the skip applies, or undefined to fall through
1011
- * to normal routing.
1122
+ * The extraction is scoped to EXACTLY this step's collect fields incidental
1123
+ * mentions of other (e.g. other-flow) fields cannot pin the routing decision.
1124
+ * It is skipped entirely when every collect field is already populated.
1125
+ *
1126
+ * Always returns what the check did so the caller can reuse the single
1127
+ * extraction result in its normal path instead of extracting twice:
1128
+ * - `extractionRan` — whether an extraction LLM call was made this turn.
1129
+ * - `extractedData` — that call's result (empty when nothing found / not run).
1012
1130
  */
1013
1131
  private async attemptRoutingSkipForCollect(params: {
1014
1132
  session: SessionState<TData>;
@@ -1016,22 +1134,27 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
1016
1134
  context: TContext;
1017
1135
  signal?: AbortSignal;
1018
1136
  }): Promise<{
1019
- selectedFlow?: Flow<TContext, TData>;
1020
- selectedStep?: Step<TContext, TData>;
1021
- responseDirectives?: string[];
1022
- session: SessionState<TData>;
1023
- isFlowComplete: boolean;
1024
- } | undefined> {
1137
+ extractionRan: boolean;
1138
+ extractedData: Partial<TData>;
1139
+ skipResult?: {
1140
+ selectedFlow?: Flow<TContext, TData>;
1141
+ selectedStep?: Step<TContext, TData>;
1142
+ responseDirectives?: string[];
1143
+ session: SessionState<TData>;
1144
+ isFlowComplete: boolean;
1145
+ };
1146
+ }> {
1025
1147
  const { session } = params;
1148
+ const notApplicable = { extractionRan: false, extractedData: {} as Partial<TData> };
1026
1149
 
1027
1150
  // Only applies when we already have a current flow and step
1028
1151
  if (!session.currentFlow || !session.currentStep) {
1029
- return undefined;
1152
+ return notApplicable;
1030
1153
  }
1031
1154
 
1032
1155
  // Also skip this optimization if there's a pending directive (it takes priority)
1033
1156
  if (session.pendingDirective) {
1034
- return undefined;
1157
+ return notApplicable;
1035
1158
  }
1036
1159
 
1037
1160
  // Look up the actual Flow and Step objects to access `collect`
@@ -1039,40 +1162,54 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
1039
1162
  (f) => f.id === session.currentFlow?.id
1040
1163
  );
1041
1164
  if (!currentFlow) {
1042
- return undefined;
1165
+ return notApplicable;
1043
1166
  }
1044
1167
 
1045
1168
  const currentStep = currentFlow.getStep(session.currentStep.id);
1046
1169
  if (!currentStep || !currentStep.collect || currentStep.collect.length === 0) {
1047
- return undefined;
1170
+ return notApplicable;
1048
1171
  }
1049
1172
 
1050
- // We have a step with collect fields. Run pre-extraction to see if the
1051
- // user's message populates any of them.
1052
1173
  const collectFields = currentStep.collect;
1053
1174
 
1175
+ // Every relevant field already set — the skip cannot fire; don't spend
1176
+ // an extraction LLM call probing for it.
1177
+ const sessionRecord = (session.data ?? {}) as Record<string, unknown>;
1178
+ const allPopulated = collectFields.every((field) => {
1179
+ const value = sessionRecord[String(field)];
1180
+ return value !== undefined && value !== null;
1181
+ });
1182
+ if (allPopulated) {
1183
+ return notApplicable;
1184
+ }
1185
+
1054
1186
  // Snapshot current data for comparison
1055
- const dataBefore = { ...session.data };
1187
+ const dataBefore = { ...sessionRecord } as Record<string, unknown>;
1056
1188
 
1057
- // Run pre-extraction against the current flow
1189
+ // Step-scoped pre-extraction: see if the user's message populates any of
1190
+ // THIS step's collect fields.
1058
1191
  const extractedData = await this.preExtractFlowData({
1059
1192
  route: currentFlow,
1060
1193
  history: params.history,
1061
1194
  context: params.context,
1062
1195
  session,
1063
1196
  signal: params.signal,
1197
+ restrictToFields: collectFields.map(String),
1064
1198
  });
1065
1199
 
1066
1200
  if (!extractedData || Object.keys(extractedData).length === 0) {
1067
- return undefined;
1201
+ return { extractionRan: true, extractedData: {} };
1068
1202
  }
1069
1203
 
1070
1204
  // Determine which collect fields were newly populated by pre-extraction
1205
+ const extractedRecord = extractedData as Record<string, unknown>;
1071
1206
  const populatedCollectFields: string[] = [];
1072
1207
  for (const field of collectFields) {
1073
- const key = field as string;
1074
- const hadValue = dataBefore[field] !== undefined && dataBefore[field] !== null;
1075
- const hasNewValue = extractedData[field] !== undefined && extractedData[field] !== null;
1208
+ const key = String(field);
1209
+ const hadValue =
1210
+ dataBefore[key] !== undefined && dataBefore[key] !== null;
1211
+ const hasNewValue =
1212
+ extractedRecord[key] !== undefined && extractedRecord[key] !== null;
1076
1213
  if (hasNewValue && !hadValue) {
1077
1214
  populatedCollectFields.push(key);
1078
1215
  }
@@ -1080,7 +1217,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
1080
1217
 
1081
1218
  if (populatedCollectFields.length === 0) {
1082
1219
  // Pre-extraction didn't populate any declared collect field — no skip
1083
- return undefined;
1220
+ return { extractionRan: true, extractedData };
1084
1221
  }
1085
1222
 
1086
1223
  // ROUTING SKIP: pre-extraction populated collect fields → retain current flow/step
@@ -1103,11 +1240,15 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
1103
1240
  });
1104
1241
 
1105
1242
  return {
1106
- selectedFlow: stepResult.flowChanged || currentFlow,
1107
- selectedStep: stepResult.nextStep,
1108
- responseDirectives: undefined,
1109
- session: stepResult.session,
1110
- isFlowComplete: false,
1243
+ extractionRan: true,
1244
+ extractedData,
1245
+ skipResult: {
1246
+ selectedFlow: stepResult.flowChanged || currentFlow,
1247
+ selectedStep: stepResult.nextStep,
1248
+ responseDirectives: undefined,
1249
+ session: stepResult.session,
1250
+ isFlowComplete: false,
1251
+ },
1111
1252
  };
1112
1253
  }
1113
1254
 
@@ -1132,6 +1273,30 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
1132
1273
  return hasDataCollectionSteps;
1133
1274
  }
1134
1275
 
1276
+ /**
1277
+ * Build an extraction schema limited to the given subset of agent schema
1278
+ * fields. Used by the routing-skip probe so only the current step's collect
1279
+ * fields are visible to the extractor.
1280
+ */
1281
+ private buildFieldRestrictedSchema(
1282
+ agentSchema: StructuredSchema,
1283
+ fields: string[],
1284
+ ): StructuredSchema {
1285
+ const properties: Record<string, StructuredSchema> = {};
1286
+ for (const field of fields) {
1287
+ const prop = agentSchema.properties?.[field];
1288
+ if (prop) {
1289
+ properties[field] = prop;
1290
+ }
1291
+ }
1292
+ return {
1293
+ type: "object",
1294
+ description: `Extraction restricted to these fields: ${fields.join(', ')}`,
1295
+ properties,
1296
+ additionalProperties: false,
1297
+ };
1298
+ }
1299
+
1135
1300
  /**
1136
1301
  * Pre-extract data from user message when entering a flow
1137
1302
  * This allows skipping steps whose data is already provided
@@ -1142,15 +1307,20 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
1142
1307
  context: TContext;
1143
1308
  session: SessionState<TData>;
1144
1309
  signal?: AbortSignal;
1310
+ /** When set, extraction is restricted to exactly these schema fields (step-scoped routing-skip probe). */
1311
+ restrictToFields?: string[];
1145
1312
  }): Promise<Partial<TData>> {
1146
- const { route: flow, history, signal } = params;
1313
+ const { route: flow, history, signal, restrictToFields } = params;
1147
1314
 
1148
1315
  // Build a schema for data extraction based on flow's fields
1149
- const extractionSchema = this.getSchema();
1150
- if (!extractionSchema) {
1316
+ const agentSchema = this.getSchema();
1317
+ if (!agentSchema) {
1151
1318
  logger.warn(`[ResponsePipeline] No schema available for pre-extraction`);
1152
1319
  return {};
1153
1320
  }
1321
+ const extractionSchema = restrictToFields
1322
+ ? this.buildFieldRestrictedSchema(agentSchema, restrictToFields)
1323
+ : agentSchema;
1154
1324
 
1155
1325
  // Get last user message
1156
1326
  const lastMessage = getLastMessageFromHistory(history);
@@ -1166,11 +1336,15 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
1166
1336
  ];
1167
1337
 
1168
1338
  // Add field descriptions
1169
- if (flow.requiredFields) {
1170
- extractionPrompt.push(`Required fields: ${flow.requiredFields.join(', ')}`);
1171
- }
1172
- if (flow.optionalFields) {
1173
- extractionPrompt.push(`Optional fields: ${flow.optionalFields.join(', ')}`);
1339
+ if (restrictToFields) {
1340
+ extractionPrompt.push(`Only these fields: ${restrictToFields.join(', ')}`);
1341
+ } else {
1342
+ if (flow.requiredFields) {
1343
+ extractionPrompt.push(`Required fields: ${flow.requiredFields.join(', ')}`);
1344
+ }
1345
+ if (flow.optionalFields) {
1346
+ extractionPrompt.push(`Optional fields: ${flow.optionalFields.join(', ')}`);
1347
+ }
1174
1348
  }
1175
1349
 
1176
1350
  extractionPrompt.push(