Stagehand × Eve Agent:三工具驱动持久化浏览器的 Agent 指令契约解析
【免费下载链接】stagehandThe SDK For Browser Agents项目地址: https://gitcode.com/GitHub_Trending/stag/stagehand
在 Stagehand 开源仓库中,Eve 集成示例通过facade(门面)模式把 Stagehand 的浏览器能力直接暴露为 Eve Agent 的原生工具,并配套了一份作为 Agent 系统提示词的指令文档 agent/instructions.md。这份文档只有 10 行,却是整个工具面(tool surface)的“使用契约”:它规定了 Agent 只能通过snapshot、run、screenshot三个工具操作浏览器,并约束了快照 ID 的时效性、动作字段的命名(op/id)以及会话的关闭方式。读完本文,你将完整理解这三工具契约的每一个细节、它在 contract.ts 中的源码级定义与运行时校验,以及如何搭建环境、运行示例并遵守其安全与会话生命周期约束。
一、指令文档全貌:三工具契约原文
instructions.md是注入 Eve Agent 的规范提示词(canonical prompt)。原文如下:
You control one persistent browser through exactly three tools:
- snapshot: inspect the active page and hydrate bracketed element IDs.
- run: provide either snapshot actions or JavaScript using the Playwright-shaped page API.
- screenshot: inspect the rendered page visually.
Use snapshot actions for simple interactions and run code for multi-step workflows. Pass run exactly one of code or actions; every action uses "op" and "id", never "kind" or "ref". Snapshot IDs are valid only for the latest snapshot of the active page; snapshot again after navigation or stale IDs. Do not launch another browser. To end a session you must run browser.close() to close the browser.
逐条拆解,这份契约包含五条核心规则:
- 工具边界:Agent 只能接触三个工具,不存在独立的
navigate或start工具,导航一律通过run里的 JavaScript 完成(await page.goto(...))。 - snapshot 的职责:检查当前活动页面的无障碍树(accessibility tree),并把可交互元素的 ID 以
[bracketed]形式“水合”(hydrate)出来,供后续run动作引用。 - run 的双模式:要么传
code(Playwright 形状的页面 API 的 JavaScript),要么传actions(基于快照 ID 的动作批次),二者必须恰好提供一个。 - 字段命名纪律:每个 action 必须使用
"op"与"id",禁止使用"kind"或"ref";快照 ID 只对最新一次快照有效,导航后或 ID 过期必须重新 snapshot。 - 会话纪律:不得另起浏览器;结束会话的唯一方式是执行
browser.close()。
需要说明的是,指令原文中最后一句关于browser.close()的会话关闭指引,属于 Eve facade 示例在自身系统提示词上的本地补充(原文见 instructions.md);而 contract.ts 中定义的规范提示词FACADE_AGENT_INSTRUCTIONS到 "snapshot again after navigation or stale IDs." 即结束,其目的正是让 Eve、Vercel AI SDK、deepagents 等所有宿主框架共享完全一致的 Agent 引导文本。
二、契约的源码出处与测试保证
instructions.md并不是一份“随手写的小纸条”,它由契约测试严格钉死:测试 tools.test.ts 读取instructions.md,与FACADE_AGENT_INSTRUCTIONS进行规范化比较(压缩空行后逐字相等),任何一边的漂移都会导致测试失败。
三工具的描述与输入 Schema 全部定义在 contract.ts:
- run 的工具描述(RUN_TOOL_DESCRIPTION):明确“导航用
await page.goto(...),没有独立的 navigate/start 工具”,并给出三个动作示例:{"actions":[{"op":"click","id":"1-42"}]}、{"actions":[{"op":"fill","id":"2-14","value":"Miami"}]}、{"actions":[{"op":"select","id":"3-9","values":"Lowest price"}]}。这些措辞是模型被提示时所依赖的精确文本(pinned string-exact)。 - snapshot 的工具描述(SNAPSHOT_TOOL_DESCRIPTION):每次调用都会替换活动页面的 ID 映射,这正是“ID 仅对最新快照有效”的底层语义。
- screenshot 的工具描述(SCREENSHOT_TOOL_DESCRIPTION):对尺寸受限的 MCP 客户端建议视口 JPEG,如
{"type":"jpeg","quality":40,"fullPage":false}。
运行时的双重校验
contract.ts 有意把契约写了两遍(文件头部注释):
- JSON Schema 字面量是“线上契约”(wire contract),即通过
tools/list广播给 MCP 客户端的精确字节;const类型的op判别字段与逐属性引导说明无法由 zod 转换保留,故手工编写。 - 底部的 zod Schema是运行时校验器。其中 CodeModeRunInputSchema 用
.refine强制code与actions的互斥性(恰好一个),这正是指令里 “Pass run exactly one of code or actions” 的机器可执行版本。
值得注意的是,契约在顶层刻意省略了oneOf: [{required:["code"]},{required:["actions"]}](注释说明):因为 Eve、Vercel AI SDK 这类基于 AI-SDK 的 MCP 客户端会拒绝带顶层 oneOf 的输入 Schema,导致每次run调用在客户端就失败。互斥性改为由描述文本声明、运行时由.refine兜底。这解释了为何指令文本反复强调“exactly one”。
run 动作支持的操作族
依据 RefActionSchema,actions共支持六种操作:
| op | 必填字段 | 说明 |
|---|---|---|
click | id | 点击快照中的元素 |
hover | id | 悬停 |
fill | id,value | 填入字符串值(如表单输入) |
type | id,text,delay? | 逐键输入,delay为可选的非负毫秒数 |
press | id,key | 按键(如Enter) |
select | id,values | 下拉选择,values可为单个字符串或字符串数组 |
每个动作都必须包含"op"与"id"两个字段(required: ["op", "id", ...]),additionalProperties: false保证多余字段会被拒绝——这是 “neverkindorref” 的机器强制。
三、三工具在 Eve Agent 中的落地实现
Eve 集成把上述契约封装为三个defineTool:
- run.ts:先用
CodeModeRunInputSchema.parse校验输入(测试 tools.test.ts 验证了未传任何参数、或同时传code与actions都会被拒绝),随后按input.code !== undefined分发到tools.run(code)或tools.runActions(actions),结果统一字符串化返回(对象走JSON.stringify(value, null, 2))。出错时调用discardFacadeToolsIfUnhealthy检查连接健康度。 - snapshot.ts:解析
includeIframes后直接返回tools.snapshot(input)的结果。 - screenshot.ts:同样转发到
tools.screenshot(input),并通过toModelOutput把截图以type: "file"、data URI 的形式喂回模型(文本为 "Screenshot captured."),让 Eve 可以“看图”。
三个工具共享同一个由 session.ts 管理的持久化 Stagehand 会话——无需 MCP 连接或任何桥接进程,这正是 “Eve + Stagehand facade(native tools)” 集成名称的由来。Agent 本体 agent.ts 只有一句话:defineAgent({ model: openai(...) }),默认模型gpt-5.6-luna,可用EVE_STAGEHAND_MODEL覆盖。
四、环境变量与运行方式
环境配置
| 变量 | 用途 |
|---|---|
STAGEHAND_BROWSER | 浏览器后端。设置了BROWSERBASE_API_KEY时默认为browserbase,否则为local |
BROWSERBASE_API_KEY | Browserbase API Key,使用 Browserbase 后端时必填 |
STAGEHAND_MODEL_NAME | 可选的 Stagehand 模型名,如openai/gpt-5.6-luna |
STAGEHAND_MODEL_API_KEY | 为STAGEHAND_MODEL_NAME显式指定 API Key;否则在受支持时推断对应提供方 Key |
STAGEHAND_EVE_SESSION_FILE | 持久化 Browserbase 会话 ID 的路径;默认放在系统临时目录 |
EVE_STAGEHAND_MODEL | Eve Agent 模型,默认gpt-5.6-luna |
OPENAI_API_KEY | Eve Agent 模型使用的 OpenAI 凭据,OpenAI 类型的 Stagehand 模型也会推断使用它 |
GOOGLE_GENERATIVE_AI_API_KEY/GEMINI_API_KEY/GOOGLE_API_KEY | Stagehand 推断的 Google 凭据;若设置了其一而未显式配置 Stagehand 模型,模型默认google/gemini-3.6-flash |
构建与测试
示例包名@browserbasehq/stagehand-integrations-example-eve-facade(见 package.json),要求 Node.js 24 及以上。从仓库根目录先构建 integrations 包:
pnpm exec turbo run build --filter @browserbasehq/stagehand-integrations契约测试不需要网络、浏览器或任何 API Key,可离线运行:
pnpm --filter @browserbasehq/stagehand-integrations-example-eve-facade test pnpm --filter @browserbasehq/stagehand-integrations-example-eve-facade typecheck交互式运行与扩展资源解析
配置好浏览器与模型凭据后:
pnpm --filter @browserbasehq/stagehand-integrations-example-eve-facade dev该脚本并不直接调用eve,而是经 scripts/eve-with-stagehand-env.mjs 启动:Eve 的打包器(build 用 nitro、dev-runtime 用 authored-module 编译器)会把@browserbasehq/stagehand内联进 bundle 并重定位import.meta.url,导致 SDK 派生的扩展资源路径在 bundle 内失效。该脚本在bundle 之外解析真实安装的 SDK 资产,并通过STAGEHAND_EXTENSION_ARCHIVE_PATH与STAGEHAND_EXTENSION_DIRECTORY_PATH两个环境变量转发给eve子进程;Windows 下.cmdshim 需要 shell 才能启动,因此shell: process.platform === "win32"。
五、安全模型与 session 生命周期
安全模型
run(code)执行的模型编写的 JavaScript 运行在extension service worker中——即浏览器侧,而不是宿主进程(README.md 的 Security model 一节)。Eve 的 world 进程只持有浏览器会话句柄,模型编写的 JavaScript 不会在 world 进程内执行。官方推荐 Browserbase 作为隔离边界。
会话生命周期
从 session.ts 的源码可以还原出完整的生命周期设计:
- 单会话共享:每个 Eve world 进程持有一个共享浏览器会话。同一进程服务的并发 Eve 会话会共享页面、认证与其他浏览器状态,因此该示例仅面向单会话使用。
- Browserbase 会话持久化:通过
browserbase.launch({ ..., keepAlive: true })(createResources)创建 keep-alive 会话,并把 sessionId 写入临时文件;STAGEHAND_EVE_SESSION_FILE可覆盖默认路径(默认路径按 API Key 的 SHA-256 前 8 位做作用域隔离,见 sessionFilePath,避免多进程/多 Key 互相抢占)。进程重启后重新挂载(reattach)该会话,而不是再创建一个遗留会话。 - 保持在线即计费:等待复用的期间,会话持续运行并计费,直到被重新挂载、通过 Browserbase 控制台或 API 释放、或达到项目超时。
- 无“卡死”(no brick)与无“遗留”(no strand)两个不变量:连接失败的会话最多重试一次(
connectToSession带 500ms 退避);若会话“可连接但已损坏”(wedged),会被标记为suspectSessionId,下次创建时先 best-effort 释放旧 keep-alive 会话再全新启动,绝不反复重连死循环;释放失败被吞掉,避免阻塞全新启动。 - 错误不重置会话:模型编写的工具代码出错不会重置会话;只有当连接不健康(
browser.closed或context.pages()抛错)时才会重建浏览器会话(discardFacadeToolsIfUnhealthy)。
六、从指令到可运行的实战总结
- Agent 视角:永远只依赖三个工具——先
snapshot拿到当前页面的水合 ID,简单交互用actions批次(op/id),多步骤工作流用run的 JavaScript(Playwright 形状的pageAPI);每轮只喂code或actions其一,导航或 ID 失效后立刻重新snapshot。 - 接入视角:
instructions.md是规范提示词,与 contract.ts 中的FACADE_AGENT_INSTRUCTIONS逐字对齐并由测试守护;任何自研宿主集成都应复用这份文本与FACADE_TOOLS定义,以保证跨框架(Eve、Vercel AI SDK、deepagents)的 Agent 引导完全一致。 - 运维视角:本地开发用
local后端零成本跑通契约测试;生产建议用 Browserbase 后端并接受 keep-alive 会话的计费模型,借助STAGEHAND_EVE_SESSION_FILE持久化、重启自动重挂载;记得通过browser.close()结束会话以停止计费。
【免费下载链接】stagehandThe SDK For Browser Agents项目地址: https://gitcode.com/GitHub_Trending/stag/stagehand
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考