@minded-ai/mindedjs 2.0.43 → 2.0.45-beta.1
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/agent.d.ts.map +1 -1
- package/dist/agent.js +29 -1
- package/dist/agent.js.map +1 -1
- package/dist/browserTask/executeBrowserTask.js +34 -33
- package/dist/browserTask/executeBrowserTask.js.map +1 -1
- package/dist/nodes/addThinkingNode.d.ts.map +1 -1
- package/dist/nodes/addThinkingNode.js +36 -11
- package/dist/nodes/addThinkingNode.js.map +1 -1
- package/dist/nodes/compilePrompt.d.ts.map +1 -1
- package/dist/nodes/compilePrompt.js +84 -4
- package/dist/nodes/compilePrompt.js.map +1 -1
- package/dist/platform/mindedConnectionTypes.d.ts +12 -1
- package/dist/platform/mindedConnectionTypes.d.ts.map +1 -1
- package/dist/platform/mindedConnectionTypes.js +1 -0
- package/dist/platform/mindedConnectionTypes.js.map +1 -1
- package/dist/types/Flows.types.d.ts +36 -23
- package/dist/types/Flows.types.d.ts.map +1 -1
- package/dist/types/Flows.types.js.map +1 -1
- package/dist/types/Tools.types.d.ts +5 -3
- package/dist/types/Tools.types.d.ts.map +1 -1
- package/dist/types/Tools.types.js.map +1 -1
- package/dist/utils/schemaConverter.d.ts +3 -0
- package/dist/utils/schemaConverter.d.ts.map +1 -0
- package/dist/utils/schemaConverter.js +73 -0
- package/dist/utils/schemaConverter.js.map +1 -0
- package/docs/low-code-editor/rpa-tools.md +17 -8
- package/package.json +2 -2
- package/src/agent.ts +40 -8
- package/src/browserTask/executeBrowserTask.ts +34 -34
- package/src/nodes/addPromptNode.ts +1 -1
- package/src/nodes/addThinkingNode.ts +40 -12
- package/src/nodes/compilePrompt.ts +92 -4
- package/src/platform/mindedConnectionTypes.ts +13 -0
- package/src/types/Flows.types.ts +39 -32
- package/src/types/Tools.types.ts +7 -3
- package/src/utils/schemaConverter.ts +82 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@minded-ai/mindedjs",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.45-beta.1",
|
|
4
4
|
"description": "MindedJS is a TypeScript library for building agents.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -72,4 +72,4 @@
|
|
|
72
72
|
"peerDependencies": {
|
|
73
73
|
"playwright": "^1.55.0"
|
|
74
74
|
}
|
|
75
|
-
}
|
|
75
|
+
}
|
package/src/agent.ts
CHANGED
|
@@ -490,6 +490,21 @@ export class Agent {
|
|
|
490
490
|
} catch (err) {
|
|
491
491
|
const { baseUrl } = getConfig();
|
|
492
492
|
logger.error({ msg: '[Trigger] Agent initialization failed', err, sessionId, baseUrl });
|
|
493
|
+
|
|
494
|
+
// Notify backend about the initialization error
|
|
495
|
+
if (mindedConnection.isConnected()) {
|
|
496
|
+
try {
|
|
497
|
+
await mindedConnection.emit(mindedConnectionSocketMessageType.ON_AGENT_RUN_ERROR, {
|
|
498
|
+
type: mindedConnectionSocketMessageType.ON_AGENT_RUN_ERROR,
|
|
499
|
+
sessionId,
|
|
500
|
+
error: err instanceof Error ? err.message : String(err),
|
|
501
|
+
stack: err instanceof Error ? err.stack : undefined,
|
|
502
|
+
});
|
|
503
|
+
} catch (emitErr) {
|
|
504
|
+
logger.error({ msg: '[Agent] Failed to emit initialization error to backend', emitErr });
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
493
508
|
throw err;
|
|
494
509
|
}
|
|
495
510
|
|
|
@@ -644,6 +659,20 @@ export class Agent {
|
|
|
644
659
|
// Release the session lock on error
|
|
645
660
|
await this.interruptSessionManager.release(sessionId);
|
|
646
661
|
|
|
662
|
+
// Notify backend about the error
|
|
663
|
+
if (mindedConnection.isConnected()) {
|
|
664
|
+
try {
|
|
665
|
+
await mindedConnection.emit(mindedConnectionSocketMessageType.ON_AGENT_RUN_ERROR, {
|
|
666
|
+
type: mindedConnectionSocketMessageType.ON_AGENT_RUN_ERROR,
|
|
667
|
+
sessionId,
|
|
668
|
+
error: err instanceof Error ? err.message : String(err),
|
|
669
|
+
stack: err instanceof Error ? err.stack : undefined,
|
|
670
|
+
});
|
|
671
|
+
} catch (emitErr) {
|
|
672
|
+
logger.error({ msg: '[Agent] Failed to emit error to backend', emitErr });
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
647
676
|
const state = await this.compiledGraph.getState(this.getLangraphConfig(sessionId));
|
|
648
677
|
const results = await this.emit(AgentEvents.ERROR, {
|
|
649
678
|
error: err instanceof Error ? err : new Error(JSON.stringify(err)),
|
|
@@ -854,7 +883,7 @@ export class Agent {
|
|
|
854
883
|
} else if (runLocally) {
|
|
855
884
|
throw new Error(
|
|
856
885
|
'No LLM configuration found in minded.json. When running locally, you must provide LLM configuration in minded.json. ' +
|
|
857
|
-
|
|
886
|
+
'Add an "llm" section to your minded.json file with provider and model settings.',
|
|
858
887
|
);
|
|
859
888
|
} else {
|
|
860
889
|
throw new Error('No LLM configuration found. Please configure LLM in Organization Settings or add it to minded.json');
|
|
@@ -1224,7 +1253,7 @@ export class Agent {
|
|
|
1224
1253
|
/**
|
|
1225
1254
|
* Handle tool execution mode when agent is run with 'runTool' argument
|
|
1226
1255
|
* This allows running tools directly via: npm run tool <toolName> [params...] [cookies=false]
|
|
1227
|
-
*
|
|
1256
|
+
*
|
|
1228
1257
|
* @private
|
|
1229
1258
|
*/
|
|
1230
1259
|
private async handleToolExecutionMode(): Promise<void> {
|
|
@@ -1252,12 +1281,15 @@ export class Agent {
|
|
|
1252
1281
|
flags,
|
|
1253
1282
|
});
|
|
1254
1283
|
|
|
1255
|
-
const result = await this.executeTool(
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1284
|
+
const result = await this.executeTool(
|
|
1285
|
+
{
|
|
1286
|
+
toolName,
|
|
1287
|
+
toolParams: params,
|
|
1288
|
+
sessionId: uuidv4(),
|
|
1289
|
+
requestId: uuidv4(),
|
|
1290
|
+
},
|
|
1291
|
+
'direct',
|
|
1292
|
+
);
|
|
1261
1293
|
|
|
1262
1294
|
if (result.error) {
|
|
1263
1295
|
logger.error({
|
|
@@ -43,26 +43,26 @@ const loadCookiesForSession = async (sessionId: string): Promise<void> => {
|
|
|
43
43
|
// Check if cookies should be disabled
|
|
44
44
|
const noCookies = process.env.MINDED_NO_COOKIES === 'true';
|
|
45
45
|
if (noCookies) {
|
|
46
|
-
logger.info({ message: 'Cookies disabled via cookies=false flag', sessionId });
|
|
46
|
+
logger.info({ message: '[Browser] Cookies disabled via cookies=false flag', sessionId });
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
const stored = await cookieStore.load();
|
|
51
51
|
if (!stored || stored.length === 0) {
|
|
52
|
-
logger.info({ message: 'No stored cookies to load', sessionId });
|
|
52
|
+
logger.info({ message: '[Browser] No stored cookies to load', sessionId });
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
55
|
const cdp = cdpClients.get(sessionId);
|
|
56
56
|
if (!cdp) {
|
|
57
|
-
logger.warn({ message: 'No CDP client found for session', sessionId });
|
|
57
|
+
logger.warn({ message: '[Browser] No CDP client found for session', sessionId });
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
try {
|
|
62
62
|
await cdp.Network.setCookies({ cookies: stored });
|
|
63
|
-
logger.info({ message: 'Loaded cookies into browser session', sessionId, count: stored.length });
|
|
63
|
+
logger.info({ message: '[Browser] Loaded cookies into browser session', sessionId, count: stored.length });
|
|
64
64
|
} catch (error) {
|
|
65
|
-
logger.warn({ message: 'Failed to set cookies via CDP', sessionId, error });
|
|
65
|
+
logger.warn({ message: '[Browser] Failed to set cookies via CDP', sessionId, error });
|
|
66
66
|
}
|
|
67
67
|
};
|
|
68
68
|
|
|
@@ -77,19 +77,19 @@ const saveCookiesForSession = async (sessionId: string): Promise<void> => {
|
|
|
77
77
|
try {
|
|
78
78
|
const cdp = cdpClients.get(sessionId);
|
|
79
79
|
if (!cdp) {
|
|
80
|
-
logger.warn({ message: 'No CDP client found for session', sessionId });
|
|
80
|
+
logger.warn({ message: '[Browser] No CDP client found for session', sessionId });
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
const result = await cdp.Network.getAllCookies();
|
|
85
85
|
if (result) {
|
|
86
86
|
await cookieStore.save(result.cookies);
|
|
87
|
-
logger.info({ message: 'Saved cookies from browser session', sessionId, count: result.cookies.length });
|
|
87
|
+
logger.info({ message: '[Browser] Saved cookies from browser session', sessionId, count: result.cookies.length });
|
|
88
88
|
} else {
|
|
89
|
-
logger.warn({ message: 'No cookies found in browser session', sessionId });
|
|
89
|
+
logger.warn({ message: '[Browser] No cookies found in browser session', sessionId });
|
|
90
90
|
}
|
|
91
91
|
} catch (error) {
|
|
92
|
-
logger.warn({ message: 'Failed to get/save cookies from session', sessionId, error: JSON.stringify(error) });
|
|
92
|
+
logger.warn({ message: '[Browser] Failed to get/save cookies from session', sessionId, error: JSON.stringify(error) });
|
|
93
93
|
}
|
|
94
94
|
};
|
|
95
95
|
|
|
@@ -100,7 +100,7 @@ const cleanupCdpClient = async (sessionId: string): Promise<void> => {
|
|
|
100
100
|
try {
|
|
101
101
|
await cdp.close();
|
|
102
102
|
} catch (error) {
|
|
103
|
-
logger.warn({ message: 'Failed to close CDP client', sessionId, error });
|
|
103
|
+
logger.warn({ message: '[Browser] Failed to close CDP client', sessionId, error });
|
|
104
104
|
}
|
|
105
105
|
cdpClients.delete(sessionId);
|
|
106
106
|
}
|
|
@@ -116,9 +116,8 @@ export const createBrowserSession = async ({
|
|
|
116
116
|
proxy?: string;
|
|
117
117
|
browserTaskMode: BrowserTaskMode;
|
|
118
118
|
}): Promise<CreateBrowserSessionResponse> => {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (browserTaskMode === BrowserTaskMode.LOCAL) {
|
|
119
|
+
if (browserTaskMode === BrowserTaskMode.LOCAL && process.env.MINDED_CLOUD !== 'true') {
|
|
120
|
+
logger.debug({ msg: '[Browser] Creating local browser session', proxy });
|
|
122
121
|
const { cdpUrl, instanceId } = await getOrStartLocalCDP({
|
|
123
122
|
headless: false,
|
|
124
123
|
});
|
|
@@ -128,19 +127,19 @@ export const createBrowserSession = async ({
|
|
|
128
127
|
|
|
129
128
|
const port = extractPortFromCdpUrl(cdpUrl);
|
|
130
129
|
if (!port) {
|
|
131
|
-
logger.warn({ message: 'Failed to extract port from CDP URL', sessionId, cdpUrl });
|
|
130
|
+
logger.warn({ message: '[Browser] Failed to extract port from CDP URL', sessionId, cdpUrl });
|
|
132
131
|
} else {
|
|
133
132
|
const cdp = await CDP({ host: 'localhost', port });
|
|
134
133
|
await cdp.Network.enable();
|
|
135
134
|
cdpClients.set(sessionId, cdp);
|
|
136
|
-
logger.info({ message: 'Created CDP client for session', sessionId, port });
|
|
135
|
+
logger.info({ message: '[Browser] Created CDP client for session', sessionId, port });
|
|
137
136
|
}
|
|
138
137
|
|
|
139
138
|
// Load cookies for this session (best-effort)
|
|
140
139
|
try {
|
|
141
140
|
await loadCookiesForSession(sessionId);
|
|
142
141
|
} catch (error) {
|
|
143
|
-
logger.warn({ message: 'Failed to load cookies for session', sessionId, error });
|
|
142
|
+
logger.warn({ message: '[Browser] Failed to load cookies for session', sessionId, error });
|
|
144
143
|
}
|
|
145
144
|
|
|
146
145
|
return {
|
|
@@ -149,6 +148,7 @@ export const createBrowserSession = async ({
|
|
|
149
148
|
};
|
|
150
149
|
}
|
|
151
150
|
|
|
151
|
+
logger.debug({ msg: '[Browser] Creating browser session via socket', proxy });
|
|
152
152
|
const response = await mindedConnection.awaitEmit<CreateBrowserSessionRequest, CreateBrowserSessionResponse>(
|
|
153
153
|
mindedConnectionSocketMessageType.CREATE_BROWSER_SESSION,
|
|
154
154
|
{
|
|
@@ -160,12 +160,12 @@ export const createBrowserSession = async ({
|
|
|
160
160
|
);
|
|
161
161
|
|
|
162
162
|
if (response.error) {
|
|
163
|
-
logger.error({ msg: 'Failed to create browser session', error: response.error });
|
|
163
|
+
logger.error({ msg: '[Browser] Failed to create browser session', error: response.error });
|
|
164
164
|
throw new Error(response.error);
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
logger.debug({
|
|
168
|
-
msg: 'Browser session created successfully',
|
|
168
|
+
msg: '[Browser] Browser session created successfully',
|
|
169
169
|
sessionId: response.sessionId,
|
|
170
170
|
hasLiveUrl: !!response.liveViewUrl,
|
|
171
171
|
});
|
|
@@ -182,7 +182,7 @@ export const destroyBrowserSession = async ({
|
|
|
182
182
|
onPrem?: boolean;
|
|
183
183
|
localRun?: boolean;
|
|
184
184
|
}): Promise<DestroyBrowserSessionResponse> => {
|
|
185
|
-
logger.debug({ msg: 'Destroying browser session via socket', sessionId, onPrem });
|
|
185
|
+
logger.debug({ msg: '[Browser] Destroying browser session via socket', sessionId, onPrem });
|
|
186
186
|
|
|
187
187
|
if (localRun || process.env.BROWSER_TASK_MODE === BrowserTaskMode.LOCAL) {
|
|
188
188
|
// Kill specific instance if we have the mapping
|
|
@@ -192,7 +192,7 @@ export const destroyBrowserSession = async ({
|
|
|
192
192
|
localSessionInstances.delete(sessionId);
|
|
193
193
|
} else {
|
|
194
194
|
// Fallback to killing all if no specific instance found
|
|
195
|
-
logger.warn({ msg: 'No instance ID found for session, killing all instances', sessionId });
|
|
195
|
+
logger.warn({ msg: '[Browser] No instance ID found for session, killing all instances', sessionId });
|
|
196
196
|
await kill();
|
|
197
197
|
}
|
|
198
198
|
return {
|
|
@@ -211,11 +211,11 @@ export const destroyBrowserSession = async ({
|
|
|
211
211
|
);
|
|
212
212
|
|
|
213
213
|
if (response.error) {
|
|
214
|
-
logger.error({ msg: 'Failed to destroy browser session', error: response.error, sessionId });
|
|
214
|
+
logger.error({ msg: '[Browser] Failed to destroy browser session', error: response.error, sessionId });
|
|
215
215
|
throw new Error(response.error);
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
logger.debug({ msg: 'Browser session destroyed successfully', sessionId });
|
|
218
|
+
logger.debug({ msg: '[Browser] Browser session destroyed successfully', sessionId });
|
|
219
219
|
return response;
|
|
220
220
|
};
|
|
221
221
|
|
|
@@ -237,7 +237,7 @@ export const invokeBrowserTask = async (options: InvokeBrowserTaskOptions): Prom
|
|
|
237
237
|
|
|
238
238
|
const args = ['run', pythonScriptPath];
|
|
239
239
|
logger.info({
|
|
240
|
-
message: 'Spawning Python process',
|
|
240
|
+
message: '[Browser] Spawning Python process',
|
|
241
241
|
args,
|
|
242
242
|
});
|
|
243
243
|
|
|
@@ -286,7 +286,7 @@ export const invokeBrowserTask = async (options: InvokeBrowserTaskOptions): Prom
|
|
|
286
286
|
const interval = setInterval(() => {
|
|
287
287
|
// Check if chromium process stopped running
|
|
288
288
|
if (!isLocalBrowserRunning()) {
|
|
289
|
-
logger.error({ message: 'Local browser process stopped running, killing browser task' });
|
|
289
|
+
logger.error({ message: '[Browser] Local browser process stopped running, killing browser task' });
|
|
290
290
|
child.kill();
|
|
291
291
|
clearInterval(interval);
|
|
292
292
|
}
|
|
@@ -305,18 +305,18 @@ export const invokeBrowserTask = async (options: InvokeBrowserTaskOptions): Prom
|
|
|
305
305
|
clearInterval(interval);
|
|
306
306
|
|
|
307
307
|
if (exitCode !== 0) {
|
|
308
|
-
logger.error({ message: 'Operator failed', exitCode, stderr: stderrBuffer });
|
|
308
|
+
logger.error({ message: '[Browser] Operator failed', exitCode, stderr: stderrBuffer });
|
|
309
309
|
throw new Error(`Local browser task failed with exit code ${exitCode}`);
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
-
logger.info({ message: 'Operator finished' });
|
|
312
|
+
logger.info({ message: '[Browser] Operator finished' });
|
|
313
313
|
|
|
314
314
|
let result = stdoutBuffer.split('___RESULT___')[1]?.trim() || stdoutBuffer;
|
|
315
315
|
if (outputSchema?.length) {
|
|
316
316
|
try {
|
|
317
317
|
result = JSON.parse(result);
|
|
318
318
|
} catch (error) {
|
|
319
|
-
logger.debug({ message: 'Failed to parse result', error });
|
|
319
|
+
logger.debug({ message: '[Browser] Failed to parse result', error });
|
|
320
320
|
}
|
|
321
321
|
}
|
|
322
322
|
|
|
@@ -326,9 +326,9 @@ export const invokeBrowserTask = async (options: InvokeBrowserTaskOptions): Prom
|
|
|
326
326
|
try {
|
|
327
327
|
const files = readdirSync(folderPath);
|
|
328
328
|
downloadedFiles = files.map((file) => path.join(folderPath, file));
|
|
329
|
-
logger.debug({ message: 'Found downloaded files', count: downloadedFiles.length, files: downloadedFiles });
|
|
329
|
+
logger.debug({ message: '[Browser] Found downloaded files', count: downloadedFiles.length, files: downloadedFiles });
|
|
330
330
|
} catch (error) {
|
|
331
|
-
logger.error({ message: 'Failed to read downloads folder', error });
|
|
331
|
+
logger.error({ message: '[Browser] Failed to read downloads folder', error });
|
|
332
332
|
}
|
|
333
333
|
}
|
|
334
334
|
|
|
@@ -336,7 +336,7 @@ export const invokeBrowserTask = async (options: InvokeBrowserTaskOptions): Prom
|
|
|
336
336
|
try {
|
|
337
337
|
await saveCookiesForSession(sessionId);
|
|
338
338
|
} catch (error) {
|
|
339
|
-
logger.warn({ message: 'Failed to save cookies after task completion', sessionId, error });
|
|
339
|
+
logger.warn({ message: '[Browser] Failed to save cookies after task completion', sessionId, error });
|
|
340
340
|
}
|
|
341
341
|
|
|
342
342
|
return {
|
|
@@ -354,7 +354,7 @@ export const invokeBrowserTask = async (options: InvokeBrowserTaskOptions): Prom
|
|
|
354
354
|
}
|
|
355
355
|
|
|
356
356
|
logger.debug({
|
|
357
|
-
msg: 'Invoking browser task via socket',
|
|
357
|
+
msg: '[Browser] Invoking browser task via socket',
|
|
358
358
|
sessionId,
|
|
359
359
|
taskLength: task.length,
|
|
360
360
|
keepAlive,
|
|
@@ -380,12 +380,12 @@ export const invokeBrowserTask = async (options: InvokeBrowserTaskOptions): Prom
|
|
|
380
380
|
);
|
|
381
381
|
|
|
382
382
|
if (response.error) {
|
|
383
|
-
logger.error({ msg: 'Failed to invoke browser task', error: response.error });
|
|
383
|
+
logger.error({ msg: '[Browser] Failed to invoke browser task', error: response.error });
|
|
384
384
|
throw new Error(response.error);
|
|
385
385
|
}
|
|
386
386
|
|
|
387
387
|
logger.debug({
|
|
388
|
-
msg: 'Browser task completed successfully',
|
|
388
|
+
msg: '[Browser] Browser task completed successfully',
|
|
389
389
|
sessionId,
|
|
390
390
|
hasResult: !!response.result,
|
|
391
391
|
stepCount: response.steps?.length || 0,
|
|
@@ -394,7 +394,7 @@ export const invokeBrowserTask = async (options: InvokeBrowserTaskOptions): Prom
|
|
|
394
394
|
|
|
395
395
|
return response;
|
|
396
396
|
} catch (err) {
|
|
397
|
-
logger.error({ message: 'Error invoking browser task', err });
|
|
397
|
+
logger.error({ message: '[Browser] Error invoking browser task', err });
|
|
398
398
|
throw err;
|
|
399
399
|
}
|
|
400
400
|
};
|
|
@@ -13,6 +13,8 @@ import { createHistoryStep } from '../utils/history';
|
|
|
13
13
|
import { compilePrompt } from './compilePrompt';
|
|
14
14
|
import { AnalyticsEventName } from '../types/Analytics.types';
|
|
15
15
|
import { trackAnalyticsEvent } from '../internalTools/analytics';
|
|
16
|
+
import { convertArraySchemaToZod } from '../utils/schemaConverter';
|
|
17
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
16
18
|
|
|
17
19
|
type AddThinkingNodeParams = {
|
|
18
20
|
graph: PreCompiledGraph;
|
|
@@ -51,8 +53,17 @@ export const addThinkingNode = async ({ graph, node, llm, agent }: AddThinkingNo
|
|
|
51
53
|
const messagesForModel = [...state.messages];
|
|
52
54
|
messagesForModel.push(systemMessage);
|
|
53
55
|
|
|
56
|
+
// Check if we need structured output
|
|
57
|
+
const useStructuredOutput = node.outputSchema && node.outputSchema.length > 0;
|
|
58
|
+
let llmWithStructuredOutput = llmToUse;
|
|
59
|
+
|
|
60
|
+
if (useStructuredOutput) {
|
|
61
|
+
const zodSchema = convertArraySchemaToZod(node.outputSchema!);
|
|
62
|
+
llmWithStructuredOutput = llmToUse.withStructuredOutput(zodSchema, { name: 'extract' });
|
|
63
|
+
}
|
|
64
|
+
|
|
54
65
|
const startTime = Date.now();
|
|
55
|
-
const result
|
|
66
|
+
const result = await llmWithStructuredOutput.invoke(messagesForModel);
|
|
56
67
|
const endTime = Date.now();
|
|
57
68
|
|
|
58
69
|
logger.debug({
|
|
@@ -65,17 +76,35 @@ export const addThinkingNode = async ({ graph, node, llm, agent }: AddThinkingNo
|
|
|
65
76
|
await agent.interruptSessionManager.checkQueueAndInterrupt(state.sessionId, state);
|
|
66
77
|
|
|
67
78
|
// Inject metadata to identify this as a thinking node output
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
nodeType: NodeType.THINKING,
|
|
74
|
-
nodeDisplayName: node.displayName,
|
|
75
|
-
prompt: node.prompt,
|
|
76
|
-
},
|
|
79
|
+
const additionalKwargs: Record<string, any> = {
|
|
80
|
+
mindedMetadata: {
|
|
81
|
+
nodeType: NodeType.THINKING,
|
|
82
|
+
nodeDisplayName: node.displayName,
|
|
83
|
+
prompt: node.prompt,
|
|
77
84
|
},
|
|
78
|
-
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
let messageId: string;
|
|
88
|
+
let messageContent: string;
|
|
89
|
+
|
|
90
|
+
// When structured output is used, result is the raw extracted data (not an AIMessage)
|
|
91
|
+
// When not used, result is an AIMessage with content and id
|
|
92
|
+
if (useStructuredOutput) {
|
|
93
|
+
additionalKwargs.structuredOutput = result;
|
|
94
|
+
messageId = uuidv4();
|
|
95
|
+
messageContent = JSON.stringify(result);
|
|
96
|
+
} else {
|
|
97
|
+
// result is an AIMessage, preserve its additional_kwargs and id
|
|
98
|
+
const aiResult = result as AIMessage;
|
|
99
|
+
Object.assign(additionalKwargs, aiResult.additional_kwargs);
|
|
100
|
+
messageId = aiResult.id!;
|
|
101
|
+
messageContent = aiResult.content as string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const messageWithMetadata = new AIMessage({
|
|
105
|
+
content: messageContent,
|
|
106
|
+
additional_kwargs: additionalKwargs,
|
|
107
|
+
id: messageId,
|
|
79
108
|
});
|
|
80
109
|
|
|
81
110
|
// Add to state messages
|
|
@@ -100,4 +129,3 @@ export const addThinkingNode = async ({ graph, node, llm, agent }: AddThinkingNo
|
|
|
100
129
|
};
|
|
101
130
|
graph.addNode(node.name, callback);
|
|
102
131
|
};
|
|
103
|
-
|
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
import * as ejs from 'ejs';
|
|
2
2
|
import { logger } from '../utils/logger';
|
|
3
|
+
import { HistoryStep } from '../types/Agent.types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extract node outputs from state.history and state.messages
|
|
7
|
+
* Returns a map of nodeId/nodeDisplayName -> output object
|
|
8
|
+
*/
|
|
9
|
+
function extractNodeOutputs(state: any): Record<string, any> {
|
|
10
|
+
if (!state?.history || !state?.messages) {
|
|
11
|
+
return {};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const outputs: Record<string, any> = {};
|
|
15
|
+
|
|
16
|
+
// Build lookup maps by message type
|
|
17
|
+
const messagesByType = {
|
|
18
|
+
tool: new Map(
|
|
19
|
+
state.messages
|
|
20
|
+
.filter((msg: any) => msg.constructor?.name === 'ToolMessage' && msg.tool_call_id)
|
|
21
|
+
.map((msg: any) => [msg.tool_call_id, msg]),
|
|
22
|
+
),
|
|
23
|
+
ai: new Map(state.messages.filter((msg: any) => msg.constructor?.name === 'AIMessage' && msg.id).map((msg: any) => [msg.id, msg])),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const setOutput = (item: HistoryStep, value: any) => {
|
|
27
|
+
if (item.nodeId) outputs[item.nodeId] = value;
|
|
28
|
+
if (item.nodeDisplayName) outputs[item.nodeDisplayName] = value;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const extractors: Record<string, (item: HistoryStep) => void> = {
|
|
32
|
+
tool: (item) => {
|
|
33
|
+
const toolCallId = item.messageIds?.[1] ?? item.messageIds?.[0];
|
|
34
|
+
const toolMessage: any = messagesByType.tool.get(toolCallId);
|
|
35
|
+
if (!toolMessage) return;
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const content = typeof toolMessage.content === 'string' ? JSON.parse(toolMessage.content) : toolMessage.content;
|
|
39
|
+
setOutput(item, content?.result ?? content);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
logger.debug({ message: 'Failed to parse tool message content', toolCallId, err });
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
thinking: (item) => {
|
|
46
|
+
const messageId = item.messageIds?.[0];
|
|
47
|
+
if (!messageId) return;
|
|
48
|
+
|
|
49
|
+
const aiMessage: any = messagesByType.ai.get(messageId);
|
|
50
|
+
if (!aiMessage) return;
|
|
51
|
+
|
|
52
|
+
const structuredOutput = aiMessage.additional_kwargs?.structuredOutput;
|
|
53
|
+
|
|
54
|
+
if (structuredOutput) {
|
|
55
|
+
setOutput(item, structuredOutput);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
for (const item of state.history) {
|
|
61
|
+
if (!item.messageIds?.length) continue;
|
|
62
|
+
extractors[item.type]?.(item);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return outputs;
|
|
66
|
+
}
|
|
3
67
|
|
|
4
68
|
/**
|
|
5
69
|
* Compile prompt with parameters using EJS and placeholder replacement
|
|
@@ -9,6 +73,17 @@ export function compilePrompt(prompt: string, params: Record<string, any> = {}):
|
|
|
9
73
|
// Define system parameters
|
|
10
74
|
params.system = { currentTime: new Date().toISOString() };
|
|
11
75
|
|
|
76
|
+
// Extract node outputs from state if available
|
|
77
|
+
// This enables placeholder replacement like {tools.NodeName.field} or {NodeName.field}
|
|
78
|
+
// by making previous node outputs available in the params object.
|
|
79
|
+
// Example: If ExtractUser node outputs { userName: "Bob", userAge: 35 },
|
|
80
|
+
// the prompt "Hello {tools.ExtractUser.userName}" becomes "Hello Bob"
|
|
81
|
+
if (params.state) {
|
|
82
|
+
const nodeOutputs = extractNodeOutputs(params.state);
|
|
83
|
+
// Merge node outputs into params so they're accessible during placeholder replacement
|
|
84
|
+
params = { ...params, ...nodeOutputs };
|
|
85
|
+
}
|
|
86
|
+
|
|
12
87
|
// First, render with EJS
|
|
13
88
|
let compiledPrompt = ejs.render(prompt, params);
|
|
14
89
|
|
|
@@ -17,24 +92,37 @@ export function compilePrompt(prompt: string, params: Record<string, any> = {}):
|
|
|
17
92
|
|
|
18
93
|
return compiledPrompt;
|
|
19
94
|
} catch (err) {
|
|
20
|
-
logger.error({ message: 'Error compiling prompt', err });
|
|
95
|
+
logger.error({ message: 'Error compiling prompt', err, prompt });
|
|
21
96
|
return prompt; // Return uncompiled if there's an error
|
|
22
97
|
}
|
|
23
98
|
}
|
|
24
99
|
|
|
25
100
|
/**
|
|
26
101
|
* Replace placeholders in {key} format
|
|
102
|
+
* Supports both:
|
|
103
|
+
* - {NodeName.field} - direct node reference
|
|
104
|
+
* - {tools.NodeName.field} - node reference with 'tools' prefix (alias)
|
|
27
105
|
*/
|
|
28
106
|
function replacePlaceholders(text: string, params: Record<string, any>): string {
|
|
29
107
|
return text.replace(/\{([^}]+)\}/g, (match, key) => {
|
|
30
|
-
|
|
108
|
+
let keys = key.split('.');
|
|
109
|
+
|
|
110
|
+
// Handle 'tools.' prefix as an alias for node outputs
|
|
111
|
+
// {tools.NodeName.field} -> {NodeName.field}
|
|
112
|
+
if (keys[0] === 'tools' && keys.length >= 3) {
|
|
113
|
+
keys = keys.slice(1); // Remove 'tools' prefix
|
|
114
|
+
}
|
|
115
|
+
|
|
31
116
|
let value: any = params;
|
|
32
117
|
|
|
33
|
-
for (
|
|
118
|
+
for (let i = 0; i < keys.length; i++) {
|
|
119
|
+
const k = keys[i];
|
|
34
120
|
if (value && typeof value === 'object' && k in value) {
|
|
35
121
|
value = value[k];
|
|
36
122
|
} else {
|
|
37
|
-
logger.warn({
|
|
123
|
+
logger.warn({
|
|
124
|
+
message: `Placeholder {${key}} in prompt not found in params. It will remain as placeholder.`,
|
|
125
|
+
});
|
|
38
126
|
return match; // Return original if key not found
|
|
39
127
|
}
|
|
40
128
|
}
|
|
@@ -72,6 +72,7 @@ export enum mindedConnectionSocketMessageType {
|
|
|
72
72
|
RPA_LOGS_UPLOAD = 'rpa-logs-upload',
|
|
73
73
|
// RPA Error Upload
|
|
74
74
|
RPA_ERROR_UPLOAD = 'rpa-error-upload',
|
|
75
|
+
ON_AGENT_RUN_ERROR = 'on-agent-run-error',
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
export type mindedConnectionSocketMessageTypeMap = {
|
|
@@ -125,6 +126,7 @@ export type mindedConnectionSocketMessageTypeMap = {
|
|
|
125
126
|
[mindedConnectionSocketMessageType.RPA_SCREENSHOT_UPLOAD]: RPAScreenshotUploadRequest;
|
|
126
127
|
[mindedConnectionSocketMessageType.RPA_LOGS_UPLOAD]: RPALogsUploadRequest;
|
|
127
128
|
[mindedConnectionSocketMessageType.RPA_ERROR_UPLOAD]: RPAErrorUploadRequest;
|
|
129
|
+
[mindedConnectionSocketMessageType.ON_AGENT_RUN_ERROR]: OnAgentRunErrorRequest;
|
|
128
130
|
};
|
|
129
131
|
|
|
130
132
|
export interface BasemindedConnectionSocketMessage {
|
|
@@ -633,3 +635,14 @@ export interface RPAErrorUploadResponse extends BaseSdkConnectionSocketMessageRe
|
|
|
633
635
|
screenshotS3Url?: string;
|
|
634
636
|
htmlS3Url?: string;
|
|
635
637
|
}
|
|
638
|
+
|
|
639
|
+
export interface OnAgentRunErrorRequest extends BasemindedConnectionSocketMessage {
|
|
640
|
+
type: mindedConnectionSocketMessageType.ON_AGENT_RUN_ERROR;
|
|
641
|
+
sessionId: string;
|
|
642
|
+
error: string;
|
|
643
|
+
stack?: string;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
export interface OnAgentRunErrorResponse extends BaseSdkConnectionSocketMessageResponseCallbackAck {
|
|
647
|
+
success?: boolean;
|
|
648
|
+
}
|