From e2408d3d243958bd20c462b15ef3577eb410ab5c Mon Sep 17 00:00:00 2001 From: Anirudh Kamath Date: Mon, 31 Mar 2025 01:46:01 -0700 Subject: [PATCH 01/14] update 2.0 --- .cursorrules | 56 ++++++++++++++++++++++---------- examples/blank.ts | 83 ++++++++++++++++++++++++++++++++++++++--------- examples/run.ts | 73 ++++++++++++++++++++++++++++++++++------- 3 files changed, 167 insertions(+), 45 deletions(-) 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/examples/blank.ts b/examples/blank.ts index 999bb6e..35012ae 100644 --- a/examples/blank.ts +++ b/examples/blank.ts @@ -1,23 +1,25 @@ +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! * - * 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 +28,54 @@ export async function main({ context: BrowserContext; // Playwright BrowserContext stagehand: Stagehand; // Stagehand instance }) { - // Add your code here + // Navigate to a URL + await page.goto("https://docs.stagehand.dev/reference/introduction"); + + /** + * 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(); + 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 index 6886771..1ecf706 100644 --- a/examples/run.ts +++ b/examples/run.ts @@ -1,20 +1,69 @@ +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! - * - * You probably DON'T NEED TO BE IN THIS FILE + * 🤘 Welcome to Stagehand! Thanks so much for trying us out! * - * You're probably instead looking for the main() function in main.ts + * šŸ“ Check out our docs for more fun use cases, like building agents + * https://docs.stagehand.dev/ * - * This is run when you do npm run start; it just calls main() + * šŸ’¬ 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, + context, + stagehand, +}: { + page: Page; // Playwright Page with act, extract, and observe methods + context: BrowserContext; // Playwright BrowserContext + stagehand: Stagehand; // Stagehand instance +}) { + // Navigate to a URL + await page.goto("https://docs.stagehand.dev/reference/introduction"); -import { Stagehand } from "@browserbasehq/stagehand"; -import StagehandConfig from "./stagehand.config.js"; -import chalk from "chalk"; -import { main } from "./main.js"; -import boxen from "boxen"; + // Use act() to take actions on the page + await page.act("Click the search box"); + + // 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, [action]); // Highlight the search box + await page.waitForTimeout(1000); + await clearOverlays(page); // Remove the highlight before typing + await page.act(action); // Take + + // 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); + + // 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(), + }), + }); + console.log(chalk.green("AI suggestion:"), text); +} +/** + * 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, @@ -45,8 +94,8 @@ async function run() { }); 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🤘 Thanks so much for using Stagehand! Reach out to us on Slack if you have any feedback: ${chalk.blue( + "https://stagehand.dev/slack" )}\n` ); } From 256db3d4af5a040a50782e7c895aeb9b74299992 Mon Sep 17 00:00:00 2001 From: Anirudh Kamath Date: Mon, 31 Mar 2025 02:51:17 -0700 Subject: [PATCH 02/14] updatre --- config.json | 3 - examples/aisdk_client.ts | 2 +- ...llama_client.ts => customOpenAI_client.ts} | 89 ++++++++------- examples/quickstart.ts | 95 +++++++++++----- examples/run.ts | 103 ------------------ stagehand.config.ts | 91 +++++----------- 6 files changed, 141 insertions(+), 242 deletions(-) rename examples/{ollama_client.ts => customOpenAI_client.ts} (78%) delete mode 100644 examples/run.ts diff --git a/config.json b/config.json index ad84e87..cf0b51b 100644 --- a/config.json +++ b/config.json @@ -10,9 +10,6 @@ "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", diff --git a/examples/aisdk_client.ts b/examples/aisdk_client.ts index b6f1d2e..fc8decb 100644 --- a/examples/aisdk_client.ts +++ b/examples/aisdk_client.ts @@ -12,7 +12,7 @@ * 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 * ``` */ 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..1ecf706 100644 --- a/examples/quickstart.ts +++ b/examples/quickstart.ts @@ -1,24 +1,25 @@ +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! * - * 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 +28,76 @@ 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); + + // 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); } + +/** + * 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 1ecf706..0000000 --- a/examples/run.ts +++ /dev/null @@ -1,103 +0,0 @@ -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! Thanks so much for trying us out! - * - * šŸ“ 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, - context, - stagehand, -}: { - page: Page; // Playwright Page with act, extract, and observe methods - context: BrowserContext; // Playwright BrowserContext - stagehand: Stagehand; // Stagehand instance -}) { - // Navigate to a URL - await page.goto("https://docs.stagehand.dev/reference/introduction"); - - // Use act() to take actions on the page - await page.act("Click the search box"); - - // 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, [action]); // Highlight the search box - await page.waitForTimeout(1000); - await clearOverlays(page); // Remove the highlight before typing - await page.act(action); // Take - - // 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); - - // 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(), - }), - }); - console.log(chalk.green("AI suggestion:"), text); -} - -/** - * 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/stagehand.config.ts b/stagehand.config.ts index 3d69e5b..8442074 100644 --- a/stagehand.config.ts +++ b/stagehand.config.ts @@ -1,79 +1,42 @@ -import type { ConstructorParams, LogLine } from "@browserbasehq/stagehand"; +import type { ConstructorParams } from "@browserbase/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"; - } -} From 489053b98e50aac3163f127d39b14dc3db811bd3 Mon Sep 17 00:00:00 2001 From: Anirudh Kamath Date: Mon, 31 Mar 2025 02:53:16 -0700 Subject: [PATCH 03/14] clean config --- config.json | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/config.json b/config.json index cf0b51b..e7b6283 100644 --- a/config.json +++ b/config.json @@ -1,10 +1,9 @@ { "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": { @@ -17,26 +16,18 @@ }, "custom-client-ollama": { "examples/custom_client_ollama.ts": "main.ts", - "examples/ollama_client.ts": "ollama_client.ts", - "examples/run.ts": "index.ts" + "examples/ollama_client.ts": "ollama_client.ts" }, "custom-client-ollama-blank": { "examples/blank.ts": "main.ts", - "examples/ollama_client.ts": "ollama_client.ts", - "examples/run.ts": "index.ts" + "examples/ollama_client.ts": "ollama_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" } } From b09727ae2d2f4d3b0d743a819d9a89cd6e348539 Mon Sep 17 00:00:00 2001 From: Anirudh Kamath Date: Mon, 31 Mar 2025 03:08:47 -0700 Subject: [PATCH 04/14] update aisdk --- examples/aisdk_client.ts | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/examples/aisdk_client.ts b/examples/aisdk_client.ts index fc8decb..d6c42bd 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 ai + * npm install @ai-sdk/openai # or @ai-sdk/anthropic, @ai-sdk/google, etc. * ``` */ @@ -28,12 +29,8 @@ import { LanguageModel, TextPart, } from "ai"; -import { ChatCompletion } from "openai/resources/chat/completions"; -import { - CreateChatCompletionOptions, - LLMClient, - AvailableModel, -} from "@browserbasehq/stagehand"; +import { CreateChatCompletionOptions, LLMClient, AvailableModel } from "@/dist"; +import { ChatCompletion } from "openai/resources"; export class AISdkClient extends LLMClient { public type = "aisdk" as const; @@ -107,12 +104,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 +129,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; } } From ad5a007e2ff81e93521abfbb40472d4dac020508 Mon Sep 17 00:00:00 2001 From: Anirudh Kamath Date: Mon, 31 Mar 2025 03:09:50 -0700 Subject: [PATCH 05/14] rm dist --- examples/aisdk_client.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/aisdk_client.ts b/examples/aisdk_client.ts index d6c42bd..ac11d17 100644 --- a/examples/aisdk_client.ts +++ b/examples/aisdk_client.ts @@ -29,7 +29,11 @@ import { LanguageModel, TextPart, } from "ai"; -import { CreateChatCompletionOptions, LLMClient, AvailableModel } from "@/dist"; +import { + CreateChatCompletionOptions, + LLMClient, + AvailableModel, +} from "@browserbasehq/stagehand"; import { ChatCompletion } from "openai/resources"; export class AISdkClient extends LLMClient { From 2f2855984a02dd2df3af14a5f690178798e309ea Mon Sep 17 00:00:00 2001 From: Anirudh Kamath Date: Mon, 31 Mar 2025 03:36:34 -0700 Subject: [PATCH 06/14] config for custom openai --- config.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/config.json b/config.json index e7b6283..c4abe03 100644 --- a/config.json +++ b/config.json @@ -14,13 +14,13 @@ "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" + "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" + "custom-client-openai-blank": { + "examples/blank.ts": "index.ts", + "examples/customOpenAI_client.ts": "customOpenAI_client.ts" }, "custom-client-aisdk": { "examples/quickstart.ts": "index.ts", From d9950252824849658b523027b146e45e25e0e490 Mon Sep 17 00:00:00 2001 From: Anirudh Kamath Date: Mon, 31 Mar 2025 03:45:51 -0700 Subject: [PATCH 07/14] blank example + config --- examples/blank.ts | 8 ++------ examples/quickstart.ts | 1 + 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/blank.ts b/examples/blank.ts index 35012ae..1b9ac64 100644 --- a/examples/blank.ts +++ b/examples/blank.ts @@ -2,11 +2,10 @@ 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! 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/ @@ -28,11 +27,8 @@ async function main({ context: BrowserContext; // Playwright BrowserContext stagehand: Stagehand; // Stagehand instance }) { - // Navigate to a URL - await page.goto("https://docs.stagehand.dev/reference/introduction"); - /** - * Your code here! + * šŸ“ Your code here! */ } diff --git a/examples/quickstart.ts b/examples/quickstart.ts index 1ecf706..e2b8346 100644 --- a/examples/quickstart.ts +++ b/examples/quickstart.ts @@ -7,6 +7,7 @@ import { z } from "zod"; /** * 🤘 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/ From b99cdf845fce94d34f6a1ac0c4c3c09131fe6e96 Mon Sep 17 00:00:00 2001 From: Anirudh Kamath Date: Mon, 31 Mar 2025 03:52:21 -0700 Subject: [PATCH 08/14] logs --- examples/blank.ts | 9 +++++---- examples/quickstart.ts | 11 ++++++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/examples/blank.ts b/examples/blank.ts index 1b9ac64..552d7bb 100644 --- a/examples/blank.ts +++ b/examples/blank.ts @@ -67,11 +67,12 @@ async function run() { 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( + 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` - ); + )}\n`, + }); } run(); diff --git a/examples/quickstart.ts b/examples/quickstart.ts index e2b8346..9d7b199 100644 --- a/examples/quickstart.ts +++ b/examples/quickstart.ts @@ -56,7 +56,16 @@ async function main({ text: z.string(), }), }); - console.log(chalk.green("AI suggestion:"), text); + stagehand.log({ + category: "create-browser-app", + message: `Got AI Suggestion`, + auxiliary: { + text: { + value: text, + type: "string", + }, + }, + }); } /** From 628b8f045c73ace39759c5c39ec025cdfb3f70ee Mon Sep 17 00:00:00 2001 From: Anirudh Kamath Date: Mon, 31 Mar 2025 04:10:18 -0700 Subject: [PATCH 09/14] longer timeout --- examples/quickstart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/quickstart.ts b/examples/quickstart.ts index 9d7b199..6b28b36 100644 --- a/examples/quickstart.ts +++ b/examples/quickstart.ts @@ -46,7 +46,7 @@ async function main({ // 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({ From f9944558aa35c1bdc0604e5b00a35806a3f98840 Mon Sep 17 00:00:00 2001 From: Anirudh Kamath Date: Tue, 1 Apr 2025 19:43:08 -0700 Subject: [PATCH 10/14] Update stagehand.config.ts --- stagehand.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stagehand.config.ts b/stagehand.config.ts index 8442074..e946535 100644 --- a/stagehand.config.ts +++ b/stagehand.config.ts @@ -1,4 +1,4 @@ -import type { ConstructorParams } from "@browserbase/stagehand"; +import type { ConstructorParams } from "@browserbasehq/stagehand"; import dotenv from "dotenv"; dotenv.config(); From 18084c2e0e7548861c8f82ddea51500263523ac5 Mon Sep 17 00:00:00 2001 From: Anirudh Kamath Date: Mon, 31 Mar 2025 04:19:42 -0700 Subject: [PATCH 11/14] metrics --- examples/quickstart.ts | 10 ++++++++++ package.json | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/examples/quickstart.ts b/examples/quickstart.ts index 6b28b36..09195da 100644 --- a/examples/quickstart.ts +++ b/examples/quickstart.ts @@ -66,6 +66,16 @@ async function main({ }, }, }); + stagehand.log({ + category: "create-browser-app", + message: `Metrics`, + auxiliary: { + metrics: { + value: JSON.stringify(stagehand.metrics), + type: "object", + }, + }, + }); } /** diff --git a/package.json b/package.json index 8abef55..af80fd3 100644 --- a/package.json +++ b/package.json @@ -18,5 +18,6 @@ "devDependencies": { "tsx": "^4.19.2", "typescript": "^5.0.0" - } + }, + "packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c" } From d1b4dfd8d6d62504c3e37c66f63af71912e7fe6a Mon Sep 17 00:00:00 2001 From: Anirudh Kamath Date: Tue, 1 Apr 2025 22:03:24 -0700 Subject: [PATCH 12/14] fix blank --- examples/blank.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ package.json | 4 ++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/examples/blank.ts b/examples/blank.ts index 552d7bb..811bcc6 100644 --- a/examples/blank.ts +++ b/examples/blank.ts @@ -75,4 +75,46 @@ async function run() { }); } +/** + * 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/package.json b/package.json index af80fd3..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", From 967977a6b7a2183d596b98873d21061266cda000 Mon Sep 17 00:00:00 2001 From: Anirudh Kamath Date: Tue, 1 Apr 2025 22:06:13 -0700 Subject: [PATCH 13/14] blank: --- examples/blank.ts | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/examples/blank.ts b/examples/blank.ts index 811bcc6..552d7bb 100644 --- a/examples/blank.ts +++ b/examples/blank.ts @@ -75,46 +75,4 @@ async function run() { }); } -/** - * 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(); From deaba07091caa77df0c960a27c740de12c2e4d76 Mon Sep 17 00:00:00 2001 From: Anirudh Kamath Date: Fri, 11 Apr 2025 07:52:30 -0700 Subject: [PATCH 14/14] chess --- examples/chess.ts | 96 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 examples/chess.ts 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();