@alpic80/rivet-core 1.19.1-aidon.3 → 1.24.0-aidon.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 (129) hide show
  1. package/README.md +4 -0
  2. package/dist/cjs/bundle.cjs +4512 -1240
  3. package/dist/cjs/bundle.cjs.map +4 -4
  4. package/dist/esm/api/createProcessor.js +8 -17
  5. package/dist/esm/api/looseDataValue.js +16 -0
  6. package/dist/esm/exports.js +2 -0
  7. package/dist/esm/integrations/CodeRunner.js +36 -0
  8. package/dist/esm/integrations/DatasetProvider.js +1 -1
  9. package/dist/esm/integrations/GptTokenizerTokenizer.js +7 -4
  10. package/dist/esm/integrations/openai/OpenAIEmbeddingGenerator.js +1 -1
  11. package/dist/esm/model/DataValue.js +14 -2
  12. package/dist/esm/model/GraphProcessor.js +276 -107
  13. package/dist/esm/model/NodeBase.js +11 -1
  14. package/dist/esm/model/NodeImpl.js +8 -0
  15. package/dist/esm/model/Nodes.js +31 -4
  16. package/dist/esm/model/ProjectReferenceLoader.js +1 -0
  17. package/dist/esm/model/nodes/AssembleMessageNode.js +12 -2
  18. package/dist/esm/model/nodes/AssemblePromptNode.js +22 -0
  19. package/dist/esm/model/nodes/CallGraphNode.js +3 -4
  20. package/dist/esm/model/nodes/ChatLoopNode.js +150 -0
  21. package/dist/esm/model/nodes/ChatNode.js +7 -934
  22. package/dist/esm/model/nodes/ChatNodeBase.js +1277 -0
  23. package/dist/esm/model/nodes/ChunkNode.js +2 -2
  24. package/dist/esm/model/nodes/CodeNode.js +40 -5
  25. package/dist/esm/model/nodes/CronNode.js +248 -0
  26. package/dist/esm/model/nodes/DelegateFunctionCallNode.js +37 -12
  27. package/dist/esm/model/nodes/DestructureNode.js +1 -1
  28. package/dist/esm/model/nodes/DocumentNode.js +183 -0
  29. package/dist/esm/model/nodes/ExtractJsonNode.js +4 -4
  30. package/dist/esm/model/nodes/ExtractRegexNode.js +10 -11
  31. package/dist/esm/model/nodes/GetAllDatasetsNode.js +1 -1
  32. package/dist/esm/model/nodes/GetEmbeddingNode.js +1 -1
  33. package/dist/esm/model/nodes/HttpCallNode.js +3 -1
  34. package/dist/esm/model/nodes/IfNode.js +5 -0
  35. package/dist/esm/model/nodes/LoopControllerNode.js +1 -1
  36. package/dist/esm/model/nodes/LoopUntilNode.js +214 -0
  37. package/dist/esm/model/nodes/ObjectNode.js +1 -1
  38. package/dist/esm/model/nodes/PromptNode.js +29 -6
  39. package/dist/esm/model/nodes/RaceInputsNode.js +1 -2
  40. package/dist/esm/model/nodes/ReadAllFilesNode.js +210 -0
  41. package/dist/esm/model/nodes/ReadDirectoryNode.js +31 -25
  42. package/dist/esm/model/nodes/ReferencedGraphAliasNode.js +199 -0
  43. package/dist/esm/model/nodes/ReplaceDatasetNode.js +1 -1
  44. package/dist/esm/model/nodes/SliceNode.js +0 -1
  45. package/dist/esm/model/nodes/SplitNode.js +1 -1
  46. package/dist/esm/model/nodes/SubGraphNode.js +0 -1
  47. package/dist/esm/model/nodes/TextNode.js +9 -4
  48. package/dist/esm/model/nodes/ToMarkdownTableNode.js +119 -0
  49. package/dist/esm/model/nodes/ToTreeNode.js +133 -0
  50. package/dist/esm/model/nodes/{GptFunctionNode.js → ToolNode.js} +10 -10
  51. package/dist/esm/model/nodes/UserInputNode.js +10 -12
  52. package/dist/esm/model/nodes/WriteFileNode.js +147 -0
  53. package/dist/esm/native/BrowserNativeApi.js +16 -1
  54. package/dist/esm/plugins/aidon/nodes/ChatAidonNode.js +5 -5
  55. package/dist/esm/plugins/anthropic/anthropic.js +29 -14
  56. package/dist/esm/plugins/anthropic/fetchEventSource.js +3 -2
  57. package/dist/esm/plugins/anthropic/nodes/ChatAnthropicNode.js +264 -147
  58. package/dist/esm/plugins/anthropic/plugin.js +9 -1
  59. package/dist/esm/plugins/assemblyAi/LemurQaNode.js +1 -1
  60. package/dist/esm/plugins/assemblyAi/LemurSummaryNode.js +1 -1
  61. package/dist/esm/plugins/gentrace/plugin.js +6 -6
  62. package/dist/esm/plugins/google/google.js +120 -6
  63. package/dist/esm/plugins/google/nodes/ChatGoogleNode.js +219 -56
  64. package/dist/esm/plugins/google/plugin.js +13 -6
  65. package/dist/esm/plugins/openai/nodes/RunThreadNode.js +2 -2
  66. package/dist/esm/plugins/openai/nodes/ThreadMessageNode.js +1 -1
  67. package/dist/esm/recording/ExecutionRecorder.js +59 -4
  68. package/dist/esm/utils/base64.js +13 -0
  69. package/dist/esm/utils/chatMessageToOpenAIChatCompletionMessage.js +15 -2
  70. package/dist/esm/utils/coerceType.js +4 -1
  71. package/dist/esm/utils/fetchEventSource.js +1 -1
  72. package/dist/esm/utils/interpolation.js +108 -3
  73. package/dist/esm/utils/openai.js +106 -50
  74. package/dist/esm/utils/paths.js +80 -0
  75. package/dist/esm/utils/serialization/serialization_v4.js +5 -0
  76. package/dist/types/api/createProcessor.d.ts +11 -5
  77. package/dist/types/api/looseDataValue.d.ts +4 -0
  78. package/dist/types/api/streaming.d.ts +1 -1
  79. package/dist/types/exports.d.ts +2 -0
  80. package/dist/types/integrations/CodeRunner.d.ts +18 -0
  81. package/dist/types/integrations/DatasetProvider.d.ts +1 -1
  82. package/dist/types/model/DataValue.d.ts +29 -6
  83. package/dist/types/model/EditorDefinition.d.ts +6 -1
  84. package/dist/types/model/GraphProcessor.d.ts +14 -7
  85. package/dist/types/model/NodeBase.d.ts +4 -0
  86. package/dist/types/model/NodeImpl.d.ts +5 -4
  87. package/dist/types/model/Nodes.d.ts +13 -4
  88. package/dist/types/model/ProcessContext.d.ts +16 -1
  89. package/dist/types/model/Project.d.ts +19 -7
  90. package/dist/types/model/ProjectReferenceLoader.d.ts +5 -0
  91. package/dist/types/model/RivetPlugin.d.ts +6 -0
  92. package/dist/types/model/RivetUIContext.d.ts +5 -1
  93. package/dist/types/model/Settings.d.ts +1 -0
  94. package/dist/types/model/nodes/AssemblePromptNode.d.ts +4 -1
  95. package/dist/types/model/nodes/ChatLoopNode.d.ts +21 -0
  96. package/dist/types/model/nodes/ChatNode.d.ts +2 -62
  97. package/dist/types/model/nodes/ChatNodeBase.d.ts +85 -0
  98. package/dist/types/model/nodes/CodeNode.d.ts +8 -2
  99. package/dist/types/model/nodes/CronNode.d.ts +34 -0
  100. package/dist/types/model/nodes/DelegateFunctionCallNode.d.ts +1 -0
  101. package/dist/types/model/nodes/DocumentNode.d.ts +28 -0
  102. package/dist/types/model/nodes/GetAllDatasetsNode.d.ts +2 -2
  103. package/dist/types/model/nodes/LoopUntilNode.d.ts +32 -0
  104. package/dist/types/model/nodes/ObjectNode.d.ts +2 -2
  105. package/dist/types/model/nodes/PromptNode.d.ts +2 -0
  106. package/dist/types/model/nodes/RaceInputsNode.d.ts +1 -2
  107. package/dist/types/model/nodes/ReadAllFilesNode.d.ts +30 -0
  108. package/dist/types/model/nodes/ReadDirectoryNode.d.ts +1 -1
  109. package/dist/types/model/nodes/ReferencedGraphAliasNode.d.ts +31 -0
  110. package/dist/types/model/nodes/SplitNode.d.ts +2 -2
  111. package/dist/types/model/nodes/ToMarkdownTableNode.d.ts +19 -0
  112. package/dist/types/model/nodes/ToTreeNode.d.ts +21 -0
  113. package/dist/types/model/nodes/UserInputNode.d.ts +2 -3
  114. package/dist/types/model/nodes/WriteFileNode.d.ts +23 -0
  115. package/dist/types/native/BrowserNativeApi.d.ts +8 -5
  116. package/dist/types/native/NativeApi.d.ts +12 -1
  117. package/dist/types/plugins/anthropic/anthropic.d.ts +94 -13
  118. package/dist/types/plugins/anthropic/nodes/ChatAnthropicNode.d.ts +7 -2
  119. package/dist/types/plugins/google/google.d.ts +101 -18
  120. package/dist/types/plugins/google/nodes/ChatGoogleNode.d.ts +3 -2
  121. package/dist/types/recording/RecordedEvents.d.ts +3 -0
  122. package/dist/types/utils/base64.d.ts +2 -1
  123. package/dist/types/utils/chatMessageToOpenAIChatCompletionMessage.d.ts +3 -1
  124. package/dist/types/utils/interpolation.d.ts +3 -0
  125. package/dist/types/utils/openai.d.ts +127 -21
  126. package/dist/types/utils/paths.d.ts +8 -0
  127. package/dist/types/utils/serialization/serialization_v3.d.ts +1 -0
  128. package/package.json +15 -11
  129. /package/dist/types/model/nodes/{GptFunctionNode.d.ts → ToolNode.d.ts} +0 -0
@@ -1,16 +1,20 @@
1
1
  import { max, range, sum, uniqBy } from 'lodash-es';
2
2
  import { isArrayDataValue, arrayizeDataValue, getScalarTypeOf, } from './DataValue.js';
3
+ import { IF_PORT, } from './NodeBase.js';
3
4
  import PQueueImport from 'p-queue';
4
5
  import { getError } from '../utils/errors.js';
5
6
  import Emittery from 'emittery';
6
7
  import { entries, fromEntries, values } from '../utils/typeSafety.js';
7
8
  import { isNotNull } from '../utils/genericUtilFunctions.js';
9
+ import {} from './Project.js';
8
10
  import { nanoid } from 'nanoid/non-secure';
9
11
  import { P, match } from 'ts-pattern';
10
12
  import { coerceTypeOptional } from '../utils/coerceType.js';
11
13
  import { globalRivetNodeRegistry } from './Nodes.js';
12
14
  import { getPluginConfig } from '../utils/index.js';
13
15
  import { GptTokenizerTokenizer } from '../integrations/GptTokenizerTokenizer.js';
16
+ // eslint-disable-next-line import/no-cycle -- There has to be a cycle because CodeRunner needs to import the entirety of Rivet
17
+ import { IsomorphicCodeRunner } from '../integrations/CodeRunner.js';
14
18
  // CJS compatibility, gets default.default for whatever reason
15
19
  let PQueue = PQueueImport;
16
20
  if (typeof PQueue !== 'function') {
@@ -23,28 +27,28 @@ export class GraphProcessor {
23
27
  #nodesById;
24
28
  #nodeInstances;
25
29
  #connections;
26
- #definitions;
27
30
  #emitter = new Emittery();
28
31
  #running = false;
29
32
  #isSubProcessor = false;
30
- #scc;
31
- #nodesNotInCycle;
32
33
  #externalFunctions = {};
33
34
  slowMode = false;
34
35
  #isPaused = false;
35
36
  #parent;
36
37
  #registry;
37
38
  id = nanoid();
39
+ #includeTrace = true;
38
40
  executor;
39
41
  /** If set, specifies the node(s) that the graph will run TO, instead of the nodes without any dependents. */
40
42
  runToNodeIds;
43
+ /** If set, specifies the node that the graph will run FROM, instead of the start nodes. Requires preloading data. */
44
+ runFromNodeId;
41
45
  /** The node that is executing this graph, almost always a subgraph node. Undefined for root. */
42
46
  #executor;
43
47
  /** The interval between nodeFinish events when playing back a recording. I.e. how fast the playback is. */
44
48
  recordingPlaybackChatLatency = 1000;
45
49
  warnOnInvalidGraph = false;
46
50
  // Per-process state
47
- #erroredNodes = undefined;
51
+ #erroredNodes = undefined; // Values are strings in recordings
48
52
  #remainingNodes = undefined;
49
53
  #visitedNodes = undefined;
50
54
  #currentlyProcessing = undefined;
@@ -68,13 +72,19 @@ export class GraphProcessor {
68
72
  #totalResponseTokens = 0;
69
73
  #totalCost = 0;
70
74
  #ignoreNodes = undefined;
75
+ #hasPreloadedData = false;
76
+ #loadedProjects = undefined;
77
+ #definitions = undefined;
78
+ #scc = undefined;
79
+ // @ts-expect-error
80
+ #nodesNotInCycle = undefined;
71
81
  #nodeAbortControllers = new Map();
72
82
  /** User input nodes that are pending user input. */
73
83
  #pendingUserInputs = undefined;
74
84
  get isRunning() {
75
85
  return this.#running;
76
86
  }
77
- constructor(project, graphId, registry) {
87
+ constructor(project, graphId, registry, includeTrace) {
78
88
  this.#project = project;
79
89
  const graph = graphId
80
90
  ? project.graphs[graphId]
@@ -85,11 +95,19 @@ export class GraphProcessor {
85
95
  throw new Error(`Graph ${graphId} not found in project`);
86
96
  }
87
97
  this.#graph = graph;
98
+ this.#includeTrace = includeTrace;
88
99
  this.#nodeInstances = {};
89
100
  this.#connections = {};
90
101
  this.#nodesById = {};
91
102
  this.#registry = registry ?? globalRivetNodeRegistry;
92
103
  this.#emitter.bindMethods(this, ['on', 'off', 'once', 'onAny', 'offAny']);
104
+ this.setExternalFunction('echo', async (value) => ({ type: 'any', value }));
105
+ this.#emitter.on('globalSet', ({ id, value }) => {
106
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
107
+ this.#emitter.emit(`globalSet:${id}`, value);
108
+ });
109
+ }
110
+ #preprocessGraph() {
93
111
  // Create node instances and store them in a lookup table
94
112
  for (const node of this.#graph.nodes) {
95
113
  this.#nodeInstances[node.id] = this.#registry.createDynamicImpl(node);
@@ -100,10 +118,10 @@ export class GraphProcessor {
100
118
  if (!this.#nodesById[conn.inputNodeId] || !this.#nodesById[conn.outputNodeId]) {
101
119
  if (this.warnOnInvalidGraph) {
102
120
  if (!this.#nodesById[conn.inputNodeId]) {
103
- console.warn(`Missing node ${conn.inputNodeId} in graph ${graphId} (connection from ${this.#nodesById[conn.outputNodeId]?.title})`);
121
+ console.warn(`Missing node ${conn.inputNodeId} in graph ${this.#graph} (connection from ${this.#nodesById[conn.outputNodeId]?.title})`);
104
122
  }
105
123
  else {
106
- console.warn(`Missing node ${conn.outputNodeId} in graph ${graphId} (connection to ${this.#nodesById[conn.inputNodeId]?.title}) `);
124
+ console.warn(`Missing node ${conn.outputNodeId} in graph ${this.#graph} (connection to ${this.#nodesById[conn.inputNodeId]?.title}) `);
107
125
  }
108
126
  }
109
127
  continue;
@@ -113,12 +131,17 @@ export class GraphProcessor {
113
131
  this.#connections[conn.inputNodeId].push(conn);
114
132
  this.#connections[conn.outputNodeId].push(conn);
115
133
  }
134
+ this.#loadInputOutputDefinitions();
135
+ this.#scc = this.#tarjanSCC();
136
+ this.#nodesNotInCycle = this.#scc.filter((cycle) => cycle.length === 1).flat();
137
+ }
138
+ #loadInputOutputDefinitions() {
116
139
  // Store input and output definitions in a lookup table
117
140
  this.#definitions = {};
118
141
  for (const node of this.#graph.nodes) {
119
142
  const connectionsForNode = this.#connections[node.id] ?? [];
120
- const inputDefs = this.#nodeInstances[node.id].getInputDefinitions(connectionsForNode, this.#nodesById, this.#project);
121
- const outputDefs = this.#nodeInstances[node.id].getOutputDefinitions(connectionsForNode, this.#nodesById, this.#project);
143
+ const inputDefs = this.#nodeInstances[node.id].getInputDefinitionsIncludingBuiltIn(connectionsForNode, this.#nodesById, this.#project, this.#loadedProjects);
144
+ const outputDefs = this.#nodeInstances[node.id].getOutputDefinitions(connectionsForNode, this.#nodesById, this.#project, this.#loadedProjects);
122
145
  this.#definitions[node.id] = { inputs: inputDefs, outputs: outputDefs };
123
146
  // Find all invalid connections to or from the node, then remove them from consideration
124
147
  const invalidConnections = connectionsForNode.filter((connection) => {
@@ -153,12 +176,12 @@ export class GraphProcessor {
153
176
  }
154
177
  }
155
178
  }
156
- this.#scc = this.#tarjanSCC();
157
- this.#nodesNotInCycle = this.#scc.filter((cycle) => cycle.length === 1).flat();
158
- this.setExternalFunction('echo', async (value) => ({ type: 'any', value }));
159
- this.#emitter.on('globalSet', ({ id, value }) => {
160
- this.#emitter.emit(`globalSet:${id}`, value);
161
- });
179
+ }
180
+ #emitTraceEvent(eventData) {
181
+ if (this.#includeTrace) {
182
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
183
+ this.#emitter.emit('trace', eventData);
184
+ }
162
185
  }
163
186
  on = undefined;
164
187
  off = undefined;
@@ -199,8 +222,10 @@ export class GraphProcessor {
199
222
  this.#abortController.abort();
200
223
  this.#abortSuccessfully = successful;
201
224
  this.#abortError = error;
225
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
202
226
  this.#emitter.emit('graphAbort', { successful, error, graph: this.#graph });
203
227
  if (!this.#isSubProcessor) {
228
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
204
229
  this.#emitter.emit('abort', { successful, error });
205
230
  }
206
231
  await this.#processingQueue.onIdle();
@@ -208,12 +233,14 @@ export class GraphProcessor {
208
233
  pause() {
209
234
  if (this.#isPaused === false) {
210
235
  this.#isPaused = true;
236
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
211
237
  this.#emitter.emit('pause', void 0);
212
238
  }
213
239
  }
214
240
  resume() {
215
241
  if (this.#isPaused) {
216
242
  this.#isPaused = false;
243
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
217
244
  this.#emitter.emit('resume', void 0);
218
245
  }
219
246
  }
@@ -234,6 +261,38 @@ export class GraphProcessor {
234
261
  }
235
262
  }
236
263
  }
264
+ preloadNodeData(nodeId, data) {
265
+ this.#nodeResults ??= new Map();
266
+ this.#visitedNodes ??= new Set();
267
+ for (const value of Object.values(data)) {
268
+ if (!value || !('type' in value) || !value.type) {
269
+ throw new Error(`Invalid data value for node ${nodeId}, must be a DataValue`);
270
+ }
271
+ }
272
+ this.#nodeResults.set(nodeId, data);
273
+ this.#visitedNodes.add(nodeId);
274
+ this.#hasPreloadedData = true;
275
+ }
276
+ /** Gets all node IDs that a given node ID depends on being complete before the given node ID can start. */
277
+ getDependencyNodesDeep(nodeId) {
278
+ const node = this.#nodesById[nodeId];
279
+ if (!node) {
280
+ return [];
281
+ }
282
+ const connections = this.#connections[nodeId];
283
+ if (!connections) {
284
+ return [];
285
+ }
286
+ const dependencyNodes = connections
287
+ .map((conn) => {
288
+ if (conn.inputNodeId === nodeId) {
289
+ return this.getDependencyNodesDeep(conn.outputNodeId);
290
+ }
291
+ return [];
292
+ })
293
+ .flat();
294
+ return [...new Set([nodeId, ...dependencyNodes])];
295
+ }
237
296
  async replayRecording(recorder) {
238
297
  const { events } = recorder;
239
298
  this.#initProcessState();
@@ -265,6 +324,7 @@ export class GraphProcessor {
265
324
  await this.#waitUntilUnpaused();
266
325
  await match(event)
267
326
  .with({ type: 'start' }, ({ data }) => {
327
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
268
328
  this.#emitter.emit('start', {
269
329
  project: this.#project,
270
330
  contextValues: data.contextValues,
@@ -275,43 +335,52 @@ export class GraphProcessor {
275
335
  this.#graphInputs = data.inputs;
276
336
  })
277
337
  .with({ type: 'abort' }, ({ data }) => {
338
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
278
339
  this.#emitter.emit('abort', data);
279
340
  })
280
341
  .with({ type: 'pause' }, () => { })
281
342
  .with({ type: 'resume' }, () => { })
282
343
  .with({ type: 'done' }, ({ data }) => {
344
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
283
345
  this.#emitter.emit('done', data);
284
346
  this.#graphOutputs = data.results;
285
347
  this.#running = false;
286
348
  })
287
349
  .with({ type: 'error' }, ({ data }) => {
350
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
288
351
  this.#emitter.emit('error', data);
289
352
  })
290
353
  .with({ type: 'globalSet' }, ({ data }) => {
354
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
291
355
  this.#emitter.emit('globalSet', data);
292
356
  })
293
357
  .with({ type: 'trace' }, ({ data }) => {
358
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
294
359
  this.#emitter.emit('trace', data);
295
360
  })
296
361
  .with({ type: 'graphStart' }, ({ data }) => {
362
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
297
363
  this.#emitter.emit('graphStart', {
298
364
  graph: getGraph(data.graphId),
299
365
  inputs: data.inputs,
300
366
  });
301
367
  })
302
368
  .with({ type: 'graphFinish' }, ({ data }) => {
369
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
303
370
  this.#emitter.emit('graphFinish', {
304
371
  graph: getGraph(data.graphId),
305
372
  outputs: data.outputs,
306
373
  });
307
374
  })
308
375
  .with({ type: 'graphError' }, ({ data }) => {
376
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
309
377
  this.#emitter.emit('graphError', {
310
378
  graph: getGraph(data.graphId),
311
379
  error: data.error,
312
380
  });
313
381
  })
314
382
  .with({ type: 'graphAbort' }, ({ data }) => {
383
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
315
384
  this.#emitter.emit('graphAbort', {
316
385
  graph: getGraph(data.graphId),
317
386
  error: data.error,
@@ -320,6 +389,7 @@ export class GraphProcessor {
320
389
  })
321
390
  .with({ type: 'nodeStart' }, async ({ data }) => {
322
391
  const node = getNode(data.nodeId);
392
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
323
393
  this.#emitter.emit('nodeStart', {
324
394
  node: getNode(data.nodeId),
325
395
  inputs: data.inputs,
@@ -332,6 +402,7 @@ export class GraphProcessor {
332
402
  })
333
403
  .with({ type: 'nodeFinish' }, ({ data }) => {
334
404
  const node = getNode(data.nodeId);
405
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
335
406
  this.#emitter.emit('nodeFinish', {
336
407
  node,
337
408
  outputs: data.outputs,
@@ -341,6 +412,7 @@ export class GraphProcessor {
341
412
  this.#visitedNodes.add(data.nodeId);
342
413
  })
343
414
  .with({ type: 'nodeError' }, ({ data }) => {
415
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
344
416
  this.#emitter.emit('nodeError', {
345
417
  node: getNode(data.nodeId),
346
418
  error: data.error,
@@ -350,6 +422,7 @@ export class GraphProcessor {
350
422
  this.#visitedNodes.add(data.nodeId);
351
423
  })
352
424
  .with({ type: 'nodeExcluded' }, ({ data }) => {
425
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
353
426
  this.#emitter.emit('nodeExcluded', {
354
427
  node: getNode(data.nodeId),
355
428
  processId: data.processId,
@@ -362,28 +435,35 @@ export class GraphProcessor {
362
435
  .with({ type: 'nodeOutputsCleared' }, () => { })
363
436
  .with({ type: 'partialOutput' }, () => { })
364
437
  .with({ type: 'userInput' }, ({ data }) => {
438
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
365
439
  this.#emitter.emit('userInput', {
366
440
  callback: undefined,
441
+ inputStrings: data.inputStrings,
367
442
  inputs: data.inputs,
368
443
  node: getNode(data.nodeId),
369
444
  processId: data.processId,
445
+ renderingType: data.renderingType,
370
446
  });
371
447
  })
372
448
  .with({ type: P.string.startsWith('globalSet:') }, ({ type, data }) => {
449
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
373
450
  this.#emitter.emit(type, data);
374
451
  })
375
452
  .with({ type: P.string.startsWith('userEvent:') }, ({ type, data }) => {
453
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
376
454
  this.#emitter.emit(type, data);
377
455
  })
378
456
  .with({ type: 'newAbortController' }, () => { })
379
457
  .with({ type: 'finish' }, () => {
458
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
380
459
  this.#emitter.emit('finish', undefined);
381
460
  })
382
- // .with(undefined, () => {})
461
+ .with(P.nullish, () => { })
383
462
  .exhaustive();
384
463
  }
385
464
  }
386
465
  catch (err) {
466
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
387
467
  this.#emitter.emit('error', { error: getError(err) });
388
468
  }
389
469
  finally {
@@ -393,9 +473,11 @@ export class GraphProcessor {
393
473
  }
394
474
  #initProcessState() {
395
475
  this.#running = true;
396
- this.#nodeResults = new Map();
476
+ if (!this.#hasPreloadedData) {
477
+ this.#nodeResults = new Map();
478
+ this.#visitedNodes = new Set();
479
+ }
397
480
  this.#erroredNodes = new Map();
398
- this.#visitedNodes = new Set();
399
481
  this.#currentlyProcessing = new Set();
400
482
  this.#remainingNodes = new Set(this.#graph.nodes.map((n) => n.id));
401
483
  this.#pendingUserInputs = {};
@@ -416,6 +498,7 @@ export class GraphProcessor {
416
498
  this.#abortError = undefined;
417
499
  this.#abortSuccessfully = false;
418
500
  this.#nodeAbortControllers = new Map();
501
+ this.#loadedProjects = {};
419
502
  }
420
503
  /** Main function for running a graph. Runs a graph and returns the outputs from the output nodes of the graph. */
421
504
  async processGraph(
@@ -435,27 +518,49 @@ export class GraphProcessor {
435
518
  this.#contextValues ??= contextValues;
436
519
  if (this.#context.tokenizer) {
437
520
  this.#context.tokenizer.on('error', (error) => {
521
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
438
522
  this.#emitter.emit('error', { error });
439
523
  });
440
524
  }
525
+ await this.#loadProjectReferences();
526
+ this.#preprocessGraph();
441
527
  if (!this.#isSubProcessor) {
442
- this.#emitter.emit('start', {
528
+ await this.#emitter.emit('start', {
443
529
  contextValues: this.#contextValues,
444
530
  inputs: this.#graphInputs,
445
531
  project: this.#project,
446
532
  startGraph: this.#graph,
447
533
  });
448
534
  }
449
- this.#emitter.emit('graphStart', { graph: this.#graph, inputs: this.#graphInputs });
535
+ await this.#emitter.emit('graphStart', { graph: this.#graph, inputs: this.#graphInputs });
536
+ if (this.#hasPreloadedData) {
537
+ for (const node of this.#graph.nodes) {
538
+ if (this.#nodeResults.has(node.id)) {
539
+ this.#emitTraceEvent(`Node ${node.title} has preloaded data`);
540
+ await this.#emitter.emit('nodeStart', {
541
+ node,
542
+ inputs: {},
543
+ processId: 'preload',
544
+ });
545
+ await this.#emitter.emit('nodeFinish', {
546
+ node,
547
+ outputs: this.#nodeResults.get(node.id),
548
+ processId: 'preload',
549
+ });
550
+ }
551
+ }
552
+ }
450
553
  const startNodes = this.runToNodeIds
451
554
  ? this.#graph.nodes.filter((node) => this.runToNodeIds?.includes(node.id))
452
555
  : this.#graph.nodes.filter((node) => this.#outputNodesFrom(node).nodes.length === 0);
453
556
  await this.#waitUntilUnpaused();
454
557
  for (const startNode of startNodes) {
558
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
455
559
  this.#processingQueue.add(async () => {
456
560
  await this.#fetchNodeDataAndProcessNode(startNode);
457
561
  });
458
562
  }
563
+ await this.#processingQueue.onIdle();
459
564
  // Anything not queued at this phase here should be ignored
460
565
  if (this.runToNodeIds) {
461
566
  // For safety, we'll only activate this if runToNodeIds is set, in case there are bugs in the first pass
@@ -465,20 +570,28 @@ export class GraphProcessor {
465
570
  }
466
571
  }
467
572
  }
468
- await this.#processingQueue.onIdle();
469
573
  // If we've aborted successfully, we can treat the graph like it succeeded
470
574
  const erroredNodes = [...this.#erroredNodes.entries()].filter(([nodeId]) => {
471
575
  const erroredNodeAttachedData = this.#getAttachedDataTo(nodeId);
472
576
  return erroredNodeAttachedData.races == null || erroredNodeAttachedData.races.completed === false;
473
577
  });
474
578
  if (erroredNodes.length && !this.#abortSuccessfully) {
475
- const error = this.#abortError ??
476
- Error(`Graph ${this.#graph.metadata.name} (${this.#graph.metadata.id}) failed to process due to errors in nodes: ${erroredNodes
477
- .map(([nodeId, error]) => `${this.#nodesById[nodeId].title} (${nodeId}): ${error}`)
478
- .join(', ')}`);
479
- this.#emitter.emit('graphError', { graph: this.#graph, error });
579
+ let error = this.#abortError;
580
+ if (!error) {
581
+ const message = `Graph ${this.#graph.metadata.name} (${this.#graph.metadata.id}) failed to process due to errors in nodes:\n${erroredNodes
582
+ .map(([nodeId]) => `- ${this.#nodesById[nodeId].title} (${nodeId})`)
583
+ .join('\n')}`;
584
+ if (erroredNodes.length === 1) {
585
+ const [, nodeError] = erroredNodes[0];
586
+ error = new Error(message, { cause: nodeError });
587
+ }
588
+ else {
589
+ error = new AggregateError(erroredNodes.map(([, nodeError]) => nodeError), message);
590
+ }
591
+ }
592
+ await this.#emitter.emit('graphError', { graph: this.#graph, error });
480
593
  if (!this.#isSubProcessor) {
481
- this.#emitter.emit('error', { error });
594
+ await this.#emitter.emit('error', { error });
482
595
  }
483
596
  throw error;
484
597
  }
@@ -502,17 +615,39 @@ export class GraphProcessor {
502
615
  }
503
616
  const outputValues = this.#graphOutputs;
504
617
  this.#running = false;
505
- this.#emitter.emit('graphFinish', { graph: this.#graph, outputs: outputValues });
618
+ await this.#emitter.emit('graphFinish', { graph: this.#graph, outputs: outputValues });
506
619
  if (!this.#isSubProcessor) {
507
- this.#emitter.emit('done', { results: outputValues });
508
- this.#emitter.emit('finish', undefined);
620
+ await this.#emitter.emit('done', { results: outputValues });
621
+ await this.#emitter.emit('finish', undefined);
509
622
  }
510
623
  return outputValues;
511
624
  }
512
625
  finally {
513
626
  this.#running = false;
514
627
  if (!this.#isSubProcessor) {
515
- this.#emitter.emit('finish', undefined);
628
+ await this.#emitter.emit('finish', undefined);
629
+ }
630
+ }
631
+ }
632
+ async #loadProjectReferences() {
633
+ if ((this.#project.references?.length ?? 0) > 0) {
634
+ if (!this.#context.projectReferenceLoader) {
635
+ throw new Error('Project references are set, but no projectReferenceLoader is set in the context. Since this project uses project references, you must provide a projectReferenceLoader in the context.');
636
+ }
637
+ const seenProjectIds = new Set();
638
+ const loadProject = async (ref) => {
639
+ if (seenProjectIds.has(ref.id)) {
640
+ return;
641
+ }
642
+ seenProjectIds.add(ref.id);
643
+ const project = await this.#context.projectReferenceLoader.loadProject(this.#context.projectPath, ref);
644
+ this.#loadedProjects[project.metadata.id] = project;
645
+ for (const reference of project.references ?? []) {
646
+ await loadProject(reference);
647
+ }
648
+ };
649
+ for (const reference of this.#project.references) {
650
+ await loadProject(reference);
516
651
  }
517
652
  }
518
653
  }
@@ -539,7 +674,7 @@ export class GraphProcessor {
539
674
  if (!inputsReady) {
540
675
  return;
541
676
  }
542
- this.#emitter.emit('trace', `Node ${node.title} has required inputs nodes: ${inputNodes.map((n) => n.title).join(', ')}`);
677
+ this.#emitTraceEvent(`Node ${node.title} has required inputs nodes: ${inputNodes.map((n) => n.title).join(', ')}`);
543
678
  const attachedData = this.#getAttachedDataTo(node);
544
679
  if (node.type === 'raceInputs' || attachedData.races) {
545
680
  for (const inputNode of inputNodes) {
@@ -556,9 +691,10 @@ export class GraphProcessor {
556
691
  }
557
692
  }
558
693
  this.#queuedNodes.add(node.id);
694
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
559
695
  this.#processingQueue.addAll(inputNodes.map((inputNode) => {
560
696
  return async () => {
561
- this.#emitter.emit('trace', `Fetching required data for node ${inputNode.title} (${inputNode.id})`);
697
+ this.#emitTraceEvent(`Fetching required data for node ${inputNode.title} (${inputNode.id})`);
562
698
  await this.#fetchNodeDataAndProcessNode(inputNode);
563
699
  };
564
700
  }));
@@ -568,27 +704,34 @@ export class GraphProcessor {
568
704
  async #processNodeIfAllInputsAvailable(node) {
569
705
  const builtInNode = node;
570
706
  if (this.#ignoreNodes.has(node.id)) {
571
- this.#emitter.emit('trace', `Node ${node.title} is ignored`);
707
+ this.#emitTraceEvent(`Node ${node.title} is ignored`);
572
708
  return;
573
709
  }
710
+ if (this.runToNodeIds) {
711
+ const dependencyNodes = this.getDependencyNodesDeep(node.id);
712
+ if (this.runToNodeIds.some((runTo) => runTo !== node.id && dependencyNodes.includes(runTo))) {
713
+ this.#emitTraceEvent(`Node ${node.title} is excluded due to runToNodeIds`);
714
+ return;
715
+ }
716
+ }
574
717
  if (this.#currentlyProcessing.has(node.id)) {
575
- this.#emitter.emit('trace', `Node ${node.title} is already being processed`);
718
+ this.#emitTraceEvent(`Node ${node.title} is already being processed`);
576
719
  return;
577
720
  }
578
721
  // For a loop controller, it can run multiple times, otherwise we already processed this node so bail out
579
722
  if (this.#visitedNodes.has(node.id) && node.type !== 'loopController') {
580
- this.#emitter.emit('trace', `Node ${node.title} has already been processed`);
723
+ this.#emitTraceEvent(`Node ${node.title} has already been processed`);
581
724
  return;
582
725
  }
583
726
  if (this.#erroredNodes.has(node.id)) {
584
- this.#emitter.emit('trace', `Node ${node.title} has already errored`);
727
+ this.#emitTraceEvent(`Node ${node.title} has already errored`);
585
728
  return;
586
729
  }
587
730
  const inputNodes = this.#inputNodesTo(node);
588
731
  // Check if all input nodes are free of errors
589
732
  for (const inputNode of inputNodes) {
590
733
  if (this.#erroredNodes.has(inputNode.id)) {
591
- this.#emitter.emit('trace', `Node ${node.title} has errored input node ${inputNode.title}`);
734
+ this.#emitTraceEvent(`Node ${node.title} has errored input node ${inputNode.title}`);
592
735
  return;
593
736
  }
594
737
  }
@@ -599,13 +742,13 @@ export class GraphProcessor {
599
742
  return connectionToInput || !input.required;
600
743
  });
601
744
  if (!inputsReady) {
602
- this.#emitter.emit('trace', `Node ${node.title} has required inputs nodes: ${inputNodes.map((n) => n.title).join(', ')}`);
745
+ this.#emitTraceEvent(`Node ${node.title} has required inputs nodes: ${inputNodes.map((n) => n.title).join(', ')}`);
603
746
  return;
604
747
  }
605
748
  // Excluded because control flow is still in a loop - difference between "will not execute" and "has not executed yet"
606
749
  const inputValues = this.#getInputValuesForNode(node);
607
750
  if (this.#excludedDueToControlFlow(node, inputValues, nanoid(), 'loop-not-broken')) {
608
- this.#emitter.emit('trace', `Node ${node.title} is excluded due to control flow`);
751
+ this.#emitTraceEvent(`Node ${node.title} is excluded due to control flow`);
609
752
  return;
610
753
  }
611
754
  let waitingForInputNode = false;
@@ -628,7 +771,7 @@ export class GraphProcessor {
628
771
  }
629
772
  }
630
773
  if (waitingForInputNode) {
631
- this.#emitter.emit('trace', `Node ${node.title} is waiting for input node ${waitingForInputNode}`);
774
+ this.#emitTraceEvent(`Node ${node.title} is waiting for input node ${waitingForInputNode}`);
632
775
  return;
633
776
  }
634
777
  this.#currentlyProcessing.add(node.id);
@@ -640,14 +783,14 @@ export class GraphProcessor {
640
783
  attachedData.loopInfo.nodes.add(node.id);
641
784
  }
642
785
  if (attachedData.races?.completed) {
643
- this.#emitter.emit('trace', `Node ${node.title} is part of a race that was completed`);
786
+ this.#emitTraceEvent(`Node ${node.title} is part of a race that was completed`);
644
787
  return;
645
788
  }
646
789
  const processId = await this.#processNode(node);
647
790
  if (this.slowMode) {
648
791
  await new Promise((resolve) => setTimeout(resolve, 250));
649
792
  }
650
- this.#emitter.emit('trace', `Finished processing node ${node.title} (${node.id})`);
793
+ this.#emitTraceEvent(`Finished processing node ${node.title} (${node.id})`);
651
794
  this.#visitedNodes.add(node.id);
652
795
  this.#currentlyProcessing.delete(node.id);
653
796
  this.#remainingNodes.delete(node.id);
@@ -657,13 +800,15 @@ export class GraphProcessor {
657
800
  const loopControllerResults = this.#nodeResults.get(node.id);
658
801
  // If the loop controller is excluded, we have to "break" it or else it'll loop forever...
659
802
  const breakValue = loopControllerResults['break'];
660
- const didBreak = !(breakValue?.type === 'control-flow-excluded' && breakValue?.value === 'loop-not-broken') ??
803
+ const didBreak =
804
+ // @ts-ignore
805
+ !(breakValue?.type === 'control-flow-excluded' && breakValue?.value === 'loop-not-broken') ??
661
806
  this.#excludedDueToControlFlow(node, this.#getInputValuesForNode(node), nanoid());
662
807
  if (!didBreak) {
663
- this.#emitter.emit('trace', `Loop controller ${node.title} did not break, so we're looping again`);
808
+ this.#emitTraceEvent(`Loop controller ${node.title} did not break, so we're looping again`);
664
809
  for (const loopNodeId of attachedData.loopInfo?.nodes ?? []) {
665
810
  const cycleNode = this.#nodesById[loopNodeId];
666
- this.#emitter.emit('trace', `Clearing cycle node ${cycleNode.title} (${cycleNode.id})`);
811
+ this.#emitTraceEvent(`Clearing cycle node ${cycleNode.title} (${cycleNode.id})`);
667
812
  this.#visitedNodes.delete(cycleNode.id);
668
813
  this.#currentlyProcessing.delete(cycleNode.id);
669
814
  this.#remainingNodes.add(cycleNode.id);
@@ -678,7 +823,7 @@ export class GraphProcessor {
678
823
  for (const [nodeId] of allNodesForRace) {
679
824
  for (const [key, abortController] of this.#nodeAbortControllers.entries()) {
680
825
  if (key.startsWith(nodeId)) {
681
- this.#emitter.emit('trace', `Aborting node ${nodeId} because other race branch won`);
826
+ this.#emitTraceEvent(`Aborting node ${nodeId} because other race branch won`);
682
827
  abortController.abort();
683
828
  }
684
829
  }
@@ -727,8 +872,9 @@ export class GraphProcessor {
727
872
  }
728
873
  }
729
874
  // Node is finished, check if we can run any more nodes that depend on this one
875
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
730
876
  this.#processingQueue.addAll(outputNodes.nodes.map((outputNode) => async () => {
731
- this.#emitter.emit('trace', `Trying to run output node from ${node.title}: ${outputNode.title} (${outputNode.id})`);
877
+ this.#emitTraceEvent(`Trying to run output node from ${node.title}: ${outputNode.title} (${outputNode.id})`);
732
878
  await this.#processNodeIfAllInputsAvailable(outputNode);
733
879
  }));
734
880
  }
@@ -756,10 +902,7 @@ export class GraphProcessor {
756
902
  this.#nodeErrored(node, error, processId);
757
903
  return processId;
758
904
  }
759
- if (this.#isNodeOfType('userInput', node)) {
760
- await this.#processUserInputNode(node, processId);
761
- }
762
- else if (node.isSplitRun) {
905
+ if (node.isSplitRun) {
763
906
  await this.#processSplitRunNode(node, processId);
764
907
  }
765
908
  else {
@@ -767,50 +910,13 @@ export class GraphProcessor {
767
910
  }
768
911
  return processId;
769
912
  }
770
- #isNodeOfType(type, node) {
771
- return node.type === type;
772
- }
773
- async #processUserInputNode(node, processId) {
774
- try {
775
- const inputValues = this.#getInputValuesForNode(node);
776
- if (this.#excludedDueToControlFlow(node, inputValues, processId)) {
777
- return;
778
- }
779
- this.#emitter.emit('nodeStart', { node, inputs: inputValues, processId });
780
- const results = await new Promise((resolve, reject) => {
781
- this.#pendingUserInputs[node.id] = {
782
- resolve,
783
- reject,
784
- };
785
- this.#abortController.signal.addEventListener('abort', () => {
786
- delete this.#pendingUserInputs[node.id];
787
- reject(new Error('Processing aborted'));
788
- });
789
- this.#emitter.emit('userInput', {
790
- node,
791
- inputs: inputValues,
792
- callback: (results) => {
793
- resolve(results);
794
- delete this.#pendingUserInputs[node.id];
795
- },
796
- processId,
797
- });
798
- });
799
- const outputValues = this.#nodeInstances[node.id].getOutputValuesFromUserInput(inputValues, results);
800
- this.#nodeResults.set(node.id, outputValues);
801
- this.#visitedNodes.add(node.id);
802
- this.#emitter.emit('nodeFinish', { node, outputs: outputValues, processId });
803
- }
804
- catch (e) {
805
- this.#nodeErrored(node, e, processId);
806
- }
807
- }
808
913
  async #processSplitRunNode(node, processId) {
809
914
  const inputValues = this.#getInputValuesForNode(node);
810
915
  if (this.#excludedDueToControlFlow(node, inputValues, processId)) {
811
916
  return;
812
917
  }
813
918
  const splittingAmount = Math.min(max(values(inputValues).map((value) => (Array.isArray(value?.value) ? value?.value.length : 1))) ?? 1, node.splitRunMax ?? 10);
919
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
814
920
  this.#emitter.emit('nodeStart', { node, inputs: inputValues, processId });
815
921
  try {
816
922
  let results = [];
@@ -824,7 +930,10 @@ export class GraphProcessor {
824
930
  isArrayDataValue(value) ? arrayizeDataValue(value)[i] ?? undefined : value,
825
931
  ]));
826
932
  try {
827
- const output = await this.#processNodeWithInputData(node, inputs, i, processId, (node, partialOutputs, index) => this.#emitter.emit('partialOutput', { node, outputs: partialOutputs, index, processId }));
933
+ const output = await this.#processNodeWithInputData(node, inputs, i, processId, (node, partialOutputs, index) => {
934
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
935
+ this.#emitter.emit('partialOutput', { node, outputs: partialOutputs, index, processId });
936
+ });
828
937
  if (output['requestTokens']?.type === 'number') {
829
938
  this.#totalRequestTokens += coerceTypeOptional(output['requestTokens'], 'number') ?? 0;
830
939
  }
@@ -848,7 +957,10 @@ export class GraphProcessor {
848
957
  isArrayDataValue(value) ? arrayizeDataValue(value)[i] ?? undefined : value,
849
958
  ]));
850
959
  try {
851
- const output = await this.#processNodeWithInputData(node, inputs, i, processId, (node, partialOutputs, index) => this.#emitter.emit('partialOutput', { node, outputs: partialOutputs, index, processId }));
960
+ const output = await this.#processNodeWithInputData(node, inputs, i, processId, (node, partialOutputs, index) => {
961
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
962
+ this.#emitter.emit('partialOutput', { node, outputs: partialOutputs, index, processId });
963
+ });
852
964
  if (output['requestTokens']?.type === 'number') {
853
965
  this.#totalRequestTokens += coerceTypeOptional(output['requestTokens'], 'number') ?? 0;
854
966
  }
@@ -871,7 +983,7 @@ export class GraphProcessor {
871
983
  throw e;
872
984
  }
873
985
  else if (errors.length > 0) {
874
- throw new Error(errors.join('\n'));
986
+ throw new AggregateError(errors);
875
987
  }
876
988
  // Combine the parallel results into the final output
877
989
  // Turn a Record<PortId, DataValue[]> into a Record<PortId, AnyArrayDataValue>
@@ -887,6 +999,7 @@ export class GraphProcessor {
887
999
  this.#totalRequestTokens += sum(results.map((r) => coerceTypeOptional(r.output?.['requestTokens'], 'number') ?? 0));
888
1000
  this.#totalResponseTokens += sum(results.map((r) => coerceTypeOptional(r.output?.['responseTokens'], 'number') ?? 0));
889
1001
  this.#totalCost += sum(results.map((r) => coerceTypeOptional(r.output?.['cost'], 'number') ?? 0));
1002
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
890
1003
  this.#emitter.emit('nodeFinish', { node, outputs: aggregateResults, processId });
891
1004
  }
892
1005
  catch (error) {
@@ -898,9 +1011,13 @@ export class GraphProcessor {
898
1011
  if (this.#excludedDueToControlFlow(node, inputValues, processId)) {
899
1012
  return;
900
1013
  }
1014
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
901
1015
  this.#emitter.emit('nodeStart', { node, inputs: inputValues, processId });
902
1016
  try {
903
- const outputValues = await this.#processNodeWithInputData(node, inputValues, 0, processId, (node, partialOutputs, index) => this.#emitter.emit('partialOutput', { node, outputs: partialOutputs, index, processId }));
1017
+ const outputValues = await this.#processNodeWithInputData(node, inputValues, 0, processId, (node, partialOutputs, index) => {
1018
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1019
+ this.#emitter.emit('partialOutput', { node, outputs: partialOutputs, index, processId });
1020
+ });
904
1021
  this.#nodeResults.set(node.id, outputValues);
905
1022
  this.#visitedNodes.add(node.id);
906
1023
  if (outputValues['requestTokens']?.type === 'number') {
@@ -912,6 +1029,7 @@ export class GraphProcessor {
912
1029
  if (outputValues['cost']?.type === 'number') {
913
1030
  this.#totalCost += coerceTypeOptional(outputValues['cost'], 'number') ?? 0;
914
1031
  }
1032
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
915
1033
  this.#emitter.emit('nodeFinish', { node, outputs: outputValues, processId });
916
1034
  }
917
1035
  catch (error) {
@@ -920,9 +1038,10 @@ export class GraphProcessor {
920
1038
  }
921
1039
  #nodeErrored(node, e, processId) {
922
1040
  const error = getError(e);
1041
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
923
1042
  this.#emitter.emit('nodeError', { node, error, processId });
924
- this.#emitter.emit('trace', `Node ${node.title} (${node.id}-${processId}) errored: ${error.stack}`);
925
- this.#erroredNodes.set(node.id, error.toString());
1043
+ this.#emitTraceEvent(`Node ${node.title} (${node.id}-${processId}) errored: ${error.stack}`);
1044
+ this.#erroredNodes.set(node.id, error);
926
1045
  }
927
1046
  getRootProcessor() {
928
1047
  let processor = this;
@@ -933,6 +1052,7 @@ export class GraphProcessor {
933
1052
  }
934
1053
  /** Raise a user event on the processor, all subprocessors, and their children. */
935
1054
  raiseEvent(event, data) {
1055
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
936
1056
  this.#emitter.emit(`userEvent:${event}`, data);
937
1057
  for (const subprocessor of this.#subprocessors) {
938
1058
  subprocessor.raiseEvent(event, data);
@@ -940,6 +1060,7 @@ export class GraphProcessor {
940
1060
  }
941
1061
  #newAbortController() {
942
1062
  const controller = new AbortController();
1063
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
943
1064
  this.#emitter.emit('newAbortController', controller);
944
1065
  return controller;
945
1066
  }
@@ -955,7 +1076,10 @@ export class GraphProcessor {
955
1076
  let tokenizer = this.#context.tokenizer;
956
1077
  if (!tokenizer) {
957
1078
  tokenizer = new GptTokenizerTokenizer();
958
- tokenizer.on('error', (e) => this.#emitter.emit('error', { error: e }));
1079
+ tokenizer.on('error', (e) => {
1080
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1081
+ this.#emitter.emit('error', { error: e });
1082
+ });
959
1083
  }
960
1084
  const context = {
961
1085
  ...this.#context,
@@ -967,6 +1091,8 @@ export class GraphProcessor {
967
1091
  graphInputs: this.#graphInputs,
968
1092
  graphOutputs: this.#graphOutputs,
969
1093
  attachedData: this.#getAttachedDataTo(node),
1094
+ codeRunner: this.#context.codeRunner ?? new IsomorphicCodeRunner(),
1095
+ referencedProjects: this.#loadedProjects,
970
1096
  waitEvent: async (event) => {
971
1097
  return new Promise((resolve, reject) => {
972
1098
  this.#emitter.once(`userEvent:${event}`).then(resolve).catch(reject);
@@ -986,6 +1112,7 @@ export class GraphProcessor {
986
1112
  if (useAsGraphPartialOutput && this.#executor && this.#parent) {
987
1113
  const executorNode = this.#parent.#nodesById[this.#executor.nodeId];
988
1114
  if (executorNode) {
1115
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
989
1116
  this.#emitter.emit('partialOutput', {
990
1117
  index: this.#executor.index,
991
1118
  node: executorNode,
@@ -1000,6 +1127,7 @@ export class GraphProcessor {
1000
1127
  getGlobal: (id) => this.#globals.get(id),
1001
1128
  setGlobal: (id, value) => {
1002
1129
  this.#globals.set(id, value);
1130
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1003
1131
  this.#emitter.emit('globalSet', { id, value, processId });
1004
1132
  },
1005
1133
  waitForGlobal: async (id) => {
@@ -1009,8 +1137,8 @@ export class GraphProcessor {
1009
1137
  await this.getRootProcessor().#emitter.once(`globalSet:${id}`);
1010
1138
  return this.#globals.get(id);
1011
1139
  },
1012
- createSubProcessor: (subGraphId, { signal } = {}) => {
1013
- const processor = new GraphProcessor(this.#project, subGraphId, this.#registry);
1140
+ createSubProcessor: (subGraphId, { signal, project } = {}) => {
1141
+ const processor = new GraphProcessor(project ?? this.#project, subGraphId, this.#registry);
1014
1142
  processor.executor = this.executor;
1015
1143
  processor.#isSubProcessor = true;
1016
1144
  processor.#executionCache = this.#executionCache;
@@ -1033,6 +1161,7 @@ export class GraphProcessor {
1033
1161
  processor.on('graphStart', (e) => this.#emitter.emit('graphStart', e));
1034
1162
  processor.on('graphFinish', (e) => this.#emitter.emit('graphFinish', e));
1035
1163
  processor.on('globalSet', (e) => this.#emitter.emit('globalSet', e));
1164
+ processor.on('newAbortController', (e) => this.#emitter.emit('newAbortController', e));
1036
1165
  processor.on('pause', () => {
1037
1166
  if (!this.#isPaused) {
1038
1167
  this.pause();
@@ -1045,26 +1174,59 @@ export class GraphProcessor {
1045
1174
  });
1046
1175
  processor.onAny((event, data) => {
1047
1176
  if (event.startsWith('globalSet:')) {
1177
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1048
1178
  this.#emitter.emit(event, data);
1049
1179
  }
1050
1180
  });
1051
1181
  this.#subprocessors.add(processor);
1052
1182
  if (signal) {
1053
- signal.addEventListener('abort', () => processor.abort());
1183
+ signal.addEventListener('abort', () => {
1184
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1185
+ processor.abort();
1186
+ });
1054
1187
  }
1055
1188
  // If parent is aborted, abort subgraph with error (it's fine, success state is on the parent)
1056
- this.#abortController.signal.addEventListener('abort', () => processor.abort());
1189
+ this.#abortController.signal.addEventListener('abort', () => {
1190
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1191
+ processor.abort();
1192
+ });
1057
1193
  this.on('pause', () => processor.pause());
1058
1194
  this.on('resume', () => processor.resume());
1059
1195
  return processor;
1060
1196
  },
1061
1197
  trace: (message) => {
1062
- this.#emitter.emit('trace', message);
1198
+ this.#emitTraceEvent(message);
1063
1199
  },
1064
1200
  abortGraph: (error) => {
1201
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1065
1202
  this.abort(error === undefined, error);
1066
1203
  },
1067
1204
  getPluginConfig: (name) => getPluginConfig(plugin, this.#context.settings, name),
1205
+ requestUserInput: async (inputStrings, renderingType) => {
1206
+ const results = await new Promise((resolve, reject) => {
1207
+ this.#pendingUserInputs[node.id] = {
1208
+ resolve,
1209
+ reject,
1210
+ };
1211
+ this.#abortController.signal.addEventListener('abort', () => {
1212
+ delete this.#pendingUserInputs[node.id];
1213
+ reject(new Error('Processing aborted'));
1214
+ });
1215
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1216
+ this.#emitter.emit('userInput', {
1217
+ node,
1218
+ inputStrings,
1219
+ inputs: inputValues,
1220
+ renderingType,
1221
+ callback: (results) => {
1222
+ resolve(results);
1223
+ delete this.#pendingUserInputs[node.id];
1224
+ },
1225
+ processId,
1226
+ });
1227
+ });
1228
+ return results;
1229
+ },
1068
1230
  };
1069
1231
  await this.#waitUntilUnpaused();
1070
1232
  const results = await instance.process(inputValues, context);
@@ -1077,11 +1239,20 @@ export class GraphProcessor {
1077
1239
  }
1078
1240
  #excludedDueToControlFlow(node, inputValues, processId, typeOfExclusion = undefined) {
1079
1241
  if (node.disabled) {
1080
- this.#emitter.emit('trace', `Excluding node ${node.title} because it's disabled`);
1242
+ this.#emitTraceEvent(`Excluding node ${node.title} because it's disabled`);
1081
1243
  this.#visitedNodes.add(node.id);
1082
1244
  this.#markAsExcluded(node, processId, inputValues, 'disabled');
1083
1245
  return true;
1084
1246
  }
1247
+ if (node.isConditional && typeOfExclusion === undefined) {
1248
+ const ifValue = coerceTypeOptional(inputValues[IF_PORT.id], 'boolean');
1249
+ if (ifValue === false) {
1250
+ this.#emitTraceEvent(`Excluding node ${node.title} because if port is false`);
1251
+ this.#visitedNodes.add(node.id);
1252
+ this.#markAsExcluded(node, processId, inputValues, 'if port is false');
1253
+ return true;
1254
+ }
1255
+ }
1085
1256
  const inputsWithValues = entries(inputValues);
1086
1257
  const controlFlowExcludedValues = inputsWithValues.filter(([, value]) => value &&
1087
1258
  getScalarTypeOf(value.type) === 'control-flow-excluded' &&
@@ -1100,7 +1271,7 @@ export class GraphProcessor {
1100
1271
  if (inputIsExcludedValue && !allowedToConsumedExcludedValue) {
1101
1272
  if (!isWaitingForLoop) {
1102
1273
  if (inputIsExcludedValue) {
1103
- this.#emitter.emit('trace', `Excluding node ${node.title} because of control flow. Input is has excluded value: ${controlFlowExcludedValues[0]?.[0]}`);
1274
+ this.#emitTraceEvent(`Excluding node ${node.title} because of control flow. Input is has excluded value: ${controlFlowExcludedValues[0]?.[0]}`);
1104
1275
  }
1105
1276
  this.#visitedNodes.add(node.id);
1106
1277
  this.#markAsExcluded(node, processId, inputValues, 'input is excluded value');
@@ -1119,6 +1290,7 @@ export class GraphProcessor {
1119
1290
  outputs['break'] = { type: 'control-flow-excluded', value: 'loop-not-broken' };
1120
1291
  }
1121
1292
  this.#nodeResults.set(node.id, outputs);
1293
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1122
1294
  this.#emitter.emit('nodeExcluded', {
1123
1295
  node,
1124
1296
  processId,
@@ -1223,9 +1395,6 @@ export class GraphProcessor {
1223
1395
  }
1224
1396
  return sccs;
1225
1397
  }
1226
- #nodeIsInCycle(nodeId) {
1227
- return this.#nodesNotInCycle.find((node) => node.id === nodeId) == null;
1228
- }
1229
1398
  #nodesAreInSameCycle(a, b) {
1230
1399
  return this.#scc.find((cycle) => cycle.find((node) => node.id === a) && cycle.find((node) => node.id === b));
1231
1400
  }