@kortyx/hooks 0.23.1 → 0.25.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 +26 -0
- package/README.md +14 -1
- package/dist/index.d.ts +17 -3
- package/dist/index.js +471 -169
- package/dist/index.js.map +1 -1
- package/dist/internal.js +30 -9
- package/dist/internal.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -19,10 +19,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
// src/index.ts
|
|
20
20
|
var index_exports = {};
|
|
21
21
|
__export(index_exports, {
|
|
22
|
+
ParallelError: () => ParallelError,
|
|
22
23
|
WorkflowCallError: () => WorkflowCallError,
|
|
23
24
|
completeResponse: () => completeResponse,
|
|
24
25
|
createWorkflowHooks: () => createWorkflowHooks,
|
|
25
26
|
emitWorkflowCall: () => emitWorkflowCall,
|
|
27
|
+
parallel: () => parallel,
|
|
26
28
|
runWithHookContext: () => runWithHookContext,
|
|
27
29
|
useAbortSignal: () => useAbortSignal,
|
|
28
30
|
useInterrupt: () => useInterrupt,
|
|
@@ -90,6 +92,11 @@ var createInternalContext = (ctx) => {
|
|
|
90
92
|
...ctx,
|
|
91
93
|
workflowCallActive: false,
|
|
92
94
|
workflowCallIds: /* @__PURE__ */ new Set(),
|
|
95
|
+
workflowTasks: [],
|
|
96
|
+
parallelJoins: [],
|
|
97
|
+
parallelGroup: void 0,
|
|
98
|
+
parallelIndex: 0,
|
|
99
|
+
workflowContextClosed: false,
|
|
93
100
|
nodeStateIndex: 0,
|
|
94
101
|
reasonCallIndex: 0,
|
|
95
102
|
currentNodeState,
|
|
@@ -161,10 +168,29 @@ function getReasonTraceAdapter() {
|
|
|
161
168
|
async function runWithHookContext(ctx, fn) {
|
|
162
169
|
const internal = createInternalContext(ctx);
|
|
163
170
|
try {
|
|
164
|
-
const result = await storage.run(internal,
|
|
171
|
+
const result = await storage.run(internal, async () => {
|
|
172
|
+
const drain = async () => {
|
|
173
|
+
let drained = -1;
|
|
174
|
+
while (drained !== internal.parallelJoins.length + internal.workflowTasks.length) {
|
|
175
|
+
drained = internal.parallelJoins.length + internal.workflowTasks.length;
|
|
176
|
+
await Promise.allSettled(internal.parallelJoins);
|
|
177
|
+
await Promise.all(internal.workflowTasks.map((task) => task.done));
|
|
178
|
+
}
|
|
179
|
+
internal.workflowContextClosed = true;
|
|
180
|
+
};
|
|
181
|
+
try {
|
|
182
|
+
const result2 = await fn();
|
|
183
|
+
await drain();
|
|
184
|
+
if (internal.workflowControlError) throw internal.workflowControlError;
|
|
185
|
+
return result2;
|
|
186
|
+
} catch (error) {
|
|
187
|
+
await drain();
|
|
188
|
+
throw internal.workflowControlError ?? error;
|
|
189
|
+
}
|
|
190
|
+
});
|
|
165
191
|
cleanupCompletedReasonCheckpoints(internal);
|
|
166
192
|
for (const key of Object.keys(internal.currentNodeState.byKey)) {
|
|
167
|
-
if (key.startsWith("__useWorkflow:") || key === "__useWorkflowActivation") {
|
|
193
|
+
if (key.startsWith("__useWorkflow:") || key.startsWith("__parallel:") || key === "__useWorkflowActivation") {
|
|
168
194
|
internal.currentNodeState.byKey[key] = null;
|
|
169
195
|
internal.stateDirty = true;
|
|
170
196
|
}
|
|
@@ -263,13 +289,18 @@ var resolveHookStatePatch = (args) => ({
|
|
|
263
289
|
});
|
|
264
290
|
|
|
265
291
|
// src/validation.ts
|
|
292
|
+
var import_errors = require("@kortyx/core/errors");
|
|
266
293
|
var parseWithSchema = (schema, value, label) => {
|
|
267
294
|
if (!schema) return value;
|
|
268
295
|
const parsed = schema.safeParse(value);
|
|
269
296
|
if (parsed.success) return parsed.data;
|
|
270
297
|
const parsedError = "error" in parsed && parsed.error ? parsed.error : void 0;
|
|
271
298
|
const reason = typeof parsedError?.message === "string" && parsedError.message.length > 0 ? parsedError.message : "invalid payload";
|
|
272
|
-
throw new
|
|
299
|
+
throw new import_errors.ValidationError(
|
|
300
|
+
label === "useReason output" ? "MODEL_OUTPUT_SCHEMA" : "SCHEMA_VALIDATION",
|
|
301
|
+
`${label} validation failed: ${reason}`,
|
|
302
|
+
parsedError
|
|
303
|
+
);
|
|
273
304
|
};
|
|
274
305
|
|
|
275
306
|
// src/interrupt.ts
|
|
@@ -411,6 +442,21 @@ var import_core3 = require("@kortyx/core");
|
|
|
411
442
|
// src/reason-engine.ts
|
|
412
443
|
var import_node_crypto2 = require("crypto");
|
|
413
444
|
var import_core2 = require("@kortyx/core");
|
|
445
|
+
var import_errors2 = require("@kortyx/core/errors");
|
|
446
|
+
function assertModelFinish(result) {
|
|
447
|
+
if (result.finishReason?.unified !== "content-filter") return;
|
|
448
|
+
throw Object.assign(
|
|
449
|
+
new import_errors2.KortyxError("PROVIDER_REFUSAL", "The provider refused the request.", {
|
|
450
|
+
category: "provider",
|
|
451
|
+
retryable: false,
|
|
452
|
+
safeMessage: "The provider refused the request."
|
|
453
|
+
}),
|
|
454
|
+
{
|
|
455
|
+
...result.usage ? { usage: result.usage } : {},
|
|
456
|
+
finishReason: result.finishReason
|
|
457
|
+
}
|
|
458
|
+
);
|
|
459
|
+
}
|
|
414
460
|
var toError = (value) => value instanceof Error ? value : new Error(String(value));
|
|
415
461
|
var createRuntimeId = () => {
|
|
416
462
|
try {
|
|
@@ -524,15 +570,18 @@ async function runReasonEngine(args) {
|
|
|
524
570
|
};
|
|
525
571
|
const failTrace = (span, error) => {
|
|
526
572
|
const detail = error;
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
573
|
+
try {
|
|
574
|
+
span?.fail?.(error, {
|
|
575
|
+
...detail?.usage ? { usage: detail.usage } : {},
|
|
576
|
+
...detail?.finishReason ? { finishReason: detail.finishReason } : {},
|
|
577
|
+
...detail?.providerMetadata ? { providerMetadata: detail.providerMetadata } : {},
|
|
578
|
+
attributes: {
|
|
579
|
+
providerId: args.model.provider.id,
|
|
580
|
+
modelId: args.model.modelId
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
} catch {
|
|
584
|
+
}
|
|
536
585
|
};
|
|
537
586
|
try {
|
|
538
587
|
if (stream) {
|
|
@@ -617,6 +666,8 @@ async function runReasonEngine(args) {
|
|
|
617
666
|
}
|
|
618
667
|
}
|
|
619
668
|
}
|
|
669
|
+
(0, import_core2.throwIfExecutionAborted)(abortSignal);
|
|
670
|
+
assertModelFinish({ finishReason, usage });
|
|
620
671
|
if (emit) {
|
|
621
672
|
emitNodeEvent(args.emitEvent, args.nodeId, "text-end", {
|
|
622
673
|
...commonMeta
|
|
@@ -638,6 +689,7 @@ async function runReasonEngine(args) {
|
|
|
638
689
|
}
|
|
639
690
|
const response = await model.invoke(messages);
|
|
640
691
|
(0, import_core2.throwIfExecutionAborted)(abortSignal);
|
|
692
|
+
assertModelFinish(response);
|
|
641
693
|
if (response.content) args.onTextChunk?.(response.content);
|
|
642
694
|
if (emit) {
|
|
643
695
|
emitNodeEvent(args.emitEvent, args.nodeId, "text-start", {
|
|
@@ -1437,6 +1489,7 @@ function createStructuredOutputStreamer(args, id, opId) {
|
|
|
1437
1489
|
}
|
|
1438
1490
|
|
|
1439
1491
|
// src/reason/parsing.ts
|
|
1492
|
+
var import_errors3 = require("@kortyx/core/errors");
|
|
1440
1493
|
var tryJsonParse = (value) => {
|
|
1441
1494
|
try {
|
|
1442
1495
|
return { ok: true, value: JSON.parse(value) };
|
|
@@ -1474,132 +1527,135 @@ var resolveOutputCandidate = (text) => {
|
|
|
1474
1527
|
}
|
|
1475
1528
|
return text;
|
|
1476
1529
|
};
|
|
1477
|
-
var
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
};
|
|
1482
|
-
var isLikelyTruncatedStructuredOutput = (text) => {
|
|
1483
|
-
const trimmed = text.trim();
|
|
1484
|
-
if (!looksLikeStructuredJsonAttempt(trimmed)) return false;
|
|
1485
|
-
const direct = tryJsonParse(trimmed);
|
|
1486
|
-
if (!direct.ok && direct.error) return true;
|
|
1487
|
-
const block = extractJsonCodeBlock(trimmed);
|
|
1488
|
-
if (!block) return false;
|
|
1489
|
-
const parsedBlock = tryJsonParse(block);
|
|
1490
|
-
return !parsedBlock.ok && Boolean(parsedBlock.error);
|
|
1491
|
-
};
|
|
1492
|
-
var createTruncatedStructuredOutputError = (label) => new Error(
|
|
1493
|
-
`${label} was truncated before producing valid structured output. The model stopped due to output length. Increase maxOutputTokens or simplify the requested output.`
|
|
1530
|
+
var createTruncatedStructuredOutputError = (label, cause) => new import_errors3.ValidationError(
|
|
1531
|
+
"OUTPUT_TRUNCATED",
|
|
1532
|
+
`${label} was truncated before producing valid structured output. The model stopped due to output length. Increase maxOutputTokens or simplify the requested output.`,
|
|
1533
|
+
cause
|
|
1494
1534
|
);
|
|
1495
|
-
var createInvalidStructuredOutputError = (label) => new
|
|
1496
|
-
|
|
1535
|
+
var createInvalidStructuredOutputError = (label, cause) => new import_errors3.ValidationError(
|
|
1536
|
+
"INVALID_MODEL_JSON",
|
|
1537
|
+
`${label} did not produce valid structured output. The model returned text instead of the expected JSON payload. Check outputSchema and provider structured-output settings.`,
|
|
1538
|
+
cause
|
|
1497
1539
|
);
|
|
1498
1540
|
var parseReasonOutputWithSchema = (args) => {
|
|
1499
1541
|
const candidate = resolveOutputCandidate(args.text);
|
|
1542
|
+
const block = extractJsonCodeBlock(args.text.trim());
|
|
1543
|
+
const validJson = tryJsonParse(args.text.trim()).ok || block !== null && tryJsonParse(block).ok;
|
|
1500
1544
|
try {
|
|
1501
1545
|
return parseWithSchema(args.schema, candidate, args.label);
|
|
1502
1546
|
} catch (error) {
|
|
1503
|
-
if (
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1547
|
+
if (!(error instanceof import_errors3.ValidationError)) throw error;
|
|
1548
|
+
const failure = args.finishReason?.unified === "length" ? createTruncatedStructuredOutputError(args.label, error) : typeof candidate === "string" && !validJson ? createInvalidStructuredOutputError(args.label, error) : error;
|
|
1549
|
+
if (failure && typeof failure === "object")
|
|
1550
|
+
Object.assign(failure, {
|
|
1551
|
+
...args.usage ? { usage: args.usage } : {},
|
|
1552
|
+
...args.finishReason ? { finishReason: args.finishReason } : {}
|
|
1553
|
+
});
|
|
1554
|
+
throw failure;
|
|
1510
1555
|
}
|
|
1511
1556
|
};
|
|
1512
1557
|
var parseInterruptFirstPassResult = (args) => {
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
if (
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
const mode = args.mode ?? "required";
|
|
1526
|
-
let interruptRequired = true;
|
|
1527
|
-
if (mode === "optional") {
|
|
1528
|
-
if (candidate.decision !== "continue" && candidate.decision !== "interrupt") {
|
|
1529
|
-
throw new Error(
|
|
1530
|
-
'useReason optional interrupt first pass must include decision "continue" or "interrupt".'
|
|
1558
|
+
try {
|
|
1559
|
+
const candidate = resolveOutputCandidate(args.text);
|
|
1560
|
+
if (!isRecord2(candidate)) {
|
|
1561
|
+
if (args.finishReason?.unified === "length") {
|
|
1562
|
+
throw createTruncatedStructuredOutputError("useReason output");
|
|
1563
|
+
}
|
|
1564
|
+
if (typeof candidate === "string" && candidate.trim().length > 0) {
|
|
1565
|
+
throw createInvalidStructuredOutputError("useReason output");
|
|
1566
|
+
}
|
|
1567
|
+
throw new import_errors3.ValidationError(
|
|
1568
|
+
"MODEL_OUTPUT_SCHEMA",
|
|
1569
|
+
"useReason first pass with interrupt must return a JSON object."
|
|
1531
1570
|
);
|
|
1532
1571
|
}
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1572
|
+
const mode = args.mode ?? "required";
|
|
1573
|
+
let interruptRequired = true;
|
|
1574
|
+
if (mode === "optional") {
|
|
1575
|
+
if (candidate.decision !== "continue" && candidate.decision !== "interrupt") {
|
|
1576
|
+
throw new import_errors3.ValidationError(
|
|
1577
|
+
"MODEL_OUTPUT_SCHEMA",
|
|
1578
|
+
'useReason optional interrupt first pass must include decision "continue" or "interrupt".'
|
|
1579
|
+
);
|
|
1580
|
+
}
|
|
1581
|
+
interruptRequired = candidate.decision === "interrupt";
|
|
1582
|
+
if (!interruptRequired) {
|
|
1583
|
+
const hasUnexpectedRequest = [
|
|
1584
|
+
candidate.interruptRequest,
|
|
1585
|
+
candidate.request,
|
|
1586
|
+
candidate.interrupt
|
|
1587
|
+
].some((value) => value !== null && value !== void 0);
|
|
1588
|
+
if (hasUnexpectedRequest) {
|
|
1589
|
+
throw new import_errors3.ValidationError(
|
|
1590
|
+
"MODEL_OUTPUT_SCHEMA",
|
|
1591
|
+
'useReason optional interrupt first pass with decision "continue" must not include an interrupt request.'
|
|
1592
|
+
);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
let request;
|
|
1597
|
+
if (interruptRequired) {
|
|
1598
|
+
const requestCandidates = [
|
|
1536
1599
|
candidate.interruptRequest,
|
|
1537
1600
|
candidate.request,
|
|
1538
|
-
candidate.interrupt
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1601
|
+
candidate.interrupt,
|
|
1602
|
+
mode === "required" ? candidate : void 0
|
|
1603
|
+
];
|
|
1604
|
+
const requestPayload = requestCandidates.find(
|
|
1605
|
+
(value) => value !== null && value !== void 0
|
|
1606
|
+
);
|
|
1607
|
+
request = parseWithSchema(
|
|
1608
|
+
args.requestSchema,
|
|
1609
|
+
requestPayload,
|
|
1610
|
+
"useReason interrupt.request"
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
let output;
|
|
1614
|
+
if (args.outputSchema) {
|
|
1615
|
+
const outputPayload = Object.hasOwn(candidate, "output") ? candidate.output : candidate;
|
|
1616
|
+
try {
|
|
1617
|
+
output = parseWithSchema(
|
|
1618
|
+
args.outputSchema,
|
|
1619
|
+
outputPayload,
|
|
1620
|
+
"useReason output"
|
|
1543
1621
|
);
|
|
1622
|
+
} catch (error) {
|
|
1623
|
+
if (!(error instanceof import_errors3.ValidationError)) throw error;
|
|
1624
|
+
if (args.finishReason?.unified === "length") {
|
|
1625
|
+
throw createTruncatedStructuredOutputError("useReason output", error);
|
|
1626
|
+
}
|
|
1627
|
+
throw error;
|
|
1544
1628
|
}
|
|
1545
1629
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
candidate.request,
|
|
1552
|
-
candidate.interrupt,
|
|
1553
|
-
mode === "required" ? candidate : void 0
|
|
1554
|
-
];
|
|
1555
|
-
const requestPayload = requestCandidates.find(
|
|
1556
|
-
(value) => value !== null && value !== void 0
|
|
1557
|
-
);
|
|
1558
|
-
request = parseWithSchema(
|
|
1559
|
-
args.requestSchema,
|
|
1560
|
-
requestPayload,
|
|
1561
|
-
"useReason interrupt.request"
|
|
1562
|
-
);
|
|
1563
|
-
}
|
|
1564
|
-
let output;
|
|
1565
|
-
if (args.outputSchema) {
|
|
1566
|
-
const outputPayload = Object.hasOwn(candidate, "output") ? candidate.output : candidate;
|
|
1567
|
-
try {
|
|
1568
|
-
output = parseWithSchema(
|
|
1569
|
-
args.outputSchema,
|
|
1570
|
-
outputPayload,
|
|
1571
|
-
"useReason output"
|
|
1630
|
+
const explicitDraft = typeof candidate.draftText === "string" && candidate.draftText.length > 0 ? candidate.draftText : typeof candidate.text === "string" && candidate.text.length > 0 ? candidate.text : void 0;
|
|
1631
|
+
if (!args.outputSchema && !explicitDraft) {
|
|
1632
|
+
throw new import_errors3.ValidationError(
|
|
1633
|
+
"MODEL_OUTPUT_SCHEMA",
|
|
1634
|
+
"useReason first pass with interrupt requires `draftText` when outputSchema is not provided."
|
|
1572
1635
|
);
|
|
1573
|
-
} catch (error) {
|
|
1574
|
-
if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
|
|
1575
|
-
throw createTruncatedStructuredOutputError("useReason output");
|
|
1576
|
-
}
|
|
1577
|
-
if (typeof outputPayload === "string" && outputPayload.trim().length > 0) {
|
|
1578
|
-
throw createInvalidStructuredOutputError("useReason output");
|
|
1579
|
-
}
|
|
1580
|
-
throw error;
|
|
1581
1636
|
}
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
if (!interruptRequired) {
|
|
1637
|
+
const draftText = explicitDraft ?? (output !== void 0 ? tryStringify(output) : String(args.text ?? ""));
|
|
1638
|
+
if (!interruptRequired) {
|
|
1639
|
+
return {
|
|
1640
|
+
draftText,
|
|
1641
|
+
interruptRequired: false,
|
|
1642
|
+
...output !== void 0 ? { output } : {}
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1591
1645
|
return {
|
|
1592
1646
|
draftText,
|
|
1593
|
-
interruptRequired:
|
|
1647
|
+
interruptRequired: true,
|
|
1648
|
+
request,
|
|
1594
1649
|
...output !== void 0 ? { output } : {}
|
|
1595
1650
|
};
|
|
1651
|
+
} catch (error) {
|
|
1652
|
+
if (error && typeof error === "object")
|
|
1653
|
+
Object.assign(error, {
|
|
1654
|
+
...args.usage ? { usage: args.usage } : {},
|
|
1655
|
+
...args.finishReason ? { finishReason: args.finishReason } : {}
|
|
1656
|
+
});
|
|
1657
|
+
throw error;
|
|
1596
1658
|
}
|
|
1597
|
-
return {
|
|
1598
|
-
draftText,
|
|
1599
|
-
interruptRequired: true,
|
|
1600
|
-
request,
|
|
1601
|
-
...output !== void 0 ? { output } : {}
|
|
1602
|
-
};
|
|
1603
1659
|
};
|
|
1604
1660
|
|
|
1605
1661
|
// src/reason/prompting.ts
|
|
@@ -1813,7 +1869,7 @@ var emitReasonStructuredOutput = (args) => {
|
|
|
1813
1869
|
|
|
1814
1870
|
// src/reason/tools.ts
|
|
1815
1871
|
var import_core4 = require("@kortyx/core");
|
|
1816
|
-
var
|
|
1872
|
+
var import_errors4 = require("@kortyx/core/errors");
|
|
1817
1873
|
var toToolDefinitions = (tools) => tools.map((tool) => ({
|
|
1818
1874
|
name: tool.name,
|
|
1819
1875
|
...tool.title ? { title: tool.title } : {},
|
|
@@ -1847,17 +1903,27 @@ var normalizeToolResult = (toolCall, value) => {
|
|
|
1847
1903
|
...value !== void 0 && value !== null && typeof value === "object" && !Array.isArray(value) ? { structuredContent: value } : {}
|
|
1848
1904
|
};
|
|
1849
1905
|
};
|
|
1850
|
-
var closeOwnedTools = async (tools) => {
|
|
1906
|
+
var closeOwnedTools = async (tools, preserveFailure = false, traceSpan) => {
|
|
1851
1907
|
const closeFns = /* @__PURE__ */ new Set();
|
|
1852
1908
|
for (const tool of tools) {
|
|
1853
1909
|
if (tool.closeAfterUse === false || !tool.close) continue;
|
|
1854
1910
|
closeFns.add(tool.close);
|
|
1855
1911
|
}
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1912
|
+
try {
|
|
1913
|
+
await Promise.all(
|
|
1914
|
+
[...closeFns].map(async (close) => {
|
|
1915
|
+
await close();
|
|
1916
|
+
})
|
|
1917
|
+
);
|
|
1918
|
+
} catch (error) {
|
|
1919
|
+
if (!preserveFailure) throw error;
|
|
1920
|
+
try {
|
|
1921
|
+
traceSpan?.addEvent?.("useReason.cleanup.failed", {
|
|
1922
|
+
failure: (0, import_errors4.serializeFailure)(error)
|
|
1923
|
+
});
|
|
1924
|
+
} catch {
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1861
1927
|
};
|
|
1862
1928
|
var createInitialMessages = (args) => [
|
|
1863
1929
|
...typeof args.system === "string" && args.system.length > 0 ? [{ role: "system", content: args.system }] : [],
|
|
@@ -1874,6 +1940,7 @@ var runReasonToolLoop = async (args) => {
|
|
|
1874
1940
|
const saved = ctx.currentNodeState.byKey[checkpointKey];
|
|
1875
1941
|
const checkpoint = saved?.status === "tool_loop" ? saved : void 0;
|
|
1876
1942
|
const opId = checkpoint?.opId ?? args.opId;
|
|
1943
|
+
let primaryFailure = false;
|
|
1877
1944
|
const tools = useReasonArgs.tools ?? [];
|
|
1878
1945
|
const toolByName = /* @__PURE__ */ new Map();
|
|
1879
1946
|
let validationCompleted = false;
|
|
@@ -1906,7 +1973,7 @@ var runReasonToolLoop = async (args) => {
|
|
|
1906
1973
|
validationCompleted = true;
|
|
1907
1974
|
} finally {
|
|
1908
1975
|
if (!validationCompleted) {
|
|
1909
|
-
await closeOwnedTools(tools);
|
|
1976
|
+
await closeOwnedTools(tools, true, traceSpan);
|
|
1910
1977
|
}
|
|
1911
1978
|
}
|
|
1912
1979
|
const toolDefinitions = toToolDefinitions(tools);
|
|
@@ -2025,6 +2092,7 @@ var runReasonToolLoop = async (args) => {
|
|
|
2025
2092
|
text: step.text,
|
|
2026
2093
|
schema: useReasonArgs.outputSchema,
|
|
2027
2094
|
...step.finishReason ? { finishReason: step.finishReason } : {},
|
|
2095
|
+
...aggregatedUsage ? { usage: aggregatedUsage } : {},
|
|
2028
2096
|
label: "useReason output"
|
|
2029
2097
|
});
|
|
2030
2098
|
aggregatedWarnings = mergeWarnings(aggregatedWarnings, [
|
|
@@ -2112,12 +2180,12 @@ var runReasonToolLoop = async (args) => {
|
|
|
2112
2180
|
options: [
|
|
2113
2181
|
{ id: "approve", label: "Approve" },
|
|
2114
2182
|
{ id: "deny", label: "Deny" }
|
|
2115
|
-
]
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2183
|
+
]
|
|
2184
|
+
},
|
|
2185
|
+
meta: {
|
|
2186
|
+
tool: tool.name,
|
|
2187
|
+
toolCallId: toolCall.id,
|
|
2188
|
+
input: toolCall.input
|
|
2121
2189
|
}
|
|
2122
2190
|
});
|
|
2123
2191
|
const approved = Array.isArray(approval) ? approval.includes("approve") : approval === "approve";
|
|
@@ -2179,12 +2247,12 @@ var runReasonToolLoop = async (args) => {
|
|
|
2179
2247
|
});
|
|
2180
2248
|
} catch (error) {
|
|
2181
2249
|
(0, import_core4.throwIfExecutionAborted)(abortSignal);
|
|
2182
|
-
if ((0,
|
|
2183
|
-
throw error;
|
|
2250
|
+
if ((0, import_errors4.isControlFlowError)(error)) throw error;
|
|
2184
2251
|
const result2 = {
|
|
2185
2252
|
toolCallId: toolCall.id,
|
|
2186
2253
|
name: tool.name,
|
|
2187
|
-
content:
|
|
2254
|
+
content: (0, import_errors4.serializeFailure)(error).message,
|
|
2255
|
+
failure: (0, import_errors4.serializeFailure)(error),
|
|
2188
2256
|
isError: true
|
|
2189
2257
|
};
|
|
2190
2258
|
toolResults.push(result2);
|
|
@@ -2193,14 +2261,16 @@ var runReasonToolLoop = async (args) => {
|
|
|
2193
2261
|
emitToolEvent("tool-call-error", {
|
|
2194
2262
|
tool: tool.name,
|
|
2195
2263
|
toolCallId: toolCall.id,
|
|
2196
|
-
message: result2.content
|
|
2264
|
+
message: result2.content,
|
|
2265
|
+
failure: result2.failure
|
|
2197
2266
|
});
|
|
2198
2267
|
}
|
|
2199
2268
|
traceSpan?.addEvent?.("useReason.tool-call.error", {
|
|
2200
2269
|
stepIndex,
|
|
2201
2270
|
tool: tool.name,
|
|
2202
2271
|
toolCallId: toolCall.id,
|
|
2203
|
-
message: result2.content
|
|
2272
|
+
message: result2.content,
|
|
2273
|
+
failure: result2.failure
|
|
2204
2274
|
});
|
|
2205
2275
|
}
|
|
2206
2276
|
const completed2 = toolResults.at(-1);
|
|
@@ -2227,6 +2297,7 @@ var runReasonToolLoop = async (args) => {
|
|
|
2227
2297
|
text: finalText,
|
|
2228
2298
|
schema: useReasonArgs.outputSchema,
|
|
2229
2299
|
...finalFinishReason ? { finishReason: finalFinishReason } : {},
|
|
2300
|
+
...aggregatedUsage ? { usage: aggregatedUsage } : {},
|
|
2230
2301
|
label: "useReason output"
|
|
2231
2302
|
});
|
|
2232
2303
|
}
|
|
@@ -2273,15 +2344,19 @@ var runReasonToolLoop = async (args) => {
|
|
|
2273
2344
|
ctx.stateDirty = true;
|
|
2274
2345
|
return result;
|
|
2275
2346
|
} catch (error) {
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2347
|
+
primaryFailure = true;
|
|
2348
|
+
try {
|
|
2349
|
+
traceSpan?.fail?.(error, {
|
|
2350
|
+
attributes: {
|
|
2351
|
+
toolStepCount: steps.length,
|
|
2352
|
+
toolCallCount: allToolCalls.length
|
|
2353
|
+
}
|
|
2354
|
+
});
|
|
2355
|
+
} catch {
|
|
2356
|
+
}
|
|
2282
2357
|
throw error;
|
|
2283
2358
|
} finally {
|
|
2284
|
-
await closeOwnedTools(tools);
|
|
2359
|
+
await closeOwnedTools(tools, primaryFailure, traceSpan);
|
|
2285
2360
|
}
|
|
2286
2361
|
};
|
|
2287
2362
|
|
|
@@ -2446,6 +2521,7 @@ async function useReason(args) {
|
|
|
2446
2521
|
text: first.text,
|
|
2447
2522
|
requestSchema: args.interrupt.requestSchema,
|
|
2448
2523
|
...first.finishReason ? { finishReason: first.finishReason } : {},
|
|
2524
|
+
...first.usage ? { usage: first.usage } : {},
|
|
2449
2525
|
...args.outputSchema ? { outputSchema: args.outputSchema } : {},
|
|
2450
2526
|
mode: args.interrupt.mode ?? "required"
|
|
2451
2527
|
});
|
|
@@ -2463,6 +2539,7 @@ async function useReason(args) {
|
|
|
2463
2539
|
text: first.text,
|
|
2464
2540
|
schema: args.outputSchema,
|
|
2465
2541
|
...first.finishReason ? { finishReason: first.finishReason } : {},
|
|
2542
|
+
...first.usage ? { usage: first.usage } : {},
|
|
2466
2543
|
label: "useReason output"
|
|
2467
2544
|
});
|
|
2468
2545
|
}
|
|
@@ -2557,6 +2634,7 @@ async function useReason(args) {
|
|
|
2557
2634
|
text: finalText,
|
|
2558
2635
|
schema: args.outputSchema,
|
|
2559
2636
|
...second.finishReason ? { finishReason: second.finishReason } : {},
|
|
2637
|
+
...aggregatedUsage ? { usage: aggregatedUsage } : {},
|
|
2560
2638
|
label: "useReason output"
|
|
2561
2639
|
});
|
|
2562
2640
|
}
|
|
@@ -2662,9 +2740,179 @@ function useWorkflowState(key, ...rest) {
|
|
|
2662
2740
|
return [getValue(), setValue];
|
|
2663
2741
|
}
|
|
2664
2742
|
|
|
2743
|
+
// src/parallel.ts
|
|
2744
|
+
var import_core6 = require("@kortyx/core");
|
|
2745
|
+
var import_errors5 = require("@kortyx/core/errors");
|
|
2746
|
+
var ParallelChildWaiting = class extends Error {
|
|
2747
|
+
name = "ParallelChildWaiting";
|
|
2748
|
+
};
|
|
2749
|
+
var ParallelError = class extends AggregateError {
|
|
2750
|
+
constructor(results) {
|
|
2751
|
+
super(
|
|
2752
|
+
results.flatMap(
|
|
2753
|
+
(result) => result.status === "rejected" ? [result.reason] : []
|
|
2754
|
+
),
|
|
2755
|
+
"One or more parallel operations failed."
|
|
2756
|
+
);
|
|
2757
|
+
this.results = results;
|
|
2758
|
+
this.name = "ParallelError";
|
|
2759
|
+
this.failure = (0, import_errors5.serializeFailure)({
|
|
2760
|
+
version: 1,
|
|
2761
|
+
code: this.code,
|
|
2762
|
+
category: "workflow",
|
|
2763
|
+
message: this.message,
|
|
2764
|
+
retryable: null,
|
|
2765
|
+
results: results.map(
|
|
2766
|
+
(result) => result.status === "fulfilled" ? { status: "fulfilled" } : { status: "rejected", failure: (0, import_errors5.serializeFailure)(result.reason) }
|
|
2767
|
+
)
|
|
2768
|
+
});
|
|
2769
|
+
}
|
|
2770
|
+
results;
|
|
2771
|
+
code = "PARALLEL_FAILED";
|
|
2772
|
+
failure;
|
|
2773
|
+
toJSON() {
|
|
2774
|
+
return (0, import_errors5.serializeFailure)(this);
|
|
2775
|
+
}
|
|
2776
|
+
};
|
|
2777
|
+
function registerWorkflowTask(id) {
|
|
2778
|
+
const ctx = getHookContext();
|
|
2779
|
+
if (ctx.workflowControlError) throw ctx.workflowControlError;
|
|
2780
|
+
if (ctx.workflowContextClosed)
|
|
2781
|
+
throw new Error("The workflow node activation has finished.");
|
|
2782
|
+
const group = ctx.parallelGroup;
|
|
2783
|
+
if (group?.restored && !group.record.calls.includes(id)) {
|
|
2784
|
+
throw new Error(`Parallel call membership changed during replay: '${id}'.`);
|
|
2785
|
+
}
|
|
2786
|
+
let finish;
|
|
2787
|
+
const task = {
|
|
2788
|
+
id,
|
|
2789
|
+
...group ? { group } : {},
|
|
2790
|
+
completed: false,
|
|
2791
|
+
done: new Promise((resolve) => {
|
|
2792
|
+
finish = resolve;
|
|
2793
|
+
}),
|
|
2794
|
+
finish: () => {
|
|
2795
|
+
task.completed = true;
|
|
2796
|
+
finish();
|
|
2797
|
+
}
|
|
2798
|
+
};
|
|
2799
|
+
ctx.workflowTasks.push(task);
|
|
2800
|
+
if (group && !group.record.calls.includes(id)) group.record.calls.push(id);
|
|
2801
|
+
return task;
|
|
2802
|
+
}
|
|
2803
|
+
function parallel(values) {
|
|
2804
|
+
const settled = Promise.allSettled(values);
|
|
2805
|
+
let ctx;
|
|
2806
|
+
try {
|
|
2807
|
+
ctx = getHookContext();
|
|
2808
|
+
} catch (error) {
|
|
2809
|
+
return Promise.reject(error);
|
|
2810
|
+
}
|
|
2811
|
+
const join = async () => {
|
|
2812
|
+
if (ctx.workflowControlError) throw ctx.workflowControlError;
|
|
2813
|
+
if (ctx.parallelGroup)
|
|
2814
|
+
throw new Error(
|
|
2815
|
+
"Await each parallel group before starting another in the same node."
|
|
2816
|
+
);
|
|
2817
|
+
const key = `__parallel:${ctx.parallelIndex++}`;
|
|
2818
|
+
const saved = ctx.currentNodeState.byKey[key];
|
|
2819
|
+
const tasks = ctx.workflowTasks.filter(
|
|
2820
|
+
(task) => !task.completed && !task.group
|
|
2821
|
+
);
|
|
2822
|
+
const ids = tasks.map((task) => task.id);
|
|
2823
|
+
if (saved && (saved.version !== 1 || ids.some((id) => !saved.calls.includes(id)))) {
|
|
2824
|
+
throw new Error("Parallel call membership changed during replay.");
|
|
2825
|
+
}
|
|
2826
|
+
const group = {
|
|
2827
|
+
key,
|
|
2828
|
+
record: saved ?? { version: 1, calls: ids, interrupts: [] },
|
|
2829
|
+
restored: Boolean(saved),
|
|
2830
|
+
ready: Promise.resolve()
|
|
2831
|
+
};
|
|
2832
|
+
ctx.currentNodeState.byKey[key] = group.record;
|
|
2833
|
+
ctx.stateDirty = true;
|
|
2834
|
+
ctx.parallelGroup = group;
|
|
2835
|
+
for (const task of tasks) task.group = group;
|
|
2836
|
+
const callRecord = (id) => ctx.currentNodeState.byKey[`__useWorkflow:${id}`];
|
|
2837
|
+
const bridge = async (callId, index) => {
|
|
2838
|
+
const interruption = callRecord(callId)?.interrupts[index];
|
|
2839
|
+
if (!interruption)
|
|
2840
|
+
throw new Error(
|
|
2841
|
+
"Parallel interrupt history does not match the saved child."
|
|
2842
|
+
);
|
|
2843
|
+
try {
|
|
2844
|
+
interruption.response = await awaitInterruptInternal({
|
|
2845
|
+
request: interruption.request,
|
|
2846
|
+
...interruption.request.meta ? { meta: interruption.request.meta } : {}
|
|
2847
|
+
});
|
|
2848
|
+
} catch (error) {
|
|
2849
|
+
ctx.workflowControlError = error;
|
|
2850
|
+
throw error;
|
|
2851
|
+
}
|
|
2852
|
+
};
|
|
2853
|
+
group.ready = (async () => {
|
|
2854
|
+
for (const entry of group.record.interrupts)
|
|
2855
|
+
await bridge(entry.callId, entry.index);
|
|
2856
|
+
})();
|
|
2857
|
+
try {
|
|
2858
|
+
const [ready, outcomes] = await Promise.all([
|
|
2859
|
+
Promise.allSettled([group.ready]),
|
|
2860
|
+
settled
|
|
2861
|
+
]);
|
|
2862
|
+
let drained = -1;
|
|
2863
|
+
while (drained !== ctx.workflowTasks.length) {
|
|
2864
|
+
drained = ctx.workflowTasks.length;
|
|
2865
|
+
await Promise.all(
|
|
2866
|
+
ctx.workflowTasks.filter((task) => task.group === group).map((task) => task.done)
|
|
2867
|
+
);
|
|
2868
|
+
}
|
|
2869
|
+
if (ready[0]?.status === "rejected") throw ready[0].reason;
|
|
2870
|
+
(0, import_core6.throwIfExecutionAborted)(ctx.node.abortSignal);
|
|
2871
|
+
if (ctx.workflowControlError) throw ctx.workflowControlError;
|
|
2872
|
+
const rejected = outcomes.filter(
|
|
2873
|
+
(outcome) => outcome.status === "rejected"
|
|
2874
|
+
);
|
|
2875
|
+
const control = rejected.find(
|
|
2876
|
+
(outcome) => (0, import_core6.isExecutionCancelled)(outcome.reason) || (0, import_core6.isExecutionLimitReached)(outcome.reason)
|
|
2877
|
+
);
|
|
2878
|
+
if (control) {
|
|
2879
|
+
ctx.workflowControlError = control.reason;
|
|
2880
|
+
throw control.reason;
|
|
2881
|
+
}
|
|
2882
|
+
const actual = ctx.workflowTasks.filter((task) => task.group === group).map((task) => task.id);
|
|
2883
|
+
if (group.record.calls.length !== actual.length || group.record.calls.some((id) => !actual.includes(id))) {
|
|
2884
|
+
throw new Error("Parallel call membership changed during replay.");
|
|
2885
|
+
}
|
|
2886
|
+
for (const callId of group.record.calls) {
|
|
2887
|
+
const record = callRecord(callId);
|
|
2888
|
+
if (record?.status !== "interrupted") continue;
|
|
2889
|
+
const index = record.interrupts.length - 1;
|
|
2890
|
+
if (record.interrupts[index]?.response !== void 0) continue;
|
|
2891
|
+
group.record.interrupts.push({ callId, index });
|
|
2892
|
+
await bridge(callId, index);
|
|
2893
|
+
throw new Error(
|
|
2894
|
+
"Parallel interrupt unexpectedly resumed without replay."
|
|
2895
|
+
);
|
|
2896
|
+
}
|
|
2897
|
+
if (rejected.length) throw new ParallelError(outcomes);
|
|
2898
|
+
return outcomes.map(
|
|
2899
|
+
(outcome) => outcome.value
|
|
2900
|
+
);
|
|
2901
|
+
} finally {
|
|
2902
|
+
ctx.parallelGroup = void 0;
|
|
2903
|
+
}
|
|
2904
|
+
};
|
|
2905
|
+
const promise = join();
|
|
2906
|
+
ctx.parallelJoins.push(promise);
|
|
2907
|
+
void promise.catch(() => {
|
|
2908
|
+
});
|
|
2909
|
+
return promise;
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2665
2912
|
// src/workflow.ts
|
|
2666
2913
|
var import_node_crypto5 = require("crypto");
|
|
2667
|
-
var
|
|
2914
|
+
var import_core7 = require("@kortyx/core");
|
|
2915
|
+
var import_errors6 = require("@kortyx/core/errors");
|
|
2668
2916
|
|
|
2669
2917
|
// src/workflow-telemetry.ts
|
|
2670
2918
|
var import_node_crypto4 = require("crypto");
|
|
@@ -2706,10 +2954,20 @@ function workflowCallContent(telemetry, side, value) {
|
|
|
2706
2954
|
}
|
|
2707
2955
|
|
|
2708
2956
|
// src/workflow.ts
|
|
2709
|
-
var WorkflowCallError = class extends
|
|
2710
|
-
constructor(workflow, message) {
|
|
2711
|
-
|
|
2957
|
+
var WorkflowCallError = class extends import_errors6.KortyxError {
|
|
2958
|
+
constructor(workflow, message, cause, failure) {
|
|
2959
|
+
const detail = (0, import_errors6.serializeFailure)(
|
|
2960
|
+
failure ?? cause ?? new import_errors6.WorkflowContractError(message)
|
|
2961
|
+
);
|
|
2962
|
+
super(detail.code, `Workflow '${workflow}': ${message}`, {
|
|
2963
|
+
...detail,
|
|
2964
|
+
safeMessage: detail.message,
|
|
2965
|
+
...cause !== void 0 ? { cause } : {}
|
|
2966
|
+
});
|
|
2712
2967
|
this.workflow = workflow;
|
|
2968
|
+
Object.assign(this.failure, detail, {
|
|
2969
|
+
context: { ...detail.context, workflow }
|
|
2970
|
+
});
|
|
2713
2971
|
this.name = "WorkflowCallError";
|
|
2714
2972
|
}
|
|
2715
2973
|
workflow;
|
|
@@ -2720,10 +2978,12 @@ function workflowCallFingerprint(value) {
|
|
|
2720
2978
|
if (v === null || typeof v === "string" || typeof v === "boolean") return v;
|
|
2721
2979
|
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
2722
2980
|
if (typeof v !== "object" || !v || seen.has(v)) {
|
|
2723
|
-
throw new
|
|
2981
|
+
throw new import_errors6.WorkflowContractError(
|
|
2982
|
+
"Workflow input and output must be JSON serializable."
|
|
2983
|
+
);
|
|
2724
2984
|
}
|
|
2725
2985
|
if (!Array.isArray(v) && Object.getPrototypeOf(v) !== Object.prototype) {
|
|
2726
|
-
throw new
|
|
2986
|
+
throw new import_errors6.WorkflowContractError(
|
|
2727
2987
|
"Workflow input and output must contain plain JSON objects."
|
|
2728
2988
|
);
|
|
2729
2989
|
}
|
|
@@ -2744,29 +3004,49 @@ function createWorkflowHooks(workflows) {
|
|
|
2744
3004
|
useWorkflow: (args) => {
|
|
2745
3005
|
const workflow = workflows[args.workflow];
|
|
2746
3006
|
if (!workflow || workflow.id !== args.workflow)
|
|
2747
|
-
throw new
|
|
3007
|
+
throw new import_errors6.WorkflowContractError(
|
|
3008
|
+
"Workflow registry keys must match definition ids."
|
|
3009
|
+
);
|
|
2748
3010
|
return useWorkflow({ ...args, workflow });
|
|
2749
3011
|
}
|
|
2750
3012
|
};
|
|
2751
3013
|
}
|
|
2752
3014
|
async function useWorkflow(args) {
|
|
3015
|
+
const task = registerWorkflowTask(args.id);
|
|
3016
|
+
try {
|
|
3017
|
+
await Promise.resolve();
|
|
3018
|
+
if (task.group) await task.group.ready;
|
|
3019
|
+
return await runWorkflowCall(args, task);
|
|
3020
|
+
} catch (error) {
|
|
3021
|
+
if ((0, import_core7.isExecutionCancelled)(error) || (0, import_core7.isExecutionLimitReached)(error))
|
|
3022
|
+
getHookContext().workflowControlError = error;
|
|
3023
|
+
throw error;
|
|
3024
|
+
} finally {
|
|
3025
|
+
task.finish();
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
async function runWorkflowCall(args, task) {
|
|
2753
3029
|
const ctx = getHookContext();
|
|
2754
|
-
(0,
|
|
3030
|
+
(0, import_core7.throwIfExecutionAborted)(ctx.node.abortSignal);
|
|
2755
3031
|
const service = ctx.node.callWorkflow;
|
|
2756
3032
|
if (!service)
|
|
2757
|
-
throw new
|
|
3033
|
+
throw new import_errors6.WorkflowContractError(
|
|
3034
|
+
"useWorkflow requires an agent workflow registry."
|
|
3035
|
+
);
|
|
2758
3036
|
if (!args.id.trim())
|
|
2759
|
-
throw new
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
3037
|
+
throw new import_errors6.WorkflowContractError(
|
|
3038
|
+
"useWorkflow requires a stable, nonempty id."
|
|
3039
|
+
);
|
|
3040
|
+
if (ctx.workflowCallActive && !task.group)
|
|
3041
|
+
throw new import_errors6.WorkflowContractError(
|
|
3042
|
+
"Concurrent workflow calls require parallel([...]); otherwise await each call."
|
|
2763
3043
|
);
|
|
2764
3044
|
if (ctx.workflowCallIds.has(args.id))
|
|
2765
|
-
throw new
|
|
3045
|
+
throw new import_errors6.WorkflowContractError(
|
|
2766
3046
|
`Duplicate workflow call id '${args.id}' in one node activation.`
|
|
2767
3047
|
);
|
|
2768
3048
|
if (ctx.workflowCallIds.size >= 64)
|
|
2769
|
-
throw new
|
|
3049
|
+
throw new import_errors6.WorkflowContractError(
|
|
2770
3050
|
"A node may call at most 64 child workflows per activation."
|
|
2771
3051
|
);
|
|
2772
3052
|
ctx.workflowCallIds.add(args.id);
|
|
@@ -2780,6 +3060,11 @@ async function useWorkflow(args) {
|
|
|
2780
3060
|
});
|
|
2781
3061
|
const key = `__useWorkflow:${args.id}`;
|
|
2782
3062
|
let record = ctx.currentNodeState.byKey[key];
|
|
3063
|
+
if (record && record.parallelKey !== task.group?.key)
|
|
3064
|
+
throw new WorkflowCallError(
|
|
3065
|
+
workflow,
|
|
3066
|
+
"parallel call membership changed during replay"
|
|
3067
|
+
);
|
|
2783
3068
|
if (record && record.fingerprint !== fingerprint)
|
|
2784
3069
|
throw new WorkflowCallError(
|
|
2785
3070
|
workflow,
|
|
@@ -2790,11 +3075,12 @@ async function useWorkflow(args) {
|
|
|
2790
3075
|
fingerprint,
|
|
2791
3076
|
invocationId: (0, import_node_crypto5.randomUUID)(),
|
|
2792
3077
|
status: "running",
|
|
2793
|
-
interrupts: []
|
|
3078
|
+
interrupts: [],
|
|
3079
|
+
...task.group ? { parallelKey: task.group.key } : {}
|
|
2794
3080
|
};
|
|
2795
3081
|
ctx.currentNodeState.byKey[key] = record;
|
|
2796
3082
|
ctx.stateDirty = true;
|
|
2797
|
-
ctx.workflowCallActive = true;
|
|
3083
|
+
if (!task.group) ctx.workflowCallActive = true;
|
|
2798
3084
|
const telemetry = ctx.node.workflowCallTelemetry;
|
|
2799
3085
|
const activationKey = "__useWorkflowActivation";
|
|
2800
3086
|
ctx.currentNodeState.byKey[activationKey] ??= (0, import_node_crypto5.randomUUID)();
|
|
@@ -2830,14 +3116,21 @@ async function useWorkflow(args) {
|
|
|
2830
3116
|
};
|
|
2831
3117
|
const cached = record.status === "completed";
|
|
2832
3118
|
try {
|
|
2833
|
-
for (const interruption of record.interrupts) {
|
|
3119
|
+
for (const interruption of task.group ? [] : record.interrupts) {
|
|
2834
3120
|
interruption.response = await awaitInterruptInternal({
|
|
2835
3121
|
request: interruption.request,
|
|
2836
3122
|
...interruption.request.meta ? { meta: interruption.request.meta } : {}
|
|
2837
3123
|
});
|
|
2838
3124
|
}
|
|
2839
3125
|
if (record.status === "failed")
|
|
2840
|
-
throw new WorkflowCallError(
|
|
3126
|
+
throw new WorkflowCallError(
|
|
3127
|
+
workflow,
|
|
3128
|
+
record.error ?? "child failed",
|
|
3129
|
+
void 0,
|
|
3130
|
+
record.failure ?? (0, import_errors6.serializeFailure)(void 0)
|
|
3131
|
+
);
|
|
3132
|
+
if (task.group && record.status === "interrupted" && record.interrupts.at(-1)?.response === void 0)
|
|
3133
|
+
throw new ParallelChildWaiting();
|
|
2841
3134
|
while (record.status !== "completed") {
|
|
2842
3135
|
let outcome;
|
|
2843
3136
|
try {
|
|
@@ -2856,13 +3149,13 @@ async function useWorkflow(args) {
|
|
|
2856
3149
|
...record.interrupts.length ? { response: record.interrupts.at(-1)?.response } : {}
|
|
2857
3150
|
});
|
|
2858
3151
|
} catch (error) {
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
3152
|
+
const child = error;
|
|
3153
|
+
recordUsage(child?.__kortyxChildUsage);
|
|
3154
|
+
(0, import_core7.throwIfExecutionAborted)(ctx.node.abortSignal);
|
|
3155
|
+
if ((0, import_core7.isExecutionCancelled)(error)) throw error;
|
|
3156
|
+
if ((0, import_core7.isExecutionLimitReached)(error)) {
|
|
3157
|
+
if (child?.__kortyxChildSnapshot)
|
|
2864
3158
|
record.snapshot = child.__kortyxChildSnapshot;
|
|
2865
|
-
recordUsage(child.__kortyxChildUsage);
|
|
2866
3159
|
report("suspended", {
|
|
2867
3160
|
reason: "limit_reached",
|
|
2868
3161
|
limit: error.limitReached
|
|
@@ -2870,11 +3163,17 @@ async function useWorkflow(args) {
|
|
|
2870
3163
|
throw error;
|
|
2871
3164
|
}
|
|
2872
3165
|
record.status = "failed";
|
|
2873
|
-
record.
|
|
3166
|
+
record.failure = (0, import_errors6.serializeFailure)(error);
|
|
3167
|
+
record.error = record.failure.message;
|
|
2874
3168
|
report("failed", { error: "Child workflow failed" });
|
|
2875
|
-
throw new WorkflowCallError(
|
|
3169
|
+
throw new WorkflowCallError(
|
|
3170
|
+
workflow,
|
|
3171
|
+
record.error,
|
|
3172
|
+
error,
|
|
3173
|
+
record.failure
|
|
3174
|
+
);
|
|
2876
3175
|
}
|
|
2877
|
-
(0,
|
|
3176
|
+
(0, import_core7.throwIfExecutionAborted)(ctx.node.abortSignal);
|
|
2878
3177
|
recordUsage(outcome.usage);
|
|
2879
3178
|
if (outcome.status === "completed") {
|
|
2880
3179
|
record.data = outcome.data;
|
|
@@ -2895,6 +3194,7 @@ async function useWorkflow(args) {
|
|
|
2895
3194
|
request: outcome.request
|
|
2896
3195
|
};
|
|
2897
3196
|
record.interrupts.push(interruption);
|
|
3197
|
+
if (task.group) throw new ParallelChildWaiting();
|
|
2898
3198
|
interruption.response = await awaitInterruptInternal({
|
|
2899
3199
|
request: interruption.request,
|
|
2900
3200
|
...interruption.request.meta ? { meta: interruption.request.meta } : {}
|
|
@@ -2908,15 +3208,17 @@ async function useWorkflow(args) {
|
|
|
2908
3208
|
data: JSON.parse(JSON.stringify(data))
|
|
2909
3209
|
};
|
|
2910
3210
|
} finally {
|
|
2911
|
-
ctx.workflowCallActive = false;
|
|
3211
|
+
if (!task.group) ctx.workflowCallActive = false;
|
|
2912
3212
|
}
|
|
2913
3213
|
}
|
|
2914
3214
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2915
3215
|
0 && (module.exports = {
|
|
3216
|
+
ParallelError,
|
|
2916
3217
|
WorkflowCallError,
|
|
2917
3218
|
completeResponse,
|
|
2918
3219
|
createWorkflowHooks,
|
|
2919
3220
|
emitWorkflowCall,
|
|
3221
|
+
parallel,
|
|
2920
3222
|
runWithHookContext,
|
|
2921
3223
|
useAbortSignal,
|
|
2922
3224
|
useInterrupt,
|