diff --git a/website/docs/developer-guide/browser-provider-plugin.md b/website/docs/developer-guide/browser-provider-plugin.md new file mode 100644 index 00000000000..abf8a8e80f4 --- /dev/null +++ b/website/docs/developer-guide/browser-provider-plugin.md @@ -0,0 +1,153 @@ +--- +sidebar_position: 13 +title: "Browser Provider Plugins" +description: "How to build a cloud browser backend plugin for Hermes Agent" +--- + +# Building a Browser Provider Plugin + +Browser provider plugins register a **cloud browser backend** that services cloud-mode `browser_*` tool calls (navigate, click, screenshot, …). Built-in providers — Browserbase, Browser Use, and Firecrawl — all ship as plugins under `plugins/browser//`. You can add a new one, or override a bundled one, by dropping a directory next to them. + +:::tip +Browser backends are one of several **backend plugins** Hermes supports. The others (with their own ABCs) are [Web Search Provider Plugins](/developer-guide/web-search-provider-plugin) (which this ABC deliberately mirrors), [Image Generation](/developer-guide/image-gen-provider-plugin), [Video Generation](/developer-guide/video-gen-provider-plugin), [Memory Providers](/developer-guide/memory-provider-plugin), [Context Engines](/developer-guide/context-engine-plugin), [Secret Sources](/developer-guide/secret-source-plugin), and [Model Providers](/developer-guide/model-provider-plugin). General tool/hook/CLI plugins live in [Build a Hermes Plugin](/developer-guide/plugins). +::: + +## How it fits together + +A browser provider does **not** implement browsing. It implements **session lifecycle**: create a remote browser session, hand back a CDP websocket URL, and tear the session down. Hermes' own browser stack (`agent-browser` + `tools/browser_tool.py`) connects to whatever CDP URL you return and drives the page from there — every provider gets the full `browser_*` toolset for free. + +The active provider is selected by `browser.cloud_provider` in `config.yaml`; the dispatcher in `tools/browser_tool.py` is a pure registry lookup with no per-provider conditionals. + +## Discovery + +Hermes scans for browser backends in three places: + +1. **Bundled** — `/plugins/browser//` (auto-loaded with `kind: backend`) +2. **User** — `~/.hermes/plugins/browser//` (opt-in via `plugins.enabled` or `hermes plugins enable `) +3. **Pip** — packages declaring a `hermes_agent.plugins` entry point + +Each plugin's `register(ctx)` calls `ctx.register_browser_provider(...)`, which puts the instance into the registry in `agent/browser_registry.py`. + +## Directory structure + +``` +plugins/browser/my-backend/ +├── __init__.py # register() entry point +├── provider.py # BrowserProvider subclass +└── plugin.yaml # Manifest with kind: backend and provides_browser_providers +``` + +`plugin.yaml`: + +```yaml +name: browser-my-backend +version: 1.0.0 +description: "My cloud browser backend. Requires MY_BACKEND_API_KEY." +author: you +kind: backend +provides_browser_providers: + - my-backend +``` + +`__init__.py`: + +```python +from plugins.browser.my_backend.provider import MyBackendProvider + + +def register(ctx) -> None: + ctx.register_browser_provider(MyBackendProvider()) +``` + +## The BrowserProvider ABC + +Implement `agent.browser_provider.BrowserProvider`. Three lifecycle methods plus identity: + +```python +from agent.browser_provider import BrowserProvider + + +class MyBackendProvider(BrowserProvider): + @property + def name(self) -> str: + return "my-backend" # the browser.cloud_provider config value + + @property + def display_name(self) -> str: + return "My Backend" # shown in `hermes tools` + + def is_available(self) -> bool: + """Cheap check only — env var present, dep importable. + NO network calls: runs at tool-registration time and on every + `hermes tools` paint.""" + return bool(os.environ.get("MY_BACKEND_API_KEY")) + + def create_session(self, task_id: str) -> dict: + """Create a remote browser session; return the session-metadata contract.""" + session = my_api.create_browser(...) + return { + "session_name": f"my-backend-{task_id}", # unique agent-browser session name + "bb_session_id": session.id, # provider session ID (for cleanup) + "cdp_url": session.cdp_ws_url, # CDP websocket URL + "features": {"stealth": True}, # feature flags you enabled + } + + def close_session(self, session_id: str) -> bool: + """Terminate by provider session ID. Log-and-return-False on error — + never raise, so the dispatcher's cleanup loop keeps moving.""" + ... + + def emergency_cleanup(self, session_id: str) -> None: + """Best-effort teardown from atexit/signal handlers. Must not raise.""" + ... +``` + +### The session-metadata contract + +`create_session()` must return at least `session_name`, `bb_session_id`, `cdp_url`, and `features`. Two quirks worth knowing: + +- **`bb_session_id` is a legacy key name** kept verbatim for backward compatibility with `tools/browser_tool.py` — it holds *your* provider's session ID regardless of vendor. Don't rename it. +- `create_session()` **may raise** — `ValueError` for missing credentials, `RuntimeError` for network/API failures. The dispatcher surfaces these to the user. This differs from `close_session`/`emergency_cleanup`, which must never raise. + +An optional `external_call_id` key supports managed-gateway billing. + +### `get_setup_schema()` — the `hermes tools` picker row + +Override this to appear as a first-class option in the Browser Automation picker with API-key prompts and an install hook: + +```python +def get_setup_schema(self) -> dict: + return { + "name": "My Backend", + "badge": "paid", + "tag": "Cloud browser with stealth and proxies", + "env_vars": [ + {"key": "MY_BACKEND_API_KEY", + "prompt": "My Backend API key", + "url": "https://mybackend.example"}, + ], + "post_setup": "agent_browser", # auto-installs the agent-browser npm dep + } +``` + +Per the project standard for tool backends: if a backend can't be selected and configured through `hermes tools`, it isn't done — "set this env var manually" is not an integration. + +## Users configure it + +```yaml +browser: + cloud_provider: my-backend +``` + +## Reference implementations + +The three bundled providers under `plugins/browser/` are the canonical examples, in ascending complexity: `firecrawl` (simplest), `browser_use`, and `browserbase` (stealth/proxy/keep-alive feature flags with graceful fallback when paid features are unavailable). Copy the closest one. + +## Checklist + +- [ ] `name` is lowercase and stable (it's a config value users write) +- [ ] `is_available()` makes zero network calls +- [ ] `create_session()` returns the full metadata contract (`bb_session_id` key name intact) +- [ ] `close_session()` / `emergency_cleanup()` never raise +- [ ] `get_setup_schema()` exposes your env vars so `hermes tools` can configure the backend +- [ ] `plugin.yaml` declares `kind: backend` + `provides_browser_providers` diff --git a/website/docs/developer-guide/plugins/index.md b/website/docs/developer-guide/plugins/index.md index 1fa5d1c5e20..99fe800b410 100644 --- a/website/docs/developer-guide/plugins/index.md +++ b/website/docs/developer-guide/plugins/index.md @@ -21,6 +21,10 @@ Hermes has several distinct pluggable interfaces — some use Python `register_* | A **context-compression engine** | [Context Engine Plugins](/developer-guide/context-engine-plugin) | | An **image-generation backend** | [Image Generation Provider Plugins](/developer-guide/image-gen-provider-plugin) | | A **video-generation backend** | [Video Generation Provider Plugins](/developer-guide/video-gen-provider-plugin) | +| A **web-search / extract backend** | [Web Search Provider Plugins](/developer-guide/web-search-provider-plugin) | +| A **cloud browser backend** (Browserbase-style CDP session provider) | [Browser Provider Plugins](/developer-guide/browser-provider-plugin) | +| A **secret-manager backend** (vault / password manager / OS keystore) | [Secret Source Plugins](/developer-guide/secret-source-plugin) | +| A **dashboard OIDC/auth provider** | [Web Dashboard — custom providers](/user-guide/features/web-dashboard#custom-providers) — `ctx.register_dashboard_auth_provider()` | | A **TTS backend** (any CLI — Piper, VoxCPM, Kokoro, voice cloning, …) | [TTS custom command providers](/user-guide/features/tts#custom-command-providers) — config-driven, no Python needed | | An **STT backend** (custom whisper / ASR CLI) | [Voice Message Transcription](/user-guide/features/tts#voice-message-transcription-stt) — set `HERMES_LOCAL_STT_COMMAND` to a shell template | | **External tools via MCP** (filesystem, GitHub, Linear, any MCP server) | [MCP](/user-guide/features/mcp) — declare `mcp_servers.` in `config.yaml` | diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/developer-guide/plugins/index.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/developer-guide/plugins/index.md index 8ffeab09209..91759dbf554 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/developer-guide/plugins/index.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/developer-guide/plugins/index.md @@ -20,6 +20,10 @@ Hermes 有多种不同的可插拔接口——有些使用 Python `register_*` A | **上下文压缩引擎** | [上下文引擎插件](/developer-guide/context-engine-plugin) | | **图像生成后端** | [图像生成提供商插件](/developer-guide/image-gen-provider-plugin) | | **视频生成后端** | [视频生成提供商插件](/developer-guide/video-gen-provider-plugin) | +| **网页搜索/提取后端** | [网页搜索提供商插件](/developer-guide/web-search-provider-plugin) | +| **云浏览器后端**(Browserbase 类 CDP 会话提供商) | [浏览器提供商插件](/developer-guide/browser-provider-plugin) | +| **密钥管理器后端**(保险库 / 密码管理器 / 系统钥匙串) | [密钥源插件](/developer-guide/secret-source-plugin) | +| **仪表盘 OIDC/认证提供商** | [Web 仪表盘 — 自定义提供商](/user-guide/features/web-dashboard#custom-providers) — `ctx.register_dashboard_auth_provider()` | | **TTS 后端**(任意 CLI——Piper、VoxCPM、Kokoro、声音克隆等) | [TTS 自定义命令提供商](/user-guide/features/tts#custom-command-providers)——配置驱动,无需 Python | | **STT 后端**(自定义 whisper / ASR CLI) | [语音消息转录](/user-guide/features/tts#voice-message-transcription-stt)——将 `HERMES_LOCAL_STT_COMMAND` 设置为 shell 模板 | | **通过 MCP 接入外部工具**(文件系统、GitHub、Linear、任意 MCP 服务器) | [MCP](/user-guide/features/mcp)——在 `config.yaml` 中声明 `mcp_servers.` | diff --git a/website/sidebars.ts b/website/sidebars.ts index 189406e2599..1843a924eb7 100644 --- a/website/sidebars.ts +++ b/website/sidebars.ts @@ -749,6 +749,7 @@ const sidebars: SidebarsConfig = { 'developer-guide/image-gen-provider-plugin', 'developer-guide/video-gen-provider-plugin', 'developer-guide/web-search-provider-plugin', + 'developer-guide/browser-provider-plugin', ], }, 'developer-guide/creating-skills',