@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/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
- failTrace(traceSpan, error);
617
- throw error;
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: args.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 import_zod3 = require("zod");
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, import_zod3.toJSONSchema)(schema);
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 matches = [];
1250
+ const matches2 = [];
1209
1251
  const visitValue = (start, currentParts) => {
1210
1252
  const valueStart = skipWhitespace(args.text, start);
1211
1253
  if (pathMatchesPattern(currentParts, targetParts)) {
1212
- matches.push({
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
- matches.push({
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
- matches.push({
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 matches;
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
- ...useReasonArgs.abortSignal ? { abortSignal: useReasonArgs.abortSignal } : {}
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({ agent, body });
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 import_runtime4 = require("@kortyx/runtime");
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
- let structuredSeq = 0;
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
- ...typeof state.input === "string" ? { input: state.input } : {}
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
- out.write({ type: "session", sessionId: sid });
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, { graphSnapshot });
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
- out.write({
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
- out.write({ type: "error", message: msg });
3384
- out.write({ type: "done" });
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
- out.write({ type: "status", message: msg });
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
- out.write({
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
- out.write({
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
- out.write({
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
- out.write({
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
- out.write({
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
- out.write({
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
- out.write({ type: "message", node, content: text });
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
- out.write({
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
- out.write({
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
- out.write({
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
- out.write({
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 uiStream = transformGraphStreamForUI(runtimeStream, {
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
- for await (const chunk of uiStream) {
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
- out.write(chunk);
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 newInput = typeof rawInputFromPayload === "string" ? rawInputFromPayload : currentState.input;
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: newInput,
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
- out.write({
4011
+ write({
3707
4012
  type: "error",
3708
4013
  message: `Transition failed to '${transitionTo}': ${err instanceof Error ? err.message : String(err)}`
3709
4014
  });
3710
- out.write({ type: "done" });
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
- out.write({
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
- out.write({ type: "done", data: workflowFinalState });
4116
+ write({ type: "done", data: workflowFinalState });
3800
4117
  out.end();
3801
4118
  return;
3802
4119
  }
3803
4120
  const naturalGraphSnapshot = await readLatestGraphSnapshot();
3804
- const naturalFinalState = naturalGraphSnapshot?.state ?? currentState;
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
- out.write({
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
- out.write({ type: "done", data: naturalFinalState });
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
- out.write({
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
- runPromise.catch((err) => {
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
- out.write({
4363
+ write({
3892
4364
  type: "error",
3893
4365
  message: err instanceof Error ? err.message : String(err)
3894
4366
  });
3895
- out.write({ type: "done" });
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
- console.log(
3972
- `[resume] token=${meta.token} requestId=${meta.requestId} selected=${JSON.stringify(
3973
- meta.selected
3974
- )} sessionId=${sessionId}`
3975
- );
3976
- const resumeData = applyResumeSelection ? applyResumeSelection({ pending, selected: meta.selected }) : meta.selected?.length ? { coordinates: String(meta.selected[0]) } : {};
3977
- const resumeDataPatch = isRecord4(resumeData) ? resumeData : {};
3978
- const pendingMeta = isRecord4(pending.schema?.meta) ? pending.schema.meta : {};
3979
- const resumeStatePatch = isRecord4(pendingMeta.__kortyxResumeStatePatch) ? pendingMeta.__kortyxResumeStatePatch : void 0;
3980
- const pendingData = isRecord4(pending.state?.data) ? pending.state?.data : {};
3981
- const workflowId = typeof pending.workflow === "string" && pending.workflow.trim() ? pending.workflow : typeof defaultWorkflowId === "string" && defaultWorkflowId.trim() ? defaultWorkflowId : "job-search";
3982
- const resumedState = {
3983
- // For static breakpoints, resume with null input (set in orchestrator),
3984
- // and stash the user selection into data so the next node can read it.
3985
- input: "",
3986
- lastNode: "__start__",
3987
- currentWorkflow: workflowId,
3988
- config,
3989
- runtime: {},
3990
- conversationHistory: [],
3991
- awaitingHumanInput: false,
3992
- data: {
3993
- ...pendingData,
3994
- ...resumeDataPatch
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
- const wf = await selectWorkflow(resumedState.currentWorkflow);
3998
- const telemetryConfig = prepareWorkflowTelemetry({
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
- ...config,
4001
- ...pending.state?.config?.executionBranchId ? { executionBranchId: pending.state.config.executionBranchId } : {}
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: wf,
4004
- runId: pending.runId,
4723
+ workflow,
4724
+ runId,
4005
4725
  sessionId,
4006
- knownWorkflowIds
4726
+ knownWorkflowIds: services.knownWorkflowIds
4007
4727
  });
4008
- resumedState.config = telemetryConfig;
4009
- const resumeUpdate = pending.graphSnapshot ? {
4010
- config: {
4011
- ...telemetryConfig,
4012
- context: pending.state?.config?.context ?? telemetryConfig.context
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
- if (pending.graphSnapshot && frameworkAdapter)
4016
- await (0, import_runtime2.restoreGraphSnapshot)(
4017
- frameworkAdapter.checkpointer,
4018
- pending.graphSnapshot,
4019
- pending.runId
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
- const args = {
4065
- sessionId,
4066
- runId: pending.runId,
4067
- graph: resumedGraph,
4068
- state: resumedState,
4069
- config: {
4070
- ...telemetryConfig,
4071
- resume: true,
4072
- telemetryInterruptId: pending.requestId,
4073
- telemetryInterruptNodeId: pending.node,
4074
- ...resumeValue !== void 0 ? { resumeValue } : {},
4075
- ...resumeCheckpointId ? { resumeCheckpointId } : {},
4076
- ...hasResumeUpdate ? { resumeUpdate } : {}
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
- selectWorkflow,
4079
- knownWorkflowIds,
4080
- frameworkAdapter
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 runtimeConfig = {
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
- runtimeConfig.selectWorkflow = workflowSelector;
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: runtimeConfig,
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 runId = (0, import_runtime3.makeRequestId)("run");
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
- runtimeConfig = prepareWorkflowTelemetry({
4184
- config: runtimeConfig,
5083
+ runtimeConfig2 = prepareWorkflowTelemetry({
5084
+ config: runtimeConfig2,
4185
5085
  workflow: selectedWorkflow,
4186
5086
  runId,
4187
5087
  sessionId: resolvedSessionId,
4188
5088
  knownWorkflowIds
4189
5089
  });
4190
- const graph = await (0, import_runtime3.createExecutionGraph)(selectedWorkflow, runtimeConfig);
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: runtimeConfig,
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
- } : { ...baseState, config: runtimeConfig, currentWorkflow };
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: runtimeConfig,
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 = import_zod2.z.object({
4218
- sessionId: import_zod2.z.string().optional(),
4219
- workflowId: import_zod2.z.string().optional(),
4220
- context: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional()
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 = import_zod2.z.object({
4223
- getProvider: import_zod2.z.unknown().optional(),
4224
- workflows: import_zod2.z.array(import_zod2.z.unknown()).optional(),
4225
- workflowsDir: import_zod2.z.string().optional(),
4226
- workflowRegistry: import_zod2.z.unknown().optional(),
4227
- defaultWorkflowId: import_zod2.z.string().optional(),
4228
- frameworkAdapter: import_zod2.z.unknown().optional(),
4229
- telemetry: import_zod2.z.unknown().optional()
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, import_runtime4.createFrameworkAdapterFromEnv)();
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, import_runtime4.createInMemoryWorkflowRegistry)(workflows, {
5232
+ return (0, import_runtime5.createInMemoryWorkflowRegistry)(workflows, {
4309
5233
  fallbackId: resolvedDefaultWorkflowId ?? "general-chat"
4310
5234
  });
4311
5235
  }
4312
5236
  if (workflowsDir) {
4313
- return (0, import_runtime4.createFileWorkflowRegistry)({
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, import_runtime4.createFileWorkflowRegistry)({
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,