168 lines
4.7 KiB
TypeScript
168 lines
4.7 KiB
TypeScript
import { createErrorResponse, ToolResult } from '@/common/tool-handler';
|
|
import { BaseBrowserToolExecutor } from '../base-browser';
|
|
import { TOOL_NAMES } from 'chrome-mcp-shared';
|
|
import { TOOL_MESSAGE_TYPES } from '@/common/message-types';
|
|
import { TIMEOUTS, ERROR_MESSAGES } from '@/common/constants';
|
|
|
|
interface Coordinates {
|
|
x: number;
|
|
y: number;
|
|
}
|
|
|
|
interface ClickToolParams {
|
|
selector?: string; // CSS selector for the element to click
|
|
coordinates?: Coordinates; // Coordinates to click at (x, y relative to viewport)
|
|
waitForNavigation?: boolean; // Whether to wait for navigation to complete after click
|
|
timeout?: number; // Timeout in milliseconds for waiting for the element or navigation
|
|
}
|
|
|
|
/**
|
|
* Tool for clicking elements on web pages
|
|
*/
|
|
class ClickTool extends BaseBrowserToolExecutor {
|
|
name = TOOL_NAMES.BROWSER.CLICK;
|
|
|
|
/**
|
|
* Execute click operation
|
|
*/
|
|
async execute(args: ClickToolParams): Promise<ToolResult> {
|
|
const {
|
|
selector,
|
|
coordinates,
|
|
waitForNavigation = false,
|
|
timeout = TIMEOUTS.DEFAULT_WAIT * 5,
|
|
} = args;
|
|
|
|
console.log(`Starting click operation with options:`, args);
|
|
|
|
if (!selector && !coordinates) {
|
|
return createErrorResponse(
|
|
ERROR_MESSAGES.INVALID_PARAMETERS + ': Either selector or coordinates must be provided',
|
|
);
|
|
}
|
|
|
|
try {
|
|
// Get current tab
|
|
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
if (!tabs[0]) {
|
|
return createErrorResponse(ERROR_MESSAGES.TAB_NOT_FOUND);
|
|
}
|
|
|
|
const tab = tabs[0];
|
|
if (!tab.id) {
|
|
return createErrorResponse(ERROR_MESSAGES.TAB_NOT_FOUND + ': Active tab has no ID');
|
|
}
|
|
|
|
await this.injectContentScript(tab.id, ['inject-scripts/click-helper.js']);
|
|
|
|
// Send click message to content script
|
|
const result = await this.sendMessageToTab(tab.id, {
|
|
action: TOOL_MESSAGE_TYPES.CLICK_ELEMENT,
|
|
selector,
|
|
coordinates,
|
|
waitForNavigation,
|
|
timeout,
|
|
});
|
|
|
|
return {
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: JSON.stringify({
|
|
success: true,
|
|
message: result.message || 'Click operation successful',
|
|
elementInfo: result.elementInfo,
|
|
navigationOccurred: result.navigationOccurred,
|
|
clickMethod: coordinates ? 'coordinates' : 'selector',
|
|
}),
|
|
},
|
|
],
|
|
isError: false,
|
|
};
|
|
} catch (error) {
|
|
console.error('Error in click operation:', error);
|
|
return createErrorResponse(
|
|
`Error performing click: ${error instanceof Error ? error.message : String(error)}`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export const clickTool = new ClickTool();
|
|
|
|
interface FillToolParams {
|
|
selector: string;
|
|
value: string;
|
|
}
|
|
|
|
/**
|
|
* Tool for filling form elements on web pages
|
|
*/
|
|
class FillTool extends BaseBrowserToolExecutor {
|
|
name = TOOL_NAMES.BROWSER.FILL;
|
|
|
|
/**
|
|
* Execute fill operation
|
|
*/
|
|
async execute(args: FillToolParams): Promise<ToolResult> {
|
|
const { selector, value } = args;
|
|
|
|
console.log(`Starting fill operation with options:`, args);
|
|
|
|
if (!selector) {
|
|
return createErrorResponse(ERROR_MESSAGES.INVALID_PARAMETERS + ': Selector must be provided');
|
|
}
|
|
|
|
if (value === undefined || value === null) {
|
|
return createErrorResponse(ERROR_MESSAGES.INVALID_PARAMETERS + ': Value must be provided');
|
|
}
|
|
|
|
try {
|
|
// Get current tab
|
|
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
if (!tabs[0]) {
|
|
return createErrorResponse(ERROR_MESSAGES.TAB_NOT_FOUND);
|
|
}
|
|
|
|
const tab = tabs[0];
|
|
if (!tab.id) {
|
|
return createErrorResponse(ERROR_MESSAGES.TAB_NOT_FOUND + ': Active tab has no ID');
|
|
}
|
|
|
|
await this.injectContentScript(tab.id, ['inject-scripts/fill-helper.js']);
|
|
|
|
// Send fill message to content script
|
|
const result = await this.sendMessageToTab(tab.id, {
|
|
action: TOOL_MESSAGE_TYPES.FILL_ELEMENT,
|
|
selector,
|
|
value,
|
|
});
|
|
|
|
if (result.error) {
|
|
return createErrorResponse(result.error);
|
|
}
|
|
|
|
return {
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: JSON.stringify({
|
|
success: true,
|
|
message: result.message || 'Fill operation successful',
|
|
elementInfo: result.elementInfo,
|
|
}),
|
|
},
|
|
],
|
|
isError: false,
|
|
};
|
|
} catch (error) {
|
|
console.error('Error in fill operation:', error);
|
|
return createErrorResponse(
|
|
`Error filling element: ${error instanceof Error ? error.message : String(error)}`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export const fillTool = new FillTool();
|