vercel-sandbox by vercel-labs/agent-browser
npx skills add https://github.com/vercel-labs/agent-browser --skill vercel-sandbox在临时的 Vercel Sandbox 微虚拟机中运行 agent-browser + 无头 Chrome。Linux 虚拟机按需启动,执行浏览器命令,然后关闭。适用于任何 Vercel 部署的框架(Next.js、SvelteKit、Nuxt、Remix、Astro 等)。
pnpm add @vercel/sandbox
沙盒虚拟机需要 Chromium 的系统依赖项以及 agent-browser 本身。使用沙盒快照(见下文)预先安装所有内容,以实现亚秒级启动。
import { Sandbox } from "@vercel/sandbox";
// 沙盒虚拟机(Amazon Linux / dnf)上 Chromium 所需的系统库
const CHROMIUM_SYSTEM_DEPS = [
"nss", "nspr", "libxkbcommon", "atk", "at-spi2-atk", "at-spi2-core",
"libXcomposite", "libXdamage", "libXrandr", "libXfixes", "libXcursor",
"libXi", "libXtst", "libXScrnSaver", "libXext", "mesa-libgbm", "libdrm",
"mesa-libGL", "mesa-libEGL", "cups-libs", "alsa-lib", "pango", "cairo",
"gtk3", "dbus-libs",
];
function getSandboxCredentials() {
if (
process.env.VERCEL_TOKEN &&
process.env.VERCEL_TEAM_ID &&
process.env.VERCEL_PROJECT_ID
) {
return {
token: process.env.VERCEL_TOKEN,
teamId: process.env.VERCEL_TEAM_ID,
projectId: process.env.VERCEL_PROJECT_ID,
};
}
return {};
}
async function withBrowser<T>(
fn: (sandbox: InstanceType<typeof Sandbox>) => Promise<T>,
): Promise<T> {
const snapshotId = process.env.AGENT_BROWSER_SNAPSHOT_ID;
const credentials = getSandboxCredentials();
const sandbox = snapshotId
? await Sandbox.create({
...credentials,
source: { type: "snapshot", snapshotId },
timeout: 120_000,
})
: await Sandbox.create({ ...credentials, runtime: "node24", timeout: 120_000 });
if (!snapshotId) {
await sandbox.runCommand("sh", [
"-c",
`sudo dnf clean all 2>&1 && sudo dnf install -y --skip-broken ${CHROMIUM_SYSTEM_DEPS.join(" ")} 2>&1 && sudo ldconfig 2>&1`,
]);
await sandbox.runCommand("npm", ["install", "-g", "agent-browser"]);
await sandbox.runCommand("npx", ["agent-browser", "install"]);
}
try {
return await fn(sandbox);
} finally {
await sandbox.stop();
}
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
screenshot --json 命令保存到文件并返回路径。将文件读回为 base64:
export async function screenshotUrl(url: string) {
return withBrowser(async (sandbox) => {
await sandbox.runCommand("agent-browser", ["open", url]);
const titleResult = await sandbox.runCommand("agent-browser", [
"get", "title", "--json",
]);
const title = JSON.parse(await titleResult.stdout())?.data?.title || url;
const ssResult = await sandbox.runCommand("agent-browser", [
"screenshot", "--json",
]);
const ssPath = JSON.parse(await ssResult.stdout())?.data?.path;
const b64Result = await sandbox.runCommand("base64", ["-w", "0", ssPath]);
const screenshot = (await b64Result.stdout()).trim();
await sandbox.runCommand("agent-browser", ["close"]);
return { title, screenshot };
});
}
export async function snapshotUrl(url: string) {
return withBrowser(async (sandbox) => {
await sandbox.runCommand("agent-browser", ["open", url]);
const titleResult = await sandbox.runCommand("agent-browser", [
"get", "title", "--json",
]);
const title = JSON.parse(await titleResult.stdout())?.data?.title || url;
const snapResult = await sandbox.runCommand("agent-browser", [
"snapshot", "-i", "-c",
]);
const snapshot = await snapResult.stdout();
await sandbox.runCommand("agent-browser", ["close"]);
return { title, snapshot };
});
}
沙盒在命令之间保持持久性,因此您可以运行完整的自动化序列:
export async function fillAndSubmitForm(url: string, data: Record<string, string>) {
return withBrowser(async (sandbox) => {
await sandbox.runCommand("agent-browser", ["open", url]);
const snapResult = await sandbox.runCommand("agent-browser", [
"snapshot", "-i",
]);
const snapshot = await snapResult.stdout();
// 解析快照以查找元素引用...
for (const [ref, value] of Object.entries(data)) {
await sandbox.runCommand("agent-browser", ["fill", ref, value]);
}
await sandbox.runCommand("agent-browser", ["click", "@e5"]);
await sandbox.runCommand("agent-browser", ["wait", "--load", "networkidle"]);
const ssResult = await sandbox.runCommand("agent-browser", [
"screenshot", "--json",
]);
const ssPath = JSON.parse(await ssResult.stdout())?.data?.path;
const b64Result = await sandbox.runCommand("base64", ["-w", "0", ssPath]);
const screenshot = (await b64Result.stdout()).trim();
await sandbox.runCommand("agent-browser", ["close"]);
return { screenshot };
});
}
沙盒快照 是一个已保存的 Vercel Sandbox 虚拟机镜像,其中已预先安装了系统依赖项 + agent-browser + Chromium。可以将其视为类似 Docker 镜像——无需每次都从头安装依赖项,沙盒可以从预构建的镜像启动。
这与 agent-browser 的 无障碍快照 功能(agent-browser snapshot)无关,后者是转储页面的无障碍树。沙盒快照是 Vercel 基础设施的一个概念,用于实现快速的虚拟机启动。
如果没有沙盒快照,每次运行都需要安装系统依赖项 + agent-browser + Chromium(约 30 秒)。使用快照后,启动时间可降至亚秒级。
快照必须包含系统依赖项(通过 dnf)、agent-browser 和 Chromium:
import { Sandbox } from "@vercel/sandbox";
const CHROMIUM_SYSTEM_DEPS = [
"nss", "nspr", "libxkbcommon", "atk", "at-spi2-atk", "at-spi2-core",
"libXcomposite", "libXdamage", "libXrandr", "libXfixes", "libXcursor",
"libXi", "libXtst", "libXScrnSaver", "libXext", "mesa-libgbm", "libdrm",
"mesa-libGL", "mesa-libEGL", "cups-libs", "alsa-lib", "pango", "cairo",
"gtk3", "dbus-libs",
];
async function createSnapshot(): Promise<string> {
const sandbox = await Sandbox.create({
runtime: "node24",
timeout: 300_000,
});
await sandbox.runCommand("sh", [
"-c",
`sudo dnf clean all 2>&1 && sudo dnf install -y --skip-broken ${CHROMIUM_SYSTEM_DEPS.join(" ")} 2>&1 && sudo ldconfig 2>&1`,
]);
await sandbox.runCommand("npm", ["install", "-g", "agent-browser"]);
await sandbox.runCommand("npx", ["agent-browser", "install"]);
const snapshot = await sandbox.snapshot();
return snapshot.snapshotId;
}
运行此代码一次,然后设置环境变量:
AGENT_BROWSER_SNAPSHOT_ID=snap_xxxxxxxxxxxx
演示应用中提供了一个辅助脚本:
npx tsx examples/environments/scripts/create-snapshot.ts
建议任何使用沙盒模式的生产部署都采用此方法。
在 Vercel 部署上,Sandbox SDK 通过 OIDC 自动进行身份验证。对于本地开发或显式控制,请设置:
VERCEL_TOKEN=<personal-access-token>
VERCEL_TEAM_ID=<team-id>
VERCEL_PROJECT_ID=<project-id>
这些变量会传入 Sandbox.create() 调用。当它们不存在时,SDK 会回退到 VERCEL_OIDC_TOKEN(在 Vercel 上自动设置)。
与 Vercel Cron Jobs 结合使用,用于重复性的浏览器任务:
// app/api/cron/route.ts (或您框架中的等效位置)
export async function GET() {
const result = await withBrowser(async (sandbox) => {
await sandbox.runCommand("agent-browser", ["open", "https://example.com/pricing"]);
const snap = await sandbox.runCommand("agent-browser", ["snapshot", "-i", "-c"]);
await sandbox.runCommand("agent-browser", ["close"]);
return await snap.stdout();
});
// 处理结果、发送警报、存储数据...
return Response.json({ ok: true, snapshot: result });
}
// vercel.json
{ "crons": [{ "path": "/api/cron", "schedule": "0 9 * * *" }] }
| 变量 | 是否必需 | 描述 |
|---|---|---|
AGENT_BROWSER_SNAPSHOT_ID | 否(但推荐) | 用于亚秒级启动的预构建沙盒快照 ID(见上文) |
VERCEL_TOKEN | 否 | Vercel 个人访问令牌(用于本地开发;在 Vercel 上 OIDC 是自动的) |
VERCEL_TEAM_ID | 否 | Vercel 团队 ID(用于本地开发) |
VERCEL_PROJECT_ID | 否 | Vercel 项目 ID(用于本地开发) |
该模式在所有框架中的工作方式相同。唯一的区别在于服务器端代码的存放位置:
| 框架 | 服务器代码位置 |
|---|---|
| Next.js | Server actions、API routes、route handlers |
| SvelteKit | +page.server.ts、+server.ts |
| Nuxt | server/api/、server/routes/ |
| Remix | loader、action 函数 |
| Astro | .astro frontmatter、API routes |
请参阅 agent-browser 仓库中的 examples/environments/,这是一个使用 Vercel Sandbox 模式的工作应用,包括沙盒快照创建脚本、流式进度 UI 和速率限制。
每周安装量
5.4K
仓库
GitHub 星标数
24.7K
首次出现
2026年3月9日
安全审计
安装于
codex5.0K
opencode5.0K
gemini-cli5.0K
cursor5.0K
github-copilot5.0K
kimi-cli5.0K
Run agent-browser + headless Chrome inside ephemeral Vercel Sandbox microVMs. A Linux VM spins up on demand, executes browser commands, and shuts down. Works with any Vercel-deployed framework (Next.js, SvelteKit, Nuxt, Remix, Astro, etc.).
pnpm add @vercel/sandbox
The sandbox VM needs system dependencies for Chromium plus agent-browser itself. Use sandbox snapshots (below) to pre-install everything for sub-second startup.
import { Sandbox } from "@vercel/sandbox";
// System libraries required by Chromium on the sandbox VM (Amazon Linux / dnf)
const CHROMIUM_SYSTEM_DEPS = [
"nss", "nspr", "libxkbcommon", "atk", "at-spi2-atk", "at-spi2-core",
"libXcomposite", "libXdamage", "libXrandr", "libXfixes", "libXcursor",
"libXi", "libXtst", "libXScrnSaver", "libXext", "mesa-libgbm", "libdrm",
"mesa-libGL", "mesa-libEGL", "cups-libs", "alsa-lib", "pango", "cairo",
"gtk3", "dbus-libs",
];
function getSandboxCredentials() {
if (
process.env.VERCEL_TOKEN &&
process.env.VERCEL_TEAM_ID &&
process.env.VERCEL_PROJECT_ID
) {
return {
token: process.env.VERCEL_TOKEN,
teamId: process.env.VERCEL_TEAM_ID,
projectId: process.env.VERCEL_PROJECT_ID,
};
}
return {};
}
async function withBrowser<T>(
fn: (sandbox: InstanceType<typeof Sandbox>) => Promise<T>,
): Promise<T> {
const snapshotId = process.env.AGENT_BROWSER_SNAPSHOT_ID;
const credentials = getSandboxCredentials();
const sandbox = snapshotId
? await Sandbox.create({
...credentials,
source: { type: "snapshot", snapshotId },
timeout: 120_000,
})
: await Sandbox.create({ ...credentials, runtime: "node24", timeout: 120_000 });
if (!snapshotId) {
await sandbox.runCommand("sh", [
"-c",
`sudo dnf clean all 2>&1 && sudo dnf install -y --skip-broken ${CHROMIUM_SYSTEM_DEPS.join(" ")} 2>&1 && sudo ldconfig 2>&1`,
]);
await sandbox.runCommand("npm", ["install", "-g", "agent-browser"]);
await sandbox.runCommand("npx", ["agent-browser", "install"]);
}
try {
return await fn(sandbox);
} finally {
await sandbox.stop();
}
}
The screenshot --json command saves to a file and returns the path. Read the file back as base64:
export async function screenshotUrl(url: string) {
return withBrowser(async (sandbox) => {
await sandbox.runCommand("agent-browser", ["open", url]);
const titleResult = await sandbox.runCommand("agent-browser", [
"get", "title", "--json",
]);
const title = JSON.parse(await titleResult.stdout())?.data?.title || url;
const ssResult = await sandbox.runCommand("agent-browser", [
"screenshot", "--json",
]);
const ssPath = JSON.parse(await ssResult.stdout())?.data?.path;
const b64Result = await sandbox.runCommand("base64", ["-w", "0", ssPath]);
const screenshot = (await b64Result.stdout()).trim();
await sandbox.runCommand("agent-browser", ["close"]);
return { title, screenshot };
});
}
export async function snapshotUrl(url: string) {
return withBrowser(async (sandbox) => {
await sandbox.runCommand("agent-browser", ["open", url]);
const titleResult = await sandbox.runCommand("agent-browser", [
"get", "title", "--json",
]);
const title = JSON.parse(await titleResult.stdout())?.data?.title || url;
const snapResult = await sandbox.runCommand("agent-browser", [
"snapshot", "-i", "-c",
]);
const snapshot = await snapResult.stdout();
await sandbox.runCommand("agent-browser", ["close"]);
return { title, snapshot };
});
}
The sandbox persists between commands, so you can run full automation sequences:
export async function fillAndSubmitForm(url: string, data: Record<string, string>) {
return withBrowser(async (sandbox) => {
await sandbox.runCommand("agent-browser", ["open", url]);
const snapResult = await sandbox.runCommand("agent-browser", [
"snapshot", "-i",
]);
const snapshot = await snapResult.stdout();
// Parse snapshot to find element refs...
for (const [ref, value] of Object.entries(data)) {
await sandbox.runCommand("agent-browser", ["fill", ref, value]);
}
await sandbox.runCommand("agent-browser", ["click", "@e5"]);
await sandbox.runCommand("agent-browser", ["wait", "--load", "networkidle"]);
const ssResult = await sandbox.runCommand("agent-browser", [
"screenshot", "--json",
]);
const ssPath = JSON.parse(await ssResult.stdout())?.data?.path;
const b64Result = await sandbox.runCommand("base64", ["-w", "0", ssPath]);
const screenshot = (await b64Result.stdout()).trim();
await sandbox.runCommand("agent-browser", ["close"]);
return { screenshot };
});
}
A sandbox snapshot is a saved VM image of a Vercel Sandbox with system dependencies + agent-browser + Chromium already installed. Think of it like a Docker image -- instead of installing dependencies from scratch every time, the sandbox boots from the pre-built image.
This is unrelated to agent-browser's accessibility snapshot feature (agent-browser snapshot), which dumps a page's accessibility tree. A sandbox snapshot is a Vercel infrastructure concept for fast VM startup.
Without a sandbox snapshot, each run installs system deps + agent-browser + Chromium (~30s). With one, startup is sub-second.
The snapshot must include system dependencies (via dnf), agent-browser, and Chromium:
import { Sandbox } from "@vercel/sandbox";
const CHROMIUM_SYSTEM_DEPS = [
"nss", "nspr", "libxkbcommon", "atk", "at-spi2-atk", "at-spi2-core",
"libXcomposite", "libXdamage", "libXrandr", "libXfixes", "libXcursor",
"libXi", "libXtst", "libXScrnSaver", "libXext", "mesa-libgbm", "libdrm",
"mesa-libGL", "mesa-libEGL", "cups-libs", "alsa-lib", "pango", "cairo",
"gtk3", "dbus-libs",
];
async function createSnapshot(): Promise<string> {
const sandbox = await Sandbox.create({
runtime: "node24",
timeout: 300_000,
});
await sandbox.runCommand("sh", [
"-c",
`sudo dnf clean all 2>&1 && sudo dnf install -y --skip-broken ${CHROMIUM_SYSTEM_DEPS.join(" ")} 2>&1 && sudo ldconfig 2>&1`,
]);
await sandbox.runCommand("npm", ["install", "-g", "agent-browser"]);
await sandbox.runCommand("npx", ["agent-browser", "install"]);
const snapshot = await sandbox.snapshot();
return snapshot.snapshotId;
}
Run this once, then set the environment variable:
AGENT_BROWSER_SNAPSHOT_ID=snap_xxxxxxxxxxxx
A helper script is available in the demo app:
npx tsx examples/environments/scripts/create-snapshot.ts
Recommended for any production deployment using the Sandbox pattern.
On Vercel deployments, the Sandbox SDK authenticates automatically via OIDC. For local development or explicit control, set:
VERCEL_TOKEN=<personal-access-token>
VERCEL_TEAM_ID=<team-id>
VERCEL_PROJECT_ID=<project-id>
These are spread into Sandbox.create() calls. When absent, the SDK falls back to VERCEL_OIDC_TOKEN (automatic on Vercel).
Combine with Vercel Cron Jobs for recurring browser tasks:
// app/api/cron/route.ts (or equivalent in your framework)
export async function GET() {
const result = await withBrowser(async (sandbox) => {
await sandbox.runCommand("agent-browser", ["open", "https://example.com/pricing"]);
const snap = await sandbox.runCommand("agent-browser", ["snapshot", "-i", "-c"]);
await sandbox.runCommand("agent-browser", ["close"]);
return await snap.stdout();
});
// Process results, send alerts, store data...
return Response.json({ ok: true, snapshot: result });
}
// vercel.json
{ "crons": [{ "path": "/api/cron", "schedule": "0 9 * * *" }] }
| Variable | Required | Description |
|---|---|---|
AGENT_BROWSER_SNAPSHOT_ID | No (but recommended) | Pre-built sandbox snapshot ID for sub-second startup (see above) |
VERCEL_TOKEN | No | Vercel personal access token (for local dev; OIDC is automatic on Vercel) |
VERCEL_TEAM_ID | No | Vercel team ID (for local dev) |
VERCEL_PROJECT_ID | No | Vercel project ID (for local dev) |
The pattern works identically across frameworks. The only difference is where you put the server-side code:
| Framework | Server code location |
|---|---|
| Next.js | Server actions, API routes, route handlers |
| SvelteKit | +page.server.ts, +server.ts |
| Nuxt | server/api/, server/routes/ |
| Remix | loader, action functions |
| Astro | .astro frontmatter, API routes |
See examples/environments/ in the agent-browser repo for a working app with the Vercel Sandbox pattern, including a sandbox snapshot creation script, streaming progress UI, and rate limiting.
Weekly Installs
5.4K
Repository
GitHub Stars
24.7K
First Seen
Mar 9, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
codex5.0K
opencode5.0K
gemini-cli5.0K
cursor5.0K
github-copilot5.0K
kimi-cli5.0K
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装
AI智能体长期记忆系统 - 精英级架构,融合6种方法,永不丢失上下文
1,200 周安装
AI新闻播客制作技能:实时新闻转对话式播客脚本与音频生成
1,200 周安装
Word文档处理器:DOCX创建、编辑、分析与修订痕迹处理全指南 | 自动化办公解决方案
1,200 周安装
React Router 框架模式指南:全栈开发、文件路由、数据加载与渲染策略
1,200 周安装
Nano Banana AI 图像生成工具:使用 Gemini 3 Pro 生成与编辑高分辨率图像
1,200 周安装
SVG Logo Designer - AI 驱动的专业矢量标识设计工具,生成可缩放品牌标识
1,200 周安装