@minded-ai/mindedjs 3.1.43 → 3.1.45

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.
Files changed (50) hide show
  1. package/dist/cli/cliUtils.d.ts +4 -2
  2. package/dist/cli/cliUtils.d.ts.map +1 -1
  3. package/dist/cli/cliUtils.js +12 -5
  4. package/dist/cli/cliUtils.js.map +1 -1
  5. package/dist/nodes/addAppToolNode.d.ts.map +1 -1
  6. package/dist/nodes/addAppToolNode.js +61 -28
  7. package/dist/nodes/addAppToolNode.js.map +1 -1
  8. package/dist/nodes/addBrowserTaskNode.d.ts.map +1 -1
  9. package/dist/nodes/addBrowserTaskNode.js +37 -7
  10. package/dist/nodes/addBrowserTaskNode.js.map +1 -1
  11. package/dist/nodes/addToolNode.d.ts.map +1 -1
  12. package/dist/nodes/addToolNode.js +24 -55
  13. package/dist/nodes/addToolNode.js.map +1 -1
  14. package/dist/nodes/addToolRunNode.d.ts.map +1 -1
  15. package/dist/nodes/addToolRunNode.js +93 -82
  16. package/dist/nodes/addToolRunNode.js.map +1 -1
  17. package/dist/nodes/compilePrompt.d.ts +10 -0
  18. package/dist/nodes/compilePrompt.d.ts.map +1 -1
  19. package/dist/nodes/compilePrompt.js +67 -21
  20. package/dist/nodes/compilePrompt.js.map +1 -1
  21. package/dist/nodes/utils/getSchemaForToolInference.d.ts +13 -0
  22. package/dist/nodes/utils/getSchemaForToolInference.d.ts.map +1 -0
  23. package/dist/nodes/utils/getSchemaForToolInference.js +34 -0
  24. package/dist/nodes/utils/getSchemaForToolInference.js.map +1 -0
  25. package/dist/nodes/utils/inferToolCallWithRetry.d.ts +18 -0
  26. package/dist/nodes/utils/inferToolCallWithRetry.d.ts.map +1 -0
  27. package/dist/nodes/utils/inferToolCallWithRetry.js +58 -0
  28. package/dist/nodes/utils/inferToolCallWithRetry.js.map +1 -0
  29. package/dist/types/Flows.types.d.ts +2 -0
  30. package/dist/types/Flows.types.d.ts.map +1 -1
  31. package/dist/types/Flows.types.js +1 -0
  32. package/dist/types/Flows.types.js.map +1 -1
  33. package/dist/utils/flowSchema.d.ts.map +1 -1
  34. package/dist/utils/flowSchema.js +20 -4
  35. package/dist/utils/flowSchema.js.map +1 -1
  36. package/dist/utils/schemaUtils.d.ts.map +1 -1
  37. package/dist/utils/schemaUtils.js +2 -0
  38. package/dist/utils/schemaUtils.js.map +1 -1
  39. package/package.json +2 -2
  40. package/src/cli/cliUtils.ts +12 -5
  41. package/src/nodes/addAppToolNode.ts +68 -31
  42. package/src/nodes/addBrowserTaskNode.ts +45 -9
  43. package/src/nodes/addToolNode.ts +30 -61
  44. package/src/nodes/addToolRunNode.ts +114 -97
  45. package/src/nodes/compilePrompt.ts +69 -23
  46. package/src/nodes/utils/getSchemaForToolInference.ts +48 -0
  47. package/src/nodes/utils/inferToolCallWithRetry.ts +89 -0
  48. package/src/types/Flows.types.ts +2 -0
  49. package/src/utils/flowSchema.ts +21 -4
  50. package/src/utils/schemaUtils.ts +2 -0
@@ -10,10 +10,11 @@ import { Agent } from '../agent';
10
10
  import { createHistoryStep } from '../utils/history';
11
11
  import { HistoryStep } from '../types/Agent.types';
12
12
  import { combinePlaybooks } from '../playbooks/playbooks';
13
- import { compileParameter, compilePrompt } from './compilePrompt';
13
+ import { compilePrompt, compileNodeParameters } from './compilePrompt';
14
14
  import { AnalyticsEventName } from '../types/Analytics.types';
15
15
  import { trackAnalyticsEvent } from '../internalTools/analytics';
16
- import { z } from 'zod';
16
+ import { inferToolCallWithRetry } from './utils/inferToolCallWithRetry';
17
+ import { getSchemaForToolInference } from './utils/getSchemaForToolInference';
17
18
 
18
19
  export const addToolNode = async ({
19
20
  graph,
@@ -50,51 +51,18 @@ export const addToolNode = async ({
50
51
  await agent.interruptSessionManager.checkQueueAndInterrupt(state.sessionId);
51
52
  logger.debug({ msg: `[Node] Executing tool node`, node: toolNode.displayName });
52
53
 
53
- // Compile parameters with variable injection support
54
- const compiledParameters: Record<string, any> = {};
55
- const compileContext = { state, memory: state.memory, env: process.env };
56
-
57
- for (const [key, value] of Object.entries(node.parameters || {})) {
58
- if (value !== '') {
59
- // If the value is a string, compile it to allow variable injection
60
- if (typeof value === 'string') {
61
- compiledParameters[key] = compileParameter(value, compileContext, key, matchedTool.input);
62
- } else {
63
- compiledParameters[key] = value;
64
- }
65
- }
66
- }
54
+ const compiledParameters = compileNodeParameters(
55
+ node.parameters,
56
+ { state, memory: state.memory, env: process.env },
57
+ matchedTool.input,
58
+ );
67
59
 
68
- // Create a filtered schema for the LLM (exclude overridden parameters)
69
- let schemaForLLM = matchedTool.input;
70
60
  const overriddenKeys = Object.keys(compiledParameters);
71
- let skipLLMCall = false;
72
-
73
- if (overriddenKeys.length > 0) {
74
- try {
75
- // Only filter if it's a ZodObject
76
- if (matchedTool.input instanceof z.ZodObject) {
77
- // Create omit map: { key1: true, key2: true, ... }
78
- const omitMap = overriddenKeys.reduce((acc, key) => {
79
- acc[key] = true;
80
- return acc;
81
- }, {} as Record<string, true>);
82
-
83
- schemaForLLM = matchedTool.input.omit(omitMap);
84
-
85
- // Check if all parameters are overridden
86
- const remainingKeys = Object.keys(schemaForLLM.shape);
87
- skipLLMCall = remainingKeys.length === 0;
88
- }
89
- } catch (err) {
90
- logger.warn({
91
- msg: '[Tool] Failed to filter schema, using original',
92
- tool: matchedTool.name,
93
- err,
94
- });
95
- // Fall back to original schema on error
96
- }
97
- }
61
+ const { schemaForLLM, skipLLMCall } = getSchemaForToolInference({
62
+ inputSchema: matchedTool.input,
63
+ overriddenKeys,
64
+ toolName: matchedTool.name,
65
+ });
98
66
 
99
67
  let AIToolCallMessage: AIMessage;
100
68
 
@@ -115,12 +83,12 @@ export const addToolNode = async ({
115
83
  const tool = langchainTool(() => {}, {
116
84
  name: matchedTool.name,
117
85
  description: matchedTool.description,
118
- schema: schemaForLLM, // Use filtered schema
86
+ schema: schemaForLLM as any, // Use filtered schema
119
87
  });
120
88
 
121
89
  const combinedPlaybooks = combinePlaybooks(agent.playbooks) || '';
122
- const compiledNodePrompt = node.prompt ? compilePrompt(node.prompt, compileContext) : null;
123
- const hasOverriddenParameters = overriddenKeys.length > 0;
90
+ const compiledNodePrompt = node.prompt ? compilePrompt(node.prompt, { state, memory: state.memory, env: process.env }) : null;
91
+ const hasOverriddenParameters = Object.keys(compiledParameters).length > 0;
124
92
 
125
93
  // Only build system message if there's actual content (playbooks, prompt, or overridden parameters)
126
94
  if (combinedPlaybooks || compiledNodePrompt || hasOverriddenParameters) {
@@ -172,20 +140,21 @@ export const addToolNode = async ({
172
140
  state.messages.unshift(systemMessage);
173
141
  }
174
142
  }
175
-
176
- const startTime = Date.now();
177
- AIToolCallMessage = await llm.bindTools([tool], { tool_choice: tool.name }).invoke(state.messages);
178
- const endTime = Date.now();
179
- logger.debug({ msg: '[Tool] Model execution time', tool: matchedTool.name, executionTimeMs: endTime - startTime });
180
- await agent.interruptSessionManager.checkQueueAndInterrupt(state.sessionId);
181
-
182
- // Merge AI-generated parameters with user-set parameters
183
- if (AIToolCallMessage.tool_calls && AIToolCallMessage.tool_calls.length > 0) {
184
- AIToolCallMessage.tool_calls[0].args = {
185
- ...AIToolCallMessage.tool_calls[0].args,
143
+ AIToolCallMessage = await inferToolCallWithRetry({
144
+ llm,
145
+ tool,
146
+ inputSchema: matchedTool.input,
147
+ messages: state.messages,
148
+ sessionId: state.sessionId,
149
+ mergeArgs: (args) => ({
150
+ ...args,
186
151
  ...compiledParameters, // User-set parameters have priority over AI-generated parameters
187
- };
188
- }
152
+ }),
153
+ onAfterAttempt: async () => {
154
+ await agent.interruptSessionManager.checkQueueAndInterrupt(state.sessionId);
155
+ },
156
+ toolNameForLogs: matchedTool.name,
157
+ });
189
158
  }
190
159
 
191
160
  state.goto = null;
@@ -35,55 +35,50 @@ export const addToolRunNode = async ({ graph, tools, toolNode, attachedToNodeNam
35
35
  const { env, browserTaskMode } = getConfig();
36
36
 
37
37
  const executeWrapper = async (input: z.infer<typeof matchedTool.input>) => {
38
- try {
39
- const startTime = Date.now();
40
-
41
- // Check if this is an RPA tool
42
- if (matchedTool.type === 'rpa') {
43
- // Wrap with browser session and screenshot capture
44
- const { result, breakpointHit, breakpointInfo } = await withBrowserSession(
45
- {
46
- sessionId: state.sessionId,
47
- browserTaskMode: matchedTool.browserTaskMode ?? browserTaskMode,
48
- toolCallId: toolCallObj.tool_calls[0].id,
49
- proxyConfig: matchedTool.proxyConfig,
50
- toolName: matchedTool.name,
51
- persistSession: matchedTool.persistSession,
52
- checkBreakpoints: env === Environment.DEVELOPMENT,
53
- },
54
- async (page) => {
55
- // Pass page directly to RPA tool execution
56
- const toolResult = await matchedTool.execute({
57
- input,
58
- state,
59
- agent,
60
- page,
61
- });
62
-
63
- return toolResult;
64
- },
65
- );
66
-
67
- const endTime = Date.now();
68
- logger.debug({ msg: '[Tool] RPA tool execution time', tool: matchedTool.name, executionTimeMs: endTime - startTime });
69
-
70
- // Check if breakpoint was hit - return special result
71
- if (breakpointHit && breakpointInfo) {
72
- return { breakpointHit: true, breakpointInfo } as unknown as ToolMessage;
73
- }
74
-
75
- return result as ToolMessage;
76
- } else {
77
- // Non-RPA tool - execute normally
78
- const response = await matchedTool.execute({ input, state, agent });
79
- const endTime = Date.now();
80
- logger.debug({ msg: '[Tool] Tool execution time', tool: matchedTool.name, executionTimeMs: endTime - startTime });
81
- return response as ToolMessage;
38
+ const startTime = Date.now();
39
+
40
+ // Check if this is an RPA tool
41
+ if (matchedTool.type === 'rpa') {
42
+ // Wrap with browser session and screenshot capture
43
+ const { result, breakpointHit, breakpointInfo } = await withBrowserSession(
44
+ {
45
+ sessionId: state.sessionId,
46
+ browserTaskMode: matchedTool.browserTaskMode ?? browserTaskMode,
47
+ toolCallId: toolCallObj.tool_calls[0].id,
48
+ proxyConfig: matchedTool.proxyConfig,
49
+ toolName: matchedTool.name,
50
+ persistSession: matchedTool.persistSession,
51
+ checkBreakpoints: env === Environment.DEVELOPMENT,
52
+ },
53
+ async (page) => {
54
+ // Pass page directly to RPA tool execution
55
+ const toolResult = await matchedTool.execute({
56
+ input,
57
+ state,
58
+ agent,
59
+ page,
60
+ });
61
+
62
+ return toolResult;
63
+ },
64
+ );
65
+
66
+ const endTime = Date.now();
67
+ logger.debug({ msg: '[Tool] RPA tool execution time', tool: matchedTool.name, executionTimeMs: endTime - startTime });
68
+
69
+ // Check if breakpoint was hit - return special result
70
+ if (breakpointHit && breakpointInfo) {
71
+ return { breakpointHit: true, breakpointInfo } as unknown as ToolMessage;
82
72
  }
83
- } catch (err) {
84
- logger.error({ message: '[Tool] Error executing tool', err, node: toolNode.displayName });
85
- throw err;
73
+
74
+ return result as ToolMessage;
86
75
  }
76
+
77
+ // Non-RPA tool - execute normally
78
+ const response = await matchedTool.execute({ input, state, agent });
79
+ const endTime = Date.now();
80
+ logger.debug({ msg: '[Tool] Tool execution time', tool: matchedTool.name, executionTimeMs: endTime - startTime });
81
+ return response as ToolMessage;
87
82
  };
88
83
 
89
84
  const toolCallObj = state.messages[state.messages.length - 1] as any;
@@ -95,72 +90,94 @@ export const addToolRunNode = async ({ graph, tools, toolNode, attachedToNodeNam
95
90
  const toolCall = toolCallObj.tool_calls[0];
96
91
  const parsedArgs = matchedTool.input.parse(toolCall.args);
97
92
 
98
- // Execute the tool with validated args
99
- const result = await executeWrapper(parsedArgs);
100
-
101
- // Check if breakpoint was hit - handle gracefully by ending the flow
102
- const resultObj = result as unknown as { breakpointHit?: boolean; breakpointInfo?: BreakpointHitResult };
103
- if (resultObj?.breakpointHit && resultObj?.breakpointInfo) {
104
- logger.info({
105
- msg: '[Tool] RPA breakpoint hit - ending flow gracefully',
106
- toolName: matchedTool.name,
107
- stepNumber: resultObj.breakpointInfo.stepNumber,
108
- });
93
+ try {
94
+ // Execute the tool with validated args
95
+ const result = await executeWrapper(parsedArgs);
109
96
 
110
- // Create a ToolMessage indicating breakpoint was hit
111
- const breakpointMessage = new ToolMessage({
112
- content: JSON.stringify({
113
- breakpointHit: true,
114
- message: `RPA breakpoint reached at step ${resultObj.breakpointInfo.stepNumber + 1}`,
115
- toolName: resultObj.breakpointInfo.toolName,
97
+ // Check if breakpoint was hit - handle gracefully by ending the flow
98
+ const resultObj = result as unknown as { breakpointHit?: boolean; breakpointInfo?: BreakpointHitResult };
99
+ if (resultObj?.breakpointHit && resultObj?.breakpointInfo) {
100
+ logger.info({
101
+ msg: '[Tool] RPA breakpoint hit - ending flow gracefully',
102
+ toolName: matchedTool.name,
116
103
  stepNumber: resultObj.breakpointInfo.stepNumber,
117
- }),
104
+ });
105
+
106
+ // Create a ToolMessage indicating breakpoint was hit
107
+ const breakpointMessage = new ToolMessage({
108
+ content: JSON.stringify({
109
+ breakpointHit: true,
110
+ message: `RPA breakpoint reached at step ${resultObj.breakpointInfo.stepNumber + 1}`,
111
+ toolName: resultObj.breakpointInfo.toolName,
112
+ stepNumber: resultObj.breakpointInfo.stepNumber,
113
+ }),
114
+ tool_call_id: toolCall.id,
115
+ name: matchedTool.name,
116
+ });
117
+
118
+ state.messages.push(breakpointMessage);
119
+ state.history.push(
120
+ createHistoryStep<HistoryStep>(state.history, {
121
+ type: NodeType.TOOL,
122
+ nodeId: toolNode.name,
123
+ nodeDisplayName: toolNode.displayName,
124
+ raw: breakpointMessage,
125
+ messageIds: [toolCall.id],
126
+ }),
127
+ );
128
+
129
+ // Set goto to __end__ to stop the flow gracefully
130
+ state.goto = '__end__';
131
+ return state;
132
+ }
133
+
134
+ // Create a ToolMessage with the result
135
+ const toolCallMessage = new ToolMessage({
136
+ content: typeof result === 'string' ? result : JSON.stringify(result),
118
137
  tool_call_id: toolCall.id,
119
138
  name: matchedTool.name,
120
139
  });
121
140
 
122
- state.messages.push(breakpointMessage);
141
+ // Add the tool message to the state
142
+ state.messages.push(toolCallMessage);
143
+
144
+ // Add history step
123
145
  state.history.push(
124
146
  createHistoryStep<HistoryStep>(state.history, {
125
147
  type: NodeType.TOOL,
126
148
  nodeId: toolNode.name,
127
149
  nodeDisplayName: toolNode.displayName,
128
- raw: breakpointMessage,
129
- messageIds: [toolCallObj.tool_calls[0].id],
150
+ raw: toolCallMessage,
151
+ messageIds: [toolCall.id],
130
152
  }),
131
153
  );
132
154
 
133
- // Set goto to __end__ to stop the flow gracefully
134
- state.goto = '__end__';
155
+ // Clear goto if it was set
156
+ state.goto = null;
157
+
158
+ // Return the entire modified state
135
159
  return state;
136
- }
160
+ } catch (err) {
161
+ logger.error({ message: '[Tool] Error executing tool', err, node: toolNode.displayName });
162
+
163
+ const errorMessage = err instanceof Error ? err.message : JSON.stringify(err);
164
+ const toolErrorMessage = new ToolMessage({
165
+ content: JSON.stringify({ error: errorMessage }),
166
+ tool_call_id: toolCall.id,
167
+ name: matchedTool.name,
168
+ });
137
169
 
138
- // Create a ToolMessage with the result
139
- const toolCallMessage = new ToolMessage({
140
- content: typeof result === 'string' ? result : JSON.stringify(result),
141
- tool_call_id: toolCall.id,
142
- name: matchedTool.name,
143
- });
144
-
145
- // Add the tool message to the state
146
- state.messages.push(toolCallMessage);
147
-
148
- // Add history step
149
- state.history.push(
150
- createHistoryStep<HistoryStep>(state.history, {
151
- type: NodeType.TOOL,
152
- nodeId: toolNode.name,
153
- nodeDisplayName: toolNode.displayName,
154
- raw: toolCallMessage,
155
- messageIds: [toolCallObj.tool_calls[0].id],
156
- }),
157
- );
158
-
159
- // Clear goto if it was set
160
- state.goto = null;
161
-
162
- // Return the entire modified state
163
- return state;
170
+ state.messages.push(toolErrorMessage);
171
+ state.history.push(
172
+ createHistoryStep<HistoryStep>(state.history, {
173
+ type: NodeType.TOOL,
174
+ nodeId: toolNode.name,
175
+ nodeDisplayName: toolNode.displayName,
176
+ raw: toolErrorMessage,
177
+ messageIds: [toolCall.id],
178
+ }),
179
+ );
180
+ }
164
181
  };
165
182
 
166
183
  graph.addNode(buildToolRunNodeName(attachedToNodeName), callback);
@@ -6,6 +6,13 @@ import { HistoryStep, TriggerHistoryStep } from '../types/Agent.types';
6
6
  import { State } from '../types/LangGraph.types';
7
7
  import { NodeType } from '../types/Flows.types';
8
8
 
9
+ // Matches dotted path identifiers — allows letters, digits, `_`, `$`, `.`, `[`, `]`, spaces, and hyphens
10
+ // (spaces/hyphens support node display names like "Greeting Prompt" and trigger keys like "some-key").
11
+ // Implicitly rejects JSON braces (which contain `"` and `:`) since those chars are not in the set.
12
+ const PLACEHOLDER_PATH_CHARS = String.raw`[A-Za-z0-9_.$\[\] -]`;
13
+ const PLACEHOLDER_REGEX = new RegExp(String.raw`\{\s*([A-Za-z_$]${PLACEHOLDER_PATH_CHARS}*)\s*\}`, 'g');
14
+ const SINGLE_PLACEHOLDER_REGEX = new RegExp(String.raw`^\{\s*([A-Za-z_$]${PLACEHOLDER_PATH_CHARS}*)\s*\}$`);
15
+
9
16
  /**
10
17
  * Extract node outputs from state.history and state.messages
11
18
  * Returns a map of nodeId/nodeDisplayName -> output object
@@ -129,29 +136,72 @@ export type ExtendedContextData = ContextData & {
129
136
  */
130
137
  export function compileParameter(parameter: string, context: ContextData = {}, inputKey?: string, inputSchema?: ZodSchema): any {
131
138
  try {
132
- context = extendContextData(context);
139
+ return compileParameterWithContext(parameter, extendContextData(context), inputKey, inputSchema);
140
+ } catch (err) {
141
+ logger.error({ message: 'Error compiling parameter', err, parameter });
142
+ return parameter;
143
+ }
144
+ }
133
145
 
134
- // First, render with EJS
135
- let compiledParameter = ejs.render(parameter, context).trim();
146
+ /**
147
+ * Recursively compile placeholders inside nested objects/arrays.
148
+ * Top-level strings get full schema-based coercion; nested string leaves are compiled without schema coercion.
149
+ */
150
+ export function compileValueDeep(value: unknown, context: ContextData = {}, inputKey?: string, inputSchema?: ZodSchema): any {
151
+ if (typeof value === 'string') return compileParameter(value, context, inputKey, inputSchema);
152
+ if (!value || typeof value !== 'object') return value;
153
+ const extCtx = extendContextData(context);
154
+ return compileValueDeepInternal(value, extCtx);
155
+ }
136
156
 
137
- // Then, replace placeholders in {} format
138
- // If the result contains only one set of curly braces, get the native value at that key path, e.g., {state.memory.arrayOfStrings} -> state.memory.arrayOfStrings
139
- // Otherwise, compile it into a string.
140
- const keyPathMatch = compiledParameter.match(/^\{([^}]+)}$/);
141
- if (keyPathMatch) {
142
- const keyPath = keyPathMatch[1];
143
- compiledParameter = getContextValueFromPath(keyPath, context, compiledParameter);
144
- } else {
145
- compiledParameter = replacePlaceholders(compiledParameter, context);
157
+ function compileValueDeepInternal(value: unknown, context: ExtendedContextData): any {
158
+ if (typeof value === 'string') {
159
+ try {
160
+ return compileParameterWithContext(value, context);
161
+ } catch (err) {
162
+ logger.error({ message: 'Error compiling nested parameter', err, value });
163
+ return value;
146
164
  }
165
+ }
166
+ if (!value || typeof value !== 'object') return value;
167
+ if (Array.isArray(value)) return value.map((v) => compileValueDeepInternal(v, context));
168
+ return Object.fromEntries(Object.entries(value as Record<string, unknown>).map(([k, v]) => [k, compileValueDeepInternal(v, context)]));
169
+ }
147
170
 
148
- compiledParameter = coerceValueWithZodType(compiledParameter, inputKey, inputSchema);
171
+ /** Shared implementation expects already-extended context. */
172
+ function compileParameterWithContext(parameter: string, context: ExtendedContextData, inputKey?: string, inputSchema?: ZodSchema): any {
173
+ let compiledParameter = ejs.render(parameter, context).trim();
149
174
 
150
- return compiledParameter;
151
- } catch (err) {
152
- logger.error({ message: 'Error compiling parameter', err, parameter });
153
- return parameter; // Return uncompiled if there's an error
175
+ // Single placeholder → return native value; otherwise replace all placeholders as strings.
176
+ // Regex matches path identifiers (allows spaces/hyphens for node display names), rejects JSON braces.
177
+ const keyPathMatch = compiledParameter.match(SINGLE_PLACEHOLDER_REGEX);
178
+ if (keyPathMatch) {
179
+ compiledParameter = getContextValueFromPath(keyPathMatch[1].trim(), context, compiledParameter);
180
+ } else {
181
+ compiledParameter = replacePlaceholders(compiledParameter, context);
154
182
  }
183
+
184
+ return coerceValueWithZodType(compiledParameter, inputKey, inputSchema);
185
+ }
186
+
187
+ /**
188
+ * Compile node parameters with placeholder replacement and type coercion.
189
+ * Use getSchemaForToolInference() separately to filter the schema for the LLM.
190
+ */
191
+ export function compileNodeParameters(
192
+ parameters: Record<string, any> | undefined,
193
+ context: ContextData,
194
+ schema?: ZodSchema,
195
+ ): Record<string, any> {
196
+ const compiledParameters: Record<string, any> = {};
197
+
198
+ for (const [key, value] of Object.entries(parameters || {})) {
199
+ if (value !== '') {
200
+ compiledParameters[key] = compileValueDeep(value, context, key, schema);
201
+ }
202
+ }
203
+
204
+ return compiledParameters;
155
205
  }
156
206
 
157
207
  /**
@@ -161,12 +211,8 @@ export function compilePrompt(prompt: string, context: ContextData): string {
161
211
  try {
162
212
  context = extendContextData(context);
163
213
 
164
- // First, render with EJS
165
214
  let compiledPrompt = ejs.render(prompt, context);
166
-
167
- // Then, replace placeholders in {} format
168
215
  compiledPrompt = replacePlaceholders(compiledPrompt, context);
169
-
170
216
  return compiledPrompt;
171
217
  } catch (err) {
172
218
  logger.error({ message: 'Error compiling prompt', err, prompt });
@@ -215,10 +261,10 @@ function extendContextData(context: ContextData = {}): ExtendedContextData {
215
261
  * - {tools.NodeName.field} - node reference with 'tools' prefix (alias)
216
262
  */
217
263
  function replacePlaceholders(text: string, context: Record<string, any>): string {
218
- return text.replace(/\{([^}]+)}/g, (match, keyPath) => {
264
+ return text.replace(PLACEHOLDER_REGEX, (match, keyPathRaw) => {
265
+ const keyPath = String(keyPathRaw).trim();
219
266
  const value = getContextValueFromPath(keyPath, context, match);
220
267
 
221
- // In the case of complex values (e.g., arrays or objects), JSON stringify them to ensure all data is preserved.
222
268
  if (Array.isArray(value) || (typeof value === 'object' && value !== null)) {
223
269
  try {
224
270
  return JSON.stringify(value);
@@ -0,0 +1,48 @@
1
+ import { z } from 'zod';
2
+ import { logger } from '../../utils/logger';
3
+
4
+ interface GetSchemaForToolInferenceParams {
5
+ inputSchema: z.ZodTypeAny;
6
+ overriddenKeys: string[];
7
+ toolName: string;
8
+ }
9
+
10
+ interface GetSchemaForToolInferenceResult {
11
+ schemaForLLM: z.ZodTypeAny;
12
+ skipLLMCall: boolean;
13
+ }
14
+
15
+ export const getSchemaForToolInference = ({
16
+ inputSchema,
17
+ overriddenKeys,
18
+ toolName,
19
+ }: GetSchemaForToolInferenceParams): GetSchemaForToolInferenceResult => {
20
+ let schemaForLLM = inputSchema;
21
+ let skipLLMCall = false;
22
+
23
+ if (overriddenKeys.length === 0) {
24
+ return { schemaForLLM, skipLLMCall };
25
+ }
26
+
27
+ try {
28
+ if (inputSchema instanceof z.ZodObject) {
29
+ const omitMap = overriddenKeys.reduce((acc, key) => {
30
+ acc[key] = true;
31
+ return acc;
32
+ }, {} as Record<string, true>);
33
+
34
+ const filteredSchema = inputSchema.omit(omitMap);
35
+ schemaForLLM = filteredSchema;
36
+ const remainingKeys = Object.keys(filteredSchema.shape);
37
+ skipLLMCall = remainingKeys.length === 0;
38
+ }
39
+ } catch (err) {
40
+ logger.warn({
41
+ msg: '[Tool] Failed to filter schema, using original',
42
+ tool: toolName,
43
+ err,
44
+ });
45
+ }
46
+
47
+ return { schemaForLLM, skipLLMCall };
48
+ };
@@ -0,0 +1,89 @@
1
+ import { AIMessage, BaseMessage, SystemMessage } from '@langchain/core/messages';
2
+ import { z } from 'zod';
3
+ import { logger } from '../../utils/logger';
4
+
5
+ interface InferToolCallWithRetryParams {
6
+ llm: any;
7
+ tool: { name: string };
8
+ inputSchema: z.ZodTypeAny;
9
+ messages: BaseMessage[];
10
+ sessionId: string;
11
+ mergeArgs?: (args: Record<string, any>) => Record<string, any>;
12
+ onAfterAttempt?: () => Promise<void>;
13
+ maxAttempts?: number;
14
+ toolNameForLogs: string;
15
+ }
16
+
17
+ export const inferToolCallWithRetry = async ({
18
+ llm,
19
+ tool,
20
+ inputSchema,
21
+ messages,
22
+ sessionId,
23
+ mergeArgs,
24
+ onAfterAttempt,
25
+ maxAttempts = 2,
26
+ toolNameForLogs,
27
+ }: InferToolCallWithRetryParams): Promise<AIMessage> => {
28
+ let inferenceMessages = [...messages];
29
+ let aiToolCallMessage: AIMessage = new AIMessage({ content: '', tool_calls: [] });
30
+
31
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
32
+ aiToolCallMessage = await llm.bindTools([tool], { tool_choice: tool.name }).invoke(inferenceMessages);
33
+ if (onAfterAttempt) {
34
+ await onAfterAttempt();
35
+ }
36
+
37
+ if (!aiToolCallMessage.tool_calls || aiToolCallMessage.tool_calls.length === 0) {
38
+ throw new Error('No tool calls generated by LLM');
39
+ }
40
+
41
+ const inferredArgs = (aiToolCallMessage.tool_calls[0].args || {}) as Record<string, any>;
42
+ const mergedArgs = mergeArgs ? mergeArgs(inferredArgs) : inferredArgs;
43
+ const parseResult = inputSchema.safeParse(mergedArgs);
44
+
45
+ if (parseResult.success) {
46
+ aiToolCallMessage.tool_calls[0].args = parseResult.data;
47
+ return aiToolCallMessage;
48
+ }
49
+
50
+ if (attempt < maxAttempts) {
51
+ const validationIssues = parseResult.error.issues
52
+ .map((issue: z.ZodIssue) => `${issue.path.join('.') || '<root>'}: ${issue.message}`)
53
+ .join('; ');
54
+
55
+ logger.error({
56
+ msg: '[Tool] Retrying parameter inference due to schema mismatch',
57
+ tool: toolNameForLogs,
58
+ sessionId,
59
+ attempt,
60
+ validationIssues,
61
+ });
62
+
63
+ inferenceMessages = [
64
+ ...inferenceMessages,
65
+ new SystemMessage(
66
+ `Your previous tool arguments were invalid for tool "${tool.name}".
67
+ Validation issues: ${validationIssues}
68
+ Return arguments that strictly match the tool schema types. Do not wrap scalar values in arrays.`,
69
+ ),
70
+ ];
71
+ continue;
72
+ }
73
+
74
+ logger.error({
75
+ msg: '[Tool] Inferred parameters remain schema-invalid after retries',
76
+ tool: toolNameForLogs,
77
+ sessionId,
78
+ validationIssues: parseResult.error.issues.map((issue: z.ZodIssue) => ({
79
+ path: issue.path.join('.') || '<root>',
80
+ message: issue.message,
81
+ })),
82
+ });
83
+
84
+ aiToolCallMessage.tool_calls[0].args = mergedArgs;
85
+ return aiToolCallMessage;
86
+ }
87
+
88
+ return aiToolCallMessage;
89
+ };
@@ -158,6 +158,7 @@ export interface BrowserTaskNode extends BaseNode {
158
158
  model?: string;
159
159
  inputSchema?: SchemaField[];
160
160
  outputSchema?: OutputSchemaField[];
161
+ parameters?: Record<string, any>;
161
162
  autoTrigger?: boolean;
162
163
  defaultPayload?: string;
163
164
  proxyConfig?: ProxyConfig; // Proxy configuration supporting all proxy types
@@ -315,6 +316,7 @@ export type ActionInputParam = {
315
316
  export enum ActionInputParamType {
316
317
  String = 'string',
317
318
  Number = 'number',
319
+ Integer = 'integer',
318
320
  Boolean = 'boolean',
319
321
  Array = 'array',
320
322
  Object = 'object',