@kortyx/agent 0.20.0 → 0.21.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 +28 -0
- package/dist/index.js +300 -35
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.21.0](https://github.com/kortyx-io/kortyx/compare/agent-v0.20.1...agent-v0.21.0) (2026-09-11)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **hooks:** support durable parallel child workflows ([#171](https://github.com/kortyx-io/kortyx/issues/171)) ([fdf2d56](https://github.com/kortyx-io/kortyx/commit/fdf2d564d88e921988e8f3e20da9fe0c30bbcdf9))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Dependencies
|
|
12
|
+
|
|
13
|
+
* The following workspace dependencies were updated
|
|
14
|
+
* dependencies
|
|
15
|
+
* @kortyx/runtime bumped to 0.16.0
|
|
16
|
+
* devDependencies
|
|
17
|
+
* @kortyx/hooks bumped to 0.24.0
|
|
18
|
+
|
|
19
|
+
## [0.20.1](https://github.com/kortyx-io/kortyx/compare/agent-v0.20.0...agent-v0.20.1) (2026-09-11)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Dependencies
|
|
23
|
+
|
|
24
|
+
* The following workspace dependencies were updated
|
|
25
|
+
* dependencies
|
|
26
|
+
* @kortyx/providers bumped to 0.6.1
|
|
27
|
+
* @kortyx/runtime bumped to 0.15.2
|
|
28
|
+
* devDependencies
|
|
29
|
+
* @kortyx/hooks bumped to 0.23.1
|
|
30
|
+
|
|
3
31
|
## [0.20.0](https://github.com/kortyx-io/kortyx/compare/agent-v0.19.0...agent-v0.20.0) (2026-09-11)
|
|
4
32
|
|
|
5
33
|
|
package/dist/index.js
CHANGED
|
@@ -51,10 +51,12 @@ var require_dist = __commonJS({
|
|
|
51
51
|
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
52
52
|
var index_exports2 = {};
|
|
53
53
|
__export2(index_exports2, {
|
|
54
|
+
ParallelError: () => ParallelError,
|
|
54
55
|
WorkflowCallError: () => WorkflowCallError,
|
|
55
56
|
completeResponse: () => completeResponse,
|
|
56
57
|
createWorkflowHooks: () => createWorkflowHooks,
|
|
57
58
|
emitWorkflowCall: () => emitWorkflowCall2,
|
|
59
|
+
parallel: () => parallel,
|
|
58
60
|
runWithHookContext: () => runWithHookContext,
|
|
59
61
|
useAbortSignal: () => useAbortSignal,
|
|
60
62
|
useInterrupt: () => useInterrupt,
|
|
@@ -118,6 +120,11 @@ var require_dist = __commonJS({
|
|
|
118
120
|
...ctx,
|
|
119
121
|
workflowCallActive: false,
|
|
120
122
|
workflowCallIds: /* @__PURE__ */ new Set(),
|
|
123
|
+
workflowTasks: [],
|
|
124
|
+
parallelJoins: [],
|
|
125
|
+
parallelGroup: void 0,
|
|
126
|
+
parallelIndex: 0,
|
|
127
|
+
workflowContextClosed: false,
|
|
121
128
|
nodeStateIndex: 0,
|
|
122
129
|
reasonCallIndex: 0,
|
|
123
130
|
currentNodeState,
|
|
@@ -189,10 +196,29 @@ var require_dist = __commonJS({
|
|
|
189
196
|
async function runWithHookContext(ctx, fn) {
|
|
190
197
|
const internal = createInternalContext(ctx);
|
|
191
198
|
try {
|
|
192
|
-
const result = await storage.run(internal,
|
|
199
|
+
const result = await storage.run(internal, async () => {
|
|
200
|
+
const drain = async () => {
|
|
201
|
+
let drained = -1;
|
|
202
|
+
while (drained !== internal.parallelJoins.length + internal.workflowTasks.length) {
|
|
203
|
+
drained = internal.parallelJoins.length + internal.workflowTasks.length;
|
|
204
|
+
await Promise.allSettled(internal.parallelJoins);
|
|
205
|
+
await Promise.all(internal.workflowTasks.map((task) => task.done));
|
|
206
|
+
}
|
|
207
|
+
internal.workflowContextClosed = true;
|
|
208
|
+
};
|
|
209
|
+
try {
|
|
210
|
+
const result2 = await fn();
|
|
211
|
+
await drain();
|
|
212
|
+
if (internal.workflowControlError) throw internal.workflowControlError;
|
|
213
|
+
return result2;
|
|
214
|
+
} catch (error) {
|
|
215
|
+
await drain();
|
|
216
|
+
throw internal.workflowControlError ?? error;
|
|
217
|
+
}
|
|
218
|
+
});
|
|
193
219
|
cleanupCompletedReasonCheckpoints(internal);
|
|
194
220
|
for (const key of Object.keys(internal.currentNodeState.byKey)) {
|
|
195
|
-
if (key.startsWith("__useWorkflow:") || key === "__useWorkflowActivation") {
|
|
221
|
+
if (key.startsWith("__useWorkflow:") || key.startsWith("__parallel:") || key === "__useWorkflowActivation") {
|
|
196
222
|
internal.currentNodeState.byKey[key] = null;
|
|
197
223
|
internal.stateDirty = true;
|
|
198
224
|
}
|
|
@@ -432,6 +458,16 @@ var require_dist = __commonJS({
|
|
|
432
458
|
...typeof nodeId === "string" && nodeId.length > 0 ? { node: nodeId } : {}
|
|
433
459
|
});
|
|
434
460
|
};
|
|
461
|
+
var resolveProviderOptions = (defaults, overrides) => {
|
|
462
|
+
const providerOptions = defaults || overrides ? { ...defaults, ...overrides } : void 0;
|
|
463
|
+
for (const [key, value] of Object.entries(overrides ?? {})) {
|
|
464
|
+
const previous = defaults?.[key];
|
|
465
|
+
if (previous && value && typeof previous === "object" && typeof value === "object" && !Array.isArray(previous) && !Array.isArray(value)) {
|
|
466
|
+
if (providerOptions) providerOptions[key] = { ...previous, ...value };
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return providerOptions;
|
|
470
|
+
};
|
|
435
471
|
async function runReasonEngine(args) {
|
|
436
472
|
let stream = args.stream ?? args.model.options?.streaming ?? true;
|
|
437
473
|
const emit = args.emit ?? true;
|
|
@@ -441,14 +477,10 @@ var require_dist = __commonJS({
|
|
|
441
477
|
const abortSignal = args.abortSignal ?? args.model.options?.abortSignal;
|
|
442
478
|
const reasoning = args.reasoning ?? args.model.options?.reasoning;
|
|
443
479
|
const responseFormat = args.responseFormat ?? args.model.options?.responseFormat;
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if (previous && value && typeof previous === "object" && typeof value === "object" && !Array.isArray(previous) && !Array.isArray(value)) {
|
|
449
|
-
if (providerOptions) providerOptions[key] = { ...previous, ...value };
|
|
450
|
-
}
|
|
451
|
-
}
|
|
480
|
+
const providerOptions = resolveProviderOptions(
|
|
481
|
+
args.model.options?.providerOptions,
|
|
482
|
+
args.providerOptions
|
|
483
|
+
);
|
|
452
484
|
const tools = args.tools ?? args.model.options?.tools;
|
|
453
485
|
(0, import_core22.throwIfExecutionAborted)(abortSignal);
|
|
454
486
|
const model = args.model.provider.getModel(args.model.modelId, {
|
|
@@ -1861,6 +1893,7 @@ ${tryStringify2(args.response)}`
|
|
|
1861
1893
|
const tools = useReasonArgs.tools ?? [];
|
|
1862
1894
|
const toolByName = /* @__PURE__ */ new Map();
|
|
1863
1895
|
let validationCompleted = false;
|
|
1896
|
+
let separateOutput = false;
|
|
1864
1897
|
try {
|
|
1865
1898
|
for (const tool of tools) {
|
|
1866
1899
|
if (toolByName.has(tool.name)) {
|
|
@@ -1875,6 +1908,17 @@ ${tryStringify2(args.response)}`
|
|
|
1875
1908
|
"useReason tools cannot be combined with useReason interrupt mode yet. Use toolExecution.approval for tool approval."
|
|
1876
1909
|
);
|
|
1877
1910
|
}
|
|
1911
|
+
separateOutput = Boolean(
|
|
1912
|
+
useReasonArgs.model.provider.getModel(useReasonArgs.model.modelId, {
|
|
1913
|
+
...useReasonArgs.model.options,
|
|
1914
|
+
...useReasonArgs.reasoning !== void 0 ? { reasoning: useReasonArgs.reasoning } : {},
|
|
1915
|
+
...useReasonArgs.responseFormat ? { responseFormat: useReasonArgs.responseFormat } : {},
|
|
1916
|
+
providerOptions: resolveProviderOptions(
|
|
1917
|
+
useReasonArgs.model.options?.providerOptions,
|
|
1918
|
+
useReasonArgs.providerOptions
|
|
1919
|
+
) ?? {}
|
|
1920
|
+
}).requiresSeparateStructuredOutput
|
|
1921
|
+
);
|
|
1878
1922
|
validationCompleted = true;
|
|
1879
1923
|
} finally {
|
|
1880
1924
|
if (!validationCompleted) {
|
|
@@ -1882,6 +1926,8 @@ ${tryStringify2(args.response)}`
|
|
|
1882
1926
|
}
|
|
1883
1927
|
}
|
|
1884
1928
|
const toolDefinitions = toToolDefinitions(tools);
|
|
1929
|
+
let finalizing = checkpoint?.finalizing ?? false;
|
|
1930
|
+
let completed = false;
|
|
1885
1931
|
const maxSteps = Math.max(1, useReasonArgs.toolExecution?.maxSteps ?? 3);
|
|
1886
1932
|
const messages = checkpoint?.messages ?? createInitialMessages({
|
|
1887
1933
|
system: useReasonArgs.system,
|
|
@@ -1916,6 +1962,7 @@ ${tryStringify2(args.response)}`
|
|
|
1916
1962
|
messages,
|
|
1917
1963
|
steps,
|
|
1918
1964
|
approvedCalls,
|
|
1965
|
+
finalizing,
|
|
1919
1966
|
...pending ? { pending } : {}
|
|
1920
1967
|
};
|
|
1921
1968
|
ctx.stateDirty = true;
|
|
@@ -1939,11 +1986,12 @@ ${tryStringify2(args.response)}`
|
|
|
1939
1986
|
const stream = useReasonArgs.stream ?? useReasonArgs.model.options?.streaming ?? true;
|
|
1940
1987
|
const emit = useReasonArgs.emit ?? true;
|
|
1941
1988
|
let textStarted = false;
|
|
1942
|
-
const structuredChunk = useReasonArgs.outputSchema && stream && emit && shouldStreamStructured(useReasonArgs.structured) ? createStructuredOutputStreamer(useReasonArgs, id, opId) : void 0;
|
|
1989
|
+
const structuredChunk = useReasonArgs.outputSchema && (!separateOutput || finalizing) && stream && emit && shouldStreamStructured(useReasonArgs.structured) ? createStructuredOutputStreamer(useReasonArgs, id, opId) : void 0;
|
|
1943
1990
|
const step = pending ?? await reasonEngine(
|
|
1944
1991
|
{
|
|
1945
1992
|
...useReasonArgs,
|
|
1946
|
-
tools: toolDefinitions,
|
|
1993
|
+
tools: finalizing ? [] : toolDefinitions,
|
|
1994
|
+
...separateOutput && !finalizing ? { responseFormat: { type: "text" } } : {},
|
|
1947
1995
|
messages,
|
|
1948
1996
|
emit: false,
|
|
1949
1997
|
stream,
|
|
@@ -1986,8 +2034,48 @@ ${tryStringify2(args.response)}`
|
|
|
1986
2034
|
...step.warnings ? { warnings: step.warnings } : {}
|
|
1987
2035
|
});
|
|
1988
2036
|
if (toolCalls.length === 0) {
|
|
2037
|
+
if (separateOutput && !finalizing) {
|
|
2038
|
+
if (args.allowValidatedToolOutput && useReasonArgs.outputSchema) {
|
|
2039
|
+
try {
|
|
2040
|
+
finalOutput = parseReasonOutputWithSchema({
|
|
2041
|
+
text: step.text,
|
|
2042
|
+
schema: useReasonArgs.outputSchema,
|
|
2043
|
+
...step.finishReason ? { finishReason: step.finishReason } : {},
|
|
2044
|
+
label: "useReason output"
|
|
2045
|
+
});
|
|
2046
|
+
aggregatedWarnings = mergeWarnings(aggregatedWarnings, [
|
|
2047
|
+
{
|
|
2048
|
+
type: "compatibility",
|
|
2049
|
+
feature: "responseFormat",
|
|
2050
|
+
details: "Reused locally validated tool-phase output without an extra native-schema request. Set responseFormat.schema explicitly to require provider schema enforcement."
|
|
2051
|
+
}
|
|
2052
|
+
]);
|
|
2053
|
+
completed = true;
|
|
2054
|
+
break;
|
|
2055
|
+
} catch {
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
messages.push({
|
|
2059
|
+
role: "assistant",
|
|
2060
|
+
content: step.text,
|
|
2061
|
+
...step.continuation ? { continuation: step.continuation } : {}
|
|
2062
|
+
});
|
|
2063
|
+
messages.push({
|
|
2064
|
+
role: "user",
|
|
2065
|
+
content: "Return the final answer using the requested output schema and the tool results above."
|
|
2066
|
+
});
|
|
2067
|
+
finalizing = true;
|
|
2068
|
+
pending = void 0;
|
|
2069
|
+
save();
|
|
2070
|
+
continue;
|
|
2071
|
+
}
|
|
2072
|
+
completed = true;
|
|
1989
2073
|
break;
|
|
1990
2074
|
}
|
|
2075
|
+
if (finalizing)
|
|
2076
|
+
throw new Error(
|
|
2077
|
+
"Provider requested a tool during schema-only finalization."
|
|
2078
|
+
);
|
|
1991
2079
|
if (!reused) {
|
|
1992
2080
|
allToolCalls.push(...toolCalls);
|
|
1993
2081
|
messages.push({
|
|
@@ -2131,26 +2219,26 @@ ${tryStringify2(args.response)}`
|
|
|
2131
2219
|
message: result2.content
|
|
2132
2220
|
});
|
|
2133
2221
|
}
|
|
2134
|
-
const
|
|
2135
|
-
if (
|
|
2222
|
+
const completed2 = toolResults.at(-1);
|
|
2223
|
+
if (completed2)
|
|
2136
2224
|
messages.push({
|
|
2137
2225
|
role: "tool",
|
|
2138
|
-
content:
|
|
2139
|
-
toolCallId:
|
|
2140
|
-
name:
|
|
2141
|
-
...
|
|
2226
|
+
content: completed2.content,
|
|
2227
|
+
toolCallId: completed2.toolCallId,
|
|
2228
|
+
name: completed2.name,
|
|
2229
|
+
...completed2.isError !== void 0 ? { isError: completed2.isError } : {}
|
|
2142
2230
|
});
|
|
2143
2231
|
save();
|
|
2144
2232
|
}
|
|
2145
2233
|
pending = void 0;
|
|
2146
2234
|
save();
|
|
2147
2235
|
}
|
|
2148
|
-
if (
|
|
2236
|
+
if (!completed) {
|
|
2149
2237
|
throw new Error(
|
|
2150
2238
|
`useReason tool loop reached maxSteps (${maxSteps}) before producing a final response.`
|
|
2151
2239
|
);
|
|
2152
2240
|
}
|
|
2153
|
-
if (useReasonArgs.outputSchema) {
|
|
2241
|
+
if (useReasonArgs.outputSchema && finalOutput === void 0) {
|
|
2154
2242
|
finalOutput = parseReasonOutputWithSchema({
|
|
2155
2243
|
text: finalText,
|
|
2156
2244
|
schema: useReasonArgs.outputSchema,
|
|
@@ -2232,6 +2320,9 @@ ${tryStringify2(args.response)}`
|
|
|
2232
2320
|
args.model.options?.abortSignal
|
|
2233
2321
|
);
|
|
2234
2322
|
(0, import_core52.throwIfExecutionAborted)(abortSignal);
|
|
2323
|
+
const allowValidatedToolOutput = Boolean(
|
|
2324
|
+
args.outputSchema && !(args.responseFormat ?? args.model.options?.responseFormat)
|
|
2325
|
+
);
|
|
2235
2326
|
const inferred = inferOutputFormat(
|
|
2236
2327
|
args.outputSchema,
|
|
2237
2328
|
args.responseFormat ?? args.model.options?.responseFormat
|
|
@@ -2284,6 +2375,7 @@ ${tryStringify2(args.response)}`
|
|
|
2284
2375
|
if (args.tools?.length) {
|
|
2285
2376
|
return runReasonToolLoop({
|
|
2286
2377
|
useReasonArgs: args,
|
|
2378
|
+
allowValidatedToolOutput,
|
|
2287
2379
|
checkpointKey,
|
|
2288
2380
|
initialWarnings: inferred.warnings,
|
|
2289
2381
|
...id ? { id } : {},
|
|
@@ -2581,8 +2673,158 @@ ${tryStringify2(args.response)}`
|
|
|
2581
2673
|
};
|
|
2582
2674
|
return [getValue(), setValue];
|
|
2583
2675
|
}
|
|
2584
|
-
var import_node_crypto5 = require("crypto");
|
|
2585
2676
|
var import_core62 = require("@kortyx/core");
|
|
2677
|
+
var ParallelChildWaiting = class extends Error {
|
|
2678
|
+
};
|
|
2679
|
+
var ParallelError = class extends AggregateError {
|
|
2680
|
+
constructor(results) {
|
|
2681
|
+
super(
|
|
2682
|
+
results.flatMap(
|
|
2683
|
+
(result) => result.status === "rejected" ? [result.reason] : []
|
|
2684
|
+
),
|
|
2685
|
+
"One or more parallel operations failed."
|
|
2686
|
+
);
|
|
2687
|
+
this.results = results;
|
|
2688
|
+
this.name = "ParallelError";
|
|
2689
|
+
}
|
|
2690
|
+
results;
|
|
2691
|
+
};
|
|
2692
|
+
function registerWorkflowTask(id) {
|
|
2693
|
+
const ctx = getHookContext();
|
|
2694
|
+
if (ctx.workflowControlError) throw ctx.workflowControlError;
|
|
2695
|
+
if (ctx.workflowContextClosed)
|
|
2696
|
+
throw new Error("The workflow node activation has finished.");
|
|
2697
|
+
const group = ctx.parallelGroup;
|
|
2698
|
+
if (group?.restored && !group.record.calls.includes(id)) {
|
|
2699
|
+
throw new Error(`Parallel call membership changed during replay: '${id}'.`);
|
|
2700
|
+
}
|
|
2701
|
+
let finish;
|
|
2702
|
+
const task = {
|
|
2703
|
+
id,
|
|
2704
|
+
...group ? { group } : {},
|
|
2705
|
+
completed: false,
|
|
2706
|
+
done: new Promise((resolve2) => {
|
|
2707
|
+
finish = resolve2;
|
|
2708
|
+
}),
|
|
2709
|
+
finish: () => {
|
|
2710
|
+
task.completed = true;
|
|
2711
|
+
finish();
|
|
2712
|
+
}
|
|
2713
|
+
};
|
|
2714
|
+
ctx.workflowTasks.push(task);
|
|
2715
|
+
if (group && !group.record.calls.includes(id)) group.record.calls.push(id);
|
|
2716
|
+
return task;
|
|
2717
|
+
}
|
|
2718
|
+
function parallel(values) {
|
|
2719
|
+
const settled = Promise.allSettled(values);
|
|
2720
|
+
let ctx;
|
|
2721
|
+
try {
|
|
2722
|
+
ctx = getHookContext();
|
|
2723
|
+
} catch (error) {
|
|
2724
|
+
return Promise.reject(error);
|
|
2725
|
+
}
|
|
2726
|
+
const join = async () => {
|
|
2727
|
+
if (ctx.workflowControlError) throw ctx.workflowControlError;
|
|
2728
|
+
if (ctx.parallelGroup)
|
|
2729
|
+
throw new Error(
|
|
2730
|
+
"Await each parallel group before starting another in the same node."
|
|
2731
|
+
);
|
|
2732
|
+
const key = `__parallel:${ctx.parallelIndex++}`;
|
|
2733
|
+
const saved = ctx.currentNodeState.byKey[key];
|
|
2734
|
+
const tasks = ctx.workflowTasks.filter(
|
|
2735
|
+
(task) => !task.completed && !task.group
|
|
2736
|
+
);
|
|
2737
|
+
const ids = tasks.map((task) => task.id);
|
|
2738
|
+
if (saved && (saved.version !== 1 || ids.some((id) => !saved.calls.includes(id)))) {
|
|
2739
|
+
throw new Error("Parallel call membership changed during replay.");
|
|
2740
|
+
}
|
|
2741
|
+
const group = {
|
|
2742
|
+
key,
|
|
2743
|
+
record: saved ?? { version: 1, calls: ids, interrupts: [] },
|
|
2744
|
+
restored: Boolean(saved),
|
|
2745
|
+
ready: Promise.resolve()
|
|
2746
|
+
};
|
|
2747
|
+
ctx.currentNodeState.byKey[key] = group.record;
|
|
2748
|
+
ctx.stateDirty = true;
|
|
2749
|
+
ctx.parallelGroup = group;
|
|
2750
|
+
for (const task of tasks) task.group = group;
|
|
2751
|
+
const callRecord = (id) => ctx.currentNodeState.byKey[`__useWorkflow:${id}`];
|
|
2752
|
+
const bridge = async (callId, index) => {
|
|
2753
|
+
const interruption = callRecord(callId)?.interrupts[index];
|
|
2754
|
+
if (!interruption)
|
|
2755
|
+
throw new Error(
|
|
2756
|
+
"Parallel interrupt history does not match the saved child."
|
|
2757
|
+
);
|
|
2758
|
+
try {
|
|
2759
|
+
interruption.response = await awaitInterruptInternal({
|
|
2760
|
+
request: interruption.request,
|
|
2761
|
+
...interruption.request.meta ? { meta: interruption.request.meta } : {}
|
|
2762
|
+
});
|
|
2763
|
+
} catch (error) {
|
|
2764
|
+
ctx.workflowControlError = error;
|
|
2765
|
+
throw error;
|
|
2766
|
+
}
|
|
2767
|
+
};
|
|
2768
|
+
group.ready = (async () => {
|
|
2769
|
+
for (const entry of group.record.interrupts)
|
|
2770
|
+
await bridge(entry.callId, entry.index);
|
|
2771
|
+
})();
|
|
2772
|
+
try {
|
|
2773
|
+
const [ready, outcomes] = await Promise.all([
|
|
2774
|
+
Promise.allSettled([group.ready]),
|
|
2775
|
+
settled
|
|
2776
|
+
]);
|
|
2777
|
+
let drained = -1;
|
|
2778
|
+
while (drained !== ctx.workflowTasks.length) {
|
|
2779
|
+
drained = ctx.workflowTasks.length;
|
|
2780
|
+
await Promise.all(
|
|
2781
|
+
ctx.workflowTasks.filter((task) => task.group === group).map((task) => task.done)
|
|
2782
|
+
);
|
|
2783
|
+
}
|
|
2784
|
+
if (ready[0]?.status === "rejected") throw ready[0].reason;
|
|
2785
|
+
(0, import_core62.throwIfExecutionAborted)(ctx.node.abortSignal);
|
|
2786
|
+
if (ctx.workflowControlError) throw ctx.workflowControlError;
|
|
2787
|
+
const rejected = outcomes.filter(
|
|
2788
|
+
(outcome) => outcome.status === "rejected"
|
|
2789
|
+
);
|
|
2790
|
+
const control = rejected.find(
|
|
2791
|
+
(outcome) => (0, import_core62.isExecutionCancelled)(outcome.reason) || (0, import_core62.isExecutionLimitReached)(outcome.reason)
|
|
2792
|
+
);
|
|
2793
|
+
if (control) {
|
|
2794
|
+
ctx.workflowControlError = control.reason;
|
|
2795
|
+
throw control.reason;
|
|
2796
|
+
}
|
|
2797
|
+
const actual = ctx.workflowTasks.filter((task) => task.group === group).map((task) => task.id);
|
|
2798
|
+
if (group.record.calls.length !== actual.length || group.record.calls.some((id) => !actual.includes(id))) {
|
|
2799
|
+
throw new Error("Parallel call membership changed during replay.");
|
|
2800
|
+
}
|
|
2801
|
+
for (const callId of group.record.calls) {
|
|
2802
|
+
const record = callRecord(callId);
|
|
2803
|
+
if (record?.status !== "interrupted") continue;
|
|
2804
|
+
const index = record.interrupts.length - 1;
|
|
2805
|
+
if (record.interrupts[index]?.response !== void 0) continue;
|
|
2806
|
+
group.record.interrupts.push({ callId, index });
|
|
2807
|
+
await bridge(callId, index);
|
|
2808
|
+
throw new Error(
|
|
2809
|
+
"Parallel interrupt unexpectedly resumed without replay."
|
|
2810
|
+
);
|
|
2811
|
+
}
|
|
2812
|
+
if (rejected.length) throw new ParallelError(outcomes);
|
|
2813
|
+
return outcomes.map(
|
|
2814
|
+
(outcome) => outcome.value
|
|
2815
|
+
);
|
|
2816
|
+
} finally {
|
|
2817
|
+
ctx.parallelGroup = void 0;
|
|
2818
|
+
}
|
|
2819
|
+
};
|
|
2820
|
+
const promise = join();
|
|
2821
|
+
ctx.parallelJoins.push(promise);
|
|
2822
|
+
void promise.catch(() => {
|
|
2823
|
+
});
|
|
2824
|
+
return promise;
|
|
2825
|
+
}
|
|
2826
|
+
var import_node_crypto5 = require("crypto");
|
|
2827
|
+
var import_core7 = require("@kortyx/core");
|
|
2586
2828
|
var import_node_crypto42 = require("crypto");
|
|
2587
2829
|
function emitWorkflowCall2(telemetry, type, payload) {
|
|
2588
2830
|
const base = telemetry?.correlation;
|
|
@@ -2664,16 +2906,30 @@ ${tryStringify2(args.response)}`
|
|
|
2664
2906
|
};
|
|
2665
2907
|
}
|
|
2666
2908
|
async function useWorkflow(args) {
|
|
2909
|
+
const task = registerWorkflowTask(args.id);
|
|
2910
|
+
try {
|
|
2911
|
+
await Promise.resolve();
|
|
2912
|
+
if (task.group) await task.group.ready;
|
|
2913
|
+
return await runWorkflowCall(args, task);
|
|
2914
|
+
} catch (error) {
|
|
2915
|
+
if ((0, import_core7.isExecutionCancelled)(error) || (0, import_core7.isExecutionLimitReached)(error))
|
|
2916
|
+
getHookContext().workflowControlError = error;
|
|
2917
|
+
throw error;
|
|
2918
|
+
} finally {
|
|
2919
|
+
task.finish();
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
async function runWorkflowCall(args, task) {
|
|
2667
2923
|
const ctx = getHookContext();
|
|
2668
|
-
(0,
|
|
2924
|
+
(0, import_core7.throwIfExecutionAborted)(ctx.node.abortSignal);
|
|
2669
2925
|
const service = ctx.node.callWorkflow;
|
|
2670
2926
|
if (!service)
|
|
2671
2927
|
throw new Error("useWorkflow requires an agent workflow registry.");
|
|
2672
2928
|
if (!args.id.trim())
|
|
2673
2929
|
throw new Error("useWorkflow requires a stable, nonempty id.");
|
|
2674
|
-
if (ctx.workflowCallActive)
|
|
2930
|
+
if (ctx.workflowCallActive && !task.group)
|
|
2675
2931
|
throw new Error(
|
|
2676
|
-
"Concurrent workflow calls
|
|
2932
|
+
"Concurrent workflow calls require parallel([...]); otherwise await each call."
|
|
2677
2933
|
);
|
|
2678
2934
|
if (ctx.workflowCallIds.has(args.id))
|
|
2679
2935
|
throw new Error(
|
|
@@ -2694,6 +2950,11 @@ ${tryStringify2(args.response)}`
|
|
|
2694
2950
|
});
|
|
2695
2951
|
const key = `__useWorkflow:${args.id}`;
|
|
2696
2952
|
let record = ctx.currentNodeState.byKey[key];
|
|
2953
|
+
if (record && record.parallelKey !== task.group?.key)
|
|
2954
|
+
throw new WorkflowCallError(
|
|
2955
|
+
workflow,
|
|
2956
|
+
"parallel call membership changed during replay"
|
|
2957
|
+
);
|
|
2697
2958
|
if (record && record.fingerprint !== fingerprint)
|
|
2698
2959
|
throw new WorkflowCallError(
|
|
2699
2960
|
workflow,
|
|
@@ -2704,11 +2965,12 @@ ${tryStringify2(args.response)}`
|
|
|
2704
2965
|
fingerprint,
|
|
2705
2966
|
invocationId: (0, import_node_crypto5.randomUUID)(),
|
|
2706
2967
|
status: "running",
|
|
2707
|
-
interrupts: []
|
|
2968
|
+
interrupts: [],
|
|
2969
|
+
...task.group ? { parallelKey: task.group.key } : {}
|
|
2708
2970
|
};
|
|
2709
2971
|
ctx.currentNodeState.byKey[key] = record;
|
|
2710
2972
|
ctx.stateDirty = true;
|
|
2711
|
-
ctx.workflowCallActive = true;
|
|
2973
|
+
if (!task.group) ctx.workflowCallActive = true;
|
|
2712
2974
|
const telemetry = ctx.node.workflowCallTelemetry;
|
|
2713
2975
|
const activationKey = "__useWorkflowActivation";
|
|
2714
2976
|
ctx.currentNodeState.byKey[activationKey] ??= (0, import_node_crypto5.randomUUID)();
|
|
@@ -2744,7 +3006,7 @@ ${tryStringify2(args.response)}`
|
|
|
2744
3006
|
};
|
|
2745
3007
|
const cached = record.status === "completed";
|
|
2746
3008
|
try {
|
|
2747
|
-
for (const interruption of record.interrupts) {
|
|
3009
|
+
for (const interruption of task.group ? [] : record.interrupts) {
|
|
2748
3010
|
interruption.response = await awaitInterruptInternal({
|
|
2749
3011
|
request: interruption.request,
|
|
2750
3012
|
...interruption.request.meta ? { meta: interruption.request.meta } : {}
|
|
@@ -2752,6 +3014,8 @@ ${tryStringify2(args.response)}`
|
|
|
2752
3014
|
}
|
|
2753
3015
|
if (record.status === "failed")
|
|
2754
3016
|
throw new WorkflowCallError(workflow, record.error ?? "child failed");
|
|
3017
|
+
if (task.group && record.status === "interrupted" && record.interrupts.at(-1)?.response === void 0)
|
|
3018
|
+
throw new ParallelChildWaiting();
|
|
2755
3019
|
while (record.status !== "completed") {
|
|
2756
3020
|
let outcome;
|
|
2757
3021
|
try {
|
|
@@ -2770,13 +3034,13 @@ ${tryStringify2(args.response)}`
|
|
|
2770
3034
|
...record.interrupts.length ? { response: record.interrupts.at(-1)?.response } : {}
|
|
2771
3035
|
});
|
|
2772
3036
|
} catch (error) {
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
3037
|
+
const child = error;
|
|
3038
|
+
recordUsage(child?.__kortyxChildUsage);
|
|
3039
|
+
(0, import_core7.throwIfExecutionAborted)(ctx.node.abortSignal);
|
|
3040
|
+
if ((0, import_core7.isExecutionCancelled)(error)) throw error;
|
|
3041
|
+
if ((0, import_core7.isExecutionLimitReached)(error)) {
|
|
3042
|
+
if (child?.__kortyxChildSnapshot)
|
|
2778
3043
|
record.snapshot = child.__kortyxChildSnapshot;
|
|
2779
|
-
recordUsage(child.__kortyxChildUsage);
|
|
2780
3044
|
report("suspended", {
|
|
2781
3045
|
reason: "limit_reached",
|
|
2782
3046
|
limit: error.limitReached
|
|
@@ -2788,7 +3052,7 @@ ${tryStringify2(args.response)}`
|
|
|
2788
3052
|
report("failed", { error: "Child workflow failed" });
|
|
2789
3053
|
throw new WorkflowCallError(workflow, record.error);
|
|
2790
3054
|
}
|
|
2791
|
-
(0,
|
|
3055
|
+
(0, import_core7.throwIfExecutionAborted)(ctx.node.abortSignal);
|
|
2792
3056
|
recordUsage(outcome.usage);
|
|
2793
3057
|
if (outcome.status === "completed") {
|
|
2794
3058
|
record.data = outcome.data;
|
|
@@ -2809,6 +3073,7 @@ ${tryStringify2(args.response)}`
|
|
|
2809
3073
|
request: outcome.request
|
|
2810
3074
|
};
|
|
2811
3075
|
record.interrupts.push(interruption);
|
|
3076
|
+
if (task.group) throw new ParallelChildWaiting();
|
|
2812
3077
|
interruption.response = await awaitInterruptInternal({
|
|
2813
3078
|
request: interruption.request,
|
|
2814
3079
|
...interruption.request.meta ? { meta: interruption.request.meta } : {}
|
|
@@ -2822,7 +3087,7 @@ ${tryStringify2(args.response)}`
|
|
|
2822
3087
|
data: JSON.parse(JSON.stringify(data))
|
|
2823
3088
|
};
|
|
2824
3089
|
} finally {
|
|
2825
|
-
ctx.workflowCallActive = false;
|
|
3090
|
+
if (!task.group) ctx.workflowCallActive = false;
|
|
2826
3091
|
}
|
|
2827
3092
|
}
|
|
2828
3093
|
}
|