Mastra BrowserViewer 实战:用 Playwright 托管 Chrome、CDP URL 与屏幕直播驱动 CLI 浏览器工具
【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra
@mastra/browser-viewer是 Mastra 浏览器体系中的 CLI 型浏览器 Provider:它用 Playwright 启动并全权管理一个 Chrome 实例,对外暴露 Chrome DevTools Protocol(CDP)WebSocket 地址,供agent-browser、browser-use、browse等命令行工具作为次级客户端连接,同时提供屏幕直播(screencast)、输入注入和线程级浏览器隔离能力。本文基于该包的 README、完整源码与仓库内参考文档展开,读完后你可以独立配置一个 BrowserViewer 接入 Workspace、理解 CDP URL 的发现与注入机制,并掌握thread/shared两种作用域、外部浏览器连接与直播流的实现细节。
一、定位:CLI 驱动的浏览器自动化
在 Mastra 中,Agent 驱动浏览器有两条路线:通过 SDK 对象编程(如AgentBrowser、Stagehand),或通过命令行工具操作。BrowserViewer属于后者。源码顶部的注释直接说明了它的设计目标(browser/browser-viewer.ts#L1-L12):
- 直接建立页面级 CDP 会话(修复 screencast 的 sessionId 问题);
- 完整的浏览器生命周期控制;
- 可预测的 CDP URL,便于注入到 CLI 命令;
- 线程(Thread)作用域的浏览器隔离。
类定义在 browser/browser-viewer.ts#L49,关键只读属性揭示了它的身份(browser/browser-viewer.ts#L50-L59):
| 属性 | 值 / 说明 |
|---|---|
id | 构造时生成为browser-viewer-{timestamp} |
name | 固定为'BrowserViewer' |
provider | 固定为'browser-viewer' |
providerType | 固定为'cli',用于区别于 SDK 型 Provider |
cli | 当前配置使用的 CLI(CLIProvider类型) |
与 SDK 型 Provider 的一个本质区别是:getTools()直接返回空对象(browser/browser-viewer.ts#L406-L414)。CLI Agent 不需要 SDK 工具,它通过workspace_execute_command配合 skills 来驱动 CLI 命令,Mastra 只负责浏览器侧的直播、输入注入与生命周期。
完整的集成指南(Workspace 快速上手、CLI 安装方式)见仓库内文档 browser-viewer 集成文档,API 级参考见 BrowserViewer 参考文档。
二、安装与基础用法
安装方式(见 browser/browser-viewer/README.md):
npm install @mastra/browser-viewer包元信息(browser/browser-viewer/package.json):
- 当前版本
0.2.3,依赖playwright-core@^1.61.1与typed-emitter; - 对
@mastra/core的 peer 要求为>=1.26.0-0 <2.0.0-0,zod兼容^3.25.0 || ^4.0.0; - 要求 Node.js
>=22.13.0。
README 给出的最简用法——独立启动浏览器并获取 CDP URL:
import { BrowserViewer } from '@mastra/browser-viewer'; const viewer = new BrowserViewer({ cli: 'agent-browser', // Agent 将使用的 CLI headless: false, // 显示浏览器窗口 }); // 启动浏览器 await viewer.launch(); // 获取供 CLI 连接的 CDP URL const cdpUrl = viewer.getCdpUrl(); console.log(cdpUrl); // ws://127.0.0.1:9222/devtools/browser/...典型的生产用法则是把BrowserViewer交给Workspace,再挂到 Agent 上(示例整理自 browser-viewer.ts#L36-L47 的 JSDoc 与仓库内集成文档):
import { Workspace, LocalSandbox } from '@mastra/core/workspace'; import { BrowserViewer } from '@mastra/browser-viewer'; const workspace = new Workspace({ sandbox: new LocalSandbox({ workingDirectory: './workspace' }), browser: new BrowserViewer({ cli: 'browser-use', headless: false, }), });当 Agent 通过workspace_execute_command执行浏览器 CLI 命令时,Mastra 会自动:检测 CLI 命令 → 若浏览器未运行则通过 Playwright 启动 Chrome → 把 CDP URL 以正确的标志注入命令 → 开始向 Studio 推流 screencast。
三、配置参数详解
BrowserViewerConfig继承自 core 包的BrowserConfigBase,再附加两个 CLI 专属字段(browser-viewer/types.ts#L15-L29):
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
cli | 'agent-browser' \| 'browser-use' \| 'browse' \| 'browse-cli' | 必填 | Agent 使用的 CLI,CLI 通过 CDP URL 连接 Chrome |
cdpPort | number | 0(自动分配可用端口) | Chrome 远程调试端口,仅在自行启动 Chrome 时生效 |
headless | boolean | true | 是否无头运行(测试 browser-viewer.test.ts#L46-L49 验证了默认值) |
scope | 'thread' \| 'shared' | 'thread';提供cdpUrl时默认'shared' | 浏览器实例作用域,详见第五节 |
cdpUrl | string \| (() => string \| Promise<string>) | - | 连接已存在的浏览器而非新启动一个 |
viewport | { width, height } \| 'window' | { width: 1280, height: 720 } | 视口尺寸;'window'表示不做视口模拟、跟随真实窗口 |
executablePath | string | Playwright 自带 Chromium | 自定义 Chrome 可执行文件路径 |
timeout | number | 10000 | 浏览器操作默认超时(毫秒) |
onLaunch/onClose | 生命周期回调 | - | 浏览器就绪 / 关闭前触发 |
screencast | ScreencastOptions | - | 直播流参数(格式、质量、尺寸) |
基础字段的定义与约束在 packages/core/src/browser/browser.ts#L233-L307。其中两个值得注意的约束:
cdpUrl与scope: 'thread'互斥——线程隔离需要为每个线程启动独立浏览器进程,连接已有浏览器时做不到。构造器中的处理逻辑印证了这一点(browser-viewer.ts#L64-L76):
// 提供 cdpUrl 时 scope 默认 'shared',否则默认 'thread' const effectiveScope = config.cdpUrl ? (config.scope ?? 'shared') : (config.scope ?? 'thread');- 构造时 CLI 专属字段(
cli、cdpPort)会被剔除后再传入基类(browser-viewer.ts#L71),因为基类BrowserConfig是判别联合,不识别这些字段。
四、核心机制:Playwright 托管 Chrome 与 CDP URL 发现
真正的启动逻辑在 thread-manager.ts 的launchBrowser中(thread-manager.ts#L182-L290),调用链如下:
chromium.launchServer(...):以--remote-debugging-port=${cdpPort}、--no-first-run、--no-default-browser-check参数启动 Chrome 进程。端口默认0,由操作系统自动分配;discoverCdpUrl(browserServer):从 Chrome 的DevToolsActivePort文件中发现真实的 CDP WebSocket 地址(thread-manager.ts#L433-L484)。该文件第一行是端口号、第二行是/devtools/browser/<guid>路径,拼出形如ws://127.0.0.1:52481/devtools/browser/abc...的 URL 供外部 CLI 连接。由于 Chrome 启动初期可能仍在写文件,这里做了最长 1500ms 的重试轮询;chromium.connect(browserServer.wsEndpoint()):Playwright 自身也连接该浏览器,用于 screencast 与会话管理;browser.newContext(...)创建上下文并打开初始页面。注意视口处理(thread-manager.ts#L217-L222):viewport: 'window'时传null给 Playwright,语义正是“禁用模拟、跟随真实窗口”;context.newCDPSession(page)为活动页面建立 CDP 会话,供 screencast 和输入注入使用。
此外还有一个可靠性增强:通过browser.newBrowserCDPSession()打开Target.setDiscoverTargets,监听Target.targetDestroyed(thread-manager.ts#L249-L280)。当页面级 CDP 看不到全局事件时(CLI 会自己创建页面),浏览器级会话能观察到所有 target 的销毁;一旦剩余页面 target 归零即判定浏览器已关闭。
五、作用域模型:thread与shared
BrowserViewerThreadManager支持两种会话组织方式(thread-manager.ts#L48-L64):
thread(默认):每个线程一个独立 Chrome 进程。threadSessions是一个threadId -> BrowserViewerSession的 Map,懒创建——线程首次调用ensureReady()/launch(threadId)时才启动(browser-viewer.ts#L157-L199)。适合并行 Agent 需要互相隔离的浏览器状态;shared:所有线程共享单个 Chrome。doLaunch()中走createSharedSession()一次性启动(browser-viewer.ts#L122-L135)。
getCdpUrl(threadId?)按作用域路由到对应会话的cdpUrl(browser-viewer.ts#L114-L116)。线程隔离的会话状态查询、活动页解析(resolveActivePage取最近打开的页面)与断连清理逻辑均在该 thread-manager 中实现,关闭入口为closeAll()(thread-manager.ts#L655-L665)。
六、连接已有浏览器:cdpUrl与connectToExternalCdp
BrowserViewer 有两种“不自己启动浏览器”的路径:
- 构造时提供
cdpUrl:可以是字符串或异步函数。doLaunch()解析 URL 后走connectToExisting→createSharedSessionFromCdp(browser-viewer.ts#L126-L130),内部用chromium.connectOverCDP(cdpUrl)建立连接(thread-manager.ts#L568-L594)。此时会话的browserServer为null,表示进程不归本包所有,清理时不会杀掉外部浏览器; - 运行时调用
connectToExternalCdp(cdpUrl, threadId?)(browser-viewer.ts#L210-L225):适用于 Agent 使用自己的外部浏览器端点(例如云端浏览器服务),Mastra 仅连接以启用 screencast,不管理其生命周期。实现上它会先关闭该线程的既有会话,避免泄漏浏览器进程(thread-manager.ts#L551-L562)。
用法示例:
// 连接本地已开启远程调试的 Chrome const viewer = new BrowserViewer({ cli: 'browser-use', cdpUrl: 'ws://127.0.0.1:9222/devtools/browser/abc123', }); // 或运行中接入外部端点 await viewer.connectToExternalCdp('wss://cloud.example.com/session', 'thread-123');七、屏幕直播与输入注入
startScreencast
startScreencast(options?)(browser-viewer.ts#L279-L382)返回一个ScreencastStream。默认直播参数写死在源码中:
format: 'jpeg'、quality: 80、maxWidth: 1280、maxHeight: 720、everyNthFrame: 1。
其设计要点是CDP 会话按需提供且不缓存:CdpSessionProvider.getCdpSession每次调用都通过createFreshCdpSession(threadId)为当前活动页面新建 CDP 会话(browser-viewer.ts#L282-L306)。源码注释解释原因——CDP 会话是页面作用域的,tab 切换后必须重新附着到当前页面,而不是启动时的初始页面。配套的 tab 切换处理会监听 context 的page事件与每个页面的close/framenavigated事件,在 100ms 延迟后自动stream.reconnect(),并在流停止时统一解绑监听器(browser-viewer.ts#L317-L378)。
injectMouseEvent / injectKeyboardEvent
Studio 的实时交互通过 CDP 的Input.dispatchMouseEvent/Input.dispatchKeyEvent实现(browser-viewer.ts#L388-L404):
await viewer.injectMouseEvent({ type: 'mousePressed', x: 100, y: 200, button: 'left' }); await viewer.injectKeyboardEvent({ type: 'keyDown', key: 'Enter', code: 'Enter' });底层getCdpSessionForThread带了一个按pageUrl判定的缓存(thread-manager.ts#L329-L370):同一活动页复用会话,页面 URL 变化或页面关闭则重建;若创建会话失败(页面已在获取与创建之间关闭),会触发断连清理流程。
八、支持的 CLI 与 CDP 注入方式
CLIProvider类型定义支持四种取值(browser-viewer/types.ts#L10)。各 CLI 需在 workspace 环境中单独安装,并各自发布一个 skill 教会 Agent 其命令与工作流。根据仓库内集成/参考文档(集成文档、参考文档):
cli取值 | 安装 | CDP 注入方式 |
|---|---|---|
agent-browser | npm install -g agent-browser+npx skills add vercel-labs/agent-browser | --cdp标志 |
browser-use | pip install browser-use+npx skills add browser-use/browser-use --skill browser-use | 直接 stdin 调用(browser-use/browseruse/browser/bu)时设置BU_CDP_WS与线程隔离的BU_NAME环境变量;遗留子命令保留--cdp-url与--session注入 |
browse/browse-cli | npm install -g browse+browse skills install | --ws标志 |
当 CLI 命令经workspace_execute_command执行时,Mastra 按cli配置自动选择正确的注入方式,无需 Agent 手写连接参数。仓库内的 browser/agent-browser 包则是 SDK 型 Provider(供对比),二者分工明确:BrowserViewer管 Chrome 与 CDP,CLI 工具执行具体的浏览操作。
九、行为验证:从测试用例看契约
包内测试(browser-viewer/src/tests/browser-viewer.test.ts)覆盖了几个关键契约:
- 无浏览器运行时,
getBrowserState()与getCurrentUrl()均返回null,而非抛错; getBrowserState('thread-1')会透传线程 ID 给底层的按线程状态查询;headless默认true,显式传false时生效(对应 README 中headless: false显示浏览器窗口的用法);cli同时支持现值browse与遗留值browse-cli。
getBrowserStateForThread返回{ tabs, activeTabIndex },其中activeTabIndex取最后一个页面(最近打开者),与 thread-manager 中resolveActivePage的取页策略保持一致(browser-viewer.ts#L235-L254)。
十、仓库文件索引
| 路径 | 内容 |
|---|---|
| browser/browser-viewer/README.md | 包 README(本文主文档) |
| browser/browser-viewer/src/browser-viewer.ts | BrowserViewer主类:生命周期、CDP、screencast、输入注入 |
| browser/browser-viewer/src/thread-manager.ts | 线程会话管理、Chrome 启动与 CDP URL 发现 |
| browser/browser-viewer/src/types.ts | CLIProvider与BrowserViewerConfig类型 |
| browser/browser-viewer/src/tests/browser-viewer.test.ts | 行为测试 |
| browser/browser-viewer/package.json | 版本、依赖与引擎要求 |
| docs/src/content/en/integrations/browsers/browser-viewer.mdx | 集成指南(Quickstart、CLI 安装) |
| docs/src/content/en/reference/browser/browser-viewer.mdx | 完整 API 参考(参数表、方法签名) |
| packages/core/src/browser/browser.ts | 基类BrowserConfigBase配置约束 |
【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考