@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.
- package/README.md +1 -1
- package/dist/adapters/MemoryAdapter.js +29 -33
- package/dist/adapters/MemoryAdapter.js.map +1 -1
- package/dist/adapters/PostgreSQLAdapter.d.ts.map +1 -1
- package/dist/adapters/PostgreSQLAdapter.js +16 -11
- package/dist/adapters/PostgreSQLAdapter.js.map +1 -1
- package/dist/adapters/RedisAdapter.d.ts +1 -0
- package/dist/adapters/RedisAdapter.d.ts.map +1 -1
- package/dist/adapters/RedisAdapter.js +75 -25
- package/dist/adapters/RedisAdapter.js.map +1 -1
- package/dist/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/adapters/SQLiteAdapter.js +7 -29
- package/dist/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/adapters/sessionRow.d.ts +22 -0
- package/dist/adapters/sessionRow.d.ts.map +1 -0
- package/dist/adapters/sessionRow.js +48 -0
- package/dist/adapters/sessionRow.js.map +1 -0
- package/dist/cjs/adapters/MemoryAdapter.js +29 -33
- package/dist/cjs/adapters/MemoryAdapter.js.map +1 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.js +16 -11
- package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -1
- package/dist/cjs/adapters/RedisAdapter.d.ts +1 -0
- package/dist/cjs/adapters/RedisAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/RedisAdapter.js +75 -25
- package/dist/cjs/adapters/RedisAdapter.js.map +1 -1
- package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/SQLiteAdapter.js +7 -29
- package/dist/cjs/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/cjs/adapters/sessionRow.d.ts +22 -0
- package/dist/cjs/adapters/sessionRow.d.ts.map +1 -0
- package/dist/cjs/adapters/sessionRow.js +52 -0
- package/dist/cjs/adapters/sessionRow.js.map +1 -0
- package/dist/cjs/core/Agent.d.ts +8 -0
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +43 -3
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/AutoChainExecutor.d.ts +8 -18
- package/dist/cjs/core/AutoChainExecutor.d.ts.map +1 -1
- package/dist/cjs/core/AutoChainExecutor.js +23 -26
- package/dist/cjs/core/AutoChainExecutor.js.map +1 -1
- package/dist/cjs/core/CompactionEngine.d.ts +14 -1
- package/dist/cjs/core/CompactionEngine.d.ts.map +1 -1
- package/dist/cjs/core/CompactionEngine.js +30 -6
- package/dist/cjs/core/CompactionEngine.js.map +1 -1
- package/dist/cjs/core/FlowRouter.d.ts.map +1 -1
- package/dist/cjs/core/FlowRouter.js +32 -10
- package/dist/cjs/core/FlowRouter.js.map +1 -1
- package/dist/cjs/core/ResponseGenerationError.d.ts.map +1 -1
- package/dist/cjs/core/ResponseGenerationError.js +3 -5
- package/dist/cjs/core/ResponseGenerationError.js.map +1 -1
- package/dist/cjs/core/ResponseModal.d.ts +29 -0
- package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
- package/dist/cjs/core/ResponseModal.js +141 -18
- package/dist/cjs/core/ResponseModal.js.map +1 -1
- package/dist/cjs/core/ResponsePipeline.d.ts +36 -6
- package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/cjs/core/ResponsePipeline.js +208 -73
- package/dist/cjs/core/ResponsePipeline.js.map +1 -1
- package/dist/cjs/core/SessionFinalizer.d.ts.map +1 -1
- package/dist/cjs/core/SessionFinalizer.js +30 -3
- package/dist/cjs/core/SessionFinalizer.js.map +1 -1
- package/dist/cjs/core/SessionManager.d.ts +10 -1
- package/dist/cjs/core/SessionManager.d.ts.map +1 -1
- package/dist/cjs/core/SessionManager.js +43 -16
- package/dist/cjs/core/SessionManager.js.map +1 -1
- package/dist/cjs/core/SignalProcessor.d.ts.map +1 -1
- package/dist/cjs/core/SignalProcessor.js +5 -77
- package/dist/cjs/core/SignalProcessor.js.map +1 -1
- package/dist/cjs/core/Step.d.ts.map +1 -1
- package/dist/cjs/core/Step.js +50 -2
- package/dist/cjs/core/Step.js.map +1 -1
- package/dist/cjs/core/StepLifecycle.d.ts +16 -6
- package/dist/cjs/core/StepLifecycle.d.ts.map +1 -1
- package/dist/cjs/core/StepLifecycle.js +97 -14
- package/dist/cjs/core/StepLifecycle.js.map +1 -1
- package/dist/cjs/core/StreamingToolExecutor.d.ts.map +1 -1
- package/dist/cjs/core/StreamingToolExecutor.js +28 -4
- package/dist/cjs/core/StreamingToolExecutor.js.map +1 -1
- package/dist/cjs/core/ToolLoopExecutor.d.ts +5 -1
- package/dist/cjs/core/ToolLoopExecutor.d.ts.map +1 -1
- package/dist/cjs/core/ToolLoopExecutor.js +156 -64
- package/dist/cjs/core/ToolLoopExecutor.js.map +1 -1
- package/dist/cjs/core/ToolManager.d.ts +1 -1
- package/dist/cjs/core/ToolManager.d.ts.map +1 -1
- package/dist/cjs/core/ToolManager.js +40 -17
- package/dist/cjs/core/ToolManager.js.map +1 -1
- package/dist/cjs/core/flow-namespace.d.ts +15 -0
- package/dist/cjs/core/flow-namespace.d.ts.map +1 -1
- package/dist/cjs/core/flow-namespace.js +22 -0
- package/dist/cjs/core/flow-namespace.js.map +1 -1
- package/dist/cjs/index.d.ts +4 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +5 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.d.ts +10 -3
- package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.js +54 -73
- package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
- package/dist/cjs/providers/GeminiProvider.d.ts +9 -3
- package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/cjs/providers/GeminiProvider.js +24 -68
- package/dist/cjs/providers/GeminiProvider.js.map +1 -1
- package/dist/cjs/providers/OpenAICompatibleProvider.d.ts +9 -0
- package/dist/cjs/providers/OpenAICompatibleProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenAICompatibleProvider.js +101 -74
- package/dist/cjs/providers/OpenAICompatibleProvider.js.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.d.ts +1 -1
- package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.js +1 -1
- package/dist/cjs/providers/errorClassification.d.ts +7 -0
- package/dist/cjs/providers/errorClassification.d.ts.map +1 -1
- package/dist/cjs/providers/errorClassification.js +27 -0
- package/dist/cjs/providers/errorClassification.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +35 -0
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/cjs/types/flow.d.ts +49 -32
- package/dist/cjs/types/flow.d.ts.map +1 -1
- package/dist/cjs/types/index.d.ts +1 -1
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/index.js.map +1 -1
- package/dist/cjs/types/tool.d.ts +2 -0
- package/dist/cjs/types/tool.d.ts.map +1 -1
- package/dist/cjs/types/tool.js.map +1 -1
- package/dist/cjs/utils/index.d.ts +4 -4
- package/dist/cjs/utils/index.d.ts.map +1 -1
- package/dist/cjs/utils/index.js +10 -1
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/utils/retry.d.ts +99 -1
- package/dist/cjs/utils/retry.d.ts.map +1 -1
- package/dist/cjs/utils/retry.js +140 -3
- package/dist/cjs/utils/retry.js.map +1 -1
- package/dist/cjs/utils/serialize.d.ts +17 -0
- package/dist/cjs/utils/serialize.d.ts.map +1 -1
- package/dist/cjs/utils/serialize.js +33 -0
- package/dist/cjs/utils/serialize.js.map +1 -1
- package/dist/cjs/utils/session.d.ts +21 -1
- package/dist/cjs/utils/session.d.ts.map +1 -1
- package/dist/cjs/utils/session.js +36 -4
- package/dist/cjs/utils/session.js.map +1 -1
- package/dist/core/Agent.d.ts +8 -0
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +43 -3
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/AutoChainExecutor.d.ts +8 -18
- package/dist/core/AutoChainExecutor.d.ts.map +1 -1
- package/dist/core/AutoChainExecutor.js +23 -26
- package/dist/core/AutoChainExecutor.js.map +1 -1
- package/dist/core/CompactionEngine.d.ts +14 -1
- package/dist/core/CompactionEngine.d.ts.map +1 -1
- package/dist/core/CompactionEngine.js +30 -6
- package/dist/core/CompactionEngine.js.map +1 -1
- package/dist/core/FlowRouter.d.ts.map +1 -1
- package/dist/core/FlowRouter.js +32 -10
- package/dist/core/FlowRouter.js.map +1 -1
- package/dist/core/ResponseGenerationError.d.ts.map +1 -1
- package/dist/core/ResponseGenerationError.js +3 -5
- package/dist/core/ResponseGenerationError.js.map +1 -1
- package/dist/core/ResponseModal.d.ts +29 -0
- package/dist/core/ResponseModal.d.ts.map +1 -1
- package/dist/core/ResponseModal.js +142 -19
- package/dist/core/ResponseModal.js.map +1 -1
- package/dist/core/ResponsePipeline.d.ts +36 -6
- package/dist/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/core/ResponsePipeline.js +208 -73
- package/dist/core/ResponsePipeline.js.map +1 -1
- package/dist/core/SessionFinalizer.d.ts.map +1 -1
- package/dist/core/SessionFinalizer.js +31 -4
- package/dist/core/SessionFinalizer.js.map +1 -1
- package/dist/core/SessionManager.d.ts +10 -1
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +44 -17
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/SignalProcessor.d.ts.map +1 -1
- package/dist/core/SignalProcessor.js +5 -77
- package/dist/core/SignalProcessor.js.map +1 -1
- package/dist/core/Step.d.ts.map +1 -1
- package/dist/core/Step.js +50 -2
- package/dist/core/Step.js.map +1 -1
- package/dist/core/StepLifecycle.d.ts +16 -6
- package/dist/core/StepLifecycle.d.ts.map +1 -1
- package/dist/core/StepLifecycle.js +97 -14
- package/dist/core/StepLifecycle.js.map +1 -1
- package/dist/core/StreamingToolExecutor.d.ts.map +1 -1
- package/dist/core/StreamingToolExecutor.js +28 -4
- package/dist/core/StreamingToolExecutor.js.map +1 -1
- package/dist/core/ToolLoopExecutor.d.ts +5 -1
- package/dist/core/ToolLoopExecutor.d.ts.map +1 -1
- package/dist/core/ToolLoopExecutor.js +156 -64
- package/dist/core/ToolLoopExecutor.js.map +1 -1
- package/dist/core/ToolManager.d.ts +1 -1
- package/dist/core/ToolManager.d.ts.map +1 -1
- package/dist/core/ToolManager.js +41 -18
- package/dist/core/ToolManager.js.map +1 -1
- package/dist/core/flow-namespace.d.ts +15 -0
- package/dist/core/flow-namespace.d.ts.map +1 -1
- package/dist/core/flow-namespace.js +22 -0
- package/dist/core/flow-namespace.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/AnthropicProvider.d.ts +10 -3
- package/dist/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/providers/AnthropicProvider.js +56 -75
- package/dist/providers/AnthropicProvider.js.map +1 -1
- package/dist/providers/GeminiProvider.d.ts +9 -3
- package/dist/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/providers/GeminiProvider.js +26 -70
- package/dist/providers/GeminiProvider.js.map +1 -1
- package/dist/providers/OpenAICompatibleProvider.d.ts +9 -0
- package/dist/providers/OpenAICompatibleProvider.d.ts.map +1 -1
- package/dist/providers/OpenAICompatibleProvider.js +103 -76
- package/dist/providers/OpenAICompatibleProvider.js.map +1 -1
- package/dist/providers/OpenAIProvider.d.ts +1 -1
- package/dist/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/providers/OpenAIProvider.js +1 -1
- package/dist/providers/errorClassification.d.ts +7 -0
- package/dist/providers/errorClassification.d.ts.map +1 -1
- package/dist/providers/errorClassification.js +26 -0
- package/dist/providers/errorClassification.js.map +1 -1
- package/dist/types/agent.d.ts +35 -0
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/flow.d.ts +49 -32
- package/dist/types/flow.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/tool.d.ts +2 -0
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/types/tool.js.map +1 -1
- package/dist/utils/index.d.ts +4 -4
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -3
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/retry.d.ts +99 -1
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +137 -3
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/serialize.d.ts +17 -0
- package/dist/utils/serialize.d.ts.map +1 -1
- package/dist/utils/serialize.js +31 -0
- package/dist/utils/serialize.js.map +1 -1
- package/dist/utils/session.d.ts +21 -1
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +33 -4
- package/dist/utils/session.js.map +1 -1
- package/docs/concepts/architecture.md +3 -3
- package/docs/concepts/directives.md +1 -1
- package/docs/guides/error-handling.md +46 -45
- package/docs/guides/flow-control.md +8 -1
- package/docs/guides/instructions.md +15 -6
- package/docs/guides/persistence.md +12 -5
- package/docs/guides/streaming.md +10 -0
- package/docs/migration/README.md +4 -0
- package/docs/migration/v2-3-to-v2-4.md +4 -0
- package/docs/migration/v2-6-to-v2-7.md +246 -0
- package/docs/reference/adapters.md +15 -1
- package/docs/reference/create-agent.md +30 -0
- package/docs/reference/directive.md +1 -1
- package/docs/reference/errors.md +29 -31
- package/docs/reference/providers.md +23 -19
- package/docs/reference/step.md +28 -21
- package/docs/reference/tool.md +14 -5
- package/docs/start/02-first-agent.md +8 -4
- package/docs/start/03-collect-data.md +19 -10
- package/examples/01-quickstart.ts +1 -1
- package/examples/02-data-extraction.ts +1 -1
- package/examples/03-tools.ts +1 -1
- package/examples/04-instructions.ts +1 -1
- package/examples/05-branching.ts +1 -1
- package/examples/06-flow-control.ts +3 -3
- package/examples/07-streaming.ts +1 -1
- package/examples/08-persistence.ts +1 -1
- package/examples/09-signals.ts +1 -1
- package/package.json +2 -2
- package/src/adapters/MemoryAdapter.ts +29 -33
- package/src/adapters/PostgreSQLAdapter.ts +23 -18
- package/src/adapters/RedisAdapter.ts +81 -34
- package/src/adapters/SQLiteAdapter.ts +10 -31
- package/src/adapters/sessionRow.ts +57 -0
- package/src/core/Agent.ts +51 -3
- package/src/core/AutoChainExecutor.ts +37 -50
- package/src/core/CompactionEngine.ts +40 -6
- package/src/core/FlowRouter.ts +38 -12
- package/src/core/ResponseGenerationError.ts +3 -6
- package/src/core/ResponseModal.ts +185 -19
- package/src/core/ResponsePipeline.ts +259 -85
- package/src/core/SessionFinalizer.ts +35 -5
- package/src/core/SessionManager.ts +55 -21
- package/src/core/SignalProcessor.ts +5 -85
- package/src/core/Step.ts +79 -2
- package/src/core/StepLifecycle.ts +125 -22
- package/src/core/StreamingToolExecutor.ts +31 -6
- package/src/core/ToolLoopExecutor.ts +201 -75
- package/src/core/ToolManager.ts +45 -18
- package/src/core/flow-namespace.ts +29 -0
- package/src/index.ts +4 -1
- package/src/providers/AnthropicProvider.ts +83 -131
- package/src/providers/GeminiProvider.ts +42 -116
- package/src/providers/OpenAICompatibleProvider.ts +132 -133
- package/src/providers/OpenAIProvider.ts +2 -2
- package/src/providers/errorClassification.ts +34 -0
- package/src/types/agent.ts +34 -0
- package/src/types/flow.ts +37 -39
- package/src/types/index.ts +1 -0
- package/src/types/tool.ts +2 -0
- package/src/utils/index.ts +22 -3
- package/src/utils/retry.ts +251 -2
- package/src/utils/serialize.ts +38 -0
- package/src/utils/session.ts +41 -4
- package/dist/cjs/core/DirectiveBus.d.ts +0 -88
- package/dist/cjs/core/DirectiveBus.d.ts.map +0 -1
- package/dist/cjs/core/DirectiveBus.js +0 -196
- package/dist/cjs/core/DirectiveBus.js.map +0 -1
- package/dist/core/DirectiveBus.d.ts +0 -88
- package/dist/core/DirectiveBus.d.ts.map +0 -1
- package/dist/core/DirectiveBus.js +0 -192
- package/dist/core/DirectiveBus.js.map +0 -1
- 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
|
-
|
|
320
|
-
|
|
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:
|
|
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
|
|
703
|
-
// one of those fields, skip FlowRouter.decideFlowAndStep
|
|
704
|
-
|
|
705
|
-
|
|
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
|
-
...
|
|
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:
|
|
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
|
-
...
|
|
728
|
-
session:
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
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
|
|
1007
|
-
* When the current step declares `collect` fields AND
|
|
1008
|
-
* at least one of those fields from the user's message,
|
|
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
|
-
*
|
|
1011
|
-
*
|
|
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
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 = { ...
|
|
1187
|
+
const dataBefore = { ...sessionRecord } as Record<string, unknown>;
|
|
1056
1188
|
|
|
1057
|
-
//
|
|
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
|
|
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
|
|
1074
|
-
const hadValue =
|
|
1075
|
-
|
|
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
|
|
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
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
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
|
|
1150
|
-
if (!
|
|
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 (
|
|
1170
|
-
extractionPrompt.push(`
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
|
|
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(
|