From 7123d46e7f7a6b02ddb93d0d67a2eef220e53ebd Mon Sep 17 00:00:00 2001 From: Paul Klein Date: Wed, 21 Jan 2026 15:49:21 -0800 Subject: [PATCH] Add Browserbase support for remote browser over CDP (#3) * Add Browserbase support for remote browser over CDP When BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID env vars are set, connect to a Browserbase session via CDP instead of launching a local browser. * Update URLs to browserbase repo * Add Browserbase support for remote browser over CDP When BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID env vars are set, connect to a Browserbase session via CDP instead of launching a local browser. * Update link to Browserbase Dashboard in README * bump browserbase sdk to latest version * remove sdk as a dep * change name back to vercel labs * added try catch blocks, functions to close session * revert package names * remove extra if statement --------- Co-authored-by: Kylejeong2 --- README.md | 17 +++++++++ src/browser.ts | 94 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 109 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4ea79d8..668f2c3 100644 --- a/README.md +++ b/README.md @@ -657,6 +657,23 @@ curl -o .claude/skills/agent-browser/SKILL.md \ https://raw.githubusercontent.com/vercel-labs/agent-browser/main/skills/agent-browser/SKILL.md ``` +## Integrations + +### Browserbase + +[Browserbase](https://browserbase.com) provides remote browser infrastructure to make deployment of agentic browsing agents easy. Use it when running the agent-browser CLI in an environment where a local browser isn't feasible. + +To enable Browserbase, set these environment variables: + +```bash +export BROWSERBASE_API_KEY="your-api-key" +export BROWSERBASE_PROJECT_ID="your-project-id" +``` + +When both variables are set, agent-browser automatically connects to a Browserbase session instead of launching a local browser. All commands work identically. + +Get your API key and project ID from the [Browserbase Dashboard](https://browserbase.com/overview). + ## License Apache-2.0 diff --git a/src/browser.ts b/src/browser.ts index 70ef6eb..c38ef75 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -70,6 +70,8 @@ export class BrowserManager { private browser: Browser | null = null; private cdpPort: number | null = null; private isPersistentContext: boolean = false; + private browserbaseSessionId: string | null = null; + private browserbaseApiKey: string | null = null; private contexts: BrowserContext[] = []; private pages: Page[] = []; private activePageIndex: number = 0; @@ -642,6 +644,79 @@ export class BrowserManager { return false; } + /** + * Close a Browserbase session via API + */ + private async closeBrowserbaseSession(sessionId: string, apiKey: string): Promise { + await fetch(`https://api.browserbase.com/v1/sessions/${sessionId}`, { + method: 'DELETE', + headers: { + 'X-BB-API-Key': apiKey, + }, + }); + } + + /** + * Connect to Browserbase remote browser via CDP. + * Returns true if connected, false if credentials not available. + */ + private async connectToBrowserbase(): Promise { + const browserbaseApiKey = process.env.BROWSERBASE_API_KEY; + const browserbaseProjectId = process.env.BROWSERBASE_PROJECT_ID; + + if (!browserbaseApiKey || !browserbaseProjectId) { + return false; + } + + const response = await fetch('https://api.browserbase.com/v1/sessions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-BB-API-Key': browserbaseApiKey, + }, + body: JSON.stringify({ + projectId: browserbaseProjectId, + }), + }); + + if (!response.ok) { + throw new Error(`Failed to create Browserbase session: ${response.statusText}`); + } + + const session = await response.json() as { id: string; connectUrl: string }; + + const browser = await chromium.connectOverCDP(session.connectUrl).catch(() => { + throw new Error('Failed to connect to Browserbase session via CDP'); + }); + + try { + const contexts = browser.contexts(); + if (contexts.length === 0) { + throw new Error('No browser context found in Browserbase session'); + } + + const context = contexts[0]; + const pages = context.pages(); + const page = pages[0] ?? (await context.newPage()); + + this.browserbaseSessionId = session.id; + this.browserbaseApiKey = browserbaseApiKey; + this.browser = browser; + context.setDefaultTimeout(10000); + this.contexts.push(context); + this.pages.push(page); + this.activePageIndex = 0; + this.setupPageTracking(page); + + return true; + } catch (error) { + await this.closeBrowserbaseSession(session.id, browserbaseApiKey).catch((sessionError) => { + console.error('Failed to close Browserbase session during cleanup:', sessionError); + }); + throw error; + } + } + /** * Launch the browser with the specified options * If already launched, this is a no-op (browser stays open) @@ -669,6 +744,12 @@ export class BrowserManager { return; } + // Try connecting to Browserbase if credentials are available + if (await this.connectToBrowserbase()) { + return; + } + + // Select browser type const browserType = options.browser ?? 'chromium'; if (hasExtensions && browserType !== 'chromium') { throw new Error('Extensions are only supported in Chromium'); @@ -1359,8 +1440,15 @@ export class BrowserManager { this.cdpSession = null; } - // CDP: only disconnect, don't close external app's pages - if (this.cdpPort !== null) { + if (this.browserbaseSessionId && this.browserbaseApiKey) { + await this.closeBrowserbaseSession(this.browserbaseSessionId, this.browserbaseApiKey).catch( + (error) => { + console.error('Failed to close Browserbase session:', error); + } + ); + this.browser = null; + } else if (this.cdpPort !== null) { + // CDP: only disconnect, don't close external app's pages if (this.browser) { await this.browser.close().catch(() => {}); this.browser = null; @@ -1382,6 +1470,8 @@ export class BrowserManager { this.pages = []; this.contexts = []; this.cdpPort = null; + this.browserbaseSessionId = null; + this.browserbaseApiKey = null; this.isPersistentContext = false; this.activePageIndex = 0; this.refMap = {};