@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
@@ -2,43 +2,63 @@ import { expect } from "chai";
2
2
  import EventEmitter from "events";
3
3
  import sinon from "sinon";
4
4
  import { z } from "zod";
5
- import { GraphController } from "../../graph/controller";
5
+ import { GraphFlowController } from "../../graph/controller";
6
6
  import { GraphFlow } from "../../graph/index";
7
- import { GraphDefinition, Node } from "../../types";
7
+ import { GraphContext, GraphDefinition, Node } from "../../types";
8
8
 
9
9
  /**
10
- * Define a valid schema using Zod.
10
+ * Test schema definition using Zod for graph context validation
11
+ * Defines a schema with:
12
+ * - value: numeric value for tracking state changes
13
+ * - eventPayload: optional object containing transaction metadata
11
14
  */
12
15
  const TestSchema = z.object({
13
16
  value: z.number().default(0),
17
+ eventPayload: z
18
+ .object({
19
+ transactionId: z.string().optional(),
20
+ status: z.string().optional(),
21
+ })
22
+ .optional(),
14
23
  });
15
24
 
16
- /**
17
- * ✅ Define the schema type for TypeScript inference.
18
- */
19
25
  type TestSchema = typeof TestSchema;
20
26
 
27
+ /**
28
+ * Test suite for the Graph Flow implementation
29
+ * This suite validates the core functionality of the graph-based workflow system:
30
+ * - Node execution and state management through context
31
+ * - Event handling (emission, correlation, waiting)
32
+ * - Error handling and retry mechanisms
33
+ * - Input/Output validation using Zod schemas
34
+ * - Complex workflows with multiple branches and conditions
35
+ * - Parallel and sequential execution patterns
36
+ *
37
+ * The tests use a simple numeric value-based context to demonstrate state changes
38
+ * and a transaction-based event payload for testing event correlation.
39
+ */
21
40
  describe("Graph", function () {
22
41
  let graph: GraphFlow<TestSchema>;
23
42
  let eventEmitter: EventEmitter;
24
43
 
25
44
  beforeEach(() => {
26
45
  eventEmitter = new EventEmitter();
27
- graph = new GraphFlow(
28
- "TestGraph",
29
- {
30
- name: "TestGraph",
31
- nodes: [],
32
- context: { value: 0 },
33
- schema: TestSchema,
34
- eventEmitter: eventEmitter,
35
- },
36
- { verbose: true }
37
- );
46
+ graph = new GraphFlow("TestGraph", {
47
+ name: "TestGraph",
48
+ nodes: [],
49
+ context: { value: 0 },
50
+ schema: TestSchema,
51
+ eventEmitter: eventEmitter,
52
+ });
38
53
  });
39
54
 
40
55
  /**
41
- * Ensure a simple node executes and updates the context correctly.
56
+ * Tests basic node execution and context update functionality
57
+ * Validates that:
58
+ * - A node can be added to the graph
59
+ * - The node's execute function is called
60
+ * - The context is properly updated
61
+ * - The updated context is accessible after execution
42
62
  */
43
63
  it("should execute a simple node and update the context", async function () {
44
64
  const simpleNode: Node<TestSchema> = {
@@ -57,7 +77,11 @@ describe("Graph", function () {
57
77
  });
58
78
 
59
79
  /**
60
- * Verify that `nodeStarted` and `nodeCompleted` events are triggered.
80
+ * Tests event emission for node lifecycle events
81
+ * Validates that the graph properly emits events for:
82
+ * - Node execution start (nodeStarted)
83
+ * - Node execution completion (nodeCompleted)
84
+ * This is crucial for monitoring and debugging workflow execution
61
85
  */
62
86
  it("should trigger `nodeStarted` and `nodeCompleted` events", async function () {
63
87
  const nodeStartedSpy = sinon.spy();
@@ -82,7 +106,12 @@ describe("Graph", function () {
82
106
  });
83
107
 
84
108
  /**
85
- * Ensure an error is thrown when a node fails and `nodeError` event is triggered.
109
+ * Tests error handling and error event emission
110
+ * Validates that:
111
+ * - Errors in node execution are properly caught
112
+ * - The nodeError event is emitted
113
+ * - The error message is preserved
114
+ * This ensures robust error handling in the workflow
86
115
  */
87
116
  it("should handle errors and trigger `nodeError` event", async function () {
88
117
  const errorNode: Node<TestSchema> = {
@@ -107,7 +136,12 @@ describe("Graph", function () {
107
136
  });
108
137
 
109
138
  /**
110
- * Ensure that context validation using Zod works correctly.
139
+ * Tests context validation using Zod schema
140
+ * Validates that:
141
+ * - Invalid context values are rejected
142
+ * - Proper error messages are generated
143
+ * - Type safety is maintained during execution
144
+ * This ensures data integrity throughout the workflow
111
145
  */
112
146
  it("should validate context with Zod", async function () {
113
147
  const invalidContext = { value: "invalid_string" };
@@ -131,7 +165,12 @@ describe("Graph", function () {
131
165
  });
132
166
 
133
167
  /**
134
- * Ensure a node with validated inputs and outputs executes correctly.
168
+ * Tests node execution with input/output validation
169
+ * Demonstrates:
170
+ * - Input parameter validation
171
+ * - Output state validation
172
+ * - Integration between node execution and validation
173
+ * Ensures type safety and data consistency in node interactions
135
174
  */
136
175
  it("should execute a node with validated inputs and outputs", async function () {
137
176
  const paramNode: Node<TestSchema, { increment: number }> = {
@@ -156,7 +195,12 @@ describe("Graph", function () {
156
195
  });
157
196
 
158
197
  /**
159
- * Ensure a node does not execute if a condition is not met.
198
+ * Tests conditional node execution
199
+ * Validates that:
200
+ * - Nodes can have conditional execution logic
201
+ * - Conditions are evaluated against current context
202
+ * - Nodes are skipped when conditions are not met
203
+ * This enables dynamic workflow paths based on state
160
204
  */
161
205
  it("should not execute a node when condition is false", async function () {
162
206
  const conditionalNode: Node<TestSchema> = {
@@ -176,7 +220,13 @@ describe("Graph", function () {
176
220
  });
177
221
 
178
222
  /**
179
- * Ensure that a node retries execution when it fails.
223
+ * Tests node retry functionality
224
+ * Validates the retry mechanism:
225
+ * - Maximum attempt limits
226
+ * - Retry delays
227
+ * - Success after retry
228
+ * - Context preservation between attempts
229
+ * Essential for handling transient failures in workflows
180
230
  */
181
231
  it("should retry a node execution when it fails", async function () {
182
232
  let attemptCount = 0;
@@ -202,45 +252,7 @@ describe("Graph", function () {
202
252
  });
203
253
 
204
254
  /**
205
- * Ensure dynamic event-based execution works via `emit`.
206
- */
207
- it("should trigger a node execution from an event", async function () {
208
- this.timeout(5000); // Ensure we have enough time to complete the test
209
-
210
- const eventNode: Node<TestSchema> = {
211
- name: "eventNode",
212
- events: ["customEvent"],
213
- execute: async (context) => {
214
- context.value = (context.value ?? 0) + 1;
215
- },
216
- next: [],
217
- };
218
-
219
- graph.addNode(eventNode);
220
-
221
- // Use a promise to ensure the event is properly handled
222
- await new Promise<void>((resolve, reject) => {
223
- const timeout = setTimeout(
224
- () => reject(new Error("Event did not trigger")),
225
- 1500
226
- );
227
-
228
- graph.on("nodeCompleted", ({ name }) => {
229
- if (name === "eventNode") {
230
- clearTimeout(timeout);
231
- resolve();
232
- }
233
- });
234
-
235
- graph.emit("customEvent").catch(reject);
236
- });
237
-
238
- const context = graph.getContext();
239
- expect(context.value).to.equal(1);
240
- });
241
-
242
- /**
243
- * ✅ Ensure that removing a node works correctly.
255
+ * Tests node removal functionality
244
256
  */
245
257
  it("should remove a node from the graph", function () {
246
258
  const testNode: Node<TestSchema> = {
@@ -253,6 +265,9 @@ describe("Graph", function () {
253
265
  expect(graph.getNodes().length).to.equal(0);
254
266
  });
255
267
 
268
+ /**
269
+ * Tests graph reloading functionality
270
+ */
256
271
  it("should clear and reload the graph using `load`", function () {
257
272
  const nodeA: Node<TestSchema> = {
258
273
  name: "A",
@@ -277,7 +292,7 @@ describe("Graph", function () {
277
292
  });
278
293
 
279
294
  /**
280
- * Test input validation failure
295
+ * Tests input validation error handling
281
296
  */
282
297
  it("should throw error when node input validation fails", async function () {
283
298
  const nodeWithInput: Node<TestSchema, { amount: number }> = {
@@ -304,7 +319,7 @@ describe("Graph", function () {
304
319
  });
305
320
 
306
321
  /**
307
- * Test output validation failure
322
+ * Tests output validation error handling
308
323
  */
309
324
  it("should throw error when node output validation fails", async function () {
310
325
  const nodeWithOutput: Node<TestSchema> = {
@@ -331,7 +346,7 @@ describe("Graph", function () {
331
346
  });
332
347
 
333
348
  /**
334
- * Test successful input and output validation
349
+ * Tests successful input/output validation flow
335
350
  */
336
351
  it("should successfully validate both inputs and outputs", async function () {
337
352
  const validatedNode: Node<TestSchema, { increment: number }> = {
@@ -366,7 +381,7 @@ describe("Graph", function () {
366
381
  });
367
382
 
368
383
  /**
369
- * Test missing required inputs
384
+ * Tests handling of missing required inputs
370
385
  */
371
386
  it("should throw error when required inputs are missing", async function () {
372
387
  const nodeWithRequiredInput: Node<TestSchema, { required: string }> = {
@@ -389,7 +404,13 @@ describe("Graph", function () {
389
404
  });
390
405
 
391
406
  /**
392
- * Test complex workflow with multiple branches
407
+ * Tests complex workflow execution with multiple branches
408
+ * Demonstrates:
409
+ * - Multiple execution paths
410
+ * - Node chaining
411
+ * - Parallel branch execution
412
+ * - Context accumulation across branches
413
+ * This validates the graph's ability to handle complex business processes
393
414
  */
394
415
  it("should execute a complex workflow with multiple nodes and accumulate the value", async function () {
395
416
  const nodeA: Node<TestSchema> = {
@@ -430,7 +451,7 @@ describe("Graph", function () {
430
451
  });
431
452
 
432
453
  /**
433
- * Test conditional workflow branching
454
+ * Tests conditional branching in workflows
434
455
  */
435
456
  it("should execute different branches based on conditions", async function () {
436
457
  const startNode: Node<TestSchema> = {
@@ -459,20 +480,22 @@ describe("Graph", function () {
459
480
  });
460
481
 
461
482
  /**
462
- * Test parallel workflow using GraphController
483
+ * Tests parallel workflow execution using GraphController
484
+ * Validates:
485
+ * - Multiple graph execution in parallel
486
+ * - Independent context maintenance
487
+ * - Proper result aggregation
488
+ * - Concurrency control
489
+ * Essential for scaling workflow processing
463
490
  */
464
491
  it("should handle parallel workflows using GraphController", async function () {
465
492
  // Graph 1
466
- const graph1 = new GraphFlow(
467
- "Graph1",
468
- {
469
- name: "Graph1",
470
- nodes: [],
471
- context: { value: 0 },
472
- schema: TestSchema,
473
- },
474
- { verbose: true }
475
- );
493
+ const graph1 = new GraphFlow("Graph1", {
494
+ name: "Graph1",
495
+ nodes: [],
496
+ context: { value: 0 },
497
+ schema: TestSchema,
498
+ });
476
499
 
477
500
  const processNode1: Node<TestSchema> = {
478
501
  name: "process1",
@@ -490,16 +513,12 @@ describe("Graph", function () {
490
513
  };
491
514
 
492
515
  // Graph 2
493
- const graph2 = new GraphFlow(
494
- "Graph2",
495
- {
496
- name: "Graph2",
497
- nodes: [],
498
- context: { value: 0 },
499
- schema: TestSchema,
500
- },
501
- { verbose: true }
502
- );
516
+ const graph2 = new GraphFlow("Graph2", {
517
+ name: "Graph2",
518
+ nodes: [],
519
+ context: { value: 0 },
520
+ schema: TestSchema,
521
+ });
503
522
 
504
523
  const processNode2: Node<TestSchema> = {
505
524
  name: "process2",
@@ -521,7 +540,7 @@ describe("Graph", function () {
521
540
  graph2.addNode(processNode2);
522
541
  graph2.addNode(finalizeNode2);
523
542
 
524
- const results = await GraphController.executeParallel(
543
+ const results = await GraphFlowController.executeParallel(
525
544
  [graph1, graph2],
526
545
  ["process1", "process2"],
527
546
  2,
@@ -533,20 +552,16 @@ describe("Graph", function () {
533
552
  });
534
553
 
535
554
  /**
536
- * Test sequential workflow using GraphController
555
+ * Tests sequential workflow execution using GraphController
537
556
  */
538
557
  it("should handle sequential workflows using GraphController", async function () {
539
558
  // Graph 1
540
- const graph1 = new GraphFlow(
541
- "Graph1",
542
- {
543
- name: "Graph1",
544
- nodes: [],
545
- context: { value: 1 },
546
- schema: TestSchema,
547
- },
548
- { verbose: true }
549
- );
559
+ const graph1 = new GraphFlow("Graph1", {
560
+ name: "Graph1",
561
+ nodes: [],
562
+ context: { value: 1 },
563
+ schema: TestSchema,
564
+ });
550
565
 
551
566
  const startNode1: Node<TestSchema> = {
552
567
  name: "start1",
@@ -556,16 +571,12 @@ describe("Graph", function () {
556
571
  };
557
572
 
558
573
  // Graph 2
559
- const graph2 = new GraphFlow(
560
- "Graph2",
561
- {
562
- name: "Graph2",
563
- nodes: [],
564
- context: { value: 3 },
565
- schema: TestSchema,
566
- },
567
- { verbose: true }
568
- );
574
+ const graph2 = new GraphFlow("Graph2", {
575
+ name: "Graph2",
576
+ nodes: [],
577
+ context: { value: 3 },
578
+ schema: TestSchema,
579
+ });
569
580
 
570
581
  const startNode2: Node<TestSchema> = {
571
582
  name: "start2",
@@ -577,7 +588,7 @@ describe("Graph", function () {
577
588
  graph1.addNode(startNode1);
578
589
  graph2.addNode(startNode2);
579
590
 
580
- const results = await GraphController.executeSequential(
591
+ const results = await GraphFlowController.executeSequential(
581
592
  [graph1, graph2],
582
593
  ["start1", "start2"],
583
594
  [{}, {}]
@@ -586,4 +597,124 @@ describe("Graph", function () {
586
597
  expect(results[0].value).to.equal(2); // Graph1: 1 * 2
587
598
  expect(results[1].value).to.equal(5); // Graph2: 3 + 2
588
599
  });
600
+
601
+ /**
602
+ * Tests event correlation functionality
603
+ * Demonstrates:
604
+ * - Event correlation based on transaction ID
605
+ * - Timeout handling
606
+ * - Multiple event synchronization
607
+ * - Context updates after correlation
608
+ * Critical for integrating with external event sources
609
+ */
610
+ it("should handle correlated events correctly", async function () {
611
+ this.timeout(10000);
612
+ const graph = new GraphFlow("test", {
613
+ name: "test",
614
+ nodes: [],
615
+ context: { value: 0 },
616
+ schema: TestSchema,
617
+ eventEmitter: new EventEmitter(),
618
+ });
619
+
620
+ let eventsReceived = 0;
621
+ const node = {
622
+ name: "testNode",
623
+ waitForEvents: {
624
+ events: ["eventA", "eventB"],
625
+ timeout: 5000,
626
+ strategy: "all" as const,
627
+ },
628
+ execute: async (context: GraphContext<typeof TestSchema>) => {
629
+ eventsReceived = 2;
630
+ context.value = 42;
631
+ },
632
+ };
633
+
634
+ graph.addNode(node);
635
+
636
+ graph.execute("testNode");
637
+
638
+ await new Promise((resolve) => setTimeout(resolve, 500));
639
+
640
+ await graph.emit("eventA", { eventPayload: { status: "A" } });
641
+ await new Promise((resolve) => setTimeout(resolve, 100));
642
+ await graph.emit("eventB", { eventPayload: { status: "B" } });
643
+
644
+ await new Promise((resolve) => setTimeout(resolve, 100));
645
+
646
+ expect(eventsReceived).to.equal(2);
647
+ expect(graph.getContext().value).to.equal(42);
648
+ });
649
+
650
+ /**
651
+ * Tests multiple event waiting functionality
652
+ */
653
+ it("should wait for multiple events before continuing", async function () {
654
+ this.timeout(10000);
655
+ const graph = new GraphFlow("test", {
656
+ name: "test",
657
+ nodes: [],
658
+ context: { value: 0 },
659
+ schema: TestSchema,
660
+ eventEmitter: new EventEmitter(),
661
+ });
662
+
663
+ const node = {
664
+ name: "testNode",
665
+ waitForEvents: {
666
+ events: ["event1", "event2"],
667
+ timeout: 5000,
668
+ strategy: "all" as const,
669
+ },
670
+ execute: async (context: GraphContext<typeof TestSchema>) => {
671
+ context.value = 42; // Ajouter une modification du contexte
672
+ },
673
+ };
674
+
675
+ graph.addNode(node);
676
+ graph.execute("testNode");
677
+
678
+ await new Promise((resolve) => setTimeout(resolve, 500));
679
+ await graph.emit("event1", { eventPayload: { status: "1" } });
680
+ await new Promise((resolve) => setTimeout(resolve, 100));
681
+ await graph.emit("event2", { eventPayload: { status: "2" } });
682
+ expect(graph.getContext().value).to.equal(42);
683
+ });
684
+
685
+ /**
686
+ * Tests single event waiting functionality
687
+ */
688
+ it("should wait for a single event before continuing", async function () {
689
+ this.timeout(5000);
690
+
691
+ const waitingNode: Node<TestSchema> = {
692
+ name: "waitingNode",
693
+ execute: async (context: GraphContext<typeof TestSchema>) => {
694
+ context.value = 1;
695
+ },
696
+ waitForEvent: true,
697
+ next: ["finalNode"],
698
+ };
699
+
700
+ const finalNode: Node<TestSchema> = {
701
+ name: "finalNode",
702
+ execute: async (context: GraphContext<typeof TestSchema>) => {
703
+ context.value = (context.value ?? 0) + 5;
704
+ },
705
+ };
706
+
707
+ [waitingNode, finalNode].forEach((node) => graph.addNode(node));
708
+
709
+ const resultPromise = graph.execute("waitingNode");
710
+
711
+ // Wait a bit to ensure the node is ready
712
+ await new Promise((resolve) => setTimeout(resolve, 500));
713
+
714
+ // Emit the event
715
+ await graph.emit("someEvent");
716
+
717
+ const result = await resultPromise;
718
+ expect(result.value).to.equal(6); // 1 (waitingNode) + 5 (finalNode)
719
+ });
589
720
  });