@ariaflowagents/core 0.6.3 → 0.7.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/README.md +58 -5
- package/dist/agents/Agent.d.ts +1 -0
- package/dist/agents/Agent.d.ts.map +1 -1
- package/dist/agents/Agent.js +10 -1
- package/dist/agents/Agent.js.map +1 -1
- package/dist/agents/CompositeAgent.d.ts +8 -12
- package/dist/agents/CompositeAgent.d.ts.map +1 -1
- package/dist/agents/CompositeAgent.js +30 -16
- package/dist/agents/CompositeAgent.js.map +1 -1
- package/dist/agents/FlowAgent.js +2 -2
- package/dist/agents/FlowAgent.js.map +1 -1
- package/dist/agents/LLMAgent.d.ts.map +1 -1
- package/dist/agents/LLMAgent.js +4 -25
- package/dist/agents/LLMAgent.js.map +1 -1
- package/dist/agents/TriageAgent.d.ts.map +1 -1
- package/dist/agents/TriageAgent.js +8 -29
- package/dist/agents/TriageAgent.js.map +1 -1
- package/dist/callbacks/httpCallback.d.ts +1 -0
- package/dist/callbacks/httpCallback.d.ts.map +1 -1
- package/dist/callbacks/httpCallback.js +20 -6
- package/dist/callbacks/httpCallback.js.map +1 -1
- package/dist/callbacks/streamCallback.d.ts +26 -0
- package/dist/callbacks/streamCallback.d.ts.map +1 -0
- package/dist/callbacks/streamCallback.js +281 -0
- package/dist/callbacks/streamCallback.js.map +1 -0
- package/dist/flows/FlowManager.d.ts +19 -4
- package/dist/flows/FlowManager.d.ts.map +1 -1
- package/dist/flows/FlowManager.js +360 -132
- package/dist/flows/FlowManager.js.map +1 -1
- package/dist/flows/extraction.d.ts +17 -0
- package/dist/flows/extraction.d.ts.map +1 -0
- package/dist/flows/extraction.js +56 -0
- package/dist/flows/extraction.js.map +1 -0
- package/dist/flows/index.d.ts +1 -2
- package/dist/flows/index.d.ts.map +1 -1
- package/dist/flows/index.js +1 -1
- package/dist/flows/index.js.map +1 -1
- package/dist/flows/template.d.ts +2 -2
- package/dist/flows/template.d.ts.map +1 -1
- package/dist/flows/template.js +13 -0
- package/dist/flows/template.js.map +1 -1
- package/dist/flows/validation.d.ts +7 -0
- package/dist/flows/validation.d.ts.map +1 -0
- package/dist/flows/validation.js +42 -0
- package/dist/flows/validation.js.map +1 -0
- package/dist/hooks/builtin/metrics.d.ts +4 -34
- package/dist/hooks/builtin/metrics.d.ts.map +1 -1
- package/dist/hooks/builtin/metrics.js +3 -65
- package/dist/hooks/builtin/metrics.js.map +1 -1
- package/dist/hooks/helpers.d.ts +8 -47
- package/dist/hooks/helpers.d.ts.map +1 -1
- package/dist/hooks/helpers.js +38 -104
- package/dist/hooks/helpers.js.map +1 -1
- package/dist/hooks/index.d.ts +4 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/processors/ProcessorRunner.d.ts.map +1 -1
- package/dist/processors/ProcessorRunner.js +13 -0
- package/dist/processors/ProcessorRunner.js.map +1 -1
- package/dist/prompts/PromptBuilder.d.ts.map +1 -1
- package/dist/prompts/PromptBuilder.js +1 -0
- package/dist/prompts/PromptBuilder.js.map +1 -1
- package/dist/prompts/types.d.ts +2 -1
- package/dist/prompts/types.d.ts.map +1 -1
- package/dist/prompts/types.js +29 -7
- package/dist/prompts/types.js.map +1 -1
- package/dist/runtime/ContextManager.d.ts +1 -1
- package/dist/runtime/ContextManager.d.ts.map +1 -1
- package/dist/runtime/ContextManager.js +141 -20
- package/dist/runtime/ContextManager.js.map +1 -1
- package/dist/runtime/ExtractionEngine.d.ts +34 -0
- package/dist/runtime/ExtractionEngine.d.ts.map +1 -0
- package/dist/runtime/ExtractionEngine.js +155 -0
- package/dist/runtime/ExtractionEngine.js.map +1 -0
- package/dist/runtime/FlowExecutor.d.ts +51 -0
- package/dist/runtime/FlowExecutor.d.ts.map +1 -0
- package/dist/runtime/FlowExecutor.js +523 -0
- package/dist/runtime/FlowExecutor.js.map +1 -0
- package/dist/runtime/InjectionQueue.d.ts +8 -1
- package/dist/runtime/InjectionQueue.d.ts.map +1 -1
- package/dist/runtime/InjectionQueue.js +33 -0
- package/dist/runtime/InjectionQueue.js.map +1 -1
- package/dist/runtime/Runtime.d.ts +32 -2
- package/dist/runtime/Runtime.d.ts.map +1 -1
- package/dist/runtime/Runtime.js +513 -612
- package/dist/runtime/Runtime.js.map +1 -1
- package/dist/runtime/SessionEventManager.d.ts +17 -0
- package/dist/runtime/SessionEventManager.d.ts.map +1 -0
- package/dist/runtime/SessionEventManager.js +149 -0
- package/dist/runtime/SessionEventManager.js.map +1 -0
- package/dist/runtime/SuggestionManager.d.ts +7 -0
- package/dist/runtime/SuggestionManager.d.ts.map +1 -0
- package/dist/runtime/SuggestionManager.js +50 -0
- package/dist/runtime/SuggestionManager.js.map +1 -0
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/services/MetricsService.d.ts +55 -0
- package/dist/services/MetricsService.d.ts.map +1 -0
- package/dist/services/MetricsService.js +86 -0
- package/dist/services/MetricsService.js.map +1 -0
- package/dist/services/TracingService.d.ts +13 -0
- package/dist/services/TracingService.d.ts.map +1 -0
- package/dist/services/TracingService.js +62 -0
- package/dist/services/TracingService.js.map +1 -0
- package/dist/session/stores/MemoryStore.js +1 -1
- package/dist/session/stores/MemoryStore.js.map +1 -1
- package/dist/tools/Tool.d.ts +25 -3
- package/dist/tools/Tool.d.ts.map +1 -1
- package/dist/tools/Tool.js.map +1 -1
- package/dist/tools/errorHandling.d.ts +1 -1
- package/dist/tools/errorHandling.d.ts.map +1 -1
- package/dist/tools/errorHandling.js +27 -20
- package/dist/tools/errorHandling.js.map +1 -1
- package/dist/tools/http.d.ts.map +1 -1
- package/dist/tools/http.js +53 -17
- package/dist/tools/http.js.map +1 -1
- package/dist/types/index.d.ts +179 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/telemetry.d.ts +52 -0
- package/dist/types/telemetry.d.ts.map +1 -0
- package/dist/types/telemetry.js +2 -0
- package/dist/types/telemetry.js.map +1 -0
- package/dist/utils/aiStream.d.ts +7 -0
- package/dist/utils/aiStream.d.ts.map +1 -0
- package/dist/utils/aiStream.js +41 -0
- package/dist/utils/aiStream.js.map +1 -0
- package/dist/utils/chrono.d.ts +3 -46
- package/dist/utils/chrono.d.ts.map +1 -1
- package/dist/utils/chrono.js.map +1 -1
- package/dist/utils/isRecord.d.ts +2 -0
- package/dist/utils/isRecord.d.ts.map +1 -0
- package/dist/utils/isRecord.js +4 -0
- package/dist/utils/isRecord.js.map +1 -0
- package/dist/utils/messageNormalization.d.ts +3 -0
- package/dist/utils/messageNormalization.d.ts.map +1 -0
- package/dist/utils/messageNormalization.js +121 -0
- package/dist/utils/messageNormalization.js.map +1 -0
- package/dist/utils/streamChunk.d.ts +5 -0
- package/dist/utils/streamChunk.d.ts.map +1 -0
- package/dist/utils/streamChunk.js +50 -0
- package/dist/utils/streamChunk.js.map +1 -0
- package/guides/EXAMPLE_VERIFICATION.md +53 -0
- package/guides/FLOWS.md +29 -0
- package/guides/GETTING_STARTED.md +14 -1
- package/guides/README.md +3 -1
- package/guides/RUNTIME.md +75 -0
- package/guides/TOOLS.md +6 -0
- package/package.json +2 -2
- package/dist/flows/AgentFlowManager.d.ts +0 -161
- package/dist/flows/AgentFlowManager.d.ts.map +0 -1
- package/dist/flows/AgentFlowManager.js +0 -448
- package/dist/flows/AgentFlowManager.js.map +0 -1
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import { streamText, generateText } from 'ai';
|
|
2
|
-
import {
|
|
1
|
+
import { streamText, generateText, tool } from 'ai';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { createFlowTransition, isFlowTransition, isFlowUpdate } from './transitions.js';
|
|
4
|
+
import { validateFlowConfig } from './validation.js';
|
|
3
5
|
import { isHandoffResult } from '../tools/handoff.js';
|
|
4
6
|
import { isFinalResult } from '../tools/final.js';
|
|
5
7
|
import { compileSanitizePattern, renderNodePrompt } from './template.js';
|
|
8
|
+
import { normalizeModelMessage } from '../utils/messageNormalization.js';
|
|
9
|
+
import { processAIStream } from '../utils/aiStream.js';
|
|
10
|
+
const implicitTransitionToolInputSchema = z.object({
|
|
11
|
+
data: z.record(z.unknown()).optional(),
|
|
12
|
+
message: z.string().optional(),
|
|
13
|
+
});
|
|
6
14
|
export class FlowManager {
|
|
7
15
|
config;
|
|
8
16
|
nodes = new Map();
|
|
@@ -11,10 +19,12 @@ export class FlowManager {
|
|
|
11
19
|
currentNodeConfig = null;
|
|
12
20
|
initialized = false;
|
|
13
21
|
pendingEvents = [];
|
|
22
|
+
deferredActions = [];
|
|
14
23
|
flowEnded = false;
|
|
15
24
|
sessionMessages;
|
|
16
25
|
constructor(config) {
|
|
17
26
|
this.config = config;
|
|
27
|
+
validateFlowConfig(config.flow, config.initialNode);
|
|
18
28
|
for (const node of config.flow.nodes) {
|
|
19
29
|
this.nodes.set(node.id, node);
|
|
20
30
|
}
|
|
@@ -37,7 +47,6 @@ export class FlowManager {
|
|
|
37
47
|
messages: config.contextMessages ?? [],
|
|
38
48
|
};
|
|
39
49
|
}
|
|
40
|
-
this.sessionMessages = config.sessionMessages;
|
|
41
50
|
if (this.initialized) {
|
|
42
51
|
this.currentNodeConfig = this.nodes.get(this.context.currentNode) ?? null;
|
|
43
52
|
}
|
|
@@ -48,11 +57,12 @@ export class FlowManager {
|
|
|
48
57
|
}
|
|
49
58
|
yield* this.transitionToGenerator(this.config.initialNode);
|
|
50
59
|
this.initialized = true;
|
|
51
|
-
if (this.currentNodeConfig?.autoRespond !== false) {
|
|
52
|
-
yield* this.runInference();
|
|
60
|
+
if (!this.flowEnded && this.currentNodeConfig?.autoRespond !== false) {
|
|
61
|
+
yield* this.runInference(false);
|
|
53
62
|
}
|
|
63
|
+
yield* this.flushDeferredActions();
|
|
54
64
|
}
|
|
55
|
-
async *process(userInput
|
|
65
|
+
async *process(userInput) {
|
|
56
66
|
if (!this.initialized) {
|
|
57
67
|
throw new Error('Flow not initialized. Call initialize() first.');
|
|
58
68
|
}
|
|
@@ -64,15 +74,15 @@ export class FlowManager {
|
|
|
64
74
|
yield { type: 'error', error: 'No current node' };
|
|
65
75
|
return;
|
|
66
76
|
}
|
|
67
|
-
this.appendMessage({ role: 'user', content: userInput }
|
|
77
|
+
this.appendMessage({ role: 'user', content: userInput });
|
|
68
78
|
const nodeId = this.currentNodeConfig.id;
|
|
69
79
|
this.context.nodeTurnCounts[nodeId] = (this.context.nodeTurnCounts[nodeId] ?? 0) + 1;
|
|
70
80
|
if (this.currentNodeConfig.maxTurns && this.context.nodeTurnCounts[nodeId] > this.currentNodeConfig.maxTurns) {
|
|
71
81
|
yield { type: 'error', error: `Max turns exceeded for node "${nodeId}"` };
|
|
72
82
|
return;
|
|
73
83
|
}
|
|
74
|
-
yield* this.runInference();
|
|
75
|
-
yield
|
|
84
|
+
yield* this.runInference(true);
|
|
85
|
+
yield* this.flushDeferredActions();
|
|
76
86
|
}
|
|
77
87
|
async transitionTo(nodeId, data) {
|
|
78
88
|
for await (const _part of this.transitionToGenerator(nodeId, data)) {
|
|
@@ -95,6 +105,9 @@ export class FlowManager {
|
|
|
95
105
|
const previousNode = this.currentNodeConfig;
|
|
96
106
|
if (previousNode) {
|
|
97
107
|
yield* this.runActions(previousNode.postActions ?? []);
|
|
108
|
+
if (this.flowEnded) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
98
111
|
yield { type: 'node-exit', nodeName: previousNode.name ?? previousNode.id };
|
|
99
112
|
}
|
|
100
113
|
this.currentNodeConfig = nextNode;
|
|
@@ -106,6 +119,9 @@ export class FlowManager {
|
|
|
106
119
|
await this.applyContextStrategy(nextNode);
|
|
107
120
|
yield { type: 'node-enter', nodeName: nextNode.name ?? nextNode.id };
|
|
108
121
|
yield* this.runActions(nextNode.preActions ?? []);
|
|
122
|
+
if (this.flowEnded) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
109
125
|
if (previousNode) {
|
|
110
126
|
yield { type: 'flow-transition', from: previousNode.id, to: nextNode.id };
|
|
111
127
|
}
|
|
@@ -126,63 +142,56 @@ export class FlowManager {
|
|
|
126
142
|
flowEnded: this.flowEnded,
|
|
127
143
|
};
|
|
128
144
|
}
|
|
129
|
-
async *runInference() {
|
|
130
|
-
if (!this.currentNodeConfig) {
|
|
145
|
+
async *runInference(triggeredByUserTurn) {
|
|
146
|
+
if (this.flowEnded || !this.currentNodeConfig) {
|
|
131
147
|
return;
|
|
132
148
|
}
|
|
133
149
|
const node = this.currentNodeConfig;
|
|
134
150
|
const systemPrompt = this.buildSystemPrompt(node);
|
|
135
151
|
const tools = this.resolveTools(node);
|
|
136
152
|
const maxSteps = node.maxSteps ?? this.config.maxSteps ?? this.config.flow.maxSteps ?? 25;
|
|
137
|
-
const
|
|
153
|
+
const streamOptions = {
|
|
138
154
|
model: this.config.model,
|
|
139
155
|
system: systemPrompt,
|
|
140
156
|
tools,
|
|
141
157
|
maxSteps,
|
|
158
|
+
abortSignal: this.config.abortSignal,
|
|
142
159
|
experimental_telemetry: this.config.telemetry,
|
|
143
160
|
};
|
|
144
161
|
// On first turn of a node (after context reset), use the node's prompt as the user message
|
|
145
162
|
// This respects the flow design without auto-generating greetings
|
|
146
163
|
if (this.context.messages.length === 0) {
|
|
147
|
-
|
|
164
|
+
streamOptions.prompt = renderNodePrompt(node.prompt, this.context);
|
|
148
165
|
}
|
|
149
166
|
else {
|
|
150
|
-
|
|
167
|
+
streamOptions.messages = this.context.messages;
|
|
151
168
|
}
|
|
152
|
-
const result = streamText(
|
|
169
|
+
const result = streamText(streamOptions);
|
|
153
170
|
const outputMode = node.output?.mode ?? 'stream';
|
|
154
171
|
const shouldBuffer = outputMode === 'buffer';
|
|
155
172
|
let responseText = '';
|
|
156
173
|
let bufferedOutputEmitted = false;
|
|
157
|
-
|
|
158
|
-
|
|
174
|
+
const pendingTransitions = [];
|
|
175
|
+
const pendingHandoffs = [];
|
|
159
176
|
let finalResult = null;
|
|
160
177
|
let finalEmitted = false;
|
|
161
178
|
let hadToolResult = false;
|
|
162
179
|
let pendingToolMessage = null;
|
|
163
|
-
for await (const
|
|
164
|
-
|
|
180
|
+
for await (const part of processAIStream(result.fullStream)) {
|
|
181
|
+
// In buffered mode, hold text-delta tokens until we decide whether to emit.
|
|
182
|
+
// This allows transition/handoff turns to stay silent in routing nodes.
|
|
183
|
+
if (!(shouldBuffer && part.type === 'text-delta')) {
|
|
184
|
+
yield part;
|
|
185
|
+
}
|
|
186
|
+
if (part.type === 'text-delta') {
|
|
165
187
|
if (finalResult) {
|
|
166
188
|
continue;
|
|
167
189
|
}
|
|
168
|
-
responseText +=
|
|
169
|
-
if (!shouldBuffer) {
|
|
170
|
-
yield { type: 'text-delta', text: chunk.text };
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
if (chunk.type === 'tool-call') {
|
|
174
|
-
const args = 'args' in chunk ? chunk.args : chunk.input;
|
|
175
|
-
yield { type: 'tool-call', toolName: chunk.toolName, args, toolCallId: chunk.toolCallId };
|
|
190
|
+
responseText += part.text;
|
|
176
191
|
}
|
|
177
|
-
if (
|
|
178
|
-
const toolResult =
|
|
192
|
+
if (part.type === 'tool-result') {
|
|
193
|
+
const toolResult = part.result;
|
|
179
194
|
hadToolResult = true;
|
|
180
|
-
yield {
|
|
181
|
-
type: 'tool-result',
|
|
182
|
-
toolName: chunk.toolName,
|
|
183
|
-
result: toolResult,
|
|
184
|
-
toolCallId: chunk.toolCallId,
|
|
185
|
-
};
|
|
186
195
|
if (isFinalResult(toolResult)) {
|
|
187
196
|
finalResult = toolResult;
|
|
188
197
|
if (!finalEmitted) {
|
|
@@ -208,18 +217,22 @@ export class FlowManager {
|
|
|
208
217
|
continue;
|
|
209
218
|
}
|
|
210
219
|
if (isFlowTransition(toolResult)) {
|
|
211
|
-
|
|
220
|
+
pendingTransitions.push({
|
|
212
221
|
targetNode: toolResult.targetNode,
|
|
213
222
|
data: toolResult.data,
|
|
214
223
|
node: toolResult.node,
|
|
215
|
-
|
|
224
|
+
toolName: part.toolName,
|
|
225
|
+
toolCallId: part.toolCallId,
|
|
226
|
+
});
|
|
216
227
|
}
|
|
217
228
|
if (isHandoffResult(toolResult)) {
|
|
218
229
|
const targetAgent = toolResult.targetAgent ?? toolResult.targetAgentId;
|
|
219
|
-
|
|
230
|
+
pendingHandoffs.push({
|
|
220
231
|
targetAgent,
|
|
221
232
|
reason: toolResult.reason,
|
|
222
|
-
|
|
233
|
+
toolName: part.toolName,
|
|
234
|
+
toolCallId: part.toolCallId,
|
|
235
|
+
});
|
|
223
236
|
}
|
|
224
237
|
if (typeof toolResult === 'object' &&
|
|
225
238
|
toolResult !== null &&
|
|
@@ -229,17 +242,6 @@ export class FlowManager {
|
|
|
229
242
|
pendingToolMessage = toolResult.message;
|
|
230
243
|
}
|
|
231
244
|
}
|
|
232
|
-
if (chunk.type === 'tool-error') {
|
|
233
|
-
const errText = typeof chunk.error === 'string'
|
|
234
|
-
? chunk.error
|
|
235
|
-
: chunk.error?.message ?? 'Tool execution error';
|
|
236
|
-
yield {
|
|
237
|
-
type: 'tool-error',
|
|
238
|
-
toolName: chunk.toolName,
|
|
239
|
-
error: errText,
|
|
240
|
-
toolCallId: chunk.toolCallId,
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
245
|
}
|
|
244
246
|
const response = await result.response;
|
|
245
247
|
let safeText = responseText;
|
|
@@ -249,28 +251,22 @@ export class FlowManager {
|
|
|
249
251
|
safeText = node.output.sanitize.message;
|
|
250
252
|
}
|
|
251
253
|
}
|
|
252
|
-
|
|
253
|
-
this.appendMessage(
|
|
254
|
-
}
|
|
255
|
-
else if (shouldBuffer) {
|
|
256
|
-
let appendedAssistant = false;
|
|
257
|
-
for (const message of response.messages) {
|
|
258
|
-
if (message.role === 'assistant' && typeof message.content === 'string') {
|
|
259
|
-
if (!appendedAssistant) {
|
|
260
|
-
this.appendMessage({ role: 'assistant', content: safeText });
|
|
261
|
-
appendedAssistant = true;
|
|
262
|
-
}
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
this.appendMessage(message);
|
|
266
|
-
}
|
|
267
|
-
if (!appendedAssistant) {
|
|
268
|
-
this.appendMessage({ role: 'assistant', content: safeText });
|
|
269
|
-
}
|
|
254
|
+
for (const message of response.messages) {
|
|
255
|
+
this.appendMessage(message);
|
|
270
256
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
257
|
+
yield { type: 'turn-end', metadata: { response } };
|
|
258
|
+
const signalResolution = this.resolvePendingSignals(pendingTransitions, pendingHandoffs);
|
|
259
|
+
if (signalResolution.conflict) {
|
|
260
|
+
yield { type: 'error', error: signalResolution.conflict };
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
let pendingTransition = signalResolution.transition ?? null;
|
|
264
|
+
const pendingHandoff = signalResolution.handoff ?? null;
|
|
265
|
+
if (pendingTransition) {
|
|
266
|
+
const policy = this.checkTransitionPolicy(node.id, pendingTransition.targetNode, 'tool', triggeredByUserTurn);
|
|
267
|
+
if (policy) {
|
|
268
|
+
pendingTransition = null;
|
|
269
|
+
yield { type: 'error', error: policy };
|
|
274
270
|
}
|
|
275
271
|
}
|
|
276
272
|
if (!finalResult && !pendingTransition && !pendingHandoff && responseText.trim().length === 0 && pendingToolMessage) {
|
|
@@ -299,6 +295,7 @@ export class FlowManager {
|
|
|
299
295
|
model: this.config.model,
|
|
300
296
|
system: `${systemPrompt}\n\nRespond to the user now using the tool results above.`,
|
|
301
297
|
messages: this.context.messages,
|
|
298
|
+
abortSignal: this.config.abortSignal,
|
|
302
299
|
experimental_telemetry: this.config.telemetry,
|
|
303
300
|
});
|
|
304
301
|
responseText = followup.text;
|
|
@@ -308,7 +305,7 @@ export class FlowManager {
|
|
|
308
305
|
bufferedOutputEmitted = true;
|
|
309
306
|
}
|
|
310
307
|
}
|
|
311
|
-
if (!finalResult && shouldBuffer && responseText.trim().length > 0 && !bufferedOutputEmitted) {
|
|
308
|
+
if (!finalResult && !pendingTransition && !pendingHandoff && shouldBuffer && responseText.trim().length > 0 && !bufferedOutputEmitted) {
|
|
312
309
|
yield { type: 'text-delta', text: safeText };
|
|
313
310
|
}
|
|
314
311
|
if (finalResult) {
|
|
@@ -326,11 +323,16 @@ export class FlowManager {
|
|
|
326
323
|
yield { type: 'flow-end', reason: 'handoff' };
|
|
327
324
|
return;
|
|
328
325
|
}
|
|
329
|
-
const
|
|
326
|
+
const evaluated = await this.evaluateTransitions(triggeredByUserTurn);
|
|
327
|
+
if (evaluated.blockedError) {
|
|
328
|
+
yield { type: 'error', error: evaluated.blockedError };
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const nextTransition = pendingTransition ?? evaluated.transition;
|
|
330
332
|
if (nextTransition) {
|
|
331
333
|
yield* this.transitionToGenerator(nextTransition.targetNode, nextTransition.data, nextTransition.node);
|
|
332
|
-
if (this.currentNodeConfig?.autoRespond !== false) {
|
|
333
|
-
yield* this.runInference();
|
|
334
|
+
if (!this.flowEnded && this.currentNodeConfig?.autoRespond !== false) {
|
|
335
|
+
yield* this.runInference(false);
|
|
334
336
|
}
|
|
335
337
|
}
|
|
336
338
|
if (node.postActions?.some(action => action.type === 'end')) {
|
|
@@ -339,7 +341,10 @@ export class FlowManager {
|
|
|
339
341
|
}
|
|
340
342
|
}
|
|
341
343
|
buildSystemPrompt(node) {
|
|
342
|
-
const
|
|
344
|
+
const includeGlobalPrompt = node.addGlobalPrompt !== false;
|
|
345
|
+
const rolePrompt = includeGlobalPrompt
|
|
346
|
+
? (this.config.defaultRolePrompt ?? this.config.flow.defaultRolePrompt ?? '')
|
|
347
|
+
: '';
|
|
343
348
|
const renderedPrompt = renderNodePrompt(node.prompt, this.context);
|
|
344
349
|
const dataKeys = Object.keys(this.context.collectedData);
|
|
345
350
|
const dataContext = dataKeys.length > 0
|
|
@@ -348,15 +353,19 @@ export class FlowManager {
|
|
|
348
353
|
const historyContext = this.context.nodeHistory.length > 1
|
|
349
354
|
? `\n\n## Progress\n${this.context.nodeHistory.slice(0, -1).join(' -> ')} -> **${node.name ?? node.id}**`
|
|
350
355
|
: '';
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
${renderedPrompt}${dataContext}${historyContext}
|
|
355
|
-
|
|
356
|
-
## Instructions
|
|
356
|
+
const currentTaskSection = `## Current Task
|
|
357
|
+
${renderedPrompt}${dataContext}${historyContext}`;
|
|
358
|
+
const instructionsSection = `## Instructions
|
|
357
359
|
- Focus only on the current task
|
|
358
360
|
- Use the available tools to progress the conversation
|
|
359
361
|
- Do not attempt tasks outside your current scope`;
|
|
362
|
+
const sections = [];
|
|
363
|
+
if (rolePrompt.trim().length > 0) {
|
|
364
|
+
sections.push(rolePrompt.trim());
|
|
365
|
+
}
|
|
366
|
+
sections.push(currentTaskSection);
|
|
367
|
+
sections.push(instructionsSection);
|
|
368
|
+
return sections.join('\n\n');
|
|
360
369
|
}
|
|
361
370
|
async applyContextStrategy(node) {
|
|
362
371
|
const strategy = node.contextStrategy ?? this.config.flow.contextStrategy ?? 'append';
|
|
@@ -367,9 +376,11 @@ ${renderedPrompt}${dataContext}${historyContext}
|
|
|
367
376
|
case 'reset_with_summary':
|
|
368
377
|
if (this.context.messages.length > 0) {
|
|
369
378
|
const summary = await this.generateSummary(node);
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
379
|
+
if (summary && summary.trim().length > 0) {
|
|
380
|
+
this.context.messages = [
|
|
381
|
+
{ role: 'system', content: `Previous conversation summary: ${summary}` },
|
|
382
|
+
];
|
|
383
|
+
}
|
|
373
384
|
}
|
|
374
385
|
break;
|
|
375
386
|
case 'append':
|
|
@@ -382,26 +393,166 @@ ${renderedPrompt}${dataContext}${historyContext}
|
|
|
382
393
|
?? this.config.flow.summaryPrompt
|
|
383
394
|
?? 'Summarize the key points from this conversation in 2-3 sentences.';
|
|
384
395
|
const maxTokens = node.summaryMaxTokens ?? this.config.flow.summaryMaxTokens;
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
396
|
+
const timeoutMs = node.summaryTimeoutMs ?? this.config.flow.summaryTimeoutMs ?? 5000;
|
|
397
|
+
try {
|
|
398
|
+
const generate = async () => {
|
|
399
|
+
const result = await generateText({
|
|
400
|
+
model: this.config.model,
|
|
401
|
+
system: prompt,
|
|
402
|
+
messages: this.context.messages,
|
|
403
|
+
...(typeof maxTokens === 'number' ? { maxTokens } : {}),
|
|
404
|
+
abortSignal: this.config.abortSignal,
|
|
405
|
+
experimental_telemetry: this.config.telemetry,
|
|
406
|
+
});
|
|
407
|
+
return result.text;
|
|
408
|
+
};
|
|
409
|
+
if (timeoutMs <= 0) {
|
|
410
|
+
return await generate();
|
|
411
|
+
}
|
|
412
|
+
let timeoutHandle;
|
|
413
|
+
const timeout = new Promise(resolve => {
|
|
414
|
+
timeoutHandle = setTimeout(() => resolve(null), timeoutMs);
|
|
415
|
+
});
|
|
416
|
+
const summary = await Promise.race([generate().catch(() => null), timeout]);
|
|
417
|
+
if (timeoutHandle) {
|
|
418
|
+
clearTimeout(timeoutHandle);
|
|
419
|
+
}
|
|
420
|
+
return summary;
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
normalizeForComparison(value) {
|
|
427
|
+
if (value === null || value === undefined) {
|
|
428
|
+
return value;
|
|
429
|
+
}
|
|
430
|
+
if (value instanceof Date) {
|
|
431
|
+
return value.toISOString();
|
|
432
|
+
}
|
|
433
|
+
if (Array.isArray(value)) {
|
|
434
|
+
return value.map(item => this.normalizeForComparison(item));
|
|
435
|
+
}
|
|
436
|
+
if (typeof value === 'function') {
|
|
437
|
+
return '[function]';
|
|
438
|
+
}
|
|
439
|
+
if (typeof value === 'object') {
|
|
440
|
+
const input = value;
|
|
441
|
+
const sortedKeys = Object.keys(input).sort();
|
|
442
|
+
const output = {};
|
|
443
|
+
for (const key of sortedKeys) {
|
|
444
|
+
output[key] = this.normalizeForComparison(input[key]);
|
|
445
|
+
}
|
|
446
|
+
return output;
|
|
447
|
+
}
|
|
448
|
+
return value;
|
|
449
|
+
}
|
|
450
|
+
isEquivalentPayload(a, b) {
|
|
451
|
+
return JSON.stringify(this.normalizeForComparison(a)) === JSON.stringify(this.normalizeForComparison(b));
|
|
452
|
+
}
|
|
453
|
+
resolvePendingSignals(transitions, handoffs) {
|
|
454
|
+
if (transitions.length === 0 && handoffs.length === 0) {
|
|
455
|
+
return {};
|
|
456
|
+
}
|
|
457
|
+
if (transitions.length > 0 && handoffs.length > 0) {
|
|
458
|
+
const transitionTool = transitions[0];
|
|
459
|
+
const handoffTool = handoffs[0];
|
|
460
|
+
return {
|
|
461
|
+
conflict: `Conflicting flow signals: both transition (${transitionTool.targetNode}) and handoff (${handoffTool.targetAgent}) requested in the same turn.`,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
if (handoffs.length > 0) {
|
|
465
|
+
const first = handoffs[0];
|
|
466
|
+
for (let i = 1; i < handoffs.length; i++) {
|
|
467
|
+
const next = handoffs[i];
|
|
468
|
+
if (next.targetAgent !== first.targetAgent) {
|
|
469
|
+
return {
|
|
470
|
+
conflict: `Conflicting handoffs requested in the same turn: "${first.targetAgent}" and "${next.targetAgent}".`,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
handoff: {
|
|
476
|
+
targetAgent: first.targetAgent,
|
|
477
|
+
reason: first.reason,
|
|
478
|
+
},
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
const firstTransition = transitions[0];
|
|
482
|
+
for (let i = 1; i < transitions.length; i++) {
|
|
483
|
+
const next = transitions[i];
|
|
484
|
+
if (next.targetNode !== firstTransition.targetNode) {
|
|
485
|
+
return {
|
|
486
|
+
conflict: `Conflicting transitions requested in the same turn: "${firstTransition.targetNode}" and "${next.targetNode}".`,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
if (!this.isEquivalentPayload(next.data, firstTransition.data)) {
|
|
490
|
+
return {
|
|
491
|
+
conflict: `Conflicting transition payloads for node "${firstTransition.targetNode}" in the same turn.`,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
if ((next.node?.id ?? null) !== (firstTransition.node?.id ?? null)) {
|
|
495
|
+
return {
|
|
496
|
+
conflict: `Conflicting dynamic node definitions for transition target "${firstTransition.targetNode}" in the same turn.`,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
transition: {
|
|
502
|
+
targetNode: firstTransition.targetNode,
|
|
503
|
+
data: firstTransition.data,
|
|
504
|
+
node: firstTransition.node,
|
|
505
|
+
},
|
|
506
|
+
};
|
|
393
507
|
}
|
|
394
508
|
resolveTools(node) {
|
|
395
|
-
|
|
509
|
+
const implicitTools = this.buildImplicitTransitionTools(node);
|
|
510
|
+
let explicitTools = {};
|
|
511
|
+
if (node.tools) {
|
|
512
|
+
explicitTools =
|
|
513
|
+
typeof node.tools === 'function'
|
|
514
|
+
? (node.tools(this.context) ?? {})
|
|
515
|
+
: node.tools;
|
|
516
|
+
}
|
|
517
|
+
// Explicit node tools win on name collision to preserve backwards compatibility.
|
|
518
|
+
return this.wrapTools({
|
|
519
|
+
...implicitTools,
|
|
520
|
+
...explicitTools,
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
buildImplicitTransitionTools(node) {
|
|
524
|
+
const transitions = this.transitions.get(node.id) ?? [];
|
|
525
|
+
if (transitions.length === 0) {
|
|
396
526
|
return {};
|
|
397
|
-
if (typeof node.tools === 'function') {
|
|
398
|
-
return this.wrapTools(node.tools(this.context) ?? {});
|
|
399
527
|
}
|
|
400
|
-
|
|
528
|
+
const implicitTools = {};
|
|
529
|
+
const seenNames = new Set();
|
|
530
|
+
for (const transition of transitions) {
|
|
531
|
+
const eventName = typeof transition.on === 'string' ? transition.on.trim() : '';
|
|
532
|
+
if (!eventName || seenNames.has(eventName)) {
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
seenNames.add(eventName);
|
|
536
|
+
const label = transition.contract?.label?.trim();
|
|
537
|
+
const conditionText = transition.contract?.conditionText?.trim();
|
|
538
|
+
const descriptionParts = [
|
|
539
|
+
`Transition from "${node.id}" to "${transition.to}".`,
|
|
540
|
+
label ? `Label: ${label}.` : '',
|
|
541
|
+
conditionText ? `Condition: ${conditionText}.` : '',
|
|
542
|
+
'Call only when transition criteria are satisfied.',
|
|
543
|
+
].filter(Boolean);
|
|
544
|
+
implicitTools[eventName] = tool({
|
|
545
|
+
description: descriptionParts.join(' '),
|
|
546
|
+
inputSchema: implicitTransitionToolInputSchema,
|
|
547
|
+
execute: async (input) => createFlowTransition(transition.to, input.data, input.message),
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
return implicitTools;
|
|
401
551
|
}
|
|
402
552
|
wrapTools(tools) {
|
|
403
553
|
const guard = this.config.toolCallGuard;
|
|
404
|
-
|
|
554
|
+
const optionsFactory = this.config.toolExecutionOptionsFactory;
|
|
555
|
+
if (!guard && !optionsFactory)
|
|
405
556
|
return tools;
|
|
406
557
|
const wrapped = {};
|
|
407
558
|
for (const [toolName, toolDef] of Object.entries(tools ?? {})) {
|
|
@@ -412,51 +563,72 @@ ${renderedPrompt}${dataContext}${historyContext}
|
|
|
412
563
|
}
|
|
413
564
|
wrapped[toolName] = {
|
|
414
565
|
...toolDef,
|
|
415
|
-
execute: async (args) => {
|
|
416
|
-
const
|
|
417
|
-
if (
|
|
418
|
-
|
|
566
|
+
execute: async (args, options) => {
|
|
567
|
+
const toolCallId = typeof options?.toolCallId === 'string' ? options.toolCallId : undefined;
|
|
568
|
+
if (guard) {
|
|
569
|
+
const decision = await guard({ toolName, args, toolCallId });
|
|
570
|
+
if (!decision.allowed) {
|
|
571
|
+
throw new Error(decision.reason || 'Tool call blocked');
|
|
572
|
+
}
|
|
419
573
|
}
|
|
420
|
-
|
|
574
|
+
const extraContext = optionsFactory?.({ toolName, args, toolCallId });
|
|
575
|
+
const mergedOptions = mergeToolExecutionOptions(options, extraContext);
|
|
576
|
+
return exec(args, mergedOptions);
|
|
421
577
|
},
|
|
422
578
|
};
|
|
423
579
|
}
|
|
424
580
|
return wrapped;
|
|
425
581
|
}
|
|
426
|
-
appendMessage(message
|
|
582
|
+
appendMessage(message) {
|
|
427
583
|
const normalized = this.normalizeMessage(message);
|
|
428
584
|
if (!normalized) {
|
|
429
585
|
return;
|
|
430
586
|
}
|
|
431
587
|
this.context.messages.push(normalized);
|
|
432
|
-
if (persistToSession && this.sessionMessages && this.sessionMessages !== this.context.messages) {
|
|
433
|
-
this.sessionMessages.push(normalized);
|
|
434
|
-
}
|
|
435
588
|
}
|
|
436
589
|
normalizeMessage(message) {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
590
|
+
return normalizeModelMessage(message);
|
|
591
|
+
}
|
|
592
|
+
async *runActions(actions) {
|
|
593
|
+
for (const action of actions) {
|
|
594
|
+
if (action.deferUntil === 'turn-end') {
|
|
595
|
+
this.deferredActions.push(action);
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
switch (action.type) {
|
|
599
|
+
case 'say':
|
|
600
|
+
yield { type: 'text-delta', text: action.text };
|
|
601
|
+
break;
|
|
602
|
+
case 'end':
|
|
603
|
+
this.flowEnded = true;
|
|
604
|
+
yield { type: 'flow-end', reason: action.reason ?? 'action' };
|
|
605
|
+
break;
|
|
606
|
+
case 'function':
|
|
607
|
+
await action.handler(this.context);
|
|
608
|
+
break;
|
|
609
|
+
case 'emit':
|
|
610
|
+
this.pendingEvents.push({ name: action.event, data: action.data });
|
|
611
|
+
yield {
|
|
612
|
+
type: 'custom',
|
|
613
|
+
name: action.event,
|
|
614
|
+
data: action.data,
|
|
615
|
+
timestamp: new Date(),
|
|
616
|
+
};
|
|
617
|
+
break;
|
|
618
|
+
default:
|
|
619
|
+
break;
|
|
454
620
|
}
|
|
455
|
-
return { role, content: text };
|
|
456
621
|
}
|
|
457
|
-
return null;
|
|
458
622
|
}
|
|
459
|
-
async *
|
|
623
|
+
async *flushDeferredActions() {
|
|
624
|
+
if (this.deferredActions.length === 0) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
const actions = this.deferredActions;
|
|
628
|
+
this.deferredActions = [];
|
|
629
|
+
if (this.flowEnded) {
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
460
632
|
for (const action of actions) {
|
|
461
633
|
switch (action.type) {
|
|
462
634
|
case 'say':
|
|
@@ -471,24 +643,57 @@ ${renderedPrompt}${dataContext}${historyContext}
|
|
|
471
643
|
break;
|
|
472
644
|
case 'emit':
|
|
473
645
|
this.pendingEvents.push({ name: action.event, data: action.data });
|
|
646
|
+
yield {
|
|
647
|
+
type: 'custom',
|
|
648
|
+
name: action.event,
|
|
649
|
+
data: action.data,
|
|
650
|
+
timestamp: new Date(),
|
|
651
|
+
};
|
|
474
652
|
break;
|
|
475
653
|
default:
|
|
476
654
|
break;
|
|
477
655
|
}
|
|
656
|
+
if (this.flowEnded) {
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
478
659
|
}
|
|
479
660
|
}
|
|
480
|
-
|
|
661
|
+
checkTransitionPolicy(currentNodeId, targetNodeId, source, triggeredByUserTurn) {
|
|
662
|
+
const candidates = (this.transitions.get(currentNodeId) ?? []).filter(transition => transition.to === targetNodeId);
|
|
663
|
+
const candidate = candidates.find(transition => transition.contract) ?? candidates[0];
|
|
664
|
+
const contract = candidate?.contract;
|
|
665
|
+
if (!contract) {
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
668
|
+
if (contract.toolOnly && source === 'condition') {
|
|
669
|
+
return `Transition "${currentNodeId}" -> "${targetNodeId}" is blocked: contract.toolOnly=true requires a tool/event signal.`;
|
|
670
|
+
}
|
|
671
|
+
if (contract.requiresUserTurn && !triggeredByUserTurn) {
|
|
672
|
+
return `Transition "${currentNodeId}" -> "${targetNodeId}" is blocked: contract.requiresUserTurn=true requires a user turn before transition.`;
|
|
673
|
+
}
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
async evaluateTransitions(triggeredByUserTurn) {
|
|
481
677
|
const currentNode = this.currentNodeConfig?.id;
|
|
482
678
|
if (!currentNode) {
|
|
483
|
-
return
|
|
679
|
+
return {};
|
|
484
680
|
}
|
|
485
681
|
const transitions = this.transitions.get(currentNode) ?? [];
|
|
486
682
|
if (this.pendingEvents.length > 0) {
|
|
487
683
|
for (const [index, event] of this.pendingEvents.entries()) {
|
|
488
684
|
const match = transitions.find(transition => transition.on === event.name);
|
|
489
685
|
if (match) {
|
|
686
|
+
const policy = this.checkTransitionPolicy(currentNode, match.to, 'event', triggeredByUserTurn);
|
|
490
687
|
this.pendingEvents.splice(index, 1);
|
|
491
|
-
|
|
688
|
+
if (policy) {
|
|
689
|
+
return { blockedError: policy };
|
|
690
|
+
}
|
|
691
|
+
return {
|
|
692
|
+
transition: {
|
|
693
|
+
targetNode: match.to,
|
|
694
|
+
data: event.data,
|
|
695
|
+
},
|
|
696
|
+
};
|
|
492
697
|
}
|
|
493
698
|
}
|
|
494
699
|
}
|
|
@@ -498,10 +703,33 @@ ${renderedPrompt}${dataContext}${historyContext}
|
|
|
498
703
|
}
|
|
499
704
|
const shouldTransition = await transition.condition(this.context);
|
|
500
705
|
if (shouldTransition) {
|
|
501
|
-
|
|
706
|
+
const policy = this.checkTransitionPolicy(currentNode, transition.to, 'condition', triggeredByUserTurn);
|
|
707
|
+
if (policy) {
|
|
708
|
+
return { blockedError: policy };
|
|
709
|
+
}
|
|
710
|
+
return { transition: { targetNode: transition.to } };
|
|
502
711
|
}
|
|
503
712
|
}
|
|
504
|
-
return
|
|
713
|
+
return {};
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
function isRecord(value) {
|
|
717
|
+
return typeof value === 'object' && value !== null;
|
|
718
|
+
}
|
|
719
|
+
function mergeToolExecutionOptions(options, extraContext) {
|
|
720
|
+
if (!extraContext || Object.keys(extraContext).length === 0) {
|
|
721
|
+
return options;
|
|
505
722
|
}
|
|
723
|
+
const baseOptions = isRecord(options) ? options : {};
|
|
724
|
+
const existingContext = isRecord(baseOptions.experimental_context)
|
|
725
|
+
? baseOptions.experimental_context
|
|
726
|
+
: {};
|
|
727
|
+
return {
|
|
728
|
+
...baseOptions,
|
|
729
|
+
experimental_context: {
|
|
730
|
+
...existingContext,
|
|
731
|
+
...extraContext,
|
|
732
|
+
},
|
|
733
|
+
};
|
|
506
734
|
}
|
|
507
735
|
//# sourceMappingURL=FlowManager.js.map
|