@kortyx/agent 0.16.0 → 0.19.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 +53 -0
- package/dist/index.d.ts +177 -8
- package/dist/index.js +1270 -317
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -52,9 +52,11 @@ var require_dist = __commonJS({
|
|
|
52
52
|
var index_exports2 = {};
|
|
53
53
|
__export2(index_exports2, {
|
|
54
54
|
WorkflowCallError: () => WorkflowCallError,
|
|
55
|
+
completeResponse: () => completeResponse,
|
|
55
56
|
createWorkflowHooks: () => createWorkflowHooks,
|
|
56
57
|
emitWorkflowCall: () => emitWorkflowCall2,
|
|
57
58
|
runWithHookContext: () => runWithHookContext,
|
|
59
|
+
useAbortSignal: () => useAbortSignal,
|
|
58
60
|
useInterrupt: () => useInterrupt,
|
|
59
61
|
useNodeState: () => useNodeState,
|
|
60
62
|
useReason: () => useReason2,
|
|
@@ -65,6 +67,7 @@ var require_dist = __commonJS({
|
|
|
65
67
|
workflowCallFingerprint: () => workflowCallFingerprint
|
|
66
68
|
});
|
|
67
69
|
module2.exports = __toCommonJS2(index_exports2);
|
|
70
|
+
var import_zod6 = require("zod");
|
|
68
71
|
var import_node_async_hooks = require("async_hooks");
|
|
69
72
|
var storage = new import_node_async_hooks.AsyncLocalStorage();
|
|
70
73
|
var isRecord5 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -217,6 +220,29 @@ var require_dist = __commonJS({
|
|
|
217
220
|
}
|
|
218
221
|
return ctx;
|
|
219
222
|
}
|
|
223
|
+
function snapshotHookState() {
|
|
224
|
+
const ctx = getHookContext();
|
|
225
|
+
return {
|
|
226
|
+
...ctx.state,
|
|
227
|
+
lastNode: ctx.node.graph.node,
|
|
228
|
+
runtime: { ...ctx.state.runtime, ...buildRuntimeStateUpdates(ctx) }
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
var optionsSchema = import_zod6.z.object({
|
|
232
|
+
message: import_zod6.z.string().optional(),
|
|
233
|
+
data: import_zod6.z.json().optional()
|
|
234
|
+
}).strict();
|
|
235
|
+
async function completeResponse(options = {}) {
|
|
236
|
+
const ctx = getHookContext();
|
|
237
|
+
const parsed = optionsSchema.parse(options);
|
|
238
|
+
if (!ctx.node.completeResponse) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
"completeResponse requires an agent-managed root workflow."
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
await ctx.node.completeResponse(parsed, snapshotHookState());
|
|
244
|
+
}
|
|
245
|
+
var import_core6 = require("@kortyx/core");
|
|
220
246
|
var resolveReasonCheckpointKey = (args) => typeof args.id === "string" && args.id.length > 0 ? `__useReason:${args.id}` : `__useReason:auto:${args.autoIndex}`;
|
|
221
247
|
var readReasonCheckpoint = (raw) => {
|
|
222
248
|
if (!raw || typeof raw !== "object") return void 0;
|
|
@@ -267,6 +293,7 @@ var require_dist = __commonJS({
|
|
|
267
293
|
};
|
|
268
294
|
var awaitInterruptInternal = (args) => {
|
|
269
295
|
const ctx = getHookContext();
|
|
296
|
+
(0, import_core6.throwIfExecutionAborted)(ctx.node.abortSignal);
|
|
270
297
|
const request = parseWithSchema(
|
|
271
298
|
args.requestSchema,
|
|
272
299
|
args.request,
|
|
@@ -299,6 +326,7 @@ var require_dist = __commonJS({
|
|
|
299
326
|
);
|
|
300
327
|
return Promise.resolve(response);
|
|
301
328
|
};
|
|
329
|
+
var import_core52 = require("@kortyx/core");
|
|
302
330
|
var import_node_crypto4 = require("crypto");
|
|
303
331
|
var parseStructuredPath = (path, source) => {
|
|
304
332
|
if (path.length === 0) {
|
|
@@ -386,7 +414,9 @@ var require_dist = __commonJS({
|
|
|
386
414
|
var shouldEmitStructured = (cfg) => Boolean(cfg);
|
|
387
415
|
var shouldStreamStructured = (cfg) => Boolean(cfg) && (cfg?.stream ?? true);
|
|
388
416
|
var import_node_crypto32 = require("crypto");
|
|
417
|
+
var import_core32 = require("@kortyx/core");
|
|
389
418
|
var import_node_crypto22 = require("crypto");
|
|
419
|
+
var import_core22 = require("@kortyx/core");
|
|
390
420
|
var toError = (value) => value instanceof Error ? value : new Error(String(value));
|
|
391
421
|
var createRuntimeId = () => {
|
|
392
422
|
try {
|
|
@@ -413,6 +443,7 @@ var require_dist = __commonJS({
|
|
|
413
443
|
const responseFormat = args.responseFormat ?? args.model.options?.responseFormat;
|
|
414
444
|
const providerOptions = args.providerOptions ?? args.model.options?.providerOptions;
|
|
415
445
|
const tools = args.tools ?? args.model.options?.tools;
|
|
446
|
+
(0, import_core22.throwIfExecutionAborted)(abortSignal);
|
|
416
447
|
const model = args.model.provider.getModel(args.model.modelId, {
|
|
417
448
|
...temperature !== void 0 ? { temperature } : {},
|
|
418
449
|
streaming: stream,
|
|
@@ -505,6 +536,7 @@ var require_dist = __commonJS({
|
|
|
505
536
|
}
|
|
506
537
|
const response2 = await model.stream(messages);
|
|
507
538
|
for await (const chunk of response2) {
|
|
539
|
+
(0, import_core22.throwIfExecutionAborted)(abortSignal);
|
|
508
540
|
switch (chunk.type) {
|
|
509
541
|
case "text-delta": {
|
|
510
542
|
if (chunk.delta.length === 0) break;
|
|
@@ -583,10 +615,12 @@ var require_dist = __commonJS({
|
|
|
583
615
|
...providerMetadata !== void 0 ? { providerMetadata } : {},
|
|
584
616
|
...warnings !== void 0 ? { warnings } : {}
|
|
585
617
|
};
|
|
618
|
+
(0, import_core22.throwIfExecutionAborted)(abortSignal);
|
|
586
619
|
endTrace(traceSpan, result2);
|
|
587
620
|
return result2;
|
|
588
621
|
}
|
|
589
622
|
const response = await model.invoke(messages);
|
|
623
|
+
(0, import_core22.throwIfExecutionAborted)(abortSignal);
|
|
590
624
|
if (emit) {
|
|
591
625
|
emitNodeEvent(args.emitEvent, args.nodeId, "text-start", {
|
|
592
626
|
...commonMeta
|
|
@@ -613,8 +647,9 @@ var require_dist = __commonJS({
|
|
|
613
647
|
endTrace(traceSpan, result);
|
|
614
648
|
return result;
|
|
615
649
|
} catch (error) {
|
|
616
|
-
|
|
617
|
-
|
|
650
|
+
const failure = abortSignal?.aborted ? (0, import_core22.createExecutionCancelledError)() : error;
|
|
651
|
+
failTrace(traceSpan, failure);
|
|
652
|
+
throw failure;
|
|
618
653
|
}
|
|
619
654
|
}
|
|
620
655
|
var createRuntimeId2 = () => {
|
|
@@ -626,6 +661,13 @@ var require_dist = __commonJS({
|
|
|
626
661
|
};
|
|
627
662
|
async function reasonEngine(args, meta, inputOverride) {
|
|
628
663
|
const ctx = getHookContext();
|
|
664
|
+
const abortSignal = (0, import_core32.combineAbortSignals)(
|
|
665
|
+
ctx.node.abortSignal,
|
|
666
|
+
args.abortSignal,
|
|
667
|
+
args.model.options?.abortSignal
|
|
668
|
+
);
|
|
669
|
+
(0, import_core32.throwIfExecutionAborted)(abortSignal);
|
|
670
|
+
ctx.node.consumeExecution?.("maxModelPasses");
|
|
629
671
|
return runReasonEngine({
|
|
630
672
|
model: args.model,
|
|
631
673
|
input: inputOverride ?? args.input,
|
|
@@ -633,7 +675,7 @@ var require_dist = __commonJS({
|
|
|
633
675
|
temperature: args.temperature,
|
|
634
676
|
maxOutputTokens: args.maxOutputTokens,
|
|
635
677
|
stopSequences: args.stopSequences,
|
|
636
|
-
abortSignal
|
|
678
|
+
abortSignal,
|
|
637
679
|
reasoning: args.reasoning,
|
|
638
680
|
responseFormat: args.responseFormat,
|
|
639
681
|
providerOptions: args.providerOptions,
|
|
@@ -815,7 +857,7 @@ var require_dist = __commonJS({
|
|
|
815
857
|
...output !== void 0 ? { output } : {}
|
|
816
858
|
};
|
|
817
859
|
};
|
|
818
|
-
var
|
|
860
|
+
var import_zod22 = require("zod");
|
|
819
861
|
var tryStringify2 = (value) => {
|
|
820
862
|
if (typeof value === "string") return value;
|
|
821
863
|
try {
|
|
@@ -826,7 +868,7 @@ var require_dist = __commonJS({
|
|
|
826
868
|
};
|
|
827
869
|
var tryGetSchemaHint = (schema) => {
|
|
828
870
|
try {
|
|
829
|
-
const hint = (0,
|
|
871
|
+
const hint = (0, import_zod22.toJSONSchema)(schema);
|
|
830
872
|
return JSON.stringify(hint, null, 2);
|
|
831
873
|
} catch {
|
|
832
874
|
return void 0;
|
|
@@ -1205,11 +1247,11 @@ ${tryStringify2(args.response)}`
|
|
|
1205
1247
|
args.path,
|
|
1206
1248
|
"useReason structured field"
|
|
1207
1249
|
);
|
|
1208
|
-
const
|
|
1250
|
+
const matches2 = [];
|
|
1209
1251
|
const visitValue = (start, currentParts) => {
|
|
1210
1252
|
const valueStart = skipWhitespace(args.text, start);
|
|
1211
1253
|
if (pathMatchesPattern(currentParts, targetParts)) {
|
|
1212
|
-
|
|
1254
|
+
matches2.push({
|
|
1213
1255
|
path: currentParts.join("."),
|
|
1214
1256
|
start: valueStart
|
|
1215
1257
|
});
|
|
@@ -1241,7 +1283,7 @@ ${tryStringify2(args.response)}`
|
|
|
1241
1283
|
const valueStart = skipWhitespace(args.text, i + 1);
|
|
1242
1284
|
const childParts = [...currentParts, key.value];
|
|
1243
1285
|
if (pathMatchesPattern(childParts, targetParts)) {
|
|
1244
|
-
|
|
1286
|
+
matches2.push({
|
|
1245
1287
|
path: childParts.join("."),
|
|
1246
1288
|
start: valueStart
|
|
1247
1289
|
});
|
|
@@ -1268,7 +1310,7 @@ ${tryStringify2(args.response)}`
|
|
|
1268
1310
|
if (args.text[i] === "]") return;
|
|
1269
1311
|
const childParts = [...currentParts, String(index)];
|
|
1270
1312
|
if (pathMatchesPattern(childParts, targetParts)) {
|
|
1271
|
-
|
|
1313
|
+
matches2.push({
|
|
1272
1314
|
path: childParts.join("."),
|
|
1273
1315
|
start: i
|
|
1274
1316
|
});
|
|
@@ -1290,7 +1332,7 @@ ${tryStringify2(args.response)}`
|
|
|
1290
1332
|
}
|
|
1291
1333
|
};
|
|
1292
1334
|
visitValue(0, []);
|
|
1293
|
-
return
|
|
1335
|
+
return matches2;
|
|
1294
1336
|
};
|
|
1295
1337
|
var extractCompletedValueAt = (args) => {
|
|
1296
1338
|
const start = skipWhitespace(args.text, args.start);
|
|
@@ -1574,6 +1616,7 @@ ${tryStringify2(args.response)}`
|
|
|
1574
1616
|
] : [];
|
|
1575
1617
|
});
|
|
1576
1618
|
};
|
|
1619
|
+
var import_core42 = require("@kortyx/core");
|
|
1577
1620
|
var toErrorMessage2 = (error) => error instanceof Error ? error.message : String(error);
|
|
1578
1621
|
var toToolDefinitions = (tools) => tools.map((tool) => ({
|
|
1579
1622
|
name: tool.name,
|
|
@@ -1627,6 +1670,11 @@ ${tryStringify2(args.response)}`
|
|
|
1627
1670
|
var runReasonToolLoop = async (args) => {
|
|
1628
1671
|
const { useReasonArgs, id, opId, traceSpan } = args;
|
|
1629
1672
|
const ctx = getHookContext();
|
|
1673
|
+
const abortSignal = (0, import_core42.combineAbortSignals)(
|
|
1674
|
+
ctx.node.abortSignal,
|
|
1675
|
+
useReasonArgs.abortSignal,
|
|
1676
|
+
useReasonArgs.model.options?.abortSignal
|
|
1677
|
+
);
|
|
1630
1678
|
const tools = useReasonArgs.tools ?? [];
|
|
1631
1679
|
const toolByName = /* @__PURE__ */ new Map();
|
|
1632
1680
|
let validationCompleted = false;
|
|
@@ -1676,6 +1724,7 @@ ${tryStringify2(args.response)}`
|
|
|
1676
1724
|
};
|
|
1677
1725
|
try {
|
|
1678
1726
|
for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
|
|
1727
|
+
(0, import_core42.throwIfExecutionAborted)(abortSignal);
|
|
1679
1728
|
traceSpan?.addEvent?.("useReason.tool-step.start", {
|
|
1680
1729
|
stepIndex,
|
|
1681
1730
|
toolCount: toolDefinitions.length
|
|
@@ -1693,6 +1742,7 @@ ${tryStringify2(args.response)}`
|
|
|
1693
1742
|
finalText = step.text;
|
|
1694
1743
|
finalRaw = step.raw;
|
|
1695
1744
|
aggregatedUsage = mergeUsage(aggregatedUsage, step.usage);
|
|
1745
|
+
accumulateTokenUsage(step.usage);
|
|
1696
1746
|
finalFinishReason = step.finishReason;
|
|
1697
1747
|
aggregatedProviderMetadata = mergeProviderMetadata(
|
|
1698
1748
|
aggregatedProviderMetadata,
|
|
@@ -1726,6 +1776,7 @@ ${tryStringify2(args.response)}`
|
|
|
1726
1776
|
toolCalls
|
|
1727
1777
|
});
|
|
1728
1778
|
for (const toolCall of toolCalls) {
|
|
1779
|
+
(0, import_core42.throwIfExecutionAborted)(abortSignal);
|
|
1729
1780
|
const tool = toolByName.get(toolCall.name);
|
|
1730
1781
|
if (!tool) {
|
|
1731
1782
|
throw new Error(
|
|
@@ -1793,10 +1844,13 @@ ${tryStringify2(args.response)}`
|
|
|
1793
1844
|
}
|
|
1794
1845
|
}
|
|
1795
1846
|
try {
|
|
1847
|
+
(0, import_core42.throwIfExecutionAborted)(abortSignal);
|
|
1848
|
+
ctx.node.consumeExecution?.("maxToolCalls");
|
|
1796
1849
|
const rawResult = await tool.execute(toolCall.input, {
|
|
1797
1850
|
toolCallId: toolCall.id,
|
|
1798
|
-
...
|
|
1851
|
+
...abortSignal ? { abortSignal } : {}
|
|
1799
1852
|
});
|
|
1853
|
+
(0, import_core42.throwIfExecutionAborted)(abortSignal);
|
|
1800
1854
|
const result2 = normalizeToolResult(toolCall, rawResult);
|
|
1801
1855
|
toolResults.push(result2);
|
|
1802
1856
|
allToolResults.push(result2);
|
|
@@ -1816,6 +1870,9 @@ ${tryStringify2(args.response)}`
|
|
|
1816
1870
|
isError: Boolean(result2.isError)
|
|
1817
1871
|
});
|
|
1818
1872
|
} catch (error) {
|
|
1873
|
+
(0, import_core42.throwIfExecutionAborted)(abortSignal);
|
|
1874
|
+
if ((0, import_core42.isExecutionLimitReached)(error) || (0, import_core42.isExecutionCancelled)(error))
|
|
1875
|
+
throw error;
|
|
1819
1876
|
const result2 = {
|
|
1820
1877
|
toolCallId: toolCall.id,
|
|
1821
1878
|
name: tool.name,
|
|
@@ -1937,6 +1994,13 @@ ${tryStringify2(args.response)}`
|
|
|
1937
1994
|
async function useReason(args) {
|
|
1938
1995
|
assertReasoningThoughtsCompatibility(args);
|
|
1939
1996
|
const ctx = getHookContext();
|
|
1997
|
+
const abortSignal = (0, import_core52.combineAbortSignals)(
|
|
1998
|
+
ctx.node.abortSignal,
|
|
1999
|
+
args.abortSignal,
|
|
2000
|
+
args.model.options?.abortSignal
|
|
2001
|
+
);
|
|
2002
|
+
(0, import_core52.throwIfExecutionAborted)(abortSignal);
|
|
2003
|
+
args = { ...args, abortSignal };
|
|
1940
2004
|
const id = typeof args.id === "string" && args.id.length > 0 ? args.id : void 0;
|
|
1941
2005
|
const opId = createRuntimeId2();
|
|
1942
2006
|
const checkpointKey = resolveReasonCheckpointKey({
|
|
@@ -2304,6 +2368,9 @@ ${tryStringify2(args.response)}`
|
|
|
2304
2368
|
function useInterrupt(args) {
|
|
2305
2369
|
return awaitInterruptInternal(args);
|
|
2306
2370
|
}
|
|
2371
|
+
function useAbortSignal() {
|
|
2372
|
+
return getHookContext().node.abortSignal;
|
|
2373
|
+
}
|
|
2307
2374
|
function useRuntimeContext() {
|
|
2308
2375
|
const ctx = getHookContext();
|
|
2309
2376
|
const config = ctx.state.config;
|
|
@@ -2346,6 +2413,7 @@ ${tryStringify2(args.response)}`
|
|
|
2346
2413
|
return [getValue(), setValue];
|
|
2347
2414
|
}
|
|
2348
2415
|
var import_node_crypto5 = require("crypto");
|
|
2416
|
+
var import_core62 = require("@kortyx/core");
|
|
2349
2417
|
var import_node_crypto42 = require("crypto");
|
|
2350
2418
|
function emitWorkflowCall2(telemetry, type, payload) {
|
|
2351
2419
|
const base = telemetry?.correlation;
|
|
@@ -2428,6 +2496,7 @@ ${tryStringify2(args.response)}`
|
|
|
2428
2496
|
}
|
|
2429
2497
|
async function useWorkflow(args) {
|
|
2430
2498
|
const ctx = getHookContext();
|
|
2499
|
+
(0, import_core62.throwIfExecutionAborted)(ctx.node.abortSignal);
|
|
2431
2500
|
const service = ctx.node.callWorkflow;
|
|
2432
2501
|
if (!service)
|
|
2433
2502
|
throw new Error("useWorkflow requires an agent workflow registry.");
|
|
@@ -2461,6 +2530,7 @@ ${tryStringify2(args.response)}`
|
|
|
2461
2530
|
workflow,
|
|
2462
2531
|
"call input or target changed during replay"
|
|
2463
2532
|
);
|
|
2533
|
+
if (!record) ctx.node.consumeExecution?.("maxChildInvocations");
|
|
2464
2534
|
record ??= {
|
|
2465
2535
|
fingerprint,
|
|
2466
2536
|
invocationId: (0, import_node_crypto5.randomUUID)(),
|
|
@@ -2494,6 +2564,15 @@ ${tryStringify2(args.response)}`
|
|
|
2494
2564
|
...content
|
|
2495
2565
|
});
|
|
2496
2566
|
};
|
|
2567
|
+
const recordUsage = (usage) => {
|
|
2568
|
+
if (!usage) return;
|
|
2569
|
+
accumulateTokenUsage({
|
|
2570
|
+
input: usage.input - (record.usage?.input ?? 0),
|
|
2571
|
+
output: usage.output - (record.usage?.output ?? 0),
|
|
2572
|
+
total: usage.total - (record.usage?.total ?? 0)
|
|
2573
|
+
});
|
|
2574
|
+
record.usage = usage;
|
|
2575
|
+
};
|
|
2497
2576
|
const cached = record.status === "completed";
|
|
2498
2577
|
try {
|
|
2499
2578
|
for (const interruption of record.interrupts) {
|
|
@@ -2522,14 +2601,28 @@ ${tryStringify2(args.response)}`
|
|
|
2522
2601
|
...record.interrupts.length ? { response: record.interrupts.at(-1)?.response } : {}
|
|
2523
2602
|
});
|
|
2524
2603
|
} catch (error) {
|
|
2604
|
+
(0, import_core62.throwIfExecutionAborted)(ctx.node.abortSignal);
|
|
2605
|
+
if ((0, import_core62.isExecutionCancelled)(error)) throw error;
|
|
2606
|
+
if ((0, import_core62.isExecutionLimitReached)(error)) {
|
|
2607
|
+
const child = error;
|
|
2608
|
+
if (child.__kortyxChildSnapshot)
|
|
2609
|
+
record.snapshot = child.__kortyxChildSnapshot;
|
|
2610
|
+
recordUsage(child.__kortyxChildUsage);
|
|
2611
|
+
report("suspended", {
|
|
2612
|
+
reason: "limit_reached",
|
|
2613
|
+
limit: error.limitReached
|
|
2614
|
+
});
|
|
2615
|
+
throw error;
|
|
2616
|
+
}
|
|
2525
2617
|
record.status = "failed";
|
|
2526
2618
|
record.error = error instanceof Error ? error.message : String(error);
|
|
2527
2619
|
report("failed", { error: "Child workflow failed" });
|
|
2528
2620
|
throw new WorkflowCallError(workflow, record.error);
|
|
2529
2621
|
}
|
|
2622
|
+
(0, import_core62.throwIfExecutionAborted)(ctx.node.abortSignal);
|
|
2623
|
+
recordUsage(outcome.usage);
|
|
2530
2624
|
if (outcome.status === "completed") {
|
|
2531
2625
|
record.data = outcome.data;
|
|
2532
|
-
accumulateTokenUsage(outcome.usage);
|
|
2533
2626
|
workflowCallFingerprint(record.data);
|
|
2534
2627
|
record.status = "completed";
|
|
2535
2628
|
delete record.snapshot;
|
|
@@ -2569,6 +2662,7 @@ ${tryStringify2(args.response)}`
|
|
|
2569
2662
|
// src/index.ts
|
|
2570
2663
|
var index_exports = {};
|
|
2571
2664
|
__export(index_exports, {
|
|
2665
|
+
ExecutionRequestError: () => ExecutionRequestError,
|
|
2572
2666
|
createAgent: () => createAgent,
|
|
2573
2667
|
createChatRouteHandler: () => createChatRouteHandler,
|
|
2574
2668
|
createCheckpointRouteHandler: () => createCheckpointRouteHandler,
|
|
@@ -2649,6 +2743,8 @@ function parseCheckpointRequestBody(value) {
|
|
|
2649
2743
|
async function handleChatRequestBody(args) {
|
|
2650
2744
|
const { agent, body } = args;
|
|
2651
2745
|
const stream = await agent.streamChat(body.messages, {
|
|
2746
|
+
...args.onExecution ? { onExecution: args.onExecution } : {},
|
|
2747
|
+
...args.abortSignal ? { abortSignal: args.abortSignal } : {},
|
|
2652
2748
|
sessionId: body.sessionId,
|
|
2653
2749
|
workflowId: body.workflowId,
|
|
2654
2750
|
context: body.context
|
|
@@ -2668,7 +2764,12 @@ function createChatRouteHandler(args) {
|
|
|
2668
2764
|
return async function POST(request) {
|
|
2669
2765
|
try {
|
|
2670
2766
|
const body = parseChatRequestBody(await request.json());
|
|
2671
|
-
return await handleChatRequestBody({
|
|
2767
|
+
return await handleChatRequestBody({
|
|
2768
|
+
agent,
|
|
2769
|
+
body,
|
|
2770
|
+
abortSignal: request.signal,
|
|
2771
|
+
onExecution: args.onExecution
|
|
2772
|
+
});
|
|
2672
2773
|
} catch (error) {
|
|
2673
2774
|
return new Response(
|
|
2674
2775
|
JSON.stringify({
|
|
@@ -2734,10 +2835,209 @@ async function* streamChatFromRoute(args) {
|
|
|
2734
2835
|
|
|
2735
2836
|
// src/chat/create-agent.ts
|
|
2736
2837
|
var import_node_path = require("path");
|
|
2838
|
+
var import_core5 = require("@kortyx/core");
|
|
2737
2839
|
var import_providers = require("@kortyx/providers");
|
|
2738
|
-
var
|
|
2840
|
+
var import_runtime5 = require("@kortyx/runtime");
|
|
2841
|
+
var import_zod5 = require("zod");
|
|
2842
|
+
|
|
2843
|
+
// src/execution/execute.ts
|
|
2844
|
+
var import_core4 = require("@kortyx/core");
|
|
2845
|
+
var import_runtime3 = require("@kortyx/runtime");
|
|
2846
|
+
var import_zod3 = require("zod");
|
|
2847
|
+
|
|
2848
|
+
// src/interrupt/resume-handler.ts
|
|
2849
|
+
var import_core3 = require("@kortyx/core");
|
|
2850
|
+
var import_runtime2 = require("@kortyx/runtime");
|
|
2851
|
+
|
|
2852
|
+
// src/orchestrator.ts
|
|
2853
|
+
var import_node_stream = require("stream");
|
|
2854
|
+
var import_core2 = require("@kortyx/core");
|
|
2855
|
+
var import_runtime = require("@kortyx/runtime");
|
|
2856
|
+
var import_langgraph = require("@langchain/langgraph");
|
|
2857
|
+
|
|
2858
|
+
// src/execution/contracts.ts
|
|
2739
2859
|
var import_zod2 = require("zod");
|
|
2740
2860
|
|
|
2861
|
+
// src/execution/types.ts
|
|
2862
|
+
var ExecutionRequestError = class extends Error {
|
|
2863
|
+
constructor(code, message) {
|
|
2864
|
+
super(message);
|
|
2865
|
+
this.code = code;
|
|
2866
|
+
}
|
|
2867
|
+
code;
|
|
2868
|
+
name = "ExecutionRequestError";
|
|
2869
|
+
};
|
|
2870
|
+
|
|
2871
|
+
// src/execution/contracts.ts
|
|
2872
|
+
async function resolveExecutionWorkflow(select, reference) {
|
|
2873
|
+
const id = typeof reference === "string" ? reference : reference.id;
|
|
2874
|
+
let workflow;
|
|
2875
|
+
try {
|
|
2876
|
+
workflow = await select(id);
|
|
2877
|
+
} catch {
|
|
2878
|
+
throw new ExecutionRequestError(
|
|
2879
|
+
"UNKNOWN_WORKFLOW",
|
|
2880
|
+
`Workflow '${id}' is not registered.`
|
|
2881
|
+
);
|
|
2882
|
+
}
|
|
2883
|
+
if (workflow.id !== id)
|
|
2884
|
+
throw new ExecutionRequestError(
|
|
2885
|
+
"UNKNOWN_WORKFLOW",
|
|
2886
|
+
`Workflow '${id}' is not registered.`
|
|
2887
|
+
);
|
|
2888
|
+
if (!workflow.inputSchema || !workflow.outputSchema)
|
|
2889
|
+
throw new ExecutionRequestError(
|
|
2890
|
+
"MISSING_CONTRACT",
|
|
2891
|
+
`Workflow '${id}' requires inputSchema and outputSchema.`
|
|
2892
|
+
);
|
|
2893
|
+
if (typeof reference !== "string" && (reference.version !== workflow.version || reference.inputSchema !== workflow.inputSchema || reference.outputSchema !== workflow.outputSchema))
|
|
2894
|
+
throw new ExecutionRequestError(
|
|
2895
|
+
"CONTRACT_MISMATCH",
|
|
2896
|
+
"The typed workflow contract does not match the registered definition."
|
|
2897
|
+
);
|
|
2898
|
+
return workflow;
|
|
2899
|
+
}
|
|
2900
|
+
function parseExecutionInput(workflow, input) {
|
|
2901
|
+
try {
|
|
2902
|
+
import_zod2.z.json().parse(input);
|
|
2903
|
+
const parsed = workflow.inputSchema ? workflow.inputSchema.parse(input) : input;
|
|
2904
|
+
import_zod2.z.json().parse(parsed);
|
|
2905
|
+
return parsed;
|
|
2906
|
+
} catch (error) {
|
|
2907
|
+
throw new ExecutionRequestError(
|
|
2908
|
+
"INVALID_INPUT",
|
|
2909
|
+
error instanceof Error ? error.message : String(error)
|
|
2910
|
+
);
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
async function validateExecutionOutput(state, select) {
|
|
2914
|
+
if (!state.config?.executionContract) return state;
|
|
2915
|
+
const terminal = await select(String(state.currentWorkflow));
|
|
2916
|
+
const contract = state.config.executionContract;
|
|
2917
|
+
const root = await select(contract.id);
|
|
2918
|
+
if (root.id !== contract.id || root.version !== contract.version)
|
|
2919
|
+
throw new Error("The root workflow changed since execution started.");
|
|
2920
|
+
let data = state.data ?? {};
|
|
2921
|
+
try {
|
|
2922
|
+
for (const schema of /* @__PURE__ */ new Set([terminal.outputSchema, root.outputSchema])) {
|
|
2923
|
+
if (schema) data = schema.parse(data);
|
|
2924
|
+
}
|
|
2925
|
+
if (terminal.outputSchema || root.outputSchema) import_zod2.z.json().parse(data);
|
|
2926
|
+
} catch (error) {
|
|
2927
|
+
const failure = new Error(
|
|
2928
|
+
`Workflow output validation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
2929
|
+
);
|
|
2930
|
+
Object.assign(failure, { code: "INVALID_OUTPUT" });
|
|
2931
|
+
throw failure;
|
|
2932
|
+
}
|
|
2933
|
+
return { ...state, data };
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2936
|
+
// src/stream/response-lifecycle.ts
|
|
2937
|
+
var import_core = require("@kortyx/core");
|
|
2938
|
+
function createResponseLifecycle(args) {
|
|
2939
|
+
const controller = new AbortController();
|
|
2940
|
+
let closed = args.restored;
|
|
2941
|
+
let completing;
|
|
2942
|
+
const abort = () => controller.abort();
|
|
2943
|
+
const detachRequest = () => args.requestSignal?.removeEventListener("abort", abort);
|
|
2944
|
+
if (!closed) {
|
|
2945
|
+
args.requestSignal?.addEventListener("abort", abort, { once: true });
|
|
2946
|
+
if (args.requestSignal?.aborted) abort();
|
|
2947
|
+
}
|
|
2948
|
+
args.executionSignal?.addEventListener("abort", abort, { once: true });
|
|
2949
|
+
if (args.executionSignal?.aborted) abort();
|
|
2950
|
+
return {
|
|
2951
|
+
get closed() {
|
|
2952
|
+
return closed;
|
|
2953
|
+
},
|
|
2954
|
+
signal: controller.signal,
|
|
2955
|
+
disconnect() {
|
|
2956
|
+
if (!closed) abort();
|
|
2957
|
+
},
|
|
2958
|
+
dispose() {
|
|
2959
|
+
detachRequest();
|
|
2960
|
+
args.executionSignal?.removeEventListener("abort", abort);
|
|
2961
|
+
},
|
|
2962
|
+
complete(options, state) {
|
|
2963
|
+
if (!args.enabled || closed) return Promise.resolve();
|
|
2964
|
+
completing ??= (async () => {
|
|
2965
|
+
(0, import_core.throwIfExecutionAborted)(controller.signal);
|
|
2966
|
+
await args.finalize(options, state);
|
|
2967
|
+
(0, import_core.throwIfExecutionAborted)(controller.signal);
|
|
2968
|
+
closed = true;
|
|
2969
|
+
detachRequest();
|
|
2970
|
+
args.onClosed();
|
|
2971
|
+
})();
|
|
2972
|
+
return completing;
|
|
2973
|
+
}
|
|
2974
|
+
};
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2977
|
+
// src/stream/transform-graph-stream-for-ui.ts
|
|
2978
|
+
var isGraphStateOutput = (value) => value !== null && typeof value === "object" && !Array.isArray(value) && "currentWorkflow" in value;
|
|
2979
|
+
async function* transformGraphStreamForUI(stream, options = {}) {
|
|
2980
|
+
const { debug = false, emitStatus = debug } = options;
|
|
2981
|
+
const startedNodes = /* @__PURE__ */ new Set();
|
|
2982
|
+
const endedNodes = /* @__PURE__ */ new Set();
|
|
2983
|
+
let latestGraphState = null;
|
|
2984
|
+
for await (const event of stream) {
|
|
2985
|
+
const { event: type, name, data } = event ?? {};
|
|
2986
|
+
if (debug) console.log(`[debug:event]`, JSON.stringify(event, null, 2));
|
|
2987
|
+
switch (type) {
|
|
2988
|
+
case "on_chain_start":
|
|
2989
|
+
if (name && !name.startsWith("ChannelWrite")) {
|
|
2990
|
+
if (name === "__start__" || name === "__end__") break;
|
|
2991
|
+
if (startedNodes.has(name)) break;
|
|
2992
|
+
startedNodes.add(name);
|
|
2993
|
+
if (debug) console.log(`[debug:start] node=${name}`);
|
|
2994
|
+
if (emitStatus) {
|
|
2995
|
+
yield { type: "status", message: `Processing node: ${name}` };
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
break;
|
|
2999
|
+
case "on_chain_end": {
|
|
3000
|
+
const nodeName = name;
|
|
3001
|
+
const output = data?.output;
|
|
3002
|
+
if (debug)
|
|
3003
|
+
console.log(
|
|
3004
|
+
`[debug:on_chain_end:${nodeName}] output=`,
|
|
3005
|
+
JSON.stringify(output, null, 2)
|
|
3006
|
+
);
|
|
3007
|
+
if (!output || nodeName?.startsWith("ChannelWrite")) break;
|
|
3008
|
+
if (isGraphStateOutput(output)) latestGraphState = output;
|
|
3009
|
+
if (nodeName !== "__start__" && nodeName !== "__end__") {
|
|
3010
|
+
if (!endedNodes.has(nodeName)) {
|
|
3011
|
+
if (emitStatus) {
|
|
3012
|
+
yield {
|
|
3013
|
+
type: "status",
|
|
3014
|
+
message: `\u2705 Completed node: ${nodeName}`
|
|
3015
|
+
};
|
|
3016
|
+
}
|
|
3017
|
+
endedNodes.add(nodeName);
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
break;
|
|
3021
|
+
}
|
|
3022
|
+
case "on_graph_end": {
|
|
3023
|
+
if (debug)
|
|
3024
|
+
console.log(`[debug:on_graph_end]`, JSON.stringify(data, null, 2));
|
|
3025
|
+
const output = data?.output;
|
|
3026
|
+
yield {
|
|
3027
|
+
type: "done",
|
|
3028
|
+
data: isGraphStateOutput(output) ? output : latestGraphState
|
|
3029
|
+
};
|
|
3030
|
+
break;
|
|
3031
|
+
}
|
|
3032
|
+
default:
|
|
3033
|
+
if (debug) {
|
|
3034
|
+
console.warn(`[debug:unknown_event]`, type);
|
|
3035
|
+
}
|
|
3036
|
+
break;
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
|
|
2741
3041
|
// src/telemetry/events.ts
|
|
2742
3042
|
var import_node_crypto = require("crypto");
|
|
2743
3043
|
var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -3008,134 +3308,6 @@ var prepareWorkflowTelemetry = (args) => {
|
|
|
3008
3308
|
};
|
|
3009
3309
|
};
|
|
3010
3310
|
|
|
3011
|
-
// src/telemetry/workflow-call-branch.ts
|
|
3012
|
-
var import_node_crypto3 = require("crypto");
|
|
3013
|
-
var import_hooks = __toESM(require_dist());
|
|
3014
|
-
function restoreWorkflowCallBranch(requests, telemetry, sourceRunId) {
|
|
3015
|
-
const branchId = (0, import_node_crypto3.randomUUID)();
|
|
3016
|
-
const emitted = /* @__PURE__ */ new Set();
|
|
3017
|
-
for (const request of requests) {
|
|
3018
|
-
if (request.state)
|
|
3019
|
-
request.state.config = {
|
|
3020
|
-
...request.state.config,
|
|
3021
|
-
executionBranchId: branchId
|
|
3022
|
-
};
|
|
3023
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3024
|
-
const visit = (value) => {
|
|
3025
|
-
if (!value || typeof value !== "object" || seen.has(value)) return;
|
|
3026
|
-
seen.add(value);
|
|
3027
|
-
const record = value;
|
|
3028
|
-
if (typeof record.invocationId === "string" && typeof record.fingerprint === "string" && Array.isArray(record.interrupts)) {
|
|
3029
|
-
const previous = record.telemetry;
|
|
3030
|
-
if (previous) {
|
|
3031
|
-
record.sequence = 0;
|
|
3032
|
-
record.telemetry = { ...previous, branchId };
|
|
3033
|
-
if (!emitted.has(record.invocationId)) {
|
|
3034
|
-
emitted.add(record.invocationId);
|
|
3035
|
-
(0, import_hooks.emitWorkflowCall)(
|
|
3036
|
-
telemetry ? {
|
|
3037
|
-
...telemetry,
|
|
3038
|
-
correlation: {
|
|
3039
|
-
runId: request.runId,
|
|
3040
|
-
workflowId: request.workflow,
|
|
3041
|
-
...request.sessionId ? { sessionId: request.sessionId } : {}
|
|
3042
|
-
}
|
|
3043
|
-
} : void 0,
|
|
3044
|
-
"workflow.call.restored",
|
|
3045
|
-
{
|
|
3046
|
-
...previous,
|
|
3047
|
-
branchId,
|
|
3048
|
-
sourceBranchId: previous.branchId,
|
|
3049
|
-
sourceRunId,
|
|
3050
|
-
sequence: 0,
|
|
3051
|
-
status: record.status
|
|
3052
|
-
}
|
|
3053
|
-
);
|
|
3054
|
-
}
|
|
3055
|
-
}
|
|
3056
|
-
}
|
|
3057
|
-
for (const child of Object.values(record)) visit(child);
|
|
3058
|
-
};
|
|
3059
|
-
visit(request);
|
|
3060
|
-
}
|
|
3061
|
-
return branchId;
|
|
3062
|
-
}
|
|
3063
|
-
|
|
3064
|
-
// src/chat/process-chat.ts
|
|
3065
|
-
var import_runtime3 = require("@kortyx/runtime");
|
|
3066
|
-
|
|
3067
|
-
// src/interrupt/resume-handler.ts
|
|
3068
|
-
var import_runtime2 = require("@kortyx/runtime");
|
|
3069
|
-
|
|
3070
|
-
// src/orchestrator.ts
|
|
3071
|
-
var import_node_stream = require("stream");
|
|
3072
|
-
var import_runtime = require("@kortyx/runtime");
|
|
3073
|
-
var import_langgraph = require("@langchain/langgraph");
|
|
3074
|
-
|
|
3075
|
-
// src/stream/transform-graph-stream-for-ui.ts
|
|
3076
|
-
var isGraphStateOutput = (value) => value !== null && typeof value === "object" && !Array.isArray(value) && "currentWorkflow" in value;
|
|
3077
|
-
async function* transformGraphStreamForUI(stream, options = {}) {
|
|
3078
|
-
const { debug = false, emitStatus = debug } = options;
|
|
3079
|
-
const startedNodes = /* @__PURE__ */ new Set();
|
|
3080
|
-
const endedNodes = /* @__PURE__ */ new Set();
|
|
3081
|
-
let latestGraphState = null;
|
|
3082
|
-
for await (const event of stream) {
|
|
3083
|
-
const { event: type, name, data } = event ?? {};
|
|
3084
|
-
if (debug) console.log(`[debug:event]`, JSON.stringify(event, null, 2));
|
|
3085
|
-
switch (type) {
|
|
3086
|
-
case "on_chain_start":
|
|
3087
|
-
if (name && !name.startsWith("ChannelWrite")) {
|
|
3088
|
-
if (name === "__start__" || name === "__end__") break;
|
|
3089
|
-
if (startedNodes.has(name)) break;
|
|
3090
|
-
startedNodes.add(name);
|
|
3091
|
-
if (debug) console.log(`[debug:start] node=${name}`);
|
|
3092
|
-
if (emitStatus) {
|
|
3093
|
-
yield { type: "status", message: `Processing node: ${name}` };
|
|
3094
|
-
}
|
|
3095
|
-
}
|
|
3096
|
-
break;
|
|
3097
|
-
case "on_chain_end": {
|
|
3098
|
-
const nodeName = name;
|
|
3099
|
-
const output = data?.output;
|
|
3100
|
-
if (debug)
|
|
3101
|
-
console.log(
|
|
3102
|
-
`[debug:on_chain_end:${nodeName}] output=`,
|
|
3103
|
-
JSON.stringify(output, null, 2)
|
|
3104
|
-
);
|
|
3105
|
-
if (!output || nodeName?.startsWith("ChannelWrite")) break;
|
|
3106
|
-
if (isGraphStateOutput(output)) latestGraphState = output;
|
|
3107
|
-
if (nodeName !== "__start__" && nodeName !== "__end__") {
|
|
3108
|
-
if (!endedNodes.has(nodeName)) {
|
|
3109
|
-
if (emitStatus) {
|
|
3110
|
-
yield {
|
|
3111
|
-
type: "status",
|
|
3112
|
-
message: `\u2705 Completed node: ${nodeName}`
|
|
3113
|
-
};
|
|
3114
|
-
}
|
|
3115
|
-
endedNodes.add(nodeName);
|
|
3116
|
-
}
|
|
3117
|
-
}
|
|
3118
|
-
break;
|
|
3119
|
-
}
|
|
3120
|
-
case "on_graph_end": {
|
|
3121
|
-
if (debug)
|
|
3122
|
-
console.log(`[debug:on_graph_end]`, JSON.stringify(data, null, 2));
|
|
3123
|
-
const output = data?.output;
|
|
3124
|
-
yield {
|
|
3125
|
-
type: "done",
|
|
3126
|
-
data: isGraphStateOutput(output) ? output : latestGraphState
|
|
3127
|
-
};
|
|
3128
|
-
break;
|
|
3129
|
-
}
|
|
3130
|
-
default:
|
|
3131
|
-
if (debug) {
|
|
3132
|
-
console.warn(`[debug:unknown_event]`, type);
|
|
3133
|
-
}
|
|
3134
|
-
break;
|
|
3135
|
-
}
|
|
3136
|
-
}
|
|
3137
|
-
}
|
|
3138
|
-
|
|
3139
3311
|
// src/orchestrator.ts
|
|
3140
3312
|
var isRecord3 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3141
3313
|
var isGraphState = (value) => isRecord3(value) && typeof value.currentWorkflow === "string";
|
|
@@ -3157,16 +3329,101 @@ async function orchestrateGraphStream({
|
|
|
3157
3329
|
graph,
|
|
3158
3330
|
state,
|
|
3159
3331
|
config: initialConfig,
|
|
3332
|
+
abortSignal: requestSignal,
|
|
3333
|
+
executionSignal,
|
|
3334
|
+
onExecution,
|
|
3335
|
+
emitOutput = true,
|
|
3336
|
+
onOutcome,
|
|
3160
3337
|
selectWorkflow,
|
|
3161
3338
|
knownWorkflowIds,
|
|
3162
3339
|
frameworkAdapter
|
|
3163
3340
|
}) {
|
|
3164
3341
|
const out = new import_node_stream.PassThrough({ objectMode: true });
|
|
3342
|
+
const write = (chunk) => {
|
|
3343
|
+
if (emitOutput && !response.closed && !out.destroyed) out.write(chunk);
|
|
3344
|
+
};
|
|
3345
|
+
let outcomeState = state;
|
|
3346
|
+
let outcomeError;
|
|
3347
|
+
let sessionCheckpointId = initialConfig.responseCheckpointId;
|
|
3165
3348
|
let config = initialConfig;
|
|
3349
|
+
const budget = config.executionBudget;
|
|
3166
3350
|
let currentGraph = graph;
|
|
3167
3351
|
let currentState = state;
|
|
3168
3352
|
let finished = false;
|
|
3169
|
-
|
|
3353
|
+
const response = createResponseLifecycle({
|
|
3354
|
+
enabled: emitOutput,
|
|
3355
|
+
restored: initialConfig.responseCompleted === true,
|
|
3356
|
+
requestSignal,
|
|
3357
|
+
executionSignal,
|
|
3358
|
+
onClosed: () => {
|
|
3359
|
+
out.write({ type: "done" });
|
|
3360
|
+
out.end();
|
|
3361
|
+
},
|
|
3362
|
+
finalize: async (options, nodeState) => {
|
|
3363
|
+
const snapshot = JSON.parse(JSON.stringify(nodeState));
|
|
3364
|
+
delete snapshot.config.responseCompleted;
|
|
3365
|
+
delete snapshot.config.responseCheckpointId;
|
|
3366
|
+
if (options.message !== void 0) {
|
|
3367
|
+
forwardEmit("message", {
|
|
3368
|
+
node: nodeState.lastNode,
|
|
3369
|
+
content: options.message
|
|
3370
|
+
});
|
|
3371
|
+
}
|
|
3372
|
+
if (options.data !== void 0) {
|
|
3373
|
+
forwardEmit("structured_data", {
|
|
3374
|
+
node: nodeState.lastNode,
|
|
3375
|
+
data: options.data
|
|
3376
|
+
});
|
|
3377
|
+
}
|
|
3378
|
+
if (sessionId && frameworkAdapter) {
|
|
3379
|
+
const checkpoint = await frameworkAdapter.sessionCheckpoints.append({
|
|
3380
|
+
sessionId,
|
|
3381
|
+
runId,
|
|
3382
|
+
workflow: String(snapshot.currentWorkflow),
|
|
3383
|
+
state: snapshot,
|
|
3384
|
+
nodes: [...touchedNodes],
|
|
3385
|
+
structuredStreamIds: [...structuredStreamIds],
|
|
3386
|
+
pendingRequests: []
|
|
3387
|
+
});
|
|
3388
|
+
sessionCheckpointId = checkpoint.id;
|
|
3389
|
+
write({
|
|
3390
|
+
type: "checkpoint",
|
|
3391
|
+
id: checkpoint.id,
|
|
3392
|
+
sessionId,
|
|
3393
|
+
turnIndex: checkpoint.turnIndex
|
|
3394
|
+
});
|
|
3395
|
+
emitTelemetryEvent({
|
|
3396
|
+
config,
|
|
3397
|
+
type: "session.checkpointed",
|
|
3398
|
+
payload: {
|
|
3399
|
+
checkpointId: checkpoint.id,
|
|
3400
|
+
turnIndex: checkpoint.turnIndex,
|
|
3401
|
+
nodes: checkpoint.nodes
|
|
3402
|
+
}
|
|
3403
|
+
});
|
|
3404
|
+
}
|
|
3405
|
+
(0, import_core2.throwIfExecutionAborted)(abortSignal);
|
|
3406
|
+
config.responseCompleted = true;
|
|
3407
|
+
config.responseCheckpointId = sessionCheckpointId;
|
|
3408
|
+
currentState.config.responseCompleted = true;
|
|
3409
|
+
nodeState.config.responseCompleted = true;
|
|
3410
|
+
currentGraph.config.responseCompleted = true;
|
|
3411
|
+
emitTelemetryEvent({
|
|
3412
|
+
config,
|
|
3413
|
+
type: "response.completed",
|
|
3414
|
+
payload: {
|
|
3415
|
+
nodeId: nodeState.lastNode,
|
|
3416
|
+
checkpointId: sessionCheckpointId
|
|
3417
|
+
},
|
|
3418
|
+
flush: true
|
|
3419
|
+
});
|
|
3420
|
+
}
|
|
3421
|
+
});
|
|
3422
|
+
const abortSignal = response.signal;
|
|
3423
|
+
out.on("close", () => {
|
|
3424
|
+
if (!finished) response.disconnect();
|
|
3425
|
+
});
|
|
3426
|
+
let structuredSeq = 0;
|
|
3170
3427
|
const debugEnabled = Boolean(config?.features?.tracing);
|
|
3171
3428
|
const traceAdapter = getTraceAdapter(config);
|
|
3172
3429
|
const contextMeta = isRecord3(config.context) ? config.context : {};
|
|
@@ -3206,7 +3463,7 @@ async function orchestrateGraphStream({
|
|
|
3206
3463
|
},
|
|
3207
3464
|
tags: Array.isArray(telemetryConfig.tags) ? telemetryConfig.tags : void 0,
|
|
3208
3465
|
captureContent: telemetryConfig.captureContent,
|
|
3209
|
-
|
|
3466
|
+
input: typeof state.input === "string" ? state.input : JSON.stringify(state.input)
|
|
3210
3467
|
}
|
|
3211
3468
|
};
|
|
3212
3469
|
const namespacesUsed = /* @__PURE__ */ new Set();
|
|
@@ -3217,7 +3474,7 @@ async function orchestrateGraphStream({
|
|
|
3217
3474
|
try {
|
|
3218
3475
|
const sid = config?.session?.id;
|
|
3219
3476
|
if (sid && typeof sid === "string") {
|
|
3220
|
-
|
|
3477
|
+
write({ type: "session", sessionId: sid });
|
|
3221
3478
|
}
|
|
3222
3479
|
} catch {
|
|
3223
3480
|
}
|
|
@@ -3241,6 +3498,7 @@ async function orchestrateGraphStream({
|
|
|
3241
3498
|
}
|
|
3242
3499
|
lastOutputSegmentId = textStreamId;
|
|
3243
3500
|
accumulatedOutput += delta;
|
|
3501
|
+
if (!emitOutput) accumulatedOutput = accumulatedOutput.slice(0, 65536);
|
|
3244
3502
|
};
|
|
3245
3503
|
const nextStructuredStreamId = () => `${runId}:structured:${structuredSeq++}`;
|
|
3246
3504
|
const runTraceEndArgs = () => accumulatedOutput.length > 0 ? { telemetry: { output: accumulatedOutput } } : void 0;
|
|
@@ -3248,17 +3506,25 @@ async function orchestrateGraphStream({
|
|
|
3248
3506
|
let wroteHumanInput = false;
|
|
3249
3507
|
const pendingStore = frameworkAdapter?.pendingRequests;
|
|
3250
3508
|
const sealPendingSnapshots = async () => {
|
|
3509
|
+
if (budget) outcomeState.config.executionBudget = budget;
|
|
3510
|
+
if (response.closed) outcomeState.config.responseCompleted = true;
|
|
3251
3511
|
if (typeof frameworkAdapter?.checkpointer?.getTuple !== "function") return;
|
|
3252
3512
|
for (const [token, request] of activePendingRequests) {
|
|
3513
|
+
if (budget) request.state.config.executionBudget = budget;
|
|
3514
|
+
if (response.closed) request.state.config.responseCompleted = true;
|
|
3253
3515
|
const graphSnapshot = await (0, import_runtime.captureGraphSnapshot)(
|
|
3254
3516
|
frameworkAdapter.checkpointer,
|
|
3255
3517
|
runId,
|
|
3256
3518
|
request.graphCheckpointId
|
|
3257
3519
|
);
|
|
3258
3520
|
if (graphSnapshot) {
|
|
3259
|
-
const sealed = { ...request, graphSnapshot };
|
|
3521
|
+
const sealed = { ...request, graphSnapshot, ready: true };
|
|
3260
3522
|
activePendingRequests.set(token, sealed);
|
|
3261
|
-
await pendingStore?.update(token, {
|
|
3523
|
+
await pendingStore?.update(token, {
|
|
3524
|
+
graphSnapshot,
|
|
3525
|
+
ready: true,
|
|
3526
|
+
state: request.state
|
|
3527
|
+
});
|
|
3262
3528
|
}
|
|
3263
3529
|
}
|
|
3264
3530
|
};
|
|
@@ -3274,6 +3540,8 @@ async function orchestrateGraphStream({
|
|
|
3274
3540
|
const isText = kind === "text";
|
|
3275
3541
|
const record = {
|
|
3276
3542
|
token,
|
|
3543
|
+
ready: false,
|
|
3544
|
+
responseCompleted: response.closed,
|
|
3277
3545
|
requestId,
|
|
3278
3546
|
sessionId,
|
|
3279
3547
|
runId,
|
|
@@ -3309,6 +3577,7 @@ async function orchestrateGraphStream({
|
|
|
3309
3577
|
activePendingRequests.set(record.token, record);
|
|
3310
3578
|
if (pendingStore) {
|
|
3311
3579
|
const savePromise = pendingStore.save(record).catch((error) => {
|
|
3580
|
+
outcomeError = error;
|
|
3312
3581
|
console.error("[orchestrator] failed to save pending request", error);
|
|
3313
3582
|
});
|
|
3314
3583
|
pendingRequestWrites.push(savePromise);
|
|
@@ -3318,7 +3587,7 @@ async function orchestrateGraphStream({
|
|
|
3318
3587
|
([key]) => !key.startsWith("__kortyx")
|
|
3319
3588
|
)
|
|
3320
3589
|
) : void 0;
|
|
3321
|
-
|
|
3590
|
+
write({
|
|
3322
3591
|
type: "interrupt",
|
|
3323
3592
|
requestId: record.requestId,
|
|
3324
3593
|
resumeToken: record.token,
|
|
@@ -3351,6 +3620,7 @@ async function orchestrateGraphStream({
|
|
|
3351
3620
|
workflowCallPath: record.schema.meta?.workflowCallPath ?? null,
|
|
3352
3621
|
branchId: config.executionBranchId ?? runId,
|
|
3353
3622
|
interruptId: record.requestId,
|
|
3623
|
+
responseCompleted: response.closed,
|
|
3354
3624
|
requestId: record.requestId,
|
|
3355
3625
|
kind: record.schema.kind,
|
|
3356
3626
|
interactionMode: record.schema.kind === "text" ? "freeform" : record.options.length > 0 ? "static-options" : record.schema.schemaId ? "dynamic-picker" : "unknown",
|
|
@@ -3380,8 +3650,9 @@ async function orchestrateGraphStream({
|
|
|
3380
3650
|
const msg = String(
|
|
3381
3651
|
payload?.message ?? "Unexpected error"
|
|
3382
3652
|
);
|
|
3383
|
-
|
|
3384
|
-
|
|
3653
|
+
outcomeError = new Error(msg);
|
|
3654
|
+
write({ type: "error", message: msg });
|
|
3655
|
+
write({ type: "done" });
|
|
3385
3656
|
finished = true;
|
|
3386
3657
|
out.end();
|
|
3387
3658
|
return;
|
|
@@ -3394,14 +3665,14 @@ async function orchestrateGraphStream({
|
|
|
3394
3665
|
if (msg && msg === lastStatusMsg && now - lastStatusAt < 250) return;
|
|
3395
3666
|
lastStatusMsg = msg;
|
|
3396
3667
|
lastStatusAt = now;
|
|
3397
|
-
|
|
3668
|
+
write({ type: "status", message: msg });
|
|
3398
3669
|
return;
|
|
3399
3670
|
}
|
|
3400
3671
|
if (event === "text-start") {
|
|
3401
3672
|
const node = payload?.node;
|
|
3402
3673
|
if (!node) return;
|
|
3403
3674
|
touchedNodes.add(node);
|
|
3404
|
-
|
|
3675
|
+
write({
|
|
3405
3676
|
type: "text-start",
|
|
3406
3677
|
node,
|
|
3407
3678
|
...typeof payload.id === "string" ? { id: payload.id } : {},
|
|
@@ -3416,7 +3687,7 @@ async function orchestrateGraphStream({
|
|
|
3416
3687
|
if (!node || !delta) return;
|
|
3417
3688
|
touchedNodes.add(node);
|
|
3418
3689
|
appendAccumulatedOutput(payload, node, delta);
|
|
3419
|
-
|
|
3690
|
+
write({
|
|
3420
3691
|
type: "text-delta",
|
|
3421
3692
|
delta,
|
|
3422
3693
|
node,
|
|
@@ -3430,7 +3701,7 @@ async function orchestrateGraphStream({
|
|
|
3430
3701
|
const node = payload?.node;
|
|
3431
3702
|
if (!node) return;
|
|
3432
3703
|
touchedNodes.add(node);
|
|
3433
|
-
|
|
3704
|
+
write({
|
|
3434
3705
|
type: "text-end",
|
|
3435
3706
|
node,
|
|
3436
3707
|
...typeof payload.id === "string" ? { id: payload.id } : {},
|
|
@@ -3444,7 +3715,7 @@ async function orchestrateGraphStream({
|
|
|
3444
3715
|
if (typeof payloadObj.tool !== "string" || typeof payloadObj.toolCallId !== "string") {
|
|
3445
3716
|
return;
|
|
3446
3717
|
}
|
|
3447
|
-
|
|
3718
|
+
write({
|
|
3448
3719
|
type: "tool-call-start",
|
|
3449
3720
|
tool: payloadObj.tool,
|
|
3450
3721
|
toolCallId: payloadObj.toolCallId,
|
|
@@ -3461,7 +3732,7 @@ async function orchestrateGraphStream({
|
|
|
3461
3732
|
if (typeof payloadObj.tool !== "string" || typeof payloadObj.toolCallId !== "string") {
|
|
3462
3733
|
return;
|
|
3463
3734
|
}
|
|
3464
|
-
|
|
3735
|
+
write({
|
|
3465
3736
|
type: "tool-call-result",
|
|
3466
3737
|
tool: payloadObj.tool,
|
|
3467
3738
|
toolCallId: payloadObj.toolCallId,
|
|
@@ -3480,7 +3751,7 @@ async function orchestrateGraphStream({
|
|
|
3480
3751
|
if (typeof payloadObj.tool !== "string" || typeof payloadObj.toolCallId !== "string") {
|
|
3481
3752
|
return;
|
|
3482
3753
|
}
|
|
3483
|
-
|
|
3754
|
+
write({
|
|
3484
3755
|
type: "tool-call-error",
|
|
3485
3756
|
tool: payloadObj.tool,
|
|
3486
3757
|
toolCallId: payloadObj.toolCallId,
|
|
@@ -3496,7 +3767,7 @@ async function orchestrateGraphStream({
|
|
|
3496
3767
|
const node = payload?.node;
|
|
3497
3768
|
const text = String(payload?.content ?? "");
|
|
3498
3769
|
if (node) touchedNodes.add(node);
|
|
3499
|
-
|
|
3770
|
+
write({ type: "message", node, content: text });
|
|
3500
3771
|
return;
|
|
3501
3772
|
}
|
|
3502
3773
|
if (event === "structured_data") {
|
|
@@ -3505,7 +3776,7 @@ async function orchestrateGraphStream({
|
|
|
3505
3776
|
const streamId = typeof payloadObj.streamId === "string" && payloadObj.streamId ? payloadObj.streamId : nextStructuredStreamId();
|
|
3506
3777
|
structuredStreamIds.add(streamId);
|
|
3507
3778
|
if (payloadObj.node) touchedNodes.add(payloadObj.node);
|
|
3508
|
-
|
|
3779
|
+
write({
|
|
3509
3780
|
type: "structured-data",
|
|
3510
3781
|
node: payloadObj.node,
|
|
3511
3782
|
streamId,
|
|
@@ -3531,7 +3802,7 @@ async function orchestrateGraphStream({
|
|
|
3531
3802
|
return;
|
|
3532
3803
|
}
|
|
3533
3804
|
if (event === "transition") {
|
|
3534
|
-
|
|
3805
|
+
write({
|
|
3535
3806
|
type: "transition",
|
|
3536
3807
|
transitionTo: payload?.transitionTo,
|
|
3537
3808
|
payload: payload?.payload ?? {}
|
|
@@ -3559,10 +3830,15 @@ async function orchestrateGraphStream({
|
|
|
3559
3830
|
};
|
|
3560
3831
|
const runLoop = async (runTraceSpan) => {
|
|
3561
3832
|
while (true) {
|
|
3833
|
+
(0, import_core2.throwIfExecutionAborted)(abortSignal);
|
|
3562
3834
|
let workflowFinalState = null;
|
|
3563
3835
|
let workflowFinalGraphCheckpointId;
|
|
3564
3836
|
let sawWorkflowDone = false;
|
|
3565
3837
|
let workflowDoneData;
|
|
3838
|
+
currentGraph.execution ??= {};
|
|
3839
|
+
currentGraph.execution.abortSignal = abortSignal;
|
|
3840
|
+
currentGraph.execution.budget = budget;
|
|
3841
|
+
currentGraph.execution.completeResponse = response.complete;
|
|
3566
3842
|
currentGraph.config = currentGraph.config || {};
|
|
3567
3843
|
currentGraph.config.emit = forwardEmit;
|
|
3568
3844
|
currentGraph.config.executionRunId = runId;
|
|
@@ -3595,7 +3871,7 @@ async function orchestrateGraphStream({
|
|
|
3595
3871
|
};
|
|
3596
3872
|
};
|
|
3597
3873
|
if (debugEnabled) {
|
|
3598
|
-
|
|
3874
|
+
write({
|
|
3599
3875
|
type: "status",
|
|
3600
3876
|
message: `\u{1F9F5} thread_id=${threadId} run_id=${runId} workflow=${currentState.currentWorkflow}`
|
|
3601
3877
|
});
|
|
@@ -3614,16 +3890,32 @@ async function orchestrateGraphStream({
|
|
|
3614
3890
|
}
|
|
3615
3891
|
});
|
|
3616
3892
|
if (debugEnabled) {
|
|
3617
|
-
|
|
3893
|
+
write({
|
|
3618
3894
|
type: "status",
|
|
3619
3895
|
message: `\u25B6\uFE0F streamEvents invoke: resume=${Boolean(currentGraph.config?.resume)} thread_id=${threadId} run_id=${runId} ns=${String(currentState.currentWorkflow || "default")}`
|
|
3620
3896
|
});
|
|
3621
3897
|
}
|
|
3622
|
-
const
|
|
3898
|
+
const observedStream = (async function* () {
|
|
3899
|
+
for await (const event of runtimeStream) {
|
|
3900
|
+
const raw = event;
|
|
3901
|
+
if ((raw.event === "on_chain_end" || raw.event === "on_graph_end") && !raw.name?.startsWith("ChannelWrite") && isGraphState(raw.data?.output)) {
|
|
3902
|
+
outcomeState = raw.data.output;
|
|
3903
|
+
}
|
|
3904
|
+
yield event;
|
|
3905
|
+
}
|
|
3906
|
+
})();
|
|
3907
|
+
const uiStream = transformGraphStreamForUI(observedStream, {
|
|
3623
3908
|
debug: debugEnabled,
|
|
3624
3909
|
emitStatus: debugEnabled
|
|
3625
3910
|
});
|
|
3626
|
-
|
|
3911
|
+
if (!emitOutput) {
|
|
3912
|
+
for await (const _event of observedStream) {
|
|
3913
|
+
if (finished) break;
|
|
3914
|
+
}
|
|
3915
|
+
sawWorkflowDone = true;
|
|
3916
|
+
workflowDoneData = outcomeState;
|
|
3917
|
+
}
|
|
3918
|
+
for await (const chunk of emitOutput ? uiStream : []) {
|
|
3627
3919
|
if (finished) break;
|
|
3628
3920
|
if ("node" in chunk && typeof chunk.node === "string") {
|
|
3629
3921
|
touchedNodes.add(chunk.node);
|
|
@@ -3633,14 +3925,18 @@ async function orchestrateGraphStream({
|
|
|
3633
3925
|
workflowDoneData = chunk.data;
|
|
3634
3926
|
continue;
|
|
3635
3927
|
}
|
|
3636
|
-
|
|
3928
|
+
write(chunk);
|
|
3929
|
+
}
|
|
3930
|
+
if (finished) {
|
|
3931
|
+
runTraceSpan.fail?.(outcomeError);
|
|
3932
|
+
return;
|
|
3637
3933
|
}
|
|
3638
|
-
if (finished) return;
|
|
3639
3934
|
if (sawWorkflowDone) {
|
|
3640
3935
|
const graphSnapshot = await readLatestGraphSnapshot();
|
|
3641
3936
|
workflowFinalGraphCheckpointId = graphSnapshot?.checkpointId;
|
|
3642
3937
|
workflowFinalState = isGraphState(workflowDoneData) ? workflowDoneData : graphSnapshot?.state ?? currentState;
|
|
3643
3938
|
}
|
|
3939
|
+
(0, import_core2.throwIfExecutionAborted)(abortSignal);
|
|
3644
3940
|
const transitionTo = pending.to;
|
|
3645
3941
|
const transitionPayload = pending.payload;
|
|
3646
3942
|
const sourceNodeId = pending.sourceNodeId;
|
|
@@ -3677,6 +3973,10 @@ async function orchestrateGraphStream({
|
|
|
3677
3973
|
} : {}
|
|
3678
3974
|
}
|
|
3679
3975
|
});
|
|
3976
|
+
delete config.resume;
|
|
3977
|
+
delete config.resumeValue;
|
|
3978
|
+
delete config.resumeUpdate;
|
|
3979
|
+
delete config.resumeCheckpointId;
|
|
3680
3980
|
const nextGraph = await (0, import_runtime.createExecutionGraph)(nextWorkflow, {
|
|
3681
3981
|
...config,
|
|
3682
3982
|
emit: forwardEmit
|
|
@@ -3687,12 +3987,13 @@ async function orchestrateGraphStream({
|
|
|
3687
3987
|
...transitionPayload
|
|
3688
3988
|
};
|
|
3689
3989
|
const rawInputFromPayload = transitionPayload?.rawInput;
|
|
3690
|
-
const
|
|
3990
|
+
const rawNextInput = typeof rawInputFromPayload === "string" ? rawInputFromPayload : currentState.input;
|
|
3691
3991
|
currentState = {
|
|
3692
3992
|
...currentState,
|
|
3993
|
+
runtime: workflowFinalState?.runtime ?? currentState.runtime,
|
|
3693
3994
|
currentWorkflow: transitionTo,
|
|
3694
3995
|
config,
|
|
3695
|
-
input:
|
|
3996
|
+
input: nextWorkflow.inputSchema ? parseExecutionInput(nextWorkflow, rawNextInput) : rawNextInput,
|
|
3696
3997
|
data: mergedData,
|
|
3697
3998
|
ui: {}
|
|
3698
3999
|
// reset UI layer on new graph
|
|
@@ -3700,14 +4001,18 @@ async function orchestrateGraphStream({
|
|
|
3700
4001
|
currentGraph = nextGraph;
|
|
3701
4002
|
continue;
|
|
3702
4003
|
} catch (err) {
|
|
4004
|
+
(0, import_core2.throwIfExecutionAborted)(abortSignal);
|
|
4005
|
+
if ((0, import_core2.isExecutionCancelled)(err)) throw err;
|
|
4006
|
+
outcomeError = err;
|
|
4007
|
+
runTraceSpan.fail?.(err);
|
|
3703
4008
|
runTraceSpan.addEvent?.("kortyx.transition.error", {
|
|
3704
4009
|
transitionTo
|
|
3705
4010
|
});
|
|
3706
|
-
|
|
4011
|
+
write({
|
|
3707
4012
|
type: "error",
|
|
3708
4013
|
message: `Transition failed to '${transitionTo}': ${err instanceof Error ? err.message : String(err)}`
|
|
3709
4014
|
});
|
|
3710
|
-
|
|
4015
|
+
write({ type: "done" });
|
|
3711
4016
|
out.end();
|
|
3712
4017
|
return;
|
|
3713
4018
|
}
|
|
@@ -3737,6 +4042,10 @@ async function orchestrateGraphStream({
|
|
|
3737
4042
|
}
|
|
3738
4043
|
const shouldKeepFrameworkState = Boolean(pendingRecordToken) || Boolean(workflowFinalState?.awaitingHumanInput);
|
|
3739
4044
|
if (!shouldKeepFrameworkState) {
|
|
4045
|
+
workflowFinalState = await validateExecutionOutput(
|
|
4046
|
+
workflowFinalState,
|
|
4047
|
+
selectWorkflow
|
|
4048
|
+
);
|
|
3740
4049
|
try {
|
|
3741
4050
|
if (frameworkAdapter?.cleanupRun) {
|
|
3742
4051
|
await frameworkAdapter.cleanupRun(
|
|
@@ -3753,6 +4062,12 @@ async function orchestrateGraphStream({
|
|
|
3753
4062
|
console.error("[orchestrator] framework cleanup failed", e);
|
|
3754
4063
|
}
|
|
3755
4064
|
}
|
|
4065
|
+
outcomeState = workflowFinalState;
|
|
4066
|
+
if (!emitOutput && !shouldKeepFrameworkState)
|
|
4067
|
+
accumulatedOutput = JSON.stringify(workflowFinalState.data).slice(
|
|
4068
|
+
0,
|
|
4069
|
+
65536
|
|
4070
|
+
);
|
|
3756
4071
|
finished = true;
|
|
3757
4072
|
runTraceSpan.setAttributes?.({
|
|
3758
4073
|
"kortyx.run.awaiting_human_input": shouldKeepFrameworkState,
|
|
@@ -3761,7 +4076,7 @@ async function orchestrateGraphStream({
|
|
|
3761
4076
|
runTraceSpan.end?.(runTraceEndArgs());
|
|
3762
4077
|
const resolvedSessionId2 = config?.session?.id || sessionId || "";
|
|
3763
4078
|
await sealPendingSnapshots();
|
|
3764
|
-
if (resolvedSessionId2 && frameworkAdapter?.sessionCheckpoints) {
|
|
4079
|
+
if (!response.closed && resolvedSessionId2 && frameworkAdapter?.sessionCheckpoints) {
|
|
3765
4080
|
try {
|
|
3766
4081
|
const checkpoint = await frameworkAdapter.sessionCheckpoints.append(
|
|
3767
4082
|
{
|
|
@@ -3775,7 +4090,8 @@ async function orchestrateGraphStream({
|
|
|
3775
4090
|
pendingRequests: Array.from(activePendingRequests.values())
|
|
3776
4091
|
}
|
|
3777
4092
|
);
|
|
3778
|
-
|
|
4093
|
+
sessionCheckpointId = checkpoint.id;
|
|
4094
|
+
write({
|
|
3779
4095
|
type: "checkpoint",
|
|
3780
4096
|
id: checkpoint.id,
|
|
3781
4097
|
sessionId: checkpoint.sessionId,
|
|
@@ -3793,15 +4109,23 @@ async function orchestrateGraphStream({
|
|
|
3793
4109
|
}
|
|
3794
4110
|
});
|
|
3795
4111
|
} catch (error) {
|
|
4112
|
+
outcomeError = error;
|
|
3796
4113
|
console.error("[orchestrator] session checkpoint failed", error);
|
|
3797
4114
|
}
|
|
3798
4115
|
}
|
|
3799
|
-
|
|
4116
|
+
write({ type: "done", data: workflowFinalState });
|
|
3800
4117
|
out.end();
|
|
3801
4118
|
return;
|
|
3802
4119
|
}
|
|
3803
4120
|
const naturalGraphSnapshot = await readLatestGraphSnapshot();
|
|
3804
|
-
|
|
4121
|
+
let naturalFinalState = naturalGraphSnapshot?.state ?? currentState;
|
|
4122
|
+
if (!pendingRecordToken && !naturalFinalState.awaitingHumanInput) {
|
|
4123
|
+
naturalFinalState = await validateExecutionOutput(
|
|
4124
|
+
naturalFinalState,
|
|
4125
|
+
selectWorkflow
|
|
4126
|
+
);
|
|
4127
|
+
}
|
|
4128
|
+
outcomeState = naturalFinalState;
|
|
3805
4129
|
if (pendingRequestWrites.length > 0) {
|
|
3806
4130
|
await Promise.all(pendingRequestWrites);
|
|
3807
4131
|
}
|
|
@@ -3823,7 +4147,7 @@ async function orchestrateGraphStream({
|
|
|
3823
4147
|
}
|
|
3824
4148
|
const resolvedSessionId = config?.session?.id || sessionId || "";
|
|
3825
4149
|
await sealPendingSnapshots();
|
|
3826
|
-
if (resolvedSessionId && frameworkAdapter?.sessionCheckpoints) {
|
|
4150
|
+
if (!response.closed && resolvedSessionId && frameworkAdapter?.sessionCheckpoints) {
|
|
3827
4151
|
try {
|
|
3828
4152
|
const checkpoint = await frameworkAdapter.sessionCheckpoints.append({
|
|
3829
4153
|
sessionId: resolvedSessionId,
|
|
@@ -3835,7 +4159,8 @@ async function orchestrateGraphStream({
|
|
|
3835
4159
|
structuredStreamIds: Array.from(structuredStreamIds),
|
|
3836
4160
|
pendingRequests: Array.from(activePendingRequests.values())
|
|
3837
4161
|
});
|
|
3838
|
-
|
|
4162
|
+
sessionCheckpointId = checkpoint.id;
|
|
4163
|
+
write({
|
|
3839
4164
|
type: "checkpoint",
|
|
3840
4165
|
id: checkpoint.id,
|
|
3841
4166
|
sessionId: checkpoint.sessionId,
|
|
@@ -3853,14 +4178,16 @@ async function orchestrateGraphStream({
|
|
|
3853
4178
|
}
|
|
3854
4179
|
});
|
|
3855
4180
|
} catch (error) {
|
|
4181
|
+
outcomeError = error;
|
|
3856
4182
|
console.error("[orchestrator] session checkpoint failed", error);
|
|
3857
4183
|
}
|
|
3858
4184
|
}
|
|
3859
4185
|
runTraceSpan.setAttributes?.({
|
|
3860
4186
|
"kortyx.run.final_workflow": String(naturalFinalState.currentWorkflow)
|
|
3861
4187
|
});
|
|
4188
|
+
finished = true;
|
|
3862
4189
|
runTraceSpan.end?.(runTraceEndArgs());
|
|
3863
|
-
|
|
4190
|
+
write({ type: "done", data: naturalFinalState });
|
|
3864
4191
|
out.end();
|
|
3865
4192
|
return;
|
|
3866
4193
|
}
|
|
@@ -3868,7 +4195,7 @@ async function orchestrateGraphStream({
|
|
|
3868
4195
|
const emitTraceChunk = () => {
|
|
3869
4196
|
const traceContext = traceAdapter?.getActiveContext?.();
|
|
3870
4197
|
if (!traceContext) return;
|
|
3871
|
-
|
|
4198
|
+
write({
|
|
3872
4199
|
type: "trace",
|
|
3873
4200
|
traceId: traceContext.traceId,
|
|
3874
4201
|
spanId: traceContext.spanId,
|
|
@@ -3884,17 +4211,173 @@ async function orchestrateGraphStream({
|
|
|
3884
4211
|
emitTraceChunk();
|
|
3885
4212
|
return runLoop(runTraceSpan);
|
|
3886
4213
|
}) : runLoop(fallbackRunSpan ?? {});
|
|
3887
|
-
|
|
4214
|
+
const finishCancelled = async () => {
|
|
4215
|
+
emitTelemetryEvent({
|
|
4216
|
+
config,
|
|
4217
|
+
type: "run.cancelled",
|
|
4218
|
+
correlation: { runId, workflowId: outcomeState.currentWorkflow },
|
|
4219
|
+
payload: { reason: "execution_aborted" },
|
|
4220
|
+
flush: true
|
|
4221
|
+
});
|
|
4222
|
+
await Promise.allSettled(pendingRequestWrites);
|
|
4223
|
+
await Promise.allSettled(
|
|
4224
|
+
[...activePendingRequests.keys()].map(
|
|
4225
|
+
(token) => pendingStore?.delete(token)
|
|
4226
|
+
)
|
|
4227
|
+
);
|
|
4228
|
+
activePendingRequests.clear();
|
|
4229
|
+
try {
|
|
4230
|
+
await frameworkAdapter?.cleanupRun?.(runId, [...namespacesUsed]);
|
|
4231
|
+
} catch (cleanupError) {
|
|
4232
|
+
console.error("[cancel:cleanupRun]", cleanupError);
|
|
4233
|
+
}
|
|
4234
|
+
outcomeError = (0, import_core2.createExecutionCancelledError)();
|
|
4235
|
+
fallbackRunSpan?.setAttributes?.({ "kortyx.run.cancelled": true });
|
|
4236
|
+
fallbackRunSpan?.end?.();
|
|
4237
|
+
write({ type: "cancelled", runId, reason: "Execution cancelled." });
|
|
4238
|
+
write({ type: "done" });
|
|
4239
|
+
finished = true;
|
|
4240
|
+
out.end();
|
|
4241
|
+
};
|
|
4242
|
+
const completion = runPromise.catch(async (err) => {
|
|
4243
|
+
if (abortSignal?.aborted || (0, import_core2.isExecutionCancelled)(err)) {
|
|
4244
|
+
await finishCancelled();
|
|
4245
|
+
return;
|
|
4246
|
+
}
|
|
4247
|
+
if ((0, import_core2.isExecutionLimitReached)(err)) {
|
|
4248
|
+
try {
|
|
4249
|
+
const snapshot = frameworkAdapter && await (0, import_runtime.captureGraphSnapshot)(frameworkAdapter.checkpointer, runId);
|
|
4250
|
+
(0, import_core2.throwIfExecutionAborted)(abortSignal);
|
|
4251
|
+
if (!snapshot)
|
|
4252
|
+
throw new Error(
|
|
4253
|
+
"Execution limit reached without a resumable checkpoint."
|
|
4254
|
+
);
|
|
4255
|
+
const savedState = snapshot.checkpoint.channel_values;
|
|
4256
|
+
const pausedState = {
|
|
4257
|
+
...savedState,
|
|
4258
|
+
config: {
|
|
4259
|
+
...savedState.config,
|
|
4260
|
+
...config,
|
|
4261
|
+
executionBudget: budget
|
|
4262
|
+
},
|
|
4263
|
+
awaitingHumanInput: true
|
|
4264
|
+
};
|
|
4265
|
+
const patch = err.__kortyxHookStatePatch;
|
|
4266
|
+
currentState = pausedState;
|
|
4267
|
+
outcomeState = pausedState;
|
|
4268
|
+
const limitReached = err.limitReached;
|
|
4269
|
+
write({ type: "limit-reached", runId, ...limitReached });
|
|
4270
|
+
emitTelemetryEvent({
|
|
4271
|
+
config,
|
|
4272
|
+
type: "run.limit_reached",
|
|
4273
|
+
payload: limitReached
|
|
4274
|
+
});
|
|
4275
|
+
await persistAndEmitInterrupt({
|
|
4276
|
+
node: String(
|
|
4277
|
+
err.nodeId ?? savedState.lastNode
|
|
4278
|
+
),
|
|
4279
|
+
workflow: String(savedState.currentWorkflow),
|
|
4280
|
+
input: {
|
|
4281
|
+
kind: "choice",
|
|
4282
|
+
question: "Limit reached \u2014 Continue?",
|
|
4283
|
+
options: [{ id: "continue", label: "Continue" }],
|
|
4284
|
+
meta: {
|
|
4285
|
+
__kortyxExecutionLimit: true,
|
|
4286
|
+
executionLimit: limitReached,
|
|
4287
|
+
...patch ? { __kortyxResumeStatePatch: patch } : {}
|
|
4288
|
+
}
|
|
4289
|
+
}
|
|
4290
|
+
});
|
|
4291
|
+
await Promise.all(pendingRequestWrites);
|
|
4292
|
+
(0, import_core2.throwIfExecutionAborted)(abortSignal);
|
|
4293
|
+
if (outcomeError) throw outcomeError;
|
|
4294
|
+
const request = activePendingRequests.get(pendingRecordToken);
|
|
4295
|
+
const sealed = {
|
|
4296
|
+
...request,
|
|
4297
|
+
state: pausedState,
|
|
4298
|
+
graphCheckpointId: snapshot.checkpoint.id,
|
|
4299
|
+
graphSnapshot: snapshot,
|
|
4300
|
+
ready: true
|
|
4301
|
+
};
|
|
4302
|
+
activePendingRequests.set(request.token, sealed);
|
|
4303
|
+
await pendingStore?.update(request.token, sealed);
|
|
4304
|
+
if (response.closed) pausedState.config.responseCompleted = true;
|
|
4305
|
+
if (!response.closed) {
|
|
4306
|
+
const checkpoint = await frameworkAdapter.sessionCheckpoints.append({
|
|
4307
|
+
sessionId,
|
|
4308
|
+
runId,
|
|
4309
|
+
graphCheckpointId: snapshot.checkpoint.id,
|
|
4310
|
+
workflow: String(pausedState.currentWorkflow),
|
|
4311
|
+
state: pausedState,
|
|
4312
|
+
nodes: Array.from(touchedNodes),
|
|
4313
|
+
structuredStreamIds: Array.from(structuredStreamIds),
|
|
4314
|
+
pendingRequests: [sealed],
|
|
4315
|
+
label: "Execution limit reached"
|
|
4316
|
+
});
|
|
4317
|
+
(0, import_core2.throwIfExecutionAborted)(abortSignal);
|
|
4318
|
+
sessionCheckpointId = checkpoint.id;
|
|
4319
|
+
write({
|
|
4320
|
+
type: "checkpoint",
|
|
4321
|
+
id: checkpoint.id,
|
|
4322
|
+
sessionId: checkpoint.sessionId,
|
|
4323
|
+
turnIndex: checkpoint.turnIndex,
|
|
4324
|
+
label: checkpoint.label
|
|
4325
|
+
});
|
|
4326
|
+
emitTelemetryEvent({
|
|
4327
|
+
config,
|
|
4328
|
+
type: "session.checkpointed",
|
|
4329
|
+
payload: {
|
|
4330
|
+
checkpointId: checkpoint.id,
|
|
4331
|
+
turnIndex: checkpoint.turnIndex,
|
|
4332
|
+
nodes: checkpoint.nodes
|
|
4333
|
+
}
|
|
4334
|
+
});
|
|
4335
|
+
}
|
|
4336
|
+
fallbackRunSpan?.setAttributes?.({
|
|
4337
|
+
"kortyx.run.awaiting_human_input": true,
|
|
4338
|
+
"kortyx.run.limit_reached": limitReached.limit
|
|
4339
|
+
});
|
|
4340
|
+
fallbackRunSpan?.end?.();
|
|
4341
|
+
finished = true;
|
|
4342
|
+
write({ type: "done" });
|
|
4343
|
+
out.end();
|
|
4344
|
+
return;
|
|
4345
|
+
} catch (pauseError) {
|
|
4346
|
+
if (abortSignal?.aborted || (0, import_core2.isExecutionCancelled)(pauseError)) {
|
|
4347
|
+
await finishCancelled();
|
|
4348
|
+
return;
|
|
4349
|
+
}
|
|
4350
|
+
await Promise.allSettled(
|
|
4351
|
+
[...activePendingRequests.keys()].map(
|
|
4352
|
+
(token) => pendingStore?.delete(token)
|
|
4353
|
+
)
|
|
4354
|
+
);
|
|
4355
|
+
activePendingRequests.clear();
|
|
4356
|
+
err = pauseError;
|
|
4357
|
+
}
|
|
4358
|
+
}
|
|
4359
|
+
outcomeError = err;
|
|
3888
4360
|
fallbackRunSpan?.fail?.(err);
|
|
3889
4361
|
emitResumeFailure(err);
|
|
3890
4362
|
console.error("[error:orchestrateGraphStream]", err);
|
|
3891
|
-
|
|
4363
|
+
write({
|
|
3892
4364
|
type: "error",
|
|
3893
4365
|
message: err instanceof Error ? err.message : String(err)
|
|
3894
4366
|
});
|
|
3895
|
-
|
|
4367
|
+
write({ type: "done" });
|
|
4368
|
+
finished = true;
|
|
3896
4369
|
out.end();
|
|
4370
|
+
}).finally(() => {
|
|
4371
|
+
response.dispose();
|
|
4372
|
+
onOutcome?.({
|
|
4373
|
+
state: outcomeState,
|
|
4374
|
+
pending: pendingRecordToken ? activePendingRequests.get(pendingRecordToken) : void 0,
|
|
4375
|
+
checkpointId: sessionCheckpointId,
|
|
4376
|
+
...outcomeError ? { error: outcomeError } : {}
|
|
4377
|
+
});
|
|
3897
4378
|
});
|
|
4379
|
+
onExecution?.(completion);
|
|
4380
|
+
if (response.closed) out.end();
|
|
3898
4381
|
return out;
|
|
3899
4382
|
}
|
|
3900
4383
|
|
|
@@ -3924,7 +4407,15 @@ function parseResumeMeta(msg) {
|
|
|
3924
4407
|
return { token, requestId, selected, cancel };
|
|
3925
4408
|
}
|
|
3926
4409
|
async function tryPrepareResumeStream({
|
|
4410
|
+
limits,
|
|
4411
|
+
abortSignal,
|
|
4412
|
+
executionSignal,
|
|
4413
|
+
onExecution,
|
|
3927
4414
|
lastMessage,
|
|
4415
|
+
meta: suppliedMeta,
|
|
4416
|
+
emitOutput,
|
|
4417
|
+
onOutcome,
|
|
4418
|
+
validatePending,
|
|
3928
4419
|
sessionId,
|
|
3929
4420
|
config,
|
|
3930
4421
|
selectWorkflow,
|
|
@@ -3933,7 +4424,7 @@ async function tryPrepareResumeStream({
|
|
|
3933
4424
|
applyResumeSelection,
|
|
3934
4425
|
frameworkAdapter
|
|
3935
4426
|
}) {
|
|
3936
|
-
const meta = parseResumeMeta(lastMessage);
|
|
4427
|
+
const meta = suppliedMeta ?? parseResumeMeta(lastMessage);
|
|
3937
4428
|
if (!meta) return null;
|
|
3938
4429
|
const store = frameworkAdapter?.pendingRequests;
|
|
3939
4430
|
if (!store) return null;
|
|
@@ -3945,6 +4436,24 @@ async function tryPrepareResumeStream({
|
|
|
3945
4436
|
}
|
|
3946
4437
|
if (pending.sessionId && pending.sessionId !== sessionId)
|
|
3947
4438
|
throw new Error("Interrupt belongs to another session.");
|
|
4439
|
+
await validatePending?.(pending);
|
|
4440
|
+
const isLimitPause = Boolean(pending.schema.meta?.__kortyxExecutionLimit);
|
|
4441
|
+
const savedBudget = pending.state?.config?.executionBudget;
|
|
4442
|
+
const resumedBudget = savedBudget && isLimitPause && !meta.cancel ? (0, import_core3.restartExecutionBudget)(savedBudget, limits) : savedBudget;
|
|
4443
|
+
if (abortSignal?.aborted) {
|
|
4444
|
+
onOutcome?.({
|
|
4445
|
+
state: pending.state,
|
|
4446
|
+
error: (0, import_core3.createExecutionCancelledError)()
|
|
4447
|
+
});
|
|
4448
|
+
return (async function* () {
|
|
4449
|
+
yield {
|
|
4450
|
+
type: "cancelled",
|
|
4451
|
+
runId: pending.runId,
|
|
4452
|
+
reason: "Execution cancelled."
|
|
4453
|
+
};
|
|
4454
|
+
yield { type: "done" };
|
|
4455
|
+
})();
|
|
4456
|
+
}
|
|
3948
4457
|
if (store.take && !await store.take(meta.token))
|
|
3949
4458
|
throw new Error("Interrupt has already been resumed or cancelled.");
|
|
3950
4459
|
if (meta.cancel) {
|
|
@@ -3964,125 +4473,510 @@ async function tryPrepareResumeStream({
|
|
|
3964
4473
|
},
|
|
3965
4474
|
flush: true
|
|
3966
4475
|
});
|
|
4476
|
+
onOutcome?.({ state: pending.state, cancelled: true });
|
|
3967
4477
|
return (async function* () {
|
|
3968
4478
|
yield { type: "done" };
|
|
3969
4479
|
})();
|
|
3970
4480
|
}
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
)
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
4481
|
+
try {
|
|
4482
|
+
const resumeData = isLimitPause ? {} : applyResumeSelection ? applyResumeSelection({ pending, selected: meta.selected }) : meta.selected?.length ? { coordinates: String(meta.selected[0]) } : {};
|
|
4483
|
+
const resumeDataPatch = isRecord4(resumeData) ? resumeData : {};
|
|
4484
|
+
const pendingMeta = isRecord4(pending.schema?.meta) ? pending.schema.meta : {};
|
|
4485
|
+
const resumeStatePatch = isRecord4(pendingMeta.__kortyxResumeStatePatch) ? pendingMeta.__kortyxResumeStatePatch : void 0;
|
|
4486
|
+
const pendingData = isRecord4(pending.state?.data) ? pending.state?.data : {};
|
|
4487
|
+
const workflowId = typeof pending.workflow === "string" && pending.workflow.trim() ? pending.workflow : typeof defaultWorkflowId === "string" && defaultWorkflowId.trim() ? defaultWorkflowId : "job-search";
|
|
4488
|
+
const resumedState = {
|
|
4489
|
+
// For static breakpoints, resume with null input (set in orchestrator),
|
|
4490
|
+
// and stash the user selection into data so the next node can read it.
|
|
4491
|
+
input: pending.state ? pending.state.input : "",
|
|
4492
|
+
lastNode: "__start__",
|
|
4493
|
+
currentWorkflow: workflowId,
|
|
4494
|
+
config,
|
|
4495
|
+
runtime: {},
|
|
4496
|
+
conversationHistory: [],
|
|
4497
|
+
awaitingHumanInput: false,
|
|
4498
|
+
data: {
|
|
4499
|
+
...pendingData,
|
|
4500
|
+
...resumeDataPatch
|
|
4501
|
+
}
|
|
4502
|
+
};
|
|
4503
|
+
const wf = await selectWorkflow(resumedState.currentWorkflow);
|
|
4504
|
+
const telemetryConfig = prepareWorkflowTelemetry({
|
|
4505
|
+
config: {
|
|
4506
|
+
...config,
|
|
4507
|
+
...pending.responseCompleted ? {
|
|
4508
|
+
responseCompleted: true,
|
|
4509
|
+
responseCheckpointId: pending.state?.config?.responseCheckpointId
|
|
4510
|
+
} : {},
|
|
4511
|
+
...resumedBudget ? { executionBudget: resumedBudget } : {},
|
|
4512
|
+
...pending.state?.config?.context ? { context: pending.state.config.context } : {},
|
|
4513
|
+
...pending.state?.config?.executionContract ? { executionContract: pending.state.config.executionContract } : {},
|
|
4514
|
+
...pending.state?.config?.executionBranchId ? { executionBranchId: pending.state.config.executionBranchId } : {}
|
|
4515
|
+
},
|
|
4516
|
+
workflow: wf,
|
|
4517
|
+
runId: pending.runId,
|
|
4518
|
+
sessionId,
|
|
4519
|
+
knownWorkflowIds
|
|
4520
|
+
});
|
|
4521
|
+
resumedState.config = telemetryConfig;
|
|
4522
|
+
const resumeUpdate = pending.graphSnapshot ? {
|
|
4523
|
+
config: {
|
|
4524
|
+
...telemetryConfig,
|
|
4525
|
+
context: pending.state?.config?.context ?? telemetryConfig.context
|
|
4526
|
+
}
|
|
4527
|
+
} : {};
|
|
4528
|
+
if (isLimitPause && pending.graphSnapshot) {
|
|
4529
|
+
pending.graphSnapshot.checkpoint.channel_values.config = telemetryConfig;
|
|
4530
|
+
if (resumeStatePatch)
|
|
4531
|
+
pending.graphSnapshot.checkpoint.channel_values.runtime = {
|
|
4532
|
+
...pending.graphSnapshot.checkpoint.channel_values.runtime,
|
|
4533
|
+
...resumeStatePatch
|
|
4534
|
+
};
|
|
4535
|
+
}
|
|
4536
|
+
if (pending.graphSnapshot && frameworkAdapter)
|
|
4537
|
+
await (0, import_runtime2.restoreGraphSnapshot)(
|
|
4538
|
+
frameworkAdapter.checkpointer,
|
|
4539
|
+
pending.graphSnapshot,
|
|
4540
|
+
pending.runId
|
|
4541
|
+
);
|
|
4542
|
+
if (Object.keys(resumeDataPatch).length > 0) {
|
|
4543
|
+
resumeUpdate.data = {
|
|
4544
|
+
...pendingData,
|
|
4545
|
+
...resumeDataPatch
|
|
4546
|
+
};
|
|
4547
|
+
}
|
|
4548
|
+
if (resumeStatePatch) {
|
|
4549
|
+
resumeUpdate.runtime = resumeStatePatch;
|
|
3995
4550
|
}
|
|
4551
|
+
const hasResumeUpdate = !isLimitPause && Object.keys(resumeUpdate).length > 0;
|
|
4552
|
+
const resumeValue = isLimitPause ? void 0 : meta.selected?.length && pending.schema.kind === "multi-choice" ? meta.selected.map((x) => String(x)) : meta.selected?.length ? String(meta.selected[0]) : void 0;
|
|
4553
|
+
const resumeCheckpointId = typeof pending.graphCheckpointId === "string" && pending.graphCheckpointId.length > 0 ? pending.graphCheckpointId : void 0;
|
|
4554
|
+
const resumedGraph = await (0, import_runtime2.createExecutionGraph)(wf, {
|
|
4555
|
+
...telemetryConfig,
|
|
4556
|
+
resume: true,
|
|
4557
|
+
...resumeValue !== void 0 ? { resumeValue } : {},
|
|
4558
|
+
...resumeCheckpointId ? { resumeCheckpointId } : {},
|
|
4559
|
+
...hasResumeUpdate ? { resumeUpdate } : {}
|
|
4560
|
+
});
|
|
4561
|
+
await store.delete(pending.token);
|
|
4562
|
+
const response = responseFromSelection(meta.selected);
|
|
4563
|
+
const telemetry = isRecord4(telemetryConfig.telemetry) ? telemetryConfig.telemetry : {};
|
|
4564
|
+
const responseCaptured = Boolean(
|
|
4565
|
+
response && shouldCaptureTelemetryContent(telemetry.captureContent, "input")
|
|
4566
|
+
);
|
|
4567
|
+
emitTelemetryEvent({
|
|
4568
|
+
config: telemetryConfig,
|
|
4569
|
+
type: "interrupt.resolved",
|
|
4570
|
+
correlation: {
|
|
4571
|
+
runId: pending.runId,
|
|
4572
|
+
sessionId,
|
|
4573
|
+
workflowId,
|
|
4574
|
+
nodeId: pending.node
|
|
4575
|
+
},
|
|
4576
|
+
payload: {
|
|
4577
|
+
interruptId: pending.requestId,
|
|
4578
|
+
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4579
|
+
resumeOutcome: "resumed",
|
|
4580
|
+
responseCaptured,
|
|
4581
|
+
...responseCaptured && response ? { response } : {}
|
|
4582
|
+
},
|
|
4583
|
+
flush: true
|
|
4584
|
+
});
|
|
4585
|
+
const args = {
|
|
4586
|
+
abortSignal,
|
|
4587
|
+
executionSignal: pending.responseCompleted && emitOutput === false ? abortSignal : executionSignal,
|
|
4588
|
+
onExecution,
|
|
4589
|
+
emitOutput,
|
|
4590
|
+
onOutcome,
|
|
4591
|
+
sessionId,
|
|
4592
|
+
runId: pending.runId,
|
|
4593
|
+
graph: resumedGraph,
|
|
4594
|
+
state: resumedState,
|
|
4595
|
+
config: {
|
|
4596
|
+
...telemetryConfig,
|
|
4597
|
+
resume: true,
|
|
4598
|
+
telemetryInterruptId: pending.requestId,
|
|
4599
|
+
telemetryInterruptNodeId: pending.node,
|
|
4600
|
+
...resumeValue !== void 0 ? { resumeValue } : {},
|
|
4601
|
+
...resumeCheckpointId ? { resumeCheckpointId } : {},
|
|
4602
|
+
...hasResumeUpdate ? { resumeUpdate } : {}
|
|
4603
|
+
},
|
|
4604
|
+
selectWorkflow,
|
|
4605
|
+
knownWorkflowIds,
|
|
4606
|
+
frameworkAdapter
|
|
4607
|
+
};
|
|
4608
|
+
const stream = await orchestrateGraphStream(args);
|
|
4609
|
+
return stream;
|
|
4610
|
+
} catch (error) {
|
|
4611
|
+
await store.save(pending);
|
|
4612
|
+
throw error;
|
|
4613
|
+
}
|
|
4614
|
+
}
|
|
4615
|
+
|
|
4616
|
+
// src/execution/execute.ts
|
|
4617
|
+
var handleSchema = import_zod3.z.object({
|
|
4618
|
+
token: import_zod3.z.string().min(1),
|
|
4619
|
+
requestId: import_zod3.z.string().min(1),
|
|
4620
|
+
sessionId: import_zod3.z.string().min(1),
|
|
4621
|
+
runId: import_zod3.z.string().min(1)
|
|
4622
|
+
}).strict();
|
|
4623
|
+
var responseSchema = import_zod3.z.discriminatedUnion("type", [
|
|
4624
|
+
import_zod3.z.object({ type: import_zod3.z.literal("text"), text: import_zod3.z.string().min(1) }).strict(),
|
|
4625
|
+
import_zod3.z.object({
|
|
4626
|
+
type: import_zod3.z.literal("select"),
|
|
4627
|
+
ids: import_zod3.z.array(import_zod3.z.string().min(1)).min(1)
|
|
4628
|
+
}).strict(),
|
|
4629
|
+
import_zod3.z.object({ type: import_zod3.z.literal("cancel") }).strict()
|
|
4630
|
+
]);
|
|
4631
|
+
function resultFromOutcome(outcome, runId, sessionId) {
|
|
4632
|
+
const usage = outcome.pending?.schema.meta?.__kortyxResumeStatePatch?.tokenUsage ?? outcome.state.runtime?.tokenUsage;
|
|
4633
|
+
const info = {
|
|
4634
|
+
runId,
|
|
4635
|
+
sessionId,
|
|
4636
|
+
...outcome.checkpointId ? { checkpointId: outcome.checkpointId } : {},
|
|
4637
|
+
...usage ? { usage } : {}
|
|
3996
4638
|
};
|
|
3997
|
-
|
|
3998
|
-
|
|
4639
|
+
if ((0, import_core4.isExecutionCancelled)(outcome.error))
|
|
4640
|
+
return { ...info, status: "cancelled", reason: "Execution cancelled." };
|
|
4641
|
+
if (outcome.error) {
|
|
4642
|
+
const error = outcome.error;
|
|
4643
|
+
return {
|
|
4644
|
+
...info,
|
|
4645
|
+
status: "failed",
|
|
4646
|
+
error: {
|
|
4647
|
+
code: typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "EXECUTION_FAILED",
|
|
4648
|
+
message: error instanceof Error ? error.message : String(error)
|
|
4649
|
+
}
|
|
4650
|
+
};
|
|
4651
|
+
}
|
|
4652
|
+
if (outcome.cancelled)
|
|
4653
|
+
return { ...info, status: "cancelled", reason: "cancelled_by_client" };
|
|
4654
|
+
if (outcome.pending) {
|
|
4655
|
+
const pending = outcome.pending;
|
|
4656
|
+
const meta = Object.fromEntries(
|
|
4657
|
+
Object.entries(pending.schema.meta ?? {}).filter(
|
|
4658
|
+
([key]) => !key.startsWith("__kortyx")
|
|
4659
|
+
)
|
|
4660
|
+
);
|
|
4661
|
+
return {
|
|
4662
|
+
...info,
|
|
4663
|
+
status: "suspended",
|
|
4664
|
+
...pending.schema.meta?.__kortyxExecutionLimit ? {
|
|
4665
|
+
reason: "limit_reached",
|
|
4666
|
+
limit: pending.schema.meta.executionLimit
|
|
4667
|
+
} : {},
|
|
4668
|
+
interrupt: {
|
|
4669
|
+
workflow: pending.workflow,
|
|
4670
|
+
node: pending.node,
|
|
4671
|
+
input: {
|
|
4672
|
+
...pending.schema,
|
|
4673
|
+
meta,
|
|
4674
|
+
options: pending.options.map(({ id, label, description }) => ({
|
|
4675
|
+
id,
|
|
4676
|
+
label,
|
|
4677
|
+
description
|
|
4678
|
+
}))
|
|
4679
|
+
}
|
|
4680
|
+
},
|
|
4681
|
+
resume: {
|
|
4682
|
+
token: pending.token,
|
|
4683
|
+
requestId: pending.requestId,
|
|
4684
|
+
sessionId,
|
|
4685
|
+
runId
|
|
4686
|
+
}
|
|
4687
|
+
};
|
|
4688
|
+
}
|
|
4689
|
+
return { ...info, status: "completed", data: outcome.state.data ?? {} };
|
|
4690
|
+
}
|
|
4691
|
+
function runtimeConfig(services, sessionId, context) {
|
|
4692
|
+
return {
|
|
4693
|
+
session: { id: sessionId },
|
|
4694
|
+
...context ? { context } : {},
|
|
4695
|
+
getProvider: services.getProvider,
|
|
4696
|
+
checkpointer: services.frameworkAdapter.checkpointer,
|
|
4697
|
+
selectWorkflow: (id) => services.registry.select(id),
|
|
4698
|
+
...services.telemetry ? { telemetry: services.telemetry } : {}
|
|
4699
|
+
};
|
|
4700
|
+
}
|
|
4701
|
+
async function executeWorkflow(services, args) {
|
|
4702
|
+
const select = (id) => services.registry.select(id);
|
|
4703
|
+
const workflow = await resolveExecutionWorkflow(select, args.workflow);
|
|
4704
|
+
const input = parseExecutionInput(workflow, args.input);
|
|
4705
|
+
if (args.sessionId !== void 0 && !args.sessionId.trim())
|
|
4706
|
+
throw new ExecutionRequestError(
|
|
4707
|
+
"INVALID_SESSION",
|
|
4708
|
+
"sessionId must not be empty."
|
|
4709
|
+
);
|
|
4710
|
+
const sessionId = args.sessionId ?? (0, import_runtime3.makeRequestId)("session");
|
|
4711
|
+
const runId = (0, import_runtime3.makeRequestId)("run");
|
|
4712
|
+
const config = prepareWorkflowTelemetry({
|
|
3999
4713
|
config: {
|
|
4000
|
-
...
|
|
4001
|
-
...
|
|
4714
|
+
...runtimeConfig(services, sessionId, args.context),
|
|
4715
|
+
...services.limits || args.limits ? {
|
|
4716
|
+
executionBudget: (0, import_core4.createExecutionBudget)(
|
|
4717
|
+
services.limits,
|
|
4718
|
+
args.limits
|
|
4719
|
+
)
|
|
4720
|
+
} : {},
|
|
4721
|
+
executionContract: { id: workflow.id, version: workflow.version }
|
|
4002
4722
|
},
|
|
4003
|
-
workflow
|
|
4004
|
-
runId
|
|
4723
|
+
workflow,
|
|
4724
|
+
runId,
|
|
4005
4725
|
sessionId,
|
|
4006
|
-
knownWorkflowIds
|
|
4726
|
+
knownWorkflowIds: services.knownWorkflowIds
|
|
4007
4727
|
});
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4728
|
+
const state = await (0, import_runtime3.buildInitialGraphState)({
|
|
4729
|
+
input,
|
|
4730
|
+
runtime: {},
|
|
4731
|
+
config,
|
|
4732
|
+
defaultWorkflowId: workflow.id
|
|
4733
|
+
});
|
|
4734
|
+
return new Promise((resolve2) => {
|
|
4735
|
+
const onOutcome = (outcome) => resolve2(resultFromOutcome(outcome, runId, sessionId));
|
|
4736
|
+
void (async () => {
|
|
4737
|
+
(0, import_core4.throwIfExecutionAborted)(args.abortSignal);
|
|
4738
|
+
const graph = await (0, import_runtime3.createExecutionGraph)(workflow, config);
|
|
4739
|
+
await orchestrateGraphStream({
|
|
4740
|
+
graph,
|
|
4741
|
+
state,
|
|
4742
|
+
config,
|
|
4743
|
+
runId,
|
|
4744
|
+
sessionId,
|
|
4745
|
+
selectWorkflow: select,
|
|
4746
|
+
frameworkAdapter: services.frameworkAdapter,
|
|
4747
|
+
knownWorkflowIds: services.knownWorkflowIds,
|
|
4748
|
+
abortSignal: args.abortSignal,
|
|
4749
|
+
emitOutput: false,
|
|
4750
|
+
onOutcome
|
|
4751
|
+
});
|
|
4752
|
+
})().catch((error) => onOutcome({ state, error }));
|
|
4753
|
+
});
|
|
4754
|
+
}
|
|
4755
|
+
async function resumeWorkflow(services, args) {
|
|
4756
|
+
const parsed = import_zod3.z.object({ resume: handleSchema, response: responseSchema }).safeParse(args);
|
|
4757
|
+
if (!parsed.success)
|
|
4758
|
+
throw new ExecutionRequestError("INVALID_RESUME", parsed.error.message);
|
|
4759
|
+
const { resume: handle, response } = parsed.data;
|
|
4760
|
+
const select = (id) => services.registry.select(id);
|
|
4761
|
+
const workflow = await resolveExecutionWorkflow(select, args.workflow);
|
|
4762
|
+
const validatePending = async (pending) => {
|
|
4763
|
+
const contract = pending.state?.config?.executionContract ?? {
|
|
4764
|
+
id: pending.workflow
|
|
4765
|
+
};
|
|
4766
|
+
if (pending.runId !== handle.runId || contract.id !== workflow.id || contract.version && contract.version !== workflow.version)
|
|
4767
|
+
throw new ExecutionRequestError(
|
|
4768
|
+
"RESUME_MISMATCH",
|
|
4769
|
+
"Resume handle does not match the root execution and workflow version."
|
|
4770
|
+
);
|
|
4771
|
+
if (response.type === "cancel") return;
|
|
4772
|
+
if (response.type === "text") {
|
|
4773
|
+
if (pending.schema.kind !== "text")
|
|
4774
|
+
throw new ExecutionRequestError(
|
|
4775
|
+
"INVALID_RESPONSE",
|
|
4776
|
+
"This interrupt requires a selection."
|
|
4777
|
+
);
|
|
4778
|
+
} else {
|
|
4779
|
+
const multi = pending.schema.kind === "multi-choice" || pending.schema.multiple;
|
|
4780
|
+
if (pending.schema.kind === "text" || !multi && response.ids.length !== 1 || new Set(response.ids).size !== response.ids.length)
|
|
4781
|
+
throw new ExecutionRequestError(
|
|
4782
|
+
"INVALID_RESPONSE",
|
|
4783
|
+
"Invalid selection for this interrupt."
|
|
4784
|
+
);
|
|
4785
|
+
if (pending.options.length && response.ids.some(
|
|
4786
|
+
(id) => !pending.options.some((option) => option.id === id)
|
|
4787
|
+
))
|
|
4788
|
+
throw new ExecutionRequestError(
|
|
4789
|
+
"INVALID_RESPONSE",
|
|
4790
|
+
"Selection contains an unknown option."
|
|
4791
|
+
);
|
|
4013
4792
|
}
|
|
4014
|
-
}
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4793
|
+
};
|
|
4794
|
+
return new Promise((resolve2, reject) => {
|
|
4795
|
+
void tryPrepareResumeStream({
|
|
4796
|
+
limits: args.limits,
|
|
4797
|
+
abortSignal: args.abortSignal,
|
|
4798
|
+
meta: {
|
|
4799
|
+
token: handle.token,
|
|
4800
|
+
requestId: handle.requestId,
|
|
4801
|
+
selected: response.type === "text" ? [response.text] : response.type === "select" ? response.ids : [],
|
|
4802
|
+
cancel: response.type === "cancel"
|
|
4803
|
+
},
|
|
4804
|
+
sessionId: handle.sessionId,
|
|
4805
|
+
config: runtimeConfig(services, handle.sessionId),
|
|
4806
|
+
selectWorkflow: select,
|
|
4807
|
+
frameworkAdapter: services.frameworkAdapter,
|
|
4808
|
+
knownWorkflowIds: services.knownWorkflowIds,
|
|
4809
|
+
validatePending,
|
|
4810
|
+
applyResumeSelection: () => ({}),
|
|
4811
|
+
emitOutput: false,
|
|
4812
|
+
onOutcome: (outcome) => resolve2(resultFromOutcome(outcome, handle.runId, handle.sessionId))
|
|
4813
|
+
}).then((stream) => {
|
|
4814
|
+
if (!stream)
|
|
4815
|
+
reject(
|
|
4816
|
+
new ExecutionRequestError(
|
|
4817
|
+
"INVALID_RESUME",
|
|
4818
|
+
"No waiting execution found."
|
|
4819
|
+
)
|
|
4820
|
+
);
|
|
4821
|
+
}).catch(
|
|
4822
|
+
(error) => reject(
|
|
4823
|
+
error instanceof ExecutionRequestError ? error : new ExecutionRequestError(
|
|
4824
|
+
"INVALID_RESUME",
|
|
4825
|
+
error instanceof Error ? error.message : String(error)
|
|
4826
|
+
)
|
|
4827
|
+
)
|
|
4020
4828
|
);
|
|
4021
|
-
if (Object.keys(resumeDataPatch).length > 0) {
|
|
4022
|
-
resumeUpdate.data = {
|
|
4023
|
-
...pendingData,
|
|
4024
|
-
...resumeDataPatch
|
|
4025
|
-
};
|
|
4026
|
-
}
|
|
4027
|
-
if (resumeStatePatch) {
|
|
4028
|
-
resumeUpdate.runtime = resumeStatePatch;
|
|
4029
|
-
}
|
|
4030
|
-
const hasResumeUpdate = Object.keys(resumeUpdate).length > 0;
|
|
4031
|
-
const resumeValue = meta.selected?.length && pending.schema.kind === "multi-choice" ? meta.selected.map((x) => String(x)) : meta.selected?.length ? String(meta.selected[0]) : void 0;
|
|
4032
|
-
const resumeCheckpointId = typeof pending.graphCheckpointId === "string" && pending.graphCheckpointId.length > 0 ? pending.graphCheckpointId : void 0;
|
|
4033
|
-
const resumedGraph = await (0, import_runtime2.createExecutionGraph)(wf, {
|
|
4034
|
-
...telemetryConfig,
|
|
4035
|
-
resume: true,
|
|
4036
|
-
...resumeValue !== void 0 ? { resumeValue } : {},
|
|
4037
|
-
...resumeCheckpointId ? { resumeCheckpointId } : {},
|
|
4038
|
-
...hasResumeUpdate ? { resumeUpdate } : {}
|
|
4039
|
-
});
|
|
4040
|
-
await store.delete(pending.token);
|
|
4041
|
-
const response = responseFromSelection(meta.selected);
|
|
4042
|
-
const telemetry = isRecord4(telemetryConfig.telemetry) ? telemetryConfig.telemetry : {};
|
|
4043
|
-
const responseCaptured = Boolean(
|
|
4044
|
-
response && shouldCaptureTelemetryContent(telemetry.captureContent, "input")
|
|
4045
|
-
);
|
|
4046
|
-
emitTelemetryEvent({
|
|
4047
|
-
config: telemetryConfig,
|
|
4048
|
-
type: "interrupt.resolved",
|
|
4049
|
-
correlation: {
|
|
4050
|
-
runId: pending.runId,
|
|
4051
|
-
sessionId,
|
|
4052
|
-
workflowId,
|
|
4053
|
-
nodeId: pending.node
|
|
4054
|
-
},
|
|
4055
|
-
payload: {
|
|
4056
|
-
interruptId: pending.requestId,
|
|
4057
|
-
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4058
|
-
resumeOutcome: "resumed",
|
|
4059
|
-
responseCaptured,
|
|
4060
|
-
...responseCaptured && response ? { response } : {}
|
|
4061
|
-
},
|
|
4062
|
-
flush: true
|
|
4063
4829
|
});
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4830
|
+
}
|
|
4831
|
+
|
|
4832
|
+
// src/interrupt/discovery.ts
|
|
4833
|
+
var import_zod4 = require("zod");
|
|
4834
|
+
var scopeSchema = import_zod4.z.object({
|
|
4835
|
+
sessionId: import_zod4.z.string().min(1).optional(),
|
|
4836
|
+
runId: import_zod4.z.string().min(1).optional(),
|
|
4837
|
+
context: import_zod4.z.record(
|
|
4838
|
+
import_zod4.z.string(),
|
|
4839
|
+
import_zod4.z.union([import_zod4.z.string(), import_zod4.z.number(), import_zod4.z.boolean(), import_zod4.z.null()])
|
|
4840
|
+
).optional()
|
|
4841
|
+
}).strict().refine(
|
|
4842
|
+
(scope) => Boolean(
|
|
4843
|
+
scope.sessionId || scope.runId || Object.keys(scope.context ?? {}).length
|
|
4844
|
+
),
|
|
4845
|
+
"Interrupt discovery requires an authorized session, run, or context scope."
|
|
4846
|
+
);
|
|
4847
|
+
var matches = (record, scope) => record.ready !== false && Date.now() < record.createdAt + record.ttlMs && (!scope.sessionId || record.sessionId === scope.sessionId) && (!scope.runId || record.runId === scope.runId) && Object.entries(scope.context ?? {}).every(
|
|
4848
|
+
([key, value]) => record.state?.config.context?.[key] === value
|
|
4849
|
+
);
|
|
4850
|
+
var summary = (record) => {
|
|
4851
|
+
const { meta: _meta, ...input } = record.schema;
|
|
4852
|
+
const contract = record.state?.config.executionContract;
|
|
4853
|
+
return {
|
|
4854
|
+
id: record.requestId,
|
|
4855
|
+
status: "pending",
|
|
4856
|
+
runId: record.runId,
|
|
4857
|
+
sessionId: record.sessionId ?? "",
|
|
4858
|
+
workflow: contract?.id ?? record.workflow,
|
|
4859
|
+
node: record.node,
|
|
4860
|
+
createdAt: new Date(record.createdAt).toISOString(),
|
|
4861
|
+
expiresAt: new Date(record.createdAt + record.ttlMs).toISOString(),
|
|
4862
|
+
afterResponseCompleted: record.responseCompleted === true,
|
|
4863
|
+
input: {
|
|
4864
|
+
...input,
|
|
4865
|
+
options: record.options.map(({ id, label, description }) => ({
|
|
4866
|
+
id,
|
|
4867
|
+
label,
|
|
4868
|
+
...description !== void 0 ? { description } : {}
|
|
4869
|
+
}))
|
|
4870
|
+
}
|
|
4871
|
+
};
|
|
4872
|
+
};
|
|
4873
|
+
function createInterruptDiscovery(store) {
|
|
4874
|
+
const records = () => {
|
|
4875
|
+
if (!store.list)
|
|
4876
|
+
throw new Error(
|
|
4877
|
+
"This persistence adapter does not support interrupt discovery; implement pendingRequests.list."
|
|
4878
|
+
);
|
|
4879
|
+
return store.list();
|
|
4880
|
+
};
|
|
4881
|
+
return {
|
|
4882
|
+
async listInterrupts(options) {
|
|
4883
|
+
const {
|
|
4884
|
+
status: _status,
|
|
4885
|
+
afterResponseCompleted,
|
|
4886
|
+
...scope
|
|
4887
|
+
} = import_zod4.z.object({
|
|
4888
|
+
sessionId: import_zod4.z.string().optional(),
|
|
4889
|
+
runId: import_zod4.z.string().optional(),
|
|
4890
|
+
context: import_zod4.z.record(
|
|
4891
|
+
import_zod4.z.string(),
|
|
4892
|
+
import_zod4.z.union([import_zod4.z.string(), import_zod4.z.number(), import_zod4.z.boolean(), import_zod4.z.null()])
|
|
4893
|
+
).optional(),
|
|
4894
|
+
status: import_zod4.z.literal("pending").optional(),
|
|
4895
|
+
afterResponseCompleted: import_zod4.z.boolean().optional()
|
|
4896
|
+
}).strict().parse(options);
|
|
4897
|
+
scopeSchema.parse(scope);
|
|
4898
|
+
return (await records()).filter(
|
|
4899
|
+
(record) => matches(record, scope) && (afterResponseCompleted === void 0 || record.responseCompleted === true === afterResponseCompleted)
|
|
4900
|
+
).sort(
|
|
4901
|
+
(a, b) => a.createdAt - b.createdAt || a.requestId.localeCompare(b.requestId)
|
|
4902
|
+
).map(summary);
|
|
4077
4903
|
},
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4904
|
+
async getInterrupt(id, scope) {
|
|
4905
|
+
import_zod4.z.string().min(1).parse(id);
|
|
4906
|
+
scopeSchema.parse(scope);
|
|
4907
|
+
const record = (await records()).find(
|
|
4908
|
+
(record2) => record2.requestId === id && matches(record2, scope)
|
|
4909
|
+
);
|
|
4910
|
+
if (!record) return null;
|
|
4911
|
+
return {
|
|
4912
|
+
...summary(record),
|
|
4913
|
+
resume: {
|
|
4914
|
+
token: record.token,
|
|
4915
|
+
requestId: record.requestId,
|
|
4916
|
+
sessionId: record.sessionId ?? "",
|
|
4917
|
+
runId: record.runId
|
|
4918
|
+
}
|
|
4919
|
+
};
|
|
4920
|
+
}
|
|
4081
4921
|
};
|
|
4082
|
-
const stream = await orchestrateGraphStream(args);
|
|
4083
|
-
return stream;
|
|
4084
4922
|
}
|
|
4085
4923
|
|
|
4924
|
+
// src/telemetry/workflow-call-branch.ts
|
|
4925
|
+
var import_node_crypto3 = require("crypto");
|
|
4926
|
+
var import_hooks = __toESM(require_dist());
|
|
4927
|
+
function restoreWorkflowCallBranch(requests, telemetry, sourceRunId) {
|
|
4928
|
+
const branchId = (0, import_node_crypto3.randomUUID)();
|
|
4929
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
4930
|
+
for (const request of requests) {
|
|
4931
|
+
if (request.state)
|
|
4932
|
+
request.state.config = {
|
|
4933
|
+
...request.state.config,
|
|
4934
|
+
executionBranchId: branchId
|
|
4935
|
+
};
|
|
4936
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4937
|
+
const visit = (value) => {
|
|
4938
|
+
if (!value || typeof value !== "object" || seen.has(value)) return;
|
|
4939
|
+
seen.add(value);
|
|
4940
|
+
const record = value;
|
|
4941
|
+
if (typeof record.invocationId === "string" && typeof record.fingerprint === "string" && Array.isArray(record.interrupts)) {
|
|
4942
|
+
const previous = record.telemetry;
|
|
4943
|
+
if (previous) {
|
|
4944
|
+
record.sequence = 0;
|
|
4945
|
+
record.telemetry = { ...previous, branchId };
|
|
4946
|
+
if (!emitted.has(record.invocationId)) {
|
|
4947
|
+
emitted.add(record.invocationId);
|
|
4948
|
+
(0, import_hooks.emitWorkflowCall)(
|
|
4949
|
+
telemetry ? {
|
|
4950
|
+
...telemetry,
|
|
4951
|
+
correlation: {
|
|
4952
|
+
runId: request.runId,
|
|
4953
|
+
workflowId: request.workflow,
|
|
4954
|
+
...request.sessionId ? { sessionId: request.sessionId } : {}
|
|
4955
|
+
}
|
|
4956
|
+
} : void 0,
|
|
4957
|
+
"workflow.call.restored",
|
|
4958
|
+
{
|
|
4959
|
+
...previous,
|
|
4960
|
+
branchId,
|
|
4961
|
+
sourceBranchId: previous.branchId,
|
|
4962
|
+
sourceRunId,
|
|
4963
|
+
sequence: 0,
|
|
4964
|
+
status: record.status
|
|
4965
|
+
}
|
|
4966
|
+
);
|
|
4967
|
+
}
|
|
4968
|
+
}
|
|
4969
|
+
}
|
|
4970
|
+
for (const child of Object.values(record)) visit(child);
|
|
4971
|
+
};
|
|
4972
|
+
visit(request);
|
|
4973
|
+
}
|
|
4974
|
+
return branchId;
|
|
4975
|
+
}
|
|
4976
|
+
|
|
4977
|
+
// src/chat/process-chat.ts
|
|
4978
|
+
var import_runtime4 = require("@kortyx/runtime");
|
|
4979
|
+
|
|
4086
4980
|
// src/utils/extract-latest-message.ts
|
|
4087
4981
|
function extractLatestUserMessage(messages) {
|
|
4088
4982
|
if (!messages || messages.length === 0) return "";
|
|
@@ -4098,6 +4992,9 @@ function extractLatestUserMessage(messages) {
|
|
|
4098
4992
|
// src/chat/process-chat.ts
|
|
4099
4993
|
async function streamChat({
|
|
4100
4994
|
messages,
|
|
4995
|
+
abortSignal,
|
|
4996
|
+
executionSignal,
|
|
4997
|
+
onExecution,
|
|
4101
4998
|
options,
|
|
4102
4999
|
sessionId,
|
|
4103
5000
|
defaultWorkflowId,
|
|
@@ -4110,7 +5007,7 @@ async function streamChat({
|
|
|
4110
5007
|
applyResumeSelection
|
|
4111
5008
|
}) {
|
|
4112
5009
|
const config = await loadRuntimeConfig(options);
|
|
4113
|
-
let
|
|
5010
|
+
let runtimeConfig2 = {
|
|
4114
5011
|
...config,
|
|
4115
5012
|
getProvider,
|
|
4116
5013
|
...frameworkAdapter ? { checkpointer: frameworkAdapter.checkpointer } : {}
|
|
@@ -4121,7 +5018,7 @@ async function streamChat({
|
|
|
4121
5018
|
"streamChat requires selectWorkflow or workflowRegistry to resolve workflows."
|
|
4122
5019
|
);
|
|
4123
5020
|
}
|
|
4124
|
-
|
|
5021
|
+
runtimeConfig2.selectWorkflow = workflowSelector;
|
|
4125
5022
|
const fallbackSessionId = options?.sessionId;
|
|
4126
5023
|
const resolvedSessionId = sessionId || fallbackSessionId || "anonymous-session";
|
|
4127
5024
|
const last = messages[messages.length - 1];
|
|
@@ -4143,16 +5040,13 @@ async function streamChat({
|
|
|
4143
5040
|
if (requestedWorkflowId.trim() === "") delete runtime.requestedWorkflow;
|
|
4144
5041
|
else runtime.requestedWorkflow = requestedWorkflowId;
|
|
4145
5042
|
}
|
|
4146
|
-
const baseState = await (0, import_runtime3.buildInitialGraphState)({
|
|
4147
|
-
input,
|
|
4148
|
-
config: runtimeConfig,
|
|
4149
|
-
runtime,
|
|
4150
|
-
...defaultWorkflowId ? { defaultWorkflowId } : {}
|
|
4151
|
-
});
|
|
4152
5043
|
const resumeStream = await tryPrepareResumeStream({
|
|
5044
|
+
abortSignal,
|
|
5045
|
+
executionSignal,
|
|
5046
|
+
onExecution,
|
|
4153
5047
|
lastMessage: last,
|
|
4154
5048
|
sessionId: resolvedSessionId,
|
|
4155
|
-
config:
|
|
5049
|
+
config: runtimeConfig2,
|
|
4156
5050
|
selectWorkflow: workflowSelector,
|
|
4157
5051
|
knownWorkflowIds,
|
|
4158
5052
|
...frameworkAdapter ? { frameworkAdapter } : {},
|
|
@@ -4160,7 +5054,13 @@ async function streamChat({
|
|
|
4160
5054
|
...applyResumeSelection ? { applyResumeSelection } : {}
|
|
4161
5055
|
});
|
|
4162
5056
|
if (resumeStream) return resumeStream;
|
|
4163
|
-
const
|
|
5057
|
+
const baseState = await (0, import_runtime4.buildInitialGraphState)({
|
|
5058
|
+
input,
|
|
5059
|
+
config: runtimeConfig2,
|
|
5060
|
+
runtime,
|
|
5061
|
+
...defaultWorkflowId ? { defaultWorkflowId } : {}
|
|
5062
|
+
});
|
|
5063
|
+
const runId = (0, import_runtime4.makeRequestId)("run");
|
|
4164
5064
|
let headCheckpoint = !isResumeRequest && frameworkAdapter?.sessionCheckpoints ? await frameworkAdapter.sessionCheckpoints.getHead(resolvedSessionId) : null;
|
|
4165
5065
|
if (!isResumeRequest && !headCheckpoint && frameworkAdapter?.sessionCheckpoints) {
|
|
4166
5066
|
headCheckpoint = await frameworkAdapter.sessionCheckpoints.append({
|
|
@@ -4180,18 +5080,26 @@ async function streamChat({
|
|
|
4180
5080
|
}
|
|
4181
5081
|
const currentWorkflow = requestedWorkflowId ? baseState.currentWorkflow : headCheckpoint?.state.currentWorkflow ?? baseState.currentWorkflow;
|
|
4182
5082
|
const selectedWorkflow = await workflowSelector(currentWorkflow);
|
|
4183
|
-
|
|
4184
|
-
config:
|
|
5083
|
+
runtimeConfig2 = prepareWorkflowTelemetry({
|
|
5084
|
+
config: runtimeConfig2,
|
|
4185
5085
|
workflow: selectedWorkflow,
|
|
4186
5086
|
runId,
|
|
4187
5087
|
sessionId: resolvedSessionId,
|
|
4188
5088
|
knownWorkflowIds
|
|
4189
5089
|
});
|
|
4190
|
-
|
|
5090
|
+
runtimeConfig2 = {
|
|
5091
|
+
...runtimeConfig2,
|
|
5092
|
+
executionContract: {
|
|
5093
|
+
id: selectedWorkflow.id,
|
|
5094
|
+
version: selectedWorkflow.version
|
|
5095
|
+
}
|
|
5096
|
+
};
|
|
5097
|
+
const parsedInput = selectedWorkflow.inputSchema ? parseExecutionInput(selectedWorkflow, input) : input;
|
|
5098
|
+
const graph = await (0, import_runtime4.createExecutionGraph)(selectedWorkflow, runtimeConfig2);
|
|
4191
5099
|
const initialState = headCheckpoint ? {
|
|
4192
5100
|
...headCheckpoint.state,
|
|
4193
|
-
input,
|
|
4194
|
-
config:
|
|
5101
|
+
input: parsedInput,
|
|
5102
|
+
config: runtimeConfig2,
|
|
4195
5103
|
currentWorkflow,
|
|
4196
5104
|
awaitingHumanInput: false,
|
|
4197
5105
|
runtime: {
|
|
@@ -4199,13 +5107,21 @@ async function streamChat({
|
|
|
4199
5107
|
...runtime.requestedWorkflow ? { requestedWorkflow: runtime.requestedWorkflow } : {},
|
|
4200
5108
|
...runtime.priorMessages ? { priorMessages: runtime.priorMessages } : {}
|
|
4201
5109
|
}
|
|
4202
|
-
} : {
|
|
5110
|
+
} : {
|
|
5111
|
+
...baseState,
|
|
5112
|
+
input: parsedInput,
|
|
5113
|
+
config: runtimeConfig2,
|
|
5114
|
+
currentWorkflow
|
|
5115
|
+
};
|
|
4203
5116
|
const orchestratedStream = await orchestrateGraphStream({
|
|
5117
|
+
abortSignal,
|
|
5118
|
+
executionSignal,
|
|
5119
|
+
onExecution,
|
|
4204
5120
|
sessionId: resolvedSessionId,
|
|
4205
5121
|
runId,
|
|
4206
5122
|
graph,
|
|
4207
5123
|
state: initialState,
|
|
4208
|
-
config:
|
|
5124
|
+
config: runtimeConfig2,
|
|
4209
5125
|
selectWorkflow: workflowSelector,
|
|
4210
5126
|
knownWorkflowIds,
|
|
4211
5127
|
...frameworkAdapter ? { frameworkAdapter } : {}
|
|
@@ -4214,19 +5130,26 @@ async function streamChat({
|
|
|
4214
5130
|
}
|
|
4215
5131
|
|
|
4216
5132
|
// src/chat/create-agent.ts
|
|
4217
|
-
var agentProcessOptionsSchema =
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
5133
|
+
var agentProcessOptionsSchema = import_zod5.z.object({
|
|
5134
|
+
executionSignal: import_zod5.z.custom((value) => value instanceof AbortSignal).optional(),
|
|
5135
|
+
onExecution: import_zod5.z.custom(
|
|
5136
|
+
(value) => typeof value === "function"
|
|
5137
|
+
).optional(),
|
|
5138
|
+
limits: import_core5.ExecutionLimitsSchema.optional(),
|
|
5139
|
+
abortSignal: import_zod5.z.custom((value) => value instanceof AbortSignal).optional(),
|
|
5140
|
+
sessionId: import_zod5.z.string().optional(),
|
|
5141
|
+
workflowId: import_zod5.z.string().optional(),
|
|
5142
|
+
context: import_zod5.z.record(import_zod5.z.string(), import_zod5.z.unknown()).optional()
|
|
4221
5143
|
}).strict();
|
|
4222
|
-
var createAgentArgsBaseSchema =
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
5144
|
+
var createAgentArgsBaseSchema = import_zod5.z.object({
|
|
5145
|
+
limits: import_core5.ExecutionLimitsSchema.optional(),
|
|
5146
|
+
getProvider: import_zod5.z.unknown().optional(),
|
|
5147
|
+
workflows: import_zod5.z.array(import_zod5.z.unknown()).optional(),
|
|
5148
|
+
workflowsDir: import_zod5.z.string().optional(),
|
|
5149
|
+
workflowRegistry: import_zod5.z.unknown().optional(),
|
|
5150
|
+
defaultWorkflowId: import_zod5.z.string().optional(),
|
|
5151
|
+
frameworkAdapter: import_zod5.z.unknown().optional(),
|
|
5152
|
+
telemetry: import_zod5.z.unknown().optional()
|
|
4230
5153
|
}).strict();
|
|
4231
5154
|
var createAgentArgsSchema = createAgentArgsBaseSchema.superRefine(
|
|
4232
5155
|
(value, ctx) => {
|
|
@@ -4290,6 +5213,7 @@ var clearPendingGraphWrites = async (frameworkAdapter, request) => {
|
|
|
4290
5213
|
function createAgent(args) {
|
|
4291
5214
|
const parsedArgs = parseCreateAgentArgs(args);
|
|
4292
5215
|
const {
|
|
5216
|
+
limits,
|
|
4293
5217
|
getProvider,
|
|
4294
5218
|
workflows,
|
|
4295
5219
|
workflowsDir,
|
|
@@ -4299,24 +5223,24 @@ function createAgent(args) {
|
|
|
4299
5223
|
telemetry
|
|
4300
5224
|
} = parsedArgs;
|
|
4301
5225
|
const resolvedDefaultWorkflowId = defaultWorkflowId;
|
|
4302
|
-
const resolvedFrameworkAdapter = frameworkAdapter ?? (0,
|
|
5226
|
+
const resolvedFrameworkAdapter = frameworkAdapter ?? (0, import_runtime5.createFrameworkAdapterFromEnv)();
|
|
4303
5227
|
const resolvedGetProvider = getProvider ?? import_providers.getProvider;
|
|
4304
5228
|
const resolvedCwd = process.cwd();
|
|
4305
5229
|
const registryPromise = (async () => {
|
|
4306
5230
|
if (workflowRegistry) return workflowRegistry;
|
|
4307
5231
|
if (workflows) {
|
|
4308
|
-
return (0,
|
|
5232
|
+
return (0, import_runtime5.createInMemoryWorkflowRegistry)(workflows, {
|
|
4309
5233
|
fallbackId: resolvedDefaultWorkflowId ?? "general-chat"
|
|
4310
5234
|
});
|
|
4311
5235
|
}
|
|
4312
5236
|
if (workflowsDir) {
|
|
4313
|
-
return (0,
|
|
5237
|
+
return (0, import_runtime5.createFileWorkflowRegistry)({
|
|
4314
5238
|
workflowsDir,
|
|
4315
5239
|
fallbackId: resolvedDefaultWorkflowId ?? "general-chat"
|
|
4316
5240
|
});
|
|
4317
5241
|
}
|
|
4318
5242
|
const resolvedWorkflowsDir = (0, import_node_path.resolve)(resolvedCwd, "src", "workflows");
|
|
4319
|
-
return (0,
|
|
5243
|
+
return (0, import_runtime5.createFileWorkflowRegistry)({
|
|
4320
5244
|
workflowsDir: resolvedWorkflowsDir,
|
|
4321
5245
|
fallbackId: resolvedDefaultWorkflowId ?? "general-chat"
|
|
4322
5246
|
});
|
|
@@ -4354,12 +5278,21 @@ function createAgent(args) {
|
|
|
4354
5278
|
return streamChat({
|
|
4355
5279
|
...resolvedDefaultWorkflowId ? { defaultWorkflowId: resolvedDefaultWorkflowId } : {},
|
|
4356
5280
|
messages,
|
|
5281
|
+
abortSignal: parsedOptions?.abortSignal,
|
|
5282
|
+
executionSignal: parsedOptions?.executionSignal,
|
|
5283
|
+
onExecution: parsedOptions?.onExecution,
|
|
4357
5284
|
options: parsedOptions,
|
|
4358
5285
|
workflowRegistry: registry,
|
|
4359
5286
|
...knownWorkflowIds ? { knownWorkflowIds } : {},
|
|
4360
5287
|
frameworkAdapter: resolvedFrameworkAdapter,
|
|
4361
5288
|
getProvider: resolvedGetProvider,
|
|
4362
5289
|
loadRuntimeConfig: (runtimeOptions) => ({
|
|
5290
|
+
...limits || runtimeOptions?.limits ? {
|
|
5291
|
+
executionBudget: (0, import_core5.createExecutionBudget)(
|
|
5292
|
+
limits,
|
|
5293
|
+
runtimeOptions?.limits
|
|
5294
|
+
)
|
|
5295
|
+
} : {},
|
|
4363
5296
|
...runtimeOptions?.sessionId ? {
|
|
4364
5297
|
session: {
|
|
4365
5298
|
id: runtimeOptions.sessionId
|
|
@@ -4460,7 +5393,26 @@ function createAgent(args) {
|
|
|
4460
5393
|
checkpoint: { ...result.checkpoint, activePendingRequests }
|
|
4461
5394
|
};
|
|
4462
5395
|
};
|
|
5396
|
+
const services = async () => {
|
|
5397
|
+
const registry = await registryPromise;
|
|
5398
|
+
if (!registry) throw new Error("Workflow registry is unavailable.");
|
|
5399
|
+
return {
|
|
5400
|
+
registry,
|
|
5401
|
+
limits,
|
|
5402
|
+
frameworkAdapter: resolvedFrameworkAdapter,
|
|
5403
|
+
getProvider: resolvedGetProvider,
|
|
5404
|
+
telemetry,
|
|
5405
|
+
knownWorkflowIds: (await registeredWorkflowsPromise)?.map(
|
|
5406
|
+
(workflow) => workflow.id
|
|
5407
|
+
)
|
|
5408
|
+
};
|
|
5409
|
+
};
|
|
5410
|
+
const execute = (async (options) => executeWorkflow(await services(), options));
|
|
5411
|
+
const resume = (async (options) => resumeWorkflow(await services(), options));
|
|
4463
5412
|
return {
|
|
5413
|
+
...createInterruptDiscovery(resolvedFrameworkAdapter.pendingRequests),
|
|
5414
|
+
execute,
|
|
5415
|
+
resume,
|
|
4464
5416
|
projectTopology,
|
|
4465
5417
|
streamChat: streamChat2,
|
|
4466
5418
|
listCheckpoints,
|
|
@@ -4471,6 +5423,7 @@ function createAgent(args) {
|
|
|
4471
5423
|
}
|
|
4472
5424
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4473
5425
|
0 && (module.exports = {
|
|
5426
|
+
ExecutionRequestError,
|
|
4474
5427
|
createAgent,
|
|
4475
5428
|
createChatRouteHandler,
|
|
4476
5429
|
createCheckpointRouteHandler,
|