@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 +36 -0
- package/dist/index.js +146 -48
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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))
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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))
|
|
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
|
|
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)
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1341
|
+
throw new import_errors6.WorkflowContractError(
|
|
1342
|
+
`Workflow '${args.workflow}' is not registered.`
|
|
1343
|
+
);
|
|
1296
1344
|
if (!workflow.inputSchema || !workflow.outputSchema)
|
|
1297
|
-
throw new
|
|
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
|
|
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
|
|
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
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
|
1953
|
-
|
|
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
|
|
1987
|
-
|
|
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;
|