@kortyx/hooks 0.24.0 → 0.25.1
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.d.ts +6 -2
- package/dist/index.js +251 -152
- 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
|
@@ -289,13 +289,18 @@ var resolveHookStatePatch = (args) => ({
|
|
|
289
289
|
});
|
|
290
290
|
|
|
291
291
|
// src/validation.ts
|
|
292
|
+
var import_errors = require("@kortyx/core/errors");
|
|
292
293
|
var parseWithSchema = (schema, value, label) => {
|
|
293
294
|
if (!schema) return value;
|
|
294
295
|
const parsed = schema.safeParse(value);
|
|
295
296
|
if (parsed.success) return parsed.data;
|
|
296
297
|
const parsedError = "error" in parsed && parsed.error ? parsed.error : void 0;
|
|
297
298
|
const reason = typeof parsedError?.message === "string" && parsedError.message.length > 0 ? parsedError.message : "invalid payload";
|
|
298
|
-
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
|
+
);
|
|
299
304
|
};
|
|
300
305
|
|
|
301
306
|
// src/interrupt.ts
|
|
@@ -437,6 +442,21 @@ var import_core3 = require("@kortyx/core");
|
|
|
437
442
|
// src/reason-engine.ts
|
|
438
443
|
var import_node_crypto2 = require("crypto");
|
|
439
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
|
+
}
|
|
440
460
|
var toError = (value) => value instanceof Error ? value : new Error(String(value));
|
|
441
461
|
var createRuntimeId = () => {
|
|
442
462
|
try {
|
|
@@ -550,15 +570,18 @@ async function runReasonEngine(args) {
|
|
|
550
570
|
};
|
|
551
571
|
const failTrace = (span, error) => {
|
|
552
572
|
const detail = error;
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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
|
+
}
|
|
562
585
|
};
|
|
563
586
|
try {
|
|
564
587
|
if (stream) {
|
|
@@ -643,6 +666,8 @@ async function runReasonEngine(args) {
|
|
|
643
666
|
}
|
|
644
667
|
}
|
|
645
668
|
}
|
|
669
|
+
(0, import_core2.throwIfExecutionAborted)(abortSignal);
|
|
670
|
+
assertModelFinish({ finishReason, usage });
|
|
646
671
|
if (emit) {
|
|
647
672
|
emitNodeEvent(args.emitEvent, args.nodeId, "text-end", {
|
|
648
673
|
...commonMeta
|
|
@@ -664,6 +689,7 @@ async function runReasonEngine(args) {
|
|
|
664
689
|
}
|
|
665
690
|
const response = await model.invoke(messages);
|
|
666
691
|
(0, import_core2.throwIfExecutionAborted)(abortSignal);
|
|
692
|
+
assertModelFinish(response);
|
|
667
693
|
if (response.content) args.onTextChunk?.(response.content);
|
|
668
694
|
if (emit) {
|
|
669
695
|
emitNodeEvent(args.emitEvent, args.nodeId, "text-start", {
|
|
@@ -1463,6 +1489,7 @@ function createStructuredOutputStreamer(args, id, opId) {
|
|
|
1463
1489
|
}
|
|
1464
1490
|
|
|
1465
1491
|
// src/reason/parsing.ts
|
|
1492
|
+
var import_errors3 = require("@kortyx/core/errors");
|
|
1466
1493
|
var tryJsonParse = (value) => {
|
|
1467
1494
|
try {
|
|
1468
1495
|
return { ok: true, value: JSON.parse(value) };
|
|
@@ -1500,132 +1527,135 @@ var resolveOutputCandidate = (text) => {
|
|
|
1500
1527
|
}
|
|
1501
1528
|
return text;
|
|
1502
1529
|
};
|
|
1503
|
-
var
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
};
|
|
1508
|
-
var isLikelyTruncatedStructuredOutput = (text) => {
|
|
1509
|
-
const trimmed = text.trim();
|
|
1510
|
-
if (!looksLikeStructuredJsonAttempt(trimmed)) return false;
|
|
1511
|
-
const direct = tryJsonParse(trimmed);
|
|
1512
|
-
if (!direct.ok && direct.error) return true;
|
|
1513
|
-
const block = extractJsonCodeBlock(trimmed);
|
|
1514
|
-
if (!block) return false;
|
|
1515
|
-
const parsedBlock = tryJsonParse(block);
|
|
1516
|
-
return !parsedBlock.ok && Boolean(parsedBlock.error);
|
|
1517
|
-
};
|
|
1518
|
-
var createTruncatedStructuredOutputError = (label) => new Error(
|
|
1519
|
-
`${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
|
|
1520
1534
|
);
|
|
1521
|
-
var createInvalidStructuredOutputError = (label) => new
|
|
1522
|
-
|
|
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
|
|
1523
1539
|
);
|
|
1524
1540
|
var parseReasonOutputWithSchema = (args) => {
|
|
1525
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;
|
|
1526
1544
|
try {
|
|
1527
1545
|
return parseWithSchema(args.schema, candidate, args.label);
|
|
1528
1546
|
} catch (error) {
|
|
1529
|
-
if (
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
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;
|
|
1536
1555
|
}
|
|
1537
1556
|
};
|
|
1538
1557
|
var parseInterruptFirstPassResult = (args) => {
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
if (
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
const mode = args.mode ?? "required";
|
|
1552
|
-
let interruptRequired = true;
|
|
1553
|
-
if (mode === "optional") {
|
|
1554
|
-
if (candidate.decision !== "continue" && candidate.decision !== "interrupt") {
|
|
1555
|
-
throw new Error(
|
|
1556
|
-
'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."
|
|
1557
1570
|
);
|
|
1558
1571
|
}
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
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 = [
|
|
1562
1599
|
candidate.interruptRequest,
|
|
1563
1600
|
candidate.request,
|
|
1564
|
-
candidate.interrupt
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
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"
|
|
1569
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;
|
|
1570
1628
|
}
|
|
1571
1629
|
}
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
candidate.request,
|
|
1578
|
-
candidate.interrupt,
|
|
1579
|
-
mode === "required" ? candidate : void 0
|
|
1580
|
-
];
|
|
1581
|
-
const requestPayload = requestCandidates.find(
|
|
1582
|
-
(value) => value !== null && value !== void 0
|
|
1583
|
-
);
|
|
1584
|
-
request = parseWithSchema(
|
|
1585
|
-
args.requestSchema,
|
|
1586
|
-
requestPayload,
|
|
1587
|
-
"useReason interrupt.request"
|
|
1588
|
-
);
|
|
1589
|
-
}
|
|
1590
|
-
let output;
|
|
1591
|
-
if (args.outputSchema) {
|
|
1592
|
-
const outputPayload = Object.hasOwn(candidate, "output") ? candidate.output : candidate;
|
|
1593
|
-
try {
|
|
1594
|
-
output = parseWithSchema(
|
|
1595
|
-
args.outputSchema,
|
|
1596
|
-
outputPayload,
|
|
1597
|
-
"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."
|
|
1598
1635
|
);
|
|
1599
|
-
} catch (error) {
|
|
1600
|
-
if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
|
|
1601
|
-
throw createTruncatedStructuredOutputError("useReason output");
|
|
1602
|
-
}
|
|
1603
|
-
if (typeof outputPayload === "string" && outputPayload.trim().length > 0) {
|
|
1604
|
-
throw createInvalidStructuredOutputError("useReason output");
|
|
1605
|
-
}
|
|
1606
|
-
throw error;
|
|
1607
1636
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
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
|
+
}
|
|
1617
1645
|
return {
|
|
1618
1646
|
draftText,
|
|
1619
|
-
interruptRequired:
|
|
1647
|
+
interruptRequired: true,
|
|
1648
|
+
request,
|
|
1620
1649
|
...output !== void 0 ? { output } : {}
|
|
1621
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;
|
|
1622
1658
|
}
|
|
1623
|
-
return {
|
|
1624
|
-
draftText,
|
|
1625
|
-
interruptRequired: true,
|
|
1626
|
-
request,
|
|
1627
|
-
...output !== void 0 ? { output } : {}
|
|
1628
|
-
};
|
|
1629
1659
|
};
|
|
1630
1660
|
|
|
1631
1661
|
// src/reason/prompting.ts
|
|
@@ -1839,7 +1869,7 @@ var emitReasonStructuredOutput = (args) => {
|
|
|
1839
1869
|
|
|
1840
1870
|
// src/reason/tools.ts
|
|
1841
1871
|
var import_core4 = require("@kortyx/core");
|
|
1842
|
-
var
|
|
1872
|
+
var import_errors4 = require("@kortyx/core/errors");
|
|
1843
1873
|
var toToolDefinitions = (tools) => tools.map((tool) => ({
|
|
1844
1874
|
name: tool.name,
|
|
1845
1875
|
...tool.title ? { title: tool.title } : {},
|
|
@@ -1873,17 +1903,27 @@ var normalizeToolResult = (toolCall, value) => {
|
|
|
1873
1903
|
...value !== void 0 && value !== null && typeof value === "object" && !Array.isArray(value) ? { structuredContent: value } : {}
|
|
1874
1904
|
};
|
|
1875
1905
|
};
|
|
1876
|
-
var closeOwnedTools = async (tools) => {
|
|
1906
|
+
var closeOwnedTools = async (tools, preserveFailure = false, traceSpan) => {
|
|
1877
1907
|
const closeFns = /* @__PURE__ */ new Set();
|
|
1878
1908
|
for (const tool of tools) {
|
|
1879
1909
|
if (tool.closeAfterUse === false || !tool.close) continue;
|
|
1880
1910
|
closeFns.add(tool.close);
|
|
1881
1911
|
}
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
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
|
+
}
|
|
1887
1927
|
};
|
|
1888
1928
|
var createInitialMessages = (args) => [
|
|
1889
1929
|
...typeof args.system === "string" && args.system.length > 0 ? [{ role: "system", content: args.system }] : [],
|
|
@@ -1900,6 +1940,7 @@ var runReasonToolLoop = async (args) => {
|
|
|
1900
1940
|
const saved = ctx.currentNodeState.byKey[checkpointKey];
|
|
1901
1941
|
const checkpoint = saved?.status === "tool_loop" ? saved : void 0;
|
|
1902
1942
|
const opId = checkpoint?.opId ?? args.opId;
|
|
1943
|
+
let primaryFailure = false;
|
|
1903
1944
|
const tools = useReasonArgs.tools ?? [];
|
|
1904
1945
|
const toolByName = /* @__PURE__ */ new Map();
|
|
1905
1946
|
let validationCompleted = false;
|
|
@@ -1932,7 +1973,7 @@ var runReasonToolLoop = async (args) => {
|
|
|
1932
1973
|
validationCompleted = true;
|
|
1933
1974
|
} finally {
|
|
1934
1975
|
if (!validationCompleted) {
|
|
1935
|
-
await closeOwnedTools(tools);
|
|
1976
|
+
await closeOwnedTools(tools, true, traceSpan);
|
|
1936
1977
|
}
|
|
1937
1978
|
}
|
|
1938
1979
|
const toolDefinitions = toToolDefinitions(tools);
|
|
@@ -2051,6 +2092,7 @@ var runReasonToolLoop = async (args) => {
|
|
|
2051
2092
|
text: step.text,
|
|
2052
2093
|
schema: useReasonArgs.outputSchema,
|
|
2053
2094
|
...step.finishReason ? { finishReason: step.finishReason } : {},
|
|
2095
|
+
...aggregatedUsage ? { usage: aggregatedUsage } : {},
|
|
2054
2096
|
label: "useReason output"
|
|
2055
2097
|
});
|
|
2056
2098
|
aggregatedWarnings = mergeWarnings(aggregatedWarnings, [
|
|
@@ -2138,12 +2180,12 @@ var runReasonToolLoop = async (args) => {
|
|
|
2138
2180
|
options: [
|
|
2139
2181
|
{ id: "approve", label: "Approve" },
|
|
2140
2182
|
{ id: "deny", label: "Deny" }
|
|
2141
|
-
]
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2183
|
+
]
|
|
2184
|
+
},
|
|
2185
|
+
meta: {
|
|
2186
|
+
tool: tool.name,
|
|
2187
|
+
toolCallId: toolCall.id,
|
|
2188
|
+
input: toolCall.input
|
|
2147
2189
|
}
|
|
2148
2190
|
});
|
|
2149
2191
|
const approved = Array.isArray(approval) ? approval.includes("approve") : approval === "approve";
|
|
@@ -2205,12 +2247,12 @@ var runReasonToolLoop = async (args) => {
|
|
|
2205
2247
|
});
|
|
2206
2248
|
} catch (error) {
|
|
2207
2249
|
(0, import_core4.throwIfExecutionAborted)(abortSignal);
|
|
2208
|
-
if ((0,
|
|
2209
|
-
throw error;
|
|
2250
|
+
if ((0, import_errors4.isControlFlowError)(error)) throw error;
|
|
2210
2251
|
const result2 = {
|
|
2211
2252
|
toolCallId: toolCall.id,
|
|
2212
2253
|
name: tool.name,
|
|
2213
|
-
content:
|
|
2254
|
+
content: (0, import_errors4.serializeFailure)(error).message,
|
|
2255
|
+
failure: (0, import_errors4.serializeFailure)(error),
|
|
2214
2256
|
isError: true
|
|
2215
2257
|
};
|
|
2216
2258
|
toolResults.push(result2);
|
|
@@ -2219,14 +2261,16 @@ var runReasonToolLoop = async (args) => {
|
|
|
2219
2261
|
emitToolEvent("tool-call-error", {
|
|
2220
2262
|
tool: tool.name,
|
|
2221
2263
|
toolCallId: toolCall.id,
|
|
2222
|
-
message: result2.content
|
|
2264
|
+
message: result2.content,
|
|
2265
|
+
failure: result2.failure
|
|
2223
2266
|
});
|
|
2224
2267
|
}
|
|
2225
2268
|
traceSpan?.addEvent?.("useReason.tool-call.error", {
|
|
2226
2269
|
stepIndex,
|
|
2227
2270
|
tool: tool.name,
|
|
2228
2271
|
toolCallId: toolCall.id,
|
|
2229
|
-
message: result2.content
|
|
2272
|
+
message: result2.content,
|
|
2273
|
+
failure: result2.failure
|
|
2230
2274
|
});
|
|
2231
2275
|
}
|
|
2232
2276
|
const completed2 = toolResults.at(-1);
|
|
@@ -2253,6 +2297,7 @@ var runReasonToolLoop = async (args) => {
|
|
|
2253
2297
|
text: finalText,
|
|
2254
2298
|
schema: useReasonArgs.outputSchema,
|
|
2255
2299
|
...finalFinishReason ? { finishReason: finalFinishReason } : {},
|
|
2300
|
+
...aggregatedUsage ? { usage: aggregatedUsage } : {},
|
|
2256
2301
|
label: "useReason output"
|
|
2257
2302
|
});
|
|
2258
2303
|
}
|
|
@@ -2299,15 +2344,19 @@ var runReasonToolLoop = async (args) => {
|
|
|
2299
2344
|
ctx.stateDirty = true;
|
|
2300
2345
|
return result;
|
|
2301
2346
|
} catch (error) {
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2347
|
+
primaryFailure = true;
|
|
2348
|
+
try {
|
|
2349
|
+
traceSpan?.fail?.(error, {
|
|
2350
|
+
attributes: {
|
|
2351
|
+
toolStepCount: steps.length,
|
|
2352
|
+
toolCallCount: allToolCalls.length
|
|
2353
|
+
}
|
|
2354
|
+
});
|
|
2355
|
+
} catch {
|
|
2356
|
+
}
|
|
2308
2357
|
throw error;
|
|
2309
2358
|
} finally {
|
|
2310
|
-
await closeOwnedTools(tools);
|
|
2359
|
+
await closeOwnedTools(tools, primaryFailure, traceSpan);
|
|
2311
2360
|
}
|
|
2312
2361
|
};
|
|
2313
2362
|
|
|
@@ -2472,6 +2521,7 @@ async function useReason(args) {
|
|
|
2472
2521
|
text: first.text,
|
|
2473
2522
|
requestSchema: args.interrupt.requestSchema,
|
|
2474
2523
|
...first.finishReason ? { finishReason: first.finishReason } : {},
|
|
2524
|
+
...first.usage ? { usage: first.usage } : {},
|
|
2475
2525
|
...args.outputSchema ? { outputSchema: args.outputSchema } : {},
|
|
2476
2526
|
mode: args.interrupt.mode ?? "required"
|
|
2477
2527
|
});
|
|
@@ -2489,6 +2539,7 @@ async function useReason(args) {
|
|
|
2489
2539
|
text: first.text,
|
|
2490
2540
|
schema: args.outputSchema,
|
|
2491
2541
|
...first.finishReason ? { finishReason: first.finishReason } : {},
|
|
2542
|
+
...first.usage ? { usage: first.usage } : {},
|
|
2492
2543
|
label: "useReason output"
|
|
2493
2544
|
});
|
|
2494
2545
|
}
|
|
@@ -2583,6 +2634,7 @@ async function useReason(args) {
|
|
|
2583
2634
|
text: finalText,
|
|
2584
2635
|
schema: args.outputSchema,
|
|
2585
2636
|
...second.finishReason ? { finishReason: second.finishReason } : {},
|
|
2637
|
+
...aggregatedUsage ? { usage: aggregatedUsage } : {},
|
|
2586
2638
|
label: "useReason output"
|
|
2587
2639
|
});
|
|
2588
2640
|
}
|
|
@@ -2690,7 +2742,9 @@ function useWorkflowState(key, ...rest) {
|
|
|
2690
2742
|
|
|
2691
2743
|
// src/parallel.ts
|
|
2692
2744
|
var import_core6 = require("@kortyx/core");
|
|
2745
|
+
var import_errors5 = require("@kortyx/core/errors");
|
|
2693
2746
|
var ParallelChildWaiting = class extends Error {
|
|
2747
|
+
name = "ParallelChildWaiting";
|
|
2694
2748
|
};
|
|
2695
2749
|
var ParallelError = class extends AggregateError {
|
|
2696
2750
|
constructor(results) {
|
|
@@ -2702,8 +2756,23 @@ var ParallelError = class extends AggregateError {
|
|
|
2702
2756
|
);
|
|
2703
2757
|
this.results = results;
|
|
2704
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
|
+
});
|
|
2705
2769
|
}
|
|
2706
2770
|
results;
|
|
2771
|
+
code = "PARALLEL_FAILED";
|
|
2772
|
+
failure;
|
|
2773
|
+
toJSON() {
|
|
2774
|
+
return (0, import_errors5.serializeFailure)(this);
|
|
2775
|
+
}
|
|
2707
2776
|
};
|
|
2708
2777
|
function registerWorkflowTask(id) {
|
|
2709
2778
|
const ctx = getHookContext();
|
|
@@ -2843,6 +2912,7 @@ function parallel(values) {
|
|
|
2843
2912
|
// src/workflow.ts
|
|
2844
2913
|
var import_node_crypto5 = require("crypto");
|
|
2845
2914
|
var import_core7 = require("@kortyx/core");
|
|
2915
|
+
var import_errors6 = require("@kortyx/core/errors");
|
|
2846
2916
|
|
|
2847
2917
|
// src/workflow-telemetry.ts
|
|
2848
2918
|
var import_node_crypto4 = require("crypto");
|
|
@@ -2884,10 +2954,20 @@ function workflowCallContent(telemetry, side, value) {
|
|
|
2884
2954
|
}
|
|
2885
2955
|
|
|
2886
2956
|
// src/workflow.ts
|
|
2887
|
-
var WorkflowCallError = class extends
|
|
2888
|
-
constructor(workflow, message) {
|
|
2889
|
-
|
|
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
|
+
});
|
|
2890
2967
|
this.workflow = workflow;
|
|
2968
|
+
Object.assign(this.failure, detail, {
|
|
2969
|
+
context: { ...detail.context, workflow }
|
|
2970
|
+
});
|
|
2891
2971
|
this.name = "WorkflowCallError";
|
|
2892
2972
|
}
|
|
2893
2973
|
workflow;
|
|
@@ -2898,10 +2978,12 @@ function workflowCallFingerprint(value) {
|
|
|
2898
2978
|
if (v === null || typeof v === "string" || typeof v === "boolean") return v;
|
|
2899
2979
|
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
2900
2980
|
if (typeof v !== "object" || !v || seen.has(v)) {
|
|
2901
|
-
throw new
|
|
2981
|
+
throw new import_errors6.WorkflowContractError(
|
|
2982
|
+
"Workflow input and output must be JSON serializable."
|
|
2983
|
+
);
|
|
2902
2984
|
}
|
|
2903
2985
|
if (!Array.isArray(v) && Object.getPrototypeOf(v) !== Object.prototype) {
|
|
2904
|
-
throw new
|
|
2986
|
+
throw new import_errors6.WorkflowContractError(
|
|
2905
2987
|
"Workflow input and output must contain plain JSON objects."
|
|
2906
2988
|
);
|
|
2907
2989
|
}
|
|
@@ -2922,7 +3004,9 @@ function createWorkflowHooks(workflows) {
|
|
|
2922
3004
|
useWorkflow: (args) => {
|
|
2923
3005
|
const workflow = workflows[args.workflow];
|
|
2924
3006
|
if (!workflow || workflow.id !== args.workflow)
|
|
2925
|
-
throw new
|
|
3007
|
+
throw new import_errors6.WorkflowContractError(
|
|
3008
|
+
"Workflow registry keys must match definition ids."
|
|
3009
|
+
);
|
|
2926
3010
|
return useWorkflow({ ...args, workflow });
|
|
2927
3011
|
}
|
|
2928
3012
|
};
|
|
@@ -2946,19 +3030,23 @@ async function runWorkflowCall(args, task) {
|
|
|
2946
3030
|
(0, import_core7.throwIfExecutionAborted)(ctx.node.abortSignal);
|
|
2947
3031
|
const service = ctx.node.callWorkflow;
|
|
2948
3032
|
if (!service)
|
|
2949
|
-
throw new
|
|
3033
|
+
throw new import_errors6.WorkflowContractError(
|
|
3034
|
+
"useWorkflow requires an agent workflow registry."
|
|
3035
|
+
);
|
|
2950
3036
|
if (!args.id.trim())
|
|
2951
|
-
throw new
|
|
3037
|
+
throw new import_errors6.WorkflowContractError(
|
|
3038
|
+
"useWorkflow requires a stable, nonempty id."
|
|
3039
|
+
);
|
|
2952
3040
|
if (ctx.workflowCallActive && !task.group)
|
|
2953
|
-
throw new
|
|
3041
|
+
throw new import_errors6.WorkflowContractError(
|
|
2954
3042
|
"Concurrent workflow calls require parallel([...]); otherwise await each call."
|
|
2955
3043
|
);
|
|
2956
3044
|
if (ctx.workflowCallIds.has(args.id))
|
|
2957
|
-
throw new
|
|
3045
|
+
throw new import_errors6.WorkflowContractError(
|
|
2958
3046
|
`Duplicate workflow call id '${args.id}' in one node activation.`
|
|
2959
3047
|
);
|
|
2960
3048
|
if (ctx.workflowCallIds.size >= 64)
|
|
2961
|
-
throw new
|
|
3049
|
+
throw new import_errors6.WorkflowContractError(
|
|
2962
3050
|
"A node may call at most 64 child workflows per activation."
|
|
2963
3051
|
);
|
|
2964
3052
|
ctx.workflowCallIds.add(args.id);
|
|
@@ -3035,7 +3123,12 @@ async function runWorkflowCall(args, task) {
|
|
|
3035
3123
|
});
|
|
3036
3124
|
}
|
|
3037
3125
|
if (record.status === "failed")
|
|
3038
|
-
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
|
+
);
|
|
3039
3132
|
if (task.group && record.status === "interrupted" && record.interrupts.at(-1)?.response === void 0)
|
|
3040
3133
|
throw new ParallelChildWaiting();
|
|
3041
3134
|
while (record.status !== "completed") {
|
|
@@ -3070,9 +3163,15 @@ async function runWorkflowCall(args, task) {
|
|
|
3070
3163
|
throw error;
|
|
3071
3164
|
}
|
|
3072
3165
|
record.status = "failed";
|
|
3073
|
-
record.
|
|
3166
|
+
record.failure = (0, import_errors6.serializeFailure)(error);
|
|
3167
|
+
record.error = record.failure.message;
|
|
3074
3168
|
report("failed", { error: "Child workflow failed" });
|
|
3075
|
-
throw new WorkflowCallError(
|
|
3169
|
+
throw new WorkflowCallError(
|
|
3170
|
+
workflow,
|
|
3171
|
+
record.error,
|
|
3172
|
+
error,
|
|
3173
|
+
record.failure
|
|
3174
|
+
);
|
|
3076
3175
|
}
|
|
3077
3176
|
(0, import_core7.throwIfExecutionAborted)(ctx.node.abortSignal);
|
|
3078
3177
|
recordUsage(outcome.usage);
|