diff --git a/.cursorrules b/.cursorrules index 3e182cf..fe68bca 100644 --- a/.cursorrules +++ b/.cursorrules @@ -9,31 +9,25 @@ This is a project that uses Stagehand, which amplifies Playwright with `act`, `e Use the following rules to write code for this project. -- To plan an instruction like "click the sign in button", use Stagehand `observe` to get the action to execute. +- To take an action on the page like "click the sign in button", use Stagehand `act` like this: ```typescript -const results = await page.observe("Click the sign in button"); +await page.act("Click the sign in button"); ``` -You can also pass in the following params: +- To plan an instruction before taking an action, use Stagehand `observe` to get the action to execute. ```typescript -await page.observe({ - instruction: the instruction to execute, - onlyVisible: false, // DEFAULT: Returns better results and less tokens, but uses Chrome a11y tree so may not always target directly visible elements - returnAction: true, // DEFAULT: return the action to execute -}); +const [action] = await page.observe("Click the sign in button"); ``` - The result of `observe` is an array of `ObserveResult` objects that can directly be used as params for `act` like this: + ```typescript - const results = await page.observe({ - instruction: the instruction to execute, - onlyVisible: false, // Returns better results and less tokens, but uses Chrome a11y tree so may not always target directly visible elements - returnAction: true, // return the action to execute - }); - await page.act(results[0]); + const [action] = await page.observe("Click the sign in button"); + await page.act(action); ``` + - When writing code that needs to extract data from the page, use Stagehand `extract`. Explicitly pass the following params by default: ```typescript @@ -42,7 +36,6 @@ const { someValue } = await page.extract({ schema: z.object({ someValue: z.string(), }), // The schema to extract - useTextExtract: true, // Set true for better results on larger extractions (sentences, paragraphs, etc), or set false for small extractions (name, birthday, etc) }); ``` @@ -83,7 +76,7 @@ if (cachedAction) { Be sure to cache the results of `observe` and use them as params for `act` to avoid unexpected DOM changes. Using `act` without caching will result in more unpredictable behavior. Act `action` should be as atomic and specific as possible, i.e. "Click the sign in button" or "Type 'hello' into the search input". -AVOID actions that are more than one step, i.e. "Order me pizza" or "Send an email to Paul asking him to call me". +AVOID actions that are more than one step, i.e. "Order me pizza" or "Type in the search bar and hit enter". ## Extract @@ -101,7 +94,6 @@ const data = await page.extract({ schema: z.object({ text: z.string(), }), - useTextExtract: true, // Set true for larger-scale extractions (multiple paragraphs), or set false for small extractions (name, birthday, etc) }); ``` @@ -116,3 +108,33 @@ const data = await page.extract({ useTextExtract: true, // Set true for larger-scale extractions (multiple paragraphs), or set false for small extractions (name, birthday, etc) }); ``` + +## Agent + +Use the `agent` method to automonously execute larger tasks like "Get the stock price of NVDA" + +```typescript +// Navigate to a website +await stagehand.page.goto("https://www.google.com"); + +const agent = stagehand.agent({ + // You can use either OpenAI or Anthropic + provider: "openai", + // The model to use (claude-3-7-sonnet-20250219 or claude-3-5-sonnet-20240620 for Anthropic) + model: "computer-use-preview", + + // Customize the system prompt + instructions: `You are a helpful assistant that can use a web browser. + Do not ask follow up questions, the user will trust your judgement.`, + + // Customize the API key + options: { + apiKey: process.env.OPENAI_API_KEY, + }, +}); + +// Execute the agent +await agent.execute( + "Apply for a library card at the San Francisco Public Library" +); +``` diff --git a/config.json b/config.json index ad84e87..c4abe03 100644 --- a/config.json +++ b/config.json @@ -1,45 +1,33 @@ { "quickstart": { - "examples/quickstart.ts": "main.ts", - "examples/run.ts": "index.ts" + "examples/quickstart.ts": "index.ts" }, "blank": { - "examples/blank.ts": "main.ts", + "examples/blank.ts": "index.ts", "examples/run.ts": "index.ts" }, "persist-context": { "examples/persist-context.ts": "index.ts" }, - "sf-ticket-agent": { - "examples/sf-ticket-agent.ts": "index.ts" - }, "deploy-vercel": { "examples/deploy_vercel.ts": "index.ts", "examples/api/stagehand.ts": "api/stagehand.ts", "examples/vercel.json": "vercel.json" }, - "custom-client-ollama": { - "examples/custom_client_ollama.ts": "main.ts", - "examples/ollama_client.ts": "ollama_client.ts", - "examples/run.ts": "index.ts" + "custom-client-openai": { + "examples/quickstart.ts": "index.ts", + "examples/customOpenAI_client.ts": "customOpenAI_client.ts" }, - "custom-client-ollama-blank": { - "examples/blank.ts": "main.ts", - "examples/ollama_client.ts": "ollama_client.ts", - "examples/run.ts": "index.ts" + "custom-client-openai-blank": { + "examples/blank.ts": "index.ts", + "examples/customOpenAI_client.ts": "customOpenAI_client.ts" }, "custom-client-aisdk": { - "examples/quickstart.ts": "main.ts", - "examples/run.ts": "index.ts", + "examples/quickstart.ts": "index.ts", "examples/aisdk_client.ts": "aisdk_client.ts" }, "custom-client-aisdk-blank": { - "examples/blank.ts": "main.ts", - "examples/aisdk_client.ts": "aisdk_client.ts", - "examples/run.ts": "index.ts" - }, - "mintlify-ai": { - "examples/mintlify_ai.ts": "main.ts", - "examples/run.ts": "index.ts" + "examples/blank.ts": "index.ts", + "examples/aisdk_client.ts": "aisdk_client.ts" } } diff --git a/examples/aisdk_client.ts b/examples/aisdk_client.ts index b6f1d2e..ac11d17 100644 --- a/examples/aisdk_client.ts +++ b/examples/aisdk_client.ts @@ -6,13 +6,14 @@ /** * Welcome to the Stagehand Vercel AI SDK client! * - * This is a client for OpenAI using Vercel AI SDK + * This is a client for Vercel AI SDK * that allows you to create chat completions with Vercel AI SDK. * * To use this client, you need to have Vercel AI SDK installed and the appropriate environment variables set. * * ```bash - * npm install @vercel/ai + * npm install ai + * npm install @ai-sdk/openai # or @ai-sdk/anthropic, @ai-sdk/google, etc. * ``` */ @@ -28,12 +29,12 @@ import { LanguageModel, TextPart, } from "ai"; -import { ChatCompletion } from "openai/resources/chat/completions"; import { CreateChatCompletionOptions, LLMClient, AvailableModel, } from "@browserbasehq/stagehand"; +import { ChatCompletion } from "openai/resources"; export class AISdkClient extends LLMClient { public type = "aisdk" as const; @@ -107,12 +108,19 @@ export class AISdkClient extends LLMClient { schema: options.response_model.schema, }); - return response.object; + return { + data: response.object, + usage: { + prompt_tokens: response.usage.promptTokens ?? 0, + completion_tokens: response.usage.completionTokens ?? 0, + total_tokens: response.usage.totalTokens ?? 0, + }, + } as T; } const tools: Record = {}; - for (const rawTool of options.tools || []) { + for (const rawTool of options.tools) { tools[rawTool.name] = { description: rawTool.description, parameters: rawTool.parameters, @@ -125,6 +133,13 @@ export class AISdkClient extends LLMClient { tools, }); - return response as T; + return { + data: response.text, + usage: { + prompt_tokens: response.usage.promptTokens ?? 0, + completion_tokens: response.usage.completionTokens ?? 0, + total_tokens: response.usage.totalTokens ?? 0, + }, + } as T; } } diff --git a/examples/blank.ts b/examples/blank.ts index 999bb6e..552d7bb 100644 --- a/examples/blank.ts +++ b/examples/blank.ts @@ -1,23 +1,24 @@ +import { Stagehand, Page, BrowserContext } from "@browserbasehq/stagehand"; +import StagehandConfig from "./stagehand.config.js"; +import chalk from "chalk"; +import boxen from "boxen"; + /** - * 🤘 Welcome to Stagehand! + * 🤘 Welcome to Stagehand! Thanks so much for trying us out! + * šŸ› ļø CONFIGURATION: stagehand.config.ts will help you configure Stagehand * - * TO RUN THIS PROJECT: - * ``` - * npm install - * npm run start - * ``` + * šŸ“ Check out our docs for more fun use cases, like building agents + * https://docs.stagehand.dev/ * - * To edit config, see `stagehand.config.ts` + * šŸ’¬ If you have any feedback, reach out to us on Slack! + * https://stagehand.dev/slack * + * šŸ“š You might also benefit from the docs for Zod, Browserbase, and Playwright: + * - https://zod.dev/ + * - https://docs.browserbase.com/ + * - https://playwright.dev/docs/intro */ -import { Page, BrowserContext, Stagehand } from "@browserbasehq/stagehand"; -import { z } from "zod"; -import chalk from "chalk"; -import dotenv from "dotenv"; - -dotenv.config(); - -export async function main({ +async function main({ page, context, stagehand, @@ -26,5 +27,52 @@ export async function main({ context: BrowserContext; // Playwright BrowserContext stagehand: Stagehand; // Stagehand instance }) { - // Add your code here + /** + * šŸ“ Your code here! + */ +} + +/** + * This is the main function that runs when you do npm run start + * + * YOU PROBABLY DON'T NEED TO MODIFY ANYTHING BELOW THIS POINT! + * + */ +async function run() { + const stagehand = new Stagehand({ + ...StagehandConfig, + }); + await stagehand.init(); + + if (StagehandConfig.env === "BROWSERBASE" && stagehand.browserbaseSessionID) { + console.log( + boxen( + `View this session live in your browser: \n${chalk.blue( + `https://browserbase.com/sessions/${stagehand.browserbaseSessionID}` + )}`, + { + title: "Browserbase", + padding: 1, + margin: 3, + } + ) + ); + } + + const page = stagehand.page; + const context = stagehand.context; + await main({ + page, + context, + stagehand, + }); + await stagehand.close(); + stagehand.log({ + category: "create-browser-app", + message: `\n🤘 Thanks so much for using Stagehand! Reach out to us on Slack if you have any feedback: ${chalk.blue( + "https://stagehand.dev/slack" + )}\n`, + }); } + +run(); diff --git a/examples/chess.ts b/examples/chess.ts new file mode 100644 index 0000000..8e8208a --- /dev/null +++ b/examples/chess.ts @@ -0,0 +1,96 @@ +import { Stagehand, Page, BrowserContext } from "@browserbasehq/stagehand"; +import StagehandConfig from "./stagehand.config.js"; +import chalk from "chalk"; +import boxen from "boxen"; + +/** + * 🤘 Welcome to Stagehand! Thanks so much for trying us out! + * šŸ› ļø CONFIGURATION: stagehand.config.ts will help you configure Stagehand + * + * šŸ“ Check out our docs for more fun use cases, like building agents + * https://docs.stagehand.dev/ + * + * šŸ’¬ If you have any feedback, reach out to us on Slack! + * https://stagehand.dev/slack + * + * šŸ“š You might also benefit from the docs for Zod, Browserbase, and Playwright: + * - https://zod.dev/ + * - https://docs.browserbase.com/ + * - https://playwright.dev/docs/intro + */ +async function main({ + page, + stagehand, +}: { + page: Page; // Playwright Page with act, extract, and observe methods + context: BrowserContext; // Playwright BrowserContext + stagehand: Stagehand; // Stagehand instance +}) { + // Navigate to the chess website + await page.goto("https://plainchess.timwoelfle.de/"); + // Execute simple action using just an LLM + await page.act("click 'play offline'"); + + // Create computer use agents + const whiteAgent = stagehand.agent({ + provider: "anthropic", + model: "claude-3-7-sonnet-20250219", + }); + const blackAgent = stagehand.agent({ + provider: "openai", + model: "computer-use-preview", + }); + while (true) { + await whiteAgent.execute( + "You are the white player. ONLY TAKE ONE MOVE, and then stop. Make the best move you can." + ); + await blackAgent.execute( + "You are the black player. ONLY TAKE ONE MOVE, and then stop. Make the best move you can." + ); + } +} + +/** + * This is the main function that runs when you do npm run start + * + * YOU PROBABLY DON'T NEED TO MODIFY ANYTHING BELOW THIS POINT! + * + */ +async function run() { + const stagehand = new Stagehand({ + ...StagehandConfig, + }); + await stagehand.init(); + + if (StagehandConfig.env === "BROWSERBASE" && stagehand.browserbaseSessionID) { + console.log( + boxen( + `View this session live in your browser: \n${chalk.blue( + `https://browserbase.com/sessions/${stagehand.browserbaseSessionID}` + )}`, + { + title: "Browserbase", + padding: 1, + margin: 3, + } + ) + ); + } + + const page = stagehand.page; + const context = stagehand.context; + await main({ + page, + context, + stagehand, + }); + await stagehand.close(); + stagehand.log({ + category: "create-browser-app", + message: `\n🤘 Thanks so much for using Stagehand! Reach out to us on Slack if you have any feedback: ${chalk.blue( + "https://stagehand.dev/slack" + )}\n`, + }); +} + +run(); diff --git a/examples/ollama_client.ts b/examples/customOpenAI_client.ts similarity index 78% rename from examples/ollama_client.ts rename to examples/customOpenAI_client.ts index a43e51b..6f405a9 100644 --- a/examples/ollama_client.ts +++ b/examples/customOpenAI_client.ts @@ -4,26 +4,18 @@ ******************************************************************************/ /** - * Welcome to the Stagehand Ollama client! + * Welcome to the Stagehand custom OpenAI client! * - * This is a client for the Ollama API. It is a wrapper around the OpenAI API - * that allows you to create chat completions with Ollama. - * - * To use this client, you need to have an Ollama instance running. You can - * start an Ollama instance by running the following command: - * - * ```bash - * ollama run deepseek-r1 - * ``` + * This is a client for models that are compatible with the OpenAI API, like Ollama, Gemini, etc. + * You can just pass in an OpenAI instance to the client and it will work. */ import { AvailableModel, - ChatMessage, CreateChatCompletionOptions, LLMClient, } from "@browserbasehq/stagehand"; -import OpenAI, { type ClientOptions } from "openai"; +import OpenAI from "openai"; import { zodResponseFormat } from "openai/helpers/zod"; import type { ChatCompletion, @@ -35,33 +27,24 @@ import type { ChatCompletionSystemMessageParam, ChatCompletionUserMessageParam, } from "openai/resources/chat/completions"; -import { validateZodSchema } from "./utils.js"; +import { z } from "zod"; + +function validateZodSchema(schema: z.ZodTypeAny, data: unknown) { + try { + schema.parse(data); + return true; + } catch { + return false; + } +} -export class OllamaClient extends LLMClient { - public type = "ollama" as const; +export class CustomOpenAIClient extends LLMClient { + public type = "openai" as const; private client: OpenAI; - constructor({ - modelName = "deepseek-r1", - clientOptions, - enableCaching = false, - }: { - modelName?: string; - clientOptions?: ClientOptions; - enableCaching?: boolean; - }) { - if (enableCaching) { - console.warn( - "Caching is not supported yet. Setting enableCaching to true will have no effect." - ); - } - + constructor({ modelName, client }: { modelName: string; client: OpenAI }) { super(modelName as AvailableModel); - this.client = new OpenAI({ - ...clientOptions, - baseURL: clientOptions?.baseURL || "http://localhost:11434/v1", - apiKey: "ollama", - }); + this.client = client; this.modelName = modelName as AvailableModel; } @@ -75,12 +58,12 @@ export class OllamaClient extends LLMClient { // TODO: Implement vision support if (image) { console.warn( - "Image provided. Vision is not currently supported for Ollama" + "Image provided. Vision is not currently supported for openai" ); } logger({ - category: "ollama", + category: "openai", message: "creating chat completion", level: 1, auxiliary: { @@ -100,7 +83,7 @@ export class OllamaClient extends LLMClient { if (options.image) { console.warn( - "Image provided. Vision is not currently supported for Ollama" + "Image provided. Vision is not currently supported for openai" ); } @@ -114,18 +97,18 @@ export class OllamaClient extends LLMClient { /* eslint-disable */ // Remove unsupported options - const { response_model, ...ollamaOptions } = { + const { response_model, ...openaiOptions } = { ...optionsWithoutImageAndRequestId, model: this.modelName, }; logger({ - category: "ollama", + category: "openai", message: "creating chat completion", level: 1, auxiliary: { - ollamaOptions: { - value: JSON.stringify(ollamaOptions), + openaiOptions: { + value: JSON.stringify(openaiOptions), type: "object", }, }, @@ -191,7 +174,7 @@ export class OllamaClient extends LLMClient { }); const body: ChatCompletionCreateParamsNonStreaming = { - ...ollamaOptions, + ...openaiOptions, model: this.modelName, messages: formattedMessages, response_format: responseFormat, @@ -209,7 +192,7 @@ export class OllamaClient extends LLMClient { const response = await this.client.chat.completions.create(body); logger({ - category: "ollama", + category: "openai", message: "response", level: 1, auxiliary: { @@ -243,9 +226,23 @@ export class OllamaClient extends LLMClient { throw new Error("Invalid response schema"); } - return parsedData; + return { + data: parsedData, + usage: { + prompt_tokens: response.usage?.prompt_tokens ?? 0, + completion_tokens: response.usage?.completion_tokens ?? 0, + total_tokens: response.usage?.total_tokens ?? 0, + }, + } as T; } - return response as T; + return { + data: response.choices[0].message.content, + usage: { + prompt_tokens: response.usage?.prompt_tokens ?? 0, + completion_tokens: response.usage?.completion_tokens ?? 0, + total_tokens: response.usage?.total_tokens ?? 0, + }, + } as T; } } diff --git a/examples/quickstart.ts b/examples/quickstart.ts index 44c44f9..09195da 100644 --- a/examples/quickstart.ts +++ b/examples/quickstart.ts @@ -1,24 +1,26 @@ +import { Stagehand, Page, BrowserContext } from "@browserbasehq/stagehand"; +import StagehandConfig from "./stagehand.config.js"; +import chalk from "chalk"; +import boxen from "boxen"; +import { drawObserveOverlay, clearOverlays, actWithCache } from "./utils.js"; +import { z } from "zod"; + /** - * 🤘 Welcome to Stagehand! + * 🤘 Welcome to Stagehand! Thanks so much for trying us out! + * šŸ› ļø CONFIGURATION: stagehand.config.ts will help you configure Stagehand * - * TO RUN THIS PROJECT: - * ``` - * npm install - * npm run start - * ``` + * šŸ“ Check out our docs for more fun use cases, like building agents + * https://docs.stagehand.dev/ * - * To edit config, see `stagehand.config.ts` + * šŸ’¬ If you have any feedback, reach out to us on Slack! + * https://stagehand.dev/slack * + * šŸ“š You might also benefit from the docs for Zod, Browserbase, and Playwright: + * - https://zod.dev/ + * - https://docs.browserbase.com/ + * - https://playwright.dev/docs/intro */ -import { Page, BrowserContext, Stagehand } from "@browserbasehq/stagehand"; -import { z } from "zod"; -import chalk from "chalk"; -import dotenv from "dotenv"; -import { actWithCache, drawObserveOverlay, clearOverlays } from "./utils.js"; - -dotenv.config(); - -export async function main({ +async function main({ page, context, stagehand, @@ -27,32 +29,95 @@ export async function main({ context: BrowserContext; // Playwright BrowserContext stagehand: Stagehand; // Stagehand instance }) { - // Navigate to the page + // Navigate to a URL await page.goto("https://docs.stagehand.dev/reference/introduction"); - // You can pass a string directly to act + // Use act() to take actions on the page await page.act("Click the search box"); - // You can use observe to plan an action before doing it - const results = await page.observe( + // Use observe() to plan an action before doing it + const [action] = await page.observe( "Type 'Tell me in one sentence why I should use Stagehand' into the search box" ); - await drawObserveOverlay(page, results); // Highlight the search box + await drawObserveOverlay(page, [action]); // Highlight the search box await page.waitForTimeout(1000); await clearOverlays(page); // Remove the highlight before typing - await page.act(results[0]); + await page.act(action); // Take - // You can also use the actWithCache function to speed up future workflows by skipping LLM calls! - // Check out the utils.ts file to see how you can cache actions + // For more on caching, check out our docs: https://docs.stagehand.dev/examples/caching await actWithCache(page, "Click the suggestion to use AI"); - await page.waitForTimeout(2000); + await page.waitForTimeout(4000); + + // Use extract() to extract structured data from the page const { text } = await page.extract({ instruction: "extract the text of the AI suggestion from the search results", schema: z.object({ text: z.string(), }), - useTextExtract: false, // Set this to true if you want to extract longer paragraphs }); - console.log(chalk.green("AI suggestion:"), text); + stagehand.log({ + category: "create-browser-app", + message: `Got AI Suggestion`, + auxiliary: { + text: { + value: text, + type: "string", + }, + }, + }); + stagehand.log({ + category: "create-browser-app", + message: `Metrics`, + auxiliary: { + metrics: { + value: JSON.stringify(stagehand.metrics), + type: "object", + }, + }, + }); +} + +/** + * This is the main function that runs when you do npm run start + * + * YOU PROBABLY DON'T NEED TO MODIFY ANYTHING BELOW THIS POINT! + * + */ +async function run() { + const stagehand = new Stagehand({ + ...StagehandConfig, + }); + await stagehand.init(); + + if (StagehandConfig.env === "BROWSERBASE" && stagehand.browserbaseSessionID) { + console.log( + boxen( + `View this session live in your browser: \n${chalk.blue( + `https://browserbase.com/sessions/${stagehand.browserbaseSessionID}` + )}`, + { + title: "Browserbase", + padding: 1, + margin: 3, + } + ) + ); + } + + const page = stagehand.page; + const context = stagehand.context; + await main({ + page, + context, + stagehand, + }); + await stagehand.close(); + console.log( + `\n🤘 Thanks so much for using Stagehand! Reach out to us on Slack if you have any feedback: ${chalk.blue( + "https://stagehand.dev/slack" + )}\n` + ); } + +run(); diff --git a/examples/run.ts b/examples/run.ts deleted file mode 100644 index 6886771..0000000 --- a/examples/run.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * 🤘 Welcome to Stagehand! - * - * You probably DON'T NEED TO BE IN THIS FILE - * - * You're probably instead looking for the main() function in main.ts - * - * This is run when you do npm run start; it just calls main() - * - */ - -import { Stagehand } from "@browserbasehq/stagehand"; -import StagehandConfig from "./stagehand.config.js"; -import chalk from "chalk"; -import { main } from "./main.js"; -import boxen from "boxen"; - -async function run() { - const stagehand = new Stagehand({ - ...StagehandConfig, - }); - await stagehand.init(); - - if (StagehandConfig.env === "BROWSERBASE" && stagehand.browserbaseSessionID) { - console.log( - boxen( - `View this session live in your browser: \n${chalk.blue( - `https://browserbase.com/sessions/${stagehand.browserbaseSessionID}` - )}`, - { - title: "Browserbase", - padding: 1, - margin: 3, - } - ) - ); - } - - const page = stagehand.page; - const context = stagehand.context; - await main({ - page, - context, - stagehand, - }); - await stagehand.close(); - console.log( - `\n🤘 Thanks for using Stagehand! Create an issue if you have any feedback: ${chalk.blue( - "https://github.com/browserbase/stagehand/issues/new" - )}\n` - ); -} - -run(); diff --git a/package.json b/package.json index 8abef55..6fcc216 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "postinstall": "playwright install" }, "dependencies": { - "@browserbasehq/sdk": "latest", - "@browserbasehq/stagehand": "latest", + "@browserbasehq/sdk": "alpha", + "@browserbasehq/stagehand": "alpha", "@playwright/test": "^1.49.1", "boxen": "^8.0.1", "chalk": "^5.3.0", @@ -18,5 +18,6 @@ "devDependencies": { "tsx": "^4.19.2", "typescript": "^5.0.0" - } + }, + "packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c" } diff --git a/stagehand.config.ts b/stagehand.config.ts index 3d69e5b..e946535 100644 --- a/stagehand.config.ts +++ b/stagehand.config.ts @@ -1,79 +1,42 @@ -import type { ConstructorParams, LogLine } from "@browserbasehq/stagehand"; +import type { ConstructorParams } from "@browserbasehq/stagehand"; import dotenv from "dotenv"; - dotenv.config(); const StagehandConfig: ConstructorParams = { + verbose: 1 /* Verbosity level for logging: 0 = silent, 1 = info, 2 = all */, + domSettleTimeoutMs: 30_000 /* Timeout for DOM to settle in milliseconds */, + + // LLM configuration + modelName: "gpt-4o" /* Name of the model to use */, + modelClientOptions: { + apiKey: process.env.OPENAI_API_KEY, + } /* Configuration options for the model client */, + + // Browser configuration env: process.env.BROWSERBASE_API_KEY && process.env.BROWSERBASE_PROJECT_ID ? "BROWSERBASE" : "LOCAL", apiKey: process.env.BROWSERBASE_API_KEY /* API key for authentication */, projectId: process.env.BROWSERBASE_PROJECT_ID /* Project identifier */, - debugDom: true /* Enable DOM debugging features */, - headless: false /* Run browser in headless mode */, - logger: (message: LogLine) => - console.log(logLineToString(message)) /* Custom logging function */, - domSettleTimeoutMs: 30_000 /* Timeout for DOM to settle in milliseconds */, + browserbaseSessionID: + undefined /* Session ID for resuming Browserbase sessions */, browserbaseSessionCreateParams: { projectId: process.env.BROWSERBASE_PROJECT_ID!, + browserSettings: { + blockAds: true, + viewport: { + width: 1024, + height: 768, + }, + }, }, - 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, - } /* Configuration options for the model client */, + localBrowserLaunchOptions: { + headless: false, + viewport: { + width: 1024, + height: 768, + }, + } /* Configuration options for the local browser */, }; export default StagehandConfig; - -/** - * Custom logging function that you can use to filter logs. - * - * General pattern here is that `message` will always be unique with no params - * Any param you would put in a log is in `auxiliary`. - * - * For example, an error log looks like this: - * - * ``` - * { - * category: "error", - * message: "Some specific error occurred", - * auxiliary: { - * message: { value: "Error message", type: "string" }, - * trace: { value: "Error trace", type: "string" } - * } - * } - * ``` - * - * You can then use `logLineToString` to filter for a specific log pattern like - * - * ``` - * if (logLine.message === "Some specific error occurred") { - * console.log(logLineToString(logLine)); - * } - * ``` - */ -export function logLineToString(logLine: LogLine): string { - // If you want more detail, set this to false. However, this will make the logs - // more verbose and harder to read. - const HIDE_AUXILIARY = true; - - try { - const timestamp = logLine.timestamp || new Date().toISOString(); - if (logLine.auxiliary?.error) { - return `${timestamp}::[stagehand:${logLine.category}] ${logLine.message}\n ${logLine.auxiliary.error.value}\n ${logLine.auxiliary.trace.value}`; - } - - // If we want to hide auxiliary information, we don't add it to the log - return `${timestamp}::[stagehand:${logLine.category}] ${logLine.message} ${ - logLine.auxiliary && !HIDE_AUXILIARY - ? JSON.stringify(logLine.auxiliary) - : "" - }`; - } catch (error) { - console.error(`Error logging line:`, error); - return "error logging line"; - } -}