UNPKG

@eko-ai/eko-nodejs

Version:

Empowering language to transform human words into action.

512 lines (504 loc) 18 kB
'use strict'; require('fs'); require('os'); require('path'); var eko = require('@eko-ai/eko'); var playwrightExtra = require('playwright-extra'); var StealthPlugin = require('puppeteer-extra-plugin-stealth'); var child_process = require('child_process'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var StealthPlugin__default = /*#__PURE__*/_interopDefault(StealthPlugin); async function getCdpWsEndpoint(port) { // Example => ws://localhost:9222/devtools/browser/{session-id} const response = await fetch(`http://localhost:${port}/json/version`); const browserInfo = await response.json(); eko.Log.info("browserInfo: ", browserInfo); return browserInfo.webSocketDebuggerUrl; } var tabId = 1000; class BrowserAgent extends eko.BaseBrowserLabelsAgent { constructor() { super(...arguments); this.browser = null; this.browserContext = null; this.activePage = null; this.headless = false; this.pageMap = new Map(); this.lastSwitchedPage = null; } setHeadless(headless) { this.headless = headless; } setCdpWsEndpoint(cdpWsEndpoint) { this.cdpWsEndpoint = cdpWsEndpoint; } initUserDataDir(userDataDir) { this.userDataDir = userDataDir; return this.userDataDir; } setCookies(cookies) { this.cookies = cookies; } setOptions(options) { this.options = options; } async screenshot(agentContext) { const page = await this.currentPage(); const screenshotBuffer = await page.screenshot({ fullPage: false, type: "jpeg", quality: 60, animations: "disabled", }); const base64 = screenshotBuffer.toString("base64"); return { imageType: "image/jpeg", imageBase64: base64, }; } async navigate_to(agentContext, url) { const page = await this.open_url(agentContext, url); this.lastSwitchedPage = page; await this.sleep(200); return { url: page.url(), title: await page.title(), }; } async loadTabs() { return await this.get_all_tabs(undefined); } getPageMap() { return this.pageMap; } async get_all_tabs(agentContext) { if (!this.browserContext) { return []; } const result = []; const pages = this.browserContext.pages(); for (let i = 0; i < pages.length; i++) { let page = pages[i]; let pageInfo = this.pageMap.get(page); if (!pageInfo) { pageInfo = { tabId: tabId++, lastAccessed: Date.now(), }; this.pageMap.set(page, pageInfo); } result.push({ tabId: pageInfo.tabId, url: page.url(), title: await page.title(), lastAccessed: pageInfo.lastAccessed, active: page === this.activePage, }); } return result; } async switch_tab(agentContext, tabId) { if (!this.browserContext) { throw new Error("tabId does not exist: " + tabId); } let switchPage = null; this.pageMap.forEach((pageInfo, page) => { if (pageInfo.tabId === tabId) { switchPage = page; return; } }); if (!switchPage) { throw new Error("tabId does not exist: " + tabId); } this.activePage = switchPage; this.lastSwitchedPage = switchPage; return { tabId: tabId, url: switchPage.url(), title: await switchPage.title(), }; } async input_text(agentContext, index, text, enter) { try { const elementHandle = await this.get_element(index, true); await elementHandle.fill(""); await elementHandle.fill(text); if (enter) { await elementHandle.press("Enter"); await this.sleep(200); } } catch (e) { await super.input_text(agentContext, index, text, enter); } } async click_element(agentContext, index, num_clicks, button) { try { const elementHandle = await this.get_element(index, true); const box = await elementHandle.boundingBox(); if (box) { const page = await this.currentPage(); page.mouse.move(box.x + box.width / 2 + (Math.random() * 10 - 5), box.y + box.height / 2 + (Math.random() * 10 - 5), { steps: Math.floor(Math.random() * 5) + 3 }); } await elementHandle.click({ button, clickCount: num_clicks, force: false, delay: Math.random() * 50 + 20, }); } catch (e) { await super.click_element(agentContext, index, num_clicks, button); } } async hover_to_element(agentContext, index) { try { const elementHandle = await this.get_element(index, true); elementHandle.hover({ force: true }); } catch (e) { await super.hover_to_element(agentContext, index); } } async execute_script(agentContext, func, args) { const page = await this.currentPage(); return await page.evaluate(func, ...args); } async open_url(agentContext, url) { const browser_context = await this.getBrowserContext(); const page = await browser_context.newPage(); try { await this.autoLoadCookies(url); await this.autoLoadLocalStorage(page, url); await page.goto(url, { waitUntil: "domcontentloaded", timeout: 10000, }); await page.waitForLoadState("networkidle", { timeout: 5000 }); } catch (e) { if ((e + "").indexOf("Timeout") == -1) { throw e; } } this.activePage = page; return page; } async currentPage() { if (this.activePage == null) { throw new Error("There is no page, please call navigate_to first"); } const page = this.activePage; try { await page.waitForLoadState("domcontentloaded", { timeout: 10000 }); } catch (e) { } return page; } async get_element(index, findInput) { const page = await this.currentPage(); return await page.evaluateHandle((params) => { let element = window.get_highlight_element(params.index); if (element && params.findInput) { if (element.tagName != "INPUT" && element.tagName != "TEXTAREA" && element.childElementCount != 0) { element = element.querySelector("input") || element.querySelector("textarea") || element; } } return element; }, { index, findInput }); } async go_back(agentContext) { try { await this.execute_script(agentContext, async () => { await window.navigation.back().finished; }, []); await this.sleep(100); } catch (e) { if (e?.name == "InvalidStateError" && this.lastSwitchedPage) { this.activePage && (await this.activePage.close()); this.activePage = this.lastSwitchedPage; this.lastSwitchedPage = null; } else { throw new Error("Cannot go back"); } } } sleep(time) { return new Promise((resolve) => setTimeout(() => resolve(), time)); } async getBrowserContext() { if (!this.browserContext) { this.activePage = null; this.browserContext = null; const viewport = this.options?.viewport || { width: 1536, height: 864 }; if (this.cdpWsEndpoint) { this.browser = (await playwrightExtra.chromium.connectOverCDP(this.cdpWsEndpoint, this.options)); this.browserContext = await this.browser.newContext({ userAgent: this.getUserAgent(), viewport: viewport, }); } else if (this.userDataDir) { this.browserContext = (await playwrightExtra.chromium.launchPersistentContext(this.userDataDir, { headless: this.headless, channel: "chrome", args: this.getChromiumArgs(), viewport: viewport, ...this.options, })); } else { this.browser = (await playwrightExtra.chromium.launch({ headless: this.headless, args: this.getChromiumArgs(), ...this.options, })); this.browserContext = await this.browser.newContext({ userAgent: this.getUserAgent(), viewport: viewport, }); } // Anti-crawling detection website: // https://bot.sannysoft.com/ // https://www.browserscan.net/ playwrightExtra.chromium.use(StealthPlugin__default.default()); const init_script = await this.initScript(); if (init_script.content || init_script.path) { this.browserContext.addInitScript(init_script); } this.browserContext.on("page", async (page) => { this.activePage = page; this.pageMap.set(page, { tabId: tabId++, lastAccessed: Date.now(), }); page.on("framenavigated", async (frame) => { if (frame === page.mainFrame()) { const url = frame.url(); if (url.startsWith("http")) { await this.autoLoadCookies(url); await this.autoLoadLocalStorage(page, url); } } }); page.on("close", () => { this.pageMap.delete(page); }); }); } if (this.cookies) { this.browserContext?.addCookies(this.cookies); } return this.browserContext; } async autoLoadCookies(url) { try { const cookies = await this.loadCookiesWithUrl(url); if (cookies && cookies.length > 0) { await this.browserContext?.clearCookies(); await this.browserContext?.addCookies(cookies); } } catch (e) { eko.Log.error("Failed to auto load cookies: " + url, e); } } async autoLoadLocalStorage(page, url) { try { const localStorageData = await this.loadLocalStorageWithUrl(url); await page.addInitScript((storage) => { try { for (const [key, value] of Object.entries(storage)) { localStorage.setItem(key, value); } } catch (e) { eko.Log.error("Failed to inject localStorage: " + url, e); } }, localStorageData); } catch (e) { eko.Log.error("Failed to auto load localStorage: " + url, e); } } async loadCookiesWithUrl(url) { return []; } async loadLocalStorageWithUrl(url) { return {}; } getChromiumArgs() { return [ "--no-sandbox", "--remote-allow-origins=*", "--disable-dev-shm-usage", "--disable-popup-blocking", "--ignore-ssl-errors", "--ignore-certificate-errors", "--ignore-certificate-errors-spki-list", "--disable-blink-features=AutomationControlled", "--disable-infobars", "--disable-notifications", "--disable-web-security", "--disable-features=IsolateOrigins,site-per-process", ]; } getUserAgent() { // const userAgents = [ // "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", // "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36", // "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", // "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36", // ]; // return userAgents[Math.floor(Math.random() * userAgents.length)]; return undefined; } async initScript() { return {}; } async getBrowser() { return this.browser; } async getActivePage() { return this.activePage; } async close() { if (this.browserContext) { await this.browserContext.close(); } if (this.browser) { await this.browser.close(); } } } class SimpleStdioMcpClient { constructor(command, args, options) { this.process = null; this.command = command; this.args = args || []; this.options = options || { stdio: ["pipe", "pipe", "pipe"], }; this.requestMap = new Map(); } async connect(signal) { if (this.process) { try { this.process.kill(); } catch (e) { } } this.process = child_process.spawn(this.command, this.args, this.options); this.process.stdout.on("data", (data) => { const response = data.toString().trim(); eko.Log.debug("MCP Client, onmessage", this.command, this.args, response); if (!response.startsWith("{")) { return; } const message = JSON.parse(response); if (message.id) { const callback = this.requestMap.get(message.id); if (callback) { callback(message); } } }); this.process.on("error", (error) => { eko.Log.error("MCP process error:", this.command, this.args, error); }); eko.Log.info("MCP Client, connection successful:", this.command, this.args); } async listTools(param, signal) { const message = await this.sendMessage("tools/list", { ...param, }, signal); return message.result.tools || []; } async callTool(param, signal) { const message = await this.sendMessage("tools/call", { ...param, }, signal); return message.result; } async sendMessage(method, params = {}, signal) { if (!this.process) { await this.connect(); if (!this.process) { throw new Error("Failed to connect to MCP server"); } } const id = eko.uuidv4(); try { const callback = new Promise((resolve, reject) => { if (signal) { signal.addEventListener("abort", () => { const error = new Error("Operation was interrupted"); error.name = "AbortError"; reject(error); }); } this.requestMap.set(id, resolve); }); const message = JSON.stringify({ jsonrpc: "2.0", id: id, method: method, params: { ...params, }, }); eko.Log.debug(`MCP Client, ${method}`, id, params); const suc = this.process.stdin.write(message + "\n", "utf-8"); if (!suc) { throw new Error("SseClient Response Exception: " + message); } const messageData = await callback; this.handleError(method, messageData); return messageData; } finally { this.requestMap.delete(id); } } handleError(method, message) { if (!message) { throw new Error(`MCP ${method} error: no response`); } if (message?.error) { eko.Log.error(`MCP ${method} error: ` + message.error); throw new Error(`MCP ${method} error: ` + (typeof message.error === "string" ? message.error : message.error.message)); } if (message.result?.isError == true) { if (message.result.content) { throw new Error(`MCP ${method} error: ` + (typeof message.result.content === "string" ? message.result.content : message.result.content[0].text)); } else { throw new Error(`MCP ${method} error: ` + JSON.stringify(message.result)); } } } isConnected() { return (this.process != null && !this.process.killed && !this.process.exitCode); } async close() { this.process && this.process.kill(); } } exports.BrowserAgent = BrowserAgent; exports.SimpleStdioMcpClient = SimpleStdioMcpClient; exports.getCdpWsEndpoint = getCdpWsEndpoint; //# sourceMappingURL=index.cjs.map