illuma-agents 1.0.7 → 1.0.8

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,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,153 @@ 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 ?? '',
122
+ } catch (handlerError) {
123
+ // eslint-disable-next-line no-console
124
+ console.error('Error in errorHandler:', {
125
+ toolName: call.name,
126
+ toolCallId: call.id,
127
+ toolArgs: call.args,
128
+ stepId: this.toolCallStepIds?.get(call.id!),
129
+ turn: this.toolUsageCount.get(call.name),
130
+ originalError: {
131
+ message: e.message,
132
+ stack: e.stack ?? undefined,
133
+ },
134
+ handlerError:
135
+ handlerError instanceof Error
136
+ ? {
137
+ message: handlerError.message,
138
+ stack: handlerError.stack ?? undefined,
139
+ }
140
+ : {
141
+ message: String(handlerError),
142
+ stack: undefined,
143
+ },
121
144
  });
122
145
  }
123
- }) ?? []
124
- );
146
+ }
147
+ return new ToolMessage({
148
+ status: 'error',
149
+ content: `Error: ${e.message}\n Please fix your mistakes.`,
150
+ name: call.name,
151
+ tool_call_id: call.id ?? '',
152
+ });
153
+ }
154
+ }
155
+
156
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
157
+ protected async run(input: any, config: RunnableConfig): Promise<T> {
158
+ let outputs: (BaseMessage | Command)[];
159
+
160
+ if (this.isSendInput(input)) {
161
+ outputs = [await this.runTool(input.lg_tool_call, config)];
162
+ } else {
163
+ let messages: BaseMessage[];
164
+ if (Array.isArray(input)) {
165
+ messages = input;
166
+ } else if (this.isMessagesState(input)) {
167
+ messages = input.messages;
168
+ } else {
169
+ throw new Error(
170
+ 'ToolNode only accepts BaseMessage[] or { messages: BaseMessage[] } as input.'
171
+ );
172
+ }
173
+
174
+ const toolMessageIds: Set<string> = new Set(
175
+ messages
176
+ .filter((msg) => msg._getType() === 'tool')
177
+ .map((msg) => (msg as ToolMessage).tool_call_id)
178
+ );
179
+
180
+ let aiMessage: AIMessage | undefined;
181
+ for (let i = messages.length - 1; i >= 0; i--) {
182
+ const message = messages[i];
183
+ if (isAIMessage(message)) {
184
+ aiMessage = message;
185
+ break;
186
+ }
187
+ }
188
+
189
+ if (aiMessage == null || !isAIMessage(aiMessage)) {
190
+ throw new Error('ToolNode only accepts AIMessages as input.');
191
+ }
192
+
193
+ if (this.loadRuntimeTools) {
194
+ const { tools, toolMap } = this.loadRuntimeTools(
195
+ aiMessage.tool_calls ?? []
196
+ );
197
+ this.tools = tools;
198
+ this.toolMap =
199
+ toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
200
+ }
201
+
202
+ outputs = await Promise.all(
203
+ aiMessage.tool_calls
204
+ ?.filter((call) => call.id == null || !toolMessageIds.has(call.id))
205
+ .map((call) => this.runTool(call, config)) ?? []
206
+ );
207
+ }
125
208
 
126
209
  if (!outputs.some(isCommand)) {
127
210
  return (Array.isArray(input) ? outputs : { messages: outputs }) as T;
128
211
  }
129
212
 
130
- const combinedOutputs = outputs.map((output) => {
213
+ const combinedOutputs: (
214
+ | { messages: BaseMessage[] }
215
+ | BaseMessage[]
216
+ | Command
217
+ )[] = [];
218
+ let parentCommand: Command | null = null;
219
+
220
+ for (const output of outputs) {
131
221
  if (isCommand(output)) {
132
- return output;
222
+ if (
223
+ output.graph === Command.PARENT &&
224
+ Array.isArray(output.goto) &&
225
+ output.goto.every((send): send is Send => isSend(send))
226
+ ) {
227
+ if (parentCommand) {
228
+ (parentCommand.goto as Send[]).push(...(output.goto as Send[]));
229
+ } else {
230
+ parentCommand = new Command({
231
+ graph: Command.PARENT,
232
+ goto: output.goto,
233
+ });
234
+ }
235
+ } else {
236
+ combinedOutputs.push(output);
237
+ }
238
+ } else {
239
+ combinedOutputs.push(
240
+ Array.isArray(input) ? [output] : { messages: [output] }
241
+ );
133
242
  }
134
- return Array.isArray(input) ? [output] : { messages: [output] };
135
- });
243
+ }
244
+
245
+ if (parentCommand) {
246
+ combinedOutputs.push(parentCommand);
247
+ }
248
+
136
249
  return combinedOutputs as T;
137
250
  }
251
+
252
+ private isSendInput(input: unknown): input is { lg_tool_call: ToolCall } {
253
+ return (
254
+ typeof input === 'object' && input != null && 'lg_tool_call' in input
255
+ );
256
+ }
257
+
258
+ private isMessagesState(
259
+ input: unknown
260
+ ): input is { messages: BaseMessage[] } {
261
+ return (
262
+ typeof input === 'object' &&
263
+ input != null &&
264
+ 'messages' in input &&
265
+ Array.isArray((input as { messages: unknown }).messages) &&
266
+ (input as { messages: unknown[] }).messages.every(isBaseMessage)
267
+ );
268
+ }
138
269
  }
139
270
 
140
271
  function areToolCallsInvoked(
@@ -1,80 +1,80 @@
1
- // src/types/tools.ts
2
- import type { StructuredToolInterface } from '@langchain/core/tools';
3
- import type { RunnableToolLike } from '@langchain/core/runnables';
4
- import type { ToolCall } from '@langchain/core/messages/tool';
5
- import type { ToolErrorData } from './stream';
6
- import { EnvVar } from '@/common';
7
-
8
- /** Replacement type for `import type { ToolCall } from '@langchain/core/messages/tool'` in order to have stringified args typed */
9
- export type CustomToolCall = {
10
- name: string;
11
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
- args: string | Record<string, any>;
13
- id?: string;
14
- type?: 'tool_call';
15
- output?: string;
16
- };
17
-
18
- export type GenericTool = StructuredToolInterface | RunnableToolLike;
19
-
20
- export type ToolMap = Map<string, GenericTool>;
21
- export type ToolRefs = {
22
- tools: GenericTool[];
23
- toolMap?: ToolMap;
24
- };
25
-
26
- export type ToolRefGenerator = (tool_calls: ToolCall[]) => ToolRefs;
27
-
28
- export type ToolNodeOptions = {
29
- name?: string;
30
- tags?: string[];
31
- handleToolErrors?: boolean;
32
- loadRuntimeTools?: ToolRefGenerator;
33
- toolCallStepIds?: Map<string, string>;
34
- errorHandler?: (
35
- data: ToolErrorData,
36
- metadata?: Record<string, unknown>
37
- ) => void;
38
- };
39
-
40
- export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;
41
-
42
- export type ToolEndEvent = {
43
- /** The Step Id of the Tool Call */
44
- id: string;
45
- /** The Completed Tool Call */
46
- tool_call: ToolCall;
47
- /** The content index of the tool call */
48
- index: number;
49
- };
50
-
51
- export type CodeEnvFile = {
52
- id: string;
53
- name: string;
54
- session_id: string;
55
- };
56
-
57
- export type CodeExecutionToolParams =
58
- | undefined
59
- | {
60
- session_id?: string;
61
- user_id?: string;
62
- apiKey?: string;
63
- files?: CodeEnvFile[];
64
- [EnvVar.CODE_API_KEY]?: string;
65
- };
66
-
67
- export type FileRef = {
68
- id: string;
69
- name: string;
70
- path?: string;
71
- };
72
-
73
- export type FileRefs = FileRef[];
74
-
75
- export type ExecuteResult = {
76
- session_id: string;
77
- stdout: string;
78
- stderr: string;
79
- files?: FileRefs;
80
- };
1
+ // src/types/tools.ts
2
+ import type { StructuredToolInterface } from '@langchain/core/tools';
3
+ import type { RunnableToolLike } from '@langchain/core/runnables';
4
+ import type { ToolCall } from '@langchain/core/messages/tool';
5
+ import type { ToolErrorData } from './stream';
6
+ import { EnvVar } from '@/common';
7
+
8
+ /** Replacement type for `import type { ToolCall } from '@langchain/core/messages/tool'` in order to have stringified args typed */
9
+ export type CustomToolCall = {
10
+ name: string;
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ args: string | Record<string, any>;
13
+ id?: string;
14
+ type?: 'tool_call';
15
+ output?: string;
16
+ };
17
+
18
+ export type GenericTool = StructuredToolInterface | RunnableToolLike;
19
+
20
+ export type ToolMap = Map<string, GenericTool>;
21
+ export type ToolRefs = {
22
+ tools: GenericTool[];
23
+ toolMap?: ToolMap;
24
+ };
25
+
26
+ export type ToolRefGenerator = (tool_calls: ToolCall[]) => ToolRefs;
27
+
28
+ export type ToolNodeOptions = {
29
+ name?: string;
30
+ tags?: string[];
31
+ handleToolErrors?: boolean;
32
+ loadRuntimeTools?: ToolRefGenerator;
33
+ toolCallStepIds?: Map<string, string>;
34
+ errorHandler?: (
35
+ data: ToolErrorData,
36
+ metadata?: Record<string, unknown>
37
+ ) => Promise<void>;
38
+ };
39
+
40
+ export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;
41
+
42
+ export type ToolEndEvent = {
43
+ /** The Step Id of the Tool Call */
44
+ id: string;
45
+ /** The Completed Tool Call */
46
+ tool_call: ToolCall;
47
+ /** The content index of the tool call */
48
+ index: number;
49
+ };
50
+
51
+ export type CodeEnvFile = {
52
+ id: string;
53
+ name: string;
54
+ session_id: string;
55
+ };
56
+
57
+ export type CodeExecutionToolParams =
58
+ | undefined
59
+ | {
60
+ session_id?: string;
61
+ user_id?: string;
62
+ apiKey?: string;
63
+ files?: CodeEnvFile[];
64
+ [EnvVar.CODE_API_KEY]?: string;
65
+ };
66
+
67
+ export type FileRef = {
68
+ id: string;
69
+ name: string;
70
+ path?: string;
71
+ };
72
+
73
+ export type FileRefs = FileRef[];
74
+
75
+ export type ExecuteResult = {
76
+ session_id: string;
77
+ stdout: string;
78
+ stderr: string;
79
+ files?: FileRefs;
80
+ };