@kortyx/runtime 0.15.2 → 0.17.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.17.0](https://github.com/kortyx-io/kortyx/compare/runtime-v0.16.0...runtime-v0.17.0) (2026-09-14)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * **errors:** public failure messages and default HTTP statuses change; malformed streams fail explicitly, provider error classes are shared, and retry helpers exclude control flow. Existing calls and legacy records remain supported. See the error-contract migration and release notes.
9
+
10
+ ### Features
11
+
12
+ * **errors:** harden failure contracts across execution and recovery ([#175](https://github.com/kortyx-io/kortyx/issues/175)) ([d231e36](https://github.com/kortyx-io/kortyx/commit/d231e364259a4f23fc3675d8783fde5ba338416e))
13
+
14
+
15
+ ### Dependencies
16
+
17
+ * The following workspace dependencies were updated
18
+ * dependencies
19
+ * @kortyx/stream bumped to 0.12.0
20
+ * @kortyx/core bumped to 0.9.0
21
+ * @kortyx/hooks bumped to 0.25.0
22
+ * @kortyx/providers bumped to 0.7.0
23
+ * @kortyx/utils bumped to 0.3.0
24
+
25
+ ## [0.16.0](https://github.com/kortyx-io/kortyx/compare/runtime-v0.15.2...runtime-v0.16.0) (2026-09-11)
26
+
27
+
28
+ ### Features
29
+
30
+ * **hooks:** support durable parallel child workflows ([#171](https://github.com/kortyx-io/kortyx/issues/171)) ([fdf2d56](https://github.com/kortyx-io/kortyx/commit/fdf2d564d88e921988e8f3e20da9fe0c30bbcdf9))
31
+
32
+
33
+ ### Dependencies
34
+
35
+ * The following workspace dependencies were updated
36
+ * dependencies
37
+ * @kortyx/hooks bumped to 0.24.0
38
+
3
39
  ## [0.15.2](https://github.com/kortyx-io/kortyx/compare/runtime-v0.15.1...runtime-v0.15.2) (2026-09-11)
4
40
 
5
41
 
package/dist/index.js CHANGED
@@ -267,6 +267,7 @@ function createRedisPendingRequestStore(options) {
267
267
  }
268
268
 
269
269
  // src/framework/redis/redis-checkpointer.ts
270
+ var import_errors = require("@kortyx/core/errors");
270
271
  var import_langgraph_checkpoint2 = require("@langchain/langgraph-checkpoint");
271
272
  var utf8Bytes2 = (s) => new Uint8Array(Buffer.from(s, "utf8"));
272
273
  var utf8String2 = (u8) => typeof u8 === "string" ? u8 : Buffer.from(u8).toString("utf8");
@@ -296,7 +297,9 @@ function createRedisCheckpointSaver(options) {
296
297
  },
297
298
  getNextVersion(current) {
298
299
  if (typeof current === "string") {
299
- throw new Error("Please override this method to use string versions.");
300
+ throw new import_errors.PersistenceError(
301
+ "Please override this method to use string versions."
302
+ );
300
303
  }
301
304
  return current !== void 0 && typeof current === "number" ? current + 1 : 1;
302
305
  },
@@ -362,7 +365,7 @@ function createRedisCheckpointSaver(options) {
362
365
  const threadId = config.configurable?.thread_id;
363
366
  const checkpointNs = config.configurable?.checkpoint_ns ?? "";
364
367
  if (!threadId) {
365
- throw new Error(
368
+ throw new import_errors.PersistenceError(
366
369
  'Failed to put checkpoint: missing "thread_id" in config.configurable.'
367
370
  );
368
371
  }
@@ -393,12 +396,12 @@ function createRedisCheckpointSaver(options) {
393
396
  const checkpointNs = config.configurable?.checkpoint_ns;
394
397
  const checkpointId = config.configurable?.checkpoint_id;
395
398
  if (!threadId) {
396
- throw new Error(
399
+ throw new import_errors.PersistenceError(
397
400
  'Failed to put writes: missing "thread_id" in config.configurable.'
398
401
  );
399
402
  }
400
403
  if (!checkpointId) {
401
- throw new Error(
404
+ throw new import_errors.PersistenceError(
402
405
  'Failed to put writes: missing "checkpoint_id" in config.configurable.'
403
406
  );
404
407
  }
@@ -439,9 +442,13 @@ function createRedisCheckpointSaver(options) {
439
442
  return saver;
440
443
  }
441
444
 
445
+ // src/framework/redis/redis-store.ts
446
+ var import_errors3 = require("@kortyx/core/errors");
447
+
442
448
  // src/framework/redis/redis-client.ts
443
449
  var import_node_net = require("net");
444
450
  var import_node_tls = require("tls");
451
+ var import_errors2 = require("@kortyx/core/errors");
445
452
  var encodeBulk = (s) => {
446
453
  const b = Buffer.from(s, "utf8");
447
454
  return Buffer.concat([
@@ -579,13 +586,17 @@ function createRedisClient(options) {
579
586
  if (password) {
580
587
  const r = await send("AUTH", [password]);
581
588
  if (typeof r === "object" && r && r.type === "error") {
582
- throw new Error(`Redis AUTH failed: ${r.message}`);
589
+ throw new import_errors2.PersistenceError(
590
+ `Redis AUTH failed: ${r.message}`
591
+ );
583
592
  }
584
593
  }
585
594
  if (db && Number.isFinite(db) && db > 0) {
586
595
  const r = await send("SELECT", [String(db)]);
587
596
  if (typeof r === "object" && r && r.type === "error") {
588
- throw new Error(`Redis SELECT failed: ${r.message}`);
597
+ throw new import_errors2.PersistenceError(
598
+ `Redis SELECT failed: ${r.message}`
599
+ );
589
600
  }
590
601
  }
591
602
  ready = true;
@@ -607,7 +618,17 @@ function createRedisClient(options) {
607
618
  // src/framework/redis/redis-store.ts
608
619
  var isRedisError = (r) => Boolean(r) && typeof r === "object" && r.type === "error";
609
620
  function createRedisFrameworkStore(options) {
610
- const client = createRedisClient({ url: options.url });
621
+ const rawClient = createRedisClient({ url: options.url });
622
+ const client = {
623
+ ...rawClient,
624
+ command: async (...args) => {
625
+ try {
626
+ return await rawClient.command(...args);
627
+ } catch (cause) {
628
+ throw new import_errors3.PersistenceError("Redis command failed.", cause);
629
+ }
630
+ }
631
+ };
611
632
  const prefix = options.prefix ?? "kortyx:fw:";
612
633
  const k = (key) => `${prefix}${key}`;
613
634
  return {
@@ -617,7 +638,7 @@ function createRedisFrameworkStore(options) {
617
638
  [...new Set(keys)].map(async (key) => {
618
639
  const value = await client.command("GET", [key]);
619
640
  if (isRedisError(value))
620
- throw new Error(`Redis GET error: ${value.message}`);
641
+ throw new import_errors3.PersistenceError(`Redis GET error: ${value.message}`);
621
642
  return value;
622
643
  })
623
644
  );
@@ -627,7 +648,8 @@ function createRedisFrameworkStore(options) {
627
648
  },
628
649
  async take(key) {
629
650
  const r = await client.command("GETDEL", [k(key)]);
630
- if (isRedisError(r)) throw new Error(`Redis GETDEL error: ${r.message}`);
651
+ if (isRedisError(r))
652
+ throw new import_errors3.PersistenceError(`Redis GETDEL error: ${r.message}`);
631
653
  return typeof r === "string" ? r : null;
632
654
  },
633
655
  async get(key) {
@@ -635,7 +657,7 @@ function createRedisFrameworkStore(options) {
635
657
  if (r === null) return null;
636
658
  if (typeof r === "string") return r;
637
659
  if (isRedisError(r)) {
638
- throw new Error(`Redis GET error: ${r.message}`);
660
+ throw new import_errors3.PersistenceError(`Redis GET error: ${r.message}`);
639
661
  }
640
662
  return null;
641
663
  },
@@ -643,32 +665,32 @@ function createRedisFrameworkStore(options) {
643
665
  const args = [k(key), value, "PX", String(Math.max(1, ttlMs))];
644
666
  const r = await client.command("SET", args);
645
667
  if (isRedisError(r)) {
646
- throw new Error(`Redis SET error: ${r.message}`);
668
+ throw new import_errors3.PersistenceError(`Redis SET error: ${r.message}`);
647
669
  }
648
670
  },
649
671
  async del(key) {
650
672
  const r = await client.command("DEL", [k(key)]);
651
673
  if (isRedisError(r)) {
652
- throw new Error(`Redis DEL error: ${r.message}`);
674
+ throw new import_errors3.PersistenceError(`Redis DEL error: ${r.message}`);
653
675
  }
654
676
  },
655
677
  async hset(key, field, value) {
656
678
  const r = await client.command("HSET", [k(key), field, value]);
657
679
  if (isRedisError(r)) {
658
- throw new Error(`Redis HSET error: ${r.message}`);
680
+ throw new import_errors3.PersistenceError(`Redis HSET error: ${r.message}`);
659
681
  }
660
682
  },
661
683
  async hsetnx(key, field, value) {
662
684
  const r = await client.command("HSETNX", [k(key), field, value]);
663
685
  if (isRedisError(r)) {
664
- throw new Error(`Redis HSETNX error: ${r.message}`);
686
+ throw new import_errors3.PersistenceError(`Redis HSETNX error: ${r.message}`);
665
687
  }
666
688
  return typeof r === "number" ? r : 0;
667
689
  },
668
690
  async hgetall(key) {
669
691
  const r = await client.command("HGETALL", [k(key)]);
670
692
  if (isRedisError(r)) {
671
- throw new Error(`Redis HGETALL error: ${r.message}`);
693
+ throw new import_errors3.PersistenceError(`Redis HGETALL error: ${r.message}`);
672
694
  }
673
695
  if (!Array.isArray(r)) return {};
674
696
  const out = {};
@@ -685,7 +707,7 @@ function createRedisFrameworkStore(options) {
685
707
  String(Math.max(1, ttlMs))
686
708
  ]);
687
709
  if (isRedisError(r)) {
688
- throw new Error(`Redis PEXPIRE error: ${r.message}`);
710
+ throw new import_errors3.PersistenceError(`Redis PEXPIRE error: ${r.message}`);
689
711
  }
690
712
  },
691
713
  async scanKeys(prefixKey) {
@@ -700,7 +722,8 @@ function createRedisFrameworkStore(options) {
700
722
  "COUNT",
701
723
  "200"
702
724
  ]);
703
- if (isRedisError(r)) throw new Error(`Redis SCAN error: ${r.message}`);
725
+ if (isRedisError(r))
726
+ throw new import_errors3.PersistenceError(`Redis SCAN error: ${r.message}`);
704
727
  if (!Array.isArray(r) || r.length < 2) break;
705
728
  cursor = typeof r[0] === "string" ? r[0] : "0";
706
729
  const batch = r[1];
@@ -716,7 +739,7 @@ function createRedisFrameworkStore(options) {
716
739
  if (keys.length === 0) return;
717
740
  const r = await client.command("DEL", keys);
718
741
  if (isRedisError(r)) {
719
- throw new Error(`Redis DEL error: ${r.message}`);
742
+ throw new import_errors3.PersistenceError(`Redis DEL error: ${r.message}`);
720
743
  }
721
744
  }
722
745
  };
@@ -724,6 +747,7 @@ function createRedisFrameworkStore(options) {
724
747
 
725
748
  // src/framework/redis/session-checkpoint-store.ts
726
749
  var import_node_crypto = require("crypto");
750
+ var import_errors4 = require("@kortyx/core/errors");
727
751
  var DEFAULT_MAX_CHECKPOINTS_PER_SESSION = 50;
728
752
  var clone = (value) => value === void 0 ? value : JSON.parse(JSON.stringify(value));
729
753
  var uniqueStrings = (values) => Array.from(
@@ -844,7 +868,12 @@ function createRedisSessionCheckpointStore(options) {
844
868
  },
845
869
  async rollbackTo(id) {
846
870
  const target = await getRecord(id);
847
- if (!target) throw new Error(`Checkpoint "${id}" not found.`);
871
+ if (!target)
872
+ throw new import_errors4.KortyxError("NOT_FOUND", `Checkpoint "${id}" not found.`, {
873
+ category: "request",
874
+ retryable: false,
875
+ safeMessage: "The requested checkpoint was not found."
876
+ });
848
877
  const records = await sortedRecords(target.sessionId);
849
878
  const trailing = records.filter(
850
879
  (record) => record.turnIndex > target.turnIndex
@@ -870,7 +899,12 @@ function createRedisSessionCheckpointStore(options) {
870
899
  },
871
900
  async fork(id, options2) {
872
901
  const source = await getRecord(id);
873
- if (!source) throw new Error(`Checkpoint "${id}" not found.`);
902
+ if (!source)
903
+ throw new import_errors4.KortyxError("NOT_FOUND", `Checkpoint "${id}" not found.`, {
904
+ category: "request",
905
+ retryable: false,
906
+ safeMessage: "The requested checkpoint was not found."
907
+ });
874
908
  const sessionId = options2?.newSessionId || createId("session");
875
909
  const { parentCheckpointId: _parentCheckpointId, ...sourceRecord } = clone(source);
876
910
  const runId = source.activePendingRequests.some(
@@ -906,6 +940,7 @@ function createRedisSessionCheckpointStore(options) {
906
940
 
907
941
  // src/framework/session-checkpoints.ts
908
942
  var import_node_crypto2 = require("crypto");
943
+ var import_errors5 = require("@kortyx/core/errors");
909
944
  var DEFAULT_MAX_CHECKPOINTS_PER_SESSION2 = 50;
910
945
  var createCheckpointId = () => `cp-${(0, import_node_crypto2.randomUUID)()}`;
911
946
  var createSessionId = () => `session-${(0, import_node_crypto2.randomUUID)()}`;
@@ -1009,7 +1044,11 @@ function createInMemorySessionCheckpointStore(options) {
1009
1044
  async rollbackTo(id) {
1010
1045
  const target = byId.get(id);
1011
1046
  if (!target) {
1012
- throw new Error(`Checkpoint "${id}" not found.`);
1047
+ throw new import_errors5.KortyxError("NOT_FOUND", `Checkpoint "${id}" not found.`, {
1048
+ category: "request",
1049
+ retryable: false,
1050
+ safeMessage: "The requested checkpoint was not found."
1051
+ });
1013
1052
  }
1014
1053
  const records = sortedRecords(target.sessionId);
1015
1054
  const trailing = records.filter(
@@ -1037,7 +1076,11 @@ function createInMemorySessionCheckpointStore(options) {
1037
1076
  async fork(id, options2) {
1038
1077
  const source = byId.get(id);
1039
1078
  if (!source) {
1040
- throw new Error(`Checkpoint "${id}" not found.`);
1079
+ throw new import_errors5.KortyxError("NOT_FOUND", `Checkpoint "${id}" not found.`, {
1080
+ category: "request",
1081
+ retryable: false,
1082
+ safeMessage: "The requested checkpoint was not found."
1083
+ });
1041
1084
  }
1042
1085
  const sessionId = options2?.newSessionId || createSessionId();
1043
1086
  const { parentCheckpointId: _parentCheckpointId, ...sourceRecord } = clone2(source);
@@ -1265,6 +1308,7 @@ async function resolveNodeHandler(args) {
1265
1308
 
1266
1309
  // src/graph/call-workflow.ts
1267
1310
  var import_core = require("@kortyx/core");
1311
+ var import_errors6 = require("@kortyx/core/errors");
1268
1312
  var import_hooks = require("@kortyx/hooks");
1269
1313
  var import_singletons = require("@langchain/core/singletons");
1270
1314
  var import_langgraph = require("@langchain/langgraph");
@@ -1276,7 +1320,7 @@ function assertSequentialWorkflow(workflow) {
1276
1320
  else group.plain++;
1277
1321
  groups.set(from, group);
1278
1322
  if (group.plain > 1 || group.plain > 0 && group.conditional > 0) {
1279
- throw new Error(
1323
+ throw new import_errors6.WorkflowContractError(
1280
1324
  `Workflow '${workflow.id}' has parallel edges; child calls require sequential graphs.`
1281
1325
  );
1282
1326
  }
@@ -1289,23 +1333,27 @@ function createWorkflowCallService(config, parent, execution = {}) {
1289
1333
  if (parent) assertSequentialWorkflow(parent);
1290
1334
  const depth = (config.workflowCallDepth ?? 0) + 1;
1291
1335
  if (depth > 16)
1292
- throw new Error("Maximum child workflow depth (16) exceeded.");
1336
+ throw new import_errors6.WorkflowContractError(
1337
+ "Maximum child workflow depth (16) exceeded."
1338
+ );
1293
1339
  const workflow = await config.selectWorkflow?.(args.workflow);
1294
1340
  if (!workflow || workflow.id !== args.workflow)
1295
- throw new Error(`Workflow '${args.workflow}' is not registered.`);
1341
+ throw new import_errors6.WorkflowContractError(
1342
+ `Workflow '${args.workflow}' is not registered.`
1343
+ );
1296
1344
  if (!workflow.inputSchema || !workflow.outputSchema)
1297
- throw new Error(
1345
+ throw new import_errors6.WorkflowContractError(
1298
1346
  `Callable workflow '${workflow.id}' requires inputSchema and outputSchema.`
1299
1347
  );
1300
1348
  if (args.definition && (args.definition.version !== workflow.version || args.definition.inputSchema !== workflow.inputSchema || args.definition.outputSchema !== workflow.outputSchema)) {
1301
- throw new Error(
1349
+ throw new import_errors6.WorkflowContractError(
1302
1350
  "The typed workflow contract does not match the registered definition."
1303
1351
  );
1304
1352
  }
1305
1353
  assertSequentialWorkflow(workflow);
1306
1354
  const snapshot = args.snapshot;
1307
1355
  if (snapshot && snapshot.version !== workflow.version)
1308
- throw new Error(
1356
+ throw new import_errors6.WorkflowContractError(
1309
1357
  `Workflow '${workflow.id}' changed since the call was suspended.`
1310
1358
  );
1311
1359
  const input = snapshot ? void 0 : workflow.inputSchema.parse(args.input);
@@ -1361,14 +1409,12 @@ function createWorkflowCallService(config, parent, execution = {}) {
1361
1409
  };
1362
1410
  return;
1363
1411
  }
1364
- if (event === "error")
1365
- throw new Error(
1366
- String(
1367
- payload.message ?? "Child workflow failed."
1368
- )
1369
- );
1412
+ if (event === "error") {
1413
+ const failure = payload?.failure;
1414
+ throw (0, import_errors6.isFailureDescriptor)(failure) ? (0, import_errors6.errorFromFailure)(failure) : new Error("Child workflow failed.");
1415
+ }
1370
1416
  if (event === "transition")
1371
- throw new Error(
1417
+ throw new import_errors6.WorkflowContractError(
1372
1418
  "transitionTo is not supported inside child workflows; useWorkflow returns to its caller."
1373
1419
  );
1374
1420
  const value = payload;
@@ -1420,11 +1466,29 @@ function createWorkflowCallService(config, parent, execution = {}) {
1420
1466
  );
1421
1467
  } catch (error) {
1422
1468
  (0, import_core.throwIfExecutionAborted)(execution.abortSignal);
1469
+ const checkpoint = await captureGraphSnapshot(saver, threadId).catch(
1470
+ (cause) => {
1471
+ if ((0, import_core.isExecutionLimitReached)(error))
1472
+ throw new import_errors6.PersistenceError(
1473
+ "Cannot checkpoint a suspended child workflow.",
1474
+ cause
1475
+ );
1476
+ return void 0;
1477
+ }
1478
+ );
1479
+ const state = checkpoint?.checkpoint.channel_values;
1480
+ const patch = error?.__kortyxHookStatePatch;
1481
+ const usage = patch?.tokenUsage ?? state?.runtime.tokenUsage;
1482
+ if (error && typeof error === "object")
1483
+ Object.assign(error, { __kortyxChildUsage: usage });
1484
+ try {
1485
+ (0, import_core.throwIfExecutionAborted)(execution.abortSignal);
1486
+ } catch (cancelled) {
1487
+ Object.assign(cancelled, { __kortyxChildUsage: usage });
1488
+ throw cancelled;
1489
+ }
1423
1490
  if ((0, import_core.isExecutionLimitReached)(error)) {
1424
- const checkpoint = await captureGraphSnapshot(saver, threadId);
1425
- if (checkpoint) {
1426
- const state = checkpoint.checkpoint.channel_values;
1427
- const patch = error.__kortyxHookStatePatch;
1491
+ if (checkpoint && state) {
1428
1492
  if (patch) state.runtime = { ...state.runtime, ...patch };
1429
1493
  Object.assign(error, {
1430
1494
  __kortyxChildSnapshot: {
@@ -1442,7 +1506,9 @@ function createWorkflowCallService(config, parent, execution = {}) {
1442
1506
  if (request) {
1443
1507
  const checkpoint = await captureGraphSnapshot(saver, threadId);
1444
1508
  if (!checkpoint)
1445
- throw new Error("Child interrupted without a graph checkpoint.");
1509
+ throw new import_errors6.WorkflowContractError(
1510
+ "Child interrupted without a graph checkpoint."
1511
+ );
1446
1512
  return {
1447
1513
  status: "interrupted",
1448
1514
  usage: request.meta?.__kortyxResumeStatePatch?.tokenUsage ?? result.runtime.tokenUsage,
@@ -1454,7 +1520,17 @@ function createWorkflowCallService(config, parent, execution = {}) {
1454
1520
  }
1455
1521
  };
1456
1522
  }
1457
- const data = workflow.outputSchema.parse(result.data ?? {});
1523
+ let data;
1524
+ try {
1525
+ data = workflow.outputSchema.parse(result.data ?? {});
1526
+ } catch (cause) {
1527
+ if (!Array.isArray((0, import_errors6.errorProperty)(cause, "issues"))) throw cause;
1528
+ throw new import_errors6.ValidationError(
1529
+ "INVALID_OUTPUT",
1530
+ "Child output validation failed.",
1531
+ cause
1532
+ );
1533
+ }
1458
1534
  (0, import_hooks.workflowCallFingerprint)(data);
1459
1535
  return {
1460
1536
  status: "completed",
@@ -1556,12 +1632,20 @@ async function createExecutionGraph(workflow, config, execution = {}) {
1556
1632
  runtimeConfig.emit(event, payload);
1557
1633
  };
1558
1634
  let suspension;
1635
+ let deniedAdmission;
1559
1636
  const hookNodeContext = {
1560
1637
  completeResponse: runtimeConfig.workflowCallDepth ? void 0 : execution.completeResponse,
1561
1638
  abortSignal: execution.abortSignal,
1562
1639
  consumeExecution: (limit) => {
1563
1640
  (0, import_core2.throwIfExecutionAborted)(execution.abortSignal);
1564
- if (execution.budget) (0, import_core2.consumeExecutionBudget)(execution.budget, limit);
1641
+ if (suspension) throw suspension;
1642
+ try {
1643
+ if (execution.budget)
1644
+ (0, import_core2.consumeExecutionBudget)(execution.budget, limit);
1645
+ } catch (error) {
1646
+ deniedAdmission = error;
1647
+ throw error;
1648
+ }
1565
1649
  },
1566
1650
  workflowCallTelemetry: runtimeConfig.telemetry ? {
1567
1651
  ...runtimeConfig.telemetry,
@@ -1667,12 +1751,12 @@ async function createExecutionGraph(workflow, config, execution = {}) {
1667
1751
  params: nodeParams ?? {}
1668
1752
  });
1669
1753
  (0, import_core2.throwIfExecutionAborted)(execution.abortSignal);
1670
- if (execution.budget) (0, import_core2.assertExecutionBudget)(execution.budget);
1754
+ if (deniedAdmission) throw deniedAdmission;
1671
1755
  if (suspension) throw suspension;
1672
1756
  return result;
1673
1757
  } catch (error) {
1674
1758
  (0, import_core2.throwIfExecutionAborted)(execution.abortSignal);
1675
- if (execution.budget) (0, import_core2.assertExecutionBudget)(execution.budget);
1759
+ if (deniedAdmission) throw deniedAdmission;
1676
1760
  throw error;
1677
1761
  }
1678
1762
  });
@@ -1876,6 +1960,7 @@ function makeRequestId(prefix = "req") {
1876
1960
  var import_promises2 = require("fs/promises");
1877
1961
  var import_node_path2 = require("path");
1878
1962
  var import_core3 = require("@kortyx/core");
1963
+ var import_errors7 = require("@kortyx/core/errors");
1879
1964
  var DEFAULT_EXTENSIONS = [
1880
1965
  ".workflow.ts",
1881
1966
  ".workflow.mts",
@@ -1949,8 +2034,14 @@ function createFileWorkflowRegistry(options) {
1949
2034
  const fallback = selectOptions?.fallbackId ?? fallbackId;
1950
2035
  const selected = workflows[id] ?? (fallback ? workflows[fallback] : null);
1951
2036
  if (!selected) {
1952
- throw new Error(
1953
- `No workflow found for id "${id}" and no fallback "${fallback}" workflow available`
2037
+ throw new import_errors7.KortyxError(
2038
+ "UNKNOWN_WORKFLOW",
2039
+ `No workflow found for id "${id}" and no fallback "${fallback}" workflow available`,
2040
+ {
2041
+ category: "request",
2042
+ retryable: false,
2043
+ safeMessage: "The requested workflow is not registered."
2044
+ }
1954
2045
  );
1955
2046
  }
1956
2047
  return selected;
@@ -1968,6 +2059,7 @@ function createFileWorkflowRegistry(options) {
1968
2059
  }
1969
2060
 
1970
2061
  // src/registry/in-memory-registry.ts
2062
+ var import_errors8 = require("@kortyx/core/errors");
1971
2063
  function createInMemoryWorkflowRegistry(workflows, options) {
1972
2064
  const byId = /* @__PURE__ */ new Map();
1973
2065
  for (const wf of workflows) byId.set(wf.id, wf);
@@ -1983,8 +2075,14 @@ function createInMemoryWorkflowRegistry(workflows, options) {
1983
2075
  const fallback = selectOptions?.fallbackId ?? fallbackId;
1984
2076
  const wf = byId.get(id) ?? (fallback ? byId.get(fallback) : void 0);
1985
2077
  if (!wf) {
1986
- throw new Error(
1987
- `Workflow "${id}" not found${fallback ? ` (fallback "${fallback}" missing too)` : ""}.`
2078
+ throw new import_errors8.KortyxError(
2079
+ "UNKNOWN_WORKFLOW",
2080
+ `Workflow "${id}" not found${fallback ? ` (fallback "${fallback}" missing too)` : ""}.`,
2081
+ {
2082
+ category: "request",
2083
+ retryable: false,
2084
+ safeMessage: "The requested workflow is not registered."
2085
+ }
1988
2086
  );
1989
2087
  }
1990
2088
  return wf;