Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 125 additions & 18 deletions browsers/file-io.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -246,25 +246,131 @@ if __name__ == "__main__":

</CodeGroup>

### Stagehand v3

When using Stagehand with Kernel browsers, you need to configure the download behavior in the `localBrowserLaunchOptions`:

```typescript
const stagehand = new Stagehand({
env: "LOCAL",
verbose: 1,
localBrowserLaunchOptions: {
cdpUrl: kernelBrowser.cdp_ws_url,
downloadsPath: DOWNLOAD_DIR, // Specify where downloads should be saved
acceptDownloads: true, // Enable downloads
},
### Stagehand

When using Stagehand with Kernel browsers, downloads are saved to the browser's filesystem and retrieved with Kernel's File I/O APIs. The setup differs by version (see the [Stagehand integration guide](/integrations/stagehand) for the full connection setup):

- **v4** connects to the running browser, and a user-initiated download — e.g. clicking a download link — is saved to the browser's default download directory, `/home/kernel/Downloads`. No download-specific configuration is required.
- **v3** sets the download directory via `localBrowserLaunchOptions` (`downloadsPath` + `acceptDownloads`).

Here's a complete example for each version:

<CodeGroup>
```typescript Stagehand v4
import { Stagehand, localBrowser } from "@browserbasehq/stagehand";
import Kernel from "@onkernel/sdk";
import fs from "fs";
import { createReadStream } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

// Kernel browsers save user-initiated downloads here by default.
const DOWNLOAD_DIR = "/home/kernel/Downloads";

// Mirror the Stagehand extension onto the running browser so localBrowser.connect can load it.
const stagehandDist = dirname(fileURLToPath(import.meta.resolve("@browserbasehq/stagehand")));
async function loadStagehandExtension(kernel: Kernel, sessionId: string) {
await kernel.browsers.fs.uploadZip(sessionId, {
dest_path: join(stagehandDist, "extension"),
zip_file: createReadStream(join(stagehandDist, "assets/stagehand-extension.zip")),
});
}

// Poll listFiles until a completed file appears (skip in-progress .crdownload files).
async function waitForFile(
kernel: Kernel,
sessionId: string,
dir: string,
timeoutMs = 30_000
) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const files = await kernel.browsers.fs.listFiles(sessionId, { path: dir });
const done = files.find((f) => !f.name.endsWith(".crdownload"));
if (done) {
return done;
}
await new Promise((r) => setTimeout(r, 500));
}
throw new Error(`No files found in ${dir} after ${timeoutMs}ms`);
}

async function main() {
const kernel = new Kernel();

console.log("Creating browser via Kernel...");
const kernelBrowser = await kernel.browsers.create({ stealth: true });

console.log(`Kernel Browser Session Started`);
console.log(`Session ID: ${kernelBrowser.session_id}`);
console.log(`Watch live: ${kernelBrowser.browser_live_view_url}`);

let stagehand: Awaited<ReturnType<typeof Stagehand.create>> | undefined;
let browser: Awaited<ReturnType<typeof localBrowser.connect>> | undefined;
try {
await loadStagehandExtension(kernel, kernelBrowser.session_id);
browser = await localBrowser.connect({ cdpUrl: kernelBrowser.cdp_ws_url });
stagehand = await Stagehand.create({
browser,
model: {
modelName: "anthropic/claude-sonnet-4-5",
apiKey: process.env.MODEL_API_KEY,
},
});

const page = await browser.context.activePage();
if (!page) throw new Error("No active page in the Kernel browser");
await page.goto("https://browser-tests-alpha.vercel.app/api/download-test");

// Use Stagehand to click the download button
await stagehand.act("Click the download file link");
console.log("Download triggered");

// Wait for the file to be fully available via Kernel's File I/O APIs
console.log("Waiting for file to appear...");
const downloadedFile = await waitForFile(
kernel,
kernelBrowser.session_id,
DOWNLOAD_DIR
);
console.log(`File found: ${downloadedFile.name}`);

const remotePath = `${DOWNLOAD_DIR}/${downloadedFile.name}`;
console.log(`Reading file from: ${remotePath}`);

// Read the file from the Kernel browser's filesystem
const resp = await kernel.browsers.fs.readFile(kernelBrowser.session_id, {
path: remotePath,
});

// Save to local filesystem
const bytes = await resp.bytes();
fs.mkdirSync("downloads", { recursive: true });
const localPath = `downloads/${downloadedFile.name}`;
fs.writeFileSync(localPath, bytes);
console.log(`Saved to ${localPath}`);
} finally {
// Nested so a rejected close() never skips deleting the Kernel browser.
try {
await stagehand?.close();
} finally {
try {
await browser?.close();
} finally {
await kernel.browsers.deleteByID(kernelBrowser.session_id);
}
}
}
console.log("Browser session closed");
}

main().catch((err) => {
console.error(err);
process.exit(1);
});
```

Here's a complete example:

```typescript
```typescript Stagehand v3
import { Stagehand } from "@browserbasehq/stagehand";
import Kernel from "@onkernel/sdk";
import fs from "fs";
Expand Down Expand Up @@ -307,8 +413,8 @@ async function main() {
verbose: 1,
localBrowserLaunchOptions: {
cdpUrl: kernelBrowser.cdp_ws_url,
downloadsPath: DOWNLOAD_DIR,
acceptDownloads: true,
downloadsPath: DOWNLOAD_DIR, // Specify where downloads should be saved
acceptDownloads: true, // Enable downloads
},
});

Expand Down Expand Up @@ -357,6 +463,7 @@ main().catch((err) => {
process.exit(1);
});
```
</CodeGroup>

### Browser Use

Expand Down
157 changes: 140 additions & 17 deletions integrations/stagehand.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,156 @@ title: "Stagehand"
[Stagehand](https://github.com/browserbase/stagehand) is an open source AI browser automation framework. It lets developers choose what to write in code vs. natural language. By integrating with Kernel, you can run Stagehand automations with cloud-hosted browsers.

<Note>
This guide is compatible with Stagehand SDK v3. If you're using an earlier version, please refer to the [Stagehand migration guide](https://docs.stagehand.dev/v3/migrations/v2) or upgrade to v3.
This guide covers both Stagehand SDK v4 and v3. v4 runs as a Chrome extension alongside the browser rather than driving it purely over CDP, so a remote Kernel browser needs the extension loaded into it — the version tabs below show each setup. To move between versions, see the [Stagehand migration guide](https://docs.stagehand.dev). The CLI template uses v4.
</Note>

## Adding Kernel to existing Stagehand implementations
## Quick start with the Stagehand template

If you already have a Stagehand (v3) implementation, you can easily switch to using Kernel's cloud browsers by updating your browser configuration.
The fastest way to run Stagehand on Kernel is our app template, which comes pre-wired for v4:

### 1. Install the Kernel SDK
```bash
kernel create --name my-stagehand-app --language typescript --template stagehand
```

This scaffolds a self-contained app with two files:

- `index.ts` — the automation (searches a startup on Y Combinator and extracts its team size).
- `stagehand-extension.ts` — a helper that loads the Stagehand extension onto the Kernel browser.

Set a provider-prefixed `MODEL` and its API key in a `.env` file:

```bash .env
# MODEL is provider-prefixed, e.g. anthropic/claude-sonnet-4-5, openai/gpt-4.1, google/gemini-2.5-flash
MODEL=anthropic/claude-sonnet-4-5
MODEL_API_KEY=your-api-key
```

Then deploy and invoke:

```bash
kernel deploy index.ts --env-file .env
kernel invoke ts-stagehand teamsize-task --payload '{"company": "kernel"}'
# → {"teamSize":"6"}
```

See the [deploy](/apps/deploy) and [invoke](/apps/invoke) guides for more.

## Adding Kernel to an existing Stagehand project

If you already have a Stagehand implementation, switch it to Kernel's cloud browsers by updating your browser setup. The steps differ between v4 and v3 — pick your version below.

<Tabs>
<Tab title="Stagehand v4">

**1. Install the Kernel SDK**

```bash
npm install @onkernel/sdk
```

**2. Load the Stagehand extension onto the Kernel browser**

Stagehand v4 runs as a Chrome extension. When `localBrowser.connect` is called without an `extensionId`, Stagehand loads the extension into the running browser over CDP (`Extensions.loadUnpacked`), reading it from a path on the **browser's** filesystem. Mirror the extension — shipped inside the `@browserbasehq/stagehand` package — onto the running Kernel browser at that exact path first:

```typescript
import { Kernel } from "@onkernel/sdk";
import { createReadStream } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

const stagehandDist = dirname(fileURLToPath(import.meta.resolve("@browserbasehq/stagehand")));
const STAGEHAND_EXTENSION_ZIP = join(stagehandDist, "assets/stagehand-extension.zip");
const STAGEHAND_EXTENSION_DIR = join(stagehandDist, "extension");

async function loadStagehandExtension(kernel: Kernel, sessionId: string): Promise<void> {
await kernel.browsers.fs.uploadZip(sessionId, {
dest_path: STAGEHAND_EXTENSION_DIR,
zip_file: createReadStream(STAGEHAND_EXTENSION_ZIP),
});
}
```

**3. Create a browser and connect**

Create a Kernel browser, load the extension, then connect Stagehand to its CDP URL:

```typescript
import { Stagehand, localBrowser } from "@browserbasehq/stagehand";
import Kernel from "@onkernel/sdk";

const kernel = new Kernel();

const kernelBrowser = await kernel.browsers.create({ stealth: true });
console.log("Live view url:", kernelBrowser.browser_live_view_url);

await loadStagehandExtension(kernel, kernelBrowser.session_id);

// With no `extensionId`, Stagehand loads the extension over CDP.
const browser = await localBrowser.connect({ cdpUrl: kernelBrowser.cdp_ws_url });

const stagehand = await Stagehand.create({
browser,
model: {
modelName: "anthropic/claude-sonnet-4-5",
apiKey: process.env.MODEL_API_KEY,
},
});
```

**4. Use your Stagehand automation**

Drive the page with Stagehand's primitives. Note the v4 API: page access is async (`activePage()`), and `extract` returns its result under `data`:

```typescript
import { z } from "zod";

const page = await browser.context.activePage();
if (!page) throw new Error("No active page in the Kernel browser");
await page.goto("https://www.ycombinator.com/companies");

await stagehand.act("Type in kernel into the search box");
await stagehand.act("Click on the first search result");

const { data } = await stagehand.extract(
"Extract the team size (number of employees) shown on this Y Combinator company page.",
z.object({ teamSize: z.string() }),
);

console.log("Team size:", data.teamSize);
```

**5. Clean up**

Stagehand v4 only closes browsers it launched, so close the connection and delete the Kernel browser yourself. Nest the cleanup so a failed `close()` never skips deleting the browser:

```typescript
try {
await stagehand.close();
} finally {
try {
await browser.close();
} finally {
await kernel.browsers.deleteByID(kernelBrowser.session_id);
}
}
```

</Tab>
<Tab title="Stagehand v3">

**1. Install the Kernel SDK**

```bash
npm install @onkernel/sdk
```

### 2. Initialize Kernel and create a browser
**2. Initialize Kernel and create a browser**

Import the libraries and create a cloud browser session:

```typescript
import { Stagehand } from "@browserbasehq/stagehand";
import Kernel from '@onkernel/sdk';
import Kernel from "@onkernel/sdk";
import { z } from "zod";

const kernel = new Kernel();
Expand All @@ -34,7 +164,7 @@ const kernelBrowser = await kernel.browsers.create({ stealth: true });
console.log("Live view url: ", kernelBrowser.browser_live_view_url);
```

### 3. Update your browser configuration
**3. Update your browser configuration**

Replace your existing browser setup to use Kernel's CDP URL:

Expand All @@ -53,7 +183,7 @@ const stagehand = new Stagehand({
await stagehand.init();
```

### 4. Use your Stagehand automation
**4. Use your Stagehand automation**

Use Stagehand's page methods with the Kernel-powered browser:

Expand All @@ -74,15 +204,8 @@ await stagehand.close();
await kernel.browsers.deleteByID(kernelBrowser.session_id);
```

## Quick setup with our Stagehand example app

Alternatively, you can use our Kernel app template that includes a pre-configured Stagehand integration:

```bash
kernel create --name my-stagehand-app --language typescript --template stagehand
```

Then follow the [deploy](/apps/deploy) and [invoke](/apps/invoke) guides to deploy and run your Stagehand automation on Kernel's infrastructure.
</Tab>
</Tabs>

## Benefits of using Kernel with Stagehand

Expand Down
2 changes: 1 addition & 1 deletion reference/cli/create.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Create a new Kernel application from a template. The CLI provides an interactive
- **`openai-computer-use`** — OpenAI Computer Using Agent (CUA)
- **`gemini-computer-use`** — Google Gemini computer use agent
- **`claude-agent-sdk`** — Claude Agent SDK browser automation agent
- **`stagehand`** — [Stagehand](https://github.com/browserbase/stagehand) v3 SDK integration
- **`stagehand`** — [Stagehand](https://github.com/browserbase/stagehand) v4 SDK integration
- **`magnitude`** — [Magnitude](https://github.com/magnitude-labs/magnitude) SDK integration
- **`tzafon`** — Tzafon Northstar CUA Fast computer use agent
- **`yutori`** — Yutori n1.5 computer use agent
Expand Down
Loading