graphai 0.6.3 → 0.6.5

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.
package/lib/bundle.esm.js CHANGED
@@ -1,7 +1,14 @@
1
1
  const sleep = async (milliseconds) => {
2
2
  return await new Promise((resolve) => setTimeout(resolve, milliseconds));
3
3
  };
4
- const parseNodeName = (inputNodeId) => {
4
+ const parseNodeName = (inputNodeId, isSelfNode = false) => {
5
+ if (isSelfNode) {
6
+ if (typeof inputNodeId === "string" && inputNodeId[0] === ".") {
7
+ const parts = inputNodeId.split(".");
8
+ return { nodeId: "self", propIds: parts.slice(1) };
9
+ }
10
+ return { value: inputNodeId };
11
+ }
5
12
  if (typeof inputNodeId === "string") {
6
13
  const regex = /^:(.*)$/;
7
14
  const match = inputNodeId.match(regex);
@@ -209,6 +216,202 @@ class TransactionLog {
209
216
  }
210
217
  }
211
218
 
219
+ const propFunctionRegex = /^[a-zA-Z]+\([^)]*\)$/;
220
+ const propArrayFunction = (result, propId) => {
221
+ if (Array.isArray(result)) {
222
+ if (propId === "length()") {
223
+ return result.length;
224
+ }
225
+ if (propId === "flat()") {
226
+ return result.flat();
227
+ }
228
+ if (propId === "toJSON()") {
229
+ return JSON.stringify(result);
230
+ }
231
+ if (propId === "isEmpty()") {
232
+ return result.length === 0;
233
+ }
234
+ // array join
235
+ const matchJoin = propId.match(/^join\(([,-\s]?)\)$/);
236
+ if (matchJoin && Array.isArray(matchJoin)) {
237
+ return result.join(matchJoin[1] ?? "");
238
+ }
239
+ }
240
+ return undefined;
241
+ };
242
+ const propObjectFunction = (result, propId) => {
243
+ if (isObject(result)) {
244
+ if (propId === "keys()") {
245
+ return Object.keys(result);
246
+ }
247
+ if (propId === "values()") {
248
+ return Object.values(result);
249
+ }
250
+ if (propId === "toJSON()") {
251
+ return JSON.stringify(result);
252
+ }
253
+ }
254
+ return undefined;
255
+ };
256
+ const propStringFunction = (result, propId) => {
257
+ if (typeof result === "string") {
258
+ if (propId === "codeBlock()") {
259
+ const match = ("\n" + result).match(/\n```[a-zA-z]*([\s\S]*?)\n```/);
260
+ if (match) {
261
+ return match[1];
262
+ }
263
+ }
264
+ if (propId === "jsonParse()") {
265
+ return JSON.parse(result);
266
+ }
267
+ if (propId === "toNumber()") {
268
+ const ret = Number(result);
269
+ if (!isNaN(ret)) {
270
+ return ret;
271
+ }
272
+ }
273
+ if (propId === "trim()") {
274
+ return result.trim();
275
+ }
276
+ if (propId === "toLowerCase()") {
277
+ return result.toLowerCase();
278
+ }
279
+ if (propId === "toUpperCase()") {
280
+ return result.toUpperCase();
281
+ }
282
+ // split()
283
+ }
284
+ return undefined;
285
+ };
286
+ const propNumberFunction = (result, propId) => {
287
+ if (result !== undefined && Number.isFinite(result)) {
288
+ if (propId === "toString()") {
289
+ return String(result);
290
+ }
291
+ const regex = /^add\((-?\d+)\)$/;
292
+ const match = propId.match(regex);
293
+ if (match) {
294
+ return Number(result) + Number(match[1]);
295
+ }
296
+ }
297
+ return undefined;
298
+ };
299
+ const propBooleanFunction = (result, propId) => {
300
+ if (typeof result === "boolean") {
301
+ if (propId === "not()") {
302
+ return !result;
303
+ }
304
+ }
305
+ return undefined;
306
+ };
307
+ const propFunctions = [propArrayFunction, propObjectFunction, propStringFunction, propNumberFunction, propBooleanFunction];
308
+
309
+ const getNestedData = (result, propId, propFunctions) => {
310
+ const match = propId.match(propFunctionRegex);
311
+ if (match) {
312
+ for (const propFunction of propFunctions) {
313
+ const ret = propFunction(result, propId);
314
+ if (!isNull(ret)) {
315
+ return ret;
316
+ }
317
+ }
318
+ }
319
+ // for array.
320
+ if (Array.isArray(result)) {
321
+ // $0, $1. array value.
322
+ const regex = /^\$(\d+)$/;
323
+ const match = propId.match(regex);
324
+ if (match) {
325
+ const index = parseInt(match[1], 10);
326
+ return result[index];
327
+ }
328
+ if (propId === "$last") {
329
+ return result[result.length - 1];
330
+ }
331
+ }
332
+ else if (isObject(result)) {
333
+ if (propId in result) {
334
+ return result[propId];
335
+ }
336
+ }
337
+ return undefined;
338
+ };
339
+ const innerGetDataFromSource = (result, propIds, propFunctions) => {
340
+ if (!isNull(result) && propIds && propIds.length > 0) {
341
+ const propId = propIds[0];
342
+ const ret = getNestedData(result, propId, propFunctions);
343
+ if (ret === undefined) {
344
+ console.error(`prop: ${propIds.join(".")} is not hit`);
345
+ }
346
+ if (propIds.length > 1) {
347
+ return innerGetDataFromSource(ret, propIds.slice(1), propFunctions);
348
+ }
349
+ return ret;
350
+ }
351
+ return result;
352
+ };
353
+ const getDataFromSource = (result, source, propFunctions = []) => {
354
+ if (!source.nodeId) {
355
+ return source.value;
356
+ }
357
+ return innerGetDataFromSource(result, source.propIds, propFunctions);
358
+ };
359
+
360
+ const resultsOfInner = (input, nodes, propFunctions, isSelfNode = false) => {
361
+ if (Array.isArray(input)) {
362
+ return input.map((inp) => resultsOfInner(inp, nodes, propFunctions, isSelfNode));
363
+ }
364
+ if (isNamedInputs(input)) {
365
+ return resultsOf(input, nodes, propFunctions, isSelfNode);
366
+ }
367
+ if (typeof input === "string") {
368
+ const templateMatch = [...input.matchAll(/\${(:[^}]+)}/g)].map((m) => m[1]);
369
+ if (templateMatch.length > 0) {
370
+ const results = resultsOfInner(templateMatch, nodes, propFunctions, isSelfNode);
371
+ return Array.from(templateMatch.keys()).reduce((tmp, key) => {
372
+ return tmp.replaceAll("${" + templateMatch[key] + "}", results[key]);
373
+ }, input);
374
+ }
375
+ }
376
+ return resultOf(parseNodeName(input, isSelfNode), nodes, propFunctions);
377
+ };
378
+ const resultsOf = (inputs, nodes, propFunctions, isSelfNode = false) => {
379
+ return Object.keys(inputs).reduce((tmp, key) => {
380
+ const input = inputs[key];
381
+ tmp[key] = isNamedInputs(input) ? resultsOf(input, nodes, propFunctions, isSelfNode) : resultsOfInner(input, nodes, propFunctions, isSelfNode);
382
+ return tmp;
383
+ }, {});
384
+ };
385
+ const resultOf = (source, nodes, propFunctions) => {
386
+ const { result } = source.nodeId ? nodes[source.nodeId] : { result: undefined };
387
+ return getDataFromSource(result, source, propFunctions);
388
+ };
389
+ // clean up object for anyInput
390
+ const cleanResultInner = (results) => {
391
+ if (Array.isArray(results)) {
392
+ return results.map((result) => cleanResultInner(result)).filter((result) => !isNull(result));
393
+ }
394
+ if (isObject(results)) {
395
+ return Object.keys(results).reduce((tmp, key) => {
396
+ const value = cleanResultInner(results[key]);
397
+ if (!isNull(value)) {
398
+ tmp[key] = value;
399
+ }
400
+ return tmp;
401
+ }, {});
402
+ }
403
+ return results;
404
+ };
405
+ const cleanResult = (results) => {
406
+ return Object.keys(results).reduce((tmp, key) => {
407
+ const value = cleanResultInner(results[key]);
408
+ if (!isNull(value)) {
409
+ tmp[key] = value;
410
+ }
411
+ return tmp;
412
+ }, {});
413
+ };
414
+
212
415
  class Node {
213
416
  constructor(nodeId, graph) {
214
417
  this.waitlist = new Set(); // List of nodes which need data from this node.
@@ -234,7 +437,7 @@ class Node {
234
437
  });
235
438
  }
236
439
  afterConsoleLog(result) {
237
- if (this.console.after === true) {
440
+ if (this.console === true || this.console.after === true) {
238
441
  console.log(typeof result === "string" ? result : JSON.stringify(result, null, 2));
239
442
  }
240
443
  else if (this.console.after) {
@@ -247,6 +450,7 @@ class ComputedNode extends Node {
247
450
  super(nodeId, graph);
248
451
  this.retryCount = 0;
249
452
  this.dataSources = []; // no longer needed. This is for transaction log.
453
+ this.isSkip = false;
250
454
  this.isStaticNode = false;
251
455
  this.isComputedNode = true;
252
456
  this.graphId = graphId;
@@ -260,7 +464,8 @@ class ComputedNode extends Node {
260
464
  this.priority = data.priority ?? 0;
261
465
  this.anyInput = data.anyInput ?? false;
262
466
  this.inputs = data.inputs;
263
- this.dataSources = data.inputs ? inputs2dataSources(data.inputs).flat(10) : [];
467
+ this.output = data.output;
468
+ this.dataSources = [...(data.inputs ? inputs2dataSources(data.inputs).flat(10) : []), ...(data.params ? inputs2dataSources(data.params).flat(10) : [])];
264
469
  if (data.inputs && Array.isArray(data.inputs)) {
265
470
  throw new Error(`array inputs have been deprecated. nodeId: ${nodeId}: see https://github.com/receptron/graphai/blob/main/docs/NamedInputs.md`);
266
471
  }
@@ -285,15 +490,10 @@ class ComputedNode extends Node {
285
490
  if (data.unless) {
286
491
  this.unlessSource = this.addPendingNode(data.unless);
287
492
  }
288
- this.dynamicParams = Object.keys(this.params).reduce((tmp, key) => {
289
- const dataSource = parseNodeName(this.params[key]);
290
- if (dataSource.nodeId) {
291
- assert(!this.anyInput, "Dynamic params are not supported with anyInput");
292
- tmp[key] = dataSource;
293
- this.pendings.add(dataSource.nodeId);
294
- }
295
- return tmp;
296
- }, {});
493
+ if (data.defaultValue) {
494
+ this.defaultValue = data.defaultValue;
495
+ }
496
+ this.isSkip = false;
297
497
  this.log.initForComputedNode(this, graph);
298
498
  }
299
499
  getAgentId() {
@@ -309,8 +509,9 @@ class ComputedNode extends Node {
309
509
  if (this.state !== NodeState.Waiting || this.pendings.size !== 0) {
310
510
  return false;
311
511
  }
312
- if ((this.ifSource && !isLogicallyTrue(this.graph.resultOf(this.ifSource))) ||
313
- (this.unlessSource && isLogicallyTrue(this.graph.resultOf(this.unlessSource)))) {
512
+ this.isSkip = !!((this.ifSource && !isLogicallyTrue(this.graph.resultOf(this.ifSource))) ||
513
+ (this.unlessSource && isLogicallyTrue(this.graph.resultOf(this.unlessSource))));
514
+ if (this.isSkip && this.defaultValue === undefined) {
314
515
  this.state = NodeState.Skipped;
315
516
  this.log.onSkipped(this, this.graph);
316
517
  return false;
@@ -404,6 +605,10 @@ class ComputedNode extends Node {
404
605
  // then it removes itself from the "running node" list of the graph.
405
606
  // Notice that setting the result of this node may make other nodes ready to run.
406
607
  async execute() {
608
+ if (this.isSkip) {
609
+ this.afterExecute(this.defaultValue, []);
610
+ return;
611
+ }
407
612
  const previousResults = this.graph.resultsOf(this.inputs, this.anyInput);
408
613
  const transactionId = Date.now();
409
614
  this.prepareExecute(transactionId, Object.values(previousResults));
@@ -420,16 +625,7 @@ class ComputedNode extends Node {
420
625
  // if this is a nested agent or not.
421
626
  if (this.nestedGraph) {
422
627
  this.graph.taskManager.prepareForNesting();
423
- // context.taskManager = this.graph.taskManager;
424
628
  context.onLogCallback = this.graph.onLogCallback;
425
- /*
426
- if ("nodes" in this.nestedGraph) {
427
- context.graphData = this.nestedGraph;
428
- } else {
429
- context.graphData = this.graph.resultOf(this.nestedGraph) as GraphData; // HACK: compiler work-around
430
- }
431
- */
432
- // context.agents = this.graph.agentFunctionInfoDictionary;
433
629
  context.forNestedGraph = {
434
630
  graphData: "nodes" in this.nestedGraph ? this.nestedGraph : this.graph.resultOf(this.nestedGraph), // HACK: compiler work-around
435
631
  agents: this.graph.agentFunctionInfoDictionary,
@@ -455,16 +651,23 @@ class ComputedNode extends Node {
455
651
  console.log(`-- transactionId mismatch with ${this.nodeId} (probably timeout)`);
456
652
  return;
457
653
  }
458
- this.state = NodeState.Completed;
459
- this.result = this.getResult(result);
460
- this.log.onComplete(this, this.graph, localLog);
461
- this.onSetResult();
462
- this.graph.onExecutionComplete(this);
654
+ // after process
655
+ this.afterExecute(result, localLog);
463
656
  }
464
657
  catch (error) {
465
658
  this.errorProcess(error, transactionId, previousResults);
466
659
  }
467
660
  }
661
+ afterExecute(result, localLog) {
662
+ this.state = NodeState.Completed;
663
+ this.result = this.getResult(result);
664
+ if (this.output) {
665
+ this.result = resultsOf(this.output, { self: this }, this.graph.propFunctions, true);
666
+ }
667
+ this.log.onComplete(this, this.graph, localLog);
668
+ this.onSetResult();
669
+ this.graph.onExecutionComplete(this);
670
+ }
468
671
  // This private method (called only by execute()) prepares the ComputedNode object
469
672
  // for execution, and create a new transaction to record it.
470
673
  prepareExecute(transactionId, inputs) {
@@ -494,24 +697,9 @@ class ComputedNode extends Node {
494
697
  this.retry(NodeState.Failed, Error("Unknown"));
495
698
  }
496
699
  }
497
- getParams() {
498
- return Object.keys(this.dynamicParams).reduce((tmp, key) => {
499
- const result = this.graph.resultOf(this.dynamicParams[key]);
500
- tmp[key] = result;
501
- return tmp;
502
- }, { ...this.params });
503
- }
504
- /*
505
- private getInputs(previousResults: Record<string, ResultData | undefined>) {
506
- if (Array.isArray(this.inputs)) {
507
- return (this.inputs ?? []).map((key) => previousResults[String(key)]).filter((a) => !this.anyInput || a);
508
- }
509
- return [];
510
- }
511
- */
512
700
  getContext(previousResults, localLog) {
513
701
  const context = {
514
- params: this.getParams(),
702
+ params: this.graph.resultsOf(this.params),
515
703
  namedInputs: previousResults,
516
704
  inputSchema: this.agentFunction ? undefined : this.graph.getAgentFunctionInfo(this.agentId)?.inputs,
517
705
  debugInfo: this.getDebugInfo(),
@@ -545,7 +733,7 @@ class ComputedNode extends Node {
545
733
  };
546
734
  }
547
735
  beforeConsoleLog(context) {
548
- if (this.console.before === true) {
736
+ if (this.console === true || this.console.before === true) {
549
737
  console.log(JSON.stringify(context.namedInputs, null, 2));
550
738
  }
551
739
  else if (this.console.before) {
@@ -574,212 +762,10 @@ class StaticNode extends Node {
574
762
  }
575
763
  }
576
764
 
577
- const propFunctionRegex = /^[a-zA-Z]+\([^)]*\)$/;
578
- const propArrayFunction = (result, propId) => {
579
- if (Array.isArray(result)) {
580
- if (propId === "length()") {
581
- return result.length;
582
- }
583
- if (propId === "flat()") {
584
- return result.flat();
585
- }
586
- if (propId === "toJSON()") {
587
- return JSON.stringify(result);
588
- }
589
- if (propId === "isEmpty()") {
590
- return result.length === 0;
591
- }
592
- // array join
593
- const matchJoin = propId.match(/^join\(([,-]?)\)$/);
594
- if (matchJoin && Array.isArray(matchJoin)) {
595
- return result.join(matchJoin[1] ?? "");
596
- }
597
- }
598
- return undefined;
599
- };
600
- const propObjectFunction = (result, propId) => {
601
- if (isObject(result)) {
602
- if (propId === "keys()") {
603
- return Object.keys(result);
604
- }
605
- if (propId === "values()") {
606
- return Object.values(result);
607
- }
608
- if (propId === "toJSON()") {
609
- return JSON.stringify(result);
610
- }
611
- }
612
- return undefined;
613
- };
614
- const propStringFunction = (result, propId) => {
615
- if (typeof result === "string") {
616
- if (propId === "codeBlock()") {
617
- const match = ("\n" + result).match(/\n```[a-zA-z]*([\s\S]*?)\n```/);
618
- if (match) {
619
- return match[1];
620
- }
621
- }
622
- if (propId === "jsonParse()") {
623
- return JSON.parse(result);
624
- }
625
- if (propId === "toNumber()") {
626
- const ret = Number(result);
627
- if (!isNaN(ret)) {
628
- return ret;
629
- }
630
- }
631
- if (propId === "trim()") {
632
- return result.trim();
633
- }
634
- if (propId === "toLowerCase()") {
635
- return result.toLowerCase();
636
- }
637
- if (propId === "toUpperCase()") {
638
- return result.toUpperCase();
639
- }
640
- // split()
641
- }
642
- return undefined;
643
- };
644
- const propNumberFunction = (result, propId) => {
645
- if (result !== undefined && Number.isFinite(result)) {
646
- if (propId === "toString()") {
647
- return String(result);
648
- }
649
- const regex = /^add\((-?\d+)\)$/;
650
- const match = propId.match(regex);
651
- if (match) {
652
- return Number(result) + Number(match[1]);
653
- }
654
- }
655
- return undefined;
656
- };
657
- const propBooleanFunction = (result, propId) => {
658
- if (typeof result === "boolean") {
659
- if (propId === "not()") {
660
- return !result;
661
- }
662
- }
663
- return undefined;
664
- };
665
- const propFunctions = [propArrayFunction, propObjectFunction, propStringFunction, propNumberFunction, propBooleanFunction];
666
-
667
- const getNestedData = (result, propId, propFunctions) => {
668
- const match = propId.match(propFunctionRegex);
669
- if (match) {
670
- for (const propFunction of propFunctions) {
671
- const ret = propFunction(result, propId);
672
- if (!isNull(ret)) {
673
- return ret;
674
- }
675
- }
676
- }
677
- // for array.
678
- if (Array.isArray(result)) {
679
- // $0, $1. array value.
680
- const regex = /^\$(\d+)$/;
681
- const match = propId.match(regex);
682
- if (match) {
683
- const index = parseInt(match[1], 10);
684
- return result[index];
685
- }
686
- if (propId === "$last") {
687
- return result[result.length - 1];
688
- }
689
- }
690
- else if (isObject(result)) {
691
- if (propId in result) {
692
- return result[propId];
693
- }
694
- }
695
- return undefined;
696
- };
697
- const innerGetDataFromSource = (result, propIds, propFunctions) => {
698
- if (!isNull(result) && propIds && propIds.length > 0) {
699
- const propId = propIds[0];
700
- const ret = getNestedData(result, propId, propFunctions);
701
- if (ret === undefined) {
702
- console.error(`prop: ${propIds.join(".")} is not hit`);
703
- }
704
- if (propIds.length > 1) {
705
- return innerGetDataFromSource(ret, propIds.slice(1), propFunctions);
706
- }
707
- return ret;
708
- }
709
- return result;
710
- };
711
- const getDataFromSource = (result, source, propFunctions = []) => {
712
- if (!source.nodeId) {
713
- return source.value;
714
- }
715
- return innerGetDataFromSource(result, source.propIds, propFunctions);
716
- };
717
-
718
- const resultsOfInner = (input, nodes, propFunctions) => {
719
- if (Array.isArray(input)) {
720
- return input.map((inp) => resultsOfInner(inp, nodes, propFunctions));
721
- }
722
- if (isNamedInputs(input)) {
723
- return resultsOf(input, nodes, propFunctions);
724
- }
725
- if (typeof input === "string") {
726
- const templateMatch = [...input.matchAll(/\${(:[^}]+)}/g)].map((m) => m[1]);
727
- if (templateMatch.length > 0) {
728
- const results = resultsOfInner(templateMatch, nodes, propFunctions);
729
- return Array.from(templateMatch.keys()).reduce((tmp, key) => {
730
- return tmp.replaceAll("${" + templateMatch[key] + "}", results[key]);
731
- }, input);
732
- }
733
- }
734
- return resultOf(parseNodeName(input), nodes, propFunctions);
735
- };
736
- const resultsOf = (inputs, nodes, propFunctions) => {
737
- // for inputs. TODO remove if array input is not supported
738
- if (Array.isArray(inputs)) {
739
- return inputs.reduce((tmp, key) => {
740
- tmp[key] = resultsOfInner(key, nodes, propFunctions);
741
- return tmp;
742
- }, {});
743
- }
744
- return Object.keys(inputs).reduce((tmp, key) => {
745
- const input = inputs[key];
746
- tmp[key] = isNamedInputs(input) ? resultsOf(input, nodes, propFunctions) : resultsOfInner(input, nodes, propFunctions);
747
- return tmp;
748
- }, {});
749
- };
750
- const resultOf = (source, nodes, propFunctions) => {
751
- const { result } = source.nodeId ? nodes[source.nodeId] : { result: undefined };
752
- return getDataFromSource(result, source, propFunctions);
753
- };
754
- // clean up object for anyInput
755
- const cleanResultInner = (results) => {
756
- if (Array.isArray(results)) {
757
- return results.map((result) => cleanResultInner(result)).filter((result) => !isNull(result));
758
- }
759
- if (isObject(results)) {
760
- return Object.keys(results).reduce((tmp, key) => {
761
- const value = cleanResultInner(results[key]);
762
- if (!isNull(value)) {
763
- tmp[key] = value;
764
- }
765
- return tmp;
766
- }, {});
767
- }
768
- return results;
769
- };
770
- const cleanResult = (results) => {
771
- return Object.keys(results).reduce((tmp, key) => {
772
- const value = cleanResultInner(results[key]);
773
- if (!isNull(value)) {
774
- tmp[key] = value;
775
- }
776
- return tmp;
777
- }, {});
778
- };
779
-
780
765
  const graphDataAttributeKeys = ["nodes", "concurrency", "agentId", "loop", "verbose", "version"];
781
766
  const computedNodeAttributeKeys = [
782
767
  "inputs",
768
+ "output",
783
769
  "anyInput",
784
770
  "params",
785
771
  "retry",
@@ -791,6 +777,7 @@ const computedNodeAttributeKeys = [
791
777
  "priority",
792
778
  "if",
793
779
  "unless",
780
+ "defaultValue",
794
781
  "filterParams",
795
782
  "console",
796
783
  "passThrough",
@@ -846,9 +833,9 @@ const nodeValidator = (nodeData) => {
846
833
  if (nodeData.agent && nodeData.value) {
847
834
  throw new ValidationError("Cannot set both agent and value");
848
835
  }
849
- if (!("agent" in nodeData) && !("value" in nodeData)) {
850
- throw new ValidationError("Either agent or value is required");
851
- }
836
+ // if (!("agent" in nodeData) && !("value" in nodeData)) {
837
+ // throw new ValidationError("Either agent or value is required");
838
+ // }
852
839
  return true;
853
840
  };
854
841
 
@@ -967,7 +954,7 @@ const validateGraphData = (data, agentIds) => {
967
954
  const graphAgentIds = new Set();
968
955
  Object.keys(data.nodes).forEach((nodeId) => {
969
956
  const node = data.nodes[nodeId];
970
- const isStaticNode = "value" in node;
957
+ const isStaticNode = !("agent" in node);
971
958
  nodeValidator(node);
972
959
  const agentId = isStaticNode ? "" : node.agent;
973
960
  isStaticNode && staticNodeValidator(node) && staticNodeIds.push(nodeId);
@@ -1054,15 +1041,13 @@ class GraphAI {
1054
1041
  createNodes(data) {
1055
1042
  const nodes = Object.keys(data.nodes).reduce((_nodes, nodeId) => {
1056
1043
  const nodeData = data.nodes[nodeId];
1057
- if ("value" in nodeData) {
1058
- _nodes[nodeId] = new StaticNode(nodeId, nodeData, this);
1059
- }
1060
- else if ("agent" in nodeData) {
1044
+ if ("agent" in nodeData) {
1061
1045
  _nodes[nodeId] = new ComputedNode(this.graphId, nodeId, nodeData, this);
1062
1046
  }
1063
1047
  else {
1064
- throw new Error("Unknown node type (neither value nor agent): " + nodeId);
1048
+ _nodes[nodeId] = new StaticNode(nodeId, nodeData, this);
1065
1049
  }
1050
+ // throw new Error("Unknown node type (neither value nor agent): " + nodeId);
1066
1051
  return _nodes;
1067
1052
  }, {});
1068
1053
  // Generate the waitlist for each node.
@@ -1231,6 +1216,11 @@ class GraphAI {
1231
1216
  }
1232
1217
  // Public API
1233
1218
  async run(all = false) {
1219
+ if (Object.values(this.nodes)
1220
+ .filter((node) => node.isStaticNode)
1221
+ .some((node) => node.result === undefined && node.update === undefined)) {
1222
+ throw new Error("Static node must have value. Set value or injectValue or set update");
1223
+ }
1234
1224
  if (this.isRunning()) {
1235
1225
  throw new Error("This GraphUI instance is already running");
1236
1226
  }