@wundr.io/langgraph-orchestrator 1.0.3

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 (57) hide show
  1. package/README.md +842 -0
  2. package/dist/checkpointing.d.ts +265 -0
  3. package/dist/checkpointing.d.ts.map +1 -0
  4. package/dist/checkpointing.js +577 -0
  5. package/dist/checkpointing.js.map +1 -0
  6. package/dist/edges/conditional-edge.d.ts +230 -0
  7. package/dist/edges/conditional-edge.d.ts.map +1 -0
  8. package/dist/edges/conditional-edge.js +439 -0
  9. package/dist/edges/conditional-edge.js.map +1 -0
  10. package/dist/edges/loop-edge.d.ts +290 -0
  11. package/dist/edges/loop-edge.d.ts.map +1 -0
  12. package/dist/edges/loop-edge.js +503 -0
  13. package/dist/edges/loop-edge.js.map +1 -0
  14. package/dist/index.d.ts +125 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +269 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/nodes/decision-node.d.ts +276 -0
  19. package/dist/nodes/decision-node.d.ts.map +1 -0
  20. package/dist/nodes/decision-node.js +403 -0
  21. package/dist/nodes/decision-node.js.map +1 -0
  22. package/dist/nodes/human-node.d.ts +272 -0
  23. package/dist/nodes/human-node.d.ts.map +1 -0
  24. package/dist/nodes/human-node.js +394 -0
  25. package/dist/nodes/human-node.js.map +1 -0
  26. package/dist/nodes/llm-node.d.ts +173 -0
  27. package/dist/nodes/llm-node.d.ts.map +1 -0
  28. package/dist/nodes/llm-node.js +325 -0
  29. package/dist/nodes/llm-node.js.map +1 -0
  30. package/dist/nodes/tool-node.d.ts +151 -0
  31. package/dist/nodes/tool-node.d.ts.map +1 -0
  32. package/dist/nodes/tool-node.js +373 -0
  33. package/dist/nodes/tool-node.js.map +1 -0
  34. package/dist/prebuilt-graphs/plan-execute-refine.d.ts +149 -0
  35. package/dist/prebuilt-graphs/plan-execute-refine.d.ts.map +1 -0
  36. package/dist/prebuilt-graphs/plan-execute-refine.js +600 -0
  37. package/dist/prebuilt-graphs/plan-execute-refine.js.map +1 -0
  38. package/dist/state-graph.d.ts +158 -0
  39. package/dist/state-graph.d.ts.map +1 -0
  40. package/dist/state-graph.js +756 -0
  41. package/dist/state-graph.js.map +1 -0
  42. package/dist/types.d.ts +762 -0
  43. package/dist/types.d.ts.map +1 -0
  44. package/dist/types.js +73 -0
  45. package/dist/types.js.map +1 -0
  46. package/package.json +57 -0
  47. package/src/checkpointing.ts +702 -0
  48. package/src/edges/conditional-edge.ts +518 -0
  49. package/src/edges/loop-edge.ts +623 -0
  50. package/src/index.ts +416 -0
  51. package/src/nodes/decision-node.ts +538 -0
  52. package/src/nodes/human-node.ts +572 -0
  53. package/src/nodes/llm-node.ts +448 -0
  54. package/src/nodes/tool-node.ts +525 -0
  55. package/src/prebuilt-graphs/plan-execute-refine.ts +769 -0
  56. package/src/state-graph.ts +990 -0
  57. package/src/types.ts +729 -0
@@ -0,0 +1,756 @@
1
+ "use strict";
2
+ /**
3
+ * StateGraph - Core class for defining workflow graphs with nodes and edges
4
+ * @module @wundr.io/langgraph-orchestrator
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.StateGraph = void 0;
8
+ const eventemitter3_1 = require("eventemitter3");
9
+ const uuid_1 = require("uuid");
10
+ /**
11
+ * Default graph configuration
12
+ */
13
+ const DEFAULT_CONFIG = {
14
+ maxIterations: 100,
15
+ timeout: 300000, // 5 minutes
16
+ checkpointEnabled: true,
17
+ checkpointInterval: 1,
18
+ parallelExecution: false,
19
+ retry: {
20
+ maxRetries: 3,
21
+ initialDelay: 1000,
22
+ backoffMultiplier: 2,
23
+ maxDelay: 30000,
24
+ retryableErrors: ['TIMEOUT', 'RATE_LIMIT', 'NETWORK_ERROR'],
25
+ },
26
+ logLevel: 'info',
27
+ };
28
+ /**
29
+ * Console-based logger implementation
30
+ */
31
+ class ConsoleLogger {
32
+ level;
33
+ constructor(level) {
34
+ this.level = level;
35
+ }
36
+ shouldLog(msgLevel) {
37
+ const levels = ['debug', 'info', 'warn', 'error', 'silent'];
38
+ return levels.indexOf(msgLevel) >= levels.indexOf(this.level);
39
+ }
40
+ debug(message, data) {
41
+ if (this.shouldLog('debug')) {
42
+ // Using process.stderr for logging to avoid polluting stdout
43
+ process.stderr.write(`[DEBUG] ${message} ${data ? JSON.stringify(data) : ''}\n`);
44
+ }
45
+ }
46
+ info(message, data) {
47
+ if (this.shouldLog('info')) {
48
+ // Using process.stderr for logging to avoid polluting stdout
49
+ process.stderr.write(`[INFO] ${message} ${data ? JSON.stringify(data) : ''}\n`);
50
+ }
51
+ }
52
+ warn(message, data) {
53
+ if (this.shouldLog('warn')) {
54
+ console.warn(`[WARN] ${message}`, data ?? '');
55
+ }
56
+ }
57
+ error(message, data) {
58
+ if (this.shouldLog('error')) {
59
+ console.error(`[ERROR] ${message}`, data ?? '');
60
+ }
61
+ }
62
+ }
63
+ /**
64
+ * StateGraph class for defining and executing workflow graphs
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * const graph = new StateGraph('my-workflow')
69
+ * .addNode('start', {
70
+ * id: 'start',
71
+ * name: 'Start Node',
72
+ * type: 'start',
73
+ * config: {},
74
+ * execute: async (state) => ({ state, next: 'process' })
75
+ * })
76
+ * .addNode('process', {
77
+ * id: 'process',
78
+ * name: 'Process Node',
79
+ * type: 'transform',
80
+ * config: {},
81
+ * execute: async (state) => ({ state, next: 'end' })
82
+ * })
83
+ * .addEdge('start', 'process')
84
+ * .setEntryPoint('start');
85
+ *
86
+ * const result = await graph.execute();
87
+ * ```
88
+ */
89
+ class StateGraph extends eventemitter3_1.EventEmitter {
90
+ id;
91
+ name;
92
+ description;
93
+ nodes = new Map();
94
+ edges = new Map();
95
+ entryPoint;
96
+ config;
97
+ _checkpointer;
98
+ services = {};
99
+ logger;
100
+ /** Get the checkpointer */
101
+ get checkpointer() {
102
+ return this._checkpointer;
103
+ }
104
+ /**
105
+ * Create a new StateGraph
106
+ * @param name - Human-readable name for the graph
107
+ * @param config - Optional configuration overrides
108
+ */
109
+ constructor(name, config) {
110
+ super();
111
+ this.id = (0, uuid_1.v4)();
112
+ this.name = name;
113
+ this.config = { ...DEFAULT_CONFIG, ...config };
114
+ this.logger = new ConsoleLogger(this.config.logLevel);
115
+ }
116
+ /**
117
+ * Add a node to the graph
118
+ * @param name - Unique name for the node
119
+ * @param definition - Node definition
120
+ * @returns this for chaining
121
+ */
122
+ addNode(name, definition) {
123
+ if (this.nodes.has(name)) {
124
+ throw new Error(`Node "${name}" already exists in graph "${this.name}"`);
125
+ }
126
+ this.nodes.set(name, definition);
127
+ this.edges.set(name, []);
128
+ return this;
129
+ }
130
+ /**
131
+ * Add a direct edge between nodes
132
+ * @param from - Source node name
133
+ * @param to - Target node name
134
+ * @returns this for chaining
135
+ */
136
+ addEdge(from, to) {
137
+ this.validateNodeExists(from);
138
+ this.validateNodeExists(to);
139
+ const edge = {
140
+ from,
141
+ to,
142
+ type: 'direct',
143
+ };
144
+ const edges = this.edges.get(from) ?? [];
145
+ edges.push(edge);
146
+ this.edges.set(from, edges);
147
+ return this;
148
+ }
149
+ /**
150
+ * Add a conditional edge
151
+ * @param from - Source node name
152
+ * @param to - Target node name
153
+ * @param condition - Condition for traversal
154
+ * @returns this for chaining
155
+ */
156
+ addConditionalEdge(from, to, condition) {
157
+ this.validateNodeExists(from);
158
+ this.validateNodeExists(to);
159
+ const edge = {
160
+ from,
161
+ to,
162
+ type: 'conditional',
163
+ condition,
164
+ };
165
+ const edges = this.edges.get(from) ?? [];
166
+ edges.push(edge);
167
+ this.edges.set(from, edges);
168
+ return this;
169
+ }
170
+ /**
171
+ * Add a loop edge that can cycle back
172
+ * @param from - Source node name
173
+ * @param to - Target node name (can be same as from)
174
+ * @param condition - Condition to continue looping
175
+ * @returns this for chaining
176
+ */
177
+ addLoopEdge(from, to, condition) {
178
+ this.validateNodeExists(from);
179
+ this.validateNodeExists(to);
180
+ const edge = {
181
+ from,
182
+ to,
183
+ type: 'loop',
184
+ condition,
185
+ };
186
+ const edges = this.edges.get(from) ?? [];
187
+ edges.push(edge);
188
+ this.edges.set(from, edges);
189
+ return this;
190
+ }
191
+ /**
192
+ * Add parallel edges to multiple targets
193
+ * @param from - Source node name
194
+ * @param targets - Target node names
195
+ * @returns this for chaining
196
+ */
197
+ addParallelEdges(from, targets) {
198
+ this.validateNodeExists(from);
199
+ targets.forEach(t => this.validateNodeExists(t));
200
+ for (const to of targets) {
201
+ const edge = {
202
+ from,
203
+ to,
204
+ type: 'parallel',
205
+ };
206
+ const edges = this.edges.get(from) ?? [];
207
+ edges.push(edge);
208
+ this.edges.set(from, edges);
209
+ }
210
+ return this;
211
+ }
212
+ /**
213
+ * Set the entry point node
214
+ * @param nodeName - Name of the entry point node
215
+ * @returns this for chaining
216
+ */
217
+ setEntryPoint(nodeName) {
218
+ this.validateNodeExists(nodeName);
219
+ this.entryPoint = nodeName;
220
+ return this;
221
+ }
222
+ /**
223
+ * Set the checkpointer for state persistence
224
+ * @param checkpointer - Checkpointer implementation
225
+ * @returns this for chaining
226
+ */
227
+ setCheckpointer(checkpointer) {
228
+ this._checkpointer = checkpointer;
229
+ this.services = { ...this.services, checkpointer };
230
+ return this;
231
+ }
232
+ /**
233
+ * Set services available to nodes
234
+ * @param services - Services to provide
235
+ * @returns this for chaining
236
+ */
237
+ setServices(services) {
238
+ this.services = { ...this.services, ...services };
239
+ return this;
240
+ }
241
+ /**
242
+ * Get the graph configuration
243
+ * @returns GraphConfig object
244
+ */
245
+ getConfig() {
246
+ if (!this.entryPoint) {
247
+ throw new Error('Entry point not set. Call setEntryPoint() before getConfig()');
248
+ }
249
+ // The nodes Map uses TState-specific NodeDefinition, but GraphConfig uses the base type.
250
+ // This cast is safe because GraphConfig is used for inspection/serialization,
251
+ // while actual execution uses the strongly-typed internal this.nodes Map.
252
+ const typedNodes = new Map();
253
+ for (const [key, value] of this.nodes) {
254
+ // Cast through unknown is necessary here because NodeDefinition<TState>
255
+ // has contravariant parameter in execute function signature
256
+ typedNodes.set(key, value);
257
+ }
258
+ return {
259
+ id: this.id,
260
+ name: this.name,
261
+ description: this.description,
262
+ entryPoint: this.entryPoint,
263
+ nodes: typedNodes,
264
+ edges: new Map(this.edges),
265
+ config: this.config,
266
+ };
267
+ }
268
+ /**
269
+ * Execute the graph
270
+ * @param options - Execution options
271
+ * @returns Execution result
272
+ */
273
+ async execute(options) {
274
+ if (!this.entryPoint) {
275
+ throw new Error('Entry point not set. Call setEntryPoint() before execute()');
276
+ }
277
+ const executionId = (0, uuid_1.v4)();
278
+ const startTime = Date.now();
279
+ const path = [];
280
+ let nodesExecuted = 0;
281
+ let iterations = 0;
282
+ let tokensUsed = 0;
283
+ let checkpointsCreated = 0;
284
+ let retries = 0;
285
+ // Initialize or restore state
286
+ let state = await this.initializeState(executionId, options);
287
+ this.emit('execution:start', state);
288
+ options?.handlers?.onStart?.(state);
289
+ let currentNode = options?.resumeFrom
290
+ ? await this.getNodeFromCheckpoint(options.resumeFrom)
291
+ : this.entryPoint;
292
+ try {
293
+ while (currentNode && iterations < this.config.maxIterations) {
294
+ iterations++;
295
+ const node = this.nodes.get(currentNode);
296
+ if (!node) {
297
+ throw this.createError('NODE_NOT_FOUND', `Node "${currentNode}" not found`);
298
+ }
299
+ path.push(currentNode);
300
+ this.logger.debug(`Entering node: ${currentNode}`, {
301
+ iteration: iterations,
302
+ });
303
+ this.emit('node:enter', currentNode, state);
304
+ options?.handlers?.onNodeEnter?.(currentNode, state);
305
+ // Execute node with retry logic
306
+ const result = await this.executeNodeWithRetry(node, state, executionId, iterations);
307
+ nodesExecuted++;
308
+ if (result.metadata?.tokensUsed) {
309
+ tokensUsed += result.metadata.tokensUsed;
310
+ }
311
+ if (result.metadata?.retryCount) {
312
+ retries += result.metadata.retryCount;
313
+ }
314
+ // Update state with history
315
+ state = this.updateStateWithHistory(result.state, currentNode, state);
316
+ this.emit('node:exit', currentNode, result);
317
+ this.emit('state:updated', state);
318
+ options?.handlers?.onNodeExit?.(currentNode, result);
319
+ // Create checkpoint if enabled
320
+ if (this.config.checkpointEnabled &&
321
+ iterations % this.config.checkpointInterval === 0 &&
322
+ this.checkpointer) {
323
+ const checkpoint = await this.createCheckpoint(state, currentNode, executionId, iterations);
324
+ checkpointsCreated++;
325
+ this.emit('checkpoint:created', checkpoint);
326
+ options?.handlers?.onCheckpoint?.(checkpoint);
327
+ }
328
+ // Check for termination
329
+ if (result.terminate) {
330
+ this.logger.info('Workflow terminated by node', {
331
+ node: currentNode,
332
+ });
333
+ break;
334
+ }
335
+ // Determine next node
336
+ const nextNode = await this.determineNextNode(currentNode, result, state);
337
+ currentNode = nextNode ?? '';
338
+ // Check abort signal
339
+ if (options?.signal?.aborted) {
340
+ throw this.createError('ABORTED', 'Execution was aborted');
341
+ }
342
+ }
343
+ if (iterations >= this.config.maxIterations) {
344
+ throw this.createError('MAX_ITERATIONS', `Exceeded maximum iterations (${this.config.maxIterations})`);
345
+ }
346
+ const duration = Date.now() - startTime;
347
+ const stats = {
348
+ duration,
349
+ nodesExecuted,
350
+ iterations,
351
+ tokensUsed,
352
+ checkpointsCreated,
353
+ retries,
354
+ };
355
+ const result = {
356
+ state,
357
+ success: true,
358
+ stats,
359
+ path,
360
+ };
361
+ this.emit('execution:complete', result);
362
+ options?.handlers?.onComplete?.(state);
363
+ return result;
364
+ }
365
+ catch (error) {
366
+ const workflowError = this.normalizeError(error, currentNode ?? 'unknown');
367
+ state = {
368
+ ...state,
369
+ error: workflowError,
370
+ updatedAt: new Date(),
371
+ };
372
+ this.emit('execution:error', workflowError);
373
+ options?.handlers?.onError?.(workflowError);
374
+ const duration = Date.now() - startTime;
375
+ return {
376
+ state,
377
+ success: false,
378
+ error: workflowError,
379
+ stats: {
380
+ duration,
381
+ nodesExecuted,
382
+ iterations,
383
+ tokensUsed,
384
+ checkpointsCreated,
385
+ retries,
386
+ },
387
+ path,
388
+ };
389
+ }
390
+ }
391
+ /**
392
+ * Compile the graph into an executable form
393
+ * @returns Compiled graph configuration
394
+ */
395
+ compile() {
396
+ // Validate graph structure
397
+ this.validateGraph();
398
+ return this.getConfig();
399
+ }
400
+ /**
401
+ * Create a subgraph from a subset of nodes
402
+ * @param nodeNames - Names of nodes to include
403
+ * @returns New StateGraph containing the subgraph
404
+ */
405
+ subgraph(nodeNames) {
406
+ const sub = new StateGraph(`${this.name}-subgraph`, this.config);
407
+ for (const name of nodeNames) {
408
+ const node = this.nodes.get(name);
409
+ if (node) {
410
+ sub.addNode(name, node);
411
+ // Add edges that connect nodes within the subgraph
412
+ const edges = this.edges.get(name) ?? [];
413
+ for (const edge of edges) {
414
+ if (nodeNames.includes(edge.to)) {
415
+ const existing = sub.edges.get(name) ?? [];
416
+ existing.push(edge);
417
+ sub.edges.set(name, existing);
418
+ }
419
+ }
420
+ }
421
+ }
422
+ return sub;
423
+ }
424
+ // Private helper methods
425
+ validateNodeExists(nodeName) {
426
+ if (!this.nodes.has(nodeName)) {
427
+ throw new Error(`Node "${nodeName}" does not exist in graph "${this.name}"`);
428
+ }
429
+ }
430
+ validateGraph() {
431
+ if (!this.entryPoint) {
432
+ throw new Error('Graph validation failed: No entry point defined');
433
+ }
434
+ if (this.nodes.size === 0) {
435
+ throw new Error('Graph validation failed: No nodes defined');
436
+ }
437
+ // Check all edge targets exist
438
+ for (const [source, edges] of this.edges) {
439
+ for (const edge of edges) {
440
+ if (!this.nodes.has(edge.to)) {
441
+ throw new Error(`Graph validation failed: Edge from "${source}" references non-existent node "${edge.to}"`);
442
+ }
443
+ }
444
+ }
445
+ // Check entry point is reachable
446
+ if (!this.nodes.has(this.entryPoint)) {
447
+ throw new Error(`Graph validation failed: Entry point "${this.entryPoint}" does not exist`);
448
+ }
449
+ }
450
+ async initializeState(executionId, options) {
451
+ const now = new Date();
452
+ const metadata = {
453
+ sessionId: (0, uuid_1.v4)(),
454
+ executionId,
455
+ stepCount: 0,
456
+ tags: [],
457
+ };
458
+ const baseState = {
459
+ id: (0, uuid_1.v4)(),
460
+ messages: [],
461
+ data: {},
462
+ currentStep: this.entryPoint ?? '',
463
+ history: [],
464
+ createdAt: now,
465
+ updatedAt: now,
466
+ metadata,
467
+ ...options?.initialState,
468
+ };
469
+ return baseState;
470
+ }
471
+ async getNodeFromCheckpoint(checkpointId) {
472
+ if (!this.checkpointer) {
473
+ throw new Error('Checkpointer not configured');
474
+ }
475
+ const checkpoint = await this.checkpointer.load(checkpointId);
476
+ if (!checkpoint) {
477
+ throw new Error(`Checkpoint "${checkpointId}" not found`);
478
+ }
479
+ return checkpoint.nodeName;
480
+ }
481
+ async executeNodeWithRetry(node, state, executionId, iterationCount) {
482
+ const retryConfig = { ...this.config.retry, ...node.config.retry };
483
+ let lastError = null;
484
+ let retryCount = 0;
485
+ const context = {
486
+ node: node,
487
+ graph: this.getConfig(),
488
+ executionId,
489
+ iterationCount,
490
+ services: {
491
+ logger: this.logger,
492
+ checkpointer: this._checkpointer,
493
+ toolRegistry: this.services.toolRegistry,
494
+ llmProvider: this.services.llmProvider,
495
+ },
496
+ };
497
+ while (retryCount <= retryConfig.maxRetries) {
498
+ try {
499
+ // Execute pre-hooks
500
+ let currentState = state;
501
+ if (node.preHooks) {
502
+ for (const hook of node.preHooks) {
503
+ currentState = await hook(currentState, context);
504
+ }
505
+ }
506
+ // Execute node
507
+ const startTime = Date.now();
508
+ let result = await node.execute(currentState, context);
509
+ const duration = Date.now() - startTime;
510
+ // Execute post-hooks
511
+ if (node.postHooks) {
512
+ for (const hook of node.postHooks) {
513
+ result = {
514
+ ...result,
515
+ state: await hook(result.state, context),
516
+ };
517
+ }
518
+ }
519
+ // Validate output if schema provided
520
+ if (node.outputSchema) {
521
+ node.outputSchema.parse(result.state);
522
+ }
523
+ return {
524
+ ...result,
525
+ metadata: {
526
+ ...result.metadata,
527
+ duration,
528
+ retryCount,
529
+ },
530
+ };
531
+ }
532
+ catch (error) {
533
+ lastError = error instanceof Error ? error : new Error(String(error));
534
+ const errorCode = error.code ?? 'UNKNOWN';
535
+ if (!retryConfig.retryableErrors.includes(errorCode) ||
536
+ retryCount >= retryConfig.maxRetries) {
537
+ throw error;
538
+ }
539
+ const delay = Math.min(retryConfig.initialDelay *
540
+ Math.pow(retryConfig.backoffMultiplier, retryCount), retryConfig.maxDelay);
541
+ this.logger.warn(`Retrying node ${node.name} after ${delay}ms`, {
542
+ attempt: retryCount + 1,
543
+ error: lastError.message,
544
+ });
545
+ await this.sleep(delay);
546
+ retryCount++;
547
+ }
548
+ }
549
+ throw lastError ?? new Error('Unknown error during node execution');
550
+ }
551
+ updateStateWithHistory(newState, nodeName, previousState) {
552
+ const changes = this.computeStateChanges(previousState, newState);
553
+ const historyEntry = {
554
+ step: nodeName,
555
+ timestamp: new Date(),
556
+ snapshot: {
557
+ currentStep: previousState.currentStep,
558
+ data: { ...previousState.data },
559
+ },
560
+ changes,
561
+ };
562
+ return {
563
+ ...newState,
564
+ currentStep: nodeName,
565
+ history: [...previousState.history, historyEntry],
566
+ updatedAt: new Date(),
567
+ metadata: {
568
+ ...newState.metadata,
569
+ stepCount: previousState.metadata.stepCount + 1,
570
+ },
571
+ };
572
+ }
573
+ computeStateChanges(previous, current) {
574
+ const changes = [];
575
+ // Compare data fields
576
+ const allKeys = new Set([
577
+ ...Object.keys(previous.data),
578
+ ...Object.keys(current.data),
579
+ ]);
580
+ for (const key of allKeys) {
581
+ const prevValue = previous.data[key];
582
+ const currValue = current.data[key];
583
+ if (JSON.stringify(prevValue) !== JSON.stringify(currValue)) {
584
+ changes.push({
585
+ path: `data.${key}`,
586
+ previousValue: prevValue,
587
+ newValue: currValue,
588
+ });
589
+ }
590
+ }
591
+ // Compare messages
592
+ if (current.messages.length !== previous.messages.length) {
593
+ changes.push({
594
+ path: 'messages',
595
+ previousValue: previous.messages.length,
596
+ newValue: current.messages.length,
597
+ });
598
+ }
599
+ return changes;
600
+ }
601
+ async determineNextNode(currentNode, result, state) {
602
+ // If node explicitly specifies next
603
+ if (result.next) {
604
+ if (Array.isArray(result.next)) {
605
+ // For parallel execution, return the first one
606
+ // (proper parallel execution would need more complex handling)
607
+ return result.next[0];
608
+ }
609
+ return result.next;
610
+ }
611
+ // Evaluate edges
612
+ const edges = this.edges.get(currentNode) ?? [];
613
+ for (const edge of edges) {
614
+ const shouldFollow = await this.evaluateEdgeCondition(edge, result, state);
615
+ if (shouldFollow) {
616
+ return edge.to;
617
+ }
618
+ }
619
+ // No matching edge - end of workflow
620
+ return undefined;
621
+ }
622
+ async evaluateEdgeCondition(edge, result, state) {
623
+ // Direct edges always follow
624
+ if (edge.type === 'direct' || edge.type === 'parallel') {
625
+ return true;
626
+ }
627
+ // Conditional and loop edges need condition evaluation
628
+ if (!edge.condition) {
629
+ return true;
630
+ }
631
+ const { condition } = edge;
632
+ const context = {
633
+ edge,
634
+ sourceResult: result,
635
+ graph: this.getConfig(),
636
+ };
637
+ switch (condition.type) {
638
+ case 'equals':
639
+ return this.getFieldValue(state, condition.field) === condition.value;
640
+ case 'not_equals':
641
+ return this.getFieldValue(state, condition.field) !== condition.value;
642
+ case 'contains': {
643
+ const fieldValue = this.getFieldValue(state, condition.field);
644
+ return (Array.isArray(fieldValue) && fieldValue.includes(condition.value));
645
+ }
646
+ case 'greater_than':
647
+ return (this.getFieldValue(state, condition.field) >
648
+ condition.value);
649
+ case 'less_than':
650
+ return (this.getFieldValue(state, condition.field) <
651
+ condition.value);
652
+ case 'exists':
653
+ return this.getFieldValue(state, condition.field) !== undefined;
654
+ case 'not_exists':
655
+ return this.getFieldValue(state, condition.field) === undefined;
656
+ case 'custom':
657
+ if (condition.evaluate) {
658
+ return await condition.evaluate(state, context);
659
+ }
660
+ return false;
661
+ default:
662
+ return false;
663
+ }
664
+ }
665
+ getFieldValue(state, field) {
666
+ if (!field) {
667
+ return undefined;
668
+ }
669
+ const parts = field.split('.');
670
+ let current = state;
671
+ for (const part of parts) {
672
+ if (current === null || current === undefined) {
673
+ return undefined;
674
+ }
675
+ current = current[part];
676
+ }
677
+ return current;
678
+ }
679
+ async createCheckpoint(state, nodeName, executionId, stepNumber) {
680
+ const checkpoint = {
681
+ id: (0, uuid_1.v4)(),
682
+ executionId,
683
+ stepNumber,
684
+ nodeName,
685
+ state,
686
+ timestamp: new Date(),
687
+ };
688
+ if (this.checkpointer) {
689
+ await this.checkpointer.save(checkpoint);
690
+ }
691
+ return checkpoint;
692
+ }
693
+ createError(code, message, node) {
694
+ return {
695
+ code,
696
+ message,
697
+ node,
698
+ recoverable: ['TIMEOUT', 'RATE_LIMIT', 'NETWORK_ERROR'].includes(code),
699
+ recoveryHints: this.getRecoveryHints(code),
700
+ };
701
+ }
702
+ normalizeError(error, node) {
703
+ if (this.isWorkflowError(error)) {
704
+ return error;
705
+ }
706
+ const err = error instanceof Error ? error : new Error(String(error));
707
+ return {
708
+ code: error.code ?? 'EXECUTION_ERROR',
709
+ message: err.message,
710
+ stack: err.stack,
711
+ node,
712
+ recoverable: false,
713
+ };
714
+ }
715
+ isWorkflowError(error) {
716
+ return (typeof error === 'object' &&
717
+ error !== null &&
718
+ 'code' in error &&
719
+ 'message' in error &&
720
+ 'recoverable' in error);
721
+ }
722
+ getRecoveryHints(code) {
723
+ const hints = {
724
+ TIMEOUT: [
725
+ 'Increase timeout configuration',
726
+ 'Check network connectivity',
727
+ 'Reduce payload size',
728
+ ],
729
+ RATE_LIMIT: [
730
+ 'Add delay between requests',
731
+ 'Reduce concurrency',
732
+ 'Contact API provider',
733
+ ],
734
+ NETWORK_ERROR: [
735
+ 'Check network connectivity',
736
+ 'Verify endpoint availability',
737
+ 'Check firewall settings',
738
+ ],
739
+ MAX_ITERATIONS: [
740
+ 'Review loop conditions',
741
+ 'Increase maxIterations config',
742
+ 'Check for infinite loops',
743
+ ],
744
+ ABORTED: [
745
+ 'Execution was cancelled by user',
746
+ 'Resume from checkpoint if needed',
747
+ ],
748
+ };
749
+ return hints[code] ?? [];
750
+ }
751
+ sleep(ms) {
752
+ return new Promise(resolve => setTimeout(resolve, ms));
753
+ }
754
+ }
755
+ exports.StateGraph = StateGraph;
756
+ //# sourceMappingURL=state-graph.js.map