@kortyx/hooks 0.15.0 → 0.16.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 CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.16.0](https://github.com/kortyx-io/kortyx/compare/hooks-v0.15.0...hooks-v0.16.0) (2026-05-25)
4
+
5
+
6
+ ### Features
7
+
8
+ * add MCP tool support to useReason ([8352721](https://github.com/kortyx-io/kortyx/commit/83527215fd2fa6362348d842f8f3b9e9d9d03704))
9
+ * rename useReason tool policy to tool execution ([d6ee2f0](https://github.com/kortyx-io/kortyx/commit/d6ee2f0c92b05f9f51cb38745e94c7fed6158278))
10
+
11
+
12
+ ### Dependencies
13
+
14
+ * The following workspace dependencies were updated
15
+ * dependencies
16
+ * @kortyx/providers bumped to 0.5.0
17
+
3
18
  ## [0.15.0](https://github.com/kortyx-io/kortyx/compare/hooks-v0.14.0...hooks-v0.15.0) (2026-05-24)
4
19
 
5
20
 
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { NodeConfig, InterruptInput, InterruptResult, GraphState } from '@kortyx/core';
2
2
  import { R as ReasonTraceAdapter, K as KortyxTraceMetadata } from './tracing-DK-T8zX5.js';
3
3
  export { a as KortyxTelemetryConfig, b as KortyxTelemetryContentCapture, c as KortyxTelemetryPrompt, d as KortyxTraceAdapter, e as ReasonTraceAttributes, f as ReasonTraceSpan, g as ReasonTraceSpanEndArgs, h as ReasonTraceSpanStartArgs } from './tracing-DK-T8zX5.js';
4
- import { ProviderModelRef, KortyxReasoningOptions, KortyxResponseFormat, KortyxUsage, KortyxFinishReason, KortyxProviderMetadata, KortyxWarning } from '@kortyx/providers';
4
+ import { ProviderModelRef, KortyxReasoningOptions, KortyxResponseFormat, KortyxExecutableTool, KortyxUsage, KortyxFinishReason, KortyxProviderMetadata, KortyxWarning, KortyxToolCall, KortyxToolResult } from '@kortyx/providers';
5
5
 
6
6
  type HookNodeRuntimeContext = {
7
7
  graph: {
@@ -91,6 +91,11 @@ type UseReasonInterruptConfig<TRequest extends InterruptInput = InterruptInput,
91
91
  schemaId?: string | undefined;
92
92
  schemaVersion?: string | undefined;
93
93
  };
94
+ type UseReasonToolExecution = {
95
+ maxSteps?: number | undefined;
96
+ approval?: boolean | Record<string, boolean> | undefined;
97
+ emit?: boolean | Record<string, boolean> | undefined;
98
+ };
94
99
  type UseReasonArgs<TOutput = unknown, TRequest extends InterruptInput = InterruptInput, TResponse = InterruptResult> = {
95
100
  model: ProviderModelRef;
96
101
  input: string;
@@ -109,6 +114,18 @@ type UseReasonArgs<TOutput = unknown, TRequest extends InterruptInput = Interrup
109
114
  outputSchema?: SchemaLike<TOutput> | undefined;
110
115
  structured?: UseReasonStructuredConfig | undefined;
111
116
  interrupt?: UseReasonInterruptConfig<TRequest, TResponse> | undefined;
117
+ tools?: KortyxExecutableTool[] | undefined;
118
+ toolExecution?: UseReasonToolExecution | undefined;
119
+ };
120
+ type UseReasonStep = {
121
+ stepIndex: number;
122
+ text: string;
123
+ toolCalls: KortyxToolCall[];
124
+ toolResults: KortyxToolResult[];
125
+ usage?: KortyxUsage | undefined;
126
+ finishReason?: KortyxFinishReason | undefined;
127
+ providerMetadata?: KortyxProviderMetadata | undefined;
128
+ warnings?: KortyxWarning[] | undefined;
112
129
  };
113
130
  type UseReasonResult<TOutput = unknown, TResponse = InterruptResult> = {
114
131
  id?: string;
@@ -121,6 +138,9 @@ type UseReasonResult<TOutput = unknown, TResponse = InterruptResult> = {
121
138
  warnings?: KortyxWarning[];
122
139
  output?: TOutput;
123
140
  interruptResponse?: TResponse;
141
+ toolCalls?: KortyxToolCall[];
142
+ toolResults?: KortyxToolResult[];
143
+ steps?: UseReasonStep[];
124
144
  };
125
145
  type UseInterruptArgs<TRequest extends InterruptInput = InterruptInput, TResponse = InterruptResult> = {
126
146
  request: TRequest;
@@ -140,4 +160,4 @@ declare function useRuntimeContext<TContext extends Record<string, unknown> = Re
140
160
  declare function useNodeState<T>(initialValue: T): [T, StateSetter<T>];
141
161
  declare function useWorkflowState<T>(key: string, ...rest: [] | [T]): [T, StateSetter<T>];
142
162
 
143
- export { type HookRuntimeContext, KortyxTraceMetadata, ReasonTraceAdapter, type UseInterruptArgs, type UseReasonArgs, type UseReasonResult, type UseStructuredDataArgs, runWithHookContext, useInterrupt, useNodeState, useReason, useRuntimeContext, useStructuredData, useWorkflowState };
163
+ export { type HookRuntimeContext, KortyxTraceMetadata, ReasonTraceAdapter, type UseInterruptArgs, type UseReasonArgs, type UseReasonResult, type UseReasonStep, type UseReasonToolExecution, type UseStructuredDataArgs, runWithHookContext, useInterrupt, useNodeState, useReason, useRuntimeContext, useStructuredData, useWorkflowState };
package/dist/index.js CHANGED
@@ -373,6 +373,7 @@ async function runReasonEngine(args) {
373
373
  const reasoning = args.reasoning ?? args.model.options?.reasoning;
374
374
  const responseFormat = args.responseFormat ?? args.model.options?.responseFormat;
375
375
  const providerOptions = args.providerOptions ?? args.model.options?.providerOptions;
376
+ const tools = args.tools ?? args.model.options?.tools;
376
377
  const model = args.model.provider.getModel(args.model.modelId, {
377
378
  ...temperature !== void 0 ? { temperature } : {},
378
379
  streaming: stream,
@@ -381,13 +382,13 @@ async function runReasonEngine(args) {
381
382
  ...abortSignal !== void 0 ? { abortSignal } : {},
382
383
  ...reasoning !== void 0 ? { reasoning } : {},
383
384
  ...responseFormat !== void 0 ? { responseFormat } : {},
385
+ ...tools !== void 0 ? { tools } : {},
384
386
  ...providerOptions !== void 0 ? { providerOptions } : {}
385
387
  });
386
- const messages = [];
387
- if (typeof args.system === "string" && args.system.length > 0) {
388
- messages.push({ role: "system", content: args.system });
389
- }
390
- messages.push({ role: "user", content: String(args.input ?? "") });
388
+ const messages = args.messages !== void 0 ? args.messages : [
389
+ ...typeof args.system === "string" && args.system.length > 0 ? [{ role: "system", content: args.system }] : [],
390
+ { role: "user", content: String(args.input ?? "") }
391
+ ];
391
392
  const opId = typeof args.opId === "string" && args.opId.length > 0 ? args.opId : createRuntimeId();
392
393
  const segmentId = typeof args.segmentId === "string" && args.segmentId.length > 0 ? args.segmentId : createRuntimeId();
393
394
  const commonMeta = {
@@ -443,6 +444,7 @@ async function runReasonEngine(args) {
443
444
  let raw;
444
445
  let usage;
445
446
  let finishReason;
447
+ let toolCalls;
446
448
  let providerMetadata;
447
449
  let warnings;
448
450
  if (emit) {
@@ -481,6 +483,9 @@ async function runReasonEngine(args) {
481
483
  if (chunk.finishReason !== void 0) {
482
484
  finishReason = chunk.finishReason;
483
485
  }
486
+ if (chunk.toolCalls !== void 0) {
487
+ toolCalls = chunk.toolCalls;
488
+ }
484
489
  if (chunk.providerMetadata !== void 0) {
485
490
  providerMetadata = chunk.providerMetadata;
486
491
  }
@@ -520,6 +525,7 @@ async function runReasonEngine(args) {
520
525
  ...raw !== void 0 ? { raw } : {},
521
526
  ...usage !== void 0 ? { usage } : {},
522
527
  ...finishReason !== void 0 ? { finishReason } : {},
528
+ ...toolCalls !== void 0 ? { toolCalls } : {},
523
529
  ...providerMetadata !== void 0 ? { providerMetadata } : {},
524
530
  ...warnings !== void 0 ? { warnings } : {}
525
531
  };
@@ -546,6 +552,7 @@ async function runReasonEngine(args) {
546
552
  ...response.raw !== void 0 ? { raw: response.raw } : {},
547
553
  ...response.usage !== void 0 ? { usage: response.usage } : {},
548
554
  ...response.finishReason !== void 0 ? { finishReason: response.finishReason } : {},
555
+ ...response.toolCalls !== void 0 ? { toolCalls: response.toolCalls } : {},
549
556
  ...response.providerMetadata !== void 0 ? { providerMetadata: response.providerMetadata } : {},
550
557
  ...response.warnings !== void 0 ? { warnings: response.warnings } : {}
551
558
  };
@@ -578,6 +585,8 @@ async function reasonEngine(args, meta, inputOverride) {
578
585
  reasoning: args.reasoning,
579
586
  responseFormat: args.responseFormat,
580
587
  providerOptions: args.providerOptions,
588
+ tools: args.tools,
589
+ messages: args.messages,
581
590
  defaultTemperature: ctx.node.config?.model?.temperature,
582
591
  stream: args.stream,
583
592
  emit: args.emit,
@@ -903,6 +912,65 @@ var withStructuredStreamHints = (input, args) => {
903
912
  ].join("\n\n");
904
913
  };
905
914
 
915
+ // src/reason/result.ts
916
+ var mergeUsage = (left, right) => {
917
+ if (!left) return right;
918
+ if (!right) return left;
919
+ const sum = (a, b) => a === void 0 && b === void 0 ? void 0 : (a ?? 0) + (b ?? 0);
920
+ const raw = left.raw || right.raw ? {
921
+ ...left.raw ?? {},
922
+ ...right.raw ?? {}
923
+ } : void 0;
924
+ const input = sum(left.input, right.input);
925
+ const output = sum(left.output, right.output);
926
+ const total = sum(left.total, right.total);
927
+ const reasoning = sum(left.reasoning, right.reasoning);
928
+ const cacheRead = sum(left.cacheRead, right.cacheRead);
929
+ const cacheWrite = sum(left.cacheWrite, right.cacheWrite);
930
+ const merged = {};
931
+ if (input !== void 0) merged.input = input;
932
+ if (output !== void 0) merged.output = output;
933
+ if (total !== void 0) merged.total = total;
934
+ if (reasoning !== void 0) merged.reasoning = reasoning;
935
+ if (cacheRead !== void 0) merged.cacheRead = cacheRead;
936
+ if (cacheWrite !== void 0) merged.cacheWrite = cacheWrite;
937
+ if (raw) merged.raw = raw;
938
+ return merged;
939
+ };
940
+ var mergeWarnings = (left, right) => {
941
+ if (!left?.length) return right;
942
+ if (!right?.length) return left;
943
+ const seen = /* @__PURE__ */ new Set();
944
+ const merged = [];
945
+ for (const warning of [...left, ...right]) {
946
+ const key = JSON.stringify(warning);
947
+ if (seen.has(key)) continue;
948
+ seen.add(key);
949
+ merged.push(warning);
950
+ }
951
+ return merged;
952
+ };
953
+ var mergeProviderMetadata = (left, right) => {
954
+ if (!left) return right;
955
+ if (!right) return left;
956
+ return {
957
+ ...left,
958
+ ...right
959
+ };
960
+ };
961
+ var emitReasonStructuredOutput = (args) => {
962
+ if (!args.emit || !shouldEmitStructured(args.structured)) return;
963
+ emitStructuredData({
964
+ dataType: args.structured?.dataType ?? "reason-output",
965
+ data: args.output,
966
+ kind: "final",
967
+ ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
968
+ ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
969
+ ...args.id ? { id: args.id } : {},
970
+ streamId: args.opId
971
+ });
972
+ };
973
+
906
974
  // src/reason/structured-stream.ts
907
975
  var resolveAppendFieldPaths = (cfg) => {
908
976
  const fields = cfg?.fields;
@@ -1463,64 +1531,353 @@ var extractStreamingStringValues = (args) => {
1463
1531
  });
1464
1532
  };
1465
1533
 
1466
- // src/reason/use-reason.ts
1467
- var mergeUsage = (left, right) => {
1468
- if (!left) return right;
1469
- if (!right) return left;
1470
- const sum = (a, b) => a === void 0 && b === void 0 ? void 0 : (a ?? 0) + (b ?? 0);
1471
- const raw = left.raw || right.raw ? {
1472
- ...left.raw ?? {},
1473
- ...right.raw ?? {}
1474
- } : void 0;
1475
- const input = sum(left.input, right.input);
1476
- const output = sum(left.output, right.output);
1477
- const total = sum(left.total, right.total);
1478
- const reasoning = sum(left.reasoning, right.reasoning);
1479
- const cacheRead = sum(left.cacheRead, right.cacheRead);
1480
- const cacheWrite = sum(left.cacheWrite, right.cacheWrite);
1481
- const merged = {};
1482
- if (input !== void 0) merged.input = input;
1483
- if (output !== void 0) merged.output = output;
1484
- if (total !== void 0) merged.total = total;
1485
- if (reasoning !== void 0) merged.reasoning = reasoning;
1486
- if (cacheRead !== void 0) merged.cacheRead = cacheRead;
1487
- if (cacheWrite !== void 0) merged.cacheWrite = cacheWrite;
1488
- if (raw) merged.raw = raw;
1489
- return merged;
1534
+ // src/reason/tools.ts
1535
+ var toErrorMessage = (error) => error instanceof Error ? error.message : String(error);
1536
+ var toToolDefinitions = (tools) => tools.map((tool) => ({
1537
+ name: tool.name,
1538
+ ...tool.title ? { title: tool.title } : {},
1539
+ ...tool.description ? { description: tool.description } : {},
1540
+ inputSchema: tool.inputSchema,
1541
+ ...tool.outputSchema !== void 0 ? { outputSchema: tool.outputSchema } : {},
1542
+ ...tool.annotations !== void 0 ? { annotations: tool.annotations } : {},
1543
+ ...tool.metadata !== void 0 ? { metadata: tool.metadata } : {}
1544
+ }));
1545
+ var resolveToolExecutionBoolean = (value, toolName, fallback) => {
1546
+ if (typeof value === "boolean") return value;
1547
+ if (value && typeof value === "object" && !Array.isArray(value)) {
1548
+ return value[toolName] ?? fallback;
1549
+ }
1550
+ return fallback;
1490
1551
  };
1491
- var mergeWarnings = (left, right) => {
1492
- if (!left?.length) return right;
1493
- if (!right?.length) return left;
1494
- const seen = /* @__PURE__ */ new Set();
1495
- const merged = [];
1496
- for (const warning of [...left, ...right]) {
1497
- const key = JSON.stringify(warning);
1498
- if (seen.has(key)) continue;
1499
- seen.add(key);
1500
- merged.push(warning);
1552
+ var normalizeToolResult = (toolCall, value) => {
1553
+ if (value && typeof value === "object" && !Array.isArray(value) && "toolCallId" in value && "name" in value && "content" in value) {
1554
+ return value;
1555
+ }
1556
+ let content;
1557
+ try {
1558
+ content = typeof value === "string" ? value : JSON.stringify(value);
1559
+ } catch {
1560
+ content = String(value);
1501
1561
  }
1502
- return merged;
1503
- };
1504
- var mergeProviderMetadata = (left, right) => {
1505
- if (!left) return right;
1506
- if (!right) return left;
1507
1562
  return {
1508
- ...left,
1509
- ...right
1563
+ toolCallId: toolCall.id,
1564
+ name: toolCall.name,
1565
+ content,
1566
+ ...value !== void 0 && value !== null && typeof value === "object" && !Array.isArray(value) ? { structuredContent: value } : {}
1510
1567
  };
1511
1568
  };
1512
- var emitReasonStructuredOutput = (args) => {
1513
- if (!args.emit || !shouldEmitStructured(args.structured)) return;
1514
- emitStructuredData({
1515
- dataType: args.structured?.dataType ?? "reason-output",
1516
- data: args.output,
1517
- kind: "final",
1518
- ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
1519
- ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
1520
- ...args.id ? { id: args.id } : {},
1521
- streamId: args.opId
1569
+ var closeOwnedTools = async (tools) => {
1570
+ const closeFns = /* @__PURE__ */ new Set();
1571
+ for (const tool of tools) {
1572
+ if (tool.closeAfterUse === false || !tool.close) continue;
1573
+ closeFns.add(tool.close);
1574
+ }
1575
+ await Promise.all(
1576
+ [...closeFns].map(async (close) => {
1577
+ await close();
1578
+ })
1579
+ );
1580
+ };
1581
+ var createInitialMessages = (args) => [
1582
+ ...typeof args.system === "string" && args.system.length > 0 ? [{ role: "system", content: args.system }] : [],
1583
+ { role: "user", content: String(args.input ?? "") }
1584
+ ];
1585
+ var runReasonToolLoop = async (args) => {
1586
+ const { useReasonArgs, id, opId, traceSpan } = args;
1587
+ const ctx = getHookContext();
1588
+ const tools = useReasonArgs.tools ?? [];
1589
+ const toolByName = /* @__PURE__ */ new Map();
1590
+ let validationCompleted = false;
1591
+ try {
1592
+ for (const tool of tools) {
1593
+ if (toolByName.has(tool.name)) {
1594
+ throw new Error(
1595
+ `useReason received duplicate tool name "${tool.name}".`
1596
+ );
1597
+ }
1598
+ toolByName.set(tool.name, tool);
1599
+ }
1600
+ if (useReasonArgs.interrupt) {
1601
+ throw new Error(
1602
+ "useReason tools cannot be combined with useReason interrupt mode yet. Use toolExecution.approval for tool approval."
1603
+ );
1604
+ }
1605
+ validationCompleted = true;
1606
+ } finally {
1607
+ if (!validationCompleted) {
1608
+ await closeOwnedTools(tools);
1609
+ }
1610
+ }
1611
+ const toolDefinitions = toToolDefinitions(tools);
1612
+ const maxSteps = Math.max(1, useReasonArgs.toolExecution?.maxSteps ?? 3);
1613
+ const messages = createInitialMessages({
1614
+ system: useReasonArgs.system,
1615
+ input: useReasonArgs.input
1522
1616
  });
1617
+ const steps = [];
1618
+ const allToolCalls = [];
1619
+ const allToolResults = [];
1620
+ let finalText = "";
1621
+ let finalRaw;
1622
+ let finalOutput;
1623
+ let aggregatedUsage;
1624
+ let finalFinishReason;
1625
+ let aggregatedProviderMetadata;
1626
+ let aggregatedWarnings;
1627
+ const emitToolEvent = (event, payload) => {
1628
+ ctx.node.emit(event, {
1629
+ ...payload,
1630
+ node: ctx.node.graph.node,
1631
+ ...id ? { id } : {},
1632
+ opId
1633
+ });
1634
+ };
1635
+ try {
1636
+ for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
1637
+ traceSpan?.addEvent?.("useReason.tool-step.start", {
1638
+ stepIndex,
1639
+ toolCount: toolDefinitions.length
1640
+ });
1641
+ const step = await reasonEngine(
1642
+ {
1643
+ ...useReasonArgs,
1644
+ tools: toolDefinitions,
1645
+ messages,
1646
+ emit: false,
1647
+ stream: false
1648
+ },
1649
+ { ...id ? { id } : {}, opId }
1650
+ );
1651
+ finalText = step.text;
1652
+ finalRaw = step.raw;
1653
+ aggregatedUsage = mergeUsage(aggregatedUsage, step.usage);
1654
+ finalFinishReason = step.finishReason;
1655
+ aggregatedProviderMetadata = mergeProviderMetadata(
1656
+ aggregatedProviderMetadata,
1657
+ step.providerMetadata
1658
+ );
1659
+ aggregatedWarnings = mergeWarnings(aggregatedWarnings, step.warnings);
1660
+ const toolCalls = step.toolCalls ?? [];
1661
+ const toolResults = [];
1662
+ steps.push({
1663
+ stepIndex,
1664
+ text: step.text,
1665
+ toolCalls,
1666
+ toolResults,
1667
+ ...step.usage ? { usage: step.usage } : {},
1668
+ ...step.finishReason ? { finishReason: step.finishReason } : {},
1669
+ ...step.providerMetadata ? { providerMetadata: step.providerMetadata } : {},
1670
+ ...step.warnings ? { warnings: step.warnings } : {}
1671
+ });
1672
+ if (toolCalls.length === 0) {
1673
+ if ((useReasonArgs.emit ?? true) && step.text.length > 0) {
1674
+ emitToolEvent("text-start", {});
1675
+ emitToolEvent("text-delta", { delta: step.text });
1676
+ emitToolEvent("text-end", {});
1677
+ }
1678
+ break;
1679
+ }
1680
+ allToolCalls.push(...toolCalls);
1681
+ messages.push({
1682
+ role: "assistant",
1683
+ content: step.text,
1684
+ toolCalls
1685
+ });
1686
+ for (const toolCall of toolCalls) {
1687
+ const tool = toolByName.get(toolCall.name);
1688
+ if (!tool) {
1689
+ throw new Error(
1690
+ `Model requested unknown tool "${toolCall.name}" from useReason.`
1691
+ );
1692
+ }
1693
+ const shouldEmitTool = resolveToolExecutionBoolean(
1694
+ useReasonArgs.toolExecution?.emit,
1695
+ tool.name,
1696
+ false
1697
+ );
1698
+ const shouldApproveTool = resolveToolExecutionBoolean(
1699
+ useReasonArgs.toolExecution?.approval,
1700
+ tool.name,
1701
+ false
1702
+ );
1703
+ if (shouldEmitTool) {
1704
+ emitToolEvent("tool-call-start", {
1705
+ tool: tool.name,
1706
+ toolCallId: toolCall.id,
1707
+ input: toolCall.input
1708
+ });
1709
+ }
1710
+ if (shouldApproveTool) {
1711
+ const approval = await awaitInterruptInternal({
1712
+ id: `tool:${toolCall.id}`,
1713
+ request: {
1714
+ kind: "choice",
1715
+ question: `Approve ${tool.name}?`,
1716
+ options: [
1717
+ { id: "approve", label: "Approve" },
1718
+ { id: "deny", label: "Deny" }
1719
+ ],
1720
+ meta: {
1721
+ tool: tool.name,
1722
+ toolCallId: toolCall.id,
1723
+ input: toolCall.input
1724
+ }
1725
+ }
1726
+ });
1727
+ const approved = Array.isArray(approval) ? approval.includes("approve") : approval === "approve";
1728
+ if (!approved) {
1729
+ const denied = {
1730
+ toolCallId: toolCall.id,
1731
+ name: tool.name,
1732
+ content: "Tool call denied by user.",
1733
+ isError: true
1734
+ };
1735
+ toolResults.push(denied);
1736
+ allToolResults.push(denied);
1737
+ if (shouldEmitTool) {
1738
+ emitToolEvent("tool-call-result", {
1739
+ tool: tool.name,
1740
+ toolCallId: toolCall.id,
1741
+ content: denied.content,
1742
+ isError: true
1743
+ });
1744
+ }
1745
+ continue;
1746
+ }
1747
+ }
1748
+ try {
1749
+ const rawResult = await tool.execute(toolCall.input, {
1750
+ toolCallId: toolCall.id,
1751
+ ...useReasonArgs.abortSignal ? { abortSignal: useReasonArgs.abortSignal } : {}
1752
+ });
1753
+ const result2 = normalizeToolResult(toolCall, rawResult);
1754
+ toolResults.push(result2);
1755
+ allToolResults.push(result2);
1756
+ if (shouldEmitTool) {
1757
+ emitToolEvent("tool-call-result", {
1758
+ tool: result2.name,
1759
+ toolCallId: result2.toolCallId,
1760
+ content: result2.content,
1761
+ ...result2.structuredContent !== void 0 ? { structuredContent: result2.structuredContent } : {},
1762
+ ...result2.isError !== void 0 ? { isError: result2.isError } : {}
1763
+ });
1764
+ }
1765
+ traceSpan?.addEvent?.("useReason.tool-call.complete", {
1766
+ stepIndex,
1767
+ tool: result2.name,
1768
+ toolCallId: result2.toolCallId,
1769
+ isError: Boolean(result2.isError)
1770
+ });
1771
+ } catch (error) {
1772
+ const result2 = {
1773
+ toolCallId: toolCall.id,
1774
+ name: tool.name,
1775
+ content: toErrorMessage(error),
1776
+ isError: true
1777
+ };
1778
+ toolResults.push(result2);
1779
+ allToolResults.push(result2);
1780
+ if (shouldEmitTool) {
1781
+ emitToolEvent("tool-call-error", {
1782
+ tool: tool.name,
1783
+ toolCallId: toolCall.id,
1784
+ message: result2.content
1785
+ });
1786
+ }
1787
+ traceSpan?.addEvent?.("useReason.tool-call.error", {
1788
+ stepIndex,
1789
+ tool: tool.name,
1790
+ toolCallId: toolCall.id,
1791
+ message: result2.content
1792
+ });
1793
+ }
1794
+ }
1795
+ const currentStep = steps[steps.length - 1];
1796
+ if (currentStep) {
1797
+ steps[steps.length - 1] = {
1798
+ ...currentStep,
1799
+ toolResults
1800
+ };
1801
+ }
1802
+ for (const result2 of toolResults) {
1803
+ messages.push({
1804
+ role: "tool",
1805
+ content: result2.content,
1806
+ toolCallId: result2.toolCallId,
1807
+ name: result2.name,
1808
+ ...result2.structuredContent !== void 0 ? { structuredContent: result2.structuredContent } : {},
1809
+ ...result2.isError !== void 0 ? { isError: result2.isError } : {},
1810
+ ...result2.raw !== void 0 ? { raw: result2.raw } : {}
1811
+ });
1812
+ }
1813
+ }
1814
+ if (steps.length >= maxSteps && steps.at(-1)?.toolCalls.length) {
1815
+ throw new Error(
1816
+ `useReason tool loop reached maxSteps (${maxSteps}) before producing a final response.`
1817
+ );
1818
+ }
1819
+ if (useReasonArgs.outputSchema) {
1820
+ finalOutput = parseReasonOutputWithSchema({
1821
+ text: finalText,
1822
+ schema: useReasonArgs.outputSchema,
1823
+ ...finalFinishReason ? { finishReason: finalFinishReason } : {},
1824
+ label: "useReason output"
1825
+ });
1826
+ }
1827
+ if (finalOutput !== void 0) {
1828
+ emitReasonStructuredOutput({
1829
+ ...id ? { id } : {},
1830
+ opId,
1831
+ output: finalOutput,
1832
+ structured: useReasonArgs.structured,
1833
+ emit: useReasonArgs.emit ?? true
1834
+ });
1835
+ }
1836
+ const result = {
1837
+ ...id ? { id } : {},
1838
+ opId,
1839
+ text: finalText,
1840
+ ...finalRaw !== void 0 ? { raw: finalRaw } : {},
1841
+ ...aggregatedUsage !== void 0 ? { usage: aggregatedUsage } : {},
1842
+ ...finalFinishReason !== void 0 ? { finishReason: finalFinishReason } : {},
1843
+ ...aggregatedProviderMetadata !== void 0 ? { providerMetadata: aggregatedProviderMetadata } : {},
1844
+ ...aggregatedWarnings !== void 0 ? { warnings: aggregatedWarnings } : {},
1845
+ ...finalOutput !== void 0 ? { output: finalOutput } : {},
1846
+ toolCalls: allToolCalls,
1847
+ toolResults: allToolResults,
1848
+ steps
1849
+ };
1850
+ traceSpan?.end?.({
1851
+ ...aggregatedUsage !== void 0 ? { usage: aggregatedUsage } : {},
1852
+ ...finalFinishReason !== void 0 ? { finishReason: finalFinishReason } : {},
1853
+ ...aggregatedProviderMetadata !== void 0 ? { providerMetadata: aggregatedProviderMetadata } : {},
1854
+ ...aggregatedWarnings !== void 0 ? { warnings: aggregatedWarnings } : {},
1855
+ attributes: {
1856
+ textLength: finalText.length,
1857
+ toolCallCount: allToolCalls.length,
1858
+ toolResultCount: allToolResults.length,
1859
+ toolStepCount: steps.length
1860
+ },
1861
+ telemetry: {
1862
+ ...useReasonArgs.telemetry ?? {},
1863
+ output: useReasonArgs.telemetry?.output ?? finalOutput ?? finalText
1864
+ }
1865
+ });
1866
+ return result;
1867
+ } catch (error) {
1868
+ traceSpan?.fail?.(error, {
1869
+ attributes: {
1870
+ toolStepCount: steps.length,
1871
+ toolCallCount: allToolCalls.length
1872
+ }
1873
+ });
1874
+ throw error;
1875
+ } finally {
1876
+ await closeOwnedTools(tools);
1877
+ }
1523
1878
  };
1879
+
1880
+ // src/reason/use-reason.ts
1524
1881
  var shouldStreamReasonStructured = (args) => (args.emit ?? true) && shouldStreamStructured(args.structured);
1525
1882
  var resolveEffectiveReasoningIncludeThoughts = (args) => args.reasoning?.includeThoughts ?? args.model.options?.reasoning?.includeThoughts ?? false;
1526
1883
  var resolveEffectiveResponseFormatType = (args) => args.responseFormat?.type ?? args.model.options?.responseFormat?.type;
@@ -1567,13 +1924,22 @@ async function useReason(args) {
1567
1924
  ...args.telemetry?.prompt?.type ? { promptType: args.telemetry.prompt.type } : {},
1568
1925
  hasOutputSchema: Boolean(args.outputSchema),
1569
1926
  hasInterrupt: Boolean(args.interrupt),
1570
- hasStructured: Boolean(args.structured)
1927
+ hasStructured: Boolean(args.structured),
1928
+ hasTools: Boolean(args.tools?.length)
1571
1929
  },
1572
1930
  telemetry: {
1573
1931
  ...args.telemetry ?? {},
1574
1932
  input: args.telemetry?.input ?? args.input
1575
1933
  }
1576
1934
  });
1935
+ if (args.tools?.length) {
1936
+ return runReasonToolLoop({
1937
+ useReasonArgs: args,
1938
+ ...id ? { id } : {},
1939
+ opId,
1940
+ ...traceSpan ? { traceSpan } : {}
1941
+ });
1942
+ }
1577
1943
  const suppressTextStream = Boolean(args.outputSchema || args.interrupt);
1578
1944
  const setFieldPaths = resolveSetFieldPaths(args.structured);
1579
1945
  const appendFieldPaths = resolveAppendFieldPaths(args.structured);