@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.
- package/dist/cli/cliUtils.d.ts +4 -2
- package/dist/cli/cliUtils.d.ts.map +1 -1
- package/dist/cli/cliUtils.js +12 -5
- package/dist/cli/cliUtils.js.map +1 -1
- package/dist/nodes/addAppToolNode.d.ts.map +1 -1
- package/dist/nodes/addAppToolNode.js +61 -28
- package/dist/nodes/addAppToolNode.js.map +1 -1
- package/dist/nodes/addBrowserTaskNode.d.ts.map +1 -1
- package/dist/nodes/addBrowserTaskNode.js +37 -7
- package/dist/nodes/addBrowserTaskNode.js.map +1 -1
- package/dist/nodes/addToolNode.d.ts.map +1 -1
- package/dist/nodes/addToolNode.js +24 -55
- package/dist/nodes/addToolNode.js.map +1 -1
- package/dist/nodes/addToolRunNode.d.ts.map +1 -1
- package/dist/nodes/addToolRunNode.js +93 -82
- package/dist/nodes/addToolRunNode.js.map +1 -1
- package/dist/nodes/compilePrompt.d.ts +10 -0
- package/dist/nodes/compilePrompt.d.ts.map +1 -1
- package/dist/nodes/compilePrompt.js +67 -21
- package/dist/nodes/compilePrompt.js.map +1 -1
- package/dist/nodes/utils/getSchemaForToolInference.d.ts +13 -0
- package/dist/nodes/utils/getSchemaForToolInference.d.ts.map +1 -0
- package/dist/nodes/utils/getSchemaForToolInference.js +34 -0
- package/dist/nodes/utils/getSchemaForToolInference.js.map +1 -0
- package/dist/nodes/utils/inferToolCallWithRetry.d.ts +18 -0
- package/dist/nodes/utils/inferToolCallWithRetry.d.ts.map +1 -0
- package/dist/nodes/utils/inferToolCallWithRetry.js +58 -0
- package/dist/nodes/utils/inferToolCallWithRetry.js.map +1 -0
- package/dist/types/Flows.types.d.ts +2 -0
- package/dist/types/Flows.types.d.ts.map +1 -1
- package/dist/types/Flows.types.js +1 -0
- package/dist/types/Flows.types.js.map +1 -1
- package/dist/utils/flowSchema.d.ts.map +1 -1
- package/dist/utils/flowSchema.js +20 -4
- package/dist/utils/flowSchema.js.map +1 -1
- package/dist/utils/schemaUtils.d.ts.map +1 -1
- package/dist/utils/schemaUtils.js +2 -0
- package/dist/utils/schemaUtils.js.map +1 -1
- package/package.json +2 -2
- package/src/cli/cliUtils.ts +12 -5
- package/src/nodes/addAppToolNode.ts +68 -31
- package/src/nodes/addBrowserTaskNode.ts +45 -9
- package/src/nodes/addToolNode.ts +30 -61
- package/src/nodes/addToolRunNode.ts +114 -97
- package/src/nodes/compilePrompt.ts +69 -23
- package/src/nodes/utils/getSchemaForToolInference.ts +48 -0
- package/src/nodes/utils/inferToolCallWithRetry.ts +89 -0
- package/src/types/Flows.types.ts +2 -0
- package/src/utils/flowSchema.ts +21 -4
- package/src/utils/schemaUtils.ts +2 -0
package/src/nodes/addToolNode.ts
CHANGED
|
@@ -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 {
|
|
13
|
+
import { compilePrompt, compileNodeParameters } from './compilePrompt';
|
|
14
14
|
import { AnalyticsEventName } from '../types/Analytics.types';
|
|
15
15
|
import { trackAnalyticsEvent } from '../internalTools/analytics';
|
|
16
|
-
import {
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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,
|
|
123
|
-
const hasOverriddenParameters =
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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
|
-
//
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
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
|
|
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:
|
|
129
|
-
messageIds: [
|
|
150
|
+
raw: toolCallMessage,
|
|
151
|
+
messageIds: [toolCall.id],
|
|
130
152
|
}),
|
|
131
153
|
);
|
|
132
154
|
|
|
133
|
-
//
|
|
134
|
-
state.goto =
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
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
|
-
|
|
135
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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(
|
|
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
|
+
};
|
package/src/types/Flows.types.ts
CHANGED
|
@@ -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',
|