@librechat/agents 3.0.22 → 3.0.24

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.
@@ -1,19 +1,26 @@
1
1
  'use strict';
2
2
 
3
- var langgraph = require('@langchain/langgraph');
4
3
  var messages = require('@langchain/core/messages');
4
+ var langgraph = require('@langchain/langgraph');
5
5
  require('../common/enum.cjs');
6
6
  require('nanoid');
7
7
  require('../messages/core.cjs');
8
8
  var run = require('../utils/run.cjs');
9
9
  require('js-tiktoken/lite');
10
10
 
11
+ /**
12
+ * Helper to check if a value is a Send object
13
+ */
14
+ function isSend(value) {
15
+ return value instanceof langgraph.Send;
16
+ }
11
17
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
18
  class ToolNode extends run.RunnableCallable {
13
19
  tools;
14
20
  toolMap;
15
21
  loadRuntimeTools;
16
22
  handleToolErrors = true;
23
+ trace = false;
17
24
  toolCallStepIds;
18
25
  errorHandler;
19
26
  toolUsageCount;
@@ -34,74 +41,147 @@ class ToolNode extends run.RunnableCallable {
34
41
  getToolUsageCounts() {
35
42
  return new Map(this.toolUsageCount); // Return a copy
36
43
  }
37
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
- async run(input, config) {
39
- const message = Array.isArray(input)
40
- ? input[input.length - 1]
41
- : input.messages[input.messages.length - 1];
42
- if (message._getType() !== 'ai') {
43
- throw new Error('ToolNode only accepts AIMessages as input.');
44
- }
45
- if (this.loadRuntimeTools) {
46
- const { tools, toolMap } = this.loadRuntimeTools(message.tool_calls ?? []);
47
- this.tools = tools;
48
- this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
44
+ /**
45
+ * Runs a single tool call with error handling
46
+ */
47
+ async runTool(call, config) {
48
+ const tool = this.toolMap.get(call.name);
49
+ try {
50
+ if (tool === undefined) {
51
+ throw new Error(`Tool "${call.name}" not found.`);
52
+ }
53
+ const turn = this.toolUsageCount.get(call.name) ?? 0;
54
+ this.toolUsageCount.set(call.name, turn + 1);
55
+ const args = call.args;
56
+ const stepId = this.toolCallStepIds?.get(call.id);
57
+ const output = await tool.invoke({ ...call, args, type: 'tool_call', stepId, turn }, config);
58
+ if ((messages.isBaseMessage(output) && output._getType() === 'tool') ||
59
+ langgraph.isCommand(output)) {
60
+ return output;
61
+ }
62
+ else {
63
+ return new messages.ToolMessage({
64
+ status: 'success',
65
+ name: tool.name,
66
+ content: typeof output === 'string' ? output : JSON.stringify(output),
67
+ tool_call_id: call.id,
68
+ });
69
+ }
49
70
  }
50
- const outputs = await Promise.all(message.tool_calls?.map(async (call) => {
51
- const tool = this.toolMap.get(call.name);
52
- try {
53
- if (tool === undefined) {
54
- throw new Error(`Tool "${call.name}" not found.`);
55
- }
56
- const turn = this.toolUsageCount.get(call.name) ?? 0;
57
- this.toolUsageCount.set(call.name, turn + 1);
58
- const args = call.args;
59
- const stepId = this.toolCallStepIds?.get(call.id);
60
- const output = await tool.invoke({ ...call, args, type: 'tool_call', stepId, turn }, config);
61
- if ((messages.isBaseMessage(output) && output._getType() === 'tool') ||
62
- langgraph.isCommand(output)) {
63
- return output;
71
+ catch (_e) {
72
+ const e = _e;
73
+ if (!this.handleToolErrors) {
74
+ throw e;
75
+ }
76
+ if (langgraph.isGraphInterrupt(e)) {
77
+ throw e;
78
+ }
79
+ if (this.errorHandler) {
80
+ try {
81
+ await this.errorHandler({
82
+ error: e,
83
+ id: call.id,
84
+ name: call.name,
85
+ input: call.args,
86
+ }, config.metadata);
64
87
  }
65
- else {
66
- return new messages.ToolMessage({
67
- name: tool.name,
68
- content: typeof output === 'string' ? output : JSON.stringify(output),
69
- tool_call_id: call.id,
70
- });
88
+ catch (handlerError) {
89
+ // eslint-disable-next-line no-console
90
+ console.error('Error in errorHandler for tool', call.name, ':', handlerError);
71
91
  }
72
92
  }
73
- catch (_e) {
74
- const e = _e;
75
- if (!this.handleToolErrors) {
76
- throw e;
77
- }
78
- if (langgraph.isGraphInterrupt(e)) {
79
- throw e;
93
+ return new messages.ToolMessage({
94
+ status: 'error',
95
+ content: `Error: ${e.message}\n Please fix your mistakes.`,
96
+ name: call.name,
97
+ tool_call_id: call.id ?? '',
98
+ });
99
+ }
100
+ }
101
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
102
+ async run(input, config) {
103
+ let outputs;
104
+ if (this.isSendInput(input)) {
105
+ outputs = [await this.runTool(input.lg_tool_call, config)];
106
+ }
107
+ else {
108
+ let messages$1;
109
+ if (Array.isArray(input)) {
110
+ messages$1 = input;
111
+ }
112
+ else if (this.isMessagesState(input)) {
113
+ messages$1 = input.messages;
114
+ }
115
+ else {
116
+ throw new Error('ToolNode only accepts BaseMessage[] or { messages: BaseMessage[] } as input.');
117
+ }
118
+ const toolMessageIds = new Set(messages$1
119
+ .filter((msg) => msg._getType() === 'tool')
120
+ .map((msg) => msg.tool_call_id));
121
+ let aiMessage;
122
+ for (let i = messages$1.length - 1; i >= 0; i--) {
123
+ const message = messages$1[i];
124
+ if (messages.isAIMessage(message)) {
125
+ aiMessage = message;
126
+ break;
80
127
  }
81
- this.errorHandler?.({
82
- error: e,
83
- id: call.id,
84
- name: call.name,
85
- input: call.args,
86
- }, config.metadata);
87
- return new messages.ToolMessage({
88
- content: `Error: ${e.message}\n Please fix your mistakes.`,
89
- name: call.name,
90
- tool_call_id: call.id ?? '',
91
- });
92
128
  }
93
- }) ?? []);
129
+ if (aiMessage == null || !messages.isAIMessage(aiMessage)) {
130
+ throw new Error('ToolNode only accepts AIMessages as input.');
131
+ }
132
+ if (this.loadRuntimeTools) {
133
+ const { tools, toolMap } = this.loadRuntimeTools(aiMessage.tool_calls ?? []);
134
+ this.tools = tools;
135
+ this.toolMap =
136
+ toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
137
+ }
138
+ outputs = await Promise.all(aiMessage.tool_calls
139
+ ?.filter((call) => call.id == null || !toolMessageIds.has(call.id))
140
+ .map((call) => this.runTool(call, config)) ?? []);
141
+ }
94
142
  if (!outputs.some(langgraph.isCommand)) {
95
143
  return (Array.isArray(input) ? outputs : { messages: outputs });
96
144
  }
97
- const combinedOutputs = outputs.map((output) => {
145
+ const combinedOutputs = [];
146
+ let parentCommand = null;
147
+ for (const output of outputs) {
98
148
  if (langgraph.isCommand(output)) {
99
- return output;
149
+ if (output.graph === langgraph.Command.PARENT &&
150
+ Array.isArray(output.goto) &&
151
+ output.goto.every((send) => isSend(send))) {
152
+ if (parentCommand) {
153
+ parentCommand.goto.push(...output.goto);
154
+ }
155
+ else {
156
+ parentCommand = new langgraph.Command({
157
+ graph: langgraph.Command.PARENT,
158
+ goto: output.goto,
159
+ });
160
+ }
161
+ }
162
+ else {
163
+ combinedOutputs.push(output);
164
+ }
100
165
  }
101
- return Array.isArray(input) ? [output] : { messages: [output] };
102
- });
166
+ else {
167
+ combinedOutputs.push(Array.isArray(input) ? [output] : { messages: [output] });
168
+ }
169
+ }
170
+ if (parentCommand) {
171
+ combinedOutputs.push(parentCommand);
172
+ }
103
173
  return combinedOutputs;
104
174
  }
175
+ isSendInput(input) {
176
+ return (typeof input === 'object' && input != null && 'lg_tool_call' in input);
177
+ }
178
+ isMessagesState(input) {
179
+ return (typeof input === 'object' &&
180
+ input != null &&
181
+ 'messages' in input &&
182
+ Array.isArray(input.messages) &&
183
+ input.messages.every(messages.isBaseMessage));
184
+ }
105
185
  }
106
186
  function areToolCallsInvoked(message, invokedToolIds) {
107
187
  if (!invokedToolIds || invokedToolIds.size === 0)
@@ -1 +1 @@
1
- {"version":3,"file":"ToolNode.cjs","sources":["../../../src/tools/ToolNode.ts"],"sourcesContent":["import {\n END,\n MessagesAnnotation,\n isCommand,\n isGraphInterrupt,\n} from '@langchain/langgraph';\nimport { ToolMessage, isBaseMessage } from '@langchain/core/messages';\nimport type {\n RunnableConfig,\n RunnableToolLike,\n} from '@langchain/core/runnables';\nimport type { BaseMessage, AIMessage } from '@langchain/core/messages';\nimport type { StructuredToolInterface } from '@langchain/core/tools';\nimport type * as t from '@/types';\nimport { RunnableCallable } from '@/utils';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class ToolNode<T = any> extends RunnableCallable<T, T> {\n tools: t.GenericTool[];\n private toolMap: Map<string, StructuredToolInterface | RunnableToolLike>;\n private loadRuntimeTools?: t.ToolRefGenerator;\n handleToolErrors = true;\n toolCallStepIds?: Map<string, string>;\n errorHandler?: t.ToolNodeConstructorParams['errorHandler'];\n private toolUsageCount: Map<string, number>;\n\n constructor({\n tools,\n toolMap,\n name,\n tags,\n errorHandler,\n toolCallStepIds,\n handleToolErrors,\n loadRuntimeTools,\n }: t.ToolNodeConstructorParams) {\n super({ name, tags, func: (input, config) => this.run(input, config) });\n this.tools = tools;\n this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));\n this.toolCallStepIds = toolCallStepIds;\n this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;\n this.loadRuntimeTools = loadRuntimeTools;\n this.errorHandler = errorHandler;\n this.toolUsageCount = new Map<string, number>();\n }\n\n /**\n * Returns a snapshot of the current tool usage counts.\n * @returns A ReadonlyMap where keys are tool names and values are their usage counts.\n */\n public getToolUsageCounts(): ReadonlyMap<string, number> {\n return new Map(this.toolUsageCount); // Return a copy\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n protected async run(input: any, config: RunnableConfig): Promise<T> {\n const message = Array.isArray(input)\n ? input[input.length - 1]\n : input.messages[input.messages.length - 1];\n\n if (message._getType() !== 'ai') {\n throw new Error('ToolNode only accepts AIMessages as input.');\n }\n\n if (this.loadRuntimeTools) {\n const { tools, toolMap } = this.loadRuntimeTools(\n (message as AIMessage).tool_calls ?? []\n );\n this.tools = tools;\n this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));\n }\n const outputs = await Promise.all(\n (message as AIMessage).tool_calls?.map(async (call) => {\n const tool = this.toolMap.get(call.name);\n try {\n if (tool === undefined) {\n throw new Error(`Tool \"${call.name}\" not found.`);\n }\n const turn = this.toolUsageCount.get(call.name) ?? 0;\n this.toolUsageCount.set(call.name, turn + 1);\n const args = call.args;\n const stepId = this.toolCallStepIds?.get(call.id!);\n const output = await tool.invoke(\n { ...call, args, type: 'tool_call', stepId, turn },\n config\n );\n if (\n (isBaseMessage(output) && output._getType() === 'tool') ||\n isCommand(output)\n ) {\n return output;\n } else {\n return new ToolMessage({\n name: tool.name,\n content:\n typeof output === 'string' ? output : JSON.stringify(output),\n tool_call_id: call.id!,\n });\n }\n } catch (_e: unknown) {\n const e = _e as Error;\n if (!this.handleToolErrors) {\n throw e;\n }\n if (isGraphInterrupt(e)) {\n throw e;\n }\n this.errorHandler?.(\n {\n error: e,\n id: call.id!,\n name: call.name,\n input: call.args,\n },\n config.metadata\n );\n return new ToolMessage({\n content: `Error: ${e.message}\\n Please fix your mistakes.`,\n name: call.name,\n tool_call_id: call.id ?? '',\n });\n }\n }) ?? []\n );\n\n if (!outputs.some(isCommand)) {\n return (Array.isArray(input) ? outputs : { messages: outputs }) as T;\n }\n\n const combinedOutputs = outputs.map((output) => {\n if (isCommand(output)) {\n return output;\n }\n return Array.isArray(input) ? [output] : { messages: [output] };\n });\n return combinedOutputs as T;\n }\n}\n\nfunction areToolCallsInvoked(\n message: AIMessage,\n invokedToolIds?: Set<string>\n): boolean {\n if (!invokedToolIds || invokedToolIds.size === 0) return false;\n return (\n message.tool_calls?.every(\n (toolCall) => toolCall.id != null && invokedToolIds.has(toolCall.id)\n ) ?? false\n );\n}\n\nexport function toolsCondition<T extends string>(\n state: BaseMessage[] | typeof MessagesAnnotation.State,\n toolNode: T,\n invokedToolIds?: Set<string>\n): T | typeof END {\n const message: AIMessage = Array.isArray(state)\n ? state[state.length - 1]\n : state.messages[state.messages.length - 1];\n\n if (\n 'tool_calls' in message &&\n (message.tool_calls?.length ?? 0) > 0 &&\n !areToolCallsInvoked(message, invokedToolIds)\n ) {\n return toolNode;\n } else {\n return END;\n }\n}\n"],"names":["RunnableCallable","isBaseMessage","isCommand","ToolMessage","isGraphInterrupt","END"],"mappings":"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"}
1
+ {"version":3,"file":"ToolNode.cjs","sources":["../../../src/tools/ToolNode.ts"],"sourcesContent":["import { ToolCall } from '@langchain/core/messages/tool';\nimport {\n ToolMessage,\n isAIMessage,\n isBaseMessage,\n} from '@langchain/core/messages';\nimport {\n END,\n Send,\n Command,\n isCommand,\n isGraphInterrupt,\n MessagesAnnotation,\n} from '@langchain/langgraph';\nimport type {\n RunnableConfig,\n RunnableToolLike,\n} from '@langchain/core/runnables';\nimport type { BaseMessage, AIMessage } from '@langchain/core/messages';\nimport type { StructuredToolInterface } from '@langchain/core/tools';\nimport type * as t from '@/types';\nimport { RunnableCallable } from '@/utils';\n\n/**\n * Helper to check if a value is a Send object\n */\nfunction isSend(value: unknown): value is Send {\n return value instanceof Send;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class ToolNode<T = any> extends RunnableCallable<T, T> {\n tools: t.GenericTool[];\n private toolMap: Map<string, StructuredToolInterface | RunnableToolLike>;\n private loadRuntimeTools?: t.ToolRefGenerator;\n handleToolErrors = true;\n trace = false;\n toolCallStepIds?: Map<string, string>;\n errorHandler?: t.ToolNodeConstructorParams['errorHandler'];\n private toolUsageCount: Map<string, number>;\n\n constructor({\n tools,\n toolMap,\n name,\n tags,\n errorHandler,\n toolCallStepIds,\n handleToolErrors,\n loadRuntimeTools,\n }: t.ToolNodeConstructorParams) {\n super({ name, tags, func: (input, config) => this.run(input, config) });\n this.tools = tools;\n this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));\n this.toolCallStepIds = toolCallStepIds;\n this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;\n this.loadRuntimeTools = loadRuntimeTools;\n this.errorHandler = errorHandler;\n this.toolUsageCount = new Map<string, number>();\n }\n\n /**\n * Returns a snapshot of the current tool usage counts.\n * @returns A ReadonlyMap where keys are tool names and values are their usage counts.\n */\n public getToolUsageCounts(): ReadonlyMap<string, number> {\n return new Map(this.toolUsageCount); // Return a copy\n }\n\n /**\n * Runs a single tool call with error handling\n */\n protected async runTool(\n call: ToolCall,\n config: RunnableConfig\n ): Promise<BaseMessage | Command> {\n const tool = this.toolMap.get(call.name);\n try {\n if (tool === undefined) {\n throw new Error(`Tool \"${call.name}\" not found.`);\n }\n const turn = this.toolUsageCount.get(call.name) ?? 0;\n this.toolUsageCount.set(call.name, turn + 1);\n const args = call.args;\n const stepId = this.toolCallStepIds?.get(call.id!);\n const output = await tool.invoke(\n { ...call, args, type: 'tool_call', stepId, turn },\n config\n );\n if (\n (isBaseMessage(output) && output._getType() === 'tool') ||\n isCommand(output)\n ) {\n return output;\n } else {\n return new ToolMessage({\n status: 'success',\n name: tool.name,\n content: typeof output === 'string' ? output : JSON.stringify(output),\n tool_call_id: call.id!,\n });\n }\n } catch (_e: unknown) {\n const e = _e as Error;\n if (!this.handleToolErrors) {\n throw e;\n }\n if (isGraphInterrupt(e)) {\n throw e;\n }\n if (this.errorHandler) {\n try {\n await this.errorHandler(\n {\n error: e,\n id: call.id!,\n name: call.name,\n input: call.args,\n },\n config.metadata\n );\n } catch (handlerError) {\n // eslint-disable-next-line no-console\n console.error(\n 'Error in errorHandler for tool',\n call.name,\n ':',\n handlerError\n );\n }\n }\n return new ToolMessage({\n status: 'error',\n content: `Error: ${e.message}\\n Please fix your mistakes.`,\n name: call.name,\n tool_call_id: call.id ?? '',\n });\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n protected async run(input: any, config: RunnableConfig): Promise<T> {\n let outputs: (BaseMessage | Command)[];\n\n if (this.isSendInput(input)) {\n outputs = [await this.runTool(input.lg_tool_call, config)];\n } else {\n let messages: BaseMessage[];\n if (Array.isArray(input)) {\n messages = input;\n } else if (this.isMessagesState(input)) {\n messages = input.messages;\n } else {\n throw new Error(\n 'ToolNode only accepts BaseMessage[] or { messages: BaseMessage[] } as input.'\n );\n }\n\n const toolMessageIds: Set<string> = new Set(\n messages\n .filter((msg) => msg._getType() === 'tool')\n .map((msg) => (msg as ToolMessage).tool_call_id)\n );\n\n let aiMessage: AIMessage | undefined;\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages[i];\n if (isAIMessage(message)) {\n aiMessage = message;\n break;\n }\n }\n\n if (aiMessage == null || !isAIMessage(aiMessage)) {\n throw new Error('ToolNode only accepts AIMessages as input.');\n }\n\n if (this.loadRuntimeTools) {\n const { tools, toolMap } = this.loadRuntimeTools(\n aiMessage.tool_calls ?? []\n );\n this.tools = tools;\n this.toolMap =\n toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));\n }\n\n outputs = await Promise.all(\n aiMessage.tool_calls\n ?.filter((call) => call.id == null || !toolMessageIds.has(call.id))\n .map((call) => this.runTool(call, config)) ?? []\n );\n }\n\n if (!outputs.some(isCommand)) {\n return (Array.isArray(input) ? outputs : { messages: outputs }) as T;\n }\n\n const combinedOutputs: (\n | { messages: BaseMessage[] }\n | BaseMessage[]\n | Command\n )[] = [];\n let parentCommand: Command | null = null;\n\n for (const output of outputs) {\n if (isCommand(output)) {\n if (\n output.graph === Command.PARENT &&\n Array.isArray(output.goto) &&\n output.goto.every((send): send is Send => isSend(send))\n ) {\n if (parentCommand) {\n (parentCommand.goto as Send[]).push(...(output.goto as Send[]));\n } else {\n parentCommand = new Command({\n graph: Command.PARENT,\n goto: output.goto,\n });\n }\n } else {\n combinedOutputs.push(output);\n }\n } else {\n combinedOutputs.push(\n Array.isArray(input) ? [output] : { messages: [output] }\n );\n }\n }\n\n if (parentCommand) {\n combinedOutputs.push(parentCommand);\n }\n\n return combinedOutputs as T;\n }\n\n private isSendInput(input: unknown): input is { lg_tool_call: ToolCall } {\n return (\n typeof input === 'object' && input != null && 'lg_tool_call' in input\n );\n }\n\n private isMessagesState(\n input: unknown\n ): input is { messages: BaseMessage[] } {\n return (\n typeof input === 'object' &&\n input != null &&\n 'messages' in input &&\n Array.isArray((input as { messages: unknown }).messages) &&\n (input as { messages: unknown[] }).messages.every(isBaseMessage)\n );\n }\n}\n\nfunction areToolCallsInvoked(\n message: AIMessage,\n invokedToolIds?: Set<string>\n): boolean {\n if (!invokedToolIds || invokedToolIds.size === 0) return false;\n return (\n message.tool_calls?.every(\n (toolCall) => toolCall.id != null && invokedToolIds.has(toolCall.id)\n ) ?? false\n );\n}\n\nexport function toolsCondition<T extends string>(\n state: BaseMessage[] | typeof MessagesAnnotation.State,\n toolNode: T,\n invokedToolIds?: Set<string>\n): T | typeof END {\n const message: AIMessage = Array.isArray(state)\n ? state[state.length - 1]\n : state.messages[state.messages.length - 1];\n\n if (\n 'tool_calls' in message &&\n (message.tool_calls?.length ?? 0) > 0 &&\n !areToolCallsInvoked(message, invokedToolIds)\n ) {\n return toolNode;\n } else {\n return END;\n }\n}\n"],"names":["Send","RunnableCallable","isBaseMessage","isCommand","ToolMessage","isGraphInterrupt","messages","isAIMessage","Command","END"],"mappings":";;;;;;;;;;AAuBA;;AAEG;AACH,SAAS,MAAM,CAAC,KAAc,EAAA;IAC5B,OAAO,KAAK,YAAYA,cAAI;AAC9B;AAEA;AACM,MAAO,QAAkB,SAAQC,oBAAsB,CAAA;AAC3D,IAAA,KAAK;AACG,IAAA,OAAO;AACP,IAAA,gBAAgB;IACxB,gBAAgB,GAAG,IAAI;IACvB,KAAK,GAAG,KAAK;AACb,IAAA,eAAe;AACf,IAAA,YAAY;AACJ,IAAA,cAAc;AAEtB,IAAA,WAAA,CAAY,EACV,KAAK,EACL,OAAO,EACP,IAAI,EACJ,IAAI,EACJ,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,gBAAgB,GACY,EAAA;QAC5B,KAAK,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC;AACvE,QAAA,IAAI,CAAC,KAAK,GAAG,KAAK;QAClB,IAAI,CAAC,OAAO,GAAG,OAAO,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AACzE,QAAA,IAAI,CAAC,eAAe,GAAG,eAAe;QACtC,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,IAAI,IAAI,CAAC,gBAAgB;AACjE,QAAA,IAAI,CAAC,gBAAgB,GAAG,gBAAgB;AACxC,QAAA,IAAI,CAAC,YAAY,GAAG,YAAY;AAChC,QAAA,IAAI,CAAC,cAAc,GAAG,IAAI,GAAG,EAAkB;;AAGjD;;;AAGG;IACI,kBAAkB,GAAA;QACvB,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;;AAGtC;;AAEG;AACO,IAAA,MAAM,OAAO,CACrB,IAAc,EACd,MAAsB,EAAA;AAEtB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;AACxC,QAAA,IAAI;AACF,YAAA,IAAI,IAAI,KAAK,SAAS,EAAE;gBACtB,MAAM,IAAI,KAAK,CAAC,CAAA,MAAA,EAAS,IAAI,CAAC,IAAI,CAAc,YAAA,CAAA,CAAC;;AAEnD,YAAA,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AACpD,YAAA,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,CAAC,CAAC;AAC5C,YAAA,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI;AACtB,YAAA,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,EAAE,GAAG,CAAC,IAAI,CAAC,EAAG,CAAC;YAClD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAC9B,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,EAClD,MAAM,CACP;AACD,YAAA,IACE,CAACC,sBAAa,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,EAAE,KAAK,MAAM;AACtD,gBAAAC,mBAAS,CAAC,MAAM,CAAC,EACjB;AACA,gBAAA,OAAO,MAAM;;iBACR;gBACL,OAAO,IAAIC,oBAAW,CAAC;AACrB,oBAAA,MAAM,EAAE,SAAS;oBACjB,IAAI,EAAE,IAAI,CAAC,IAAI;AACf,oBAAA,OAAO,EAAE,OAAO,MAAM,KAAK,QAAQ,GAAG,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;oBACrE,YAAY,EAAE,IAAI,CAAC,EAAG;AACvB,iBAAA,CAAC;;;QAEJ,OAAO,EAAW,EAAE;YACpB,MAAM,CAAC,GAAG,EAAW;AACrB,YAAA,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE;AAC1B,gBAAA,MAAM,CAAC;;AAET,YAAA,IAAIC,0BAAgB,CAAC,CAAC,CAAC,EAAE;AACvB,gBAAA,MAAM,CAAC;;AAET,YAAA,IAAI,IAAI,CAAC,YAAY,EAAE;AACrB,gBAAA,IAAI;oBACF,MAAM,IAAI,CAAC,YAAY,CACrB;AACE,wBAAA,KAAK,EAAE,CAAC;wBACR,EAAE,EAAE,IAAI,CAAC,EAAG;wBACZ,IAAI,EAAE,IAAI,CAAC,IAAI;wBACf,KAAK,EAAE,IAAI,CAAC,IAAI;AACjB,qBAAA,EACD,MAAM,CAAC,QAAQ,CAChB;;gBACD,OAAO,YAAY,EAAE;;AAErB,oBAAA,OAAO,CAAC,KAAK,CACX,gCAAgC,EAChC,IAAI,CAAC,IAAI,EACT,GAAG,EACH,YAAY,CACb;;;YAGL,OAAO,IAAID,oBAAW,CAAC;AACrB,gBAAA,MAAM,EAAE,OAAO;AACf,gBAAA,OAAO,EAAE,CAAA,OAAA,EAAU,CAAC,CAAC,OAAO,CAA8B,4BAAA,CAAA;gBAC1D,IAAI,EAAE,IAAI,CAAC,IAAI;AACf,gBAAA,YAAY,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE;AAC5B,aAAA,CAAC;;;;AAKI,IAAA,MAAM,GAAG,CAAC,KAAU,EAAE,MAAsB,EAAA;AACpD,QAAA,IAAI,OAAkC;AAEtC,QAAA,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE;AAC3B,YAAA,OAAO,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;;aACrD;AACL,YAAA,IAAIE,UAAuB;AAC3B,YAAA,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;gBACxBA,UAAQ,GAAG,KAAK;;AACX,iBAAA,IAAI,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE;AACtC,gBAAAA,UAAQ,GAAG,KAAK,CAAC,QAAQ;;iBACpB;AACL,gBAAA,MAAM,IAAI,KAAK,CACb,8EAA8E,CAC/E;;AAGH,YAAA,MAAM,cAAc,GAAgB,IAAI,GAAG,CACzCA;AACG,iBAAA,MAAM,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,QAAQ,EAAE,KAAK,MAAM;iBACzC,GAAG,CAAC,CAAC,GAAG,KAAM,GAAmB,CAAC,YAAY,CAAC,CACnD;AAED,YAAA,IAAI,SAAgC;AACpC,YAAA,KAAK,IAAI,CAAC,GAAGA,UAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE;AAC7C,gBAAA,MAAM,OAAO,GAAGA,UAAQ,CAAC,CAAC,CAAC;AAC3B,gBAAA,IAAIC,oBAAW,CAAC,OAAO,CAAC,EAAE;oBACxB,SAAS,GAAG,OAAO;oBACnB;;;YAIJ,IAAI,SAAS,IAAI,IAAI,IAAI,CAACA,oBAAW,CAAC,SAAS,CAAC,EAAE;AAChD,gBAAA,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC;;AAG/D,YAAA,IAAI,IAAI,CAAC,gBAAgB,EAAE;AACzB,gBAAA,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,gBAAgB,CAC9C,SAAS,CAAC,UAAU,IAAI,EAAE,CAC3B;AACD,gBAAA,IAAI,CAAC,KAAK,GAAG,KAAK;AAClB,gBAAA,IAAI,CAAC,OAAO;oBACV,OAAO,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;;YAG9D,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CACzB,SAAS,CAAC;kBACN,MAAM,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;AACjE,iBAAA,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CACnD;;QAGH,IAAI,CAAC,OAAO,CAAC,IAAI,CAACJ,mBAAS,CAAC,EAAE;YAC5B,QAAQ,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,OAAO,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE;;QAGhE,MAAM,eAAe,GAIf,EAAE;QACR,IAAI,aAAa,GAAmB,IAAI;AAExC,QAAA,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE;AAC5B,YAAA,IAAIA,mBAAS,CAAC,MAAM,CAAC,EAAE;AACrB,gBAAA,IACE,MAAM,CAAC,KAAK,KAAKK,iBAAO,CAAC,MAAM;AAC/B,oBAAA,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC;AAC1B,oBAAA,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,KAAmB,MAAM,CAAC,IAAI,CAAC,CAAC,EACvD;oBACA,IAAI,aAAa,EAAE;wBAChB,aAAa,CAAC,IAAe,CAAC,IAAI,CAAC,GAAI,MAAM,CAAC,IAAe,CAAC;;yBAC1D;wBACL,aAAa,GAAG,IAAIA,iBAAO,CAAC;4BAC1B,KAAK,EAAEA,iBAAO,CAAC,MAAM;4BACrB,IAAI,EAAE,MAAM,CAAC,IAAI;AAClB,yBAAA,CAAC;;;qBAEC;AACL,oBAAA,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC;;;iBAEzB;gBACL,eAAe,CAAC,IAAI,CAClB,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC,MAAM,CAAC,EAAE,CACzD;;;QAIL,IAAI,aAAa,EAAE;AACjB,YAAA,eAAe,CAAC,IAAI,CAAC,aAAa,CAAC;;AAGrC,QAAA,OAAO,eAAoB;;AAGrB,IAAA,WAAW,CAAC,KAAc,EAAA;AAChC,QAAA,QACE,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,IAAI,IAAI,IAAI,cAAc,IAAI,KAAK;;AAIjE,IAAA,eAAe,CACrB,KAAc,EAAA;AAEd,QAAA,QACE,OAAO,KAAK,KAAK,QAAQ;AACzB,YAAA,KAAK,IAAI,IAAI;AACb,YAAA,UAAU,IAAI,KAAK;AACnB,YAAA,KAAK,CAAC,OAAO,CAAE,KAA+B,CAAC,QAAQ,CAAC;YACvD,KAAiC,CAAC,QAAQ,CAAC,KAAK,CAACN,sBAAa,CAAC;;AAGrE;AAED,SAAS,mBAAmB,CAC1B,OAAkB,EAClB,cAA4B,EAAA;AAE5B,IAAA,IAAI,CAAC,cAAc,IAAI,cAAc,CAAC,IAAI,KAAK,CAAC;AAAE,QAAA,OAAO,KAAK;AAC9D,IAAA,QACE,OAAO,CAAC,UAAU,EAAE,KAAK,CACvB,CAAC,QAAQ,KAAK,QAAQ,CAAC,EAAE,IAAI,IAAI,IAAI,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CACrE,IAAI,KAAK;AAEd;SAEgB,cAAc,CAC5B,KAAsD,EACtD,QAAW,EACX,cAA4B,EAAA;AAE5B,IAAA,MAAM,OAAO,GAAc,KAAK,CAAC,OAAO,CAAC,KAAK;UAC1C,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;AACxB,UAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IAE7C,IACE,YAAY,IAAI,OAAO;QACvB,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,IAAI,CAAC,IAAI,CAAC;AACrC,QAAA,CAAC,mBAAmB,CAAC,OAAO,EAAE,cAAc,CAAC,EAC7C;AACA,QAAA,OAAO,QAAQ;;SACV;AACL,QAAA,OAAOO,aAAG;;AAEd;;;;;"}
@@ -1,17 +1,24 @@
1
- import { isCommand, isGraphInterrupt, END } from '@langchain/langgraph';
2
- import { isBaseMessage, ToolMessage } from '@langchain/core/messages';
1
+ import { isBaseMessage, ToolMessage, isAIMessage } from '@langchain/core/messages';
2
+ import { isCommand, isGraphInterrupt, Command, END, Send } from '@langchain/langgraph';
3
3
  import '../common/enum.mjs';
4
4
  import 'nanoid';
5
5
  import '../messages/core.mjs';
6
6
  import { RunnableCallable } from '../utils/run.mjs';
7
7
  import 'js-tiktoken/lite';
8
8
 
9
+ /**
10
+ * Helper to check if a value is a Send object
11
+ */
12
+ function isSend(value) {
13
+ return value instanceof Send;
14
+ }
9
15
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
16
  class ToolNode extends RunnableCallable {
11
17
  tools;
12
18
  toolMap;
13
19
  loadRuntimeTools;
14
20
  handleToolErrors = true;
21
+ trace = false;
15
22
  toolCallStepIds;
16
23
  errorHandler;
17
24
  toolUsageCount;
@@ -32,74 +39,147 @@ class ToolNode extends RunnableCallable {
32
39
  getToolUsageCounts() {
33
40
  return new Map(this.toolUsageCount); // Return a copy
34
41
  }
35
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
- async run(input, config) {
37
- const message = Array.isArray(input)
38
- ? input[input.length - 1]
39
- : input.messages[input.messages.length - 1];
40
- if (message._getType() !== 'ai') {
41
- throw new Error('ToolNode only accepts AIMessages as input.');
42
- }
43
- if (this.loadRuntimeTools) {
44
- const { tools, toolMap } = this.loadRuntimeTools(message.tool_calls ?? []);
45
- this.tools = tools;
46
- this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
42
+ /**
43
+ * Runs a single tool call with error handling
44
+ */
45
+ async runTool(call, config) {
46
+ const tool = this.toolMap.get(call.name);
47
+ try {
48
+ if (tool === undefined) {
49
+ throw new Error(`Tool "${call.name}" not found.`);
50
+ }
51
+ const turn = this.toolUsageCount.get(call.name) ?? 0;
52
+ this.toolUsageCount.set(call.name, turn + 1);
53
+ const args = call.args;
54
+ const stepId = this.toolCallStepIds?.get(call.id);
55
+ const output = await tool.invoke({ ...call, args, type: 'tool_call', stepId, turn }, config);
56
+ if ((isBaseMessage(output) && output._getType() === 'tool') ||
57
+ isCommand(output)) {
58
+ return output;
59
+ }
60
+ else {
61
+ return new ToolMessage({
62
+ status: 'success',
63
+ name: tool.name,
64
+ content: typeof output === 'string' ? output : JSON.stringify(output),
65
+ tool_call_id: call.id,
66
+ });
67
+ }
47
68
  }
48
- const outputs = await Promise.all(message.tool_calls?.map(async (call) => {
49
- const tool = this.toolMap.get(call.name);
50
- try {
51
- if (tool === undefined) {
52
- throw new Error(`Tool "${call.name}" not found.`);
53
- }
54
- const turn = this.toolUsageCount.get(call.name) ?? 0;
55
- this.toolUsageCount.set(call.name, turn + 1);
56
- const args = call.args;
57
- const stepId = this.toolCallStepIds?.get(call.id);
58
- const output = await tool.invoke({ ...call, args, type: 'tool_call', stepId, turn }, config);
59
- if ((isBaseMessage(output) && output._getType() === 'tool') ||
60
- isCommand(output)) {
61
- return output;
69
+ catch (_e) {
70
+ const e = _e;
71
+ if (!this.handleToolErrors) {
72
+ throw e;
73
+ }
74
+ if (isGraphInterrupt(e)) {
75
+ throw e;
76
+ }
77
+ if (this.errorHandler) {
78
+ try {
79
+ await this.errorHandler({
80
+ error: e,
81
+ id: call.id,
82
+ name: call.name,
83
+ input: call.args,
84
+ }, config.metadata);
62
85
  }
63
- else {
64
- return new ToolMessage({
65
- name: tool.name,
66
- content: typeof output === 'string' ? output : JSON.stringify(output),
67
- tool_call_id: call.id,
68
- });
86
+ catch (handlerError) {
87
+ // eslint-disable-next-line no-console
88
+ console.error('Error in errorHandler for tool', call.name, ':', handlerError);
69
89
  }
70
90
  }
71
- catch (_e) {
72
- const e = _e;
73
- if (!this.handleToolErrors) {
74
- throw e;
75
- }
76
- if (isGraphInterrupt(e)) {
77
- throw e;
91
+ return new ToolMessage({
92
+ status: 'error',
93
+ content: `Error: ${e.message}\n Please fix your mistakes.`,
94
+ name: call.name,
95
+ tool_call_id: call.id ?? '',
96
+ });
97
+ }
98
+ }
99
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
100
+ async run(input, config) {
101
+ let outputs;
102
+ if (this.isSendInput(input)) {
103
+ outputs = [await this.runTool(input.lg_tool_call, config)];
104
+ }
105
+ else {
106
+ let messages;
107
+ if (Array.isArray(input)) {
108
+ messages = input;
109
+ }
110
+ else if (this.isMessagesState(input)) {
111
+ messages = input.messages;
112
+ }
113
+ else {
114
+ throw new Error('ToolNode only accepts BaseMessage[] or { messages: BaseMessage[] } as input.');
115
+ }
116
+ const toolMessageIds = new Set(messages
117
+ .filter((msg) => msg._getType() === 'tool')
118
+ .map((msg) => msg.tool_call_id));
119
+ let aiMessage;
120
+ for (let i = messages.length - 1; i >= 0; i--) {
121
+ const message = messages[i];
122
+ if (isAIMessage(message)) {
123
+ aiMessage = message;
124
+ break;
78
125
  }
79
- this.errorHandler?.({
80
- error: e,
81
- id: call.id,
82
- name: call.name,
83
- input: call.args,
84
- }, config.metadata);
85
- return new ToolMessage({
86
- content: `Error: ${e.message}\n Please fix your mistakes.`,
87
- name: call.name,
88
- tool_call_id: call.id ?? '',
89
- });
90
126
  }
91
- }) ?? []);
127
+ if (aiMessage == null || !isAIMessage(aiMessage)) {
128
+ throw new Error('ToolNode only accepts AIMessages as input.');
129
+ }
130
+ if (this.loadRuntimeTools) {
131
+ const { tools, toolMap } = this.loadRuntimeTools(aiMessage.tool_calls ?? []);
132
+ this.tools = tools;
133
+ this.toolMap =
134
+ toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
135
+ }
136
+ outputs = await Promise.all(aiMessage.tool_calls
137
+ ?.filter((call) => call.id == null || !toolMessageIds.has(call.id))
138
+ .map((call) => this.runTool(call, config)) ?? []);
139
+ }
92
140
  if (!outputs.some(isCommand)) {
93
141
  return (Array.isArray(input) ? outputs : { messages: outputs });
94
142
  }
95
- const combinedOutputs = outputs.map((output) => {
143
+ const combinedOutputs = [];
144
+ let parentCommand = null;
145
+ for (const output of outputs) {
96
146
  if (isCommand(output)) {
97
- return output;
147
+ if (output.graph === Command.PARENT &&
148
+ Array.isArray(output.goto) &&
149
+ output.goto.every((send) => isSend(send))) {
150
+ if (parentCommand) {
151
+ parentCommand.goto.push(...output.goto);
152
+ }
153
+ else {
154
+ parentCommand = new Command({
155
+ graph: Command.PARENT,
156
+ goto: output.goto,
157
+ });
158
+ }
159
+ }
160
+ else {
161
+ combinedOutputs.push(output);
162
+ }
163
+ }
164
+ else {
165
+ combinedOutputs.push(Array.isArray(input) ? [output] : { messages: [output] });
98
166
  }
99
- return Array.isArray(input) ? [output] : { messages: [output] };
100
- });
167
+ }
168
+ if (parentCommand) {
169
+ combinedOutputs.push(parentCommand);
170
+ }
101
171
  return combinedOutputs;
102
172
  }
173
+ isSendInput(input) {
174
+ return (typeof input === 'object' && input != null && 'lg_tool_call' in input);
175
+ }
176
+ isMessagesState(input) {
177
+ return (typeof input === 'object' &&
178
+ input != null &&
179
+ 'messages' in input &&
180
+ Array.isArray(input.messages) &&
181
+ input.messages.every(isBaseMessage));
182
+ }
103
183
  }
104
184
  function areToolCallsInvoked(message, invokedToolIds) {
105
185
  if (!invokedToolIds || invokedToolIds.size === 0)
@@ -1 +1 @@
1
- {"version":3,"file":"ToolNode.mjs","sources":["../../../src/tools/ToolNode.ts"],"sourcesContent":["import {\n END,\n MessagesAnnotation,\n isCommand,\n isGraphInterrupt,\n} from '@langchain/langgraph';\nimport { ToolMessage, isBaseMessage } from '@langchain/core/messages';\nimport type {\n RunnableConfig,\n RunnableToolLike,\n} from '@langchain/core/runnables';\nimport type { BaseMessage, AIMessage } from '@langchain/core/messages';\nimport type { StructuredToolInterface } from '@langchain/core/tools';\nimport type * as t from '@/types';\nimport { RunnableCallable } from '@/utils';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class ToolNode<T = any> extends RunnableCallable<T, T> {\n tools: t.GenericTool[];\n private toolMap: Map<string, StructuredToolInterface | RunnableToolLike>;\n private loadRuntimeTools?: t.ToolRefGenerator;\n handleToolErrors = true;\n toolCallStepIds?: Map<string, string>;\n errorHandler?: t.ToolNodeConstructorParams['errorHandler'];\n private toolUsageCount: Map<string, number>;\n\n constructor({\n tools,\n toolMap,\n name,\n tags,\n errorHandler,\n toolCallStepIds,\n handleToolErrors,\n loadRuntimeTools,\n }: t.ToolNodeConstructorParams) {\n super({ name, tags, func: (input, config) => this.run(input, config) });\n this.tools = tools;\n this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));\n this.toolCallStepIds = toolCallStepIds;\n this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;\n this.loadRuntimeTools = loadRuntimeTools;\n this.errorHandler = errorHandler;\n this.toolUsageCount = new Map<string, number>();\n }\n\n /**\n * Returns a snapshot of the current tool usage counts.\n * @returns A ReadonlyMap where keys are tool names and values are their usage counts.\n */\n public getToolUsageCounts(): ReadonlyMap<string, number> {\n return new Map(this.toolUsageCount); // Return a copy\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n protected async run(input: any, config: RunnableConfig): Promise<T> {\n const message = Array.isArray(input)\n ? input[input.length - 1]\n : input.messages[input.messages.length - 1];\n\n if (message._getType() !== 'ai') {\n throw new Error('ToolNode only accepts AIMessages as input.');\n }\n\n if (this.loadRuntimeTools) {\n const { tools, toolMap } = this.loadRuntimeTools(\n (message as AIMessage).tool_calls ?? []\n );\n this.tools = tools;\n this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));\n }\n const outputs = await Promise.all(\n (message as AIMessage).tool_calls?.map(async (call) => {\n const tool = this.toolMap.get(call.name);\n try {\n if (tool === undefined) {\n throw new Error(`Tool \"${call.name}\" not found.`);\n }\n const turn = this.toolUsageCount.get(call.name) ?? 0;\n this.toolUsageCount.set(call.name, turn + 1);\n const args = call.args;\n const stepId = this.toolCallStepIds?.get(call.id!);\n const output = await tool.invoke(\n { ...call, args, type: 'tool_call', stepId, turn },\n config\n );\n if (\n (isBaseMessage(output) && output._getType() === 'tool') ||\n isCommand(output)\n ) {\n return output;\n } else {\n return new ToolMessage({\n name: tool.name,\n content:\n typeof output === 'string' ? output : JSON.stringify(output),\n tool_call_id: call.id!,\n });\n }\n } catch (_e: unknown) {\n const e = _e as Error;\n if (!this.handleToolErrors) {\n throw e;\n }\n if (isGraphInterrupt(e)) {\n throw e;\n }\n this.errorHandler?.(\n {\n error: e,\n id: call.id!,\n name: call.name,\n input: call.args,\n },\n config.metadata\n );\n return new ToolMessage({\n content: `Error: ${e.message}\\n Please fix your mistakes.`,\n name: call.name,\n tool_call_id: call.id ?? '',\n });\n }\n }) ?? []\n );\n\n if (!outputs.some(isCommand)) {\n return (Array.isArray(input) ? outputs : { messages: outputs }) as T;\n }\n\n const combinedOutputs = outputs.map((output) => {\n if (isCommand(output)) {\n return output;\n }\n return Array.isArray(input) ? [output] : { messages: [output] };\n });\n return combinedOutputs as T;\n }\n}\n\nfunction areToolCallsInvoked(\n message: AIMessage,\n invokedToolIds?: Set<string>\n): boolean {\n if (!invokedToolIds || invokedToolIds.size === 0) return false;\n return (\n message.tool_calls?.every(\n (toolCall) => toolCall.id != null && invokedToolIds.has(toolCall.id)\n ) ?? false\n );\n}\n\nexport function toolsCondition<T extends string>(\n state: BaseMessage[] | typeof MessagesAnnotation.State,\n toolNode: T,\n invokedToolIds?: Set<string>\n): T | typeof END {\n const message: AIMessage = Array.isArray(state)\n ? state[state.length - 1]\n : state.messages[state.messages.length - 1];\n\n if (\n 'tool_calls' in message &&\n (message.tool_calls?.length ?? 0) > 0 &&\n !areToolCallsInvoked(message, invokedToolIds)\n ) {\n return toolNode;\n } else {\n return END;\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ToolNode.mjs","sources":["../../../src/tools/ToolNode.ts"],"sourcesContent":["import { ToolCall } from '@langchain/core/messages/tool';\nimport {\n ToolMessage,\n isAIMessage,\n isBaseMessage,\n} from '@langchain/core/messages';\nimport {\n END,\n Send,\n Command,\n isCommand,\n isGraphInterrupt,\n MessagesAnnotation,\n} from '@langchain/langgraph';\nimport type {\n RunnableConfig,\n RunnableToolLike,\n} from '@langchain/core/runnables';\nimport type { BaseMessage, AIMessage } from '@langchain/core/messages';\nimport type { StructuredToolInterface } from '@langchain/core/tools';\nimport type * as t from '@/types';\nimport { RunnableCallable } from '@/utils';\n\n/**\n * Helper to check if a value is a Send object\n */\nfunction isSend(value: unknown): value is Send {\n return value instanceof Send;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class ToolNode<T = any> extends RunnableCallable<T, T> {\n tools: t.GenericTool[];\n private toolMap: Map<string, StructuredToolInterface | RunnableToolLike>;\n private loadRuntimeTools?: t.ToolRefGenerator;\n handleToolErrors = true;\n trace = false;\n toolCallStepIds?: Map<string, string>;\n errorHandler?: t.ToolNodeConstructorParams['errorHandler'];\n private toolUsageCount: Map<string, number>;\n\n constructor({\n tools,\n toolMap,\n name,\n tags,\n errorHandler,\n toolCallStepIds,\n handleToolErrors,\n loadRuntimeTools,\n }: t.ToolNodeConstructorParams) {\n super({ name, tags, func: (input, config) => this.run(input, config) });\n this.tools = tools;\n this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));\n this.toolCallStepIds = toolCallStepIds;\n this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;\n this.loadRuntimeTools = loadRuntimeTools;\n this.errorHandler = errorHandler;\n this.toolUsageCount = new Map<string, number>();\n }\n\n /**\n * Returns a snapshot of the current tool usage counts.\n * @returns A ReadonlyMap where keys are tool names and values are their usage counts.\n */\n public getToolUsageCounts(): ReadonlyMap<string, number> {\n return new Map(this.toolUsageCount); // Return a copy\n }\n\n /**\n * Runs a single tool call with error handling\n */\n protected async runTool(\n call: ToolCall,\n config: RunnableConfig\n ): Promise<BaseMessage | Command> {\n const tool = this.toolMap.get(call.name);\n try {\n if (tool === undefined) {\n throw new Error(`Tool \"${call.name}\" not found.`);\n }\n const turn = this.toolUsageCount.get(call.name) ?? 0;\n this.toolUsageCount.set(call.name, turn + 1);\n const args = call.args;\n const stepId = this.toolCallStepIds?.get(call.id!);\n const output = await tool.invoke(\n { ...call, args, type: 'tool_call', stepId, turn },\n config\n );\n if (\n (isBaseMessage(output) && output._getType() === 'tool') ||\n isCommand(output)\n ) {\n return output;\n } else {\n return new ToolMessage({\n status: 'success',\n name: tool.name,\n content: typeof output === 'string' ? output : JSON.stringify(output),\n tool_call_id: call.id!,\n });\n }\n } catch (_e: unknown) {\n const e = _e as Error;\n if (!this.handleToolErrors) {\n throw e;\n }\n if (isGraphInterrupt(e)) {\n throw e;\n }\n if (this.errorHandler) {\n try {\n await this.errorHandler(\n {\n error: e,\n id: call.id!,\n name: call.name,\n input: call.args,\n },\n config.metadata\n );\n } catch (handlerError) {\n // eslint-disable-next-line no-console\n console.error(\n 'Error in errorHandler for tool',\n call.name,\n ':',\n handlerError\n );\n }\n }\n return new ToolMessage({\n status: 'error',\n content: `Error: ${e.message}\\n Please fix your mistakes.`,\n name: call.name,\n tool_call_id: call.id ?? '',\n });\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n protected async run(input: any, config: RunnableConfig): Promise<T> {\n let outputs: (BaseMessage | Command)[];\n\n if (this.isSendInput(input)) {\n outputs = [await this.runTool(input.lg_tool_call, config)];\n } else {\n let messages: BaseMessage[];\n if (Array.isArray(input)) {\n messages = input;\n } else if (this.isMessagesState(input)) {\n messages = input.messages;\n } else {\n throw new Error(\n 'ToolNode only accepts BaseMessage[] or { messages: BaseMessage[] } as input.'\n );\n }\n\n const toolMessageIds: Set<string> = new Set(\n messages\n .filter((msg) => msg._getType() === 'tool')\n .map((msg) => (msg as ToolMessage).tool_call_id)\n );\n\n let aiMessage: AIMessage | undefined;\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages[i];\n if (isAIMessage(message)) {\n aiMessage = message;\n break;\n }\n }\n\n if (aiMessage == null || !isAIMessage(aiMessage)) {\n throw new Error('ToolNode only accepts AIMessages as input.');\n }\n\n if (this.loadRuntimeTools) {\n const { tools, toolMap } = this.loadRuntimeTools(\n aiMessage.tool_calls ?? []\n );\n this.tools = tools;\n this.toolMap =\n toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));\n }\n\n outputs = await Promise.all(\n aiMessage.tool_calls\n ?.filter((call) => call.id == null || !toolMessageIds.has(call.id))\n .map((call) => this.runTool(call, config)) ?? []\n );\n }\n\n if (!outputs.some(isCommand)) {\n return (Array.isArray(input) ? outputs : { messages: outputs }) as T;\n }\n\n const combinedOutputs: (\n | { messages: BaseMessage[] }\n | BaseMessage[]\n | Command\n )[] = [];\n let parentCommand: Command | null = null;\n\n for (const output of outputs) {\n if (isCommand(output)) {\n if (\n output.graph === Command.PARENT &&\n Array.isArray(output.goto) &&\n output.goto.every((send): send is Send => isSend(send))\n ) {\n if (parentCommand) {\n (parentCommand.goto as Send[]).push(...(output.goto as Send[]));\n } else {\n parentCommand = new Command({\n graph: Command.PARENT,\n goto: output.goto,\n });\n }\n } else {\n combinedOutputs.push(output);\n }\n } else {\n combinedOutputs.push(\n Array.isArray(input) ? [output] : { messages: [output] }\n );\n }\n }\n\n if (parentCommand) {\n combinedOutputs.push(parentCommand);\n }\n\n return combinedOutputs as T;\n }\n\n private isSendInput(input: unknown): input is { lg_tool_call: ToolCall } {\n return (\n typeof input === 'object' && input != null && 'lg_tool_call' in input\n );\n }\n\n private isMessagesState(\n input: unknown\n ): input is { messages: BaseMessage[] } {\n return (\n typeof input === 'object' &&\n input != null &&\n 'messages' in input &&\n Array.isArray((input as { messages: unknown }).messages) &&\n (input as { messages: unknown[] }).messages.every(isBaseMessage)\n );\n }\n}\n\nfunction areToolCallsInvoked(\n message: AIMessage,\n invokedToolIds?: Set<string>\n): boolean {\n if (!invokedToolIds || invokedToolIds.size === 0) return false;\n return (\n message.tool_calls?.every(\n (toolCall) => toolCall.id != null && invokedToolIds.has(toolCall.id)\n ) ?? false\n );\n}\n\nexport function toolsCondition<T extends string>(\n state: BaseMessage[] | typeof MessagesAnnotation.State,\n toolNode: T,\n invokedToolIds?: Set<string>\n): T | typeof END {\n const message: AIMessage = Array.isArray(state)\n ? state[state.length - 1]\n : state.messages[state.messages.length - 1];\n\n if (\n 'tool_calls' in message &&\n (message.tool_calls?.length ?? 0) > 0 &&\n !areToolCallsInvoked(message, invokedToolIds)\n ) {\n return toolNode;\n } else {\n return END;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- import { END, MessagesAnnotation } from '@langchain/langgraph';
1
+ import { ToolCall } from '@langchain/core/messages/tool';
2
+ import { END, Command, MessagesAnnotation } from '@langchain/langgraph';
2
3
  import type { RunnableConfig } from '@langchain/core/runnables';
3
4
  import type { BaseMessage } from '@langchain/core/messages';
4
5
  import type * as t from '@/types';
@@ -8,6 +9,7 @@ export declare class ToolNode<T = any> extends RunnableCallable<T, T> {
8
9
  private toolMap;
9
10
  private loadRuntimeTools?;
10
11
  handleToolErrors: boolean;
12
+ trace: boolean;
11
13
  toolCallStepIds?: Map<string, string>;
12
14
  errorHandler?: t.ToolNodeConstructorParams['errorHandler'];
13
15
  private toolUsageCount;
@@ -17,6 +19,12 @@ export declare class ToolNode<T = any> extends RunnableCallable<T, T> {
17
19
  * @returns A ReadonlyMap where keys are tool names and values are their usage counts.
18
20
  */
19
21
  getToolUsageCounts(): ReadonlyMap<string, number>;
22
+ /**
23
+ * Runs a single tool call with error handling
24
+ */
25
+ protected runTool(call: ToolCall, config: RunnableConfig): Promise<BaseMessage | Command>;
20
26
  protected run(input: any, config: RunnableConfig): Promise<T>;
27
+ private isSendInput;
28
+ private isMessagesState;
21
29
  }
22
30
  export declare function toolsCondition<T extends string>(state: BaseMessage[] | typeof MessagesAnnotation.State, toolNode: T, invokedToolIds?: Set<string>): T | typeof END;
@@ -24,7 +24,7 @@ export type ToolNodeOptions = {
24
24
  handleToolErrors?: boolean;
25
25
  loadRuntimeTools?: ToolRefGenerator;
26
26
  toolCallStepIds?: Map<string, string>;
27
- errorHandler?: (data: ToolErrorData, metadata?: Record<string, unknown>) => void;
27
+ errorHandler?: (data: ToolErrorData, metadata?: Record<string, unknown>) => Promise<void>;
28
28
  };
29
29
  export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;
30
30
  export type ToolEndEvent = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "3.0.22",
3
+ "version": "3.0.24",
4
4
  "main": "./dist/cjs/main.cjs",
5
5
  "module": "./dist/esm/main.mjs",
6
6
  "types": "./dist/types/index.d.ts",
@@ -100,8 +100,8 @@
100
100
  "@langchain/aws": "^0.1.15",
101
101
  "@langchain/core": "^0.3.79",
102
102
  "@langchain/deepseek": "^0.0.2",
103
- "@langchain/google-genai": "^0.2.13",
104
- "@langchain/google-vertexai": "^0.2.13",
103
+ "@langchain/google-genai": "^0.2.18",
104
+ "@langchain/google-vertexai": "^0.2.18",
105
105
  "@langchain/langgraph": "^0.4.9",
106
106
  "@langchain/mistralai": "^0.2.1",
107
107
  "@langchain/ollama": "^0.2.3",
@@ -1,10 +1,17 @@
1
+ import { ToolCall } from '@langchain/core/messages/tool';
2
+ import {
3
+ ToolMessage,
4
+ isAIMessage,
5
+ isBaseMessage,
6
+ } from '@langchain/core/messages';
1
7
  import {
2
8
  END,
3
- MessagesAnnotation,
9
+ Send,
10
+ Command,
4
11
  isCommand,
5
12
  isGraphInterrupt,
13
+ MessagesAnnotation,
6
14
  } from '@langchain/langgraph';
7
- import { ToolMessage, isBaseMessage } from '@langchain/core/messages';
8
15
  import type {
9
16
  RunnableConfig,
10
17
  RunnableToolLike,
@@ -14,12 +21,20 @@ import type { StructuredToolInterface } from '@langchain/core/tools';
14
21
  import type * as t from '@/types';
15
22
  import { RunnableCallable } from '@/utils';
16
23
 
24
+ /**
25
+ * Helper to check if a value is a Send object
26
+ */
27
+ function isSend(value: unknown): value is Send {
28
+ return value instanceof Send;
29
+ }
30
+
17
31
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
32
  export class ToolNode<T = any> extends RunnableCallable<T, T> {
19
33
  tools: t.GenericTool[];
20
34
  private toolMap: Map<string, StructuredToolInterface | RunnableToolLike>;
21
35
  private loadRuntimeTools?: t.ToolRefGenerator;
22
36
  handleToolErrors = true;
37
+ trace = false;
23
38
  toolCallStepIds?: Map<string, string>;
24
39
  errorHandler?: t.ToolNodeConstructorParams['errorHandler'];
25
40
  private toolUsageCount: Map<string, number>;
@@ -52,60 +67,50 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
52
67
  return new Map(this.toolUsageCount); // Return a copy
53
68
  }
54
69
 
55
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
- protected async run(input: any, config: RunnableConfig): Promise<T> {
57
- const message = Array.isArray(input)
58
- ? input[input.length - 1]
59
- : input.messages[input.messages.length - 1];
60
-
61
- if (message._getType() !== 'ai') {
62
- throw new Error('ToolNode only accepts AIMessages as input.');
63
- }
64
-
65
- if (this.loadRuntimeTools) {
66
- const { tools, toolMap } = this.loadRuntimeTools(
67
- (message as AIMessage).tool_calls ?? []
70
+ /**
71
+ * Runs a single tool call with error handling
72
+ */
73
+ protected async runTool(
74
+ call: ToolCall,
75
+ config: RunnableConfig
76
+ ): Promise<BaseMessage | Command> {
77
+ const tool = this.toolMap.get(call.name);
78
+ try {
79
+ if (tool === undefined) {
80
+ throw new Error(`Tool "${call.name}" not found.`);
81
+ }
82
+ const turn = this.toolUsageCount.get(call.name) ?? 0;
83
+ this.toolUsageCount.set(call.name, turn + 1);
84
+ const args = call.args;
85
+ const stepId = this.toolCallStepIds?.get(call.id!);
86
+ const output = await tool.invoke(
87
+ { ...call, args, type: 'tool_call', stepId, turn },
88
+ config
68
89
  );
69
- this.tools = tools;
70
- this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
71
- }
72
- const outputs = await Promise.all(
73
- (message as AIMessage).tool_calls?.map(async (call) => {
74
- const tool = this.toolMap.get(call.name);
90
+ if (
91
+ (isBaseMessage(output) && output._getType() === 'tool') ||
92
+ isCommand(output)
93
+ ) {
94
+ return output;
95
+ } else {
96
+ return new ToolMessage({
97
+ status: 'success',
98
+ name: tool.name,
99
+ content: typeof output === 'string' ? output : JSON.stringify(output),
100
+ tool_call_id: call.id!,
101
+ });
102
+ }
103
+ } catch (_e: unknown) {
104
+ const e = _e as Error;
105
+ if (!this.handleToolErrors) {
106
+ throw e;
107
+ }
108
+ if (isGraphInterrupt(e)) {
109
+ throw e;
110
+ }
111
+ if (this.errorHandler) {
75
112
  try {
76
- if (tool === undefined) {
77
- throw new Error(`Tool "${call.name}" not found.`);
78
- }
79
- const turn = this.toolUsageCount.get(call.name) ?? 0;
80
- this.toolUsageCount.set(call.name, turn + 1);
81
- const args = call.args;
82
- const stepId = this.toolCallStepIds?.get(call.id!);
83
- const output = await tool.invoke(
84
- { ...call, args, type: 'tool_call', stepId, turn },
85
- config
86
- );
87
- if (
88
- (isBaseMessage(output) && output._getType() === 'tool') ||
89
- isCommand(output)
90
- ) {
91
- return output;
92
- } else {
93
- return new ToolMessage({
94
- name: tool.name,
95
- content:
96
- typeof output === 'string' ? output : JSON.stringify(output),
97
- tool_call_id: call.id!,
98
- });
99
- }
100
- } catch (_e: unknown) {
101
- const e = _e as Error;
102
- if (!this.handleToolErrors) {
103
- throw e;
104
- }
105
- if (isGraphInterrupt(e)) {
106
- throw e;
107
- }
108
- this.errorHandler?.(
113
+ await this.errorHandler(
109
114
  {
110
115
  error: e,
111
116
  id: call.id!,
@@ -114,27 +119,138 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
114
119
  },
115
120
  config.metadata
116
121
  );
117
- return new ToolMessage({
118
- content: `Error: ${e.message}\n Please fix your mistakes.`,
119
- name: call.name,
120
- tool_call_id: call.id ?? '',
121
- });
122
+ } catch (handlerError) {
123
+ // eslint-disable-next-line no-console
124
+ console.error(
125
+ 'Error in errorHandler for tool',
126
+ call.name,
127
+ ':',
128
+ handlerError
129
+ );
122
130
  }
123
- }) ?? []
124
- );
131
+ }
132
+ return new ToolMessage({
133
+ status: 'error',
134
+ content: `Error: ${e.message}\n Please fix your mistakes.`,
135
+ name: call.name,
136
+ tool_call_id: call.id ?? '',
137
+ });
138
+ }
139
+ }
140
+
141
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
142
+ protected async run(input: any, config: RunnableConfig): Promise<T> {
143
+ let outputs: (BaseMessage | Command)[];
144
+
145
+ if (this.isSendInput(input)) {
146
+ outputs = [await this.runTool(input.lg_tool_call, config)];
147
+ } else {
148
+ let messages: BaseMessage[];
149
+ if (Array.isArray(input)) {
150
+ messages = input;
151
+ } else if (this.isMessagesState(input)) {
152
+ messages = input.messages;
153
+ } else {
154
+ throw new Error(
155
+ 'ToolNode only accepts BaseMessage[] or { messages: BaseMessage[] } as input.'
156
+ );
157
+ }
158
+
159
+ const toolMessageIds: Set<string> = new Set(
160
+ messages
161
+ .filter((msg) => msg._getType() === 'tool')
162
+ .map((msg) => (msg as ToolMessage).tool_call_id)
163
+ );
164
+
165
+ let aiMessage: AIMessage | undefined;
166
+ for (let i = messages.length - 1; i >= 0; i--) {
167
+ const message = messages[i];
168
+ if (isAIMessage(message)) {
169
+ aiMessage = message;
170
+ break;
171
+ }
172
+ }
173
+
174
+ if (aiMessage == null || !isAIMessage(aiMessage)) {
175
+ throw new Error('ToolNode only accepts AIMessages as input.');
176
+ }
177
+
178
+ if (this.loadRuntimeTools) {
179
+ const { tools, toolMap } = this.loadRuntimeTools(
180
+ aiMessage.tool_calls ?? []
181
+ );
182
+ this.tools = tools;
183
+ this.toolMap =
184
+ toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
185
+ }
186
+
187
+ outputs = await Promise.all(
188
+ aiMessage.tool_calls
189
+ ?.filter((call) => call.id == null || !toolMessageIds.has(call.id))
190
+ .map((call) => this.runTool(call, config)) ?? []
191
+ );
192
+ }
125
193
 
126
194
  if (!outputs.some(isCommand)) {
127
195
  return (Array.isArray(input) ? outputs : { messages: outputs }) as T;
128
196
  }
129
197
 
130
- const combinedOutputs = outputs.map((output) => {
198
+ const combinedOutputs: (
199
+ | { messages: BaseMessage[] }
200
+ | BaseMessage[]
201
+ | Command
202
+ )[] = [];
203
+ let parentCommand: Command | null = null;
204
+
205
+ for (const output of outputs) {
131
206
  if (isCommand(output)) {
132
- return output;
207
+ if (
208
+ output.graph === Command.PARENT &&
209
+ Array.isArray(output.goto) &&
210
+ output.goto.every((send): send is Send => isSend(send))
211
+ ) {
212
+ if (parentCommand) {
213
+ (parentCommand.goto as Send[]).push(...(output.goto as Send[]));
214
+ } else {
215
+ parentCommand = new Command({
216
+ graph: Command.PARENT,
217
+ goto: output.goto,
218
+ });
219
+ }
220
+ } else {
221
+ combinedOutputs.push(output);
222
+ }
223
+ } else {
224
+ combinedOutputs.push(
225
+ Array.isArray(input) ? [output] : { messages: [output] }
226
+ );
133
227
  }
134
- return Array.isArray(input) ? [output] : { messages: [output] };
135
- });
228
+ }
229
+
230
+ if (parentCommand) {
231
+ combinedOutputs.push(parentCommand);
232
+ }
233
+
136
234
  return combinedOutputs as T;
137
235
  }
236
+
237
+ private isSendInput(input: unknown): input is { lg_tool_call: ToolCall } {
238
+ return (
239
+ typeof input === 'object' && input != null && 'lg_tool_call' in input
240
+ );
241
+ }
242
+
243
+ private isMessagesState(
244
+ input: unknown
245
+ ): input is { messages: BaseMessage[] } {
246
+ return (
247
+ typeof input === 'object' &&
248
+ input != null &&
249
+ 'messages' in input &&
250
+ Array.isArray((input as { messages: unknown }).messages) &&
251
+ (input as { messages: unknown[] }).messages.every(isBaseMessage)
252
+ );
253
+ }
138
254
  }
139
255
 
140
256
  function areToolCallsInvoked(
@@ -34,7 +34,7 @@ export type ToolNodeOptions = {
34
34
  errorHandler?: (
35
35
  data: ToolErrorData,
36
36
  metadata?: Record<string, unknown>
37
- ) => void;
37
+ ) => Promise<void>;
38
38
  };
39
39
 
40
40
  export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;