diff --git a/stagehand/src/server.ts b/stagehand/src/server.ts index b05a7e9..76437b1 100644 --- a/stagehand/src/server.ts +++ b/stagehand/src/server.ts @@ -54,17 +54,18 @@ export const stagehandConfig: ConstructorParams = { : undefined, } : undefined, - localBrowserLaunchOptions: process.env.LOCAL_CDP_URL - ? { - cdpUrl: process.env.LOCAL_CDP_URL, - } - : undefined, + // Set a default CDP URL if running in LOCAL mode + localBrowserLaunchOptions: process.env.BROWSERBASE_API_KEY && process.env.BROWSERBASE_PROJECT_ID + ? undefined + : { + cdpUrl: process.env.LOCAL_CDP_URL || "http://localhost:9222", + }, enableCaching: true /* Enable caching functionality */, browserbaseSessionID: undefined /* Session ID for resuming Browserbase sessions */, modelName: "gpt-4o" /* Name of the model to use */, modelClientOptions: { - apiKey: process.env.OPENAI_API_KEY, + apiKey: process.env.OPENAI_API_KEY || "dummy-api-key-for-browserbase", } /* Configuration options for the model client */, useAPI: false, }; @@ -74,20 +75,48 @@ let stagehand: Stagehand | undefined; // Ensure Stagehand is initialized export async function ensureStagehand() { + // We've added a default CDP URL in the config, so this check should always pass if ( stagehandConfig.env === "LOCAL" && !stagehandConfig.localBrowserLaunchOptions?.cdpUrl ) { - throw new Error( - 'Using a local browser without providing a CDP URL is not supported. Please provide a CDP URL using the LOCAL_CDP_URL environment variable.\n\nTo launch your browser in "debug", see our documentation.\n\nhttps://docs.stagehand.dev/examples/customize_browser#use-your-personal-browser' - ); + log("No CDP URL provided, using http://localhost:9222 as fallback", "info"); + stagehandConfig.localBrowserLaunchOptions = { + cdpUrl: "http://localhost:9222" + }; } try { + // If stagehand doesn't exist, create a new instance if (!stagehand) { + log("Creating new Stagehand instance", "info"); stagehand = new Stagehand(stagehandConfig); - await stagehand.init(); - return stagehand; + try { + await stagehand.init(); + log("Successfully initialized new Stagehand instance", "info"); + return stagehand; + } catch (initError) { + const initErrorMsg = initError instanceof Error ? initError.message : String(initError); + log(`Failed to initialize new Stagehand instance: ${initErrorMsg}`, "error"); + throw initError; + } + } + + // If stagehand exists but page is not initialized + if (!stagehand.page) { + log("Stagehand exists but page is not initialized, reinitializing...", "info"); + try { + await stagehand.init(); + log("Successfully reinitialized Stagehand page", "info"); + return stagehand; + } catch (initError) { + const initErrorMsg = initError instanceof Error ? initError.message : String(initError); + log(`Failed to reinitialize Stagehand page: ${initErrorMsg}`, "error"); + // Create a new instance as fallback + stagehand = new Stagehand(stagehandConfig); + await stagehand.init(); + return stagehand; + } } // Try to perform a simple operation to check if the session is still valid diff --git a/stagehand/src/tools.ts b/stagehand/src/tools.ts index e04a39e..50229e7 100644 --- a/stagehand/src/tools.ts +++ b/stagehand/src/tools.ts @@ -75,7 +75,12 @@ export const TOOLS: Tool[] = [ "Takes a screenshot of the current page. Use this tool to learn where you are on the page when controlling the browser with Stagehand. Only use this tool when the other tools are not sufficient to get the information you need.", inputSchema: { type: "object", - properties: {}, + properties: { + path: { + type: "string", + description: "Optional path where the screenshot should be saved (e.g., 'screenshot.png')" + } + }, }, }, ]; @@ -86,9 +91,66 @@ export async function handleToolCall( args: any, stagehand: Stagehand ): Promise { + // Ensure stagehand is properly initialized + if (!stagehand) { + return { + content: [ + { + type: "text", + text: "Stagehand is not initialized properly", + }, + { + type: "text", + text: `Operation logs:\n${operationLogs.join("\n")}`, + }, + ], + isError: true, + }; + } + + // Make sure init has been called + if (!stagehand.page) { + try { + await stagehand.init(); + console.log("Re-initialized Stagehand"); + } catch (initError) { + const initErrorMsg = initError instanceof Error ? initError.message : String(initError); + return { + content: [ + { + type: "text", + text: `Failed to initialize Stagehand: ${initErrorMsg}`, + }, + { + type: "text", + text: `Operation logs:\n${operationLogs.join("\n")}`, + }, + ], + isError: true, + }; + } + } + switch (name) { case "stagehand_navigate": try { + // Make sure we have a valid page + if (!stagehand || !stagehand.page) { + return { + content: [ + { + type: "text", + text: "Unable to navigate: Stagehand instance or page not initialized", + }, + { + type: "text", + text: `Operation logs:\n${operationLogs.join("\n")}`, + }, + ], + isError: true, + }; + } + await stagehand.page.goto(args.url); return { content: [ @@ -98,7 +160,7 @@ export async function handleToolCall( }, { type: "text", - text: `View the live session here: https://browserbase.com/sessions/${stagehand.browserbaseSessionID}`, + text: `View the live session here: https://browserbase.com/sessions/${stagehand.browserbaseSessionID || 'not-available'}`, }, ], isError: false, @@ -235,9 +297,59 @@ export async function handleToolCall( case "screenshot": try { - const screenshotBuffer = await stagehand.page.screenshot({ + // Make sure we have a valid page + if (!stagehand || !stagehand.page) { + return { + content: [ + { + type: "text", + text: "Unable to take screenshot: Stagehand instance or page not initialized", + }, + { + type: "text", + text: `Operation logs:\n${operationLogs.join("\n")}`, + }, + ], + isError: true, + }; + } + + // Check if the page is ready + try { + await stagehand.page.evaluate(() => document.title); + } catch (pageError) { + // If page is not ready, try to navigate to google.com as a baseline + try { + await stagehand.page.goto("https://www.google.com"); + console.log("Navigated to Google as a fallback for screenshot"); + } catch (navError) { + return { + content: [ + { + type: "text", + text: `Failed to prepare page for screenshot: ${String(navError)}`, + }, + { + type: "text", + text: `Operation logs:\n${operationLogs.join("\n")}`, + }, + ], + isError: true, + }; + } + } + + // Create screenshot options + const screenshotOptions: any = { fullPage: false, - }); + }; + + // Add path if provided + if (args.path) { + screenshotOptions.path = args.path; + } + + const screenshotBuffer = await stagehand.page.screenshot(screenshotOptions); // Convert buffer to base64 string and store in memory const screenshotBase64 = screenshotBuffer.toString("base64"); @@ -254,11 +366,15 @@ export async function handleToolCall( }); } + const saveMessage = args.path + ? `Screenshot saved to path: ${args.path} and` + : "Screenshot"; + return { content: [ { type: "text", - text: `Screenshot taken with name: ${name}`, + text: `${saveMessage} taken with name: ${name}`, }, { type: "image",