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:
+113
-4
@@ -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 = {};
|
||||
|
||||
Reference in New Issue
Block a user