@ariaflowagents/core 0.8.1 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capabilities/AutoRetrieveCapability.d.ts +30 -0
- package/dist/capabilities/AutoRetrieveCapability.d.ts.map +1 -0
- package/dist/capabilities/AutoRetrieveCapability.js +36 -0
- package/dist/capabilities/AutoRetrieveCapability.js.map +1 -0
- package/dist/capabilities/ExtractionCapability.d.ts +25 -0
- package/dist/capabilities/ExtractionCapability.d.ts.map +1 -0
- package/dist/capabilities/ExtractionCapability.js +74 -0
- package/dist/capabilities/ExtractionCapability.js.map +1 -0
- package/dist/capabilities/FlowCapability.d.ts +81 -0
- package/dist/capabilities/FlowCapability.d.ts.map +1 -0
- package/dist/capabilities/FlowCapability.js +482 -0
- package/dist/capabilities/FlowCapability.js.map +1 -0
- package/dist/capabilities/GuardrailCapability.d.ts +30 -0
- package/dist/capabilities/GuardrailCapability.d.ts.map +1 -0
- package/dist/capabilities/GuardrailCapability.js +38 -0
- package/dist/capabilities/GuardrailCapability.js.map +1 -0
- package/dist/capabilities/HandoffCapability.d.ts +19 -0
- package/dist/capabilities/HandoffCapability.d.ts.map +1 -0
- package/dist/capabilities/HandoffCapability.js +58 -0
- package/dist/capabilities/HandoffCapability.js.map +1 -0
- package/dist/capabilities/LivePromptAssembler.d.ts +108 -0
- package/dist/capabilities/LivePromptAssembler.d.ts.map +1 -0
- package/dist/capabilities/LivePromptAssembler.js +157 -0
- package/dist/capabilities/LivePromptAssembler.js.map +1 -0
- package/dist/capabilities/TriageCapability.d.ts +16 -0
- package/dist/capabilities/TriageCapability.d.ts.map +1 -0
- package/dist/capabilities/TriageCapability.js +61 -0
- package/dist/capabilities/TriageCapability.js.map +1 -0
- package/dist/capabilities/adapters/ai-sdk.d.ts +14 -0
- package/dist/capabilities/adapters/ai-sdk.d.ts.map +1 -0
- package/dist/capabilities/adapters/ai-sdk.js +29 -0
- package/dist/capabilities/adapters/ai-sdk.js.map +1 -0
- package/dist/capabilities/adapters/gemini.d.ts +15 -0
- package/dist/capabilities/adapters/gemini.d.ts.map +1 -0
- package/dist/capabilities/adapters/gemini.js +40 -0
- package/dist/capabilities/adapters/gemini.js.map +1 -0
- package/dist/capabilities/index.d.ts +154 -0
- package/dist/capabilities/index.d.ts.map +1 -0
- package/dist/capabilities/index.js +128 -0
- package/dist/capabilities/index.js.map +1 -0
- package/dist/eval/EvalRunner.d.ts +12 -0
- package/dist/eval/EvalRunner.d.ts.map +1 -0
- package/dist/eval/EvalRunner.js +64 -0
- package/dist/eval/EvalRunner.js.map +1 -0
- package/dist/eval/scoring.d.ts +15 -0
- package/dist/eval/scoring.d.ts.map +1 -0
- package/dist/eval/scoring.js +152 -0
- package/dist/eval/scoring.js.map +1 -0
- package/dist/eval/types.d.ts +59 -0
- package/dist/eval/types.d.ts.map +1 -0
- package/dist/eval/types.js +2 -0
- package/dist/eval/types.js.map +1 -0
- package/dist/flows/FlowGraph.d.ts +3 -1
- package/dist/flows/FlowGraph.d.ts.map +1 -1
- package/dist/flows/FlowGraph.js +5 -0
- package/dist/flows/FlowGraph.js.map +1 -1
- package/dist/flows/FlowManager.d.ts +60 -1
- package/dist/flows/FlowManager.d.ts.map +1 -1
- package/dist/flows/FlowManager.js +467 -34
- package/dist/flows/FlowManager.js.map +1 -1
- package/dist/flows/extraction.d.ts +16 -1
- package/dist/flows/extraction.d.ts.map +1 -1
- package/dist/flows/extraction.js +34 -0
- package/dist/flows/extraction.js.map +1 -1
- package/dist/flows/index.d.ts +2 -0
- package/dist/flows/index.d.ts.map +1 -1
- package/dist/flows/index.js +1 -0
- package/dist/flows/index.js.map +1 -1
- package/dist/flows/validation.d.ts +1 -1
- package/dist/flows/validation.d.ts.map +1 -1
- package/dist/flows/validation.js +13 -1
- package/dist/flows/validation.js.map +1 -1
- package/dist/hooks/HookRunner.d.ts +3 -1
- package/dist/hooks/HookRunner.d.ts.map +1 -1
- package/dist/hooks/HookRunner.js +3 -0
- package/dist/hooks/HookRunner.js.map +1 -1
- package/dist/hooks/builtin/metrics.d.ts.map +1 -1
- package/dist/hooks/builtin/metrics.js +12 -0
- package/dist/hooks/builtin/metrics.js.map +1 -1
- package/dist/hooks/builtin/observability.d.ts +21 -0
- package/dist/hooks/builtin/observability.d.ts.map +1 -0
- package/dist/hooks/builtin/observability.js +535 -0
- package/dist/hooks/builtin/observability.js.map +1 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/orchestration/DefaultOrchestrationAuthority.d.ts +91 -0
- package/dist/orchestration/DefaultOrchestrationAuthority.d.ts.map +1 -0
- package/dist/orchestration/DefaultOrchestrationAuthority.js +786 -0
- package/dist/orchestration/DefaultOrchestrationAuthority.js.map +1 -0
- package/dist/orchestration/OrchestrationAuthority.d.ts +119 -0
- package/dist/orchestration/OrchestrationAuthority.d.ts.map +1 -0
- package/dist/orchestration/OrchestrationAuthority.js +2 -0
- package/dist/orchestration/OrchestrationAuthority.js.map +1 -0
- package/dist/orchestration/RealtimeExtractionRunner.d.ts +25 -0
- package/dist/orchestration/RealtimeExtractionRunner.d.ts.map +1 -0
- package/dist/orchestration/RealtimeExtractionRunner.js +62 -0
- package/dist/orchestration/RealtimeExtractionRunner.js.map +1 -0
- package/dist/orchestration/index.d.ts +5 -0
- package/dist/orchestration/index.d.ts.map +1 -0
- package/dist/orchestration/index.js +4 -0
- package/dist/orchestration/index.js.map +1 -0
- package/dist/orchestration/types.d.ts +134 -0
- package/dist/orchestration/types.d.ts.map +1 -0
- package/dist/orchestration/types.js +2 -0
- package/dist/orchestration/types.js.map +1 -0
- package/dist/realtime/RealtimeAudioClient.d.ts +105 -0
- package/dist/realtime/RealtimeAudioClient.d.ts.map +1 -0
- package/dist/realtime/RealtimeAudioClient.js +15 -0
- package/dist/realtime/RealtimeAudioClient.js.map +1 -0
- package/dist/realtime/RealtimeRuntime.d.ts +136 -0
- package/dist/realtime/RealtimeRuntime.d.ts.map +1 -0
- package/dist/realtime/RealtimeRuntime.js +270 -0
- package/dist/realtime/RealtimeRuntime.js.map +1 -0
- package/dist/realtime/index.d.ts +4 -0
- package/dist/realtime/index.d.ts.map +1 -0
- package/dist/realtime/index.js +2 -0
- package/dist/realtime/index.js.map +1 -0
- package/dist/runtime/ExtractionEngine.d.ts +2 -1
- package/dist/runtime/ExtractionEngine.d.ts.map +1 -1
- package/dist/runtime/ExtractionEngine.js +11 -0
- package/dist/runtime/ExtractionEngine.js.map +1 -1
- package/dist/runtime/FlowExecutor.d.ts +7 -5
- package/dist/runtime/FlowExecutor.d.ts.map +1 -1
- package/dist/runtime/FlowExecutor.js +71 -12
- package/dist/runtime/FlowExecutor.js.map +1 -1
- package/dist/runtime/Runtime.d.ts +22 -0
- package/dist/runtime/Runtime.d.ts.map +1 -1
- package/dist/runtime/Runtime.js +47 -0
- package/dist/runtime/Runtime.js.map +1 -1
- package/dist/runtime/pipeline/AgentExecuteStage.d.ts.map +1 -1
- package/dist/runtime/pipeline/AgentExecuteStage.js +94 -25
- package/dist/runtime/pipeline/AgentExecuteStage.js.map +1 -1
- package/dist/runtime/pipeline/ContextAssembleStage.js +1 -1
- package/dist/types/index.d.ts +61 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +4 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/telemetry.d.ts +107 -0
- package/dist/types/telemetry.d.ts.map +1 -1
- package/package.json +15 -2
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { streamText, generateText, tool } from 'ai';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { isExtractionNode, } from '../types/index.js';
|
|
3
4
|
import { createFlowTransition, isFlowTransition, isFlowUpdate } from './transitions.js';
|
|
5
|
+
import { extractStructuredFields, mergeExtractionData, computeMissingFields, toNullableSchema } from './extraction.js';
|
|
4
6
|
import { validateFlowConfig } from './validation.js';
|
|
5
7
|
import { isHandoffResult } from '../tools/handoff.js';
|
|
6
8
|
import { isFinalResult } from '../tools/final.js';
|
|
7
9
|
import { compileSanitizePattern, renderNodePrompt } from './template.js';
|
|
8
10
|
import { normalizeModelMessage } from '../utils/messageNormalization.js';
|
|
9
11
|
import { processAIStream } from '../utils/aiStream.js';
|
|
12
|
+
import { FlowCapability } from '../capabilities/FlowCapability.js';
|
|
10
13
|
const implicitTransitionToolInputSchema = z.object({
|
|
11
14
|
data: z.record(z.unknown()).optional(),
|
|
12
15
|
message: z.string().optional(),
|
|
@@ -24,6 +27,13 @@ export class FlowManager {
|
|
|
24
27
|
deferredActions = [];
|
|
25
28
|
flowEnded = false;
|
|
26
29
|
sessionMessages;
|
|
30
|
+
/**
|
|
31
|
+
* Headless FlowCapability used to share state-management code with
|
|
32
|
+
* CapabilityCallWorker. FlowManager rebuilds this lazily from its own
|
|
33
|
+
* authoritative state (context/initialized/flowEnded) whenever a getter
|
|
34
|
+
* or resolveTools() needs it — keeping streaming logic untouched.
|
|
35
|
+
*/
|
|
36
|
+
flowCapability;
|
|
27
37
|
/**
|
|
28
38
|
* Tracks transition edges (from->to) within a single process() call.
|
|
29
39
|
* Reset at the start of each user turn. Used to detect oscillation
|
|
@@ -32,6 +42,18 @@ export class FlowManager {
|
|
|
32
42
|
turnTransitionCounts = new Map();
|
|
33
43
|
/** Maximum times the same from->to edge can fire in one turn before being blocked. */
|
|
34
44
|
maxOscillations;
|
|
45
|
+
pendingMetrics = [];
|
|
46
|
+
emitMetric(name, data) {
|
|
47
|
+
this.pendingMetrics.push({ name, data });
|
|
48
|
+
this.config.metricsEmitter?.(name, data);
|
|
49
|
+
}
|
|
50
|
+
/** Drain queued metrics as custom stream events. Call from any generator. */
|
|
51
|
+
*drainMetrics() {
|
|
52
|
+
while (this.pendingMetrics.length > 0) {
|
|
53
|
+
const metric = this.pendingMetrics.shift();
|
|
54
|
+
yield { type: 'custom', name: metric.name, data: metric.data, timestamp: new Date() };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
35
57
|
constructor(config) {
|
|
36
58
|
this.config = config;
|
|
37
59
|
validateFlowConfig(config.flow, config.initialNode);
|
|
@@ -72,6 +94,7 @@ export class FlowManager {
|
|
|
72
94
|
yield* this.runInference(false);
|
|
73
95
|
}
|
|
74
96
|
yield* this.flushDeferredActions();
|
|
97
|
+
yield* this.drainMetrics();
|
|
75
98
|
}
|
|
76
99
|
async *process(userInput) {
|
|
77
100
|
if (!this.initialized) {
|
|
@@ -96,6 +119,7 @@ export class FlowManager {
|
|
|
96
119
|
}
|
|
97
120
|
yield* this.runInference(true);
|
|
98
121
|
yield* this.flushDeferredActions();
|
|
122
|
+
yield* this.drainMetrics();
|
|
99
123
|
}
|
|
100
124
|
async transitionTo(nodeId, data) {
|
|
101
125
|
for await (const _part of this.transitionToGenerator(nodeId, data)) {
|
|
@@ -108,6 +132,7 @@ export class FlowManager {
|
|
|
108
132
|
}
|
|
109
133
|
}
|
|
110
134
|
async *transitionToGenerator(nodeId, data, dynamicNode) {
|
|
135
|
+
const transitionStart = Date.now();
|
|
111
136
|
if (!this.nodes.has(nodeId) && dynamicNode) {
|
|
112
137
|
this.nodes.set(nodeId, dynamicNode);
|
|
113
138
|
}
|
|
@@ -133,6 +158,16 @@ export class FlowManager {
|
|
|
133
158
|
return;
|
|
134
159
|
}
|
|
135
160
|
}
|
|
161
|
+
// Output contract validation: validate exiting node's outputSchema
|
|
162
|
+
if (previousNode?.outputSchema) {
|
|
163
|
+
const result = previousNode.outputSchema.safeParse(this.context.collectedData);
|
|
164
|
+
if (!result.success) {
|
|
165
|
+
this.emitMetric('flow.contract.validation_fail', { nodeId: previousNode.id, direction: 'output' });
|
|
166
|
+
yield { type: 'error', error: `Output contract violation on node "${previousNode.id}": ${result.error.message}` };
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
this.emitMetric('flow.contract.validation_pass', { nodeId: previousNode.id, direction: 'output' });
|
|
170
|
+
}
|
|
136
171
|
if (previousNode) {
|
|
137
172
|
yield* this.runActions(previousNode.postActions ?? []);
|
|
138
173
|
if (this.flowEnded) {
|
|
@@ -147,6 +182,16 @@ export class FlowManager {
|
|
|
147
182
|
Object.assign(this.context.collectedData, data);
|
|
148
183
|
}
|
|
149
184
|
await this.applyContextStrategy(nextNode);
|
|
185
|
+
// Input contract validation: validate entering node's inputSchema
|
|
186
|
+
if (nextNode.inputSchema) {
|
|
187
|
+
const result = nextNode.inputSchema.safeParse(this.context.collectedData);
|
|
188
|
+
if (!result.success) {
|
|
189
|
+
this.emitMetric('flow.contract.validation_fail', { nodeId: nextNode.id, direction: 'input' });
|
|
190
|
+
yield { type: 'error', error: `Input contract violation on node "${nextNode.id}": ${result.error.message}` };
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
this.emitMetric('flow.contract.validation_pass', { nodeId: nextNode.id, direction: 'input' });
|
|
194
|
+
}
|
|
150
195
|
yield { type: 'node-enter', nodeName: nextNode.name ?? nextNode.id };
|
|
151
196
|
yield* this.runActions(nextNode.preActions ?? []);
|
|
152
197
|
if (this.flowEnded) {
|
|
@@ -155,28 +200,59 @@ export class FlowManager {
|
|
|
155
200
|
if (previousNode) {
|
|
156
201
|
yield { type: 'flow-transition', from: previousNode.id, to: nextNode.id };
|
|
157
202
|
}
|
|
203
|
+
this.emitMetric('flow.transition.duration', {
|
|
204
|
+
durationMs: Date.now() - transitionStart,
|
|
205
|
+
from: previousNode?.id ?? '__init__',
|
|
206
|
+
to: nodeId,
|
|
207
|
+
});
|
|
158
208
|
}
|
|
159
209
|
get collectedData() {
|
|
160
|
-
return this.
|
|
210
|
+
return this.rebuildCapability().collectedData;
|
|
161
211
|
}
|
|
162
212
|
get currentNode() {
|
|
163
|
-
return this.
|
|
213
|
+
return this.rebuildCapability().currentNode;
|
|
164
214
|
}
|
|
165
215
|
get hasEnded() {
|
|
166
|
-
return this.
|
|
216
|
+
return this.rebuildCapability().hasEnded;
|
|
167
217
|
}
|
|
168
218
|
getState() {
|
|
219
|
+
const capState = this.rebuildCapability().getState();
|
|
169
220
|
return {
|
|
221
|
+
context: capState.context,
|
|
222
|
+
initialized: capState.initialized,
|
|
223
|
+
flowEnded: capState.flowEnded,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Build a FlowCapability snapshot from FlowManager's current authoritative state.
|
|
228
|
+
* FlowManager owns transitions/streaming; FlowCapability owns state-query logic.
|
|
229
|
+
* Rebuilding on each call is cheap (pure in-memory graph traversal, no I/O).
|
|
230
|
+
*/
|
|
231
|
+
rebuildCapability() {
|
|
232
|
+
const state = {
|
|
170
233
|
context: this.context,
|
|
171
234
|
initialized: this.initialized,
|
|
172
235
|
flowEnded: this.flowEnded,
|
|
173
236
|
};
|
|
237
|
+
this.flowCapability = new FlowCapability({
|
|
238
|
+
flow: this.config.flow,
|
|
239
|
+
initialNode: this.config.initialNode,
|
|
240
|
+
defaultRolePrompt: this.config.defaultRolePrompt,
|
|
241
|
+
state,
|
|
242
|
+
});
|
|
243
|
+
return this.flowCapability;
|
|
174
244
|
}
|
|
175
245
|
async *runInference(triggeredByUserTurn) {
|
|
176
246
|
if (this.flowEnded || !this.currentNodeConfig) {
|
|
177
247
|
return;
|
|
178
248
|
}
|
|
249
|
+
const inferenceStart = Date.now();
|
|
179
250
|
const node = this.currentNodeConfig;
|
|
251
|
+
// Delegate to extraction node handler if applicable
|
|
252
|
+
if (isExtractionNode(node)) {
|
|
253
|
+
yield* this.runExtractionNodeInference(node, triggeredByUserTurn);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
180
256
|
const systemPrompt = this.buildSystemPrompt(node);
|
|
181
257
|
const tools = this.resolveTools(node);
|
|
182
258
|
const maxSteps = node.maxSteps ?? this.config.maxSteps ?? this.config.flow.maxSteps ?? 25;
|
|
@@ -207,12 +283,21 @@ export class FlowManager {
|
|
|
207
283
|
let finalEmitted = false;
|
|
208
284
|
let hadToolResult = false;
|
|
209
285
|
let pendingToolMessage = null;
|
|
286
|
+
let ttftEmitted = false;
|
|
210
287
|
for await (const part of processAIStream(result.fullStream)) {
|
|
211
288
|
// In buffered mode, hold text-delta tokens until we decide whether to emit.
|
|
212
289
|
// This allows transition/handoff turns to stay silent in routing nodes.
|
|
213
290
|
if (!(shouldBuffer && part.type === 'text-delta')) {
|
|
214
291
|
yield part;
|
|
215
292
|
}
|
|
293
|
+
// Emit TTFT metric on first text-delta
|
|
294
|
+
if (part.type === 'text-delta' && !ttftEmitted) {
|
|
295
|
+
ttftEmitted = true;
|
|
296
|
+
this.emitMetric('flow.inference.ttft', {
|
|
297
|
+
durationMs: Date.now() - inferenceStart,
|
|
298
|
+
nodeId: node.id,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
216
301
|
if (part.type === 'text-delta') {
|
|
217
302
|
if (finalResult) {
|
|
218
303
|
continue;
|
|
@@ -274,6 +359,20 @@ export class FlowManager {
|
|
|
274
359
|
}
|
|
275
360
|
}
|
|
276
361
|
const response = await result.response;
|
|
362
|
+
// Emit inference duration and cache metrics
|
|
363
|
+
this.emitMetric('flow.inference.duration', {
|
|
364
|
+
durationMs: Date.now() - inferenceStart,
|
|
365
|
+
nodeId: node.id,
|
|
366
|
+
});
|
|
367
|
+
const anthropicMeta = response.providerMetadata?.anthropic;
|
|
368
|
+
if (anthropicMeta) {
|
|
369
|
+
if (typeof anthropicMeta.cacheReadInputTokens === 'number' && anthropicMeta.cacheReadInputTokens > 0) {
|
|
370
|
+
this.emitMetric('flow.cache.hit_tokens', { tokens: anthropicMeta.cacheReadInputTokens, nodeId: node.id });
|
|
371
|
+
}
|
|
372
|
+
if (typeof anthropicMeta.cacheCreationInputTokens === 'number' && anthropicMeta.cacheCreationInputTokens > 0) {
|
|
373
|
+
this.emitMetric('flow.cache.miss_tokens', { tokens: anthropicMeta.cacheCreationInputTokens, nodeId: node.id });
|
|
374
|
+
}
|
|
375
|
+
}
|
|
277
376
|
let safeText = responseText;
|
|
278
377
|
if (shouldBuffer && node.output?.sanitize?.pattern && node.output?.sanitize?.message) {
|
|
279
378
|
const re = compileSanitizePattern(node.output.sanitize.pattern);
|
|
@@ -370,15 +469,25 @@ export class FlowManager {
|
|
|
370
469
|
yield { type: 'flow-end', reason: 'post-action' };
|
|
371
470
|
}
|
|
372
471
|
}
|
|
472
|
+
/**
|
|
473
|
+
* Builds the system prompt as an array of SystemModelMessage objects.
|
|
474
|
+
* Layer 1 (role prompt) is marked with Anthropic cache_control for prompt caching.
|
|
475
|
+
* Uses AI SDK SystemModelMessage format: { role: 'system', content: string, providerOptions? }.
|
|
476
|
+
*/
|
|
373
477
|
buildSystemPrompt(node) {
|
|
374
478
|
const includeGlobalPrompt = node.addGlobalPrompt !== false;
|
|
375
479
|
const rolePrompt = includeGlobalPrompt
|
|
376
480
|
? (this.config.defaultRolePrompt ?? this.config.flow.defaultRolePrompt ?? '')
|
|
377
481
|
: '';
|
|
378
482
|
const renderedPrompt = renderNodePrompt(node.prompt, this.context);
|
|
379
|
-
|
|
483
|
+
// Filter collectedData to relevantFields if specified (Phase 3: selective data inclusion)
|
|
484
|
+
const allData = this.context.collectedData;
|
|
485
|
+
const filteredData = node.relevantFields
|
|
486
|
+
? Object.fromEntries(Object.entries(allData).filter(([k]) => node.relevantFields.includes(k)))
|
|
487
|
+
: allData;
|
|
488
|
+
const dataKeys = Object.keys(filteredData);
|
|
380
489
|
const dataContext = dataKeys.length > 0
|
|
381
|
-
? `\n\n## Collected Information\n${JSON.stringify(
|
|
490
|
+
? `\n\n## Collected Information\n${JSON.stringify(filteredData, null, 2)}`
|
|
382
491
|
: '';
|
|
383
492
|
const historyContext = this.context.nodeHistory.length > 1
|
|
384
493
|
? `\n\n## Progress\n${this.context.nodeHistory.slice(0, -1).join(' -> ')} -> **${node.name ?? node.id}**`
|
|
@@ -389,13 +498,22 @@ ${renderedPrompt}${dataContext}${historyContext}`;
|
|
|
389
498
|
- Focus only on the current task
|
|
390
499
|
- Use the available tools to progress the conversation
|
|
391
500
|
- Do not attempt tasks outside your current scope`;
|
|
392
|
-
const
|
|
501
|
+
const blocks = [];
|
|
502
|
+
// Layer 1: Role prompt (cacheable -- stable across all turns and nodes)
|
|
393
503
|
if (rolePrompt.trim().length > 0) {
|
|
394
|
-
|
|
504
|
+
blocks.push({
|
|
505
|
+
role: 'system',
|
|
506
|
+
content: rolePrompt.trim(),
|
|
507
|
+
providerOptions: {
|
|
508
|
+
anthropic: { cacheControl: { type: 'ephemeral' } },
|
|
509
|
+
},
|
|
510
|
+
});
|
|
395
511
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
512
|
+
// Layer 2: Node-specific task context (changes per node)
|
|
513
|
+
blocks.push({ role: 'system', content: currentTaskSection });
|
|
514
|
+
// Layer 3: Fixed instructions
|
|
515
|
+
blocks.push({ role: 'system', content: instructionsSection });
|
|
516
|
+
return blocks;
|
|
399
517
|
}
|
|
400
518
|
async applyContextStrategy(node) {
|
|
401
519
|
const strategy = node.contextStrategy ?? this.config.flow.contextStrategy ?? 'append';
|
|
@@ -419,6 +537,7 @@ ${renderedPrompt}${dataContext}${historyContext}`;
|
|
|
419
537
|
}
|
|
420
538
|
}
|
|
421
539
|
async generateSummary(node) {
|
|
540
|
+
const summaryStart = Date.now();
|
|
422
541
|
const prompt = node.summaryPrompt
|
|
423
542
|
?? this.config.flow.summaryPrompt
|
|
424
543
|
?? 'Summarize the key points from this conversation in 2-3 sentences.';
|
|
@@ -436,20 +555,33 @@ ${renderedPrompt}${dataContext}${historyContext}`;
|
|
|
436
555
|
});
|
|
437
556
|
return result.text;
|
|
438
557
|
};
|
|
558
|
+
let summary;
|
|
439
559
|
if (timeoutMs <= 0) {
|
|
440
|
-
|
|
560
|
+
summary = await generate();
|
|
441
561
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
562
|
+
else {
|
|
563
|
+
let timeoutHandle;
|
|
564
|
+
const timeout = new Promise(resolve => {
|
|
565
|
+
timeoutHandle = setTimeout(() => resolve(null), timeoutMs);
|
|
566
|
+
});
|
|
567
|
+
summary = await Promise.race([generate().catch(() => null), timeout]);
|
|
568
|
+
if (timeoutHandle) {
|
|
569
|
+
clearTimeout(timeoutHandle);
|
|
570
|
+
}
|
|
449
571
|
}
|
|
572
|
+
this.emitMetric('flow.summary.duration', {
|
|
573
|
+
durationMs: Date.now() - summaryStart,
|
|
574
|
+
nodeId: node.id,
|
|
575
|
+
timedOut: summary === null,
|
|
576
|
+
});
|
|
450
577
|
return summary;
|
|
451
578
|
}
|
|
452
579
|
catch {
|
|
580
|
+
this.emitMetric('flow.summary.duration', {
|
|
581
|
+
durationMs: Date.now() - summaryStart,
|
|
582
|
+
nodeId: node.id,
|
|
583
|
+
error: true,
|
|
584
|
+
});
|
|
453
585
|
return null;
|
|
454
586
|
}
|
|
455
587
|
}
|
|
@@ -508,23 +640,26 @@ ${renderedPrompt}${dataContext}${historyContext}`;
|
|
|
508
640
|
},
|
|
509
641
|
};
|
|
510
642
|
}
|
|
643
|
+
// When multiple transitions are requested in the same turn, take the first one.
|
|
644
|
+
// The LLM calls tools in order of intent -- the first tool call is the primary action.
|
|
645
|
+
// Conflicting targets or payloads are warned, not errored, to avoid leaving the user
|
|
646
|
+
// with no response. The first transition is the safest bet because it corresponds
|
|
647
|
+
// to the LLM's first (and usually correct) tool call.
|
|
511
648
|
const firstTransition = transitions[0];
|
|
512
649
|
for (let i = 1; i < transitions.length; i++) {
|
|
513
650
|
const next = transitions[i];
|
|
514
651
|
if (next.targetNode !== firstTransition.targetNode) {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
652
|
+
console.warn(`[AriaFlow] Conflicting transitions in the same turn: "${firstTransition.targetNode}" (tool: ${firstTransition.toolName}) ` +
|
|
653
|
+
`vs "${next.targetNode}" (tool: ${next.toolName}). Taking the first transition "${firstTransition.targetNode}".`);
|
|
654
|
+
break;
|
|
518
655
|
}
|
|
519
656
|
if (!this.isEquivalentPayload(next.data, firstTransition.data)) {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
};
|
|
657
|
+
console.warn(`[AriaFlow] Conflicting transition payloads for node "${firstTransition.targetNode}" in the same turn. Using the first payload.`);
|
|
658
|
+
break;
|
|
523
659
|
}
|
|
524
660
|
if ((next.node?.id ?? null) !== (firstTransition.node?.id ?? null)) {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
};
|
|
661
|
+
console.warn(`[AriaFlow] Conflicting dynamic node definitions for transition target "${firstTransition.targetNode}" in the same turn. Using the first definition.`);
|
|
662
|
+
break;
|
|
528
663
|
}
|
|
529
664
|
}
|
|
530
665
|
return {
|
|
@@ -536,13 +671,25 @@ ${renderedPrompt}${dataContext}${historyContext}`;
|
|
|
536
671
|
};
|
|
537
672
|
}
|
|
538
673
|
resolveTools(node) {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
674
|
+
// Resolve explicit node tools first so we know which names to exclude.
|
|
675
|
+
const explicitTools = node.tools
|
|
676
|
+
? (typeof node.tools === 'function' ? (node.tools(this.context) ?? {}) : node.tools)
|
|
677
|
+
: {};
|
|
678
|
+
const explicitNames = new Set(Object.keys(explicitTools));
|
|
679
|
+
// Use FlowCapability to generate implicit transition tool declarations.
|
|
680
|
+
// This shares tool-building logic with CapabilityCallWorker; FlowManager
|
|
681
|
+
// wraps each ToolDeclaration in the AI SDK tool() format for streamText.
|
|
682
|
+
const cap = this.rebuildCapability();
|
|
683
|
+
const implicitTools = {};
|
|
684
|
+
for (const decl of cap.getTools()) {
|
|
685
|
+
// Skip explicit node tools — they will be merged in below with higher priority.
|
|
686
|
+
if (explicitNames.has(decl.name))
|
|
687
|
+
continue;
|
|
688
|
+
implicitTools[decl.name] = tool({
|
|
689
|
+
description: decl.description,
|
|
690
|
+
inputSchema: decl.parameters,
|
|
691
|
+
execute: decl.execute,
|
|
692
|
+
});
|
|
546
693
|
}
|
|
547
694
|
// Explicit node tools win on name collision to preserve backwards compatibility.
|
|
548
695
|
return this.wrapTools({
|
|
@@ -742,6 +889,292 @@ ${renderedPrompt}${dataContext}${historyContext}`;
|
|
|
742
889
|
}
|
|
743
890
|
return {};
|
|
744
891
|
}
|
|
892
|
+
/**
|
|
893
|
+
* Extraction node inference: loops until a Zod schema is fully satisfied.
|
|
894
|
+
* Replaces both the ExtractionEngine's generateObject call and the standard
|
|
895
|
+
* streamText inference -- single LLM call per turn.
|
|
896
|
+
*
|
|
897
|
+
* Three key correctness properties:
|
|
898
|
+
* 1. safeParse uses nullified collectedData (undefined -> null) so nullable
|
|
899
|
+
* schema fields don't fail on absent keys.
|
|
900
|
+
* 2. The follow-up prompt only mentions REQUIRED missing fields, not optional ones.
|
|
901
|
+
* 3. On auto-transition, if the next node is also an extraction node, the last
|
|
902
|
+
* user message is re-extracted against the new schema (cross-node carry-forward).
|
|
903
|
+
*/
|
|
904
|
+
async *runExtractionNodeInference(node, triggeredByUserTurn) {
|
|
905
|
+
const extractionStart = Date.now();
|
|
906
|
+
const turnCount = this.context.nodeTurnCounts[node.id] ?? 0;
|
|
907
|
+
// Check if extraction is already complete from prior turns.
|
|
908
|
+
// Fill undefined schema keys with null so .nullable() fields pass safeParse.
|
|
909
|
+
if (this.isExtractionComplete(node)) {
|
|
910
|
+
this.emitMetric('flow.extraction.complete', { nodeId: node.id, turns: turnCount });
|
|
911
|
+
if (node.extractionCompleteTransition) {
|
|
912
|
+
yield* this.transitionToGenerator(node.extractionCompleteTransition, this.context.collectedData);
|
|
913
|
+
if (!this.flowEnded && this.currentNodeConfig) {
|
|
914
|
+
// Cross-node carry-forward: if the next node is also an extraction node,
|
|
915
|
+
// re-extract from the last user message against the new schema.
|
|
916
|
+
if (isExtractionNode(this.currentNodeConfig)) {
|
|
917
|
+
yield* this.runExtractionCarryForward(this.currentNodeConfig);
|
|
918
|
+
}
|
|
919
|
+
else if (this.currentNodeConfig.autoRespond !== false) {
|
|
920
|
+
yield* this.runInference(false);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
// Enforce max extraction turns
|
|
927
|
+
const maxTurns = node.extractionMaxTurns ?? 10;
|
|
928
|
+
if (turnCount > maxTurns) {
|
|
929
|
+
this.emitMetric('flow.extraction.max_turns_exceeded', { nodeId: node.id, turnCount });
|
|
930
|
+
yield { type: 'error', error: `Extraction node "${node.id}" exceeded max turns (${maxTurns})` };
|
|
931
|
+
if (node.extractionCompleteTransition) {
|
|
932
|
+
yield* this.transitionToGenerator(node.extractionCompleteTransition, this.context.collectedData);
|
|
933
|
+
if (!this.flowEnded && this.currentNodeConfig?.autoRespond !== false) {
|
|
934
|
+
yield* this.runInference(false);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
// Extract structured fields from the latest user message.
|
|
940
|
+
// Use a nullable version of the schema so the LLM can return null for
|
|
941
|
+
// fields not mentioned, instead of hallucinating values.
|
|
942
|
+
//
|
|
943
|
+
// Run extraction if:
|
|
944
|
+
// - This was triggered by a user turn (normal extraction loop), OR
|
|
945
|
+
// - This is the first entry to the extraction node (turnCount === 0)
|
|
946
|
+
// and there is a user message available (e.g., the user said something
|
|
947
|
+
// that triggered a transition from a non-extraction node).
|
|
948
|
+
const shouldExtract = (triggeredByUserTurn || turnCount === 0) && this.context.messages.length > 0;
|
|
949
|
+
if (shouldExtract) {
|
|
950
|
+
await this.extractFromLastUserMessage(node);
|
|
951
|
+
}
|
|
952
|
+
// Re-check after extraction
|
|
953
|
+
if (this.isExtractionComplete(node)) {
|
|
954
|
+
const reqFields = node.extractionRequiredFields ?? Object.keys(node.extractionSchema?.shape ?? {});
|
|
955
|
+
const doneCollected = {};
|
|
956
|
+
for (const key of reqFields) {
|
|
957
|
+
const v = this.context.collectedData[key];
|
|
958
|
+
if (v !== undefined && v !== null) {
|
|
959
|
+
doneCollected[key] = v;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
this.emitMetric('flow.extraction.update', {
|
|
963
|
+
nodeId: node.id,
|
|
964
|
+
collected: doneCollected,
|
|
965
|
+
missing: [],
|
|
966
|
+
});
|
|
967
|
+
this.emitMetric('flow.extraction.complete', { nodeId: node.id, turns: turnCount });
|
|
968
|
+
this.emitMetric('flow.extraction.duration', { durationMs: Date.now() - extractionStart, nodeId: node.id });
|
|
969
|
+
yield* this.drainMetrics();
|
|
970
|
+
if (node.extractionCompleteTransition) {
|
|
971
|
+
yield* this.transitionToGenerator(node.extractionCompleteTransition, this.context.collectedData);
|
|
972
|
+
if (!this.flowEnded && this.currentNodeConfig) {
|
|
973
|
+
if (isExtractionNode(this.currentNodeConfig)) {
|
|
974
|
+
yield* this.runExtractionCarryForward(this.currentNodeConfig);
|
|
975
|
+
}
|
|
976
|
+
else if (this.currentNodeConfig.autoRespond !== false) {
|
|
977
|
+
yield* this.runInference(false);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
// Compute missing REQUIRED fields only -- do not ask for optional fields.
|
|
984
|
+
const requiredFields = node.extractionRequiredFields
|
|
985
|
+
?? Object.keys(node.extractionSchema?.shape ?? {});
|
|
986
|
+
const missingFields = computeMissingFields(this.context.collectedData, requiredFields);
|
|
987
|
+
const collectedSnapshot = {};
|
|
988
|
+
for (const key of requiredFields) {
|
|
989
|
+
const v = this.context.collectedData[key];
|
|
990
|
+
if (v !== undefined && v !== null) {
|
|
991
|
+
collectedSnapshot[key] = v;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
this.emitMetric('flow.extraction.update', {
|
|
995
|
+
nodeId: node.id,
|
|
996
|
+
collected: collectedSnapshot,
|
|
997
|
+
missing: missingFields,
|
|
998
|
+
});
|
|
999
|
+
this.emitMetric('flow.extraction.fields.collected', {
|
|
1000
|
+
nodeId: node.id,
|
|
1001
|
+
total: requiredFields.length,
|
|
1002
|
+
collected: requiredFields.length - missingFields.length,
|
|
1003
|
+
missing: missingFields.length,
|
|
1004
|
+
});
|
|
1005
|
+
yield* this.drainMetrics();
|
|
1006
|
+
// If all REQUIRED fields are present but safeParse still fails (e.g., validation
|
|
1007
|
+
// errors like min length), check which fields have validation issues.
|
|
1008
|
+
if (missingFields.length === 0) {
|
|
1009
|
+
// All required fields exist but schema validation failed.
|
|
1010
|
+
// This means a field has an invalid value (wrong format, too short, etc.).
|
|
1011
|
+
// Auto-transition with what we have rather than looping forever.
|
|
1012
|
+
this.emitMetric('flow.extraction.complete', { nodeId: node.id, turns: turnCount });
|
|
1013
|
+
this.emitMetric('flow.extraction.duration', { durationMs: Date.now() - extractionStart, nodeId: node.id });
|
|
1014
|
+
if (node.extractionCompleteTransition) {
|
|
1015
|
+
yield* this.transitionToGenerator(node.extractionCompleteTransition, this.context.collectedData);
|
|
1016
|
+
if (!this.flowEnded && this.currentNodeConfig?.autoRespond !== false) {
|
|
1017
|
+
yield* this.runInference(false);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
// Generate follow-up prompt asking ONLY for required missing fields.
|
|
1023
|
+
const promptMode = node.extractionPromptMode ?? 'llm';
|
|
1024
|
+
if (promptMode === 'template') {
|
|
1025
|
+
const prompts = missingFields.map(field => node.extractionFieldPrompts?.[field] ?? `Could you please provide your ${field}?`);
|
|
1026
|
+
const followUpText = prompts.join(' ');
|
|
1027
|
+
yield { type: 'text-delta', text: followUpText };
|
|
1028
|
+
this.appendMessage({ role: 'assistant', content: followUpText });
|
|
1029
|
+
}
|
|
1030
|
+
else {
|
|
1031
|
+
const systemPrompt = this.buildSystemPrompt(node);
|
|
1032
|
+
const collectedSummary = Object.entries(this.context.collectedData)
|
|
1033
|
+
.filter(([, v]) => v !== null && v !== undefined)
|
|
1034
|
+
.map(([k, v]) => ` ${k}: ${v}`)
|
|
1035
|
+
.join('\n');
|
|
1036
|
+
const extractionContext = `\n\nYou are collecting required information. ` +
|
|
1037
|
+
`Already collected:\n${collectedSummary || ' (nothing yet)'}\n` +
|
|
1038
|
+
`Still needed: ${missingFields.join(', ')}.\n` +
|
|
1039
|
+
`Ask ONLY for the missing fields listed above. Do not ask for anything else. Be concise.`;
|
|
1040
|
+
const blocks = [...systemPrompt];
|
|
1041
|
+
if (blocks.length > 0) {
|
|
1042
|
+
blocks[blocks.length - 1] = {
|
|
1043
|
+
...blocks[blocks.length - 1],
|
|
1044
|
+
content: blocks[blocks.length - 1].content + extractionContext,
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
const streamOptions = {
|
|
1048
|
+
model: this.config.model,
|
|
1049
|
+
system: blocks,
|
|
1050
|
+
abortSignal: this.config.abortSignal,
|
|
1051
|
+
experimental_telemetry: this.config.telemetry,
|
|
1052
|
+
};
|
|
1053
|
+
if (this.context.messages.length === 0) {
|
|
1054
|
+
streamOptions.prompt = renderNodePrompt(node.prompt, this.context);
|
|
1055
|
+
}
|
|
1056
|
+
else {
|
|
1057
|
+
streamOptions.messages = this.context.messages;
|
|
1058
|
+
}
|
|
1059
|
+
const result = streamText(streamOptions);
|
|
1060
|
+
let responseText = '';
|
|
1061
|
+
for await (const part of processAIStream(result.fullStream)) {
|
|
1062
|
+
yield part;
|
|
1063
|
+
if (part.type === 'text-delta') {
|
|
1064
|
+
responseText += part.text;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
const response = await result.response;
|
|
1068
|
+
for (const message of response.messages) {
|
|
1069
|
+
this.appendMessage(message);
|
|
1070
|
+
}
|
|
1071
|
+
yield { type: 'turn-end', metadata: { response } };
|
|
1072
|
+
}
|
|
1073
|
+
this.emitMetric('flow.extraction.duration', {
|
|
1074
|
+
durationMs: Date.now() - extractionStart,
|
|
1075
|
+
nodeId: node.id,
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Check if extraction is complete by running safeParse with nullified data.
|
|
1080
|
+
* Fills undefined schema keys with null so .nullable() fields don't fail
|
|
1081
|
+
* on absent keys (undefined !== null in Zod).
|
|
1082
|
+
*/
|
|
1083
|
+
isExtractionComplete(node) {
|
|
1084
|
+
const dataForParse = { ...this.context.collectedData };
|
|
1085
|
+
const schemaShape = node.extractionSchema?.shape;
|
|
1086
|
+
if (schemaShape) {
|
|
1087
|
+
for (const key of Object.keys(schemaShape)) {
|
|
1088
|
+
if (!(key in dataForParse)) {
|
|
1089
|
+
dataForParse[key] = null;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
return node.extractionSchema.safeParse(dataForParse).success;
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Extract structured fields from the last user message against a node's schema.
|
|
1097
|
+
* Returns the number of NEW fields that were extracted (0 if nothing new).
|
|
1098
|
+
* Used both for normal extraction turns and cross-node carry-forward.
|
|
1099
|
+
*/
|
|
1100
|
+
async extractFromLastUserMessage(node) {
|
|
1101
|
+
try {
|
|
1102
|
+
const lastUserMsg = this.context.messages
|
|
1103
|
+
.slice()
|
|
1104
|
+
.reverse()
|
|
1105
|
+
.find((m) => m.role === 'user');
|
|
1106
|
+
const userInput = typeof lastUserMsg?.content === 'string' ? lastUserMsg.content : '';
|
|
1107
|
+
if (userInput.length > 0) {
|
|
1108
|
+
const nullableSchema = toNullableSchema(node.extractionSchema);
|
|
1109
|
+
const before = { ...this.context.collectedData };
|
|
1110
|
+
const extracted = await extractStructuredFields({
|
|
1111
|
+
model: this.config.model,
|
|
1112
|
+
schema: nullableSchema,
|
|
1113
|
+
userMessage: userInput,
|
|
1114
|
+
systemPrompt: node.extraction?.systemPrompt
|
|
1115
|
+
?? 'Extract only facts explicitly stated in the latest user message. Return null for any field not clearly provided. Do not infer or guess.',
|
|
1116
|
+
abortSignal: this.config.abortSignal,
|
|
1117
|
+
telemetry: this.config.telemetry,
|
|
1118
|
+
});
|
|
1119
|
+
const merged = mergeExtractionData(this.context.collectedData, extracted);
|
|
1120
|
+
// Count how many fields are genuinely new (not already in collectedData)
|
|
1121
|
+
let newFields = 0;
|
|
1122
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
1123
|
+
if (value !== null && value !== undefined && before[key] !== value) {
|
|
1124
|
+
newFields++;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
Object.assign(this.context.collectedData, merged);
|
|
1128
|
+
return newFields;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
catch {
|
|
1132
|
+
// Extraction failed; continue with what we have
|
|
1133
|
+
}
|
|
1134
|
+
return 0;
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Cross-node extraction carry-forward: when transitioning from one extraction
|
|
1138
|
+
* node to another, re-extract from the last user message against the new node's
|
|
1139
|
+
* schema. This handles the case where a user provides data for multiple schemas
|
|
1140
|
+
* in a single message (e.g., incident details AND vehicle details in one turn).
|
|
1141
|
+
*
|
|
1142
|
+
* Stops the cascade when zero new fields are extracted -- the user message has
|
|
1143
|
+
* no data for this schema, so further hops would waste LLM calls.
|
|
1144
|
+
*/
|
|
1145
|
+
async *runExtractionCarryForward(nextNode) {
|
|
1146
|
+
// Try to extract from the last user message against the new schema.
|
|
1147
|
+
const newFieldCount = await this.extractFromLastUserMessage(nextNode);
|
|
1148
|
+
// If we extracted nothing new, stop cascading. The user message has no data
|
|
1149
|
+
// for this schema. Ask the user for the missing information instead of
|
|
1150
|
+
// burning LLM calls on downstream nodes.
|
|
1151
|
+
if (newFieldCount === 0) {
|
|
1152
|
+
if (nextNode.autoRespond !== false) {
|
|
1153
|
+
yield* this.runInference(false);
|
|
1154
|
+
}
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
// If carry-forward satisfied the schema, continue the cascade.
|
|
1158
|
+
if (this.isExtractionComplete(nextNode)) {
|
|
1159
|
+
this.emitMetric('flow.extraction.complete', { nodeId: nextNode.id, turns: 0 });
|
|
1160
|
+
if (nextNode.extractionCompleteTransition) {
|
|
1161
|
+
yield* this.transitionToGenerator(nextNode.extractionCompleteTransition, this.context.collectedData);
|
|
1162
|
+
if (!this.flowEnded && this.currentNodeConfig) {
|
|
1163
|
+
if (isExtractionNode(this.currentNodeConfig)) {
|
|
1164
|
+
yield* this.runExtractionCarryForward(this.currentNodeConfig);
|
|
1165
|
+
}
|
|
1166
|
+
else if (this.currentNodeConfig.autoRespond !== false) {
|
|
1167
|
+
yield* this.runInference(false);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
// Extracted some fields but not enough -- ask for the rest.
|
|
1174
|
+
if (nextNode.autoRespond !== false) {
|
|
1175
|
+
yield* this.runInference(false);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
745
1178
|
}
|
|
746
1179
|
function isRecord(value) {
|
|
747
1180
|
return typeof value === 'object' && value !== null;
|