@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/CHANGELOG.md +21 -0
- package/dist/index.d.ts +30 -14
- package/dist/index.js +588 -72
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +1 -0
- package/dist/internal.js +1 -0
- package/dist/internal.js.map +1 -1
- package/package.json +3 -4
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
|
|
49
|
-
const
|
|
50
|
-
const internal = isRecord(
|
|
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
|
|
58
|
+
const hookRuntimeState = getHookRuntimeState(ctx.state);
|
|
60
59
|
const nodeId = ctx.node.graph.node;
|
|
61
|
-
const
|
|
62
|
-
const
|
|
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(
|
|
69
|
+
workflowState: cloneWorkflowState(hookRuntimeState.workflowState),
|
|
70
70
|
dirty: false
|
|
71
71
|
};
|
|
72
72
|
};
|
|
73
|
-
var
|
|
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,
|
|
89
|
+
return { result, runtimeUpdates: buildRuntimeStateUpdates(internal) };
|
|
90
90
|
} catch (err) {
|
|
91
|
-
const
|
|
92
|
-
if (
|
|
93
|
-
const
|
|
94
|
-
internal.state.
|
|
95
|
-
...
|
|
96
|
-
...
|
|
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.
|
|
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
|
-
|
|
157
|
-
|
|
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 ??
|
|
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
|
|
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
|
|
243
|
+
var import_node_crypto3 = require("crypto");
|
|
202
244
|
|
|
203
245
|
// src/reason-engine.ts
|
|
204
|
-
var
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
1234
|
+
function useWorkflowState(key, ...rest) {
|
|
719
1235
|
const ctx = getHookContext();
|
|
720
1236
|
const workflowState = ctx.workflowState;
|
|
721
|
-
const hasInitial =
|
|
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,
|