diff --git a/gptme/tools/_browser_playwright.py b/gptme/tools/_browser_playwright.py index 1d7cfb8d..9f841e18 100644 --- a/gptme/tools/_browser_playwright.py +++ b/gptme/tools/_browser_playwright.py @@ -10,6 +10,7 @@ from pathlib import Path from playwright.sync_api import Browser, ElementHandle + from ._browser_thread import BrowserThread _browser: BrowserThread | None = None @@ -19,7 +20,6 @@ def get_browser() -> BrowserThread: global _browser if _browser is None: - logger.info("Starting browser thread") _browser = BrowserThread() atexit.register(_browser.stop) return _browser diff --git a/gptme/tools/_browser_thread.py b/gptme/tools/_browser_thread.py index 503b34b4..d4044d39 100644 --- a/gptme/tools/_browser_thread.py +++ b/gptme/tools/_browser_thread.py @@ -1,3 +1,4 @@ +import importlib import logging import time from collections.abc import Callable @@ -31,19 +32,35 @@ def __init__(self): self.results: dict[object, tuple[Any, Exception | None]] = {} self.lock = Lock() self.ready = Event() + self._init_error: Exception | None = None self.thread = Thread(target=self._run, daemon=True) self.thread.start() # Wait for browser to be ready if not self.ready.wait(timeout=TIMEOUT): raise TimeoutError("Browser failed to start") - logger.info("Browser thread started") + if self._init_error: + raise self._init_error + + logger.debug("Browser thread started") def _run(self): try: playwright = sync_playwright().start() - browser = playwright.chromium.launch() - logger.info("Browser launched") - self.ready.set() + try: + browser = playwright.chromium.launch() + logger.info("Browser launched") + except Exception as e: + if "Executable doesn't exist" in str(e): + pw_version = importlib.metadata.version("playwright") + self._init_error = RuntimeError( + f"Browser executable not found. Run: pipx run playwright=={pw_version} install chromium-headless-shell" + ) + else: + self._init_error = e + self.ready.set() # Signal init complete (with error) + return + + self.ready.set() # Signal successful init while True: try: @@ -88,6 +105,7 @@ def execute(self, func: Callable[..., T], *args, **kwargs) -> T: result, error = self.results.pop(cmd_id) if error: raise error + logger.info("Browser operation completed") return result time.sleep(0.1) # Prevent busy-waiting diff --git a/gptme/tools/browser.py b/gptme/tools/browser.py index 06887f2a..016301fa 100644 --- a/gptme/tools/browser.py +++ b/gptme/tools/browser.py @@ -15,9 +15,9 @@ pipx install 'gptme[browser]' # We need to use the same version of Playwright as the one installed by gptme - # when downloading the browser binaries. + # when downloading the browser binaries. gptme will attempt this automatically PW_VERSION=$(pipx runpip gptme show playwright | grep Version | cut -d' ' -f2) - pipx run playwright==$PW_VERSION install chromium + pipx run playwright==$PW_VERSION install chromium-headless-shell Lynx backend: - Text-only browser for basic page reading and searching @@ -38,11 +38,13 @@ """ import importlib.util +import importlib.metadata import logging import shutil from pathlib import Path from typing import Literal +from ..util import console from .base import ToolSpec, ToolUse has_playwright = lambda: importlib.util.find_spec("playwright") is not None # noqa @@ -103,6 +105,10 @@ def examples(tool_format): def has_browser_tool(): + if browser == "playwright": + console.log("Browser tool available (using playwright)") + elif browser == "lynx": + console.log("Browser tool available (using lynx)") return browser is not None