+73
-12
@@ -16,23 +16,58 @@ pnpm add @vercel/sandbox
|
|||||||
|
|
||||||
## Server action
|
## Server action
|
||||||
|
|
||||||
|
The Vercel Sandbox runs Amazon Linux. Chromium requires system libraries
|
||||||
|
that are not installed by default, so fresh sandboxes need a `dnf install`
|
||||||
|
step before agent-browser can launch Chrome. Use a sandbox snapshot
|
||||||
|
(below) to skip this entirely in production.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
"use server";
|
"use server";
|
||||||
import { Sandbox } from "@vercel/sandbox";
|
import { Sandbox } from "@vercel/sandbox";
|
||||||
|
|
||||||
const snapshotId = process.env.AGENT_BROWSER_SNAPSHOT_ID;
|
const snapshotId = process.env.AGENT_BROWSER_SNAPSHOT_ID;
|
||||||
|
|
||||||
|
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>(
|
async function withBrowser<T>(
|
||||||
fn: (sandbox: InstanceType<typeof Sandbox>) => Promise<T>,
|
fn: (sandbox: InstanceType<typeof Sandbox>) => Promise<T>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
|
const credentials = getSandboxCredentials();
|
||||||
|
|
||||||
const sandbox = snapshotId
|
const sandbox = snapshotId
|
||||||
? await Sandbox.create({
|
? await Sandbox.create({
|
||||||
|
...credentials,
|
||||||
source: { type: "snapshot", snapshotId },
|
source: { type: "snapshot", snapshotId },
|
||||||
timeout: 120_000,
|
timeout: 120_000,
|
||||||
})
|
})
|
||||||
: await Sandbox.create({ runtime: "node24", timeout: 120_000 });
|
: await Sandbox.create({ ...credentials, runtime: "node24", timeout: 120_000 });
|
||||||
|
|
||||||
if (!snapshotId) {
|
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("npm", ["install", "-g", "agent-browser"]);
|
||||||
await sandbox.runCommand("npx", ["agent-browser", "install"]);
|
await sandbox.runCommand("npx", ["agent-browser", "install"]);
|
||||||
}
|
}
|
||||||
@@ -48,13 +83,15 @@ export async function screenshotUrl(url: string) {
|
|||||||
return withBrowser(async (sandbox) => {
|
return withBrowser(async (sandbox) => {
|
||||||
await sandbox.runCommand("agent-browser", ["open", url]);
|
await sandbox.runCommand("agent-browser", ["open", url]);
|
||||||
|
|
||||||
const result = await sandbox.runCommand("agent-browser", [
|
const ssResult = await sandbox.runCommand("agent-browser", [
|
||||||
"screenshot", "--json",
|
"screenshot", "--json",
|
||||||
]);
|
]);
|
||||||
const data = JSON.parse(await result.stdout());
|
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"]);
|
await sandbox.runCommand("agent-browser", ["close"]);
|
||||||
return { ok: true, screenshot: data.data.base64 };
|
return { ok: true, screenshot };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,11 +112,12 @@ export async function snapshotUrl(url: string) {
|
|||||||
|
|
||||||
## Sandbox snapshots
|
## Sandbox snapshots
|
||||||
|
|
||||||
Without optimization, each Sandbox run installs agent-browser + Chromium
|
Without optimization, each Sandbox run installs system dependencies +
|
||||||
from scratch (~30 seconds). A **sandbox snapshot** is a saved VM image
|
agent-browser + Chromium from scratch (~30 seconds). A **sandbox snapshot**
|
||||||
with everything pre-installed -- like a Docker image for Vercel Sandbox.
|
is a saved VM image with everything pre-installed -- like a Docker image
|
||||||
When `AGENT_BROWSER_SNAPSHOT_ID` is set, the sandbox boots from that
|
for Vercel Sandbox. When `AGENT_BROWSER_SNAPSHOT_ID` is set, the sandbox
|
||||||
image instead of installing, bringing startup down to sub-second.
|
boots from that image instead of installing, bringing startup down to
|
||||||
|
sub-second.
|
||||||
|
|
||||||
This is different from an agent-browser *accessibility snapshot* (which
|
This is different from an agent-browser *accessibility snapshot* (which
|
||||||
dumps a page's accessibility tree). A sandbox snapshot is a Vercel
|
dumps a page's accessibility tree). A sandbox snapshot is a Vercel
|
||||||
@@ -91,8 +129,8 @@ Create a sandbox snapshot by running the helper script once:
|
|||||||
npx tsx scripts/create-snapshot.ts
|
npx tsx scripts/create-snapshot.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
The script spins up a fresh sandbox, installs agent-browser + Chromium,
|
The script spins up a fresh sandbox, installs system dependencies +
|
||||||
saves the VM state, and prints the snapshot ID:
|
agent-browser + Chromium, saves the VM state, and prints the snapshot ID:
|
||||||
|
|
||||||
```
|
```
|
||||||
AGENT_BROWSER_SNAPSHOT_ID=snap_xxxxxxxxxxxx
|
AGENT_BROWSER_SNAPSHOT_ID=snap_xxxxxxxxxxxx
|
||||||
@@ -101,6 +139,25 @@ AGENT_BROWSER_SNAPSHOT_ID=snap_xxxxxxxxxxxx
|
|||||||
Add this to your Vercel project environment variables (or `.env.local`
|
Add this to your Vercel project environment variables (or `.env.local`
|
||||||
for local development). Recommended for any production deployment.
|
for local development). Recommended for any production deployment.
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
On Vercel deployments, the Sandbox SDK authenticates automatically via
|
||||||
|
OIDC. For local development, provide explicit credentials:
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr><th>Variable</th><th>Description</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>VERCEL_TOKEN</code></td><td>Vercel personal access token</td></tr>
|
||||||
|
<tr><td><code>VERCEL_TEAM_ID</code></td><td>Vercel team ID</td></tr>
|
||||||
|
<tr><td><code>VERCEL_PROJECT_ID</code></td><td>Vercel project ID</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
When all three are set, they are passed to `Sandbox.create()`. When
|
||||||
|
absent, the SDK falls back to `VERCEL_OIDC_TOKEN` (automatic on Vercel).
|
||||||
|
|
||||||
## Scheduled workflows (cron)
|
## Scheduled workflows (cron)
|
||||||
|
|
||||||
For recurring tasks like daily monitoring, use Vercel Cron Jobs:
|
For recurring tasks like daily monitoring, use Vercel Cron Jobs:
|
||||||
@@ -141,10 +198,14 @@ export async function GET() {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr><td><code>AGENT_BROWSER_SNAPSHOT_ID</code></td><td>Sandbox snapshot ID for sub-second startup (see above)</td></tr>
|
<tr><td><code>AGENT_BROWSER_SNAPSHOT_ID</code></td><td>Sandbox snapshot ID for sub-second startup (see above)</td></tr>
|
||||||
|
<tr><td><code>VERCEL_TOKEN</code></td><td>Vercel personal access token (for local dev; OIDC is automatic on Vercel)</td></tr>
|
||||||
|
<tr><td><code>VERCEL_TEAM_ID</code></td><td>Vercel team ID (for local dev)</td></tr>
|
||||||
|
<tr><td><code>VERCEL_PROJECT_ID</code></td><td>Vercel project ID (for local dev)</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
## Demo app
|
## Demo app
|
||||||
|
|
||||||
A working demo with a UI and deploy-to-Vercel button is at
|
A working demo with streaming progress UI, rate limiting, and a
|
||||||
|
deploy-to-Vercel button is at
|
||||||
[`examples/environments/`](https://github.com/agent-browser/agent-browser/tree/main/examples/environments).
|
[`examples/environments/`](https://github.com/agent-browser/agent-browser/tree/main/examples/environments).
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
# agent-browser Environments
|
# agent-browser Environments
|
||||||
|
|
||||||
A demo of agent-browser running in a Vercel Sandbox. Enter a URL and take a screenshot or accessibility snapshot.
|
A demo of agent-browser running in a Vercel Sandbox. Pick a URL, take a screenshot or accessibility snapshot, and watch each command execute in real time.
|
||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
The app runs agent-browser + Chrome inside an ephemeral Vercel Sandbox microVM. A Linux VM spins up on demand, executes agent-browser commands, and shuts down. No binary size limits, no Chromium bundling complexity.
|
The app runs agent-browser + Chrome inside an ephemeral Vercel Sandbox microVM. A Linux VM spins up on demand, executes agent-browser commands, and shuts down. No binary size limits, no Chromium bundling complexity.
|
||||||
|
|
||||||
|
The UI streams progress via Server-Sent Events so you can see each step as it runs (sandbox creation, browser startup, navigation, screenshot/snapshot, cleanup).
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -14,9 +16,11 @@ pnpm install
|
|||||||
pnpm dev
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For local development, set `VERCEL_TOKEN`, `VERCEL_TEAM_ID`, and `VERCEL_PROJECT_ID` in `.env.local` so the Sandbox SDK can authenticate.
|
||||||
|
|
||||||
## Sandbox Snapshots
|
## Sandbox Snapshots
|
||||||
|
|
||||||
Without optimization, each Sandbox run installs agent-browser + Chromium from scratch (~30s). A **sandbox snapshot** is a saved VM image with everything pre-installed -- the sandbox boots from the image instead of installing, bringing startup down to sub-second. (This is unrelated to agent-browser's *accessibility snapshot* feature, which dumps a page's accessibility tree.)
|
Without optimization, each Sandbox run installs system dependencies + agent-browser + Chromium from scratch (~30s). A **sandbox snapshot** is a saved VM image with everything pre-installed -- the sandbox boots from the image instead of installing, bringing startup down to sub-second. (This is unrelated to agent-browser's *accessibility snapshot* feature, which dumps a page's accessibility tree.)
|
||||||
|
|
||||||
Create a sandbox snapshot by running the helper script once:
|
Create a sandbox snapshot by running the helper script once:
|
||||||
|
|
||||||
@@ -32,6 +36,9 @@ Add the ID to your Vercel project environment variables or `.env.local`. Recomme
|
|||||||
| Variable | Description |
|
| Variable | Description |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `AGENT_BROWSER_SNAPSHOT_ID` | Sandbox snapshot ID for sub-second startup (see above) |
|
| `AGENT_BROWSER_SNAPSHOT_ID` | Sandbox snapshot ID for sub-second startup (see above) |
|
||||||
|
| `VERCEL_TOKEN` | Vercel personal access token (for local dev; OIDC is automatic on Vercel) |
|
||||||
|
| `VERCEL_TEAM_ID` | Vercel team ID (for local dev) |
|
||||||
|
| `VERCEL_PROJECT_ID` | Vercel project ID (for local dev) |
|
||||||
| `KV_REST_API_URL` | Upstash Redis URL for rate limiting (optional) |
|
| `KV_REST_API_URL` | Upstash Redis URL for rate limiting (optional) |
|
||||||
| `KV_REST_API_TOKEN` | Upstash Redis token for rate limiting (optional) |
|
| `KV_REST_API_TOKEN` | Upstash Redis token for rate limiting (optional) |
|
||||||
| `RATE_LIMIT_PER_MINUTE` | Max requests per minute per IP (default: 10) |
|
| `RATE_LIMIT_PER_MINUTE` | Max requests per minute per IP (default: 10) |
|
||||||
@@ -42,11 +49,11 @@ Add the ID to your Vercel project environment variables or `.env.local`. Recomme
|
|||||||
```
|
```
|
||||||
examples/environments/
|
examples/environments/
|
||||||
app/
|
app/
|
||||||
page.tsx # Demo UI
|
page.tsx # Demo UI with streaming progress
|
||||||
actions/browse.ts # Server actions
|
actions/browse.ts # Server action (env status check)
|
||||||
api/browse/route.ts # API route for programmatic access
|
api/browse/route.ts # Streaming SSE endpoint
|
||||||
lib/
|
lib/
|
||||||
agent-browser-sandbox.ts # Vercel Sandbox client
|
agent-browser-sandbox.ts # Vercel Sandbox client with progress callbacks
|
||||||
constants.ts # Allowed URLs
|
constants.ts # Allowed URLs
|
||||||
rate-limit.ts # Upstash rate limiting
|
rate-limit.ts # Upstash rate limiting
|
||||||
scripts/
|
scripts/
|
||||||
|
|||||||
@@ -13,26 +13,56 @@ Run agent-browser + headless Chrome inside ephemeral Vercel Sandbox microVMs. A
|
|||||||
pnpm add @vercel/sandbox
|
pnpm add @vercel/sandbox
|
||||||
```
|
```
|
||||||
|
|
||||||
The sandbox VM installs agent-browser and Chrome on first run. Use sandbox snapshots (below) to skip this step.
|
The sandbox VM needs system dependencies for Chromium plus agent-browser itself. Use sandbox snapshots (below) to pre-install everything for sub-second startup.
|
||||||
|
|
||||||
## Core Pattern
|
## Core Pattern
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { Sandbox } from "@vercel/sandbox";
|
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>(
|
async function withBrowser<T>(
|
||||||
fn: (sandbox: InstanceType<typeof Sandbox>) => Promise<T>,
|
fn: (sandbox: InstanceType<typeof Sandbox>) => Promise<T>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const snapshotId = process.env.AGENT_BROWSER_SNAPSHOT_ID;
|
const snapshotId = process.env.AGENT_BROWSER_SNAPSHOT_ID;
|
||||||
|
const credentials = getSandboxCredentials();
|
||||||
|
|
||||||
const sandbox = snapshotId
|
const sandbox = snapshotId
|
||||||
? await Sandbox.create({
|
? await Sandbox.create({
|
||||||
|
...credentials,
|
||||||
source: { type: "snapshot", snapshotId },
|
source: { type: "snapshot", snapshotId },
|
||||||
timeout: 120_000,
|
timeout: 120_000,
|
||||||
})
|
})
|
||||||
: await Sandbox.create({ runtime: "node24", timeout: 120_000 });
|
: await Sandbox.create({ ...credentials, runtime: "node24", timeout: 120_000 });
|
||||||
|
|
||||||
if (!snapshotId) {
|
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("npm", ["install", "-g", "agent-browser"]);
|
||||||
await sandbox.runCommand("npx", ["agent-browser", "install"]);
|
await sandbox.runCommand("npx", ["agent-browser", "install"]);
|
||||||
}
|
}
|
||||||
@@ -47,6 +77,8 @@ async function withBrowser<T>(
|
|||||||
|
|
||||||
## Screenshot
|
## Screenshot
|
||||||
|
|
||||||
|
The `screenshot --json` command saves to a file and returns the path. Read the file back as base64:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
export async function screenshotUrl(url: string) {
|
export async function screenshotUrl(url: string) {
|
||||||
return withBrowser(async (sandbox) => {
|
return withBrowser(async (sandbox) => {
|
||||||
@@ -60,7 +92,9 @@ export async function screenshotUrl(url: string) {
|
|||||||
const ssResult = await sandbox.runCommand("agent-browser", [
|
const ssResult = await sandbox.runCommand("agent-browser", [
|
||||||
"screenshot", "--json",
|
"screenshot", "--json",
|
||||||
]);
|
]);
|
||||||
const screenshot = JSON.parse(await ssResult.stdout())?.data?.base64 || "";
|
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"]);
|
await sandbox.runCommand("agent-browser", ["close"]);
|
||||||
|
|
||||||
@@ -118,7 +152,9 @@ export async function fillAndSubmitForm(url: string, data: Record<string, string
|
|||||||
const ssResult = await sandbox.runCommand("agent-browser", [
|
const ssResult = await sandbox.runCommand("agent-browser", [
|
||||||
"screenshot", "--json",
|
"screenshot", "--json",
|
||||||
]);
|
]);
|
||||||
const screenshot = JSON.parse(await ssResult.stdout())?.data?.base64 || "";
|
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"]);
|
await sandbox.runCommand("agent-browser", ["close"]);
|
||||||
|
|
||||||
@@ -129,23 +165,37 @@ export async function fillAndSubmitForm(url: string, data: Record<string, string
|
|||||||
|
|
||||||
## Sandbox Snapshots (Fast Startup)
|
## Sandbox Snapshots (Fast Startup)
|
||||||
|
|
||||||
A **sandbox snapshot** is a saved VM image of a Vercel Sandbox with 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.
|
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.
|
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 agent-browser + Chromium (~30s). With one, startup is sub-second.
|
Without a sandbox snapshot, each run installs system deps + agent-browser + Chromium (~30s). With one, startup is sub-second.
|
||||||
|
|
||||||
### Creating a sandbox snapshot
|
### Creating a sandbox snapshot
|
||||||
|
|
||||||
|
The snapshot must include system dependencies (via `dnf`), agent-browser, and Chromium:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { Sandbox } from "@vercel/sandbox";
|
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> {
|
async function createSnapshot(): Promise<string> {
|
||||||
const sandbox = await Sandbox.create({
|
const sandbox = await Sandbox.create({
|
||||||
runtime: "node24",
|
runtime: "node24",
|
||||||
timeout: 300_000,
|
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("npm", ["install", "-g", "agent-browser"]);
|
||||||
await sandbox.runCommand("npx", ["agent-browser", "install"]);
|
await sandbox.runCommand("npx", ["agent-browser", "install"]);
|
||||||
|
|
||||||
@@ -168,6 +218,18 @@ npx tsx examples/environments/scripts/create-snapshot.ts
|
|||||||
|
|
||||||
Recommended for any production deployment using the Sandbox pattern.
|
Recommended for any production deployment using the Sandbox pattern.
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
On Vercel deployments, the Sandbox SDK authenticates automatically via OIDC. For local development or explicit control, set:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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).
|
||||||
|
|
||||||
## Scheduled Workflows (Cron)
|
## Scheduled Workflows (Cron)
|
||||||
|
|
||||||
Combine with Vercel Cron Jobs for recurring browser tasks:
|
Combine with Vercel Cron Jobs for recurring browser tasks:
|
||||||
@@ -197,8 +259,9 @@ export async function GET() {
|
|||||||
| Variable | Required | Description |
|
| Variable | Required | Description |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `AGENT_BROWSER_SNAPSHOT_ID` | No (but recommended) | Pre-built sandbox snapshot ID for sub-second startup (see above) |
|
| `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) |
|
||||||
The Vercel Sandbox SDK handles OIDC authentication automatically when deployed on Vercel. For local development, run `vercel link` and `vercel env pull` to get the required tokens.
|
| `VERCEL_TEAM_ID` | No | Vercel team ID (for local dev) |
|
||||||
|
| `VERCEL_PROJECT_ID` | No | Vercel project ID (for local dev) |
|
||||||
|
|
||||||
## Framework Examples
|
## Framework Examples
|
||||||
|
|
||||||
@@ -214,4 +277,4 @@ The pattern works identically across frameworks. The only difference is where yo
|
|||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
See `examples/environments/` in the agent-browser repo for a working app with the Vercel Sandbox pattern, including a sandbox snapshot creation script and demo UI.
|
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.
|
||||||
|
|||||||
+5
-3
@@ -2523,9 +2523,11 @@ export class BrowserManager {
|
|||||||
);
|
);
|
||||||
this.browser = null;
|
this.browser = null;
|
||||||
} else if (this.kernelSessionId) {
|
} else if (this.kernelSessionId) {
|
||||||
await this.closeKernelSession(this.kernelSessionId, this.kernelApiKey ?? undefined).catch((error) => {
|
await this.closeKernelSession(this.kernelSessionId, this.kernelApiKey ?? undefined).catch(
|
||||||
console.error('Failed to close Kernel session:', error);
|
(error) => {
|
||||||
});
|
console.error('Failed to close Kernel session:', error);
|
||||||
|
}
|
||||||
|
);
|
||||||
this.browser = null;
|
this.browser = null;
|
||||||
} else if (this.cdpEndpoint !== null) {
|
} else if (this.cdpEndpoint !== null) {
|
||||||
// CDP: only disconnect, don't close external app's pages
|
// CDP: only disconnect, don't close external app's pages
|
||||||
|
|||||||
Reference in New Issue
Block a user