@ai.ntellect/core 0.6.20 → 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/.mocharc.json +2 -1
- package/README.md +87 -148
- package/graph/controller.ts +1 -1
- package/graph/event-manager.ts +288 -0
- package/graph/index.ts +152 -384
- package/graph/logger.ts +70 -0
- package/graph/node.ts +398 -0
- package/graph/observer.ts +361 -0
- package/interfaces/index.ts +102 -1
- package/modules/agenda/index.ts +3 -16
- package/modules/embedding/index.ts +3 -3
- package/package.json +12 -20
- package/test/graph/index.test.ts +296 -154
- package/test/graph/observer.test.ts +398 -0
- package/test/modules/agenda/node-cron.test.ts +37 -16
- package/test/modules/memory/adapters/in-memory.test.ts +2 -2
- package/test/modules/memory/adapters/meilisearch.test.ts +28 -24
- package/test/modules/memory/base.test.ts +3 -3
- package/tsconfig.json +4 -2
- package/types/index.ts +23 -2
- package/utils/generate-action-schema.ts +8 -7
- package/.env.example +0 -2
- package/dist/graph/controller.js +0 -75
- package/dist/graph/index.js +0 -402
- package/dist/index.js +0 -41
- package/dist/interfaces/index.js +0 -17
- package/dist/modules/agenda/adapters/node-cron/index.js +0 -29
- package/dist/modules/agenda/index.js +0 -140
- package/dist/modules/embedding/adapters/ai/index.js +0 -57
- package/dist/modules/embedding/index.js +0 -59
- package/dist/modules/memory/adapters/in-memory/index.js +0 -210
- package/dist/modules/memory/adapters/meilisearch/index.js +0 -320
- package/dist/modules/memory/adapters/redis/index.js +0 -158
- package/dist/modules/memory/index.js +0 -103
- package/dist/types/index.js +0 -2
- package/dist/utils/generate-action-schema.js +0 -42
- package/dist/utils/header-builder.js +0 -34
- package/graph.ts +0 -74
- package/test/modules/embedding/ai.test.ts +0 -78
- package/test/modules/memory/adapters/redis.test.ts +0 -169
- package/test/services/agenda.test.ts +0 -279
package/graph/index.ts
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
import { EventEmitter } from "events";
|
2
|
-
import {
|
2
|
+
import { BehaviorSubject, Subject } from "rxjs";
|
3
3
|
import { ZodSchema } from "zod";
|
4
|
-
import {
|
4
|
+
import { GraphObservable, IEventEmitter } from "../interfaces";
|
5
|
+
import { GraphContext, GraphDefinition, GraphEvent, Node } from "../types";
|
6
|
+
import { GraphEventManager } from "./event-manager";
|
7
|
+
import { GraphLogger } from "./logger";
|
8
|
+
import { GraphNode } from "./node";
|
9
|
+
import { GraphObserver } from "./observer";
|
5
10
|
|
6
11
|
/**
|
7
12
|
* @module GraphFlow
|
@@ -17,15 +22,23 @@ import { GraphContext, GraphDefinition, Node } from "../types";
|
|
17
22
|
* @template T - Extends ZodSchema for type validation
|
18
23
|
*/
|
19
24
|
export class GraphFlow<T extends ZodSchema> {
|
20
|
-
private nodes: Map<string, Node<T, any>>;
|
21
25
|
private context: GraphContext<T>;
|
22
26
|
public validator?: T;
|
23
27
|
private eventEmitter: IEventEmitter;
|
24
28
|
private globalErrorHandler?: (error: Error, context: GraphContext<T>) => void;
|
25
29
|
private graphEvents?: string[];
|
26
30
|
private entryNode?: string;
|
27
|
-
private logs: string[] = [];
|
28
31
|
private verbose: boolean = false;
|
32
|
+
public nodes: Map<string, Node<T, any>>;
|
33
|
+
|
34
|
+
private eventSubject: Subject<GraphEvent<T>> = new Subject();
|
35
|
+
private stateSubject: BehaviorSubject<GraphContext<T>>;
|
36
|
+
private destroySubject: Subject<void> = new Subject();
|
37
|
+
|
38
|
+
public observer: GraphObserver<T>;
|
39
|
+
private logger: GraphLogger;
|
40
|
+
private eventManager: GraphEventManager<T>;
|
41
|
+
private nodeExecutor: GraphNode<T>;
|
29
42
|
|
30
43
|
/**
|
31
44
|
* Creates a new instance of GraphFlow
|
@@ -49,17 +62,36 @@ export class GraphFlow<T extends ZodSchema> {
|
|
49
62
|
this.graphEvents = config.events;
|
50
63
|
this.verbose = options.verbose ?? false;
|
51
64
|
|
65
|
+
this.stateSubject = new BehaviorSubject<GraphContext<T>>(this.context);
|
66
|
+
|
67
|
+
this.logger = new GraphLogger(name, options.verbose);
|
68
|
+
this.eventManager = new GraphEventManager(
|
69
|
+
this.eventEmitter,
|
70
|
+
this.nodes,
|
71
|
+
name,
|
72
|
+
this.context,
|
73
|
+
config.events,
|
74
|
+
config.entryNode,
|
75
|
+
config.onError
|
76
|
+
);
|
77
|
+
this.nodeExecutor = new GraphNode(
|
78
|
+
this.nodes,
|
79
|
+
this.logger,
|
80
|
+
this.eventManager,
|
81
|
+
this.eventSubject,
|
82
|
+
this.stateSubject
|
83
|
+
);
|
84
|
+
|
85
|
+
this.setupEventStreams();
|
52
86
|
this.setupEventListeners();
|
53
87
|
this.setupGraphEventListeners();
|
54
|
-
}
|
55
88
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
return structuredClone(this.context);
|
89
|
+
this.observer = new GraphObserver(
|
90
|
+
this,
|
91
|
+
this.eventSubject,
|
92
|
+
this.stateSubject,
|
93
|
+
this.destroySubject
|
94
|
+
);
|
63
95
|
}
|
64
96
|
|
65
97
|
/**
|
@@ -67,77 +99,49 @@ export class GraphFlow<T extends ZodSchema> {
|
|
67
99
|
* @private
|
68
100
|
* @description Attaches all node-based event triggers while preserving external listeners
|
69
101
|
*/
|
70
|
-
private
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
.rawListeners("*")
|
75
|
-
.entries()) {
|
76
|
-
// This can be tricky—EventEmitter doesn't directly let you remove by "type" of listener.
|
77
|
-
// Alternatively, we can store references in a separate structure.
|
78
|
-
// For simplicity, let's do a full removeAllListeners() on node-specified events (only),
|
79
|
-
// then re-add them below, but keep the test-based events like "nodeStarted" or "nodeCompleted".
|
80
|
-
}
|
81
|
-
|
82
|
-
// The simplest approach: removeAllListeners for each event that is declared as a node event
|
83
|
-
// so we don't stack up duplicates:
|
84
|
-
const allEvents = new Set<string>();
|
85
|
-
for (const node of this.nodes.values()) {
|
86
|
-
if (node.events) {
|
87
|
-
node.events.forEach((evt) => allEvents.add(evt));
|
88
|
-
}
|
89
|
-
}
|
90
|
-
for (const evt of allEvents) {
|
91
|
-
// remove only those events that are used by nodes
|
92
|
-
this.eventEmitter.removeAllListeners(evt);
|
93
|
-
}
|
102
|
+
private setupEventStreams(): void {
|
103
|
+
this.eventManager.on("nodeStarted", (data) => {
|
104
|
+
this.addLog(`Event: Node "${data.name}" started`);
|
105
|
+
});
|
94
106
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
node.events.forEach((event) => {
|
99
|
-
this.eventEmitter.on(
|
100
|
-
event,
|
101
|
-
async (data?: Partial<GraphContext<T>>) => {
|
102
|
-
const freshContext = this.createNewContext();
|
103
|
-
if (data) Object.assign(freshContext, data);
|
107
|
+
this.eventManager.on("nodeCompleted", (data) => {
|
108
|
+
this.addLog(`Event: Node "${data.name}" completed`);
|
109
|
+
});
|
104
110
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
});
|
111
|
+
this.eventManager.on("nodeError", (data) => {
|
112
|
+
let errorMessage = "Unknown error";
|
113
|
+
if (data.error) {
|
114
|
+
errorMessage =
|
115
|
+
data.error instanceof Error
|
116
|
+
? data.error.message
|
117
|
+
: data.error.errors?.[0]?.message ||
|
118
|
+
data.error.message ||
|
119
|
+
"Unknown error";
|
115
120
|
}
|
116
|
-
|
117
|
-
|
121
|
+
this.addLog(`Event: Node "${data.name}" error: ${errorMessage}`);
|
122
|
+
});
|
118
123
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
if (this.verbose) {
|
123
|
-
console.log(`[${this.name}] ${message}`);
|
124
|
-
}
|
124
|
+
this.eventManager.on("nodeStateChanged", (data) => {
|
125
|
+
this.addLog(`Event: Node "${data.name}" state changed`);
|
126
|
+
});
|
125
127
|
}
|
126
128
|
|
127
129
|
/**
|
128
|
-
*
|
129
|
-
* @
|
130
|
+
* Sets up event listeners for node-based events
|
131
|
+
* @private
|
132
|
+
* @description Attaches all node-based event triggers while preserving external listeners
|
130
133
|
*/
|
131
|
-
|
132
|
-
this.
|
134
|
+
private setupEventListeners(): void {
|
135
|
+
this.eventManager.setupEventListeners();
|
133
136
|
}
|
134
137
|
|
135
138
|
/**
|
136
|
-
*
|
137
|
-
* @
|
139
|
+
* Sets up event listeners for graph-based events
|
140
|
+
* @private
|
141
|
+
* @description Attaches all graph-based event triggers
|
138
142
|
*/
|
139
|
-
|
140
|
-
|
143
|
+
private setupGraphEventListeners(): void {
|
144
|
+
this.eventManager.setupGraphEventListeners();
|
141
145
|
}
|
142
146
|
|
143
147
|
/**
|
@@ -155,211 +159,58 @@ export class GraphFlow<T extends ZodSchema> {
|
|
155
159
|
inputs: any,
|
156
160
|
triggeredByEvent: boolean = false
|
157
161
|
): Promise<void> {
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
const localContext = structuredClone(context);
|
166
|
-
|
167
|
-
if (node.condition && !node.condition(localContext)) {
|
168
|
-
this.addLog(`⏭️ Skipping node "${nodeName}" - condition not met`);
|
169
|
-
return;
|
170
|
-
}
|
171
|
-
|
172
|
-
// Validate inputs
|
173
|
-
if (node.inputs) {
|
174
|
-
if (!inputs) {
|
175
|
-
this.addLog(`❌ Missing required inputs for node "${nodeName}"`);
|
176
|
-
throw new Error(`Inputs required for node "${nodeName}"`);
|
177
|
-
}
|
178
|
-
this.addLog(`📥 Validating inputs for node "${nodeName}"`);
|
179
|
-
inputs = node.inputs.parse(inputs);
|
180
|
-
}
|
181
|
-
|
182
|
-
// Handle retry logic
|
183
|
-
if (node.retry && node.retry.maxAttempts > 0) {
|
184
|
-
let attempts = 0;
|
185
|
-
let lastError: Error | null = null;
|
186
|
-
|
187
|
-
while (attempts < node.retry.maxAttempts) {
|
188
|
-
try {
|
189
|
-
this.addLog(`🔄 Attempt ${attempts + 1}/${node.retry.maxAttempts}`);
|
190
|
-
await node.execute(localContext, inputs);
|
191
|
-
lastError = null;
|
192
|
-
break;
|
193
|
-
} catch (error: any) {
|
194
|
-
lastError = error as Error;
|
195
|
-
attempts++;
|
196
|
-
this.addLog(`❌ Attempt ${attempts} failed: ${error.message}`);
|
197
|
-
|
198
|
-
if (attempts === node.retry.maxAttempts) {
|
199
|
-
// Si toutes les tentatives ont échoué et qu'il y a un gestionnaire d'échec
|
200
|
-
if (node.retry.onRetryFailed) {
|
201
|
-
this.addLog(
|
202
|
-
`🔄 Executing retry failure handler for node "${nodeName}"`
|
203
|
-
);
|
204
|
-
try {
|
205
|
-
await node.retry.onRetryFailed(lastError, localContext);
|
206
|
-
// Si le gestionnaire d'échec réussit, on continue l'exécution
|
207
|
-
// SEULEMENT si le gestionnaire a explicitement retourné true
|
208
|
-
if (node.retry.continueOnFailed) {
|
209
|
-
this.addLog(
|
210
|
-
`✅ Retry failure handler succeeded for node "${nodeName}" - continuing execution`
|
211
|
-
);
|
212
|
-
break;
|
213
|
-
} else {
|
214
|
-
this.addLog(
|
215
|
-
`⚠️ Retry failure handler executed but node "${nodeName}" will still fail`
|
216
|
-
);
|
217
|
-
throw lastError;
|
218
|
-
}
|
219
|
-
} catch (handlerError: any) {
|
220
|
-
this.addLog(
|
221
|
-
`❌ Retry failure handler failed for node "${nodeName}": ${handlerError.message}`
|
222
|
-
);
|
223
|
-
throw handlerError;
|
224
|
-
}
|
225
|
-
}
|
226
|
-
// Si pas de gestionnaire d'échec ou si le gestionnaire a échoué
|
227
|
-
throw lastError;
|
228
|
-
}
|
229
|
-
|
230
|
-
if (attempts < node.retry.maxAttempts) {
|
231
|
-
this.addLog(
|
232
|
-
`⏳ Waiting ${node.retry.delay}ms before next attempt`
|
233
|
-
);
|
234
|
-
await new Promise((resolve) =>
|
235
|
-
setTimeout(resolve, node.retry?.delay || 0)
|
236
|
-
);
|
237
|
-
}
|
238
|
-
}
|
239
|
-
}
|
240
|
-
} else {
|
241
|
-
await node.execute(localContext, inputs);
|
242
|
-
}
|
243
|
-
|
244
|
-
// Validate outputs
|
245
|
-
if (node.outputs) {
|
246
|
-
this.addLog(`📤 Validating outputs for node "${nodeName}"`);
|
247
|
-
node.outputs.parse(localContext);
|
248
|
-
}
|
249
|
-
|
250
|
-
Object.assign(context, localContext);
|
251
|
-
|
252
|
-
this.addLog(
|
253
|
-
`✅ Node "${nodeName}" executed successfully ${JSON.stringify(context)}`
|
254
|
-
);
|
255
|
-
this.eventEmitter.emit("nodeCompleted", { name: nodeName });
|
256
|
-
|
257
|
-
// Handle waitForEvent
|
258
|
-
if (node.waitForEvent && !triggeredByEvent) {
|
259
|
-
this.addLog(
|
260
|
-
`⏳ Node "${nodeName}" waiting for events: ${node.events?.join(", ")}`
|
261
|
-
);
|
262
|
-
|
263
|
-
await new Promise<void>((resolve) => {
|
264
|
-
const eventHandler = () => {
|
265
|
-
this.addLog(`🚀 Event received for node "${nodeName}"`);
|
266
|
-
resolve();
|
267
|
-
};
|
268
|
-
|
269
|
-
node.events?.forEach((event) => {
|
270
|
-
this.eventEmitter.once(event, eventHandler);
|
271
|
-
});
|
272
|
-
});
|
162
|
+
return this.nodeExecutor.executeNode(
|
163
|
+
nodeName,
|
164
|
+
context,
|
165
|
+
inputs,
|
166
|
+
triggeredByEvent
|
167
|
+
);
|
168
|
+
}
|
273
169
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
: node.next || [];
|
278
|
-
|
279
|
-
if (nextNodes.length > 0) {
|
280
|
-
this.addLog(`➡️ Executing next nodes: ${nextNodes.join(", ")}`);
|
281
|
-
|
282
|
-
// Créer un contexte unique pour toutes les branches
|
283
|
-
const branchContext = structuredClone(context);
|
284
|
-
|
285
|
-
// Exécuter les branches séquentiellement avec le même contexte
|
286
|
-
for (const nextNodeName of nextNodes) {
|
287
|
-
this.addLog(`🔄 Starting branch for node "${nextNodeName}"`);
|
288
|
-
const nextNode = this.nodes.get(nextNodeName);
|
289
|
-
if (nextNode) {
|
290
|
-
// Utiliser le même contexte pour toutes les branches
|
291
|
-
await this.executeNode(
|
292
|
-
nextNodeName,
|
293
|
-
branchContext,
|
294
|
-
undefined,
|
295
|
-
nextNode.waitForEvent
|
296
|
-
);
|
297
|
-
}
|
298
|
-
this.addLog(`✅ Branch "${nextNodeName}" completed`);
|
299
|
-
}
|
170
|
+
private addLog(message: string): void {
|
171
|
+
this.logger.addLog(message);
|
172
|
+
}
|
300
173
|
|
301
|
-
|
302
|
-
|
303
|
-
|
174
|
+
public getLogs(): string[] {
|
175
|
+
return this.logger.getLogs();
|
176
|
+
}
|
304
177
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
});
|
178
|
+
public clearLogs(): void {
|
179
|
+
this.logger.clearLogs();
|
180
|
+
}
|
309
181
|
|
310
|
-
|
311
|
-
|
312
|
-
|
182
|
+
/**
|
183
|
+
* Get the observer instance for monitoring graph state and events
|
184
|
+
*/
|
185
|
+
public observe(
|
186
|
+
options: {
|
187
|
+
debounce?: number;
|
188
|
+
delay?: number;
|
189
|
+
stream?: boolean;
|
190
|
+
properties?: (keyof GraphContext<T> extends string
|
191
|
+
? keyof GraphContext<T>
|
192
|
+
: never)[];
|
193
|
+
onStreamLetter?: (data: { letter: string; property: string }) => void;
|
194
|
+
onStreamComplete?: () => void;
|
195
|
+
} = {}
|
196
|
+
): GraphObservable<T> {
|
197
|
+
return this.observer.state(options) as GraphObservable<T>;
|
198
|
+
}
|
313
199
|
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
if (nextNodes.length > 0) {
|
321
|
-
this.addLog(`➡️ Executing next nodes: ${nextNodes.join(", ")}`);
|
322
|
-
|
323
|
-
// Créer un contexte unique pour toutes les branches
|
324
|
-
const branchContext = structuredClone(context);
|
325
|
-
|
326
|
-
// Exécuter les branches séquentiellement avec le même contexte
|
327
|
-
for (const nextNodeName of nextNodes) {
|
328
|
-
this.addLog(`🔄 Starting branch for node "${nextNodeName}"`);
|
329
|
-
const nextNode = this.nodes.get(nextNodeName);
|
330
|
-
if (nextNode) {
|
331
|
-
// Utiliser le même contexte pour toutes les branches
|
332
|
-
await this.executeNode(
|
333
|
-
nextNodeName,
|
334
|
-
branchContext,
|
335
|
-
undefined,
|
336
|
-
nextNode.waitForEvent
|
337
|
-
);
|
338
|
-
}
|
339
|
-
this.addLog(`✅ Branch "${nextNodeName}" completed`);
|
340
|
-
}
|
341
|
-
|
342
|
-
// Mettre à jour le contexte global avec le résultat final des branches
|
343
|
-
Object.assign(context, branchContext);
|
344
|
-
this.context = structuredClone(context);
|
345
|
-
}
|
346
|
-
} catch (error: any) {
|
347
|
-
this.addLog(`❌ Error in node "${nodeName}": ${error.message}`);
|
348
|
-
this.eventEmitter.emit("nodeError", { name: nodeName, error });
|
349
|
-
throw error;
|
350
|
-
}
|
200
|
+
/**
|
201
|
+
* Enable or disable verbose logging
|
202
|
+
* @param {boolean} enabled - Whether to enable verbose logging
|
203
|
+
*/
|
204
|
+
public setVerbose(enabled: boolean): void {
|
205
|
+
this.logger.setVerbose(enabled);
|
351
206
|
}
|
352
207
|
|
353
208
|
/**
|
354
|
-
*
|
355
|
-
* @
|
356
|
-
* @param {GraphContext<T>} context - Context to validate
|
357
|
-
* @throws {Error} If validation fails
|
209
|
+
* Get current verbose setting
|
210
|
+
* @returns {boolean} Current verbose setting
|
358
211
|
*/
|
359
|
-
|
360
|
-
|
361
|
-
this.validator.parse(context);
|
362
|
-
}
|
212
|
+
public isVerbose(): boolean {
|
213
|
+
return this.logger.isVerbose();
|
363
214
|
}
|
364
215
|
|
365
216
|
/**
|
@@ -369,13 +220,12 @@ export class GraphFlow<T extends ZodSchema> {
|
|
369
220
|
* @param {any} inputParams - Optional input parameters for the start node
|
370
221
|
* @returns {Promise<GraphContext<T>>} Final context after execution
|
371
222
|
*/
|
372
|
-
async execute(
|
223
|
+
public async execute(
|
373
224
|
startNode: string,
|
374
225
|
inputParams?: any,
|
375
226
|
inputContext?: Partial<GraphContext<T>>
|
376
227
|
): Promise<GraphContext<T>> {
|
377
228
|
if (inputParams) {
|
378
|
-
// Merge inputParams into context
|
379
229
|
Object.assign(this.context, inputParams);
|
380
230
|
}
|
381
231
|
|
@@ -386,7 +236,12 @@ export class GraphFlow<T extends ZodSchema> {
|
|
386
236
|
this.eventEmitter.emit("graphStarted", { name: this.name });
|
387
237
|
|
388
238
|
try {
|
389
|
-
await this.executeNode(
|
239
|
+
await this.nodeExecutor.executeNode(
|
240
|
+
startNode,
|
241
|
+
this.context,
|
242
|
+
inputParams,
|
243
|
+
false
|
244
|
+
);
|
390
245
|
|
391
246
|
this.eventEmitter.emit("graphCompleted", {
|
392
247
|
name: this.name,
|
@@ -405,36 +260,19 @@ export class GraphFlow<T extends ZodSchema> {
|
|
405
260
|
* Emits an event to trigger event-based nodes
|
406
261
|
* @param {string} eventName - Name of the event to emit
|
407
262
|
* @param {Partial<GraphContext<T>>} data - Optional data to merge with context
|
408
|
-
* @returns {Promise<
|
263
|
+
* @returns {Promise<void>}
|
409
264
|
*/
|
410
265
|
public async emit(
|
411
266
|
eventName: string,
|
412
267
|
data?: Partial<GraphContext<T>>
|
413
|
-
): Promise<
|
414
|
-
const
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
}
|
419
|
-
|
420
|
-
|
421
|
-
node.events?.includes(eventName)
|
422
|
-
);
|
423
|
-
|
424
|
-
// Execute event nodes sequentially with shared context
|
425
|
-
for (const node of eventNodes) {
|
426
|
-
await this.executeNode(node.name, workingContext, undefined, true);
|
427
|
-
}
|
428
|
-
|
429
|
-
// Update global context after all event nodes are executed
|
430
|
-
this.context = structuredClone(workingContext);
|
431
|
-
|
432
|
-
this.eventEmitter.emit("graphCompleted", {
|
433
|
-
name: this.name,
|
434
|
-
context: this.context,
|
435
|
-
});
|
436
|
-
|
437
|
-
return this.getContext();
|
268
|
+
): Promise<void> {
|
269
|
+
const event: GraphEvent<T> = {
|
270
|
+
type: eventName,
|
271
|
+
payload: data,
|
272
|
+
timestamp: Date.now(),
|
273
|
+
};
|
274
|
+
this.eventSubject.next(event);
|
275
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
438
276
|
}
|
439
277
|
|
440
278
|
/**
|
@@ -442,15 +280,15 @@ export class GraphFlow<T extends ZodSchema> {
|
|
442
280
|
* @param {string} eventName - Name of the event to listen for
|
443
281
|
* @param {Function} handler - Handler function to execute when event is emitted
|
444
282
|
*/
|
445
|
-
on(eventName: string, handler: (...args: any[]) => void): void {
|
446
|
-
this.
|
283
|
+
public on(eventName: string, handler: (...args: any[]) => void): void {
|
284
|
+
this.eventManager.on(eventName, handler);
|
447
285
|
}
|
448
286
|
|
449
287
|
/**
|
450
288
|
* Updates the graph definition with new configuration
|
451
289
|
* @param {GraphDefinition<T>} definition - New graph definition
|
452
290
|
*/
|
453
|
-
load(definition: GraphDefinition<T>): void {
|
291
|
+
public load(definition: GraphDefinition<T>): void {
|
454
292
|
// Clear all existing nodes
|
455
293
|
this.nodes.clear();
|
456
294
|
// Wipe out old node-based event listeners
|
@@ -512,8 +350,8 @@ export class GraphFlow<T extends ZodSchema> {
|
|
512
350
|
* @param {string} message - Message to log
|
513
351
|
* @param {any} data - Optional data to log
|
514
352
|
*/
|
515
|
-
log(message: string, data?: any): void {
|
516
|
-
|
353
|
+
public log(message: string, data?: any): void {
|
354
|
+
this.logger.log(message, data);
|
517
355
|
}
|
518
356
|
|
519
357
|
/**
|
@@ -521,105 +359,35 @@ export class GraphFlow<T extends ZodSchema> {
|
|
521
359
|
* @param {Node<T>} node - Node to add
|
522
360
|
* @throws {Error} If node with same name already exists
|
523
361
|
*/
|
524
|
-
addNode(node: Node<T, any>): void {
|
362
|
+
public addNode(node: Node<T, any>): void {
|
525
363
|
this.nodes.set(node.name, node);
|
526
|
-
|
527
|
-
for (const evt of node.events) {
|
528
|
-
this.eventEmitter.on(evt, async (data?: Partial<GraphContext<T>>) => {
|
529
|
-
const freshContext = this.createNewContext();
|
530
|
-
if (data) Object.assign(freshContext, data);
|
531
|
-
await this.executeNode(node.name, freshContext, undefined, true);
|
532
|
-
});
|
533
|
-
}
|
534
|
-
}
|
364
|
+
this.eventManager.setupEventListeners();
|
535
365
|
}
|
536
366
|
|
537
367
|
/**
|
538
368
|
* Removes a node from the graph
|
539
369
|
* @param {string} nodeName - Name of the node to remove
|
540
370
|
*/
|
541
|
-
removeNode(nodeName: string): void {
|
542
|
-
const node = this.nodes.get(nodeName);
|
543
|
-
if (!node) return;
|
544
|
-
|
545
|
-
// remove the node from the map
|
371
|
+
public removeNode(nodeName: string): void {
|
546
372
|
this.nodes.delete(nodeName);
|
547
|
-
|
548
|
-
// remove any of its event-based listeners
|
549
|
-
if (node.events && node.events.length > 0) {
|
550
|
-
for (const evt of node.events) {
|
551
|
-
// removeAllListeners(evt) would also remove other node listeners,
|
552
|
-
// so we need a more fine-grained approach. Ideally, we should keep a reference
|
553
|
-
// to the exact listener function we attached. For brevity, let's remove all for that event:
|
554
|
-
this.eventEmitter.removeAllListeners(evt);
|
555
|
-
}
|
556
|
-
// Then reattach the others that remain in the graph
|
557
|
-
for (const n of this.nodes.values()) {
|
558
|
-
if (n.events && n.events.length > 0) {
|
559
|
-
n.events.forEach((e) => {
|
560
|
-
this.eventEmitter.on(e, async (data?: Partial<GraphContext<T>>) => {
|
561
|
-
const freshContext = this.createNewContext();
|
562
|
-
if (data) Object.assign(freshContext, data);
|
563
|
-
await this.executeNode(n.name, freshContext, undefined, true);
|
564
|
-
});
|
565
|
-
});
|
566
|
-
}
|
567
|
-
}
|
568
|
-
}
|
373
|
+
this.eventManager.setupEventListeners();
|
569
374
|
}
|
570
375
|
|
571
376
|
/**
|
572
377
|
* Returns all nodes in the graph
|
573
378
|
* @returns {Node<T>[]} Array of all nodes
|
574
379
|
*/
|
575
|
-
getNodes(): Node<T, any>[] {
|
380
|
+
public getNodes(): Node<T, any>[] {
|
576
381
|
return Array.from(this.nodes.values());
|
577
382
|
}
|
578
383
|
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
this.eventEmitter.emit("graphStarted", { name: this.name });
|
588
|
-
|
589
|
-
try {
|
590
|
-
// Execute the graph starting from the entry node
|
591
|
-
if (!this.entryNode) {
|
592
|
-
throw new Error("No entry node defined for graph event handling");
|
593
|
-
}
|
594
|
-
|
595
|
-
await this.executeNode(
|
596
|
-
this.entryNode,
|
597
|
-
freshContext,
|
598
|
-
undefined,
|
599
|
-
false
|
600
|
-
);
|
601
|
-
|
602
|
-
// Emit "graphCompleted"
|
603
|
-
this.eventEmitter.emit("graphCompleted", {
|
604
|
-
name: this.name,
|
605
|
-
context: this.context,
|
606
|
-
});
|
607
|
-
} catch (error) {
|
608
|
-
// Emit "graphError"
|
609
|
-
this.eventEmitter.emit("graphError", { name: this.name, error });
|
610
|
-
this.globalErrorHandler?.(error as Error, freshContext);
|
611
|
-
throw error;
|
612
|
-
}
|
613
|
-
});
|
614
|
-
});
|
615
|
-
}
|
616
|
-
}
|
617
|
-
|
618
|
-
getLogs(): string[] {
|
619
|
-
return [...this.logs];
|
620
|
-
}
|
621
|
-
|
622
|
-
clearLogs(): void {
|
623
|
-
this.logs = [];
|
384
|
+
/**
|
385
|
+
* Cleanup resources
|
386
|
+
*/
|
387
|
+
public destroy(): void {
|
388
|
+
this.destroySubject.next();
|
389
|
+
this.destroySubject.complete();
|
390
|
+
this.eventSubject.complete();
|
391
|
+
this.stateSubject.complete();
|
624
392
|
}
|
625
393
|
}
|