diff --git a/.gitignore b/.gitignore index 7c3cd722..423f2e81 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ Thumbs.db # npm package lock files (if you don't want them tracked by Git) package-lock.json yarn.lock +pnpm-lock.yaml # Test coverage directory coverage/ diff --git a/eko-browser-extension-template b/eko-browser-extension-template deleted file mode 160000 index 53e406d1..00000000 --- a/eko-browser-extension-template +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 53e406d199be0eb4d11c8cb5766aa387886f130f diff --git a/package.json b/package.json index d677d9ed..519395f3 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "chromium-bidi": "^0.12.0", "dotenv": "^16.0.0", "html2canvas": "^1.4.1", + "loglevel": "^1.9.2", "openai": "^4.77.0", "playwright": "^1.49.1", "uuid": "^11.0.3", diff --git a/src/common/chrome/proxy.ts b/src/common/chrome/proxy.ts index 0f762d09..95086ef0 100644 --- a/src/common/chrome/proxy.ts +++ b/src/common/chrome/proxy.ts @@ -21,9 +21,10 @@ * ``` * In this example, `tabs_get` is a mock implementation that logs the `tabId` before calling the original `chrome.tabs.get` method, and the same as `chrome.windows.create` method. */ +import { logger } from '../log'; export function createChromeApiProxy(mockClass: any): any { - console.log("debug mockClass:"); - console.log(mockClass); + logger.debug("debug mockClass:"); + logger.debug(mockClass); return new Proxy(chrome, { get(target: any, prop: string | symbol) { diff --git a/src/common/log.ts b/src/common/log.ts new file mode 100644 index 00000000..c3476eb2 --- /dev/null +++ b/src/common/log.ts @@ -0,0 +1,43 @@ +import log from 'loglevel'; + +export class EkoLoggerFactory { + private logger: log.Logger; + private logStorage: string[] = []; + + constructor() { + this.logger = log.getLogger('EkoLogger'); + this.logger.setLevel(log.levels.TRACE); + + const originalFactory = this.logger.methodFactory; + + this.logger.methodFactory = (methodName, logLevel, loggerName) => { + const rawMethod = originalFactory(methodName, logLevel, loggerName); + return (...args: any[]) => { + const truncatedArgs = args.map(arg => this.toReadableString(arg)); + this.logStorage.push(`[${methodName.toUpperCase()}] ${truncatedArgs.join(' ')}`); + rawMethod(...truncatedArgs); + }; + }; + + } + // truncate content if it exceeds 2048 characters + private toReadableString(content: any): string { + const contentString = JSON.stringify(content); + const maxLength = 2048; + if (contentString.length > maxLength) { + return contentString.substring(0, maxLength - 3) + '...'; + } else { + return contentString; + } + } + + public getLoggerInstance(): log.Logger { + return this.logger; + } + + public getAllLogs(): string[] { + return this.logStorage; + } +} + +export const logger = new EkoLoggerFactory().getLoggerInstance(); diff --git a/src/common/tools/cancel_workflow.ts b/src/common/tools/cancel_workflow.ts index 4f682f1d..8f8126bb 100644 --- a/src/common/tools/cancel_workflow.ts +++ b/src/common/tools/cancel_workflow.ts @@ -1,5 +1,6 @@ import { CancelWorkflowInput } from '../../types/tools.types'; import { Tool, InputSchema, ExecutionContext } from '../../types/action.types'; +import { logger } from '../../common/log'; export class CancelWorkflow implements Tool { name: string; @@ -26,7 +27,7 @@ export class CancelWorkflow implements Tool { throw new Error('Invalid parameters. Expected an object with a "reason" property.'); } const reason = params.reason; - console.log("The workflow has been cancelled because: " + reason); + logger.debug("The workflow has been cancelled because: " + reason); await context.workflow?.cancel(); return; } diff --git a/src/common/tools/human.ts b/src/common/tools/human.ts index 8324c879..514f2851 100644 --- a/src/common/tools/human.ts +++ b/src/common/tools/human.ts @@ -9,6 +9,7 @@ import { HumanOperateResult, } from '../../types/tools.types'; import { Tool, InputSchema, ExecutionContext } from '../../types/action.types'; +import { logger } from '../../common/log'; export class HumanInputText implements Tool { name: string; @@ -35,20 +36,20 @@ export class HumanInputText implements Tool throw new Error('Invalid parameters. Expected an object with a "reason" property.'); } const reason = params.reason; - console.log("reason: " + reason); + logger.debug("reason: " + reason); let onHumanOperate = context.callback?.hooks.onHumanOperate; if (onHumanOperate) { let userOperation; try { userOperation = await onHumanOperate(reason); } catch (e) { - console.error(e); + logger.error(e); return {status: "`onHumanOperate` not implemented", userOperation: ""}; } - console.log("userOperation: " + userOperation); + logger.debug("userOperation: " + userOperation); return {status: "OK", userOperation: userOperation}; } else { - console.error("Cannot get user's operation."); + logger.error("Cannot get user's operation."); return {status: "Error: Cannot get user's operation.", userOperation: ""}; } } diff --git a/src/core/eko.ts b/src/core/eko.ts index c6d7d402..955f84e0 100644 --- a/src/core/eko.ts +++ b/src/core/eko.ts @@ -12,6 +12,7 @@ import { WorkflowResult } from '../types'; import { ToolRegistry } from './tool-registry'; +import { logger } from '../common/log'; /** * Eko core @@ -25,7 +26,7 @@ export class Eko { private workflowGeneratorMap = new Map(); constructor(llmConfig: LLMConfig, ekoConfig?: EkoConfig) { - console.info("using Eko@" + process.env.COMMIT_HASH); + logger.info("using Eko@" + process.env.COMMIT_HASH); this.llmProvider = LLMProviderFactory.buildLLMProvider(llmConfig); this.ekoConfig = this.buildEkoConfig(ekoConfig); this.registerTools(); @@ -33,7 +34,7 @@ export class Eko { private buildEkoConfig(ekoConfig: Partial | undefined): EkoConfig { if (!ekoConfig) { - console.warn("`ekoConfig` is missing when construct `Eko` instance"); + logger.warn("`ekoConfig` is missing when construct `Eko` instance"); } const defaultEkoConfig: EkoConfig = { workingWindowId: undefined, @@ -70,7 +71,7 @@ export class Eko { } }); } else { - console.warn("`ekoConfig.callback` is missing when construct `Eko` instance.") + logger.warn("`ekoConfig.callback` is missing when construct `Eko` instance.") } tools.forEach(tool => this.toolRegistry.registerTool(tool)); @@ -116,7 +117,7 @@ export class Eko { } const result = await workflow.execute(this.ekoConfig.callback); - console.log(result); + logger.debug(result); return result; } diff --git a/src/extension/content/index.ts b/src/extension/content/index.ts index b27dd87e..59f67c98 100644 --- a/src/extension/content/index.ts +++ b/src/extension/content/index.ts @@ -1,3 +1,5 @@ +import { logger } from '../../common/log'; + declare const eko: any; if (!(window as any).eko) { @@ -29,7 +31,7 @@ chrome.runtime.onMessage.addListener(function (request: any, sender: any, sendRe try { result = await eko.subListeners[request.event](request.params); } catch (e) { - console.log(e); + logger.error(e); } } sendResponse(result); @@ -94,7 +96,7 @@ chrome.runtime.onMessage.addListener(function (request: any, sender: any, sendRe } } } catch (e) { - console.log('onMessage error', e); + logger.error('onMessage error', e); sendResponse(false); } })(); @@ -178,7 +180,7 @@ function type(request: any): boolean { input.dispatchEvent(event); }); } - console.log('type', input, request, result); + logger.debug('type', input, request, result); return true; } @@ -196,7 +198,7 @@ function mouse_move(request: any): boolean { clientY: y, }); let result = document.body.dispatchEvent(event); - console.log('mouse_move', document.body, request, result); + logger.debug('mouse_move', document.body, request, result); return true; } @@ -234,7 +236,7 @@ function simulateMouseEvent(request: any, eventTypes: Array, button: 0 | button, // 0 left; 2 right }); let result = element.dispatchEvent(event); - console.log('simulateMouse', element, { ...request, eventTypes, button }, result); + logger.debug('simulateMouse', element, { ...request, eventTypes, button }, result); } return true; } @@ -272,7 +274,7 @@ function scroll_to(request: any): boolean { behavior: 'smooth', }); } - console.log('scroll_to', request); + logger.debug('scroll_to', request); return true; } diff --git a/src/extension/core.ts b/src/extension/core.ts index a26fd4be..0ab05e76 100644 --- a/src/extension/core.ts +++ b/src/extension/core.ts @@ -1,5 +1,6 @@ import * as tools from './tools'; import { Tool } from '../types'; +import { logger } from '../common/log'; export async function pub(chromeProxy: any, tabId: number, event: string, params: any): Promise { return await chromeProxy.tabs.sendMessage(tabId as number, { @@ -28,7 +29,7 @@ export function loadTools(): Map> { let instance = new tool(); toolsMap.set(instance.name || key, instance); } catch (e) { - console.error(`Failed to instantiate ${key}:`, e); + logger.error(`Failed to instantiate ${key}:`, e); } } } diff --git a/src/extension/script/build_dom_tree.js b/src/extension/script/build_dom_tree.js index 2b6e4af5..8ea17561 100644 --- a/src/extension/script/build_dom_tree.js +++ b/src/extension/script/build_dom_tree.js @@ -643,7 +643,7 @@ function build_dom_tree(doHighlightElements) { nodeData.children.push(...iframeChildren); } } catch (e) { - console.warn('Unable to access iframe:', node); + logger.warn('Unable to access iframe:', node); } } else { const children = Array.from(node.childNodes).map((child) => diff --git a/src/extension/tools/browser.ts b/src/extension/tools/browser.ts index a7889622..a6560de8 100644 --- a/src/extension/tools/browser.ts +++ b/src/extension/tools/browser.ts @@ -1,5 +1,6 @@ import { ScreenshotResult } from '../../types/tools.types'; import { getPageSize } from '../utils'; +import { logger } from '../../common/log'; export async function type( chromeProxy: any, @@ -7,7 +8,7 @@ export async function type( text: string, coordinate?: [number, number] ): Promise { - console.log('Sending type message to tab:', tabId, { text, coordinate }); + logger.debug('Sending type message to tab:', tabId, { text, coordinate }); try { if (!coordinate) { coordinate = (await cursor_position(chromeProxy, tabId)).coordinate; @@ -18,10 +19,10 @@ export async function type( text, coordinate, }); - console.log('Got response:', response); + logger.debug('Got response:', response); return response; } catch (e) { - console.error('Failed to send type message:', e); + logger.error('Failed to send type message:', e); throw e; } } @@ -33,7 +34,7 @@ export async function type_by( xpath?: string, highlightIndex?: number ): Promise { - console.log('Sending type message to tab:', tabId, { text, xpath, highlightIndex }); + logger.debug('Sending type message to tab:', tabId, { text, xpath, highlightIndex }); try { const response = await chromeProxy.tabs.sendMessage(tabId, { type: 'computer:type', @@ -41,16 +42,16 @@ export async function type_by( xpath, highlightIndex, }); - console.log('Got response:', response); + logger.debug('Got response:', response); return response; } catch (e) { - console.error('Failed to send type message:', e); + logger.error('Failed to send type message:', e); throw e; } } export async function clear_input(chromeProxy: any, tabId: number, coordinate?: [number, number]): Promise { - console.log('Sending clear_input message to tab:', tabId, { coordinate }); + logger.debug('Sending clear_input message to tab:', tabId, { coordinate }); try { if (!coordinate) { coordinate = (await cursor_position(chromeProxy, tabId)).coordinate; @@ -61,10 +62,10 @@ export async function clear_input(chromeProxy: any, tabId: number, coordinate?: text: '', coordinate, }); - console.log('Got response:', response); + logger.debug('Got response:', response); return response; } catch (e) { - console.error('Failed to send clear_input message:', e); + logger.error('Failed to send clear_input message:', e); throw e; } } @@ -75,7 +76,7 @@ export async function clear_input_by( xpath?: string, highlightIndex?: number ): Promise { - console.log('Sending clear_input_by message to tab:', tabId, { xpath, highlightIndex }); + logger.debug('Sending clear_input_by message to tab:', tabId, { xpath, highlightIndex }); try { const response = await chromeProxy.tabs.sendMessage(tabId, { type: 'computer:type', @@ -83,31 +84,31 @@ export async function clear_input_by( xpath, highlightIndex, }); - console.log('Got response:', response); + logger.debug('Got response:', response); return response; } catch (e) { - console.error('Failed to send clear_input_by message:', e); + logger.error('Failed to send clear_input_by message:', e); throw e; } } export async function mouse_move(chromeProxy: any, tabId: number, coordinate: [number, number]): Promise { - console.log('Sending mouse_move message to tab:', tabId, { coordinate }); + logger.debug('Sending mouse_move message to tab:', tabId, { coordinate }); try { const response = await chromeProxy.tabs.sendMessage(tabId, { type: 'computer:mouse_move', coordinate, }); - console.log('Got response:', response); + logger.debug('Got response:', response); return response; } catch (e) { - console.error('Failed to send mouse_move message:', e); + logger.error('Failed to send mouse_move message:', e); throw e; } } export async function left_click(chromeProxy: any, tabId: number, coordinate?: [number, number]): Promise { - console.log('Sending left_click message to tab:', tabId, { coordinate }); + logger.debug('Sending left_click message to tab:', tabId, { coordinate }); try { if (!coordinate) { coordinate = (await cursor_position(chromeProxy, tabId)).coordinate; @@ -116,10 +117,10 @@ export async function left_click(chromeProxy: any, tabId: number, coordinate?: [ type: 'computer:left_click', coordinate, }); - console.log('Got response:', response); + logger.debug('Got response:', response); return response; } catch (e) { - console.error('Failed to send left_click message:', e); + logger.error('Failed to send left_click message:', e); throw e; } } @@ -130,23 +131,23 @@ export async function left_click_by( xpath?: string, highlightIndex?: number ): Promise { - console.log('Sending left_click_by message to tab:', tabId, { xpath, highlightIndex }); + logger.debug('Sending left_click_by message to tab:', tabId, { xpath, highlightIndex }); try { const response = await chromeProxy.tabs.sendMessage(tabId, { type: 'computer:left_click', xpath, highlightIndex, }); - console.log('Got response:', response); + logger.debug('Got response:', response); return response; } catch (e) { - console.error('Failed to send left_click_by message:', e); + logger.error('Failed to send left_click_by message:', e); throw e; } } export async function right_click(chromeProxy: any, tabId: number, coordinate?: [number, number]): Promise { - console.log('Sending right_click message to tab:', tabId, { coordinate }); + logger.debug('Sending right_click message to tab:', tabId, { coordinate }); try { if (!coordinate) { coordinate = (await cursor_position(chromeProxy, tabId)).coordinate; @@ -155,10 +156,10 @@ export async function right_click(chromeProxy: any, tabId: number, coordinate?: type: 'computer:right_click', coordinate, }); - console.log('Got response:', response); + logger.debug('Got response:', response); return response; } catch (e) { - console.error('Failed to send right_click message:', e); + logger.error('Failed to send right_click message:', e); throw e; } } @@ -169,23 +170,23 @@ export async function right_click_by( xpath?: string, highlightIndex?: number ): Promise { - console.log('Sending right_click_by message to tab:', tabId, { xpath, highlightIndex }); + logger.debug('Sending right_click_by message to tab:', tabId, { xpath, highlightIndex }); try { const response = await chromeProxy.tabs.sendMessage(tabId, { type: 'computer:right_click', xpath, highlightIndex, }); - console.log('Got response:', response); + logger.debug('Got response:', response); return response; } catch (e) { - console.error('Failed to send right_click_by message:', e); + logger.error('Failed to send right_click_by message:', e); throw e; } } export async function double_click(chromeProxy: any, tabId: number, coordinate?: [number, number]): Promise { - console.log('Sending double_click message to tab:', tabId, { coordinate }); + logger.debug('Sending double_click message to tab:', tabId, { coordinate }); try { if (!coordinate) { coordinate = (await cursor_position(chromeProxy, tabId)).coordinate; @@ -194,10 +195,10 @@ export async function double_click(chromeProxy: any, tabId: number, coordinate?: type: 'computer:double_click', coordinate, }); - console.log('Got response:', response); + logger.debug('Got response:', response); return response; } catch (e) { - console.error('Failed to send double_click message:', e); + logger.error('Failed to send double_click message:', e); throw e; } } @@ -208,23 +209,23 @@ export async function double_click_by( xpath?: string, highlightIndex?: number ): Promise { - console.log('Sending double_click_by message to tab:', tabId, { xpath, highlightIndex }); + logger.debug('Sending double_click_by message to tab:', tabId, { xpath, highlightIndex }); try { const response = await chromeProxy.tabs.sendMessage(tabId, { type: 'computer:double_click', xpath, highlightIndex, }); - console.log('Got response:', response); + logger.debug('Got response:', response); return response; } catch (e) { - console.error('Failed to send double_click_by message:', e); + logger.error('Failed to send double_click_by message:', e); throw e; } } export async function screenshot(chromeProxy: any, windowId: number, compress?: boolean): Promise { - console.log('Taking screenshot of window:', windowId, { compress }); + logger.debug('Taking screenshot of window:', windowId, { compress }); try { let dataUrl; if (compress) { @@ -247,10 +248,10 @@ export async function screenshot(chromeProxy: any, windowId: number, compress?: data: data, }, } as ScreenshotResult; - console.log('Got screenshot result:', result); + logger.debug('Got screenshot result:', result); return result; } catch (e) { - console.error('Failed to take screenshot:', e); + logger.error('Failed to take screenshot:', e); throw e; } } @@ -260,7 +261,7 @@ export async function compress_image( scale: number = 0.8, quality: number = 0.8 ): Promise { - console.log('Compressing image', { scale, quality }); + logger.debug('Compressing image', { scale, quality }); try { const bitmap = await createImageBitmap(await (await fetch(dataUrl)).blob()); let width = bitmap.width * scale; @@ -276,19 +277,19 @@ export async function compress_image( const reader = new FileReader(); reader.onloadend = () => { const result = reader.result as string; - console.log('Got compressed image result:', result); + logger.debug('Got compressed image result:', result); resolve(result); }; reader.readAsDataURL(blob); }); } catch (e) { - console.error('Failed to compress image:', e); + logger.error('Failed to compress image:', e); throw e; } } export async function scroll_to(chromeProxy: any, tabId: number, coordinate: [number, number]): Promise { - console.log('Sending scroll_to message to tab:', tabId, { coordinate }); + logger.debug('Sending scroll_to message to tab:', tabId, { coordinate }); try { let from_coordinate = (await cursor_position(chromeProxy, tabId)).coordinate; const response = await chromeProxy.tabs.sendMessage(tabId, { @@ -296,10 +297,10 @@ export async function scroll_to(chromeProxy: any, tabId: number, coordinate: [nu from_coordinate, to_coordinate: coordinate, }); - console.log('Got response:', response); + logger.debug('Got response:', response); return response; } catch (e) { - console.error('Failed to send scroll_to message:', e); + logger.error('Failed to send scroll_to message:', e); throw e; } } @@ -310,17 +311,17 @@ export async function scroll_to_by( xpath?: string, highlightIndex?: number ): Promise { - console.log('Sending scroll_to_by message to tab:', tabId, { xpath, highlightIndex }); + logger.debug('Sending scroll_to_by message to tab:', tabId, { xpath, highlightIndex }); try { const response = await chromeProxy.tabs.sendMessage(tabId, { type: 'computer:scroll_to', xpath, highlightIndex, }); - console.log('Got response:', response); + logger.debug('Got response:', response); return response; } catch (e) { - console.error('Failed to send scroll_to_by message:', e); + logger.error('Failed to send scroll_to_by message:', e); throw e; } } @@ -331,17 +332,17 @@ export async function get_dropdown_options( xpath?: string, highlightIndex?: number ): Promise { - console.log('Sending get_dropdown_options message to tab:', tabId, { xpath, highlightIndex }); + logger.debug('Sending get_dropdown_options message to tab:', tabId, { xpath, highlightIndex }); try { const response = await chromeProxy.tabs.sendMessage(tabId, { type: 'computer:get_dropdown_options', xpath, highlightIndex, }); - console.log('Got response:', response); + logger.debug('Got response:', response); return response; } catch (e) { - console.error('Failed to send get_dropdown_options message:', e); + logger.error('Failed to send get_dropdown_options message:', e); throw e; } } @@ -353,7 +354,7 @@ export async function select_dropdown_option( xpath?: string, highlightIndex?: number ): Promise { - console.log('Sending select_dropdown_option message to tab:', tabId, { text, xpath, highlightIndex }); + logger.debug('Sending select_dropdown_option message to tab:', tabId, { text, xpath, highlightIndex }); try { const response = await chromeProxy.tabs.sendMessage(tabId, { type: 'computer:select_dropdown_option', @@ -361,10 +362,10 @@ export async function select_dropdown_option( xpath, highlightIndex, }); - console.log('Got response:', response); + logger.debug('Got response:', response); return response; } catch (e) { - console.error('Failed to send select_dropdown_option message:', e); + logger.error('Failed to send select_dropdown_option message:', e); throw e; } } @@ -372,27 +373,27 @@ export async function select_dropdown_option( export async function cursor_position(chromeProxy: any, tabId: number): Promise<{ coordinate: [number, number]; }> { - console.log('Sending cursor_position message to tab:', tabId); + logger.debug('Sending cursor_position message to tab:', tabId); try { let result: any = await chromeProxy.tabs.sendMessage(tabId, { type: 'computer:cursor_position', }); - console.log('Got cursor position:', result.coordinate); + logger.debug('Got cursor position:', result.coordinate); return { coordinate: result.coordinate as [number, number] }; } catch (e) { - console.error('Failed to send cursor_position message:', e); + logger.error('Failed to send cursor_position message:', e); throw e; } } export async function size(chromeProxy: any, tabId?: number): Promise<[number, number]> { - console.log('Getting page size for tab:', tabId); + logger.debug('Getting page size for tab:', tabId); try { const pageSize = await getPageSize(chromeProxy, tabId); - console.log('Got page size:', pageSize); + logger.debug('Got page size:', pageSize); return pageSize; } catch (e) { - console.error('Failed to get page size:', e); + logger.error('Failed to get page size:', e); throw e; } } diff --git a/src/extension/tools/browser_use.ts b/src/extension/tools/browser_use.ts index 99331e4c..934a869a 100644 --- a/src/extension/tools/browser_use.ts +++ b/src/extension/tools/browser_use.ts @@ -2,6 +2,7 @@ import { BrowserUseParam, BrowserUseResult } from '../../types/tools.types'; import { Tool, InputSchema, ExecutionContext } from '../../types/action.types'; import { getWindowId, getTabId, sleep, injectScript, executeScript } from '../utils'; import * as browser from './browser'; +import { logger } from '../../common/log'; /** * Browser Use for general @@ -76,7 +77,7 @@ export class BrowserUse implements Tool { * @returns > { success: true, image?: { type: 'base64', media_type: 'image/jpeg', data: '/9j...' }, text?: string } */ async execute(context: ExecutionContext, params: BrowserUseParam): Promise { - console.log("execute 'browser_use'..."); + logger.debug("execute 'browser_use'..."); try { if (params === null || !params.action) { throw new Error('Invalid parameters. Expected an object with a "action" property.'); @@ -90,7 +91,7 @@ export class BrowserUse implements Tool { throw new Error('Could not get valid tab ID'); } } catch (e) { - console.error('Tab ID error:', e); + logger.error('Tab ID error:', e); return { success: false, error: 'Could not access browser tab' }; } let windowId = await getWindowId(context); @@ -178,39 +179,39 @@ export class BrowserUse implements Tool { ); break; case 'screenshot_extract_element': - console.log("execute 'screenshot_extract_element'..."); + logger.debug("execute 'screenshot_extract_element'..."); await sleep(100); - console.log("injectScript..."); + logger.debug("injectScript..."); await injectScript(context.ekoConfig.chromeProxy, tabId, 'build_dom_tree.js'); await sleep(100); - console.log("executeScript..."); + logger.debug("executeScript..."); let element_result = await executeScript(context.ekoConfig.chromeProxy, tabId, () => { return (window as any).get_clickable_elements(true); }, []); context.selector_map = element_result.selector_map; - console.log("browser.screenshot..."); + logger.debug("browser.screenshot..."); let screenshot = await browser.screenshot(context.ekoConfig.chromeProxy, windowId, true); - console.log("executeScript #2..."); + logger.debug("executeScript #2..."); await executeScript(context.ekoConfig.chromeProxy, tabId, () => { return (window as any).remove_highlight(); }, []); result = { image: screenshot.image, text: element_result.element_str }; - console.log("execute 'screenshot_extract_element'...done"); + logger.debug("execute 'screenshot_extract_element'...done"); break; default: throw Error( `Invalid parameters. The "${params.action}" value is not included in the "action" enumeration.` ); } - console.log("execute 'browser_use'...done, result="); - console.log(result); + logger.debug("execute 'browser_use'...done, result="); + logger.debug(result); if (result) { return { success: true, ...result }; } else { return { success: false }; } } catch (e: any) { - console.error('Browser use error:', e); + logger.error('Browser use error:', e); return { success: false, error: e?.message }; } } diff --git a/src/extension/tools/element_click.ts b/src/extension/tools/element_click.ts index f6573a2c..a5569363 100644 --- a/src/extension/tools/element_click.ts +++ b/src/extension/tools/element_click.ts @@ -4,6 +4,7 @@ import { executeScript, getTabId, getWindowId } from '../utils'; import { extractOperableElements, clickOperableElement } from './html_script'; import { left_click, screenshot } from './browser'; import { TaskPrompt } from '../../types/tools.types'; +import { logger } from '../../common/log'; /** * Element click @@ -37,7 +38,7 @@ export class ElementClick implements Tool { try { result = await executeWithHtmlElement(context, task_prompt); } catch (e) { - console.log(e); + logger.error(e); result = false; } if (!result) { diff --git a/src/extension/tools/find_element_position.ts b/src/extension/tools/find_element_position.ts index feb8de43..f001c204 100644 --- a/src/extension/tools/find_element_position.ts +++ b/src/extension/tools/find_element_position.ts @@ -4,6 +4,7 @@ import { TaskPrompt, ElementRect } from '../../types/tools.types'; import { executeScript, getTabId, getWindowId } from '../utils'; import { extractOperableElements, getOperableElementRect } from './html_script'; import { screenshot } from './browser'; +import { logger } from '../../common/log'; /** * Find Element Position @@ -37,7 +38,7 @@ export class FindElementPosition implements Tool try { result = await executeWithHtmlElement(context, task_prompt); } catch (e) { - console.log(e); + logger.error(e); result = null; } if (!result) { diff --git a/src/extension/tools/get_all_tabs.ts b/src/extension/tools/get_all_tabs.ts index 3bb7b27e..8bc703ca 100644 --- a/src/extension/tools/get_all_tabs.ts +++ b/src/extension/tools/get_all_tabs.ts @@ -1,6 +1,7 @@ import { BrowserTab } from '../../types/tools.types'; import { Tool, InputSchema, ExecutionContext } from '../../types/action.types'; import { getTabId, executeScript, injectScript, sleep } from '../utils'; +import { logger } from '../../common/log'; export class GetAllTabs implements Tool { name: string; @@ -24,7 +25,7 @@ export class GetAllTabs implements Tool { for (const tab of tabs) { if (tab.id === undefined) { - console.warn(`Tab ID is undefined for tab with URL: ${tab.url}`); + logger.warn(`Tab ID is undefined for tab with URL: ${tab.url}`); continue; } @@ -45,9 +46,9 @@ export class GetAllTabs implements Tool { description: description, }; - console.log("url: " + tab.url); - console.log("title: " + tab.title); - console.log("description: " + description); + logger.debug(`url: ${tab.url}`); + logger.debug(`title: ${tab.title}`); + logger.debug(`description: ${description}`); tabsInfo.push(tabInfo); } diff --git a/src/extension/tools/web_search.ts b/src/extension/tools/web_search.ts index 267110b9..95595cd1 100644 --- a/src/extension/tools/web_search.ts +++ b/src/extension/tools/web_search.ts @@ -1,6 +1,7 @@ import { WebSearchParam, WebSearchResult } from '../../types/tools.types'; import { Tool, InputSchema, ExecutionContext } from '../../types/action.types'; import { MsgEvent, CountDownLatch, sleep, injectScript } from '../utils'; +import { logger } from '../../common/log'; import { createChromeApiProxy } from '@/common/chrome/proxy'; /** @@ -158,7 +159,7 @@ async function deepSearch( let detailLinkGroups = await doDetailLinkGroups(context, taskId, searchs, detailsMaxNum, windowId); // crawler all details page content and comments let searchInfo = await doPageContent(context, taskId, detailLinkGroups, windowId); - console.log('searchInfo: ', searchInfo); + logger.debug('searchInfo: ', searchInfo); // close window closeWindow && context.ekoConfig.chromeProxy.windows.remove(windowId); return searchInfo; @@ -213,7 +214,7 @@ async function doDetailLinkGroups( // TODO error detailLinks = { links: [] }; } - console.log('detailLinks: ', detailLinks); + logger.debug('detailLinks: ', detailLinks); let links = detailLinks.links.slice(0, detailsMaxNum); detailLinkGroups.push({ url, links, filename }); countDownLatch.countDown(); @@ -225,7 +226,7 @@ async function doDetailLinkGroups( } }, eventId); } catch (e) { - console.error(e); + logger.error(e); countDownLatch.countDown(); } } @@ -327,7 +328,7 @@ async function doPageContent( try { await Promise.race([monitorTabPromise, timeoutPromise]); } catch (e) { - console.error(`${link.title} failed:`, e); + logger.error(`${link.title} failed:`, e); searchInfo.running--; searchInfo.failed++; searchInfo.failedLinks.push(link); diff --git a/src/extension/utils.ts b/src/extension/utils.ts index de30390f..d8373998 100644 --- a/src/extension/utils.ts +++ b/src/extension/utils.ts @@ -1,3 +1,4 @@ +import { logger } from '../common/log'; import { ExecutionContext } from '../types/action.types'; export async function getWindowId(context: ExecutionContext): Promise { @@ -31,7 +32,7 @@ export async function getWindowId(context: ExecutionContext): Promise { } if (!windowId) { - console.warn("`getWindowId()` returns " + windowId); + logger.warn("`getWindowId()` returns " + windowId); } return windowId as number; @@ -246,7 +247,7 @@ export class MsgEvent { await result; } } catch (e) { - console.error(e); + logger.error(e); } } } diff --git a/src/index.ts b/src/index.ts index 6f8bd00a..2d3cf5be 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,6 @@ import { ClaudeProvider } from './services/llm/claude-provider'; import { OpenaiProvider } from './services/llm/openai-provider'; import { WorkflowParser } from './services/parser/workflow-parser'; import { WorkflowGenerator } from "./services/workflow/generator" -import { ExecutionLogger } from './utils/execution-logger'; import { LLMProviderFactory } from './services/llm/provider-factory'; import { createChromeApiProxy } from './common/chrome/proxy'; @@ -17,7 +16,6 @@ export { OpenaiProvider, ToolRegistry, WorkflowParser, - ExecutionLogger, LLMProviderFactory, createChromeApiProxy, } diff --git a/src/models/action.ts b/src/models/action.ts index e35941ff..241b97a4 100644 --- a/src/models/action.ts +++ b/src/models/action.ts @@ -10,7 +10,7 @@ import { ToolDefinition, LLMResponse, } from '../types/llm.types'; -import { ExecutionLogger } from '@/utils/execution-logger'; +import { logger } from '../common/log'; import { WriteContextTool } from '@/common/tools/write_context'; function createReturnTool( @@ -53,7 +53,6 @@ export class ActionImpl implements Action { private readonly maxRounds: number = 10; // Default max rounds private writeContextTool: WriteContextTool; private toolResults: Map = new Map(); - private logger: ExecutionLogger = new ExecutionLogger(); constructor( public type: 'prompt', // Only support prompt type @@ -81,7 +80,6 @@ export class ActionImpl implements Action { hasToolUse: boolean; roundMessages: Message[]; }> { - this.logger = context.logger; const roundMessages: Message[] = []; let hasToolUse = false; let response: LLMResponse | null = null; @@ -108,8 +106,8 @@ export class ActionImpl implements Action { } }, onToolUse: async (toolCall) => { - this.logger.log('info', `Assistant: ${assistantTextMessage}`); - this.logger.logToolExecution(toolCall.name, toolCall.input, context); + logger.debug('debug', `Assistant: ${assistantTextMessage}`); + logger.debug(toolCall.name, toolCall.input, context); hasToolUse = true; const tool = toolMap.get(toolCall.name); @@ -200,14 +198,13 @@ export class ActionImpl implements Action { content: [resultContent], }; toolResultMessage = resultMessage; - this.logger.logToolResult(tool.name, result, context); + logger.debug(tool.name, result, context); // Store tool results except for the return_output tool if (tool.name !== 'return_output') { this.toolResults.set(toolCall.id, resultContentText); } } catch (err) { - console.log("An error occurred when calling tool:"); - console.log(err); + logger.error("An error occurred when calling tool:", err); const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred'; const errorResult: Message = { role: 'user', @@ -221,7 +218,7 @@ export class ActionImpl implements Action { ], }; toolResultMessage = errorResult; - this.logger.logError(err as Error, context); + logger.error(err as Error, context); } })(); }, @@ -229,8 +226,8 @@ export class ActionImpl implements Action { response = llmResponse; }, onError: (error) => { - console.error('Stream Error:', error); - console.log('Last message array sent to LLM:', JSON.stringify(messages, null, 2)); + logger.error('Stream Error:', error); + logger.debug('Last message array sent to LLM:', JSON.stringify(messages, null, 2)); }, }; @@ -300,7 +297,7 @@ export class ActionImpl implements Action { const finalImageCount = this.countImages(messages); if (initialImageCount !== finalImageCount) { - this.logger.log("info", `Removed ${initialImageCount - finalImageCount} images from history`); + logger.debug(`Removed ${initialImageCount - finalImageCount} images from history`); } } @@ -324,8 +321,7 @@ export class ActionImpl implements Action { context: ExecutionContext, outputSchema?: unknown ): Promise { - this.logger = context.logger; - console.log(`Executing action started: ${this.name}`); + logger.debug(`Executing action started: ${this.name}`); // Create return tool with output schema const returnTool = createReturnTool(this.name, output.description, outputSchema); @@ -341,7 +337,7 @@ export class ActionImpl implements Action { { role: 'user', content: this.formatUserPrompt(context, input) }, ]; - this.logger.logActionStart(this.name, input, context); + logger.debug(this.name, input, context); // Configure tool parameters const params: LLMParameters = { @@ -363,7 +359,7 @@ export class ActionImpl implements Action { } roundCount++; - this.logger.log('info', `Starting round ${roundCount} of ${this.maxRounds}`, context); + logger.debug(`Starting round ${roundCount} of ${this.maxRounds}`, context); const { response, hasToolUse, roundMessages } = await this.executeSingleRound( messages, @@ -380,17 +376,13 @@ export class ActionImpl implements Action { // Add round messages to conversation history messages.push(...roundMessages); - this.logger.log( - 'debug', - `Round ${roundCount} messages: ${JSON.stringify(roundMessages)}`, - context - ); + logger.debug(`Round ${roundCount} messages: ${JSON.stringify(roundMessages)}`, context); // Check termination conditions if (!hasToolUse && response) { // LLM sent a message without using tools - request explicit return - this.logger.log('info', `Assistant: ${response.textContent}`); - this.logger.log('warn', 'LLM sent a message without using tools; requesting explicit return'); + logger.debug(`Assistant: ${response.textContent}`) + logger.warn('LLM sent a message without using tools; requesting explicit return'); const returnOnlyParams = { ...params, tools: [ @@ -424,7 +416,7 @@ export class ActionImpl implements Action { // If this is the last round, force an explicit return if (roundCount === this.maxRounds) { - this.logger.log('warn', 'Max rounds reached, requesting explicit return'); + logger.warn('Max rounds reached, requesting explicit return'); const returnOnlyParams = { ...params, tools: [ @@ -467,7 +459,7 @@ export class ActionImpl implements Action { : outputParams?.value; if (outputValue === undefined) { - console.warn('Action completed without returning a value'); + logger.warn('Action completed without returning a value'); return {}; } diff --git a/src/models/workflow.ts b/src/models/workflow.ts index 991d0932..dcc13405 100644 --- a/src/models/workflow.ts +++ b/src/models/workflow.ts @@ -5,7 +5,6 @@ import { summarizeWorkflow } from "@/common/summarize-workflow"; export class WorkflowImpl implements Workflow { abort?: boolean; - private logger?: ExecutionLogger; abortControllers: Map = new Map(); constructor( @@ -17,16 +16,7 @@ export class WorkflowImpl implements Workflow { public nodes: WorkflowNode[] = [], public variables: Map = new Map(), public llmProvider?: LLMProvider, - loggerOptions?: LogOptions - ) { - if (loggerOptions) { - this.logger = new ExecutionLogger(loggerOptions); - } - } - - setLogger(logger: ExecutionLogger) { - this.logger = logger; - } + ) {} async cancel(): Promise { this.abort = true; @@ -72,7 +62,6 @@ export class WorkflowImpl implements Workflow { ekoConfig: this.ekoConfig, tools: new Map(node.action.tools.map(tool => [tool.name, tool])), callback, - logger: this.logger, next: () => context.__skip = true, abortAll: () => { this.abort = context.__abort = true; diff --git a/src/nodejs/core.ts b/src/nodejs/core.ts index 74003769..fa53291d 100644 --- a/src/nodejs/core.ts +++ b/src/nodejs/core.ts @@ -1,5 +1,6 @@ import * as tools from './tools'; import { Tool } from '../types'; +import { logger } from '../common/log'; export function loadTools(): Map> { let toolsMap = new Map>(); @@ -10,7 +11,7 @@ export function loadTools(): Map> { let instance = new tool(); toolsMap.set(instance.name || key, instance); } catch (e) { - console.error(`Failed to instantiate ${key}:`, e); + logger.error(`Failed to instantiate ${key}:`, e); } } } diff --git a/src/nodejs/script/build_dom_tree.js b/src/nodejs/script/build_dom_tree.js index d6b9290a..4b12be10 100644 --- a/src/nodejs/script/build_dom_tree.js +++ b/src/nodejs/script/build_dom_tree.js @@ -1,3 +1,5 @@ +import { logger } from "@/common/log"; + export function run_build_dom_tree() { /** * Get clickable elements on the page @@ -644,7 +646,7 @@ export function run_build_dom_tree() { nodeData.children.push(...iframeChildren); } } catch (e) { - console.warn('Unable to access iframe:', node); + logger.warn('Unable to access iframe:', node); } } else { const children = Array.from(node.childNodes).map((child) => diff --git a/src/nodejs/tools/browser_use.ts b/src/nodejs/tools/browser_use.ts index 3cc39ad5..67a535cf 100644 --- a/src/nodejs/tools/browser_use.ts +++ b/src/nodejs/tools/browser_use.ts @@ -2,6 +2,7 @@ import { BrowserUseParam, BrowserUseResult } from '../../types/tools.types'; import { Tool, InputSchema, ExecutionContext } from '../../types/action.types'; import { chromium, Browser, Page, ElementHandle, BrowserContext } from 'playwright'; import { run_build_dom_tree } from '../script/build_dom_tree'; +import { logger } from '../../common/log'; /** * Browser Use => `npx playwright install` @@ -253,7 +254,7 @@ export class BrowserUse implements Tool { return { success: false }; } } catch (e: any) { - console.log(e); + logger.error(e); return { success: false, error: e?.message }; } } @@ -424,7 +425,7 @@ function do_click(param: any) { button, // 0 left; 2 right }); let result = element.dispatchEvent(event); - console.log('simulateMouse', element, { eventTypes, button }, result); + logger.debug('simulateMouse', element, { eventTypes, button }, result); } return true; } @@ -503,6 +504,6 @@ function do_input(params: any): boolean { input.dispatchEvent(event); }); } - console.log('type', input, result); + logger.debug('type', input, result); return true; } diff --git a/src/services/llm/claude-provider.ts b/src/services/llm/claude-provider.ts index 441b3209..8cb37eef 100644 --- a/src/services/llm/claude-provider.ts +++ b/src/services/llm/claude-provider.ts @@ -7,6 +7,7 @@ import { LLMStreamHandler, ToolCall, } from '../../types/llm.types'; +import { logger } from '../../common/log'; interface PartialToolUse { id: string; @@ -35,7 +36,7 @@ export class ClaudeProvider implements LLMProvider { typeof document !== 'undefined' && (typeof param == 'string' || param.apiKey) ) { - console.warn(` + logger.warn(` ⚠️ Security Warning: DO NOT use API Keys in browser/frontend code! This will expose your credentials and may lead to unauthorized usage. diff --git a/src/services/llm/openai-provider.ts b/src/services/llm/openai-provider.ts index ba3a1728..64252ccd 100644 --- a/src/services/llm/openai-provider.ts +++ b/src/services/llm/openai-provider.ts @@ -17,6 +17,7 @@ import { ChatCompletionCreateParamsBase, ChatCompletionCreateParamsStreaming, } from 'openai/resources/chat/completions'; +import { logger } from '../../common/log'; interface PartialToolUse { id: string; @@ -45,7 +46,7 @@ export class OpenaiProvider implements LLMProvider { typeof document !== 'undefined' && (typeof param == 'string' || param.apiKey) ) { - console.warn(` + logger.warn(` ⚠️ Security Warning: DO NOT use API Keys in browser/frontend code! This will expose your credentials and may lead to unauthorized usage. diff --git a/src/services/parser/workflow-parser.ts b/src/services/parser/workflow-parser.ts index a1aefd46..cd1eb072 100644 --- a/src/services/parser/workflow-parser.ts +++ b/src/services/parser/workflow-parser.ts @@ -166,10 +166,6 @@ export class WorkflowParser { [], variables, undefined, - { - logLevel: 'info', - includeTimestamp: true, - } ); // Convert nodes diff --git a/src/services/workflow/generator.ts b/src/services/workflow/generator.ts index c09acf22..7d7cb91c 100644 --- a/src/services/workflow/generator.ts +++ b/src/services/workflow/generator.ts @@ -6,6 +6,7 @@ import { ToolRegistry } from '../../core/tool-registry'; import { createWorkflowPrompts, createWorkflowGenerationTool } from './templates'; import { v4 as uuidv4 } from 'uuid'; import { EkoConfig } from '@/types'; +import { logger } from '../../common/log'; export class WorkflowGenerator { message_history: Message[] = []; @@ -121,10 +122,6 @@ export class WorkflowGenerator { [], new Map(Object.entries(data.variables || {})), this.llmProvider, - { - logLevel: 'info', - includeTimestamp: true, - } ); // Add nodes to workflow diff --git a/src/types/workflow.types.ts b/src/types/workflow.types.ts index 20d3245a..f0566e51 100644 --- a/src/types/workflow.types.ts +++ b/src/types/workflow.types.ts @@ -1,6 +1,5 @@ import { Action, ExecutionContext, Tool } from "./action.types"; import { LLMProvider } from "./llm.types"; -import { ExecutionLogger } from "@/utils/execution-logger"; import { ExportFileParam } from "./tools.types"; import { WorkflowResult } from "./eko.types"; @@ -32,7 +31,6 @@ export interface Workflow { variables: Map; llmProvider?: LLMProvider; - setLogger(logger: ExecutionLogger): void; execute(callback?: WorkflowCallback): Promise; cancel(): Promise; addNode(node: WorkflowNode): void; diff --git a/src/utils/execution-logger.ts b/src/utils/execution-logger.ts deleted file mode 100644 index 9c51470c..00000000 --- a/src/utils/execution-logger.ts +++ /dev/null @@ -1,256 +0,0 @@ -import { Message } from '../types/llm.types'; -import { ExecutionContext } from '../types/action.types'; - -interface ImageData { - type: 'base64'; - media_type: string; - data: string; -} - -export interface LogOptions { - maxHistoryLength?: number; // Maximum number of messages to keep in history - logLevel?: 'debug' | 'info' | 'warn' | 'error'; - includeTimestamp?: boolean; - debugImagePath?: string; // Directory path to save debug images (Node.js only) - imageSaver?: (imageData: ImageData, filename: string) => Promise; // Custom image saver function -} - -/** - * Manages logging for action execution, providing a cleaner view of the execution - * flow while maintaining important context and history. - */ -export class ExecutionLogger { - private history: Message[] = []; - private readonly maxHistoryLength: number; - private readonly logLevel: string; - private readonly includeTimestamp: boolean; - private readonly debugImagePath?: string; - private readonly imageSaver?: (imageData: ImageData, filename: string) => Promise; - private readonly isNode: boolean; - - constructor(options: LogOptions = {}) { - this.maxHistoryLength = options.maxHistoryLength || 10; - this.logLevel = options.logLevel || 'info'; - this.includeTimestamp = options.includeTimestamp ?? true; - this.debugImagePath = options.debugImagePath; - this.imageSaver = options.imageSaver; - - // Check if running in Node.js environment - this.isNode = - typeof process !== 'undefined' && process.versions != null && process.versions.node != null; - } - - /** - * Logs a message with execution context - */ - log(level: string, message: string, context?: ExecutionContext) { - if (this.shouldLog(level)) { - const timestamp = this.includeTimestamp ? new Date().toISOString() : ''; - const contextSummary = this.summarizeContext(context); - console.log(`${timestamp} [${level.toUpperCase()}] ${message}${contextSummary}`); - } - } - - /** - * Updates conversation history while maintaining size limit - */ - updateHistory(messages: Message[]) { - // Keep system messages and last N messages - const systemMessages = messages.filter((m) => m.role === 'system'); - const nonSystemMessages = messages.filter((m) => m.role !== 'system'); - - const recentMessages = nonSystemMessages.slice(-this.maxHistoryLength); - this.history = [...systemMessages, ...recentMessages]; - } - - /** - * Gets current conversation history - */ - getHistory(): Message[] { - return this.history; - } - - /** - * Summarizes the execution context for logging - */ - private summarizeContext(context?: ExecutionContext): string { - if (!context) return ''; - - const summary = { - variables: Object.fromEntries(context.variables), - tools: context.tools ? Array.from(context.tools.keys()) : [], - }; - - return `\nContext: ${JSON.stringify(summary, null, 2)}`; - } - - /** - * Checks if message should be logged based on log level - */ - private shouldLog(level: string): boolean { - const levels = { - error: 0, - warn: 1, - info: 2, - debug: 3, - } as Record; - - return levels[level] <= levels[this.logLevel]; - } - - /** - * Logs the start of an action execution - */ - logActionStart(actionName: string, input: unknown, context?: ExecutionContext) { - this.log('info', `Starting action: ${actionName}`, context); - this.log('info', `Input: ${JSON.stringify(input, null, 2)}`); - } - - /** - * Logs the completion of an action execution - */ - logActionComplete(actionName: string, result: unknown, context?: ExecutionContext) { - this.log('info', `Completed action: ${actionName}`, context); - this.log('info', `Result: ${JSON.stringify(result, null, 2)}`); - } - - /** - * Logs a tool execution - */ - logToolExecution(toolName: string, input: unknown, context?: ExecutionContext) { - this.log('info', `Executing tool: ${toolName}`); - this.log('info', `Tool input: ${JSON.stringify(input, null, 2)}`); - } - - /** - * Logs an error that occurred during execution - */ - logError(error: Error, context?: ExecutionContext) { - console.error(error); - try { - this.log('error', `Error occurred: ${error.message}`, context); - if (error.stack) { - this.log('debug', `Stack trace: ${error.stack}`); - } - } catch (error) { - console.error("An error occurs when trying to log another error:"); - console.error(error); - } - } - - private extractFromDataUrl(dataUrl: string): { extension: string; base64Data: string } { - const matches = dataUrl.match(/^data:image\/([a-zA-Z0-9]+);base64,(.+)$/); - if (!matches) { - throw new Error('Invalid data URL format'); - } - return { - extension: matches[1], - base64Data: matches[2], - }; - } - - private async saveDebugImage(imageData: string | ImageData, toolName: string): Promise { - try { - let extension: string; - let base64Data: string; - - // Handle both data URL strings and ImageData objects - if (typeof imageData === 'string' && imageData.startsWith('data:')) { - const extracted = this.extractFromDataUrl(imageData); - extension = extracted.extension; - base64Data = extracted.base64Data; - } else if (typeof imageData === 'object' && 'type' in imageData) { - extension = imageData.media_type.split('/')[1] || 'png'; - base64Data = imageData.data; - } else { - return '[image]'; - } - - // If custom image saver is provided, use it - if (this.imageSaver) { - const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); - const filename = `${toolName}_${timestamp}.${extension}`; - return await this.imageSaver( - { type: 'base64', media_type: `image/${extension}`, data: base64Data }, - filename - ); - } - - // If in Node.js environment and debugImagePath is set - if (this.isNode && this.debugImagePath) { - // Dynamically import Node.js modules only when needed - const { promises: fs } = await import('fs'); - const { join } = await import('path'); - - await fs.mkdir(this.debugImagePath, { recursive: true }); - - const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); - const filename = `${toolName}_${timestamp}.${extension}`; - const filepath = join(this.debugImagePath, filename); - - const buffer = Buffer.from(base64Data, 'base64'); - await fs.writeFile(filepath, buffer); - - return `[image saved to: ${filepath}]`; - } - - // Default case - just return placeholder - return '[image]'; - } catch (error) { - console.warn('Failed to save debug image:', error); - return '[image]'; - } - } - - private async formatToolResult(result: any): Promise { - // Handle null/undefined - if (result == null) { - return 'null'; - } - - // Handle direct image result - if (result.image) { - const imagePlaceholder = await this.saveDebugImage(result.image, 'tool'); - const modifiedResult = { ...result, image: imagePlaceholder }; - return JSON.stringify(modifiedResult); - } - - // Handle nested images in result object - if (typeof result === 'object') { - const formatted = { ...result }; - for (const [key, value] of Object.entries(formatted)) { - if (value && typeof value === 'string' && value.startsWith('data:image/')) { - formatted[key] = await this.saveDebugImage(value, key); - } else if ( - value && - typeof value === 'object' && - 'type' in value && - value.type === 'base64' - ) { - formatted[key] = await this.saveDebugImage(value as ImageData, key); - } - } - return JSON.stringify(formatted); - } - - // Handle primitive values - return String(result); - } - - async logToolResult( - toolName: string, - result: unknown, - context?: ExecutionContext - ): Promise { - if (this.shouldLog('info')) { - const timestamp = this.includeTimestamp ? new Date().toISOString() : ''; - const contextSummary = this.summarizeContext(context); - const formattedResult = await this.formatToolResult(result); - - console.log( - `${timestamp} [INFO] Tool executed: ${toolName}\n` + - `${timestamp} [INFO] Tool result: ${formattedResult}${contextSummary}` - ); - } - } -} diff --git a/src/web/core.ts b/src/web/core.ts index 74003769..fa53291d 100644 --- a/src/web/core.ts +++ b/src/web/core.ts @@ -1,5 +1,6 @@ import * as tools from './tools'; import { Tool } from '../types'; +import { logger } from '../common/log'; export function loadTools(): Map> { let toolsMap = new Map>(); @@ -10,7 +11,7 @@ export function loadTools(): Map> { let instance = new tool(); toolsMap.set(instance.name || key, instance); } catch (e) { - console.error(`Failed to instantiate ${key}:`, e); + logger.error(`Failed to instantiate ${key}:`, e); } } } diff --git a/src/web/script/build_dom_tree.js b/src/web/script/build_dom_tree.js index 565903d5..37b03c54 100644 --- a/src/web/script/build_dom_tree.js +++ b/src/web/script/build_dom_tree.js @@ -1,3 +1,4 @@ +import { logger } from "@/common/log"; /** * Get clickable elements on the page * @@ -643,7 +644,7 @@ function build_dom_tree(doHighlightElements) { nodeData.children.push(...iframeChildren); } } catch (e) { - console.warn('Unable to access iframe:', node); + logger.warn('Unable to access iframe:', node); } } else { const children = Array.from(node.childNodes).map((child) => diff --git a/src/web/tools/browser.ts b/src/web/tools/browser.ts index 147bab79..8ebaeeab 100644 --- a/src/web/tools/browser.ts +++ b/src/web/tools/browser.ts @@ -1,5 +1,6 @@ import html2canvas from 'html2canvas'; import { ScreenshotResult } from '../../types/tools.types'; +import { logger } from '../../common/log'; export function type(text: string, xpath?: string, highlightIndex?: number): boolean { return do_input(text, xpath, highlightIndex); @@ -251,7 +252,7 @@ function do_input(text: string, xpath?: string, highlightIndex?: number): boolea input.dispatchEvent(event); }); } - console.log('type', input, result); + logger.debug('type', input, result); return true; } @@ -279,7 +280,7 @@ function simulateMouseEvent( button, // 0 left; 2 right }); let result = element.dispatchEvent(event); - console.log('simulateMouse', element, { xpath, eventTypes, button }, result); + logger.debug('simulateMouse', element, { xpath, eventTypes, button }, result); } return true; } diff --git a/src/web/tools/element_click.ts b/src/web/tools/element_click.ts index fc7c313c..3873b18b 100644 --- a/src/web/tools/element_click.ts +++ b/src/web/tools/element_click.ts @@ -3,6 +3,7 @@ import { Tool, InputSchema, ExecutionContext } from '../../types/action.types'; import { extractOperableElements, clickOperableElement, xpath } from './html_script'; import { left_click, screenshot } from './browser'; import { TaskPrompt } from '../../types/tools.types'; +import { logger } from '../../common/log'; /** * Element click @@ -36,7 +37,7 @@ export class ElementClick implements Tool { try { result = await executeWithHtmlElement(context, task_prompt); } catch (e) { - console.log(e); + logger.error(e); result = false; } if (!result) { diff --git a/src/web/tools/find_element_position.ts b/src/web/tools/find_element_position.ts index a55d17e5..3ad7e835 100644 --- a/src/web/tools/find_element_position.ts +++ b/src/web/tools/find_element_position.ts @@ -3,6 +3,7 @@ import { Tool, InputSchema, ExecutionContext } from '../../types/action.types'; import { TaskPrompt, ElementRect } from '../../types/tools.types'; import { extractOperableElements, getOperableElementRect } from './html_script'; import { screenshot } from './browser'; +import { logger } from '../../common/log'; /** * Find Element Position @@ -36,7 +37,7 @@ export class FindElementPosition implements Tool try { result = await executeWithHtmlElement(context, task_prompt); } catch (e) { - console.log(e); + logger.error(e); result = null; } if (!result) {