feat: add Browser Use cloud browser as available provider (#138)

* feat: add Browser Use cloud browser
  integration

* feat: enhance Browser Use integration with provider flag support

- Updated README to reflect new usage instructions for enabling Browser Use with the `-p` flag.
- Modified CLI to parse and handle the `-p` flag for specifying the provider.
- Implemented logic in the main application to launch with the specified cloud provider.
- Adjusted BrowserManager to connect to Browser Use based on the provider flag or environment variable.
- Updated types and protocol schemas to include provider information.

* feat: add validation for mutually exclusive CLI options

- Implemented checks to prevent the use of both --cdp and --provider flags simultaneously.
- Added validation to ensure --extension cannot be used with the --provider flag.
- Enhanced error handling to provide clear feedback in both JSON and console output formats.
This commit is contained in:
Aitor
2026-01-21 18:01:19 -06:00
committed by GitHub
parent 7123d46e7f
commit c4139fa389
6 changed files with 194 additions and 7 deletions
+113 -4
View File
@@ -72,6 +72,8 @@ export class BrowserManager {
private isPersistentContext: boolean = false;
private browserbaseSessionId: string | null = null;
private browserbaseApiKey: string | null = null;
private browserUseSessionId: string | null = null;
private browserUseApiKey: string | null = null;
private contexts: BrowserContext[] = [];
private pages: Page[] = [];
private activePageIndex: number = 0;
@@ -656,6 +658,24 @@ export class BrowserManager {
});
}
/**
* Close a Browser Use session via API
*/
private async closeBrowserUseSession(sessionId: string, apiKey: string): Promise<void> {
const response = await fetch(`https://api.browser-use.com/api/v2/browsers/${sessionId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-Browser-Use-API-Key': apiKey,
},
body: JSON.stringify({ action: 'stop' }),
});
if (!response.ok) {
throw new Error(`Failed to close Browser Use session: ${response.statusText}`);
}
}
/**
* Connect to Browserbase remote browser via CDP.
* Returns true if connected, false if credentials not available.
@@ -683,8 +703,8 @@ export class BrowserManager {
throw new Error(`Failed to create Browserbase session: ${response.statusText}`);
}
const session = await response.json() as { id: string; connectUrl: string };
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');
});
@@ -717,6 +737,79 @@ export class BrowserManager {
}
}
/**
* Connect to Browser Use remote browser via CDP.
* Requires BROWSER_USE_API_KEY environment variable.
*/
private async connectToBrowserUse(): Promise<void> {
const browserUseApiKey = process.env.BROWSER_USE_API_KEY;
if (!browserUseApiKey) {
throw new Error('BROWSER_USE_API_KEY is required when using browseruse as a provider');
}
const response = await fetch('https://api.browser-use.com/api/v2/browsers', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Browser-Use-API-Key': browserUseApiKey,
},
body: JSON.stringify({}),
});
if (!response.ok) {
throw new Error(`Failed to create Browser Use session: ${response.statusText}`);
}
let session: { id: string; cdpUrl: string };
try {
session = (await response.json()) as { id: string; cdpUrl: string };
} catch (error) {
throw new Error(
`Failed to parse Browser Use session response: ${error instanceof Error ? error.message : String(error)}`
);
}
if (!session.id || !session.cdpUrl) {
throw new Error(
`Invalid Browser Use session response: missing ${!session.id ? 'id' : 'cdpUrl'}`
);
}
const browser = await chromium.connectOverCDP(session.cdpUrl).catch(() => {
throw new Error('Failed to connect to Browser Use session via CDP');
});
try {
const contexts = browser.contexts();
let context: BrowserContext;
let page: Page;
if (contexts.length === 0) {
context = await browser.newContext();
page = await context.newPage();
} else {
context = contexts[0];
const pages = context.pages();
page = pages[0] ?? (await context.newPage());
}
this.browserUseSessionId = session.id;
this.browserUseApiKey = browserUseApiKey;
this.browser = browser;
context.setDefaultTimeout(60000);
this.contexts.push(context);
this.pages.push(page);
this.activePageIndex = 0;
this.setupPageTracking(page);
this.setupContextTracking(context);
} catch (error) {
await this.closeBrowserUseSession(session.id, browserUseApiKey).catch((sessionError) => {
console.error('Failed to close Browser Use session during cleanup:', sessionError);
});
throw error;
}
}
/**
* Launch the browser with the specified options
* If already launched, this is a no-op (browser stays open)
@@ -744,12 +837,19 @@ export class BrowserManager {
return;
}
// Try connecting to Browserbase if credentials are available
// Try connecting to cloud browser providers if configured
// Browserbase: auto-connects when BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID are set
if (await this.connectToBrowserbase()) {
return;
}
// Select browser type
// Browser Use: requires explicit opt-in via -p browseruse flag or AGENT_BROWSER_PROVIDER=browseruse
const provider = options.provider ?? process.env.AGENT_BROWSER_PROVIDER;
if (provider === 'browseruse') {
await this.connectToBrowserUse();
return;
}
const browserType = options.browser ?? 'chromium';
if (hasExtensions && browserType !== 'chromium') {
throw new Error('Extensions are only supported in Chromium');
@@ -1447,6 +1547,13 @@ export class BrowserManager {
}
);
this.browser = null;
} else if (this.browserUseSessionId && this.browserUseApiKey) {
await this.closeBrowserUseSession(this.browserUseSessionId, this.browserUseApiKey).catch(
(error) => {
console.error('Failed to close Browser Use session:', error);
}
);
this.browser = null;
} else if (this.cdpPort !== null) {
// CDP: only disconnect, don't close external app's pages
if (this.browser) {
@@ -1472,6 +1579,8 @@ export class BrowserManager {
this.cdpPort = null;
this.browserbaseSessionId = null;
this.browserbaseApiKey = null;
this.browserUseSessionId = null;
this.browserUseApiKey = null;
this.isPersistentContext = false;
this.activePageIndex = 0;
this.refMap = {};