diff --git a/browsers/file-io.mdx b/browsers/file-io.mdx index 9fbcd26..cb5fbab 100644 --- a/browsers/file-io.mdx +++ b/browsers/file-io.mdx @@ -246,25 +246,131 @@ if __name__ == "__main__": -### 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: + + +```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> | undefined; + let browser: Awaited> | 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"; @@ -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 }, }); @@ -357,6 +463,7 @@ main().catch((err) => { process.exit(1); }); ``` + ### Browser Use diff --git a/integrations/stagehand.mdx b/integrations/stagehand.mdx index d633f02..8789124 100644 --- a/integrations/stagehand.mdx +++ b/integrations/stagehand.mdx @@ -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. -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. -## 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. + + + + +**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 { + 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); + } +} +``` + + + + +**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(); @@ -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: @@ -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: @@ -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. + + ## Benefits of using Kernel with Stagehand diff --git a/reference/cli/create.mdx b/reference/cli/create.mdx index 32ffd15..bde2127 100644 --- a/reference/cli/create.mdx +++ b/reference/cli/create.mdx @@ -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