@kortyx/agent 0.18.0 → 0.20.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,6 +52,7 @@ 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,
@@ -66,6 +67,7 @@ var require_dist = __commonJS({
66
67
  workflowCallFingerprint: () => workflowCallFingerprint
67
68
  });
68
69
  module2.exports = __toCommonJS2(index_exports2);
70
+ var import_zod6 = require("zod");
69
71
  var import_node_async_hooks = require("async_hooks");
70
72
  var storage = new import_node_async_hooks.AsyncLocalStorage();
71
73
  var isRecord5 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -155,7 +157,7 @@ var require_dist = __commonJS({
155
157
  if (!usage) return void 0;
156
158
  const input = usage.input ?? 0;
157
159
  const output = usage.output ?? 0;
158
- const total = usage.total ?? input + output + (usage.reasoning ?? 0);
160
+ const total = usage.total ?? input + output + (usage.outputIncludesReasoning ? 0 : usage.reasoning ?? 0);
159
161
  if (input === 0 && output === 0 && total === 0) {
160
162
  return void 0;
161
163
  }
@@ -218,7 +220,29 @@ var require_dist = __commonJS({
218
220
  }
219
221
  return ctx;
220
222
  }
221
- var import_core5 = require("@kortyx/core");
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");
222
246
  var resolveReasonCheckpointKey = (args) => typeof args.id === "string" && args.id.length > 0 ? `__useReason:${args.id}` : `__useReason:auto:${args.autoIndex}`;
223
247
  var readReasonCheckpoint = (raw) => {
224
248
  if (!raw || typeof raw !== "object") return void 0;
@@ -269,7 +293,7 @@ var require_dist = __commonJS({
269
293
  };
270
294
  var awaitInterruptInternal = (args) => {
271
295
  const ctx = getHookContext();
272
- (0, import_core5.throwIfExecutionAborted)(ctx.node.abortSignal);
296
+ (0, import_core6.throwIfExecutionAborted)(ctx.node.abortSignal);
273
297
  const request = parseWithSchema(
274
298
  args.requestSchema,
275
299
  args.request,
@@ -409,7 +433,7 @@ var require_dist = __commonJS({
409
433
  });
410
434
  };
411
435
  async function runReasonEngine(args) {
412
- const stream = args.stream ?? args.model.options?.streaming ?? true;
436
+ let stream = args.stream ?? args.model.options?.streaming ?? true;
413
437
  const emit = args.emit ?? true;
414
438
  const temperature = args.temperature ?? args.model.options?.temperature ?? args.defaultTemperature;
415
439
  const maxOutputTokens = args.maxOutputTokens ?? args.model.options?.maxOutputTokens;
@@ -417,7 +441,14 @@ var require_dist = __commonJS({
417
441
  const abortSignal = args.abortSignal ?? args.model.options?.abortSignal;
418
442
  const reasoning = args.reasoning ?? args.model.options?.reasoning;
419
443
  const responseFormat = args.responseFormat ?? args.model.options?.responseFormat;
420
- const providerOptions = args.providerOptions ?? args.model.options?.providerOptions;
444
+ const defaults = args.model.options?.providerOptions;
445
+ const providerOptions = defaults || args.providerOptions ? { ...defaults, ...args.providerOptions } : void 0;
446
+ for (const [key, value] of Object.entries(args.providerOptions ?? {})) {
447
+ const previous = defaults?.[key];
448
+ if (previous && value && typeof previous === "object" && typeof value === "object" && !Array.isArray(previous) && !Array.isArray(value)) {
449
+ if (providerOptions) providerOptions[key] = { ...previous, ...value };
450
+ }
451
+ }
421
452
  const tools = args.tools ?? args.model.options?.tools;
422
453
  (0, import_core22.throwIfExecutionAborted)(abortSignal);
423
454
  const model = args.model.provider.getModel(args.model.modelId, {
@@ -431,6 +462,7 @@ var require_dist = __commonJS({
431
462
  ...tools !== void 0 ? { tools } : {},
432
463
  ...providerOptions !== void 0 ? { providerOptions } : {}
433
464
  });
465
+ if (tools?.length && !model.supportsToolStreaming) stream = false;
434
466
  const messages = args.messages !== void 0 ? args.messages : [
435
467
  ...typeof args.system === "string" && args.system.length > 0 ? [{ role: "system", content: args.system }] : [],
436
468
  { role: "user", content: String(args.input ?? "") }
@@ -458,7 +490,9 @@ var require_dist = __commonJS({
458
490
  },
459
491
  telemetry: {
460
492
  ...args.telemetry ?? {},
461
- input: args.telemetry?.input ?? messages
493
+ input: args.telemetry?.input ?? messages.map(
494
+ ({ continuation: _continuation, raw: _raw, ...message }) => message
495
+ )
462
496
  }
463
497
  });
464
498
  const modelRequestStartedAt = Date.now();
@@ -489,7 +523,11 @@ var require_dist = __commonJS({
489
523
  });
490
524
  };
491
525
  const failTrace = (span, error) => {
526
+ const detail = error;
492
527
  span?.fail?.(error, {
528
+ ...detail?.usage ? { usage: detail.usage } : {},
529
+ ...detail?.finishReason ? { finishReason: detail.finishReason } : {},
530
+ ...detail?.providerMetadata ? { providerMetadata: detail.providerMetadata } : {},
493
531
  attributes: {
494
532
  providerId: args.model.provider.id,
495
533
  modelId: args.model.modelId
@@ -498,6 +536,7 @@ var require_dist = __commonJS({
498
536
  };
499
537
  try {
500
538
  if (stream) {
539
+ let continuation;
501
540
  let final = "";
502
541
  let raw;
503
542
  let usage;
@@ -536,6 +575,7 @@ var require_dist = __commonJS({
536
575
  break;
537
576
  }
538
577
  case "finish": {
578
+ continuation = chunk.continuation;
539
579
  if (chunk.raw !== void 0) {
540
580
  raw = chunk.raw;
541
581
  }
@@ -584,6 +624,7 @@ var require_dist = __commonJS({
584
624
  }
585
625
  const result2 = {
586
626
  text: final,
627
+ ...continuation ? { continuation } : {},
587
628
  ...raw !== void 0 ? { raw } : {},
588
629
  ...usage !== void 0 ? { usage } : {},
589
630
  ...finishReason !== void 0 ? { finishReason } : {},
@@ -597,6 +638,7 @@ var require_dist = __commonJS({
597
638
  }
598
639
  const response = await model.invoke(messages);
599
640
  (0, import_core22.throwIfExecutionAborted)(abortSignal);
641
+ if (response.content) args.onTextChunk?.(response.content);
600
642
  if (emit) {
601
643
  emitNodeEvent(args.emitEvent, args.nodeId, "text-start", {
602
644
  ...commonMeta
@@ -613,6 +655,7 @@ var require_dist = __commonJS({
613
655
  }
614
656
  const result = {
615
657
  text: response.content,
658
+ ...response.continuation ? { continuation: response.continuation } : {},
616
659
  ...response.raw !== void 0 ? { raw: response.raw } : {},
617
660
  ...response.usage !== void 0 ? { usage: response.usage } : {},
618
661
  ...response.finishReason !== void 0 ? { finishReason: response.finishReason } : {},
@@ -644,472 +687,184 @@ var require_dist = __commonJS({
644
687
  );
645
688
  (0, import_core32.throwIfExecutionAborted)(abortSignal);
646
689
  ctx.node.consumeExecution?.("maxModelPasses");
647
- return runReasonEngine({
648
- model: args.model,
649
- input: inputOverride ?? args.input,
650
- system: args.system,
651
- temperature: args.temperature,
652
- maxOutputTokens: args.maxOutputTokens,
653
- stopSequences: args.stopSequences,
654
- abortSignal,
655
- reasoning: args.reasoning,
656
- responseFormat: args.responseFormat,
657
- providerOptions: args.providerOptions,
658
- tools: args.tools,
659
- messages: args.messages,
660
- defaultTemperature: ctx.node.config?.model?.temperature,
661
- stream: args.stream,
662
- emit: args.emit,
663
- onTextChunk: args.onTextChunk,
664
- telemetry: args.telemetry,
665
- nodeId: ctx.node.graph.node,
666
- emitEvent: ctx.node.emit,
667
- reasonTrace: ctx.reasonTrace,
668
- id: meta.id,
669
- opId: meta.opId
670
- });
671
- }
672
- var tryJsonParse = (value) => {
673
690
  try {
674
- return { ok: true, value: JSON.parse(value) };
691
+ return await runReasonEngine({
692
+ model: args.model,
693
+ input: inputOverride ?? args.input,
694
+ system: args.system,
695
+ temperature: args.temperature,
696
+ maxOutputTokens: args.maxOutputTokens,
697
+ stopSequences: args.stopSequences,
698
+ abortSignal,
699
+ reasoning: args.reasoning,
700
+ responseFormat: args.responseFormat,
701
+ providerOptions: args.providerOptions,
702
+ tools: args.tools,
703
+ messages: args.messages,
704
+ defaultTemperature: ctx.node.config?.model?.temperature,
705
+ stream: args.stream,
706
+ emit: args.emit,
707
+ onTextChunk: args.onTextChunk,
708
+ telemetry: args.telemetry,
709
+ nodeId: ctx.node.graph.node,
710
+ emitEvent: ctx.node.emit,
711
+ reasonTrace: ctx.reasonTrace,
712
+ id: meta.id,
713
+ opId: meta.opId
714
+ });
675
715
  } catch (error) {
676
- return {
677
- ok: false,
678
- error: error instanceof Error ? error.message : String(error)
679
- };
716
+ const usage = error?.usage;
717
+ if (usage) accumulateTokenUsage(usage);
718
+ throw error;
680
719
  }
681
- };
682
- var extractJsonCodeBlock = (value) => {
683
- const match = value.match(/```(?:json)?\s*([\s\S]*?)```/i);
684
- if (!match || typeof match[1] !== "string") return null;
685
- const inner = match[1].trim();
686
- return inner.length > 0 ? inner : null;
687
- };
688
- var tryStringify = (value) => {
689
- if (typeof value === "string") return value;
720
+ }
721
+ var import_zod22 = require("zod");
722
+ function inferOutputFormat(schema, explicit) {
723
+ if (explicit) return { responseFormat: explicit };
724
+ if (!schema) return {};
690
725
  try {
691
- return JSON.stringify(value, null, 2);
726
+ const json = (0, import_zod22.toJSONSchema)(schema);
727
+ const clean = (value) => {
728
+ if (Array.isArray(value)) return value.map(clean);
729
+ if (!value || typeof value !== "object") return value;
730
+ const object = value;
731
+ if ([
732
+ "allOf",
733
+ "not",
734
+ "dependentRequired",
735
+ "dependentSchemas",
736
+ "if",
737
+ "then",
738
+ "else",
739
+ "prefixItems"
740
+ ].some((key) => key in object))
741
+ throw new Error(
742
+ "Schema composition requires an explicit provider schema"
743
+ );
744
+ if (object.type === "object") {
745
+ if (object.additionalProperties !== false)
746
+ throw new Error(
747
+ "Dynamic properties require an explicit provider schema"
748
+ );
749
+ const properties = object.properties;
750
+ const required = object.required;
751
+ if (properties && Object.keys(properties).some((key) => !required?.includes(key))) {
752
+ throw new Error(
753
+ "Optional properties require an explicit provider schema"
754
+ );
755
+ }
756
+ }
757
+ return Object.fromEntries(
758
+ Object.entries(object).filter(([key]) => key !== "$schema" && key !== "default").map(([key, child]) => {
759
+ if ((key === "properties" || key === "$defs" || key === "definitions") && child && typeof child === "object")
760
+ return [
761
+ key,
762
+ Object.fromEntries(
763
+ Object.entries(child).map(([name, definition]) => [
764
+ name,
765
+ clean(definition)
766
+ ])
767
+ )
768
+ ];
769
+ return [
770
+ key,
771
+ key === "enum" || key === "const" ? child : clean(child)
772
+ ];
773
+ })
774
+ );
775
+ };
776
+ if (json.type !== "object")
777
+ throw new Error("The provider schema must have an object root");
778
+ return { responseFormat: { type: "json", schema: clean(json) } };
692
779
  } catch {
693
- return String(value);
780
+ return {
781
+ responseFormat: { type: "json" },
782
+ warnings: [
783
+ {
784
+ type: "compatibility",
785
+ feature: "outputSchema",
786
+ details: "This validator cannot be inferred as a strict provider JSON schema. Kortyx still validates the final output locally. Supply responseFormat.schema to enforce a wire schema."
787
+ }
788
+ ]
789
+ };
694
790
  }
695
- };
696
- var isRecord22 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
697
- var resolveOutputCandidate = (text) => {
698
- const trimmed = text.trim();
699
- if (!trimmed) return text;
700
- const direct = tryJsonParse(trimmed);
701
- if (direct.ok) return direct.value;
702
- const block = extractJsonCodeBlock(trimmed);
703
- if (block) {
704
- const parsedBlock = tryJsonParse(block);
705
- if (parsedBlock.ok) return parsedBlock.value;
791
+ }
792
+ var resolveAppendFieldPaths = (cfg) => {
793
+ const fields = cfg?.fields;
794
+ if (!fields) return [];
795
+ const appendPaths = Object.entries(fields).filter(([, mode]) => mode === "append").map(([path]) => path);
796
+ for (const path of appendPaths) {
797
+ assertStructuredPath(path, "useReason structured append");
706
798
  }
707
- return text;
708
- };
709
- var looksLikeStructuredJsonAttempt = (value) => {
710
- const trimmed = value.trim();
711
- if (!trimmed) return false;
712
- return trimmed.startsWith("{") || trimmed.startsWith("[") || /^```(?:json)?/i.test(trimmed);
799
+ return appendPaths;
713
800
  };
714
- var isLikelyTruncatedStructuredOutput = (text) => {
715
- const trimmed = text.trim();
716
- if (!looksLikeStructuredJsonAttempt(trimmed)) return false;
717
- const direct = tryJsonParse(trimmed);
718
- if (!direct.ok && direct.error) return true;
719
- const block = extractJsonCodeBlock(trimmed);
720
- if (!block) return false;
721
- const parsedBlock = tryJsonParse(block);
722
- return !parsedBlock.ok && Boolean(parsedBlock.error);
801
+ var resolveSetFieldPaths = (cfg) => {
802
+ const fields = cfg?.fields;
803
+ if (!fields) return [];
804
+ const setPaths = Object.entries(fields).filter(([, mode]) => mode === "set").map(([path]) => path);
805
+ for (const path of setPaths) {
806
+ assertStructuredPath(path, "useReason structured set");
807
+ }
808
+ return setPaths;
723
809
  };
724
- var createTruncatedStructuredOutputError = (label) => new Error(
725
- `${label} was truncated before producing valid structured output. The model stopped due to output length. Increase maxOutputTokens or simplify the requested output.`
726
- );
727
- var createInvalidStructuredOutputError = (label) => new Error(
728
- `${label} did not produce valid structured output. The model returned text instead of the expected JSON payload. This often happens when output is truncated or the provider does not follow JSON mode. Increase maxOutputTokens or simplify the requested output.`
729
- );
730
- var parseReasonOutputWithSchema = (args) => {
731
- const candidate = resolveOutputCandidate(args.text);
732
- try {
733
- return parseWithSchema(args.schema, candidate, args.label);
734
- } catch (error) {
735
- if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
736
- throw createTruncatedStructuredOutputError(args.label);
737
- }
738
- if (typeof candidate === "string" && candidate.trim().length > 0) {
739
- throw createInvalidStructuredOutputError(args.label);
740
- }
741
- throw error;
810
+ var resolveTextDeltaFieldPaths = (cfg) => {
811
+ const fields = cfg?.fields;
812
+ if (!fields) return [];
813
+ const textPaths = Object.entries(fields).filter(([, mode]) => mode === "text-delta").map(([path]) => path);
814
+ for (const path of textPaths) {
815
+ assertStructuredPath(path, "useReason structured text-delta");
742
816
  }
817
+ return textPaths;
743
818
  };
744
- var parseInterruptFirstPassResult = (args) => {
745
- const candidate = resolveOutputCandidate(args.text);
746
- if (!isRecord22(candidate)) {
747
- if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
748
- throw createTruncatedStructuredOutputError("useReason output");
819
+ var extractCompletedStringValueAt = (args) => {
820
+ let value = "";
821
+ for (let i = args.start; i < args.text.length; i += 1) {
822
+ const ch = args.text[i];
823
+ if (ch === void 0) break;
824
+ if (ch === '"') {
825
+ return {
826
+ complete: true,
827
+ value
828
+ };
749
829
  }
750
- if (typeof candidate === "string" && candidate.trim().length > 0) {
751
- throw createInvalidStructuredOutputError("useReason output");
830
+ if (ch !== "\\") {
831
+ value += ch;
832
+ continue;
752
833
  }
753
- throw new Error(
754
- "useReason first pass with interrupt must return a JSON object."
755
- );
756
- }
757
- const mode = args.mode ?? "required";
758
- let interruptRequired = true;
759
- if (mode === "optional") {
760
- if (candidate.decision !== "continue" && candidate.decision !== "interrupt") {
761
- throw new Error(
762
- 'useReason optional interrupt first pass must include decision "continue" or "interrupt".'
763
- );
834
+ const next = args.text[i + 1];
835
+ if (!next) {
836
+ return {
837
+ complete: false
838
+ };
764
839
  }
765
- interruptRequired = candidate.decision === "interrupt";
766
- if (!interruptRequired) {
767
- const hasUnexpectedRequest = [
768
- candidate.interruptRequest,
769
- candidate.request,
770
- candidate.interrupt
771
- ].some((value) => value !== null && value !== void 0);
772
- if (hasUnexpectedRequest) {
773
- throw new Error(
774
- 'useReason optional interrupt first pass with decision "continue" must not include an interrupt request.'
775
- );
776
- }
840
+ if (next === '"' || next === "\\" || next === "/" || next === "b" || next === "f" || next === "n" || next === "r" || next === "t") {
841
+ value += next === "b" ? "\b" : next === "f" ? "\f" : next === "n" ? "\n" : next === "r" ? "\r" : next === "t" ? " " : next;
842
+ i += 1;
843
+ continue;
777
844
  }
778
- }
779
- let request;
780
- if (interruptRequired) {
781
- const requestCandidates = [
782
- candidate.interruptRequest,
783
- candidate.request,
784
- candidate.interrupt,
785
- mode === "required" ? candidate : void 0
786
- ];
787
- const requestPayload = requestCandidates.find(
788
- (value) => value !== null && value !== void 0
789
- );
790
- request = parseWithSchema(
791
- args.requestSchema,
792
- requestPayload,
793
- "useReason interrupt.request"
794
- );
795
- }
796
- let output;
797
- if (args.outputSchema) {
798
- const outputPayload = Object.hasOwn(candidate, "output") ? candidate.output : candidate;
799
- try {
800
- output = parseWithSchema(
801
- args.outputSchema,
802
- outputPayload,
803
- "useReason output"
804
- );
805
- } catch (error) {
806
- if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
807
- throw createTruncatedStructuredOutputError("useReason output");
808
- }
809
- if (typeof outputPayload === "string" && outputPayload.trim().length > 0) {
810
- throw createInvalidStructuredOutputError("useReason output");
845
+ if (next === "u") {
846
+ const hex = args.text.slice(i + 2, i + 6);
847
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
848
+ return {
849
+ complete: false
850
+ };
811
851
  }
812
- throw error;
852
+ value += String.fromCharCode(Number.parseInt(hex, 16));
853
+ i += 5;
854
+ continue;
813
855
  }
814
- }
815
- const explicitDraft = typeof candidate.draftText === "string" && candidate.draftText.length > 0 ? candidate.draftText : typeof candidate.text === "string" && candidate.text.length > 0 ? candidate.text : void 0;
816
- if (!args.outputSchema && !explicitDraft) {
817
- throw new Error(
818
- "useReason first pass with interrupt requires `draftText` when outputSchema is not provided."
819
- );
820
- }
821
- const draftText = explicitDraft ?? (output !== void 0 ? tryStringify(output) : String(args.text ?? ""));
822
- if (!interruptRequired) {
823
856
  return {
824
- draftText,
825
- interruptRequired: false,
826
- ...output !== void 0 ? { output } : {}
857
+ complete: false
827
858
  };
828
859
  }
829
860
  return {
830
- draftText,
831
- interruptRequired: true,
832
- request,
833
- ...output !== void 0 ? { output } : {}
861
+ complete: false
834
862
  };
835
863
  };
836
- var import_zod5 = require("zod");
837
- var tryStringify2 = (value) => {
838
- if (typeof value === "string") return value;
839
- try {
840
- return JSON.stringify(value, null, 2);
841
- } catch {
842
- return String(value);
843
- }
844
- };
845
- var tryGetSchemaHint = (schema) => {
846
- try {
847
- const hint = (0, import_zod5.toJSONSchema)(schema);
848
- return JSON.stringify(hint, null, 2);
849
- } catch {
850
- return void 0;
851
- }
852
- };
853
- var withSchemaHint = (input, label, schema) => {
854
- const hint = tryGetSchemaHint(schema);
855
- if (!hint) return input;
856
- return `${input}
857
-
858
- ${label}:
859
- ${hint}`;
860
- };
861
- var defaultInterruptFirstPassInput = (args) => {
862
- const mode = args.mode ?? "required";
863
- const base = mode === "optional" ? args.outputSchema ? [
864
- args.input,
865
- "Output rules:",
866
- "- Return JSON only. No markdown fences.",
867
- "- Return an object with keys: decision, output, interruptRequest, draftText.",
868
- '- decision must be either "continue" or "interrupt".',
869
- '- Use decision "continue" when you can produce the final output without asking the user.',
870
- '- Use decision "interrupt" only when user input is needed before finalizing.',
871
- "- output must match the expected output JSON schema.",
872
- '- When decision is "interrupt", interruptRequest must match the expected interrupt request JSON schema and must be a non-null object.',
873
- '- When decision is "continue", interruptRequest must be null.',
874
- "- draftText should be a concise plain-language summary of output."
875
- ].join("\n\n") : [
876
- args.input,
877
- "Output rules:",
878
- "- Return JSON only. No markdown fences.",
879
- "- Return an object with keys: decision, draftText, interruptRequest.",
880
- '- decision must be either "continue" or "interrupt".',
881
- '- Use decision "continue" when you can produce the final answer without asking the user.',
882
- '- Use decision "interrupt" only when user input is needed before finalizing.',
883
- "- draftText should be the current answer text.",
884
- '- When decision is "interrupt", interruptRequest must match the expected interrupt request JSON schema and must be a non-null object.',
885
- '- When decision is "continue", interruptRequest must be null.'
886
- ].join("\n\n") : args.outputSchema ? [
887
- args.input,
888
- "Output rules:",
889
- "- Return JSON only. No markdown fences.",
890
- "- Return an object with keys: output, interruptRequest, draftText.",
891
- "- output must match the expected output JSON schema.",
892
- "- interruptRequest must match the expected interrupt request JSON schema.",
893
- "- interruptRequest must be a non-null object.",
894
- "- draftText should be a concise plain-language summary of output.",
895
- "- If uncertain, still return a valid interruptRequest with safe default options."
896
- ].join("\n\n") : [
897
- args.input,
898
- "Output rules:",
899
- "- Return JSON only. No markdown fences.",
900
- "- Return an object with keys: draftText, interruptRequest.",
901
- "- draftText should be the current draft answer text.",
902
- "- interruptRequest must match the expected interrupt request JSON schema.",
903
- "- interruptRequest must be a non-null object.",
904
- "- If uncertain, still return a valid interruptRequest with safe default options."
905
- ].join("\n\n");
906
- const withInterruptSchema = withSchemaHint(
907
- base,
908
- "Expected interrupt request JSON schema (for `interruptRequest`)",
909
- args.requestSchema
910
- );
911
- if (!args.outputSchema) return withInterruptSchema;
912
- const withOutputSchema = withSchemaHint(
913
- withInterruptSchema,
914
- "Expected output JSON schema (for `output`)",
915
- args.outputSchema
916
- );
917
- return [
918
- withOutputSchema,
919
- "JSON template:",
920
- mode === "optional" ? '{ "decision": "continue", "output": { ... }, "interruptRequest": null, "draftText": "..." }' : '{ "output": { ... }, "interruptRequest": { ... }, "draftText": "..." }'
921
- ].join("\n\n");
922
- };
923
- var defaultContinuationInput = (args) => [
924
- "You are continuing a paused reasoning task after user input.",
925
- "Update the previous result using the user response and return the final answer only.",
926
- "If the previous output was JSON, return JSON in the same schema.",
927
- "",
928
- `Original request:
929
- ${args.input}`,
930
- `Draft answer text:
931
- ${args.draftText}`,
932
- `Draft output object:
933
- ${tryStringify2(args.draftOutput ?? args.draftText)}`,
934
- `Interrupt request shown to user:
935
- ${tryStringify2(args.request)}`,
936
- `User response:
937
- ${tryStringify2(args.response)}`
938
- ].join("\n\n");
939
- var withOutputGuardrails = (input, schema) => {
940
- const base = [
941
- input,
942
- "Output rules:",
943
- "- Return only JSON, no markdown fences.",
944
- "- Ensure the response conforms to the expected output schema.",
945
- "- Do not include explanatory text outside the JSON object."
946
- ].join("\n\n");
947
- return withSchemaHint(base, "Expected output JSON schema", schema);
948
- };
949
- var withStructuredStreamHints = (input, args) => {
950
- const rules = [];
951
- for (const path of args.setFieldPaths ?? []) {
952
- rules.push(
953
- `- For the field path pattern \`${path}\`, write one stable final value and do not rewrite it after it is complete.`
954
- );
955
- }
956
- for (const path of args.appendFieldPaths ?? []) {
957
- rules.push(
958
- `- For the array field path pattern \`${path}\`, write items in final order and finish each item before starting the next.`
959
- );
960
- rules.push(
961
- `- Once an item in \`${path}\` is complete, do not rewrite or reorder earlier items.`
962
- );
963
- }
964
- for (const path of args.textDeltaFieldPaths ?? []) {
965
- rules.push(
966
- `- For the string field path pattern \`${path}\`, write the value progressively by appending text to the end.`
967
- );
968
- rules.push(
969
- `- Do not rewrite or replace the earlier prefix of \`${path}\` once emitted.`
970
- );
971
- }
972
- if (rules.length === 0) return input;
973
- return [
974
- input,
975
- "Streaming rules:",
976
- ...rules,
977
- "- Keep the overall response as valid JSON."
978
- ].join("\n\n");
979
- };
980
- var mergeUsage = (left, right) => {
981
- if (!left) return right;
982
- if (!right) return left;
983
- const sum = (a, b) => a === void 0 && b === void 0 ? void 0 : (a ?? 0) + (b ?? 0);
984
- const raw = left.raw || right.raw ? {
985
- ...left.raw ?? {},
986
- ...right.raw ?? {}
987
- } : void 0;
988
- const input = sum(left.input, right.input);
989
- const output = sum(left.output, right.output);
990
- const total = sum(left.total, right.total);
991
- const reasoning = sum(left.reasoning, right.reasoning);
992
- const cacheRead = sum(left.cacheRead, right.cacheRead);
993
- const cacheWrite = sum(left.cacheWrite, right.cacheWrite);
994
- const merged = {};
995
- if (input !== void 0) merged.input = input;
996
- if (output !== void 0) merged.output = output;
997
- if (total !== void 0) merged.total = total;
998
- if (reasoning !== void 0) merged.reasoning = reasoning;
999
- if (cacheRead !== void 0) merged.cacheRead = cacheRead;
1000
- if (cacheWrite !== void 0) merged.cacheWrite = cacheWrite;
1001
- if (raw) merged.raw = raw;
1002
- return merged;
1003
- };
1004
- var mergeWarnings = (left, right) => {
1005
- if (!left?.length) return right;
1006
- if (!right?.length) return left;
1007
- const seen = /* @__PURE__ */ new Set();
1008
- const merged = [];
1009
- for (const warning of [...left, ...right]) {
1010
- const key = JSON.stringify(warning);
1011
- if (seen.has(key)) continue;
1012
- seen.add(key);
1013
- merged.push(warning);
1014
- }
1015
- return merged;
1016
- };
1017
- var mergeProviderMetadata = (left, right) => {
1018
- if (!left) return right;
1019
- if (!right) return left;
1020
- return {
1021
- ...left,
1022
- ...right
1023
- };
1024
- };
1025
- var emitReasonStructuredOutput = (args) => {
1026
- if (!args.emit || !shouldEmitStructured(args.structured)) return;
1027
- emitStructuredData({
1028
- dataType: args.structured?.dataType ?? "reason-output",
1029
- data: args.output,
1030
- kind: "final",
1031
- ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
1032
- ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
1033
- ...args.id ? { id: args.id } : {},
1034
- streamId: args.opId
1035
- });
1036
- };
1037
- var resolveAppendFieldPaths = (cfg) => {
1038
- const fields = cfg?.fields;
1039
- if (!fields) return [];
1040
- const appendPaths = Object.entries(fields).filter(([, mode]) => mode === "append").map(([path]) => path);
1041
- for (const path of appendPaths) {
1042
- assertStructuredPath(path, "useReason structured append");
1043
- }
1044
- return appendPaths;
1045
- };
1046
- var resolveSetFieldPaths = (cfg) => {
1047
- const fields = cfg?.fields;
1048
- if (!fields) return [];
1049
- const setPaths = Object.entries(fields).filter(([, mode]) => mode === "set").map(([path]) => path);
1050
- for (const path of setPaths) {
1051
- assertStructuredPath(path, "useReason structured set");
1052
- }
1053
- return setPaths;
1054
- };
1055
- var resolveTextDeltaFieldPaths = (cfg) => {
1056
- const fields = cfg?.fields;
1057
- if (!fields) return [];
1058
- const textPaths = Object.entries(fields).filter(([, mode]) => mode === "text-delta").map(([path]) => path);
1059
- for (const path of textPaths) {
1060
- assertStructuredPath(path, "useReason structured text-delta");
1061
- }
1062
- return textPaths;
1063
- };
1064
- var extractCompletedStringValueAt = (args) => {
1065
- let value = "";
1066
- for (let i = args.start; i < args.text.length; i += 1) {
1067
- const ch = args.text[i];
1068
- if (ch === void 0) break;
1069
- if (ch === '"') {
1070
- return {
1071
- complete: true,
1072
- value
1073
- };
1074
- }
1075
- if (ch !== "\\") {
1076
- value += ch;
1077
- continue;
1078
- }
1079
- const next = args.text[i + 1];
1080
- if (!next) {
1081
- return {
1082
- complete: false
1083
- };
1084
- }
1085
- if (next === '"' || next === "\\" || next === "/" || next === "b" || next === "f" || next === "n" || next === "r" || next === "t") {
1086
- value += next === "b" ? "\b" : next === "f" ? "\f" : next === "n" ? "\n" : next === "r" ? "\r" : next === "t" ? " " : next;
1087
- i += 1;
1088
- continue;
1089
- }
1090
- if (next === "u") {
1091
- const hex = args.text.slice(i + 2, i + 6);
1092
- if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
1093
- return {
1094
- complete: false
1095
- };
1096
- }
1097
- value += String.fromCharCode(Number.parseInt(hex, 16));
1098
- i += 5;
1099
- continue;
1100
- }
1101
- return {
1102
- complete: false
1103
- };
1104
- }
1105
- return {
1106
- complete: false
1107
- };
1108
- };
1109
- var skipWhitespace = (text, start) => {
1110
- let i = start;
1111
- while (i < text.length && /\s/.test(text[i] ?? "")) i += 1;
1112
- return i;
864
+ var skipWhitespace = (text, start) => {
865
+ let i = start;
866
+ while (i < text.length && /\s/.test(text[i] ?? "")) i += 1;
867
+ return i;
1113
868
  };
1114
869
  var pathSegmentMatches = (patternPart, actualPart) => patternPart === "*" || patternPart === actualPart;
1115
870
  var pathMatchesPattern = (actualParts, patternParts) => actualParts.length === patternParts.length && actualParts.every(
@@ -1223,11 +978,11 @@ ${tryStringify2(args.response)}`
1223
978
  args.path,
1224
979
  "useReason structured field"
1225
980
  );
1226
- const matches = [];
981
+ const matches2 = [];
1227
982
  const visitValue = (start, currentParts) => {
1228
983
  const valueStart = skipWhitespace(args.text, start);
1229
984
  if (pathMatchesPattern(currentParts, targetParts)) {
1230
- matches.push({
985
+ matches2.push({
1231
986
  path: currentParts.join("."),
1232
987
  start: valueStart
1233
988
  });
@@ -1259,7 +1014,7 @@ ${tryStringify2(args.response)}`
1259
1014
  const valueStart = skipWhitespace(args.text, i + 1);
1260
1015
  const childParts = [...currentParts, key.value];
1261
1016
  if (pathMatchesPattern(childParts, targetParts)) {
1262
- matches.push({
1017
+ matches2.push({
1263
1018
  path: childParts.join("."),
1264
1019
  start: valueStart
1265
1020
  });
@@ -1286,7 +1041,7 @@ ${tryStringify2(args.response)}`
1286
1041
  if (args.text[i] === "]") return;
1287
1042
  const childParts = [...currentParts, String(index)];
1288
1043
  if (pathMatchesPattern(childParts, targetParts)) {
1289
- matches.push({
1044
+ matches2.push({
1290
1045
  path: childParts.join("."),
1291
1046
  start: i
1292
1047
  });
@@ -1308,7 +1063,7 @@ ${tryStringify2(args.response)}`
1308
1063
  }
1309
1064
  };
1310
1065
  visitValue(0, []);
1311
- return matches;
1066
+ return matches2;
1312
1067
  };
1313
1068
  var extractCompletedValueAt = (args) => {
1314
1069
  const start = skipWhitespace(args.text, args.start);
@@ -1572,24 +1327,473 @@ ${tryStringify2(args.response)}`
1572
1327
  };
1573
1328
  }
1574
1329
  return {
1575
- found: true,
1576
- complete: false,
1577
- value
1330
+ found: true,
1331
+ complete: false,
1332
+ value
1333
+ };
1334
+ };
1335
+ var extractStreamingStringValues = (args) => {
1336
+ return locateValueStarts(args).flatMap((match) => {
1337
+ const current = extractStreamingStringValue({
1338
+ text: args.text,
1339
+ path: match.path
1340
+ });
1341
+ return current.found ? [
1342
+ {
1343
+ path: match.path,
1344
+ complete: current.complete,
1345
+ value: current.value
1346
+ }
1347
+ ] : [];
1348
+ });
1349
+ };
1350
+ function createStructuredOutputStreamer(args, id, opId) {
1351
+ let firstText = "";
1352
+ const setFieldPaths = resolveSetFieldPaths(args.structured);
1353
+ const appendFieldPaths = resolveAppendFieldPaths(args.structured);
1354
+ const textDeltaFieldPaths = resolveTextDeltaFieldPaths(args.structured);
1355
+ const emittedSetValues = /* @__PURE__ */ new Map();
1356
+ const emittedAppendCounts = /* @__PURE__ */ new Map();
1357
+ const emittedTextValues = /* @__PURE__ */ new Map();
1358
+ return (delta) => {
1359
+ firstText += delta;
1360
+ for (const fieldPath of setFieldPaths) {
1361
+ const values = extractCompletedFieldValues({
1362
+ text: firstText,
1363
+ path: fieldPath
1364
+ });
1365
+ for (const { path, value } of values) {
1366
+ const nextSerialized = JSON.stringify(value);
1367
+ if (emittedSetValues.get(path) === nextSerialized) continue;
1368
+ emittedSetValues.set(path, nextSerialized);
1369
+ emitStructuredData({
1370
+ kind: "set",
1371
+ path,
1372
+ value,
1373
+ dataType: args.structured?.dataType ?? "reason-output",
1374
+ ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
1375
+ ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
1376
+ ...id ? { id } : {},
1377
+ streamId: opId
1378
+ });
1379
+ }
1380
+ }
1381
+ for (const fieldPath of appendFieldPaths) {
1382
+ const groups = extractCompletedArrayItemGroups({
1383
+ text: firstText,
1384
+ path: fieldPath
1385
+ });
1386
+ for (const { path, items } of groups) {
1387
+ const emittedAppendCount = emittedAppendCounts.get(path) ?? 0;
1388
+ if (items.length <= emittedAppendCount) continue;
1389
+ const nextItems = items.slice(emittedAppendCount);
1390
+ emittedAppendCounts.set(path, items.length);
1391
+ emitStructuredData({
1392
+ kind: "append",
1393
+ path,
1394
+ items: nextItems,
1395
+ dataType: args.structured?.dataType ?? "reason-output",
1396
+ ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
1397
+ ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
1398
+ ...id ? { id } : {},
1399
+ streamId: opId
1400
+ });
1401
+ }
1402
+ }
1403
+ for (const fieldPath of textDeltaFieldPaths) {
1404
+ const values = extractStreamingStringValues({
1405
+ text: firstText,
1406
+ path: fieldPath
1407
+ });
1408
+ for (const { path, value } of values) {
1409
+ const emittedTextValue = emittedTextValues.get(path) ?? "";
1410
+ if (value.length <= emittedTextValue.length || !value.startsWith(emittedTextValue)) {
1411
+ continue;
1412
+ }
1413
+ const nextDelta = value.slice(emittedTextValue.length);
1414
+ emittedTextValues.set(path, value);
1415
+ if (nextDelta.length === 0) continue;
1416
+ emitStructuredData({
1417
+ kind: "text-delta",
1418
+ path,
1419
+ delta: nextDelta,
1420
+ dataType: args.structured?.dataType ?? "reason-output",
1421
+ ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
1422
+ ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
1423
+ ...id ? { id } : {},
1424
+ streamId: opId
1425
+ });
1426
+ }
1427
+ }
1428
+ };
1429
+ }
1430
+ var tryJsonParse = (value) => {
1431
+ try {
1432
+ return { ok: true, value: JSON.parse(value) };
1433
+ } catch (error) {
1434
+ return {
1435
+ ok: false,
1436
+ error: error instanceof Error ? error.message : String(error)
1437
+ };
1438
+ }
1439
+ };
1440
+ var extractJsonCodeBlock = (value) => {
1441
+ const match = value.match(/```(?:json)?\s*([\s\S]*?)```/i);
1442
+ if (!match || typeof match[1] !== "string") return null;
1443
+ const inner = match[1].trim();
1444
+ return inner.length > 0 ? inner : null;
1445
+ };
1446
+ var tryStringify = (value) => {
1447
+ if (typeof value === "string") return value;
1448
+ try {
1449
+ return JSON.stringify(value, null, 2);
1450
+ } catch {
1451
+ return String(value);
1452
+ }
1453
+ };
1454
+ var isRecord22 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
1455
+ var resolveOutputCandidate = (text) => {
1456
+ const trimmed = text.trim();
1457
+ if (!trimmed) return text;
1458
+ const direct = tryJsonParse(trimmed);
1459
+ if (direct.ok) return direct.value;
1460
+ const block = extractJsonCodeBlock(trimmed);
1461
+ if (block) {
1462
+ const parsedBlock = tryJsonParse(block);
1463
+ if (parsedBlock.ok) return parsedBlock.value;
1464
+ }
1465
+ return text;
1466
+ };
1467
+ var looksLikeStructuredJsonAttempt = (value) => {
1468
+ const trimmed = value.trim();
1469
+ if (!trimmed) return false;
1470
+ return trimmed.startsWith("{") || trimmed.startsWith("[") || /^```(?:json)?/i.test(trimmed);
1471
+ };
1472
+ var isLikelyTruncatedStructuredOutput = (text) => {
1473
+ const trimmed = text.trim();
1474
+ if (!looksLikeStructuredJsonAttempt(trimmed)) return false;
1475
+ const direct = tryJsonParse(trimmed);
1476
+ if (!direct.ok && direct.error) return true;
1477
+ const block = extractJsonCodeBlock(trimmed);
1478
+ if (!block) return false;
1479
+ const parsedBlock = tryJsonParse(block);
1480
+ return !parsedBlock.ok && Boolean(parsedBlock.error);
1481
+ };
1482
+ var createTruncatedStructuredOutputError = (label) => new Error(
1483
+ `${label} was truncated before producing valid structured output. The model stopped due to output length. Increase maxOutputTokens or simplify the requested output.`
1484
+ );
1485
+ var createInvalidStructuredOutputError = (label) => new Error(
1486
+ `${label} did not produce valid structured output. The model returned text instead of the expected JSON payload. This often happens when output is truncated or the provider does not follow JSON mode. Increase maxOutputTokens or simplify the requested output.`
1487
+ );
1488
+ var parseReasonOutputWithSchema = (args) => {
1489
+ const candidate = resolveOutputCandidate(args.text);
1490
+ try {
1491
+ return parseWithSchema(args.schema, candidate, args.label);
1492
+ } catch (error) {
1493
+ if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
1494
+ throw createTruncatedStructuredOutputError(args.label);
1495
+ }
1496
+ if (typeof candidate === "string" && candidate.trim().length > 0) {
1497
+ throw createInvalidStructuredOutputError(args.label);
1498
+ }
1499
+ throw error;
1500
+ }
1501
+ };
1502
+ var parseInterruptFirstPassResult = (args) => {
1503
+ const candidate = resolveOutputCandidate(args.text);
1504
+ if (!isRecord22(candidate)) {
1505
+ if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
1506
+ throw createTruncatedStructuredOutputError("useReason output");
1507
+ }
1508
+ if (typeof candidate === "string" && candidate.trim().length > 0) {
1509
+ throw createInvalidStructuredOutputError("useReason output");
1510
+ }
1511
+ throw new Error(
1512
+ "useReason first pass with interrupt must return a JSON object."
1513
+ );
1514
+ }
1515
+ const mode = args.mode ?? "required";
1516
+ let interruptRequired = true;
1517
+ if (mode === "optional") {
1518
+ if (candidate.decision !== "continue" && candidate.decision !== "interrupt") {
1519
+ throw new Error(
1520
+ 'useReason optional interrupt first pass must include decision "continue" or "interrupt".'
1521
+ );
1522
+ }
1523
+ interruptRequired = candidate.decision === "interrupt";
1524
+ if (!interruptRequired) {
1525
+ const hasUnexpectedRequest = [
1526
+ candidate.interruptRequest,
1527
+ candidate.request,
1528
+ candidate.interrupt
1529
+ ].some((value) => value !== null && value !== void 0);
1530
+ if (hasUnexpectedRequest) {
1531
+ throw new Error(
1532
+ 'useReason optional interrupt first pass with decision "continue" must not include an interrupt request.'
1533
+ );
1534
+ }
1535
+ }
1536
+ }
1537
+ let request;
1538
+ if (interruptRequired) {
1539
+ const requestCandidates = [
1540
+ candidate.interruptRequest,
1541
+ candidate.request,
1542
+ candidate.interrupt,
1543
+ mode === "required" ? candidate : void 0
1544
+ ];
1545
+ const requestPayload = requestCandidates.find(
1546
+ (value) => value !== null && value !== void 0
1547
+ );
1548
+ request = parseWithSchema(
1549
+ args.requestSchema,
1550
+ requestPayload,
1551
+ "useReason interrupt.request"
1552
+ );
1553
+ }
1554
+ let output;
1555
+ if (args.outputSchema) {
1556
+ const outputPayload = Object.hasOwn(candidate, "output") ? candidate.output : candidate;
1557
+ try {
1558
+ output = parseWithSchema(
1559
+ args.outputSchema,
1560
+ outputPayload,
1561
+ "useReason output"
1562
+ );
1563
+ } catch (error) {
1564
+ if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
1565
+ throw createTruncatedStructuredOutputError("useReason output");
1566
+ }
1567
+ if (typeof outputPayload === "string" && outputPayload.trim().length > 0) {
1568
+ throw createInvalidStructuredOutputError("useReason output");
1569
+ }
1570
+ throw error;
1571
+ }
1572
+ }
1573
+ const explicitDraft = typeof candidate.draftText === "string" && candidate.draftText.length > 0 ? candidate.draftText : typeof candidate.text === "string" && candidate.text.length > 0 ? candidate.text : void 0;
1574
+ if (!args.outputSchema && !explicitDraft) {
1575
+ throw new Error(
1576
+ "useReason first pass with interrupt requires `draftText` when outputSchema is not provided."
1577
+ );
1578
+ }
1579
+ const draftText = explicitDraft ?? (output !== void 0 ? tryStringify(output) : String(args.text ?? ""));
1580
+ if (!interruptRequired) {
1581
+ return {
1582
+ draftText,
1583
+ interruptRequired: false,
1584
+ ...output !== void 0 ? { output } : {}
1585
+ };
1586
+ }
1587
+ return {
1588
+ draftText,
1589
+ interruptRequired: true,
1590
+ request,
1591
+ ...output !== void 0 ? { output } : {}
1592
+ };
1593
+ };
1594
+ var import_zod32 = require("zod");
1595
+ var tryStringify2 = (value) => {
1596
+ if (typeof value === "string") return value;
1597
+ try {
1598
+ return JSON.stringify(value, null, 2);
1599
+ } catch {
1600
+ return String(value);
1601
+ }
1602
+ };
1603
+ var tryGetSchemaHint = (schema) => {
1604
+ try {
1605
+ const hint = (0, import_zod32.toJSONSchema)(schema);
1606
+ return JSON.stringify(hint, null, 2);
1607
+ } catch {
1608
+ return void 0;
1609
+ }
1610
+ };
1611
+ var withSchemaHint = (input, label, schema) => {
1612
+ const hint = tryGetSchemaHint(schema);
1613
+ if (!hint) return input;
1614
+ return `${input}
1615
+
1616
+ ${label}:
1617
+ ${hint}`;
1618
+ };
1619
+ var defaultInterruptFirstPassInput = (args) => {
1620
+ const mode = args.mode ?? "required";
1621
+ const base = mode === "optional" ? args.outputSchema ? [
1622
+ args.input,
1623
+ "Output rules:",
1624
+ "- Return JSON only. No markdown fences.",
1625
+ "- Return an object with keys: decision, output, interruptRequest, draftText.",
1626
+ '- decision must be either "continue" or "interrupt".',
1627
+ '- Use decision "continue" when you can produce the final output without asking the user.',
1628
+ '- Use decision "interrupt" only when user input is needed before finalizing.',
1629
+ "- output must match the expected output JSON schema.",
1630
+ '- When decision is "interrupt", interruptRequest must match the expected interrupt request JSON schema and must be a non-null object.',
1631
+ '- When decision is "continue", interruptRequest must be null.',
1632
+ "- draftText should be a concise plain-language summary of output."
1633
+ ].join("\n\n") : [
1634
+ args.input,
1635
+ "Output rules:",
1636
+ "- Return JSON only. No markdown fences.",
1637
+ "- Return an object with keys: decision, draftText, interruptRequest.",
1638
+ '- decision must be either "continue" or "interrupt".',
1639
+ '- Use decision "continue" when you can produce the final answer without asking the user.',
1640
+ '- Use decision "interrupt" only when user input is needed before finalizing.',
1641
+ "- draftText should be the current answer text.",
1642
+ '- When decision is "interrupt", interruptRequest must match the expected interrupt request JSON schema and must be a non-null object.',
1643
+ '- When decision is "continue", interruptRequest must be null.'
1644
+ ].join("\n\n") : args.outputSchema ? [
1645
+ args.input,
1646
+ "Output rules:",
1647
+ "- Return JSON only. No markdown fences.",
1648
+ "- Return an object with keys: output, interruptRequest, draftText.",
1649
+ "- output must match the expected output JSON schema.",
1650
+ "- interruptRequest must match the expected interrupt request JSON schema.",
1651
+ "- interruptRequest must be a non-null object.",
1652
+ "- draftText should be a concise plain-language summary of output.",
1653
+ "- If uncertain, still return a valid interruptRequest with safe default options."
1654
+ ].join("\n\n") : [
1655
+ args.input,
1656
+ "Output rules:",
1657
+ "- Return JSON only. No markdown fences.",
1658
+ "- Return an object with keys: draftText, interruptRequest.",
1659
+ "- draftText should be the current draft answer text.",
1660
+ "- interruptRequest must match the expected interrupt request JSON schema.",
1661
+ "- interruptRequest must be a non-null object.",
1662
+ "- If uncertain, still return a valid interruptRequest with safe default options."
1663
+ ].join("\n\n");
1664
+ const withInterruptSchema = withSchemaHint(
1665
+ base,
1666
+ "Expected interrupt request JSON schema (for `interruptRequest`)",
1667
+ args.requestSchema
1668
+ );
1669
+ if (!args.outputSchema) return withInterruptSchema;
1670
+ const withOutputSchema = withSchemaHint(
1671
+ withInterruptSchema,
1672
+ "Expected output JSON schema (for `output`)",
1673
+ args.outputSchema
1674
+ );
1675
+ return [
1676
+ withOutputSchema,
1677
+ "JSON template:",
1678
+ mode === "optional" ? '{ "decision": "continue", "output": { ... }, "interruptRequest": null, "draftText": "..." }' : '{ "output": { ... }, "interruptRequest": { ... }, "draftText": "..." }'
1679
+ ].join("\n\n");
1680
+ };
1681
+ var defaultContinuationInput = (args) => [
1682
+ "You are continuing a paused reasoning task after user input.",
1683
+ "Update the previous result using the user response and return the final answer only.",
1684
+ "If the previous output was JSON, return JSON in the same schema.",
1685
+ "",
1686
+ `Original request:
1687
+ ${args.input}`,
1688
+ `Draft answer text:
1689
+ ${args.draftText}`,
1690
+ `Draft output object:
1691
+ ${tryStringify2(args.draftOutput ?? args.draftText)}`,
1692
+ `Interrupt request shown to user:
1693
+ ${tryStringify2(args.request)}`,
1694
+ `User response:
1695
+ ${tryStringify2(args.response)}`
1696
+ ].join("\n\n");
1697
+ var withOutputGuardrails = (input, schema) => {
1698
+ const base = [
1699
+ input,
1700
+ "Output rules:",
1701
+ "- Return only JSON, no markdown fences.",
1702
+ "- Ensure the response conforms to the expected output schema.",
1703
+ "- Do not include explanatory text outside the JSON object."
1704
+ ].join("\n\n");
1705
+ return withSchemaHint(base, "Expected output JSON schema", schema);
1706
+ };
1707
+ var withStructuredStreamHints = (input, args) => {
1708
+ const rules = [];
1709
+ for (const path of args.setFieldPaths ?? []) {
1710
+ rules.push(
1711
+ `- For the field path pattern \`${path}\`, write one stable final value and do not rewrite it after it is complete.`
1712
+ );
1713
+ }
1714
+ for (const path of args.appendFieldPaths ?? []) {
1715
+ rules.push(
1716
+ `- For the array field path pattern \`${path}\`, write items in final order and finish each item before starting the next.`
1717
+ );
1718
+ rules.push(
1719
+ `- Once an item in \`${path}\` is complete, do not rewrite or reorder earlier items.`
1720
+ );
1721
+ }
1722
+ for (const path of args.textDeltaFieldPaths ?? []) {
1723
+ rules.push(
1724
+ `- For the string field path pattern \`${path}\`, write the value progressively by appending text to the end.`
1725
+ );
1726
+ rules.push(
1727
+ `- Do not rewrite or replace the earlier prefix of \`${path}\` once emitted.`
1728
+ );
1729
+ }
1730
+ if (rules.length === 0) return input;
1731
+ return [
1732
+ input,
1733
+ "Streaming rules:",
1734
+ ...rules,
1735
+ "- Keep the overall response as valid JSON."
1736
+ ].join("\n\n");
1737
+ };
1738
+ var mergeUsage = (left, right) => {
1739
+ if (!left) return right;
1740
+ if (!right) return left;
1741
+ const sum = (a, b) => a === void 0 && b === void 0 ? void 0 : (a ?? 0) + (b ?? 0);
1742
+ const raw = left.raw || right.raw ? {
1743
+ ...left.raw ?? {},
1744
+ ...right.raw ?? {}
1745
+ } : void 0;
1746
+ const input = sum(left.input, right.input);
1747
+ const output = sum(left.output, right.output);
1748
+ const total = sum(left.total, right.total);
1749
+ const reasoning = sum(left.reasoning, right.reasoning);
1750
+ const cacheRead = sum(left.cacheRead, right.cacheRead);
1751
+ const cacheWrite = sum(left.cacheWrite, right.cacheWrite);
1752
+ const merged = {
1753
+ ...right.outputIncludesReasoning !== void 0 ? { outputIncludesReasoning: right.outputIncludesReasoning } : {},
1754
+ ...right.inputIncludesCacheWrite !== void 0 ? { inputIncludesCacheWrite: right.inputIncludesCacheWrite } : {},
1755
+ ...right.inputIncludesCacheRead !== void 0 ? { inputIncludesCacheRead: right.inputIncludesCacheRead } : {}
1756
+ };
1757
+ if (input !== void 0) merged.input = input;
1758
+ if (output !== void 0) merged.output = output;
1759
+ if (total !== void 0) merged.total = total;
1760
+ if (reasoning !== void 0) merged.reasoning = reasoning;
1761
+ if (cacheRead !== void 0) merged.cacheRead = cacheRead;
1762
+ if (cacheWrite !== void 0) merged.cacheWrite = cacheWrite;
1763
+ if (raw) merged.raw = raw;
1764
+ return merged;
1765
+ };
1766
+ var mergeWarnings = (left, right) => {
1767
+ if (!left?.length) return right;
1768
+ if (!right?.length) return left;
1769
+ const seen = /* @__PURE__ */ new Set();
1770
+ const merged = [];
1771
+ for (const warning of [...left, ...right]) {
1772
+ const key = JSON.stringify(warning);
1773
+ if (seen.has(key)) continue;
1774
+ seen.add(key);
1775
+ merged.push(warning);
1776
+ }
1777
+ return merged;
1778
+ };
1779
+ var mergeProviderMetadata = (left, right) => {
1780
+ if (!left) return right;
1781
+ if (!right) return left;
1782
+ return {
1783
+ ...left,
1784
+ ...right
1578
1785
  };
1579
1786
  };
1580
- var extractStreamingStringValues = (args) => {
1581
- return locateValueStarts(args).flatMap((match) => {
1582
- const current = extractStreamingStringValue({
1583
- text: args.text,
1584
- path: match.path
1585
- });
1586
- return current.found ? [
1587
- {
1588
- path: match.path,
1589
- complete: current.complete,
1590
- value: current.value
1591
- }
1592
- ] : [];
1787
+ var emitReasonStructuredOutput = (args) => {
1788
+ if (!args.emit || !shouldEmitStructured(args.structured)) return;
1789
+ emitStructuredData({
1790
+ dataType: args.structured?.dataType ?? "reason-output",
1791
+ data: args.output,
1792
+ kind: "final",
1793
+ ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
1794
+ ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
1795
+ ...args.id ? { id: args.id } : {},
1796
+ streamId: args.opId
1593
1797
  });
1594
1798
  };
1595
1799
  var import_core42 = require("@kortyx/core");
@@ -1644,13 +1848,16 @@ ${tryStringify2(args.response)}`
1644
1848
  { role: "user", content: String(args.input ?? "") }
1645
1849
  ];
1646
1850
  var runReasonToolLoop = async (args) => {
1647
- const { useReasonArgs, id, opId, traceSpan } = args;
1851
+ const { useReasonArgs, id, traceSpan, checkpointKey } = args;
1648
1852
  const ctx = getHookContext();
1649
1853
  const abortSignal = (0, import_core42.combineAbortSignals)(
1650
1854
  ctx.node.abortSignal,
1651
1855
  useReasonArgs.abortSignal,
1652
1856
  useReasonArgs.model.options?.abortSignal
1653
1857
  );
1858
+ const saved = ctx.currentNodeState.byKey[checkpointKey];
1859
+ const checkpoint = saved?.status === "tool_loop" ? saved : void 0;
1860
+ const opId = checkpoint?.opId ?? args.opId;
1654
1861
  const tools = useReasonArgs.tools ?? [];
1655
1862
  const toolByName = /* @__PURE__ */ new Map();
1656
1863
  let validationCompleted = false;
@@ -1676,20 +1883,43 @@ ${tryStringify2(args.response)}`
1676
1883
  }
1677
1884
  const toolDefinitions = toToolDefinitions(tools);
1678
1885
  const maxSteps = Math.max(1, useReasonArgs.toolExecution?.maxSteps ?? 3);
1679
- const messages = createInitialMessages({
1886
+ const messages = checkpoint?.messages ?? createInitialMessages({
1680
1887
  system: useReasonArgs.system,
1681
- input: useReasonArgs.input
1888
+ input: useReasonArgs.outputSchema ? withOutputGuardrails(useReasonArgs.input, useReasonArgs.outputSchema) : useReasonArgs.input
1682
1889
  });
1683
- const steps = [];
1684
- const allToolCalls = [];
1685
- const allToolResults = [];
1890
+ const steps = checkpoint?.steps ?? [];
1891
+ const allToolCalls = steps.flatMap(
1892
+ (step) => step.toolCalls
1893
+ );
1894
+ const allToolResults = steps.flatMap(
1895
+ (step) => step.toolResults
1896
+ );
1686
1897
  let finalText = "";
1687
1898
  let finalRaw;
1688
1899
  let finalOutput;
1689
- let aggregatedUsage;
1900
+ let aggregatedUsage = steps.reduce(
1901
+ (usage, step) => mergeUsage(usage, step.usage),
1902
+ void 0
1903
+ );
1690
1904
  let finalFinishReason;
1691
1905
  let aggregatedProviderMetadata;
1692
- let aggregatedWarnings;
1906
+ let aggregatedWarnings = steps.reduce(
1907
+ (warnings, step) => mergeWarnings(warnings, step.warnings),
1908
+ args.initialWarnings
1909
+ );
1910
+ let pending = checkpoint?.pending;
1911
+ const approvedCalls = checkpoint?.approvedCalls ?? [];
1912
+ const save = () => {
1913
+ ctx.currentNodeState.byKey[checkpointKey] = {
1914
+ status: "tool_loop",
1915
+ opId,
1916
+ messages,
1917
+ steps,
1918
+ approvedCalls,
1919
+ ...pending ? { pending } : {}
1920
+ };
1921
+ ctx.stateDirty = true;
1922
+ };
1693
1923
  const emitToolEvent = (event, payload) => {
1694
1924
  ctx.node.emit(event, {
1695
1925
  ...payload,
@@ -1699,26 +1929,43 @@ ${tryStringify2(args.response)}`
1699
1929
  });
1700
1930
  };
1701
1931
  try {
1702
- for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
1932
+ for (let stepIndex = pending ? steps.length - 1 : steps.length; stepIndex < maxSteps; stepIndex += 1) {
1703
1933
  (0, import_core42.throwIfExecutionAborted)(abortSignal);
1704
1934
  traceSpan?.addEvent?.("useReason.tool-step.start", {
1705
1935
  stepIndex,
1706
1936
  toolCount: toolDefinitions.length
1707
1937
  });
1708
- const step = await reasonEngine(
1938
+ const reused = Boolean(pending);
1939
+ const stream = useReasonArgs.stream ?? useReasonArgs.model.options?.streaming ?? true;
1940
+ const emit = useReasonArgs.emit ?? true;
1941
+ let textStarted = false;
1942
+ const structuredChunk = useReasonArgs.outputSchema && stream && emit && shouldStreamStructured(useReasonArgs.structured) ? createStructuredOutputStreamer(useReasonArgs, id, opId) : void 0;
1943
+ const step = pending ?? await reasonEngine(
1709
1944
  {
1710
1945
  ...useReasonArgs,
1711
1946
  tools: toolDefinitions,
1712
1947
  messages,
1713
1948
  emit: false,
1714
- stream: false
1949
+ stream,
1950
+ onTextChunk: (delta) => {
1951
+ if (structuredChunk) structuredChunk(delta);
1952
+ if (!emit || useReasonArgs.outputSchema || !delta) return;
1953
+ if (!textStarted) {
1954
+ emitToolEvent("text-start", {});
1955
+ textStarted = true;
1956
+ }
1957
+ emitToolEvent("text-delta", { delta });
1958
+ }
1715
1959
  },
1716
1960
  { ...id ? { id } : {}, opId }
1717
1961
  );
1962
+ if (textStarted) emitToolEvent("text-end", {});
1718
1963
  finalText = step.text;
1719
1964
  finalRaw = step.raw;
1720
- aggregatedUsage = mergeUsage(aggregatedUsage, step.usage);
1721
- accumulateTokenUsage(step.usage);
1965
+ if (!reused) {
1966
+ aggregatedUsage = mergeUsage(aggregatedUsage, step.usage);
1967
+ accumulateTokenUsage(step.usage);
1968
+ }
1722
1969
  finalFinishReason = step.finishReason;
1723
1970
  aggregatedProviderMetadata = mergeProviderMetadata(
1724
1971
  aggregatedProviderMetadata,
@@ -1726,32 +1973,35 @@ ${tryStringify2(args.response)}`
1726
1973
  );
1727
1974
  aggregatedWarnings = mergeWarnings(aggregatedWarnings, step.warnings);
1728
1975
  const toolCalls = step.toolCalls ?? [];
1729
- const toolResults = [];
1730
- steps.push({
1731
- stepIndex,
1732
- text: step.text,
1733
- toolCalls,
1734
- toolResults,
1735
- ...step.usage ? { usage: step.usage } : {},
1736
- ...step.finishReason ? { finishReason: step.finishReason } : {},
1737
- ...step.providerMetadata ? { providerMetadata: step.providerMetadata } : {},
1738
- ...step.warnings ? { warnings: step.warnings } : {}
1739
- });
1976
+ const toolResults = reused ? steps[stepIndex]?.toolResults ?? [] : [];
1977
+ if (!reused)
1978
+ steps.push({
1979
+ stepIndex,
1980
+ text: step.text,
1981
+ toolCalls,
1982
+ toolResults,
1983
+ ...step.usage ? { usage: step.usage } : {},
1984
+ ...step.finishReason ? { finishReason: step.finishReason } : {},
1985
+ ...step.providerMetadata ? { providerMetadata: step.providerMetadata } : {},
1986
+ ...step.warnings ? { warnings: step.warnings } : {}
1987
+ });
1740
1988
  if (toolCalls.length === 0) {
1741
- if ((useReasonArgs.emit ?? true) && step.text.length > 0) {
1742
- emitToolEvent("text-start", {});
1743
- emitToolEvent("text-delta", { delta: step.text });
1744
- emitToolEvent("text-end", {});
1745
- }
1746
1989
  break;
1747
1990
  }
1748
- allToolCalls.push(...toolCalls);
1749
- messages.push({
1750
- role: "assistant",
1751
- content: step.text,
1752
- toolCalls
1753
- });
1991
+ if (!reused) {
1992
+ allToolCalls.push(...toolCalls);
1993
+ messages.push({
1994
+ role: "assistant",
1995
+ content: step.text,
1996
+ toolCalls,
1997
+ ...step.continuation ? { continuation: step.continuation } : {}
1998
+ });
1999
+ }
2000
+ pending = step;
2001
+ save();
1754
2002
  for (const toolCall of toolCalls) {
2003
+ if (toolResults.some((result2) => result2.toolCallId === toolCall.id))
2004
+ continue;
1755
2005
  (0, import_core42.throwIfExecutionAborted)(abortSignal);
1756
2006
  const tool = toolByName.get(toolCall.name);
1757
2007
  if (!tool) {
@@ -1781,7 +2031,7 @@ ${tryStringify2(args.response)}`
1781
2031
  input: toolCall.input
1782
2032
  });
1783
2033
  }
1784
- if (shouldApproveTool) {
2034
+ if (shouldApproveTool && !approvedCalls.includes(toolCall.id)) {
1785
2035
  const approval = await awaitInterruptInternal({
1786
2036
  id: `tool:${toolCall.id}`,
1787
2037
  request: {
@@ -1816,8 +2066,18 @@ ${tryStringify2(args.response)}`
1816
2066
  isError: true
1817
2067
  });
1818
2068
  }
2069
+ messages.push({
2070
+ role: "tool",
2071
+ content: denied.content,
2072
+ toolCallId: denied.toolCallId,
2073
+ name: denied.name,
2074
+ isError: true
2075
+ });
2076
+ save();
1819
2077
  continue;
1820
2078
  }
2079
+ approvedCalls.push(toolCall.id);
2080
+ save();
1821
2081
  }
1822
2082
  try {
1823
2083
  (0, import_core42.throwIfExecutionAborted)(abortSignal);
@@ -1871,25 +2131,19 @@ ${tryStringify2(args.response)}`
1871
2131
  message: result2.content
1872
2132
  });
1873
2133
  }
2134
+ const completed = toolResults.at(-1);
2135
+ if (completed)
2136
+ messages.push({
2137
+ role: "tool",
2138
+ content: completed.content,
2139
+ toolCallId: completed.toolCallId,
2140
+ name: completed.name,
2141
+ ...completed.isError !== void 0 ? { isError: completed.isError } : {}
2142
+ });
2143
+ save();
1874
2144
  }
1875
- const currentStep = steps[steps.length - 1];
1876
- if (currentStep) {
1877
- steps[steps.length - 1] = {
1878
- ...currentStep,
1879
- toolResults
1880
- };
1881
- }
1882
- for (const result2 of toolResults) {
1883
- messages.push({
1884
- role: "tool",
1885
- content: result2.content,
1886
- toolCallId: result2.toolCallId,
1887
- name: result2.name,
1888
- ...result2.structuredContent !== void 0 ? { structuredContent: result2.structuredContent } : {},
1889
- ...result2.isError !== void 0 ? { isError: result2.isError } : {},
1890
- ...result2.raw !== void 0 ? { raw: result2.raw } : {}
1891
- });
1892
- }
2145
+ pending = void 0;
2146
+ save();
1893
2147
  }
1894
2148
  if (steps.length >= maxSteps && steps.at(-1)?.toolCalls.length) {
1895
2149
  throw new Error(
@@ -1943,6 +2197,8 @@ ${tryStringify2(args.response)}`
1943
2197
  output: useReasonArgs.telemetry?.output ?? finalOutput ?? finalText
1944
2198
  }
1945
2199
  });
2200
+ ctx.currentNodeState.byKey[checkpointKey] = { status: "completed", result };
2201
+ ctx.stateDirty = true;
1946
2202
  return result;
1947
2203
  } catch (error) {
1948
2204
  traceSpan?.fail?.(error, {
@@ -1976,7 +2232,15 @@ ${tryStringify2(args.response)}`
1976
2232
  args.model.options?.abortSignal
1977
2233
  );
1978
2234
  (0, import_core52.throwIfExecutionAborted)(abortSignal);
1979
- args = { ...args, abortSignal };
2235
+ const inferred = inferOutputFormat(
2236
+ args.outputSchema,
2237
+ args.responseFormat ?? args.model.options?.responseFormat
2238
+ );
2239
+ args = {
2240
+ ...args,
2241
+ abortSignal,
2242
+ ...!args.interrupt && inferred.responseFormat ? { responseFormat: inferred.responseFormat } : {}
2243
+ };
1980
2244
  const id = typeof args.id === "string" && args.id.length > 0 ? args.id : void 0;
1981
2245
  const opId = createRuntimeId2();
1982
2246
  const checkpointKey = resolveReasonCheckpointKey({
@@ -2020,6 +2284,8 @@ ${tryStringify2(args.response)}`
2020
2284
  if (args.tools?.length) {
2021
2285
  return runReasonToolLoop({
2022
2286
  useReasonArgs: args,
2287
+ checkpointKey,
2288
+ initialWarnings: inferred.warnings,
2023
2289
  ...id ? { id } : {},
2024
2290
  opId,
2025
2291
  ...traceSpan ? { traceSpan } : {}
@@ -2043,10 +2309,7 @@ ${tryStringify2(args.response)}`
2043
2309
  let aggregatedUsage;
2044
2310
  let finalFinishReason;
2045
2311
  let aggregatedProviderMetadata;
2046
- let aggregatedWarnings;
2047
- const emittedSetValues = /* @__PURE__ */ new Map();
2048
- const emittedAppendCounts = /* @__PURE__ */ new Map();
2049
- const emittedTextValues = /* @__PURE__ */ new Map();
2312
+ let aggregatedWarnings = inferred.warnings;
2050
2313
  if (existingCheckpoint) {
2051
2314
  firstText = existingCheckpoint.firstText;
2052
2315
  firstRaw = existingCheckpoint.firstRaw;
@@ -2068,78 +2331,7 @@ ${tryStringify2(args.response)}`
2068
2331
  emit: suppressTextStream ? false : args.emit,
2069
2332
  stream: useStructuredIncrementalStreaming ? true : suppressTextStream ? false : args.stream,
2070
2333
  ...useStructuredIncrementalStreaming ? {
2071
- onTextChunk: (delta) => {
2072
- firstText += delta;
2073
- finalText = firstText;
2074
- for (const fieldPath of setFieldPaths) {
2075
- const values = extractCompletedFieldValues({
2076
- text: firstText,
2077
- path: fieldPath
2078
- });
2079
- for (const { path, value } of values) {
2080
- const nextSerialized = JSON.stringify(value);
2081
- if (emittedSetValues.get(path) === nextSerialized) continue;
2082
- emittedSetValues.set(path, nextSerialized);
2083
- emitStructuredData({
2084
- kind: "set",
2085
- path,
2086
- value,
2087
- dataType: args.structured?.dataType ?? "reason-output",
2088
- ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
2089
- ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
2090
- ...id ? { id } : {},
2091
- streamId: opId
2092
- });
2093
- }
2094
- }
2095
- for (const fieldPath of appendFieldPaths) {
2096
- const groups = extractCompletedArrayItemGroups({
2097
- text: firstText,
2098
- path: fieldPath
2099
- });
2100
- for (const { path, items } of groups) {
2101
- const emittedAppendCount = emittedAppendCounts.get(path) ?? 0;
2102
- if (items.length <= emittedAppendCount) continue;
2103
- const nextItems = items.slice(emittedAppendCount);
2104
- emittedAppendCounts.set(path, items.length);
2105
- emitStructuredData({
2106
- kind: "append",
2107
- path,
2108
- items: nextItems,
2109
- dataType: args.structured?.dataType ?? "reason-output",
2110
- ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
2111
- ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
2112
- ...id ? { id } : {},
2113
- streamId: opId
2114
- });
2115
- }
2116
- }
2117
- for (const fieldPath of textDeltaFieldPaths) {
2118
- const values = extractStreamingStringValues({
2119
- text: firstText,
2120
- path: fieldPath
2121
- });
2122
- for (const { path, value } of values) {
2123
- const emittedTextValue = emittedTextValues.get(path) ?? "";
2124
- if (value.length <= emittedTextValue.length || !value.startsWith(emittedTextValue)) {
2125
- continue;
2126
- }
2127
- const nextDelta = value.slice(emittedTextValue.length);
2128
- emittedTextValues.set(path, value);
2129
- if (nextDelta.length === 0) continue;
2130
- emitStructuredData({
2131
- kind: "text-delta",
2132
- path,
2133
- delta: nextDelta,
2134
- dataType: args.structured?.dataType ?? "reason-output",
2135
- ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
2136
- ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
2137
- ...id ? { id } : {},
2138
- streamId: opId
2139
- });
2140
- }
2141
- }
2142
- }
2334
+ onTextChunk: createStructuredOutputStreamer(args, id, opId)
2143
2335
  } : {}
2144
2336
  },
2145
2337
  reasonMeta,
@@ -2259,6 +2451,7 @@ ${tryStringify2(args.response)}`
2259
2451
  const second = await reasonEngine(
2260
2452
  {
2261
2453
  ...args,
2454
+ ...inferred.responseFormat ? { responseFormat: inferred.responseFormat } : {},
2262
2455
  emit: suppressTextStream ? false : args.emit,
2263
2456
  stream: suppressTextStream ? false : args.stream
2264
2457
  },
@@ -2389,7 +2582,7 @@ ${tryStringify2(args.response)}`
2389
2582
  return [getValue(), setValue];
2390
2583
  }
2391
2584
  var import_node_crypto5 = require("crypto");
2392
- var import_core6 = require("@kortyx/core");
2585
+ var import_core62 = require("@kortyx/core");
2393
2586
  var import_node_crypto42 = require("crypto");
2394
2587
  function emitWorkflowCall2(telemetry, type, payload) {
2395
2588
  const base = telemetry?.correlation;
@@ -2472,7 +2665,7 @@ ${tryStringify2(args.response)}`
2472
2665
  }
2473
2666
  async function useWorkflow(args) {
2474
2667
  const ctx = getHookContext();
2475
- (0, import_core6.throwIfExecutionAborted)(ctx.node.abortSignal);
2668
+ (0, import_core62.throwIfExecutionAborted)(ctx.node.abortSignal);
2476
2669
  const service = ctx.node.callWorkflow;
2477
2670
  if (!service)
2478
2671
  throw new Error("useWorkflow requires an agent workflow registry.");
@@ -2577,9 +2770,9 @@ ${tryStringify2(args.response)}`
2577
2770
  ...record.interrupts.length ? { response: record.interrupts.at(-1)?.response } : {}
2578
2771
  });
2579
2772
  } catch (error) {
2580
- (0, import_core6.throwIfExecutionAborted)(ctx.node.abortSignal);
2581
- if ((0, import_core6.isExecutionCancelled)(error)) throw error;
2582
- if ((0, import_core6.isExecutionLimitReached)(error)) {
2773
+ (0, import_core62.throwIfExecutionAborted)(ctx.node.abortSignal);
2774
+ if ((0, import_core62.isExecutionCancelled)(error)) throw error;
2775
+ if ((0, import_core62.isExecutionLimitReached)(error)) {
2583
2776
  const child = error;
2584
2777
  if (child.__kortyxChildSnapshot)
2585
2778
  record.snapshot = child.__kortyxChildSnapshot;
@@ -2595,7 +2788,7 @@ ${tryStringify2(args.response)}`
2595
2788
  report("failed", { error: "Child workflow failed" });
2596
2789
  throw new WorkflowCallError(workflow, record.error);
2597
2790
  }
2598
- (0, import_core6.throwIfExecutionAborted)(ctx.node.abortSignal);
2791
+ (0, import_core62.throwIfExecutionAborted)(ctx.node.abortSignal);
2599
2792
  recordUsage(outcome.usage);
2600
2793
  if (outcome.status === "completed") {
2601
2794
  record.data = outcome.data;
@@ -2719,6 +2912,7 @@ function parseCheckpointRequestBody(value) {
2719
2912
  async function handleChatRequestBody(args) {
2720
2913
  const { agent, body } = args;
2721
2914
  const stream = await agent.streamChat(body.messages, {
2915
+ ...args.onExecution ? { onExecution: args.onExecution } : {},
2722
2916
  ...args.abortSignal ? { abortSignal: args.abortSignal } : {},
2723
2917
  sessionId: body.sessionId,
2724
2918
  workflowId: body.workflowId,
@@ -2742,7 +2936,8 @@ function createChatRouteHandler(args) {
2742
2936
  return await handleChatRequestBody({
2743
2937
  agent,
2744
2938
  body,
2745
- abortSignal: request.signal
2939
+ abortSignal: request.signal,
2940
+ onExecution: args.onExecution
2746
2941
  });
2747
2942
  } catch (error) {
2748
2943
  return new Response(
@@ -2809,23 +3004,23 @@ async function* streamChatFromRoute(args) {
2809
3004
 
2810
3005
  // src/chat/create-agent.ts
2811
3006
  var import_node_path = require("path");
2812
- var import_core4 = require("@kortyx/core");
3007
+ var import_core5 = require("@kortyx/core");
2813
3008
  var import_providers = require("@kortyx/providers");
2814
3009
  var import_runtime5 = require("@kortyx/runtime");
2815
- var import_zod4 = require("zod");
3010
+ var import_zod5 = require("zod");
2816
3011
 
2817
3012
  // src/execution/execute.ts
2818
- var import_core3 = require("@kortyx/core");
3013
+ var import_core4 = require("@kortyx/core");
2819
3014
  var import_runtime3 = require("@kortyx/runtime");
2820
3015
  var import_zod3 = require("zod");
2821
3016
 
2822
3017
  // src/interrupt/resume-handler.ts
2823
- var import_core2 = require("@kortyx/core");
3018
+ var import_core3 = require("@kortyx/core");
2824
3019
  var import_runtime2 = require("@kortyx/runtime");
2825
3020
 
2826
3021
  // src/orchestrator.ts
2827
3022
  var import_node_stream = require("stream");
2828
- var import_core = require("@kortyx/core");
3023
+ var import_core2 = require("@kortyx/core");
2829
3024
  var import_runtime = require("@kortyx/runtime");
2830
3025
  var import_langgraph = require("@langchain/langgraph");
2831
3026
 
@@ -2907,6 +3102,47 @@ async function validateExecutionOutput(state, select) {
2907
3102
  return { ...state, data };
2908
3103
  }
2909
3104
 
3105
+ // src/stream/response-lifecycle.ts
3106
+ var import_core = require("@kortyx/core");
3107
+ function createResponseLifecycle(args) {
3108
+ const controller = new AbortController();
3109
+ let closed = args.restored;
3110
+ let completing;
3111
+ const abort = () => controller.abort();
3112
+ const detachRequest = () => args.requestSignal?.removeEventListener("abort", abort);
3113
+ if (!closed) {
3114
+ args.requestSignal?.addEventListener("abort", abort, { once: true });
3115
+ if (args.requestSignal?.aborted) abort();
3116
+ }
3117
+ args.executionSignal?.addEventListener("abort", abort, { once: true });
3118
+ if (args.executionSignal?.aborted) abort();
3119
+ return {
3120
+ get closed() {
3121
+ return closed;
3122
+ },
3123
+ signal: controller.signal,
3124
+ disconnect() {
3125
+ if (!closed) abort();
3126
+ },
3127
+ dispose() {
3128
+ detachRequest();
3129
+ args.executionSignal?.removeEventListener("abort", abort);
3130
+ },
3131
+ complete(options, state) {
3132
+ if (!args.enabled || closed) return Promise.resolve();
3133
+ completing ??= (async () => {
3134
+ (0, import_core.throwIfExecutionAborted)(controller.signal);
3135
+ await args.finalize(options, state);
3136
+ (0, import_core.throwIfExecutionAborted)(controller.signal);
3137
+ closed = true;
3138
+ detachRequest();
3139
+ args.onClosed();
3140
+ })();
3141
+ return completing;
3142
+ }
3143
+ };
3144
+ }
3145
+
2910
3146
  // src/stream/transform-graph-stream-for-ui.ts
2911
3147
  var isGraphStateOutput = (value) => value !== null && typeof value === "object" && !Array.isArray(value) && "currentWorkflow" in value;
2912
3148
  async function* transformGraphStreamForUI(stream, options = {}) {
@@ -3263,6 +3499,8 @@ async function orchestrateGraphStream({
3263
3499
  state,
3264
3500
  config: initialConfig,
3265
3501
  abortSignal: requestSignal,
3502
+ executionSignal,
3503
+ onExecution,
3266
3504
  emitOutput = true,
3267
3505
  onOutcome,
3268
3506
  selectWorkflow,
@@ -3271,20 +3509,88 @@ async function orchestrateGraphStream({
3271
3509
  }) {
3272
3510
  const out = new import_node_stream.PassThrough({ objectMode: true });
3273
3511
  const write = (chunk) => {
3274
- if (emitOutput && !out.destroyed) out.write(chunk);
3512
+ if (emitOutput && !response.closed && !out.destroyed) out.write(chunk);
3275
3513
  };
3276
3514
  let outcomeState = state;
3277
3515
  let outcomeError;
3278
- let sessionCheckpointId;
3516
+ let sessionCheckpointId = initialConfig.responseCheckpointId;
3279
3517
  let config = initialConfig;
3280
3518
  const budget = config.executionBudget;
3281
3519
  let currentGraph = graph;
3282
3520
  let currentState = state;
3283
3521
  let finished = false;
3284
- const controller = new AbortController();
3285
- const abortSignal = (0, import_core.combineAbortSignals)(requestSignal, controller.signal);
3522
+ const response = createResponseLifecycle({
3523
+ enabled: emitOutput,
3524
+ restored: initialConfig.responseCompleted === true,
3525
+ requestSignal,
3526
+ executionSignal,
3527
+ onClosed: () => {
3528
+ out.write({ type: "done" });
3529
+ out.end();
3530
+ },
3531
+ finalize: async (options, nodeState) => {
3532
+ const snapshot = JSON.parse(JSON.stringify(nodeState));
3533
+ delete snapshot.config.responseCompleted;
3534
+ delete snapshot.config.responseCheckpointId;
3535
+ if (options.message !== void 0) {
3536
+ forwardEmit("message", {
3537
+ node: nodeState.lastNode,
3538
+ content: options.message
3539
+ });
3540
+ }
3541
+ if (options.data !== void 0) {
3542
+ forwardEmit("structured_data", {
3543
+ node: nodeState.lastNode,
3544
+ data: options.data
3545
+ });
3546
+ }
3547
+ if (sessionId && frameworkAdapter) {
3548
+ const checkpoint = await frameworkAdapter.sessionCheckpoints.append({
3549
+ sessionId,
3550
+ runId,
3551
+ workflow: String(snapshot.currentWorkflow),
3552
+ state: snapshot,
3553
+ nodes: [...touchedNodes],
3554
+ structuredStreamIds: [...structuredStreamIds],
3555
+ pendingRequests: []
3556
+ });
3557
+ sessionCheckpointId = checkpoint.id;
3558
+ write({
3559
+ type: "checkpoint",
3560
+ id: checkpoint.id,
3561
+ sessionId,
3562
+ turnIndex: checkpoint.turnIndex
3563
+ });
3564
+ emitTelemetryEvent({
3565
+ config,
3566
+ type: "session.checkpointed",
3567
+ payload: {
3568
+ checkpointId: checkpoint.id,
3569
+ turnIndex: checkpoint.turnIndex,
3570
+ nodes: checkpoint.nodes
3571
+ }
3572
+ });
3573
+ }
3574
+ (0, import_core2.throwIfExecutionAborted)(abortSignal);
3575
+ config.responseCompleted = true;
3576
+ config.responseCheckpointId = sessionCheckpointId;
3577
+ currentState.config.responseCompleted = true;
3578
+ nodeState.config.responseCompleted = true;
3579
+ currentGraph.config.responseCompleted = true;
3580
+ emitTelemetryEvent({
3581
+ config,
3582
+ type: "response.completed",
3583
+ payload: {
3584
+ nodeId: nodeState.lastNode,
3585
+ checkpointId: sessionCheckpointId
3586
+ },
3587
+ flush: true
3588
+ });
3589
+ }
3590
+ });
3591
+ const abortSignal = response.signal;
3286
3592
  out.on("close", () => {
3287
- if (!finished) controller.abort();
3593
+ if (!finished) response.disconnect();
3288
3594
  });
3289
3595
  let structuredSeq = 0;
3290
3596
  const debugEnabled = Boolean(config?.features?.tracing);
@@ -3370,19 +3676,22 @@ async function orchestrateGraphStream({
3370
3676
  const pendingStore = frameworkAdapter?.pendingRequests;
3371
3677
  const sealPendingSnapshots = async () => {
3372
3678
  if (budget) outcomeState.config.executionBudget = budget;
3679
+ if (response.closed) outcomeState.config.responseCompleted = true;
3373
3680
  if (typeof frameworkAdapter?.checkpointer?.getTuple !== "function") return;
3374
3681
  for (const [token, request] of activePendingRequests) {
3375
3682
  if (budget) request.state.config.executionBudget = budget;
3683
+ if (response.closed) request.state.config.responseCompleted = true;
3376
3684
  const graphSnapshot = await (0, import_runtime.captureGraphSnapshot)(
3377
3685
  frameworkAdapter.checkpointer,
3378
3686
  runId,
3379
3687
  request.graphCheckpointId
3380
3688
  );
3381
3689
  if (graphSnapshot) {
3382
- const sealed = { ...request, graphSnapshot };
3690
+ const sealed = { ...request, graphSnapshot, ready: true };
3383
3691
  activePendingRequests.set(token, sealed);
3384
3692
  await pendingStore?.update(token, {
3385
3693
  graphSnapshot,
3694
+ ready: true,
3386
3695
  state: request.state
3387
3696
  });
3388
3697
  }
@@ -3400,6 +3709,8 @@ async function orchestrateGraphStream({
3400
3709
  const isText = kind === "text";
3401
3710
  const record = {
3402
3711
  token,
3712
+ ready: false,
3713
+ responseCompleted: response.closed,
3403
3714
  requestId,
3404
3715
  sessionId,
3405
3716
  runId,
@@ -3478,6 +3789,7 @@ async function orchestrateGraphStream({
3478
3789
  workflowCallPath: record.schema.meta?.workflowCallPath ?? null,
3479
3790
  branchId: config.executionBranchId ?? runId,
3480
3791
  interruptId: record.requestId,
3792
+ responseCompleted: response.closed,
3481
3793
  requestId: record.requestId,
3482
3794
  kind: record.schema.kind,
3483
3795
  interactionMode: record.schema.kind === "text" ? "freeform" : record.options.length > 0 ? "static-options" : record.schema.schemaId ? "dynamic-picker" : "unknown",
@@ -3687,7 +3999,7 @@ async function orchestrateGraphStream({
3687
3999
  };
3688
4000
  const runLoop = async (runTraceSpan) => {
3689
4001
  while (true) {
3690
- (0, import_core.throwIfExecutionAborted)(abortSignal);
4002
+ (0, import_core2.throwIfExecutionAborted)(abortSignal);
3691
4003
  let workflowFinalState = null;
3692
4004
  let workflowFinalGraphCheckpointId;
3693
4005
  let sawWorkflowDone = false;
@@ -3695,6 +4007,7 @@ async function orchestrateGraphStream({
3695
4007
  currentGraph.execution ??= {};
3696
4008
  currentGraph.execution.abortSignal = abortSignal;
3697
4009
  currentGraph.execution.budget = budget;
4010
+ currentGraph.execution.completeResponse = response.complete;
3698
4011
  currentGraph.config = currentGraph.config || {};
3699
4012
  currentGraph.config.emit = forwardEmit;
3700
4013
  currentGraph.config.executionRunId = runId;
@@ -3792,7 +4105,7 @@ async function orchestrateGraphStream({
3792
4105
  workflowFinalGraphCheckpointId = graphSnapshot?.checkpointId;
3793
4106
  workflowFinalState = isGraphState(workflowDoneData) ? workflowDoneData : graphSnapshot?.state ?? currentState;
3794
4107
  }
3795
- (0, import_core.throwIfExecutionAborted)(abortSignal);
4108
+ (0, import_core2.throwIfExecutionAborted)(abortSignal);
3796
4109
  const transitionTo = pending.to;
3797
4110
  const transitionPayload = pending.payload;
3798
4111
  const sourceNodeId = pending.sourceNodeId;
@@ -3857,8 +4170,8 @@ async function orchestrateGraphStream({
3857
4170
  currentGraph = nextGraph;
3858
4171
  continue;
3859
4172
  } catch (err) {
3860
- (0, import_core.throwIfExecutionAborted)(abortSignal);
3861
- if ((0, import_core.isExecutionCancelled)(err)) throw err;
4173
+ (0, import_core2.throwIfExecutionAborted)(abortSignal);
4174
+ if ((0, import_core2.isExecutionCancelled)(err)) throw err;
3862
4175
  outcomeError = err;
3863
4176
  runTraceSpan.fail?.(err);
3864
4177
  runTraceSpan.addEvent?.("kortyx.transition.error", {
@@ -3932,7 +4245,7 @@ async function orchestrateGraphStream({
3932
4245
  runTraceSpan.end?.(runTraceEndArgs());
3933
4246
  const resolvedSessionId2 = config?.session?.id || sessionId || "";
3934
4247
  await sealPendingSnapshots();
3935
- if (resolvedSessionId2 && frameworkAdapter?.sessionCheckpoints) {
4248
+ if (!response.closed && resolvedSessionId2 && frameworkAdapter?.sessionCheckpoints) {
3936
4249
  try {
3937
4250
  const checkpoint = await frameworkAdapter.sessionCheckpoints.append(
3938
4251
  {
@@ -4003,7 +4316,7 @@ async function orchestrateGraphStream({
4003
4316
  }
4004
4317
  const resolvedSessionId = config?.session?.id || sessionId || "";
4005
4318
  await sealPendingSnapshots();
4006
- if (resolvedSessionId && frameworkAdapter?.sessionCheckpoints) {
4319
+ if (!response.closed && resolvedSessionId && frameworkAdapter?.sessionCheckpoints) {
4007
4320
  try {
4008
4321
  const checkpoint = await frameworkAdapter.sessionCheckpoints.append({
4009
4322
  sessionId: resolvedSessionId,
@@ -4087,7 +4400,7 @@ async function orchestrateGraphStream({
4087
4400
  } catch (cleanupError) {
4088
4401
  console.error("[cancel:cleanupRun]", cleanupError);
4089
4402
  }
4090
- outcomeError = (0, import_core.createExecutionCancelledError)();
4403
+ outcomeError = (0, import_core2.createExecutionCancelledError)();
4091
4404
  fallbackRunSpan?.setAttributes?.({ "kortyx.run.cancelled": true });
4092
4405
  fallbackRunSpan?.end?.();
4093
4406
  write({ type: "cancelled", runId, reason: "Execution cancelled." });
@@ -4095,15 +4408,15 @@ async function orchestrateGraphStream({
4095
4408
  finished = true;
4096
4409
  out.end();
4097
4410
  };
4098
- runPromise.catch(async (err) => {
4099
- if (abortSignal?.aborted || (0, import_core.isExecutionCancelled)(err)) {
4411
+ const completion = runPromise.catch(async (err) => {
4412
+ if (abortSignal?.aborted || (0, import_core2.isExecutionCancelled)(err)) {
4100
4413
  await finishCancelled();
4101
4414
  return;
4102
4415
  }
4103
- if ((0, import_core.isExecutionLimitReached)(err)) {
4416
+ if ((0, import_core2.isExecutionLimitReached)(err)) {
4104
4417
  try {
4105
4418
  const snapshot = frameworkAdapter && await (0, import_runtime.captureGraphSnapshot)(frameworkAdapter.checkpointer, runId);
4106
- (0, import_core.throwIfExecutionAborted)(abortSignal);
4419
+ (0, import_core2.throwIfExecutionAborted)(abortSignal);
4107
4420
  if (!snapshot)
4108
4421
  throw new Error(
4109
4422
  "Execution limit reached without a resumable checkpoint."
@@ -4145,46 +4458,50 @@ async function orchestrateGraphStream({
4145
4458
  }
4146
4459
  });
4147
4460
  await Promise.all(pendingRequestWrites);
4148
- (0, import_core.throwIfExecutionAborted)(abortSignal);
4461
+ (0, import_core2.throwIfExecutionAborted)(abortSignal);
4149
4462
  if (outcomeError) throw outcomeError;
4150
4463
  const request = activePendingRequests.get(pendingRecordToken);
4151
4464
  const sealed = {
4152
4465
  ...request,
4153
4466
  state: pausedState,
4154
4467
  graphCheckpointId: snapshot.checkpoint.id,
4155
- graphSnapshot: snapshot
4468
+ graphSnapshot: snapshot,
4469
+ ready: true
4156
4470
  };
4157
4471
  activePendingRequests.set(request.token, sealed);
4158
4472
  await pendingStore?.update(request.token, sealed);
4159
- const checkpoint = await frameworkAdapter.sessionCheckpoints.append({
4160
- sessionId,
4161
- runId,
4162
- graphCheckpointId: snapshot.checkpoint.id,
4163
- workflow: String(pausedState.currentWorkflow),
4164
- state: pausedState,
4165
- nodes: Array.from(touchedNodes),
4166
- structuredStreamIds: Array.from(structuredStreamIds),
4167
- pendingRequests: [sealed],
4168
- label: "Execution limit reached"
4169
- });
4170
- (0, import_core.throwIfExecutionAborted)(abortSignal);
4171
- sessionCheckpointId = checkpoint.id;
4172
- write({
4173
- type: "checkpoint",
4174
- id: checkpoint.id,
4175
- sessionId: checkpoint.sessionId,
4176
- turnIndex: checkpoint.turnIndex,
4177
- label: checkpoint.label
4178
- });
4179
- emitTelemetryEvent({
4180
- config,
4181
- type: "session.checkpointed",
4182
- payload: {
4183
- checkpointId: checkpoint.id,
4473
+ if (response.closed) pausedState.config.responseCompleted = true;
4474
+ if (!response.closed) {
4475
+ const checkpoint = await frameworkAdapter.sessionCheckpoints.append({
4476
+ sessionId,
4477
+ runId,
4478
+ graphCheckpointId: snapshot.checkpoint.id,
4479
+ workflow: String(pausedState.currentWorkflow),
4480
+ state: pausedState,
4481
+ nodes: Array.from(touchedNodes),
4482
+ structuredStreamIds: Array.from(structuredStreamIds),
4483
+ pendingRequests: [sealed],
4484
+ label: "Execution limit reached"
4485
+ });
4486
+ (0, import_core2.throwIfExecutionAborted)(abortSignal);
4487
+ sessionCheckpointId = checkpoint.id;
4488
+ write({
4489
+ type: "checkpoint",
4490
+ id: checkpoint.id,
4491
+ sessionId: checkpoint.sessionId,
4184
4492
  turnIndex: checkpoint.turnIndex,
4185
- nodes: checkpoint.nodes
4186
- }
4187
- });
4493
+ label: checkpoint.label
4494
+ });
4495
+ emitTelemetryEvent({
4496
+ config,
4497
+ type: "session.checkpointed",
4498
+ payload: {
4499
+ checkpointId: checkpoint.id,
4500
+ turnIndex: checkpoint.turnIndex,
4501
+ nodes: checkpoint.nodes
4502
+ }
4503
+ });
4504
+ }
4188
4505
  fallbackRunSpan?.setAttributes?.({
4189
4506
  "kortyx.run.awaiting_human_input": true,
4190
4507
  "kortyx.run.limit_reached": limitReached.limit
@@ -4195,7 +4512,7 @@ async function orchestrateGraphStream({
4195
4512
  out.end();
4196
4513
  return;
4197
4514
  } catch (pauseError) {
4198
- if (abortSignal?.aborted || (0, import_core.isExecutionCancelled)(pauseError)) {
4515
+ if (abortSignal?.aborted || (0, import_core2.isExecutionCancelled)(pauseError)) {
4199
4516
  await finishCancelled();
4200
4517
  return;
4201
4518
  }
@@ -4220,6 +4537,7 @@ async function orchestrateGraphStream({
4220
4537
  finished = true;
4221
4538
  out.end();
4222
4539
  }).finally(() => {
4540
+ response.dispose();
4223
4541
  onOutcome?.({
4224
4542
  state: outcomeState,
4225
4543
  pending: pendingRecordToken ? activePendingRequests.get(pendingRecordToken) : void 0,
@@ -4227,6 +4545,8 @@ async function orchestrateGraphStream({
4227
4545
  ...outcomeError ? { error: outcomeError } : {}
4228
4546
  });
4229
4547
  });
4548
+ onExecution?.(completion);
4549
+ if (response.closed) out.end();
4230
4550
  return out;
4231
4551
  }
4232
4552
 
@@ -4258,6 +4578,8 @@ function parseResumeMeta(msg) {
4258
4578
  async function tryPrepareResumeStream({
4259
4579
  limits,
4260
4580
  abortSignal,
4581
+ executionSignal,
4582
+ onExecution,
4261
4583
  lastMessage,
4262
4584
  meta: suppliedMeta,
4263
4585
  emitOutput,
@@ -4286,11 +4608,11 @@ async function tryPrepareResumeStream({
4286
4608
  await validatePending?.(pending);
4287
4609
  const isLimitPause = Boolean(pending.schema.meta?.__kortyxExecutionLimit);
4288
4610
  const savedBudget = pending.state?.config?.executionBudget;
4289
- const resumedBudget = savedBudget && isLimitPause && !meta.cancel ? (0, import_core2.restartExecutionBudget)(savedBudget, limits) : savedBudget;
4611
+ const resumedBudget = savedBudget && isLimitPause && !meta.cancel ? (0, import_core3.restartExecutionBudget)(savedBudget, limits) : savedBudget;
4290
4612
  if (abortSignal?.aborted) {
4291
4613
  onOutcome?.({
4292
4614
  state: pending.state,
4293
- error: (0, import_core2.createExecutionCancelledError)()
4615
+ error: (0, import_core3.createExecutionCancelledError)()
4294
4616
  });
4295
4617
  return (async function* () {
4296
4618
  yield {
@@ -4351,6 +4673,10 @@ async function tryPrepareResumeStream({
4351
4673
  const telemetryConfig = prepareWorkflowTelemetry({
4352
4674
  config: {
4353
4675
  ...config,
4676
+ ...pending.responseCompleted ? {
4677
+ responseCompleted: true,
4678
+ responseCheckpointId: pending.state?.config?.responseCheckpointId
4679
+ } : {},
4354
4680
  ...resumedBudget ? { executionBudget: resumedBudget } : {},
4355
4681
  ...pending.state?.config?.context ? { context: pending.state.config.context } : {},
4356
4682
  ...pending.state?.config?.executionContract ? { executionContract: pending.state.config.executionContract } : {},
@@ -4427,6 +4753,8 @@ async function tryPrepareResumeStream({
4427
4753
  });
4428
4754
  const args = {
4429
4755
  abortSignal,
4756
+ executionSignal: pending.responseCompleted && emitOutput === false ? abortSignal : executionSignal,
4757
+ onExecution,
4430
4758
  emitOutput,
4431
4759
  onOutcome,
4432
4760
  sessionId,
@@ -4470,14 +4798,14 @@ var responseSchema = import_zod3.z.discriminatedUnion("type", [
4470
4798
  import_zod3.z.object({ type: import_zod3.z.literal("cancel") }).strict()
4471
4799
  ]);
4472
4800
  function resultFromOutcome(outcome, runId, sessionId) {
4473
- const usage = outcome.pending?.schema.meta?.__kortyxResumeStatePatch?.tokenUsage ?? outcome.state.runtime?.tokenUsage;
4801
+ const usage = outcome.pending?.schema.meta?.__kortyxResumeStatePatch?.tokenUsage ?? outcome.error?.__kortyxHookStatePatch?.tokenUsage ?? outcome.state.runtime?.tokenUsage;
4474
4802
  const info = {
4475
4803
  runId,
4476
4804
  sessionId,
4477
4805
  ...outcome.checkpointId ? { checkpointId: outcome.checkpointId } : {},
4478
4806
  ...usage ? { usage } : {}
4479
4807
  };
4480
- if ((0, import_core3.isExecutionCancelled)(outcome.error))
4808
+ if ((0, import_core4.isExecutionCancelled)(outcome.error))
4481
4809
  return { ...info, status: "cancelled", reason: "Execution cancelled." };
4482
4810
  if (outcome.error) {
4483
4811
  const error = outcome.error;
@@ -4554,7 +4882,7 @@ async function executeWorkflow(services, args) {
4554
4882
  config: {
4555
4883
  ...runtimeConfig(services, sessionId, args.context),
4556
4884
  ...services.limits || args.limits ? {
4557
- executionBudget: (0, import_core3.createExecutionBudget)(
4885
+ executionBudget: (0, import_core4.createExecutionBudget)(
4558
4886
  services.limits,
4559
4887
  args.limits
4560
4888
  )
@@ -4575,7 +4903,7 @@ async function executeWorkflow(services, args) {
4575
4903
  return new Promise((resolve2) => {
4576
4904
  const onOutcome = (outcome) => resolve2(resultFromOutcome(outcome, runId, sessionId));
4577
4905
  void (async () => {
4578
- (0, import_core3.throwIfExecutionAborted)(args.abortSignal);
4906
+ (0, import_core4.throwIfExecutionAborted)(args.abortSignal);
4579
4907
  const graph = await (0, import_runtime3.createExecutionGraph)(workflow, config);
4580
4908
  await orchestrateGraphStream({
4581
4909
  graph,
@@ -4670,6 +4998,98 @@ async function resumeWorkflow(services, args) {
4670
4998
  });
4671
4999
  }
4672
5000
 
5001
+ // src/interrupt/discovery.ts
5002
+ var import_zod4 = require("zod");
5003
+ var scopeSchema = import_zod4.z.object({
5004
+ sessionId: import_zod4.z.string().min(1).optional(),
5005
+ runId: import_zod4.z.string().min(1).optional(),
5006
+ context: import_zod4.z.record(
5007
+ import_zod4.z.string(),
5008
+ import_zod4.z.union([import_zod4.z.string(), import_zod4.z.number(), import_zod4.z.boolean(), import_zod4.z.null()])
5009
+ ).optional()
5010
+ }).strict().refine(
5011
+ (scope) => Boolean(
5012
+ scope.sessionId || scope.runId || Object.keys(scope.context ?? {}).length
5013
+ ),
5014
+ "Interrupt discovery requires an authorized session, run, or context scope."
5015
+ );
5016
+ 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(
5017
+ ([key, value]) => record.state?.config.context?.[key] === value
5018
+ );
5019
+ var summary = (record) => {
5020
+ const { meta: _meta, ...input } = record.schema;
5021
+ const contract = record.state?.config.executionContract;
5022
+ return {
5023
+ id: record.requestId,
5024
+ status: "pending",
5025
+ runId: record.runId,
5026
+ sessionId: record.sessionId ?? "",
5027
+ workflow: contract?.id ?? record.workflow,
5028
+ node: record.node,
5029
+ createdAt: new Date(record.createdAt).toISOString(),
5030
+ expiresAt: new Date(record.createdAt + record.ttlMs).toISOString(),
5031
+ afterResponseCompleted: record.responseCompleted === true,
5032
+ input: {
5033
+ ...input,
5034
+ options: record.options.map(({ id, label, description }) => ({
5035
+ id,
5036
+ label,
5037
+ ...description !== void 0 ? { description } : {}
5038
+ }))
5039
+ }
5040
+ };
5041
+ };
5042
+ function createInterruptDiscovery(store) {
5043
+ const records = () => {
5044
+ if (!store.list)
5045
+ throw new Error(
5046
+ "This persistence adapter does not support interrupt discovery; implement pendingRequests.list."
5047
+ );
5048
+ return store.list();
5049
+ };
5050
+ return {
5051
+ async listInterrupts(options) {
5052
+ const {
5053
+ status: _status,
5054
+ afterResponseCompleted,
5055
+ ...scope
5056
+ } = import_zod4.z.object({
5057
+ sessionId: import_zod4.z.string().optional(),
5058
+ runId: import_zod4.z.string().optional(),
5059
+ context: import_zod4.z.record(
5060
+ import_zod4.z.string(),
5061
+ import_zod4.z.union([import_zod4.z.string(), import_zod4.z.number(), import_zod4.z.boolean(), import_zod4.z.null()])
5062
+ ).optional(),
5063
+ status: import_zod4.z.literal("pending").optional(),
5064
+ afterResponseCompleted: import_zod4.z.boolean().optional()
5065
+ }).strict().parse(options);
5066
+ scopeSchema.parse(scope);
5067
+ return (await records()).filter(
5068
+ (record) => matches(record, scope) && (afterResponseCompleted === void 0 || record.responseCompleted === true === afterResponseCompleted)
5069
+ ).sort(
5070
+ (a, b) => a.createdAt - b.createdAt || a.requestId.localeCompare(b.requestId)
5071
+ ).map(summary);
5072
+ },
5073
+ async getInterrupt(id, scope) {
5074
+ import_zod4.z.string().min(1).parse(id);
5075
+ scopeSchema.parse(scope);
5076
+ const record = (await records()).find(
5077
+ (record2) => record2.requestId === id && matches(record2, scope)
5078
+ );
5079
+ if (!record) return null;
5080
+ return {
5081
+ ...summary(record),
5082
+ resume: {
5083
+ token: record.token,
5084
+ requestId: record.requestId,
5085
+ sessionId: record.sessionId ?? "",
5086
+ runId: record.runId
5087
+ }
5088
+ };
5089
+ }
5090
+ };
5091
+ }
5092
+
4673
5093
  // src/telemetry/workflow-call-branch.ts
4674
5094
  var import_node_crypto3 = require("crypto");
4675
5095
  var import_hooks = __toESM(require_dist());
@@ -4742,6 +5162,8 @@ function extractLatestUserMessage(messages) {
4742
5162
  async function streamChat({
4743
5163
  messages,
4744
5164
  abortSignal,
5165
+ executionSignal,
5166
+ onExecution,
4745
5167
  options,
4746
5168
  sessionId,
4747
5169
  defaultWorkflowId,
@@ -4789,6 +5211,8 @@ async function streamChat({
4789
5211
  }
4790
5212
  const resumeStream = await tryPrepareResumeStream({
4791
5213
  abortSignal,
5214
+ executionSignal,
5215
+ onExecution,
4792
5216
  lastMessage: last,
4793
5217
  sessionId: resolvedSessionId,
4794
5218
  config: runtimeConfig2,
@@ -4860,6 +5284,8 @@ async function streamChat({
4860
5284
  };
4861
5285
  const orchestratedStream = await orchestrateGraphStream({
4862
5286
  abortSignal,
5287
+ executionSignal,
5288
+ onExecution,
4863
5289
  sessionId: resolvedSessionId,
4864
5290
  runId,
4865
5291
  graph,
@@ -4873,22 +5299,26 @@ async function streamChat({
4873
5299
  }
4874
5300
 
4875
5301
  // src/chat/create-agent.ts
4876
- var agentProcessOptionsSchema = import_zod4.z.object({
4877
- limits: import_core4.ExecutionLimitsSchema.optional(),
4878
- abortSignal: import_zod4.z.custom((value) => value instanceof AbortSignal).optional(),
4879
- sessionId: import_zod4.z.string().optional(),
4880
- workflowId: import_zod4.z.string().optional(),
4881
- context: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional()
5302
+ var agentProcessOptionsSchema = import_zod5.z.object({
5303
+ executionSignal: import_zod5.z.custom((value) => value instanceof AbortSignal).optional(),
5304
+ onExecution: import_zod5.z.custom(
5305
+ (value) => typeof value === "function"
5306
+ ).optional(),
5307
+ limits: import_core5.ExecutionLimitsSchema.optional(),
5308
+ abortSignal: import_zod5.z.custom((value) => value instanceof AbortSignal).optional(),
5309
+ sessionId: import_zod5.z.string().optional(),
5310
+ workflowId: import_zod5.z.string().optional(),
5311
+ context: import_zod5.z.record(import_zod5.z.string(), import_zod5.z.unknown()).optional()
4882
5312
  }).strict();
4883
- var createAgentArgsBaseSchema = import_zod4.z.object({
4884
- limits: import_core4.ExecutionLimitsSchema.optional(),
4885
- getProvider: import_zod4.z.unknown().optional(),
4886
- workflows: import_zod4.z.array(import_zod4.z.unknown()).optional(),
4887
- workflowsDir: import_zod4.z.string().optional(),
4888
- workflowRegistry: import_zod4.z.unknown().optional(),
4889
- defaultWorkflowId: import_zod4.z.string().optional(),
4890
- frameworkAdapter: import_zod4.z.unknown().optional(),
4891
- telemetry: import_zod4.z.unknown().optional()
5313
+ var createAgentArgsBaseSchema = import_zod5.z.object({
5314
+ limits: import_core5.ExecutionLimitsSchema.optional(),
5315
+ getProvider: import_zod5.z.unknown().optional(),
5316
+ workflows: import_zod5.z.array(import_zod5.z.unknown()).optional(),
5317
+ workflowsDir: import_zod5.z.string().optional(),
5318
+ workflowRegistry: import_zod5.z.unknown().optional(),
5319
+ defaultWorkflowId: import_zod5.z.string().optional(),
5320
+ frameworkAdapter: import_zod5.z.unknown().optional(),
5321
+ telemetry: import_zod5.z.unknown().optional()
4892
5322
  }).strict();
4893
5323
  var createAgentArgsSchema = createAgentArgsBaseSchema.superRefine(
4894
5324
  (value, ctx) => {
@@ -5018,6 +5448,8 @@ function createAgent(args) {
5018
5448
  ...resolvedDefaultWorkflowId ? { defaultWorkflowId: resolvedDefaultWorkflowId } : {},
5019
5449
  messages,
5020
5450
  abortSignal: parsedOptions?.abortSignal,
5451
+ executionSignal: parsedOptions?.executionSignal,
5452
+ onExecution: parsedOptions?.onExecution,
5021
5453
  options: parsedOptions,
5022
5454
  workflowRegistry: registry,
5023
5455
  ...knownWorkflowIds ? { knownWorkflowIds } : {},
@@ -5025,7 +5457,7 @@ function createAgent(args) {
5025
5457
  getProvider: resolvedGetProvider,
5026
5458
  loadRuntimeConfig: (runtimeOptions) => ({
5027
5459
  ...limits || runtimeOptions?.limits ? {
5028
- executionBudget: (0, import_core4.createExecutionBudget)(
5460
+ executionBudget: (0, import_core5.createExecutionBudget)(
5029
5461
  limits,
5030
5462
  runtimeOptions?.limits
5031
5463
  )
@@ -5147,6 +5579,7 @@ function createAgent(args) {
5147
5579
  const execute = (async (options) => executeWorkflow(await services(), options));
5148
5580
  const resume = (async (options) => resumeWorkflow(await services(), options));
5149
5581
  return {
5582
+ ...createInterruptDiscovery(resolvedFrameworkAdapter.pendingRequests),
5150
5583
  execute,
5151
5584
  resume,
5152
5585
  projectTopology,