@kortyx/agent 0.16.0 → 0.18.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 +37 -0
- package/dist/index.d.ts +128 -7
- package/dist/index.js +998 -309
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -55,6 +55,7 @@ var require_dist = __commonJS({
|
|
|
55
55
|
createWorkflowHooks: () => createWorkflowHooks,
|
|
56
56
|
emitWorkflowCall: () => emitWorkflowCall2,
|
|
57
57
|
runWithHookContext: () => runWithHookContext,
|
|
58
|
+
useAbortSignal: () => useAbortSignal,
|
|
58
59
|
useInterrupt: () => useInterrupt,
|
|
59
60
|
useNodeState: () => useNodeState,
|
|
60
61
|
useReason: () => useReason2,
|
|
@@ -217,6 +218,7 @@ var require_dist = __commonJS({
|
|
|
217
218
|
}
|
|
218
219
|
return ctx;
|
|
219
220
|
}
|
|
221
|
+
var import_core5 = require("@kortyx/core");
|
|
220
222
|
var resolveReasonCheckpointKey = (args) => typeof args.id === "string" && args.id.length > 0 ? `__useReason:${args.id}` : `__useReason:auto:${args.autoIndex}`;
|
|
221
223
|
var readReasonCheckpoint = (raw) => {
|
|
222
224
|
if (!raw || typeof raw !== "object") return void 0;
|
|
@@ -267,6 +269,7 @@ var require_dist = __commonJS({
|
|
|
267
269
|
};
|
|
268
270
|
var awaitInterruptInternal = (args) => {
|
|
269
271
|
const ctx = getHookContext();
|
|
272
|
+
(0, import_core5.throwIfExecutionAborted)(ctx.node.abortSignal);
|
|
270
273
|
const request = parseWithSchema(
|
|
271
274
|
args.requestSchema,
|
|
272
275
|
args.request,
|
|
@@ -299,6 +302,7 @@ var require_dist = __commonJS({
|
|
|
299
302
|
);
|
|
300
303
|
return Promise.resolve(response);
|
|
301
304
|
};
|
|
305
|
+
var import_core52 = require("@kortyx/core");
|
|
302
306
|
var import_node_crypto4 = require("crypto");
|
|
303
307
|
var parseStructuredPath = (path, source) => {
|
|
304
308
|
if (path.length === 0) {
|
|
@@ -386,7 +390,9 @@ var require_dist = __commonJS({
|
|
|
386
390
|
var shouldEmitStructured = (cfg) => Boolean(cfg);
|
|
387
391
|
var shouldStreamStructured = (cfg) => Boolean(cfg) && (cfg?.stream ?? true);
|
|
388
392
|
var import_node_crypto32 = require("crypto");
|
|
393
|
+
var import_core32 = require("@kortyx/core");
|
|
389
394
|
var import_node_crypto22 = require("crypto");
|
|
395
|
+
var import_core22 = require("@kortyx/core");
|
|
390
396
|
var toError = (value) => value instanceof Error ? value : new Error(String(value));
|
|
391
397
|
var createRuntimeId = () => {
|
|
392
398
|
try {
|
|
@@ -413,6 +419,7 @@ var require_dist = __commonJS({
|
|
|
413
419
|
const responseFormat = args.responseFormat ?? args.model.options?.responseFormat;
|
|
414
420
|
const providerOptions = args.providerOptions ?? args.model.options?.providerOptions;
|
|
415
421
|
const tools = args.tools ?? args.model.options?.tools;
|
|
422
|
+
(0, import_core22.throwIfExecutionAborted)(abortSignal);
|
|
416
423
|
const model = args.model.provider.getModel(args.model.modelId, {
|
|
417
424
|
...temperature !== void 0 ? { temperature } : {},
|
|
418
425
|
streaming: stream,
|
|
@@ -505,6 +512,7 @@ var require_dist = __commonJS({
|
|
|
505
512
|
}
|
|
506
513
|
const response2 = await model.stream(messages);
|
|
507
514
|
for await (const chunk of response2) {
|
|
515
|
+
(0, import_core22.throwIfExecutionAborted)(abortSignal);
|
|
508
516
|
switch (chunk.type) {
|
|
509
517
|
case "text-delta": {
|
|
510
518
|
if (chunk.delta.length === 0) break;
|
|
@@ -583,10 +591,12 @@ var require_dist = __commonJS({
|
|
|
583
591
|
...providerMetadata !== void 0 ? { providerMetadata } : {},
|
|
584
592
|
...warnings !== void 0 ? { warnings } : {}
|
|
585
593
|
};
|
|
594
|
+
(0, import_core22.throwIfExecutionAborted)(abortSignal);
|
|
586
595
|
endTrace(traceSpan, result2);
|
|
587
596
|
return result2;
|
|
588
597
|
}
|
|
589
598
|
const response = await model.invoke(messages);
|
|
599
|
+
(0, import_core22.throwIfExecutionAborted)(abortSignal);
|
|
590
600
|
if (emit) {
|
|
591
601
|
emitNodeEvent(args.emitEvent, args.nodeId, "text-start", {
|
|
592
602
|
...commonMeta
|
|
@@ -613,8 +623,9 @@ var require_dist = __commonJS({
|
|
|
613
623
|
endTrace(traceSpan, result);
|
|
614
624
|
return result;
|
|
615
625
|
} catch (error) {
|
|
616
|
-
|
|
617
|
-
|
|
626
|
+
const failure = abortSignal?.aborted ? (0, import_core22.createExecutionCancelledError)() : error;
|
|
627
|
+
failTrace(traceSpan, failure);
|
|
628
|
+
throw failure;
|
|
618
629
|
}
|
|
619
630
|
}
|
|
620
631
|
var createRuntimeId2 = () => {
|
|
@@ -626,6 +637,13 @@ var require_dist = __commonJS({
|
|
|
626
637
|
};
|
|
627
638
|
async function reasonEngine(args, meta, inputOverride) {
|
|
628
639
|
const ctx = getHookContext();
|
|
640
|
+
const abortSignal = (0, import_core32.combineAbortSignals)(
|
|
641
|
+
ctx.node.abortSignal,
|
|
642
|
+
args.abortSignal,
|
|
643
|
+
args.model.options?.abortSignal
|
|
644
|
+
);
|
|
645
|
+
(0, import_core32.throwIfExecutionAborted)(abortSignal);
|
|
646
|
+
ctx.node.consumeExecution?.("maxModelPasses");
|
|
629
647
|
return runReasonEngine({
|
|
630
648
|
model: args.model,
|
|
631
649
|
input: inputOverride ?? args.input,
|
|
@@ -633,7 +651,7 @@ var require_dist = __commonJS({
|
|
|
633
651
|
temperature: args.temperature,
|
|
634
652
|
maxOutputTokens: args.maxOutputTokens,
|
|
635
653
|
stopSequences: args.stopSequences,
|
|
636
|
-
abortSignal
|
|
654
|
+
abortSignal,
|
|
637
655
|
reasoning: args.reasoning,
|
|
638
656
|
responseFormat: args.responseFormat,
|
|
639
657
|
providerOptions: args.providerOptions,
|
|
@@ -815,7 +833,7 @@ var require_dist = __commonJS({
|
|
|
815
833
|
...output !== void 0 ? { output } : {}
|
|
816
834
|
};
|
|
817
835
|
};
|
|
818
|
-
var
|
|
836
|
+
var import_zod5 = require("zod");
|
|
819
837
|
var tryStringify2 = (value) => {
|
|
820
838
|
if (typeof value === "string") return value;
|
|
821
839
|
try {
|
|
@@ -826,7 +844,7 @@ var require_dist = __commonJS({
|
|
|
826
844
|
};
|
|
827
845
|
var tryGetSchemaHint = (schema) => {
|
|
828
846
|
try {
|
|
829
|
-
const hint = (0,
|
|
847
|
+
const hint = (0, import_zod5.toJSONSchema)(schema);
|
|
830
848
|
return JSON.stringify(hint, null, 2);
|
|
831
849
|
} catch {
|
|
832
850
|
return void 0;
|
|
@@ -1574,6 +1592,7 @@ ${tryStringify2(args.response)}`
|
|
|
1574
1592
|
] : [];
|
|
1575
1593
|
});
|
|
1576
1594
|
};
|
|
1595
|
+
var import_core42 = require("@kortyx/core");
|
|
1577
1596
|
var toErrorMessage2 = (error) => error instanceof Error ? error.message : String(error);
|
|
1578
1597
|
var toToolDefinitions = (tools) => tools.map((tool) => ({
|
|
1579
1598
|
name: tool.name,
|
|
@@ -1627,6 +1646,11 @@ ${tryStringify2(args.response)}`
|
|
|
1627
1646
|
var runReasonToolLoop = async (args) => {
|
|
1628
1647
|
const { useReasonArgs, id, opId, traceSpan } = args;
|
|
1629
1648
|
const ctx = getHookContext();
|
|
1649
|
+
const abortSignal = (0, import_core42.combineAbortSignals)(
|
|
1650
|
+
ctx.node.abortSignal,
|
|
1651
|
+
useReasonArgs.abortSignal,
|
|
1652
|
+
useReasonArgs.model.options?.abortSignal
|
|
1653
|
+
);
|
|
1630
1654
|
const tools = useReasonArgs.tools ?? [];
|
|
1631
1655
|
const toolByName = /* @__PURE__ */ new Map();
|
|
1632
1656
|
let validationCompleted = false;
|
|
@@ -1676,6 +1700,7 @@ ${tryStringify2(args.response)}`
|
|
|
1676
1700
|
};
|
|
1677
1701
|
try {
|
|
1678
1702
|
for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
|
|
1703
|
+
(0, import_core42.throwIfExecutionAborted)(abortSignal);
|
|
1679
1704
|
traceSpan?.addEvent?.("useReason.tool-step.start", {
|
|
1680
1705
|
stepIndex,
|
|
1681
1706
|
toolCount: toolDefinitions.length
|
|
@@ -1693,6 +1718,7 @@ ${tryStringify2(args.response)}`
|
|
|
1693
1718
|
finalText = step.text;
|
|
1694
1719
|
finalRaw = step.raw;
|
|
1695
1720
|
aggregatedUsage = mergeUsage(aggregatedUsage, step.usage);
|
|
1721
|
+
accumulateTokenUsage(step.usage);
|
|
1696
1722
|
finalFinishReason = step.finishReason;
|
|
1697
1723
|
aggregatedProviderMetadata = mergeProviderMetadata(
|
|
1698
1724
|
aggregatedProviderMetadata,
|
|
@@ -1726,6 +1752,7 @@ ${tryStringify2(args.response)}`
|
|
|
1726
1752
|
toolCalls
|
|
1727
1753
|
});
|
|
1728
1754
|
for (const toolCall of toolCalls) {
|
|
1755
|
+
(0, import_core42.throwIfExecutionAborted)(abortSignal);
|
|
1729
1756
|
const tool = toolByName.get(toolCall.name);
|
|
1730
1757
|
if (!tool) {
|
|
1731
1758
|
throw new Error(
|
|
@@ -1793,10 +1820,13 @@ ${tryStringify2(args.response)}`
|
|
|
1793
1820
|
}
|
|
1794
1821
|
}
|
|
1795
1822
|
try {
|
|
1823
|
+
(0, import_core42.throwIfExecutionAborted)(abortSignal);
|
|
1824
|
+
ctx.node.consumeExecution?.("maxToolCalls");
|
|
1796
1825
|
const rawResult = await tool.execute(toolCall.input, {
|
|
1797
1826
|
toolCallId: toolCall.id,
|
|
1798
|
-
...
|
|
1827
|
+
...abortSignal ? { abortSignal } : {}
|
|
1799
1828
|
});
|
|
1829
|
+
(0, import_core42.throwIfExecutionAborted)(abortSignal);
|
|
1800
1830
|
const result2 = normalizeToolResult(toolCall, rawResult);
|
|
1801
1831
|
toolResults.push(result2);
|
|
1802
1832
|
allToolResults.push(result2);
|
|
@@ -1816,6 +1846,9 @@ ${tryStringify2(args.response)}`
|
|
|
1816
1846
|
isError: Boolean(result2.isError)
|
|
1817
1847
|
});
|
|
1818
1848
|
} catch (error) {
|
|
1849
|
+
(0, import_core42.throwIfExecutionAborted)(abortSignal);
|
|
1850
|
+
if ((0, import_core42.isExecutionLimitReached)(error) || (0, import_core42.isExecutionCancelled)(error))
|
|
1851
|
+
throw error;
|
|
1819
1852
|
const result2 = {
|
|
1820
1853
|
toolCallId: toolCall.id,
|
|
1821
1854
|
name: tool.name,
|
|
@@ -1937,6 +1970,13 @@ ${tryStringify2(args.response)}`
|
|
|
1937
1970
|
async function useReason(args) {
|
|
1938
1971
|
assertReasoningThoughtsCompatibility(args);
|
|
1939
1972
|
const ctx = getHookContext();
|
|
1973
|
+
const abortSignal = (0, import_core52.combineAbortSignals)(
|
|
1974
|
+
ctx.node.abortSignal,
|
|
1975
|
+
args.abortSignal,
|
|
1976
|
+
args.model.options?.abortSignal
|
|
1977
|
+
);
|
|
1978
|
+
(0, import_core52.throwIfExecutionAborted)(abortSignal);
|
|
1979
|
+
args = { ...args, abortSignal };
|
|
1940
1980
|
const id = typeof args.id === "string" && args.id.length > 0 ? args.id : void 0;
|
|
1941
1981
|
const opId = createRuntimeId2();
|
|
1942
1982
|
const checkpointKey = resolveReasonCheckpointKey({
|
|
@@ -2304,6 +2344,9 @@ ${tryStringify2(args.response)}`
|
|
|
2304
2344
|
function useInterrupt(args) {
|
|
2305
2345
|
return awaitInterruptInternal(args);
|
|
2306
2346
|
}
|
|
2347
|
+
function useAbortSignal() {
|
|
2348
|
+
return getHookContext().node.abortSignal;
|
|
2349
|
+
}
|
|
2307
2350
|
function useRuntimeContext() {
|
|
2308
2351
|
const ctx = getHookContext();
|
|
2309
2352
|
const config = ctx.state.config;
|
|
@@ -2346,6 +2389,7 @@ ${tryStringify2(args.response)}`
|
|
|
2346
2389
|
return [getValue(), setValue];
|
|
2347
2390
|
}
|
|
2348
2391
|
var import_node_crypto5 = require("crypto");
|
|
2392
|
+
var import_core6 = require("@kortyx/core");
|
|
2349
2393
|
var import_node_crypto42 = require("crypto");
|
|
2350
2394
|
function emitWorkflowCall2(telemetry, type, payload) {
|
|
2351
2395
|
const base = telemetry?.correlation;
|
|
@@ -2428,6 +2472,7 @@ ${tryStringify2(args.response)}`
|
|
|
2428
2472
|
}
|
|
2429
2473
|
async function useWorkflow(args) {
|
|
2430
2474
|
const ctx = getHookContext();
|
|
2475
|
+
(0, import_core6.throwIfExecutionAborted)(ctx.node.abortSignal);
|
|
2431
2476
|
const service = ctx.node.callWorkflow;
|
|
2432
2477
|
if (!service)
|
|
2433
2478
|
throw new Error("useWorkflow requires an agent workflow registry.");
|
|
@@ -2461,6 +2506,7 @@ ${tryStringify2(args.response)}`
|
|
|
2461
2506
|
workflow,
|
|
2462
2507
|
"call input or target changed during replay"
|
|
2463
2508
|
);
|
|
2509
|
+
if (!record) ctx.node.consumeExecution?.("maxChildInvocations");
|
|
2464
2510
|
record ??= {
|
|
2465
2511
|
fingerprint,
|
|
2466
2512
|
invocationId: (0, import_node_crypto5.randomUUID)(),
|
|
@@ -2494,6 +2540,15 @@ ${tryStringify2(args.response)}`
|
|
|
2494
2540
|
...content
|
|
2495
2541
|
});
|
|
2496
2542
|
};
|
|
2543
|
+
const recordUsage = (usage) => {
|
|
2544
|
+
if (!usage) return;
|
|
2545
|
+
accumulateTokenUsage({
|
|
2546
|
+
input: usage.input - (record.usage?.input ?? 0),
|
|
2547
|
+
output: usage.output - (record.usage?.output ?? 0),
|
|
2548
|
+
total: usage.total - (record.usage?.total ?? 0)
|
|
2549
|
+
});
|
|
2550
|
+
record.usage = usage;
|
|
2551
|
+
};
|
|
2497
2552
|
const cached = record.status === "completed";
|
|
2498
2553
|
try {
|
|
2499
2554
|
for (const interruption of record.interrupts) {
|
|
@@ -2522,14 +2577,28 @@ ${tryStringify2(args.response)}`
|
|
|
2522
2577
|
...record.interrupts.length ? { response: record.interrupts.at(-1)?.response } : {}
|
|
2523
2578
|
});
|
|
2524
2579
|
} catch (error) {
|
|
2580
|
+
(0, import_core6.throwIfExecutionAborted)(ctx.node.abortSignal);
|
|
2581
|
+
if ((0, import_core6.isExecutionCancelled)(error)) throw error;
|
|
2582
|
+
if ((0, import_core6.isExecutionLimitReached)(error)) {
|
|
2583
|
+
const child = error;
|
|
2584
|
+
if (child.__kortyxChildSnapshot)
|
|
2585
|
+
record.snapshot = child.__kortyxChildSnapshot;
|
|
2586
|
+
recordUsage(child.__kortyxChildUsage);
|
|
2587
|
+
report("suspended", {
|
|
2588
|
+
reason: "limit_reached",
|
|
2589
|
+
limit: error.limitReached
|
|
2590
|
+
});
|
|
2591
|
+
throw error;
|
|
2592
|
+
}
|
|
2525
2593
|
record.status = "failed";
|
|
2526
2594
|
record.error = error instanceof Error ? error.message : String(error);
|
|
2527
2595
|
report("failed", { error: "Child workflow failed" });
|
|
2528
2596
|
throw new WorkflowCallError(workflow, record.error);
|
|
2529
2597
|
}
|
|
2598
|
+
(0, import_core6.throwIfExecutionAborted)(ctx.node.abortSignal);
|
|
2599
|
+
recordUsage(outcome.usage);
|
|
2530
2600
|
if (outcome.status === "completed") {
|
|
2531
2601
|
record.data = outcome.data;
|
|
2532
|
-
accumulateTokenUsage(outcome.usage);
|
|
2533
2602
|
workflowCallFingerprint(record.data);
|
|
2534
2603
|
record.status = "completed";
|
|
2535
2604
|
delete record.snapshot;
|
|
@@ -2569,6 +2638,7 @@ ${tryStringify2(args.response)}`
|
|
|
2569
2638
|
// src/index.ts
|
|
2570
2639
|
var index_exports = {};
|
|
2571
2640
|
__export(index_exports, {
|
|
2641
|
+
ExecutionRequestError: () => ExecutionRequestError,
|
|
2572
2642
|
createAgent: () => createAgent,
|
|
2573
2643
|
createChatRouteHandler: () => createChatRouteHandler,
|
|
2574
2644
|
createCheckpointRouteHandler: () => createCheckpointRouteHandler,
|
|
@@ -2649,6 +2719,7 @@ function parseCheckpointRequestBody(value) {
|
|
|
2649
2719
|
async function handleChatRequestBody(args) {
|
|
2650
2720
|
const { agent, body } = args;
|
|
2651
2721
|
const stream = await agent.streamChat(body.messages, {
|
|
2722
|
+
...args.abortSignal ? { abortSignal: args.abortSignal } : {},
|
|
2652
2723
|
sessionId: body.sessionId,
|
|
2653
2724
|
workflowId: body.workflowId,
|
|
2654
2725
|
context: body.context
|
|
@@ -2668,7 +2739,11 @@ function createChatRouteHandler(args) {
|
|
|
2668
2739
|
return async function POST(request) {
|
|
2669
2740
|
try {
|
|
2670
2741
|
const body = parseChatRequestBody(await request.json());
|
|
2671
|
-
return await handleChatRequestBody({
|
|
2742
|
+
return await handleChatRequestBody({
|
|
2743
|
+
agent,
|
|
2744
|
+
body,
|
|
2745
|
+
abortSignal: request.signal
|
|
2746
|
+
});
|
|
2672
2747
|
} catch (error) {
|
|
2673
2748
|
return new Response(
|
|
2674
2749
|
JSON.stringify({
|
|
@@ -2734,10 +2809,168 @@ async function* streamChatFromRoute(args) {
|
|
|
2734
2809
|
|
|
2735
2810
|
// src/chat/create-agent.ts
|
|
2736
2811
|
var import_node_path = require("path");
|
|
2812
|
+
var import_core4 = require("@kortyx/core");
|
|
2737
2813
|
var import_providers = require("@kortyx/providers");
|
|
2738
|
-
var
|
|
2814
|
+
var import_runtime5 = require("@kortyx/runtime");
|
|
2815
|
+
var import_zod4 = require("zod");
|
|
2816
|
+
|
|
2817
|
+
// src/execution/execute.ts
|
|
2818
|
+
var import_core3 = require("@kortyx/core");
|
|
2819
|
+
var import_runtime3 = require("@kortyx/runtime");
|
|
2820
|
+
var import_zod3 = require("zod");
|
|
2821
|
+
|
|
2822
|
+
// src/interrupt/resume-handler.ts
|
|
2823
|
+
var import_core2 = require("@kortyx/core");
|
|
2824
|
+
var import_runtime2 = require("@kortyx/runtime");
|
|
2825
|
+
|
|
2826
|
+
// src/orchestrator.ts
|
|
2827
|
+
var import_node_stream = require("stream");
|
|
2828
|
+
var import_core = require("@kortyx/core");
|
|
2829
|
+
var import_runtime = require("@kortyx/runtime");
|
|
2830
|
+
var import_langgraph = require("@langchain/langgraph");
|
|
2831
|
+
|
|
2832
|
+
// src/execution/contracts.ts
|
|
2739
2833
|
var import_zod2 = require("zod");
|
|
2740
2834
|
|
|
2835
|
+
// src/execution/types.ts
|
|
2836
|
+
var ExecutionRequestError = class extends Error {
|
|
2837
|
+
constructor(code, message) {
|
|
2838
|
+
super(message);
|
|
2839
|
+
this.code = code;
|
|
2840
|
+
}
|
|
2841
|
+
code;
|
|
2842
|
+
name = "ExecutionRequestError";
|
|
2843
|
+
};
|
|
2844
|
+
|
|
2845
|
+
// src/execution/contracts.ts
|
|
2846
|
+
async function resolveExecutionWorkflow(select, reference) {
|
|
2847
|
+
const id = typeof reference === "string" ? reference : reference.id;
|
|
2848
|
+
let workflow;
|
|
2849
|
+
try {
|
|
2850
|
+
workflow = await select(id);
|
|
2851
|
+
} catch {
|
|
2852
|
+
throw new ExecutionRequestError(
|
|
2853
|
+
"UNKNOWN_WORKFLOW",
|
|
2854
|
+
`Workflow '${id}' is not registered.`
|
|
2855
|
+
);
|
|
2856
|
+
}
|
|
2857
|
+
if (workflow.id !== id)
|
|
2858
|
+
throw new ExecutionRequestError(
|
|
2859
|
+
"UNKNOWN_WORKFLOW",
|
|
2860
|
+
`Workflow '${id}' is not registered.`
|
|
2861
|
+
);
|
|
2862
|
+
if (!workflow.inputSchema || !workflow.outputSchema)
|
|
2863
|
+
throw new ExecutionRequestError(
|
|
2864
|
+
"MISSING_CONTRACT",
|
|
2865
|
+
`Workflow '${id}' requires inputSchema and outputSchema.`
|
|
2866
|
+
);
|
|
2867
|
+
if (typeof reference !== "string" && (reference.version !== workflow.version || reference.inputSchema !== workflow.inputSchema || reference.outputSchema !== workflow.outputSchema))
|
|
2868
|
+
throw new ExecutionRequestError(
|
|
2869
|
+
"CONTRACT_MISMATCH",
|
|
2870
|
+
"The typed workflow contract does not match the registered definition."
|
|
2871
|
+
);
|
|
2872
|
+
return workflow;
|
|
2873
|
+
}
|
|
2874
|
+
function parseExecutionInput(workflow, input) {
|
|
2875
|
+
try {
|
|
2876
|
+
import_zod2.z.json().parse(input);
|
|
2877
|
+
const parsed = workflow.inputSchema ? workflow.inputSchema.parse(input) : input;
|
|
2878
|
+
import_zod2.z.json().parse(parsed);
|
|
2879
|
+
return parsed;
|
|
2880
|
+
} catch (error) {
|
|
2881
|
+
throw new ExecutionRequestError(
|
|
2882
|
+
"INVALID_INPUT",
|
|
2883
|
+
error instanceof Error ? error.message : String(error)
|
|
2884
|
+
);
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
async function validateExecutionOutput(state, select) {
|
|
2888
|
+
if (!state.config?.executionContract) return state;
|
|
2889
|
+
const terminal = await select(String(state.currentWorkflow));
|
|
2890
|
+
const contract = state.config.executionContract;
|
|
2891
|
+
const root = await select(contract.id);
|
|
2892
|
+
if (root.id !== contract.id || root.version !== contract.version)
|
|
2893
|
+
throw new Error("The root workflow changed since execution started.");
|
|
2894
|
+
let data = state.data ?? {};
|
|
2895
|
+
try {
|
|
2896
|
+
for (const schema of /* @__PURE__ */ new Set([terminal.outputSchema, root.outputSchema])) {
|
|
2897
|
+
if (schema) data = schema.parse(data);
|
|
2898
|
+
}
|
|
2899
|
+
if (terminal.outputSchema || root.outputSchema) import_zod2.z.json().parse(data);
|
|
2900
|
+
} catch (error) {
|
|
2901
|
+
const failure = new Error(
|
|
2902
|
+
`Workflow output validation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
2903
|
+
);
|
|
2904
|
+
Object.assign(failure, { code: "INVALID_OUTPUT" });
|
|
2905
|
+
throw failure;
|
|
2906
|
+
}
|
|
2907
|
+
return { ...state, data };
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
// src/stream/transform-graph-stream-for-ui.ts
|
|
2911
|
+
var isGraphStateOutput = (value) => value !== null && typeof value === "object" && !Array.isArray(value) && "currentWorkflow" in value;
|
|
2912
|
+
async function* transformGraphStreamForUI(stream, options = {}) {
|
|
2913
|
+
const { debug = false, emitStatus = debug } = options;
|
|
2914
|
+
const startedNodes = /* @__PURE__ */ new Set();
|
|
2915
|
+
const endedNodes = /* @__PURE__ */ new Set();
|
|
2916
|
+
let latestGraphState = null;
|
|
2917
|
+
for await (const event of stream) {
|
|
2918
|
+
const { event: type, name, data } = event ?? {};
|
|
2919
|
+
if (debug) console.log(`[debug:event]`, JSON.stringify(event, null, 2));
|
|
2920
|
+
switch (type) {
|
|
2921
|
+
case "on_chain_start":
|
|
2922
|
+
if (name && !name.startsWith("ChannelWrite")) {
|
|
2923
|
+
if (name === "__start__" || name === "__end__") break;
|
|
2924
|
+
if (startedNodes.has(name)) break;
|
|
2925
|
+
startedNodes.add(name);
|
|
2926
|
+
if (debug) console.log(`[debug:start] node=${name}`);
|
|
2927
|
+
if (emitStatus) {
|
|
2928
|
+
yield { type: "status", message: `Processing node: ${name}` };
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
break;
|
|
2932
|
+
case "on_chain_end": {
|
|
2933
|
+
const nodeName = name;
|
|
2934
|
+
const output = data?.output;
|
|
2935
|
+
if (debug)
|
|
2936
|
+
console.log(
|
|
2937
|
+
`[debug:on_chain_end:${nodeName}] output=`,
|
|
2938
|
+
JSON.stringify(output, null, 2)
|
|
2939
|
+
);
|
|
2940
|
+
if (!output || nodeName?.startsWith("ChannelWrite")) break;
|
|
2941
|
+
if (isGraphStateOutput(output)) latestGraphState = output;
|
|
2942
|
+
if (nodeName !== "__start__" && nodeName !== "__end__") {
|
|
2943
|
+
if (!endedNodes.has(nodeName)) {
|
|
2944
|
+
if (emitStatus) {
|
|
2945
|
+
yield {
|
|
2946
|
+
type: "status",
|
|
2947
|
+
message: `\u2705 Completed node: ${nodeName}`
|
|
2948
|
+
};
|
|
2949
|
+
}
|
|
2950
|
+
endedNodes.add(nodeName);
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
break;
|
|
2954
|
+
}
|
|
2955
|
+
case "on_graph_end": {
|
|
2956
|
+
if (debug)
|
|
2957
|
+
console.log(`[debug:on_graph_end]`, JSON.stringify(data, null, 2));
|
|
2958
|
+
const output = data?.output;
|
|
2959
|
+
yield {
|
|
2960
|
+
type: "done",
|
|
2961
|
+
data: isGraphStateOutput(output) ? output : latestGraphState
|
|
2962
|
+
};
|
|
2963
|
+
break;
|
|
2964
|
+
}
|
|
2965
|
+
default:
|
|
2966
|
+
if (debug) {
|
|
2967
|
+
console.warn(`[debug:unknown_event]`, type);
|
|
2968
|
+
}
|
|
2969
|
+
break;
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2741
2974
|
// src/telemetry/events.ts
|
|
2742
2975
|
var import_node_crypto = require("crypto");
|
|
2743
2976
|
var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -3008,134 +3241,6 @@ var prepareWorkflowTelemetry = (args) => {
|
|
|
3008
3241
|
};
|
|
3009
3242
|
};
|
|
3010
3243
|
|
|
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
3244
|
// src/orchestrator.ts
|
|
3140
3245
|
var isRecord3 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3141
3246
|
var isGraphState = (value) => isRecord3(value) && typeof value.currentWorkflow === "string";
|
|
@@ -3157,15 +3262,30 @@ async function orchestrateGraphStream({
|
|
|
3157
3262
|
graph,
|
|
3158
3263
|
state,
|
|
3159
3264
|
config: initialConfig,
|
|
3265
|
+
abortSignal: requestSignal,
|
|
3266
|
+
emitOutput = true,
|
|
3267
|
+
onOutcome,
|
|
3160
3268
|
selectWorkflow,
|
|
3161
3269
|
knownWorkflowIds,
|
|
3162
3270
|
frameworkAdapter
|
|
3163
3271
|
}) {
|
|
3164
3272
|
const out = new import_node_stream.PassThrough({ objectMode: true });
|
|
3273
|
+
const write = (chunk) => {
|
|
3274
|
+
if (emitOutput && !out.destroyed) out.write(chunk);
|
|
3275
|
+
};
|
|
3276
|
+
let outcomeState = state;
|
|
3277
|
+
let outcomeError;
|
|
3278
|
+
let sessionCheckpointId;
|
|
3165
3279
|
let config = initialConfig;
|
|
3280
|
+
const budget = config.executionBudget;
|
|
3166
3281
|
let currentGraph = graph;
|
|
3167
3282
|
let currentState = state;
|
|
3168
3283
|
let finished = false;
|
|
3284
|
+
const controller = new AbortController();
|
|
3285
|
+
const abortSignal = (0, import_core.combineAbortSignals)(requestSignal, controller.signal);
|
|
3286
|
+
out.on("close", () => {
|
|
3287
|
+
if (!finished) controller.abort();
|
|
3288
|
+
});
|
|
3169
3289
|
let structuredSeq = 0;
|
|
3170
3290
|
const debugEnabled = Boolean(config?.features?.tracing);
|
|
3171
3291
|
const traceAdapter = getTraceAdapter(config);
|
|
@@ -3206,7 +3326,7 @@ async function orchestrateGraphStream({
|
|
|
3206
3326
|
},
|
|
3207
3327
|
tags: Array.isArray(telemetryConfig.tags) ? telemetryConfig.tags : void 0,
|
|
3208
3328
|
captureContent: telemetryConfig.captureContent,
|
|
3209
|
-
|
|
3329
|
+
input: typeof state.input === "string" ? state.input : JSON.stringify(state.input)
|
|
3210
3330
|
}
|
|
3211
3331
|
};
|
|
3212
3332
|
const namespacesUsed = /* @__PURE__ */ new Set();
|
|
@@ -3217,7 +3337,7 @@ async function orchestrateGraphStream({
|
|
|
3217
3337
|
try {
|
|
3218
3338
|
const sid = config?.session?.id;
|
|
3219
3339
|
if (sid && typeof sid === "string") {
|
|
3220
|
-
|
|
3340
|
+
write({ type: "session", sessionId: sid });
|
|
3221
3341
|
}
|
|
3222
3342
|
} catch {
|
|
3223
3343
|
}
|
|
@@ -3241,6 +3361,7 @@ async function orchestrateGraphStream({
|
|
|
3241
3361
|
}
|
|
3242
3362
|
lastOutputSegmentId = textStreamId;
|
|
3243
3363
|
accumulatedOutput += delta;
|
|
3364
|
+
if (!emitOutput) accumulatedOutput = accumulatedOutput.slice(0, 65536);
|
|
3244
3365
|
};
|
|
3245
3366
|
const nextStructuredStreamId = () => `${runId}:structured:${structuredSeq++}`;
|
|
3246
3367
|
const runTraceEndArgs = () => accumulatedOutput.length > 0 ? { telemetry: { output: accumulatedOutput } } : void 0;
|
|
@@ -3248,8 +3369,10 @@ async function orchestrateGraphStream({
|
|
|
3248
3369
|
let wroteHumanInput = false;
|
|
3249
3370
|
const pendingStore = frameworkAdapter?.pendingRequests;
|
|
3250
3371
|
const sealPendingSnapshots = async () => {
|
|
3372
|
+
if (budget) outcomeState.config.executionBudget = budget;
|
|
3251
3373
|
if (typeof frameworkAdapter?.checkpointer?.getTuple !== "function") return;
|
|
3252
3374
|
for (const [token, request] of activePendingRequests) {
|
|
3375
|
+
if (budget) request.state.config.executionBudget = budget;
|
|
3253
3376
|
const graphSnapshot = await (0, import_runtime.captureGraphSnapshot)(
|
|
3254
3377
|
frameworkAdapter.checkpointer,
|
|
3255
3378
|
runId,
|
|
@@ -3258,7 +3381,10 @@ async function orchestrateGraphStream({
|
|
|
3258
3381
|
if (graphSnapshot) {
|
|
3259
3382
|
const sealed = { ...request, graphSnapshot };
|
|
3260
3383
|
activePendingRequests.set(token, sealed);
|
|
3261
|
-
await pendingStore?.update(token, {
|
|
3384
|
+
await pendingStore?.update(token, {
|
|
3385
|
+
graphSnapshot,
|
|
3386
|
+
state: request.state
|
|
3387
|
+
});
|
|
3262
3388
|
}
|
|
3263
3389
|
}
|
|
3264
3390
|
};
|
|
@@ -3309,6 +3435,7 @@ async function orchestrateGraphStream({
|
|
|
3309
3435
|
activePendingRequests.set(record.token, record);
|
|
3310
3436
|
if (pendingStore) {
|
|
3311
3437
|
const savePromise = pendingStore.save(record).catch((error) => {
|
|
3438
|
+
outcomeError = error;
|
|
3312
3439
|
console.error("[orchestrator] failed to save pending request", error);
|
|
3313
3440
|
});
|
|
3314
3441
|
pendingRequestWrites.push(savePromise);
|
|
@@ -3318,7 +3445,7 @@ async function orchestrateGraphStream({
|
|
|
3318
3445
|
([key]) => !key.startsWith("__kortyx")
|
|
3319
3446
|
)
|
|
3320
3447
|
) : void 0;
|
|
3321
|
-
|
|
3448
|
+
write({
|
|
3322
3449
|
type: "interrupt",
|
|
3323
3450
|
requestId: record.requestId,
|
|
3324
3451
|
resumeToken: record.token,
|
|
@@ -3380,8 +3507,9 @@ async function orchestrateGraphStream({
|
|
|
3380
3507
|
const msg = String(
|
|
3381
3508
|
payload?.message ?? "Unexpected error"
|
|
3382
3509
|
);
|
|
3383
|
-
|
|
3384
|
-
|
|
3510
|
+
outcomeError = new Error(msg);
|
|
3511
|
+
write({ type: "error", message: msg });
|
|
3512
|
+
write({ type: "done" });
|
|
3385
3513
|
finished = true;
|
|
3386
3514
|
out.end();
|
|
3387
3515
|
return;
|
|
@@ -3394,14 +3522,14 @@ async function orchestrateGraphStream({
|
|
|
3394
3522
|
if (msg && msg === lastStatusMsg && now - lastStatusAt < 250) return;
|
|
3395
3523
|
lastStatusMsg = msg;
|
|
3396
3524
|
lastStatusAt = now;
|
|
3397
|
-
|
|
3525
|
+
write({ type: "status", message: msg });
|
|
3398
3526
|
return;
|
|
3399
3527
|
}
|
|
3400
3528
|
if (event === "text-start") {
|
|
3401
3529
|
const node = payload?.node;
|
|
3402
3530
|
if (!node) return;
|
|
3403
3531
|
touchedNodes.add(node);
|
|
3404
|
-
|
|
3532
|
+
write({
|
|
3405
3533
|
type: "text-start",
|
|
3406
3534
|
node,
|
|
3407
3535
|
...typeof payload.id === "string" ? { id: payload.id } : {},
|
|
@@ -3416,7 +3544,7 @@ async function orchestrateGraphStream({
|
|
|
3416
3544
|
if (!node || !delta) return;
|
|
3417
3545
|
touchedNodes.add(node);
|
|
3418
3546
|
appendAccumulatedOutput(payload, node, delta);
|
|
3419
|
-
|
|
3547
|
+
write({
|
|
3420
3548
|
type: "text-delta",
|
|
3421
3549
|
delta,
|
|
3422
3550
|
node,
|
|
@@ -3430,7 +3558,7 @@ async function orchestrateGraphStream({
|
|
|
3430
3558
|
const node = payload?.node;
|
|
3431
3559
|
if (!node) return;
|
|
3432
3560
|
touchedNodes.add(node);
|
|
3433
|
-
|
|
3561
|
+
write({
|
|
3434
3562
|
type: "text-end",
|
|
3435
3563
|
node,
|
|
3436
3564
|
...typeof payload.id === "string" ? { id: payload.id } : {},
|
|
@@ -3444,7 +3572,7 @@ async function orchestrateGraphStream({
|
|
|
3444
3572
|
if (typeof payloadObj.tool !== "string" || typeof payloadObj.toolCallId !== "string") {
|
|
3445
3573
|
return;
|
|
3446
3574
|
}
|
|
3447
|
-
|
|
3575
|
+
write({
|
|
3448
3576
|
type: "tool-call-start",
|
|
3449
3577
|
tool: payloadObj.tool,
|
|
3450
3578
|
toolCallId: payloadObj.toolCallId,
|
|
@@ -3461,7 +3589,7 @@ async function orchestrateGraphStream({
|
|
|
3461
3589
|
if (typeof payloadObj.tool !== "string" || typeof payloadObj.toolCallId !== "string") {
|
|
3462
3590
|
return;
|
|
3463
3591
|
}
|
|
3464
|
-
|
|
3592
|
+
write({
|
|
3465
3593
|
type: "tool-call-result",
|
|
3466
3594
|
tool: payloadObj.tool,
|
|
3467
3595
|
toolCallId: payloadObj.toolCallId,
|
|
@@ -3480,7 +3608,7 @@ async function orchestrateGraphStream({
|
|
|
3480
3608
|
if (typeof payloadObj.tool !== "string" || typeof payloadObj.toolCallId !== "string") {
|
|
3481
3609
|
return;
|
|
3482
3610
|
}
|
|
3483
|
-
|
|
3611
|
+
write({
|
|
3484
3612
|
type: "tool-call-error",
|
|
3485
3613
|
tool: payloadObj.tool,
|
|
3486
3614
|
toolCallId: payloadObj.toolCallId,
|
|
@@ -3496,7 +3624,7 @@ async function orchestrateGraphStream({
|
|
|
3496
3624
|
const node = payload?.node;
|
|
3497
3625
|
const text = String(payload?.content ?? "");
|
|
3498
3626
|
if (node) touchedNodes.add(node);
|
|
3499
|
-
|
|
3627
|
+
write({ type: "message", node, content: text });
|
|
3500
3628
|
return;
|
|
3501
3629
|
}
|
|
3502
3630
|
if (event === "structured_data") {
|
|
@@ -3505,7 +3633,7 @@ async function orchestrateGraphStream({
|
|
|
3505
3633
|
const streamId = typeof payloadObj.streamId === "string" && payloadObj.streamId ? payloadObj.streamId : nextStructuredStreamId();
|
|
3506
3634
|
structuredStreamIds.add(streamId);
|
|
3507
3635
|
if (payloadObj.node) touchedNodes.add(payloadObj.node);
|
|
3508
|
-
|
|
3636
|
+
write({
|
|
3509
3637
|
type: "structured-data",
|
|
3510
3638
|
node: payloadObj.node,
|
|
3511
3639
|
streamId,
|
|
@@ -3531,7 +3659,7 @@ async function orchestrateGraphStream({
|
|
|
3531
3659
|
return;
|
|
3532
3660
|
}
|
|
3533
3661
|
if (event === "transition") {
|
|
3534
|
-
|
|
3662
|
+
write({
|
|
3535
3663
|
type: "transition",
|
|
3536
3664
|
transitionTo: payload?.transitionTo,
|
|
3537
3665
|
payload: payload?.payload ?? {}
|
|
@@ -3559,10 +3687,14 @@ async function orchestrateGraphStream({
|
|
|
3559
3687
|
};
|
|
3560
3688
|
const runLoop = async (runTraceSpan) => {
|
|
3561
3689
|
while (true) {
|
|
3690
|
+
(0, import_core.throwIfExecutionAborted)(abortSignal);
|
|
3562
3691
|
let workflowFinalState = null;
|
|
3563
3692
|
let workflowFinalGraphCheckpointId;
|
|
3564
3693
|
let sawWorkflowDone = false;
|
|
3565
3694
|
let workflowDoneData;
|
|
3695
|
+
currentGraph.execution ??= {};
|
|
3696
|
+
currentGraph.execution.abortSignal = abortSignal;
|
|
3697
|
+
currentGraph.execution.budget = budget;
|
|
3566
3698
|
currentGraph.config = currentGraph.config || {};
|
|
3567
3699
|
currentGraph.config.emit = forwardEmit;
|
|
3568
3700
|
currentGraph.config.executionRunId = runId;
|
|
@@ -3595,7 +3727,7 @@ async function orchestrateGraphStream({
|
|
|
3595
3727
|
};
|
|
3596
3728
|
};
|
|
3597
3729
|
if (debugEnabled) {
|
|
3598
|
-
|
|
3730
|
+
write({
|
|
3599
3731
|
type: "status",
|
|
3600
3732
|
message: `\u{1F9F5} thread_id=${threadId} run_id=${runId} workflow=${currentState.currentWorkflow}`
|
|
3601
3733
|
});
|
|
@@ -3614,16 +3746,32 @@ async function orchestrateGraphStream({
|
|
|
3614
3746
|
}
|
|
3615
3747
|
});
|
|
3616
3748
|
if (debugEnabled) {
|
|
3617
|
-
|
|
3749
|
+
write({
|
|
3618
3750
|
type: "status",
|
|
3619
3751
|
message: `\u25B6\uFE0F streamEvents invoke: resume=${Boolean(currentGraph.config?.resume)} thread_id=${threadId} run_id=${runId} ns=${String(currentState.currentWorkflow || "default")}`
|
|
3620
3752
|
});
|
|
3621
3753
|
}
|
|
3622
|
-
const
|
|
3754
|
+
const observedStream = (async function* () {
|
|
3755
|
+
for await (const event of runtimeStream) {
|
|
3756
|
+
const raw = event;
|
|
3757
|
+
if ((raw.event === "on_chain_end" || raw.event === "on_graph_end") && !raw.name?.startsWith("ChannelWrite") && isGraphState(raw.data?.output)) {
|
|
3758
|
+
outcomeState = raw.data.output;
|
|
3759
|
+
}
|
|
3760
|
+
yield event;
|
|
3761
|
+
}
|
|
3762
|
+
})();
|
|
3763
|
+
const uiStream = transformGraphStreamForUI(observedStream, {
|
|
3623
3764
|
debug: debugEnabled,
|
|
3624
3765
|
emitStatus: debugEnabled
|
|
3625
3766
|
});
|
|
3626
|
-
|
|
3767
|
+
if (!emitOutput) {
|
|
3768
|
+
for await (const _event of observedStream) {
|
|
3769
|
+
if (finished) break;
|
|
3770
|
+
}
|
|
3771
|
+
sawWorkflowDone = true;
|
|
3772
|
+
workflowDoneData = outcomeState;
|
|
3773
|
+
}
|
|
3774
|
+
for await (const chunk of emitOutput ? uiStream : []) {
|
|
3627
3775
|
if (finished) break;
|
|
3628
3776
|
if ("node" in chunk && typeof chunk.node === "string") {
|
|
3629
3777
|
touchedNodes.add(chunk.node);
|
|
@@ -3633,14 +3781,18 @@ async function orchestrateGraphStream({
|
|
|
3633
3781
|
workflowDoneData = chunk.data;
|
|
3634
3782
|
continue;
|
|
3635
3783
|
}
|
|
3636
|
-
|
|
3784
|
+
write(chunk);
|
|
3785
|
+
}
|
|
3786
|
+
if (finished) {
|
|
3787
|
+
runTraceSpan.fail?.(outcomeError);
|
|
3788
|
+
return;
|
|
3637
3789
|
}
|
|
3638
|
-
if (finished) return;
|
|
3639
3790
|
if (sawWorkflowDone) {
|
|
3640
3791
|
const graphSnapshot = await readLatestGraphSnapshot();
|
|
3641
3792
|
workflowFinalGraphCheckpointId = graphSnapshot?.checkpointId;
|
|
3642
3793
|
workflowFinalState = isGraphState(workflowDoneData) ? workflowDoneData : graphSnapshot?.state ?? currentState;
|
|
3643
3794
|
}
|
|
3795
|
+
(0, import_core.throwIfExecutionAborted)(abortSignal);
|
|
3644
3796
|
const transitionTo = pending.to;
|
|
3645
3797
|
const transitionPayload = pending.payload;
|
|
3646
3798
|
const sourceNodeId = pending.sourceNodeId;
|
|
@@ -3677,6 +3829,10 @@ async function orchestrateGraphStream({
|
|
|
3677
3829
|
} : {}
|
|
3678
3830
|
}
|
|
3679
3831
|
});
|
|
3832
|
+
delete config.resume;
|
|
3833
|
+
delete config.resumeValue;
|
|
3834
|
+
delete config.resumeUpdate;
|
|
3835
|
+
delete config.resumeCheckpointId;
|
|
3680
3836
|
const nextGraph = await (0, import_runtime.createExecutionGraph)(nextWorkflow, {
|
|
3681
3837
|
...config,
|
|
3682
3838
|
emit: forwardEmit
|
|
@@ -3687,12 +3843,13 @@ async function orchestrateGraphStream({
|
|
|
3687
3843
|
...transitionPayload
|
|
3688
3844
|
};
|
|
3689
3845
|
const rawInputFromPayload = transitionPayload?.rawInput;
|
|
3690
|
-
const
|
|
3846
|
+
const rawNextInput = typeof rawInputFromPayload === "string" ? rawInputFromPayload : currentState.input;
|
|
3691
3847
|
currentState = {
|
|
3692
3848
|
...currentState,
|
|
3849
|
+
runtime: workflowFinalState?.runtime ?? currentState.runtime,
|
|
3693
3850
|
currentWorkflow: transitionTo,
|
|
3694
3851
|
config,
|
|
3695
|
-
input:
|
|
3852
|
+
input: nextWorkflow.inputSchema ? parseExecutionInput(nextWorkflow, rawNextInput) : rawNextInput,
|
|
3696
3853
|
data: mergedData,
|
|
3697
3854
|
ui: {}
|
|
3698
3855
|
// reset UI layer on new graph
|
|
@@ -3700,14 +3857,18 @@ async function orchestrateGraphStream({
|
|
|
3700
3857
|
currentGraph = nextGraph;
|
|
3701
3858
|
continue;
|
|
3702
3859
|
} catch (err) {
|
|
3860
|
+
(0, import_core.throwIfExecutionAborted)(abortSignal);
|
|
3861
|
+
if ((0, import_core.isExecutionCancelled)(err)) throw err;
|
|
3862
|
+
outcomeError = err;
|
|
3863
|
+
runTraceSpan.fail?.(err);
|
|
3703
3864
|
runTraceSpan.addEvent?.("kortyx.transition.error", {
|
|
3704
3865
|
transitionTo
|
|
3705
3866
|
});
|
|
3706
|
-
|
|
3867
|
+
write({
|
|
3707
3868
|
type: "error",
|
|
3708
3869
|
message: `Transition failed to '${transitionTo}': ${err instanceof Error ? err.message : String(err)}`
|
|
3709
3870
|
});
|
|
3710
|
-
|
|
3871
|
+
write({ type: "done" });
|
|
3711
3872
|
out.end();
|
|
3712
3873
|
return;
|
|
3713
3874
|
}
|
|
@@ -3737,6 +3898,10 @@ async function orchestrateGraphStream({
|
|
|
3737
3898
|
}
|
|
3738
3899
|
const shouldKeepFrameworkState = Boolean(pendingRecordToken) || Boolean(workflowFinalState?.awaitingHumanInput);
|
|
3739
3900
|
if (!shouldKeepFrameworkState) {
|
|
3901
|
+
workflowFinalState = await validateExecutionOutput(
|
|
3902
|
+
workflowFinalState,
|
|
3903
|
+
selectWorkflow
|
|
3904
|
+
);
|
|
3740
3905
|
try {
|
|
3741
3906
|
if (frameworkAdapter?.cleanupRun) {
|
|
3742
3907
|
await frameworkAdapter.cleanupRun(
|
|
@@ -3753,6 +3918,12 @@ async function orchestrateGraphStream({
|
|
|
3753
3918
|
console.error("[orchestrator] framework cleanup failed", e);
|
|
3754
3919
|
}
|
|
3755
3920
|
}
|
|
3921
|
+
outcomeState = workflowFinalState;
|
|
3922
|
+
if (!emitOutput && !shouldKeepFrameworkState)
|
|
3923
|
+
accumulatedOutput = JSON.stringify(workflowFinalState.data).slice(
|
|
3924
|
+
0,
|
|
3925
|
+
65536
|
|
3926
|
+
);
|
|
3756
3927
|
finished = true;
|
|
3757
3928
|
runTraceSpan.setAttributes?.({
|
|
3758
3929
|
"kortyx.run.awaiting_human_input": shouldKeepFrameworkState,
|
|
@@ -3775,7 +3946,8 @@ async function orchestrateGraphStream({
|
|
|
3775
3946
|
pendingRequests: Array.from(activePendingRequests.values())
|
|
3776
3947
|
}
|
|
3777
3948
|
);
|
|
3778
|
-
|
|
3949
|
+
sessionCheckpointId = checkpoint.id;
|
|
3950
|
+
write({
|
|
3779
3951
|
type: "checkpoint",
|
|
3780
3952
|
id: checkpoint.id,
|
|
3781
3953
|
sessionId: checkpoint.sessionId,
|
|
@@ -3793,15 +3965,23 @@ async function orchestrateGraphStream({
|
|
|
3793
3965
|
}
|
|
3794
3966
|
});
|
|
3795
3967
|
} catch (error) {
|
|
3968
|
+
outcomeError = error;
|
|
3796
3969
|
console.error("[orchestrator] session checkpoint failed", error);
|
|
3797
3970
|
}
|
|
3798
3971
|
}
|
|
3799
|
-
|
|
3972
|
+
write({ type: "done", data: workflowFinalState });
|
|
3800
3973
|
out.end();
|
|
3801
3974
|
return;
|
|
3802
3975
|
}
|
|
3803
3976
|
const naturalGraphSnapshot = await readLatestGraphSnapshot();
|
|
3804
|
-
|
|
3977
|
+
let naturalFinalState = naturalGraphSnapshot?.state ?? currentState;
|
|
3978
|
+
if (!pendingRecordToken && !naturalFinalState.awaitingHumanInput) {
|
|
3979
|
+
naturalFinalState = await validateExecutionOutput(
|
|
3980
|
+
naturalFinalState,
|
|
3981
|
+
selectWorkflow
|
|
3982
|
+
);
|
|
3983
|
+
}
|
|
3984
|
+
outcomeState = naturalFinalState;
|
|
3805
3985
|
if (pendingRequestWrites.length > 0) {
|
|
3806
3986
|
await Promise.all(pendingRequestWrites);
|
|
3807
3987
|
}
|
|
@@ -3835,7 +4015,8 @@ async function orchestrateGraphStream({
|
|
|
3835
4015
|
structuredStreamIds: Array.from(structuredStreamIds),
|
|
3836
4016
|
pendingRequests: Array.from(activePendingRequests.values())
|
|
3837
4017
|
});
|
|
3838
|
-
|
|
4018
|
+
sessionCheckpointId = checkpoint.id;
|
|
4019
|
+
write({
|
|
3839
4020
|
type: "checkpoint",
|
|
3840
4021
|
id: checkpoint.id,
|
|
3841
4022
|
sessionId: checkpoint.sessionId,
|
|
@@ -3853,14 +4034,16 @@ async function orchestrateGraphStream({
|
|
|
3853
4034
|
}
|
|
3854
4035
|
});
|
|
3855
4036
|
} catch (error) {
|
|
4037
|
+
outcomeError = error;
|
|
3856
4038
|
console.error("[orchestrator] session checkpoint failed", error);
|
|
3857
4039
|
}
|
|
3858
4040
|
}
|
|
3859
4041
|
runTraceSpan.setAttributes?.({
|
|
3860
4042
|
"kortyx.run.final_workflow": String(naturalFinalState.currentWorkflow)
|
|
3861
4043
|
});
|
|
4044
|
+
finished = true;
|
|
3862
4045
|
runTraceSpan.end?.(runTraceEndArgs());
|
|
3863
|
-
|
|
4046
|
+
write({ type: "done", data: naturalFinalState });
|
|
3864
4047
|
out.end();
|
|
3865
4048
|
return;
|
|
3866
4049
|
}
|
|
@@ -3868,7 +4051,7 @@ async function orchestrateGraphStream({
|
|
|
3868
4051
|
const emitTraceChunk = () => {
|
|
3869
4052
|
const traceContext = traceAdapter?.getActiveContext?.();
|
|
3870
4053
|
if (!traceContext) return;
|
|
3871
|
-
|
|
4054
|
+
write({
|
|
3872
4055
|
type: "trace",
|
|
3873
4056
|
traceId: traceContext.traceId,
|
|
3874
4057
|
spanId: traceContext.spanId,
|
|
@@ -3884,16 +4067,165 @@ async function orchestrateGraphStream({
|
|
|
3884
4067
|
emitTraceChunk();
|
|
3885
4068
|
return runLoop(runTraceSpan);
|
|
3886
4069
|
}) : runLoop(fallbackRunSpan ?? {});
|
|
3887
|
-
|
|
4070
|
+
const finishCancelled = async () => {
|
|
4071
|
+
emitTelemetryEvent({
|
|
4072
|
+
config,
|
|
4073
|
+
type: "run.cancelled",
|
|
4074
|
+
correlation: { runId, workflowId: outcomeState.currentWorkflow },
|
|
4075
|
+
payload: { reason: "execution_aborted" },
|
|
4076
|
+
flush: true
|
|
4077
|
+
});
|
|
4078
|
+
await Promise.allSettled(pendingRequestWrites);
|
|
4079
|
+
await Promise.allSettled(
|
|
4080
|
+
[...activePendingRequests.keys()].map(
|
|
4081
|
+
(token) => pendingStore?.delete(token)
|
|
4082
|
+
)
|
|
4083
|
+
);
|
|
4084
|
+
activePendingRequests.clear();
|
|
4085
|
+
try {
|
|
4086
|
+
await frameworkAdapter?.cleanupRun?.(runId, [...namespacesUsed]);
|
|
4087
|
+
} catch (cleanupError) {
|
|
4088
|
+
console.error("[cancel:cleanupRun]", cleanupError);
|
|
4089
|
+
}
|
|
4090
|
+
outcomeError = (0, import_core.createExecutionCancelledError)();
|
|
4091
|
+
fallbackRunSpan?.setAttributes?.({ "kortyx.run.cancelled": true });
|
|
4092
|
+
fallbackRunSpan?.end?.();
|
|
4093
|
+
write({ type: "cancelled", runId, reason: "Execution cancelled." });
|
|
4094
|
+
write({ type: "done" });
|
|
4095
|
+
finished = true;
|
|
4096
|
+
out.end();
|
|
4097
|
+
};
|
|
4098
|
+
runPromise.catch(async (err) => {
|
|
4099
|
+
if (abortSignal?.aborted || (0, import_core.isExecutionCancelled)(err)) {
|
|
4100
|
+
await finishCancelled();
|
|
4101
|
+
return;
|
|
4102
|
+
}
|
|
4103
|
+
if ((0, import_core.isExecutionLimitReached)(err)) {
|
|
4104
|
+
try {
|
|
4105
|
+
const snapshot = frameworkAdapter && await (0, import_runtime.captureGraphSnapshot)(frameworkAdapter.checkpointer, runId);
|
|
4106
|
+
(0, import_core.throwIfExecutionAborted)(abortSignal);
|
|
4107
|
+
if (!snapshot)
|
|
4108
|
+
throw new Error(
|
|
4109
|
+
"Execution limit reached without a resumable checkpoint."
|
|
4110
|
+
);
|
|
4111
|
+
const savedState = snapshot.checkpoint.channel_values;
|
|
4112
|
+
const pausedState = {
|
|
4113
|
+
...savedState,
|
|
4114
|
+
config: {
|
|
4115
|
+
...savedState.config,
|
|
4116
|
+
...config,
|
|
4117
|
+
executionBudget: budget
|
|
4118
|
+
},
|
|
4119
|
+
awaitingHumanInput: true
|
|
4120
|
+
};
|
|
4121
|
+
const patch = err.__kortyxHookStatePatch;
|
|
4122
|
+
currentState = pausedState;
|
|
4123
|
+
outcomeState = pausedState;
|
|
4124
|
+
const limitReached = err.limitReached;
|
|
4125
|
+
write({ type: "limit-reached", runId, ...limitReached });
|
|
4126
|
+
emitTelemetryEvent({
|
|
4127
|
+
config,
|
|
4128
|
+
type: "run.limit_reached",
|
|
4129
|
+
payload: limitReached
|
|
4130
|
+
});
|
|
4131
|
+
await persistAndEmitInterrupt({
|
|
4132
|
+
node: String(
|
|
4133
|
+
err.nodeId ?? savedState.lastNode
|
|
4134
|
+
),
|
|
4135
|
+
workflow: String(savedState.currentWorkflow),
|
|
4136
|
+
input: {
|
|
4137
|
+
kind: "choice",
|
|
4138
|
+
question: "Limit reached \u2014 Continue?",
|
|
4139
|
+
options: [{ id: "continue", label: "Continue" }],
|
|
4140
|
+
meta: {
|
|
4141
|
+
__kortyxExecutionLimit: true,
|
|
4142
|
+
executionLimit: limitReached,
|
|
4143
|
+
...patch ? { __kortyxResumeStatePatch: patch } : {}
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4146
|
+
});
|
|
4147
|
+
await Promise.all(pendingRequestWrites);
|
|
4148
|
+
(0, import_core.throwIfExecutionAborted)(abortSignal);
|
|
4149
|
+
if (outcomeError) throw outcomeError;
|
|
4150
|
+
const request = activePendingRequests.get(pendingRecordToken);
|
|
4151
|
+
const sealed = {
|
|
4152
|
+
...request,
|
|
4153
|
+
state: pausedState,
|
|
4154
|
+
graphCheckpointId: snapshot.checkpoint.id,
|
|
4155
|
+
graphSnapshot: snapshot
|
|
4156
|
+
};
|
|
4157
|
+
activePendingRequests.set(request.token, sealed);
|
|
4158
|
+
await pendingStore?.update(request.token, sealed);
|
|
4159
|
+
const checkpoint = await frameworkAdapter.sessionCheckpoints.append({
|
|
4160
|
+
sessionId,
|
|
4161
|
+
runId,
|
|
4162
|
+
graphCheckpointId: snapshot.checkpoint.id,
|
|
4163
|
+
workflow: String(pausedState.currentWorkflow),
|
|
4164
|
+
state: pausedState,
|
|
4165
|
+
nodes: Array.from(touchedNodes),
|
|
4166
|
+
structuredStreamIds: Array.from(structuredStreamIds),
|
|
4167
|
+
pendingRequests: [sealed],
|
|
4168
|
+
label: "Execution limit reached"
|
|
4169
|
+
});
|
|
4170
|
+
(0, import_core.throwIfExecutionAborted)(abortSignal);
|
|
4171
|
+
sessionCheckpointId = checkpoint.id;
|
|
4172
|
+
write({
|
|
4173
|
+
type: "checkpoint",
|
|
4174
|
+
id: checkpoint.id,
|
|
4175
|
+
sessionId: checkpoint.sessionId,
|
|
4176
|
+
turnIndex: checkpoint.turnIndex,
|
|
4177
|
+
label: checkpoint.label
|
|
4178
|
+
});
|
|
4179
|
+
emitTelemetryEvent({
|
|
4180
|
+
config,
|
|
4181
|
+
type: "session.checkpointed",
|
|
4182
|
+
payload: {
|
|
4183
|
+
checkpointId: checkpoint.id,
|
|
4184
|
+
turnIndex: checkpoint.turnIndex,
|
|
4185
|
+
nodes: checkpoint.nodes
|
|
4186
|
+
}
|
|
4187
|
+
});
|
|
4188
|
+
fallbackRunSpan?.setAttributes?.({
|
|
4189
|
+
"kortyx.run.awaiting_human_input": true,
|
|
4190
|
+
"kortyx.run.limit_reached": limitReached.limit
|
|
4191
|
+
});
|
|
4192
|
+
fallbackRunSpan?.end?.();
|
|
4193
|
+
finished = true;
|
|
4194
|
+
write({ type: "done" });
|
|
4195
|
+
out.end();
|
|
4196
|
+
return;
|
|
4197
|
+
} catch (pauseError) {
|
|
4198
|
+
if (abortSignal?.aborted || (0, import_core.isExecutionCancelled)(pauseError)) {
|
|
4199
|
+
await finishCancelled();
|
|
4200
|
+
return;
|
|
4201
|
+
}
|
|
4202
|
+
await Promise.allSettled(
|
|
4203
|
+
[...activePendingRequests.keys()].map(
|
|
4204
|
+
(token) => pendingStore?.delete(token)
|
|
4205
|
+
)
|
|
4206
|
+
);
|
|
4207
|
+
activePendingRequests.clear();
|
|
4208
|
+
err = pauseError;
|
|
4209
|
+
}
|
|
4210
|
+
}
|
|
4211
|
+
outcomeError = err;
|
|
3888
4212
|
fallbackRunSpan?.fail?.(err);
|
|
3889
4213
|
emitResumeFailure(err);
|
|
3890
4214
|
console.error("[error:orchestrateGraphStream]", err);
|
|
3891
|
-
|
|
4215
|
+
write({
|
|
3892
4216
|
type: "error",
|
|
3893
4217
|
message: err instanceof Error ? err.message : String(err)
|
|
3894
4218
|
});
|
|
3895
|
-
|
|
4219
|
+
write({ type: "done" });
|
|
4220
|
+
finished = true;
|
|
3896
4221
|
out.end();
|
|
4222
|
+
}).finally(() => {
|
|
4223
|
+
onOutcome?.({
|
|
4224
|
+
state: outcomeState,
|
|
4225
|
+
pending: pendingRecordToken ? activePendingRequests.get(pendingRecordToken) : void 0,
|
|
4226
|
+
checkpointId: sessionCheckpointId,
|
|
4227
|
+
...outcomeError ? { error: outcomeError } : {}
|
|
4228
|
+
});
|
|
3897
4229
|
});
|
|
3898
4230
|
return out;
|
|
3899
4231
|
}
|
|
@@ -3924,7 +4256,13 @@ function parseResumeMeta(msg) {
|
|
|
3924
4256
|
return { token, requestId, selected, cancel };
|
|
3925
4257
|
}
|
|
3926
4258
|
async function tryPrepareResumeStream({
|
|
4259
|
+
limits,
|
|
4260
|
+
abortSignal,
|
|
3927
4261
|
lastMessage,
|
|
4262
|
+
meta: suppliedMeta,
|
|
4263
|
+
emitOutput,
|
|
4264
|
+
onOutcome,
|
|
4265
|
+
validatePending,
|
|
3928
4266
|
sessionId,
|
|
3929
4267
|
config,
|
|
3930
4268
|
selectWorkflow,
|
|
@@ -3933,7 +4271,7 @@ async function tryPrepareResumeStream({
|
|
|
3933
4271
|
applyResumeSelection,
|
|
3934
4272
|
frameworkAdapter
|
|
3935
4273
|
}) {
|
|
3936
|
-
const meta = parseResumeMeta(lastMessage);
|
|
4274
|
+
const meta = suppliedMeta ?? parseResumeMeta(lastMessage);
|
|
3937
4275
|
if (!meta) return null;
|
|
3938
4276
|
const store = frameworkAdapter?.pendingRequests;
|
|
3939
4277
|
if (!store) return null;
|
|
@@ -3945,6 +4283,24 @@ async function tryPrepareResumeStream({
|
|
|
3945
4283
|
}
|
|
3946
4284
|
if (pending.sessionId && pending.sessionId !== sessionId)
|
|
3947
4285
|
throw new Error("Interrupt belongs to another session.");
|
|
4286
|
+
await validatePending?.(pending);
|
|
4287
|
+
const isLimitPause = Boolean(pending.schema.meta?.__kortyxExecutionLimit);
|
|
4288
|
+
const savedBudget = pending.state?.config?.executionBudget;
|
|
4289
|
+
const resumedBudget = savedBudget && isLimitPause && !meta.cancel ? (0, import_core2.restartExecutionBudget)(savedBudget, limits) : savedBudget;
|
|
4290
|
+
if (abortSignal?.aborted) {
|
|
4291
|
+
onOutcome?.({
|
|
4292
|
+
state: pending.state,
|
|
4293
|
+
error: (0, import_core2.createExecutionCancelledError)()
|
|
4294
|
+
});
|
|
4295
|
+
return (async function* () {
|
|
4296
|
+
yield {
|
|
4297
|
+
type: "cancelled",
|
|
4298
|
+
runId: pending.runId,
|
|
4299
|
+
reason: "Execution cancelled."
|
|
4300
|
+
};
|
|
4301
|
+
yield { type: "done" };
|
|
4302
|
+
})();
|
|
4303
|
+
}
|
|
3948
4304
|
if (store.take && !await store.take(meta.token))
|
|
3949
4305
|
throw new Error("Interrupt has already been resumed or cancelled.");
|
|
3950
4306
|
if (meta.cancel) {
|
|
@@ -3964,125 +4320,412 @@ async function tryPrepareResumeStream({
|
|
|
3964
4320
|
},
|
|
3965
4321
|
flush: true
|
|
3966
4322
|
});
|
|
4323
|
+
onOutcome?.({ state: pending.state, cancelled: true });
|
|
3967
4324
|
return (async function* () {
|
|
3968
4325
|
yield { type: "done" };
|
|
3969
4326
|
})();
|
|
3970
4327
|
}
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
)
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
data: {
|
|
3993
|
-
...pendingData,
|
|
3994
|
-
...resumeDataPatch
|
|
3995
|
-
}
|
|
3996
|
-
};
|
|
3997
|
-
const wf = await selectWorkflow(resumedState.currentWorkflow);
|
|
3998
|
-
const telemetryConfig = prepareWorkflowTelemetry({
|
|
3999
|
-
config: {
|
|
4000
|
-
...config,
|
|
4001
|
-
...pending.state?.config?.executionBranchId ? { executionBranchId: pending.state.config.executionBranchId } : {}
|
|
4002
|
-
},
|
|
4003
|
-
workflow: wf,
|
|
4004
|
-
runId: pending.runId,
|
|
4005
|
-
sessionId,
|
|
4006
|
-
knownWorkflowIds
|
|
4007
|
-
});
|
|
4008
|
-
resumedState.config = telemetryConfig;
|
|
4009
|
-
const resumeUpdate = pending.graphSnapshot ? {
|
|
4010
|
-
config: {
|
|
4011
|
-
...telemetryConfig,
|
|
4012
|
-
context: pending.state?.config?.context ?? telemetryConfig.context
|
|
4013
|
-
}
|
|
4014
|
-
} : {};
|
|
4015
|
-
if (pending.graphSnapshot && frameworkAdapter)
|
|
4016
|
-
await (0, import_runtime2.restoreGraphSnapshot)(
|
|
4017
|
-
frameworkAdapter.checkpointer,
|
|
4018
|
-
pending.graphSnapshot,
|
|
4019
|
-
pending.runId
|
|
4020
|
-
);
|
|
4021
|
-
if (Object.keys(resumeDataPatch).length > 0) {
|
|
4022
|
-
resumeUpdate.data = {
|
|
4023
|
-
...pendingData,
|
|
4024
|
-
...resumeDataPatch
|
|
4328
|
+
try {
|
|
4329
|
+
const resumeData = isLimitPause ? {} : applyResumeSelection ? applyResumeSelection({ pending, selected: meta.selected }) : meta.selected?.length ? { coordinates: String(meta.selected[0]) } : {};
|
|
4330
|
+
const resumeDataPatch = isRecord4(resumeData) ? resumeData : {};
|
|
4331
|
+
const pendingMeta = isRecord4(pending.schema?.meta) ? pending.schema.meta : {};
|
|
4332
|
+
const resumeStatePatch = isRecord4(pendingMeta.__kortyxResumeStatePatch) ? pendingMeta.__kortyxResumeStatePatch : void 0;
|
|
4333
|
+
const pendingData = isRecord4(pending.state?.data) ? pending.state?.data : {};
|
|
4334
|
+
const workflowId = typeof pending.workflow === "string" && pending.workflow.trim() ? pending.workflow : typeof defaultWorkflowId === "string" && defaultWorkflowId.trim() ? defaultWorkflowId : "job-search";
|
|
4335
|
+
const resumedState = {
|
|
4336
|
+
// For static breakpoints, resume with null input (set in orchestrator),
|
|
4337
|
+
// and stash the user selection into data so the next node can read it.
|
|
4338
|
+
input: pending.state ? pending.state.input : "",
|
|
4339
|
+
lastNode: "__start__",
|
|
4340
|
+
currentWorkflow: workflowId,
|
|
4341
|
+
config,
|
|
4342
|
+
runtime: {},
|
|
4343
|
+
conversationHistory: [],
|
|
4344
|
+
awaitingHumanInput: false,
|
|
4345
|
+
data: {
|
|
4346
|
+
...pendingData,
|
|
4347
|
+
...resumeDataPatch
|
|
4348
|
+
}
|
|
4025
4349
|
};
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
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: {
|
|
4350
|
+
const wf = await selectWorkflow(resumedState.currentWorkflow);
|
|
4351
|
+
const telemetryConfig = prepareWorkflowTelemetry({
|
|
4352
|
+
config: {
|
|
4353
|
+
...config,
|
|
4354
|
+
...resumedBudget ? { executionBudget: resumedBudget } : {},
|
|
4355
|
+
...pending.state?.config?.context ? { context: pending.state.config.context } : {},
|
|
4356
|
+
...pending.state?.config?.executionContract ? { executionContract: pending.state.config.executionContract } : {},
|
|
4357
|
+
...pending.state?.config?.executionBranchId ? { executionBranchId: pending.state.config.executionBranchId } : {}
|
|
4358
|
+
},
|
|
4359
|
+
workflow: wf,
|
|
4050
4360
|
runId: pending.runId,
|
|
4051
4361
|
sessionId,
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4362
|
+
knownWorkflowIds
|
|
4363
|
+
});
|
|
4364
|
+
resumedState.config = telemetryConfig;
|
|
4365
|
+
const resumeUpdate = pending.graphSnapshot ? {
|
|
4366
|
+
config: {
|
|
4367
|
+
...telemetryConfig,
|
|
4368
|
+
context: pending.state?.config?.context ?? telemetryConfig.context
|
|
4369
|
+
}
|
|
4370
|
+
} : {};
|
|
4371
|
+
if (isLimitPause && pending.graphSnapshot) {
|
|
4372
|
+
pending.graphSnapshot.checkpoint.channel_values.config = telemetryConfig;
|
|
4373
|
+
if (resumeStatePatch)
|
|
4374
|
+
pending.graphSnapshot.checkpoint.channel_values.runtime = {
|
|
4375
|
+
...pending.graphSnapshot.checkpoint.channel_values.runtime,
|
|
4376
|
+
...resumeStatePatch
|
|
4377
|
+
};
|
|
4378
|
+
}
|
|
4379
|
+
if (pending.graphSnapshot && frameworkAdapter)
|
|
4380
|
+
await (0, import_runtime2.restoreGraphSnapshot)(
|
|
4381
|
+
frameworkAdapter.checkpointer,
|
|
4382
|
+
pending.graphSnapshot,
|
|
4383
|
+
pending.runId
|
|
4384
|
+
);
|
|
4385
|
+
if (Object.keys(resumeDataPatch).length > 0) {
|
|
4386
|
+
resumeUpdate.data = {
|
|
4387
|
+
...pendingData,
|
|
4388
|
+
...resumeDataPatch
|
|
4389
|
+
};
|
|
4390
|
+
}
|
|
4391
|
+
if (resumeStatePatch) {
|
|
4392
|
+
resumeUpdate.runtime = resumeStatePatch;
|
|
4393
|
+
}
|
|
4394
|
+
const hasResumeUpdate = !isLimitPause && Object.keys(resumeUpdate).length > 0;
|
|
4395
|
+
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;
|
|
4396
|
+
const resumeCheckpointId = typeof pending.graphCheckpointId === "string" && pending.graphCheckpointId.length > 0 ? pending.graphCheckpointId : void 0;
|
|
4397
|
+
const resumedGraph = await (0, import_runtime2.createExecutionGraph)(wf, {
|
|
4070
4398
|
...telemetryConfig,
|
|
4071
4399
|
resume: true,
|
|
4072
|
-
telemetryInterruptId: pending.requestId,
|
|
4073
|
-
telemetryInterruptNodeId: pending.node,
|
|
4074
4400
|
...resumeValue !== void 0 ? { resumeValue } : {},
|
|
4075
4401
|
...resumeCheckpointId ? { resumeCheckpointId } : {},
|
|
4076
4402
|
...hasResumeUpdate ? { resumeUpdate } : {}
|
|
4403
|
+
});
|
|
4404
|
+
await store.delete(pending.token);
|
|
4405
|
+
const response = responseFromSelection(meta.selected);
|
|
4406
|
+
const telemetry = isRecord4(telemetryConfig.telemetry) ? telemetryConfig.telemetry : {};
|
|
4407
|
+
const responseCaptured = Boolean(
|
|
4408
|
+
response && shouldCaptureTelemetryContent(telemetry.captureContent, "input")
|
|
4409
|
+
);
|
|
4410
|
+
emitTelemetryEvent({
|
|
4411
|
+
config: telemetryConfig,
|
|
4412
|
+
type: "interrupt.resolved",
|
|
4413
|
+
correlation: {
|
|
4414
|
+
runId: pending.runId,
|
|
4415
|
+
sessionId,
|
|
4416
|
+
workflowId,
|
|
4417
|
+
nodeId: pending.node
|
|
4418
|
+
},
|
|
4419
|
+
payload: {
|
|
4420
|
+
interruptId: pending.requestId,
|
|
4421
|
+
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4422
|
+
resumeOutcome: "resumed",
|
|
4423
|
+
responseCaptured,
|
|
4424
|
+
...responseCaptured && response ? { response } : {}
|
|
4425
|
+
},
|
|
4426
|
+
flush: true
|
|
4427
|
+
});
|
|
4428
|
+
const args = {
|
|
4429
|
+
abortSignal,
|
|
4430
|
+
emitOutput,
|
|
4431
|
+
onOutcome,
|
|
4432
|
+
sessionId,
|
|
4433
|
+
runId: pending.runId,
|
|
4434
|
+
graph: resumedGraph,
|
|
4435
|
+
state: resumedState,
|
|
4436
|
+
config: {
|
|
4437
|
+
...telemetryConfig,
|
|
4438
|
+
resume: true,
|
|
4439
|
+
telemetryInterruptId: pending.requestId,
|
|
4440
|
+
telemetryInterruptNodeId: pending.node,
|
|
4441
|
+
...resumeValue !== void 0 ? { resumeValue } : {},
|
|
4442
|
+
...resumeCheckpointId ? { resumeCheckpointId } : {},
|
|
4443
|
+
...hasResumeUpdate ? { resumeUpdate } : {}
|
|
4444
|
+
},
|
|
4445
|
+
selectWorkflow,
|
|
4446
|
+
knownWorkflowIds,
|
|
4447
|
+
frameworkAdapter
|
|
4448
|
+
};
|
|
4449
|
+
const stream = await orchestrateGraphStream(args);
|
|
4450
|
+
return stream;
|
|
4451
|
+
} catch (error) {
|
|
4452
|
+
await store.save(pending);
|
|
4453
|
+
throw error;
|
|
4454
|
+
}
|
|
4455
|
+
}
|
|
4456
|
+
|
|
4457
|
+
// src/execution/execute.ts
|
|
4458
|
+
var handleSchema = import_zod3.z.object({
|
|
4459
|
+
token: import_zod3.z.string().min(1),
|
|
4460
|
+
requestId: import_zod3.z.string().min(1),
|
|
4461
|
+
sessionId: import_zod3.z.string().min(1),
|
|
4462
|
+
runId: import_zod3.z.string().min(1)
|
|
4463
|
+
}).strict();
|
|
4464
|
+
var responseSchema = import_zod3.z.discriminatedUnion("type", [
|
|
4465
|
+
import_zod3.z.object({ type: import_zod3.z.literal("text"), text: import_zod3.z.string().min(1) }).strict(),
|
|
4466
|
+
import_zod3.z.object({
|
|
4467
|
+
type: import_zod3.z.literal("select"),
|
|
4468
|
+
ids: import_zod3.z.array(import_zod3.z.string().min(1)).min(1)
|
|
4469
|
+
}).strict(),
|
|
4470
|
+
import_zod3.z.object({ type: import_zod3.z.literal("cancel") }).strict()
|
|
4471
|
+
]);
|
|
4472
|
+
function resultFromOutcome(outcome, runId, sessionId) {
|
|
4473
|
+
const usage = outcome.pending?.schema.meta?.__kortyxResumeStatePatch?.tokenUsage ?? outcome.state.runtime?.tokenUsage;
|
|
4474
|
+
const info = {
|
|
4475
|
+
runId,
|
|
4476
|
+
sessionId,
|
|
4477
|
+
...outcome.checkpointId ? { checkpointId: outcome.checkpointId } : {},
|
|
4478
|
+
...usage ? { usage } : {}
|
|
4479
|
+
};
|
|
4480
|
+
if ((0, import_core3.isExecutionCancelled)(outcome.error))
|
|
4481
|
+
return { ...info, status: "cancelled", reason: "Execution cancelled." };
|
|
4482
|
+
if (outcome.error) {
|
|
4483
|
+
const error = outcome.error;
|
|
4484
|
+
return {
|
|
4485
|
+
...info,
|
|
4486
|
+
status: "failed",
|
|
4487
|
+
error: {
|
|
4488
|
+
code: typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "EXECUTION_FAILED",
|
|
4489
|
+
message: error instanceof Error ? error.message : String(error)
|
|
4490
|
+
}
|
|
4491
|
+
};
|
|
4492
|
+
}
|
|
4493
|
+
if (outcome.cancelled)
|
|
4494
|
+
return { ...info, status: "cancelled", reason: "cancelled_by_client" };
|
|
4495
|
+
if (outcome.pending) {
|
|
4496
|
+
const pending = outcome.pending;
|
|
4497
|
+
const meta = Object.fromEntries(
|
|
4498
|
+
Object.entries(pending.schema.meta ?? {}).filter(
|
|
4499
|
+
([key]) => !key.startsWith("__kortyx")
|
|
4500
|
+
)
|
|
4501
|
+
);
|
|
4502
|
+
return {
|
|
4503
|
+
...info,
|
|
4504
|
+
status: "suspended",
|
|
4505
|
+
...pending.schema.meta?.__kortyxExecutionLimit ? {
|
|
4506
|
+
reason: "limit_reached",
|
|
4507
|
+
limit: pending.schema.meta.executionLimit
|
|
4508
|
+
} : {},
|
|
4509
|
+
interrupt: {
|
|
4510
|
+
workflow: pending.workflow,
|
|
4511
|
+
node: pending.node,
|
|
4512
|
+
input: {
|
|
4513
|
+
...pending.schema,
|
|
4514
|
+
meta,
|
|
4515
|
+
options: pending.options.map(({ id, label, description }) => ({
|
|
4516
|
+
id,
|
|
4517
|
+
label,
|
|
4518
|
+
description
|
|
4519
|
+
}))
|
|
4520
|
+
}
|
|
4521
|
+
},
|
|
4522
|
+
resume: {
|
|
4523
|
+
token: pending.token,
|
|
4524
|
+
requestId: pending.requestId,
|
|
4525
|
+
sessionId,
|
|
4526
|
+
runId
|
|
4527
|
+
}
|
|
4528
|
+
};
|
|
4529
|
+
}
|
|
4530
|
+
return { ...info, status: "completed", data: outcome.state.data ?? {} };
|
|
4531
|
+
}
|
|
4532
|
+
function runtimeConfig(services, sessionId, context) {
|
|
4533
|
+
return {
|
|
4534
|
+
session: { id: sessionId },
|
|
4535
|
+
...context ? { context } : {},
|
|
4536
|
+
getProvider: services.getProvider,
|
|
4537
|
+
checkpointer: services.frameworkAdapter.checkpointer,
|
|
4538
|
+
selectWorkflow: (id) => services.registry.select(id),
|
|
4539
|
+
...services.telemetry ? { telemetry: services.telemetry } : {}
|
|
4540
|
+
};
|
|
4541
|
+
}
|
|
4542
|
+
async function executeWorkflow(services, args) {
|
|
4543
|
+
const select = (id) => services.registry.select(id);
|
|
4544
|
+
const workflow = await resolveExecutionWorkflow(select, args.workflow);
|
|
4545
|
+
const input = parseExecutionInput(workflow, args.input);
|
|
4546
|
+
if (args.sessionId !== void 0 && !args.sessionId.trim())
|
|
4547
|
+
throw new ExecutionRequestError(
|
|
4548
|
+
"INVALID_SESSION",
|
|
4549
|
+
"sessionId must not be empty."
|
|
4550
|
+
);
|
|
4551
|
+
const sessionId = args.sessionId ?? (0, import_runtime3.makeRequestId)("session");
|
|
4552
|
+
const runId = (0, import_runtime3.makeRequestId)("run");
|
|
4553
|
+
const config = prepareWorkflowTelemetry({
|
|
4554
|
+
config: {
|
|
4555
|
+
...runtimeConfig(services, sessionId, args.context),
|
|
4556
|
+
...services.limits || args.limits ? {
|
|
4557
|
+
executionBudget: (0, import_core3.createExecutionBudget)(
|
|
4558
|
+
services.limits,
|
|
4559
|
+
args.limits
|
|
4560
|
+
)
|
|
4561
|
+
} : {},
|
|
4562
|
+
executionContract: { id: workflow.id, version: workflow.version }
|
|
4077
4563
|
},
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4564
|
+
workflow,
|
|
4565
|
+
runId,
|
|
4566
|
+
sessionId,
|
|
4567
|
+
knownWorkflowIds: services.knownWorkflowIds
|
|
4568
|
+
});
|
|
4569
|
+
const state = await (0, import_runtime3.buildInitialGraphState)({
|
|
4570
|
+
input,
|
|
4571
|
+
runtime: {},
|
|
4572
|
+
config,
|
|
4573
|
+
defaultWorkflowId: workflow.id
|
|
4574
|
+
});
|
|
4575
|
+
return new Promise((resolve2) => {
|
|
4576
|
+
const onOutcome = (outcome) => resolve2(resultFromOutcome(outcome, runId, sessionId));
|
|
4577
|
+
void (async () => {
|
|
4578
|
+
(0, import_core3.throwIfExecutionAborted)(args.abortSignal);
|
|
4579
|
+
const graph = await (0, import_runtime3.createExecutionGraph)(workflow, config);
|
|
4580
|
+
await orchestrateGraphStream({
|
|
4581
|
+
graph,
|
|
4582
|
+
state,
|
|
4583
|
+
config,
|
|
4584
|
+
runId,
|
|
4585
|
+
sessionId,
|
|
4586
|
+
selectWorkflow: select,
|
|
4587
|
+
frameworkAdapter: services.frameworkAdapter,
|
|
4588
|
+
knownWorkflowIds: services.knownWorkflowIds,
|
|
4589
|
+
abortSignal: args.abortSignal,
|
|
4590
|
+
emitOutput: false,
|
|
4591
|
+
onOutcome
|
|
4592
|
+
});
|
|
4593
|
+
})().catch((error) => onOutcome({ state, error }));
|
|
4594
|
+
});
|
|
4595
|
+
}
|
|
4596
|
+
async function resumeWorkflow(services, args) {
|
|
4597
|
+
const parsed = import_zod3.z.object({ resume: handleSchema, response: responseSchema }).safeParse(args);
|
|
4598
|
+
if (!parsed.success)
|
|
4599
|
+
throw new ExecutionRequestError("INVALID_RESUME", parsed.error.message);
|
|
4600
|
+
const { resume: handle, response } = parsed.data;
|
|
4601
|
+
const select = (id) => services.registry.select(id);
|
|
4602
|
+
const workflow = await resolveExecutionWorkflow(select, args.workflow);
|
|
4603
|
+
const validatePending = async (pending) => {
|
|
4604
|
+
const contract = pending.state?.config?.executionContract ?? {
|
|
4605
|
+
id: pending.workflow
|
|
4606
|
+
};
|
|
4607
|
+
if (pending.runId !== handle.runId || contract.id !== workflow.id || contract.version && contract.version !== workflow.version)
|
|
4608
|
+
throw new ExecutionRequestError(
|
|
4609
|
+
"RESUME_MISMATCH",
|
|
4610
|
+
"Resume handle does not match the root execution and workflow version."
|
|
4611
|
+
);
|
|
4612
|
+
if (response.type === "cancel") return;
|
|
4613
|
+
if (response.type === "text") {
|
|
4614
|
+
if (pending.schema.kind !== "text")
|
|
4615
|
+
throw new ExecutionRequestError(
|
|
4616
|
+
"INVALID_RESPONSE",
|
|
4617
|
+
"This interrupt requires a selection."
|
|
4618
|
+
);
|
|
4619
|
+
} else {
|
|
4620
|
+
const multi = pending.schema.kind === "multi-choice" || pending.schema.multiple;
|
|
4621
|
+
if (pending.schema.kind === "text" || !multi && response.ids.length !== 1 || new Set(response.ids).size !== response.ids.length)
|
|
4622
|
+
throw new ExecutionRequestError(
|
|
4623
|
+
"INVALID_RESPONSE",
|
|
4624
|
+
"Invalid selection for this interrupt."
|
|
4625
|
+
);
|
|
4626
|
+
if (pending.options.length && response.ids.some(
|
|
4627
|
+
(id) => !pending.options.some((option) => option.id === id)
|
|
4628
|
+
))
|
|
4629
|
+
throw new ExecutionRequestError(
|
|
4630
|
+
"INVALID_RESPONSE",
|
|
4631
|
+
"Selection contains an unknown option."
|
|
4632
|
+
);
|
|
4633
|
+
}
|
|
4081
4634
|
};
|
|
4082
|
-
|
|
4083
|
-
|
|
4635
|
+
return new Promise((resolve2, reject) => {
|
|
4636
|
+
void tryPrepareResumeStream({
|
|
4637
|
+
limits: args.limits,
|
|
4638
|
+
abortSignal: args.abortSignal,
|
|
4639
|
+
meta: {
|
|
4640
|
+
token: handle.token,
|
|
4641
|
+
requestId: handle.requestId,
|
|
4642
|
+
selected: response.type === "text" ? [response.text] : response.type === "select" ? response.ids : [],
|
|
4643
|
+
cancel: response.type === "cancel"
|
|
4644
|
+
},
|
|
4645
|
+
sessionId: handle.sessionId,
|
|
4646
|
+
config: runtimeConfig(services, handle.sessionId),
|
|
4647
|
+
selectWorkflow: select,
|
|
4648
|
+
frameworkAdapter: services.frameworkAdapter,
|
|
4649
|
+
knownWorkflowIds: services.knownWorkflowIds,
|
|
4650
|
+
validatePending,
|
|
4651
|
+
applyResumeSelection: () => ({}),
|
|
4652
|
+
emitOutput: false,
|
|
4653
|
+
onOutcome: (outcome) => resolve2(resultFromOutcome(outcome, handle.runId, handle.sessionId))
|
|
4654
|
+
}).then((stream) => {
|
|
4655
|
+
if (!stream)
|
|
4656
|
+
reject(
|
|
4657
|
+
new ExecutionRequestError(
|
|
4658
|
+
"INVALID_RESUME",
|
|
4659
|
+
"No waiting execution found."
|
|
4660
|
+
)
|
|
4661
|
+
);
|
|
4662
|
+
}).catch(
|
|
4663
|
+
(error) => reject(
|
|
4664
|
+
error instanceof ExecutionRequestError ? error : new ExecutionRequestError(
|
|
4665
|
+
"INVALID_RESUME",
|
|
4666
|
+
error instanceof Error ? error.message : String(error)
|
|
4667
|
+
)
|
|
4668
|
+
)
|
|
4669
|
+
);
|
|
4670
|
+
});
|
|
4084
4671
|
}
|
|
4085
4672
|
|
|
4673
|
+
// src/telemetry/workflow-call-branch.ts
|
|
4674
|
+
var import_node_crypto3 = require("crypto");
|
|
4675
|
+
var import_hooks = __toESM(require_dist());
|
|
4676
|
+
function restoreWorkflowCallBranch(requests, telemetry, sourceRunId) {
|
|
4677
|
+
const branchId = (0, import_node_crypto3.randomUUID)();
|
|
4678
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
4679
|
+
for (const request of requests) {
|
|
4680
|
+
if (request.state)
|
|
4681
|
+
request.state.config = {
|
|
4682
|
+
...request.state.config,
|
|
4683
|
+
executionBranchId: branchId
|
|
4684
|
+
};
|
|
4685
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4686
|
+
const visit = (value) => {
|
|
4687
|
+
if (!value || typeof value !== "object" || seen.has(value)) return;
|
|
4688
|
+
seen.add(value);
|
|
4689
|
+
const record = value;
|
|
4690
|
+
if (typeof record.invocationId === "string" && typeof record.fingerprint === "string" && Array.isArray(record.interrupts)) {
|
|
4691
|
+
const previous = record.telemetry;
|
|
4692
|
+
if (previous) {
|
|
4693
|
+
record.sequence = 0;
|
|
4694
|
+
record.telemetry = { ...previous, branchId };
|
|
4695
|
+
if (!emitted.has(record.invocationId)) {
|
|
4696
|
+
emitted.add(record.invocationId);
|
|
4697
|
+
(0, import_hooks.emitWorkflowCall)(
|
|
4698
|
+
telemetry ? {
|
|
4699
|
+
...telemetry,
|
|
4700
|
+
correlation: {
|
|
4701
|
+
runId: request.runId,
|
|
4702
|
+
workflowId: request.workflow,
|
|
4703
|
+
...request.sessionId ? { sessionId: request.sessionId } : {}
|
|
4704
|
+
}
|
|
4705
|
+
} : void 0,
|
|
4706
|
+
"workflow.call.restored",
|
|
4707
|
+
{
|
|
4708
|
+
...previous,
|
|
4709
|
+
branchId,
|
|
4710
|
+
sourceBranchId: previous.branchId,
|
|
4711
|
+
sourceRunId,
|
|
4712
|
+
sequence: 0,
|
|
4713
|
+
status: record.status
|
|
4714
|
+
}
|
|
4715
|
+
);
|
|
4716
|
+
}
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
for (const child of Object.values(record)) visit(child);
|
|
4720
|
+
};
|
|
4721
|
+
visit(request);
|
|
4722
|
+
}
|
|
4723
|
+
return branchId;
|
|
4724
|
+
}
|
|
4725
|
+
|
|
4726
|
+
// src/chat/process-chat.ts
|
|
4727
|
+
var import_runtime4 = require("@kortyx/runtime");
|
|
4728
|
+
|
|
4086
4729
|
// src/utils/extract-latest-message.ts
|
|
4087
4730
|
function extractLatestUserMessage(messages) {
|
|
4088
4731
|
if (!messages || messages.length === 0) return "";
|
|
@@ -4098,6 +4741,7 @@ function extractLatestUserMessage(messages) {
|
|
|
4098
4741
|
// src/chat/process-chat.ts
|
|
4099
4742
|
async function streamChat({
|
|
4100
4743
|
messages,
|
|
4744
|
+
abortSignal,
|
|
4101
4745
|
options,
|
|
4102
4746
|
sessionId,
|
|
4103
4747
|
defaultWorkflowId,
|
|
@@ -4110,7 +4754,7 @@ async function streamChat({
|
|
|
4110
4754
|
applyResumeSelection
|
|
4111
4755
|
}) {
|
|
4112
4756
|
const config = await loadRuntimeConfig(options);
|
|
4113
|
-
let
|
|
4757
|
+
let runtimeConfig2 = {
|
|
4114
4758
|
...config,
|
|
4115
4759
|
getProvider,
|
|
4116
4760
|
...frameworkAdapter ? { checkpointer: frameworkAdapter.checkpointer } : {}
|
|
@@ -4121,7 +4765,7 @@ async function streamChat({
|
|
|
4121
4765
|
"streamChat requires selectWorkflow or workflowRegistry to resolve workflows."
|
|
4122
4766
|
);
|
|
4123
4767
|
}
|
|
4124
|
-
|
|
4768
|
+
runtimeConfig2.selectWorkflow = workflowSelector;
|
|
4125
4769
|
const fallbackSessionId = options?.sessionId;
|
|
4126
4770
|
const resolvedSessionId = sessionId || fallbackSessionId || "anonymous-session";
|
|
4127
4771
|
const last = messages[messages.length - 1];
|
|
@@ -4143,16 +4787,11 @@ async function streamChat({
|
|
|
4143
4787
|
if (requestedWorkflowId.trim() === "") delete runtime.requestedWorkflow;
|
|
4144
4788
|
else runtime.requestedWorkflow = requestedWorkflowId;
|
|
4145
4789
|
}
|
|
4146
|
-
const baseState = await (0, import_runtime3.buildInitialGraphState)({
|
|
4147
|
-
input,
|
|
4148
|
-
config: runtimeConfig,
|
|
4149
|
-
runtime,
|
|
4150
|
-
...defaultWorkflowId ? { defaultWorkflowId } : {}
|
|
4151
|
-
});
|
|
4152
4790
|
const resumeStream = await tryPrepareResumeStream({
|
|
4791
|
+
abortSignal,
|
|
4153
4792
|
lastMessage: last,
|
|
4154
4793
|
sessionId: resolvedSessionId,
|
|
4155
|
-
config:
|
|
4794
|
+
config: runtimeConfig2,
|
|
4156
4795
|
selectWorkflow: workflowSelector,
|
|
4157
4796
|
knownWorkflowIds,
|
|
4158
4797
|
...frameworkAdapter ? { frameworkAdapter } : {},
|
|
@@ -4160,7 +4799,13 @@ async function streamChat({
|
|
|
4160
4799
|
...applyResumeSelection ? { applyResumeSelection } : {}
|
|
4161
4800
|
});
|
|
4162
4801
|
if (resumeStream) return resumeStream;
|
|
4163
|
-
const
|
|
4802
|
+
const baseState = await (0, import_runtime4.buildInitialGraphState)({
|
|
4803
|
+
input,
|
|
4804
|
+
config: runtimeConfig2,
|
|
4805
|
+
runtime,
|
|
4806
|
+
...defaultWorkflowId ? { defaultWorkflowId } : {}
|
|
4807
|
+
});
|
|
4808
|
+
const runId = (0, import_runtime4.makeRequestId)("run");
|
|
4164
4809
|
let headCheckpoint = !isResumeRequest && frameworkAdapter?.sessionCheckpoints ? await frameworkAdapter.sessionCheckpoints.getHead(resolvedSessionId) : null;
|
|
4165
4810
|
if (!isResumeRequest && !headCheckpoint && frameworkAdapter?.sessionCheckpoints) {
|
|
4166
4811
|
headCheckpoint = await frameworkAdapter.sessionCheckpoints.append({
|
|
@@ -4180,18 +4825,26 @@ async function streamChat({
|
|
|
4180
4825
|
}
|
|
4181
4826
|
const currentWorkflow = requestedWorkflowId ? baseState.currentWorkflow : headCheckpoint?.state.currentWorkflow ?? baseState.currentWorkflow;
|
|
4182
4827
|
const selectedWorkflow = await workflowSelector(currentWorkflow);
|
|
4183
|
-
|
|
4184
|
-
config:
|
|
4828
|
+
runtimeConfig2 = prepareWorkflowTelemetry({
|
|
4829
|
+
config: runtimeConfig2,
|
|
4185
4830
|
workflow: selectedWorkflow,
|
|
4186
4831
|
runId,
|
|
4187
4832
|
sessionId: resolvedSessionId,
|
|
4188
4833
|
knownWorkflowIds
|
|
4189
4834
|
});
|
|
4190
|
-
|
|
4835
|
+
runtimeConfig2 = {
|
|
4836
|
+
...runtimeConfig2,
|
|
4837
|
+
executionContract: {
|
|
4838
|
+
id: selectedWorkflow.id,
|
|
4839
|
+
version: selectedWorkflow.version
|
|
4840
|
+
}
|
|
4841
|
+
};
|
|
4842
|
+
const parsedInput = selectedWorkflow.inputSchema ? parseExecutionInput(selectedWorkflow, input) : input;
|
|
4843
|
+
const graph = await (0, import_runtime4.createExecutionGraph)(selectedWorkflow, runtimeConfig2);
|
|
4191
4844
|
const initialState = headCheckpoint ? {
|
|
4192
4845
|
...headCheckpoint.state,
|
|
4193
|
-
input,
|
|
4194
|
-
config:
|
|
4846
|
+
input: parsedInput,
|
|
4847
|
+
config: runtimeConfig2,
|
|
4195
4848
|
currentWorkflow,
|
|
4196
4849
|
awaitingHumanInput: false,
|
|
4197
4850
|
runtime: {
|
|
@@ -4199,13 +4852,19 @@ async function streamChat({
|
|
|
4199
4852
|
...runtime.requestedWorkflow ? { requestedWorkflow: runtime.requestedWorkflow } : {},
|
|
4200
4853
|
...runtime.priorMessages ? { priorMessages: runtime.priorMessages } : {}
|
|
4201
4854
|
}
|
|
4202
|
-
} : {
|
|
4855
|
+
} : {
|
|
4856
|
+
...baseState,
|
|
4857
|
+
input: parsedInput,
|
|
4858
|
+
config: runtimeConfig2,
|
|
4859
|
+
currentWorkflow
|
|
4860
|
+
};
|
|
4203
4861
|
const orchestratedStream = await orchestrateGraphStream({
|
|
4862
|
+
abortSignal,
|
|
4204
4863
|
sessionId: resolvedSessionId,
|
|
4205
4864
|
runId,
|
|
4206
4865
|
graph,
|
|
4207
4866
|
state: initialState,
|
|
4208
|
-
config:
|
|
4867
|
+
config: runtimeConfig2,
|
|
4209
4868
|
selectWorkflow: workflowSelector,
|
|
4210
4869
|
knownWorkflowIds,
|
|
4211
4870
|
...frameworkAdapter ? { frameworkAdapter } : {}
|
|
@@ -4214,19 +4873,22 @@ async function streamChat({
|
|
|
4214
4873
|
}
|
|
4215
4874
|
|
|
4216
4875
|
// src/chat/create-agent.ts
|
|
4217
|
-
var agentProcessOptionsSchema =
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4876
|
+
var agentProcessOptionsSchema = import_zod4.z.object({
|
|
4877
|
+
limits: import_core4.ExecutionLimitsSchema.optional(),
|
|
4878
|
+
abortSignal: import_zod4.z.custom((value) => value instanceof AbortSignal).optional(),
|
|
4879
|
+
sessionId: import_zod4.z.string().optional(),
|
|
4880
|
+
workflowId: import_zod4.z.string().optional(),
|
|
4881
|
+
context: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional()
|
|
4221
4882
|
}).strict();
|
|
4222
|
-
var createAgentArgsBaseSchema =
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4883
|
+
var createAgentArgsBaseSchema = import_zod4.z.object({
|
|
4884
|
+
limits: import_core4.ExecutionLimitsSchema.optional(),
|
|
4885
|
+
getProvider: import_zod4.z.unknown().optional(),
|
|
4886
|
+
workflows: import_zod4.z.array(import_zod4.z.unknown()).optional(),
|
|
4887
|
+
workflowsDir: import_zod4.z.string().optional(),
|
|
4888
|
+
workflowRegistry: import_zod4.z.unknown().optional(),
|
|
4889
|
+
defaultWorkflowId: import_zod4.z.string().optional(),
|
|
4890
|
+
frameworkAdapter: import_zod4.z.unknown().optional(),
|
|
4891
|
+
telemetry: import_zod4.z.unknown().optional()
|
|
4230
4892
|
}).strict();
|
|
4231
4893
|
var createAgentArgsSchema = createAgentArgsBaseSchema.superRefine(
|
|
4232
4894
|
(value, ctx) => {
|
|
@@ -4290,6 +4952,7 @@ var clearPendingGraphWrites = async (frameworkAdapter, request) => {
|
|
|
4290
4952
|
function createAgent(args) {
|
|
4291
4953
|
const parsedArgs = parseCreateAgentArgs(args);
|
|
4292
4954
|
const {
|
|
4955
|
+
limits,
|
|
4293
4956
|
getProvider,
|
|
4294
4957
|
workflows,
|
|
4295
4958
|
workflowsDir,
|
|
@@ -4299,24 +4962,24 @@ function createAgent(args) {
|
|
|
4299
4962
|
telemetry
|
|
4300
4963
|
} = parsedArgs;
|
|
4301
4964
|
const resolvedDefaultWorkflowId = defaultWorkflowId;
|
|
4302
|
-
const resolvedFrameworkAdapter = frameworkAdapter ?? (0,
|
|
4965
|
+
const resolvedFrameworkAdapter = frameworkAdapter ?? (0, import_runtime5.createFrameworkAdapterFromEnv)();
|
|
4303
4966
|
const resolvedGetProvider = getProvider ?? import_providers.getProvider;
|
|
4304
4967
|
const resolvedCwd = process.cwd();
|
|
4305
4968
|
const registryPromise = (async () => {
|
|
4306
4969
|
if (workflowRegistry) return workflowRegistry;
|
|
4307
4970
|
if (workflows) {
|
|
4308
|
-
return (0,
|
|
4971
|
+
return (0, import_runtime5.createInMemoryWorkflowRegistry)(workflows, {
|
|
4309
4972
|
fallbackId: resolvedDefaultWorkflowId ?? "general-chat"
|
|
4310
4973
|
});
|
|
4311
4974
|
}
|
|
4312
4975
|
if (workflowsDir) {
|
|
4313
|
-
return (0,
|
|
4976
|
+
return (0, import_runtime5.createFileWorkflowRegistry)({
|
|
4314
4977
|
workflowsDir,
|
|
4315
4978
|
fallbackId: resolvedDefaultWorkflowId ?? "general-chat"
|
|
4316
4979
|
});
|
|
4317
4980
|
}
|
|
4318
4981
|
const resolvedWorkflowsDir = (0, import_node_path.resolve)(resolvedCwd, "src", "workflows");
|
|
4319
|
-
return (0,
|
|
4982
|
+
return (0, import_runtime5.createFileWorkflowRegistry)({
|
|
4320
4983
|
workflowsDir: resolvedWorkflowsDir,
|
|
4321
4984
|
fallbackId: resolvedDefaultWorkflowId ?? "general-chat"
|
|
4322
4985
|
});
|
|
@@ -4354,12 +5017,19 @@ function createAgent(args) {
|
|
|
4354
5017
|
return streamChat({
|
|
4355
5018
|
...resolvedDefaultWorkflowId ? { defaultWorkflowId: resolvedDefaultWorkflowId } : {},
|
|
4356
5019
|
messages,
|
|
5020
|
+
abortSignal: parsedOptions?.abortSignal,
|
|
4357
5021
|
options: parsedOptions,
|
|
4358
5022
|
workflowRegistry: registry,
|
|
4359
5023
|
...knownWorkflowIds ? { knownWorkflowIds } : {},
|
|
4360
5024
|
frameworkAdapter: resolvedFrameworkAdapter,
|
|
4361
5025
|
getProvider: resolvedGetProvider,
|
|
4362
5026
|
loadRuntimeConfig: (runtimeOptions) => ({
|
|
5027
|
+
...limits || runtimeOptions?.limits ? {
|
|
5028
|
+
executionBudget: (0, import_core4.createExecutionBudget)(
|
|
5029
|
+
limits,
|
|
5030
|
+
runtimeOptions?.limits
|
|
5031
|
+
)
|
|
5032
|
+
} : {},
|
|
4363
5033
|
...runtimeOptions?.sessionId ? {
|
|
4364
5034
|
session: {
|
|
4365
5035
|
id: runtimeOptions.sessionId
|
|
@@ -4460,7 +5130,25 @@ function createAgent(args) {
|
|
|
4460
5130
|
checkpoint: { ...result.checkpoint, activePendingRequests }
|
|
4461
5131
|
};
|
|
4462
5132
|
};
|
|
5133
|
+
const services = async () => {
|
|
5134
|
+
const registry = await registryPromise;
|
|
5135
|
+
if (!registry) throw new Error("Workflow registry is unavailable.");
|
|
5136
|
+
return {
|
|
5137
|
+
registry,
|
|
5138
|
+
limits,
|
|
5139
|
+
frameworkAdapter: resolvedFrameworkAdapter,
|
|
5140
|
+
getProvider: resolvedGetProvider,
|
|
5141
|
+
telemetry,
|
|
5142
|
+
knownWorkflowIds: (await registeredWorkflowsPromise)?.map(
|
|
5143
|
+
(workflow) => workflow.id
|
|
5144
|
+
)
|
|
5145
|
+
};
|
|
5146
|
+
};
|
|
5147
|
+
const execute = (async (options) => executeWorkflow(await services(), options));
|
|
5148
|
+
const resume = (async (options) => resumeWorkflow(await services(), options));
|
|
4463
5149
|
return {
|
|
5150
|
+
execute,
|
|
5151
|
+
resume,
|
|
4464
5152
|
projectTopology,
|
|
4465
5153
|
streamChat: streamChat2,
|
|
4466
5154
|
listCheckpoints,
|
|
@@ -4471,6 +5159,7 @@ function createAgent(args) {
|
|
|
4471
5159
|
}
|
|
4472
5160
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4473
5161
|
0 && (module.exports = {
|
|
5162
|
+
ExecutionRequestError,
|
|
4474
5163
|
createAgent,
|
|
4475
5164
|
createChatRouteHandler,
|
|
4476
5165
|
createCheckpointRouteHandler,
|