构建浏览器 Provider 插件
浏览器 provider 插件注册一个云浏览器后端,为 cloud 模式的 browser_* 工具调用(导航、点击、截图……)提供服务。内置 provider——Browserbase、Browser Use、Firecrawl——都以插件形式放在 plugins/browser/<name>/ 下。你可以在它们旁边放一个目录来新增一个,或覆盖某个内置的。
浏览器后端只是 Hermes 支持的若干后端插件之一。其他几类(各有自己的 ABC)是 Web 搜索 Provider 插件(本 ABC 刻意与其对齐)、图像生成、视频生成、记忆 Provider、上下文引擎、机密源、模型 Provider。通用的工具/hook/CLI 插件见构建 Hermes 插件。
它如何组装
浏览器 provider 不实现浏览本身。它实现的是会话生命周期:创建远程浏览器会话、回传一个 CDP websocket URL、再拆掉会话。Hermes 自己的浏览器栈(agent-browser + tools/browser_tool.py)连接到你返回的任意 CDP URL 并从那里驱动页面——每个 provider 都免费获得完整的 browser_* 工具集。
激活的 provider 由 config.yaml 中的 browser.cloud_provider 选定;tools/browser_tool.py 中的分发器是纯粹的注册表查找,不含任何按 provider 的条件分支。
发现
Hermes 在三处扫描浏览器后端:
- 内置——
<repo>/plugins/browser/<name>/(以kind: backend自动加载) - 用户——
~/.hermes/plugins/browser/<name>/(经plugins.enabled或hermes plugins enable <name>选择启用) - Pip——声明了
hermes_agent.pluginsentry point 的包
每个插件的 register(ctx) 调用 ctx.register_browser_provider(...),把实例放入 agent/browser_registry.py 的注册表。
目录结构
plugins/browser/my-backend/
├── __init__.py # register() 入口
├── provider.py # BrowserProvider 子类
└── plugin.yaml # 清单,含 kind: backend 与 provides_browser_providers
plugin.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:
from plugins.browser.my_backend.provider import MyBackendProvider
def register(ctx) -> None:
ctx.register_browser_provider(MyBackendProvider())
BrowserProvider ABC
实现 agent.browser_provider.BrowserProvider。三个生命周期方法加身份标识:
from agent.browser_provider import BrowserProvider
class MyBackendProvider(BrowserProvider):
@property
def name(self) -> str:
return "my-backend" # 即 browser.cloud_provider 的配置值
@property
def display_name(self) -> str:
return "My Backend" # 显示在 `hermes tools` 中
def is_available(self) -> bool:
"""只做廉价检查——环境变量是否存在、依赖可否导入。
不做网络调用:它在工具注册时以及每次
`hermes tools` 绘制时运行。"""
return bool(os.environ.get("MY_BACKEND_API_KEY"))
def create_session(self, task_id: str) -> dict:
"""创建远程浏览器会话;返回会话元数据契约。"""
session = my_api.create_browser(...)
return {
"session_name": f"my-backend-{task_id}", # 唯一的 agent-browser 会话名
"bb_session_id": session.id, # provider 会话 ID(用于清理)
"cdp_url": session.cdp_ws_url, # CDP websocket URL
"features": {"stealth": True}, # 你启用的特性开关
}
def close_session(self, session_id: str) -> bool:
"""按 provider 会话 ID 终止。出错时记录日志并返回 False——
绝不抛异常,以便分发器的清理循环继续推进。"""
...
def emergency_cleanup(self, session_id: str) -> None:
"""来自 atexit/信号处理器的尽力拆除。不得抛异常。"""
...
会话元数据契约
create_session() 必须至少返回 session_name、bb_session_id、cdp_url 和 features。两个需要知道的怪癖:
bb_session_id是一个遗留键名,为与tools/browser_tool.py向后兼容而原样保留——无论哪家厂商,它都装着你的 provider 的会话 ID。不要改名。create_session()可以抛异常——缺凭据抛ValueError,网络/API 失败抛RuntimeError。分发器会把这些呈现给用户。这与close_session/emergency_cleanup不同,后两者绝不能抛异常。
可选的 external_call_id 键支持托管 gateway 计费。
get_setup_schema()——hermes tools 选择器中的一行
重写它,即可在浏览器自动化选择器中作为一等选项出现,并带 API key 提示和安装钩子:
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", # 确保本地 Chromium 已安装(agent-browser 自身经 npx 解析)
}
按项目对工具后端的标准:如果一个后端无法通过 hermes tools 被选中并配置,它就不算完成——"手动设置这个环境变量"不算集成。
用户如何配置
browser:
cloud_provider: my-backend
参考实现
plugins/browser/ 下的三个内置 provider 是规范示例,按复杂度递增排列:firecrawl(最简单)、browser_use、browserbase(带 stealth/代理/保活特性开关,并在付费特性不可用时优雅回退)。复制最接近的那一个。
检查清单
-
name小写且稳定(它是用户写入的配置值) -
is_available()零网络调用 -
create_session()返回完整元数据契约(bb_session_id键名原样保留) -
close_session()/emergency_cleanup()绝不抛异常 -
get_setup_schema()暴露你的环境变量,让hermes tools能配置该后端 -
plugin.yaml声明kind: backend+provides_browser_providers