Skip to content

Commit 4ab6446

Browse files
authored
Feat/eval js (#70)
- Added endpoint to evaluate JS (primarily for as a verification for benchmarks) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Added `/page/execute` API endpoint to execute JavaScript code within browser tabs * Supports dynamic code evaluation with options for asynchronous operations and return value handling * Includes execution logging and exception reporting for error tracking <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent e387e90 commit 4ab6446

File tree

2 files changed

+106
-0
lines changed

2 files changed

+106
-0
lines changed

agent-server/nodejs/src/api-server.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,14 @@ class APIServer {
147147
result = await this.getScreenshot(JSON.parse(body));
148148
break;
149149

150+
case '/page/execute':
151+
if (method !== 'POST') {
152+
this.sendError(res, 405, 'Method not allowed');
153+
return;
154+
}
155+
result = await this.executeJavaScript(JSON.parse(body));
156+
break;
157+
150158
default:
151159
this.sendError(res, 404, 'Not found');
152160
return;
@@ -336,6 +344,36 @@ class APIServer {
336344
};
337345
}
338346

347+
async executeJavaScript(payload) {
348+
const { clientId, tabId, expression, returnByValue = true, awaitPromise = false } = payload;
349+
350+
if (!clientId) {
351+
throw new Error('Client ID is required');
352+
}
353+
354+
if (!tabId) {
355+
throw new Error('Tab ID is required');
356+
}
357+
358+
if (!expression) {
359+
throw new Error('JavaScript expression is required');
360+
}
361+
362+
const baseClientId = clientId.split(':')[0];
363+
364+
logger.info('Executing JavaScript', { baseClientId, tabId, expression: expression.substring(0, 100) });
365+
366+
const result = await this.browserAgentServer.evaluateJavaScript(tabId, expression, { returnByValue, awaitPromise });
367+
368+
return {
369+
clientId: baseClientId,
370+
tabId: result.tabId,
371+
result: result.result,
372+
exceptionDetails: result.exceptionDetails,
373+
timestamp: Date.now()
374+
};
375+
}
376+
339377
/**
340378
* Handle OpenAI Responses API compatible requests with nested model format
341379
*/

agent-server/nodejs/src/lib/BrowserAgentServer.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,6 +1220,74 @@ export class BrowserAgentServer extends EventEmitter {
12201220
}
12211221
}
12221222

1223+
/**
1224+
* Execute JavaScript in a browser tab
1225+
* @param {string} tabId - Tab ID (target ID)
1226+
* @param {string} expression - JavaScript expression to execute
1227+
* @param {Object} options - Execution options
1228+
* @param {boolean} options.returnByValue - Whether to return by value (default: true)
1229+
* @param {boolean} options.awaitPromise - Whether to await promises (default: false)
1230+
* @returns {Promise<Object>} Result with execution result
1231+
*/
1232+
async evaluateJavaScript(tabId, expression, options = {}) {
1233+
const { returnByValue = true, awaitPromise = false } = options;
1234+
1235+
try {
1236+
logger.info('Executing JavaScript via CDP', { tabId, expressionLength: expression.length });
1237+
1238+
// Use Runtime.evaluate to execute JavaScript
1239+
const result = await this.sendCDPCommandToTarget(tabId, 'Runtime.evaluate', {
1240+
expression,
1241+
returnByValue,
1242+
awaitPromise
1243+
});
1244+
1245+
if (result.exceptionDetails) {
1246+
logger.warn('JavaScript execution threw exception', {
1247+
tabId,
1248+
exception: result.exceptionDetails
1249+
});
1250+
} else {
1251+
logger.info('JavaScript executed successfully', {
1252+
tabId,
1253+
resultType: result.result?.type
1254+
});
1255+
}
1256+
1257+
// Extract value from CDP RemoteObject
1258+
// CDP returns RemoteObject with structure: {type: 'string', value: 'foo'}
1259+
// For undefined/null, CDP returns: {type: 'undefined'} or {type: 'null', value: null}
1260+
// We need to check if 'value' property exists, not if it's undefined
1261+
let extractedResult;
1262+
if (result.result) {
1263+
if ('value' in result.result) {
1264+
// RemoteObject has a value field - extract it
1265+
extractedResult = result.result.value;
1266+
} else if (result.result.type === 'undefined') {
1267+
// Special case: undefined has no value field
1268+
extractedResult = undefined;
1269+
} else {
1270+
// For objects/functions without returnByValue, return the whole RemoteObject
1271+
extractedResult = result.result;
1272+
}
1273+
} else {
1274+
extractedResult = result.result;
1275+
}
1276+
1277+
return {
1278+
tabId,
1279+
result: extractedResult,
1280+
exceptionDetails: result.exceptionDetails
1281+
};
1282+
} catch (error) {
1283+
logger.error('Failed to execute JavaScript via CDP', {
1284+
tabId,
1285+
error: error.message
1286+
});
1287+
throw error;
1288+
}
1289+
}
1290+
12231291
}
12241292

12251293
/**

0 commit comments

Comments
 (0)