@kortyx/runtime 0.11.1 → 0.14.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 +48 -0
- package/dist/index.d.ts +26 -7
- package/dist/index.js +366 -33
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
var index_exports = {};
|
|
21
21
|
__export(index_exports, {
|
|
22
22
|
buildInitialGraphState: () => buildInitialGraphState,
|
|
23
|
+
captureGraphSnapshot: () => captureGraphSnapshot,
|
|
23
24
|
clearRegisteredNodes: () => clearRegisteredNodes,
|
|
24
25
|
createExecutionGraph: () => createExecutionGraph,
|
|
25
26
|
createFileWorkflowRegistry: () => createFileWorkflowRegistry,
|
|
@@ -35,7 +36,8 @@ __export(index_exports, {
|
|
|
35
36
|
makeRequestId: () => makeRequestId,
|
|
36
37
|
makeResumeToken: () => makeResumeToken,
|
|
37
38
|
registerNode: () => registerNode,
|
|
38
|
-
resolveNode: () => resolveNode
|
|
39
|
+
resolveNode: () => resolveNode,
|
|
40
|
+
restoreGraphSnapshot: () => restoreGraphSnapshot
|
|
39
41
|
});
|
|
40
42
|
module.exports = __toCommonJS(index_exports);
|
|
41
43
|
|
|
@@ -100,7 +102,7 @@ function createInMemoryCheckpointSaver(options) {
|
|
|
100
102
|
}
|
|
101
103
|
};
|
|
102
104
|
}
|
|
103
|
-
return tuple;
|
|
105
|
+
return JSON.parse(JSON.stringify(tuple));
|
|
104
106
|
},
|
|
105
107
|
async *list(_config, _options) {
|
|
106
108
|
},
|
|
@@ -195,6 +197,12 @@ function createInMemoryPendingRequestStore() {
|
|
|
195
197
|
}
|
|
196
198
|
};
|
|
197
199
|
return {
|
|
200
|
+
async take(token) {
|
|
201
|
+
prune();
|
|
202
|
+
const record = store.get(token);
|
|
203
|
+
store.delete(token);
|
|
204
|
+
return record ? JSON.parse(JSON.stringify(record)) : null;
|
|
205
|
+
},
|
|
198
206
|
async save(rec) {
|
|
199
207
|
prune();
|
|
200
208
|
store.set(rec.token, rec);
|
|
@@ -219,6 +227,12 @@ function createRedisPendingRequestStore(options) {
|
|
|
219
227
|
const prefix = options.prefix ?? "kortyx:pending:";
|
|
220
228
|
const key = (token) => `${prefix}${token}`;
|
|
221
229
|
return {
|
|
230
|
+
...options.store.take ? {
|
|
231
|
+
take: async (token) => {
|
|
232
|
+
const raw = await options.store.take(key(token));
|
|
233
|
+
return raw ? JSON.parse(raw) : null;
|
|
234
|
+
}
|
|
235
|
+
} : {},
|
|
222
236
|
async save(rec) {
|
|
223
237
|
await options.store.set(key(rec.token), JSON.stringify(rec), rec.ttlMs);
|
|
224
238
|
},
|
|
@@ -586,6 +600,11 @@ function createRedisFrameworkStore(options) {
|
|
|
586
600
|
const prefix = options.prefix ?? "kortyx:fw:";
|
|
587
601
|
const k = (key) => `${prefix}${key}`;
|
|
588
602
|
return {
|
|
603
|
+
async take(key) {
|
|
604
|
+
const r = await client.command("GETDEL", [k(key)]);
|
|
605
|
+
if (isRedisError(r)) throw new Error(`Redis GETDEL error: ${r.message}`);
|
|
606
|
+
return typeof r === "string" ? r : null;
|
|
607
|
+
},
|
|
589
608
|
async get(key) {
|
|
590
609
|
const r = await client.command("GET", [k(key)]);
|
|
591
610
|
if (r === null) return null;
|
|
@@ -828,8 +847,12 @@ function createRedisSessionCheckpointStore(options) {
|
|
|
828
847
|
if (!source) throw new Error(`Checkpoint "${id}" not found.`);
|
|
829
848
|
const sessionId = options2?.newSessionId || createId("session");
|
|
830
849
|
const { parentCheckpointId: _parentCheckpointId, ...sourceRecord } = clone(source);
|
|
850
|
+
const runId = source.activePendingRequests.some(
|
|
851
|
+
(request) => request.graphSnapshot
|
|
852
|
+
) ? `run-${(0, import_node_crypto.randomUUID)()}` : source.runId;
|
|
831
853
|
const forked = {
|
|
832
854
|
...sourceRecord,
|
|
855
|
+
runId,
|
|
833
856
|
id: createId("cp"),
|
|
834
857
|
sessionId,
|
|
835
858
|
parentSessionId: source.sessionId,
|
|
@@ -837,6 +860,7 @@ function createRedisSessionCheckpointStore(options) {
|
|
|
837
860
|
activePendingRequests: clone(source.activePendingRequests).map(
|
|
838
861
|
(request) => ({
|
|
839
862
|
...request,
|
|
863
|
+
runId,
|
|
840
864
|
token: createResumeToken(),
|
|
841
865
|
requestId: createId("human"),
|
|
842
866
|
sessionId
|
|
@@ -991,8 +1015,12 @@ function createInMemorySessionCheckpointStore(options) {
|
|
|
991
1015
|
}
|
|
992
1016
|
const sessionId = options2?.newSessionId || createSessionId();
|
|
993
1017
|
const { parentCheckpointId: _parentCheckpointId, ...sourceRecord } = clone2(source);
|
|
1018
|
+
const runId = source.activePendingRequests.some(
|
|
1019
|
+
(request) => request.graphSnapshot
|
|
1020
|
+
) ? `run-${(0, import_node_crypto2.randomUUID)()}` : source.runId;
|
|
994
1021
|
const forked = {
|
|
995
1022
|
...sourceRecord,
|
|
1023
|
+
runId,
|
|
996
1024
|
id: createCheckpointId(),
|
|
997
1025
|
sessionId,
|
|
998
1026
|
parentSessionId: source.sessionId,
|
|
@@ -1000,6 +1028,7 @@ function createInMemorySessionCheckpointStore(options) {
|
|
|
1000
1028
|
activePendingRequests: clone2(source.activePendingRequests).map(
|
|
1001
1029
|
(request) => ({
|
|
1002
1030
|
...request,
|
|
1031
|
+
runId,
|
|
1003
1032
|
token: createResumeToken2(),
|
|
1004
1033
|
requestId: createRequestId(),
|
|
1005
1034
|
sessionId
|
|
@@ -1089,10 +1118,44 @@ function createFrameworkAdapterFromEnv(env = process.env) {
|
|
|
1089
1118
|
return createInMemoryFrameworkAdapter({ ...ttlMs ? { ttlMs } : {} });
|
|
1090
1119
|
}
|
|
1091
1120
|
|
|
1121
|
+
// src/framework/graph-snapshot.ts
|
|
1122
|
+
async function captureGraphSnapshot(saver, threadId, checkpointId) {
|
|
1123
|
+
const tuple = await saver.getTuple({
|
|
1124
|
+
configurable: {
|
|
1125
|
+
thread_id: threadId,
|
|
1126
|
+
checkpoint_ns: "",
|
|
1127
|
+
...checkpointId ? { checkpoint_id: checkpointId } : {}
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
return tuple ? JSON.parse(JSON.stringify(tuple)) : void 0;
|
|
1131
|
+
}
|
|
1132
|
+
async function restoreGraphSnapshot(saver, snapshot, threadId) {
|
|
1133
|
+
const copy = JSON.parse(JSON.stringify(snapshot));
|
|
1134
|
+
const config = await saver.put(
|
|
1135
|
+
{ configurable: { thread_id: threadId, checkpoint_ns: "" } },
|
|
1136
|
+
copy.checkpoint,
|
|
1137
|
+
copy.metadata,
|
|
1138
|
+
copy.checkpoint.channel_versions
|
|
1139
|
+
);
|
|
1140
|
+
const reset = saver;
|
|
1141
|
+
if (reset.deleteCheckpointWrites)
|
|
1142
|
+
await reset.deleteCheckpointWrites(threadId, "", copy.checkpoint.id);
|
|
1143
|
+
const writes = /* @__PURE__ */ new Map();
|
|
1144
|
+
for (const [task, channel, value] of copy.pendingWrites ?? []) {
|
|
1145
|
+
const group = writes.get(task) ?? [];
|
|
1146
|
+
group.push([channel, value]);
|
|
1147
|
+
writes.set(task, group);
|
|
1148
|
+
}
|
|
1149
|
+
for (const [task, group] of writes)
|
|
1150
|
+
await saver.putWrites(config, group, task);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1092
1153
|
// src/graph/create-execution-graph.ts
|
|
1093
|
-
var
|
|
1154
|
+
var import_promises = require("timers/promises");
|
|
1155
|
+
var import_core2 = require("@kortyx/core");
|
|
1156
|
+
var import_hooks2 = require("@kortyx/hooks");
|
|
1094
1157
|
var import_utils = require("@kortyx/utils");
|
|
1095
|
-
var
|
|
1158
|
+
var import_langgraph2 = require("@langchain/langgraph");
|
|
1096
1159
|
|
|
1097
1160
|
// src/node-loader.ts
|
|
1098
1161
|
var import_node_path = require("path");
|
|
@@ -1174,31 +1237,250 @@ async function resolveNodeHandler(args) {
|
|
|
1174
1237
|
return resolveNode(run);
|
|
1175
1238
|
}
|
|
1176
1239
|
|
|
1240
|
+
// src/graph/call-workflow.ts
|
|
1241
|
+
var import_core = require("@kortyx/core");
|
|
1242
|
+
var import_hooks = require("@kortyx/hooks");
|
|
1243
|
+
var import_singletons = require("@langchain/core/singletons");
|
|
1244
|
+
var import_langgraph = require("@langchain/langgraph");
|
|
1245
|
+
function assertSequentialWorkflow(workflow) {
|
|
1246
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1247
|
+
for (const [from, , condition] of workflow.edges) {
|
|
1248
|
+
const group = groups.get(from) ?? { plain: 0, conditional: 0 };
|
|
1249
|
+
if (condition) group.conditional++;
|
|
1250
|
+
else group.plain++;
|
|
1251
|
+
groups.set(from, group);
|
|
1252
|
+
if (group.plain > 1 || group.plain > 0 && group.conditional > 0) {
|
|
1253
|
+
throw new Error(
|
|
1254
|
+
`Workflow '${workflow.id}' has parallel edges; child calls require sequential graphs.`
|
|
1255
|
+
);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
function createWorkflowCallService(config, parent, execution = {}) {
|
|
1260
|
+
return async (args) => {
|
|
1261
|
+
const execute = async () => {
|
|
1262
|
+
(0, import_core.throwIfExecutionAborted)(execution.abortSignal);
|
|
1263
|
+
if (parent) assertSequentialWorkflow(parent);
|
|
1264
|
+
const depth = (config.workflowCallDepth ?? 0) + 1;
|
|
1265
|
+
if (depth > 16)
|
|
1266
|
+
throw new Error("Maximum child workflow depth (16) exceeded.");
|
|
1267
|
+
const workflow = await config.selectWorkflow?.(args.workflow);
|
|
1268
|
+
if (!workflow || workflow.id !== args.workflow)
|
|
1269
|
+
throw new Error(`Workflow '${args.workflow}' is not registered.`);
|
|
1270
|
+
if (!workflow.inputSchema || !workflow.outputSchema)
|
|
1271
|
+
throw new Error(
|
|
1272
|
+
`Callable workflow '${workflow.id}' requires inputSchema and outputSchema.`
|
|
1273
|
+
);
|
|
1274
|
+
if (args.definition && (args.definition.version !== workflow.version || args.definition.inputSchema !== workflow.inputSchema || args.definition.outputSchema !== workflow.outputSchema)) {
|
|
1275
|
+
throw new Error(
|
|
1276
|
+
"The typed workflow contract does not match the registered definition."
|
|
1277
|
+
);
|
|
1278
|
+
}
|
|
1279
|
+
assertSequentialWorkflow(workflow);
|
|
1280
|
+
const snapshot = args.snapshot;
|
|
1281
|
+
if (snapshot && snapshot.version !== workflow.version)
|
|
1282
|
+
throw new Error(
|
|
1283
|
+
`Workflow '${workflow.id}' changed since the call was suspended.`
|
|
1284
|
+
);
|
|
1285
|
+
const input = snapshot ? void 0 : workflow.inputSchema.parse(args.input);
|
|
1286
|
+
if (!snapshot) (0, import_hooks.workflowCallFingerprint)(input);
|
|
1287
|
+
const saver = createInMemoryCheckpointSaver();
|
|
1288
|
+
const threadId = "child";
|
|
1289
|
+
let request;
|
|
1290
|
+
const invocationPath = `${config.invocationPath ?? "root"}/${encodeURIComponent(args.id)}:${args.invocationId}`;
|
|
1291
|
+
let childConfig = {
|
|
1292
|
+
executionBranchId: config.executionBranchId,
|
|
1293
|
+
prepareChildTelemetry: config.prepareChildTelemetry,
|
|
1294
|
+
context: config.context,
|
|
1295
|
+
session: config.session,
|
|
1296
|
+
selectWorkflow: config.selectWorkflow,
|
|
1297
|
+
getProvider: config.getProvider,
|
|
1298
|
+
telemetry: config.telemetry ? {
|
|
1299
|
+
...config.telemetry,
|
|
1300
|
+
correlation: {
|
|
1301
|
+
runId: config.telemetry.correlation?.runId,
|
|
1302
|
+
sessionId: config.telemetry.correlation?.sessionId,
|
|
1303
|
+
workflowId: workflow.id,
|
|
1304
|
+
invocationId: args.invocationId,
|
|
1305
|
+
parentInvocationId: config.telemetry.correlation?.invocationId,
|
|
1306
|
+
branchId: config.executionBranchId ?? config.executionRunId
|
|
1307
|
+
}
|
|
1308
|
+
} : void 0,
|
|
1309
|
+
reasonTrace: config.reasonTrace,
|
|
1310
|
+
checkpointer: saver,
|
|
1311
|
+
executionRunId: config.executionRunId,
|
|
1312
|
+
workflowCallDepth: depth,
|
|
1313
|
+
invocationPath,
|
|
1314
|
+
emit: (event, payload) => {
|
|
1315
|
+
if (event === "interrupt") {
|
|
1316
|
+
const original = payload.input;
|
|
1317
|
+
request = {
|
|
1318
|
+
...original,
|
|
1319
|
+
meta: {
|
|
1320
|
+
...original.meta,
|
|
1321
|
+
workflowCallPath: [
|
|
1322
|
+
{
|
|
1323
|
+
workflowId: workflow.id,
|
|
1324
|
+
invocationId: args.invocationId,
|
|
1325
|
+
callId: args.id
|
|
1326
|
+
},
|
|
1327
|
+
...Array.isArray(original.meta?.workflowCallPath) ? original.meta.workflowCallPath : []
|
|
1328
|
+
],
|
|
1329
|
+
workflowCall: original.meta?.workflowCall ?? {
|
|
1330
|
+
invocationId: args.invocationId,
|
|
1331
|
+
workflowId: workflow.id,
|
|
1332
|
+
nodeId: payload.node
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
if (event === "error")
|
|
1339
|
+
throw new Error(
|
|
1340
|
+
String(
|
|
1341
|
+
payload.message ?? "Child workflow failed."
|
|
1342
|
+
)
|
|
1343
|
+
);
|
|
1344
|
+
if (event === "transition")
|
|
1345
|
+
throw new Error(
|
|
1346
|
+
"transitionTo is not supported inside child workflows; useWorkflow returns to its caller."
|
|
1347
|
+
);
|
|
1348
|
+
const value = payload;
|
|
1349
|
+
const scoped = { ...value, workflow: workflow.id, invocationPath };
|
|
1350
|
+
for (const key of ["node", "streamId", "opId", "segmentId"]) {
|
|
1351
|
+
if (typeof value[key] === "string")
|
|
1352
|
+
scoped[key] = `${config.executionRunId ?? "run"}/${args.invocationId}/${value[key]}`;
|
|
1353
|
+
}
|
|
1354
|
+
config.emit?.(event, scoped);
|
|
1355
|
+
}
|
|
1356
|
+
};
|
|
1357
|
+
childConfig = config.prepareChildTelemetry?.(workflow, childConfig) ?? childConfig;
|
|
1358
|
+
if (snapshot) {
|
|
1359
|
+
if (snapshot.limitPaused)
|
|
1360
|
+
snapshot.checkpoint.checkpoint.channel_values.config = childConfig;
|
|
1361
|
+
await restoreGraphSnapshot(saver, snapshot.checkpoint, threadId);
|
|
1362
|
+
}
|
|
1363
|
+
const graph = await createExecutionGraph(
|
|
1364
|
+
workflow,
|
|
1365
|
+
childConfig,
|
|
1366
|
+
execution
|
|
1367
|
+
);
|
|
1368
|
+
const initial = {
|
|
1369
|
+
input,
|
|
1370
|
+
data: {},
|
|
1371
|
+
runtime: {},
|
|
1372
|
+
config: childConfig,
|
|
1373
|
+
currentWorkflow: workflow.id,
|
|
1374
|
+
lastNode: "__start__",
|
|
1375
|
+
awaitingHumanInput: false,
|
|
1376
|
+
conversationHistory: []
|
|
1377
|
+
};
|
|
1378
|
+
const resumePatch = snapshot?.request?.meta?.__kortyxResumeStatePatch;
|
|
1379
|
+
const command = snapshot?.limitPaused ? null : snapshot ? new import_langgraph.Command({
|
|
1380
|
+
resume: args.response,
|
|
1381
|
+
update: {
|
|
1382
|
+
config: childConfig,
|
|
1383
|
+
...resumePatch ? { runtime: resumePatch } : {}
|
|
1384
|
+
}
|
|
1385
|
+
}) : initial;
|
|
1386
|
+
let result;
|
|
1387
|
+
try {
|
|
1388
|
+
result = await import_singletons.AsyncLocalStorageProviderSingleton.runWithConfig(
|
|
1389
|
+
{},
|
|
1390
|
+
() => graph.invoke(command, {
|
|
1391
|
+
configurable: { thread_id: threadId, checkpoint_ns: "" },
|
|
1392
|
+
callbacks: []
|
|
1393
|
+
})
|
|
1394
|
+
);
|
|
1395
|
+
} catch (error) {
|
|
1396
|
+
(0, import_core.throwIfExecutionAborted)(execution.abortSignal);
|
|
1397
|
+
if ((0, import_core.isExecutionLimitReached)(error)) {
|
|
1398
|
+
const checkpoint = await captureGraphSnapshot(saver, threadId);
|
|
1399
|
+
if (checkpoint) {
|
|
1400
|
+
const state = checkpoint.checkpoint.channel_values;
|
|
1401
|
+
const patch = error.__kortyxHookStatePatch;
|
|
1402
|
+
if (patch) state.runtime = { ...state.runtime, ...patch };
|
|
1403
|
+
Object.assign(error, {
|
|
1404
|
+
__kortyxChildSnapshot: {
|
|
1405
|
+
version: workflow.version,
|
|
1406
|
+
checkpoint,
|
|
1407
|
+
limitPaused: true
|
|
1408
|
+
},
|
|
1409
|
+
__kortyxChildUsage: state.runtime.tokenUsage
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
throw error;
|
|
1414
|
+
}
|
|
1415
|
+
(0, import_core.throwIfExecutionAborted)(execution.abortSignal);
|
|
1416
|
+
if (request) {
|
|
1417
|
+
const checkpoint = await captureGraphSnapshot(saver, threadId);
|
|
1418
|
+
if (!checkpoint)
|
|
1419
|
+
throw new Error("Child interrupted without a graph checkpoint.");
|
|
1420
|
+
return {
|
|
1421
|
+
status: "interrupted",
|
|
1422
|
+
usage: request.meta?.__kortyxResumeStatePatch?.tokenUsage ?? result.runtime.tokenUsage,
|
|
1423
|
+
request,
|
|
1424
|
+
snapshot: {
|
|
1425
|
+
version: workflow.version,
|
|
1426
|
+
checkpoint,
|
|
1427
|
+
request
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
const data = workflow.outputSchema.parse(result.data ?? {});
|
|
1432
|
+
(0, import_hooks.workflowCallFingerprint)(data);
|
|
1433
|
+
return {
|
|
1434
|
+
status: "completed",
|
|
1435
|
+
data,
|
|
1436
|
+
usage: result.runtime.tokenUsage
|
|
1437
|
+
};
|
|
1438
|
+
};
|
|
1439
|
+
const trace = config.telemetry?.trace ?? config.reasonTrace;
|
|
1440
|
+
return trace?.withSpan ? trace.withSpan(
|
|
1441
|
+
{
|
|
1442
|
+
name: "kortyx.workflow.call",
|
|
1443
|
+
attributes: {
|
|
1444
|
+
workflowId: args.workflow,
|
|
1445
|
+
runId: config.executionRunId,
|
|
1446
|
+
branchId: config.executionBranchId ?? config.executionRunId,
|
|
1447
|
+
parentInvocationId: config.telemetry?.correlation?.invocationId,
|
|
1448
|
+
callerNodeExecutionId: args.callerNodeExecutionId,
|
|
1449
|
+
callId: args.id,
|
|
1450
|
+
invocationId: args.invocationId
|
|
1451
|
+
}
|
|
1452
|
+
},
|
|
1453
|
+
execute
|
|
1454
|
+
) : execute();
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1177
1458
|
// src/graph/create-execution-graph.ts
|
|
1178
|
-
async function createExecutionGraph(workflow, config) {
|
|
1179
|
-
const StateAnnotation =
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1459
|
+
async function createExecutionGraph(workflow, config, execution = {}) {
|
|
1460
|
+
const StateAnnotation = import_langgraph2.Annotation.Root({
|
|
1461
|
+
currentWorkflow: import_langgraph2.Annotation,
|
|
1462
|
+
input: import_langgraph2.Annotation,
|
|
1463
|
+
output: import_langgraph2.Annotation,
|
|
1464
|
+
lastNode: import_langgraph2.Annotation,
|
|
1465
|
+
lastCondition: import_langgraph2.Annotation,
|
|
1466
|
+
lastIntent: import_langgraph2.Annotation,
|
|
1467
|
+
runtime: (0, import_langgraph2.Annotation)({
|
|
1186
1468
|
reducer: (l, r) => (0, import_utils.deepMergeWithArrayOverwrite)(l ?? {}, r ?? {}),
|
|
1187
1469
|
default: () => ({})
|
|
1188
1470
|
}),
|
|
1189
|
-
config:
|
|
1190
|
-
transitionTo:
|
|
1191
|
-
data: (0,
|
|
1471
|
+
config: import_langgraph2.Annotation,
|
|
1472
|
+
transitionTo: import_langgraph2.Annotation,
|
|
1473
|
+
data: (0, import_langgraph2.Annotation)({
|
|
1192
1474
|
reducer: (l, r) => (0, import_utils.deepMergeWithArrayOverwrite)(l ?? {}, r ?? {}),
|
|
1193
1475
|
default: () => ({})
|
|
1194
1476
|
}),
|
|
1195
|
-
conversationHistory: (0,
|
|
1477
|
+
conversationHistory: (0, import_langgraph2.Annotation)({
|
|
1196
1478
|
reducer: (l, r) => [...l ?? [], ...r ?? []],
|
|
1197
1479
|
default: () => []
|
|
1198
1480
|
}),
|
|
1199
|
-
awaitingHumanInput:
|
|
1481
|
+
awaitingHumanInput: import_langgraph2.Annotation
|
|
1200
1482
|
});
|
|
1201
|
-
const builder = new
|
|
1483
|
+
const builder = new import_langgraph2.StateGraph(StateAnnotation);
|
|
1202
1484
|
const edgeApi = builder;
|
|
1203
1485
|
const workflowName = workflow.id;
|
|
1204
1486
|
const runtimeConfig = config;
|
|
@@ -1243,14 +1525,41 @@ async function createExecutionGraph(workflow, config) {
|
|
|
1243
1525
|
);
|
|
1244
1526
|
const behavior = nodeConfig.behavior ?? {};
|
|
1245
1527
|
builder.addNode(nodeId, async (state) => {
|
|
1528
|
+
(0, import_core2.throwIfExecutionAborted)(execution.abortSignal);
|
|
1246
1529
|
const emitRuntimeEvent = (event, payload) => {
|
|
1247
1530
|
runtimeConfig.emit(event, payload);
|
|
1248
1531
|
};
|
|
1532
|
+
let suspension;
|
|
1249
1533
|
const hookNodeContext = {
|
|
1534
|
+
abortSignal: execution.abortSignal,
|
|
1535
|
+
consumeExecution: (limit) => {
|
|
1536
|
+
(0, import_core2.throwIfExecutionAborted)(execution.abortSignal);
|
|
1537
|
+
if (execution.budget) (0, import_core2.consumeExecutionBudget)(execution.budget, limit);
|
|
1538
|
+
},
|
|
1539
|
+
workflowCallTelemetry: runtimeConfig.telemetry ? {
|
|
1540
|
+
...runtimeConfig.telemetry,
|
|
1541
|
+
correlation: {
|
|
1542
|
+
...runtimeConfig.telemetry.correlation,
|
|
1543
|
+
runId: runtimeConfig.executionRunId,
|
|
1544
|
+
branchId: runtimeConfig.executionBranchId ?? runtimeConfig.executionRunId
|
|
1545
|
+
}
|
|
1546
|
+
} : void 0,
|
|
1547
|
+
...runtimeConfig.selectWorkflow ? {
|
|
1548
|
+
callWorkflow: createWorkflowCallService(
|
|
1549
|
+
{
|
|
1550
|
+
...runtimeConfig,
|
|
1551
|
+
context: state.config?.context ?? runtimeConfig.context
|
|
1552
|
+
},
|
|
1553
|
+
workflow,
|
|
1554
|
+
execution
|
|
1555
|
+
)
|
|
1556
|
+
} : {},
|
|
1250
1557
|
graph: { name: workflowName, node: nodeId },
|
|
1251
1558
|
config: nodeConfig,
|
|
1252
1559
|
emit: emitRuntimeEvent,
|
|
1253
1560
|
awaitInterrupt: (interruptConfig) => {
|
|
1561
|
+
(0, import_core2.throwIfExecutionAborted)(execution.abortSignal);
|
|
1562
|
+
if (execution.budget) (0, import_core2.assertExecutionBudget)(execution.budget);
|
|
1254
1563
|
const { kind, question } = interruptConfig;
|
|
1255
1564
|
const isMulti = kind === "multi-choice" || interruptConfig.kind !== "text" && interruptConfig.multiple === true;
|
|
1256
1565
|
const sharedInterruptFields = {
|
|
@@ -1272,8 +1581,9 @@ async function createExecutionGraph(workflow, config) {
|
|
|
1272
1581
|
};
|
|
1273
1582
|
let resumed;
|
|
1274
1583
|
try {
|
|
1275
|
-
resumed = (0,
|
|
1584
|
+
resumed = (0, import_langgraph2.interrupt)(payload);
|
|
1276
1585
|
} catch (error) {
|
|
1586
|
+
suspension = error;
|
|
1277
1587
|
emitRuntimeEvent("interrupt", {
|
|
1278
1588
|
node: nodeId,
|
|
1279
1589
|
workflow: workflowName,
|
|
@@ -1307,6 +1617,7 @@ async function createExecutionGraph(workflow, config) {
|
|
|
1307
1617
|
let retryHookRuntime = null;
|
|
1308
1618
|
while (attempt < maxAttempts) {
|
|
1309
1619
|
try {
|
|
1620
|
+
(0, import_core2.throwIfExecutionAborted)(execution.abortSignal);
|
|
1310
1621
|
attempt++;
|
|
1311
1622
|
const attemptState = retryHookRuntime && typeof retryHookRuntime === "object" ? {
|
|
1312
1623
|
...state,
|
|
@@ -1321,13 +1632,23 @@ async function createExecutionGraph(workflow, config) {
|
|
|
1321
1632
|
state: attemptState,
|
|
1322
1633
|
reasonTrace: activeTrace
|
|
1323
1634
|
};
|
|
1324
|
-
const runNode = () => (0,
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1635
|
+
const runNode = () => (0, import_hooks2.runWithHookContext)(hookContext, async () => {
|
|
1636
|
+
try {
|
|
1637
|
+
hookNodeContext.consumeExecution("maxNodeExecutions");
|
|
1638
|
+
const result = await resolvedRun({
|
|
1639
|
+
input: state.input,
|
|
1640
|
+
params: nodeParams ?? {}
|
|
1641
|
+
});
|
|
1642
|
+
(0, import_core2.throwIfExecutionAborted)(execution.abortSignal);
|
|
1643
|
+
if (execution.budget) (0, import_core2.assertExecutionBudget)(execution.budget);
|
|
1644
|
+
if (suspension) throw suspension;
|
|
1645
|
+
return result;
|
|
1646
|
+
} catch (error) {
|
|
1647
|
+
(0, import_core2.throwIfExecutionAborted)(execution.abortSignal);
|
|
1648
|
+
if (execution.budget) (0, import_core2.assertExecutionBudget)(execution.budget);
|
|
1649
|
+
throw error;
|
|
1650
|
+
}
|
|
1651
|
+
});
|
|
1331
1652
|
const context = configContext();
|
|
1332
1653
|
const spanArgs = {
|
|
1333
1654
|
name: "kortyx.node",
|
|
@@ -1339,6 +1660,8 @@ async function createExecutionGraph(workflow, config) {
|
|
|
1339
1660
|
workflowRevisionId: telemetryCorrelation()?.workflowRevisionId
|
|
1340
1661
|
} : {},
|
|
1341
1662
|
...typeof telemetryCorrelation()?.topologyHash === "string" ? { topologyHash: telemetryCorrelation()?.topologyHash } : {},
|
|
1663
|
+
invocationId: runtimeConfig.telemetry?.correlation?.invocationId,
|
|
1664
|
+
branchId: runtimeConfig.executionBranchId ?? runtimeConfig.executionRunId,
|
|
1342
1665
|
nodeId,
|
|
1343
1666
|
attempt,
|
|
1344
1667
|
...typeof context?.userId === "string" ? { userId: context.userId } : {},
|
|
@@ -1369,15 +1692,22 @@ async function createExecutionGraph(workflow, config) {
|
|
|
1369
1692
|
patch
|
|
1370
1693
|
);
|
|
1371
1694
|
}
|
|
1695
|
+
(0, import_core2.throwIfExecutionAborted)(execution.abortSignal);
|
|
1696
|
+
if ((0, import_core2.isExecutionLimitReached)(err)) {
|
|
1697
|
+
Object.assign(err, { nodeId, workflowId: workflowName });
|
|
1698
|
+
throw err;
|
|
1699
|
+
}
|
|
1700
|
+
if ((0, import_core2.isExecutionCancelled)(err) || (0, import_langgraph2.isGraphInterrupt)(err)) throw err;
|
|
1372
1701
|
const hasMore = attempt < maxAttempts;
|
|
1373
1702
|
const delayMs = behavior.retry?.delayMs ?? 0;
|
|
1374
1703
|
if (hasMore && delayMs > 0) {
|
|
1375
|
-
await
|
|
1704
|
+
await (0, import_promises.setTimeout)(delayMs, void 0, { signal: execution.abortSignal });
|
|
1376
1705
|
continue;
|
|
1377
1706
|
}
|
|
1378
1707
|
if (!hasMore) throw err;
|
|
1379
1708
|
}
|
|
1380
1709
|
}
|
|
1710
|
+
(0, import_core2.throwIfExecutionAborted)(execution.abortSignal);
|
|
1381
1711
|
const res = nodeResult ?? {};
|
|
1382
1712
|
if (hookRuntimeUpdates) {
|
|
1383
1713
|
res.infra = {
|
|
@@ -1490,6 +1820,7 @@ async function createExecutionGraph(workflow, config) {
|
|
|
1490
1820
|
const runtimeGraph = graph;
|
|
1491
1821
|
runtimeGraph.name = workflow.id;
|
|
1492
1822
|
runtimeGraph.config = config;
|
|
1823
|
+
runtimeGraph.execution = execution;
|
|
1493
1824
|
runtimeGraph.resume = async (state, input) => {
|
|
1494
1825
|
const resumed = { ...state, input, awaitingHumanInput: false };
|
|
1495
1826
|
return await graph.invoke(resumed);
|
|
@@ -1515,9 +1846,9 @@ function makeRequestId(prefix = "req") {
|
|
|
1515
1846
|
}
|
|
1516
1847
|
|
|
1517
1848
|
// src/registry/file-registry.ts
|
|
1518
|
-
var
|
|
1849
|
+
var import_promises2 = require("fs/promises");
|
|
1519
1850
|
var import_node_path2 = require("path");
|
|
1520
|
-
var
|
|
1851
|
+
var import_core3 = require("@kortyx/core");
|
|
1521
1852
|
var DEFAULT_EXTENSIONS = [
|
|
1522
1853
|
".workflow.ts",
|
|
1523
1854
|
".workflow.mts",
|
|
@@ -1541,15 +1872,15 @@ function createFileWorkflowRegistry(options) {
|
|
|
1541
1872
|
let workflowCache = null;
|
|
1542
1873
|
async function loadWorkflows() {
|
|
1543
1874
|
if (shouldCache && workflowCache) return workflowCache;
|
|
1544
|
-
const files = await (0,
|
|
1875
|
+
const files = await (0, import_promises2.readdir)(workflowsDir, { withFileTypes: true });
|
|
1545
1876
|
const workflowFiles = files.filter((entry) => entry.isFile()).map((entry) => entry.name).filter((file) => isWorkflowFile(file, extensions));
|
|
1546
1877
|
const workflows = {};
|
|
1547
1878
|
for (const file of workflowFiles) {
|
|
1548
1879
|
try {
|
|
1549
1880
|
const modulePath = (0, import_node_path2.join)(workflowsDir, file);
|
|
1550
1881
|
if (isDeclarativeWorkflowFile(file)) {
|
|
1551
|
-
const raw = await (0,
|
|
1552
|
-
const workflow = (0,
|
|
1882
|
+
const raw = await (0, import_promises2.readFile)(modulePath, "utf8");
|
|
1883
|
+
const workflow = (0, import_core3.loadWorkflow)(raw);
|
|
1553
1884
|
workflow.metadata = {
|
|
1554
1885
|
...workflow.metadata ?? {},
|
|
1555
1886
|
__filePath: modulePath
|
|
@@ -1660,6 +1991,7 @@ async function buildInitialGraphState({
|
|
|
1660
1991
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1661
1992
|
0 && (module.exports = {
|
|
1662
1993
|
buildInitialGraphState,
|
|
1994
|
+
captureGraphSnapshot,
|
|
1663
1995
|
clearRegisteredNodes,
|
|
1664
1996
|
createExecutionGraph,
|
|
1665
1997
|
createFileWorkflowRegistry,
|
|
@@ -1675,6 +2007,7 @@ async function buildInitialGraphState({
|
|
|
1675
2007
|
makeRequestId,
|
|
1676
2008
|
makeResumeToken,
|
|
1677
2009
|
registerNode,
|
|
1678
|
-
resolveNode
|
|
2010
|
+
resolveNode,
|
|
2011
|
+
restoreGraphSnapshot
|
|
1679
2012
|
});
|
|
1680
2013
|
//# sourceMappingURL=index.js.map
|