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 <kylejeong21@gmail.com>
This commit is contained in:
@@ -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
|
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
|
## License
|
||||||
|
|
||||||
Apache-2.0
|
Apache-2.0
|
||||||
|
|||||||
+92
-2
@@ -70,6 +70,8 @@ export class BrowserManager {
|
|||||||
private browser: Browser | null = null;
|
private browser: Browser | null = null;
|
||||||
private cdpPort: number | null = null;
|
private cdpPort: number | null = null;
|
||||||
private isPersistentContext: boolean = false;
|
private isPersistentContext: boolean = false;
|
||||||
|
private browserbaseSessionId: string | null = null;
|
||||||
|
private browserbaseApiKey: string | null = null;
|
||||||
private contexts: BrowserContext[] = [];
|
private contexts: BrowserContext[] = [];
|
||||||
private pages: Page[] = [];
|
private pages: Page[] = [];
|
||||||
private activePageIndex: number = 0;
|
private activePageIndex: number = 0;
|
||||||
@@ -642,6 +644,79 @@ export class BrowserManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close a Browserbase session via API
|
||||||
|
*/
|
||||||
|
private async closeBrowserbaseSession(sessionId: string, apiKey: string): Promise<void> {
|
||||||
|
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<boolean> {
|
||||||
|
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
|
* Launch the browser with the specified options
|
||||||
* If already launched, this is a no-op (browser stays open)
|
* If already launched, this is a no-op (browser stays open)
|
||||||
@@ -669,6 +744,12 @@ export class BrowserManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try connecting to Browserbase if credentials are available
|
||||||
|
if (await this.connectToBrowserbase()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select browser type
|
||||||
const browserType = options.browser ?? 'chromium';
|
const browserType = options.browser ?? 'chromium';
|
||||||
if (hasExtensions && browserType !== 'chromium') {
|
if (hasExtensions && browserType !== 'chromium') {
|
||||||
throw new Error('Extensions are only supported in Chromium');
|
throw new Error('Extensions are only supported in Chromium');
|
||||||
@@ -1359,8 +1440,15 @@ export class BrowserManager {
|
|||||||
this.cdpSession = null;
|
this.cdpSession = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// CDP: only disconnect, don't close external app's pages
|
if (this.browserbaseSessionId && this.browserbaseApiKey) {
|
||||||
if (this.cdpPort !== null) {
|
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) {
|
if (this.browser) {
|
||||||
await this.browser.close().catch(() => {});
|
await this.browser.close().catch(() => {});
|
||||||
this.browser = null;
|
this.browser = null;
|
||||||
@@ -1382,6 +1470,8 @@ export class BrowserManager {
|
|||||||
this.pages = [];
|
this.pages = [];
|
||||||
this.contexts = [];
|
this.contexts = [];
|
||||||
this.cdpPort = null;
|
this.cdpPort = null;
|
||||||
|
this.browserbaseSessionId = null;
|
||||||
|
this.browserbaseApiKey = null;
|
||||||
this.isPersistentContext = false;
|
this.isPersistentContext = false;
|
||||||
this.activePageIndex = 0;
|
this.activePageIndex = 0;
|
||||||
this.refMap = {};
|
this.refMap = {};
|
||||||
|
|||||||
Reference in New Issue
Block a user