Skip to content

Commit 55e37cc

Browse files
dprevoznikclaude
andcommitted
Update Stagehand docs to v4
Rewrite the Stagehand integration guide for v4: a CLI-template quick start and a step-by-step path for adding Kernel to an existing v4 project. v4 runs as a Chrome extension, so the guide covers loading it onto the running Kernel browser and connecting with localBrowser.connect (no extensionId). Also update the file I/O download example and the CLI template reference for v4. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent a358df4 commit 55e37cc

3 files changed

Lines changed: 178 additions & 115 deletions

File tree

‎browsers/file-io.mdx‎

Lines changed: 80 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -246,32 +246,34 @@ if __name__ == "__main__":
246246
247247
</CodeGroup>
248248
249-
### Stagehand v3
249+
### Stagehand
250250
251-
When using Stagehand with Kernel browsers, you need to configure the download behavior in the `localBrowserLaunchOptions`:
252-
253-
```typescript
254-
const stagehand = new Stagehand({
255-
env: "LOCAL",
256-
verbose: 1,
257-
localBrowserLaunchOptions: {
258-
cdpUrl: kernelBrowser.cdp_ws_url,
259-
downloadsPath: DOWNLOAD_DIR, // Specify where downloads should be saved
260-
acceptDownloads: true, // Enable downloads
261-
},
262-
});
263-
```
251+
Stagehand v4 connects to the running Kernel browser (see the [Stagehand integration guide](/integrations/stagehand) for the full setup). A user-initiated download — e.g. clicking a download link — is saved to the browser's default download directory, `/home/kernel/Downloads`, which you retrieve with Kernel's File I/O APIs. No download-specific launch configuration is required.
264252
265253
Here's a complete example:
266254
267255
```typescript
268-
import { Stagehand } from "@browserbasehq/stagehand";
256+
import { Stagehand, localBrowser } from "@browserbasehq/stagehand";
269257
import Kernel from "@onkernel/sdk";
270258
import fs from "fs";
259+
import { createReadStream } from "node:fs";
260+
import { dirname, join } from "node:path";
261+
import { fileURLToPath } from "node:url";
262+
263+
// Kernel browsers save user-initiated downloads here by default.
264+
const DOWNLOAD_DIR = "/home/kernel/Downloads";
265+
266+
// Stagehand v4 runs as a Chrome extension; mirror it onto the running browser
267+
// so `localBrowser.connect` (no `extensionId`) can load it over CDP.
268+
const stagehandDist = dirname(fileURLToPath(import.meta.resolve("@browserbasehq/stagehand")));
269+
async function loadStagehandExtension(kernel: Kernel, sessionId: string) {
270+
await kernel.browsers.fs.uploadZip(sessionId, {
271+
dest_path: join(stagehandDist, "extension"),
272+
zip_file: createReadStream(join(stagehandDist, "assets/stagehand-extension.zip")),
273+
});
274+
}
271275

272-
const DOWNLOAD_DIR = "/tmp/downloads";
273-
274-
// Poll listFiles until any file appears in the directory
276+
// Poll listFiles until a completed file appears (skip in-progress .crdownload files).
275277
async function waitForFile(
276278
kernel: Kernel,
277279
sessionId: string,
@@ -281,8 +283,9 @@ async function waitForFile(
281283
const start = Date.now();
282284
while (Date.now() - start < timeoutMs) {
283285
const files = await kernel.browsers.fs.listFiles(sessionId, { path: dir });
284-
if (files.length > 0) {
285-
return files[0];
286+
const done = files.find((f) => !f.name.endsWith(".crdownload"));
287+
if (done) {
288+
return done;
286289
}
287290
await new Promise((r) => setTimeout(r, 500));
288291
}
@@ -293,62 +296,68 @@ async function main() {
293296
const kernel = new Kernel();
294297

295298
console.log("Creating browser via Kernel...");
296-
const kernelBrowser = await kernel.browsers.create({
297-
stealth: true,
298-
});
299+
const kernelBrowser = await kernel.browsers.create({ stealth: true });
299300

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

304-
// Initialize Stagehand with Kernel's CDP URL and download configuration
305-
const stagehand = new Stagehand({
306-
env: "LOCAL",
307-
verbose: 1,
308-
localBrowserLaunchOptions: {
309-
cdpUrl: kernelBrowser.cdp_ws_url,
310-
downloadsPath: DOWNLOAD_DIR,
311-
acceptDownloads: true,
312-
},
313-
});
314-
315-
await stagehand.init();
316-
317-
const page = stagehand.context.pages()[0];
318-
319-
await page.goto("https://browser-tests-alpha.vercel.app/api/download-test");
320-
321-
// Use Stagehand to click the download button
322-
await stagehand.act("Click the download file link");
323-
console.log("Download triggered");
324-
325-
// Wait for the file to be fully available via Kernel's File I/O APIs
326-
console.log("Waiting for file to appear...");
327-
const downloadedFile = await waitForFile(
328-
kernel,
329-
kernelBrowser.session_id,
330-
DOWNLOAD_DIR
331-
);
332-
console.log(`File found: ${downloadedFile.name}`);
333-
334-
const remotePath = `${DOWNLOAD_DIR}/${downloadedFile.name}`;
335-
console.log(`Reading file from: ${remotePath}`);
336-
337-
// Read the file from Kernel browser's filesystem
338-
const resp = await kernel.browsers.fs.readFile(kernelBrowser.session_id, {
339-
path: remotePath,
340-
});
341-
342-
// Save to local filesystem
343-
const bytes = await resp.bytes();
344-
fs.mkdirSync("downloads", { recursive: true });
345-
const localPath = `downloads/${downloadedFile.name}`;
346-
fs.writeFileSync(localPath, bytes);
347-
console.log(`Saved to ${localPath}`);
348-
349-
// Clean up
350-
await stagehand.close();
351-
await kernel.browsers.deleteByID(kernelBrowser.session_id);
305+
let stagehand: Awaited<ReturnType<typeof Stagehand.create>> | undefined;
306+
let browser: Awaited<ReturnType<typeof localBrowser.connect>> | undefined;
307+
try {
308+
await loadStagehandExtension(kernel, kernelBrowser.session_id);
309+
browser = await localBrowser.connect({ cdpUrl: kernelBrowser.cdp_ws_url });
310+
stagehand = await Stagehand.create({
311+
browser,
312+
model: {
313+
modelName: "anthropic/claude-sonnet-4-5",
314+
apiKey: process.env.MODEL_API_KEY,
315+
},
316+
});
317+
318+
const page = await browser.context.activePage();
319+
if (!page) throw new Error("No active page in the Kernel browser");
320+
await page.goto("https://browser-tests-alpha.vercel.app/api/download-test");
321+
322+
// Use Stagehand to click the download button
323+
await stagehand.act("Click the download file link");
324+
console.log("Download triggered");
325+
326+
// Wait for the file to be fully available via Kernel's File I/O APIs
327+
console.log("Waiting for file to appear...");
328+
const downloadedFile = await waitForFile(
329+
kernel,
330+
kernelBrowser.session_id,
331+
DOWNLOAD_DIR
332+
);
333+
console.log(`File found: ${downloadedFile.name}`);
334+
335+
const remotePath = `${DOWNLOAD_DIR}/${downloadedFile.name}`;
336+
console.log(`Reading file from: ${remotePath}`);
337+
338+
// Read the file from the Kernel browser's filesystem
339+
const resp = await kernel.browsers.fs.readFile(kernelBrowser.session_id, {
340+
path: remotePath,
341+
});
342+
343+
// Save to local filesystem
344+
const bytes = await resp.bytes();
345+
fs.mkdirSync("downloads", { recursive: true });
346+
const localPath = `downloads/${downloadedFile.name}`;
347+
fs.writeFileSync(localPath, bytes);
348+
console.log(`Saved to ${localPath}`);
349+
} finally {
350+
// Nested so a rejected close() never skips deleting the Kernel browser.
351+
try {
352+
await stagehand?.close();
353+
} finally {
354+
try {
355+
await browser?.close();
356+
} finally {
357+
await kernel.browsers.deleteByID(kernelBrowser.session_id);
358+
}
359+
}
360+
}
352361
console.log("Browser session closed");
353362
}
354363

‎integrations/stagehand.mdx‎

Lines changed: 97 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,85 +5,139 @@ title: "Stagehand"
55
[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.
66

77
<Note>
8-
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.
8+
This guide targets Stagehand SDK v4. Stagehand 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 (covered below). If you're on an earlier version, see the [Stagehand migration guide](https://docs.stagehand.dev).
99
</Note>
1010

11-
## Adding Kernel to existing Stagehand implementations
11+
## Quick start with the Stagehand template
1212

13-
If you already have a Stagehand (v3) implementation, you can easily switch to using Kernel's cloud browsers by updating your browser configuration.
13+
The fastest way to run Stagehand on Kernel is our app template, which comes pre-wired for v4:
14+
15+
```bash
16+
kernel create --name my-stagehand-app --language typescript --template stagehand
17+
```
18+
19+
This scaffolds a self-contained app with two files:
20+
21+
- `index.ts` — the automation (searches a startup on Y Combinator and extracts its team size).
22+
- `stagehand-extension.ts` — a helper that loads the Stagehand extension onto the Kernel browser.
23+
24+
Set a provider-prefixed `MODEL` and its API key in a `.env` file:
25+
26+
```bash .env
27+
# MODEL is provider-prefixed, e.g. anthropic/claude-sonnet-4-5, openai/gpt-4.1, google/gemini-2.5-flash
28+
MODEL=anthropic/claude-sonnet-4-5
29+
MODEL_API_KEY=your-api-key
30+
```
31+
32+
Then deploy and invoke:
33+
34+
```bash
35+
kernel deploy index.ts --env-file .env
36+
kernel invoke ts-stagehand teamsize-task --payload '{"company": "kernel"}'
37+
# → {"teamSize":"6"}
38+
```
39+
40+
See the [deploy](/apps/deploy) and [invoke](/apps/invoke) guides for more.
41+
42+
## Adding Kernel to an existing Stagehand v4 project
43+
44+
If you already have a Stagehand v4 implementation, switch it to Kernel's cloud browsers with the steps below.
1445

1546
### 1. Install the Kernel SDK
1647

1748
```bash
1849
npm install @onkernel/sdk
1950
```
2051

21-
### 2. Initialize Kernel and create a browser
52+
Stagehand v4 requires `zod` v4 for its schema types (it pins `zod@4.4.3`). If your project is on zod v3, upgrade it.
2253

23-
Import the libraries and create a cloud browser session:
54+
### 2. Load the Stagehand extension onto the Kernel browser
55+
56+
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:
2457

2558
```typescript
26-
import { Stagehand } from "@browserbasehq/stagehand";
27-
import Kernel from '@onkernel/sdk';
28-
import { z } from "zod";
59+
import { Kernel } from "@onkernel/sdk";
60+
import { createReadStream } from "node:fs";
61+
import { dirname, join } from "node:path";
62+
import { fileURLToPath } from "node:url";
63+
64+
const stagehandDist = dirname(fileURLToPath(import.meta.resolve("@browserbasehq/stagehand")));
65+
const STAGEHAND_EXTENSION_ZIP = join(stagehandDist, "assets/stagehand-extension.zip");
66+
const STAGEHAND_EXTENSION_DIR = join(stagehandDist, "extension");
67+
68+
async function loadStagehandExtension(kernel: Kernel, sessionId: string): Promise<void> {
69+
await kernel.browsers.fs.uploadZip(sessionId, {
70+
dest_path: STAGEHAND_EXTENSION_DIR,
71+
zip_file: createReadStream(STAGEHAND_EXTENSION_ZIP),
72+
});
73+
}
74+
```
75+
76+
### 3. Create a browser and connect
77+
78+
Create a Kernel browser, load the extension, then connect Stagehand to its CDP URL:
79+
80+
```typescript
81+
import { Stagehand, localBrowser } from "@browserbasehq/stagehand";
82+
import Kernel from "@onkernel/sdk";
2983

3084
const kernel = new Kernel();
3185

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

34-
console.log("Live view url: ", kernelBrowser.browser_live_view_url);
35-
```
36-
37-
### 3. Update your browser configuration
89+
await loadStagehandExtension(kernel, kernelBrowser.session_id);
3890

39-
Replace your existing browser setup to use Kernel's CDP URL:
91+
// With no `extensionId`, Stagehand loads the extension over CDP.
92+
const browser = await localBrowser.connect({ cdpUrl: kernelBrowser.cdp_ws_url });
4093

41-
```typescript
42-
const stagehand = new Stagehand({
43-
env: "LOCAL",
44-
localBrowserLaunchOptions: {
45-
cdpUrl: kernelBrowser.cdp_ws_url,
94+
const stagehand = await Stagehand.create({
95+
browser,
96+
model: {
97+
modelName: "anthropic/claude-sonnet-4-5",
98+
apiKey: process.env.MODEL_API_KEY,
4699
},
47-
model: "openai/gpt-4.1",
48-
apiKey: process.env.OPENAI_API_KEY,
49-
verbose: 1,
50-
domSettleTimeout: 30_000
51100
});
52-
53-
await stagehand.init();
54101
```
55102

56103
### 4. Use your Stagehand automation
57104

58-
Use Stagehand's page methods with the Kernel-powered browser:
105+
Drive the page with Stagehand's primitives. Note the v4 API: page access is async (`activePage()`), and `extract` returns its result under `data`:
59106

60107
```typescript
61-
const page = stagehand.context.pages()[0];
62-
await page.goto("https://onkernel.com");
63-
await stagehand.act("Click on Blog in the navbar");
64-
await stagehand.act("Click on the newest blog post");
65-
const output = await stagehand.extract(
66-
"Extract a summary of the blog post",
67-
z.object({ summary: z.string() })
68-
);
108+
import { z } from "zod";
109+
110+
const page = await browser.context.activePage();
111+
if (!page) throw new Error("No active page in the Kernel browser");
112+
await page.goto("https://www.ycombinator.com/companies");
69113

70-
console.log("Newest blog post summary: ", output.summary);
114+
await stagehand.act("Type in kernel into the search box");
115+
await stagehand.act("Click on the first search result");
116+
117+
const { data } = await stagehand.extract(
118+
"Extract the team size (number of employees) shown on this Y Combinator company page.",
119+
z.object({ teamSize: z.string() }),
120+
);
71121

72-
// Clean up
73-
await stagehand.close();
74-
await kernel.browsers.deleteByID(kernelBrowser.session_id);
122+
console.log("Team size:", data.teamSize);
75123
```
76124

77-
## Quick setup with our Stagehand example app
125+
### 5. Clean up
78126

79-
Alternatively, you can use our Kernel app template that includes a pre-configured Stagehand integration:
127+
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:
80128

81-
```bash
82-
kernel create --name my-stagehand-app --language typescript --template stagehand
129+
```typescript
130+
try {
131+
await stagehand.close();
132+
} finally {
133+
try {
134+
await browser.close();
135+
} finally {
136+
await kernel.browsers.deleteByID(kernelBrowser.session_id);
137+
}
138+
}
83139
```
84140

85-
Then follow the [deploy](/apps/deploy) and [invoke](/apps/invoke) guides to deploy and run your Stagehand automation on Kernel's infrastructure.
86-
87141
## Benefits of using Kernel with Stagehand
88142

89143
- **No local browser management**: Run automations without installing or maintaining browsers locally

‎reference/cli/create.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Create a new Kernel application from a template. The CLI provides an interactive
2222
- **`openai-computer-use`** — OpenAI Computer Using Agent (CUA)
2323
- **`gemini-computer-use`** — Google Gemini computer use agent
2424
- **`claude-agent-sdk`** — Claude Agent SDK browser automation agent
25-
- **`stagehand`** — [Stagehand](https://github.com/browserbase/stagehand) v3 SDK integration
25+
- **`stagehand`** — [Stagehand](https://github.com/browserbase/stagehand) v4 SDK integration
2626
- **`magnitude`** — [Magnitude](https://github.com/magnitude-labs/magnitude) SDK integration
2727
- **`tzafon`** — Tzafon Northstar CUA Fast computer use agent
2828
- **`yutori`** — Yutori n1.5 computer use agent

0 commit comments

Comments
 (0)