Skip to content

Commit 85da86c

Browse files
Daedaeliusoleshoclaude
authored
Added API layer for tool calls (#89)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Direct tool execution without LLM orchestration, returning structured results with execution time and metadata. * Public endpoints to list and run available tools, plus actions: click, type, scroll, navigate, hover. * Accessibility-tree retrieval and OpenAI-compatible execution result formatting. * Execution tracing/logging surfaced for diagnostics. * **Chores** * Configurable upstream branch for browser operator builds and updated bundled agent asset. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Oleh Luchkiv <olesho@gmail.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent cce0d15 commit 85da86c

8 files changed

Lines changed: 918 additions & 6 deletions

File tree

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

Lines changed: 507 additions & 0 deletions
Large diffs are not rendered by default.

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

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,6 +1418,55 @@ export class BrowserAgentServer extends EventEmitter {
14181418
}
14191419
}
14201420

1421+
/**
1422+
* Execute a tool directly on a connected DevTools client
1423+
* This bypasses LLM orchestration and calls the tool directly
1424+
* @param {Object} connection - DevTools WebSocket connection
1425+
* @param {string} tool - Tool name (e.g., 'perform_action', 'navigate_url')
1426+
* @param {Object} args - Tool-specific arguments
1427+
* @param {number} timeout - Execution timeout in milliseconds
1428+
* @returns {Promise<Object>} Tool execution result
1429+
*/
1430+
async executeToolDirect(connection, tool, args, timeout = 30000) {
1431+
const rpcId = `tool-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
1432+
1433+
logger.info('Executing tool directly', {
1434+
clientId: connection.clientId,
1435+
tool,
1436+
timeout
1437+
});
1438+
1439+
try {
1440+
// Prepare RPC request for execute_tool method
1441+
const response = await connection.rpcClient.callMethod(
1442+
connection.ws,
1443+
'execute_tool',
1444+
{
1445+
tool,
1446+
args,
1447+
timeout
1448+
},
1449+
timeout + 5000 // Add buffer for network overhead
1450+
);
1451+
1452+
logger.info('Tool execution completed', {
1453+
clientId: connection.clientId,
1454+
tool,
1455+
success: response?.result?.success
1456+
});
1457+
1458+
return response;
1459+
1460+
} catch (error) {
1461+
logger.error('Tool execution failed', {
1462+
clientId: connection.clientId,
1463+
tool,
1464+
error: error.message
1465+
});
1466+
throw error;
1467+
}
1468+
}
1469+
14211470
/**
14221471
* Execute JavaScript in a browser tab
14231472
* @param {string} tabId - Tab ID (target ID)

config/gni/devtools_grd_files.gni

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -867,7 +867,7 @@ grd_files_bundled_sources = [
867867
"front_end/panels/ai_chat/agent_framework/implementation/agents/ScrollActionAgent.js",
868868
"front_end/panels/ai_chat/agent_framework/implementation/agents/WebTaskAgent.js",
869869
"front_end/panels/ai_chat/agent_framework/implementation/agents/SearchAgent.js",
870-
"front_end/panels/ai_chat/agent_framework/implementation/agents/ActionAgentV0.js",
870+
"front_end/panels/ai_chat/agent_framework/implementation/agents/ActionAgentV1.js",
871871
"front_end/panels/ai_chat/agent_framework/implementation/agents/ActionAgentV2.js",
872872
"front_end/panels/ai_chat/common/MarkdownViewerUtil.js",
873873
"front_end/panels/ai_chat/evaluation/runner/VisionAgentEvaluationRunner.js",

docker/Dockerfile

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,22 +43,27 @@ RUN /workspace/depot_tools/ensure_bootstrap
4343
RUN npm run build
4444

4545
# Add Browser Operator fork and switch to it
46+
# Branch is configurable via build arg (default: main)
47+
ARG BROWSER_OPERATOR_BRANCH=main
4648
RUN git remote add upstream https://github.com/BrowserOperator/browser-operator-core.git
4749
RUN git fetch upstream
48-
RUN git checkout upstream/main
50+
RUN git checkout upstream/${BROWSER_OPERATOR_BRANCH}
4951

5052
# Copy local LLM changes on top of the upstream code
51-
# This allows iterative development without breaking BUILD.gn compatibility
53+
# This allows iterative development of LLM providers without rebuilding everything
5254
COPY front_end/panels/ai_chat/LLM /workspace/devtools/devtools-frontend/front_end/panels/ai_chat/LLM
5355

56+
# Regenerate GN build files
57+
RUN gn gen out/Default
58+
5459
# AUTOMATED_MODE: Default true for API mode (Type 2/3). Override with --build-arg AUTOMATED_MODE=false for Type 1.
5560
ARG AUTOMATED_MODE=true
5661
RUN if [ "$AUTOMATED_MODE" = "true" ]; then \
5762
sed -i 's/AUTOMATED_MODE: false/AUTOMATED_MODE: true/' \
5863
front_end/panels/ai_chat/core/BuildConfig.ts; \
5964
fi
6065

61-
# Build Browser Operator version with local changes
66+
# Build Browser Operator version with local changes (fresh GN generation)
6267
RUN npm run build
6368

6469
# ==============================================================================

front_end/panels/ai_chat/evaluation/EvaluationAgent.ts

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,23 @@ import {
2020
EvaluationRequest,
2121
EvaluationSuccessResponse,
2222
EvaluationErrorResponse,
23+
ToolExecutionRequest,
24+
ToolExecutionSuccessResponse,
25+
ToolExecutionErrorResponse,
2326
ErrorCodes,
2427
isWelcomeMessage,
2528
isRegistrationAckMessage,
2629
isEvaluationRequest,
30+
isToolExecutionRequest,
2731
isPongMessage,
2832
createRegisterMessage,
2933
createReadyMessage,
3034
createAuthVerifyMessage,
3135
createStatusMessage,
3236
createSuccessResponse,
33-
createErrorResponse
37+
createErrorResponse,
38+
createToolExecutionSuccessResponse,
39+
createToolExecutionErrorResponse
3440
} from './EvaluationProtocol.js';
3541

3642
const logger = createLogger('EvaluationAgent');
@@ -171,6 +177,9 @@ export class EvaluationAgent {
171177
else if (isEvaluationRequest(message)) {
172178
await this.handleEvaluationRequest(message);
173179
}
180+
else if (isToolExecutionRequest(message)) {
181+
await this.handleToolExecutionRequest(message);
182+
}
174183
else if (isPongMessage(message)) {
175184
logger.debug('Received pong');
176185
}
@@ -599,6 +608,91 @@ export class EvaluationAgent {
599608
}
600609
}
601610

611+
/**
612+
* Handle direct tool execution request (no LLM orchestration)
613+
* This allows calling browser automation tools directly via API
614+
*/
615+
private async handleToolExecutionRequest(request: ToolExecutionRequest): Promise<void> {
616+
const { params, id } = request;
617+
const startTime = Date.now();
618+
619+
logger.info('Received tool execution request', {
620+
tool: params.tool,
621+
hasArgs: !!params.args,
622+
timeout: params.timeout
623+
});
624+
625+
try {
626+
// Get the tool from registry
627+
const tool = ToolRegistry.getRegisteredTool(params.tool);
628+
if (!tool) {
629+
const errorResponse = createToolExecutionErrorResponse(
630+
id,
631+
ErrorCodes.INVALID_TOOL,
632+
`Tool not found: ${params.tool}`,
633+
params.tool,
634+
`Tool '${params.tool}' is not registered in the ToolRegistry`
635+
);
636+
if (this.client) {
637+
this.client.send(errorResponse);
638+
}
639+
return;
640+
}
641+
642+
// Execute the tool directly (no LLM, no navigation, no retries)
643+
const timeout = params.timeout || 30000;
644+
const result = await this.executeToolWithTimeout(
645+
tool,
646+
params.args,
647+
timeout,
648+
undefined, // No tracing context for direct tool calls
649+
params.tool
650+
);
651+
652+
const executionTime = Date.now() - startTime;
653+
654+
// Send success response
655+
const successResponse = createToolExecutionSuccessResponse(
656+
id,
657+
params.tool,
658+
result,
659+
executionTime
660+
);
661+
662+
if (this.client) {
663+
this.client.send(successResponse);
664+
}
665+
666+
logger.info('Tool execution completed', {
667+
tool: params.tool,
668+
executionTime,
669+
success: true
670+
});
671+
672+
} catch (error) {
673+
const executionTime = Date.now() - startTime;
674+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
675+
676+
logger.error(`Tool execution failed: ${errorMessage}`, {
677+
tool: params.tool,
678+
executionTime
679+
});
680+
681+
// Send error response
682+
const errorResponse = createToolExecutionErrorResponse(
683+
id,
684+
ErrorCodes.TOOL_EXECUTION_ERROR,
685+
'Tool execution failed',
686+
params.tool,
687+
errorMessage
688+
);
689+
690+
if (this.client) {
691+
this.client.send(errorResponse);
692+
}
693+
}
694+
}
695+
602696
private async executeToolWithTimeout(
603697
tool: any,
604698
input: any,

front_end/panels/ai_chat/evaluation/EvaluationProtocol.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,44 @@ export interface EvaluationRequest {
7979
id: string;
8080
}
8181

82+
// Direct tool execution request (no LLM orchestration)
83+
export interface ToolExecutionRequest {
84+
jsonrpc: '2.0';
85+
method: 'execute_tool';
86+
params: ToolExecutionParams;
87+
id: string;
88+
}
89+
90+
export interface ToolExecutionParams {
91+
tool: string; // Tool name (e.g., 'perform_action', 'navigate_url')
92+
args: any; // Tool-specific arguments
93+
timeout?: number; // Optional timeout (default 30000ms)
94+
}
95+
96+
export interface ToolExecutionSuccessResponse {
97+
jsonrpc: '2.0';
98+
result: {
99+
success: true;
100+
output: any;
101+
executionTime: number;
102+
tool: string;
103+
};
104+
id: string;
105+
}
106+
107+
export interface ToolExecutionErrorResponse {
108+
jsonrpc: '2.0';
109+
error: {
110+
code: number;
111+
message: string;
112+
data?: {
113+
tool: string;
114+
error: string;
115+
};
116+
};
117+
id: string;
118+
}
119+
82120
export interface EvaluationParams {
83121
evaluationId: string;
84122
name: string;
@@ -170,6 +208,10 @@ export function isEvaluationRequest(msg: any): msg is EvaluationRequest {
170208
return msg?.jsonrpc === '2.0' && msg?.method === 'evaluate';
171209
}
172210

211+
export function isToolExecutionRequest(msg: any): msg is ToolExecutionRequest {
212+
return msg?.jsonrpc === '2.0' && msg?.method === 'execute_tool';
213+
}
214+
173215
export function isPongMessage(msg: any): msg is PongMessage {
174216
return msg?.type === 'pong';
175217
}
@@ -254,4 +296,43 @@ export function createErrorResponse(
254296
},
255297
id
256298
};
299+
}
300+
301+
export function createToolExecutionSuccessResponse(
302+
id: string,
303+
tool: string,
304+
output: any,
305+
executionTime: number
306+
): ToolExecutionSuccessResponse {
307+
return {
308+
jsonrpc: '2.0',
309+
result: {
310+
success: true,
311+
output,
312+
executionTime,
313+
tool
314+
},
315+
id
316+
};
317+
}
318+
319+
export function createToolExecutionErrorResponse(
320+
id: string,
321+
code: number,
322+
message: string,
323+
tool: string,
324+
error: string
325+
): ToolExecutionErrorResponse {
326+
return {
327+
jsonrpc: '2.0',
328+
error: {
329+
code,
330+
message,
331+
data: {
332+
tool,
333+
error
334+
}
335+
},
336+
id
337+
};
257338
}

0 commit comments

Comments
 (0)