@kortyx/hooks 0.9.0 → 0.10.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 +25 -0
- package/dist/index.d.ts +28 -7
- package/dist/index.js +465 -90
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +14 -3
- package/dist/internal.js +149 -47
- package/dist/internal.js.map +1 -1
- package/dist/tracing-BG-j1cer.d.ts +25 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -44,6 +44,20 @@ var normalizeNodeState = (value) => {
|
|
|
44
44
|
return { byIndex: [], byKey: {} };
|
|
45
45
|
};
|
|
46
46
|
var cloneWorkflowState = (value) => isRecord(value) ? { ...value } : {};
|
|
47
|
+
var cloneTokenUsage = (value) => {
|
|
48
|
+
if (!isRecord(value)) return void 0;
|
|
49
|
+
const input = typeof value.input === "number" ? value.input : void 0;
|
|
50
|
+
const output = typeof value.output === "number" ? value.output : void 0;
|
|
51
|
+
const total = typeof value.total === "number" ? value.total : void 0;
|
|
52
|
+
if (input === void 0 && output === void 0 && total === void 0) {
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
input: input ?? 0,
|
|
57
|
+
output: output ?? 0,
|
|
58
|
+
total: total ?? 0
|
|
59
|
+
};
|
|
60
|
+
};
|
|
47
61
|
var getHookRuntimeState = (state) => {
|
|
48
62
|
const runtime = state.runtime ?? {};
|
|
49
63
|
const internal = isRecord(runtime.__kortyx) ? runtime.__kortyx : void 0;
|
|
@@ -67,21 +81,60 @@ var createInternalContext = (ctx) => {
|
|
|
67
81
|
reasonCallIndex: 0,
|
|
68
82
|
currentNodeState,
|
|
69
83
|
workflowState: cloneWorkflowState(hookRuntimeState.workflowState),
|
|
70
|
-
|
|
84
|
+
tokenUsage: cloneTokenUsage(ctx.state.runtime?.tokenUsage),
|
|
85
|
+
stateDirty: false,
|
|
86
|
+
runtimeDirty: false
|
|
71
87
|
};
|
|
72
88
|
};
|
|
73
89
|
var buildRuntimeStateUpdates = (ctx) => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
__kortyx
|
|
90
|
+
const updates = {};
|
|
91
|
+
if (ctx.stateDirty) {
|
|
92
|
+
updates.__kortyx = {
|
|
77
93
|
nodeState: {
|
|
78
94
|
nodeId: ctx.node.graph.node,
|
|
79
95
|
state: ctx.currentNodeState
|
|
80
96
|
},
|
|
81
97
|
workflowState: ctx.workflowState
|
|
82
|
-
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (ctx.runtimeDirty && ctx.tokenUsage) {
|
|
101
|
+
updates.tokenUsage = ctx.tokenUsage;
|
|
102
|
+
}
|
|
103
|
+
return Object.keys(updates).length > 0 ? updates : null;
|
|
104
|
+
};
|
|
105
|
+
var toTokenUsageDelta = (usage) => {
|
|
106
|
+
if (!usage) return void 0;
|
|
107
|
+
const input = usage.input ?? 0;
|
|
108
|
+
const output = usage.output ?? 0;
|
|
109
|
+
const total = usage.total ?? input + output + (usage.reasoning ?? 0);
|
|
110
|
+
if (input === 0 && output === 0 && total === 0) {
|
|
111
|
+
return void 0;
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
input,
|
|
115
|
+
output,
|
|
116
|
+
total
|
|
83
117
|
};
|
|
84
118
|
};
|
|
119
|
+
function accumulateTokenUsage(usage) {
|
|
120
|
+
const delta = toTokenUsageDelta(usage);
|
|
121
|
+
if (!delta) return;
|
|
122
|
+
const ctx = getHookContext();
|
|
123
|
+
const current = ctx.tokenUsage ?? {
|
|
124
|
+
input: 0,
|
|
125
|
+
output: 0,
|
|
126
|
+
total: 0
|
|
127
|
+
};
|
|
128
|
+
ctx.tokenUsage = {
|
|
129
|
+
input: current.input + delta.input,
|
|
130
|
+
output: current.output + delta.output,
|
|
131
|
+
total: current.total + delta.total
|
|
132
|
+
};
|
|
133
|
+
ctx.runtimeDirty = true;
|
|
134
|
+
}
|
|
135
|
+
function getReasonTraceAdapter() {
|
|
136
|
+
return getHookContext().reasonTrace;
|
|
137
|
+
}
|
|
85
138
|
async function runWithHookContext(ctx, fn) {
|
|
86
139
|
const internal = createInternalContext(ctx);
|
|
87
140
|
try {
|
|
@@ -146,6 +199,25 @@ var awaitInterruptInternal = (args) => {
|
|
|
146
199
|
|
|
147
200
|
// src/structured.ts
|
|
148
201
|
var import_node_crypto = require("crypto");
|
|
202
|
+
|
|
203
|
+
// src/structured-path.ts
|
|
204
|
+
var parseStructuredPath = (path, source) => {
|
|
205
|
+
if (path.length === 0) {
|
|
206
|
+
throw new Error(`${source} path must be a non-empty dot-separated string.`);
|
|
207
|
+
}
|
|
208
|
+
const parts = path.split(".");
|
|
209
|
+
if (parts.some((part) => part.length === 0)) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
`${source} path "${path}" must not contain empty segments.`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
return parts;
|
|
215
|
+
};
|
|
216
|
+
var assertStructuredPath = (path, source) => {
|
|
217
|
+
parseStructuredPath(path, source);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// src/structured.ts
|
|
149
221
|
var createStructuredStreamId = () => {
|
|
150
222
|
try {
|
|
151
223
|
return (0, import_node_crypto.randomUUID)();
|
|
@@ -166,6 +238,7 @@ var emitStructuredData = (args) => {
|
|
|
166
238
|
...typeof args.id === "string" && args.id.length > 0 ? { id: args.id } : {}
|
|
167
239
|
};
|
|
168
240
|
if (args.kind === "set") {
|
|
241
|
+
assertStructuredPath(args.path, "useStructuredData");
|
|
169
242
|
const value = parseWithSchema(
|
|
170
243
|
args.valueSchema,
|
|
171
244
|
args.value,
|
|
@@ -180,6 +253,7 @@ var emitStructuredData = (args) => {
|
|
|
180
253
|
return;
|
|
181
254
|
}
|
|
182
255
|
if (args.kind === "append") {
|
|
256
|
+
assertStructuredPath(args.path, "useStructuredData");
|
|
183
257
|
const items = args.items.map(
|
|
184
258
|
(item) => parseWithSchema(args.itemSchema, item, "useStructuredData append item")
|
|
185
259
|
);
|
|
@@ -192,6 +266,7 @@ var emitStructuredData = (args) => {
|
|
|
192
266
|
return;
|
|
193
267
|
}
|
|
194
268
|
if (args.kind === "text-delta") {
|
|
269
|
+
assertStructuredPath(args.path, "useStructuredData");
|
|
195
270
|
ctx.node.emit("structured_data", {
|
|
196
271
|
...base,
|
|
197
272
|
kind: "text-delta",
|
|
@@ -226,6 +301,12 @@ var readReasonCheckpoint = (raw) => {
|
|
|
226
301
|
request: value.request,
|
|
227
302
|
firstText: value.firstText,
|
|
228
303
|
...Object.hasOwn(value, "firstRaw") ? { firstRaw: value.firstRaw } : {},
|
|
304
|
+
...Object.hasOwn(value, "firstUsage") ? { firstUsage: value.firstUsage } : {},
|
|
305
|
+
...Object.hasOwn(value, "firstFinishReason") ? { firstFinishReason: value.firstFinishReason } : {},
|
|
306
|
+
...Object.hasOwn(value, "firstProviderMetadata") ? {
|
|
307
|
+
firstProviderMetadata: value.firstProviderMetadata
|
|
308
|
+
} : {},
|
|
309
|
+
...Object.hasOwn(value, "firstWarnings") ? { firstWarnings: value.firstWarnings } : {},
|
|
229
310
|
...Object.hasOwn(value, "firstOutput") ? { firstOutput: value.firstOutput } : {}
|
|
230
311
|
};
|
|
231
312
|
};
|
|
@@ -244,12 +325,7 @@ var import_node_crypto3 = require("crypto");
|
|
|
244
325
|
|
|
245
326
|
// src/reason-engine.ts
|
|
246
327
|
var import_node_crypto2 = require("crypto");
|
|
247
|
-
var
|
|
248
|
-
if (typeof chunk === "string") return chunk;
|
|
249
|
-
if (typeof chunk.text === "string") return chunk.text;
|
|
250
|
-
if (typeof chunk.content === "string") return chunk.content;
|
|
251
|
-
return "";
|
|
252
|
-
};
|
|
328
|
+
var toError = (value) => value instanceof Error ? value : new Error(String(value));
|
|
253
329
|
var createRuntimeId = () => {
|
|
254
330
|
try {
|
|
255
331
|
return (0, import_node_crypto2.randomUUID)();
|
|
@@ -268,9 +344,21 @@ async function runReasonEngine(args) {
|
|
|
268
344
|
const stream = args.stream ?? args.model.options?.streaming ?? true;
|
|
269
345
|
const emit = args.emit ?? true;
|
|
270
346
|
const temperature = args.temperature ?? args.model.options?.temperature ?? args.defaultTemperature;
|
|
271
|
-
const
|
|
347
|
+
const maxOutputTokens = args.maxOutputTokens ?? args.model.options?.maxOutputTokens;
|
|
348
|
+
const stopSequences = args.stopSequences ?? args.model.options?.stopSequences;
|
|
349
|
+
const abortSignal = args.abortSignal ?? args.model.options?.abortSignal;
|
|
350
|
+
const reasoning = args.reasoning ?? args.model.options?.reasoning;
|
|
351
|
+
const responseFormat = args.responseFormat ?? args.model.options?.responseFormat;
|
|
352
|
+
const providerOptions = args.providerOptions ?? args.model.options?.providerOptions;
|
|
353
|
+
const model = args.model.provider.getModel(args.model.modelId, {
|
|
272
354
|
...temperature !== void 0 ? { temperature } : {},
|
|
273
|
-
streaming: stream
|
|
355
|
+
streaming: stream,
|
|
356
|
+
...maxOutputTokens !== void 0 ? { maxOutputTokens } : {},
|
|
357
|
+
...stopSequences !== void 0 ? { stopSequences } : {},
|
|
358
|
+
...abortSignal !== void 0 ? { abortSignal } : {},
|
|
359
|
+
...reasoning !== void 0 ? { reasoning } : {},
|
|
360
|
+
...responseFormat !== void 0 ? { responseFormat } : {},
|
|
361
|
+
...providerOptions !== void 0 ? { providerOptions } : {}
|
|
274
362
|
});
|
|
275
363
|
const messages = [];
|
|
276
364
|
if (typeof args.system === "string" && args.system.length > 0) {
|
|
@@ -284,59 +372,154 @@ async function runReasonEngine(args) {
|
|
|
284
372
|
opId,
|
|
285
373
|
segmentId
|
|
286
374
|
};
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
375
|
+
const traceSpan = args.reasonTrace?.startSpan({
|
|
376
|
+
name: "runReasonEngine",
|
|
377
|
+
attributes: {
|
|
378
|
+
...commonMeta,
|
|
379
|
+
providerId: args.model.provider.id,
|
|
380
|
+
modelId: args.model.modelId,
|
|
381
|
+
stream,
|
|
382
|
+
emit,
|
|
383
|
+
...typeof args.nodeId === "string" && args.nodeId.length > 0 ? { nodeId: args.nodeId } : {}
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
const endTrace = (span, result) => {
|
|
387
|
+
span?.end?.({
|
|
388
|
+
...result.usage !== void 0 ? { usage: result.usage } : {},
|
|
389
|
+
...result.finishReason !== void 0 ? { finishReason: result.finishReason } : {},
|
|
390
|
+
...result.providerMetadata !== void 0 ? { providerMetadata: result.providerMetadata } : {},
|
|
391
|
+
...result.warnings !== void 0 ? { warnings: result.warnings } : {},
|
|
392
|
+
attributes: {
|
|
393
|
+
textLength: result.text.length
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
};
|
|
397
|
+
const failTrace = (span, error) => {
|
|
398
|
+
span?.fail?.(error, {
|
|
399
|
+
attributes: {
|
|
400
|
+
providerId: args.model.provider.id,
|
|
401
|
+
modelId: args.model.modelId
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
};
|
|
405
|
+
try {
|
|
406
|
+
if (stream) {
|
|
407
|
+
let final = "";
|
|
408
|
+
let raw;
|
|
409
|
+
let usage;
|
|
410
|
+
let finishReason;
|
|
411
|
+
let providerMetadata;
|
|
412
|
+
let warnings;
|
|
413
|
+
if (emit) {
|
|
414
|
+
emitNodeEvent(args.emitEvent, args.nodeId, "text-start", {
|
|
415
|
+
...commonMeta
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
const response2 = await model.stream(messages);
|
|
419
|
+
for await (const chunk of response2) {
|
|
420
|
+
switch (chunk.type) {
|
|
421
|
+
case "text-delta": {
|
|
422
|
+
if (chunk.delta.length === 0) break;
|
|
423
|
+
final += chunk.delta;
|
|
424
|
+
args.onTextChunk?.(chunk.delta);
|
|
425
|
+
if (chunk.raw !== void 0) {
|
|
426
|
+
raw = chunk.raw;
|
|
427
|
+
}
|
|
428
|
+
if (chunk.providerMetadata !== void 0) {
|
|
429
|
+
providerMetadata = chunk.providerMetadata;
|
|
430
|
+
}
|
|
431
|
+
if (emit) {
|
|
432
|
+
emitNodeEvent(args.emitEvent, args.nodeId, "text-delta", {
|
|
433
|
+
...commonMeta,
|
|
434
|
+
delta: chunk.delta
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
case "finish": {
|
|
440
|
+
if (chunk.raw !== void 0) {
|
|
441
|
+
raw = chunk.raw;
|
|
442
|
+
}
|
|
443
|
+
if (chunk.usage !== void 0) {
|
|
444
|
+
usage = chunk.usage;
|
|
445
|
+
}
|
|
446
|
+
if (chunk.finishReason !== void 0) {
|
|
447
|
+
finishReason = chunk.finishReason;
|
|
448
|
+
}
|
|
449
|
+
if (chunk.providerMetadata !== void 0) {
|
|
450
|
+
providerMetadata = chunk.providerMetadata;
|
|
451
|
+
}
|
|
452
|
+
if (chunk.warnings !== void 0) {
|
|
453
|
+
warnings = chunk.warnings;
|
|
454
|
+
}
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
case "raw": {
|
|
458
|
+
raw = chunk.raw;
|
|
459
|
+
if (chunk.providerMetadata !== void 0) {
|
|
460
|
+
providerMetadata = chunk.providerMetadata;
|
|
461
|
+
}
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
case "error": {
|
|
465
|
+
if (chunk.raw !== void 0) {
|
|
466
|
+
raw = chunk.raw;
|
|
467
|
+
}
|
|
468
|
+
if (chunk.providerMetadata !== void 0) {
|
|
469
|
+
providerMetadata = chunk.providerMetadata;
|
|
470
|
+
}
|
|
471
|
+
if (chunk.warnings !== void 0) {
|
|
472
|
+
warnings = chunk.warnings;
|
|
473
|
+
}
|
|
474
|
+
throw toError(chunk.error);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (emit) {
|
|
479
|
+
emitNodeEvent(args.emitEvent, args.nodeId, "text-end", {
|
|
480
|
+
...commonMeta
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
const result2 = {
|
|
484
|
+
text: final,
|
|
485
|
+
...raw !== void 0 ? { raw } : {},
|
|
486
|
+
...usage !== void 0 ? { usage } : {},
|
|
487
|
+
...finishReason !== void 0 ? { finishReason } : {},
|
|
488
|
+
...providerMetadata !== void 0 ? { providerMetadata } : {},
|
|
489
|
+
...warnings !== void 0 ? { warnings } : {}
|
|
490
|
+
};
|
|
491
|
+
endTrace(traceSpan, result2);
|
|
492
|
+
return result2;
|
|
493
|
+
}
|
|
494
|
+
const response = await model.invoke(messages);
|
|
290
495
|
if (emit) {
|
|
291
496
|
emitNodeEvent(args.emitEvent, args.nodeId, "text-start", {
|
|
292
497
|
...commonMeta
|
|
293
498
|
});
|
|
294
|
-
|
|
295
|
-
const response2 = await model.stream(messages);
|
|
296
|
-
for await (const chunk of response2) {
|
|
297
|
-
const text = chunkToText(chunk);
|
|
298
|
-
if (!text) continue;
|
|
299
|
-
final += text;
|
|
300
|
-
args.onTextChunk?.(text);
|
|
301
|
-
if (typeof chunk === "object" && chunk && "raw" in chunk) {
|
|
302
|
-
raw = chunk.raw;
|
|
303
|
-
}
|
|
304
|
-
if (emit) {
|
|
499
|
+
if (response.content) {
|
|
305
500
|
emitNodeEvent(args.emitEvent, args.nodeId, "text-delta", {
|
|
306
501
|
...commonMeta,
|
|
307
|
-
delta:
|
|
502
|
+
delta: response.content
|
|
308
503
|
});
|
|
309
504
|
}
|
|
310
|
-
}
|
|
311
|
-
if (emit) {
|
|
312
505
|
emitNodeEvent(args.emitEvent, args.nodeId, "text-end", {
|
|
313
506
|
...commonMeta
|
|
314
507
|
});
|
|
315
508
|
}
|
|
316
|
-
|
|
317
|
-
text:
|
|
318
|
-
...raw !== void 0 ? { raw } : {}
|
|
509
|
+
const result = {
|
|
510
|
+
text: response.content,
|
|
511
|
+
...response.raw !== void 0 ? { raw: response.raw } : {},
|
|
512
|
+
...response.usage !== void 0 ? { usage: response.usage } : {},
|
|
513
|
+
...response.finishReason !== void 0 ? { finishReason: response.finishReason } : {},
|
|
514
|
+
...response.providerMetadata !== void 0 ? { providerMetadata: response.providerMetadata } : {},
|
|
515
|
+
...response.warnings !== void 0 ? { warnings: response.warnings } : {}
|
|
319
516
|
};
|
|
517
|
+
endTrace(traceSpan, result);
|
|
518
|
+
return result;
|
|
519
|
+
} catch (error) {
|
|
520
|
+
failTrace(traceSpan, error);
|
|
521
|
+
throw error;
|
|
320
522
|
}
|
|
321
|
-
const response = await model.invoke(messages);
|
|
322
|
-
if (emit) {
|
|
323
|
-
emitNodeEvent(args.emitEvent, args.nodeId, "text-start", {
|
|
324
|
-
...commonMeta
|
|
325
|
-
});
|
|
326
|
-
if (response.content) {
|
|
327
|
-
emitNodeEvent(args.emitEvent, args.nodeId, "text-delta", {
|
|
328
|
-
...commonMeta,
|
|
329
|
-
delta: response.content
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
emitNodeEvent(args.emitEvent, args.nodeId, "text-end", {
|
|
333
|
-
...commonMeta
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
return {
|
|
337
|
-
text: response.content,
|
|
338
|
-
...response.raw !== void 0 ? { raw: response.raw } : {}
|
|
339
|
-
};
|
|
340
523
|
}
|
|
341
524
|
|
|
342
525
|
// src/reason/engine.ts
|
|
@@ -349,22 +532,24 @@ var createRuntimeId2 = () => {
|
|
|
349
532
|
};
|
|
350
533
|
async function reasonEngine(args, meta, inputOverride) {
|
|
351
534
|
const ctx = getHookContext();
|
|
352
|
-
const getProvider = ctx.getProvider;
|
|
353
|
-
if (!getProvider) {
|
|
354
|
-
throw new Error("useReason requires a provider factory in runtime config.");
|
|
355
|
-
}
|
|
356
535
|
return runReasonEngine({
|
|
357
|
-
getProvider,
|
|
358
536
|
model: args.model,
|
|
359
537
|
input: inputOverride ?? args.input,
|
|
360
538
|
system: args.system,
|
|
361
539
|
temperature: args.temperature,
|
|
540
|
+
maxOutputTokens: args.maxOutputTokens,
|
|
541
|
+
stopSequences: args.stopSequences,
|
|
542
|
+
abortSignal: args.abortSignal,
|
|
543
|
+
reasoning: args.reasoning,
|
|
544
|
+
responseFormat: args.responseFormat,
|
|
545
|
+
providerOptions: args.providerOptions,
|
|
362
546
|
defaultTemperature: ctx.node.config?.model?.temperature,
|
|
363
547
|
stream: args.stream,
|
|
364
548
|
emit: args.emit,
|
|
365
549
|
onTextChunk: args.onTextChunk,
|
|
366
550
|
nodeId: ctx.node.graph.node,
|
|
367
551
|
emitEvent: ctx.node.emit,
|
|
552
|
+
reasonTrace: ctx.reasonTrace,
|
|
368
553
|
id: meta.id,
|
|
369
554
|
opId: meta.opId
|
|
370
555
|
});
|
|
@@ -374,8 +559,11 @@ async function reasonEngine(args, meta, inputOverride) {
|
|
|
374
559
|
var tryJsonParse = (value) => {
|
|
375
560
|
try {
|
|
376
561
|
return { ok: true, value: JSON.parse(value) };
|
|
377
|
-
} catch {
|
|
378
|
-
return {
|
|
562
|
+
} catch (error) {
|
|
563
|
+
return {
|
|
564
|
+
ok: false,
|
|
565
|
+
error: error instanceof Error ? error.message : String(error)
|
|
566
|
+
};
|
|
379
567
|
}
|
|
380
568
|
};
|
|
381
569
|
var extractJsonCodeBlock = (value) => {
|
|
@@ -405,9 +593,50 @@ var resolveOutputCandidate = (text) => {
|
|
|
405
593
|
}
|
|
406
594
|
return text;
|
|
407
595
|
};
|
|
596
|
+
var looksLikeStructuredJsonAttempt = (value) => {
|
|
597
|
+
const trimmed = value.trim();
|
|
598
|
+
if (!trimmed) return false;
|
|
599
|
+
return trimmed.startsWith("{") || trimmed.startsWith("[") || /^```(?:json)?/i.test(trimmed);
|
|
600
|
+
};
|
|
601
|
+
var isLikelyTruncatedStructuredOutput = (text) => {
|
|
602
|
+
const trimmed = text.trim();
|
|
603
|
+
if (!looksLikeStructuredJsonAttempt(trimmed)) return false;
|
|
604
|
+
const direct = tryJsonParse(trimmed);
|
|
605
|
+
if (!direct.ok && direct.error) return true;
|
|
606
|
+
const block = extractJsonCodeBlock(trimmed);
|
|
607
|
+
if (!block) return false;
|
|
608
|
+
const parsedBlock = tryJsonParse(block);
|
|
609
|
+
return !parsedBlock.ok && Boolean(parsedBlock.error);
|
|
610
|
+
};
|
|
611
|
+
var createTruncatedStructuredOutputError = (label) => new Error(
|
|
612
|
+
`${label} was truncated before producing valid structured output. The model stopped due to output length. Increase maxOutputTokens or simplify the requested output.`
|
|
613
|
+
);
|
|
614
|
+
var createInvalidStructuredOutputError = (label) => new Error(
|
|
615
|
+
`${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.`
|
|
616
|
+
);
|
|
617
|
+
var parseReasonOutputWithSchema = (args) => {
|
|
618
|
+
const candidate = resolveOutputCandidate(args.text);
|
|
619
|
+
try {
|
|
620
|
+
return parseWithSchema(args.schema, candidate, args.label);
|
|
621
|
+
} catch (error) {
|
|
622
|
+
if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
|
|
623
|
+
throw createTruncatedStructuredOutputError(args.label);
|
|
624
|
+
}
|
|
625
|
+
if (typeof candidate === "string" && candidate.trim().length > 0) {
|
|
626
|
+
throw createInvalidStructuredOutputError(args.label);
|
|
627
|
+
}
|
|
628
|
+
throw error;
|
|
629
|
+
}
|
|
630
|
+
};
|
|
408
631
|
var parseInterruptFirstPassResult = (args) => {
|
|
409
632
|
const candidate = resolveOutputCandidate(args.text);
|
|
410
633
|
if (!isRecord2(candidate)) {
|
|
634
|
+
if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
|
|
635
|
+
throw createTruncatedStructuredOutputError("useReason output");
|
|
636
|
+
}
|
|
637
|
+
if (typeof candidate === "string" && candidate.trim().length > 0) {
|
|
638
|
+
throw createInvalidStructuredOutputError("useReason output");
|
|
639
|
+
}
|
|
411
640
|
throw new Error(
|
|
412
641
|
"useReason first pass with interrupt must return a JSON object."
|
|
413
642
|
);
|
|
@@ -429,11 +658,21 @@ var parseInterruptFirstPassResult = (args) => {
|
|
|
429
658
|
let output;
|
|
430
659
|
if (args.outputSchema) {
|
|
431
660
|
const outputPayload = Object.hasOwn(candidate, "output") ? candidate.output : candidate;
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
661
|
+
try {
|
|
662
|
+
output = parseWithSchema(
|
|
663
|
+
args.outputSchema,
|
|
664
|
+
outputPayload,
|
|
665
|
+
"useReason output"
|
|
666
|
+
);
|
|
667
|
+
} catch (error) {
|
|
668
|
+
if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
|
|
669
|
+
throw createTruncatedStructuredOutputError("useReason output");
|
|
670
|
+
}
|
|
671
|
+
if (typeof outputPayload === "string" && outputPayload.trim().length > 0) {
|
|
672
|
+
throw createInvalidStructuredOutputError("useReason output");
|
|
673
|
+
}
|
|
674
|
+
throw error;
|
|
675
|
+
}
|
|
437
676
|
}
|
|
438
677
|
const explicitDraft = typeof candidate.draftText === "string" && candidate.draftText.length > 0 ? candidate.draftText : typeof candidate.text === "string" && candidate.text.length > 0 ? candidate.text : void 0;
|
|
439
678
|
if (!args.outputSchema && !explicitDraft) {
|
|
@@ -582,9 +821,10 @@ var resolveAppendFieldPaths = (cfg) => {
|
|
|
582
821
|
if (!fields) return [];
|
|
583
822
|
const appendPaths = Object.entries(fields).filter(([, mode]) => mode === "append").map(([path]) => path);
|
|
584
823
|
for (const path of appendPaths) {
|
|
585
|
-
|
|
824
|
+
assertStructuredPath(path, "useReason structured append");
|
|
825
|
+
if (path.includes(".")) {
|
|
586
826
|
throw new Error(
|
|
587
|
-
"useReason structured append streaming
|
|
827
|
+
"useReason structured append streaming requires non-empty top-level array field keys."
|
|
588
828
|
);
|
|
589
829
|
}
|
|
590
830
|
}
|
|
@@ -595,9 +835,10 @@ var resolveSetFieldPaths = (cfg) => {
|
|
|
595
835
|
if (!fields) return [];
|
|
596
836
|
const setPaths = Object.entries(fields).filter(([, mode]) => mode === "set").map(([path]) => path);
|
|
597
837
|
for (const path of setPaths) {
|
|
598
|
-
|
|
838
|
+
assertStructuredPath(path, "useReason structured set");
|
|
839
|
+
if (path.includes(".")) {
|
|
599
840
|
throw new Error(
|
|
600
|
-
"useReason structured set streaming
|
|
841
|
+
"useReason structured set streaming requires non-empty top-level field keys."
|
|
601
842
|
);
|
|
602
843
|
}
|
|
603
844
|
}
|
|
@@ -608,9 +849,10 @@ var resolveTextDeltaFieldPaths = (cfg) => {
|
|
|
608
849
|
if (!fields) return [];
|
|
609
850
|
const textPaths = Object.entries(fields).filter(([, mode]) => mode === "text-delta").map(([path]) => path);
|
|
610
851
|
for (const path of textPaths) {
|
|
611
|
-
|
|
852
|
+
assertStructuredPath(path, "useReason structured text-delta");
|
|
853
|
+
if (path.includes(".")) {
|
|
612
854
|
throw new Error(
|
|
613
|
-
"useReason structured text-delta streaming
|
|
855
|
+
"useReason structured text-delta streaming requires non-empty top-level string field keys."
|
|
614
856
|
);
|
|
615
857
|
}
|
|
616
858
|
}
|
|
@@ -948,8 +1190,53 @@ var extractStreamingStringValue = (args) => {
|
|
|
948
1190
|
};
|
|
949
1191
|
|
|
950
1192
|
// src/reason/use-reason.ts
|
|
1193
|
+
var mergeUsage = (left, right) => {
|
|
1194
|
+
if (!left) return right;
|
|
1195
|
+
if (!right) return left;
|
|
1196
|
+
const sum = (a, b) => a === void 0 && b === void 0 ? void 0 : (a ?? 0) + (b ?? 0);
|
|
1197
|
+
const raw = left.raw || right.raw ? {
|
|
1198
|
+
...left.raw ?? {},
|
|
1199
|
+
...right.raw ?? {}
|
|
1200
|
+
} : void 0;
|
|
1201
|
+
const input = sum(left.input, right.input);
|
|
1202
|
+
const output = sum(left.output, right.output);
|
|
1203
|
+
const total = sum(left.total, right.total);
|
|
1204
|
+
const reasoning = sum(left.reasoning, right.reasoning);
|
|
1205
|
+
const cacheRead = sum(left.cacheRead, right.cacheRead);
|
|
1206
|
+
const cacheWrite = sum(left.cacheWrite, right.cacheWrite);
|
|
1207
|
+
const merged = {};
|
|
1208
|
+
if (input !== void 0) merged.input = input;
|
|
1209
|
+
if (output !== void 0) merged.output = output;
|
|
1210
|
+
if (total !== void 0) merged.total = total;
|
|
1211
|
+
if (reasoning !== void 0) merged.reasoning = reasoning;
|
|
1212
|
+
if (cacheRead !== void 0) merged.cacheRead = cacheRead;
|
|
1213
|
+
if (cacheWrite !== void 0) merged.cacheWrite = cacheWrite;
|
|
1214
|
+
if (raw) merged.raw = raw;
|
|
1215
|
+
return merged;
|
|
1216
|
+
};
|
|
1217
|
+
var mergeWarnings = (left, right) => {
|
|
1218
|
+
if (!left?.length) return right;
|
|
1219
|
+
if (!right?.length) return left;
|
|
1220
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1221
|
+
const merged = [];
|
|
1222
|
+
for (const warning of [...left, ...right]) {
|
|
1223
|
+
const key = JSON.stringify(warning);
|
|
1224
|
+
if (seen.has(key)) continue;
|
|
1225
|
+
seen.add(key);
|
|
1226
|
+
merged.push(warning);
|
|
1227
|
+
}
|
|
1228
|
+
return merged;
|
|
1229
|
+
};
|
|
1230
|
+
var mergeProviderMetadata = (left, right) => {
|
|
1231
|
+
if (!left) return right;
|
|
1232
|
+
if (!right) return left;
|
|
1233
|
+
return {
|
|
1234
|
+
...left,
|
|
1235
|
+
...right
|
|
1236
|
+
};
|
|
1237
|
+
};
|
|
951
1238
|
var emitReasonStructuredOutput = (args) => {
|
|
952
|
-
if (!shouldEmitStructured(args.structured)) return;
|
|
1239
|
+
if (!args.emit || !shouldEmitStructured(args.structured)) return;
|
|
953
1240
|
emitStructuredData({
|
|
954
1241
|
dataType: args.structured?.dataType ?? "reason-output",
|
|
955
1242
|
data: args.output,
|
|
@@ -960,7 +1247,19 @@ var emitReasonStructuredOutput = (args) => {
|
|
|
960
1247
|
streamId: args.opId
|
|
961
1248
|
});
|
|
962
1249
|
};
|
|
1250
|
+
var shouldEmitReasonStructured = (args) => (args.emit ?? true) && shouldEmitStructured(args.structured);
|
|
1251
|
+
var resolveEffectiveReasoningIncludeThoughts = (args) => args.reasoning?.includeThoughts ?? args.model.options?.reasoning?.includeThoughts ?? false;
|
|
1252
|
+
var resolveEffectiveResponseFormatType = (args) => args.responseFormat?.type ?? args.model.options?.responseFormat?.type;
|
|
1253
|
+
var assertReasoningThoughtsCompatibility = (args) => {
|
|
1254
|
+
if (!resolveEffectiveReasoningIncludeThoughts(args)) return;
|
|
1255
|
+
const usesStructuredOutput = Boolean(args.outputSchema) || Boolean(args.interrupt) || Boolean(args.structured) || resolveEffectiveResponseFormatType(args) === "json";
|
|
1256
|
+
if (!usesStructuredOutput) return;
|
|
1257
|
+
throw new Error(
|
|
1258
|
+
"useReason does not support reasoning.includeThoughts with structured output, interrupt mode, or JSON responseFormat. Disable includeThoughts for this call."
|
|
1259
|
+
);
|
|
1260
|
+
};
|
|
963
1261
|
async function useReason(args) {
|
|
1262
|
+
assertReasoningThoughtsCompatibility(args);
|
|
964
1263
|
const ctx = getHookContext();
|
|
965
1264
|
const id = typeof args.id === "string" && args.id.length > 0 ? args.id : void 0;
|
|
966
1265
|
const opId = createRuntimeId2();
|
|
@@ -972,12 +1271,27 @@ async function useReason(args) {
|
|
|
972
1271
|
ctx.currentNodeState.byKey[checkpointKey]
|
|
973
1272
|
);
|
|
974
1273
|
const reasonMeta = typeof id === "string" ? { id, opId } : { opId };
|
|
1274
|
+
const traceSpan = getReasonTraceAdapter()?.startSpan({
|
|
1275
|
+
name: "useReason",
|
|
1276
|
+
attributes: {
|
|
1277
|
+
...id ? { id } : {},
|
|
1278
|
+
opId,
|
|
1279
|
+
nodeId: ctx.node.graph.node,
|
|
1280
|
+
providerId: args.model.provider.id,
|
|
1281
|
+
modelId: args.model.modelId,
|
|
1282
|
+
stream: args.stream ?? args.model.options?.streaming ?? true,
|
|
1283
|
+
emit: args.emit ?? true,
|
|
1284
|
+
hasOutputSchema: Boolean(args.outputSchema),
|
|
1285
|
+
hasInterrupt: Boolean(args.interrupt),
|
|
1286
|
+
hasStructured: Boolean(args.structured)
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
975
1289
|
const suppressTextStream = Boolean(args.outputSchema || args.interrupt);
|
|
976
1290
|
const setFieldPaths = resolveSetFieldPaths(args.structured);
|
|
977
1291
|
const appendFieldPaths = resolveAppendFieldPaths(args.structured);
|
|
978
1292
|
const textDeltaFieldPaths = resolveTextDeltaFieldPaths(args.structured);
|
|
979
1293
|
const useStructuredIncrementalStreaming = Boolean(
|
|
980
|
-
args.outputSchema && !args.interrupt && (setFieldPaths.length > 0 || appendFieldPaths.length > 0 || textDeltaFieldPaths.length > 0) && args.stream !== false &&
|
|
1294
|
+
args.outputSchema && !args.interrupt && (setFieldPaths.length > 0 || appendFieldPaths.length > 0 || textDeltaFieldPaths.length > 0) && args.stream !== false && shouldEmitReasonStructured(args)
|
|
981
1295
|
);
|
|
982
1296
|
let firstText = "";
|
|
983
1297
|
let firstRaw;
|
|
@@ -986,6 +1300,10 @@ async function useReason(args) {
|
|
|
986
1300
|
let finalText = "";
|
|
987
1301
|
let finalRaw;
|
|
988
1302
|
let finalOutput;
|
|
1303
|
+
let aggregatedUsage;
|
|
1304
|
+
let finalFinishReason;
|
|
1305
|
+
let aggregatedProviderMetadata;
|
|
1306
|
+
let aggregatedWarnings;
|
|
989
1307
|
const emittedSetValues = /* @__PURE__ */ new Map();
|
|
990
1308
|
const emittedAppendCounts = /* @__PURE__ */ new Map();
|
|
991
1309
|
const emittedTextValues = /* @__PURE__ */ new Map();
|
|
@@ -996,6 +1314,13 @@ async function useReason(args) {
|
|
|
996
1314
|
finalText = firstText;
|
|
997
1315
|
finalRaw = firstRaw;
|
|
998
1316
|
finalOutput = firstOutput;
|
|
1317
|
+
aggregatedUsage = existingCheckpoint.firstUsage;
|
|
1318
|
+
finalFinishReason = existingCheckpoint.firstFinishReason;
|
|
1319
|
+
aggregatedProviderMetadata = existingCheckpoint.firstProviderMetadata;
|
|
1320
|
+
aggregatedWarnings = existingCheckpoint.firstWarnings;
|
|
1321
|
+
traceSpan?.addEvent?.("useReason.resume", {
|
|
1322
|
+
checkpointKey
|
|
1323
|
+
});
|
|
999
1324
|
} else {
|
|
1000
1325
|
const first = await reasonEngine(
|
|
1001
1326
|
{
|
|
@@ -1090,10 +1415,23 @@ async function useReason(args) {
|
|
|
1090
1415
|
);
|
|
1091
1416
|
firstRaw = first.raw;
|
|
1092
1417
|
finalRaw = first.raw;
|
|
1418
|
+
accumulateTokenUsage(first.usage);
|
|
1419
|
+
aggregatedUsage = mergeUsage(aggregatedUsage, first.usage);
|
|
1420
|
+
finalFinishReason = first.finishReason;
|
|
1421
|
+
aggregatedProviderMetadata = mergeProviderMetadata(
|
|
1422
|
+
aggregatedProviderMetadata,
|
|
1423
|
+
first.providerMetadata
|
|
1424
|
+
);
|
|
1425
|
+
aggregatedWarnings = mergeWarnings(aggregatedWarnings, first.warnings);
|
|
1426
|
+
traceSpan?.addEvent?.("useReason.first-pass.complete", {
|
|
1427
|
+
textLength: first.text.length,
|
|
1428
|
+
hasInterrupt: Boolean(args.interrupt)
|
|
1429
|
+
});
|
|
1093
1430
|
if (args.interrupt) {
|
|
1094
1431
|
const firstPass = parseInterruptFirstPassResult({
|
|
1095
1432
|
text: first.text,
|
|
1096
1433
|
requestSchema: args.interrupt.requestSchema,
|
|
1434
|
+
...first.finishReason ? { finishReason: first.finishReason } : {},
|
|
1097
1435
|
...args.outputSchema ? { outputSchema: args.outputSchema } : {}
|
|
1098
1436
|
});
|
|
1099
1437
|
firstText = firstPass.draftText;
|
|
@@ -1105,11 +1443,12 @@ async function useReason(args) {
|
|
|
1105
1443
|
firstText = first.text;
|
|
1106
1444
|
finalText = first.text;
|
|
1107
1445
|
if (args.outputSchema) {
|
|
1108
|
-
firstOutput =
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1446
|
+
firstOutput = parseReasonOutputWithSchema({
|
|
1447
|
+
text: first.text,
|
|
1448
|
+
schema: args.outputSchema,
|
|
1449
|
+
...first.finishReason ? { finishReason: first.finishReason } : {},
|
|
1450
|
+
label: "useReason output"
|
|
1451
|
+
});
|
|
1113
1452
|
}
|
|
1114
1453
|
finalOutput = firstOutput;
|
|
1115
1454
|
}
|
|
@@ -1133,9 +1472,13 @@ async function useReason(args) {
|
|
|
1133
1472
|
request: interruptRequest,
|
|
1134
1473
|
firstText,
|
|
1135
1474
|
...firstRaw !== void 0 ? { firstRaw } : {},
|
|
1475
|
+
...aggregatedUsage !== void 0 ? { firstUsage: aggregatedUsage } : {},
|
|
1476
|
+
...finalFinishReason !== void 0 ? { firstFinishReason: finalFinishReason } : {},
|
|
1477
|
+
...aggregatedProviderMetadata !== void 0 ? { firstProviderMetadata: aggregatedProviderMetadata } : {},
|
|
1478
|
+
...aggregatedWarnings !== void 0 ? { firstWarnings: aggregatedWarnings } : {},
|
|
1136
1479
|
...firstOutput !== void 0 ? { firstOutput } : {}
|
|
1137
1480
|
};
|
|
1138
|
-
ctx.
|
|
1481
|
+
ctx.stateDirty = true;
|
|
1139
1482
|
}
|
|
1140
1483
|
const resumeStatePatch = resolveHookStatePatch({
|
|
1141
1484
|
nodeId: ctx.node.graph.node,
|
|
@@ -1153,9 +1496,12 @@ async function useReason(args) {
|
|
|
1153
1496
|
__kortyxResumeStatePatch: resumeStatePatch
|
|
1154
1497
|
}
|
|
1155
1498
|
});
|
|
1499
|
+
traceSpan?.addEvent?.("useReason.interrupt.resolved", {
|
|
1500
|
+
checkpointKey
|
|
1501
|
+
});
|
|
1156
1502
|
if (Object.hasOwn(ctx.currentNodeState.byKey, checkpointKey)) {
|
|
1157
1503
|
delete ctx.currentNodeState.byKey[checkpointKey];
|
|
1158
|
-
ctx.
|
|
1504
|
+
ctx.stateDirty = true;
|
|
1159
1505
|
}
|
|
1160
1506
|
const continuationInput = defaultContinuationInput({
|
|
1161
1507
|
input: args.input,
|
|
@@ -1178,12 +1524,24 @@ async function useReason(args) {
|
|
|
1178
1524
|
);
|
|
1179
1525
|
finalText = second.text;
|
|
1180
1526
|
finalRaw = second.raw;
|
|
1527
|
+
accumulateTokenUsage(second.usage);
|
|
1528
|
+
aggregatedUsage = mergeUsage(aggregatedUsage, second.usage);
|
|
1529
|
+
finalFinishReason = second.finishReason;
|
|
1530
|
+
aggregatedProviderMetadata = mergeProviderMetadata(
|
|
1531
|
+
aggregatedProviderMetadata,
|
|
1532
|
+
second.providerMetadata
|
|
1533
|
+
);
|
|
1534
|
+
aggregatedWarnings = mergeWarnings(aggregatedWarnings, second.warnings);
|
|
1535
|
+
traceSpan?.addEvent?.("useReason.continuation.complete", {
|
|
1536
|
+
textLength: second.text.length
|
|
1537
|
+
});
|
|
1181
1538
|
if (args.outputSchema) {
|
|
1182
|
-
finalOutput =
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1539
|
+
finalOutput = parseReasonOutputWithSchema({
|
|
1540
|
+
text: finalText,
|
|
1541
|
+
schema: args.outputSchema,
|
|
1542
|
+
...second.finishReason ? { finishReason: second.finishReason } : {},
|
|
1543
|
+
label: "useReason output"
|
|
1544
|
+
});
|
|
1187
1545
|
}
|
|
1188
1546
|
}
|
|
1189
1547
|
if (finalOutput !== void 0) {
|
|
@@ -1191,17 +1549,34 @@ async function useReason(args) {
|
|
|
1191
1549
|
...id ? { id } : {},
|
|
1192
1550
|
opId,
|
|
1193
1551
|
output: finalOutput,
|
|
1194
|
-
structured: args.structured
|
|
1552
|
+
structured: args.structured,
|
|
1553
|
+
emit: args.emit ?? true
|
|
1195
1554
|
});
|
|
1196
1555
|
}
|
|
1197
|
-
|
|
1556
|
+
const result = {
|
|
1198
1557
|
...id ? { id } : {},
|
|
1199
1558
|
opId,
|
|
1200
1559
|
text: finalText,
|
|
1201
1560
|
...finalRaw !== void 0 ? { raw: finalRaw } : {},
|
|
1561
|
+
...aggregatedUsage !== void 0 ? { usage: aggregatedUsage } : {},
|
|
1562
|
+
...finalFinishReason !== void 0 ? { finishReason: finalFinishReason } : {},
|
|
1563
|
+
...aggregatedProviderMetadata !== void 0 ? { providerMetadata: aggregatedProviderMetadata } : {},
|
|
1564
|
+
...aggregatedWarnings !== void 0 ? { warnings: aggregatedWarnings } : {},
|
|
1202
1565
|
...finalOutput !== void 0 ? { output: finalOutput } : {},
|
|
1203
1566
|
...interruptResponse !== void 0 ? { interruptResponse } : {}
|
|
1204
1567
|
};
|
|
1568
|
+
traceSpan?.end?.({
|
|
1569
|
+
...aggregatedUsage !== void 0 ? { usage: aggregatedUsage } : {},
|
|
1570
|
+
...finalFinishReason !== void 0 ? { finishReason: finalFinishReason } : {},
|
|
1571
|
+
...aggregatedProviderMetadata !== void 0 ? { providerMetadata: aggregatedProviderMetadata } : {},
|
|
1572
|
+
...aggregatedWarnings !== void 0 ? { warnings: aggregatedWarnings } : {},
|
|
1573
|
+
attributes: {
|
|
1574
|
+
textLength: finalText.length,
|
|
1575
|
+
resumedFromCheckpoint: Boolean(existingCheckpoint),
|
|
1576
|
+
interrupted: Boolean(interruptResponse !== void 0)
|
|
1577
|
+
}
|
|
1578
|
+
});
|
|
1579
|
+
return result;
|
|
1205
1580
|
}
|
|
1206
1581
|
|
|
1207
1582
|
// src/hooks.ts
|
|
@@ -1220,14 +1595,14 @@ function useNodeState(initialValue) {
|
|
|
1220
1595
|
const index = ctx.nodeStateIndex++;
|
|
1221
1596
|
if (index >= nodeState.byIndex.length) {
|
|
1222
1597
|
nodeState.byIndex[index] = initialValue;
|
|
1223
|
-
ctx.
|
|
1598
|
+
ctx.stateDirty = true;
|
|
1224
1599
|
}
|
|
1225
1600
|
const getValue = () => nodeState.byIndex[index];
|
|
1226
1601
|
const setValue = (next) => {
|
|
1227
1602
|
const prev = getValue();
|
|
1228
1603
|
const resolved = typeof next === "function" ? next(prev) : next;
|
|
1229
1604
|
nodeState.byIndex[index] = resolved;
|
|
1230
|
-
ctx.
|
|
1605
|
+
ctx.stateDirty = true;
|
|
1231
1606
|
};
|
|
1232
1607
|
return [getValue(), setValue];
|
|
1233
1608
|
}
|
|
@@ -1238,14 +1613,14 @@ function useWorkflowState(key, ...rest) {
|
|
|
1238
1613
|
const initialValue = rest[0];
|
|
1239
1614
|
if (!Object.hasOwn(workflowState, key) && hasInitial) {
|
|
1240
1615
|
workflowState[key] = initialValue;
|
|
1241
|
-
ctx.
|
|
1616
|
+
ctx.stateDirty = true;
|
|
1242
1617
|
}
|
|
1243
1618
|
const getValue = () => workflowState[key];
|
|
1244
1619
|
const setValue = (next) => {
|
|
1245
1620
|
const prev = getValue();
|
|
1246
1621
|
const resolved = typeof next === "function" ? next(prev) : next;
|
|
1247
1622
|
workflowState[key] = resolved;
|
|
1248
|
-
ctx.
|
|
1623
|
+
ctx.stateDirty = true;
|
|
1249
1624
|
};
|
|
1250
1625
|
return [getValue(), setValue];
|
|
1251
1626
|
}
|