UNPKG

mycoder-agent

Version:

Agent module for mycoder - an AI-powered software development assistant

187 lines 8.52 kB
import { z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; import { errorToString } from '../../utils/errorToString.js'; import { sleep } from '../../utils/sleep.js'; import { filterPageContent } from './lib/filterPageContent.js'; import { browserSessions, SelectorType } from './lib/types.js'; import { SessionStatus } from './SessionTracker.js'; // Main parameter schema const parameterSchema = z.object({ instanceId: z.string().describe('The ID returned by sessionStart'), actionType: z .enum(['goto', 'click', 'type', 'wait', 'content', 'close']) .describe('Browser action to perform'), url: z .string() .url() .optional() .describe('URL to navigate to if "goto" actionType'), selector: z .string() .optional() .describe('Selector to click if "click" actionType'), selectorType: z .nativeEnum(SelectorType) .optional() .describe('Type of selector if "click" actionType'), text: z .string() .optional() .describe('Text to type if "type" actionType, for other actionType, this is ignored'), description: z .string() .describe('The reason for this browser action (max 80 chars)'), }); // Return schema const returnSchema = z.object({ status: z.string(), content: z.string().optional(), error: z.string().optional(), }); // Helper function to handle selectors const getSelector = (selector, type) => { switch (type) { case SelectorType.XPATH: return `xpath=${selector}`; case SelectorType.TEXT: return `text=${selector}`; default: return selector; // CSS selector is default } }; export const sessionMessageTool = { name: 'sessionMessage', logPrefix: '🏄', description: 'Performs actions in an active browser session', parameters: parameterSchema, parametersJsonSchema: zodToJsonSchema(parameterSchema), returns: returnSchema, returnsJsonSchema: zodToJsonSchema(returnSchema), execute: async ({ instanceId, actionType, url, selector, selectorType, text }, { logger, pageFilter, browserTracker, ..._ }) => { // Validate action format if (!actionType) { logger.error('Invalid action format: actionType is required'); return { status: 'error', error: 'Invalid action format: actionType is required', }; } logger.debug(`Executing browser action: ${actionType}`); logger.debug(`Webpage processing mode: ${pageFilter}`); try { const session = browserSessions.get(instanceId); if (!session) { throw new Error(`No browser session found with ID ${instanceId}`); } const { page } = session; switch (actionType) { case 'goto': { if (!url) { throw new Error('URL required for goto action'); } try { // Try with 'domcontentloaded' first which is more reliable than 'networkidle' logger.debug(`Navigating to ${url} with 'domcontentloaded' waitUntil`); await page.goto(url, { waitUntil: 'domcontentloaded' }); await sleep(3000); const content = await filterPageContent(page, pageFilter); logger.debug(`Content: ${content}`); logger.debug('Navigation completed with domcontentloaded strategy'); logger.debug(`Content length: ${content.length} characters`); return { status: 'success', content }; } catch (navError) { // If that fails, try with no waitUntil option logger.warn(`Failed with domcontentloaded strategy: ${errorToString(navError)}`); logger.debug(`Retrying navigation to ${url} with no waitUntil option`); try { await page.goto(url); await sleep(3000); const content = await filterPageContent(page, pageFilter); logger.debug(`Content: ${content}`); logger.debug('Navigation completed with basic strategy'); return { status: 'success', content }; } catch (innerError) { logger.error(`Failed with basic navigation strategy: ${errorToString(innerError)}`); throw innerError; // Re-throw to be caught by outer catch block } } } case 'click': { if (!selector) { throw new Error('Selector required for click action'); } const clickSelector = getSelector(selector, selectorType); await page.click(clickSelector); await sleep(1000); // Wait for any content changes after click const content = await filterPageContent(page, pageFilter); logger.debug(`Click action completed on selector: ${clickSelector}`); return { status: 'success', content }; } case 'type': { if (!selector || !text) { throw new Error('Selector and text required for type action'); } const typeSelector = getSelector(selector, selectorType); await page.fill(typeSelector, text); logger.debug(`Type action completed on selector: ${typeSelector}`); return { status: 'success' }; } case 'wait': { if (!selector) { throw new Error('Selector required for wait action'); } const waitSelector = getSelector(selector, selectorType); await page.waitForSelector(waitSelector); logger.debug(`Wait action completed for selector: ${waitSelector}`); return { status: 'success' }; } case 'content': { const content = await filterPageContent(page, pageFilter); logger.debug('Page content retrieved successfully'); logger.debug(`Content length: ${content.length} characters`); return { status: 'success', content }; } case 'close': { await session.page.context().close(); await session.browser.close(); browserSessions.delete(instanceId); // Update browser tracker when browser is explicitly closed browserTracker.updateSessionStatus(instanceId, SessionStatus.COMPLETED, { closedExplicitly: true, }); logger.debug('Browser session closed successfully'); return { status: 'closed' }; } default: { throw new Error(`Unsupported action type: ${actionType}`); } } } catch (error) { logger.error('Browser action failed:', { error }); // Update browser tracker with error status if action fails browserTracker.updateSessionStatus(instanceId, SessionStatus.ERROR, { error: errorToString(error), actionType, }); return { status: 'error', error: errorToString(error), }; } }, logParameters: ({ actionType, description }, { logger, pageFilter = 'simple' }) => { logger.log(`Performing browser action: ${actionType} with ${pageFilter} processing, ${description}`); }, logReturns: (output, { logger }) => { if (output.error) { logger.error(`Browser action failed: ${output.error}`); } else { logger.log(`Browser action completed with status: ${output.status}`); } }, }; //# sourceMappingURL=sessionMessage.js.map