graphai 0.5.15 → 0.5.17

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.
@@ -0,0 +1,1310 @@
1
+ const sleep = async (milliseconds) => {
2
+ return await new Promise((resolve) => setTimeout(resolve, milliseconds));
3
+ };
4
+ const parseNodeName = (inputNodeId) => {
5
+ if (typeof inputNodeId === "string") {
6
+ const regex = /^:(.*)$/;
7
+ const match = inputNodeId.match(regex);
8
+ if (!match) {
9
+ return { value: inputNodeId }; // string literal
10
+ }
11
+ const parts = match[1].split(".");
12
+ if (parts.length == 1) {
13
+ return { nodeId: parts[0] };
14
+ }
15
+ return { nodeId: parts[0], propIds: parts.slice(1) };
16
+ }
17
+ return { value: inputNodeId }; // non-string literal
18
+ };
19
+ function assert(condition, message, isWarn = false) {
20
+ if (!condition) {
21
+ if (!isWarn) {
22
+ throw new Error(message);
23
+ }
24
+ console.warn("warn: " + message);
25
+ }
26
+ }
27
+ const isObject = (x) => {
28
+ return x !== null && typeof x === "object";
29
+ };
30
+ const isNull = (data) => {
31
+ return data === null || data === undefined;
32
+ };
33
+ const strIntentionalError = "Intentional Error for Debugging";
34
+ const defaultAgentInfo = {
35
+ name: "defaultAgentInfo",
36
+ samples: [
37
+ {
38
+ inputs: [],
39
+ params: {},
40
+ result: {},
41
+ },
42
+ ],
43
+ description: "",
44
+ category: [],
45
+ author: "",
46
+ repository: "",
47
+ license: "",
48
+ };
49
+ const agentInfoWrapper = (agent) => {
50
+ return {
51
+ agent,
52
+ mock: agent,
53
+ ...defaultAgentInfo,
54
+ };
55
+ };
56
+ const objectToKeyArray = (innerData) => {
57
+ const ret = [];
58
+ Object.keys(innerData).forEach((key) => {
59
+ ret.push([key]);
60
+ if (Object.keys(innerData[key]).length > 0) {
61
+ objectToKeyArray(innerData[key]).forEach((tmp) => {
62
+ ret.push([key, ...tmp]);
63
+ });
64
+ }
65
+ });
66
+ return ret;
67
+ };
68
+ const debugResultKey = (agentId, result) => {
69
+ return objectToKeyArray({ [agentId]: debugResultKeyInner(result) }).map((objectKeys) => {
70
+ return ":" + objectKeys.join(".");
71
+ });
72
+ };
73
+ const debugResultKeyInner = (result) => {
74
+ if (result === null || result === undefined) {
75
+ return {};
76
+ }
77
+ if (typeof result === "string") {
78
+ return {};
79
+ }
80
+ if (Array.isArray(result)) {
81
+ return Array.from(result.keys()).reduce((tmp, index) => {
82
+ tmp["$" + String(index)] = debugResultKeyInner(result[index]);
83
+ return tmp;
84
+ }, {});
85
+ }
86
+ return Object.keys(result).reduce((tmp, key) => {
87
+ tmp[key] = debugResultKeyInner(result[key]);
88
+ return tmp;
89
+ }, {});
90
+ };
91
+ const isLogicallyTrue = (value) => {
92
+ // Notice that empty aray is not true under GraphAI
93
+ if (Array.isArray(value) ? value.length === 0 : !value) {
94
+ return false;
95
+ }
96
+ return true;
97
+ };
98
+ const defaultTestContext = {
99
+ debugInfo: {
100
+ nodeId: "test",
101
+ retry: 0,
102
+ verbose: true,
103
+ },
104
+ params: {},
105
+ filterParams: {},
106
+ agents: {},
107
+ log: [],
108
+ };
109
+ const isNamedInputs = (namedInputs) => {
110
+ return isObject(namedInputs) && !Array.isArray(namedInputs) && Object.keys(namedInputs || {}).length > 0;
111
+ };
112
+
113
+ // for dataSource
114
+ const inputs2dataSources = (inputs) => {
115
+ if (Array.isArray(inputs)) {
116
+ return inputs.map((inp) => inputs2dataSources(inp)).flat();
117
+ }
118
+ if (isObject(inputs)) {
119
+ return Object.values(inputs)
120
+ .map((input) => inputs2dataSources(input))
121
+ .flat();
122
+ }
123
+ if (typeof inputs === "string") {
124
+ const templateMatch = [...inputs.matchAll(/\${(:[^}]+)}/g)].map((m) => m[1]);
125
+ if (templateMatch.length > 0) {
126
+ return inputs2dataSources(templateMatch);
127
+ }
128
+ }
129
+ return parseNodeName(inputs);
130
+ };
131
+ const dataSourceNodeIds = (sources) => {
132
+ return sources.filter((source) => source.nodeId).map((source) => source.nodeId);
133
+ };
134
+
135
+ var NodeState;
136
+ (function (NodeState) {
137
+ NodeState["Waiting"] = "waiting";
138
+ NodeState["Queued"] = "queued";
139
+ NodeState["Executing"] = "executing";
140
+ NodeState["ExecutingServer"] = "executing-server";
141
+ NodeState["Failed"] = "failed";
142
+ NodeState["TimedOut"] = "timed-out";
143
+ NodeState["Completed"] = "completed";
144
+ NodeState["Injected"] = "injected";
145
+ NodeState["Skipped"] = "skipped";
146
+ })(NodeState || (NodeState = {}));
147
+
148
+ class TransactionLog {
149
+ constructor(nodeId) {
150
+ this.nodeId = nodeId;
151
+ this.state = NodeState.Waiting;
152
+ }
153
+ initForComputedNode(node, graph) {
154
+ this.agentId = node.getAgentId();
155
+ this.params = node.params;
156
+ graph.appendLog(this);
157
+ }
158
+ onInjected(node, graph, injectFrom) {
159
+ const isUpdating = "endTime" in this;
160
+ this.result = node.result;
161
+ this.state = node.state;
162
+ this.endTime = Date.now();
163
+ this.injectFrom = injectFrom;
164
+ graph.setLoopLog(this);
165
+ // console.log(this)
166
+ if (isUpdating) {
167
+ graph.updateLog(this);
168
+ }
169
+ else {
170
+ graph.appendLog(this);
171
+ }
172
+ }
173
+ onComplete(node, graph, localLog) {
174
+ this.result = node.result;
175
+ this.resultKeys = debugResultKey(this.agentId || "", node.result);
176
+ this.state = node.state;
177
+ this.endTime = Date.now();
178
+ graph.setLoopLog(this);
179
+ if (localLog.length > 0) {
180
+ this.log = localLog;
181
+ }
182
+ graph.updateLog(this);
183
+ }
184
+ beforeExecute(node, graph, transactionId, inputs) {
185
+ this.state = node.state;
186
+ this.retryCount = node.retryCount > 0 ? node.retryCount : undefined;
187
+ this.startTime = transactionId;
188
+ this.inputs = dataSourceNodeIds(node.dataSources);
189
+ this.inputsData = inputs.length > 0 ? inputs : undefined;
190
+ graph.setLoopLog(this);
191
+ graph.appendLog(this);
192
+ }
193
+ beforeAddTask(node, graph) {
194
+ this.state = node.state;
195
+ graph.setLoopLog(this);
196
+ graph.appendLog(this);
197
+ }
198
+ onError(node, graph, errorMessage) {
199
+ this.state = node.state;
200
+ this.errorMessage = errorMessage;
201
+ this.endTime = Date.now();
202
+ graph.setLoopLog(this);
203
+ graph.updateLog(this);
204
+ }
205
+ onSkipped(node, graph) {
206
+ this.state = node.state;
207
+ graph.setLoopLog(this);
208
+ graph.updateLog(this);
209
+ }
210
+ }
211
+
212
+ class Node {
213
+ constructor(nodeId, graph) {
214
+ this.waitlist = new Set(); // List of nodes which need data from this node.
215
+ this.state = NodeState.Waiting;
216
+ this.result = undefined;
217
+ this.nodeId = nodeId;
218
+ this.graph = graph;
219
+ this.log = new TransactionLog(nodeId);
220
+ this.console = {};
221
+ }
222
+ asString() {
223
+ return `${this.nodeId}: ${this.state} ${[...this.waitlist]}`;
224
+ }
225
+ // This method is called either as the result of computation (computed node) or
226
+ // injection (static node).
227
+ onSetResult() {
228
+ this.waitlist.forEach((waitingNodeId) => {
229
+ const waitingNode = this.graph.nodes[waitingNodeId];
230
+ if (waitingNode.isComputedNode) {
231
+ waitingNode.removePending(this.nodeId);
232
+ this.graph.pushQueueIfReadyAndRunning(waitingNode);
233
+ }
234
+ });
235
+ }
236
+ afterConsoleLog(result) {
237
+ if (this.console.after === true) {
238
+ console.log(typeof result === "string" ? result : JSON.stringify(result, null, 2));
239
+ }
240
+ else if (this.console.after) {
241
+ console.log(this.console.after);
242
+ }
243
+ }
244
+ }
245
+ class ComputedNode extends Node {
246
+ constructor(graphId, nodeId, data, graph) {
247
+ super(nodeId, graph);
248
+ this.retryCount = 0;
249
+ this.dataSources = []; // no longer needed. This is for transaction log.
250
+ this.isNamedInputs = false;
251
+ this.isStaticNode = false;
252
+ this.isComputedNode = true;
253
+ this.graphId = graphId;
254
+ this.params = data.params ?? {};
255
+ this.console = data.console ?? {};
256
+ this.filterParams = data.filterParams ?? {};
257
+ this.passThrough = data.passThrough;
258
+ this.retryLimit = data.retry ?? graph.retryLimit ?? 0;
259
+ this.timeout = data.timeout;
260
+ this.isResult = data.isResult ?? false;
261
+ this.priority = data.priority ?? 0;
262
+ this.anyInput = data.anyInput ?? false;
263
+ this.inputs = data.inputs;
264
+ this.isNamedInputs = isObject(data.inputs) && !Array.isArray(data.inputs);
265
+ this.dataSources = data.inputs ? inputs2dataSources(data.inputs).flat(10) : [];
266
+ if (data.inputs && !this.isNamedInputs) {
267
+ console.warn(`array inputs have been deprecated. nodeId: ${nodeId}: see https://github.com/receptron/graphai/blob/main/docs/NamedInputs.md`);
268
+ }
269
+ this.pendings = new Set(dataSourceNodeIds(this.dataSources));
270
+ assert(["function", "string"].includes(typeof data.agent), "agent must be either string or function");
271
+ if (typeof data.agent === "string") {
272
+ this.agentId = data.agent;
273
+ }
274
+ else {
275
+ const agent = data.agent;
276
+ this.agentFunction = this.isNamedInputs ? async ({ namedInputs }) => agent(namedInputs) : async ({ inputs }) => agent(...inputs);
277
+ }
278
+ if (data.graph) {
279
+ this.nestedGraph = typeof data.graph === "string" ? this.addPendingNode(data.graph) : data.graph;
280
+ }
281
+ if (data.graphLoader && graph.graphLoader) {
282
+ this.nestedGraph = graph.graphLoader(data.graphLoader);
283
+ }
284
+ if (data.if) {
285
+ this.ifSource = this.addPendingNode(data.if);
286
+ }
287
+ if (data.unless) {
288
+ this.unlessSource = this.addPendingNode(data.unless);
289
+ }
290
+ this.dynamicParams = Object.keys(this.params).reduce((tmp, key) => {
291
+ const dataSource = parseNodeName(this.params[key]);
292
+ if (dataSource.nodeId) {
293
+ assert(!this.anyInput, "Dynamic params are not supported with anyInput");
294
+ tmp[key] = dataSource;
295
+ this.pendings.add(dataSource.nodeId);
296
+ }
297
+ return tmp;
298
+ }, {});
299
+ this.log.initForComputedNode(this, graph);
300
+ }
301
+ getAgentId() {
302
+ return this.agentId ?? "__custom__function"; // only for display purpose in the log.
303
+ }
304
+ addPendingNode(nodeId) {
305
+ const source = parseNodeName(nodeId);
306
+ assert(!!source.nodeId, `Invalid data source ${nodeId}`);
307
+ this.pendings.add(source.nodeId);
308
+ return source;
309
+ }
310
+ isReadyNode() {
311
+ if (this.state !== NodeState.Waiting || this.pendings.size !== 0) {
312
+ return false;
313
+ }
314
+ if ((this.ifSource && !isLogicallyTrue(this.graph.resultOf(this.ifSource))) ||
315
+ (this.unlessSource && isLogicallyTrue(this.graph.resultOf(this.unlessSource)))) {
316
+ this.state = NodeState.Skipped;
317
+ this.log.onSkipped(this, this.graph);
318
+ return false;
319
+ }
320
+ return true;
321
+ }
322
+ // This private method (only called while executing execute()) performs
323
+ // the "retry" if specified. The transaction log must be updated before
324
+ // callling this method.
325
+ retry(state, error) {
326
+ this.state = state; // this.execute() will update to NodeState.Executing
327
+ this.log.onError(this, this.graph, error.message);
328
+ if (this.retryCount < this.retryLimit) {
329
+ this.retryCount++;
330
+ this.execute();
331
+ }
332
+ else {
333
+ this.result = undefined;
334
+ this.error = error;
335
+ this.transactionId = undefined; // This is necessary for timeout case
336
+ this.graph.onExecutionComplete(this);
337
+ }
338
+ }
339
+ checkDataAvailability() {
340
+ return Object.values(this.graph.resultsOf(this.inputs))
341
+ .flat()
342
+ .some((result) => result !== undefined);
343
+ }
344
+ // This method is called right before the Graph add this node to the task manager.
345
+ beforeAddTask() {
346
+ this.state = NodeState.Queued;
347
+ this.log.beforeAddTask(this, this.graph);
348
+ }
349
+ // This method is called when the data became available on one of nodes,
350
+ // which this node needs data from.
351
+ removePending(nodeId) {
352
+ if (this.anyInput) {
353
+ if (this.checkDataAvailability()) {
354
+ this.pendings.clear();
355
+ }
356
+ }
357
+ else {
358
+ this.pendings.delete(nodeId);
359
+ }
360
+ }
361
+ isCurrentTransaction(transactionId) {
362
+ return this.transactionId === transactionId;
363
+ }
364
+ // This private method (called only fro execute) checks if the callback from
365
+ // the timer came before the completion of agent function call, record it
366
+ // and attempt to retry (if specified).
367
+ executeTimeout(transactionId) {
368
+ if (this.state === NodeState.Executing && this.isCurrentTransaction(transactionId)) {
369
+ console.warn(`-- timeout ${this.timeout} with ${this.nodeId}`);
370
+ this.retry(NodeState.TimedOut, Error("Timeout"));
371
+ }
372
+ }
373
+ // Check if we need to apply this filter to this node or not.
374
+ shouldApplyAgentFilter(agentFilter) {
375
+ if (agentFilter.agentIds && Array.isArray(agentFilter.agentIds) && agentFilter.agentIds.length > 0) {
376
+ if (this.agentId && agentFilter.agentIds.includes(this.agentId)) {
377
+ return true;
378
+ }
379
+ }
380
+ if (agentFilter.nodeIds && Array.isArray(agentFilter.nodeIds) && agentFilter.nodeIds.length > 0) {
381
+ if (agentFilter.nodeIds.includes(this.nodeId)) {
382
+ return true;
383
+ }
384
+ }
385
+ return !agentFilter.agentIds && !agentFilter.nodeIds;
386
+ }
387
+ agentFilterHandler(context, agentFunction) {
388
+ let index = 0;
389
+ const next = (innerContext) => {
390
+ const agentFilter = this.graph.agentFilters[index++];
391
+ if (agentFilter) {
392
+ if (this.shouldApplyAgentFilter(agentFilter)) {
393
+ if (agentFilter.filterParams) {
394
+ innerContext.filterParams = { ...agentFilter.filterParams, ...innerContext.filterParams };
395
+ }
396
+ return agentFilter.agent(innerContext, next);
397
+ }
398
+ return next(innerContext);
399
+ }
400
+ return agentFunction(innerContext);
401
+ };
402
+ return next(context);
403
+ }
404
+ // This method is called when this computed node became ready to run.
405
+ // It asynchronously calls the associated with agent function and set the result,
406
+ // then it removes itself from the "running node" list of the graph.
407
+ // Notice that setting the result of this node may make other nodes ready to run.
408
+ async execute() {
409
+ const previousResults = this.graph.resultsOf(this.inputs, this.anyInput);
410
+ const transactionId = Date.now();
411
+ this.prepareExecute(transactionId, Object.values(previousResults));
412
+ if (this.timeout && this.timeout > 0) {
413
+ setTimeout(() => {
414
+ this.executeTimeout(transactionId);
415
+ }, this.timeout);
416
+ }
417
+ try {
418
+ const agentFunction = this.agentFunction ?? this.graph.getAgentFunctionInfo(this.agentId).agent;
419
+ const localLog = [];
420
+ const context = this.getContext(previousResults, localLog);
421
+ // NOTE: We use the existence of graph object in the agent-specific params to determine
422
+ // if this is a nested agent or not.
423
+ if (this.nestedGraph) {
424
+ this.graph.taskManager.prepareForNesting();
425
+ context.taskManager = this.graph.taskManager;
426
+ context.onLogCallback = this.graph.onLogCallback;
427
+ if ("nodes" in this.nestedGraph) {
428
+ context.graphData = this.nestedGraph;
429
+ }
430
+ else {
431
+ context.graphData = this.graph.resultOf(this.nestedGraph); // HACK: compiler work-around
432
+ }
433
+ context.agents = this.graph.agentFunctionInfoDictionary;
434
+ }
435
+ this.beforeConsoleLog(context);
436
+ const result = await this.agentFilterHandler(context, agentFunction);
437
+ this.afterConsoleLog(result);
438
+ if (this.nestedGraph) {
439
+ this.graph.taskManager.restoreAfterNesting();
440
+ }
441
+ if (!this.isCurrentTransaction(transactionId)) {
442
+ // This condition happens when the agent function returns
443
+ // after the timeout (either retried or not).
444
+ console.log(`-- transactionId mismatch with ${this.nodeId} (probably timeout)`);
445
+ return;
446
+ }
447
+ this.state = NodeState.Completed;
448
+ this.result = this.getResult(result);
449
+ this.log.onComplete(this, this.graph, localLog);
450
+ this.onSetResult();
451
+ this.graph.onExecutionComplete(this);
452
+ }
453
+ catch (error) {
454
+ this.errorProcess(error, transactionId, previousResults);
455
+ }
456
+ }
457
+ // This private method (called only by execute()) prepares the ComputedNode object
458
+ // for execution, and create a new transaction to record it.
459
+ prepareExecute(transactionId, inputs) {
460
+ this.state = NodeState.Executing;
461
+ this.log.beforeExecute(this, this.graph, transactionId, inputs);
462
+ this.transactionId = transactionId;
463
+ }
464
+ // This private method (called only by execute) processes an error received from
465
+ // the agent function. It records the error in the transaction log and handles
466
+ // the retry if specified.
467
+ errorProcess(error, transactionId, namedInputs) {
468
+ if (error instanceof Error && error.message !== strIntentionalError) {
469
+ console.error(`<-- NodeId: ${this.nodeId}, Agent: ${this.agentId}`);
470
+ console.error({ namedInputs });
471
+ console.error(error);
472
+ console.error("-->");
473
+ }
474
+ if (!this.isCurrentTransaction(transactionId)) {
475
+ console.warn(`-- transactionId mismatch with ${this.nodeId} (not timeout)`);
476
+ return;
477
+ }
478
+ if (error instanceof Error) {
479
+ this.retry(NodeState.Failed, error);
480
+ }
481
+ else {
482
+ console.error(`-- NodeId: ${this.nodeId}: Unknown error was caught`);
483
+ this.retry(NodeState.Failed, Error("Unknown"));
484
+ }
485
+ }
486
+ getParams() {
487
+ return Object.keys(this.dynamicParams).reduce((tmp, key) => {
488
+ const result = this.graph.resultOf(this.dynamicParams[key]);
489
+ tmp[key] = result;
490
+ return tmp;
491
+ }, { ...this.params });
492
+ }
493
+ getInputs(previousResults) {
494
+ if (Array.isArray(this.inputs)) {
495
+ return (this.inputs ?? []).map((key) => previousResults[String(key)]).filter((a) => !this.anyInput || a);
496
+ }
497
+ return [];
498
+ }
499
+ getContext(previousResults, localLog) {
500
+ const context = {
501
+ params: this.getParams(),
502
+ inputs: this.getInputs(previousResults),
503
+ namedInputs: this.isNamedInputs ? previousResults : {},
504
+ inputSchema: this.agentFunction ? undefined : this.graph.getAgentFunctionInfo(this.agentId)?.inputs,
505
+ debugInfo: this.getDebugInfo(),
506
+ filterParams: this.filterParams,
507
+ agentFilters: this.graph.agentFilters,
508
+ config: this.graph.config,
509
+ log: localLog,
510
+ };
511
+ return context;
512
+ }
513
+ getResult(result) {
514
+ if (result && this.passThrough) {
515
+ if (isObject(result) && !Array.isArray(result)) {
516
+ return { ...result, ...this.passThrough };
517
+ }
518
+ else if (Array.isArray(result)) {
519
+ return result.map((r) => (isObject(r) && !Array.isArray(r) ? { ...r, ...this.passThrough } : r));
520
+ }
521
+ }
522
+ return result;
523
+ }
524
+ getDebugInfo() {
525
+ return {
526
+ nodeId: this.nodeId,
527
+ agentId: this.agentId,
528
+ retry: this.retryCount,
529
+ verbose: this.graph.verbose,
530
+ version: this.graph.version,
531
+ isResult: this.isResult,
532
+ };
533
+ }
534
+ beforeConsoleLog(context) {
535
+ if (this.console.before === true) {
536
+ console.log(JSON.stringify(this.isNamedInputs ? context.namedInputs : context.inputs, null, 2));
537
+ }
538
+ else if (this.console.before) {
539
+ console.log(this.console.before);
540
+ }
541
+ }
542
+ }
543
+ class StaticNode extends Node {
544
+ constructor(nodeId, data, graph) {
545
+ super(nodeId, graph);
546
+ this.isStaticNode = true;
547
+ this.isComputedNode = false;
548
+ this.value = data.value;
549
+ this.update = data.update ? parseNodeName(data.update) : undefined;
550
+ this.isResult = data.isResult ?? false;
551
+ this.console = data.console ?? {};
552
+ }
553
+ injectValue(value, injectFrom) {
554
+ this.state = NodeState.Injected;
555
+ this.result = value;
556
+ this.log.onInjected(this, this.graph, injectFrom);
557
+ this.onSetResult();
558
+ }
559
+ consoleLog() {
560
+ this.afterConsoleLog(this.result);
561
+ }
562
+ }
563
+
564
+ const propFunctionRegex = /^[a-zA-Z]+\([^)]*\)$/;
565
+ const propArrayFunction = (result, propId) => {
566
+ if (Array.isArray(result)) {
567
+ if (propId === "length()") {
568
+ return result.length;
569
+ }
570
+ if (propId === "flat()") {
571
+ return result.flat();
572
+ }
573
+ if (propId === "toJSON()") {
574
+ return JSON.stringify(result);
575
+ }
576
+ if (propId === "isEmpty()") {
577
+ return result.length === 0;
578
+ }
579
+ // array join
580
+ const matchJoin = propId.match(/^join\(([,-]?)\)$/);
581
+ if (matchJoin && Array.isArray(matchJoin)) {
582
+ return result.join(matchJoin[1] ?? "");
583
+ }
584
+ }
585
+ return undefined;
586
+ };
587
+ const propObjectFunction = (result, propId) => {
588
+ if (isObject(result)) {
589
+ if (propId === "keys()") {
590
+ return Object.keys(result);
591
+ }
592
+ if (propId === "values()") {
593
+ return Object.values(result);
594
+ }
595
+ if (propId === "toJSON()") {
596
+ return JSON.stringify(result);
597
+ }
598
+ }
599
+ return undefined;
600
+ };
601
+ const propStringFunction = (result, propId) => {
602
+ if (typeof result === "string") {
603
+ if (propId === "codeBlock()") {
604
+ const match = ("\n" + result).match(/\n```[a-zA-z]*([\s\S]*?)\n```/);
605
+ if (match) {
606
+ return match[1];
607
+ }
608
+ }
609
+ if (propId === "jsonParse()") {
610
+ return JSON.parse(result);
611
+ }
612
+ if (propId === "toNumber()") {
613
+ const ret = Number(result);
614
+ if (!isNaN(ret)) {
615
+ return ret;
616
+ }
617
+ }
618
+ }
619
+ return undefined;
620
+ };
621
+ const propNumberFunction = (result, propId) => {
622
+ if (result !== undefined && Number.isFinite(result)) {
623
+ if (propId === "toString()") {
624
+ return String(result);
625
+ }
626
+ const regex = /^add\((-?\d+)\)$/;
627
+ const match = propId.match(regex);
628
+ if (match) {
629
+ return Number(result) + Number(match[1]);
630
+ }
631
+ }
632
+ return undefined;
633
+ };
634
+ const propBooleanFunction = (result, propId) => {
635
+ if (typeof result === "boolean") {
636
+ if (propId === "not()") {
637
+ return !result;
638
+ }
639
+ }
640
+ return undefined;
641
+ };
642
+ const propFunctions = [propArrayFunction, propObjectFunction, propStringFunction, propNumberFunction, propBooleanFunction];
643
+
644
+ const getNestedData = (result, propId, propFunctions) => {
645
+ const match = propId.match(propFunctionRegex);
646
+ if (match) {
647
+ for (const propFunction of propFunctions) {
648
+ const ret = propFunction(result, propId);
649
+ if (!isNull(ret)) {
650
+ return ret;
651
+ }
652
+ }
653
+ }
654
+ // for array.
655
+ if (Array.isArray(result)) {
656
+ // $0, $1. array value.
657
+ const regex = /^\$(\d+)$/;
658
+ const match = propId.match(regex);
659
+ if (match) {
660
+ const index = parseInt(match[1], 10);
661
+ return result[index];
662
+ }
663
+ if (propId === "$last") {
664
+ return result[result.length - 1];
665
+ }
666
+ }
667
+ else if (isObject(result)) {
668
+ if (propId in result) {
669
+ return result[propId];
670
+ }
671
+ }
672
+ return undefined;
673
+ };
674
+ const innerGetDataFromSource = (result, propIds, propFunctions) => {
675
+ if (!isNull(result) && propIds && propIds.length > 0) {
676
+ const propId = propIds[0];
677
+ const ret = getNestedData(result, propId, propFunctions);
678
+ if (ret === undefined) {
679
+ console.error(`prop: ${propIds.join(".")} is not hit`);
680
+ }
681
+ if (propIds.length > 1) {
682
+ return innerGetDataFromSource(ret, propIds.slice(1), propFunctions);
683
+ }
684
+ return ret;
685
+ }
686
+ return result;
687
+ };
688
+ const getDataFromSource = (result, source, propFunctions = []) => {
689
+ if (!source.nodeId) {
690
+ return source.value;
691
+ }
692
+ return innerGetDataFromSource(result, source.propIds, propFunctions);
693
+ };
694
+
695
+ const resultsOfInner = (input, nodes, propFunctions) => {
696
+ if (Array.isArray(input)) {
697
+ return input.map((inp) => resultsOfInner(inp, nodes, propFunctions));
698
+ }
699
+ if (isNamedInputs(input)) {
700
+ return resultsOf(input, nodes, propFunctions);
701
+ }
702
+ if (typeof input === "string") {
703
+ const templateMatch = [...input.matchAll(/\${(:[^}]+)}/g)].map((m) => m[1]);
704
+ if (templateMatch.length > 0) {
705
+ const results = resultsOfInner(templateMatch, nodes, propFunctions);
706
+ return Array.from(templateMatch.keys()).reduce((tmp, key) => {
707
+ return tmp.replaceAll("${" + templateMatch[key] + "}", results[key]);
708
+ }, input);
709
+ }
710
+ }
711
+ return resultOf(parseNodeName(input), nodes, propFunctions);
712
+ };
713
+ const resultsOf = (inputs, nodes, propFunctions) => {
714
+ // for inputs. TODO remove if array input is not supported
715
+ if (Array.isArray(inputs)) {
716
+ return inputs.reduce((tmp, key) => {
717
+ tmp[key] = resultsOfInner(key, nodes, propFunctions);
718
+ return tmp;
719
+ }, {});
720
+ }
721
+ return Object.keys(inputs).reduce((tmp, key) => {
722
+ const input = inputs[key];
723
+ tmp[key] = isNamedInputs(input) ? resultsOf(input, nodes, propFunctions) : resultsOfInner(input, nodes, propFunctions);
724
+ return tmp;
725
+ }, {});
726
+ };
727
+ const resultOf = (source, nodes, propFunctions) => {
728
+ const { result } = source.nodeId ? nodes[source.nodeId] : { result: undefined };
729
+ return getDataFromSource(result, source, propFunctions);
730
+ };
731
+ // clean up object for anyInput
732
+ const cleanResultInner = (results) => {
733
+ if (Array.isArray(results)) {
734
+ return results.map((result) => cleanResultInner(result)).filter((result) => !isNull(result));
735
+ }
736
+ if (isObject(results)) {
737
+ return Object.keys(results).reduce((tmp, key) => {
738
+ const value = cleanResultInner(results[key]);
739
+ if (!isNull(value)) {
740
+ tmp[key] = value;
741
+ }
742
+ return tmp;
743
+ }, {});
744
+ }
745
+ return results;
746
+ };
747
+ const cleanResult = (results) => {
748
+ return Object.keys(results).reduce((tmp, key) => {
749
+ const value = cleanResultInner(results[key]);
750
+ if (!isNull(value)) {
751
+ tmp[key] = value;
752
+ }
753
+ return tmp;
754
+ }, {});
755
+ };
756
+
757
+ const graphDataAttributeKeys = ["nodes", "concurrency", "agentId", "loop", "verbose", "version"];
758
+ const computedNodeAttributeKeys = [
759
+ "inputs",
760
+ "anyInput",
761
+ "params",
762
+ "retry",
763
+ "timeout",
764
+ "agent",
765
+ "graph",
766
+ "graphLoader",
767
+ "isResult",
768
+ "priority",
769
+ "if",
770
+ "unless",
771
+ "filterParams",
772
+ "console",
773
+ "passThrough",
774
+ ];
775
+ const staticNodeAttributeKeys = ["value", "update", "isResult", "console"];
776
+ class ValidationError extends Error {
777
+ constructor(message) {
778
+ super(`\x1b[41m${message}\x1b[0m`); // Pass the message to the base Error class
779
+ // Set the prototype explicitly to ensure correct prototype chain
780
+ Object.setPrototypeOf(this, ValidationError.prototype);
781
+ }
782
+ }
783
+
784
+ const graphNodesValidator = (data) => {
785
+ if (data.nodes === undefined) {
786
+ throw new ValidationError("Invalid Graph Data: no nodes");
787
+ }
788
+ if (typeof data.nodes !== "object") {
789
+ throw new ValidationError("Invalid Graph Data: invalid nodes");
790
+ }
791
+ if (Array.isArray(data.nodes)) {
792
+ throw new ValidationError("Invalid Graph Data: nodes must be object");
793
+ }
794
+ if (Object.keys(data.nodes).length === 0) {
795
+ throw new ValidationError("Invalid Graph Data: nodes is empty");
796
+ }
797
+ Object.keys(data).forEach((key) => {
798
+ if (!graphDataAttributeKeys.includes(key)) {
799
+ throw new ValidationError("Graph Data does not allow " + key);
800
+ }
801
+ });
802
+ };
803
+ const graphDataValidator = (data) => {
804
+ if (data.loop) {
805
+ if (data.loop.count === undefined && data.loop.while === undefined) {
806
+ throw new ValidationError("Loop: Either count or while is required in loop");
807
+ }
808
+ if (data.loop.count !== undefined && data.loop.while !== undefined) {
809
+ throw new ValidationError("Loop: Both count and while cannot be set");
810
+ }
811
+ }
812
+ if (data.concurrency !== undefined) {
813
+ if (!Number.isInteger(data.concurrency)) {
814
+ throw new ValidationError("Concurrency must be an integer");
815
+ }
816
+ if (data.concurrency < 1) {
817
+ throw new ValidationError("Concurrency must be a positive integer");
818
+ }
819
+ }
820
+ };
821
+
822
+ const nodeValidator = (nodeData) => {
823
+ if (nodeData.agent && nodeData.value) {
824
+ throw new ValidationError("Cannot set both agent and value");
825
+ }
826
+ if (!("agent" in nodeData) && !("value" in nodeData)) {
827
+ throw new ValidationError("Either agent or value is required");
828
+ }
829
+ return true;
830
+ };
831
+
832
+ const staticNodeValidator = (nodeData) => {
833
+ Object.keys(nodeData).forEach((key) => {
834
+ if (!staticNodeAttributeKeys.includes(key)) {
835
+ throw new ValidationError("Static node does not allow " + key);
836
+ }
837
+ });
838
+ return true;
839
+ };
840
+
841
+ const computedNodeValidator = (nodeData) => {
842
+ Object.keys(nodeData).forEach((key) => {
843
+ if (!computedNodeAttributeKeys.includes(key)) {
844
+ throw new ValidationError("Computed node does not allow " + key);
845
+ }
846
+ });
847
+ return true;
848
+ };
849
+
850
+ const relationValidator = (data, staticNodeIds, computedNodeIds) => {
851
+ const nodeIds = new Set(Object.keys(data.nodes));
852
+ const pendings = {};
853
+ const waitlist = {};
854
+ // validate input relation and set pendings and wait list
855
+ computedNodeIds.forEach((computedNodeId) => {
856
+ const nodeData = data.nodes[computedNodeId];
857
+ pendings[computedNodeId] = new Set();
858
+ const dataSourceValidator = (sourceType, sourceNodeIds) => {
859
+ sourceNodeIds.forEach((sourceNodeId) => {
860
+ if (sourceNodeId) {
861
+ if (!nodeIds.has(sourceNodeId)) {
862
+ throw new ValidationError(`${sourceType} not match: NodeId ${computedNodeId}, Inputs: ${sourceNodeId}`);
863
+ }
864
+ waitlist[sourceNodeId] === undefined && (waitlist[sourceNodeId] = new Set());
865
+ pendings[computedNodeId].add(sourceNodeId);
866
+ waitlist[sourceNodeId].add(computedNodeId);
867
+ }
868
+ });
869
+ };
870
+ if ("agent" in nodeData && nodeData) {
871
+ if (nodeData.inputs) {
872
+ const sourceNodeIds = dataSourceNodeIds(inputs2dataSources(nodeData.inputs));
873
+ dataSourceValidator("Inputs", sourceNodeIds);
874
+ }
875
+ if (nodeData.if) {
876
+ const sourceNodeIds = dataSourceNodeIds(inputs2dataSources({ if: nodeData.if }));
877
+ dataSourceValidator("If", sourceNodeIds);
878
+ }
879
+ if (nodeData.unless) {
880
+ const sourceNodeIds = dataSourceNodeIds(inputs2dataSources({ unless: nodeData.unless }));
881
+ dataSourceValidator("Unless", sourceNodeIds);
882
+ }
883
+ if (nodeData.graph && typeof nodeData?.graph === "string") {
884
+ const sourceNodeIds = dataSourceNodeIds(inputs2dataSources({ graph: nodeData.graph }));
885
+ dataSourceValidator("Graph", sourceNodeIds);
886
+ }
887
+ }
888
+ });
889
+ // TODO. validate update
890
+ staticNodeIds.forEach((staticNodeId) => {
891
+ const nodeData = data.nodes[staticNodeId];
892
+ if ("value" in nodeData && nodeData.update) {
893
+ const update = nodeData.update;
894
+ const updateNodeId = parseNodeName(update).nodeId;
895
+ if (!updateNodeId) {
896
+ throw new ValidationError("Update it a literal");
897
+ }
898
+ if (!nodeIds.has(updateNodeId)) {
899
+ throw new ValidationError(`Update not match: NodeId ${staticNodeId}, update: ${update}`);
900
+ }
901
+ }
902
+ });
903
+ const cycle = (possibles) => {
904
+ possibles.forEach((possobleNodeId) => {
905
+ (waitlist[possobleNodeId] || []).forEach((waitingNodeId) => {
906
+ pendings[waitingNodeId].delete(possobleNodeId);
907
+ });
908
+ });
909
+ const running = [];
910
+ Object.keys(pendings).forEach((pendingNodeId) => {
911
+ if (pendings[pendingNodeId].size === 0) {
912
+ running.push(pendingNodeId);
913
+ delete pendings[pendingNodeId];
914
+ }
915
+ });
916
+ return running;
917
+ };
918
+ let runningQueue = cycle(staticNodeIds);
919
+ if (runningQueue.length === 0) {
920
+ throw new ValidationError("No Initial Runnning Node");
921
+ }
922
+ do {
923
+ runningQueue = cycle(runningQueue);
924
+ } while (runningQueue.length > 0);
925
+ if (Object.keys(pendings).length > 0) {
926
+ throw new ValidationError("Some nodes are not executed: " + Object.keys(pendings).join(", "));
927
+ }
928
+ };
929
+
930
+ const agentValidator = (graphAgentIds, agentIds) => {
931
+ graphAgentIds.forEach((agentId) => {
932
+ if (!agentIds.has(agentId)) {
933
+ throw new ValidationError("Invalid Agent : " + agentId + " is not in AgentFunctionInfoDictionary.");
934
+ }
935
+ });
936
+ return true;
937
+ };
938
+
939
+ const validateGraphData = (data, agentIds) => {
940
+ graphNodesValidator(data);
941
+ graphDataValidator(data);
942
+ const computedNodeIds = [];
943
+ const staticNodeIds = [];
944
+ const graphAgentIds = new Set();
945
+ Object.keys(data.nodes).forEach((nodeId) => {
946
+ const node = data.nodes[nodeId];
947
+ const isStaticNode = "value" in node;
948
+ nodeValidator(node);
949
+ const agentId = isStaticNode ? "" : node.agent;
950
+ isStaticNode && staticNodeValidator(node) && staticNodeIds.push(nodeId);
951
+ !isStaticNode && computedNodeValidator(node) && computedNodeIds.push(nodeId) && typeof agentId === "string" && graphAgentIds.add(agentId);
952
+ });
953
+ agentValidator(graphAgentIds, new Set(agentIds));
954
+ relationValidator(data, staticNodeIds, computedNodeIds);
955
+ return true;
956
+ };
957
+
958
+ // TaskManage object controls the concurrency of ComputedNode execution.
959
+ //
960
+ // NOTE: A TaskManager instance will be shared between parent graph and its children
961
+ // when nested agents are involved.
962
+ class TaskManager {
963
+ constructor(concurrency) {
964
+ this.taskQueue = [];
965
+ this.runningNodes = new Set();
966
+ this.concurrency = concurrency;
967
+ }
968
+ // This internal method dequeus a task from the task queue
969
+ // and call the associated callback method, if the number of
970
+ // running task is lower than the spcified limit.
971
+ dequeueTaskIfPossible() {
972
+ if (this.runningNodes.size < this.concurrency) {
973
+ const task = this.taskQueue.shift();
974
+ if (task) {
975
+ this.runningNodes.add(task.node);
976
+ task.callback(task.node);
977
+ }
978
+ }
979
+ }
980
+ // Node will call this method to put itself in the execution queue.
981
+ // We call the associated callback function when it is dequeued.
982
+ addTask(node, graphId, callback) {
983
+ // Finder tasks in the queue, which has either the same or higher priority.
984
+ const count = this.taskQueue.filter((task) => {
985
+ return task.node.priority >= node.priority;
986
+ }).length;
987
+ assert(count <= this.taskQueue.length, "TaskManager.addTask: Something is really wrong.");
988
+ this.taskQueue.splice(count, 0, { node, graphId, callback });
989
+ this.dequeueTaskIfPossible();
990
+ }
991
+ isRunning(graphId) {
992
+ const count = [...this.runningNodes].filter((node) => {
993
+ return node.graphId == graphId;
994
+ }).length;
995
+ return count > 0 || Array.from(this.taskQueue).filter((data) => data.graphId === graphId).length > 0;
996
+ }
997
+ // Node MUST call this method once the execution of agent function is completed
998
+ // either successfully or not.
999
+ onComplete(node) {
1000
+ assert(this.runningNodes.has(node), `TaskManager.onComplete node(${node.nodeId}) is not in list`);
1001
+ this.runningNodes.delete(node);
1002
+ this.dequeueTaskIfPossible();
1003
+ }
1004
+ // Node will call this method before it hands the task manager from the graph
1005
+ // to a nested agent. We need to make it sure that there is enough room to run
1006
+ // computed nodes inside the nested graph to avoid a deadlock.
1007
+ prepareForNesting() {
1008
+ this.concurrency++;
1009
+ }
1010
+ restoreAfterNesting() {
1011
+ this.concurrency--;
1012
+ }
1013
+ getStatus(verbose = false) {
1014
+ const runningNodes = Array.from(this.runningNodes).map((node) => node.nodeId);
1015
+ const queuedNodes = this.taskQueue.map((task) => task.node.nodeId);
1016
+ const nodes = verbose ? { runningNodes, queuedNodes } : {};
1017
+ return {
1018
+ concurrency: this.concurrency,
1019
+ queue: this.taskQueue.length,
1020
+ running: this.runningNodes.size,
1021
+ ...nodes,
1022
+ };
1023
+ }
1024
+ }
1025
+
1026
+ const defaultConcurrency = 8;
1027
+ const graphDataLatestVersion = 0.5;
1028
+ class GraphAI {
1029
+ // This method is called when either the GraphAI obect was created,
1030
+ // or we are about to start n-th iteration (n>2).
1031
+ createNodes(data) {
1032
+ const nodes = Object.keys(data.nodes).reduce((_nodes, nodeId) => {
1033
+ const nodeData = data.nodes[nodeId];
1034
+ if ("value" in nodeData) {
1035
+ _nodes[nodeId] = new StaticNode(nodeId, nodeData, this);
1036
+ }
1037
+ else if ("agent" in nodeData) {
1038
+ _nodes[nodeId] = new ComputedNode(this.graphId, nodeId, nodeData, this);
1039
+ }
1040
+ else {
1041
+ throw new Error("Unknown node type (neither value nor agent): " + nodeId);
1042
+ }
1043
+ return _nodes;
1044
+ }, {});
1045
+ // Generate the waitlist for each node.
1046
+ Object.keys(nodes).forEach((nodeId) => {
1047
+ const node = nodes[nodeId];
1048
+ if (node.isComputedNode) {
1049
+ node.pendings.forEach((pending) => {
1050
+ if (nodes[pending]) {
1051
+ nodes[pending].waitlist.add(nodeId); // previousNode
1052
+ }
1053
+ else {
1054
+ throw new Error(`createNode: invalid input ${pending} for node, ${nodeId}`);
1055
+ }
1056
+ });
1057
+ }
1058
+ });
1059
+ return nodes;
1060
+ }
1061
+ getValueFromResults(source, results) {
1062
+ return getDataFromSource(source.nodeId ? results[source.nodeId] : undefined, source, this.propFunctions);
1063
+ }
1064
+ // for static
1065
+ initializeStaticNodes(enableConsoleLog = false) {
1066
+ // If the result property is specified, inject it.
1067
+ // If the previousResults exists (indicating we are in a loop),
1068
+ // process the update property (nodeId or nodeId.propId).
1069
+ Object.keys(this.data.nodes).forEach((nodeId) => {
1070
+ const node = this.nodes[nodeId];
1071
+ if (node?.isStaticNode) {
1072
+ const value = node?.value;
1073
+ if (value !== undefined) {
1074
+ this.injectValue(nodeId, value, nodeId);
1075
+ }
1076
+ if (enableConsoleLog) {
1077
+ node.consoleLog();
1078
+ }
1079
+ }
1080
+ });
1081
+ }
1082
+ updateStaticNodes(previousResults, enableConsoleLog = false) {
1083
+ // If the result property is specified, inject it.
1084
+ // If the previousResults exists (indicating we are in a loop),
1085
+ // process the update property (nodeId or nodeId.propId).
1086
+ Object.keys(this.data.nodes).forEach((nodeId) => {
1087
+ const node = this.nodes[nodeId];
1088
+ if (node?.isStaticNode) {
1089
+ const update = node?.update;
1090
+ if (update && previousResults) {
1091
+ const result = this.getValueFromResults(update, previousResults);
1092
+ this.injectValue(nodeId, result, update.nodeId);
1093
+ }
1094
+ if (enableConsoleLog) {
1095
+ node.consoleLog();
1096
+ }
1097
+ }
1098
+ });
1099
+ }
1100
+ constructor(data, agentFunctionInfoDictionary, options = {
1101
+ taskManager: undefined,
1102
+ agentFilters: [],
1103
+ bypassAgentIds: [],
1104
+ config: {},
1105
+ graphLoader: undefined,
1106
+ }) {
1107
+ this.logs = [];
1108
+ this.config = {};
1109
+ this.onLogCallback = (__log, __isUpdate) => { };
1110
+ this.repeatCount = 0;
1111
+ if (!data.version && !options.taskManager) {
1112
+ console.warn("------------ missing version number");
1113
+ }
1114
+ this.version = data.version ?? graphDataLatestVersion;
1115
+ if (this.version < graphDataLatestVersion) {
1116
+ console.warn(`------------ upgrade to ${graphDataLatestVersion}!`);
1117
+ }
1118
+ this.retryLimit = data.retry; // optional
1119
+ this.graphId = URL.createObjectURL(new Blob()).slice(-36);
1120
+ this.data = data;
1121
+ this.agentFunctionInfoDictionary = agentFunctionInfoDictionary;
1122
+ this.propFunctions = propFunctions;
1123
+ this.taskManager = options.taskManager ?? new TaskManager(data.concurrency ?? defaultConcurrency);
1124
+ this.agentFilters = options.agentFilters ?? [];
1125
+ this.bypassAgentIds = options.bypassAgentIds ?? [];
1126
+ this.config = options.config;
1127
+ this.graphLoader = options.graphLoader;
1128
+ this.loop = data.loop;
1129
+ this.verbose = data.verbose === true;
1130
+ this.onComplete = () => {
1131
+ throw new Error("SOMETHING IS WRONG: onComplete is called without run()");
1132
+ };
1133
+ validateGraphData(data, [...Object.keys(agentFunctionInfoDictionary), ...this.bypassAgentIds]);
1134
+ this.nodes = this.createNodes(data);
1135
+ this.initializeStaticNodes(true);
1136
+ }
1137
+ getAgentFunctionInfo(agentId) {
1138
+ if (agentId && this.agentFunctionInfoDictionary[agentId]) {
1139
+ return this.agentFunctionInfoDictionary[agentId];
1140
+ }
1141
+ if (agentId && this.bypassAgentIds.includes(agentId)) {
1142
+ return {
1143
+ agent: async () => {
1144
+ return null;
1145
+ },
1146
+ inputs: null,
1147
+ };
1148
+ }
1149
+ // We are not supposed to hit this error because the validator will catch it.
1150
+ throw new Error("No agent: " + agentId);
1151
+ }
1152
+ asString() {
1153
+ return Object.values(this.nodes)
1154
+ .map((node) => node.asString())
1155
+ .join("\n");
1156
+ }
1157
+ // Public API
1158
+ results(all) {
1159
+ return Object.keys(this.nodes)
1160
+ .filter((nodeId) => all || this.nodes[nodeId].isResult)
1161
+ .reduce((results, nodeId) => {
1162
+ const node = this.nodes[nodeId];
1163
+ if (node.result !== undefined) {
1164
+ results[nodeId] = node.result;
1165
+ }
1166
+ return results;
1167
+ }, {});
1168
+ }
1169
+ // Public API
1170
+ errors() {
1171
+ return Object.keys(this.nodes).reduce((errors, nodeId) => {
1172
+ const node = this.nodes[nodeId];
1173
+ if (node.isComputedNode) {
1174
+ if (node.error !== undefined) {
1175
+ errors[nodeId] = node.error;
1176
+ }
1177
+ }
1178
+ return errors;
1179
+ }, {});
1180
+ }
1181
+ pushReadyNodesIntoQueue() {
1182
+ // Nodes without pending data should run immediately.
1183
+ Object.keys(this.nodes).forEach((nodeId) => {
1184
+ const node = this.nodes[nodeId];
1185
+ if (node.isComputedNode) {
1186
+ this.pushQueueIfReady(node);
1187
+ }
1188
+ });
1189
+ }
1190
+ pushQueueIfReady(node) {
1191
+ if (node.isReadyNode()) {
1192
+ this.pushQueue(node);
1193
+ }
1194
+ }
1195
+ pushQueueIfReadyAndRunning(node) {
1196
+ if (this.isRunning()) {
1197
+ this.pushQueueIfReady(node);
1198
+ }
1199
+ }
1200
+ // for computed
1201
+ pushQueue(node) {
1202
+ node.beforeAddTask();
1203
+ this.taskManager.addTask(node, this.graphId, (_node) => {
1204
+ assert(node.nodeId === _node.nodeId, "GraphAI.pushQueue node mismatch");
1205
+ node.execute();
1206
+ });
1207
+ }
1208
+ // Public API
1209
+ async run(all = false) {
1210
+ if (this.isRunning()) {
1211
+ throw new Error("This GraphUI instance is already running");
1212
+ }
1213
+ this.pushReadyNodesIntoQueue();
1214
+ if (!this.isRunning()) {
1215
+ console.warn("-- nothing to execute");
1216
+ return {};
1217
+ }
1218
+ return new Promise((resolve, reject) => {
1219
+ this.onComplete = () => {
1220
+ const errors = this.errors();
1221
+ const nodeIds = Object.keys(errors);
1222
+ if (nodeIds.length > 0) {
1223
+ reject(errors[nodeIds[0]]);
1224
+ }
1225
+ else {
1226
+ resolve(this.results(all));
1227
+ }
1228
+ };
1229
+ });
1230
+ }
1231
+ // Public only for testing
1232
+ isRunning() {
1233
+ return this.taskManager.isRunning(this.graphId);
1234
+ }
1235
+ // callback from execute
1236
+ onExecutionComplete(node) {
1237
+ this.taskManager.onComplete(node);
1238
+ if (this.isRunning() || this.processLoopIfNecessary()) {
1239
+ return; // continue running
1240
+ }
1241
+ this.onComplete(); // Nothing to run. Finish it.
1242
+ }
1243
+ // Must be called only from onExecutionComplete righ after removeRunning
1244
+ // Check if there is any running computed nodes.
1245
+ // In case of no running computed note, start the another iteration if ncessary (loop)
1246
+ processLoopIfNecessary() {
1247
+ this.repeatCount++;
1248
+ const loop = this.loop;
1249
+ if (!loop) {
1250
+ return false;
1251
+ }
1252
+ // We need to update static nodes, before checking the condition
1253
+ const previousResults = this.results(true); // results from previous loop
1254
+ this.updateStaticNodes(previousResults);
1255
+ if (loop.count === undefined || this.repeatCount < loop.count) {
1256
+ if (loop.while) {
1257
+ const source = parseNodeName(loop.while);
1258
+ const value = this.getValueFromResults(source, this.results(true));
1259
+ // NOTE: We treat an empty array as false.
1260
+ if (!isLogicallyTrue(value)) {
1261
+ return false; // while condition is not met
1262
+ }
1263
+ }
1264
+ this.nodes = this.createNodes(this.data);
1265
+ this.initializeStaticNodes();
1266
+ this.updateStaticNodes(previousResults, true);
1267
+ this.pushReadyNodesIntoQueue();
1268
+ return true; // Indicating that we are going to continue.
1269
+ }
1270
+ return false;
1271
+ }
1272
+ setLoopLog(log) {
1273
+ log.isLoop = !!this.loop;
1274
+ log.repeatCount = this.repeatCount;
1275
+ }
1276
+ appendLog(log) {
1277
+ this.logs.push(log);
1278
+ this.onLogCallback(log, false);
1279
+ }
1280
+ updateLog(log) {
1281
+ this.onLogCallback(log, true);
1282
+ }
1283
+ // Public API
1284
+ transactionLogs() {
1285
+ return this.logs;
1286
+ }
1287
+ // Public API
1288
+ injectValue(nodeId, value, injectFrom) {
1289
+ const node = this.nodes[nodeId];
1290
+ if (node && node.isStaticNode) {
1291
+ node.injectValue(value, injectFrom);
1292
+ }
1293
+ else {
1294
+ throw new Error(`injectValue with Invalid nodeId, ${nodeId}`);
1295
+ }
1296
+ }
1297
+ resultsOf(inputs, anyInput = false) {
1298
+ const results = resultsOf(inputs ?? [], this.nodes, this.propFunctions);
1299
+ if (anyInput) {
1300
+ return cleanResult(results);
1301
+ }
1302
+ return results;
1303
+ }
1304
+ resultOf(source) {
1305
+ return resultOf(source, this.nodes, this.propFunctions);
1306
+ }
1307
+ }
1308
+
1309
+ export { GraphAI, NodeState, ValidationError, agentInfoWrapper, assert, defaultAgentInfo, defaultConcurrency, defaultTestContext, graphDataLatestVersion, inputs2dataSources, isObject, parseNodeName, sleep, strIntentionalError };
1310
+ //# sourceMappingURL=bundle.esm.js.map