diff --git a/lib/index.ts b/lib/index.ts index 25d1b0d9..4de56381 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -12,6 +12,8 @@ import { modelsWithVision } from "./llm/LLMClient"; require("dotenv").config({ path: ".env" }); async function getBrowser( + apiKey: string | undefined, + projectId: string | undefined, env: "LOCAL" | "BROWSERBASE" = "LOCAL", headless: boolean = false, logger: (message: { @@ -20,55 +22,103 @@ async function getBrowser( level?: 0 | 1 | 2; }) => void, browserbaseSessionCreateParams?: Browserbase.Sessions.SessionCreateParams, + browserbaseResumeSessionID?: string, ) { - if (env === "BROWSERBASE" && !process.env.BROWSERBASE_API_KEY) { - logger({ - category: "Init", - message: - "BROWSERBASE_API_KEY is required to use BROWSERBASE env. Defaulting to LOCAL.", - level: 0, - }); - env = "LOCAL"; - } - - if (env === "BROWSERBASE" && !process.env.BROWSERBASE_PROJECT_ID) { - logger({ - category: "Init", - message: - "BROWSERBASE_PROJECT_ID is required to use BROWSERBASE env. Defaulting to LOCAL.", - level: 0, - }); - env = "LOCAL"; + if (env === "BROWSERBASE") { + if (!apiKey) { + logger({ + category: "Init", + message: + "BROWSERBASE_API_KEY is required to use BROWSERBASE env. Defaulting to LOCAL.", + level: 0, + }); + env = "LOCAL"; + } + if (!projectId) { + logger({ + category: "Init", + message: + "BROWSERBASE_PROJECT_ID is required for some Browserbase features that may not work without it.", + level: 1, + }); + } } if (env === "BROWSERBASE") { + if (!apiKey) { + throw new Error("BROWSERBASE_API_KEY is required."); + } + let debugUrl: string | undefined = undefined; let sessionUrl: string | undefined = undefined; + let sessionId: string; + let connectUrl: string; - logger({ - category: "Init", - message: "Connecting you to Browserbase...", - level: 0, - }); const browserbase = new Browserbase({ - apiKey: process.env.BROWSERBASE_API_KEY, - }); - const session = await browserbase.sessions.create({ - projectId: process.env.BROWSERBASE_PROJECT_ID, - ...browserbaseSessionCreateParams, + apiKey, }); - const sessionId = session.id; - const connectUrl = session.connectUrl; - const browser = await chromium.connectOverCDP(connectUrl); + if (browserbaseResumeSessionID) { + // Validate the session status + try { + const sessionStatus = await browserbase.sessions.retrieve( + browserbaseResumeSessionID, + ); + + if (sessionStatus.status !== "RUNNING") { + throw new Error( + `Session ${browserbaseResumeSessionID} is not running (status: ${sessionStatus.status})`, + ); + } + + sessionId = browserbaseResumeSessionID; + connectUrl = `wss://connect.browserbase.com?apiKey=${apiKey}&sessionId=${sessionId}`; + + logger({ + category: "Init", + message: "Resuming existing Browserbase session...", + level: 0, + }); + } catch (error) { + logger({ + category: "Init", + message: `Failed to resume session ${browserbaseResumeSessionID}: ${error.message}`, + level: 0, + }); + throw error; + } + } else { + // Create new session (existing code) + logger({ + category: "Init", + message: "Creating new Browserbase session...", + level: 0, + }); + + if (!projectId) { + throw new Error( + "BROWSERBASE_PROJECT_ID is required for new Browserbase sessions.", + ); + } + const session = await browserbase.sessions.create({ + projectId, + ...browserbaseSessionCreateParams, + }); + + sessionId = session.id; + connectUrl = session.connectUrl; + } + + const browser = await chromium.connectOverCDP(connectUrl); const { debuggerUrl } = await browserbase.sessions.debug(sessionId); debugUrl = debuggerUrl; sessionUrl = `https://www.browserbase.com/sessions/${sessionId}`; + logger({ category: "Init", - message: `Browserbase session started.\n\nSession Url: ${sessionUrl}\n\nLive debug accessible here: ${debugUrl}.`, + message: `Browserbase session ${browserbaseResumeSessionID ? "resumed" : "started"}.\n\nSession Url: ${sessionUrl}\n\nLive debug accessible here: ${debugUrl}.`, level: 0, }); @@ -182,6 +232,8 @@ export class Stagehand { public page: Page; public context: BrowserContext; private env: "LOCAL" | "BROWSERBASE"; + private apiKey: string | undefined; + private projectId: string | undefined; private verbose: 0 | 1 | 2; private debugDom: boolean; private defaultModelName: AvailableModel; @@ -194,10 +246,13 @@ export class Stagehand { private domSettleTimeoutMs: number; private browserBaseSessionCreateParams?: Browserbase.Sessions.SessionCreateParams; private enableCaching: boolean; + private browserbaseResumeSessionID?: string; constructor( { env, + apiKey, + projectId, verbose, debugDom, llmProvider, @@ -206,8 +261,11 @@ export class Stagehand { browserBaseSessionCreateParams, domSettleTimeoutMs, enableCaching, + browserbaseResumeSessionID, }: { env: "LOCAL" | "BROWSERBASE"; + apiKey?: string; + projectId?: string; verbose?: 0 | 1 | 2; debugDom?: boolean; llmProvider?: LLMProvider; @@ -220,6 +278,7 @@ export class Stagehand { domSettleTimeoutMs?: number; browserBaseSessionCreateParams?: Browserbase.Sessions.SessionCreateParams; enableCaching?: boolean; + browserbaseResumeSessionID?: string; } = { env: "BROWSERBASE", }, @@ -231,6 +290,8 @@ export class Stagehand { llmProvider || new LLMProvider(this.logger, this.enableCaching); this.env = env; this.observations = {}; + this.apiKey = apiKey; + this.projectId = projectId; this.actions = {}; this.verbose = verbose ?? 0; this.debugDom = debugDom ?? false; @@ -238,6 +299,7 @@ export class Stagehand { this.domSettleTimeoutMs = domSettleTimeoutMs ?? 60_000; this.headless = headless ?? false; this.browserBaseSessionCreateParams = browserBaseSessionCreateParams; + this.browserbaseResumeSessionID = browserbaseResumeSessionID; } async init({ @@ -247,10 +309,13 @@ export class Stagehand { sessionUrl: string; }> { const { context, debugUrl, sessionUrl } = await getBrowser( + this.apiKey, + this.projectId, this.env, this.headless, this.logger, this.browserBaseSessionCreateParams, + this.browserbaseResumeSessionID, ).catch((e) => { console.error("Error in init:", e); return { context: undefined, debugUrl: undefined, sessionUrl: undefined }; diff --git a/package-lock.json b/package-lock.json index d3a4b7cb..f3cd9d11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -126,6 +126,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@browserbasehq/sdk/-/sdk-2.0.0.tgz", "integrity": "sha512-BdPlZyn0dpXlL70gNK4acpqWIRB+edo2z0/GalQdWghRq8iQjySd9fVIF3evKH1p2wCYekZJRK6tm29YfXB67g==", + "license": "Apache-2.0", "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4",