@ai.ntellect/core 0.6.21 → 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.
Files changed (37) hide show
  1. package/.mocharc.json +2 -1
  2. package/README.md +61 -85
  3. package/graph/controller.ts +1 -1
  4. package/graph/event-manager.ts +288 -0
  5. package/graph/index.ts +153 -367
  6. package/graph/logger.ts +70 -0
  7. package/graph/node.ts +398 -0
  8. package/graph/observer.ts +361 -0
  9. package/interfaces/index.ts +102 -1
  10. package/modules/agenda/index.ts +3 -16
  11. package/package.json +10 -5
  12. package/test/graph/index.test.ts +244 -113
  13. package/test/graph/observer.test.ts +398 -0
  14. package/test/modules/agenda/node-cron.test.ts +37 -16
  15. package/test/modules/memory/adapters/in-memory.test.ts +2 -2
  16. package/test/modules/memory/adapters/meilisearch.test.ts +28 -24
  17. package/test/modules/memory/base.test.ts +3 -3
  18. package/tsconfig.json +4 -2
  19. package/types/index.ts +23 -2
  20. package/dist/graph/controller.js +0 -72
  21. package/dist/graph/index.js +0 -501
  22. package/dist/index.js +0 -41
  23. package/dist/interfaces/index.js +0 -17
  24. package/dist/modules/agenda/adapters/node-cron/index.js +0 -29
  25. package/dist/modules/agenda/index.js +0 -140
  26. package/dist/modules/embedding/adapters/ai/index.js +0 -57
  27. package/dist/modules/embedding/index.js +0 -59
  28. package/dist/modules/memory/adapters/in-memory/index.js +0 -210
  29. package/dist/modules/memory/adapters/meilisearch/index.js +0 -320
  30. package/dist/modules/memory/adapters/redis/index.js +0 -158
  31. package/dist/modules/memory/index.js +0 -103
  32. package/dist/types/index.js +0 -2
  33. package/dist/utils/generate-action-schema.js +0 -43
  34. package/dist/utils/header-builder.js +0 -34
  35. package/test/modules/embedding/ai.test.ts +0 -78
  36. package/test/modules/memory/adapters/redis.test.ts +0 -169
  37. package/test/services/agenda.test.ts +0 -279
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Handles logging operations for a graph instance
3
+ * Provides methods for adding, retrieving, and managing logs with optional verbose output
4
+ */
5
+ export class GraphLogger {
6
+ private logs: string[] = [];
7
+ private verbose: boolean = false;
8
+
9
+ /**
10
+ * Creates a new GraphLogger instance
11
+ * @param graphName - The name of the graph this logger is associated with
12
+ * @param verbose - Whether to output logs to console in real-time
13
+ */
14
+ constructor(private graphName: string, verbose: boolean = false) {
15
+ this.verbose = verbose;
16
+ }
17
+
18
+ /**
19
+ * Adds a new log entry with timestamp
20
+ * @param message - The message to log
21
+ */
22
+ public addLog(message: string): void {
23
+ const timestamp = new Date().toISOString();
24
+ const logMessage = `[${timestamp}] ${message}`;
25
+ this.logs.push(logMessage);
26
+ if (this.verbose) {
27
+ console.log(`${this.graphName} - ${message}`);
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Returns a copy of all stored logs
33
+ * @returns Array of log messages
34
+ */
35
+ public getLogs(): string[] {
36
+ return [...this.logs];
37
+ }
38
+
39
+ /**
40
+ * Clears all stored logs
41
+ */
42
+ public clearLogs(): void {
43
+ this.logs = [];
44
+ }
45
+
46
+ /**
47
+ * Sets the verbose mode
48
+ * @param enabled - Whether to enable verbose mode
49
+ */
50
+ public setVerbose(enabled: boolean): void {
51
+ this.verbose = enabled;
52
+ }
53
+
54
+ /**
55
+ * Gets the current verbose mode status
56
+ * @returns Current verbose mode state
57
+ */
58
+ public isVerbose(): boolean {
59
+ return this.verbose;
60
+ }
61
+
62
+ /**
63
+ * Logs a message to console with graph name prefix
64
+ * @param message - The message to log
65
+ * @param data - Optional data to log
66
+ */
67
+ log(message: string, data?: any): void {
68
+ console.log(`[Graph ${this.graphName}] ${message}`, data);
69
+ }
70
+ }
package/graph/node.ts ADDED
@@ -0,0 +1,398 @@
1
+ import { BehaviorSubject, Subject } from "rxjs";
2
+ import { ZodSchema } from "zod";
3
+ import { GraphContext, GraphEvent, Node } from "../types";
4
+ import { GraphEventManager } from "./event-manager";
5
+ import { GraphLogger } from "./logger";
6
+
7
+ /**
8
+ * Represents a node in the graph that can execute operations and manage state
9
+ * @template T - The Zod schema type for validation
10
+ */
11
+ export class GraphNode<T extends ZodSchema> {
12
+ /**
13
+ * Creates a new GraphNode instance
14
+ * @param nodes - Map of all nodes in the graph
15
+ * @param logger - Logger instance for tracking node operations
16
+ * @param eventManager - Manager for handling graph events
17
+ * @param eventSubject - Subject for emitting events
18
+ * @param stateSubject - Subject for managing graph state
19
+ */
20
+ constructor(
21
+ private nodes: Map<string, Node<T, any>>,
22
+ private logger: GraphLogger,
23
+ private eventManager: GraphEventManager<T>,
24
+ private eventSubject: Subject<GraphEvent<T>>,
25
+ private stateSubject: BehaviorSubject<GraphContext<T>>
26
+ ) {}
27
+
28
+ /**
29
+ * Emits an event with the specified type and payload
30
+ * @param type - The type of event to emit
31
+ * @param payload - The data associated with the event
32
+ * @private
33
+ */
34
+ private emitEvent(type: string, payload: any) {
35
+ this.logger.addLog(`📢 Event: ${type}`);
36
+ const event = {
37
+ type,
38
+ payload: {
39
+ ...payload,
40
+ name:
41
+ type === "nodeStateChanged"
42
+ ? payload.name || payload.nodeName
43
+ : payload.name,
44
+ context: { ...payload.context },
45
+ },
46
+ timestamp: Date.now(),
47
+ };
48
+
49
+ this.eventSubject.next(event);
50
+ this.eventManager.emitEvent(type, event);
51
+
52
+ // Update state subject only for state changes
53
+ if (type === "nodeStateChanged") {
54
+ this.stateSubject.next({ ...payload.context });
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Executes a node with the given name and context
60
+ * @param nodeName - The name of the node to execute
61
+ * @param context - The current graph context
62
+ * @param inputs - Input data for the node
63
+ * @param triggeredByEvent - Whether the execution was triggered by an event
64
+ * @throws Error if the node is not found or execution fails
65
+ */
66
+ async executeNode(
67
+ nodeName: string,
68
+ context: GraphContext<T>,
69
+ inputs: any,
70
+ triggeredByEvent: boolean = false
71
+ ): Promise<void> {
72
+ const node = this.nodes.get(nodeName);
73
+ if (!node) throw new Error(`Node "${nodeName}" not found.`);
74
+
75
+ this.logger.addLog(`🚀 Starting node "${nodeName}"`);
76
+ this.emitEvent("nodeStarted", { name: nodeName, context: { ...context } });
77
+
78
+ try {
79
+ const contextProxy = new Proxy(context, {
80
+ set: (target, prop, value) => {
81
+ const oldValue = target[prop];
82
+ target[prop] = value;
83
+
84
+ this.emitEvent("nodeStateChanged", {
85
+ nodeName,
86
+ name: nodeName,
87
+ property: prop.toString(),
88
+ oldValue,
89
+ newValue: value,
90
+ context: { ...target },
91
+ });
92
+
93
+ return true;
94
+ },
95
+ get: (target, prop) => {
96
+ return target[prop];
97
+ },
98
+ });
99
+
100
+ if (node.condition && !node.condition(contextProxy)) {
101
+ this.logger.addLog(
102
+ `⏭️ Skipping node "${nodeName}" - condition not met`
103
+ );
104
+ return;
105
+ }
106
+
107
+ if (node.inputs) {
108
+ await this.validateInputs(node, inputs, nodeName);
109
+ }
110
+
111
+ if (node.retry && node.retry.maxAttempts > 0) {
112
+ await this.executeWithRetry(node, contextProxy, inputs, nodeName);
113
+ } else {
114
+ await node.execute(contextProxy, inputs);
115
+ }
116
+
117
+ if (node.outputs) {
118
+ await this.validateOutputs(node, contextProxy, nodeName);
119
+ }
120
+
121
+ if (!triggeredByEvent) {
122
+ const nextNodes =
123
+ typeof node.next === "function"
124
+ ? node.next(contextProxy)
125
+ : node.next || [];
126
+
127
+ for (const nextNodeName of nextNodes) {
128
+ const nextNode = this.nodes.get(nextNodeName);
129
+ if (
130
+ nextNode &&
131
+ (!nextNode.condition || nextNode.condition(contextProxy))
132
+ ) {
133
+ await this.executeNode(
134
+ nextNodeName,
135
+ contextProxy,
136
+ undefined,
137
+ false
138
+ );
139
+ }
140
+ }
141
+ }
142
+
143
+ if (!triggeredByEvent) {
144
+ await this.handleEvents(node, nodeName, contextProxy);
145
+ }
146
+
147
+ this.logger.addLog(`✅ Node "${nodeName}" executed successfully`);
148
+ this.emitEvent("nodeCompleted", {
149
+ name: nodeName,
150
+ context: { ...contextProxy },
151
+ });
152
+ } catch (error: any) {
153
+ const errorToThrow =
154
+ error instanceof Error
155
+ ? error
156
+ : new Error(error.message || "Unknown error");
157
+
158
+ this.logger.addLog(
159
+ `❌ Error in node "${nodeName}": ${errorToThrow.message}`
160
+ );
161
+
162
+ this.emitEvent("nodeError", {
163
+ name: nodeName,
164
+ error: errorToThrow,
165
+ context,
166
+ });
167
+
168
+ throw errorToThrow;
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Validates the inputs for a node using its schema
174
+ * @param node - The node whose inputs need validation
175
+ * @param inputs - The input data to validate
176
+ * @param nodeName - The name of the node (for error messages)
177
+ * @throws Error if validation fails
178
+ * @private
179
+ */
180
+ private async validateInputs(
181
+ node: Node<T, any>,
182
+ inputs: any,
183
+ nodeName: string
184
+ ): Promise<void> {
185
+ if (!inputs) {
186
+ throw new Error(`Inputs required for node "${nodeName}"`);
187
+ }
188
+
189
+ try {
190
+ return node.inputs!.parse(inputs);
191
+ } catch (error: any) {
192
+ throw new Error(
193
+ error.errors?.[0]?.message || error.message || "Input validation failed"
194
+ );
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Validates the outputs of a node against its schema
200
+ * @param node - The node whose outputs need validation
201
+ * @param context - The current graph context
202
+ * @param nodeName - The name of the node (for error messages)
203
+ * @throws Error if validation fails
204
+ * @private
205
+ */
206
+ private async validateOutputs(
207
+ node: Node<T, any>,
208
+ context: GraphContext<T>,
209
+ nodeName: string
210
+ ): Promise<void> {
211
+ try {
212
+ node.outputs!.parse(context);
213
+ } catch (error: any) {
214
+ throw new Error(
215
+ error.errors?.[0]?.message ||
216
+ error.message ||
217
+ "Output validation failed"
218
+ );
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Handles event-related operations for a node
224
+ * @param node - The node whose events need handling
225
+ * @param nodeName - The name of the node
226
+ * @param context - The current graph context
227
+ * @private
228
+ */
229
+ private async handleEvents(
230
+ node: Node<T, any>,
231
+ nodeName: string,
232
+ context: GraphContext<T>
233
+ ): Promise<void> {
234
+ if (node.correlateEvents) {
235
+ await this.handleCorrelatedEvents(node, nodeName);
236
+ }
237
+
238
+ if (node.waitForEvents) {
239
+ await this.handleWaitForEvents(node, nodeName);
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Executes a node with retry logic
245
+ * @param node - The node to execute
246
+ * @param contextProxy - The proxied graph context
247
+ * @param inputs - Input data for the node
248
+ * @param nodeName - The name of the node
249
+ * @throws Error if all retry attempts fail
250
+ * @private
251
+ */
252
+ private async executeWithRetry(
253
+ node: Node<T, any>,
254
+ contextProxy: GraphContext<T>,
255
+ inputs: any,
256
+ nodeName: string
257
+ ): Promise<void> {
258
+ let attempts = 0;
259
+ let lastError: Error | null = null;
260
+
261
+ while (attempts < node.retry!.maxAttempts) {
262
+ try {
263
+ this.logger.addLog(
264
+ `🔄 Attempt ${attempts + 1}/${node.retry!.maxAttempts}`
265
+ );
266
+ await node.execute(contextProxy, inputs);
267
+ return;
268
+ } catch (error: any) {
269
+ lastError = error instanceof Error ? error : new Error(error.message);
270
+ attempts++;
271
+ this.logger.addLog(
272
+ `❌ Attempt ${attempts} failed: ${lastError.message}`
273
+ );
274
+
275
+ if (attempts === node.retry!.maxAttempts) {
276
+ if (node.retry!.onRetryFailed && lastError) {
277
+ await this.handleRetryFailure(
278
+ node,
279
+ lastError,
280
+ contextProxy,
281
+ nodeName
282
+ );
283
+ }
284
+ throw lastError;
285
+ }
286
+
287
+ await new Promise((resolve) =>
288
+ setTimeout(resolve, node.retry?.delay || 0)
289
+ );
290
+ }
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Handles the failure of retry attempts
296
+ * @param node - The node that failed
297
+ * @param error - The error that caused the failure
298
+ * @param context - The current graph context
299
+ * @param nodeName - The name of the node
300
+ * @private
301
+ */
302
+ private async handleRetryFailure(
303
+ node: Node<T, any>,
304
+ error: Error,
305
+ context: GraphContext<T>,
306
+ nodeName: string
307
+ ): Promise<void> {
308
+ this.logger.addLog(
309
+ `🔄 Executing retry failure handler for node "${nodeName}"`
310
+ );
311
+ try {
312
+ if (node.retry?.onRetryFailed) {
313
+ await node.retry.onRetryFailed(error, context);
314
+ if (node.retry.continueOnFailed) {
315
+ this.logger.addLog(
316
+ `✅ Retry failure handler succeeded - continuing execution`
317
+ );
318
+ return;
319
+ }
320
+ this.logger.addLog(
321
+ `⚠️ Retry failure handler executed but node will still fail`
322
+ );
323
+ }
324
+ } catch (handlerError: any) {
325
+ this.logger.addLog(
326
+ `❌ Retry failure handler failed: ${handlerError.message}`
327
+ );
328
+ throw handlerError;
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Handles correlated events for a node
334
+ * @param node - The node with correlated events
335
+ * @param nodeName - The name of the node
336
+ * @throws Error if correlation fails or timeout occurs
337
+ * @private
338
+ */
339
+ private async handleCorrelatedEvents(
340
+ node: Node<T, any>,
341
+ nodeName: string
342
+ ): Promise<void> {
343
+ if (node.correlateEvents) {
344
+ const { events, timeout, correlation } = node.correlateEvents;
345
+ this.logger.addLog(
346
+ `⏳ Node "${nodeName}" waiting for correlated events: ${events.join(
347
+ ", "
348
+ )}`
349
+ );
350
+
351
+ try {
352
+ // Attendre les événements
353
+ const receivedEvents = await this.eventManager.waitForEvents(
354
+ events,
355
+ timeout
356
+ );
357
+
358
+ // Vérifier la corrélation
359
+ if (!correlation(receivedEvents)) {
360
+ this.logger.addLog(
361
+ `❌ Event correlation failed for node "${nodeName}"`
362
+ );
363
+ throw new Error(`Event correlation failed for node "${nodeName}"`);
364
+ }
365
+
366
+ this.logger.addLog(
367
+ `✅ Event correlation succeeded for node "${nodeName}"`
368
+ );
369
+ } catch (error) {
370
+ this.logger.addLog(
371
+ `❌ Error waiting for events: ${(error as Error).message}`
372
+ );
373
+ throw error;
374
+ }
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Handles waiting for events
380
+ * @param node - The node waiting for events
381
+ * @param nodeName - The name of the node
382
+ * @throws Error if timeout occurs
383
+ * @private
384
+ */
385
+ private async handleWaitForEvents(
386
+ node: Node<T, any>,
387
+ nodeName: string
388
+ ): Promise<void> {
389
+ if (node.waitForEvents) {
390
+ const { events, timeout } = node.waitForEvents;
391
+ this.logger.addLog(
392
+ `⏳ Node "${nodeName}" waiting for events: ${events.join(", ")}`
393
+ );
394
+ await this.eventManager.waitForEvents(events, timeout);
395
+ this.logger.addLog(`✅ All events received for node "${nodeName}"`);
396
+ }
397
+ }
398
+ }