@ariaflowagents/core 0.6.1 → 0.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 +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 +355 -131
- 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/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 +10 -3
- 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 +46 -20
- 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 -633
- 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,52 @@ 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
|
+
yield part;
|
|
182
|
+
if (part.type === 'text-delta') {
|
|
165
183
|
if (finalResult) {
|
|
166
184
|
continue;
|
|
167
185
|
}
|
|
168
|
-
responseText +=
|
|
169
|
-
if (!shouldBuffer) {
|
|
170
|
-
yield { type: 'text-delta', text: chunk.text };
|
|
171
|
-
}
|
|
186
|
+
responseText += part.text;
|
|
172
187
|
}
|
|
173
|
-
if (
|
|
174
|
-
const
|
|
175
|
-
yield { type: 'tool-call', toolName: chunk.toolName, args, toolCallId: chunk.toolCallId };
|
|
176
|
-
}
|
|
177
|
-
if (chunk.type === 'tool-result') {
|
|
178
|
-
const toolResult = 'result' in chunk ? chunk.result : chunk.output;
|
|
188
|
+
if (part.type === 'tool-result') {
|
|
189
|
+
const toolResult = part.result;
|
|
179
190
|
hadToolResult = true;
|
|
180
|
-
yield {
|
|
181
|
-
type: 'tool-result',
|
|
182
|
-
toolName: chunk.toolName,
|
|
183
|
-
result: toolResult,
|
|
184
|
-
toolCallId: chunk.toolCallId,
|
|
185
|
-
};
|
|
186
191
|
if (isFinalResult(toolResult)) {
|
|
187
192
|
finalResult = toolResult;
|
|
188
193
|
if (!finalEmitted) {
|
|
@@ -208,18 +213,22 @@ export class FlowManager {
|
|
|
208
213
|
continue;
|
|
209
214
|
}
|
|
210
215
|
if (isFlowTransition(toolResult)) {
|
|
211
|
-
|
|
216
|
+
pendingTransitions.push({
|
|
212
217
|
targetNode: toolResult.targetNode,
|
|
213
218
|
data: toolResult.data,
|
|
214
219
|
node: toolResult.node,
|
|
215
|
-
|
|
220
|
+
toolName: part.toolName,
|
|
221
|
+
toolCallId: part.toolCallId,
|
|
222
|
+
});
|
|
216
223
|
}
|
|
217
224
|
if (isHandoffResult(toolResult)) {
|
|
218
225
|
const targetAgent = toolResult.targetAgent ?? toolResult.targetAgentId;
|
|
219
|
-
|
|
226
|
+
pendingHandoffs.push({
|
|
220
227
|
targetAgent,
|
|
221
228
|
reason: toolResult.reason,
|
|
222
|
-
|
|
229
|
+
toolName: part.toolName,
|
|
230
|
+
toolCallId: part.toolCallId,
|
|
231
|
+
});
|
|
223
232
|
}
|
|
224
233
|
if (typeof toolResult === 'object' &&
|
|
225
234
|
toolResult !== null &&
|
|
@@ -229,17 +238,6 @@ export class FlowManager {
|
|
|
229
238
|
pendingToolMessage = toolResult.message;
|
|
230
239
|
}
|
|
231
240
|
}
|
|
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
241
|
}
|
|
244
242
|
const response = await result.response;
|
|
245
243
|
let safeText = responseText;
|
|
@@ -249,28 +247,22 @@ export class FlowManager {
|
|
|
249
247
|
safeText = node.output.sanitize.message;
|
|
250
248
|
}
|
|
251
249
|
}
|
|
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
|
-
}
|
|
250
|
+
for (const message of response.messages) {
|
|
251
|
+
this.appendMessage(message);
|
|
270
252
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
253
|
+
yield { type: 'turn-end', metadata: { response } };
|
|
254
|
+
const signalResolution = this.resolvePendingSignals(pendingTransitions, pendingHandoffs);
|
|
255
|
+
if (signalResolution.conflict) {
|
|
256
|
+
yield { type: 'error', error: signalResolution.conflict };
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
let pendingTransition = signalResolution.transition ?? null;
|
|
260
|
+
const pendingHandoff = signalResolution.handoff ?? null;
|
|
261
|
+
if (pendingTransition) {
|
|
262
|
+
const policy = this.checkTransitionPolicy(node.id, pendingTransition.targetNode, 'tool', triggeredByUserTurn);
|
|
263
|
+
if (policy) {
|
|
264
|
+
pendingTransition = null;
|
|
265
|
+
yield { type: 'error', error: policy };
|
|
274
266
|
}
|
|
275
267
|
}
|
|
276
268
|
if (!finalResult && !pendingTransition && !pendingHandoff && responseText.trim().length === 0 && pendingToolMessage) {
|
|
@@ -299,6 +291,7 @@ export class FlowManager {
|
|
|
299
291
|
model: this.config.model,
|
|
300
292
|
system: `${systemPrompt}\n\nRespond to the user now using the tool results above.`,
|
|
301
293
|
messages: this.context.messages,
|
|
294
|
+
abortSignal: this.config.abortSignal,
|
|
302
295
|
experimental_telemetry: this.config.telemetry,
|
|
303
296
|
});
|
|
304
297
|
responseText = followup.text;
|
|
@@ -326,11 +319,16 @@ export class FlowManager {
|
|
|
326
319
|
yield { type: 'flow-end', reason: 'handoff' };
|
|
327
320
|
return;
|
|
328
321
|
}
|
|
329
|
-
const
|
|
322
|
+
const evaluated = await this.evaluateTransitions(triggeredByUserTurn);
|
|
323
|
+
if (evaluated.blockedError) {
|
|
324
|
+
yield { type: 'error', error: evaluated.blockedError };
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const nextTransition = pendingTransition ?? evaluated.transition;
|
|
330
328
|
if (nextTransition) {
|
|
331
329
|
yield* this.transitionToGenerator(nextTransition.targetNode, nextTransition.data, nextTransition.node);
|
|
332
|
-
if (this.currentNodeConfig?.autoRespond !== false) {
|
|
333
|
-
yield* this.runInference();
|
|
330
|
+
if (!this.flowEnded && this.currentNodeConfig?.autoRespond !== false) {
|
|
331
|
+
yield* this.runInference(false);
|
|
334
332
|
}
|
|
335
333
|
}
|
|
336
334
|
if (node.postActions?.some(action => action.type === 'end')) {
|
|
@@ -339,7 +337,10 @@ export class FlowManager {
|
|
|
339
337
|
}
|
|
340
338
|
}
|
|
341
339
|
buildSystemPrompt(node) {
|
|
342
|
-
const
|
|
340
|
+
const includeGlobalPrompt = node.addGlobalPrompt !== false;
|
|
341
|
+
const rolePrompt = includeGlobalPrompt
|
|
342
|
+
? (this.config.defaultRolePrompt ?? this.config.flow.defaultRolePrompt ?? '')
|
|
343
|
+
: '';
|
|
343
344
|
const renderedPrompt = renderNodePrompt(node.prompt, this.context);
|
|
344
345
|
const dataKeys = Object.keys(this.context.collectedData);
|
|
345
346
|
const dataContext = dataKeys.length > 0
|
|
@@ -348,15 +349,19 @@ export class FlowManager {
|
|
|
348
349
|
const historyContext = this.context.nodeHistory.length > 1
|
|
349
350
|
? `\n\n## Progress\n${this.context.nodeHistory.slice(0, -1).join(' -> ')} -> **${node.name ?? node.id}**`
|
|
350
351
|
: '';
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
${renderedPrompt}${dataContext}${historyContext}
|
|
355
|
-
|
|
356
|
-
## Instructions
|
|
352
|
+
const currentTaskSection = `## Current Task
|
|
353
|
+
${renderedPrompt}${dataContext}${historyContext}`;
|
|
354
|
+
const instructionsSection = `## Instructions
|
|
357
355
|
- Focus only on the current task
|
|
358
356
|
- Use the available tools to progress the conversation
|
|
359
357
|
- Do not attempt tasks outside your current scope`;
|
|
358
|
+
const sections = [];
|
|
359
|
+
if (rolePrompt.trim().length > 0) {
|
|
360
|
+
sections.push(rolePrompt.trim());
|
|
361
|
+
}
|
|
362
|
+
sections.push(currentTaskSection);
|
|
363
|
+
sections.push(instructionsSection);
|
|
364
|
+
return sections.join('\n\n');
|
|
360
365
|
}
|
|
361
366
|
async applyContextStrategy(node) {
|
|
362
367
|
const strategy = node.contextStrategy ?? this.config.flow.contextStrategy ?? 'append';
|
|
@@ -367,9 +372,11 @@ ${renderedPrompt}${dataContext}${historyContext}
|
|
|
367
372
|
case 'reset_with_summary':
|
|
368
373
|
if (this.context.messages.length > 0) {
|
|
369
374
|
const summary = await this.generateSummary(node);
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
375
|
+
if (summary && summary.trim().length > 0) {
|
|
376
|
+
this.context.messages = [
|
|
377
|
+
{ role: 'system', content: `Previous conversation summary: ${summary}` },
|
|
378
|
+
];
|
|
379
|
+
}
|
|
373
380
|
}
|
|
374
381
|
break;
|
|
375
382
|
case 'append':
|
|
@@ -382,26 +389,166 @@ ${renderedPrompt}${dataContext}${historyContext}
|
|
|
382
389
|
?? this.config.flow.summaryPrompt
|
|
383
390
|
?? 'Summarize the key points from this conversation in 2-3 sentences.';
|
|
384
391
|
const maxTokens = node.summaryMaxTokens ?? this.config.flow.summaryMaxTokens;
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
392
|
+
const timeoutMs = node.summaryTimeoutMs ?? this.config.flow.summaryTimeoutMs ?? 5000;
|
|
393
|
+
try {
|
|
394
|
+
const generate = async () => {
|
|
395
|
+
const result = await generateText({
|
|
396
|
+
model: this.config.model,
|
|
397
|
+
system: prompt,
|
|
398
|
+
messages: this.context.messages,
|
|
399
|
+
...(typeof maxTokens === 'number' ? { maxTokens } : {}),
|
|
400
|
+
abortSignal: this.config.abortSignal,
|
|
401
|
+
experimental_telemetry: this.config.telemetry,
|
|
402
|
+
});
|
|
403
|
+
return result.text;
|
|
404
|
+
};
|
|
405
|
+
if (timeoutMs <= 0) {
|
|
406
|
+
return await generate();
|
|
407
|
+
}
|
|
408
|
+
let timeoutHandle;
|
|
409
|
+
const timeout = new Promise(resolve => {
|
|
410
|
+
timeoutHandle = setTimeout(() => resolve(null), timeoutMs);
|
|
411
|
+
});
|
|
412
|
+
const summary = await Promise.race([generate().catch(() => null), timeout]);
|
|
413
|
+
if (timeoutHandle) {
|
|
414
|
+
clearTimeout(timeoutHandle);
|
|
415
|
+
}
|
|
416
|
+
return summary;
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
normalizeForComparison(value) {
|
|
423
|
+
if (value === null || value === undefined) {
|
|
424
|
+
return value;
|
|
425
|
+
}
|
|
426
|
+
if (value instanceof Date) {
|
|
427
|
+
return value.toISOString();
|
|
428
|
+
}
|
|
429
|
+
if (Array.isArray(value)) {
|
|
430
|
+
return value.map(item => this.normalizeForComparison(item));
|
|
431
|
+
}
|
|
432
|
+
if (typeof value === 'function') {
|
|
433
|
+
return '[function]';
|
|
434
|
+
}
|
|
435
|
+
if (typeof value === 'object') {
|
|
436
|
+
const input = value;
|
|
437
|
+
const sortedKeys = Object.keys(input).sort();
|
|
438
|
+
const output = {};
|
|
439
|
+
for (const key of sortedKeys) {
|
|
440
|
+
output[key] = this.normalizeForComparison(input[key]);
|
|
441
|
+
}
|
|
442
|
+
return output;
|
|
443
|
+
}
|
|
444
|
+
return value;
|
|
445
|
+
}
|
|
446
|
+
isEquivalentPayload(a, b) {
|
|
447
|
+
return JSON.stringify(this.normalizeForComparison(a)) === JSON.stringify(this.normalizeForComparison(b));
|
|
448
|
+
}
|
|
449
|
+
resolvePendingSignals(transitions, handoffs) {
|
|
450
|
+
if (transitions.length === 0 && handoffs.length === 0) {
|
|
451
|
+
return {};
|
|
452
|
+
}
|
|
453
|
+
if (transitions.length > 0 && handoffs.length > 0) {
|
|
454
|
+
const transitionTool = transitions[0];
|
|
455
|
+
const handoffTool = handoffs[0];
|
|
456
|
+
return {
|
|
457
|
+
conflict: `Conflicting flow signals: both transition (${transitionTool.targetNode}) and handoff (${handoffTool.targetAgent}) requested in the same turn.`,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
if (handoffs.length > 0) {
|
|
461
|
+
const first = handoffs[0];
|
|
462
|
+
for (let i = 1; i < handoffs.length; i++) {
|
|
463
|
+
const next = handoffs[i];
|
|
464
|
+
if (next.targetAgent !== first.targetAgent) {
|
|
465
|
+
return {
|
|
466
|
+
conflict: `Conflicting handoffs requested in the same turn: "${first.targetAgent}" and "${next.targetAgent}".`,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
handoff: {
|
|
472
|
+
targetAgent: first.targetAgent,
|
|
473
|
+
reason: first.reason,
|
|
474
|
+
},
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
const firstTransition = transitions[0];
|
|
478
|
+
for (let i = 1; i < transitions.length; i++) {
|
|
479
|
+
const next = transitions[i];
|
|
480
|
+
if (next.targetNode !== firstTransition.targetNode) {
|
|
481
|
+
return {
|
|
482
|
+
conflict: `Conflicting transitions requested in the same turn: "${firstTransition.targetNode}" and "${next.targetNode}".`,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
if (!this.isEquivalentPayload(next.data, firstTransition.data)) {
|
|
486
|
+
return {
|
|
487
|
+
conflict: `Conflicting transition payloads for node "${firstTransition.targetNode}" in the same turn.`,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
if ((next.node?.id ?? null) !== (firstTransition.node?.id ?? null)) {
|
|
491
|
+
return {
|
|
492
|
+
conflict: `Conflicting dynamic node definitions for transition target "${firstTransition.targetNode}" in the same turn.`,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return {
|
|
497
|
+
transition: {
|
|
498
|
+
targetNode: firstTransition.targetNode,
|
|
499
|
+
data: firstTransition.data,
|
|
500
|
+
node: firstTransition.node,
|
|
501
|
+
},
|
|
502
|
+
};
|
|
393
503
|
}
|
|
394
504
|
resolveTools(node) {
|
|
395
|
-
|
|
505
|
+
const implicitTools = this.buildImplicitTransitionTools(node);
|
|
506
|
+
let explicitTools = {};
|
|
507
|
+
if (node.tools) {
|
|
508
|
+
explicitTools =
|
|
509
|
+
typeof node.tools === 'function'
|
|
510
|
+
? (node.tools(this.context) ?? {})
|
|
511
|
+
: node.tools;
|
|
512
|
+
}
|
|
513
|
+
// Explicit node tools win on name collision to preserve backwards compatibility.
|
|
514
|
+
return this.wrapTools({
|
|
515
|
+
...implicitTools,
|
|
516
|
+
...explicitTools,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
buildImplicitTransitionTools(node) {
|
|
520
|
+
const transitions = this.transitions.get(node.id) ?? [];
|
|
521
|
+
if (transitions.length === 0) {
|
|
396
522
|
return {};
|
|
397
|
-
if (typeof node.tools === 'function') {
|
|
398
|
-
return this.wrapTools(node.tools(this.context) ?? {});
|
|
399
523
|
}
|
|
400
|
-
|
|
524
|
+
const implicitTools = {};
|
|
525
|
+
const seenNames = new Set();
|
|
526
|
+
for (const transition of transitions) {
|
|
527
|
+
const eventName = typeof transition.on === 'string' ? transition.on.trim() : '';
|
|
528
|
+
if (!eventName || seenNames.has(eventName)) {
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
seenNames.add(eventName);
|
|
532
|
+
const label = transition.contract?.label?.trim();
|
|
533
|
+
const conditionText = transition.contract?.conditionText?.trim();
|
|
534
|
+
const descriptionParts = [
|
|
535
|
+
`Transition from "${node.id}" to "${transition.to}".`,
|
|
536
|
+
label ? `Label: ${label}.` : '',
|
|
537
|
+
conditionText ? `Condition: ${conditionText}.` : '',
|
|
538
|
+
'Call only when transition criteria are satisfied.',
|
|
539
|
+
].filter(Boolean);
|
|
540
|
+
implicitTools[eventName] = tool({
|
|
541
|
+
description: descriptionParts.join(' '),
|
|
542
|
+
inputSchema: implicitTransitionToolInputSchema,
|
|
543
|
+
execute: async (input) => createFlowTransition(transition.to, input.data, input.message),
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
return implicitTools;
|
|
401
547
|
}
|
|
402
548
|
wrapTools(tools) {
|
|
403
549
|
const guard = this.config.toolCallGuard;
|
|
404
|
-
|
|
550
|
+
const optionsFactory = this.config.toolExecutionOptionsFactory;
|
|
551
|
+
if (!guard && !optionsFactory)
|
|
405
552
|
return tools;
|
|
406
553
|
const wrapped = {};
|
|
407
554
|
for (const [toolName, toolDef] of Object.entries(tools ?? {})) {
|
|
@@ -412,51 +559,72 @@ ${renderedPrompt}${dataContext}${historyContext}
|
|
|
412
559
|
}
|
|
413
560
|
wrapped[toolName] = {
|
|
414
561
|
...toolDef,
|
|
415
|
-
execute: async (args) => {
|
|
416
|
-
const
|
|
417
|
-
if (
|
|
418
|
-
|
|
562
|
+
execute: async (args, options) => {
|
|
563
|
+
const toolCallId = typeof options?.toolCallId === 'string' ? options.toolCallId : undefined;
|
|
564
|
+
if (guard) {
|
|
565
|
+
const decision = await guard({ toolName, args, toolCallId });
|
|
566
|
+
if (!decision.allowed) {
|
|
567
|
+
throw new Error(decision.reason || 'Tool call blocked');
|
|
568
|
+
}
|
|
419
569
|
}
|
|
420
|
-
|
|
570
|
+
const extraContext = optionsFactory?.({ toolName, args, toolCallId });
|
|
571
|
+
const mergedOptions = mergeToolExecutionOptions(options, extraContext);
|
|
572
|
+
return exec(args, mergedOptions);
|
|
421
573
|
},
|
|
422
574
|
};
|
|
423
575
|
}
|
|
424
576
|
return wrapped;
|
|
425
577
|
}
|
|
426
|
-
appendMessage(message
|
|
578
|
+
appendMessage(message) {
|
|
427
579
|
const normalized = this.normalizeMessage(message);
|
|
428
580
|
if (!normalized) {
|
|
429
581
|
return;
|
|
430
582
|
}
|
|
431
583
|
this.context.messages.push(normalized);
|
|
432
|
-
if (persistToSession && this.sessionMessages && this.sessionMessages !== this.context.messages) {
|
|
433
|
-
this.sessionMessages.push(normalized);
|
|
434
|
-
}
|
|
435
584
|
}
|
|
436
585
|
normalizeMessage(message) {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
586
|
+
return normalizeModelMessage(message);
|
|
587
|
+
}
|
|
588
|
+
async *runActions(actions) {
|
|
589
|
+
for (const action of actions) {
|
|
590
|
+
if (action.deferUntil === 'turn-end') {
|
|
591
|
+
this.deferredActions.push(action);
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
switch (action.type) {
|
|
595
|
+
case 'say':
|
|
596
|
+
yield { type: 'text-delta', text: action.text };
|
|
597
|
+
break;
|
|
598
|
+
case 'end':
|
|
599
|
+
this.flowEnded = true;
|
|
600
|
+
yield { type: 'flow-end', reason: action.reason ?? 'action' };
|
|
601
|
+
break;
|
|
602
|
+
case 'function':
|
|
603
|
+
await action.handler(this.context);
|
|
604
|
+
break;
|
|
605
|
+
case 'emit':
|
|
606
|
+
this.pendingEvents.push({ name: action.event, data: action.data });
|
|
607
|
+
yield {
|
|
608
|
+
type: 'custom',
|
|
609
|
+
name: action.event,
|
|
610
|
+
data: action.data,
|
|
611
|
+
timestamp: new Date(),
|
|
612
|
+
};
|
|
613
|
+
break;
|
|
614
|
+
default:
|
|
615
|
+
break;
|
|
454
616
|
}
|
|
455
|
-
return { role, content: text };
|
|
456
617
|
}
|
|
457
|
-
return null;
|
|
458
618
|
}
|
|
459
|
-
async *
|
|
619
|
+
async *flushDeferredActions() {
|
|
620
|
+
if (this.deferredActions.length === 0) {
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
const actions = this.deferredActions;
|
|
624
|
+
this.deferredActions = [];
|
|
625
|
+
if (this.flowEnded) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
460
628
|
for (const action of actions) {
|
|
461
629
|
switch (action.type) {
|
|
462
630
|
case 'say':
|
|
@@ -471,24 +639,57 @@ ${renderedPrompt}${dataContext}${historyContext}
|
|
|
471
639
|
break;
|
|
472
640
|
case 'emit':
|
|
473
641
|
this.pendingEvents.push({ name: action.event, data: action.data });
|
|
642
|
+
yield {
|
|
643
|
+
type: 'custom',
|
|
644
|
+
name: action.event,
|
|
645
|
+
data: action.data,
|
|
646
|
+
timestamp: new Date(),
|
|
647
|
+
};
|
|
474
648
|
break;
|
|
475
649
|
default:
|
|
476
650
|
break;
|
|
477
651
|
}
|
|
652
|
+
if (this.flowEnded) {
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
478
655
|
}
|
|
479
656
|
}
|
|
480
|
-
|
|
657
|
+
checkTransitionPolicy(currentNodeId, targetNodeId, source, triggeredByUserTurn) {
|
|
658
|
+
const candidates = (this.transitions.get(currentNodeId) ?? []).filter(transition => transition.to === targetNodeId);
|
|
659
|
+
const candidate = candidates.find(transition => transition.contract) ?? candidates[0];
|
|
660
|
+
const contract = candidate?.contract;
|
|
661
|
+
if (!contract) {
|
|
662
|
+
return null;
|
|
663
|
+
}
|
|
664
|
+
if (contract.toolOnly && source === 'condition') {
|
|
665
|
+
return `Transition "${currentNodeId}" -> "${targetNodeId}" is blocked: contract.toolOnly=true requires a tool/event signal.`;
|
|
666
|
+
}
|
|
667
|
+
if (contract.requiresUserTurn && !triggeredByUserTurn) {
|
|
668
|
+
return `Transition "${currentNodeId}" -> "${targetNodeId}" is blocked: contract.requiresUserTurn=true requires a user turn before transition.`;
|
|
669
|
+
}
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
async evaluateTransitions(triggeredByUserTurn) {
|
|
481
673
|
const currentNode = this.currentNodeConfig?.id;
|
|
482
674
|
if (!currentNode) {
|
|
483
|
-
return
|
|
675
|
+
return {};
|
|
484
676
|
}
|
|
485
677
|
const transitions = this.transitions.get(currentNode) ?? [];
|
|
486
678
|
if (this.pendingEvents.length > 0) {
|
|
487
679
|
for (const [index, event] of this.pendingEvents.entries()) {
|
|
488
680
|
const match = transitions.find(transition => transition.on === event.name);
|
|
489
681
|
if (match) {
|
|
682
|
+
const policy = this.checkTransitionPolicy(currentNode, match.to, 'event', triggeredByUserTurn);
|
|
490
683
|
this.pendingEvents.splice(index, 1);
|
|
491
|
-
|
|
684
|
+
if (policy) {
|
|
685
|
+
return { blockedError: policy };
|
|
686
|
+
}
|
|
687
|
+
return {
|
|
688
|
+
transition: {
|
|
689
|
+
targetNode: match.to,
|
|
690
|
+
data: event.data,
|
|
691
|
+
},
|
|
692
|
+
};
|
|
492
693
|
}
|
|
493
694
|
}
|
|
494
695
|
}
|
|
@@ -498,10 +699,33 @@ ${renderedPrompt}${dataContext}${historyContext}
|
|
|
498
699
|
}
|
|
499
700
|
const shouldTransition = await transition.condition(this.context);
|
|
500
701
|
if (shouldTransition) {
|
|
501
|
-
|
|
702
|
+
const policy = this.checkTransitionPolicy(currentNode, transition.to, 'condition', triggeredByUserTurn);
|
|
703
|
+
if (policy) {
|
|
704
|
+
return { blockedError: policy };
|
|
705
|
+
}
|
|
706
|
+
return { transition: { targetNode: transition.to } };
|
|
502
707
|
}
|
|
503
708
|
}
|
|
504
|
-
return
|
|
709
|
+
return {};
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
function isRecord(value) {
|
|
713
|
+
return typeof value === 'object' && value !== null;
|
|
714
|
+
}
|
|
715
|
+
function mergeToolExecutionOptions(options, extraContext) {
|
|
716
|
+
if (!extraContext || Object.keys(extraContext).length === 0) {
|
|
717
|
+
return options;
|
|
505
718
|
}
|
|
719
|
+
const baseOptions = isRecord(options) ? options : {};
|
|
720
|
+
const existingContext = isRecord(baseOptions.experimental_context)
|
|
721
|
+
? baseOptions.experimental_context
|
|
722
|
+
: {};
|
|
723
|
+
return {
|
|
724
|
+
...baseOptions,
|
|
725
|
+
experimental_context: {
|
|
726
|
+
...existingContext,
|
|
727
|
+
...extraContext,
|
|
728
|
+
},
|
|
729
|
+
};
|
|
506
730
|
}
|
|
507
731
|
//# sourceMappingURL=FlowManager.js.map
|