@kortyx/hooks 0.7.0 → 0.9.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
@@ -20,7 +20,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  var index_exports = {};
21
21
  __export(index_exports, {
22
22
  runWithHookContext: () => runWithHookContext,
23
- useAiMemory: () => useAiMemory,
24
23
  useInterrupt: () => useInterrupt,
25
24
  useNodeState: () => useNodeState,
26
25
  useReason: () => useReason2,
@@ -45,9 +44,9 @@ var normalizeNodeState = (value) => {
45
44
  return { byIndex: [], byKey: {} };
46
45
  };
47
46
  var cloneWorkflowState = (value) => isRecord(value) ? { ...value } : {};
48
- var getHookMemory = (state) => {
49
- const memory = state.memory ?? {};
50
- const internal = isRecord(memory.__kortyx) ? memory.__kortyx : void 0;
47
+ var getHookRuntimeState = (state) => {
48
+ const runtime = state.runtime ?? {};
49
+ const internal = isRecord(runtime.__kortyx) ? runtime.__kortyx : void 0;
51
50
  const result = {};
52
51
  if (isRecord(internal?.nodeState)) result.nodeState = internal?.nodeState;
53
52
  if (isRecord(internal?.workflowState)) {
@@ -56,21 +55,22 @@ var getHookMemory = (state) => {
56
55
  return result;
57
56
  };
58
57
  var createInternalContext = (ctx) => {
59
- const hookMemory = getHookMemory(ctx.state);
58
+ const hookRuntimeState = getHookRuntimeState(ctx.state);
60
59
  const nodeId = ctx.node.graph.node;
61
- const storedNodeId = typeof hookMemory.nodeState?.nodeId === "string" ? String(hookMemory.nodeState?.nodeId) : "";
62
- const storedState = hookMemory.nodeState?.state;
60
+ const storedNodeState = isRecord(hookRuntimeState.nodeState) ? hookRuntimeState.nodeState : void 0;
61
+ const storedNodeId = typeof storedNodeState?.nodeId === "string" ? String(storedNodeState.nodeId) : "";
62
+ const storedState = storedNodeState?.state;
63
63
  const currentNodeState = storedNodeId && storedNodeId === nodeId ? normalizeNodeState(storedState) : { byIndex: [], byKey: {} };
64
64
  return {
65
65
  ...ctx,
66
66
  nodeStateIndex: 0,
67
67
  reasonCallIndex: 0,
68
68
  currentNodeState,
69
- workflowState: cloneWorkflowState(hookMemory.workflowState),
69
+ workflowState: cloneWorkflowState(hookRuntimeState.workflowState),
70
70
  dirty: false
71
71
  };
72
72
  };
73
- var buildMemoryUpdates = (ctx) => {
73
+ var buildRuntimeStateUpdates = (ctx) => {
74
74
  if (!ctx.dirty) return null;
75
75
  return {
76
76
  __kortyx: {
@@ -86,17 +86,17 @@ async function runWithHookContext(ctx, fn) {
86
86
  const internal = createInternalContext(ctx);
87
87
  try {
88
88
  const result = await storage.run(internal, fn);
89
- return { result, memoryUpdates: buildMemoryUpdates(internal) };
89
+ return { result, runtimeUpdates: buildRuntimeStateUpdates(internal) };
90
90
  } catch (err) {
91
- const memoryUpdates = buildMemoryUpdates(internal);
92
- if (memoryUpdates) {
93
- const currentMemory = isRecord(internal.state.memory) ? internal.state.memory : {};
94
- internal.state.memory = {
95
- ...currentMemory,
96
- ...memoryUpdates
91
+ const runtimeUpdates = buildRuntimeStateUpdates(internal);
92
+ if (runtimeUpdates) {
93
+ const currentRuntime = isRecord(internal.state.runtime) ? internal.state.runtime : {};
94
+ internal.state.runtime = {
95
+ ...currentRuntime,
96
+ ...runtimeUpdates
97
97
  };
98
98
  if (err && typeof err === "object") {
99
- err.__kortyxMemoryUpdates = memoryUpdates;
99
+ err.__kortyxHookStatePatch = runtimeUpdates;
100
100
  }
101
101
  }
102
102
  throw err;
@@ -145,31 +145,73 @@ var awaitInterruptInternal = (args) => {
145
145
  };
146
146
 
147
147
  // src/structured.ts
148
+ var import_node_crypto = require("crypto");
149
+ var createStructuredStreamId = () => {
150
+ try {
151
+ return (0, import_node_crypto.randomUUID)();
152
+ } catch {
153
+ return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
154
+ }
155
+ };
148
156
  var emitStructuredData = (args) => {
149
157
  const ctx = getHookContext();
158
+ const streamId = typeof args.streamId === "string" && args.streamId.length > 0 ? args.streamId : createStructuredStreamId();
159
+ const dataType = typeof args.dataType === "string" && args.dataType.length > 0 ? args.dataType : "generic";
160
+ const base = {
161
+ node: ctx.node.graph.node,
162
+ streamId,
163
+ dataType,
164
+ ...typeof args.schemaId === "string" && args.schemaId.length > 0 ? { schemaId: args.schemaId } : {},
165
+ ...typeof args.schemaVersion === "string" && args.schemaVersion.length > 0 ? { schemaVersion: args.schemaVersion } : {},
166
+ ...typeof args.id === "string" && args.id.length > 0 ? { id: args.id } : {}
167
+ };
168
+ if (args.kind === "set") {
169
+ const value = parseWithSchema(
170
+ args.valueSchema,
171
+ args.value,
172
+ "useStructuredData value"
173
+ );
174
+ ctx.node.emit("structured_data", {
175
+ ...base,
176
+ kind: "set",
177
+ path: args.path,
178
+ value
179
+ });
180
+ return;
181
+ }
182
+ if (args.kind === "append") {
183
+ const items = args.items.map(
184
+ (item) => parseWithSchema(args.itemSchema, item, "useStructuredData append item")
185
+ );
186
+ ctx.node.emit("structured_data", {
187
+ ...base,
188
+ kind: "append",
189
+ path: args.path,
190
+ items
191
+ });
192
+ return;
193
+ }
194
+ if (args.kind === "text-delta") {
195
+ ctx.node.emit("structured_data", {
196
+ ...base,
197
+ kind: "text-delta",
198
+ path: args.path,
199
+ delta: args.delta
200
+ });
201
+ return;
202
+ }
150
203
  const validated = parseWithSchema(
151
204
  args.dataSchema,
152
205
  args.data,
153
206
  "useStructuredData data"
154
207
  );
155
208
  ctx.node.emit("structured_data", {
156
- node: ctx.node.graph.node,
157
- ...typeof args.dataType === "string" && args.dataType.length > 0 ? { dataType: args.dataType } : {},
158
- ...typeof args.mode === "string" ? { mode: args.mode } : {},
159
- ...typeof args.schemaId === "string" && args.schemaId.length > 0 ? { schemaId: args.schemaId } : {},
160
- ...typeof args.schemaVersion === "string" && args.schemaVersion.length > 0 ? { schemaVersion: args.schemaVersion } : {},
161
- ...typeof args.id === "string" && args.id.length > 0 ? { id: args.id } : {},
162
- ...typeof args.opId === "string" && args.opId.length > 0 ? { opId: args.opId } : {},
209
+ ...base,
210
+ kind: "final",
163
211
  data: validated
164
212
  });
165
213
  };
166
- var shouldEmitStructured = (cfg) => (cfg?.stream ?? "patch") !== "off";
167
- var resolveStructuredMode = (cfg) => {
168
- const mode = cfg?.stream ?? "patch";
169
- if (mode === "snapshot") return "snapshot";
170
- if (mode === "patch") return "patch";
171
- return "final";
172
- };
214
+ var shouldEmitStructured = (cfg) => Boolean(cfg) && (cfg?.stream ?? true);
173
215
 
174
216
  // src/reason/checkpoint.ts
175
217
  var resolveReasonCheckpointKey = (args) => typeof args.id === "string" && args.id.length > 0 ? `__useReason:${args.id}` : `__useReason:auto:${args.autoIndex}`;
@@ -187,7 +229,7 @@ var readReasonCheckpoint = (raw) => {
187
229
  ...Object.hasOwn(value, "firstOutput") ? { firstOutput: value.firstOutput } : {}
188
230
  };
189
231
  };
190
- var resolveHookMemoryPatch = (args) => ({
232
+ var resolveHookStatePatch = (args) => ({
191
233
  __kortyx: {
192
234
  nodeState: {
193
235
  nodeId: args.nodeId,
@@ -198,10 +240,10 @@ var resolveHookMemoryPatch = (args) => ({
198
240
  });
199
241
 
200
242
  // src/reason/engine.ts
201
- var import_node_crypto2 = require("crypto");
243
+ var import_node_crypto3 = require("crypto");
202
244
 
203
245
  // src/reason-engine.ts
204
- var import_node_crypto = require("crypto");
246
+ var import_node_crypto2 = require("crypto");
205
247
  var chunkToText = (chunk) => {
206
248
  if (typeof chunk === "string") return chunk;
207
249
  if (typeof chunk.text === "string") return chunk.text;
@@ -210,7 +252,7 @@ var chunkToText = (chunk) => {
210
252
  };
211
253
  var createRuntimeId = () => {
212
254
  try {
213
- return (0, import_node_crypto.randomUUID)();
255
+ return (0, import_node_crypto2.randomUUID)();
214
256
  } catch {
215
257
  return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
216
258
  }
@@ -255,6 +297,7 @@ async function runReasonEngine(args) {
255
297
  const text = chunkToText(chunk);
256
298
  if (!text) continue;
257
299
  final += text;
300
+ args.onTextChunk?.(text);
258
301
  if (typeof chunk === "object" && chunk && "raw" in chunk) {
259
302
  raw = chunk.raw;
260
303
  }
@@ -299,7 +342,7 @@ async function runReasonEngine(args) {
299
342
  // src/reason/engine.ts
300
343
  var createRuntimeId2 = () => {
301
344
  try {
302
- return (0, import_node_crypto2.randomUUID)();
345
+ return (0, import_node_crypto3.randomUUID)();
303
346
  } catch {
304
347
  return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
305
348
  }
@@ -319,6 +362,7 @@ async function reasonEngine(args, meta, inputOverride) {
319
362
  defaultTemperature: ctx.node.config?.model?.temperature,
320
363
  stream: args.stream,
321
364
  emit: args.emit,
365
+ onTextChunk: args.onTextChunk,
322
366
  nodeId: ctx.node.graph.node,
323
367
  emitEvent: ctx.node.emit,
324
368
  id: meta.id,
@@ -495,6 +539,413 @@ var withOutputGuardrails = (input, schema) => {
495
539
  ].join("\n\n");
496
540
  return withSchemaHint(base, "Expected output JSON schema", schema);
497
541
  };
542
+ var withStructuredStreamHints = (input, args) => {
543
+ const rules = [];
544
+ for (const path of args.setFieldPaths ?? []) {
545
+ rules.push(
546
+ `- For the top-level field \`${path}\`, write one stable final value and do not rewrite it after it is complete.`
547
+ );
548
+ }
549
+ for (const path of args.appendFieldPaths ?? []) {
550
+ rules.push(
551
+ `- For the top-level array field \`${path}\`, write items in final order and finish each item before starting the next.`
552
+ );
553
+ rules.push(
554
+ `- Once an item in \`${path}\` is complete, do not rewrite or reorder earlier items.`
555
+ );
556
+ }
557
+ for (const path of args.textDeltaFieldPaths ?? []) {
558
+ rules.push(
559
+ `- For the top-level string field \`${path}\`, write the value progressively by appending text to the end.`
560
+ );
561
+ rules.push(
562
+ `- Do not rewrite or replace the earlier prefix of \`${path}\` once emitted.`
563
+ );
564
+ }
565
+ if (rules.length === 0) return input;
566
+ return [
567
+ input,
568
+ "Streaming rules:",
569
+ ...rules,
570
+ "- Keep the overall response as valid JSON."
571
+ ].join("\n\n");
572
+ };
573
+
574
+ // src/reason/structured-stream.ts
575
+ var escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
576
+ var findTopLevelArrayStart = (text, path) => {
577
+ const match = new RegExp(`"${escapeRegex(path)}"\\s*:\\s*\\[`).exec(text);
578
+ return match ? match.index + match[0].length : -1;
579
+ };
580
+ var resolveAppendFieldPaths = (cfg) => {
581
+ const fields = cfg?.fields;
582
+ if (!fields) return [];
583
+ const appendPaths = Object.entries(fields).filter(([, mode]) => mode === "append").map(([path]) => path);
584
+ for (const path of appendPaths) {
585
+ if (!path || path.includes(".")) {
586
+ throw new Error(
587
+ "useReason structured append streaming currently supports top-level array fields only."
588
+ );
589
+ }
590
+ }
591
+ return appendPaths;
592
+ };
593
+ var resolveSetFieldPaths = (cfg) => {
594
+ const fields = cfg?.fields;
595
+ if (!fields) return [];
596
+ const setPaths = Object.entries(fields).filter(([, mode]) => mode === "set").map(([path]) => path);
597
+ for (const path of setPaths) {
598
+ if (!path || path.includes(".")) {
599
+ throw new Error(
600
+ "useReason structured set streaming currently supports top-level fields only."
601
+ );
602
+ }
603
+ }
604
+ return setPaths;
605
+ };
606
+ var resolveTextDeltaFieldPaths = (cfg) => {
607
+ const fields = cfg?.fields;
608
+ if (!fields) return [];
609
+ const textPaths = Object.entries(fields).filter(([, mode]) => mode === "text-delta").map(([path]) => path);
610
+ for (const path of textPaths) {
611
+ if (!path || path.includes(".")) {
612
+ throw new Error(
613
+ "useReason structured text-delta streaming currently supports top-level string fields only."
614
+ );
615
+ }
616
+ }
617
+ return textPaths;
618
+ };
619
+ var findTopLevelFieldValueStart = (text, path) => {
620
+ const match = new RegExp(`"${escapeRegex(path)}"\\s*:\\s*`).exec(text);
621
+ return match ? match.index + match[0].length : -1;
622
+ };
623
+ var findTopLevelStringStart = (text, path) => {
624
+ const match = new RegExp(`"${escapeRegex(path)}"\\s*:\\s*"`).exec(text);
625
+ return match ? match.index + match[0].length : -1;
626
+ };
627
+ var extractCompletedStringValueAt = (args) => {
628
+ let value = "";
629
+ for (let i = args.start; i < args.text.length; i += 1) {
630
+ const ch = args.text[i];
631
+ if (ch === void 0) break;
632
+ if (ch === '"') {
633
+ return {
634
+ complete: true,
635
+ value
636
+ };
637
+ }
638
+ if (ch !== "\\") {
639
+ value += ch;
640
+ continue;
641
+ }
642
+ const next = args.text[i + 1];
643
+ if (!next) {
644
+ return {
645
+ complete: false
646
+ };
647
+ }
648
+ if (next === '"' || next === "\\" || next === "/" || next === "b" || next === "f" || next === "n" || next === "r" || next === "t") {
649
+ value += next === "b" ? "\b" : next === "f" ? "\f" : next === "n" ? "\n" : next === "r" ? "\r" : next === "t" ? " " : next;
650
+ i += 1;
651
+ continue;
652
+ }
653
+ if (next === "u") {
654
+ const hex = args.text.slice(i + 2, i + 6);
655
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
656
+ return {
657
+ complete: false
658
+ };
659
+ }
660
+ value += String.fromCharCode(Number.parseInt(hex, 16));
661
+ i += 5;
662
+ continue;
663
+ }
664
+ return {
665
+ complete: false
666
+ };
667
+ }
668
+ return {
669
+ complete: false
670
+ };
671
+ };
672
+ var extractCompletedNestedValueAt = (args) => {
673
+ let inString = false;
674
+ let isEscaped = false;
675
+ let nesting = 0;
676
+ for (let i = args.start; i < args.text.length; i += 1) {
677
+ const ch = args.text[i];
678
+ if (ch === void 0) break;
679
+ if (inString) {
680
+ if (isEscaped) {
681
+ isEscaped = false;
682
+ continue;
683
+ }
684
+ if (ch === "\\") {
685
+ isEscaped = true;
686
+ continue;
687
+ }
688
+ if (ch === '"') {
689
+ inString = false;
690
+ }
691
+ continue;
692
+ }
693
+ if (ch === '"') {
694
+ inString = true;
695
+ continue;
696
+ }
697
+ if (ch === args.opener) {
698
+ nesting += 1;
699
+ continue;
700
+ }
701
+ if (ch === args.closer) {
702
+ nesting -= 1;
703
+ if (nesting === 0) {
704
+ const raw = args.text.slice(args.start, i + 1);
705
+ try {
706
+ return {
707
+ complete: true,
708
+ value: JSON.parse(raw)
709
+ };
710
+ } catch {
711
+ return {
712
+ complete: false
713
+ };
714
+ }
715
+ }
716
+ }
717
+ }
718
+ return {
719
+ complete: false
720
+ };
721
+ };
722
+ var extractCompletedFieldValue = (args) => {
723
+ const start = findTopLevelFieldValueStart(args.text, args.path);
724
+ if (start < 0) {
725
+ return {
726
+ found: false,
727
+ complete: false
728
+ };
729
+ }
730
+ const first = args.text[start];
731
+ if (!first) {
732
+ return {
733
+ found: true,
734
+ complete: false
735
+ };
736
+ }
737
+ if (first === '"') {
738
+ const parsed = extractCompletedStringValueAt({
739
+ text: args.text,
740
+ start: start + 1
741
+ });
742
+ return parsed.complete ? {
743
+ found: true,
744
+ complete: true,
745
+ value: parsed.value
746
+ } : {
747
+ found: true,
748
+ complete: false
749
+ };
750
+ }
751
+ if (first === "{") {
752
+ const parsed = extractCompletedNestedValueAt({
753
+ text: args.text,
754
+ start,
755
+ opener: "{",
756
+ closer: "}"
757
+ });
758
+ return parsed.complete ? {
759
+ found: true,
760
+ complete: true,
761
+ value: parsed.value
762
+ } : {
763
+ found: true,
764
+ complete: false
765
+ };
766
+ }
767
+ if (first === "[") {
768
+ const parsed = extractCompletedNestedValueAt({
769
+ text: args.text,
770
+ start,
771
+ opener: "[",
772
+ closer: "]"
773
+ });
774
+ return parsed.complete ? {
775
+ found: true,
776
+ complete: true,
777
+ value: parsed.value
778
+ } : {
779
+ found: true,
780
+ complete: false
781
+ };
782
+ }
783
+ for (let i = start; i < args.text.length; i += 1) {
784
+ const ch = args.text[i];
785
+ if (ch === void 0) break;
786
+ if (ch !== "," && ch !== "}") continue;
787
+ const raw = args.text.slice(start, i).trim();
788
+ if (!raw) {
789
+ return {
790
+ found: true,
791
+ complete: false
792
+ };
793
+ }
794
+ try {
795
+ return {
796
+ found: true,
797
+ complete: true,
798
+ value: JSON.parse(raw)
799
+ };
800
+ } catch {
801
+ return {
802
+ found: true,
803
+ complete: false
804
+ };
805
+ }
806
+ }
807
+ return {
808
+ found: true,
809
+ complete: false
810
+ };
811
+ };
812
+ var extractCompletedArrayItems = (args) => {
813
+ const start = findTopLevelArrayStart(args.text, args.path);
814
+ if (start < 0) return [];
815
+ const items = [];
816
+ let inString = false;
817
+ let isEscaped = false;
818
+ let nesting = 0;
819
+ let itemStart = -1;
820
+ for (let i = start; i < args.text.length; i += 1) {
821
+ const ch = args.text[i];
822
+ if (ch === void 0) break;
823
+ if (inString) {
824
+ if (isEscaped) {
825
+ isEscaped = false;
826
+ continue;
827
+ }
828
+ if (ch === "\\") {
829
+ isEscaped = true;
830
+ continue;
831
+ }
832
+ if (ch === '"') {
833
+ inString = false;
834
+ }
835
+ continue;
836
+ }
837
+ if (ch === '"') {
838
+ inString = true;
839
+ if (itemStart < 0) itemStart = i;
840
+ continue;
841
+ }
842
+ if (itemStart < 0) {
843
+ if (/\s/.test(ch) || ch === ",") continue;
844
+ if (ch === "]") break;
845
+ itemStart = i;
846
+ if (ch === "{" || ch === "[") {
847
+ nesting = 1;
848
+ } else {
849
+ nesting = 0;
850
+ }
851
+ continue;
852
+ }
853
+ if (ch === "{" || ch === "[") {
854
+ nesting += 1;
855
+ continue;
856
+ }
857
+ if (ch === "}" || ch === "]") {
858
+ if (nesting > 0) {
859
+ nesting -= 1;
860
+ if (nesting > 0) continue;
861
+ }
862
+ if (ch === "]") {
863
+ const raw = args.text.slice(itemStart, i).trim();
864
+ if (raw) {
865
+ try {
866
+ items.push(JSON.parse(raw));
867
+ } catch {
868
+ }
869
+ }
870
+ break;
871
+ }
872
+ }
873
+ if (nesting === 0 && (ch === "," || ch === "]")) {
874
+ const raw = args.text.slice(itemStart, i).trim();
875
+ if (raw) {
876
+ try {
877
+ items.push(JSON.parse(raw));
878
+ } catch {
879
+ }
880
+ }
881
+ itemStart = -1;
882
+ if (ch === "]") break;
883
+ }
884
+ }
885
+ return items;
886
+ };
887
+ var extractStreamingStringValue = (args) => {
888
+ const start = findTopLevelStringStart(args.text, args.path);
889
+ if (start < 0) {
890
+ return {
891
+ found: false,
892
+ complete: false,
893
+ value: ""
894
+ };
895
+ }
896
+ let value = "";
897
+ for (let i = start; i < args.text.length; i += 1) {
898
+ const ch = args.text[i];
899
+ if (ch === void 0) break;
900
+ if (ch === '"') {
901
+ return {
902
+ found: true,
903
+ complete: true,
904
+ value
905
+ };
906
+ }
907
+ if (ch !== "\\") {
908
+ value += ch;
909
+ continue;
910
+ }
911
+ const next = args.text[i + 1];
912
+ if (!next) {
913
+ return {
914
+ found: true,
915
+ complete: false,
916
+ value
917
+ };
918
+ }
919
+ if (next === '"' || next === "\\" || next === "/" || next === "b" || next === "f" || next === "n" || next === "r" || next === "t") {
920
+ value += next === "b" ? "\b" : next === "f" ? "\f" : next === "n" ? "\n" : next === "r" ? "\r" : next === "t" ? " " : next;
921
+ i += 1;
922
+ continue;
923
+ }
924
+ if (next === "u") {
925
+ const hex = args.text.slice(i + 2, i + 6);
926
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
927
+ return {
928
+ found: true,
929
+ complete: false,
930
+ value
931
+ };
932
+ }
933
+ value += String.fromCharCode(Number.parseInt(hex, 16));
934
+ i += 5;
935
+ continue;
936
+ }
937
+ return {
938
+ found: true,
939
+ complete: false,
940
+ value
941
+ };
942
+ }
943
+ return {
944
+ found: true,
945
+ complete: false,
946
+ value
947
+ };
948
+ };
498
949
 
499
950
  // src/reason/use-reason.ts
500
951
  var emitReasonStructuredOutput = (args) => {
@@ -502,11 +953,11 @@ var emitReasonStructuredOutput = (args) => {
502
953
  emitStructuredData({
503
954
  dataType: args.structured?.dataType ?? "reason-output",
504
955
  data: args.output,
505
- mode: resolveStructuredMode(args.structured),
956
+ kind: "final",
506
957
  ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
507
958
  ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
508
959
  ...args.id ? { id: args.id } : {},
509
- opId: args.opId
960
+ streamId: args.opId
510
961
  });
511
962
  };
512
963
  async function useReason(args) {
@@ -522,6 +973,12 @@ async function useReason(args) {
522
973
  );
523
974
  const reasonMeta = typeof id === "string" ? { id, opId } : { opId };
524
975
  const suppressTextStream = Boolean(args.outputSchema || args.interrupt);
976
+ const setFieldPaths = resolveSetFieldPaths(args.structured);
977
+ const appendFieldPaths = resolveAppendFieldPaths(args.structured);
978
+ const textDeltaFieldPaths = resolveTextDeltaFieldPaths(args.structured);
979
+ const useStructuredIncrementalStreaming = Boolean(
980
+ args.outputSchema && !args.interrupt && (setFieldPaths.length > 0 || appendFieldPaths.length > 0 || textDeltaFieldPaths.length > 0) && args.stream !== false && shouldEmitStructured(args.structured)
981
+ );
525
982
  let firstText = "";
526
983
  let firstRaw;
527
984
  let firstOutput;
@@ -529,6 +986,9 @@ async function useReason(args) {
529
986
  let finalText = "";
530
987
  let finalRaw;
531
988
  let finalOutput;
989
+ const emittedSetValues = /* @__PURE__ */ new Map();
990
+ const emittedAppendCounts = /* @__PURE__ */ new Map();
991
+ const emittedTextValues = /* @__PURE__ */ new Map();
532
992
  if (existingCheckpoint) {
533
993
  firstText = existingCheckpoint.firstText;
534
994
  firstRaw = existingCheckpoint.firstRaw;
@@ -541,7 +1001,78 @@ async function useReason(args) {
541
1001
  {
542
1002
  ...args,
543
1003
  emit: suppressTextStream ? false : args.emit,
544
- stream: suppressTextStream ? false : args.stream
1004
+ stream: useStructuredIncrementalStreaming ? true : suppressTextStream ? false : args.stream,
1005
+ ...useStructuredIncrementalStreaming ? {
1006
+ onTextChunk: (delta) => {
1007
+ firstText += delta;
1008
+ finalText = firstText;
1009
+ for (const path of setFieldPaths) {
1010
+ const current = extractCompletedFieldValue({
1011
+ text: firstText,
1012
+ path
1013
+ });
1014
+ if (!current.found || !current.complete) continue;
1015
+ const nextSerialized = JSON.stringify(current.value);
1016
+ if (emittedSetValues.get(path) === nextSerialized) continue;
1017
+ emittedSetValues.set(path, nextSerialized);
1018
+ emitStructuredData({
1019
+ kind: "set",
1020
+ path,
1021
+ value: current.value,
1022
+ dataType: args.structured?.dataType ?? "reason-output",
1023
+ ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
1024
+ ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
1025
+ ...id ? { id } : {},
1026
+ streamId: opId
1027
+ });
1028
+ }
1029
+ for (const path of appendFieldPaths) {
1030
+ const items = extractCompletedArrayItems({
1031
+ text: firstText,
1032
+ path
1033
+ });
1034
+ const emittedAppendCount = emittedAppendCounts.get(path) ?? 0;
1035
+ if (items.length <= emittedAppendCount) continue;
1036
+ const nextItems = items.slice(emittedAppendCount);
1037
+ emittedAppendCounts.set(path, items.length);
1038
+ emitStructuredData({
1039
+ kind: "append",
1040
+ path,
1041
+ items: nextItems,
1042
+ dataType: args.structured?.dataType ?? "reason-output",
1043
+ ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
1044
+ ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
1045
+ ...id ? { id } : {},
1046
+ streamId: opId
1047
+ });
1048
+ }
1049
+ for (const path of textDeltaFieldPaths) {
1050
+ const current = extractStreamingStringValue({
1051
+ text: firstText,
1052
+ path
1053
+ });
1054
+ const emittedTextValue = emittedTextValues.get(path) ?? "";
1055
+ if (!current.found || current.value.length <= emittedTextValue.length || !current.value.startsWith(emittedTextValue)) {
1056
+ continue;
1057
+ }
1058
+ const nextDelta = current.value.slice(
1059
+ emittedTextValue.length
1060
+ );
1061
+ emittedTextValues.set(path, current.value);
1062
+ if (nextDelta.length === 0) continue;
1063
+ emitStructuredData({
1064
+ kind: "text-delta",
1065
+ path,
1066
+ delta: nextDelta,
1067
+ dataType: args.structured?.dataType ?? "reason-output",
1068
+ ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
1069
+ ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
1070
+ ...id ? { id } : {},
1071
+ streamId: opId
1072
+ });
1073
+ }
1074
+ }
1075
+ } : {}
545
1076
  },
546
1077
  reasonMeta,
547
1078
  args.interrupt ? defaultInterruptFirstPassInput({
@@ -549,7 +1080,11 @@ async function useReason(args) {
549
1080
  requestSchema: args.interrupt.requestSchema,
550
1081
  ...args.outputSchema ? { outputSchema: args.outputSchema } : {}
551
1082
  }) : args.outputSchema ? withOutputGuardrails(
552
- args.input,
1083
+ withStructuredStreamHints(args.input, {
1084
+ ...setFieldPaths.length > 0 ? { setFieldPaths } : {},
1085
+ ...appendFieldPaths.length > 0 ? { appendFieldPaths } : {},
1086
+ ...textDeltaFieldPaths.length > 0 ? { textDeltaFieldPaths } : {}
1087
+ }),
553
1088
  args.outputSchema
554
1089
  ) : void 0
555
1090
  );
@@ -566,14 +1101,6 @@ async function useReason(args) {
566
1101
  firstInterruptRequest = firstPass.request;
567
1102
  firstOutput = firstPass.output;
568
1103
  finalOutput = firstPass.output;
569
- if (firstOutput !== void 0) {
570
- emitReasonStructuredOutput({
571
- ...id ? { id } : {},
572
- opId,
573
- output: firstOutput,
574
- structured: args.structured
575
- });
576
- }
577
1104
  } else {
578
1105
  firstText = first.text;
579
1106
  finalText = first.text;
@@ -583,12 +1110,6 @@ async function useReason(args) {
583
1110
  resolveOutputCandidate(first.text),
584
1111
  "useReason output"
585
1112
  );
586
- emitReasonStructuredOutput({
587
- ...id ? { id } : {},
588
- opId,
589
- output: firstOutput,
590
- structured: args.structured
591
- });
592
1113
  }
593
1114
  finalOutput = firstOutput;
594
1115
  }
@@ -616,7 +1137,7 @@ async function useReason(args) {
616
1137
  };
617
1138
  ctx.dirty = true;
618
1139
  }
619
- const resumeMemoryPatch = resolveHookMemoryPatch({
1140
+ const resumeStatePatch = resolveHookStatePatch({
620
1141
  nodeId: ctx.node.graph.node,
621
1142
  currentNodeState: ctx.currentNodeState,
622
1143
  workflowState: ctx.workflowState
@@ -629,7 +1150,7 @@ async function useReason(args) {
629
1150
  ...args.interrupt.schemaVersion ? { schemaVersion: args.interrupt.schemaVersion } : {},
630
1151
  ...id ? { id } : {},
631
1152
  meta: {
632
- __kortyxResumeMemory: resumeMemoryPatch
1153
+ __kortyxResumeStatePatch: resumeStatePatch
633
1154
  }
634
1155
  });
635
1156
  if (Object.hasOwn(ctx.currentNodeState.byKey, checkpointKey)) {
@@ -663,14 +1184,16 @@ async function useReason(args) {
663
1184
  resolveOutputCandidate(finalText),
664
1185
  "useReason output"
665
1186
  );
666
- emitReasonStructuredOutput({
667
- ...id ? { id } : {},
668
- opId,
669
- output: finalOutput,
670
- structured: args.structured
671
- });
672
1187
  }
673
1188
  }
1189
+ if (finalOutput !== void 0) {
1190
+ emitReasonStructuredOutput({
1191
+ ...id ? { id } : {},
1192
+ opId,
1193
+ output: finalOutput,
1194
+ structured: args.structured
1195
+ });
1196
+ }
674
1197
  return {
675
1198
  ...id ? { id } : {},
676
1199
  opId,
@@ -688,13 +1211,6 @@ function useStructuredData(args) {
688
1211
  function useReason2(args) {
689
1212
  return useReason(args);
690
1213
  }
691
- function useAiMemory() {
692
- const ctx = getHookContext();
693
- if (!ctx.memoryAdapter) {
694
- throw new Error("useAiMemory requires a memory adapter in runtime config.");
695
- }
696
- return ctx.memoryAdapter;
697
- }
698
1214
  function useInterrupt(args) {
699
1215
  return awaitInterruptInternal(args);
700
1216
  }
@@ -715,10 +1231,11 @@ function useNodeState(initialValue) {
715
1231
  };
716
1232
  return [getValue(), setValue];
717
1233
  }
718
- function useWorkflowState(key, initialValue) {
1234
+ function useWorkflowState(key, ...rest) {
719
1235
  const ctx = getHookContext();
720
1236
  const workflowState = ctx.workflowState;
721
- const hasInitial = arguments.length > 1;
1237
+ const hasInitial = rest.length > 0;
1238
+ const initialValue = rest[0];
722
1239
  if (!Object.hasOwn(workflowState, key) && hasInitial) {
723
1240
  workflowState[key] = initialValue;
724
1241
  ctx.dirty = true;
@@ -735,7 +1252,6 @@ function useWorkflowState(key, initialValue) {
735
1252
  // Annotate the CommonJS export names for ESM import in node:
736
1253
  0 && (module.exports = {
737
1254
  runWithHookContext,
738
- useAiMemory,
739
1255
  useInterrupt,
740
1256
  useNodeState,
741
1257
  useReason,