数据截至 (上游 commit 544ec87f2ede)
第 1 章 v4 的外科手术 —— 协议与瘦客户端
本章讲什么: v4 为什么敢把引擎搬进浏览器扩展——因为它先把「客户端 ↔ 引擎」的界面冻成了一份带版本号的协议。本章讲这张契约长什么样、版本怎么协商、客户端薄到什么程度。
1. 为什么需要一张正式协议
v3 时代,Stagehand 是一个 TypeScript npm 包:引擎和你的代码同进程。这有一个隐形上限——引擎被锁死在 JS 生态里,而且引擎升级就逼客户端升级。
v4 把引擎做成 Chrome 扩展后,客户端和引擎之间隔着进程边界,必须有一份正式契约:
packages/protocol/stagehand.v4.json:全部方法(init/act/extract/observe/close…)、参数 schema、通知类型的机器可读清单;packages/protocol/types.ts+schemas.ts:TS 侧的类型与 zod 校验;packages/protocol/protocol-version.ts:版本协商逻辑。
2. 版本协商:SemVer 主版本不对等就拒连
协议版本直接取 packages/protocol/package.json 的 version 字段,经 SemVer 正则校验后成为 STAGEHAND_PROTOCOL_VERSION(protocol-version.ts:14-16)。
扩展侧在 init 时检查客户端报上来的版本(controllers/stagehandController.ts 的 init 里调 checkProtocolCompatibility):
- 非法版本 →
protocol-invalid-version; - 主版本不同 →
protocol-major-mismatch; - 服务端比客户端老 →
protocol-server-too-old; - 预发布号不一致 →
protocol-prerelease-mismatch。
不兼容就抛 StagehandProtocolCompatibilityError——宁可连不上,不让两个不同代的端在半路鸡同鸭讲。这是多语言客户端时代的必需品:Go 客户端和 TS 客户端可能差着好几个发布周期。
3. 客户端薄到什么程度
以 TS SDK 为例(packages/sdk-ts/src/stagehand.ts):
- 参数校验:zod schema(
StagehandClientActOptionsSchema等)在客户端就先拦一遍; - 打包:
act的重载(:206-208)把「字符串指令」和「Action 对象」归一成协议参数; - 发请求:
RPCClient(sdk-ts/src/rpcClient.ts)经 CDP/WebSocket 把 JSON-RPC 发给扩展,收结果。
客户端不知道什么 a11y 树、什么 XPath——它面对的只是「指令进去、ActResult 出来」。BrowserContext/claimStagehandBrowser(sdk-ts/src/stagehand.ts imports)负责的是另一半:帮你在本地 Chrome 里装上扩展、认领一个浏览器实例。
Go/Python 端同理(packages/sdk-go、packages/sdk-python),同一张 stagehand.v4.json 是三端共同的事实源。
4. 这个拆法换来了什么
- 多语言几乎免费:新语言 = 一个 JSON-RPC 客户端 + 协议 schema;
- 引擎贴着浏览器跑:扩展和被控页面同源同进程,CDP 调用不必绕远端;
- 版本各自演进:协议主版本是唯一的硬对齐点,次版本可以错开;
- 代价:多一个「装扩展、连扩展」的启动环节(
claimStagehandBrowser),调试链变长——排错时要分清问题在客户端、协议还是扩展。