@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/esm/index.js CHANGED
@@ -304,7 +304,7 @@ var WorkflowRuntimeType;
304
304
  const expectedType = types[0];
305
305
  types.forEach((type) => {
306
306
  if (type !== expectedType) {
307
- throw new Error(`array items type must be same, expect ${expectedType}, but got ${type}`);
307
+ throw new Error(`Array items type must be same, expect ${expectedType}, but got ${type}`);
308
308
  }
309
309
  });
310
310
  return expectedType;
@@ -462,8 +462,8 @@ var validateObject = (value, schema, path) => {
462
462
  }
463
463
  }
464
464
  if (schema.properties) {
465
- for (const [propertyName, propertySchema] of Object.entries(schema.properties)) {
466
- const isRequired = propertySchema.isPropertyRequired === true;
465
+ for (const [propertyName] of Object.entries(schema.properties)) {
466
+ const isRequired = schema.required?.includes(propertyName) ?? false;
467
467
  if (isRequired && !(propertyName in objectValue)) {
468
468
  return {
469
469
  result: false,
@@ -573,7 +573,7 @@ var LoopExecutor = class {
573
573
  const subNodes = context.node.children;
574
574
  const blockStartNode = subNodes.find((node) => node.type === FlowGramNode.BlockStart);
575
575
  if (!blockStartNode) {
576
- throw new Error("block start node not found");
576
+ throw new Error("Loop block start node not found");
577
577
  }
578
578
  const blockOutputs = [];
579
579
  for (let index = 0; index < loopArray.length; index++) {
@@ -591,10 +591,14 @@ var LoopExecutor = class {
591
591
  type: WorkflowVariableType.Number,
592
592
  value: index
593
593
  });
594
- await engine.executeNode({
595
- context: subContext,
596
- node: blockStartNode
597
- });
594
+ try {
595
+ await engine.executeNode({
596
+ context: subContext,
597
+ node: blockStartNode
598
+ });
599
+ } catch (e) {
600
+ throw new Error(`Loop block execute error`);
601
+ }
598
602
  const blockOutput = this.getBlockOutput(context, subContext);
599
603
  blockOutputs.push(blockOutput);
600
604
  }
@@ -615,15 +619,15 @@ var LoopExecutor = class {
615
619
  checkLoopArray(LoopArrayVariable) {
616
620
  const loopArray = LoopArrayVariable?.value;
617
621
  if (!loopArray || isNil(loopArray) || !Array.isArray(loopArray)) {
618
- throw new Error("loopFor is required");
622
+ throw new Error('Loop "loopFor" is required');
619
623
  }
620
624
  const loopArrayType = LoopArrayVariable.type;
621
625
  if (loopArrayType !== WorkflowVariableType.Array) {
622
- throw new Error("loopFor must be an array");
626
+ throw new Error('Loop "loopFor" must be an array');
623
627
  }
624
628
  const loopArrayItemType = LoopArrayVariable.itemsType;
625
629
  if (isNil(loopArrayItemType)) {
626
- throw new Error("loopFor items must be array items");
630
+ throw new Error('Loop "loopFor.items" must be array items');
627
631
  }
628
632
  }
629
633
  getBlockOutput(executionContext, subContext) {
@@ -698,14 +702,24 @@ var LLMExecutor = class {
698
702
  apiKey,
699
703
  configuration: {
700
704
  baseURL: apiHost
701
- }
705
+ },
706
+ maxRetries: 3
702
707
  });
703
708
  const messages = [];
704
709
  if (systemPrompt) {
705
710
  messages.push(new SystemMessage(systemPrompt));
706
711
  }
707
712
  messages.push(new HumanMessage(prompt));
708
- const apiMessage = await model.invoke(messages);
713
+ let apiMessage;
714
+ try {
715
+ apiMessage = await model.invoke(messages);
716
+ } catch (error) {
717
+ const errorMessage = error?.message;
718
+ if (errorMessage === "Connection error.") {
719
+ throw new Error(`Network error: unreachable api "${apiHost}"`);
720
+ }
721
+ throw error;
722
+ }
709
723
  const result = apiMessage.content;
710
724
  return {
711
725
  outputs: {
@@ -716,13 +730,23 @@ var LLMExecutor = class {
716
730
  checkInputs(inputs) {
717
731
  const { modelName, temperature, apiKey, apiHost, prompt } = inputs;
718
732
  const missingInputs = [];
719
- if (isNil2(modelName)) missingInputs.push("modelName");
733
+ if (!modelName) missingInputs.push("modelName");
720
734
  if (isNil2(temperature)) missingInputs.push("temperature");
721
- if (isNil2(apiKey)) missingInputs.push("apiKey");
722
- if (isNil2(apiHost)) missingInputs.push("apiHost");
723
- if (isNil2(prompt)) missingInputs.push("prompt");
735
+ if (!apiKey) missingInputs.push("apiKey");
736
+ if (!apiHost) missingInputs.push("apiHost");
737
+ if (!prompt) missingInputs.push("prompt");
724
738
  if (missingInputs.length > 0) {
725
- throw new Error(`LLM node missing required inputs: ${missingInputs.join(", ")}`);
739
+ throw new Error(`LLM node missing required inputs: "${missingInputs.join('", "')}"`);
740
+ }
741
+ this.checkApiHost(apiHost);
742
+ }
743
+ checkApiHost(apiHost) {
744
+ if (!apiHost || typeof apiHost !== "string") {
745
+ throw new Error(`Invalid API host format - ${apiHost}`);
746
+ }
747
+ const url = new URL(apiHost);
748
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
749
+ throw new Error(`Invalid API host protocol - ${url.protocol}`);
726
750
  }
727
751
  }
728
752
  };
@@ -1017,7 +1041,7 @@ var ConditionExecutor = class {
1017
1041
  const parsedConditions = conditions.map((item) => this.parseCondition(item, context)).filter((item) => this.checkCondition(item));
1018
1042
  const activatedCondition = parsedConditions.find((item) => this.handleCondition(item));
1019
1043
  if (!activatedCondition) {
1020
- throw new Error("no condition is activated");
1044
+ throw new Error("No condition is activated");
1021
1045
  }
1022
1046
  return {
1023
1047
  outputs: {},
@@ -1045,11 +1069,13 @@ var ConditionExecutor = class {
1045
1069
  checkCondition(condition) {
1046
1070
  const rule = conditionRules[condition.leftType];
1047
1071
  if (isNil9(rule)) {
1048
- throw new Error(`condition left type ${condition.leftType} is not supported`);
1072
+ throw new Error(`Condition left type "${condition.leftType}" is not supported`);
1049
1073
  }
1050
1074
  const ruleType = rule[condition.operator];
1051
1075
  if (isNil9(ruleType)) {
1052
- throw new Error(`condition operator ${condition.operator} is not supported`);
1076
+ throw new Error(
1077
+ `Condition left type "${condition.leftType}" has no operator "${condition.operator}"`
1078
+ );
1053
1079
  }
1054
1080
  if (ruleType !== condition.rightType) {
1055
1081
  return false;
@@ -1059,7 +1085,7 @@ var ConditionExecutor = class {
1059
1085
  handleCondition(condition) {
1060
1086
  const handler = conditionHandlers[condition.leftType];
1061
1087
  if (!handler) {
1062
- throw new Error(`condition left type ${condition.leftType} is not supported`);
1088
+ throw new Error(`Condition left type ${condition.leftType} is not supported`);
1063
1089
  }
1064
1090
  const isActive = handler(condition);
1065
1091
  return isActive;
@@ -1077,6 +1103,241 @@ var WorkflowRuntimeNodeExecutors = [
1077
1103
  BlockEndExecutor
1078
1104
  ];
1079
1105
 
1106
+ // src/domain/validation/validators/cycle-detection.ts
1107
+ var cycleDetection = (schema) => {
1108
+ const { nodes, edges } = schema;
1109
+ const adjacencyList = /* @__PURE__ */ new Map();
1110
+ const nodeIds = new Set(nodes.map((node) => node.id));
1111
+ nodeIds.forEach((nodeId) => {
1112
+ adjacencyList.set(nodeId, []);
1113
+ });
1114
+ edges.forEach((edge) => {
1115
+ const sourceList = adjacencyList.get(edge.sourceNodeID);
1116
+ if (sourceList) {
1117
+ sourceList.push(edge.targetNodeID);
1118
+ }
1119
+ });
1120
+ let NodeStatus;
1121
+ ((NodeStatus2) => {
1122
+ NodeStatus2[NodeStatus2["Unvisited"] = 0] = "Unvisited";
1123
+ NodeStatus2[NodeStatus2["Visiting"] = 1] = "Visiting";
1124
+ NodeStatus2[NodeStatus2["Visited"] = 2] = "Visited";
1125
+ })(NodeStatus || (NodeStatus = {}));
1126
+ const nodeStatusMap = /* @__PURE__ */ new Map();
1127
+ nodeIds.forEach((nodeId) => {
1128
+ nodeStatusMap.set(nodeId, 0 /* Unvisited */);
1129
+ });
1130
+ const detectCycleFromNode = (nodeId) => {
1131
+ nodeStatusMap.set(nodeId, 1 /* Visiting */);
1132
+ const neighbors = adjacencyList.get(nodeId) || [];
1133
+ for (const neighbor of neighbors) {
1134
+ const neighborColor = nodeStatusMap.get(neighbor);
1135
+ if (neighborColor === 1 /* Visiting */) {
1136
+ return true;
1137
+ }
1138
+ if (neighborColor === 0 /* Unvisited */ && detectCycleFromNode(neighbor)) {
1139
+ return true;
1140
+ }
1141
+ }
1142
+ nodeStatusMap.set(nodeId, 2 /* Visited */);
1143
+ return false;
1144
+ };
1145
+ for (const nodeId of nodeIds) {
1146
+ if (nodeStatusMap.get(nodeId) === 0 /* Unvisited */) {
1147
+ if (detectCycleFromNode(nodeId)) {
1148
+ throw new Error("Workflow schema contains a cycle, which is not allowed");
1149
+ }
1150
+ }
1151
+ }
1152
+ nodes.forEach((node) => {
1153
+ if (node.blocks) {
1154
+ cycleDetection({
1155
+ nodes: node.blocks,
1156
+ edges: node.edges ?? []
1157
+ });
1158
+ }
1159
+ });
1160
+ };
1161
+
1162
+ // src/domain/validation/validators/start-end-node.ts
1163
+ var blockStartEndNode = (schema) => {
1164
+ const { blockStartNodes, blockEndNodes } = schema.nodes.reduce(
1165
+ (acc, node) => {
1166
+ if (node.type === FlowGramNode.BlockStart) {
1167
+ acc.blockStartNodes.push(node);
1168
+ } else if (node.type === FlowGramNode.BlockEnd) {
1169
+ acc.blockEndNodes.push(node);
1170
+ }
1171
+ return acc;
1172
+ },
1173
+ { blockStartNodes: [], blockEndNodes: [] }
1174
+ );
1175
+ if (!blockStartNodes.length && !blockEndNodes.length) {
1176
+ throw new Error("Workflow block schema must have a block-start node and a block-end node");
1177
+ }
1178
+ if (!blockStartNodes.length) {
1179
+ throw new Error("Workflow block schema must have a block-start node");
1180
+ }
1181
+ if (!blockEndNodes.length) {
1182
+ throw new Error("Workflow block schema must have an block-end node");
1183
+ }
1184
+ if (blockStartNodes.length > 1) {
1185
+ throw new Error("Workflow block schema must have only one block-start node");
1186
+ }
1187
+ if (blockEndNodes.length > 1) {
1188
+ throw new Error("Workflow block schema must have only one block-end node");
1189
+ }
1190
+ schema.nodes.forEach((node) => {
1191
+ if (node.blocks) {
1192
+ blockStartEndNode({
1193
+ nodes: node.blocks,
1194
+ edges: node.edges ?? []
1195
+ });
1196
+ }
1197
+ });
1198
+ };
1199
+ var startEndNode = (schema) => {
1200
+ const { startNodes, endNodes } = schema.nodes.reduce(
1201
+ (acc, node) => {
1202
+ if (node.type === FlowGramNode.Start) {
1203
+ acc.startNodes.push(node);
1204
+ } else if (node.type === FlowGramNode.End) {
1205
+ acc.endNodes.push(node);
1206
+ }
1207
+ return acc;
1208
+ },
1209
+ { startNodes: [], endNodes: [] }
1210
+ );
1211
+ if (!startNodes.length && !endNodes.length) {
1212
+ throw new Error("Workflow schema must have a start node and an end node");
1213
+ }
1214
+ if (!startNodes.length) {
1215
+ throw new Error("Workflow schema must have a start node");
1216
+ }
1217
+ if (!endNodes.length) {
1218
+ throw new Error("Workflow schema must have an end node");
1219
+ }
1220
+ if (startNodes.length > 1) {
1221
+ throw new Error("Workflow schema must have only one start node");
1222
+ }
1223
+ if (endNodes.length > 1) {
1224
+ throw new Error("Workflow schema must have only one end node");
1225
+ }
1226
+ schema.nodes.forEach((node) => {
1227
+ if (node.blocks) {
1228
+ blockStartEndNode({
1229
+ nodes: node.blocks,
1230
+ edges: node.edges ?? []
1231
+ });
1232
+ }
1233
+ });
1234
+ };
1235
+
1236
+ // src/domain/validation/validators/edge-source-target-exist.ts
1237
+ var edgeSourceTargetExist = (schema) => {
1238
+ const { nodes, edges } = schema;
1239
+ const nodeSet = new Set(nodes.map((node) => node.id));
1240
+ edges.forEach((edge) => {
1241
+ if (!nodeSet.has(edge.sourceNodeID)) {
1242
+ throw new Error(`Workflow schema edge source node "${edge.sourceNodeID}" not exist`);
1243
+ }
1244
+ if (!nodeSet.has(edge.targetNodeID)) {
1245
+ throw new Error(`Workflow schema edge target node "${edge.targetNodeID}" not exist`);
1246
+ }
1247
+ });
1248
+ nodes.forEach((node) => {
1249
+ if (node.blocks) {
1250
+ edgeSourceTargetExist({
1251
+ nodes: node.blocks,
1252
+ edges: node.edges ?? []
1253
+ });
1254
+ }
1255
+ });
1256
+ };
1257
+
1258
+ // src/domain/validation/validators/schema-format.ts
1259
+ var schemaFormat = (schema) => {
1260
+ if (!schema || typeof schema !== "object") {
1261
+ throw new Error("Workflow schema must be a valid object");
1262
+ }
1263
+ if (!Array.isArray(schema.nodes)) {
1264
+ throw new Error("Workflow schema must have a valid nodes array");
1265
+ }
1266
+ if (!Array.isArray(schema.edges)) {
1267
+ throw new Error("Workflow schema must have a valid edges array");
1268
+ }
1269
+ schema.nodes.forEach((node, index) => {
1270
+ validateNodeFormat(node, `nodes[${index}]`);
1271
+ });
1272
+ schema.edges.forEach((edge, index) => {
1273
+ validateEdgeFormat(edge, `edges[${index}]`);
1274
+ });
1275
+ schema.nodes.forEach((node, nodeIndex) => {
1276
+ if (node.blocks) {
1277
+ if (!Array.isArray(node.blocks)) {
1278
+ throw new Error(`Node nodes[${nodeIndex}].blocks must be an array`);
1279
+ }
1280
+ const nestedSchema = {
1281
+ nodes: node.blocks,
1282
+ edges: node.edges || []
1283
+ };
1284
+ schemaFormat(nestedSchema);
1285
+ }
1286
+ });
1287
+ };
1288
+ var validateNodeFormat = (node, path) => {
1289
+ if (!node || typeof node !== "object") {
1290
+ throw new Error(`${path} must be a valid object`);
1291
+ }
1292
+ if (typeof node.id !== "string" || !node.id.trim()) {
1293
+ throw new Error(`${path}.id must be a non-empty string`);
1294
+ }
1295
+ if (typeof node.type !== "string" || !node.type.trim()) {
1296
+ throw new Error(`${path}.type must be a non-empty string`);
1297
+ }
1298
+ if (!node.meta || typeof node.meta !== "object") {
1299
+ throw new Error(`${path}.meta must be a valid object`);
1300
+ }
1301
+ if (!node.data || typeof node.data !== "object") {
1302
+ throw new Error(`${path}.data must be a valid object`);
1303
+ }
1304
+ if (node.blocks !== void 0 && !Array.isArray(node.blocks)) {
1305
+ throw new Error(`${path}.blocks must be an array if present`);
1306
+ }
1307
+ if (node.edges !== void 0 && !Array.isArray(node.edges)) {
1308
+ throw new Error(`${path}.edges must be an array if present`);
1309
+ }
1310
+ if (node.data.inputs !== void 0 && (typeof node.data.inputs !== "object" || node.data.inputs === null)) {
1311
+ throw new Error(`${path}.data.inputs must be a valid object if present`);
1312
+ }
1313
+ if (node.data.outputs !== void 0 && (typeof node.data.outputs !== "object" || node.data.outputs === null)) {
1314
+ throw new Error(`${path}.data.outputs must be a valid object if present`);
1315
+ }
1316
+ if (node.data.inputsValues !== void 0 && (typeof node.data.inputsValues !== "object" || node.data.inputsValues === null)) {
1317
+ throw new Error(`${path}.data.inputsValues must be a valid object if present`);
1318
+ }
1319
+ if (node.data.title !== void 0 && typeof node.data.title !== "string") {
1320
+ throw new Error(`${path}.data.title must be a string if present`);
1321
+ }
1322
+ };
1323
+ var validateEdgeFormat = (edge, path) => {
1324
+ if (!edge || typeof edge !== "object") {
1325
+ throw new Error(`${path} must be a valid object`);
1326
+ }
1327
+ if (typeof edge.sourceNodeID !== "string" || !edge.sourceNodeID.trim()) {
1328
+ throw new Error(`${path}.sourceNodeID must be a non-empty string`);
1329
+ }
1330
+ if (typeof edge.targetNodeID !== "string" || !edge.targetNodeID.trim()) {
1331
+ throw new Error(`${path}.targetNodeID must be a non-empty string`);
1332
+ }
1333
+ if (edge.sourcePortID !== void 0 && typeof edge.sourcePortID !== "string") {
1334
+ throw new Error(`${path}.sourcePortID must be a string if present`);
1335
+ }
1336
+ if (edge.targetPortID !== void 0 && typeof edge.targetPortID !== "string") {
1337
+ throw new Error(`${path}.targetPortID must be a string if present`);
1338
+ }
1339
+ };
1340
+
1080
1341
  // src/domain/validation/index.ts
1081
1342
  var WorkflowRuntimeValidation = class {
1082
1343
  invoke(params) {
@@ -1094,8 +1355,23 @@ var WorkflowRuntimeValidation = class {
1094
1355
  };
1095
1356
  }
1096
1357
  schema(schema) {
1358
+ const errors = [];
1359
+ const validations = [
1360
+ () => schemaFormat(schema),
1361
+ () => cycleDetection(schema),
1362
+ () => edgeSourceTargetExist(schema),
1363
+ () => startEndNode(schema)
1364
+ ];
1365
+ validations.forEach((validation) => {
1366
+ try {
1367
+ validation();
1368
+ } catch (error) {
1369
+ errors.push(error instanceof Error ? error.message : String(error));
1370
+ }
1371
+ });
1097
1372
  return {
1098
- valid: true
1373
+ valid: errors.length === 0,
1374
+ errors: errors.length > 0 ? errors : void 0
1099
1375
  };
1100
1376
  }
1101
1377
  inputs(inputsSchema, inputs) {
@@ -1138,7 +1414,7 @@ var WorkflowRuntimeExecutor = class {
1138
1414
  const nodeType = context.node.type;
1139
1415
  const nodeExecutor = this.nodeExecutors.get(nodeType);
1140
1416
  if (!nodeExecutor) {
1141
- throw new Error(`no executor found for node type ${nodeType}`);
1417
+ throw new Error(`No executor found for node type ${nodeType}`);
1142
1418
  }
1143
1419
  const output = await nodeExecutor.execute(context);
1144
1420
  return output;
@@ -1513,7 +1789,7 @@ var WorkflowRuntimeState = class {
1513
1789
  }
1514
1790
  parseRef(ref) {
1515
1791
  if (ref?.type !== "ref") {
1516
- throw new Error(`invalid ref value: ${ref}`);
1792
+ throw new Error(`Invalid ref value: ${ref}`);
1517
1793
  }
1518
1794
  if (!ref.content || ref.content.length < 2) {
1519
1795
  return null;
@@ -1531,7 +1807,7 @@ var WorkflowRuntimeState = class {
1531
1807
  }
1532
1808
  parseTemplate(template) {
1533
1809
  if (template?.type !== "template") {
1534
- throw new Error(`invalid template value: ${template}`);
1810
+ throw new Error(`Invalid template value: ${template}`);
1535
1811
  }
1536
1812
  if (!template.content) {
1537
1813
  return null;
@@ -1557,7 +1833,7 @@ var WorkflowRuntimeState = class {
1557
1833
  }
1558
1834
  parseValue(flowValue) {
1559
1835
  if (!flowValue?.type) {
1560
- throw new Error(`invalid flow value type: ${flowValue.type}`);
1836
+ throw new Error(`Invalid flow value type: ${flowValue.type}`);
1561
1837
  }
1562
1838
  if (flowValue.type === "constant") {
1563
1839
  const value = flowValue.content;
@@ -1576,7 +1852,7 @@ var WorkflowRuntimeState = class {
1576
1852
  if (flowValue.type === "template") {
1577
1853
  return this.parseTemplate(flowValue);
1578
1854
  }
1579
- throw new Error(`unknown flow value type: ${flowValue.type}`);
1855
+ throw new Error(`Unknown flow value type: ${flowValue.type}`);
1580
1856
  }
1581
1857
  isExecutedNode(node) {
1582
1858
  return this.executedNodes.has(node.id);
@@ -1961,7 +2237,7 @@ var createStore = (params) => {
1961
2237
  const from = store.nodes.get(sourceNodeID);
1962
2238
  const to = store.nodes.get(targetNodeID);
1963
2239
  if (!from || !to) {
1964
- throw new Error(`invalid edge schema ID: ${id}, from: ${sourceNodeID}, to: ${targetNodeID}`);
2240
+ throw new Error(`Invalid edge schema ID: ${id}, from: ${sourceNodeID}, to: ${targetNodeID}`);
1965
2241
  }
1966
2242
  const edge = createEdge(store, {
1967
2243
  id,
@@ -2236,7 +2512,7 @@ var WorkflowRuntimeEngine = class {
2236
2512
  }
2237
2513
  const targetPort = node.ports.outputs.find((port) => port.id === branch);
2238
2514
  if (!targetPort) {
2239
- throw new Error(`branch ${branch} not found`);
2515
+ throw new Error(`Engine branch ${branch} not found`);
2240
2516
  }
2241
2517
  const nextNodeIDs = new Set(targetPort.edges.map((edge) => edge.to.id));
2242
2518
  const nextNodes = allNextNodes.filter((nextNode) => nextNodeIDs.has(nextNode.id));
@@ -2251,11 +2527,11 @@ var WorkflowRuntimeEngine = class {
2251
2527
  }
2252
2528
  async executeNext(params) {
2253
2529
  const { context, node, nextNodes } = params;
2254
- if (node.type === FlowGramNode.End) {
2530
+ if (node.type === FlowGramNode.End || node.type === FlowGramNode.BlockEnd) {
2255
2531
  return;
2256
2532
  }
2257
2533
  if (nextNodes.length === 0) {
2258
- return;
2534
+ throw new Error(`Node ${node.id} has no next nodes`);
2259
2535
  }
2260
2536
  await Promise.all(
2261
2537
  nextNodes.map(