@flowgram.ai/runtime-js 0.2.25 → 0.2.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -345,7 +345,7 @@ var WorkflowRuntimeType;
345
345
  const expectedType = types[0];
346
346
  types.forEach((type) => {
347
347
  if (type !== expectedType) {
348
- throw new Error(`array items type must be same, expect ${expectedType}, but got ${type}`);
348
+ throw new Error(`Array items type must be same, expect ${expectedType}, but got ${type}`);
349
349
  }
350
350
  });
351
351
  return expectedType;
@@ -503,8 +503,8 @@ var validateObject = (value, schema, path) => {
503
503
  }
504
504
  }
505
505
  if (schema.properties) {
506
- for (const [propertyName, propertySchema] of Object.entries(schema.properties)) {
507
- const isRequired = propertySchema.isPropertyRequired === true;
506
+ for (const [propertyName] of Object.entries(schema.properties)) {
507
+ const isRequired = schema.required?.includes(propertyName) ?? false;
508
508
  if (isRequired && !(propertyName in objectValue)) {
509
509
  return {
510
510
  result: false,
@@ -614,7 +614,7 @@ var LoopExecutor = class {
614
614
  const subNodes = context.node.children;
615
615
  const blockStartNode = subNodes.find((node) => node.type === FlowGramNode.BlockStart);
616
616
  if (!blockStartNode) {
617
- throw new Error("block start node not found");
617
+ throw new Error("Loop block start node not found");
618
618
  }
619
619
  const blockOutputs = [];
620
620
  for (let index = 0; index < loopArray.length; index++) {
@@ -632,10 +632,14 @@ var LoopExecutor = class {
632
632
  type: WorkflowVariableType.Number,
633
633
  value: index
634
634
  });
635
- await engine.executeNode({
636
- context: subContext,
637
- node: blockStartNode
638
- });
635
+ try {
636
+ await engine.executeNode({
637
+ context: subContext,
638
+ node: blockStartNode
639
+ });
640
+ } catch (e) {
641
+ throw new Error(`Loop block execute error`);
642
+ }
639
643
  const blockOutput = this.getBlockOutput(context, subContext);
640
644
  blockOutputs.push(blockOutput);
641
645
  }
@@ -656,15 +660,15 @@ var LoopExecutor = class {
656
660
  checkLoopArray(LoopArrayVariable) {
657
661
  const loopArray = LoopArrayVariable?.value;
658
662
  if (!loopArray || (0, import_lodash_es.isNil)(loopArray) || !Array.isArray(loopArray)) {
659
- throw new Error("loopFor is required");
663
+ throw new Error('Loop "loopFor" is required');
660
664
  }
661
665
  const loopArrayType = LoopArrayVariable.type;
662
666
  if (loopArrayType !== WorkflowVariableType.Array) {
663
- throw new Error("loopFor must be an array");
667
+ throw new Error('Loop "loopFor" must be an array');
664
668
  }
665
669
  const loopArrayItemType = LoopArrayVariable.itemsType;
666
670
  if ((0, import_lodash_es.isNil)(loopArrayItemType)) {
667
- throw new Error("loopFor items must be array items");
671
+ throw new Error('Loop "loopFor.items" must be array items');
668
672
  }
669
673
  }
670
674
  getBlockOutput(executionContext, subContext) {
@@ -739,14 +743,24 @@ var LLMExecutor = class {
739
743
  apiKey,
740
744
  configuration: {
741
745
  baseURL: apiHost
742
- }
746
+ },
747
+ maxRetries: 3
743
748
  });
744
749
  const messages = [];
745
750
  if (systemPrompt) {
746
751
  messages.push(new import_messages.SystemMessage(systemPrompt));
747
752
  }
748
753
  messages.push(new import_messages.HumanMessage(prompt));
749
- const apiMessage = await model.invoke(messages);
754
+ let apiMessage;
755
+ try {
756
+ apiMessage = await model.invoke(messages);
757
+ } catch (error) {
758
+ const errorMessage = error?.message;
759
+ if (errorMessage === "Connection error.") {
760
+ throw new Error(`Network error: unreachable api "${apiHost}"`);
761
+ }
762
+ throw error;
763
+ }
750
764
  const result = apiMessage.content;
751
765
  return {
752
766
  outputs: {
@@ -757,13 +771,23 @@ var LLMExecutor = class {
757
771
  checkInputs(inputs) {
758
772
  const { modelName, temperature, apiKey, apiHost, prompt } = inputs;
759
773
  const missingInputs = [];
760
- if ((0, import_lodash_es2.isNil)(modelName)) missingInputs.push("modelName");
774
+ if (!modelName) missingInputs.push("modelName");
761
775
  if ((0, import_lodash_es2.isNil)(temperature)) missingInputs.push("temperature");
762
- if ((0, import_lodash_es2.isNil)(apiKey)) missingInputs.push("apiKey");
763
- if ((0, import_lodash_es2.isNil)(apiHost)) missingInputs.push("apiHost");
764
- if ((0, import_lodash_es2.isNil)(prompt)) missingInputs.push("prompt");
776
+ if (!apiKey) missingInputs.push("apiKey");
777
+ if (!apiHost) missingInputs.push("apiHost");
778
+ if (!prompt) missingInputs.push("prompt");
765
779
  if (missingInputs.length > 0) {
766
- throw new Error(`LLM node missing required inputs: ${missingInputs.join(", ")}`);
780
+ throw new Error(`LLM node missing required inputs: "${missingInputs.join('", "')}"`);
781
+ }
782
+ this.checkApiHost(apiHost);
783
+ }
784
+ checkApiHost(apiHost) {
785
+ if (!apiHost || typeof apiHost !== "string") {
786
+ throw new Error(`Invalid API host format - ${apiHost}`);
787
+ }
788
+ const url = new URL(apiHost);
789
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
790
+ throw new Error(`Invalid API host protocol - ${url.protocol}`);
767
791
  }
768
792
  }
769
793
  };
@@ -1058,7 +1082,7 @@ var ConditionExecutor = class {
1058
1082
  const parsedConditions = conditions.map((item) => this.parseCondition(item, context)).filter((item) => this.checkCondition(item));
1059
1083
  const activatedCondition = parsedConditions.find((item) => this.handleCondition(item));
1060
1084
  if (!activatedCondition) {
1061
- throw new Error("no condition is activated");
1085
+ throw new Error("No condition is activated");
1062
1086
  }
1063
1087
  return {
1064
1088
  outputs: {},
@@ -1086,11 +1110,13 @@ var ConditionExecutor = class {
1086
1110
  checkCondition(condition) {
1087
1111
  const rule = conditionRules[condition.leftType];
1088
1112
  if ((0, import_lodash_es9.isNil)(rule)) {
1089
- throw new Error(`condition left type ${condition.leftType} is not supported`);
1113
+ throw new Error(`Condition left type "${condition.leftType}" is not supported`);
1090
1114
  }
1091
1115
  const ruleType = rule[condition.operator];
1092
1116
  if ((0, import_lodash_es9.isNil)(ruleType)) {
1093
- throw new Error(`condition operator ${condition.operator} is not supported`);
1117
+ throw new Error(
1118
+ `Condition left type "${condition.leftType}" has no operator "${condition.operator}"`
1119
+ );
1094
1120
  }
1095
1121
  if (ruleType !== condition.rightType) {
1096
1122
  return false;
@@ -1100,7 +1126,7 @@ var ConditionExecutor = class {
1100
1126
  handleCondition(condition) {
1101
1127
  const handler = conditionHandlers[condition.leftType];
1102
1128
  if (!handler) {
1103
- throw new Error(`condition left type ${condition.leftType} is not supported`);
1129
+ throw new Error(`Condition left type ${condition.leftType} is not supported`);
1104
1130
  }
1105
1131
  const isActive = handler(condition);
1106
1132
  return isActive;
@@ -1118,6 +1144,241 @@ var WorkflowRuntimeNodeExecutors = [
1118
1144
  BlockEndExecutor
1119
1145
  ];
1120
1146
 
1147
+ // src/domain/validation/validators/cycle-detection.ts
1148
+ var cycleDetection = (schema) => {
1149
+ const { nodes, edges } = schema;
1150
+ const adjacencyList = /* @__PURE__ */ new Map();
1151
+ const nodeIds = new Set(nodes.map((node) => node.id));
1152
+ nodeIds.forEach((nodeId) => {
1153
+ adjacencyList.set(nodeId, []);
1154
+ });
1155
+ edges.forEach((edge) => {
1156
+ const sourceList = adjacencyList.get(edge.sourceNodeID);
1157
+ if (sourceList) {
1158
+ sourceList.push(edge.targetNodeID);
1159
+ }
1160
+ });
1161
+ let NodeStatus;
1162
+ ((NodeStatus2) => {
1163
+ NodeStatus2[NodeStatus2["Unvisited"] = 0] = "Unvisited";
1164
+ NodeStatus2[NodeStatus2["Visiting"] = 1] = "Visiting";
1165
+ NodeStatus2[NodeStatus2["Visited"] = 2] = "Visited";
1166
+ })(NodeStatus || (NodeStatus = {}));
1167
+ const nodeStatusMap = /* @__PURE__ */ new Map();
1168
+ nodeIds.forEach((nodeId) => {
1169
+ nodeStatusMap.set(nodeId, 0 /* Unvisited */);
1170
+ });
1171
+ const detectCycleFromNode = (nodeId) => {
1172
+ nodeStatusMap.set(nodeId, 1 /* Visiting */);
1173
+ const neighbors = adjacencyList.get(nodeId) || [];
1174
+ for (const neighbor of neighbors) {
1175
+ const neighborColor = nodeStatusMap.get(neighbor);
1176
+ if (neighborColor === 1 /* Visiting */) {
1177
+ return true;
1178
+ }
1179
+ if (neighborColor === 0 /* Unvisited */ && detectCycleFromNode(neighbor)) {
1180
+ return true;
1181
+ }
1182
+ }
1183
+ nodeStatusMap.set(nodeId, 2 /* Visited */);
1184
+ return false;
1185
+ };
1186
+ for (const nodeId of nodeIds) {
1187
+ if (nodeStatusMap.get(nodeId) === 0 /* Unvisited */) {
1188
+ if (detectCycleFromNode(nodeId)) {
1189
+ throw new Error("Workflow schema contains a cycle, which is not allowed");
1190
+ }
1191
+ }
1192
+ }
1193
+ nodes.forEach((node) => {
1194
+ if (node.blocks) {
1195
+ cycleDetection({
1196
+ nodes: node.blocks,
1197
+ edges: node.edges ?? []
1198
+ });
1199
+ }
1200
+ });
1201
+ };
1202
+
1203
+ // src/domain/validation/validators/start-end-node.ts
1204
+ var blockStartEndNode = (schema) => {
1205
+ const { blockStartNodes, blockEndNodes } = schema.nodes.reduce(
1206
+ (acc, node) => {
1207
+ if (node.type === FlowGramNode.BlockStart) {
1208
+ acc.blockStartNodes.push(node);
1209
+ } else if (node.type === FlowGramNode.BlockEnd) {
1210
+ acc.blockEndNodes.push(node);
1211
+ }
1212
+ return acc;
1213
+ },
1214
+ { blockStartNodes: [], blockEndNodes: [] }
1215
+ );
1216
+ if (!blockStartNodes.length && !blockEndNodes.length) {
1217
+ throw new Error("Workflow block schema must have a block-start node and a block-end node");
1218
+ }
1219
+ if (!blockStartNodes.length) {
1220
+ throw new Error("Workflow block schema must have a block-start node");
1221
+ }
1222
+ if (!blockEndNodes.length) {
1223
+ throw new Error("Workflow block schema must have an block-end node");
1224
+ }
1225
+ if (blockStartNodes.length > 1) {
1226
+ throw new Error("Workflow block schema must have only one block-start node");
1227
+ }
1228
+ if (blockEndNodes.length > 1) {
1229
+ throw new Error("Workflow block schema must have only one block-end node");
1230
+ }
1231
+ schema.nodes.forEach((node) => {
1232
+ if (node.blocks) {
1233
+ blockStartEndNode({
1234
+ nodes: node.blocks,
1235
+ edges: node.edges ?? []
1236
+ });
1237
+ }
1238
+ });
1239
+ };
1240
+ var startEndNode = (schema) => {
1241
+ const { startNodes, endNodes } = schema.nodes.reduce(
1242
+ (acc, node) => {
1243
+ if (node.type === FlowGramNode.Start) {
1244
+ acc.startNodes.push(node);
1245
+ } else if (node.type === FlowGramNode.End) {
1246
+ acc.endNodes.push(node);
1247
+ }
1248
+ return acc;
1249
+ },
1250
+ { startNodes: [], endNodes: [] }
1251
+ );
1252
+ if (!startNodes.length && !endNodes.length) {
1253
+ throw new Error("Workflow schema must have a start node and an end node");
1254
+ }
1255
+ if (!startNodes.length) {
1256
+ throw new Error("Workflow schema must have a start node");
1257
+ }
1258
+ if (!endNodes.length) {
1259
+ throw new Error("Workflow schema must have an end node");
1260
+ }
1261
+ if (startNodes.length > 1) {
1262
+ throw new Error("Workflow schema must have only one start node");
1263
+ }
1264
+ if (endNodes.length > 1) {
1265
+ throw new Error("Workflow schema must have only one end node");
1266
+ }
1267
+ schema.nodes.forEach((node) => {
1268
+ if (node.blocks) {
1269
+ blockStartEndNode({
1270
+ nodes: node.blocks,
1271
+ edges: node.edges ?? []
1272
+ });
1273
+ }
1274
+ });
1275
+ };
1276
+
1277
+ // src/domain/validation/validators/edge-source-target-exist.ts
1278
+ var edgeSourceTargetExist = (schema) => {
1279
+ const { nodes, edges } = schema;
1280
+ const nodeSet = new Set(nodes.map((node) => node.id));
1281
+ edges.forEach((edge) => {
1282
+ if (!nodeSet.has(edge.sourceNodeID)) {
1283
+ throw new Error(`Workflow schema edge source node "${edge.sourceNodeID}" not exist`);
1284
+ }
1285
+ if (!nodeSet.has(edge.targetNodeID)) {
1286
+ throw new Error(`Workflow schema edge target node "${edge.targetNodeID}" not exist`);
1287
+ }
1288
+ });
1289
+ nodes.forEach((node) => {
1290
+ if (node.blocks) {
1291
+ edgeSourceTargetExist({
1292
+ nodes: node.blocks,
1293
+ edges: node.edges ?? []
1294
+ });
1295
+ }
1296
+ });
1297
+ };
1298
+
1299
+ // src/domain/validation/validators/schema-format.ts
1300
+ var schemaFormat = (schema) => {
1301
+ if (!schema || typeof schema !== "object") {
1302
+ throw new Error("Workflow schema must be a valid object");
1303
+ }
1304
+ if (!Array.isArray(schema.nodes)) {
1305
+ throw new Error("Workflow schema must have a valid nodes array");
1306
+ }
1307
+ if (!Array.isArray(schema.edges)) {
1308
+ throw new Error("Workflow schema must have a valid edges array");
1309
+ }
1310
+ schema.nodes.forEach((node, index) => {
1311
+ validateNodeFormat(node, `nodes[${index}]`);
1312
+ });
1313
+ schema.edges.forEach((edge, index) => {
1314
+ validateEdgeFormat(edge, `edges[${index}]`);
1315
+ });
1316
+ schema.nodes.forEach((node, nodeIndex) => {
1317
+ if (node.blocks) {
1318
+ if (!Array.isArray(node.blocks)) {
1319
+ throw new Error(`Node nodes[${nodeIndex}].blocks must be an array`);
1320
+ }
1321
+ const nestedSchema = {
1322
+ nodes: node.blocks,
1323
+ edges: node.edges || []
1324
+ };
1325
+ schemaFormat(nestedSchema);
1326
+ }
1327
+ });
1328
+ };
1329
+ var validateNodeFormat = (node, path) => {
1330
+ if (!node || typeof node !== "object") {
1331
+ throw new Error(`${path} must be a valid object`);
1332
+ }
1333
+ if (typeof node.id !== "string" || !node.id.trim()) {
1334
+ throw new Error(`${path}.id must be a non-empty string`);
1335
+ }
1336
+ if (typeof node.type !== "string" || !node.type.trim()) {
1337
+ throw new Error(`${path}.type must be a non-empty string`);
1338
+ }
1339
+ if (!node.meta || typeof node.meta !== "object") {
1340
+ throw new Error(`${path}.meta must be a valid object`);
1341
+ }
1342
+ if (!node.data || typeof node.data !== "object") {
1343
+ throw new Error(`${path}.data must be a valid object`);
1344
+ }
1345
+ if (node.blocks !== void 0 && !Array.isArray(node.blocks)) {
1346
+ throw new Error(`${path}.blocks must be an array if present`);
1347
+ }
1348
+ if (node.edges !== void 0 && !Array.isArray(node.edges)) {
1349
+ throw new Error(`${path}.edges must be an array if present`);
1350
+ }
1351
+ if (node.data.inputs !== void 0 && (typeof node.data.inputs !== "object" || node.data.inputs === null)) {
1352
+ throw new Error(`${path}.data.inputs must be a valid object if present`);
1353
+ }
1354
+ if (node.data.outputs !== void 0 && (typeof node.data.outputs !== "object" || node.data.outputs === null)) {
1355
+ throw new Error(`${path}.data.outputs must be a valid object if present`);
1356
+ }
1357
+ if (node.data.inputsValues !== void 0 && (typeof node.data.inputsValues !== "object" || node.data.inputsValues === null)) {
1358
+ throw new Error(`${path}.data.inputsValues must be a valid object if present`);
1359
+ }
1360
+ if (node.data.title !== void 0 && typeof node.data.title !== "string") {
1361
+ throw new Error(`${path}.data.title must be a string if present`);
1362
+ }
1363
+ };
1364
+ var validateEdgeFormat = (edge, path) => {
1365
+ if (!edge || typeof edge !== "object") {
1366
+ throw new Error(`${path} must be a valid object`);
1367
+ }
1368
+ if (typeof edge.sourceNodeID !== "string" || !edge.sourceNodeID.trim()) {
1369
+ throw new Error(`${path}.sourceNodeID must be a non-empty string`);
1370
+ }
1371
+ if (typeof edge.targetNodeID !== "string" || !edge.targetNodeID.trim()) {
1372
+ throw new Error(`${path}.targetNodeID must be a non-empty string`);
1373
+ }
1374
+ if (edge.sourcePortID !== void 0 && typeof edge.sourcePortID !== "string") {
1375
+ throw new Error(`${path}.sourcePortID must be a string if present`);
1376
+ }
1377
+ if (edge.targetPortID !== void 0 && typeof edge.targetPortID !== "string") {
1378
+ throw new Error(`${path}.targetPortID must be a string if present`);
1379
+ }
1380
+ };
1381
+
1121
1382
  // src/domain/validation/index.ts
1122
1383
  var WorkflowRuntimeValidation = class {
1123
1384
  invoke(params) {
@@ -1135,8 +1396,23 @@ var WorkflowRuntimeValidation = class {
1135
1396
  };
1136
1397
  }
1137
1398
  schema(schema) {
1399
+ const errors = [];
1400
+ const validations = [
1401
+ () => schemaFormat(schema),
1402
+ () => cycleDetection(schema),
1403
+ () => edgeSourceTargetExist(schema),
1404
+ () => startEndNode(schema)
1405
+ ];
1406
+ validations.forEach((validation) => {
1407
+ try {
1408
+ validation();
1409
+ } catch (error) {
1410
+ errors.push(error instanceof Error ? error.message : String(error));
1411
+ }
1412
+ });
1138
1413
  return {
1139
- valid: true
1414
+ valid: errors.length === 0,
1415
+ errors: errors.length > 0 ? errors : void 0
1140
1416
  };
1141
1417
  }
1142
1418
  inputs(inputsSchema, inputs) {
@@ -1179,7 +1455,7 @@ var WorkflowRuntimeExecutor = class {
1179
1455
  const nodeType = context.node.type;
1180
1456
  const nodeExecutor = this.nodeExecutors.get(nodeType);
1181
1457
  if (!nodeExecutor) {
1182
- throw new Error(`no executor found for node type ${nodeType}`);
1458
+ throw new Error(`No executor found for node type ${nodeType}`);
1183
1459
  }
1184
1460
  const output = await nodeExecutor.execute(context);
1185
1461
  return output;
@@ -1554,7 +1830,7 @@ var WorkflowRuntimeState = class {
1554
1830
  }
1555
1831
  parseRef(ref) {
1556
1832
  if (ref?.type !== "ref") {
1557
- throw new Error(`invalid ref value: ${ref}`);
1833
+ throw new Error(`Invalid ref value: ${ref}`);
1558
1834
  }
1559
1835
  if (!ref.content || ref.content.length < 2) {
1560
1836
  return null;
@@ -1572,7 +1848,7 @@ var WorkflowRuntimeState = class {
1572
1848
  }
1573
1849
  parseTemplate(template) {
1574
1850
  if (template?.type !== "template") {
1575
- throw new Error(`invalid template value: ${template}`);
1851
+ throw new Error(`Invalid template value: ${template}`);
1576
1852
  }
1577
1853
  if (!template.content) {
1578
1854
  return null;
@@ -1598,7 +1874,7 @@ var WorkflowRuntimeState = class {
1598
1874
  }
1599
1875
  parseValue(flowValue) {
1600
1876
  if (!flowValue?.type) {
1601
- throw new Error(`invalid flow value type: ${flowValue.type}`);
1877
+ throw new Error(`Invalid flow value type: ${flowValue.type}`);
1602
1878
  }
1603
1879
  if (flowValue.type === "constant") {
1604
1880
  const value = flowValue.content;
@@ -1617,7 +1893,7 @@ var WorkflowRuntimeState = class {
1617
1893
  if (flowValue.type === "template") {
1618
1894
  return this.parseTemplate(flowValue);
1619
1895
  }
1620
- throw new Error(`unknown flow value type: ${flowValue.type}`);
1896
+ throw new Error(`Unknown flow value type: ${flowValue.type}`);
1621
1897
  }
1622
1898
  isExecutedNode(node) {
1623
1899
  return this.executedNodes.has(node.id);
@@ -2002,7 +2278,7 @@ var createStore = (params) => {
2002
2278
  const from = store.nodes.get(sourceNodeID);
2003
2279
  const to = store.nodes.get(targetNodeID);
2004
2280
  if (!from || !to) {
2005
- throw new Error(`invalid edge schema ID: ${id}, from: ${sourceNodeID}, to: ${targetNodeID}`);
2281
+ throw new Error(`Invalid edge schema ID: ${id}, from: ${sourceNodeID}, to: ${targetNodeID}`);
2006
2282
  }
2007
2283
  const edge = createEdge(store, {
2008
2284
  id,
@@ -2277,7 +2553,7 @@ var WorkflowRuntimeEngine = class {
2277
2553
  }
2278
2554
  const targetPort = node.ports.outputs.find((port) => port.id === branch);
2279
2555
  if (!targetPort) {
2280
- throw new Error(`branch ${branch} not found`);
2556
+ throw new Error(`Engine branch ${branch} not found`);
2281
2557
  }
2282
2558
  const nextNodeIDs = new Set(targetPort.edges.map((edge) => edge.to.id));
2283
2559
  const nextNodes = allNextNodes.filter((nextNode) => nextNodeIDs.has(nextNode.id));
@@ -2292,11 +2568,11 @@ var WorkflowRuntimeEngine = class {
2292
2568
  }
2293
2569
  async executeNext(params) {
2294
2570
  const { context, node, nextNodes } = params;
2295
- if (node.type === FlowGramNode.End) {
2571
+ if (node.type === FlowGramNode.End || node.type === FlowGramNode.BlockEnd) {
2296
2572
  return;
2297
2573
  }
2298
2574
  if (nextNodes.length === 0) {
2299
- return;
2575
+ throw new Error(`Node ${node.id} has no next nodes`);
2300
2576
  }
2301
2577
  await Promise.all(
2302
2578
  nextNodes.map(