@ai.ntellect/core 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/.mocharc.json +1 -1
  2. package/README.md +311 -272
  3. package/create-llm-to-select-multiple-graph copy.ts +243 -0
  4. package/create-llm-to-select-multiple-graph.ts +148 -0
  5. package/dist/graph/controller.js +63 -0
  6. package/dist/graph/engine.js +563 -0
  7. package/dist/index.js +6 -6
  8. package/dist/memory/adapters/meilisearch/index.js +249 -0
  9. package/dist/memory/adapters/redis/index.js +96 -0
  10. package/dist/memory/index.js +9 -0
  11. package/dist/services/agenda.js +115 -0
  12. package/dist/services/embedding.js +40 -0
  13. package/dist/services/queue.js +99 -103
  14. package/dist/test/graph/controller.test.js +170 -0
  15. package/dist/test/graph/engine.test.js +465 -0
  16. package/dist/test/memory/adapters/meilisearch.test.js +250 -0
  17. package/dist/test/memory/adapters/redis.test.js +143 -0
  18. package/dist/test/memory/base.test.js +209 -0
  19. package/dist/test/services/agenda.test.js +230 -0
  20. package/dist/test/services/queue.test.js +258 -0
  21. package/dist/types/index.js +2 -0
  22. package/dist/utils/generate-object.js +32 -11
  23. package/dist/utils/inject-actions.js +2 -2
  24. package/dist/utils/queue-item-transformer.js +2 -2
  25. package/dist/utils/state-manager.js +20 -0
  26. package/graph/controller.ts +64 -0
  27. package/graph/engine.ts +790 -0
  28. package/index copy.ts +81 -0
  29. package/index.ts +7 -7
  30. package/interfaces/index.ts +119 -0
  31. package/memory/adapters/meilisearch/index.ts +286 -0
  32. package/memory/adapters/redis/index.ts +103 -0
  33. package/memory/index.ts +22 -0
  34. package/package.json +7 -2
  35. package/services/agenda.ts +48 -43
  36. package/services/embedding.ts +26 -0
  37. package/services/queue.ts +2 -29
  38. package/test/.env.test +4 -0
  39. package/test/graph/controller.test.ts +186 -0
  40. package/test/graph/engine.test.ts +546 -0
  41. package/test/memory/adapters/meilisearch.test.ts +297 -0
  42. package/test/memory/adapters/redis.test.ts +160 -0
  43. package/test/memory/base.test.ts +229 -0
  44. package/test/services/agenda.test.ts +280 -0
  45. package/test/services/queue.test.ts +286 -44
  46. package/tsconfig.json +10 -10
  47. package/types/index.ts +278 -0
  48. package/utils/queue-item-transformer.ts +8 -11
  49. package/utils/setup-graphs.ts +45 -0
  50. package/utils/stringifiy-zod-schema.ts +45 -0
  51. package/.nvmrc +0 -1
  52. package/README.FR.md +0 -916
  53. package/agent/index.ts +0 -151
  54. package/agent/workflow/conditions.ts +0 -16
  55. package/agent/workflow/handlers/interpreter.handler.ts +0 -48
  56. package/agent/workflow/handlers/memory.handler.ts +0 -106
  57. package/agent/workflow/handlers/orchestrator.handler.ts +0 -23
  58. package/agent/workflow/handlers/queue.handler.ts +0 -34
  59. package/agent/workflow/handlers/scheduler.handler.ts +0 -61
  60. package/agent/workflow/index.ts +0 -62
  61. package/dist/agent/index.d.ts +0 -38
  62. package/dist/agent/index.js +0 -143
  63. package/dist/agent/tools/get-rss.d.ts +0 -16
  64. package/dist/agent/tools/get-rss.js +0 -62
  65. package/dist/bull.d.ts +0 -1
  66. package/dist/bull.js +0 -9
  67. package/dist/examples/index.d.ts +0 -2
  68. package/dist/examples/index.js +0 -89
  69. package/dist/index.d.ts +0 -7
  70. package/dist/llm/interpreter/context.d.ts +0 -15
  71. package/dist/llm/interpreter/context.js +0 -89
  72. package/dist/llm/interpreter/index.d.ts +0 -21
  73. package/dist/llm/interpreter/index.js +0 -87
  74. package/dist/llm/memory-manager/context.d.ts +0 -2
  75. package/dist/llm/memory-manager/context.js +0 -22
  76. package/dist/llm/memory-manager/index.d.ts +0 -17
  77. package/dist/llm/memory-manager/index.js +0 -107
  78. package/dist/llm/orchestrator/context.d.ts +0 -2
  79. package/dist/llm/orchestrator/context.js +0 -23
  80. package/dist/llm/orchestrator/index.d.ts +0 -44
  81. package/dist/llm/orchestrator/index.js +0 -139
  82. package/dist/llm/orchestrator/types.d.ts +0 -12
  83. package/dist/memory/cache.d.ts +0 -22
  84. package/dist/memory/cache.js +0 -165
  85. package/dist/memory/persistent.d.ts +0 -57
  86. package/dist/memory/persistent.js +0 -189
  87. package/dist/services/queue.d.ts +0 -13
  88. package/dist/services/redis-cache.d.ts +0 -37
  89. package/dist/services/redis-cache.js +0 -93
  90. package/dist/services/scheduler.d.ts +0 -40
  91. package/dist/services/scheduler.js +0 -99
  92. package/dist/services/telegram-monitor.d.ts +0 -0
  93. package/dist/services/telegram-monitor.js +0 -118
  94. package/dist/t.d.ts +0 -46
  95. package/dist/t.js +0 -102
  96. package/dist/test.d.ts +0 -0
  97. package/dist/test.js +0 -438
  98. package/dist/types.d.ts +0 -258
  99. package/dist/types.js +0 -22
  100. package/dist/utils/generate-object.d.ts +0 -12
  101. package/dist/utils/header-builder.d.ts +0 -11
  102. package/dist/utils/inject-actions.d.ts +0 -2
  103. package/dist/utils/queue-item-transformer.d.ts +0 -7
  104. package/dist/utils/sanitize-results.d.ts +0 -17
  105. package/dist/utils/schema-generator.d.ts +0 -16
  106. package/examples/actions/get-rss.ts +0 -71
  107. package/examples/index.ts +0 -98
  108. package/index.html +0 -42
  109. package/llm/dynamic-condition/example.ts +0 -36
  110. package/llm/dynamic-condition/index.ts +0 -108
  111. package/llm/interpreter/context.ts +0 -94
  112. package/llm/interpreter/index.ts +0 -140
  113. package/llm/memory-manager/context.ts +0 -19
  114. package/llm/memory-manager/index.ts +0 -115
  115. package/llm/orchestrator/context.ts +0 -19
  116. package/llm/orchestrator/index.ts +0 -192
  117. package/llm/orchestrator/types.ts +0 -14
  118. package/memory/cache.ts +0 -221
  119. package/memory/persistent.ts +0 -265
  120. package/script.js +0 -167
  121. package/services/cache.ts +0 -298
  122. package/services/telegram-monitor.ts +0 -138
  123. package/services/workflow.ts +0 -491
  124. package/t.py +0 -79
  125. package/t.ts +0 -25
  126. package/test/llm/orchestrator.test.ts +0 -47
  127. package/test/llm/synthesizer.test.ts +0 -31
  128. package/types.ts +0 -367
  129. package/utils/schema-generator.ts +0 -73
  130. package/utils/state-manager.ts +0 -25
  131. /package/dist/{llm/orchestrator/types.js → interfaces/index.js} +0 -0
@@ -0,0 +1,790 @@
1
+ import { Persistence, RealTimeNotifier } from "@/interfaces";
2
+ import { GraphDefinition, Node, SharedState } from "@/types";
3
+ import { configDotenv } from "dotenv";
4
+ import EventEmitter from "events";
5
+ import { z } from "zod";
6
+
7
+ configDotenv();
8
+
9
+ interface GraphOptions<T> {
10
+ initialState?: SharedState<T>;
11
+ schema?: z.ZodSchema<T>;
12
+ autoDetectCycles?: boolean;
13
+ }
14
+
15
+ /**
16
+ * Représente un workflow dirigé capable d’exécuter des noeuds en séquence ou en parallèle.
17
+ *
18
+ * @template T - Le type de données stockées dans le contexte du workflow
19
+ */
20
+ export class GraphEngine<T> {
21
+ /** Données globales accessibles à tous les nœuds */
22
+ public globalContext: Map<string, any>;
23
+
24
+ /** Event emitter pour gérer les événements du workflow */
25
+ private eventEmitter: EventEmitter;
26
+
27
+ /** Map de tous les nœuds du workflow */
28
+ public nodes: Map<string, Node<T>>;
29
+
30
+ /** Ensemble des nœuds déjà exécutés */
31
+ public executedNodes: Set<string>;
32
+
33
+ /** Nom du workflow */
34
+ public name: string;
35
+
36
+ /** Couche de persistance optionnelle pour sauvegarder l'état du workflow */
37
+ private persistence: Persistence<T> | null;
38
+
39
+ /** Notifier en temps réel optionnel */
40
+ private notifier: RealTimeNotifier | null;
41
+
42
+ /** Schéma global Zod pour valider l’état ou le contexte du workflow */
43
+ private schema?: z.ZodSchema<T>;
44
+
45
+ /** État interne actuel du workflow */
46
+ private currentState: SharedState<T>;
47
+
48
+ /**
49
+ * Crée une nouvelle instance de GraphEngine.
50
+ *
51
+ * @param {GraphDefinition<T>} [definition] - La définition initiale du workflow
52
+ * @param {GraphOptions<T>} [options] - Options de configuration
53
+ */
54
+ constructor(definition?: GraphDefinition<T>, options?: GraphOptions<T>) {
55
+ this.name = definition?.name || "anonymous";
56
+ this.eventEmitter = new EventEmitter();
57
+ this.globalContext = new Map();
58
+ this.nodes = new Map();
59
+ this.executedNodes = new Set();
60
+ this.persistence = null;
61
+ this.notifier = null;
62
+ this.schema = options?.schema;
63
+ this.currentState = {} as SharedState<T>;
64
+
65
+ if (definition) {
66
+ this.loadFromDefinition(definition);
67
+ }
68
+
69
+ if (options?.autoDetectCycles && this.checkForCycles()) {
70
+ throw new Error("Cycle détecté dans le workflow");
71
+ }
72
+
73
+ if (options?.initialState) {
74
+ this.setState(options.initialState);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Ajoute un élément au contexte global.
80
+ * @param {string} key - La clé
81
+ * @param {any} value - La valeur
82
+ */
83
+ addToContext(key: string, value: any): void {
84
+ this.globalContext.set(key, value);
85
+ }
86
+
87
+ /**
88
+ * Récupère un élément du contexte global.
89
+ * @param {string} key - La clé
90
+ */
91
+ getContext(key: string): any {
92
+ return this.globalContext.get(key);
93
+ }
94
+
95
+ /**
96
+ * Supprime un élément du contexte global.
97
+ * @param {string} key - La clé
98
+ */
99
+ removeFromContext(key: string): void {
100
+ this.globalContext.delete(key);
101
+ }
102
+
103
+ /**
104
+ * Définit la couche de persistance.
105
+ * @param {Persistence<T>} persistence
106
+ */
107
+ setPersistence(persistence: Persistence<T>): void {
108
+ this.persistence = persistence;
109
+ }
110
+
111
+ /**
112
+ * Définit le notifier en temps réel.
113
+ * @param {RealTimeNotifier} notifier
114
+ */
115
+ setNotifier(notifier: RealTimeNotifier): void {
116
+ this.notifier = notifier;
117
+ }
118
+
119
+ /**
120
+ * Charge un workflow à partir d'une définition.
121
+ * @private
122
+ * @param {GraphDefinition<T>} definition
123
+ */
124
+ private loadFromDefinition(definition: GraphDefinition<T>): void {
125
+ Object.entries(definition.nodes).forEach(([_, nodeConfig]) => {
126
+ this.addNode(nodeConfig);
127
+ });
128
+ }
129
+
130
+ /**
131
+ * Vérifie récursivement s’il existe un cycle dans le workflow.
132
+ * @param {string} nodeName
133
+ * @param {Set<string>} visited
134
+ * @param {Set<string>} recStack
135
+ * @returns {boolean}
136
+ */
137
+ private isCyclic(
138
+ nodeName: string,
139
+ visited: Set<string>,
140
+ recStack: Set<string>
141
+ ): boolean {
142
+ if (!visited.has(nodeName)) {
143
+ visited.add(nodeName);
144
+ recStack.add(nodeName);
145
+
146
+ const currentNode = this.nodes.get(nodeName);
147
+ if (currentNode?.relationships) {
148
+ for (const relation of currentNode.relationships) {
149
+ const targetNode = relation.name;
150
+ if (
151
+ !visited.has(targetNode) &&
152
+ this.isCyclic(targetNode, visited, recStack)
153
+ ) {
154
+ return true;
155
+ } else if (recStack.has(targetNode)) {
156
+ return true;
157
+ }
158
+ }
159
+ }
160
+ }
161
+ recStack.delete(nodeName);
162
+ return false;
163
+ }
164
+
165
+ /**
166
+ * Vérifie si le workflow contient des cycles.
167
+ * @returns {boolean}
168
+ */
169
+ public checkForCycles(): boolean {
170
+ const visited = new Set<string>();
171
+ const recStack = new Set<string>();
172
+
173
+ for (const nodeName of this.nodes.keys()) {
174
+ if (this.isCyclic(nodeName, visited, recStack)) {
175
+ return true;
176
+ }
177
+ }
178
+ return false;
179
+ }
180
+
181
+ /**
182
+ * Ajoute un nouveau nœud au workflow.
183
+ * @param {Node<T>} node
184
+ */
185
+ addNode(node: Node<T>): void {
186
+ if (node.relationships) {
187
+ node.relationships.forEach((relationship) => {
188
+ this.nodes.get(relationship.name)?.relationships?.push(relationship);
189
+ });
190
+ }
191
+
192
+ if (node.events) {
193
+ node.events.forEach((event) => {
194
+ this.eventEmitter.on(event, async (data) => {
195
+ const state = data.state || {};
196
+ await this.execute(state, node.name);
197
+ });
198
+ });
199
+ }
200
+
201
+ this.nodes.set(node.name, node);
202
+ }
203
+
204
+ /**
205
+ * Émet un événement sur l'event emitter du workflow.
206
+ * @param {string} eventName
207
+ * @param {any} data
208
+ */
209
+ public emit(eventName: string, data: any): void {
210
+ this.eventEmitter.emit(eventName, data);
211
+ }
212
+
213
+ /**
214
+ * Ajoute un sous-graph (GraphEngine) comme un nœud dans le workflow courant.
215
+ * @param {GraphEngine<T>} subGraph
216
+ * @param {string} entryNode - Le nom du nœud de démarrage dans le sous-graph
217
+ * @param {string} name - Le nom symbolique à donner au sous-graph
218
+ */
219
+ addSubGraph(subGraph: GraphEngine<T>, entryNode: string, name: string): void {
220
+ const subGraphNode: Node<T> = {
221
+ name: name,
222
+ execute: async (state) => {
223
+ await subGraph.execute(state, entryNode);
224
+ return state;
225
+ },
226
+ };
227
+ this.nodes.set(name, subGraphNode);
228
+ }
229
+
230
+ /**
231
+ * Exécute le workflow à partir d’un nœud donné.
232
+ * @param {SharedState<T>} state
233
+ * @param {string} startNode
234
+ * @param {(state: SharedState<T>) => void} [onStream] - Callback sur l’évolution de l’état
235
+ * @param {(error: Error, nodeName: string, state: SharedState<T>) => void} [onError] - Callback sur erreur
236
+ */
237
+ async execute(
238
+ state: SharedState<T>,
239
+ startNode: string,
240
+ onStream?: (graph: GraphEngine<T>) => void,
241
+ onError?: (error: Error, nodeName: string, state: SharedState<T>) => void
242
+ ): Promise<SharedState<T>> {
243
+ try {
244
+ // Valide l'état initial via le schéma global (si défini)
245
+ if (this.schema) {
246
+ try {
247
+ this.schema.parse(state);
248
+ } catch (error) {
249
+ const validationError = new Error(
250
+ `Échec de la validation de l'état initial: ${
251
+ error instanceof Error ? error.message : error
252
+ }`
253
+ );
254
+ if (onError) onError(validationError, startNode, state);
255
+ throw validationError;
256
+ }
257
+ }
258
+
259
+ this.setState(state);
260
+ let currentNodeName = startNode;
261
+
262
+ while (currentNodeName) {
263
+ this.executedNodes.add(currentNodeName);
264
+ const currentNode = this.nodes.get(currentNodeName);
265
+ if (!currentNode) {
266
+ throw new Error(`Nœud ${currentNodeName} introuvable.`);
267
+ }
268
+
269
+ // Vérification de condition (si présente)
270
+ if (
271
+ currentNode.condition &&
272
+ !currentNode.condition(this.currentState)
273
+ ) {
274
+ break;
275
+ }
276
+
277
+ try {
278
+ // Notifier : début d'exécution du nœud
279
+ if (this.notifier) {
280
+ this.notifier.notify("nodeExecutionStarted", {
281
+ workflow: this.name,
282
+ node: currentNodeName,
283
+ });
284
+ }
285
+
286
+ const params = currentNode.schema?.parse(this.currentState);
287
+ const newState = await currentNode.execute(
288
+ params || {},
289
+ this.currentState
290
+ );
291
+
292
+ if (newState) {
293
+ this.setState(newState);
294
+ if (onStream) onStream(this);
295
+ }
296
+
297
+ // Sauvegarde via la persistence (optionnel)
298
+ if (this.persistence) {
299
+ await this.persistence.saveState(
300
+ this.name,
301
+ this.currentState,
302
+ currentNodeName
303
+ );
304
+ }
305
+
306
+ // Notifier : fin d'exécution du nœud
307
+ if (this.notifier) {
308
+ await this.notifier.notify("nodeExecutionCompleted", {
309
+ workflow: this.name,
310
+ node: currentNodeName,
311
+ state: this.currentState,
312
+ });
313
+ }
314
+ } catch (error) {
315
+ if (onError) {
316
+ onError(error as Error, currentNodeName, this.currentState);
317
+ }
318
+ if (this.notifier) {
319
+ this.notifier.notify("nodeExecutionFailed", {
320
+ workflow: this.name,
321
+ node: currentNodeName,
322
+ state: this.currentState,
323
+ error,
324
+ });
325
+ }
326
+ break;
327
+ }
328
+
329
+ // Gestion des relations (branchements)
330
+ const relationsNodes = currentNode.relationships || [];
331
+ if (relationsNodes.length > 1) {
332
+ // Exécution parallèle des branches
333
+ await Promise.all(
334
+ relationsNodes.map((relation) =>
335
+ this.execute(this.currentState, relation.name, onStream, onError)
336
+ )
337
+ );
338
+ // Après exécution en parallèle, on arrête la boucle
339
+ break;
340
+ } else {
341
+ // Cas normal : un seul chemin
342
+ currentNodeName = relationsNodes[0]?.name || "";
343
+ }
344
+ }
345
+
346
+ return this.getState();
347
+ } catch (error) {
348
+ if (onError) {
349
+ onError(error as Error, startNode, state);
350
+ }
351
+ throw error;
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Exécute plusieurs nœuds en parallèle au sein du même workflow, avec une limite de concurrence.
357
+ * @param {SharedState<T>} state
358
+ * @param {string[]} nodeNames
359
+ * @param {number} [concurrencyLimit=5]
360
+ */
361
+ async executeParallel(
362
+ state: SharedState<T>,
363
+ nodeNames: string[],
364
+ concurrencyLimit: number = 5,
365
+ onStream?: (graph: GraphEngine<T>) => void,
366
+ onError?: (error: Error, nodeName: string, state: SharedState<T>) => void
367
+ ): Promise<void> {
368
+ const executeWithLimit = async (nodeName: string) => {
369
+ await this.execute(state, nodeName, onStream, onError);
370
+ };
371
+
372
+ const chunks = [];
373
+ for (let i = 0; i < nodeNames.length; i += concurrencyLimit) {
374
+ chunks.push(nodeNames.slice(i, i + concurrencyLimit));
375
+ }
376
+
377
+ for (const chunk of chunks) {
378
+ await Promise.all(chunk.map(executeWithLimit));
379
+ }
380
+ }
381
+
382
+ /**
383
+ * Met à jour le workflow avec une nouvelle définition (mise à jour des nœuds existants ou ajout de nouveaux).
384
+ * @param {GraphDefinition<T>} definition
385
+ */
386
+ updateGraph(definition: GraphDefinition<T>): void {
387
+ Object.entries(definition.nodes).forEach(([_, nodeConfig]) => {
388
+ if (this.nodes.has(nodeConfig.name)) {
389
+ const existingNode = this.nodes.get(nodeConfig.name)!;
390
+ existingNode.relationships =
391
+ nodeConfig.relationships || existingNode.relationships;
392
+ existingNode.condition = nodeConfig.condition || existingNode.condition;
393
+ existingNode.events = nodeConfig.events || existingNode.events;
394
+ } else {
395
+ this.addNode(nodeConfig);
396
+ }
397
+ });
398
+ }
399
+
400
+ /**
401
+ * Remplace complètement le workflow par une nouvelle définition.
402
+ * @param {GraphDefinition<T>} definition
403
+ */
404
+ replaceGraph(definition: GraphDefinition<T>): void {
405
+ this.nodes.clear();
406
+ this.loadFromDefinition(definition);
407
+ }
408
+
409
+ /**
410
+ * Génère un diagramme Mermaid pour visualiser le workflow.
411
+ * @param {string} [title]
412
+ * @returns {string}
413
+ */
414
+ generateMermaidDiagram(title?: string): string {
415
+ const lines: string[] = ["flowchart TD"];
416
+
417
+ if (title) {
418
+ lines.push(` subgraph ${title}`);
419
+ }
420
+
421
+ // Ajout des nœuds
422
+ this.nodes.forEach((node, nodeName) => {
423
+ const hasEvents = node.events && node.events.length > 0;
424
+ const hasCondition = !!node.condition;
425
+
426
+ // Style selon les propriétés
427
+ let style = "";
428
+ if (hasEvents) {
429
+ style = "style " + nodeName + " fill:#FFD700,stroke:#DAA520"; // Jaune pour event
430
+ } else if (hasCondition) {
431
+ style = "style " + nodeName + " fill:#FFA500,stroke:#FF8C00"; // Orange pour condition
432
+ }
433
+
434
+ lines.push(` ${nodeName}[${nodeName}]`);
435
+ if (style) {
436
+ lines.push(` ${style}`);
437
+ }
438
+ });
439
+
440
+ // Ajout des connexions
441
+ this.nodes.forEach((node, nodeName) => {
442
+ if (node.relationships) {
443
+ node.relationships.forEach((relationsNode) => {
444
+ let connectionStyle = "";
445
+ if (node.condition) {
446
+ connectionStyle = "---|condition|";
447
+ } else {
448
+ connectionStyle = "-->";
449
+ }
450
+ lines.push(` ${nodeName} ${connectionStyle} ${relationsNode}`);
451
+ });
452
+ }
453
+
454
+ // Gestion des events
455
+ if (node.events && node.events.length > 0) {
456
+ node.events.forEach((event: string) => {
457
+ const eventNodeId = `${event}_event`;
458
+ lines.push(` ${eventNodeId}((${event})):::event`);
459
+ lines.push(` ${eventNodeId} -.->|trigger| ${nodeName}`);
460
+ });
461
+ lines.push(" classDef event fill:#FFD700,stroke:#DAA520");
462
+ }
463
+ });
464
+
465
+ if (title) {
466
+ lines.push(" end");
467
+ }
468
+
469
+ return lines.join("\n");
470
+ }
471
+
472
+ /**
473
+ * Affiche le diagramme Mermaid dans la console.
474
+ * @param {string} [title]
475
+ */
476
+ visualize(title?: string): void {
477
+ const diagram = this.generateMermaidDiagram(title);
478
+ console.log(
479
+ "Pour visualiser ce workflow, utilisez un rendu compatible Mermaid avec la syntaxe suivante :"
480
+ );
481
+ console.log("\n```mermaid");
482
+ console.log(diagram);
483
+ console.log("```\n");
484
+ }
485
+
486
+ /**
487
+ * Exporte la définition du workflow au format JSON (pour debug ou documentation).
488
+ * @param {GraphDefinition<T>} workflow
489
+ * @returns {string} JSON string
490
+ */
491
+ exportGraphToJson(workflow: GraphDefinition<T>): string {
492
+ const result = {
493
+ workflowName: workflow.name,
494
+ entryNode: workflow.entryNode,
495
+ nodes: Object.entries(workflow.nodes).reduce((acc, [key, node]) => {
496
+ acc[key] = {
497
+ name: node.name,
498
+ description: node.description || "No description provided",
499
+ execute: node.execute.name,
500
+ condition: node.condition ? node.condition.toString() : "None",
501
+ relationships: node.relationships || [],
502
+ };
503
+ return acc;
504
+ }, {} as Record<string, any>),
505
+ };
506
+ return JSON.stringify(result, null, 2);
507
+ }
508
+
509
+ /**
510
+ * Génère une représentation textuelle (console) du schéma du workflow.
511
+ * @returns {string}
512
+ */
513
+ visualizeSchema(): string {
514
+ const output: string[] = [];
515
+
516
+ output.push(`📋 Graph: ${this.name}`);
517
+ output.push("=".repeat(50));
518
+
519
+ // Schéma global
520
+ if (this.schema) {
521
+ output.push("🔷 Global Schema:");
522
+ output.push("-".repeat(30));
523
+
524
+ if (this.schema instanceof z.ZodObject) {
525
+ const shape = this.schema.shape;
526
+ Object.entries(shape).forEach(([key, value]) => {
527
+ const description = this.describeZodType(value as z.ZodType, 1);
528
+ output.push(`${key}:`);
529
+ output.push(description);
530
+ });
531
+ }
532
+ output.push("");
533
+ }
534
+
535
+ // Détails des nœuds
536
+ output.push("🔷 Nodes:");
537
+ output.push("-".repeat(30));
538
+
539
+ this.nodes.forEach((node, nodeName) => {
540
+ output.push(`\n📍 Node: ${nodeName}`);
541
+ output.push(
542
+ `Description: ${node.description || "No description provided"}`
543
+ );
544
+
545
+ if (node.relationships && node.relationships.length > 0) {
546
+ const rels = node.relationships.map((r) => r.name).join(", ");
547
+ output.push(`Next nodes: ${rels}`);
548
+ }
549
+
550
+ output.push("");
551
+ });
552
+
553
+ return output.join("\n");
554
+ }
555
+
556
+ /**
557
+ * Décrit récursivement un type Zod pour l'affichage.
558
+ * @param {z.ZodType} type
559
+ * @param {number} indent
560
+ * @returns {string}
561
+ */
562
+ public describeZodType(type: z.ZodType, indent: number = 0): string {
563
+ const padding = " ".repeat(indent);
564
+
565
+ if (type instanceof z.ZodObject) {
566
+ const shape = type.shape;
567
+ const lines: string[] = [];
568
+
569
+ Object.entries(shape).forEach(([key, value]) => {
570
+ const isOptional = value instanceof z.ZodOptional;
571
+ const actualType = isOptional
572
+ ? (value as z.ZodOptional<z.ZodType<any, any, any>>).unwrap()
573
+ : (value as z.ZodType<any, any, any>);
574
+ const description = this.describeZodType(actualType, indent + 1);
575
+
576
+ lines.push(`${padding}${key}${isOptional ? "?" : ""}: ${description}`);
577
+ });
578
+
579
+ return lines.join("\n");
580
+ }
581
+
582
+ if (type instanceof z.ZodArray) {
583
+ const elementType = this.describeZodType(type.element, indent);
584
+ return `Array<${elementType}>`;
585
+ }
586
+
587
+ if (type instanceof z.ZodString) {
588
+ const checks = type._def.checks || [];
589
+ const constraints = checks
590
+ .map((check) => {
591
+ if (check.kind === "url") return "url";
592
+ if (check.kind === "email") return "email";
593
+ return check.kind;
594
+ })
595
+ .join(", ");
596
+
597
+ return constraints ? `string (${constraints})` : "string";
598
+ }
599
+
600
+ if (type instanceof z.ZodNumber) {
601
+ return "number";
602
+ }
603
+
604
+ if (type instanceof z.ZodBoolean) {
605
+ return "boolean";
606
+ }
607
+
608
+ if (type instanceof z.ZodOptional) {
609
+ return `${this.describeZodType(type.unwrap(), indent)} (optional)`;
610
+ }
611
+
612
+ return type.constructor.name.replace("Zod", "") || "unknown";
613
+ }
614
+
615
+ /**
616
+ * Met à jour le contexte du workflow pour un nœud, en renvoyant un nouvel état.
617
+ * @param {SharedState<T>} state
618
+ * @param {Partial<T>} updates
619
+ * @returns {SharedState<T>}
620
+ */
621
+ protected updateNodeState(state: SharedState<T>, updates: Partial<T>) {
622
+ return {
623
+ ...state,
624
+ ...updates,
625
+ };
626
+ }
627
+
628
+ /**
629
+ * Récupère l'état courant du workflow.
630
+ * @returns {SharedState<T>}
631
+ */
632
+ public getState(): SharedState<T> {
633
+ return this.currentState;
634
+ }
635
+
636
+ /**
637
+ * Définit le nouvel état courant du workflow et met à jour le contexte global.
638
+ * @param {Partial<SharedState<T>>} state
639
+ */
640
+ public setState(state: Partial<SharedState<T>>): void {
641
+ this.currentState = this.mergeStates(this.currentState, state);
642
+
643
+ if (state) {
644
+ Object.entries(state).forEach(([key, value]) => {
645
+ this.globalContext.set(key, value);
646
+ });
647
+ }
648
+ const currentNode = Array.from(this.executedNodes).pop();
649
+ if (currentNode) {
650
+ const node = this.nodes.get(currentNode);
651
+ if (node) {
652
+ node.state = {
653
+ ...(node.state || {}),
654
+ ...(state || {}),
655
+ };
656
+ }
657
+ }
658
+ }
659
+
660
+ /**
661
+ * Fusionne deux états.
662
+ * @param {SharedState<T>} currentState
663
+ * @param {Partial<SharedState<T>>} newState
664
+ * @returns {SharedState<T>}
665
+ */
666
+ private mergeStates(
667
+ currentState: SharedState<T>,
668
+ newState: Partial<SharedState<T>>
669
+ ): SharedState<T> {
670
+ return {
671
+ ...currentState,
672
+ ...(newState || {}),
673
+ };
674
+ }
675
+
676
+ /**
677
+ * Met à jour l'état courant et le renvoie.
678
+ * @param {Partial<T>} updates
679
+ * @returns {SharedState<T>}
680
+ */
681
+ public updateState(updates: Partial<T>): SharedState<T> {
682
+ const currentState = this.getState();
683
+ const newState: SharedState<T> = {
684
+ ...currentState,
685
+ ...updates,
686
+ };
687
+ this.setState(newState);
688
+ return newState;
689
+ }
690
+
691
+ /* =============================================
692
+ = MÉTHODES STATIQUES POUR PLUSIEURS GRAPHES =
693
+ ============================================= */
694
+
695
+ /**
696
+ * Exécute plusieurs GraphEngine en **séquence** (l'un après l'autre).
697
+ * @param graphs Liste des graphes à exécuter
698
+ * @param startNodes Noms des nœuds de départ correspondants
699
+ * @param initialStates États initiaux correspondants
700
+ * @param onStream Callback d'avancement
701
+ * @param onError Callback d'erreur
702
+ * @returns Tableau des états finaux de chaque graphe
703
+ */
704
+ public static async executeGraphsInSequence<U>(
705
+ graphs: GraphEngine<U>[],
706
+ startNodes: string[],
707
+ initialStates: SharedState<U>[],
708
+ onStream?: (graph: GraphEngine<U>) => void,
709
+ onError?: (error: Error, nodeName: string, state: SharedState<U>) => void
710
+ ): Promise<SharedState<U>[]> {
711
+ const finalStates: SharedState<U>[] = [];
712
+
713
+ for (let i = 0; i < graphs.length; i++) {
714
+ const graph = graphs[i];
715
+ const startNode = startNodes[i];
716
+ const initialState = initialStates[i];
717
+ const result = await graph.execute(
718
+ initialState,
719
+ startNode,
720
+ onStream,
721
+ onError
722
+ );
723
+ finalStates.push(result);
724
+ }
725
+
726
+ return finalStates;
727
+ }
728
+
729
+ /**
730
+ * Exécute plusieurs GraphEngine en **parallèle** (sans limite de concurrence).
731
+ * @param graphs Liste des graphes
732
+ * @param startNodes Noms des nœuds de départ
733
+ * @param initialStates États initiaux
734
+ * @param onStream Callback d'avancement
735
+ * @param onError Callback d'erreur
736
+ * @returns Tableau des états finaux de chaque graphe
737
+ */
738
+ public static async executeGraphsInParallel<U>(
739
+ graphs: GraphEngine<U>[],
740
+ startNodes: string[],
741
+ initialStates: SharedState<U>[],
742
+ onStream?: (graph: GraphEngine<U>) => void,
743
+ onError?: (error: Error, nodeName: string, state: SharedState<U>) => void
744
+ ): Promise<SharedState<U>[]> {
745
+ const promises = graphs.map((graph, index) =>
746
+ graph.execute(initialStates[index], startNodes[index], onStream, onError)
747
+ );
748
+ return Promise.all(promises);
749
+ }
750
+
751
+ /**
752
+ * Exécute plusieurs GraphEngine en parallèle **avec une limite de concurrence**.
753
+ * @param graphs Liste des graphes
754
+ * @param startNodes Noms des nœuds de départ
755
+ * @param initialStates États initiaux
756
+ * @param concurrencyLimit Limite de concurrence
757
+ * @param onStream Callback d'avancement
758
+ * @param onError Callback d'erreur
759
+ * @returns Tableau des états finaux de chaque graphe
760
+ */
761
+ public static async executeGraphsWithConcurrencyLimit<U>(
762
+ graphs: GraphEngine<U>[],
763
+ startNodes: string[],
764
+ initialStates: SharedState<U>[],
765
+ concurrencyLimit: number,
766
+ onStream?: (graph: GraphEngine<U>) => void,
767
+ onError?: (error: Error, nodeName: string, state: SharedState<U>) => void
768
+ ): Promise<SharedState<U>[]> {
769
+ const results: SharedState<U>[] = [];
770
+
771
+ for (let i = 0; i < graphs.length; i += concurrencyLimit) {
772
+ const chunkGraphs = graphs.slice(i, i + concurrencyLimit);
773
+ const chunkStartNodes = startNodes.slice(i, i + concurrencyLimit);
774
+ const chunkInitialStates = initialStates.slice(i, i + concurrencyLimit);
775
+
776
+ const chunkPromises = chunkGraphs.map((graph, index) => {
777
+ return graph.execute(
778
+ chunkInitialStates[index],
779
+ chunkStartNodes[index],
780
+ onStream,
781
+ onError
782
+ );
783
+ });
784
+ const chunkResults = await Promise.all(chunkPromises);
785
+ results.push(...chunkResults);
786
+ }
787
+
788
+ return results;
789
+ }
790
+ }