@ai.ntellect/core 0.4.1 → 0.6.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 (125) hide show
  1. package/.mocharc.json +1 -1
  2. package/README.md +311 -272
  3. package/dist/graph/controller.js +63 -0
  4. package/dist/graph/engine.js +563 -0
  5. package/dist/index.js +6 -6
  6. package/dist/memory/adapters/meilisearch/index.js +249 -0
  7. package/dist/memory/adapters/redis/index.js +96 -0
  8. package/dist/memory/index.js +9 -0
  9. package/dist/services/agenda.js +115 -0
  10. package/dist/services/embedding.js +40 -0
  11. package/dist/services/queue.js +99 -103
  12. package/dist/test/graph/controller.test.js +170 -0
  13. package/dist/test/graph/engine.test.js +465 -0
  14. package/dist/test/memory/adapters/meilisearch.test.js +250 -0
  15. package/dist/test/memory/adapters/redis.test.js +143 -0
  16. package/dist/test/memory/base.test.js +209 -0
  17. package/dist/test/services/agenda.test.js +230 -0
  18. package/dist/test/services/queue.test.js +258 -0
  19. package/dist/types/index.js +2 -0
  20. package/dist/utils/generate-object.js +32 -11
  21. package/dist/utils/inject-actions.js +2 -2
  22. package/dist/utils/queue-item-transformer.js +2 -2
  23. package/dist/utils/state-manager.js +20 -0
  24. package/graph/controller.ts +60 -0
  25. package/graph/engine.ts +709 -0
  26. package/index.ts +7 -7
  27. package/interfaces/index.ts +119 -0
  28. package/memory/adapters/meilisearch/index.ts +286 -0
  29. package/memory/adapters/redis/index.ts +103 -0
  30. package/memory/index.ts +22 -0
  31. package/package.json +9 -2
  32. package/services/agenda.ts +118 -0
  33. package/services/embedding.ts +26 -0
  34. package/services/queue.ts +5 -32
  35. package/test/.env.test +4 -0
  36. package/test/graph/controller.test.ts +186 -0
  37. package/test/graph/engine.test.ts +563 -0
  38. package/test/memory/adapters/meilisearch.test.ts +297 -0
  39. package/test/memory/adapters/redis.test.ts +160 -0
  40. package/test/memory/base.test.ts +229 -0
  41. package/test/services/agenda.test.ts +280 -0
  42. package/test/services/queue.test.ts +286 -44
  43. package/tsconfig.json +10 -9
  44. package/types/index.ts +270 -0
  45. package/utils/generate-object.js +111 -0
  46. package/utils/generate-object.ts +24 -12
  47. package/utils/header-builder.js +34 -0
  48. package/utils/inject-actions.js +16 -0
  49. package/utils/inject-actions.ts +3 -3
  50. package/utils/queue-item-transformer.js +24 -0
  51. package/utils/queue-item-transformer.ts +8 -11
  52. package/utils/sanitize-results.js +60 -0
  53. package/utils/schema-generator.js +46 -0
  54. package/utils/state-manager.js +20 -0
  55. package/utils/state-manager.ts +30 -0
  56. package/.nvmrc +0 -1
  57. package/README.FR.md +0 -365
  58. package/agent/index.ts +0 -244
  59. package/agent/tools/get-rss.ts +0 -64
  60. package/bull.ts +0 -5
  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/index.ts +0 -103
  107. package/llm/interpreter/context.ts +0 -101
  108. package/llm/interpreter/index.ts +0 -136
  109. package/llm/memory-manager/context.ts +0 -21
  110. package/llm/memory-manager/index.ts +0 -163
  111. package/llm/orchestrator/context.ts +0 -22
  112. package/llm/orchestrator/index.ts +0 -232
  113. package/llm/orchestrator/types.ts +0 -14
  114. package/memory/cache.ts +0 -221
  115. package/memory/persistent.ts +0 -265
  116. package/services/redis-cache.ts +0 -128
  117. package/services/scheduler.ts +0 -128
  118. package/services/telegram-monitor.ts +0 -138
  119. package/t.py +0 -79
  120. package/t.spec +0 -38
  121. package/t.ts +0 -133
  122. package/test/llm/orchestrator.test.ts +0 -47
  123. package/test/llm/synthesizer.test.ts +0 -31
  124. package/types.ts +0 -288
  125. /package/dist/{llm/orchestrator/types.js → interfaces/index.js} +0 -0
@@ -0,0 +1,709 @@
1
+ import { Persistence, RealTimeNotifier } from "@/interfaces";
2
+ import { GraphDefinition, Node, NodeRelationship, 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
+ * Represents a directed worflow structure capable of executing nodes in sequence or parallel.
17
+ * The worflow can handle state management, event emissions, and conditional execution paths.
18
+ *
19
+ * @template T - The type of data stored in the worflow's context
20
+ */
21
+ export class GraphEngine<T> {
22
+ /** Stores global context data accessible to all nodes */
23
+ public globalContext: Map<string, any>;
24
+
25
+ /** Event emitter for handling worflow-wide events */
26
+ private eventEmitter: EventEmitter;
27
+
28
+ /** Map of all nodes in the worflow */
29
+ public nodes: Map<string, Node<T>>;
30
+
31
+ /** Set of nodes that have been executed */
32
+ public executedNodes: Set<string>;
33
+
34
+ /** Name identifier for the worflow */
35
+ public name: string;
36
+
37
+ /** Optional persistence layer for saving worflow state */
38
+ private persistence: Persistence<T> | null;
39
+
40
+ /** Optional notifier for real-time updates */
41
+ private notifier: RealTimeNotifier | null;
42
+
43
+ private schema?: z.ZodSchema<T>;
44
+
45
+ private currentState: SharedState<T>;
46
+
47
+ /**
48
+ * Creates a new Graph instance.
49
+ *
50
+ * @param {GraphDefinition<T>} [definition] - Initial worflow structure and configuration
51
+ * @param {Object} [config] - Additional configuration options
52
+ * @param {boolean} [config.autoDetectCycles] - Whether to check for cycles during initialization
53
+ * @throws {Error} If cycles are detected when autoDetectCycles is true
54
+ */
55
+ constructor(definition?: GraphDefinition<T>, options?: GraphOptions<T>) {
56
+ this.name = definition?.name || "anonymous";
57
+ this.eventEmitter = new EventEmitter();
58
+ this.globalContext = new Map();
59
+ this.nodes = new Map();
60
+ this.executedNodes = new Set();
61
+ this.persistence = null;
62
+ this.notifier = null;
63
+ this.schema = options?.schema;
64
+ this.currentState = { context: {} } as SharedState<T>;
65
+
66
+ if (definition) {
67
+ this.loadFromDefinition(definition);
68
+ }
69
+
70
+ if (options?.autoDetectCycles && this.checkForCycles()) {
71
+ throw new Error("Cycle detected in the workflow");
72
+ }
73
+
74
+ if (options?.initialState) {
75
+ this.setState(options.initialState);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Adds a value to the global context.
81
+ * @param {string} key - The key to store the value under
82
+ * @param {any} value - The value to store
83
+ */
84
+ addToContext(key: string, value: any): void {
85
+ this.globalContext.set(key, value);
86
+ }
87
+
88
+ /**
89
+ * Retrieves a value from the global context.
90
+ * @param {string} key - The key to retrieve
91
+ * @returns {any} The stored value, or undefined if not found
92
+ */
93
+ getContext(key: string): any {
94
+ return this.globalContext.get(key);
95
+ }
96
+
97
+ /**
98
+ * Removes a value from the global context.
99
+ * @param {string} key - The key to remove
100
+ */
101
+ removeFromContext(key: string): void {
102
+ this.globalContext.delete(key);
103
+ }
104
+
105
+ /**
106
+ * Sets the persistence layer for the worflow.
107
+ * @param {Persistence<T>} persistence - The persistence implementation
108
+ */
109
+ setPersistence(persistence: Persistence<T>): void {
110
+ this.persistence = persistence;
111
+ }
112
+
113
+ /**
114
+ * Sets the real-time notifier for the worflow.
115
+ * @param {RealTimeNotifier} notifier - The notifier implementation
116
+ */
117
+ setNotifier(notifier: RealTimeNotifier): void {
118
+ this.notifier = notifier;
119
+ }
120
+
121
+ /**
122
+ * Loads a worflow structure from a definition object.
123
+ * @private
124
+ * @param {GraphDefinition<T>} definition - The worflow definition
125
+ */
126
+ private loadFromDefinition(definition: GraphDefinition<T>): void {
127
+ Object.entries(definition.nodes).forEach(([_, nodeConfig]) => {
128
+ this.addNode(nodeConfig, {
129
+ condition: nodeConfig.condition,
130
+ relationships: nodeConfig.relationships,
131
+ });
132
+ });
133
+ }
134
+
135
+ /**
136
+ * Recursively checks if a node is part of a cycle.
137
+ * @private
138
+ * @param {string} nodeName - The name of the node to check
139
+ * @param {Set<string>} visited - Set of visited nodes
140
+ * @param {Set<string>} recStack - Set of nodes in the current recursion stack
141
+ * @returns {boolean} True if a cycle is detected, false otherwise
142
+ */
143
+ private isCyclic(
144
+ nodeName: string,
145
+ visited: Set<string>,
146
+ recStack: Set<string>
147
+ ): boolean {
148
+ if (!visited.has(nodeName)) {
149
+ visited.add(nodeName);
150
+ recStack.add(nodeName);
151
+
152
+ const currentNode = this.nodes.get(nodeName);
153
+ if (currentNode?.relationships) {
154
+ for (const relation of currentNode.relationships) {
155
+ const targetNode = relation.name;
156
+ if (
157
+ !visited.has(targetNode) &&
158
+ this.isCyclic(targetNode, visited, recStack)
159
+ ) {
160
+ return true;
161
+ } else if (recStack.has(targetNode)) {
162
+ return true;
163
+ }
164
+ }
165
+ }
166
+ }
167
+ recStack.delete(nodeName);
168
+ return false;
169
+ }
170
+
171
+ /**
172
+ * Checks if the worflow contains any cycles.
173
+ * @returns {boolean} True if cycles are detected, false otherwise
174
+ */
175
+ public checkForCycles(): boolean {
176
+ const visited = new Set<string>();
177
+ const recStack = new Set<string>();
178
+
179
+ for (const nodeName of this.nodes.keys()) {
180
+ if (this.isCyclic(nodeName, visited, recStack)) {
181
+ return true;
182
+ }
183
+ }
184
+ return false;
185
+ }
186
+
187
+ /**
188
+ * Adds a new node to the worflow.
189
+ * @param {Node<T>} node - The node to add
190
+ * @param {Object} options - Node configuration options
191
+ * @param {Function} [options.condition] - Condition function for node execution
192
+ * @param {string[]} [options.relations] - Array of relations node names
193
+ * @param {string[]} [options.events] - Array of event names to listen for
194
+ */
195
+ addNode(
196
+ node: Node<T>,
197
+ {
198
+ condition,
199
+ relationships,
200
+ events,
201
+ }: {
202
+ condition?: (state: SharedState<T>) => boolean;
203
+ relationships?: NodeRelationship[];
204
+ events?: string[];
205
+ }
206
+ ): void {
207
+ node.relationships = relationships;
208
+ node.condition = condition;
209
+
210
+ if (events) {
211
+ events.forEach((event) => {
212
+ this.eventEmitter.on(event, async (data) => {
213
+ const state = data.state || {};
214
+ await this.execute(state, node.name);
215
+ });
216
+ });
217
+ }
218
+
219
+ this.nodes.set(node.name, node);
220
+ }
221
+
222
+ /**
223
+ * Emits an event to the worflow's event emitter.
224
+ * @param {string} eventName - Name of the event to emit
225
+ * @param {any} data - Data to pass with the event
226
+ */
227
+ public emit(eventName: string, data: any): void {
228
+ this.eventEmitter.emit(eventName, data);
229
+ }
230
+
231
+ /**
232
+ * Adds a subworflow as a node in the current worflow.
233
+ * @param {Graph<T>} subGraph - The subworflow to add
234
+ * @param {string} entryNode - The entry node name in the subworflow
235
+ * @param {string} name - The name for the subworflow node
236
+ */
237
+ addSubGraph(subGraph: GraphEngine<T>, entryNode: string, name: string): void {
238
+ const subGraphNode: Node<T> = {
239
+ name: name,
240
+ execute: async (state) => {
241
+ await subGraph.execute(state, entryNode);
242
+ return state;
243
+ },
244
+ };
245
+ this.nodes.set(name, subGraphNode);
246
+ }
247
+
248
+ /**
249
+ * Executes the worflow starting from a specific node.
250
+ * @param {SharedState<T>} state - The initial state
251
+ * @param {string} startNode - The name of the starting node
252
+ * @param {Function} [onStream] - Callback for streaming state updates
253
+ * @param {Function} [onError] - Callback for handling errors
254
+ */
255
+ async execute(
256
+ state: SharedState<T>,
257
+ startNode: string,
258
+ onStream?: (state: SharedState<T>) => void,
259
+ onError?: (error: Error, nodeName: string, state: SharedState<T>) => void
260
+ ): Promise<SharedState<T>> {
261
+ try {
262
+ if (this.schema) {
263
+ try {
264
+ this.schema.parse(state.context);
265
+ } catch (error) {
266
+ const validationError = new Error(
267
+ `Initial state validation failed: ${
268
+ error instanceof Error ? error.message : error
269
+ }`
270
+ );
271
+ if (onError) onError(validationError, startNode, state);
272
+ throw validationError;
273
+ }
274
+ }
275
+
276
+ this.setState(state);
277
+ let currentNodeName = startNode;
278
+
279
+ while (currentNodeName) {
280
+ this.executedNodes.add(currentNodeName);
281
+ const currentNode = this.nodes.get(currentNodeName);
282
+ if (!currentNode) throw new Error(`Node ${currentNodeName} not found.`);
283
+
284
+ if (
285
+ currentNode.condition &&
286
+ !currentNode.condition(this.currentState)
287
+ ) {
288
+ break;
289
+ }
290
+
291
+ try {
292
+ if (this.notifier) {
293
+ this.notifier.notify("nodeExecutionStarted", {
294
+ workflow: this.name,
295
+ node: currentNodeName,
296
+ });
297
+ }
298
+
299
+ const params = currentNode.schema?.parse(this.currentState);
300
+ const newState = await currentNode.execute(
301
+ params || {},
302
+ this.currentState
303
+ );
304
+
305
+ if (newState) {
306
+ this.setState(newState);
307
+ if (onStream) onStream(this.currentState);
308
+ }
309
+
310
+ if (this.persistence) {
311
+ await this.persistence.saveState(
312
+ this.name,
313
+ this.currentState,
314
+ currentNodeName
315
+ );
316
+ }
317
+
318
+ if (this.notifier) {
319
+ await this.notifier.notify("nodeExecutionCompleted", {
320
+ workflow: this.name,
321
+ node: currentNodeName,
322
+ state: this.currentState,
323
+ });
324
+ }
325
+ } catch (error) {
326
+ if (onError)
327
+ onError(error as Error, currentNodeName, this.currentState);
328
+ if (this.notifier) {
329
+ this.notifier.notify("nodeExecutionFailed", {
330
+ workflow: this.name,
331
+ node: currentNodeName,
332
+ state: this.currentState,
333
+ error,
334
+ });
335
+ }
336
+ break;
337
+ }
338
+
339
+ const relationsNodes = currentNode.relationships || [];
340
+ if (relationsNodes.length > 1) {
341
+ await Promise.all(
342
+ relationsNodes.map((relation) =>
343
+ this.execute(this.currentState, relation.name, onStream, onError)
344
+ )
345
+ );
346
+ break;
347
+ } else {
348
+ currentNodeName = relationsNodes[0]?.name || "";
349
+ }
350
+ }
351
+
352
+ return this.getState();
353
+ } catch (error) {
354
+ if (onError) {
355
+ onError(error as Error, startNode, state);
356
+ }
357
+ throw error;
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Executes multiple nodes in parallel with a concurrency limit.
363
+ * @param {SharedState<T>} state - The shared state
364
+ * @param {string[]} nodeNames - Array of node names to execute
365
+ * @param {number} [concurrencyLimit=5] - Maximum number of concurrent executions
366
+ * @param {Function} [onStream] - Callback for streaming state updates
367
+ * @param {Function} [onError] - Callback for handling errors
368
+ */
369
+ async executeParallel(
370
+ state: SharedState<T>,
371
+ nodeNames: string[],
372
+ concurrencyLimit: number = 5,
373
+ onStream?: (state: SharedState<T>) => void,
374
+ onError?: (error: Error, nodeName: string, state: SharedState<T>) => void
375
+ ): Promise<void> {
376
+ const executeWithLimit = async (nodeName: string) => {
377
+ await this.execute(state, nodeName, onStream, onError);
378
+ };
379
+
380
+ const chunks = [];
381
+ for (let i = 0; i < nodeNames.length; i += concurrencyLimit) {
382
+ chunks.push(nodeNames.slice(i, i + concurrencyLimit));
383
+ }
384
+
385
+ for (const chunk of chunks) {
386
+ await Promise.all(chunk.map(executeWithLimit));
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Updates the worflow structure with a new definition.
392
+ * @param {GraphDefinition<T>} definition - The new worflow definition
393
+ */
394
+ updateGraph(definition: GraphDefinition<T>): void {
395
+ Object.entries(definition.nodes).forEach(([_, nodeConfig]) => {
396
+ if (this.nodes.has(nodeConfig.name)) {
397
+ const existingNode = this.nodes.get(nodeConfig.name)!;
398
+ existingNode.relationships =
399
+ nodeConfig.relationships || existingNode.relationships;
400
+ existingNode.condition = nodeConfig.condition || existingNode.condition;
401
+ } else {
402
+ this.addNode(nodeConfig, {
403
+ condition: nodeConfig.condition,
404
+ relationships: nodeConfig.relationships,
405
+ });
406
+ }
407
+ });
408
+ }
409
+
410
+ /**
411
+ * Replace the worflow with a new definition.
412
+ * @param {GraphDefinition<T>} definition - The new worflow definition
413
+ */
414
+ replaceGraph(definition: GraphDefinition<T>): void {
415
+ this.nodes.clear();
416
+ this.loadFromDefinition(definition);
417
+ }
418
+
419
+ /**
420
+ * Generates a visual representation of the worflow using Mermaid diagram syntax.
421
+ * The diagram shows all nodes and their connections, with special highlighting for:
422
+ * - Entry nodes (green)
423
+ * - Event nodes (yellow)
424
+ * - Conditional nodes (orange)
425
+ *
426
+ * @param {string} [title] - Optional title for the diagram
427
+ * @returns {string} Mermaid diagram syntax representing the worflow
428
+ */
429
+ generateMermaidDiagram(title?: string): string {
430
+ const lines: string[] = ["flowchart TD"];
431
+
432
+ if (title) {
433
+ lines.push(` subgraph ${title}`);
434
+ }
435
+
436
+ // Add nodes with styling
437
+ this.nodes.forEach((node, nodeName) => {
438
+ const hasEvents = node.events && node.events.length > 0;
439
+ const hasCondition = !!node.condition;
440
+
441
+ // Style nodes based on their properties
442
+ let style = "";
443
+ if (hasEvents) {
444
+ style = "style " + nodeName + " fill:#FFD700,stroke:#DAA520"; // Yellow for event nodes
445
+ } else if (hasCondition) {
446
+ style = "style " + nodeName + " fill:#FFA500,stroke:#FF8C00"; // Orange for conditional nodes
447
+ }
448
+
449
+ // Add node definition
450
+ lines.push(` ${nodeName}[${nodeName}]`);
451
+ if (style) {
452
+ lines.push(` ${style}`);
453
+ }
454
+ });
455
+
456
+ // Add connections
457
+ this.nodes.forEach((node, nodeName) => {
458
+ if (node.relationships) {
459
+ node.relationships.forEach((relationsNode) => {
460
+ let connectionStyle = "";
461
+ if (node.condition) {
462
+ connectionStyle = "---|condition|"; // Add label for conditional connections
463
+ } else {
464
+ connectionStyle = "-->"; // Normal connection
465
+ }
466
+ lines.push(` ${nodeName} ${connectionStyle} ${relationsNode}`);
467
+ });
468
+ }
469
+
470
+ // Add event connections if any
471
+ if (node.events && node.events.length > 0) {
472
+ node.events.forEach((event: string) => {
473
+ const eventNodeId = `${event}_event`;
474
+ lines.push(` ${eventNodeId}((${event})):::event`);
475
+ lines.push(` ${eventNodeId} -.->|trigger| ${nodeName}`);
476
+ });
477
+ // Add style class for event nodes
478
+ lines.push(" classDef event fill:#FFD700,stroke:#DAA520");
479
+ }
480
+ });
481
+
482
+ if (title) {
483
+ lines.push(" end");
484
+ }
485
+
486
+ return lines.join("\n");
487
+ }
488
+
489
+ /**
490
+ * Renders the worflow visualization using Mermaid syntax.
491
+ * This method can be used to visualize the worflow structure in supported environments.
492
+ *
493
+ * @param {string} [title] - Optional title for the visualization
494
+ */
495
+ visualize(title?: string): void {
496
+ const diagram = this.generateMermaidDiagram(title);
497
+ console.log(
498
+ "To visualize this worflow, use a Mermaid-compatible renderer with this syntax:"
499
+ );
500
+ console.log("\n```mermaid");
501
+ console.log(diagram);
502
+ console.log("```\n");
503
+ }
504
+
505
+ exportGraphToJson<T>(worflow: GraphDefinition<T>): string {
506
+ const result = {
507
+ worflowName: worflow.name,
508
+ entryNode: worflow.entryNode,
509
+ nodes: Object.entries(worflow.nodes).reduce((acc, [key, node]) => {
510
+ acc[key] = {
511
+ name: node.name,
512
+ description: node.description || "No description provided",
513
+ execute: node.execute.name,
514
+ condition: node.condition ? node.condition.toString() : "None",
515
+ relationships: node.relationships || [],
516
+ };
517
+ return acc;
518
+ }, {} as Record<string, any>),
519
+ };
520
+ return JSON.stringify(result, null, 2);
521
+ }
522
+
523
+ /**
524
+ * Generates a visual representation of the workflow schema.
525
+ * Displays the structure of the data expected for each node.
526
+ *
527
+ * @returns {string} A formatted string describing the workflow schema
528
+ */
529
+ visualizeSchema(): string {
530
+ const output: string[] = [];
531
+
532
+ output.push(`📋 Graph: ${this.name}`);
533
+ output.push("=".repeat(50));
534
+
535
+ if (this.schema) {
536
+ output.push("🔷 Global Schema:");
537
+ output.push("-".repeat(30));
538
+
539
+ if (this.schema instanceof z.ZodObject) {
540
+ const shape = this.schema.shape;
541
+ Object.entries(shape).forEach(([key, value]) => {
542
+ const description = this.describeZodType(value as z.ZodType, 1);
543
+ output.push(`${key}:`);
544
+ output.push(description);
545
+ });
546
+ }
547
+ output.push("");
548
+ }
549
+
550
+ output.push("🔷 Nodes:");
551
+ output.push("-".repeat(30));
552
+
553
+ this.nodes.forEach((node, nodeName) => {
554
+ output.push(`\n📍 Node: ${nodeName}`);
555
+ output.push(
556
+ `Description: ${node.description || "No description provided"}`
557
+ );
558
+
559
+ if (node.relationships && node.relationships.length > 0) {
560
+ output.push(`Next nodes: ${node.relationships.join(", ")}`);
561
+ }
562
+
563
+ output.push("");
564
+ });
565
+
566
+ return output.join("\n");
567
+ }
568
+
569
+ /**
570
+ * Recursively describes a Zod type.
571
+ */
572
+ public describeZodType(type: z.ZodType, indent: number = 0): string {
573
+ const padding = " ".repeat(indent);
574
+
575
+ if (type instanceof z.ZodObject) {
576
+ const shape = type.shape;
577
+ const lines: string[] = [];
578
+
579
+ Object.entries(shape).forEach(([key, value]) => {
580
+ const isOptional = value instanceof z.ZodOptional;
581
+ const actualType = isOptional
582
+ ? (value as z.ZodOptional<z.ZodType<any, any, any>>).unwrap()
583
+ : (value as z.ZodType<any, any, any>);
584
+ const description = this.describeZodType(actualType, indent + 1);
585
+
586
+ lines.push(`${padding}${key}${isOptional ? "?" : ""}: ${description}`);
587
+ });
588
+
589
+ return lines.join("\n");
590
+ }
591
+
592
+ if (type instanceof z.ZodArray) {
593
+ const elementType = this.describeZodType(type.element, indent);
594
+ return `Array<${elementType}>`;
595
+ }
596
+
597
+ if (type instanceof z.ZodString) {
598
+ const checks = type._def.checks || [];
599
+ const constraints = checks
600
+ .map((check) => {
601
+ if (check.kind === "url") return "url";
602
+ if (check.kind === "email") return "email";
603
+ return check.kind;
604
+ })
605
+ .join(", ");
606
+
607
+ return constraints ? `string (${constraints})` : "string";
608
+ }
609
+
610
+ if (type instanceof z.ZodNumber) {
611
+ return "number";
612
+ }
613
+
614
+ if (type instanceof z.ZodBoolean) {
615
+ return "boolean";
616
+ }
617
+
618
+ if (type instanceof z.ZodOptional) {
619
+ return `${this.describeZodType(type.unwrap(), indent)} (optional)`;
620
+ }
621
+
622
+ return type.constructor.name.replace("Zod", "") || "unknown";
623
+ }
624
+
625
+ /**
626
+ * Updates the state of a node.
627
+ * @param {SharedState<T>} state - The current state
628
+ * @param {Partial<T>} updates - The updates to apply
629
+ * @returns {SharedState<T>} The updated state
630
+ */
631
+ protected updateNodeState(state: SharedState<T>, updates: Partial<T>) {
632
+ return {
633
+ ...state,
634
+ context: {
635
+ ...(state.context || {}),
636
+ ...updates,
637
+ },
638
+ };
639
+ }
640
+
641
+ /**
642
+ * Retrieves the current state of the workflow.
643
+ * @returns {SharedState<T>} The current state
644
+ */
645
+ public getState(): SharedState<T> {
646
+ return this.currentState;
647
+ }
648
+
649
+ /**
650
+ * Sets the state of the workflow.
651
+ * @param {Partial<SharedState<T>>} state - The new state
652
+ */
653
+ public setState(state: Partial<SharedState<T>>): void {
654
+ this.currentState = this.mergeStates(this.currentState, state);
655
+
656
+ if (state.context) {
657
+ Object.entries(state.context).forEach(([key, value]) => {
658
+ this.globalContext.set(key, value);
659
+ });
660
+ }
661
+ const currentNode = Array.from(this.executedNodes).pop();
662
+ if (currentNode) {
663
+ const node = this.nodes.get(currentNode);
664
+ if (node) {
665
+ node.state = {
666
+ ...(node.state || {}),
667
+ ...(state.context || {}),
668
+ };
669
+ }
670
+ }
671
+ }
672
+
673
+ /**
674
+ * Merges two states.
675
+ * @param {SharedState<T>} currentState - The current state
676
+ * @param {Partial<SharedState<T>>} newState - The new state
677
+ * @returns {SharedState<T>} The merged state
678
+ */
679
+ private mergeStates(
680
+ currentState: SharedState<T>,
681
+ newState: Partial<SharedState<T>>
682
+ ): SharedState<T> {
683
+ return {
684
+ ...currentState,
685
+ context: {
686
+ ...(currentState.context || {}),
687
+ ...(newState.context || {}),
688
+ },
689
+ };
690
+ }
691
+
692
+ /**
693
+ * Updates the state of the workflow.
694
+ * @param {Partial<SharedState<T>>} updates - The updates to apply
695
+ * @returns {SharedState<T>} The updated state
696
+ */
697
+ public updateState(updates: Partial<SharedState<T>>): SharedState<T> {
698
+ const currentState = this.getState();
699
+ const newState = {
700
+ ...currentState,
701
+ context: {
702
+ ...currentState.context,
703
+ ...(updates.context || {}),
704
+ },
705
+ };
706
+ this.setState(newState);
707
+ return newState;
708
+ }
709
+ }